From 7f6002caba3f0a6749820c2772161caf55b8d267 Mon Sep 17 00:00:00 2001 From: neonloop Date: Fri, 7 May 2021 20:00:12 +0000 Subject: Initial commit (uqm-0.8.0) --- src/Makeinfo | 21 + src/abxadec/Makefile | 9 + src/abxadec/abxaud.c | 638 ++++ src/abxadec/abxaud.def | 16 + src/abxadec/abxaud.h | 34 + src/config.h | 21 + src/config_unix.h.in | 63 + src/config_vc6.h | 61 + src/config_win.h.in | 65 + src/darwin/Makeinfo | 2 + src/darwin/SDLMain.h | 16 + src/darwin/SDLMain.m | 404 ++ src/endian_uqm.h | 136 + src/getopt/Makeinfo | 2 + src/getopt/getopt.c | 1061 ++++++ src/getopt/getopt.h | 180 + src/getopt/getopt1.c | 189 + src/libs/Makeinfo | 19 + src/libs/alarm.h | 9 + src/libs/async.h | 10 + src/libs/callback.h | 10 + src/libs/callback/Makeinfo | 2 + src/libs/callback/alarm.c | 177 + src/libs/callback/alarm.h | 56 + src/libs/callback/async.c | 56 + src/libs/callback/async.h | 28 + src/libs/callback/callback.c | 193 + src/libs/callback/callback.h | 43 + src/libs/cdp/Makeinfo | 3 + src/libs/cdp/cdp.c | 437 +++ src/libs/cdp/cdp.h | 47 + src/libs/cdp/cdp_alli.h | 31 + src/libs/cdp/cdp_iio.h | 50 + src/libs/cdp/cdp_imem.h | 42 + src/libs/cdp/cdp_isnd.h | 43 + src/libs/cdp/cdp_ivid.h | 43 + src/libs/cdp/cdpapi.c | 864 +++++ src/libs/cdp/cdpapi.h | 154 + src/libs/cdp/cdpint.h | 48 + src/libs/cdp/cdpmod.h | 92 + src/libs/cdp/windl.c | 76 + src/libs/cdp/windl.h | 37 + src/libs/cdplib.h | 32 + src/libs/compiler.h | 96 + src/libs/declib.h | 57 + src/libs/decomp/Makeinfo | 2 + src/libs/decomp/lzdecode.c | 415 +++ src/libs/decomp/lzencode.c | 468 +++ src/libs/decomp/lzh.h | 91 + src/libs/decomp/update.c | 115 + src/libs/file.h | 95 + src/libs/file/Makeinfo | 2 + src/libs/file/dirs.c | 830 +++++ src/libs/file/files.c | 165 + src/libs/file/filintrn.h | 24 + src/libs/file/temp.c | 199 + src/libs/gfxlib.h | 474 +++ src/libs/graphics/Makeinfo | 12 + src/libs/graphics/bbox.c | 133 + src/libs/graphics/bbox.h | 46 + src/libs/graphics/boxint.c | 183 + src/libs/graphics/clipline.c | 241 ++ src/libs/graphics/cmap.c | 663 ++++ src/libs/graphics/cmap.h | 77 + src/libs/graphics/context.c | 404 ++ src/libs/graphics/context.h | 147 + src/libs/graphics/dcqueue.c | 670 ++++ src/libs/graphics/dcqueue.h | 55 + src/libs/graphics/drawable.c | 501 +++ src/libs/graphics/drawable.h | 88 + src/libs/graphics/drawcmd.h | 202 + src/libs/graphics/filegfx.c | 72 + src/libs/graphics/font.c | 334 ++ src/libs/graphics/font.h | 71 + src/libs/graphics/frame.c | 266 ++ src/libs/graphics/gfx_common.c | 196 + src/libs/graphics/gfx_common.h | 112 + src/libs/graphics/gfxintrn.h | 32 + src/libs/graphics/gfxload.c | 597 +++ src/libs/graphics/intersec.c | 415 +++ src/libs/graphics/loaddisp.c | 65 + src/libs/graphics/pixmap.c | 170 + src/libs/graphics/prim.h | 80 + src/libs/graphics/resgfx.c | 54 + src/libs/graphics/sdl/2xscalers.c | 260 ++ src/libs/graphics/sdl/2xscalers.h | 30 + src/libs/graphics/sdl/2xscalers_3dnow.c | 102 + src/libs/graphics/sdl/2xscalers_mmx.c | 136 + src/libs/graphics/sdl/2xscalers_mmx.h | 56 + src/libs/graphics/sdl/2xscalers_sse.c | 100 + src/libs/graphics/sdl/Makeinfo | 9 + src/libs/graphics/sdl/biadv2x.c | 532 +++ src/libs/graphics/sdl/bilinear2x.c | 112 + src/libs/graphics/sdl/canvas.c | 2176 +++++++++++ src/libs/graphics/sdl/hq2x.c | 2888 +++++++++++++++ src/libs/graphics/sdl/nearest2x.c | 207 ++ src/libs/graphics/sdl/opengl.c | 575 +++ src/libs/graphics/sdl/opengl.h | 89 + src/libs/graphics/sdl/palette.c | 47 + src/libs/graphics/sdl/palette.h | 57 + src/libs/graphics/sdl/png2sdl.c | 300 ++ src/libs/graphics/sdl/png2sdl.h | 24 + src/libs/graphics/sdl/primitives.c | 633 ++++ src/libs/graphics/sdl/primitives.h | 62 + src/libs/graphics/sdl/pure.c | 474 +++ src/libs/graphics/sdl/pure.h | 29 + src/libs/graphics/sdl/rotozoom.c | 1038 ++++++ src/libs/graphics/sdl/rotozoom.h | 96 + src/libs/graphics/sdl/scaleint.h | 433 +++ src/libs/graphics/sdl/scalemmx.h | 793 ++++ src/libs/graphics/sdl/scalers.c | 289 ++ src/libs/graphics/sdl/scalers.h | 27 + src/libs/graphics/sdl/sdl1_common.c | 247 ++ src/libs/graphics/sdl/sdl2_common.c | 222 ++ src/libs/graphics/sdl/sdl2_pure.c | 465 +++ src/libs/graphics/sdl/sdl_common.c | 308 ++ src/libs/graphics/sdl/sdl_common.h | 63 + src/libs/graphics/sdl/sdluio.c | 153 + src/libs/graphics/sdl/sdluio.h | 39 + src/libs/graphics/sdl/triscan2x.c | 155 + src/libs/graphics/tfb_draw.c | 493 +++ src/libs/graphics/tfb_draw.h | 199 + src/libs/graphics/tfb_prim.c | 237 ++ src/libs/graphics/tfb_prim.h | 30 + src/libs/graphics/widgets.c | 941 +++++ src/libs/graphics/widgets.h | 222 ++ src/libs/heap.h | 9 + src/libs/heap/Makeinfo | 2 + src/libs/heap/heap.c | 197 + src/libs/heap/heap.h | 69 + src/libs/inplib.h | 70 + src/libs/input/Makeinfo | 6 + src/libs/input/inpintrn.h | 25 + src/libs/input/input_common.c | 20 + src/libs/input/input_common.h | 39 + src/libs/input/sdl/Makeinfo | 2 + src/libs/input/sdl/input.c | 625 ++++ src/libs/input/sdl/input.h | 27 + src/libs/input/sdl/keynames.c | 229 ++ src/libs/input/sdl/keynames.h | 22 + src/libs/input/sdl/vcontrol.c | 1300 +++++++ src/libs/input/sdl/vcontrol.h | 108 + src/libs/list.h | 29 + src/libs/list/Makeinfo | 2 + src/libs/list/list.c | 132 + src/libs/list/list.h | 70 + src/libs/log.h | 25 + src/libs/log/Makeinfo | 15 + src/libs/log/loginternal.h | 24 + src/libs/log/msgbox.h | 23 + src/libs/log/msgbox_macosx.m | 42 + src/libs/log/msgbox_stub.c | 34 + src/libs/log/msgbox_win.c | 67 + src/libs/log/uqmlog.c | 331 ++ src/libs/log/uqmlog.h | 59 + src/libs/math/Makeinfo | 2 + src/libs/math/mthintrn.h | 25 + src/libs/math/random.c | 101 + src/libs/math/random.h | 56 + src/libs/math/random2.c | 89 + src/libs/math/sqrt.c | 97 + src/libs/mathlib.h | 36 + src/libs/md5.h | 32 + src/libs/md5/Makeinfo | 2 + src/libs/md5/README | 6 + src/libs/md5/md5.c | 452 +++ src/libs/md5/md5.h | 130 + src/libs/memlib.h | 43 + src/libs/memory/Makeinfo | 1 + src/libs/memory/w_memlib.c | 84 + src/libs/mikmod/AUTHORS | 124 + src/libs/mikmod/Makeinfo | 5 + src/libs/mikmod/README | 5 + src/libs/mikmod/drv_nos.c | 107 + src/libs/mikmod/load_it.c | 1008 +++++ src/libs/mikmod/load_mod.c | 512 +++ src/libs/mikmod/load_s3m.c | 470 +++ src/libs/mikmod/load_stm.c | 376 ++ src/libs/mikmod/load_xm.c | 817 ++++ src/libs/mikmod/mdreg.c | 47 + src/libs/mikmod/mdriver.c | 935 +++++ src/libs/mikmod/mikmod.h | 730 ++++ src/libs/mikmod/mikmod_build.h | 9 + src/libs/mikmod/mikmod_internals.h | 679 ++++ src/libs/mikmod/mloader.c | 607 +++ src/libs/mikmod/mlreg.c | 50 + src/libs/mikmod/mlutil.c | 337 ++ src/libs/mikmod/mmalloc.c | 73 + src/libs/mikmod/mmerror.c | 197 + src/libs/mikmod/mmio.c | 490 +++ src/libs/mikmod/mplayer.c | 3561 ++++++++++++++++++ src/libs/mikmod/munitrk.c | 303 ++ src/libs/mikmod/mwav.c | 210 ++ src/libs/mikmod/npertab.c | 48 + src/libs/mikmod/sloader.c | 519 +++ src/libs/mikmod/virtch.c | 935 +++++ src/libs/mikmod/virtch2.c | 887 +++++ src/libs/mikmod/virtch_common.c | 459 +++ src/libs/misc.h | 66 + src/libs/net.h | 36 + src/libs/network/FILES | 26 + src/libs/network/Makeinfo | 14 + src/libs/network/bytesex.h | 96 + src/libs/network/connect/Makeinfo | 2 + src/libs/network/connect/connect.c | 490 +++ src/libs/network/connect/connect.h | 111 + src/libs/network/connect/listen.c | 456 +++ src/libs/network/connect/listen.h | 106 + src/libs/network/connect/resolve.c | 211 ++ src/libs/network/connect/resolve.h | 109 + src/libs/network/netmanager/Makeinfo | 11 + src/libs/network/netmanager/ndesc.c | 211 ++ src/libs/network/netmanager/ndesc.h | 82 + src/libs/network/netmanager/ndindex.ci | 103 + src/libs/network/netmanager/netmanager.h | 48 + src/libs/network/netmanager/netmanager_bsd.c | 223 ++ src/libs/network/netmanager/netmanager_bsd.h | 26 + src/libs/network/netmanager/netmanager_common.ci | 58 + src/libs/network/netmanager/netmanager_win.c | 464 +++ src/libs/network/netmanager/netmanager_win.h | 35 + src/libs/network/netport.c | 91 + src/libs/network/netport.h | 43 + src/libs/network/network.h | 27 + src/libs/network/network_bsd.c | 30 + src/libs/network/network_win.c | 75 + src/libs/network/socket/Makeinfo | 11 + src/libs/network/socket/socket.c | 61 + src/libs/network/socket/socket.h | 99 + src/libs/network/socket/socket_bsd.c | 283 ++ src/libs/network/socket/socket_bsd.h | 33 + src/libs/network/socket/socket_win.c | 314 ++ src/libs/network/socket/socket_win.h | 34 + src/libs/network/wspiapiwrap.c | 34 + src/libs/network/wspiapiwrap.h | 33 + src/libs/platform.h | 57 + src/libs/reslib.h | 140 + src/libs/resource/Makeinfo | 3 + src/libs/resource/direct.c | 101 + src/libs/resource/filecntl.c | 146 + src/libs/resource/getres.c | 257 ++ src/libs/resource/index.h | 54 + src/libs/resource/loadres.c | 54 + src/libs/resource/propfile.c | 129 + src/libs/resource/propfile.h | 30 + src/libs/resource/resinit.c | 651 ++++ src/libs/resource/resintrn.h | 34 + src/libs/resource/stringbank.c | 181 + src/libs/resource/stringbank.h | 57 + src/libs/sndlib.h | 107 + src/libs/sound/Makeinfo | 9 + src/libs/sound/audiocore.c | 272 ++ src/libs/sound/audiocore.h | 169 + src/libs/sound/decoders/Makeinfo | 8 + src/libs/sound/decoders/aiffaud.c | 650 ++++ src/libs/sound/decoders/aiffaud.h | 36 + src/libs/sound/decoders/decoder.c | 936 +++++ src/libs/sound/decoders/decoder.h | 129 + src/libs/sound/decoders/dukaud.c | 546 +++ src/libs/sound/decoders/dukaud.h | 36 + src/libs/sound/decoders/modaud.c | 430 +++ src/libs/sound/decoders/modaud.h | 26 + src/libs/sound/decoders/oggaud.c | 278 ++ src/libs/sound/decoders/oggaud.h | 26 + src/libs/sound/decoders/wav.c | 385 ++ src/libs/sound/decoders/wav.h | 26 + src/libs/sound/fileinst.c | 87 + src/libs/sound/mixer/Makeinfo | 3 + src/libs/sound/mixer/mixer.c | 1760 +++++++++ src/libs/sound/mixer/mixer.h | 274 ++ src/libs/sound/mixer/mixerint.h | 110 + src/libs/sound/mixer/nosound/Makeinfo | 2 + src/libs/sound/mixer/nosound/audiodrv_nosound.c | 410 ++ src/libs/sound/mixer/nosound/audiodrv_nosound.h | 69 + src/libs/sound/mixer/sdl/Makeinfo | 2 + src/libs/sound/mixer/sdl/audiodrv_sdl.c | 486 +++ src/libs/sound/mixer/sdl/audiodrv_sdl.h | 66 + src/libs/sound/music.c | 233 ++ src/libs/sound/openal/Makeinfo | 2 + src/libs/sound/openal/audiodrv_openal.c | 420 +++ src/libs/sound/openal/audiodrv_openal.h | 86 + src/libs/sound/resinst.c | 65 + src/libs/sound/sfx.c | 306 ++ src/libs/sound/sndintrn.h | 76 + src/libs/sound/sound.c | 178 + src/libs/sound/sound.h | 81 + src/libs/sound/stream.c | 814 ++++ src/libs/sound/stream.h | 37 + src/libs/sound/trackint.h | 41 + src/libs/sound/trackplayer.c | 874 +++++ src/libs/sound/trackplayer.h | 52 + src/libs/strings/Makeinfo | 2 + src/libs/strings/getstr.c | 643 ++++ src/libs/strings/sfileins.c | 50 + src/libs/strings/sresins.c | 55 + src/libs/strings/stringhashtable.c | 67 + src/libs/strings/stringhashtable.h | 43 + src/libs/strings/strings.c | 347 ++ src/libs/strings/strintrn.h | 56 + src/libs/strings/unicode.c | 541 +++ src/libs/strlib.h | 80 + src/libs/task/Makeinfo | 1 + src/libs/task/tasklib.c | 139 + src/libs/tasklib.h | 62 + src/libs/threadlib.h | 186 + src/libs/threads/Makeinfo | 11 + src/libs/threads/pthread/Makeinfo | 2 + src/libs/threads/pthread/posixthreads.c | 672 ++++ src/libs/threads/pthread/posixthreads.h | 103 + src/libs/threads/sdl/Makeinfo | 2 + src/libs/threads/sdl/sdlthreads.c | 706 ++++ src/libs/threads/sdl/sdlthreads.h | 106 + src/libs/threads/thrcommon.c | 451 +++ src/libs/threads/thrcommon.h | 28 + src/libs/time/Makeinfo | 3 + src/libs/time/sdl/Makeinfo | 2 + src/libs/time/sdl/sdltime.c | 30 + src/libs/time/sdl/sdltime.h | 35 + src/libs/time/timecommon.c | 41 + src/libs/time/timecommon.h | 30 + src/libs/timelib.h | 49 + src/libs/uio.h | 34 + src/libs/uio/COPYING | 350 ++ src/libs/uio/Makeinfo | 22 + src/libs/uio/charhashtable.c | 77 + src/libs/uio/charhashtable.h | 39 + src/libs/uio/debug.c | 914 +++++ src/libs/uio/debug.h | 29 + src/libs/uio/defaultfs.c | 41 + src/libs/uio/defaultfs.h | 41 + src/libs/uio/doc/basics | 178 + src/libs/uio/doc/conventions | 30 + src/libs/uio/doc/todo | 144 + src/libs/uio/fileblock.c | 332 ++ src/libs/uio/fileblock.h | 88 + src/libs/uio/fstypes.c | 272 ++ src/libs/uio/fstypes.h | 113 + src/libs/uio/getint.h | 141 + src/libs/uio/gphys.c | 620 ++++ src/libs/uio/gphys.h | 313 ++ src/libs/uio/hashtable.c | 374 ++ src/libs/uio/hashtable.h | 157 + src/libs/uio/io.c | 1859 ++++++++++ src/libs/uio/io.h | 159 + src/libs/uio/ioaux.c | 930 +++++ src/libs/uio/ioaux.h | 53 + src/libs/uio/iointrn.h | 197 + src/libs/uio/match.c | 569 +++ src/libs/uio/match.h | 182 + src/libs/uio/mem.h | 61 + src/libs/uio/memdebug.c | 293 ++ src/libs/uio/memdebug.h | 97 + src/libs/uio/mount.c | 168 + src/libs/uio/mount.h | 64 + src/libs/uio/mounttree.c | 814 ++++ src/libs/uio/mounttree.h | 204 + src/libs/uio/paths.c | 602 +++ src/libs/uio/paths.h | 96 + src/libs/uio/physical.c | 174 + src/libs/uio/physical.h | 92 + src/libs/uio/stdio/Makeinfo | 2 + src/libs/uio/stdio/stdio.c | 854 +++++ src/libs/uio/stdio/stdio.h | 111 + src/libs/uio/types.h | 64 + src/libs/uio/uioport.h | 173 + src/libs/uio/uiostream.c | 603 +++ src/libs/uio/uiostream.h | 97 + src/libs/uio/uioutils.c | 228 ++ src/libs/uio/uioutils.h | 92 + src/libs/uio/utils.c | 497 +++ src/libs/uio/utils.h | 43 + src/libs/uio/zip/Makeinfo | 2 + src/libs/uio/zip/zip.c | 1680 +++++++++ src/libs/uio/zip/zip.h | 106 + src/libs/uioutils.h | 34 + src/libs/unicode.h | 72 + src/libs/video/Makeinfo | 3 + src/libs/video/dukvid.c | 748 ++++ src/libs/video/dukvid.h | 36 + src/libs/video/legacyplayer.c | 81 + src/libs/video/vfileins.c | 28 + src/libs/video/video.c | 190 + src/libs/video/video.h | 56 + src/libs/video/videodec.c | 363 ++ src/libs/video/videodec.h | 124 + src/libs/video/vidintrn.h | 41 + src/libs/video/vidplayer.c | 481 +++ src/libs/video/vidplayer.h | 31 + src/libs/video/vresins.c | 186 + src/libs/vidlib.h | 68 + src/options.c | 647 ++++ src/options.h | 94 + src/port.c | 145 + src/port.h | 554 +++ src/regex/Makeinfo | 2 + src/regex/regcomp.ci | 3931 ++++++++++++++++++++ src/regex/regex.c | 99 + src/regex/regex.h | 593 +++ src/regex/regex_internal.ci | 1673 +++++++++ src/regex/regex_internal.h | 801 ++++ src/regex/regexec.ci | 4325 ++++++++++++++++++++++ src/res/Makeinfo | 6 + src/res/UrQuanMasters.rc | 76 + src/res/darwin/Info.plist | 22 + src/res/darwin/PkgInfo | 1 + src/res/darwin/The Ur-Quan Masters.icns | Bin 0 -> 66834 bytes src/res/darwin/uqm.r | 3 + src/res/kohr-ah1.ico | Bin 0 -> 2238 bytes src/res/sis1.ico | Bin 0 -> 2238 bytes src/res/starcon2.ico | Bin 0 -> 766 bytes src/res/ur-quan-icon-24-hover-alpha.ico | Bin 0 -> 4718 bytes src/res/ur-quan-icon-24-hover.ico | Bin 0 -> 2262 bytes src/res/ur-quan-icon-alpha.ico | Bin 0 -> 25214 bytes src/res/ur-quan-icon-std.ico | Bin 0 -> 10134 bytes src/res/ur-quan1.ico | Bin 0 -> 2238 bytes src/res/ur-quan2.ico | Bin 0 -> 2238 bytes src/symbian/bld.inf | 9 + src/symbian/config.h | 57 + src/symbian/icons_scalable_dc.mk | 37 + src/symbian/uqm-armv5.pkg | 26 + src/symbian/uqm-gcce.pkg | 26 + src/symbian/uqm.cfg | 26 + src/symbian/uqm.mmp | 45 + src/symbian/uqm.rss | 26 + src/symbian/uqm.svg | 70 + src/symbian/uqm_reg.rss | 12 + src/symbian/uqmapp.cpp | 308 ++ src/types.h | 188 + src/uqm.c | 1285 +++++++ src/uqm/Makeinfo | 24 + src/uqm/battle.c | 512 +++ src/uqm/battle.h | 66 + src/uqm/battlecontrols.c | 100 + src/uqm/battlecontrols.h | 99 + src/uqm/border.c | 200 + src/uqm/build.c | 547 +++ src/uqm/build.h | 69 + src/uqm/cleanup.c | 99 + src/uqm/clock.c | 314 ++ src/uqm/clock.h | 111 + src/uqm/cnctdlg.c | 630 ++++ src/uqm/cnctdlg.h | 38 + src/uqm/coderes.h | 43 + src/uqm/collide.c | 183 + src/uqm/collide.h | 70 + src/uqm/colors.h | 440 +++ src/uqm/comm.c | 1649 +++++++++ src/uqm/comm.h | 142 + src/uqm/comm/Makeinfo | 4 + src/uqm/comm/arilou/Makeinfo | 2 + src/uqm/comm/arilou/arilouc.c | 855 +++++ src/uqm/comm/arilou/resinst.h | 9 + src/uqm/comm/arilou/strings.h | 123 + src/uqm/comm/blackur/Makeinfo | 2 + src/uqm/comm/blackur/blackurc.c | 567 +++ src/uqm/comm/blackur/resinst.h | 9 + src/uqm/comm/blackur/strings.h | 103 + src/uqm/comm/chmmr/Makeinfo | 2 + src/uqm/comm/chmmr/chmmrc.c | 641 ++++ src/uqm/comm/chmmr/resinst.h | 9 + src/uqm/comm/chmmr/strings.h | 105 + src/uqm/comm/comandr/Makeinfo | 2 + src/uqm/comm/comandr/comandr.c | 694 ++++ src/uqm/comm/comandr/resinst.h | 12 + src/uqm/comm/comandr/strings.h | 127 + src/uqm/comm/commall.h | 26 + src/uqm/comm/druuge/Makeinfo | 2 + src/uqm/comm/druuge/druugec.c | 926 +++++ src/uqm/comm/druuge/resinst.h | 9 + src/uqm/comm/druuge/strings.h | 132 + src/uqm/comm/ilwrath/Makeinfo | 2 + src/uqm/comm/ilwrath/ilwrathc.c | 649 ++++ src/uqm/comm/ilwrath/resinst.h | 9 + src/uqm/comm/ilwrath/strings.h | 135 + src/uqm/comm/melnorm/Makeinfo | 2 + src/uqm/comm/melnorm/melnorm.c | 1855 ++++++++++ src/uqm/comm/melnorm/resinst.h | 9 + src/uqm/comm/melnorm/strings.h | 309 ++ src/uqm/comm/mycon/Makeinfo | 2 + src/uqm/comm/mycon/myconc.c | 643 ++++ src/uqm/comm/mycon/resinst.h | 9 + src/uqm/comm/mycon/strings.h | 136 + src/uqm/comm/orz/Makeinfo | 2 + src/uqm/comm/orz/orzc.c | 898 +++++ src/uqm/comm/orz/resinst.h | 9 + src/uqm/comm/orz/strings.h | 143 + src/uqm/comm/pkunk/Makeinfo | 2 + src/uqm/comm/pkunk/pkunkc.c | 1148 ++++++ src/uqm/comm/pkunk/resinst.h | 9 + src/uqm/comm/pkunk/strings.h | 214 ++ src/uqm/comm/rebel/Makeinfo | 2 + src/uqm/comm/rebel/rebel.c | 449 +++ src/uqm/comm/rebel/strings.h | 61 + src/uqm/comm/shofixt/Makeinfo | 2 + src/uqm/comm/shofixt/resinst.h | 9 + src/uqm/comm/shofixt/shofixt.c | 652 ++++ src/uqm/comm/shofixt/strings.h | 122 + src/uqm/comm/slyhome/Makeinfo | 2 + src/uqm/comm/slyhome/resinst.h | 9 + src/uqm/comm/slyhome/slyhome.c | 921 +++++ src/uqm/comm/slyhome/strings.h | 143 + src/uqm/comm/slyland/Makeinfo | 2 + src/uqm/comm/slyland/resinst.h | 9 + src/uqm/comm/slyland/slyland.c | 518 +++ src/uqm/comm/slyland/strings.h | 113 + src/uqm/comm/spahome/Makeinfo | 2 + src/uqm/comm/spahome/spahome.c | 1018 +++++ src/uqm/comm/spahome/strings.h | 174 + src/uqm/comm/spathi/Makeinfo | 2 + src/uqm/comm/spathi/resinst.h | 14 + src/uqm/comm/spathi/spathic.c | 834 +++++ src/uqm/comm/spathi/strings.h | 160 + src/uqm/comm/starbas/Makeinfo | 2 + src/uqm/comm/starbas/starbas.c | 1961 ++++++++++ src/uqm/comm/starbas/strings.h | 327 ++ src/uqm/comm/supox/Makeinfo | 2 + src/uqm/comm/supox/resinst.h | 9 + src/uqm/comm/supox/strings.h | 124 + src/uqm/comm/supox/supoxc.c | 708 ++++ src/uqm/comm/syreen/Makeinfo | 2 + src/uqm/comm/syreen/resinst.h | 9 + src/uqm/comm/syreen/strings.h | 158 + src/uqm/comm/syreen/syreenc.c | 878 +++++ src/uqm/comm/talkpet/Makeinfo | 2 + src/uqm/comm/talkpet/resinst.h | 9 + src/uqm/comm/talkpet/strings.h | 140 + src/uqm/comm/talkpet/talkpet.c | 841 +++++ src/uqm/comm/thradd/Makeinfo | 2 + src/uqm/comm/thradd/resinst.h | 9 + src/uqm/comm/thradd/strings.h | 181 + src/uqm/comm/thradd/thraddc.c | 954 +++++ src/uqm/comm/umgah/Makeinfo | 2 + src/uqm/comm/umgah/resinst.h | 9 + src/uqm/comm/umgah/strings.h | 114 + src/uqm/comm/umgah/umgahc.c | 729 ++++ src/uqm/comm/urquan/Makeinfo | 2 + src/uqm/comm/urquan/resinst.h | 9 + src/uqm/comm/urquan/strings.h | 101 + src/uqm/comm/urquan/urquanc.c | 555 +++ src/uqm/comm/utwig/Makeinfo | 2 + src/uqm/comm/utwig/resinst.h | 10 + src/uqm/comm/utwig/strings.h | 144 + src/uqm/comm/utwig/utwigc.c | 996 +++++ src/uqm/comm/vux/Makeinfo | 2 + src/uqm/comm/vux/resinst.h | 9 + src/uqm/comm/vux/strings.h | 129 + src/uqm/comm/vux/vuxc.c | 796 ++++ src/uqm/comm/yehat/Makeinfo | 2 + src/uqm/comm/yehat/resinst.h | 11 + src/uqm/comm/yehat/strings.h | 102 + src/uqm/comm/yehat/yehatc.c | 685 ++++ src/uqm/comm/zoqfot/Makeinfo | 2 + src/uqm/comm/zoqfot/resinst.h | 9 + src/uqm/comm/zoqfot/strings.h | 365 ++ src/uqm/comm/zoqfot/zoqfotc.c | 975 +++++ src/uqm/commanim.c | 623 ++++ src/uqm/commanim.h | 141 + src/uqm/commglue.c | 421 +++ src/uqm/commglue.h | 183 + src/uqm/confirm.c | 250 ++ src/uqm/cons_res.c | 112 + src/uqm/cons_res.h | 38 + src/uqm/controls.h | 172 + src/uqm/corecode.h | 49 + src/uqm/credits.c | 839 +++++ src/uqm/credits.h | 32 + src/uqm/cyborg.c | 1339 +++++++ src/uqm/demo.c | 141 + src/uqm/demo.h | 55 + src/uqm/displist.c | 274 ++ src/uqm/displist.h | 131 + src/uqm/dummy.c | 207 ++ src/uqm/dummy.h | 52 + src/uqm/element.h | 242 ++ src/uqm/encount.c | 844 +++++ src/uqm/encount.h | 119 + src/uqm/flash.c | 805 ++++ src/uqm/flash.h | 223 ++ src/uqm/fmv.c | 134 + src/uqm/fmv.h | 41 + src/uqm/galaxy.c | 464 +++ src/uqm/gameev.c | 729 ++++ src/uqm/gameev.h | 68 + src/uqm/gameinp.c | 496 +++ src/uqm/gameopt.c | 1347 +++++++ src/uqm/gameopt.h | 36 + src/uqm/gamestr.h | 93 + src/uqm/gendef.c | 137 + src/uqm/gendef.h | 71 + src/uqm/getchar.c | 442 +++ src/uqm/globdata.c | 511 +++ src/uqm/globdata.h | 1059 ++++++ src/uqm/gravity.c | 200 + src/uqm/grpinfo.c | 865 +++++ src/uqm/grpinfo.h | 93 + src/uqm/grpintrn.h | 56 + src/uqm/hyper.c | 1747 +++++++++ src/uqm/hyper.h | 90 + src/uqm/ifontres.h | 12 + src/uqm/igfxres.h | 274 ++ src/uqm/ikey_con.h | 2 + src/uqm/imusicre.h | 20 + src/uqm/init.c | 351 ++ src/uqm/init.h | 46 + src/uqm/intel.c | 76 + src/uqm/intel.h | 85 + src/uqm/intro.c | 875 +++++ src/uqm/ipdisp.c | 777 ++++ src/uqm/ipdisp.h | 37 + src/uqm/isndres.h | 7 + src/uqm/istrtab.h | 154 + src/uqm/load.c | 774 ++++ src/uqm/load_legacy.c | 821 ++++ src/uqm/loadship.c | 200 + src/uqm/master.c | 217 ++ src/uqm/master.h | 69 + src/uqm/menu.c | 603 +++ src/uqm/menustat.h | 131 + src/uqm/misc.c | 407 ++ src/uqm/nameref.h | 33 + src/uqm/oscill.c | 191 + src/uqm/oscill.h | 43 + src/uqm/outfit.c | 795 ++++ src/uqm/pickship.c | 501 +++ src/uqm/pickship.h | 35 + src/uqm/plandata.c | 1850 +++++++++ src/uqm/planets/Makeinfo | 7 + src/uqm/planets/calc.c | 530 +++ src/uqm/planets/cargo.c | 356 ++ src/uqm/planets/devices.c | 690 ++++ src/uqm/planets/elemdata.h | 215 ++ src/uqm/planets/generate.h | 110 + src/uqm/planets/generate/Makeinfo | 6 + src/uqm/planets/generate/genall.h | 27 + src/uqm/planets/generate/genand.c | 164 + src/uqm/planets/generate/genburv.c | 192 + src/uqm/planets/generate/genchmmr.c | 154 + src/uqm/planets/generate/gencol.c | 126 + src/uqm/planets/generate/gendefault.c | 373 ++ src/uqm/planets/generate/gendefault.h | 66 + src/uqm/planets/generate/gendru.c | 169 + src/uqm/planets/generate/genilw.c | 150 + src/uqm/planets/generate/genmel.c | 114 + src/uqm/planets/generate/genmyc.c | 286 ++ src/uqm/planets/generate/genorz.c | 222 ++ src/uqm/planets/generate/genpet.c | 257 ++ src/uqm/planets/generate/genpku.c | 159 + src/uqm/planets/generate/genrain.c | 102 + src/uqm/planets/generate/gensam.c | 324 ++ src/uqm/planets/generate/genshof.c | 178 + src/uqm/planets/generate/gensly.c | 70 + src/uqm/planets/generate/gensol.c | 671 ++++ src/uqm/planets/generate/genspa.c | 283 ++ src/uqm/planets/generate/gensup.c | 159 + src/uqm/planets/generate/gensyr.c | 102 + src/uqm/planets/generate/genthrad.c | 217 ++ src/uqm/planets/generate/gentrap.c | 80 + src/uqm/planets/generate/genutw.c | 269 ++ src/uqm/planets/generate/genvault.c | 130 + src/uqm/planets/generate/genvux.c | 329 ++ src/uqm/planets/generate/genwreck.c | 111 + src/uqm/planets/generate/genyeh.c | 140 + src/uqm/planets/generate/genzfpscout.c | 96 + src/uqm/planets/generate/genzoq.c | 170 + src/uqm/planets/gentopo.c | 206 ++ src/uqm/planets/lander.c | 2101 +++++++++++ src/uqm/planets/lander.h | 88 + src/uqm/planets/lifeform.h | 75 + src/uqm/planets/orbits.c | 629 ++++ src/uqm/planets/oval.c | 329 ++ src/uqm/planets/pl_stuff.c | 318 ++ src/uqm/planets/plandata.h | 318 ++ src/uqm/planets/planets.c | 483 +++ src/uqm/planets/planets.h | 322 ++ src/uqm/planets/plangen.c | 1954 ++++++++++ src/uqm/planets/pstarmap.c | 1631 ++++++++ src/uqm/planets/report.c | 271 ++ src/uqm/planets/roster.c | 428 +++ src/uqm/planets/scan.c | 1385 +++++++ src/uqm/planets/scan.h | 72 + src/uqm/planets/solarsys.c | 2021 ++++++++++ src/uqm/planets/solarsys.h | 34 + src/uqm/planets/sundata.h | 73 + src/uqm/planets/surface.c | 251 ++ src/uqm/process.c | 1108 ++++++ src/uqm/process.h | 37 + src/uqm/races.h | 675 ++++ src/uqm/resinst.h | 24 + src/uqm/restart.c | 413 +++ src/uqm/restart.h | 33 + src/uqm/save.c | 813 ++++ src/uqm/save.h | 78 + src/uqm/settings.c | 97 + src/uqm/settings.h | 41 + src/uqm/setup.c | 332 ++ src/uqm/setup.h | 89 + src/uqm/setupmenu.c | 1613 ++++++++ src/uqm/setupmenu.h | 100 + src/uqm/ship.c | 574 +++ src/uqm/ship.h | 43 + src/uqm/shipcont.h | 44 + src/uqm/ships/Makeinfo | 5 + src/uqm/ships/androsyn/Makeinfo | 2 + src/uqm/ships/androsyn/androsyn.c | 528 +++ src/uqm/ships/androsyn/androsyn.h | 31 + src/uqm/ships/androsyn/icode.h | 5 + src/uqm/ships/androsyn/resinst.h | 19 + src/uqm/ships/arilou/Makeinfo | 2 + src/uqm/ships/arilou/arilou.c | 303 ++ src/uqm/ships/arilou/arilou.h | 31 + src/uqm/ships/arilou/icode.h | 5 + src/uqm/ships/arilou/resinst.h | 16 + src/uqm/ships/blackurq/Makeinfo | 2 + src/uqm/ships/blackurq/blackurq.c | 567 +++ src/uqm/ships/blackurq/blackurq.h | 31 + src/uqm/ships/blackurq/icode.h | 5 + src/uqm/ships/blackurq/resinst.h | 19 + src/uqm/ships/chenjesu/Makeinfo | 2 + src/uqm/ships/chenjesu/chenjesu.c | 588 +++ src/uqm/ships/chenjesu/chenjesu.h | 31 + src/uqm/ships/chenjesu/icode.h | 5 + src/uqm/ships/chenjesu/resinst.h | 19 + src/uqm/ships/chmmr/Makeinfo | 2 + src/uqm/ships/chmmr/chmmr.c | 790 ++++ src/uqm/ships/chmmr/chmmr.h | 31 + src/uqm/ships/chmmr/icode.h | 5 + src/uqm/ships/chmmr/resinst.h | 19 + src/uqm/ships/druuge/Makeinfo | 2 + src/uqm/ships/druuge/druuge.c | 324 ++ src/uqm/ships/druuge/druuge.h | 31 + src/uqm/ships/druuge/icode.h | 5 + src/uqm/ships/druuge/resinst.h | 16 + src/uqm/ships/human/Makeinfo | 2 + src/uqm/ships/human/human.c | 360 ++ src/uqm/ships/human/human.h | 31 + src/uqm/ships/human/icode.h | 5 + src/uqm/ships/human/resinst.h | 16 + src/uqm/ships/ilwrath/Makeinfo | 2 + src/uqm/ships/ilwrath/icode.h | 5 + src/uqm/ships/ilwrath/ilwrath.c | 409 ++ src/uqm/ships/ilwrath/ilwrath.h | 31 + src/uqm/ships/ilwrath/resinst.h | 16 + src/uqm/ships/lastbat/Makeinfo | 2 + src/uqm/ships/lastbat/icode.h | 5 + src/uqm/ships/lastbat/lastbat.c | 926 +++++ src/uqm/ships/lastbat/lastbat.h | 31 + src/uqm/ships/lastbat/resinst.h | 16 + src/uqm/ships/melnorme/Makeinfo | 2 + src/uqm/ships/melnorme/icode.h | 5 + src/uqm/ships/melnorme/melnorme.c | 658 ++++ src/uqm/ships/melnorme/melnorme.h | 31 + src/uqm/ships/melnorme/resinst.h | 19 + src/uqm/ships/mmrnmhrm/Makeinfo | 2 + src/uqm/ships/mmrnmhrm/icode.h | 5 + src/uqm/ships/mmrnmhrm/mmrnmhrm.c | 527 +++ src/uqm/ships/mmrnmhrm/mmrnmhrm.h | 31 + src/uqm/ships/mmrnmhrm/resinst.h | 19 + src/uqm/ships/mycon/Makeinfo | 2 + src/uqm/ships/mycon/icode.h | 5 + src/uqm/ships/mycon/mycon.c | 376 ++ src/uqm/ships/mycon/mycon.h | 31 + src/uqm/ships/mycon/resinst.h | 16 + src/uqm/ships/orz/Makeinfo | 2 + src/uqm/ships/orz/icode.h | 5 + src/uqm/ships/orz/orz.c | 1083 ++++++ src/uqm/ships/orz/orz.h | 35 + src/uqm/ships/orz/resinst.h | 19 + src/uqm/ships/pkunk/Makeinfo | 2 + src/uqm/ships/pkunk/icode.h | 5 + src/uqm/ships/pkunk/pkunk.c | 640 ++++ src/uqm/ships/pkunk/pkunk.h | 31 + src/uqm/ships/pkunk/resinst.h | 16 + src/uqm/ships/probe/Makeinfo | 2 + src/uqm/ships/probe/icode.h | 5 + src/uqm/ships/probe/probe.c | 118 + src/uqm/ships/probe/probe.h | 31 + src/uqm/ships/probe/resinst.h | 5 + src/uqm/ships/ship.h | 37 + src/uqm/ships/shofixti/Makeinfo | 2 + src/uqm/ships/shofixti/icode.h | 5 + src/uqm/ships/shofixti/resinst.h | 23 + src/uqm/ships/shofixti/shofixti.c | 521 +++ src/uqm/ships/shofixti/shofixti.h | 31 + src/uqm/ships/sis_ship/Makeinfo | 2 + src/uqm/ships/sis_ship/icode.h | 5 + src/uqm/ships/sis_ship/resinst.h | 15 + src/uqm/ships/sis_ship/sis_ship.c | 1002 +++++ src/uqm/ships/sis_ship/sis_ship.h | 31 + src/uqm/ships/slylandr/Makeinfo | 2 + src/uqm/ships/slylandr/icode.h | 5 + src/uqm/ships/slylandr/resinst.h | 13 + src/uqm/ships/slylandr/slylandr.c | 438 +++ src/uqm/ships/slylandr/slylandr.h | 31 + src/uqm/ships/spathi/Makeinfo | 2 + src/uqm/ships/spathi/icode.h | 5 + src/uqm/ships/spathi/resinst.h | 19 + src/uqm/ships/spathi/spathi.c | 301 ++ src/uqm/ships/spathi/spathi.h | 31 + src/uqm/ships/supox/Makeinfo | 2 + src/uqm/ships/supox/icode.h | 5 + src/uqm/ships/supox/resinst.h | 16 + src/uqm/ships/supox/supox.c | 288 ++ src/uqm/ships/supox/supox.h | 31 + src/uqm/ships/syreen/Makeinfo | 2 + src/uqm/ships/syreen/icode.h | 5 + src/uqm/ships/syreen/resinst.h | 16 + src/uqm/ships/syreen/syreen.c | 284 ++ src/uqm/ships/syreen/syreen.h | 31 + src/uqm/ships/thradd/Makeinfo | 2 + src/uqm/ships/thradd/icode.h | 5 + src/uqm/ships/thradd/resinst.h | 19 + src/uqm/ships/thradd/thradd.c | 400 ++ src/uqm/ships/thradd/thradd.h | 31 + src/uqm/ships/umgah/Makeinfo | 2 + src/uqm/ships/umgah/icode.h | 5 + src/uqm/ships/umgah/resinst.h | 17 + src/uqm/ships/umgah/umgah.c | 434 +++ src/uqm/ships/umgah/umgah.h | 31 + src/uqm/ships/urquan/Makeinfo | 2 + src/uqm/ships/urquan/icode.h | 5 + src/uqm/ships/urquan/resinst.h | 19 + src/uqm/ships/urquan/urquan.c | 554 +++ src/uqm/ships/urquan/urquan.h | 31 + src/uqm/ships/utwig/Makeinfo | 2 + src/uqm/ships/utwig/icode.h | 5 + src/uqm/ships/utwig/resinst.h | 16 + src/uqm/ships/utwig/utwig.c | 380 ++ src/uqm/ships/utwig/utwig.h | 31 + src/uqm/ships/vux/Makeinfo | 2 + src/uqm/ships/vux/icode.h | 5 + src/uqm/ships/vux/resinst.h | 17 + src/uqm/ships/vux/vux.c | 398 ++ src/uqm/ships/vux/vux.h | 31 + src/uqm/ships/yehat/Makeinfo | 2 + src/uqm/ships/yehat/icode.h | 5 + src/uqm/ships/yehat/resinst.h | 19 + src/uqm/ships/yehat/yehat.c | 369 ++ src/uqm/ships/yehat/yehat.h | 31 + src/uqm/ships/zoqfot/Makeinfo | 2 + src/uqm/ships/zoqfot/icode.h | 5 + src/uqm/ships/zoqfot/resinst.h | 19 + src/uqm/ships/zoqfot/zoqfot.c | 377 ++ src/uqm/ships/zoqfot/zoqfot.h | 31 + src/uqm/shipstat.c | 437 +++ src/uqm/shipyard.c | 1495 ++++++++ src/uqm/sis.c | 1741 +++++++++ src/uqm/sis.h | 241 ++ src/uqm/sounds.c | 199 + src/uqm/sounds.h | 85 + src/uqm/starbase.c | 602 +++ src/uqm/starbase.h | 55 + src/uqm/starcon.c | 323 ++ src/uqm/starcon.h | 35 + src/uqm/starmap.c | 125 + src/uqm/starmap.h | 42 + src/uqm/state.c | 354 ++ src/uqm/state.h | 166 + src/uqm/status.c | 582 +++ src/uqm/status.h | 75 + src/uqm/supermelee/Makeinfo | 5 + src/uqm/supermelee/buildpick.c | 221 ++ src/uqm/supermelee/buildpick.h | 25 + src/uqm/supermelee/loadmele.c | 826 +++++ src/uqm/supermelee/loadmele.h | 67 + src/uqm/supermelee/melee.c | 2640 +++++++++++++ src/uqm/supermelee/melee.h | 144 + src/uqm/supermelee/meleesetup.c | 440 +++ src/uqm/supermelee/meleesetup.h | 143 + src/uqm/supermelee/meleeship.h | 55 + src/uqm/supermelee/netplay/FILES | 50 + src/uqm/supermelee/netplay/Makeinfo | 4 + src/uqm/supermelee/netplay/checkbuf.c | 145 + src/uqm/supermelee/netplay/checkbuf.h | 77 + src/uqm/supermelee/netplay/checksum.c | 302 ++ src/uqm/supermelee/netplay/checksum.h | 99 + src/uqm/supermelee/netplay/crc.c | 142 + src/uqm/supermelee/netplay/crc.h | 60 + src/uqm/supermelee/netplay/nc_connect.ci | 300 ++ src/uqm/supermelee/netplay/netconnection.c | 378 ++ src/uqm/supermelee/netplay/netconnection.h | 260 ++ src/uqm/supermelee/netplay/netinput.c | 157 + src/uqm/supermelee/netplay/netinput.h | 57 + src/uqm/supermelee/netplay/netmelee.c | 740 ++++ src/uqm/supermelee/netplay/netmelee.h | 90 + src/uqm/supermelee/netplay/netmisc.c | 134 + src/uqm/supermelee/netplay/netmisc.h | 77 + src/uqm/supermelee/netplay/netoptions.c | 39 + src/uqm/supermelee/netplay/netoptions.h | 56 + src/uqm/supermelee/netplay/netplay.h | 77 + src/uqm/supermelee/netplay/netrcv.c | 193 + src/uqm/supermelee/netplay/netrcv.h | 34 + src/uqm/supermelee/netplay/netsend.c | 95 + src/uqm/supermelee/netplay/netsend.h | 35 + src/uqm/supermelee/netplay/netstate.c | 48 + src/uqm/supermelee/netplay/netstate.h | 83 + src/uqm/supermelee/netplay/notify.c | 118 + src/uqm/supermelee/netplay/notify.h | 62 + src/uqm/supermelee/netplay/notifyall.c | 146 + src/uqm/supermelee/netplay/notifyall.h | 49 + src/uqm/supermelee/netplay/packet.c | 263 ++ src/uqm/supermelee/netplay/packet.h | 299 ++ src/uqm/supermelee/netplay/packethandlers.c | 649 ++++ src/uqm/supermelee/netplay/packethandlers.h | 56 + src/uqm/supermelee/netplay/packetq.c | 149 + src/uqm/supermelee/netplay/packetq.h | 59 + src/uqm/supermelee/netplay/packetsenders.c | 197 + src/uqm/supermelee/netplay/packetsenders.h | 67 + src/uqm/supermelee/netplay/proto/Makeinfo | 2 + src/uqm/supermelee/netplay/proto/npconfirm.c | 81 + src/uqm/supermelee/netplay/proto/npconfirm.h | 36 + src/uqm/supermelee/netplay/proto/ready.c | 106 + src/uqm/supermelee/netplay/proto/ready.h | 38 + src/uqm/supermelee/netplay/proto/reset.c | 166 + src/uqm/supermelee/netplay/proto/reset.h | 41 + src/uqm/supermelee/pickmele.c | 948 +++++ src/uqm/supermelee/pickmele.h | 102 + src/uqm/tactrans.c | 1032 ++++++ src/uqm/tactrans.h | 59 + src/uqm/trans.c | 154 + src/uqm/units.h | 227 ++ src/uqm/uqmdebug.c | 1926 ++++++++++ src/uqm/uqmdebug.h | 200 + src/uqm/util.c | 312 ++ src/uqm/util.h | 39 + src/uqm/velocity.c | 153 + src/uqm/velocity.h | 76 + src/uqm/weapon.c | 414 +++ src/uqm/weapon.h | 68 + src/uqmversion.h | 36 + 928 files changed, 227500 insertions(+) create mode 100644 src/Makeinfo create mode 100644 src/abxadec/Makefile create mode 100644 src/abxadec/abxaud.c create mode 100644 src/abxadec/abxaud.def create mode 100644 src/abxadec/abxaud.h create mode 100644 src/config.h create mode 100644 src/config_unix.h.in create mode 100644 src/config_vc6.h create mode 100644 src/config_win.h.in create mode 100644 src/darwin/Makeinfo create mode 100644 src/darwin/SDLMain.h create mode 100644 src/darwin/SDLMain.m create mode 100644 src/endian_uqm.h create mode 100644 src/getopt/Makeinfo create mode 100644 src/getopt/getopt.c create mode 100644 src/getopt/getopt.h create mode 100644 src/getopt/getopt1.c create mode 100644 src/libs/Makeinfo create mode 100644 src/libs/alarm.h create mode 100644 src/libs/async.h create mode 100644 src/libs/callback.h create mode 100644 src/libs/callback/Makeinfo create mode 100644 src/libs/callback/alarm.c create mode 100644 src/libs/callback/alarm.h create mode 100644 src/libs/callback/async.c create mode 100644 src/libs/callback/async.h create mode 100644 src/libs/callback/callback.c create mode 100644 src/libs/callback/callback.h create mode 100644 src/libs/cdp/Makeinfo create mode 100644 src/libs/cdp/cdp.c create mode 100644 src/libs/cdp/cdp.h create mode 100644 src/libs/cdp/cdp_alli.h create mode 100644 src/libs/cdp/cdp_iio.h create mode 100644 src/libs/cdp/cdp_imem.h create mode 100644 src/libs/cdp/cdp_isnd.h create mode 100644 src/libs/cdp/cdp_ivid.h create mode 100644 src/libs/cdp/cdpapi.c create mode 100644 src/libs/cdp/cdpapi.h create mode 100644 src/libs/cdp/cdpint.h create mode 100644 src/libs/cdp/cdpmod.h create mode 100644 src/libs/cdp/windl.c create mode 100644 src/libs/cdp/windl.h create mode 100644 src/libs/cdplib.h create mode 100644 src/libs/compiler.h create mode 100644 src/libs/declib.h create mode 100644 src/libs/decomp/Makeinfo create mode 100644 src/libs/decomp/lzdecode.c create mode 100644 src/libs/decomp/lzencode.c create mode 100644 src/libs/decomp/lzh.h create mode 100644 src/libs/decomp/update.c create mode 100644 src/libs/file.h create mode 100644 src/libs/file/Makeinfo create mode 100644 src/libs/file/dirs.c create mode 100644 src/libs/file/files.c create mode 100644 src/libs/file/filintrn.h create mode 100644 src/libs/file/temp.c create mode 100644 src/libs/gfxlib.h create mode 100644 src/libs/graphics/Makeinfo create mode 100644 src/libs/graphics/bbox.c create mode 100644 src/libs/graphics/bbox.h create mode 100644 src/libs/graphics/boxint.c create mode 100644 src/libs/graphics/clipline.c create mode 100644 src/libs/graphics/cmap.c create mode 100644 src/libs/graphics/cmap.h create mode 100644 src/libs/graphics/context.c create mode 100644 src/libs/graphics/context.h create mode 100644 src/libs/graphics/dcqueue.c create mode 100644 src/libs/graphics/dcqueue.h create mode 100644 src/libs/graphics/drawable.c create mode 100644 src/libs/graphics/drawable.h create mode 100644 src/libs/graphics/drawcmd.h create mode 100644 src/libs/graphics/filegfx.c create mode 100644 src/libs/graphics/font.c create mode 100644 src/libs/graphics/font.h create mode 100644 src/libs/graphics/frame.c create mode 100644 src/libs/graphics/gfx_common.c create mode 100644 src/libs/graphics/gfx_common.h create mode 100644 src/libs/graphics/gfxintrn.h create mode 100644 src/libs/graphics/gfxload.c create mode 100644 src/libs/graphics/intersec.c create mode 100644 src/libs/graphics/loaddisp.c create mode 100644 src/libs/graphics/pixmap.c create mode 100644 src/libs/graphics/prim.h create mode 100644 src/libs/graphics/resgfx.c create mode 100644 src/libs/graphics/sdl/2xscalers.c create mode 100644 src/libs/graphics/sdl/2xscalers.h create mode 100644 src/libs/graphics/sdl/2xscalers_3dnow.c create mode 100644 src/libs/graphics/sdl/2xscalers_mmx.c create mode 100644 src/libs/graphics/sdl/2xscalers_mmx.h create mode 100644 src/libs/graphics/sdl/2xscalers_sse.c create mode 100644 src/libs/graphics/sdl/Makeinfo create mode 100644 src/libs/graphics/sdl/biadv2x.c create mode 100644 src/libs/graphics/sdl/bilinear2x.c create mode 100644 src/libs/graphics/sdl/canvas.c create mode 100644 src/libs/graphics/sdl/hq2x.c create mode 100644 src/libs/graphics/sdl/nearest2x.c create mode 100644 src/libs/graphics/sdl/opengl.c create mode 100644 src/libs/graphics/sdl/opengl.h create mode 100644 src/libs/graphics/sdl/palette.c create mode 100644 src/libs/graphics/sdl/palette.h create mode 100644 src/libs/graphics/sdl/png2sdl.c create mode 100644 src/libs/graphics/sdl/png2sdl.h create mode 100644 src/libs/graphics/sdl/primitives.c create mode 100644 src/libs/graphics/sdl/primitives.h create mode 100644 src/libs/graphics/sdl/pure.c create mode 100644 src/libs/graphics/sdl/pure.h create mode 100644 src/libs/graphics/sdl/rotozoom.c create mode 100644 src/libs/graphics/sdl/rotozoom.h create mode 100644 src/libs/graphics/sdl/scaleint.h create mode 100644 src/libs/graphics/sdl/scalemmx.h create mode 100644 src/libs/graphics/sdl/scalers.c create mode 100644 src/libs/graphics/sdl/scalers.h create mode 100644 src/libs/graphics/sdl/sdl1_common.c create mode 100644 src/libs/graphics/sdl/sdl2_common.c create mode 100644 src/libs/graphics/sdl/sdl2_pure.c create mode 100644 src/libs/graphics/sdl/sdl_common.c create mode 100644 src/libs/graphics/sdl/sdl_common.h create mode 100644 src/libs/graphics/sdl/sdluio.c create mode 100644 src/libs/graphics/sdl/sdluio.h create mode 100644 src/libs/graphics/sdl/triscan2x.c create mode 100644 src/libs/graphics/tfb_draw.c create mode 100644 src/libs/graphics/tfb_draw.h create mode 100644 src/libs/graphics/tfb_prim.c create mode 100644 src/libs/graphics/tfb_prim.h create mode 100644 src/libs/graphics/widgets.c create mode 100644 src/libs/graphics/widgets.h create mode 100644 src/libs/heap.h create mode 100644 src/libs/heap/Makeinfo create mode 100644 src/libs/heap/heap.c create mode 100644 src/libs/heap/heap.h create mode 100644 src/libs/inplib.h create mode 100644 src/libs/input/Makeinfo create mode 100644 src/libs/input/inpintrn.h create mode 100644 src/libs/input/input_common.c create mode 100644 src/libs/input/input_common.h create mode 100644 src/libs/input/sdl/Makeinfo create mode 100644 src/libs/input/sdl/input.c create mode 100644 src/libs/input/sdl/input.h create mode 100644 src/libs/input/sdl/keynames.c create mode 100644 src/libs/input/sdl/keynames.h create mode 100644 src/libs/input/sdl/vcontrol.c create mode 100644 src/libs/input/sdl/vcontrol.h create mode 100644 src/libs/list.h create mode 100644 src/libs/list/Makeinfo create mode 100644 src/libs/list/list.c create mode 100644 src/libs/list/list.h create mode 100644 src/libs/log.h create mode 100644 src/libs/log/Makeinfo create mode 100644 src/libs/log/loginternal.h create mode 100644 src/libs/log/msgbox.h create mode 100644 src/libs/log/msgbox_macosx.m create mode 100644 src/libs/log/msgbox_stub.c create mode 100644 src/libs/log/msgbox_win.c create mode 100644 src/libs/log/uqmlog.c create mode 100644 src/libs/log/uqmlog.h create mode 100644 src/libs/math/Makeinfo create mode 100644 src/libs/math/mthintrn.h create mode 100644 src/libs/math/random.c create mode 100644 src/libs/math/random.h create mode 100644 src/libs/math/random2.c create mode 100644 src/libs/math/sqrt.c create mode 100644 src/libs/mathlib.h create mode 100644 src/libs/md5.h create mode 100644 src/libs/md5/Makeinfo create mode 100644 src/libs/md5/README create mode 100644 src/libs/md5/md5.c create mode 100644 src/libs/md5/md5.h create mode 100644 src/libs/memlib.h create mode 100644 src/libs/memory/Makeinfo create mode 100644 src/libs/memory/w_memlib.c create mode 100644 src/libs/mikmod/AUTHORS create mode 100644 src/libs/mikmod/Makeinfo create mode 100644 src/libs/mikmod/README create mode 100644 src/libs/mikmod/drv_nos.c create mode 100644 src/libs/mikmod/load_it.c create mode 100644 src/libs/mikmod/load_mod.c create mode 100644 src/libs/mikmod/load_s3m.c create mode 100644 src/libs/mikmod/load_stm.c create mode 100644 src/libs/mikmod/load_xm.c create mode 100644 src/libs/mikmod/mdreg.c create mode 100644 src/libs/mikmod/mdriver.c create mode 100644 src/libs/mikmod/mikmod.h create mode 100644 src/libs/mikmod/mikmod_build.h create mode 100644 src/libs/mikmod/mikmod_internals.h create mode 100644 src/libs/mikmod/mloader.c create mode 100644 src/libs/mikmod/mlreg.c create mode 100644 src/libs/mikmod/mlutil.c create mode 100644 src/libs/mikmod/mmalloc.c create mode 100644 src/libs/mikmod/mmerror.c create mode 100644 src/libs/mikmod/mmio.c create mode 100644 src/libs/mikmod/mplayer.c create mode 100644 src/libs/mikmod/munitrk.c create mode 100644 src/libs/mikmod/mwav.c create mode 100644 src/libs/mikmod/npertab.c create mode 100644 src/libs/mikmod/sloader.c create mode 100644 src/libs/mikmod/virtch.c create mode 100644 src/libs/mikmod/virtch2.c create mode 100644 src/libs/mikmod/virtch_common.c create mode 100644 src/libs/misc.h create mode 100644 src/libs/net.h create mode 100644 src/libs/network/FILES create mode 100644 src/libs/network/Makeinfo create mode 100644 src/libs/network/bytesex.h create mode 100644 src/libs/network/connect/Makeinfo create mode 100644 src/libs/network/connect/connect.c create mode 100644 src/libs/network/connect/connect.h create mode 100644 src/libs/network/connect/listen.c create mode 100644 src/libs/network/connect/listen.h create mode 100644 src/libs/network/connect/resolve.c create mode 100644 src/libs/network/connect/resolve.h create mode 100644 src/libs/network/netmanager/Makeinfo create mode 100644 src/libs/network/netmanager/ndesc.c create mode 100644 src/libs/network/netmanager/ndesc.h create mode 100644 src/libs/network/netmanager/ndindex.ci create mode 100644 src/libs/network/netmanager/netmanager.h create mode 100644 src/libs/network/netmanager/netmanager_bsd.c create mode 100644 src/libs/network/netmanager/netmanager_bsd.h create mode 100644 src/libs/network/netmanager/netmanager_common.ci create mode 100644 src/libs/network/netmanager/netmanager_win.c create mode 100644 src/libs/network/netmanager/netmanager_win.h create mode 100644 src/libs/network/netport.c create mode 100644 src/libs/network/netport.h create mode 100644 src/libs/network/network.h create mode 100644 src/libs/network/network_bsd.c create mode 100644 src/libs/network/network_win.c create mode 100644 src/libs/network/socket/Makeinfo create mode 100644 src/libs/network/socket/socket.c create mode 100644 src/libs/network/socket/socket.h create mode 100644 src/libs/network/socket/socket_bsd.c create mode 100644 src/libs/network/socket/socket_bsd.h create mode 100644 src/libs/network/socket/socket_win.c create mode 100644 src/libs/network/socket/socket_win.h create mode 100644 src/libs/network/wspiapiwrap.c create mode 100644 src/libs/network/wspiapiwrap.h create mode 100644 src/libs/platform.h create mode 100644 src/libs/reslib.h create mode 100644 src/libs/resource/Makeinfo create mode 100644 src/libs/resource/direct.c create mode 100644 src/libs/resource/filecntl.c create mode 100644 src/libs/resource/getres.c create mode 100644 src/libs/resource/index.h create mode 100644 src/libs/resource/loadres.c create mode 100644 src/libs/resource/propfile.c create mode 100644 src/libs/resource/propfile.h create mode 100644 src/libs/resource/resinit.c create mode 100644 src/libs/resource/resintrn.h create mode 100644 src/libs/resource/stringbank.c create mode 100644 src/libs/resource/stringbank.h create mode 100644 src/libs/sndlib.h create mode 100644 src/libs/sound/Makeinfo create mode 100644 src/libs/sound/audiocore.c create mode 100644 src/libs/sound/audiocore.h create mode 100644 src/libs/sound/decoders/Makeinfo create mode 100644 src/libs/sound/decoders/aiffaud.c create mode 100644 src/libs/sound/decoders/aiffaud.h create mode 100644 src/libs/sound/decoders/decoder.c create mode 100644 src/libs/sound/decoders/decoder.h create mode 100644 src/libs/sound/decoders/dukaud.c create mode 100644 src/libs/sound/decoders/dukaud.h create mode 100644 src/libs/sound/decoders/modaud.c create mode 100644 src/libs/sound/decoders/modaud.h create mode 100644 src/libs/sound/decoders/oggaud.c create mode 100644 src/libs/sound/decoders/oggaud.h create mode 100644 src/libs/sound/decoders/wav.c create mode 100644 src/libs/sound/decoders/wav.h create mode 100644 src/libs/sound/fileinst.c create mode 100644 src/libs/sound/mixer/Makeinfo create mode 100644 src/libs/sound/mixer/mixer.c create mode 100644 src/libs/sound/mixer/mixer.h create mode 100644 src/libs/sound/mixer/mixerint.h create mode 100644 src/libs/sound/mixer/nosound/Makeinfo create mode 100644 src/libs/sound/mixer/nosound/audiodrv_nosound.c create mode 100644 src/libs/sound/mixer/nosound/audiodrv_nosound.h create mode 100644 src/libs/sound/mixer/sdl/Makeinfo create mode 100644 src/libs/sound/mixer/sdl/audiodrv_sdl.c create mode 100644 src/libs/sound/mixer/sdl/audiodrv_sdl.h create mode 100644 src/libs/sound/music.c create mode 100644 src/libs/sound/openal/Makeinfo create mode 100644 src/libs/sound/openal/audiodrv_openal.c create mode 100644 src/libs/sound/openal/audiodrv_openal.h create mode 100644 src/libs/sound/resinst.c create mode 100644 src/libs/sound/sfx.c create mode 100644 src/libs/sound/sndintrn.h create mode 100644 src/libs/sound/sound.c create mode 100644 src/libs/sound/sound.h create mode 100644 src/libs/sound/stream.c create mode 100644 src/libs/sound/stream.h create mode 100644 src/libs/sound/trackint.h create mode 100644 src/libs/sound/trackplayer.c create mode 100644 src/libs/sound/trackplayer.h create mode 100644 src/libs/strings/Makeinfo create mode 100644 src/libs/strings/getstr.c create mode 100644 src/libs/strings/sfileins.c create mode 100644 src/libs/strings/sresins.c create mode 100644 src/libs/strings/stringhashtable.c create mode 100644 src/libs/strings/stringhashtable.h create mode 100644 src/libs/strings/strings.c create mode 100644 src/libs/strings/strintrn.h create mode 100644 src/libs/strings/unicode.c create mode 100644 src/libs/strlib.h create mode 100644 src/libs/task/Makeinfo create mode 100644 src/libs/task/tasklib.c create mode 100644 src/libs/tasklib.h create mode 100644 src/libs/threadlib.h create mode 100644 src/libs/threads/Makeinfo create mode 100644 src/libs/threads/pthread/Makeinfo create mode 100644 src/libs/threads/pthread/posixthreads.c create mode 100644 src/libs/threads/pthread/posixthreads.h create mode 100644 src/libs/threads/sdl/Makeinfo create mode 100644 src/libs/threads/sdl/sdlthreads.c create mode 100644 src/libs/threads/sdl/sdlthreads.h create mode 100644 src/libs/threads/thrcommon.c create mode 100644 src/libs/threads/thrcommon.h create mode 100644 src/libs/time/Makeinfo create mode 100644 src/libs/time/sdl/Makeinfo create mode 100644 src/libs/time/sdl/sdltime.c create mode 100644 src/libs/time/sdl/sdltime.h create mode 100644 src/libs/time/timecommon.c create mode 100644 src/libs/time/timecommon.h create mode 100644 src/libs/timelib.h create mode 100644 src/libs/uio.h create mode 100644 src/libs/uio/COPYING create mode 100644 src/libs/uio/Makeinfo create mode 100644 src/libs/uio/charhashtable.c create mode 100644 src/libs/uio/charhashtable.h create mode 100644 src/libs/uio/debug.c create mode 100644 src/libs/uio/debug.h create mode 100644 src/libs/uio/defaultfs.c create mode 100644 src/libs/uio/defaultfs.h create mode 100644 src/libs/uio/doc/basics create mode 100644 src/libs/uio/doc/conventions create mode 100644 src/libs/uio/doc/todo create mode 100644 src/libs/uio/fileblock.c create mode 100644 src/libs/uio/fileblock.h create mode 100644 src/libs/uio/fstypes.c create mode 100644 src/libs/uio/fstypes.h create mode 100644 src/libs/uio/getint.h create mode 100644 src/libs/uio/gphys.c create mode 100644 src/libs/uio/gphys.h create mode 100644 src/libs/uio/hashtable.c create mode 100644 src/libs/uio/hashtable.h create mode 100644 src/libs/uio/io.c create mode 100644 src/libs/uio/io.h create mode 100644 src/libs/uio/ioaux.c create mode 100644 src/libs/uio/ioaux.h create mode 100644 src/libs/uio/iointrn.h create mode 100644 src/libs/uio/match.c create mode 100644 src/libs/uio/match.h create mode 100644 src/libs/uio/mem.h create mode 100644 src/libs/uio/memdebug.c create mode 100644 src/libs/uio/memdebug.h create mode 100644 src/libs/uio/mount.c create mode 100644 src/libs/uio/mount.h create mode 100644 src/libs/uio/mounttree.c create mode 100644 src/libs/uio/mounttree.h create mode 100644 src/libs/uio/paths.c create mode 100644 src/libs/uio/paths.h create mode 100644 src/libs/uio/physical.c create mode 100644 src/libs/uio/physical.h create mode 100644 src/libs/uio/stdio/Makeinfo create mode 100644 src/libs/uio/stdio/stdio.c create mode 100644 src/libs/uio/stdio/stdio.h create mode 100644 src/libs/uio/types.h create mode 100644 src/libs/uio/uioport.h create mode 100644 src/libs/uio/uiostream.c create mode 100644 src/libs/uio/uiostream.h create mode 100644 src/libs/uio/uioutils.c create mode 100644 src/libs/uio/uioutils.h create mode 100644 src/libs/uio/utils.c create mode 100644 src/libs/uio/utils.h create mode 100644 src/libs/uio/zip/Makeinfo create mode 100644 src/libs/uio/zip/zip.c create mode 100644 src/libs/uio/zip/zip.h create mode 100644 src/libs/uioutils.h create mode 100644 src/libs/unicode.h create mode 100644 src/libs/video/Makeinfo create mode 100644 src/libs/video/dukvid.c create mode 100644 src/libs/video/dukvid.h create mode 100644 src/libs/video/legacyplayer.c create mode 100644 src/libs/video/vfileins.c create mode 100644 src/libs/video/video.c create mode 100644 src/libs/video/video.h create mode 100644 src/libs/video/videodec.c create mode 100644 src/libs/video/videodec.h create mode 100644 src/libs/video/vidintrn.h create mode 100644 src/libs/video/vidplayer.c create mode 100644 src/libs/video/vidplayer.h create mode 100644 src/libs/video/vresins.c create mode 100644 src/libs/vidlib.h create mode 100644 src/options.c create mode 100644 src/options.h create mode 100644 src/port.c create mode 100644 src/port.h create mode 100644 src/regex/Makeinfo create mode 100644 src/regex/regcomp.ci create mode 100644 src/regex/regex.c create mode 100644 src/regex/regex.h create mode 100644 src/regex/regex_internal.ci create mode 100644 src/regex/regex_internal.h create mode 100644 src/regex/regexec.ci create mode 100644 src/res/Makeinfo create mode 100644 src/res/UrQuanMasters.rc create mode 100644 src/res/darwin/Info.plist create mode 100644 src/res/darwin/PkgInfo create mode 100644 src/res/darwin/The Ur-Quan Masters.icns create mode 100644 src/res/darwin/uqm.r create mode 100644 src/res/kohr-ah1.ico create mode 100644 src/res/sis1.ico create mode 100644 src/res/starcon2.ico create mode 100644 src/res/ur-quan-icon-24-hover-alpha.ico create mode 100644 src/res/ur-quan-icon-24-hover.ico create mode 100644 src/res/ur-quan-icon-alpha.ico create mode 100644 src/res/ur-quan-icon-std.ico create mode 100644 src/res/ur-quan1.ico create mode 100644 src/res/ur-quan2.ico create mode 100644 src/symbian/bld.inf create mode 100644 src/symbian/config.h create mode 100644 src/symbian/icons_scalable_dc.mk create mode 100644 src/symbian/uqm-armv5.pkg create mode 100644 src/symbian/uqm-gcce.pkg create mode 100644 src/symbian/uqm.cfg create mode 100644 src/symbian/uqm.mmp create mode 100644 src/symbian/uqm.rss create mode 100644 src/symbian/uqm.svg create mode 100644 src/symbian/uqm_reg.rss create mode 100644 src/symbian/uqmapp.cpp create mode 100644 src/types.h create mode 100644 src/uqm.c create mode 100644 src/uqm/Makeinfo create mode 100644 src/uqm/battle.c create mode 100644 src/uqm/battle.h create mode 100644 src/uqm/battlecontrols.c create mode 100644 src/uqm/battlecontrols.h create mode 100644 src/uqm/border.c create mode 100644 src/uqm/build.c create mode 100644 src/uqm/build.h create mode 100644 src/uqm/cleanup.c create mode 100644 src/uqm/clock.c create mode 100644 src/uqm/clock.h create mode 100644 src/uqm/cnctdlg.c create mode 100644 src/uqm/cnctdlg.h create mode 100644 src/uqm/coderes.h create mode 100644 src/uqm/collide.c create mode 100644 src/uqm/collide.h create mode 100644 src/uqm/colors.h create mode 100644 src/uqm/comm.c create mode 100644 src/uqm/comm.h create mode 100644 src/uqm/comm/Makeinfo create mode 100644 src/uqm/comm/arilou/Makeinfo create mode 100644 src/uqm/comm/arilou/arilouc.c create mode 100644 src/uqm/comm/arilou/resinst.h create mode 100644 src/uqm/comm/arilou/strings.h create mode 100644 src/uqm/comm/blackur/Makeinfo create mode 100644 src/uqm/comm/blackur/blackurc.c create mode 100644 src/uqm/comm/blackur/resinst.h create mode 100644 src/uqm/comm/blackur/strings.h create mode 100644 src/uqm/comm/chmmr/Makeinfo create mode 100644 src/uqm/comm/chmmr/chmmrc.c create mode 100644 src/uqm/comm/chmmr/resinst.h create mode 100644 src/uqm/comm/chmmr/strings.h create mode 100644 src/uqm/comm/comandr/Makeinfo create mode 100644 src/uqm/comm/comandr/comandr.c create mode 100644 src/uqm/comm/comandr/resinst.h create mode 100644 src/uqm/comm/comandr/strings.h create mode 100644 src/uqm/comm/commall.h create mode 100644 src/uqm/comm/druuge/Makeinfo create mode 100644 src/uqm/comm/druuge/druugec.c create mode 100644 src/uqm/comm/druuge/resinst.h create mode 100644 src/uqm/comm/druuge/strings.h create mode 100644 src/uqm/comm/ilwrath/Makeinfo create mode 100644 src/uqm/comm/ilwrath/ilwrathc.c create mode 100644 src/uqm/comm/ilwrath/resinst.h create mode 100644 src/uqm/comm/ilwrath/strings.h create mode 100644 src/uqm/comm/melnorm/Makeinfo create mode 100644 src/uqm/comm/melnorm/melnorm.c create mode 100644 src/uqm/comm/melnorm/resinst.h create mode 100644 src/uqm/comm/melnorm/strings.h create mode 100644 src/uqm/comm/mycon/Makeinfo create mode 100644 src/uqm/comm/mycon/myconc.c create mode 100644 src/uqm/comm/mycon/resinst.h create mode 100644 src/uqm/comm/mycon/strings.h create mode 100644 src/uqm/comm/orz/Makeinfo create mode 100644 src/uqm/comm/orz/orzc.c create mode 100644 src/uqm/comm/orz/resinst.h create mode 100644 src/uqm/comm/orz/strings.h create mode 100644 src/uqm/comm/pkunk/Makeinfo create mode 100644 src/uqm/comm/pkunk/pkunkc.c create mode 100644 src/uqm/comm/pkunk/resinst.h create mode 100644 src/uqm/comm/pkunk/strings.h create mode 100644 src/uqm/comm/rebel/Makeinfo create mode 100644 src/uqm/comm/rebel/rebel.c create mode 100644 src/uqm/comm/rebel/strings.h create mode 100644 src/uqm/comm/shofixt/Makeinfo create mode 100644 src/uqm/comm/shofixt/resinst.h create mode 100644 src/uqm/comm/shofixt/shofixt.c create mode 100644 src/uqm/comm/shofixt/strings.h create mode 100644 src/uqm/comm/slyhome/Makeinfo create mode 100644 src/uqm/comm/slyhome/resinst.h create mode 100644 src/uqm/comm/slyhome/slyhome.c create mode 100644 src/uqm/comm/slyhome/strings.h create mode 100644 src/uqm/comm/slyland/Makeinfo create mode 100644 src/uqm/comm/slyland/resinst.h create mode 100644 src/uqm/comm/slyland/slyland.c create mode 100644 src/uqm/comm/slyland/strings.h create mode 100644 src/uqm/comm/spahome/Makeinfo create mode 100644 src/uqm/comm/spahome/spahome.c create mode 100644 src/uqm/comm/spahome/strings.h create mode 100644 src/uqm/comm/spathi/Makeinfo create mode 100644 src/uqm/comm/spathi/resinst.h create mode 100644 src/uqm/comm/spathi/spathic.c create mode 100644 src/uqm/comm/spathi/strings.h create mode 100644 src/uqm/comm/starbas/Makeinfo create mode 100644 src/uqm/comm/starbas/starbas.c create mode 100644 src/uqm/comm/starbas/strings.h create mode 100644 src/uqm/comm/supox/Makeinfo create mode 100644 src/uqm/comm/supox/resinst.h create mode 100644 src/uqm/comm/supox/strings.h create mode 100644 src/uqm/comm/supox/supoxc.c create mode 100644 src/uqm/comm/syreen/Makeinfo create mode 100644 src/uqm/comm/syreen/resinst.h create mode 100644 src/uqm/comm/syreen/strings.h create mode 100644 src/uqm/comm/syreen/syreenc.c create mode 100644 src/uqm/comm/talkpet/Makeinfo create mode 100644 src/uqm/comm/talkpet/resinst.h create mode 100644 src/uqm/comm/talkpet/strings.h create mode 100644 src/uqm/comm/talkpet/talkpet.c create mode 100644 src/uqm/comm/thradd/Makeinfo create mode 100644 src/uqm/comm/thradd/resinst.h create mode 100644 src/uqm/comm/thradd/strings.h create mode 100644 src/uqm/comm/thradd/thraddc.c create mode 100644 src/uqm/comm/umgah/Makeinfo create mode 100644 src/uqm/comm/umgah/resinst.h create mode 100644 src/uqm/comm/umgah/strings.h create mode 100644 src/uqm/comm/umgah/umgahc.c create mode 100644 src/uqm/comm/urquan/Makeinfo create mode 100644 src/uqm/comm/urquan/resinst.h create mode 100644 src/uqm/comm/urquan/strings.h create mode 100644 src/uqm/comm/urquan/urquanc.c create mode 100644 src/uqm/comm/utwig/Makeinfo create mode 100644 src/uqm/comm/utwig/resinst.h create mode 100644 src/uqm/comm/utwig/strings.h create mode 100644 src/uqm/comm/utwig/utwigc.c create mode 100644 src/uqm/comm/vux/Makeinfo create mode 100644 src/uqm/comm/vux/resinst.h create mode 100644 src/uqm/comm/vux/strings.h create mode 100644 src/uqm/comm/vux/vuxc.c create mode 100644 src/uqm/comm/yehat/Makeinfo create mode 100644 src/uqm/comm/yehat/resinst.h create mode 100644 src/uqm/comm/yehat/strings.h create mode 100644 src/uqm/comm/yehat/yehatc.c create mode 100644 src/uqm/comm/zoqfot/Makeinfo create mode 100644 src/uqm/comm/zoqfot/resinst.h create mode 100644 src/uqm/comm/zoqfot/strings.h create mode 100644 src/uqm/comm/zoqfot/zoqfotc.c create mode 100644 src/uqm/commanim.c create mode 100644 src/uqm/commanim.h create mode 100644 src/uqm/commglue.c create mode 100644 src/uqm/commglue.h create mode 100644 src/uqm/confirm.c create mode 100644 src/uqm/cons_res.c create mode 100644 src/uqm/cons_res.h create mode 100644 src/uqm/controls.h create mode 100644 src/uqm/corecode.h create mode 100644 src/uqm/credits.c create mode 100644 src/uqm/credits.h create mode 100644 src/uqm/cyborg.c create mode 100644 src/uqm/demo.c create mode 100644 src/uqm/demo.h create mode 100644 src/uqm/displist.c create mode 100644 src/uqm/displist.h create mode 100644 src/uqm/dummy.c create mode 100644 src/uqm/dummy.h create mode 100644 src/uqm/element.h create mode 100644 src/uqm/encount.c create mode 100644 src/uqm/encount.h create mode 100644 src/uqm/flash.c create mode 100644 src/uqm/flash.h create mode 100644 src/uqm/fmv.c create mode 100644 src/uqm/fmv.h create mode 100644 src/uqm/galaxy.c create mode 100644 src/uqm/gameev.c create mode 100644 src/uqm/gameev.h create mode 100644 src/uqm/gameinp.c create mode 100644 src/uqm/gameopt.c create mode 100644 src/uqm/gameopt.h create mode 100644 src/uqm/gamestr.h create mode 100644 src/uqm/gendef.c create mode 100644 src/uqm/gendef.h create mode 100644 src/uqm/getchar.c create mode 100644 src/uqm/globdata.c create mode 100644 src/uqm/globdata.h create mode 100644 src/uqm/gravity.c create mode 100644 src/uqm/grpinfo.c create mode 100644 src/uqm/grpinfo.h create mode 100644 src/uqm/grpintrn.h create mode 100644 src/uqm/hyper.c create mode 100644 src/uqm/hyper.h create mode 100644 src/uqm/ifontres.h create mode 100644 src/uqm/igfxres.h create mode 100644 src/uqm/ikey_con.h create mode 100644 src/uqm/imusicre.h create mode 100644 src/uqm/init.c create mode 100644 src/uqm/init.h create mode 100644 src/uqm/intel.c create mode 100644 src/uqm/intel.h create mode 100644 src/uqm/intro.c create mode 100644 src/uqm/ipdisp.c create mode 100644 src/uqm/ipdisp.h create mode 100644 src/uqm/isndres.h create mode 100644 src/uqm/istrtab.h create mode 100644 src/uqm/load.c create mode 100644 src/uqm/load_legacy.c create mode 100644 src/uqm/loadship.c create mode 100644 src/uqm/master.c create mode 100644 src/uqm/master.h create mode 100644 src/uqm/menu.c create mode 100644 src/uqm/menustat.h create mode 100644 src/uqm/misc.c create mode 100644 src/uqm/nameref.h create mode 100644 src/uqm/oscill.c create mode 100644 src/uqm/oscill.h create mode 100644 src/uqm/outfit.c create mode 100644 src/uqm/pickship.c create mode 100644 src/uqm/pickship.h create mode 100644 src/uqm/plandata.c create mode 100644 src/uqm/planets/Makeinfo create mode 100644 src/uqm/planets/calc.c create mode 100644 src/uqm/planets/cargo.c create mode 100644 src/uqm/planets/devices.c create mode 100644 src/uqm/planets/elemdata.h create mode 100644 src/uqm/planets/generate.h create mode 100644 src/uqm/planets/generate/Makeinfo create mode 100644 src/uqm/planets/generate/genall.h create mode 100644 src/uqm/planets/generate/genand.c create mode 100644 src/uqm/planets/generate/genburv.c create mode 100644 src/uqm/planets/generate/genchmmr.c create mode 100644 src/uqm/planets/generate/gencol.c create mode 100644 src/uqm/planets/generate/gendefault.c create mode 100644 src/uqm/planets/generate/gendefault.h create mode 100644 src/uqm/planets/generate/gendru.c create mode 100644 src/uqm/planets/generate/genilw.c create mode 100644 src/uqm/planets/generate/genmel.c create mode 100644 src/uqm/planets/generate/genmyc.c create mode 100644 src/uqm/planets/generate/genorz.c create mode 100644 src/uqm/planets/generate/genpet.c create mode 100644 src/uqm/planets/generate/genpku.c create mode 100644 src/uqm/planets/generate/genrain.c create mode 100644 src/uqm/planets/generate/gensam.c create mode 100644 src/uqm/planets/generate/genshof.c create mode 100644 src/uqm/planets/generate/gensly.c create mode 100644 src/uqm/planets/generate/gensol.c create mode 100644 src/uqm/planets/generate/genspa.c create mode 100644 src/uqm/planets/generate/gensup.c create mode 100644 src/uqm/planets/generate/gensyr.c create mode 100644 src/uqm/planets/generate/genthrad.c create mode 100644 src/uqm/planets/generate/gentrap.c create mode 100644 src/uqm/planets/generate/genutw.c create mode 100644 src/uqm/planets/generate/genvault.c create mode 100644 src/uqm/planets/generate/genvux.c create mode 100644 src/uqm/planets/generate/genwreck.c create mode 100644 src/uqm/planets/generate/genyeh.c create mode 100644 src/uqm/planets/generate/genzfpscout.c create mode 100644 src/uqm/planets/generate/genzoq.c create mode 100644 src/uqm/planets/gentopo.c create mode 100644 src/uqm/planets/lander.c create mode 100644 src/uqm/planets/lander.h create mode 100644 src/uqm/planets/lifeform.h create mode 100644 src/uqm/planets/orbits.c create mode 100644 src/uqm/planets/oval.c create mode 100644 src/uqm/planets/pl_stuff.c create mode 100644 src/uqm/planets/plandata.h create mode 100644 src/uqm/planets/planets.c create mode 100644 src/uqm/planets/planets.h create mode 100644 src/uqm/planets/plangen.c create mode 100644 src/uqm/planets/pstarmap.c create mode 100644 src/uqm/planets/report.c create mode 100644 src/uqm/planets/roster.c create mode 100644 src/uqm/planets/scan.c create mode 100644 src/uqm/planets/scan.h create mode 100644 src/uqm/planets/solarsys.c create mode 100644 src/uqm/planets/solarsys.h create mode 100644 src/uqm/planets/sundata.h create mode 100644 src/uqm/planets/surface.c create mode 100644 src/uqm/process.c create mode 100644 src/uqm/process.h create mode 100644 src/uqm/races.h create mode 100644 src/uqm/resinst.h create mode 100644 src/uqm/restart.c create mode 100644 src/uqm/restart.h create mode 100644 src/uqm/save.c create mode 100644 src/uqm/save.h create mode 100644 src/uqm/settings.c create mode 100644 src/uqm/settings.h create mode 100644 src/uqm/setup.c create mode 100644 src/uqm/setup.h create mode 100644 src/uqm/setupmenu.c create mode 100644 src/uqm/setupmenu.h create mode 100644 src/uqm/ship.c create mode 100644 src/uqm/ship.h create mode 100644 src/uqm/shipcont.h create mode 100644 src/uqm/ships/Makeinfo create mode 100644 src/uqm/ships/androsyn/Makeinfo create mode 100644 src/uqm/ships/androsyn/androsyn.c create mode 100644 src/uqm/ships/androsyn/androsyn.h create mode 100644 src/uqm/ships/androsyn/icode.h create mode 100644 src/uqm/ships/androsyn/resinst.h create mode 100644 src/uqm/ships/arilou/Makeinfo create mode 100644 src/uqm/ships/arilou/arilou.c create mode 100644 src/uqm/ships/arilou/arilou.h create mode 100644 src/uqm/ships/arilou/icode.h create mode 100644 src/uqm/ships/arilou/resinst.h create mode 100644 src/uqm/ships/blackurq/Makeinfo create mode 100644 src/uqm/ships/blackurq/blackurq.c create mode 100644 src/uqm/ships/blackurq/blackurq.h create mode 100644 src/uqm/ships/blackurq/icode.h create mode 100644 src/uqm/ships/blackurq/resinst.h create mode 100644 src/uqm/ships/chenjesu/Makeinfo create mode 100644 src/uqm/ships/chenjesu/chenjesu.c create mode 100644 src/uqm/ships/chenjesu/chenjesu.h create mode 100644 src/uqm/ships/chenjesu/icode.h create mode 100644 src/uqm/ships/chenjesu/resinst.h create mode 100644 src/uqm/ships/chmmr/Makeinfo create mode 100644 src/uqm/ships/chmmr/chmmr.c create mode 100644 src/uqm/ships/chmmr/chmmr.h create mode 100644 src/uqm/ships/chmmr/icode.h create mode 100644 src/uqm/ships/chmmr/resinst.h create mode 100644 src/uqm/ships/druuge/Makeinfo create mode 100644 src/uqm/ships/druuge/druuge.c create mode 100644 src/uqm/ships/druuge/druuge.h create mode 100644 src/uqm/ships/druuge/icode.h create mode 100644 src/uqm/ships/druuge/resinst.h create mode 100644 src/uqm/ships/human/Makeinfo create mode 100644 src/uqm/ships/human/human.c create mode 100644 src/uqm/ships/human/human.h create mode 100644 src/uqm/ships/human/icode.h create mode 100644 src/uqm/ships/human/resinst.h create mode 100644 src/uqm/ships/ilwrath/Makeinfo create mode 100644 src/uqm/ships/ilwrath/icode.h create mode 100644 src/uqm/ships/ilwrath/ilwrath.c create mode 100644 src/uqm/ships/ilwrath/ilwrath.h create mode 100644 src/uqm/ships/ilwrath/resinst.h create mode 100644 src/uqm/ships/lastbat/Makeinfo create mode 100644 src/uqm/ships/lastbat/icode.h create mode 100644 src/uqm/ships/lastbat/lastbat.c create mode 100644 src/uqm/ships/lastbat/lastbat.h create mode 100644 src/uqm/ships/lastbat/resinst.h create mode 100644 src/uqm/ships/melnorme/Makeinfo create mode 100644 src/uqm/ships/melnorme/icode.h create mode 100644 src/uqm/ships/melnorme/melnorme.c create mode 100644 src/uqm/ships/melnorme/melnorme.h create mode 100644 src/uqm/ships/melnorme/resinst.h create mode 100644 src/uqm/ships/mmrnmhrm/Makeinfo create mode 100644 src/uqm/ships/mmrnmhrm/icode.h create mode 100644 src/uqm/ships/mmrnmhrm/mmrnmhrm.c create mode 100644 src/uqm/ships/mmrnmhrm/mmrnmhrm.h create mode 100644 src/uqm/ships/mmrnmhrm/resinst.h create mode 100644 src/uqm/ships/mycon/Makeinfo create mode 100644 src/uqm/ships/mycon/icode.h create mode 100644 src/uqm/ships/mycon/mycon.c create mode 100644 src/uqm/ships/mycon/mycon.h create mode 100644 src/uqm/ships/mycon/resinst.h create mode 100644 src/uqm/ships/orz/Makeinfo create mode 100644 src/uqm/ships/orz/icode.h create mode 100644 src/uqm/ships/orz/orz.c create mode 100644 src/uqm/ships/orz/orz.h create mode 100644 src/uqm/ships/orz/resinst.h create mode 100644 src/uqm/ships/pkunk/Makeinfo create mode 100644 src/uqm/ships/pkunk/icode.h create mode 100644 src/uqm/ships/pkunk/pkunk.c create mode 100644 src/uqm/ships/pkunk/pkunk.h create mode 100644 src/uqm/ships/pkunk/resinst.h create mode 100644 src/uqm/ships/probe/Makeinfo create mode 100644 src/uqm/ships/probe/icode.h create mode 100644 src/uqm/ships/probe/probe.c create mode 100644 src/uqm/ships/probe/probe.h create mode 100644 src/uqm/ships/probe/resinst.h create mode 100644 src/uqm/ships/ship.h create mode 100644 src/uqm/ships/shofixti/Makeinfo create mode 100644 src/uqm/ships/shofixti/icode.h create mode 100644 src/uqm/ships/shofixti/resinst.h create mode 100644 src/uqm/ships/shofixti/shofixti.c create mode 100644 src/uqm/ships/shofixti/shofixti.h create mode 100644 src/uqm/ships/sis_ship/Makeinfo create mode 100644 src/uqm/ships/sis_ship/icode.h create mode 100644 src/uqm/ships/sis_ship/resinst.h create mode 100644 src/uqm/ships/sis_ship/sis_ship.c create mode 100644 src/uqm/ships/sis_ship/sis_ship.h create mode 100644 src/uqm/ships/slylandr/Makeinfo create mode 100644 src/uqm/ships/slylandr/icode.h create mode 100644 src/uqm/ships/slylandr/resinst.h create mode 100644 src/uqm/ships/slylandr/slylandr.c create mode 100644 src/uqm/ships/slylandr/slylandr.h create mode 100644 src/uqm/ships/spathi/Makeinfo create mode 100644 src/uqm/ships/spathi/icode.h create mode 100644 src/uqm/ships/spathi/resinst.h create mode 100644 src/uqm/ships/spathi/spathi.c create mode 100644 src/uqm/ships/spathi/spathi.h create mode 100644 src/uqm/ships/supox/Makeinfo create mode 100644 src/uqm/ships/supox/icode.h create mode 100644 src/uqm/ships/supox/resinst.h create mode 100644 src/uqm/ships/supox/supox.c create mode 100644 src/uqm/ships/supox/supox.h create mode 100644 src/uqm/ships/syreen/Makeinfo create mode 100644 src/uqm/ships/syreen/icode.h create mode 100644 src/uqm/ships/syreen/resinst.h create mode 100644 src/uqm/ships/syreen/syreen.c create mode 100644 src/uqm/ships/syreen/syreen.h create mode 100644 src/uqm/ships/thradd/Makeinfo create mode 100644 src/uqm/ships/thradd/icode.h create mode 100644 src/uqm/ships/thradd/resinst.h create mode 100644 src/uqm/ships/thradd/thradd.c create mode 100644 src/uqm/ships/thradd/thradd.h create mode 100644 src/uqm/ships/umgah/Makeinfo create mode 100644 src/uqm/ships/umgah/icode.h create mode 100644 src/uqm/ships/umgah/resinst.h create mode 100644 src/uqm/ships/umgah/umgah.c create mode 100644 src/uqm/ships/umgah/umgah.h create mode 100644 src/uqm/ships/urquan/Makeinfo create mode 100644 src/uqm/ships/urquan/icode.h create mode 100644 src/uqm/ships/urquan/resinst.h create mode 100644 src/uqm/ships/urquan/urquan.c create mode 100644 src/uqm/ships/urquan/urquan.h create mode 100644 src/uqm/ships/utwig/Makeinfo create mode 100644 src/uqm/ships/utwig/icode.h create mode 100644 src/uqm/ships/utwig/resinst.h create mode 100644 src/uqm/ships/utwig/utwig.c create mode 100644 src/uqm/ships/utwig/utwig.h create mode 100644 src/uqm/ships/vux/Makeinfo create mode 100644 src/uqm/ships/vux/icode.h create mode 100644 src/uqm/ships/vux/resinst.h create mode 100644 src/uqm/ships/vux/vux.c create mode 100644 src/uqm/ships/vux/vux.h create mode 100644 src/uqm/ships/yehat/Makeinfo create mode 100644 src/uqm/ships/yehat/icode.h create mode 100644 src/uqm/ships/yehat/resinst.h create mode 100644 src/uqm/ships/yehat/yehat.c create mode 100644 src/uqm/ships/yehat/yehat.h create mode 100644 src/uqm/ships/zoqfot/Makeinfo create mode 100644 src/uqm/ships/zoqfot/icode.h create mode 100644 src/uqm/ships/zoqfot/resinst.h create mode 100644 src/uqm/ships/zoqfot/zoqfot.c create mode 100644 src/uqm/ships/zoqfot/zoqfot.h create mode 100644 src/uqm/shipstat.c create mode 100644 src/uqm/shipyard.c create mode 100644 src/uqm/sis.c create mode 100644 src/uqm/sis.h create mode 100644 src/uqm/sounds.c create mode 100644 src/uqm/sounds.h create mode 100644 src/uqm/starbase.c create mode 100644 src/uqm/starbase.h create mode 100644 src/uqm/starcon.c create mode 100644 src/uqm/starcon.h create mode 100644 src/uqm/starmap.c create mode 100644 src/uqm/starmap.h create mode 100644 src/uqm/state.c create mode 100644 src/uqm/state.h create mode 100644 src/uqm/status.c create mode 100644 src/uqm/status.h create mode 100644 src/uqm/supermelee/Makeinfo create mode 100644 src/uqm/supermelee/buildpick.c create mode 100644 src/uqm/supermelee/buildpick.h create mode 100644 src/uqm/supermelee/loadmele.c create mode 100644 src/uqm/supermelee/loadmele.h create mode 100644 src/uqm/supermelee/melee.c create mode 100644 src/uqm/supermelee/melee.h create mode 100644 src/uqm/supermelee/meleesetup.c create mode 100644 src/uqm/supermelee/meleesetup.h create mode 100644 src/uqm/supermelee/meleeship.h create mode 100644 src/uqm/supermelee/netplay/FILES create mode 100644 src/uqm/supermelee/netplay/Makeinfo create mode 100644 src/uqm/supermelee/netplay/checkbuf.c create mode 100644 src/uqm/supermelee/netplay/checkbuf.h create mode 100644 src/uqm/supermelee/netplay/checksum.c create mode 100644 src/uqm/supermelee/netplay/checksum.h create mode 100644 src/uqm/supermelee/netplay/crc.c create mode 100644 src/uqm/supermelee/netplay/crc.h create mode 100644 src/uqm/supermelee/netplay/nc_connect.ci create mode 100644 src/uqm/supermelee/netplay/netconnection.c create mode 100644 src/uqm/supermelee/netplay/netconnection.h create mode 100644 src/uqm/supermelee/netplay/netinput.c create mode 100644 src/uqm/supermelee/netplay/netinput.h create mode 100644 src/uqm/supermelee/netplay/netmelee.c create mode 100644 src/uqm/supermelee/netplay/netmelee.h create mode 100644 src/uqm/supermelee/netplay/netmisc.c create mode 100644 src/uqm/supermelee/netplay/netmisc.h create mode 100644 src/uqm/supermelee/netplay/netoptions.c create mode 100644 src/uqm/supermelee/netplay/netoptions.h create mode 100644 src/uqm/supermelee/netplay/netplay.h create mode 100644 src/uqm/supermelee/netplay/netrcv.c create mode 100644 src/uqm/supermelee/netplay/netrcv.h create mode 100644 src/uqm/supermelee/netplay/netsend.c create mode 100644 src/uqm/supermelee/netplay/netsend.h create mode 100644 src/uqm/supermelee/netplay/netstate.c create mode 100644 src/uqm/supermelee/netplay/netstate.h create mode 100644 src/uqm/supermelee/netplay/notify.c create mode 100644 src/uqm/supermelee/netplay/notify.h create mode 100644 src/uqm/supermelee/netplay/notifyall.c create mode 100644 src/uqm/supermelee/netplay/notifyall.h create mode 100644 src/uqm/supermelee/netplay/packet.c create mode 100644 src/uqm/supermelee/netplay/packet.h create mode 100644 src/uqm/supermelee/netplay/packethandlers.c create mode 100644 src/uqm/supermelee/netplay/packethandlers.h create mode 100644 src/uqm/supermelee/netplay/packetq.c create mode 100644 src/uqm/supermelee/netplay/packetq.h create mode 100644 src/uqm/supermelee/netplay/packetsenders.c create mode 100644 src/uqm/supermelee/netplay/packetsenders.h create mode 100644 src/uqm/supermelee/netplay/proto/Makeinfo create mode 100644 src/uqm/supermelee/netplay/proto/npconfirm.c create mode 100644 src/uqm/supermelee/netplay/proto/npconfirm.h create mode 100644 src/uqm/supermelee/netplay/proto/ready.c create mode 100644 src/uqm/supermelee/netplay/proto/ready.h create mode 100644 src/uqm/supermelee/netplay/proto/reset.c create mode 100644 src/uqm/supermelee/netplay/proto/reset.h create mode 100644 src/uqm/supermelee/pickmele.c create mode 100644 src/uqm/supermelee/pickmele.h create mode 100644 src/uqm/tactrans.c create mode 100644 src/uqm/tactrans.h create mode 100644 src/uqm/trans.c create mode 100644 src/uqm/units.h create mode 100644 src/uqm/uqmdebug.c create mode 100644 src/uqm/uqmdebug.h create mode 100644 src/uqm/util.c create mode 100644 src/uqm/util.h create mode 100644 src/uqm/velocity.c create mode 100644 src/uqm/velocity.h create mode 100644 src/uqm/weapon.c create mode 100644 src/uqm/weapon.h create mode 100644 src/uqmversion.h (limited to 'src') diff --git a/src/Makeinfo b/src/Makeinfo new file mode 100644 index 0000000..80a2dd2 --- /dev/null +++ b/src/Makeinfo @@ -0,0 +1,21 @@ +uqm_SUBDIRS="libs res uqm" +uqm_CFILES="options.c port.c uqm.c" +uqm_HFILES="config.h endian_uqm.h options.h port.h types.h uqmversion.h" + +if [ "$uqm_HAVE_GETOPT_LONG" = 0 ]; then + uqm_SUBDIRS="$uqm_SUBDIRS getopt" +fi + +case "$HOST_SYSTEM" in + Darwin) + uqm_SUBDIRS="$uqm_SUBDIRS darwin" + ;; + MSVC) + uqm_HFILES="$uqm_HFILES config_vc6.h" + ;; +esac + +if [ "$uqm_HAVE_REGEX" = 0 ]; then + uqm_SUBDIRS="$uqm_SUBDIRS regex" +fi + diff --git a/src/abxadec/Makefile b/src/abxadec/Makefile new file mode 100644 index 0000000..35b9955 --- /dev/null +++ b/src/abxadec/Makefile @@ -0,0 +1,9 @@ +uqm_CFLAGS=-W -Wall -g -O0 -fPIC +uqm_INCLUDE=-I .. -I ../sc2code -I ../sc2code/libs +uqm_LDFLAGS=-shared + +abxadec.so: abxaud.c abxaud.h + gcc $(uqm_CFLAGS) $(uqm_INCLUDE) $(uqm_LDFLAGS) abxaud.c -o abxadec.so + +clean: + rm abxadec.so diff --git a/src/abxadec/abxaud.c b/src/abxadec/abxaud.c new file mode 100644 index 0000000..a943f16 --- /dev/null +++ b/src/abxadec/abxaud.c @@ -0,0 +1,638 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* CDP module sample + * .abx speech track decoder + */ + +/* + * Derived from decoder by Serge van den Boom (svdb@stack.nl), + * The actual conversion code (somewhat moded) is from Toys for Bob. + * So far, it ignores sample rates, so it will work ok as long as all + * the frames have the same frequency. This is probably + * enough for our purposes. + */ + +#include +#include +#include +//#include "port.h" +#include "types.h" +#include "port.h" +#include "libs/cdp/cdp_imem.h" +#include "libs/cdp/cdp_iio.h" +#include "libs/cdp/cdp_isnd.h" +#include "libs/cdp/cdpmod.h" +#include "abxaud.h" +#include "endian_uqm.h" + +#define DATA_BUF_SIZE 0x8000 +#define DUCK_GENERAL_FPS 14.622f + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* abxa_GetName (void); +static bool abxa_InitModule (int flags, const TFB_DecoderFormats*); +static void abxa_TermModule (); +static uint32 abxa_GetStructSize (void); +static int abxa_GetError (THIS_PTR); +static bool abxa_Init (THIS_PTR); +static void abxa_Term (THIS_PTR); +static bool abxa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void abxa_Close (THIS_PTR); +static int abxa_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 abxa_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 abxa_GetFrame (THIS_PTR); + +static TFB_SoundDecoderFuncs abxa_DecoderVtbl = +{ + abxa_GetName, + abxa_InitModule, + abxa_TermModule, + abxa_GetStructSize, + abxa_GetError, + abxa_Init, + abxa_Term, + abxa_Open, + abxa_Close, + abxa_Decode, + abxa_Seek, + abxa_GetFrame, +}; + +static bool abxa_module_init (cdp_Module* module, cdp_Itf_Host* hostitf); +static void abxa_module_term (); + +// The one and only "cdpmodinfo" symbol +// on win32, it does not have to be named like so +// the exported name can be overridden with .def file +cdp_ModuleInfo CDPEXPORT CDP_INFO_SYM = +{ + sizeof (cdp_ModuleInfo), // size of struct for version control + CDPAPI_VERSION, // API version we are using + 1, 0, 2, // our module version + 0, 3, 0, // host version required, purely informational + CDP_MODINFO_RESERVED1, // reserved + "UQM", // CDP context name (we can use UQM) + "Abx Decoder", // CDP mod name + "1.0", // CDP mod version + "Alex Volkov", // CDP mod author + "http://sc2.sf.net", // CDP mod URL (do not have any yet) + "Sample CDP-based decoder", // CDP mod comment + CDP_MODINFO_RESERVED2, // reserved + abxa_module_init, // init entrypoint + abxa_module_term, // term entrypoint +}; + +typedef struct +{ + uint16 num_frames; // total number of frame + uint32 tot_size; // total size of decoded stream + uint16 frame_samps; // samples per frame + uint16 freq; // general sampling frequency + +} abxa_Header; + +typedef struct +{ + uint32 ofs; // file offset of frame + uint16 fsize; // compressed file size + uint16 usize; // uncompressed file size + +} abxa_FrameInfo; + +#define SQLCH 0x40 // Squelch byte flag +#define RESYNC 0x80 // Resync byte flag. + +#define DELTAMOD 0x30 // Delta modulation bits. + +#define ONEBIT 0x10 // One bit delta modulate +#define TWOBIT 0x20 // Two bit delta modulate +#define FOURBIT 0x30 // four bit delta modulate + +#define MULTIPLIER 0x0F // Bottom nibble contains multiplier value. +#define SQUELCHCNT 0x3F // Bits for squelching. + +typedef struct +{ + uint16 usize; + uint16 freq; + uint8 frame_size; + uint8 sqelch; + uint16 max_error; + +} abxa_FrameHeader; + +typedef struct +{ + // always the first member + TFB_SoundDecoder decoder; + + // public read-only + uint32 iframe; // current frame index + uint32 cframes; // total count of frames + uint32 channels; // number of channels + uint32 pcm_frame; // samples per frame + + // private + sint32 last_error; + uio_Stream* abx; + abxa_FrameInfo* frames; + // buffer + void* data; + uint32 maxdata; + uint32 cbdata; + uint32 dataofs; + +} abxa_SoundDecoder; + +// interfaces our decoder needs +cdp_ItfDef game_itfs[] = +{ + {CDPITF_KIND_MEMORY}, + {CDPITF_KIND_IO}, + {CDPITF_KIND_SOUND}, + + {CDPITF_KIND_INVALID} // term +}; + +static cdp_Module* abxa_mod = NULL; // our module handle +static cdp_Itf_Host* abxa_ihost = NULL; // HOST interface ptr +static cdp_Itf_Memory* abxa_imem = NULL; // MEMORY interface ptr +static cdp_Itf_Io* abxa_iio = NULL; // IO interface ptr +static cdp_Itf_Sound* abxa_isnd = NULL; // SOUND interface ptr +static const TFB_DecoderFormats* abxa_formats = NULL; +static TFB_RegSoundDecoder* abxa_regdec = NULL; // registered decoder + +static bool +abxa_module_init (cdp_Module* module, cdp_Itf_Host* hostitf) +{ + abxa_mod = module; + abxa_ihost = hostitf; + + if (!hostitf->GetItfs (game_itfs)) + return false; + + abxa_imem = game_itfs[0].itf; + abxa_iio = game_itfs[1].itf; + abxa_isnd = game_itfs[2].itf; + + abxa_regdec = abxa_isnd->RegisterDecoder ("abx", &abxa_DecoderVtbl); + if (!abxa_regdec) + { + fprintf (stderr, "abxa_module_init(): " + "Could not register audio decoder\n"); + return false; + } + + return true; +} + +static void +abxa_module_term () +{ + if (abxa_regdec) + abxa_isnd->UnregisterDecoder (abxa_regdec); + + // do nothing loop for 1 trillion iterations +} + +static sint32 +abxa_readHeader (abxa_SoundDecoder* abxa, abxa_Header* hdr) +{ + if (1 != abxa_iio->fread (&hdr->num_frames, sizeof (hdr->num_frames), 1, abxa->abx) || + 1 != abxa_iio->fread (&hdr->tot_size, sizeof (hdr->tot_size), 1, abxa->abx) || + 1 != abxa_iio->fread (&hdr->frame_samps, sizeof (hdr->frame_samps), 1, abxa->abx) || + 1 != abxa_iio->fread (&hdr->freq, sizeof (hdr->freq), 1, abxa->abx)) + { + abxa->last_error = errno; + return abxa_ErrBadFile; + } + // byte swap when necessary + hdr->num_frames = UQM_SwapLE16 (hdr->num_frames); + hdr->tot_size = UQM_SwapLE32 (hdr->tot_size); + hdr->frame_samps = UQM_SwapLE16 (hdr->frame_samps); + hdr->freq = UQM_SwapLE16 (hdr->freq); + + return 0; +} + +static signed char abxa_trans[16 * 16] = +{ + -8,-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7,8, // Multiplier of 1 + -16,-14,-12,-10,-8,-6,-4,-2,2,4,6,8,10,12,14,16, // Multiplier of 2 + -24,-21,-18,-15,-12,-9,-6,-3,3,6,9,12,15,18,21,24, // Multiplier of 3 + -32,-28,-24,-20,-16,-12,-8,-4,4,8,12,16,20,24,28,32, // Multiplier of 4 + -40,-35,-30,-25,-20,-15,-10,-5,5,10,15,20,25,30,35,40, // Multiplier of 5 + -48,-42,-36,-30,-24,-18,-12,-6,6,12,18,24,30,36,42,48, // Multiplier of 6 + -56,-49,-42,-35,-28,-21,-14,-7,7,14,21,28,35,42,49,56, // Multiplier of 7 + -64,-56,-48,-40,-32,-24,-16,-8,8,16,24,32,40,48,56,64, // Multiplier of 8 + -72,-63,-54,-45,-36,-27,-18,-9,9,18,27,36,45,54,63,72, // Multiplier of 9 + -80,-70,-60,-50,-40,-30,-20,-10,10,20,30,40,50,60,70,80, // Multiplier of 10 + -88,-77,-66,-55,-44,-33,-22,-11,11,22,33,44,55,66,77,88, // Multiplier of 11 + -96,-84,-72,-60,-48,-36,-24,-12,12,24,36,48,60,72,84,96, // Multiplier of 12 + -104,-91,-78,-65,-52,-39,-26,-13,13,26,39,52,65,78,91,104, // Multiplier of 13 + -112,-98,-84,-70,-56,-42,-28,-14,14,28,42,56,70,84,98,112, // Multiplier of 14 + -120,-105,-90,-75,-60,-45,-30,-15,15,30,45,60,75,90,105,120,// Multiplier of 15 + -128,-112,-96,-80,-64,-48,-32,-16,16,32,48,64,80,96,112,127,// Multiplier of 16 +}; + +static sint32 +abxa_decodeFrame (abxa_SoundDecoder* abxa, abxa_FrameHeader* hdr, + uint8* input, uint32 inputsize) +{ + uint8* inend; + uint8* output; + uint8* outptr; + sint16 prev; + sint32 outputsize; + + output = outptr = abxa->data; + inend = input + inputsize; + prev = *input++; // Get initial previous data point. + *output++ = prev; + + while (input < inend) + { + uint16 bytes; + uint8 sample; + + sample = *input++; // Get sample. + if (sample & RESYNC) // Is it a resync byte? + { + //--slen; // Decrement output sample length. + + prev = (sample & 0x7F) << 1; // Store resync byte. + *output++ = prev; + } + else if (sample & SQLCH) // Is it a squelch byte? + { + bytes = sample & SQUELCHCNT; // And off the number of squelch bytes + // ?? the following makes no sense, should be --slen; + //slen -= bytes; // Decrement total samples remaining count. + + memset (output, prev, bytes); + output += bytes; + } + else // Must be a delta modulate byte!! + { + sint8 *base; + + //slen -= hdr->frame_size; // Pulling one frame out. + // Compute base address to multiplier table. + base = abxa_trans + (sample & MULTIPLIER) * 16; + switch (sample & DELTAMOD) // Delta mod resolution. + { + case ONEBIT: + { + sint16 up; + + up = base[8]; // Go up 1 bit. + for (bytes = hdr->frame_size / 8; bytes; bytes--) + { + uint8 mask; + + sample = *input++; + for (mask = 0x80; mask; mask >>= 1) + { + if ( sample & mask ) + prev += up; + else + prev -= up; + if ( prev < 0 ) prev = 0; + else if ( prev > 255 ) prev = 255; + *output++ = prev; + } + } + break; + } + case TWOBIT: + base += 6; // Base address of two bit delta's. + for (bytes = hdr->frame_size / 4; bytes; bytes--) + { + sample = *input++; + + prev += base[sample>>6]; + if ( prev < 0 ) prev = 0; + else if ( prev > 255 ) prev = 255; + *output++ = prev; + + prev += base[(sample>>4)&0x3]; + if ( prev < 0 ) prev = 0; + else if ( prev > 255 ) prev = 255; + *output++ = prev; + + prev += base[(sample>>2)&0x3]; + if ( prev < 0 ) prev = 0; + else if ( prev > 255 ) prev = 255; + *output++ = prev; + + prev += base[sample&0x3]; + if ( prev < 0 ) prev = 0; + else if ( prev > 255 ) prev = 255; + *output++ = prev; + } + break; + case FOURBIT: + for (bytes = hdr->frame_size / 2; bytes; bytes--) + { + sample = *input++; + + prev += base[sample>>4]; + if ( prev < 0 ) prev = 0; + else if ( prev > 255 ) prev = 255; + *output++ = prev; + + prev += base[sample&0x0F]; + if ( prev < 0 ) prev = 0; + else if ( prev > 255 ) prev = 255; + *output++ = prev; + } + break; + } + } + // While still audio data to decompress.... + } + + outputsize = output - outptr; + abxa->cbdata += outputsize; + + return outputsize; +} + + +static sint32 +abxa_readNextFrame (abxa_SoundDecoder* abxa) +{ + abxa_FrameHeader* hdr; + uint8* p; + uint32 fsize; + sint32 ret; + + fsize = abxa->frames[abxa->iframe].fsize; + abxa_iio->fseek (abxa->abx, abxa->frames[abxa->iframe].ofs, SEEK_SET); + // dump encoded data at the end of the buffer aligned on 8-byte + p = ((uint8*)abxa->data + abxa->maxdata - ((fsize + 7) & (-8))); + if (abxa_iio->fread (p, 1, fsize, abxa->abx) != fsize) + { + abxa->last_error = errno; + return abxa_ErrBadFile; + } + hdr = (abxa_FrameHeader*) p; + p += sizeof (abxa_FrameHeader); + fsize -= sizeof (abxa_FrameHeader); + + hdr->usize = UQM_SwapLE16 (hdr->usize); + hdr->freq = UQM_SwapLE16 (hdr->freq); + hdr->max_error = UQM_SwapLE16 (hdr->max_error); + + if (hdr->freq == 0) + { + hdr->freq = abxa->decoder.frequency; + } + else if (hdr->freq != abxa->decoder.frequency) + { + fprintf (stderr, "abxa_readNextFrame(): " + "WARNING: Frame frequency (%u) != global frequency (%u) " + "for frame %u\n", + hdr->freq, abxa->decoder.frequency, abxa->iframe); + //abxa->decoder.frequency = hdr->freq; + } + + abxa->iframe++; + + ret = abxa_decodeFrame (abxa, hdr, p, fsize); + if (ret > 0 && ret != hdr->usize) + { + fprintf (stderr, "abxa_readNextFrame(): " + "WARNING: decompressed frame size (%d) != specified size (%u) " + "for frame %u\n", + ret, hdr->usize, abxa->iframe); + } + + return ret; +} + +static sint32 +abxa_stuffBuffer (abxa_SoundDecoder* abxa, void* buf, sint32 bufsize) +{ + sint32 dataleft; + + dataleft = abxa->cbdata - abxa->dataofs; + if (dataleft > 0) + { + if (dataleft > bufsize) + dataleft = bufsize & (-4); + memcpy (buf, (uint8*)abxa->data + abxa->dataofs, dataleft); + abxa->dataofs += dataleft; + } + + if (abxa->cbdata > 0 && abxa->dataofs >= abxa->cbdata) + abxa->cbdata = abxa->dataofs = 0; // reset for new data + + return dataleft; +} + + +static const char* +abxa_GetName (void) +{ + return "Abx"; +} + +static bool +abxa_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + abxa_formats = fmts; + return true; + + (void)flags; // laugh at compiler warning +} + +static void +abxa_TermModule () +{ + // no specific module term +} + +static uint32 +abxa_GetStructSize (void) +{ + return sizeof (abxa_SoundDecoder); +} + +static int +abxa_GetError (THIS_PTR) +{ + abxa_SoundDecoder* abxa = (abxa_SoundDecoder*) This; + int ret = abxa->last_error; + abxa->last_error = abxa_ErrNone; + return ret; +} + +static bool +abxa_Init (THIS_PTR) +{ + //abxa_SoundDecoder* abxa = (abxa_SoundDecoder*) This; + /* enable if 16bit PCM abx exist + This->need_swap = + abxa_formats->big_endian != abxa_formats->want_big_endian; + */ + This->need_swap = false; + return true; +} + +static void +abxa_Term (THIS_PTR) +{ + //abxa_SoundDecoder* abxa = (abxa_SoundDecoder*) This; + abxa_Close (This); // ensure cleanup +} + +static bool +abxa_Open (THIS_PTR, uio_DirHandle *dir, const char *file) +{ + abxa_SoundDecoder* abxa = (abxa_SoundDecoder*) This; + abxa_Header hdr; + abxa_FrameInfo* info; + size_t cread; + uint32 i; + + abxa->abx = abxa_iio->fopen (dir, file, "rb"); + if (!abxa->abx) + { + abxa->last_error = errno; + return false; + } + if (abxa_readHeader (abxa, &hdr)) + { + abxa_Close (This); + return false; + } + + abxa->cframes = hdr.num_frames; + + abxa->frames = abxa_imem->malloc (abxa->cframes * sizeof (abxa_FrameInfo)); + cread = abxa_iio->fread (abxa->frames, sizeof (abxa_FrameInfo), + abxa->cframes, abxa->abx); + if (cread != abxa->cframes) + { + abxa->last_error = abxa_ErrBadFile; + abxa_Close (This); + return false; + } + + // byte swap when necessary + for (i = 0, info = abxa->frames; i < abxa->cframes; ++i, ++info) + { + info->ofs = UQM_SwapLE32 (info->ofs); + info->fsize = UQM_SwapLE16 (info->fsize); + info->usize = UQM_SwapLE16 (info->usize); + } + + This->frequency = hdr.freq; + This->format = abxa_formats->mono8; + abxa->channels = 1; + abxa->pcm_frame = hdr.frame_samps; + abxa->data = abxa_imem->malloc (hdr.frame_samps * 2); + abxa->maxdata = hdr.frame_samps * 2; + + // estimate + This->length = (float) hdr.tot_size / hdr.freq; + + abxa->last_error = 0; + + return true; +} + +static void +abxa_Close (THIS_PTR) +{ + abxa_SoundDecoder* abxa = (abxa_SoundDecoder*) This; + + if (abxa->data) + { + abxa_imem->free (abxa->data); + abxa->data = NULL; + } + if (abxa->frames) + { + abxa_imem->free (abxa->frames); + abxa->frames = NULL; + } + if (abxa->abx) + { + abxa_iio->fclose (abxa->abx); + abxa->abx = NULL; + } + abxa->last_error = 0; +} + +static int +abxa_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + abxa_SoundDecoder* abxa = (abxa_SoundDecoder*) This; + sint32 stuffed; + sint32 total = 0; + + if (bufsize <= 0) + return abxa->last_error = abxa_ErrBadArg; + + do + { + stuffed = abxa_stuffBuffer (abxa, buf, bufsize); + ((uint8*)buf) += stuffed; + bufsize -= stuffed; + total += stuffed; + + if (bufsize > 0 && abxa->iframe < abxa->cframes) + { + stuffed = abxa_readNextFrame (abxa); + if (stuffed <= 0) + return stuffed; + } + } while (bufsize > 0 && abxa->iframe < abxa->cframes); + + return total; +} + +static uint32 +abxa_Seek (THIS_PTR, uint32 pcm_pos) +{ + abxa_SoundDecoder* abxa = (abxa_SoundDecoder*) This; + uint32 iframe; + + iframe = pcm_pos / abxa->pcm_frame; + if (iframe < abxa->cframes) + { + abxa->iframe = iframe; + abxa->cbdata = 0; + abxa->dataofs = 0; + } + return abxa->iframe * abxa->pcm_frame; +} + +static uint32 +abxa_GetFrame (THIS_PTR) +{ + abxa_SoundDecoder* abxa = (abxa_SoundDecoder*) This; + + // if there is nothing buffered return the actual current frame + // otherwise return previous + return abxa->dataofs == abxa->cbdata ? + abxa->iframe : abxa->iframe - 1; +} diff --git a/src/abxadec/abxaud.def b/src/abxadec/abxaud.def new file mode 100644 index 0000000..da805a7 --- /dev/null +++ b/src/abxadec/abxaud.def @@ -0,0 +1,16 @@ +;//////////////////////////////////////////////////////////////////// +;// abxaud.def +;// +;// MODULE : abxadec.dll +;// PURPOSE : Declaration of DLL exports +;// +;// This file is a part of Abx Decoder sample CDP module +;// It is only necessary if you do not name your +;// (only) exported symbol "cdpmodinfo" +;// GPL applies. +;// + +LIBRARY "abxadec.dll" + +EXPORTS + cdpmodinfo = abxa_mod_info @1 DATA diff --git a/src/abxadec/abxaud.h b/src/abxadec/abxaud.h new file mode 100644 index 0000000..e72ac07 --- /dev/null +++ b/src/abxadec/abxaud.h @@ -0,0 +1,34 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* CDP module sample + * .abx speech track decoder + */ + +#ifndef ABXADEC_ABXAUD_H_ +#define ABXADEC_ABXAUD_H_ + +typedef enum +{ + // positive values are the same as in errno + abxa_ErrNone = 0, + abxa_ErrUnknown = -1, + abxa_ErrBadFile = -2, + abxa_ErrBadArg = -3, + abxa_ErrOther = -1000, +} abxa_Error; + +#endif // ABXADEC_ABXAUD_H_ diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..f2ef1d8 --- /dev/null +++ b/src/config.h @@ -0,0 +1,21 @@ +/* This file contains some compile-time configuration options. + */ + +#ifdef _MSC_VER + /* In this case, build.sh is not run to generate a config file, so + * we use a default file config_vc6.h instead. + * If you want anything else than the defaults, you'll have to edit + * that file manually. */ +# include "config_vc6.h" +#elif defined(__SYMBIAN32__) +# include "symbian/config.h" +#elif defined (__MINGW32__) || defined (__CYGWIN__) + /* If we're compiling on MS Windows using build.sh, use + * config_win.h, generated from src/config_win.h.in. */ +# include "config_win.h" +#else + /* If we're compiling in unix, use config_unix.h, generated from + * src/config_unix.h.in by build.sh. */ +# include "config_unix.h" +#endif + diff --git a/src/config_unix.h.in b/src/config_unix.h.in new file mode 100644 index 0000000..63d1e0a --- /dev/null +++ b/src/config_unix.h.in @@ -0,0 +1,63 @@ +/* This file contains some compile-time configuration options for *nix + * systems. + * config_unix.h is generated from config_unix.h.in by build.sh + * When building on MS Windows using build.sh (MinGW, Cygwin), + * config_win.h is generated from src/config_win.h.in. + * When using MSVC on MS Windows, you'll have to edit src/config_vc6.h + * manually if you want anything else than the defaults. + */ + +#ifndef CONFIG_UNIX_H_ +#define CONFIG_UNIX_H_ + +/* Directory where the UQM game data is located */ +#define CONTENTDIR "@CONTENTDIR@" + +/* Directory where game data will be stored */ +#define USERDIR "~/.uqm/" + +/* Directory where config files will be stored */ +#define CONFIGDIR USERDIR + +/* Directory where supermelee teams will be stored */ +#define MELEEDIR "${UQM_CONFIG_DIR}/teams/" + +/* Directory where save games will be stored */ +#define SAVEDIR "${UQM_CONFIG_DIR}/save/" + +/* Defined if words are stored with the most significant byte first */ +@WORDS_BIGENDIAN@ + +/* Defined if your system has readdir_r of its own */ +@HAVE_READDIR_R@ + +/* Defined if your system has setenv of its own */ +@HAVE_SETENV@ + +/* Defined if your system has strupr of its own */ +@HAVE_STRUPR@ + +/* Defined if your system has strcasecmp of its own */ +@HAVE_STRCASECMP_UQM@ + // Not using "HAVE_STRCASECMP" as that conflicts with SDL. + +/* Defined if your system has stricmp of its own */ +@HAVE_STRICMP@ + +/* Defined if your system has getopt_long */ +@HAVE_GETOPT_LONG@ + +/* Defined if your system has iswgraph of its own*/ +@HAVE_ISWGRAPH@ + +/* Defined if your system has wchar_t of its own */ +@HAVE_WCHAR_T@ + +/* Defined if your system has wint_t of its own */ +@HAVE_WINT_T@ + +/* Defined if your system has _Bool of its own */ +@HAVE__BOOL@ + +#endif /* CONFIG_UNIX_H_ */ + diff --git a/src/config_vc6.h b/src/config_vc6.h new file mode 100644 index 0000000..d3ed0e3 --- /dev/null +++ b/src/config_vc6.h @@ -0,0 +1,61 @@ +/* This file contains some compile-time configuration options for MS Windows + * systems when building using MSVC. + * Change the values below if you want anything other than the defaults. + * For *nix systems, config_unix.h is used, which is generated by build.sh + * from src/config_unix.h.in. + * When building on MS Windows using build.sh (MinGW, Cygwin), + * config_win.h is generated from src/config_win.h.in. + */ + +#ifndef CONFIG_VC6_H_ +#define CONFIG_VC6_H_ + +/* Directory where the UQM game data is located */ +#define CONTENTDIR "../content/" + +/* Directory where game data will be stored */ +//#define USERDIR "../userdata/" +#define USERDIR "%APPDATA%/uqm/" + +/* Directory where config files will be stored */ +#define CONFIGDIR USERDIR + +/* Directory where supermelee teams will be stored */ +#define MELEEDIR "%UQM_CONFIG_DIR%/teams/" + +/* Directory where save games will be stored */ +#define SAVEDIR "%UQM_CONFIG_DIR%/save/" + +/* Define if words are stored with the most significant byte first */ +#undef WORDS_BIGENDIAN + +/* Defined if your system has readdir_r of its own */ +#undef HAVE_READDIR_R + +/* Defined if your system has setenv of its own */ +#undef HAVE_SETENV + +/* Defined if your system has strupr of its own */ +#define HAVE_STRUPR + +/* Defined if your system has strcasecmp of its own */ +#undef HAVE_STRCASECMP_UQM + // Not using "HAVE_STRCASECMP" as that conflicts with SDL. + +/* Defined if your system has stricmp of its own */ +#define HAVE_STRICMP + +/* Defined if your system has getopt_long */ +#undef HAVE_GETOPT_LONG + +/* Defined if your system has iswgraph of its own*/ +#define HAVE_ISWGRAPH + +/* Defined if your system has wchar_t of its own */ +#define HAVE_WCHAR_T + +/* Defined if your system has wint_t of its own */ +#define HAVE_WINT_T + +#endif /* CONFIG_VC6_H_ */ + diff --git a/src/config_win.h.in b/src/config_win.h.in new file mode 100644 index 0000000..3001f1f --- /dev/null +++ b/src/config_win.h.in @@ -0,0 +1,65 @@ +/* This file contains some compile-time configuration options for MS Windows + * systems when building using build.sh (MinGW, Cygwin). + * config_win.h is generated from src/config_win.h.in by build.sh + * For building using MSVC, you'll have to edit src/config_vc6.h manually + * if you want anything else than the defaults. + * For *nix systems, config_unix.h is used, which is generated by build.sh + * from src/config_unix.h.in. + */ + +#ifndef CONFIG_WIN_H_ +#define CONFIG_WIN_H_ + +/* Directory where the UQM game data is located */ +#define CONTENTDIR "../content/" + +/* Directory where game data will be stored */ +//#define USERDIR "../userdata/" +#define USERDIR "%APPDATA%/uqm/" + +/* Directory where config files will be stored */ +#define CONFIGDIR USERDIR + +/* Directory where supermelee teams will be stored */ +#define MELEEDIR "%UQM_CONFIG_DIR%/teams/" + +/* Directory where save games will be stored */ +#define SAVEDIR "%UQM_CONFIG_DIR%/save/" + +/* Defined if words are stored with the most significant byte first */ +@WORDS_BIGENDIAN@ + +/* Defined if your system has readdir_r of its own */ +@HAVE_READDIR_R@ + +/* Defined if your system has setenv of its own */ +@HAVE_SETENV@ + +/* Defined if your system has strupr of its own */ +@HAVE_STRUPR@ + +/* Defined if your system has strcasecmp of its own */ +@HAVE_STRCASECMP_UQM@ + // Not using "HAVE_STRCASECMP" as that conflicts with SDL. + +/* Defined if your system has stricmp of its own */ +@HAVE_STRICMP@ + +/* Defined if your system has getopt_long */ +@HAVE_GETOPT_LONG@ + +/* Defined if your system has iswgraph of its own*/ +@HAVE_ISWGRAPH@ + +/* Defined if your system has wchar_t of its own */ +@HAVE_WCHAR_T@ + +/* Defined if your system has wint_t of its own */ +@HAVE_WINT_T@ + +/* Defined if your system has _Bool of its own */ +@HAVE__BOOL@ + +#endif /* CONFIG_WIN_H_ */ + + diff --git a/src/darwin/Makeinfo b/src/darwin/Makeinfo new file mode 100644 index 0000000..6b019b2 --- /dev/null +++ b/src/darwin/Makeinfo @@ -0,0 +1,2 @@ +uqm_MFILES="SDLMain.m" +uqm_HFILES="SDLMain.h" diff --git a/src/darwin/SDLMain.h b/src/darwin/SDLMain.h new file mode 100644 index 0000000..c56d90c --- /dev/null +++ b/src/darwin/SDLMain.h @@ -0,0 +1,16 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +#ifndef _SDLMain_h_ +#define _SDLMain_h_ + +#import + +@interface SDLMain : NSObject +@end + +#endif /* _SDLMain_h_ */ diff --git a/src/darwin/SDLMain.m b/src/darwin/SDLMain.m new file mode 100644 index 0000000..4bb038e --- /dev/null +++ b/src/darwin/SDLMain.m @@ -0,0 +1,404 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +#import "port.h" +#import SDL_INCLUDE(SDL.h) + +#if SDL_MAJOR_VERSION == 1 + +#import "SDLMain.h" +#import + /* for PATH_MAX */ +#import + /* for strrchr() */ +#import + +static int gArgc; +static char **gArgv; +static BOOL gFinderLaunch; + +@implementation SDLApplication +/* Invoked from the Quit menu item */ +- (void)terminate:(id)sender +{ + /* Post a SDL_QUIT event */ + SDL_Event event; + event.type = SDL_QUIT; + SDL_PushEvent (&event); + (void) sender; /* Get rid of unused variable warning */ +} + +/* For some reaon, Apple removed setAppleMenu from the headers in 10.4, + but the method still is there and works. To avoid warnings, we declare + it ourselves here. */ +@interface NSApplication(SDL_Missing_Methods) +- (void)setAppleMenu:(NSMenu *)menu; +@end + +/* Use this flag to determine whether we use SDLMain.nib or not */ +#define SDL_USE_NIB_FILE 0 + +/* Use this flag to determine whether we use CPS (docking) or not */ +#define SDL_USE_CPS 1 +#ifdef SDL_USE_CPS +/* Portions of CPS.h */ +typedef struct CPSProcessSerNum +{ + UInt32 lo; + UInt32 hi; +} CPSProcessSerNum; + +extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); +extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); +extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); + +#endif /* SDL_USE_CPS */ + +static int gArgc; +static char **gArgv; +static BOOL gFinderLaunch; +static BOOL gCalledAppMainline = FALSE; + +static NSString *getApplicationName(void) +{ + const NSDictionary *dict; + NSString *appName = 0; + + /* Determine the application name */ + dict = (const NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); + if (dict) + appName = [dict objectForKey: @"CFBundleName"]; + + if (![appName length]) + appName = [[NSProcessInfo processInfo] processName]; + + return appName; +} + +#if SDL_USE_NIB_FILE +/* A helper category for NSString */ +@interface NSString (ReplaceSubString) +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; +@end +#endif + +@interface NSApplication (SDLApplication) +@end + +@implementation NSApplication (SDLApplication) +/* Invoked from the Quit menu item */ +- (void)terminate:(id)sender +{ + /* Post a SDL_QUIT event */ + SDL_Event event; + event.type = SDL_QUIT; + SDL_PushEvent(&event); +} +@end + +/* The main class of the application, the application's delegate */ +@implementation SDLMain + +/* Set the working directory to the .app's parent directory */ +- (void) setupWorkingDirectory:(BOOL)shouldChdir +{ + if (shouldChdir) + { + char parentdir[MAXPATHLEN]; + CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); + if (CFURLGetFileSystemRepresentation(url2, 1, (UInt8 *)parentdir, MAXPATHLEN)) { + chdir(parentdir); /* chdir to the binary app's parent */ + } + CFRelease(url); + CFRelease(url2); + } +} + +#if SDL_USE_NIB_FILE + +/* Fix menu to contain the real app name instead of "SDL App" */ +- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName +{ + NSRange aRange; + NSEnumerator *enumerator; + NSMenuItem *menuItem; + + aRange = [[aMenu title] rangeOfString:@"SDL App"]; + if (aRange.length != 0) + [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; + + enumerator = [[aMenu itemArray] objectEnumerator]; + while ((menuItem = [enumerator nextObject])) + { + aRange = [[menuItem title] rangeOfString:@"SDL App"]; + if (aRange.length != 0) + [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; + if ([menuItem hasSubmenu]) + [self fixMenu:[menuItem submenu] withAppName:appName]; + } +} + +#else + +static void setApplicationMenu(void) +{ + /* warning: this code is very odd */ + NSMenu *appleMenu; + NSMenuItem *menuItem; + NSString *title; + NSString *appName; + + appName = getApplicationName(); + appleMenu = [[NSMenu alloc] initWithTitle:@""]; + + /* Add menu items */ + title = [@"About " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Hide " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; + + menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; + [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; + + [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Quit " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; + + + /* Put menu into the menubar */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:appleMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Tell the application object that this is now the application menu */ + [NSApp setAppleMenu:appleMenu]; + + /* Finally give up our references to the objects */ + [appleMenu release]; + [menuItem release]; +} + +/* Create a window menu */ +static void setupWindowMenu(void) +{ + NSMenu *windowMenu; + NSMenuItem *windowMenuItem; + NSMenuItem *menuItem; + + windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + + /* "Minimize" item */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; + [windowMenu addItem:menuItem]; + [menuItem release]; + + /* Put menu into the menubar */ + windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; + [windowMenuItem setSubmenu:windowMenu]; + [[NSApp mainMenu] addItem:windowMenuItem]; + + /* Tell the application object that this is now the window menu */ + [NSApp setWindowsMenu:windowMenu]; + + /* Finally give up our references to the objects */ + [windowMenu release]; + [windowMenuItem release]; +} + +/* Replacement for NSApplicationMain */ +static void CustomApplicationMain (int argc, char **argv) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + SDLMain *sdlMain; + + /* Ensure the application object is initialised */ + [NSApplication sharedApplication]; + +#ifdef SDL_USE_CPS + { + CPSProcessSerNum PSN; + /* Tell the dock about us */ + if (!CPSGetCurrentProcess(&PSN)) + if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) + if (!CPSSetFrontProcess(&PSN)) + [NSApplication sharedApplication]; + } +#endif /* SDL_USE_CPS */ + + /* Set up the menubar */ + [NSApp setMainMenu:[[NSMenu alloc] init]]; + setApplicationMenu(); + setupWindowMenu(); + + /* Create SDLMain and make it the app delegate */ + sdlMain = [[SDLMain alloc] init]; + [NSApp setDelegate:sdlMain]; + + /* Start the main event loop */ + [NSApp run]; + + [sdlMain release]; + [pool release]; +} + +#endif + + +/* + * Catch document open requests...this lets us notice files when the app + * was launched by double-clicking a document, or when a document was + * dragged/dropped on the app's icon. You need to have a + * CFBundleDocumentsType section in your Info.plist to get this message, + * apparently. + * + * Files are added to gArgv, so to the app, they'll look like command line + * arguments. Previously, apps launched from the finder had nothing but + * an argv[0]. + * + * This message may be received multiple times to open several docs on launch. + * + * This message is ignored once the app's mainline has been called. + */ +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename +{ + const char *temparg; + size_t arglen; + char *arg; + char **newargv; + + if (!gFinderLaunch) /* MacOS is passing command line args. */ + return FALSE; + + if (gCalledAppMainline) /* app has started, ignore this document. */ + return FALSE; + + temparg = [filename UTF8String]; + arglen = SDL_strlen(temparg) + 1; + arg = (char *) SDL_malloc(arglen); + if (arg == NULL) + return FALSE; + + newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); + if (newargv == NULL) + { + SDL_free(arg); + return FALSE; + } + gArgv = newargv; + + SDL_strlcpy(arg, temparg, arglen); + gArgv[gArgc++] = arg; + gArgv[gArgc] = NULL; + return TRUE; +} + + +/* Called when the internal event loop has just started running */ +- (void) applicationDidFinishLaunching: (NSNotification *) note +{ + int status; + + /* Set the working directory to the .app's parent directory */ + [self setupWorkingDirectory:gFinderLaunch]; + +#if SDL_USE_NIB_FILE + /* Set the main menu to contain the real app name instead of "SDL App" */ + [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; +#endif + + /* Hand off to main application code */ + gCalledAppMainline = TRUE; + status = SDL_main (gArgc, gArgv); + + /* We're done, thank you for playing */ + exit(status); +} +@end + + +@implementation NSString (ReplaceSubString) + +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString +{ + unsigned int bufferSize; + unsigned int selfLen = [self length]; + unsigned int aStringLen = [aString length]; + unichar *buffer; + NSRange localRange; + NSString *result; + + bufferSize = selfLen + aStringLen - aRange.length; + buffer = (unichar *)NSAllocateMemoryPages(bufferSize*sizeof(unichar)); + + /* Get first part into buffer */ + localRange.location = 0; + localRange.length = aRange.location; + [self getCharacters:buffer range:localRange]; + + /* Get middle part into buffer */ + localRange.location = 0; + localRange.length = aStringLen; + [aString getCharacters:(buffer+aRange.location) range:localRange]; + + /* Get last part into buffer */ + localRange.location = aRange.location + aRange.length; + localRange.length = selfLen - localRange.location; + [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; + + /* Build output string */ + result = [NSString stringWithCharacters:buffer length:bufferSize]; + + NSDeallocateMemoryPages(buffer, bufferSize); + + return result; +} + +@end + + + +#ifdef main +# undef main +#endif + + +/* Main entry point to executable - should *not* be SDL_main! */ +int main (int argc, char **argv) +{ + /* Copy the arguments into a global variable */ + /* This is passed if we are launched by double-clicking */ + if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { + gArgv = (char **) SDL_malloc(sizeof (char *) * 2); + gArgv[0] = argv[0]; + gArgv[1] = NULL; + gArgc = 1; + gFinderLaunch = YES; + } else { + int i; + gArgc = argc; + gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); + for (i = 0; i <= argc; i++) + gArgv[i] = argv[i]; + gFinderLaunch = NO; + } + +#if SDL_USE_NIB_FILE + NSApplicationMain (argc, argv); +#else + CustomApplicationMain (argc, argv); +#endif + return 0; +} + +#endif diff --git a/src/endian_uqm.h b/src/endian_uqm.h new file mode 100644 index 0000000..057da4c --- /dev/null +++ b/src/endian_uqm.h @@ -0,0 +1,136 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * Endian swapping, taken from SDL-1.2.5 sources and modified + * Original copyright (C) Sam Lantinga + */ + +#ifndef ENDIAN_UQM_H_ +#define ENDIAN_UQM_H_ + +#include "config.h" +#include "types.h" + +#if defined (__APPLE__) && defined (__GNUC__) +// When using the MacOS gcc compiler to build universal binaries, +// each file will be compiled once for each platform. +// This means that checking endianness beforehand from build.sh will not do, +// but fortunately, gcc defines __BIG_ENDIAN__ or __LITTLE_ENDIAN__ on +// this platform. +# if defined(__BIG_ENDIAN__) +# undef WORDS_BIGENDIAN +# define WORDS_BIGENDIAN +# elif defined(__LITTLE_ENDIAN__) +# undef WORDS_BIGENDIAN +# else + // Neither __BIG_ENDIAN__ nor __LITTLE_ENDIAN__ is defined. + // Fallback to using the build.sh defined value. +# endif +#endif /* __APPLE__ */ + +#if defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) +#ifndef __inline__ +#define __inline__ __inline +#endif +#endif + +/* The macros used to swap values */ +/* Try to use superfast macros on systems that support them */ +#ifdef linux +#include +#ifdef __arch__swab16 +#define UQM_Swap16 __arch__swab16 +#endif +#ifdef __arch__swab32 +#define UQM_Swap32 __arch__swab32 +#endif +#endif /* linux */ + +#if defined(__cplusplus) +extern "C" { +#endif + +/* Use inline functions for compilers that support them, and static + functions for those that do not. Because these functions become + static for compilers that do not support inline functions, this + header should only be included in files that actually use them. +*/ +#ifndef UQM_Swap16 +static __inline__ uint16 UQM_Swap16(uint16 D) +{ + return((D<<8)|(D>>8)); +} +#endif +#ifndef UQM_Swap32 +static __inline__ uint32 UQM_Swap32(uint32 D) +{ + return((D<<24)|((D<<8)&0x00FF0000)|((D>>8)&0x0000FF00)|(D>>24)); +} +#endif +#ifdef UQM_INT64 +#ifndef UQM_Swap64 +static __inline__ uint64 UQM_Swap64(uint64 val) +{ + uint32 hi, lo; + + /* Separate into high and low 32-bit values and swap them */ + lo = (uint32)(val&0xFFFFFFFF); + val >>= 32; + hi = (uint32)(val&0xFFFFFFFF); + val = UQM_Swap32(lo); + val <<= 32; + val |= UQM_Swap32(hi); + return(val); +} +#endif +#else +#ifndef UQM_Swap64 +/* This is mainly to keep compilers from complaining in SDL code. + If there is no real 64-bit datatype, then compilers will complain about + the fake 64-bit datatype that SDL provides when it compiles user code. +*/ +#define UQM_Swap64(X) (X) +#endif +#endif /* UQM_INT64 */ + + +/* Byteswap item from the specified endianness to the native endianness + * or vice versa. + */ +#ifndef WORDS_BIGENDIAN +#define UQM_SwapLE16(X) (X) +#define UQM_SwapLE32(X) (X) +#define UQM_SwapLE64(X) (X) +#define UQM_SwapBE16(X) UQM_Swap16(X) +#define UQM_SwapBE32(X) UQM_Swap32(X) +#define UQM_SwapBE64(X) UQM_Swap64(X) +#else +#define UQM_SwapLE16(X) UQM_Swap16(X) +#define UQM_SwapLE32(X) UQM_Swap32(X) +#define UQM_SwapLE64(X) UQM_Swap64(X) +#define UQM_SwapBE16(X) (X) +#define UQM_SwapBE32(X) (X) +#define UQM_SwapBE64(X) (X) +#endif + +#if defined(__cplusplus) +} +#endif + +#endif /* _ENDIAN_H */ diff --git a/src/getopt/Makeinfo b/src/getopt/Makeinfo new file mode 100644 index 0000000..281a7f2 --- /dev/null +++ b/src/getopt/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="getopt.c getopt1.c" +uqm_HFILES="getopt.h" diff --git a/src/getopt/getopt.c b/src/getopt/getopt.c new file mode 100644 index 0000000..271f634 --- /dev/null +++ b/src/getopt/getopt.c @@ -0,0 +1,1061 @@ +/* Getopt for GNU. + NOTE: getopt is now part of the C library, so if you don't know what + "Keep this file name-space clean" means, talk to drepper@gnu.org + before changing it! + Copyright (C) 1987,88,89,90,91,92,93,94,95,96,98,99,2000,2001 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +/* This tells Alpha OSF/1 not to define a getopt prototype in . + Ditto for AIX 3.2 and . */ +#ifndef _NO_PROTO +# define _NO_PROTO +#endif + +#include +#include + +#ifndef HAVE_GETOPT_LONG +#if !defined __STDC__ || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +# ifndef const +# define const +# endif +#endif + +#include + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#define GETOPT_INTERFACE_VERSION 2 +#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 +# include +# if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION +# define ELIDE_CODE +# endif +#endif + +#ifndef ELIDE_CODE + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +/* Don't include stdlib.h for non-GNU C libraries because some of them + contain conflicting prototypes for getopt. */ +# include +# include +#endif /* GNU C library. */ + +#ifdef VMS +# include +# if HAVE_STRING_H - 0 +# include +# endif +#endif + +#ifndef _ +/* This is for other GNU distributions with internationalized messages. */ +# if defined HAVE_LIBINTL_H || defined _LIBC +# include +# ifndef _ +# define _(msgid) gettext (msgid) +# endif +# else +# define _(msgid) (msgid) +# endif +#endif + +/* This version of `getopt' appears to the caller like standard Unix `getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#include "getopt.h" + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* 1003.2 says this must be 1 before any call. */ +int optind = 1; + +/* Formerly, initialization of getopt depended on optind==0, which + causes problems with re-calling getopt as programs generally don't + know that. */ + +int __getopt_initialized; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int optopt = '?'; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return -1 with `optind' != ARGC. */ + +static enum +{ + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +/* Value of POSIXLY_CORRECT environment variable. */ +static char *posixly_correct; + +#ifdef __GNU_LIBRARY__ +/* We want to avoid inclusion of string.h with non-GNU libraries + because there are many ways it can cause trouble. + On some systems, it contains special magic macros that don't work + in GCC. */ +# include +# define my_index strchr +#else + +#ifndef WIN32 +# if HAVE_STRING_H +# include +# else +# include +# endif +#else +# include +#endif + +/* Avoid depending on library functions or files + whose names are inconsistent. */ + +#ifndef getenv +extern char *getenv (); +#endif + +static char * +my_index (str, chr) + const char *str; + int chr; +{ + while (*str) + { + if (*str == chr) + return (char *) str; + str++; + } + return 0; +} + +/* If using GCC, we can safely declare strlen this way. + If not using GCC, it is ok not to declare it. */ +#ifdef __GNUC__ +/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. + That was relevant to code that was here before. */ +# if (!defined __STDC__ || !__STDC__) && !defined strlen +/* gcc with -traditional declares the built-in strlen to return int, + and has done so at least since version 2.4.5. -- rms. */ +extern int strlen (const char *); +# endif /* not __STDC__ */ +#endif /* __GNUC__ */ + +#endif /* not __GNU_LIBRARY__ */ + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +#ifdef _LIBC +/* Stored original parameters. + XXX This is no good solution. We should rather copy the args so + that we can compare them later. But we must not use malloc(3). */ +extern int __libc_argc; +extern char **__libc_argv; + +/* Bash 2.0 gives us an environment variable containing flags + indicating ARGV elements that should not be considered arguments. */ + +# ifdef USE_NONOPTION_FLAGS +/* Defined in getopt_init.c */ +extern char *__getopt_nonoption_flags; + +static int nonoption_flags_max_len; +static int nonoption_flags_len; +# endif + +# ifdef USE_NONOPTION_FLAGS +# define SWAP_FLAGS(ch1, ch2) \ + if (nonoption_flags_len > 0) \ + { \ + char __tmp = __getopt_nonoption_flags[ch1]; \ + __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ + __getopt_nonoption_flags[ch2] = __tmp; \ + } +# else +# define SWAP_FLAGS(ch1, ch2) +# endif +#else /* !_LIBC */ +# define SWAP_FLAGS(ch1, ch2) +#endif /* _LIBC */ + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +#if defined __STDC__ && __STDC__ +static void exchange (char **); +#endif + +static void +exchange (argv) + char **argv; +{ + int bottom = first_nonopt; + int middle = last_nonopt; + int top = optind; + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + +#if defined _LIBC && defined USE_NONOPTION_FLAGS + /* First make sure the handling of the `__getopt_nonoption_flags' + string can work normally. Our top argument must be in the range + of the string. */ + if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) + { + /* We must extend the array. The user plays games with us and + presents new arguments. */ + char *new_str = HMalloc (top + 1); + if (new_str == NULL) + nonoption_flags_len = nonoption_flags_max_len = 0; + else + { + memset (__mempcpy (new_str, __getopt_nonoption_flags, + nonoption_flags_max_len), + '\0', top + 1 - nonoption_flags_max_len); + nonoption_flags_max_len = top + 1; + __getopt_nonoption_flags = new_str; + } + } +#endif + + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + /* Bottom segment is the short one. */ + int len = middle - bottom; + int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } + else + { + /* Top segment is the short one. */ + int len = top - middle; + int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + SWAP_FLAGS (bottom + i, middle + i); + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (optind - last_nonopt); + last_nonopt = optind; +} + +/* Initialize the internal data when the first call is made. */ + +#if defined __STDC__ && __STDC__ +static const char *_getopt_initialize (int, char *const *, const char *); +#endif +static const char * +_getopt_initialize (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + first_nonopt = last_nonopt = optind; + + nextchar = NULL; + + posixly_correct = getenv ("POSIXLY_CORRECT"); + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + ordering = REQUIRE_ORDER; + ++optstring; + } + else if (posixly_correct != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + +#if defined _LIBC && defined USE_NONOPTION_FLAGS + if (posixly_correct == NULL + && argc == __libc_argc && argv == __libc_argv) + { + if (nonoption_flags_max_len == 0) + { + if (__getopt_nonoption_flags == NULL + || __getopt_nonoption_flags[0] == '\0') + nonoption_flags_max_len = -1; + else + { + const char *orig_str = __getopt_nonoption_flags; + int len = nonoption_flags_max_len = strlen (orig_str); + if (nonoption_flags_max_len < argc) + nonoption_flags_max_len = argc; + __getopt_nonoption_flags = + (char *) HMalloc (nonoption_flags_max_len); + if (__getopt_nonoption_flags == NULL) + nonoption_flags_max_len = -1; + else + memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), + '\0', nonoption_flags_max_len - len); + } + } + nonoption_flags_len = nonoption_flags_max_len; + } + else + nonoption_flags_len = 0; +#endif + + return optstring; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns -1. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +int +_getopt_internal (argc, argv, optstring, longopts, longind, long_only) + int argc; + char *const *argv; + const char *optstring; + const struct option *longopts; + int *longind; + int long_only; +{ + int print_errors = opterr; + if (optstring[0] == ':') + print_errors = 0; + + if (argc < 1) + return -1; + + optarg = NULL; + + if (optind == 0 || !__getopt_initialized) + { + if (optind == 0) + optind = 1; /* Don't scan ARGV[0], the program name. */ + optstring = _getopt_initialize (argc, argv, optstring); + __getopt_initialized = 1; + } + + /* Test whether ARGV[optind] points to a non-option argument. + Either it does not have option syntax, or there is an environment flag + from the shell indicating it is not an option. The later information + is only used when the used in the GNU libc. */ +#if defined _LIBC && defined USE_NONOPTION_FLAGS +# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ + || (optind < nonoption_flags_len \ + && __getopt_nonoption_flags[optind] == '1')) +#else +# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') +#endif + + if (nextchar == NULL || *nextchar == '\0') + { + /* Advance to the next ARGV-element. */ + + /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been + moved back by the user (who may also have changed the arguments). */ + if (last_nonopt > optind) + last_nonopt = optind; + if (first_nonopt > optind) + first_nonopt = optind; + + if (ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (last_nonopt != optind) + first_nonopt = optind; + + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (optind < argc && NONOPTION_P) + optind++; + last_nonopt = optind; + } + + /* The special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (optind != argc && !strcmp (argv[optind], "--")) + { + optind++; + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; + + optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return -1; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if (NONOPTION_P) + { + if (ordering == REQUIRE_ORDER) + return -1; + optarg = argv[optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Skip the initial punctuation. */ + + nextchar = (argv[optind] + 1 + + (longopts != NULL && argv[optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* Check whether the ARGV-element is a long option. + + If long_only and the ARGV-element has the form "-f", where f is + a valid short option, don't consider it an abbreviated form of + a long option that starts with f. Otherwise there would be no + way to give the -f short option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an abbreviation of + the long option, just like "--fu", and not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + + if (longopts != NULL + && (argv[optind][1] == '-' + || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = -1; + int option_index; + + for (nameend = nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((unsigned int) (nameend - nextchar) + == (unsigned int) strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else if (long_only + || pfound->has_arg != p->has_arg + || pfound->flag != p->flag + || pfound->val != p->val) + /* Second or later nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + if (print_errors) + fprintf (stderr, _("%s: option `%s' is ambiguous\n"), + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + optopt = 0; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + optind++; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (print_errors) + { + if (argv[optind - 1][1] == '-') + /* --option */ + fprintf (stderr, + _("%s: option `--%s' doesn't allow an argument\n"), + argv[0], pfound->name); + else + /* +option or -option */ + fprintf (stderr, + _("%s: option `%c%s' doesn't allow an argument\n"), + argv[0], argv[optind - 1][0], pfound->name); + } + + nextchar += strlen (nextchar); + + optopt = pfound->val; + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (print_errors) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + optopt = pfound->val; + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + /* Can't find it as a long option. If this is not getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[optind][1] == '-' + || my_index (optstring, *nextchar) == NULL) + { + if (print_errors) + { + if (argv[optind][1] == '-') + /* --option */ + fprintf (stderr, _("%s: unrecognized option `--%s'\n"), + argv[0], nextchar); + else + /* +option or -option */ + fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), + argv[0], argv[optind][0], nextchar); + } + nextchar = (char *) ""; + optind++; + optopt = 0; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + + { + char c = *nextchar++; + char *temp = my_index (optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++optind; + + if (temp == NULL || c == ':') + { + if (print_errors) + { + if (posixly_correct) + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: illegal option -- %c\n"), + argv[0], c); + else + fprintf (stderr, _("%s: invalid option -- %c\n"), + argv[0], c); + } + optopt = c; + return '?'; + } + /* Convenience. Treat POSIX -W foo same as long option --foo */ + if (temp[0] == 'W' && temp[1] == ';') + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (print_errors) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + + /* optarg is now the argument, see if it's in the + table of longopts. */ + + for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((unsigned int) (nameend - nextchar) == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + if (ambig && !exact) + { + if (print_errors) + fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + return '?'; + } + if (pfound != NULL) + { + option_index = indfound; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (print_errors) + fprintf (stderr, _("\ +%s: option `-W %s' doesn't allow an argument\n"), + argv[0], pfound->name); + + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (print_errors) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + nextchar = NULL; + return 'W'; /* Let the application handle it. */ + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else + optarg = NULL; + nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (print_errors) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, + _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + nextchar = NULL; + } + } + return c; + } +} + +int +getopt (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + return _getopt_internal (argc, argv, optstring, + (const struct option *) 0, + (int *) 0, + 0); +} + +#endif /* Not ELIDE_CODE. */ + +#ifdef TEST + +/* Compile with -DTEST to make an executable for use in testing + the above definition of `getopt'. */ + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + + c = getopt (argc, argv, "abc:d:0123456789"); + if (c == -1) + break; + + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ + +#endif /* HAVE_GETOPT_LONG */ diff --git a/src/getopt/getopt.h b/src/getopt/getopt.h new file mode 100644 index 0000000..a1b8dd6 --- /dev/null +++ b/src/getopt/getopt.h @@ -0,0 +1,180 @@ +/* Declarations for getopt. + Copyright (C) 1989-1994, 1996-1999, 2001 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#ifndef _GETOPT_H + +#ifndef __need_getopt +# define _GETOPT_H 1 +#endif + +/* If __GNU_LIBRARY__ is not already defined, either we are being used + standalone, or this is the first header included in the source file. + If we are being used with glibc, we need to include , but + that does not exist if we are standalone. So: if __GNU_LIBRARY__ is + not defined, include , which will pull in for us + if it's from glibc. (Why ctype.h? It's guaranteed to exist and it + doesn't flood the namespace with stuff the way some other headers do.) */ +#if !defined __GNU_LIBRARY__ +# include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Set to an option character which was unrecognized. */ + +extern int optopt; + +#ifndef __need_getopt +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ +# if (defined __STDC__ && __STDC__) || defined __cplusplus + const char *name; +# else + char *name; +# endif + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct option'. */ + +# define no_argument 0 +# define required_argument 1 +# define optional_argument 2 +#endif /* need getopt */ + + +/* Get definitions and prototypes for functions to process the + arguments in ARGV (ARGC of them, minus the program name) for + options given in OPTS. + + Return the option character from OPTS just read. Return -1 when + there are no more options. For unrecognized options, or options + missing arguments, `optopt' is set to the option letter, and '?' is + returned. + + The OPTS string is a list of characters which are recognized option + letters, optionally followed by colons, specifying that that letter + takes an argument, to be placed in `optarg'. + + If a letter in OPTS is followed by two colons, its argument is + optional. This behavior is specific to the GNU `getopt'. + + The argument `--' causes premature termination of argument + scanning, explicitly telling `getopt' that there are no more + options. + + If OPTS begins with `--', then non-option arguments are treated as + arguments to the option '\0'. This behavior is specific to the GNU + `getopt'. */ + +#if (defined __STDC__ && __STDC__) || defined __cplusplus +# ifdef __GNU_LIBRARY__ +/* Many other libraries have conflicting prototypes for getopt, with + differences in the consts, in stdlib.h. To avoid compilation + errors, only prototype getopt for the GNU C library. */ +extern int getopt (int __argc, char *const *__argv, const char *__shortopts); +# else /* not __GNU_LIBRARY__ */ +extern int getopt (); +# endif /* __GNU_LIBRARY__ */ + +# ifndef __need_getopt +extern int getopt_long (int __argc, char *const *__argv, const char *__shortopts, + const struct option *__longopts, int *__longind); +extern int getopt_long_only (int __argc, char *const *__argv, + const char *__shortopts, + const struct option *__longopts, int *__longind); + +/* Internal only. Users should not call this directly. */ +extern int _getopt_internal (int __argc, char *const *__argv, + const char *__shortopts, + const struct option *__longopts, int *__longind, + int __long_only); +# endif +#else /* not __STDC__ */ +extern int getopt (); +# ifndef __need_getopt +extern int getopt_long (); +extern int getopt_long_only (); + +extern int _getopt_internal (); +# endif +#endif /* __STDC__ */ + +#ifdef __cplusplus +} +#endif + +/* Make sure we later can get all the definitions and declarations. */ +#undef __need_getopt + +#endif /* getopt.h */ diff --git a/src/getopt/getopt1.c b/src/getopt/getopt1.c new file mode 100644 index 0000000..9acca1b --- /dev/null +++ b/src/getopt/getopt1.c @@ -0,0 +1,189 @@ +/* getopt_long and getopt_long_only entry points for GNU getopt. + Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#include + +#ifndef HAVE_GETOPT_LONG +#include "getopt.h" + +#if !defined __STDC__ || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +#ifndef const +#define const +#endif +#endif + +#include + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#define GETOPT_INTERFACE_VERSION 2 +#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 +#include +#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION +#define ELIDE_CODE +#endif +#endif + +#ifndef ELIDE_CODE + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#include +#endif + +#ifndef NULL +#define NULL 0 +#endif + +int +getopt_long (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 0); +} + +/* Like getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int +getopt_long_only (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 1); +} + + +#endif /* Not ELIDE_CODE. */ + +#ifdef TEST + +#include + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + static struct option long_options[] = + { + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 0, 0, 0}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} + }; + + c = getopt_long (argc, argv, "abc:d:0123456789", + long_options, &option_index); + if (c == -1) + break; + + switch (c) + { + case 0: + printf ("option %s", long_options[option_index].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case 'd': + printf ("option d with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ + +#endif /* HAVE_GETOPT_LONG */ diff --git a/src/libs/Makeinfo b/src/libs/Makeinfo new file mode 100644 index 0000000..dbaa7d4 --- /dev/null +++ b/src/libs/Makeinfo @@ -0,0 +1,19 @@ +uqm_SUBDIRS="callback decomp file graphics heap input list math memory + resource sound strings task threads time uio video log" +if [ -n "$uqm_USE_INTERNAL_MIKMOD" ]; then + uqm_SUBDIRS="$uqm_SUBDIRS mikmod" +fi + +if [ -n "$uqm_NETPLAY" ]; then + uqm_SUBDIRS="$uqm_SUBDIRS network" +fi + +#if [ "$DEBUG" = 1 ]; then +# uqm_SUBDIRS="$UQM_SUBDIRS debug" +#fi + +uqm_HFILES="alarm.h async.h callback.h cdplib.h compiler.h declib.h file.h + gfxlib.h heap.h inplib.h list.h log.h mathlib.h md5.h memlib.h + misc.h net.h platform.h reslib.h sndlib.h strlib.h tasklib.h + threadlib.h timelib.h uio.h uioutils.h unicode.h vidlib.h" + diff --git a/src/libs/alarm.h b/src/libs/alarm.h new file mode 100644 index 0000000..b4a72ca --- /dev/null +++ b/src/libs/alarm.h @@ -0,0 +1,9 @@ +#if defined(__cplusplus) +extern "C" { +#endif + +#include "callback/alarm.h" + +#if defined(__cplusplus) +} +#endif diff --git a/src/libs/async.h b/src/libs/async.h new file mode 100644 index 0000000..25b71af --- /dev/null +++ b/src/libs/async.h @@ -0,0 +1,10 @@ +#if defined(__cplusplus) +extern "C" { +#endif + +#include "callback/async.h" + +#if defined(__cplusplus) +} +#endif + diff --git a/src/libs/callback.h b/src/libs/callback.h new file mode 100644 index 0000000..025160a --- /dev/null +++ b/src/libs/callback.h @@ -0,0 +1,10 @@ +#if defined(__cplusplus) +extern "C" { +#endif + +#include "callback/callback.h" + +#if defined(__cplusplus) +} +#endif + diff --git a/src/libs/callback/Makeinfo b/src/libs/callback/Makeinfo new file mode 100644 index 0000000..8842cba --- /dev/null +++ b/src/libs/callback/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="alarm.c async.c callback.c" +uqm_HFILES="alarm.h async.h callback.h" diff --git a/src/libs/callback/alarm.c b/src/libs/callback/alarm.c new file mode 100644 index 0000000..c9bd6ce --- /dev/null +++ b/src/libs/callback/alarm.c @@ -0,0 +1,177 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "alarm.h" + +#include SDL_INCLUDE(SDL.h) +#include "libs/heap.h" + +#include +#include + + +Heap *alarmHeap; + + +static inline Alarm * +Alarm_alloc(void) { + return malloc(sizeof (Alarm)); +} + +static inline void +Alarm_free(Alarm *alarm) { + free(alarm); +} + +static inline int +AlarmTime_compare(const AlarmTime t1, const AlarmTime t2) { + if (t1 < t2) + return -1; + if (t1 > t2) + return 1; + return 0; +} + +static int +Alarm_compare(const Alarm *a1, const Alarm *a2) { + return AlarmTime_compare(a1->time, a2->time); +} + +void +Alarm_init(void) { + assert(alarmHeap == NULL); + alarmHeap = Heap_new((HeapValue_Comparator) Alarm_compare, + 4, 4, 0.8); +} + +void +Alarm_uninit(void) { + assert(alarmHeap != NULL); + + while (Heap_hasMore(alarmHeap)) { + Alarm *alarm = (Alarm *) Heap_pop(alarmHeap); + Alarm_free(alarm); + } + Heap_delete(alarmHeap); + alarmHeap = NULL; +} + +static inline AlarmTime +AlarmTime_nowMs(void) { + return SDL_GetTicks(); +} + +Alarm * +Alarm_addAbsoluteMs(uint32 ms, AlarmCallback callback, + AlarmCallbackArg arg) { + Alarm *alarm; + + assert(alarmHeap != NULL); + + alarm = Alarm_alloc(); + alarm->time = ms; + alarm->callback = callback; + alarm->arg = arg; + + Heap_add(alarmHeap, (HeapValue *) alarm); + + return alarm; +} + +Alarm * +Alarm_addRelativeMs(uint32 ms, AlarmCallback callback, + AlarmCallbackArg arg) { + Alarm *alarm; + + assert(alarmHeap != NULL); + + alarm = Alarm_alloc(); + alarm->time = AlarmTime_nowMs() + ms; + alarm->callback = callback; + alarm->arg = arg; + + Heap_add(alarmHeap, (HeapValue *) alarm); + + return alarm; +} + +void +Alarm_remove(Alarm *alarm) { + assert(alarmHeap != NULL); + Heap_remove(alarmHeap, (HeapValue *) alarm); + Alarm_free(alarm); +} + +// Process at most one alarm, if its time has come. +// It is safe to call this function again from inside a callback function +// that it called. It should not be called from multiple threads at once. +bool +Alarm_processOne(void) +{ + AlarmTime now; + Alarm *alarm; + + assert(alarmHeap != NULL); + if (!Heap_hasMore(alarmHeap)) + return false; + + now = AlarmTime_nowMs(); + alarm = (Alarm *) Heap_first(alarmHeap); + if (now < alarm->time) + return false; + + Heap_pop(alarmHeap); + alarm->callback(alarm->arg); + Alarm_free(alarm); + return true; +} + +#if 0 +// It is safe to call this function again from inside a callback function +// that it called. It should not be called from multiple threads at once. +void +Alarm_processAll(void) { + AlarmTime now; + + assert(alarmHeap != NULL); + + now = AlarmTime_nowMs(); + while (Heap_hasMore(alarmHeap)) { + Alarm *alarm = (Alarm *) Heap_first(alarmHeap); + + if (now < alarm->time) + break; + + Heap_pop(alarmHeap); + alarm->callback(alarm->arg); + Alarm_free(alarm); + } +} +#endif + +uint32 +Alarm_timeBeforeNextMs(void) { + Alarm *alarm; + + if (!Heap_hasMore(alarmHeap)) + return UINT32_MAX; + + alarm = (Alarm *) Heap_first(alarmHeap); + return alarmTimeToMsUint32(alarm->time); +} + diff --git a/src/libs/callback/alarm.h b/src/libs/callback/alarm.h new file mode 100644 index 0000000..9f61263 --- /dev/null +++ b/src/libs/callback/alarm.h @@ -0,0 +1,56 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_CALLBACK_ALARM_H_ +#define LIBS_CALLBACK_ALARM_H_ + +#include "port.h" +#include "types.h" + +typedef uint32 AlarmTime; +static inline uint32 +alarmTimeToMsUint32(AlarmTime time) { + return (uint32) time; +} + +typedef struct Alarm Alarm; +typedef void *AlarmCallbackArg; +typedef void (*AlarmCallback)(AlarmCallbackArg arg); + +struct Alarm { + size_t index; + // For the HeapValue 'base struct'. + + AlarmTime time; + AlarmCallback callback; + AlarmCallbackArg arg; +}; + +void Alarm_init(void); +void Alarm_uninit(void); +Alarm *Alarm_addAbsoluteMs(uint32 ms, AlarmCallback callback, + AlarmCallbackArg arg); +Alarm *Alarm_addRelativeMs(uint32 ms, AlarmCallback callback, + AlarmCallbackArg arg); +void Alarm_remove(Alarm *alarm); +bool Alarm_processOne(void); +void Alarm_processAll(void); +uint32 Alarm_timeBeforeNextMs(void); + +#endif /* LIBS_CALLBACK_ALARM_H_ */ + diff --git a/src/libs/callback/async.c b/src/libs/callback/async.c new file mode 100644 index 0000000..d901158 --- /dev/null +++ b/src/libs/callback/async.c @@ -0,0 +1,56 @@ +/* + * Copyright 2012 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "async.h" + +#include "libs/alarm.h" +#include "libs/callback.h" + + +// Process all alarms and callbacks. +// First, all scheduled callbacks are called. +// Then each alarm due is called, and after each of these alarms, the +// callbacks scheduled by this alarm are called. +void +Async_process(void) +{ + // Call pending callbacks. + Callback_process(); + + for (;;) { + if (!Alarm_processOne()) + return; + + // Call callbacks scheduled from the last alarm. + Callback_process(); + } +} + +// Returns the next time that some asynchronous callback is +// to be called. Note that all values lower than the current time +// should be considered as 'somewhere in the past'. +uint32 +Async_timeBeforeNextMs(void) { + if (Callback_haveMore()) { + // Any time before the current time is ok, though we reserve 0 so + // that the caller may use it as a special value in its own code. + return 1; + } + return Alarm_timeBeforeNextMs(); +} + diff --git a/src/libs/callback/async.h b/src/libs/callback/async.h new file mode 100644 index 0000000..8cfae39 --- /dev/null +++ b/src/libs/callback/async.h @@ -0,0 +1,28 @@ +/* + * Copyright 2012 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 _ASYNC_H +#define _ASYNC_H + +#include "types.h" + +void Async_process(void); +uint32 Async_timeBeforeNextMs(void); + +#endif /* _ASYNC_H */ + diff --git a/src/libs/callback/callback.c b/src/libs/callback/callback.c new file mode 100644 index 0000000..e8ae8e9 --- /dev/null +++ b/src/libs/callback/callback.c @@ -0,0 +1,193 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "types.h" + +#include +#include +#include + +#include "libs/threadlib.h" + +typedef struct CallbackLink CallbackLink; + +#define CALLBACK_INTERNAL +#include "callback.h" + +struct CallbackLink { + CallbackLink *next; + CallbackFunction callback; + CallbackArg arg; +}; + +static CallbackLink *callbacks; +static CallbackLink **callbacksEnd; +static CallbackLink *const *callbacksProcessEnd; + +static Mutex callbackListLock; + +static inline void +CallbackList_lock(void) { + LockMutex(callbackListLock); +} + +static inline void +CallbackList_unlock(void) { + UnlockMutex(callbackListLock); +} + +#if 0 +static inline bool +CallbackList_isLocked(void) { + // TODO +} +#endif + +void +Callback_init(void) { + callbacks = NULL; + callbacksEnd = &callbacks; + callbacksProcessEnd = &callbacks; + callbackListLock = CreateMutex("Callback List Lock", SYNC_CLASS_TOPLEVEL); +} + +void +Callback_uninit(void) { + // TODO: cleanup the queue? + DestroyMutex (callbackListLock); + callbackListLock = 0; +} + +// Callbacks are guaranteed to be called in the order that they are queued. +CallbackID +Callback_add(CallbackFunction callback, CallbackArg arg) { + CallbackLink *link = malloc(sizeof (CallbackLink)); + link->callback = callback; + link->arg = arg; + link->next = NULL; + + CallbackList_lock(); + *callbacksEnd = link; + callbacksEnd = &link->next; + CallbackList_unlock(); + return (CallbackID) link; +} + + +static void +CallbackLink_delete(CallbackLink *link) { + free(link); +} + +// Pre: CallbackList is locked. +static CallbackLink ** +CallbackLink_find(CallbackLink *link) { + CallbackLink **ptr; + + //assert(CallbackList_isLocked()); + for (ptr = &callbacks; *ptr != NULL; ptr = &(*ptr)->next) { + if (*ptr == link) + return ptr; + } + return NULL; +} + +bool +Callback_remove(CallbackID id) { + CallbackLink *link = (CallbackLink *) id; + CallbackLink **linkPtr; + + CallbackList_lock(); + + linkPtr = CallbackLink_find(link); + if (linkPtr == NULL) { + CallbackList_unlock(); + return false; + } + + if (callbacksEnd == &(*linkPtr)->next) + callbacksEnd = linkPtr; + if (callbacksProcessEnd == &(*linkPtr)->next) + callbacksProcessEnd = linkPtr; + *linkPtr = (*linkPtr)->next; + + CallbackList_unlock(); + + CallbackLink_delete(link); + return true; +} + +static inline void +CallbackLink_doCallback(CallbackLink *link) { + (link->callback)(link->arg); +} + +// Call all queued callbacks currently in the queue. Callbacks queued +// from inside the called functions will not be processed until the next +// call of Callback_process(). +// It is allowed to remove callbacks from inside the called functions. +// NB: Callback_process() must never be called from more than one thread +// at the same time. It's the only sensible way to ensure that the +// callbacks are called in the order in which they were queued. +// It is however allowed to call Callback_process() from inside the +// callback function called by Callback_process() itself. +void +Callback_process(void) { + CallbackLink *link; + + // We set 'callbacksProcessEnd' to callbacksEnd. Callbacks added + // from inside a callback function will be placed after + // callbacksProcessEnd, and will hence not be processed this + // call of Callback_process(). + CallbackList_lock(); + callbacksProcessEnd = callbacksEnd; + CallbackList_unlock(); + + for (;;) { + CallbackList_lock(); + if (callbacksProcessEnd == &callbacks) { + CallbackList_unlock(); + break; + } + assert(callbacks != NULL); + // If callbacks == NULL, then callbacksProcessEnd == &callbacks + link = callbacks; + callbacks = link->next; + if (callbacksEnd == &link->next) + callbacksEnd = &callbacks; + if (callbacksProcessEnd == &link->next) + callbacksProcessEnd = &callbacks; + CallbackList_unlock(); + + CallbackLink_doCallback(link); + CallbackLink_delete(link); + } +} + +bool +Callback_haveMore(void) { + bool result; + + CallbackList_lock(); + result = (callbacks != NULL); + CallbackList_unlock(); + + return result; +} + diff --git a/src/libs/callback/callback.h b/src/libs/callback/callback.h new file mode 100644 index 0000000..e04ebe8 --- /dev/null +++ b/src/libs/callback/callback.h @@ -0,0 +1,43 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_CALLBACK_CALLBACK_H_ +#define LIBS_CALLBACK_CALLBACK_H_ + +#include "types.h" + +#ifdef CALLBACK_INTERNAL +typedef CallbackLink *CallbackID; +#else +typedef void *CallbackID; + // Uniquely identifies a queued callback. +#endif +#define CallbackID_invalid ((CallbackID ) NULL) + +typedef void *CallbackArg; +typedef void (*CallbackFunction)(CallbackArg arg); + +void Callback_init(void); +void Callback_uninit(void); +CallbackID Callback_add(CallbackFunction callback, CallbackArg arg); +bool Callback_remove(CallbackID id); +void Callback_process(void); +bool Callback_haveMore(void); + +#endif /* LIBS_CALLBACK_CALLBACK_H_ */ + diff --git a/src/libs/cdp/Makeinfo b/src/libs/cdp/Makeinfo new file mode 100644 index 0000000..b2a83b5 --- /dev/null +++ b/src/libs/cdp/Makeinfo @@ -0,0 +1,3 @@ +uqm_CFILES="cdp.c cdpapi.c" +uqm_HFILES="cdp_alli.h cdpapi.h cdp.h cdp_iio.h cdp_imem.h cdpint.h + cdp_isnd.h cdp_ivid.h cdpmod.h windl.h" diff --git a/src/libs/cdp/cdp.c b/src/libs/cdp/cdp.c new file mode 100644 index 0000000..ca9536e --- /dev/null +++ b/src/libs/cdp/cdp.c @@ -0,0 +1,437 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP library definitions + */ + +#include +#include +#include "cdp.h" +#include "port.h" +#include "cdpint.h" +#include "cdpmod.h" +#include "uio.h" +#include "uqmversion.h" +#ifdef WIN32 +# include "windl.h" +#else +# include +#endif + +#define MAX_CDPS 63 +#define CDPDIR "cdps" + +// internal CDP module representation +struct cdp_Module +{ + bool builtin; // used at least once indicator + bool used; // used at least once indicator + void* hmodule; // loaded module handle + uint32 refcount; // reference count + cdp_ModuleInfo* info; // cdp exported info + +}; + +// Kernel module info +// not a real module, and not loadable either +// this just provides information to other modules +cdp_ModuleInfo cdp_kernel_info = +{ + sizeof (cdp_ModuleInfo), + CDPAPI_VERSION, // API version we are using + UQM_MAJOR_VERSION, UQM_MINOR_VERSION, UQM_PATCH_VERSION, + UQM_MAJOR_VERSION, UQM_MINOR_VERSION, UQM_PATCH_VERSION, + CDP_MODINFO_RESERVED1, + "UQM", // CDP context cannonical name + "UQM Kernel", // CDP mod name +# define S(i) #i + // CDP mod version + S(UQM_MAJOR_VERSION) "." S(UQM_MINOR_VERSION) UQM_EXTRA_VERSION, +# undef S + "UQM Team", // CDP mod author + "http://sc2.sf.net", // CDP mod URL + "Eternal doctrine executor", // CDP mod comment + CDP_MODINFO_RESERVED2, + NULL, NULL // no entrypoints defined/needed +}; + +static cdp_Module cdp_modules[MAX_CDPS + 1] = +{ + {true, true, NULL, 1, &cdp_kernel_info}, + + {false, false, NULL, 0, NULL} // term +}; + +extern uio_DirHandle *cdpDir; + +static bool cdp_inited = false; +static cdp_Error cdp_last_error = CDPERR_NONE; +static char cdp_path[PATH_MAX] = ""; + +cdp_Error +cdp_GetError (void) +{ + cdp_Error ret = cdp_last_error; + cdp_last_error = CDPERR_NONE; + return ret; +} + +bool +cdp_Init (void) +{ + int i; + void* hkernel; + + if (cdp_inited) + { + fprintf (stderr, "cdp_Init(): called when already inited\n"); + return true; + } + + // preprocess built-in modules + hkernel = dlopen (NULL, RTLD_LAZY); + + for (i = 0; cdp_modules[i].builtin; ++i) + cdp_modules[i].hmodule = hkernel; + + // clear the rest + //memset (cdp_modules + i, 0, + // sizeof (cdp_modules) - sizeof (cdp_Module) * i); + + //strcpy (cdp_path, cdpDir->path); + cdp_InitApi (); + cdp_inited = true; + + return true; +} + +void +cdp_Uninit (void) +{ + if (!cdp_inited) + { + fprintf (stderr, "cdp_Uninit(): called when not inited\n"); + return; + } + + cdp_UninitApi (); + cdp_FreeAllModules (); + cdp_inited = false; +} + +cdp_Module* +cdp_LoadModule (const char* modname) + // special value for modname: NULL - refers to kernel (UQM exe) +{ + void* mod; + char modpath[PATH_MAX]; + const char* errstr; + cdp_ModuleInfo* info; + int i; + cdp_Module* cdp; + cdp_Module* newslot = 0; + cdp_Itf* ihost; + + if (modname == NULL) + return cdp_modules; + + if (!cdp_inited) + { + fprintf (stderr, "cdp_LoadModule(): called when not inited\n"); + return 0; + } + + // load dynamic lib + sprintf (modpath, "%s/%s%s", CDPDIR, modname, CDPEXT); + mod = dlopen (modpath, RTLD_NOW); + if (!mod) + { + cdp_last_error = CDPERR_NOT_FOUND; + return NULL; + } + + // look it up in already loaded + for (i = 0, cdp = cdp_modules; cdp->used && cdp->hmodule != mod; + ++cdp, ++i) + { + // and pick up an empty slot (where available) + if (!newslot && !cdp->hmodule) + newslot = cdp; + } + if (i >= MAX_CDPS) + { + fprintf (stderr, "cdp_LoadModule(): " + "CDPs limit reached while loading %s\n", + modname); + dlclose (mod); + cdp_last_error = CDPERR_TOO_MANY; + return NULL; + } + + if (cdp->hmodule) + { // module has already been loaded + cdp->refcount++; + return cdp; + } + + dlerror (); // clear any error + info = dlsym (mod, CDP_INFO_SYM_NAME); + if (!info && (errstr = dlerror ())) + { + dlclose (mod); + cdp_last_error = CDPERR_BAD_MODULE; + return NULL; + } + + if (info->size < CDP_MODINFO_MIN_SIZE || info->api_ver > CDPAPI_VERSION) + { + fprintf (stderr, "cdp_LoadModule(): " + "CDP %s is invalid or newer API version\n", + modname); + dlclose (mod); + cdp_last_error = CDPERR_UNKNOWN_VER; + return NULL; + } + + ihost = cdp_GetInterface (CDPITF_KIND_HOST, info->api_ver); + if (!ihost) + { + fprintf (stderr, "cdp_LoadModule(): " + "CDP %s requested unsupported API version 0x%08x\n", + modname, info->api_ver); + dlclose (mod); + cdp_last_error = CDPERR_UNKNOWN_VER; + return NULL; + } + + if (!newslot) + { + newslot = cdp; + newslot->used = true; + // make next one a term + cdp[1].builtin = false; + cdp[1].used = false; + cdp[1].hmodule = NULL; + cdp[1].refcount = 0; + } + newslot->hmodule = mod; + newslot->refcount = 1; + newslot->info = info; + + if (!info->module_init (newslot, (cdp_Itf_Host*)ihost)) + { + fprintf (stderr, "cdp_LoadModule(): " + "CDP %s failed to init\n", + modname); + dlclose (mod); + newslot->hmodule = NULL; + newslot->info = NULL; + newslot->refcount = 0; + cdp_last_error = CDPERR_INIT_FAILED; + return NULL; + } + + + return newslot; +} + +cdp_Module* +cdp_CheckModule (cdp_Module* module) +{ + if (module < cdp_modules || module >= cdp_modules + MAX_CDPS || + !module->hmodule || !module->info) + return NULL; + else + return module; +} + +void +cdp_FreeModule (cdp_Module* module) +{ + cdp_Module* modslot = cdp_CheckModule (module); + + if (!modslot || modslot->builtin) + return; + + modslot->refcount--; + if (modslot->refcount == 0) + modslot->info->module_term (); + + dlclose (modslot->hmodule); + + if (modslot->refcount == 0) + { + modslot->hmodule = NULL; + modslot->info = NULL; + } +} + +const char* +cdp_GetModuleContext (cdp_Module* module, bool bMetaString) +{ + cdp_Module* modslot = cdp_CheckModule (module); + if (bMetaString) + { + if (!modslot) + return "(Error)"; + if (!modslot->info->context_name) + return "(Null)"; + } + else if (!modslot) + { + return NULL; + } + return modslot->info->context_name; +} + +const char* +cdp_GetModuleName (cdp_Module* module, bool bMetaString) +{ + cdp_Module* modslot = cdp_CheckModule (module); + if (bMetaString) + { + if (!modslot) + return "(Error)"; + if (!modslot->info->name) + return "(Null)"; + } + else if (!modslot) + { + return NULL; + } + return modslot->info->name; +} + +uint32 +cdp_GetModuleVersion (cdp_Module* module) +{ + cdp_Module* modslot = cdp_CheckModule (module); + if (!modslot) + return 0; + return (modslot->info->ver_major << 16) | modslot->info->ver_minor; +} + +const char* +cdp_GetModuleVersionString (cdp_Module* module, bool bMetaString) +{ + cdp_Module* modslot = cdp_CheckModule (module); + if (bMetaString) + { + if (!modslot) + return "(Error)"; + if (!modslot->info->ver_string) + return "(Null)"; + } + else if (!modslot) + { + return NULL; + } + return modslot->info->ver_string; +} + +const char* +cdp_GetModuleComment (cdp_Module* module, bool bMetaString) +{ + cdp_Module* modslot = cdp_CheckModule (module); + if (bMetaString) + { + if (!modslot) + return "(Error)"; + if (!modslot->info->comments) + return "(Null)"; + } + else if (!modslot) + { + return NULL; + } + return modslot->info->comments; +} + +// load-all and free-all are here temporarily until +// configs are in place +int +cdp_LoadAllModules (void) +{ + uio_DirList *dirList; + int nummods = 0; + int i; + + if (!cdp_inited) + { + fprintf (stderr, "cdp_LoadAllModules(): called when not inited\n"); + return 0; + } + + if (!cdpDir) + return 0; + + fprintf (stderr, "Loading all CDPs...\n"); + + dirList = uio_getDirList (cdpDir, "", CDPEXT, match_MATCH_SUFFIX); + if (!dirList) + return 0; + + for (i = 0; i < dirList->numNames; i++) + { + char modname[PATH_MAX]; + char* pext; + cdp_Module* mod; + + fprintf (stderr, "Loading CDP %s...\n", dirList->names[i]); + strcpy (modname, dirList->names[i]); + pext = strrchr (modname, '.'); + if (pext) // strip extension + *pext = 0; + + mod = cdp_LoadModule (modname); + if (mod) + { + nummods++; + fprintf (stderr, "\tloaded CDP: %s v%s (%s)\n", + cdp_GetModuleName (mod, true), + cdp_GetModuleVersionString (mod, true), + cdp_GetModuleComment (mod, true)); + } + else + { + fprintf (stderr, "\tload failed, error %u\n", + cdp_GetError ()); + } + } + uio_freeDirList (dirList); + + return nummods; +} + +void +cdp_FreeAllModules (void) +{ + cdp_Module* cdp; + + if (!cdp_inited) + { + fprintf (stderr, "cdp_FreeAllModules(): called when not inited\n"); + return; + } + + for (cdp = cdp_modules; cdp->used; ++cdp) + { + if (!cdp->builtin && cdp->hmodule) + cdp_FreeModule (cdp); + } +} diff --git a/src/libs/cdp/cdp.h b/src/libs/cdp/cdp.h new file mode 100644 index 0000000..0390f07 --- /dev/null +++ b/src/libs/cdp/cdp.h @@ -0,0 +1,47 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP library declarations + */ + +#ifndef LIBS_CDP_CDP_H_ +#define LIBS_CDP_CDP_H_ + +#include "types.h" +#include "cdpapi.h" + +// these will be called by the UQM engine +// and plugins manager +bool cdp_Init (void); +void cdp_Uninit (void); +cdp_Error cdp_GetError (void); +cdp_Module* cdp_LoadModule (const char* modname); +void cdp_FreeModule (cdp_Module* module); +// in the following calls when bMetaString is set +// function will never return a NULL, instead it will +// return a valid string -- error meta-string +const char* cdp_GetModuleContext (cdp_Module* module, bool bMetaString); +const char* cdp_GetModuleName (cdp_Module* module, bool bMetaString); +uint32 cdp_GetModuleVersion (cdp_Module* module); +const char* cdp_GetModuleVersionString (cdp_Module* module, bool bMetaString); +const char* cdp_GetModuleComment (cdp_Module* module, bool bMetaString); + +int cdp_LoadAllModules (void); +void cdp_FreeAllModules (void); + +#endif /* LIBS_CDP_CDP_H_ */ diff --git a/src/libs/cdp/cdp_alli.h b/src/libs/cdp/cdp_alli.h new file mode 100644 index 0000000..31520e5 --- /dev/null +++ b/src/libs/cdp/cdp_alli.h @@ -0,0 +1,31 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP All-interface list (for simplicity) + */ + +#ifndef LIBS_CDP_CDP_ALLI_H_ +#define LIBS_CDP_CDP_ALLI_H_ + +#include "cdp_iio.h" +#include "cdp_imem.h" +#include "cdp_isnd.h" +#include "cdp_ivid.h" +// TODO: add more cdp_iXXX.h here as they are defined + +#endif /* LIBS_CDP_CDP_ALLI_H_ */ diff --git a/src/libs/cdp/cdp_iio.h b/src/libs/cdp/cdp_iio.h new file mode 100644 index 0000000..19e6513 --- /dev/null +++ b/src/libs/cdp/cdp_iio.h @@ -0,0 +1,50 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP Unified IO Interface + */ + +#ifndef LIBS_CDP_CDP_IIO_H_ +#define LIBS_CDP_CDP_IIO_H_ + +#include "types.h" +#include "libs/uio.h" + +// CDP IO Interface entry points +typedef struct +{ + uio_Stream* (* fopen) (uio_DirHandle *dir, const char *path, + const char *mode); + int (* fclose) (uio_Stream *stream); + size_t (* fread) (void *buf, size_t size, size_t nmemb, + uio_Stream *stream); + size_t (* fwrite) (const void *buf, size_t size, size_t nmemb, + uio_Stream *stream); + int (* fseek) (uio_Stream *stream, long offset, int whence); + long (* ftell) (uio_Stream *stream); + int (* fflush) (uio_Stream *stream); + int (* feof) (uio_Stream *stream); + int (* ferror) (uio_Stream *stream); + +} cdp_Itf_IoVtbl_v1; + +// the following are for the sake of module writers +typedef cdp_Itf_IoVtbl_v1 cdp_Itf_IoVtbl; +typedef cdp_Itf_IoVtbl cdp_Itf_Io; + +#endif /* LIBS_CDP_CDP_IIO_H_ */ diff --git a/src/libs/cdp/cdp_imem.h b/src/libs/cdp/cdp_imem.h new file mode 100644 index 0000000..7d1b59c --- /dev/null +++ b/src/libs/cdp/cdp_imem.h @@ -0,0 +1,42 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP Memory Interface + */ + +#ifndef LIBS_CDP_CDP_IMEM_H_ +#define LIBS_CDP_CDP_IMEM_H_ + +#include "types.h" +#include "libs/memlib.h" + +// CDP Memory Interface entry points +typedef struct +{ + void* (* malloc) (int size); + void (* free) (void *p); + void* (* calloc) (int size); + void* (* realloc) (void *p, int size); + +} cdp_Itf_MemoryVtbl_v1; + +// the following are for the sake of module writers +typedef cdp_Itf_MemoryVtbl_v1 cdp_Itf_MemoryVtbl; +typedef cdp_Itf_MemoryVtbl cdp_Itf_Memory; + +#endif /* LIBS_CDP_CDP_IMEM_H_ */ diff --git a/src/libs/cdp/cdp_isnd.h b/src/libs/cdp/cdp_isnd.h new file mode 100644 index 0000000..ae4aa94 --- /dev/null +++ b/src/libs/cdp/cdp_isnd.h @@ -0,0 +1,43 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP Sound Interface + */ + +#ifndef LIBS_CDP_CDP_ISND_H_ +#define LIBS_CDP_CDP_ISND_H_ + +#include "types.h" +#include "libs/sound/sound.h" +#include "libs/sound/decoders/decoder.h" + +// CDP Sound Interface entry points +typedef struct +{ + TFB_RegSoundDecoder* (* RegisterDecoder) (const char* fileext, + TFB_SoundDecoderFuncs*); + void (* UnregisterDecoder) (TFB_RegSoundDecoder*); + const TFB_SoundDecoderFuncs* (* LookupDecoder) (const char* fileext); + +} cdp_Itf_SoundVtbl_v1; + +// the following are for the sake of module writers +typedef cdp_Itf_SoundVtbl_v1 cdp_Itf_SoundVtbl; +typedef cdp_Itf_SoundVtbl cdp_Itf_Sound; + +#endif /* LIBS_CDP_CDP_ISND_H_ */ diff --git a/src/libs/cdp/cdp_ivid.h b/src/libs/cdp/cdp_ivid.h new file mode 100644 index 0000000..a9fc0b2 --- /dev/null +++ b/src/libs/cdp/cdp_ivid.h @@ -0,0 +1,43 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP Video Interface + */ + +#ifndef LIBS_CDP_CDP_IVID_H_ +#define LIBS_CDP_CDP_IVID_H_ + +#include "types.h" +#include "libs/video/video.h" +#include "libs/video/videodec.h" + +// CDP Video Interface entry points +typedef struct +{ + TFB_RegVideoDecoder* (* RegisterDecoder) (const char* fileext, + TFB_VideoDecoderFuncs*); + void (* UnregisterDecoder) (TFB_RegVideoDecoder*); + const TFB_VideoDecoderFuncs* (* LookupDecoder) (const char* fileext); + +} cdp_Itf_VideoVtbl_v1; + +// the following are for the sake of module writers +typedef cdp_Itf_VideoVtbl_v1 cdp_Itf_VideoVtbl; +typedef cdp_Itf_VideoVtbl cdp_Itf_Video; + +#endif /* LIBS_CDP_CDP_IVID_H_ */ diff --git a/src/libs/cdp/cdpapi.c b/src/libs/cdp/cdpapi.c new file mode 100644 index 0000000..e50e39d --- /dev/null +++ b/src/libs/cdp/cdpapi.c @@ -0,0 +1,864 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP API definitions + * the API is used by both the engine and modules + */ + +#include "cdp.h" +#include "port.h" +#include "cdpint.h" +#include "uqmversion.h" + +#define MAX_REG_ITFS 255 +#define MAX_REG_EVENTS 1023 + +static cdp_Error cdp_api_error = CDPERR_NONE; + +static uint32 cdp_Host_GetApiVersion (void); +static uint32 cdp_Host_GetVersion (void); +static cdp_Error cdp_Host_GetApiError (void); +static cdp_Itf* cdp_Host_GetItf (const char* name); +static bool cdp_Host_GetItfs (cdp_ItfDef* defs); +static cdp_ItfReg* cdp_Host_RegisterItf (const char* name, + cdp_ApiVersion ver_from, cdp_ApiVersion ver_to, + cdp_Itf*, cdp_Module*); +static void cdp_Host_UnregisterItf (cdp_ItfReg*); +static bool cdp_Host_RegisterItfs (cdp_ItfDef* defs, cdp_Module*); +static void cdp_Host_UnregisterItfs (cdp_ItfDef* defs); +static cdp_Event cdp_Host_GetEvent (const char* name); +static bool cdp_Host_GetEvents (cdp_EventDef* defs); +static cdp_EventReg* cdp_Host_RegisterEvent (const char* name, cdp_Module*); +static void cdp_Host_UnregisterEvent (cdp_EventReg*); +static bool cdp_Host_RegisterEvents (cdp_EventDef* defs, cdp_Module*); +static void cdp_Host_UnregisterEvents (cdp_EventDef* defs); +static bool cdp_Host_SubscribeEvent (cdp_Event, cdp_EventProc, cdp_Module*); +static void cdp_Host_UnsubscribeEvent (cdp_Event, cdp_EventProc); +static bool cdp_Host_SubscribeEvents (cdp_EventDef* defs, cdp_Module*); +static void cdp_Host_UnsubscribeEvents (cdp_EventDef* defs); +static cdp_EventResult cdp_Host_FireEvent (cdp_EventReg*, uint32, void*); + +// Interfaces +cdp_Itf_HostVtbl_v1 cdp_host_itf_v1 = +{ + cdp_Host_GetApiVersion, + cdp_Host_GetVersion, + cdp_Host_GetApiError, + cdp_Host_GetItf, + cdp_Host_GetItfs, + cdp_Host_RegisterItf, + cdp_Host_UnregisterItf, + cdp_Host_RegisterItfs, + cdp_Host_UnregisterItfs, + cdp_Host_GetEvent, + cdp_Host_GetEvents, + cdp_Host_RegisterEvent, + cdp_Host_UnregisterEvent, + cdp_Host_RegisterEvents, + cdp_Host_UnregisterEvents, + cdp_Host_SubscribeEvent, + cdp_Host_UnsubscribeEvent, + cdp_Host_SubscribeEvents, + cdp_Host_UnsubscribeEvents, + cdp_Host_FireEvent, +}; + +cdp_Itf_MemoryVtbl_v1 cdp_memory_itf_v1 = +{ + HMalloc, + HFree, + HCalloc, + HRealloc, +}; + +cdp_Itf_IoVtbl_v1 cdp_io_itf_v1 = +{ + uio_fopen, + uio_fclose, + uio_fread, + uio_fwrite, + uio_fseek, + uio_ftell, + uio_fflush, + uio_feof, + uio_ferror, +}; + +cdp_Itf_SoundVtbl_v1 cdp_sound_itf_v1 = +{ + SoundDecoder_Register, + SoundDecoder_Unregister, + SoundDecoder_Lookup, +}; + +cdp_Itf_VideoVtbl_v1 cdp_video_itf_v1 = +{ + VideoDecoder_Register, + VideoDecoder_Unregister, + VideoDecoder_Lookup, +}; + +// the actual interface registration struct/handle +struct cdp_ItfReg +{ + bool builtin; + bool used; + const char* name; + cdp_ApiVersion ver_from; + cdp_ApiVersion ver_to; + cdp_Itf* itfvtbl; + cdp_Module* module; +}; + +#define CDP_DECLARE_ITF(kind,vf,vt,vtbl) \ + {true, true, CDPITF_KIND_##kind, \ + CDPAPI_VERSION_##vf, CDPAPI_VERSION_##vt, vtbl, NULL} + +// Built-in interfaces + space for loadable +cdp_ItfReg cdp_itfs[MAX_REG_ITFS + 1] = +{ + CDP_DECLARE_ITF (HOST, 1, 1, &cdp_host_itf_v1), + CDP_DECLARE_ITF (MEMORY, 1, 1, &cdp_memory_itf_v1), + CDP_DECLARE_ITF (IO, 1, 1, &cdp_io_itf_v1), + CDP_DECLARE_ITF (SOUND, 1, 1, &cdp_sound_itf_v1), + CDP_DECLARE_ITF (VIDEO, 1, 1, &cdp_video_itf_v1), + // TODO: put newly defined built-in interfaces here + + {false, false, "", 0, 0, NULL} // term +}; + +// event bind descriptor +typedef struct +{ + cdp_EventProc proc; + cdp_Module* module; + +} cdp_EventBind; + +#define EVENT_BIND_GROW 16 + +// the actual event registration struct/handle +struct cdp_EventReg +{ + bool builtin; + bool used; + const char* name; + cdp_EventBind* binds; + uint32 bindslots; + cdp_Module* module; +}; + +#define CDP_DECLARE_EVENT(name) \ + {true, true, "UQM." #name, NULL, 0, NULL} + +// Built-in events + space for loadable +// a cdp_Event handle is an index into this array +cdp_EventReg cdp_evts[MAX_REG_EVENTS + 1] = +{ + // sample - no real events defined yet + CDP_DECLARE_EVENT (PlanetSide.TouchDown), + CDP_DECLARE_EVENT (PlanetSide.LiftOff), + // TODO: put newly defined built-in events here + + {false, false, "", NULL, 0, NULL} // term +}; + +cdp_Error +cdp_GetApiError (void) +{ + cdp_Error ret = cdp_api_error; + cdp_api_error = CDPERR_NONE; + return ret; +} + +bool +cdp_InitApi (void) +{ + int i; + cdp_Module* kernel; + + // preprocess built-in itfs + kernel = cdp_LoadModule (NULL); + + for (i = 0; cdp_itfs[i].builtin; ++i) + { + cdp_itfs[i].module = kernel; + } + // clear the rest + //memset (cdp_itfs + i, 0, + // sizeof (cdp_itfs) - sizeof (cdp_ItfReg) * i); + + for (i = 0; cdp_evts[i].builtin; ++i) + { + cdp_evts[i].module = kernel; + cdp_evts[i].bindslots = 0; + cdp_evts[i].binds = NULL; + } + + return true; +} + +void +cdp_UninitApi (void) +{ + cdp_ItfReg* itf; + + // unregister custom interfaces + for (itf = cdp_itfs; itf->used; ++itf) + { + if (!itf->builtin) + { + itf->used = false; + if (itf->name) + HFree (itf->name); + itf->name = NULL; + itf->itfvtbl = NULL; + itf->module = NULL; + } + } +} + +static uint32 +cdp_Host_GetApiVersion (void) +{ + return CDPAPI_VERSION; +} + +static uint32 +cdp_Host_GetVersion (void) +{ + return (UQM_MAJOR_VERSION << 20) | (UQM_MINOR_VERSION << 15) | + UQM_PATCH_VERSION; +} + +static cdp_Error +cdp_Host_GetApiError (void) +{ + return cdp_GetApiError (); +} + +static char* +cdp_MakeContextName (const char* ctx, const char* name) +{ + int namelen; + char* id_name; + + namelen = strlen(ctx) + strlen(name) + 2; + id_name = HMalloc (namelen); + strcpy(id_name, ctx); + strcat(id_name, "."); + strcat(id_name, name); + + return id_name; +} + +/*********************************************************** + * Interface system * + ***********************************************************/ + +cdp_ItfReg* +cdp_GetInterfaceReg (const char* name, cdp_ApiVersion api_ver) +{ + cdp_ItfReg* itf; + + for (itf = cdp_itfs; itf->used && + (!itf->name || strcasecmp(itf->name, name) != 0 || + api_ver < itf->ver_from || api_ver > itf->ver_to); + itf++) + ; + if (!itf->name) + { + cdp_api_error = CDPERR_NO_ITF; + return NULL; + } + + return itf; +} + +cdp_Itf* +cdp_GetInterface (const char* name, cdp_ApiVersion api_ver) +{ + cdp_ItfReg* reg; + + reg = cdp_GetInterfaceReg (name, api_ver); + return reg ? reg->itfvtbl : NULL; +} + +static cdp_Itf* +cdp_Host_GetItf (const char* name) +{ + return cdp_GetInterface (name, CDPAPI_VERSION_1); +} + +static bool +cdp_Host_GetItfs (cdp_ItfDef* defs) +{ + cdp_ItfDef* def; + cdp_ItfReg* reg; + int errors = 0; + + for (def = defs; def->name; ++def) + { + // registration handle is not returned + def->reg = NULL; + + reg = cdp_GetInterfaceReg (def->name, CDPAPI_VERSION_1); + if (reg) + { + def->itf = reg->itfvtbl; + def->name = reg->name; // set to cannonical name + def->ver_from = reg->ver_from; + def->ver_to = reg->ver_to; + def->module = reg->module; + } + else + { + def->itf = NULL; + def->module = NULL; + def->ver_from = 0; + def->ver_to = 0; + ++errors; + } + } + + return !errors; +} + +static cdp_ItfReg* +cdp_Host_RegisterItf (const char* name, cdp_ApiVersion ver_from, + cdp_ApiVersion ver_to, cdp_Itf* itfvtbl, + cdp_Module* owner) +{ + cdp_ItfReg* itfreg; + cdp_ItfReg* newslot = NULL; + char* id_name; + const char* ctx; + + if (!owner) + { + fprintf (stderr, "cdp_Host_RegisterItf(): " + "No owner info supplied\n"); + //return NULL; + } + if (!name || !*name || !itfvtbl) + { + fprintf (stderr, "cdp_Host_RegisterItf(): " + "Null or invalid interface (from %s)\n", + cdp_GetModuleName (owner, true)); + return NULL; + } + ctx = cdp_GetModuleContext (owner, false); + if (!ctx) + { + fprintf (stderr, "cdp_Host_RegisterItf(): " + "Null or invalid context (from %s)\n", + cdp_GetModuleName (owner, true)); + return NULL; + } + + // TODO: review version policy (below) + // enforce version policy and do not allow obsolete interfaces + // POLICY: all modules MUST be aware of recent API changes and will not + // be allowed to expose interfaces that support and/or utilize obsoleted + // API versions + if (ver_from < CDPAPI_VERSION_MIN) + ver_from = CDPAPI_VERSION_MIN; + if (ver_to < CDPAPI_VERSION_MIN) + { + fprintf (stderr, "cdp_Host_RegisterItf(): " + "Obsolete interface %s (from %s)\n", + name, cdp_GetModuleName (owner, true)); + return NULL; + } + + id_name = cdp_MakeContextName (ctx, name); + + // check if interface already registered + for (itfreg = cdp_itfs; itfreg->used && + (!itfreg->name || strcasecmp(itfreg->name, id_name) != 0 || + ver_from < itfreg->ver_from || ver_to > itfreg->ver_to); + ++itfreg) + { + // and pick up an empty slot (where available) + if (!newslot && !itfreg->name) + newslot = itfreg; + } + + if (itfreg >= cdp_itfs + MAX_REG_ITFS) + { + fprintf (stderr, "cdp_Host_RegisterItf(): " + "Interfaces limit reached\n"); + HFree (id_name); + return NULL; + } + else if (itfreg->name) + { + fprintf (stderr, "cdp_Host_RegisterItf(): " + "Interface %s already registered for these versions, " + "%s denied\n", + name, cdp_GetModuleName (owner, true)); + HFree (id_name); + return NULL; + } + + if (!newslot) + { + newslot = itfreg; + newslot->used = true; + // make next one a term + itfreg[1].builtin = false; + itfreg[1].used = false; + itfreg[1].name = NULL; + itfreg[1].itfvtbl = NULL; + } + + newslot->name = id_name; + newslot->ver_from = ver_from; + newslot->ver_to = ver_to; + newslot->itfvtbl = itfvtbl; + newslot->module = owner; + + return newslot; +} + +static void +cdp_Host_UnregisterItf (cdp_ItfReg* itfreg) +{ + if (itfreg < cdp_itfs || itfreg >= cdp_itfs + MAX_REG_ITFS || + !itfreg->name || !itfreg->itfvtbl) + { + fprintf (stderr, "cdp_Host_UnregisterItf(): " + "Invalid or expired interface passed\n"); + return; + } + + if (!itfreg->builtin) + { + HFree (itfreg->name); + } + itfreg->module = NULL; + itfreg->name = NULL; + itfreg->itfvtbl = NULL; +} + +static bool +cdp_Host_RegisterItfs (cdp_ItfDef* defs, cdp_Module* owner) +{ + cdp_ItfDef* def; + int errors = 0; + + for (def = defs; def->name; ++def) + { + def->reg = cdp_Host_RegisterItf (def->name, def->ver_from, + def->ver_to, def->itf, owner); + if (def->reg) + { + def->module = owner; + } + else + { + def->module = NULL; + ++errors; + } + } + + return !errors; +} + +static void +cdp_Host_UnregisterItfs (cdp_ItfDef* defs) +{ + cdp_ItfDef* def; + + for (def = defs; def->name; ++def) + { + if (def->reg) + cdp_Host_UnregisterItf (def->reg); + } +} + +/*********************************************************** + * Event system * + ***********************************************************/ + +cdp_EventReg* +cdp_GetEventReg (const char* name) +{ + cdp_EventReg* evt; + + for (evt = cdp_evts; evt->used && + (!evt->name || strcasecmp(evt->name, name) != 0); + evt++) + ; + if (!evt->name) + { + cdp_api_error = CDPERR_NO_EVENT; + return NULL; + } + + return evt; +} + +// hopefully inlinable +static cdp_Event +cdp_EventFromReg (cdp_EventReg* reg) +{ + return (reg - cdp_evts) / sizeof (cdp_EventReg); +} + +// hopefully inlinable +static cdp_EventReg* +cdp_RegFromEvent (cdp_Event event) +{ + return cdp_evts + event; +} + +cdp_Event +cdp_GetEvent (const char* name) +{ + cdp_EventReg* reg; + + reg = cdp_GetEventReg (name); + return reg ? cdp_EventFromReg (reg) : CDP_EVENT_INVALID; +} + +static cdp_EventBind* +cdp_AllocEventBinds (cdp_EventBind* binds, uint32 ccur, uint32 cnew) +{ + cdp_EventBind* newbinds; + uint32 newsize; + + newsize = cnew * sizeof (cdp_EventBind); + if (binds) + newbinds = HRealloc (binds, newsize); + else + newbinds = HMalloc (newsize); + + if (cnew > ccur) + memset (newbinds + ccur, 0, + (cnew - ccur) * sizeof (cdp_EventBind)); + + return newbinds; +} + +static cdp_Event +cdp_Host_GetEvent (const char* name) +{ + return cdp_GetEvent (name); +} + +static bool +cdp_Host_GetEvents (cdp_EventDef* defs) +{ + cdp_EventDef* def; + cdp_EventReg* reg; + int errors = 0; + + for (def = defs; def->name; ++def) + { + // registration handle is not returned + def->reg = NULL; + + reg = cdp_GetEventReg (def->name); + if (reg) + { + def->event = cdp_EventFromReg(reg); + def->name = reg->name; // set to cannonical name + def->module = reg->module; + } + else + { + def->event = CDP_EVENT_INVALID; + def->module = NULL; + ++errors; + } + } + + return !errors; +} + +static cdp_EventReg* +cdp_Host_RegisterEvent (const char* name, cdp_Module* owner) +{ + cdp_EventReg* evtreg; + cdp_EventReg* newslot = NULL; + char* id_name; + const char* ctx; + + if (!owner) + { + fprintf (stderr, "cdp_Host_RegisterEvent(): " + "No owner info supplied\n"); + //return NULL; + } + if (!name || !*name) + { + fprintf (stderr, "cdp_Host_RegisterEvent(): " + "Null or invalid event (from %s)\n", + cdp_GetModuleName (owner, true)); + return NULL; + } + ctx = cdp_GetModuleContext (owner, false); + if (!ctx) + { + fprintf (stderr, "cdp_Host_RegisterEvent(): " + "Null or invalid context (from %s)\n", + cdp_GetModuleName (owner, true)); + return NULL; + } + + id_name = cdp_MakeContextName (ctx, name); + + // check if event already registered + for (evtreg = cdp_evts; evtreg->used && + (!evtreg->name || strcasecmp(evtreg->name, id_name) != 0); + ++evtreg) + { + // and pick up an empty slot (where available) + if (!newslot && !evtreg->name) + newslot = evtreg; + } + + if (evtreg >= cdp_evts + MAX_REG_EVENTS) + { + fprintf (stderr, "cdp_Host_RegisterEvent(): " + "Event limit reached\n"); + HFree (id_name); + return NULL; + } + else if (evtreg->name) + { + fprintf (stderr, "cdp_Host_RegisterEvent(): " + "Event %s already registered, " + "%s denied\n", + name, cdp_GetModuleName (owner, true)); + HFree (id_name); + return NULL; + } + + if (!newslot) + { + newslot = evtreg; + newslot->used = true; + // make next one a term + evtreg[1].builtin = false; + evtreg[1].used = false; + evtreg[1].name = NULL; + } + + newslot->name = id_name; + newslot->module = owner; + newslot->binds = NULL; + newslot->bindslots = 0; + + return newslot; +} + +static void +cdp_Host_UnregisterEvent (cdp_EventReg* evtreg) +{ + if (evtreg < cdp_evts || evtreg >= cdp_evts + MAX_REG_EVENTS || + !evtreg->name) + { + fprintf (stderr, "cdp_Host_UnregisterEvent(): " + "Invalid or expired event passed\n"); + return; + } + + if (!evtreg->builtin) + { + HFree (evtreg->name); + } + evtreg->module = NULL; + evtreg->name = NULL; + if (evtreg->binds) + HFree (evtreg->binds); + evtreg->binds = NULL; + evtreg->bindslots = 0; +} + +static bool +cdp_Host_RegisterEvents (cdp_EventDef* defs, cdp_Module* owner) +{ + cdp_EventDef* def; + int errors = 0; + + for (def = defs; def->name; ++def) + { + def->reg = cdp_Host_RegisterEvent (def->name, owner); + if (def->reg) + { + def->module = owner; + } + else + { + def->module = NULL; + ++errors; + } + } + + return !errors; +} + +static void +cdp_Host_UnregisterEvents (cdp_EventDef* defs) +{ + cdp_EventDef* def; + + for (def = defs; def->name; ++def) + { + if (def->reg) + cdp_Host_UnregisterEvent (def->reg); + } +} + +static bool +cdp_Host_SubscribeEvent (cdp_Event event, cdp_EventProc proc, cdp_Module* module) +{ + cdp_EventReg* reg = cdp_RegFromEvent (event); + cdp_EventBind* bind = NULL; + uint32 i; + + if (reg < cdp_evts || reg >= cdp_evts + MAX_REG_EVENTS || + !reg->name) + { + fprintf (stderr, "cdp_Host_SubscribeEvent(): " + "Invalid or expired event passed\n"); + return false; + } + + if (reg->binds) + { + // check for duplicate or find a new slot + for (i = 0, bind = reg->binds; i < reg->bindslots && + (!bind->proc || bind->proc != proc); + ++i, ++bind) + ; + if (i >= reg->bindslots) + { // full - add more slots + reg->binds = cdp_AllocEventBinds (reg->binds, + reg->bindslots, reg->bindslots + EVENT_BIND_GROW); + bind = reg->binds + reg->bindslots; + reg->bindslots += EVENT_BIND_GROW; + } + else if (bind->proc == proc) + { // already bound + return true; + } + } + else + { + reg->binds = cdp_AllocEventBinds (NULL, 0, EVENT_BIND_GROW); + reg->bindslots = EVENT_BIND_GROW; + bind = reg->binds; + } + + bind->proc = proc; + bind->module = module; + + return true; +} + +static void +cdp_Host_UnsubscribeEvent (cdp_Event event, cdp_EventProc proc) +{ + cdp_EventReg* reg = cdp_RegFromEvent (event); + cdp_EventBind* bind = NULL; + uint32 i; + + if (reg < cdp_evts || reg >= cdp_evts + MAX_REG_EVENTS || + !reg->name) + { // event either expired or invalid + return; + } + + if (!reg->binds || !reg->bindslots) + return; // hmm, no bindings + + // check for duplicate or find a new slot + for (i = 0, bind = reg->binds; i < reg->bindslots && + bind->proc != proc; + ++i, ++bind) + ; + if (i >= reg->bindslots) + return; // binding not found + + bind->proc = NULL; + bind->module = NULL; +} + +static bool +cdp_Host_SubscribeEvents (cdp_EventDef* defs, cdp_Module* module) +{ + cdp_EventDef* def; + int errors = 0; + + for (def = defs; def->name; ++def) + { + if (def->event != CDP_EVENT_INVALID && def->proc) + if (!cdp_Host_SubscribeEvent (def->event, def->proc, module)) + ++errors; + } + return !errors; +} + +static void +cdp_Host_UnsubscribeEvents (cdp_EventDef* defs) +{ + cdp_EventDef* def; + + for (def = defs; def->name; ++def) + { + if (def->event != CDP_EVENT_INVALID && def->proc) + cdp_Host_UnsubscribeEvent (def->event, def->proc); + } +} + +static cdp_EventResult +cdp_Host_FireEvent (cdp_EventReg* evtreg, uint32 iparam, void* pparam) +{ + bool bHandled = false; + cdp_EventResult ret = 0; + cdp_Event event; + cdp_EventBind* bind; + uint32 i; + + if (evtreg < cdp_evts || evtreg >= cdp_evts + MAX_REG_EVENTS || + !evtreg->name) + { +#ifdef DEBUG + fprintf (stderr, "cdp_Host_FireEvent(): Invalid event\n"); +#endif + return 0; + } + + if (!evtreg->binds) + return 0; // no subscribers + + event = cdp_EventFromReg (evtreg); + + // call event procs in opposite order of binding + for (i = evtreg->bindslots, bind = evtreg->binds + i - 1; + !bHandled && i > 0; + --i, --bind) + { + if (bind->proc) + ret = bind->proc (event, iparam, pparam, &bHandled); + } + return ret; +} diff --git a/src/libs/cdp/cdpapi.h b/src/libs/cdp/cdpapi.h new file mode 100644 index 0000000..e1a0e0b --- /dev/null +++ b/src/libs/cdp/cdpapi.h @@ -0,0 +1,154 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP API declarations + * the API is used by both the engine and modules + */ + +#ifndef LIBS_CDP_CDPAPI_H_ +#define LIBS_CDP_CDPAPI_H_ + +#include "types.h" + +typedef enum +{ + CDPAPI_VERSION_1 = 0x00000001, // version 0.1 + + CDPAPI_VERSION = CDPAPI_VERSION_1, + CDPAPI_VERSION_MIN = CDPAPI_VERSION_1, + +} cdp_ApiVersion; + +typedef enum +{ + CDPERR_NONE = 0, + CDPERR_UKNOWN = 1, + CDPERR_NOT_FOUND = 2, + CDPERR_BAD_MODULE = 3, + CDPERR_OLD_VER = 4, + CDPERR_UNKNOWN_VER = 5, + CDPERR_TOO_MANY = 6, + CDPERR_INIT_FAILED = 7, + CDPERR_NO_ITF = 8, + CDPERR_DUPE_ITF = 9, + CDPERR_NO_EVENT = 10, + CDPERR_DUPE_EVENT = 11, + CDPERR_OTHER = 1000, + +} cdp_Error; + +typedef struct cdp_Module cdp_Module; +typedef void cdp_Itf; +typedef struct cdp_ItfReg cdp_ItfReg; + +// Interface KINDs - for convinience and uniformity +#define CDPITF_KIND_INVALID NULL +#define CDPITF_KIND_HOST "UQM.Host" +#define CDPITF_KIND_MEMORY "UQM.Memory" +#define CDPITF_KIND_IO "UQM.IO" +#define CDPITF_KIND_THREADS "UQM.Threads" +#define CDPITF_KIND_TIME "UQM.Time" +#define CDPITF_KIND_INPUT "UQM.Input" +#define CDPITF_KIND_TASK "UQM.Task" +#define CDPITF_KIND_RESOURCE "UQM.Resource" +#define CDPITF_KIND_SOUND "UQM.Sound" +#define CDPITF_KIND_VIDEO "UQM.Video" +#define CDPITF_KIND_GFX "UQM.Gfx" +#define CDPITF_KIND_MIXER "UQM.Mixer" + +// Interface definition structure +// pass an array of these to Host->GetItfs() for batch lookup +// pass an array of these to Host->RegisterItfs() for batch registration +typedef struct +{ + // fill in the first 4 members for batch registration + // fill in the 1st member for batch lookup + // terminate an array of these defs with name == NULL + const char* name; // interface ID + cdp_Itf* itf; // interface pointer + cdp_ApiVersion ver_from; // lowest supported version + cdp_ApiVersion ver_to; // highest supported version + + cdp_Module* module; // owner module + // the following member is only set during registration + cdp_ItfReg* reg; // registration handle (not set on lookup) + +} cdp_ItfDef; + +typedef unsigned int cdp_Event; +typedef struct cdp_EventReg cdp_EventReg; +typedef intptr_t cdp_EventResult; + +#define CDP_EVENT_INVALID (-1) + // used with cdp_Event + +typedef cdp_EventResult (* cdp_EventProc) + (cdp_Event, uint32, void*, bool* pbHandled); + +// Event definition structure +// pass an array of these to Host->GetItfs() for batch lookup +typedef struct +{ + // fill in the 1st member for batch lookup or registration + // also fill in the 2nd member for batch subscription + // terminate an array of these defs with name == NULL + const char* name; // event ID + cdp_EventProc proc; // event proc, set to NULL for no bind + + cdp_Event event; // subscribable event handle + cdp_Module* module; // owner module + // the following member is only set during registration + cdp_EventReg* reg; // registration handle (not set on lookup) + +} cdp_EventDef; + +// Host Interface +// the main itf of the API, it is passed to a loaded module +// module does everything else through this itf and itfs +// acquired through this itf +typedef struct +{ + uint32 (* GetApiVersion) (void); + uint32 (* GetVersion) (void); + cdp_Error (* GetApiError) (void); + cdp_Itf* (* GetItf) (const char* name); + bool (* GetItfs) (cdp_ItfDef* defs); + cdp_ItfReg* (* RegisterItf) (const char* name, + cdp_ApiVersion ver_from, cdp_ApiVersion ver_to, + cdp_Itf*, cdp_Module*); + void (* UnregisterItf) (cdp_ItfReg*); + bool (* RegisterItfs) (cdp_ItfDef* defs, cdp_Module*); + void (* UnregisterItfs) (cdp_ItfDef* defs); + cdp_Event (* GetEvent) (const char* name); + bool (* GetEvents) (cdp_EventDef* defs); + cdp_EventReg* (* RegisterEvent) (const char* name, cdp_Module*); + void (* UnregisterEvent) (cdp_EventReg*); + bool (* RegisterEvents) (cdp_EventDef* defs, cdp_Module*); + void (* UnregisterEvents) (cdp_EventDef* defs); + bool (* SubscribeEvent) (cdp_Event, cdp_EventProc, cdp_Module*); + void (* UnsubscribeEvent) (cdp_Event, cdp_EventProc); + bool (* SubscribeEvents) (cdp_EventDef* defs, cdp_Module*); + void (* UnsubscribeEvents) (cdp_EventDef* defs); + cdp_EventResult (* FireEvent) (cdp_EventReg*, uint32, void*); + +} cdp_Itf_HostVtbl_v1; + +typedef cdp_Itf_HostVtbl_v1 cdp_Itf_HostVtbl; +typedef cdp_Itf_HostVtbl cdp_Itf_Host; + +#endif /* LIBS_CDP_CDPAPI_H_ */ diff --git a/src/libs/cdp/cdpint.h b/src/libs/cdp/cdpint.h new file mode 100644 index 0000000..fbc3238 --- /dev/null +++ b/src/libs/cdp/cdpint.h @@ -0,0 +1,48 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP common internal definitions + */ + +#ifndef LIBS_CDP_CDPINT_H_ +#define LIBS_CDP_CDPINT_H_ + +#include "cdpapi.h" +#include "cdp_imem.h" +#include "cdp_iio.h" +#include "cdp_isnd.h" +#include "cdp_ivid.h" + +#ifdef WIN32 +# define CDPEXT ".dll" +#else +# define CDPEXT ".so" +#endif + +extern cdp_Itf_HostVtbl_v1 cdp_host_itf_v1; +extern cdp_Itf_MemoryVtbl_v1 cdp_memory_itf_v1; +extern cdp_Itf_IoVtbl_v1 cdp_io_itf_v1; +extern cdp_Itf_SoundVtbl_v1 cdp_sound_itf_v1; + +bool cdp_InitApi (void); +void cdp_UninitApi (void); +cdp_Error cdp_GetApiError (void); +cdp_Itf* cdp_GetInterface (const char* name, cdp_ApiVersion); +cdp_ItfReg* cdp_GetInterfaceReg (const char* name, cdp_ApiVersion); + +#endif /* _CDPISND_H */ diff --git a/src/libs/cdp/cdpmod.h b/src/libs/cdp/cdpmod.h new file mode 100644 index 0000000..a1fdcae --- /dev/null +++ b/src/libs/cdp/cdpmod.h @@ -0,0 +1,92 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP module definitions + * all CDP modules should #include this .h + */ + +#ifndef LIBS_CDP_CDPMOD_H_ +#define LIBS_CDP_CDPMOD_H_ + +#include "types.h" +#include "cdpapi.h" + +#define CDP_INFO_SYM cdpmodinfo +#define CDP_INFO_SYM_NAME "cdpmodinfo" + +// this struct will be exported from the module +// under 'cdpmodinfo' +typedef struct +{ + // mandatory, version control + uint32 size; // size of this structure + cdp_ApiVersion api_ver; // version of cdp API used, set to CDPAPI_VERSION + uint16 ver_major; // module version, somewhat informational + uint16 ver_minor; + uint16 ver_patch; + uint16 host_ver_major; // minimum host version required, purely informational + uint16 host_ver_minor; + uint16 host_ver_patch; + + // reserved members: set all to 0 or use CDP_MODINFO_RESERVED1 + uint32 _32_reserved1; + uint32 _32_reserved2; + uint32 _32_reserved3; + uint32 _32_reserved4; + + const char* context_name; + // cannonical context name (in proper case) + // this context will be used with all exposed objects + // English preferred; try to keep it below 32 chars + // informational, human-only; these fields have no real size + // restriction other than to keep it reasonable + const char* name; // descriptive name + const char* ver_string; // descriptive version + const char* author; // go nuts + const char* url; // go nuts + const char* comments; // go nuts + + // reserved members, set all to 0 or use CDP_MODINFO_RESERVED2 + const char* _sz_reserved1; + const char* _sz_reserved2; + const char* _sz_reserved3; + const char* _sz_reserved4; + + // mandatory, CDP entry points + // TODO: decide whether more EPs are necessary and if not move + // EPs above info-string members, abolishing _sz_reservedX + bool (* module_init) (cdp_Module* module, cdp_Itf_Host* hostitf); + void (* module_term) (); + +} cdp_ModuleInfo; + +#define CDP_MODINFO_RESERVED1 0,0,0,0 +#define CDP_MODINFO_RESERVED2 0,0,0,0 + +// the following is defined via the last mandatory member +#define CDP_MODINFO_MIN_SIZE \ + ( ((uint32) &((cdp_ModuleInfo*)0)->module_term) + \ + sizeof (((cdp_ModuleInfo*)0)->module_term) ) + +#if defined(WIN32) +# define CDPEXPORT __declspec(dllexport) +#else +# define CDPEXPORT +#endif + +#endif /* LIBS_CDP_CDPMOD_H_ */ diff --git a/src/libs/cdp/windl.c b/src/libs/cdp/windl.c new file mode 100644 index 0000000..4bc76f6 --- /dev/null +++ b/src/libs/cdp/windl.c @@ -0,0 +1,76 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP dlopen() & Co. WIN32 implementation + */ + +#include "windl.h" +#include "port.h" +#define WIN32_LEAN_AND_MEAN +//#include +#include +#include +#include + +static uint32 wdl_last_error = 0; +static char wdl_errstr[128] = ""; + +void* +dlopen (const char *filename, int flag) + // all defined flags are not possible on win32 +{ + HMODULE hlib; + + if (filename == NULL) + hlib = GetModuleHandleA(NULL); + else + hlib = LoadLibraryA (filename); + + if (!hlib) + wdl_last_error = GetLastError (); + + return hlib; +} + +void* +dlsym (void *handle, const char *symbol) +{ + void* ptr = GetProcAddress (handle, symbol); + if (!ptr) + wdl_last_error = GetLastError (); + return ptr; +} + +int +dlclose (void *handle) +{ + return FreeLibrary (handle); +} + +char* +dlerror (void) +{ + if (wdl_last_error) + { + sprintf (wdl_errstr, "Windows error %u", wdl_last_error); + wdl_last_error = 0; + return wdl_errstr; + } + else + return NULL; +} diff --git a/src/libs/cdp/windl.h b/src/libs/cdp/windl.h new file mode 100644 index 0000000..71ca240 --- /dev/null +++ b/src/libs/cdp/windl.h @@ -0,0 +1,37 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * CDP dlopen() & Co. WIN32 implementation + */ + +#ifndef LIBS_CDP_WINDL_H_ +#define LIBS_CDP_WINDL_H_ + +#include "types.h" + +extern void *dlopen (const char *filename, int flag); +extern void *dlsym (void *handle, const char *symbol); +extern int dlclose (void *handle); +extern char *dlerror (void); + +/* these dlopen() flags are meaningless on win32 */ +#define RTLD_LAZY 1 /* lazy function call binding */ +#define RTLD_NOW 2 /* immediate function call binding */ +#define RTLD_GLOBAL 4 /* symbols in this dlopen'ed obj are visible to other dlopen'ed objs */ + +#endif /* LIBS_CDP_WINDL_H_ */ diff --git a/src/libs/cdplib.h b/src/libs/cdplib.h new file mode 100644 index 0000000..83d6a69 --- /dev/null +++ b/src/libs/cdplib.h @@ -0,0 +1,32 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_CDPLIB_H_ +#define LIBS_CDPLIB_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "cdp/cdp.h" + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_CDPLIB_H_ */ diff --git a/src/libs/compiler.h b/src/libs/compiler.h new file mode 100644 index 0000000..a53f779 --- /dev/null +++ b/src/libs/compiler.h @@ -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. + */ + +#ifndef LIBS_COMPILER_H_ +#define LIBS_COMPILER_H_ + +#include "types.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef uint8 BYTE; +typedef uint8 UBYTE; +typedef sint8 SBYTE; +typedef uint16 UWORD; +typedef sint16 SWORD; +typedef uint32 DWORD; +typedef sint32 SDWORD; + +typedef UWORD COUNT; +typedef SWORD SIZE; + +typedef char UNICODE; + + +typedef enum +{ + FALSE = 0, + TRUE +} BOOLEAN; + +typedef void (*PVOIDFUNC) (void); +typedef BOOLEAN (*PBOOLFUNC) (void); +typedef BYTE (*PBYTEFUNC) (void); +typedef UWORD (*PUWORDFUNC) (void); +typedef SWORD (*PSWORDFUNC) (void); +typedef DWORD (*PDWORDFUNC) (void); + +#define MAKE_BYTE(lo, hi) ((BYTE) (((BYTE) (hi) << (BYTE) 4) | (BYTE) (lo))) +#define LONIBBLE(x) ((BYTE) ((BYTE) (x) & (BYTE) 0x0F)) +#define HINIBBLE(x) ((BYTE) ((BYTE) (x) >> (BYTE) 4)) +#define MAKE_WORD(lo, hi) ((UWORD) ((BYTE) (hi) << 8) | (BYTE) (lo)) +#define LOBYTE(x) ((BYTE) ((UWORD) (x))) +#define HIBYTE(x) ((BYTE) ((UWORD) (x) >> 8)) +#define MAKE_DWORD(lo, hi) (((DWORD) (hi) << 16) | (UWORD) (lo)) +#define LOWORD(x) ((UWORD) ((DWORD) (x))) +#define HIWORD(x) ((UWORD) ((DWORD) (x) >> 16)) + + +// To be moved to port.h: +// _ALIGNED_ANY specifies an alignment suitable for any type +// _ALIGNED_ON specifies a caller-supplied alignment (should be a power of 2) +#if defined(__GNUC__) +# define _PACKED __attribute__((packed)) +# define _ALIGNED_ANY __attribute__((aligned)) +# define _ALIGNED_ON(bytes) __attribute__((aligned(bytes))) +#elif defined(_MSC_VER) +# define _ALIGNED_ANY +//# define _ALIGNED_ON(bytes) __declspec(align(bytes)) + // __declspec(align(bytes)) expects a constant. 'sizeof (type)' + // will not do. This is something that needs some attention, + // once we find someone with a 64 bits Windows machine. + // Leaving it alone for now. +# define _PACKED +# define _ALIGNED_ON(bytes) +#elif defined(__ARMCC__) +# define _PACKED __attribute__((packed)) +# define _ALIGNED_ANY __attribute__((aligned)) +# define _ALIGNED_ON(bytes) __attribute__((aligned(bytes))) +#elif defined(__WINSCW__) +# define _PACKED +# define _ALIGNED_ANY +# define _ALIGNED_ON(bytes) +#endif + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_COMPILER_H_ */ diff --git a/src/libs/declib.h b/src/libs/declib.h new file mode 100644 index 0000000..80d767c --- /dev/null +++ b/src/libs/declib.h @@ -0,0 +1,57 @@ +//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 LIBS_DECLIB_H_ +#define LIBS_DECLIB_H_ + +#include "libs/compiler.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct _LZHCODE_DESC* DECODE_REF; + +enum +{ + FILE_STREAM = 0, + MEMORY_STREAM +}; +typedef BYTE STREAM_TYPE; + +enum +{ + STREAM_READ = 0, + STREAM_WRITE +}; +typedef BYTE STREAM_MODE; + +extern DECODE_REF copen (void *InStream, STREAM_TYPE SType, + STREAM_MODE SMode); +extern DWORD cclose (DECODE_REF DecodeRef); +extern void cfilelength (DECODE_REF DecodeRef, DWORD *pfilelen); +extern COUNT cread (void *pStr, COUNT size, COUNT count, + DECODE_REF DecodeRef); +extern COUNT cwrite (const void *pStr, COUNT size, COUNT count, + DECODE_REF DecodeRef); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_DECLIB_H_ */ diff --git a/src/libs/decomp/Makeinfo b/src/libs/decomp/Makeinfo new file mode 100644 index 0000000..699a24e --- /dev/null +++ b/src/libs/decomp/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="lzdecode.c lzencode.c update.c" +uqm_HFILES="lzh.h" diff --git a/src/libs/decomp/lzdecode.c b/src/libs/decomp/lzdecode.c new file mode 100644 index 0000000..3b64a90 --- /dev/null +++ b/src/libs/decomp/lzdecode.c @@ -0,0 +1,415 @@ +//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. + */ + + /* + * LZHUF.C English version 1.0 + * Based on Japanese version 29-NOV-1988 + * LZSS coded by Haruhiko OKUMURA + * Adaptive Huffman Coding coded by Haruyasu YOSHIZAKI + * Edited and translated to English by Kenji RIKITAKE + */ + +#include +#include +#include +#include +#include "lzh.h" +#include "libs/reslib.h" + +PLZHCODE_DESC _lpCurCodeDesc; +STREAM_TYPE _StreamType; +BYTE* _Stream; +UWORD _workbuf; +BYTE _workbuflen; + +/* get one bit */ +static SWORD +GetBit (void) +{ + SWORD i; + + while (_workbuflen <= 8) + { + if ((i = InChar ()) < 0) + i = 0; + _workbuf |= i << (8 - _workbuflen); + _workbuflen += 8; + } + i = (_workbuf & 0xFFFF) >> (16 - 1); + _workbuf = (_workbuf << 1) & 0xFFFF; + _workbuflen--; + + return (i); +} + +static UWORD +GetBits (BYTE num_bits) +{ + SWORD i; + + while (_workbuflen <= 8) + { + if ((i = InChar ()) < 0) + i = 0; + _workbuf |= i << (8 - _workbuflen); + _workbuflen += 8; + } + i = (_workbuf & 0xFFFF) >> (16 - num_bits); + _workbuf = (_workbuf << num_bits) & 0xFFFF; + _workbuflen -= num_bits; + + return (i); +} + +/* initialize freq tree */ + +void +StartHuff (void) +{ + COUNT i, j; + + for (i = 0; i < N_CHAR; i++) + { + _lpCurCodeDesc->freq[i] = 1; + _lpCurCodeDesc->son[i] = i + T; + _lpCurCodeDesc->prnt[i + T] = i; + } + i = 0; j = N_CHAR; + while (j <= R) + { + _lpCurCodeDesc->freq[j] = _lpCurCodeDesc->freq[i] + _lpCurCodeDesc->freq[i + 1]; + _lpCurCodeDesc->son[j] = i; + _lpCurCodeDesc->prnt[i] = _lpCurCodeDesc->prnt[i + 1] = j; + i += 2; j++; + } + _lpCurCodeDesc->freq[T] = 0xffff; + _lpCurCodeDesc->prnt[R] = 0; +} + +DECODE_REF +copen (void *InStream, STREAM_TYPE SType, STREAM_MODE SMode) +{ + DWORD StreamLength; + + _StreamType = SType; + _Stream = InStream; + if (SMode == STREAM_WRITE) /* writing */ + { + OutChar (0); /* skip future StreamLength */ + OutChar (0); + OutChar (0); + OutChar (0); + + StreamLength = 0; + } + else /* reading */ + { + BYTE lobyte, hibyte; + UWORD loword, hiword; + + lobyte = (BYTE)InChar (); + hibyte = (BYTE)InChar (); + loword = MAKE_WORD (lobyte, hibyte); + lobyte = (BYTE)InChar (); + hibyte = (BYTE)InChar (); + hiword = MAKE_WORD (lobyte, hibyte); + + StreamLength = MAKE_DWORD (loword, hiword); + } + + if (StreamLength == 0xFFFFFFFF + || (_lpCurCodeDesc = AllocCodeDesc ()) == NULL) + { + FreeCodeDesc (_lpCurCodeDesc); + _lpCurCodeDesc = NULL; + } + else + { + _lpCurCodeDesc->Stream = _Stream; + _lpCurCodeDesc->StreamType = _StreamType; + _lpCurCodeDesc->StreamMode = SMode; + _lpCurCodeDesc->StreamLength = StreamLength; + _lpCurCodeDesc->buf_index = N - F; + memset (&_lpCurCodeDesc->text_buf[0], ' ', N - F); + + StartHuff (); + } + + return ((DECODE_REF)_lpCurCodeDesc); +} + +DWORD +cclose (PLZHCODE_DESC lpCodeDesc) +{ + _lpCurCodeDesc = lpCodeDesc; + if (_lpCurCodeDesc) + { + DWORD StreamIndex; + + if (_lpCurCodeDesc->CleanupFunc) + (*_lpCurCodeDesc->CleanupFunc) (); + + StreamIndex = lpCodeDesc->StreamIndex; + FreeCodeDesc (lpCodeDesc); + _lpCurCodeDesc = NULL; + + return (StreamIndex); + } + + return (0); +} + +void +cfilelength (PLZHCODE_DESC lpCodeDesc, DWORD *pfilelen) +{ + if (lpCodeDesc == 0) + *pfilelen = 0; + else + *pfilelen = lpCodeDesc->StreamLength; +} + + /* decoder table */ +static const BYTE d_code[256] = +{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, + 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, + 0x0E, 0x0E, 0x0E, 0x0E, 0x0F, 0x0F, 0x0F, 0x0F, + 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, + 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, + 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, + 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, + 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1A, 0x1B, 0x1B, + 0x1C, 0x1C, 0x1D, 0x1D, 0x1E, 0x1E, 0x1F, 0x1F, + 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, + 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x27, 0x27, + 0x28, 0x28, 0x29, 0x29, 0x2A, 0x2A, 0x2B, 0x2B, + 0x2C, 0x2C, 0x2D, 0x2D, 0x2E, 0x2E, 0x2F, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, +}; +static const BYTE d_len[256] = +{ + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, +}; + + /* decode upper 6 bits from given table */ +#define DecodePosition(p) \ +{ \ + while (_workbuflen <= 8) \ + { \ + *(p) = InChar (); \ + _workbuf |= *(p) << (8 - _workbuflen); \ + _workbuflen += 8; \ + } \ + *(p) = HIBYTE (_workbuf); \ + _workbuf = (_workbuf << 8) & 0xFFFF; \ + _workbuflen -= 8; \ + \ + /* input lower 6 bits directly */ \ + j = d_len[*(p)]; \ + *(p) = ((UWORD)d_code[*(p)] << 6) \ + | (((*(p) << j) | GetBits (j)) & 0x3f); \ +} + + /* start searching tree from the root to leaves. + * choose node #(son[]) if input bit == 0 + * else choose #(son[]+1) (input bit == 1) + */ +#define DecodeChar(c) \ +{ \ + for (*(c) = lpCodeDesc->son[R]; \ + *(c) < T; \ + *(c) = lpCodeDesc->son[*(c) + GetBit ()]) \ + ; \ + _update (*(c)); \ + *(c) -= T; \ +} + +COUNT +cread (void *buf, COUNT size, COUNT count, PLZHCODE_DESC lpCodeDesc) +{ + COUNT r, j, i; + BYTE *lpStr; + + if ((_lpCurCodeDesc = lpCodeDesc) == 0) + return (0); + + size *= count; + if (lpCodeDesc->StreamIndex + size > lpCodeDesc->StreamLength) + { + size /= count; + count = (COUNT)((lpCodeDesc->StreamLength + - lpCodeDesc->StreamIndex) / size); + + size *= count; + } + + if (size == 0) + return (0); + + lpStr = (BYTE*)buf; + _StreamType = lpCodeDesc->StreamType; + + _Stream = lpCodeDesc->Stream; + _workbuf = lpCodeDesc->workbuf; + _workbuflen = lpCodeDesc->workbuflen; + + lpCodeDesc->StreamIndex += size; + r = lpCodeDesc->buf_index; + j = lpCodeDesc->bytes_left; + if (j) + { + lpCodeDesc->bytes_left = 0; + i = lpCodeDesc->restart_index; + + goto ReenterRun; + } + + do + { + COUNT c; + + DecodeChar (&c); + + if (c < 256) + { + size--; + + *lpStr++ = lpCodeDesc->text_buf[r++ & (N - 1)] = (BYTE)c; + } + else + { + COUNT copy_size; + + //i is a COUNT; + DecodePosition(&i); + i = r - i - 1; + j = c - 255 + THRESHOLD; +ReenterRun: + if (j > size) + { + lpCodeDesc->bytes_left = j - size; + lpCodeDesc->restart_index = i + size; + j = size; + } + + size -= j; + do + { + COUNT loc_size; + + i &= (N - 1); + r &= (N - 1); + if ((i < r && i + j > r) || (i > r && i + j > r + N)) + copy_size = (r - i) & (N - 1); + else if ((copy_size = j) > N) + copy_size = N; + + loc_size = copy_size; + if (i + loc_size > N) + { + COUNT k; + + k = N - i; + memcpy (lpStr, &lpCodeDesc->text_buf[i], k); + lpStr += k; + loc_size -= k; + i = 0; + } + + memcpy (lpStr, &lpCodeDesc->text_buf[i], loc_size); + lpStr += loc_size; + i += loc_size; + + lpStr -= copy_size; + + loc_size = copy_size; + if (r + loc_size > N) + { + COUNT k; + + k = N - r; + memcpy (&lpCodeDesc->text_buf[r], lpStr, k); + lpStr += k; + loc_size -= k; + r = 0; + } + + memcpy (&lpCodeDesc->text_buf[r], lpStr, loc_size); + lpStr += loc_size; + r += loc_size; + } while (j -= copy_size); + } + } while (size); + + lpCodeDesc->buf_index = r; + lpCodeDesc->Stream = _Stream; + lpCodeDesc->workbuf = _workbuf; + lpCodeDesc->workbuflen = _workbuflen; + + return (count); +} + diff --git a/src/libs/decomp/lzencode.c b/src/libs/decomp/lzencode.c new file mode 100644 index 0000000..e26f344 --- /dev/null +++ b/src/libs/decomp/lzencode.c @@ -0,0 +1,468 @@ +//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. + */ + + /* + * LZHUF.C English version 1.0 + * Based on Japanese version 29-NOV-1988 + * LZSS coded by Haruhiko OKUMURA + * Adaptive Huffman Coding coded by Haruyasu YOSHIZAKI + * Edited and translated to English by Kenji RIKITAKE + */ + +#include +#include "lzh.h" +#include "libs/reslib.h" + +static UWORD match_position, match_length; +static SWORD *lson; +static SWORD *rson; +static SWORD *dad; +static SWORD *encode_arrays; + +#define AllocEncodeArrays() \ + HCalloc ( \ + (((N + 1) + (N + 257) + (N + 1)) \ + * sizeof (lson[0]))) +#define FreeCodeArrays HFree + +static BOOLEAN +InitTree (void) +{ + if ((encode_arrays = AllocEncodeArrays ()) == NULL) + { + FreeCodeArrays (encode_arrays); + encode_arrays = NULL; + return (FALSE); + } + else + { + SWORD i; + + lson = encode_arrays; + rson = lson + (N + 1); + dad = rson + (N + 257); + + for (i = N + 1; i <= N + 256; i++) + rson[i] = NIL; /* root */ + for (i = 0; i < N; i++) + dad[i] = NIL; /* node */ + + return (TRUE); + } +} + +static void +InsertNode (SWORD r) +{ + SWORD p, cmp; + BYTE *lpBuf; + + cmp = 1; + lpBuf = _lpCurCodeDesc->text_buf; + p = N + 1 + lpBuf[r]; + rson[r] = lson[r] = NIL; + match_length = 0; + for (;;) + { + UWORD i; + + if (cmp >= 0) + { + if (rson[p] != NIL) + p = rson[p]; + else + { + rson[p] = r; + dad[r] = p; + return; + } + } + else + { + if (lson[p] != NIL) + p = lson[p]; + else + { + lson[p] = r; + dad[r] = p; + return; + } + } + + i = F; + { + SWORD _r, _p; + + _r = r; + _p = p; + while (--i && (cmp = lpBuf[++_r] - lpBuf[++_p]) == 0) + ; + } + if ((i = F - i) > THRESHOLD) + { + if (i > match_length) + { + match_position = ((r - p) & (N - 1)) - 1; + if ((match_length = i) >= F) + break; + } + else if (i == match_length) + { + if ((i = ((r - p) & (N - 1)) - 1) < match_position) + { + match_position = i; + } + } + } + } + dad[r] = dad[p]; + lson[r] = lson[p]; + rson[r] = rson[p]; + dad[lson[p]] = r; + dad[rson[p]] = r; + if (rson[dad[p]] == p) + rson[dad[p]] = r; + else + lson[dad[p]] = r; + dad[p] = NIL; /* remove p */ +} + +static void +DeleteNode (SWORD p) +{ + SWORD q; + + if (dad[p] == NIL) + return; /* unregistered */ + if (rson[p] == NIL) + q = lson[p]; + else if (lson[p] == NIL) + q = rson[p]; + else + { + q = lson[p]; + if (rson[q] != NIL) + { + do + { + q = rson[q]; + } while (rson[q] != NIL); + rson[dad[q]] = lson[q]; + dad[lson[q]] = dad[q]; + lson[q] = lson[p]; + dad[lson[p]] = q; + } + rson[q] = rson[p]; + dad[rson[p]] = q; + } + dad[q] = dad[p]; + if (rson[dad[p]] == p) + rson[dad[p]] = q; + else + lson[dad[p]] = q; + dad[p] = NIL; +} + +static void +Putcode (SWORD l, UWORD c) +{ + _workbuf |= c >> _workbuflen; + if ((_workbuflen += l) >= 8) + { + OutChar ((BYTE)(_workbuf >> 8)); + ++_lpCurCodeDesc->StreamIndex; + if ((_workbuflen -= 8) >= 8) + { + OutChar ((BYTE)(_workbuf)); + ++_lpCurCodeDesc->StreamIndex; + _workbuflen -= 8; + _workbuf = c << (l - _workbuflen); + } + else + { + _workbuf <<= 8; + } + _workbuf &= 0xFFFF; + } +} + +static void +EncodeChar (UWORD c) +{ + UWORD i; + SWORD j, k; + + i = 0; + j = 0; + k = _lpCurCodeDesc->prnt[c + T]; + + /* search connections from leaf node to the root */ + do + { + i >>= 1; + + /* + if node's address is odd, output 1 + else output 0 + */ + if (k & 1) + i += 0x8000; + + j++; + } while ((k = _lpCurCodeDesc->prnt[k]) != R); + Putcode (j, i); + _update (c + T); +} + +static void +EncodePosition (UWORD c) +{ + UWORD i; + /* + * Tables for encoding/decoding upper 6 bits of + * sliding dictionary pointer + */ + /* encoder table */ + static const BYTE p_len[64] = + { + 0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 + }; + + static const BYTE p_code[64] = + { + 0x00, 0x20, 0x30, 0x40, 0x50, 0x58, 0x60, 0x68, + 0x70, 0x78, 0x80, 0x88, 0x90, 0x94, 0x98, 0x9C, + 0xA0, 0xA4, 0xA8, 0xAC, 0xB0, 0xB4, 0xB8, 0xBC, + 0xC0, 0xC2, 0xC4, 0xC6, 0xC8, 0xCA, 0xCC, 0xCE, + 0xD0, 0xD2, 0xD4, 0xD6, 0xD8, 0xDA, 0xDC, 0xDE, + 0xE0, 0xE2, 0xE4, 0xE6, 0xE8, 0xEA, 0xEC, 0xEE, + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF + }; + + /* output upper 6 bits with encoding */ + i = c >> 6; + Putcode (p_len[i], (UWORD)p_code[i] << 8); + + /* output lower 6 bits directly */ + Putcode (6, (c & 0x3f) << 10); +} + +static void +UninitTree (void) +{ + if (_workbuflen) + { + OutChar ((BYTE)(_workbuf >> 8)); + ++_lpCurCodeDesc->StreamIndex; + } + + FreeCodeArrays (encode_arrays); + encode_arrays = NULL; + lson = NULL; + rson = NULL; + dad = NULL; +} + +static void +_encode_cleanup (void) +{ + UWORD r, s, last_match_length, len; + + _StreamType = _lpCurCodeDesc->StreamType; + _Stream = _lpCurCodeDesc->Stream; + _workbuf = _lpCurCodeDesc->workbuf; + _workbuflen = _lpCurCodeDesc->workbuflen; + + r = _lpCurCodeDesc->buf_index; + s = _lpCurCodeDesc->restart_index; + last_match_length = _lpCurCodeDesc->bytes_left; + if (_lpCurCodeDesc->StreamLength >= F) + len = F; + else + { + UWORD i; + + for (i = 1; i <= F; i++) + InsertNode (r - i); + InsertNode (r); + + len = (UWORD)_lpCurCodeDesc->StreamLength; + } + + while (1) + { + while (last_match_length--) + { + DeleteNode (s); + if (--len == 0) + { + BYTE lobyte, hibyte; + UWORD loword, hiword; + + UninitTree (); + + _lpCurCodeDesc->StreamIndex += 4; + /* rewind */ + if (_lpCurCodeDesc->StreamType == FILE_STREAM) + SeekResFile ((uio_Stream *)_Stream, + -(int)_lpCurCodeDesc->StreamIndex, SEEK_CUR); + else /* _lpCurCodeDesc->StreamType == MEMORY_STREAM */ + _Stream = (BYTE*)_Stream - _lpCurCodeDesc->StreamIndex; + + loword = LOWORD (_lpCurCodeDesc->StreamLength); + lobyte = LOBYTE (loword); + hibyte = HIBYTE (loword); + OutChar (lobyte); + OutChar (hibyte); + hiword = HIWORD (_lpCurCodeDesc->StreamLength); + lobyte = LOBYTE (hiword); + hibyte = HIBYTE (hiword); + OutChar (lobyte); + OutChar (hibyte); + + return; + } + s = (s + 1) & (N - 1); + r = (r + 1) & (N - 1); + InsertNode (r); + } + if (match_length > len) + match_length = len; + if (match_length <= THRESHOLD) + { + match_length = 1; + EncodeChar (_lpCurCodeDesc->text_buf[r]); + } + else + { + EncodeChar (255 - THRESHOLD + match_length); + EncodePosition (match_position); + } + last_match_length = match_length; + } +} + +COUNT +cwrite (const void *buf, COUNT size, COUNT count, PLZHCODE_DESC lpCodeDesc) +{ + UWORD r, s, last_match_length; + BYTE *lpBuf; + const BYTE *lpStr; + + if ((_lpCurCodeDesc = lpCodeDesc) == 0 + || (size *= count) == 0) + return (0); + + _StreamType = lpCodeDesc->StreamType; + _Stream = lpCodeDesc->Stream; + _workbuf = lpCodeDesc->workbuf; + _workbuflen = lpCodeDesc->workbuflen; + lpStr = (const BYTE *) buf; + lpBuf = lpCodeDesc->text_buf; + + r = lpCodeDesc->buf_index; + s = lpCodeDesc->restart_index; + last_match_length = lpCodeDesc->bytes_left; + if (last_match_length) + { + lpCodeDesc->StreamLength += size; + goto EncodeRestart; + } + else if (lpCodeDesc->StreamLength < F) + { + UWORD i; + + if ((i = (UWORD)lpCodeDesc->StreamLength) == 0) + { + if (!InitTree ()) + return (0); + + _lpCurCodeDesc->StreamIndex = 0; + lpCodeDesc->CleanupFunc = _encode_cleanup; + } + + lpCodeDesc->StreamLength += size; + + for (; i < F && size; ++i, --size) + lpBuf[r + i] = *lpStr++; + if (i < F) + goto EncodeExit; + + for (i = 1; i <= F; i++) + InsertNode (r - i); + InsertNode (r); + if (size == 0) + goto EncodeExit; + } + else + lpCodeDesc->StreamLength += size; + + do + { + if (match_length > F) + match_length = F; + if (match_length <= THRESHOLD) + { + match_length = 1; + EncodeChar (lpBuf[r]); + } + else + { + EncodeChar (255 - THRESHOLD + match_length); + EncodePosition (match_position); + } + last_match_length = match_length; +EncodeRestart: + while (last_match_length && size) + { + BYTE c; + + --size; + --last_match_length; + + DeleteNode (s); + c = *lpStr++; + lpBuf[s] = c; + if (s < F - 1) + lpBuf[s + N] = c; + s = (s + 1) & (N - 1); + r = (r + 1) & (N - 1); + InsertNode (r); + } + } while (last_match_length == 0); + +EncodeExit: + lpCodeDesc->buf_index = r; + lpCodeDesc->restart_index = s; + lpCodeDesc->bytes_left = last_match_length; + + lpCodeDesc->Stream = _Stream; + lpCodeDesc->workbuf = _workbuf; + lpCodeDesc->workbuflen = _workbuflen; + + return (count); +} + diff --git a/src/libs/decomp/lzh.h b/src/libs/decomp/lzh.h new file mode 100644 index 0000000..8ebdef4 --- /dev/null +++ b/src/libs/decomp/lzh.h @@ -0,0 +1,91 @@ +//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 LIBS_DECOMP_LZH_H_ +#define LIBS_DECOMP_LZH_H_ + +#include "libs/declib.h" +#include "libs/memlib.h" + +/* LZSS Parameters */ + +#define N 4096 /* Size of string buffer */ +#define F 16 /* Size of look-ahead buffer */ +//#define F 60 /* Size of look-ahead buffer */ +#define THRESHOLD 2 +#define NIL N /* End of tree's node */ + +/* Huffman coding parameters */ + +#define N_CHAR (256 - THRESHOLD + F) + /* character code (= 0..N_CHAR-1) */ +#define T (N_CHAR * 2 - 1) /* Size of table */ +#define R (T - 1) /* root position */ +#define MAX_FREQ 0x8000 + /* update when cumulative frequency */ + +struct _LZHCODE_DESC +{ + COUNT buf_index, restart_index, bytes_left; + BYTE text_buf[N + F - 1]; + /* reconstruct freq tree */ + COUNT freq[T + 1]; /* cumulative freq table */ + /* + * pointing parent nodes. + * area [T..(T + N_CHAR - 1)] are pointers for leaves + */ + COUNT prnt[T + N_CHAR]; + /* pointing children nodes (son[], son[] + 1)*/ + COUNT son[T]; + UWORD workbuf; + BYTE workbuflen; + + STREAM_TYPE StreamType; + + void *Stream; + DWORD StreamIndex, StreamLength; + + STREAM_MODE StreamMode; + PVOIDFUNC CleanupFunc; +}; + +typedef struct _LZHCODE_DESC LZHCODE_DESC; +typedef LZHCODE_DESC *PLZHCODE_DESC; + +#define InChar() (_StreamType == FILE_STREAM ? \ + GetResFileChar ((uio_Stream *)_Stream) : \ + (int)*_Stream++) +#define OutChar(c) (_StreamType == FILE_STREAM ? \ + PutResFileChar ((c), (uio_Stream *)_Stream) : \ + (*_Stream++ = (BYTE)(c))) + + +#define AllocCodeDesc() HCalloc (sizeof (LZHCODE_DESC)) +#define FreeCodeDesc HFree + +extern void _update (COUNT c); +extern void StartHuff (void); + +extern PLZHCODE_DESC _lpCurCodeDesc; +extern STREAM_TYPE _StreamType; +extern BYTE* _Stream; +extern UWORD _workbuf; +extern BYTE _workbuflen; + +#endif /* LIBS_DECOMP_LZH_H_ */ + diff --git a/src/libs/decomp/update.c b/src/libs/decomp/update.c new file mode 100644 index 0000000..13de988 --- /dev/null +++ b/src/libs/decomp/update.c @@ -0,0 +1,115 @@ +//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 +#include "lzh.h" + +static void +reconst (void) +{ + COUNT i, j; + + /* halven cumulative freq for leaf nodes */ + j = 0; + for (i = 0; i < T; i++) + { + if (_lpCurCodeDesc->son[i] >= T) + { + _lpCurCodeDesc->freq[j] = (_lpCurCodeDesc->freq[i] + 1) >> 1; + _lpCurCodeDesc->son[j] = _lpCurCodeDesc->son[i]; + j++; + } + } + /* make a tree : first, connect children nodes */ + for (i = 0, j = N_CHAR; j < T; i += 2, j++) + { + SWORD k; + UWORD f, l; + + k = i + 1; + f = _lpCurCodeDesc->freq[j] = _lpCurCodeDesc->freq[i] + _lpCurCodeDesc->freq[k]; + for (k = j - 1; f < _lpCurCodeDesc->freq[k]; k--) + ; + k++; + l = (j - k); + + memmove (_lpCurCodeDesc->freq + k + 1, _lpCurCodeDesc->freq + k, + sizeof(_lpCurCodeDesc->freq[0]) * l); + _lpCurCodeDesc->freq[k] = f; + memmove (_lpCurCodeDesc->son + k + 1, _lpCurCodeDesc->son + k, + sizeof(_lpCurCodeDesc->son[0]) * l); + _lpCurCodeDesc->son[k] = i; + } + /* connect parent nodes */ + for (i = 0; i < T; i++) + { + if ((j = _lpCurCodeDesc->son[i]) >= T) + _lpCurCodeDesc->prnt[j] = i; + else + _lpCurCodeDesc->prnt[j] = _lpCurCodeDesc->prnt[j + 1] = i; + } +} + + +/* update freq tree */ + +void +_update (COUNT c) +{ + PLZHCODE_DESC lpCD; + + if ((lpCD = _lpCurCodeDesc)->freq[R] == MAX_FREQ) + reconst (); + + c = lpCD->prnt[c]; + do + { + COUNT i, l; + + i = ++lpCD->freq[c]; + + /* swap nodes to keep the tree freq-ordered */ + if (i > lpCD->freq[l = c + 1]) + { + COUNT j; + + while (i > lpCD->freq[++l]) + ; + l--; + lpCD->freq[c] = lpCD->freq[l]; + lpCD->freq[l] = i; + + i = lpCD->son[c]; + j = lpCD->son[l]; + lpCD->son[l] = i; + lpCD->son[c] = j; + + lpCD->prnt[i] = l; + if (i < T) + lpCD->prnt[i + 1] = l; + + lpCD->prnt[j] = c; + if (j < T) + lpCD->prnt[j + 1] = c; + + c = l; + } + } while ((c = lpCD->prnt[c]) != 0); /* do it until reaching the root */ +} + + diff --git a/src/libs/file.h b/src/libs/file.h new file mode 100644 index 0000000..df8de5e --- /dev/null +++ b/src/libs/file.h @@ -0,0 +1,95 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Contains file handling code + +#ifndef LIBS_FILE_H_ +#define LIBS_FILE_H_ + +#include "port.h" +#include "libs/uio.h" + +// for bool +#include "types.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +#if 0 +// from temp.h +void initTempDir (void); +void unInitTempDir (void); +char *tempFilePath (const char *filename); +extern uio_DirHandle *tempDir; +#endif + + +// from dirs.h +int mkdirhier (const char *path); +const char *getHomeDir (void); +int createDirectory (const char *dir, int mode); + +int expandPath (char *dest, size_t len, const char *src, int what); +// values for 'what': +#define EP_HOME 1 + // Expand '~' for home dirs. +#define EP_ABSOLUTE 2 + // Make paths absolute +#define EP_ENVVARS 4 + // Expand environment variables. +#define EP_DOTS 8 + // Process ".." and "." +#define EP_SLASHES 16 + // Consider backslashes as path component separators. + // They will be replaced by slashes. Windows UNC paths will always + // start with "\\server\share", with backslashes. +#define EP_SINGLESEP 32 + // Replace multiple consecutive path separators by a single one. +#define EP_ALL (EP_HOME | EP_ENVVARS | EP_ABSOLUTE | EP_DOTS | EP_SLASHES \ + EP_SINGLESEP) + // Everything +// Everything except Windows style backslashes on Unix Systems: +#ifdef WIN32 +# define EP_ALL_SYSTEM (EP_HOME | EP_ENVVARS | EP_ABSOLUTE | EP_DOTS | \ + EP_SLASHES | EP_SINGLESEP) +#else +# define EP_ALL_SYSTEM (EP_HOME | EP_ENVVARS | EP_ABSOLUTE | EP_DOTS | \ + EP_SINGLESEP) +#endif + +// from files.h +int copyFile (uio_DirHandle *srcDir, const char *srcName, + uio_DirHandle *dstDir, const char *newName); +bool fileExists (const char *name); +bool fileExists2(uio_DirHandle *dir, const char *fileName); +#ifdef HAVE_UNC_PATHS +size_t skipUNCServerShare(const char *inPath); +#endif /* HAVE_UNC_PATHS */ + +#ifdef HAVE_DRIVE_LETTERS +static inline int isDriveLetter(int c) +{ + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +} +#endif /* HAVE_DRIVE_LETTERS */ + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_FILE_H_ */ + diff --git a/src/libs/file/Makeinfo b/src/libs/file/Makeinfo new file mode 100644 index 0000000..b4ea11d --- /dev/null +++ b/src/libs/file/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="dirs.c files.c" +uqm_HFILES="filintrn.h" diff --git a/src/libs/file/dirs.c b/src/libs/file/dirs.c new file mode 100644 index 0000000..39a44f5 --- /dev/null +++ b/src/libs/file/dirs.c @@ -0,0 +1,830 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Contains code handling directories + +#include +#include +#include +#include +#include +#include +#include "port.h" +#include "config.h" +#include "filintrn.h" +#include "libs/compiler.h" +#include "libs/memlib.h" +#include "libs/misc.h" +#include "libs/log.h" + +#ifdef HAVE_DRIVE_LETTERS +# include + // For tolower() +#endif /* HAVE_DRIVE_LETTERS */ +#ifdef WIN32 +# include + // For _getdcwd() +#else +# include + // For getpwuid() +#endif + +/* Try to find a suitable value for %APPDATA% if it isn't defined on + * Windows. + */ +#define APPDATA_FALLBACK + + +static char *expandPathAbsolute (char *dest, size_t destLen, const char *src, + size_t *skipSrc, int what); +static char *strrchr2(const char *start, int c, const char *end); + + +int +createDirectory(const char *dir, int mode) +{ + return MKDIR(dir, mode); +} + +// make all components of the path if they don't exist already +// returns 0 on success, -1 on failure. +// on failure, some parts may still have been created. +int +mkdirhier (const char *path) +{ + char *buf; // buffer + char *ptr; // end of the string in buf + const char *pathstart; // start of a component of path + const char *pathend; // first char past the end of a component of path + size_t len; + struct stat statbuf; + + len = strlen (path); + buf = HMalloc (len + 2); // one extra for possibly added '/' + + ptr = buf; + pathstart = path; + +#ifdef HAVE_DRIVE_LETTERS + if (isDriveLetter(pathstart[0]) && pathstart[1] == ':') + { + // Driveletter + semicolon on Windows. + // Copy as is; don't try to create directories for it. + *(ptr++) = *(pathstart++); + *(ptr++) = *(pathstart++); + + ptr[0] = '/'; + ptr[1] = '\0'; + if (stat (buf, &statbuf) == -1) + { + log_add (log_Error, "Can't stat \"%s\": %s", buf, strerror (errno)); + goto err; + } + } + else +#endif /* HAVE_DRIVE_LETTERS */ +#ifdef HAVE_UNC_PATHS + if (pathstart[0] == '\\' && pathstart[1] == '\\') + { + // Universal Naming Convention path. (\\server\share\...) + // Copy the server part as is; don't try to create directories for + // it, or stat it. Don't create a dir for the share either. + *(ptr++) = *(pathstart++); + *(ptr++) = *(pathstart++); + + // Copy the server part + while (*pathstart != '\0' && *pathstart != '\\' && *pathstart != '/') + *(ptr++) = *(pathstart++); + + if (*pathstart == '\0') + { + log_add (log_Error, "Incomplete UNC path \"%s\"", pathstart); + goto err; + } + + // Copy the path seperator. + *(ptr++) = *(pathstart++); + + // Copy the share part + while (*pathstart != '\0' && *pathstart != '\\' && *pathstart != '/') + *(ptr++) = *(pathstart++); + + ptr[0] = '/'; + ptr[1] = '\0'; + if (stat (buf, &statbuf) == -1) + { + log_add (log_Error, "Can't stat \"%s\": %s", buf, strerror (errno)); + goto err; + } + } +#else + { + // Making sure that there is an 'else' case if HAVE_DRIVE_LETTERS is + // defined. + } +#endif /* HAVE_UNC_PATHS */ + + if (*pathstart == '/') + *(ptr++) = *(pathstart++); + + if (*pathstart == '\0') { + // path exists completely, nothing more to do + goto success; + } + + // walk through the path as long as the components exist + while (1) + { + pathend = strchr (pathstart, '/'); + if (pathend == NULL) + pathend = path + len; + memcpy(ptr, pathstart, pathend - pathstart); + ptr += pathend - pathstart; + *ptr = '\0'; + + if (stat (buf, &statbuf) == -1) + { + if (errno == ENOENT) + break; +#ifdef __SYMBIAN32__ + // XXX: HACK: If we don't have access to a directory, we can + // still have access to the underlying entries. We don't + // actually know whether the entry is a directory, but I know of + // no way to find out. We just pretend that it is; if we were + // wrong, an error will occur when we try to do something with + // the directory. That /should/ not be a problem, as any such + // action should have its own error checking. + if (errno != EACCES) +#endif + { + log_add (log_Error, "Can't stat \"%s\": %s", buf, + strerror (errno)); + goto err; + } + } + + if (*pathend == '\0') + goto success; + + *ptr = '/'; + ptr++; + pathstart = pathend + 1; + while (*pathstart == '/') + pathstart++; + // pathstart is the next non-slash character + + if (*pathstart == '\0') + goto success; + } + + // create all components left + while (1) + { + if (createDirectory (buf, 0777) == -1) + { + log_add (log_Error, "Error: Can't create %s: %s", buf, + strerror (errno)); + goto err; + } + + if (*pathend == '\0') + break; + + *ptr = '/'; + ptr++; + pathstart = pathend + 1; + while (*pathstart == '/') + pathstart++; + // pathstart is the next non-slash character + + if (*pathstart == '\0') + break; + + pathend = strchr (pathstart, '/'); + if (pathend == NULL) + pathend = path + len; + + memcpy (ptr, pathstart, pathend - pathstart); + ptr += pathend - pathstart; + *ptr = '\0'; + } + +success: + HFree (buf); + return 0; + +err: + { + int savedErrno = errno; + HFree (buf); + errno = savedErrno; + } + return -1; +} + +// Get the user's home dir +// returns a pointer to a static buffer from either getenv() or getpwuid(). +const char * +getHomeDir (void) +{ +#ifdef WIN32 + return getenv ("HOME"); +#else + const char *home; + struct passwd *pw; + + home = getenv ("HOME"); + if (home != NULL) + return home; + + pw = getpwuid (getuid ()); + if (pw == NULL) + return NULL; + // NB: pw points to a static buffer. + + return pw->pw_dir; +#endif +} + +// Performs various types of string expansions on a path. +// 'what' is an OR'd compination of the folowing flags, which +// specify what type of exmansions will be performed. +// EP_HOME - Expand '~' for home dirs. +// EP_ABSOLUTE - Make relative paths absolute +// EP_ENVVARS - Expand environment variables +// EP_DOTS - Process ".." and "." +// EP_SLASHES - Consider backslashes as path component separators. +// They will be replaced by slashes. +// EP_SINGLESEP - Replace multiple consecutive path seperators (which POSIX +// considers equivalent to a single one) by a single one. +// Additionally, there's EP_ALL, which indicates all of the above, +// and EP_ALL_SYSTEM, which does the same as EP_ALL, with the exception +// of EP_SLASHES, which will only be included if the operating system +// accepts backslashes as path terminators. +// Returns 0 on success. +// Returns -1 on failure, setting errno. +int +expandPath (char *dest, size_t len, const char *src, int what) +{ + char *destptr, *destend; + char *buf = NULL; + char *bufptr, *bufend; + const char *srcend; + +#define CHECKLEN(bufname, n) \ + if (bufname##ptr + (n) >= bufname##end) \ + { \ + errno = ENAMETOOLONG; \ + goto err; \ + } \ + else \ + (void) 0 + + destptr = dest; + destend = dest + len; + + if (what & EP_ENVVARS) + { + buf = HMalloc (len); + bufptr = buf; + bufend = buf + len; + while (*src != '\0') + { + switch (*src) + { +#ifdef WIN32 + case '%': + { + /* Environment variable substitution in Windows */ + const char *end; // end of env var name in src + const char *envVar; + char *envName; + size_t envNameLen, envVarLen; + + src++; + end = strchr (src, '%'); + if (end == NULL) + { + errno = EINVAL; + goto err; + } + + envNameLen = end - src; + envName = HMalloc (envNameLen + 1); + memcpy (envName, src, envNameLen + 1); + envName[envNameLen] = '\0'; + envVar = getenv (envName); + HFree (envName); + + if (envVar == NULL) + { +#ifdef APPDATA_FALLBACK + if (strncmp (src, "APPDATA", envNameLen) != 0) + { + // Substitute an empty string + src = end + 1; + break; + } + + // fallback for when the APPDATA env var is not set + // Using SHGetFolderPath or SHGetSpecialFolderPath + // is problematic (not everywhere available). + log_add (log_Warning, "Warning: %%APPDATA%% is not set. " + "Falling back to \"%%USERPROFILE%%\\Application " + "Data\""); + envVar = getenv ("USERPROFILE"); + if (envVar != NULL) + { +#define APPDATA_STRING "\\Application Data" + envVarLen = strlen (envVar); + CHECKLEN (buf, + envVarLen + sizeof (APPDATA_STRING) - 1); + strcpy (bufptr, envVar); + bufptr += envVarLen; + strcpy (bufptr, APPDATA_STRING); + bufptr += sizeof (APPDATA_STRING) - 1; + src = end + 1; + break; + } + + // fallback to "./userdata" +#define APPDATA_FALLBACK_STRING ".\\userdata" + log_add (log_Warning, + "Warning: %%USERPROFILE%% is not set. " + "Falling back to \"%s\" for %%APPDATA%%", + APPDATA_FALLBACK_STRING); + CHECKLEN (buf, sizeof (APPDATA_FALLBACK_STRING) - 1); + strcpy (bufptr, APPDATA_FALLBACK_STRING); + bufptr += sizeof (APPDATA_FALLBACK_STRING) - 1; + src = end + 1; + break; + +#else /* !defined (APPDATA_FALLBACK) */ + // Substitute an empty string + src = end + 1; + break; +#endif /* APPDATA_FALLBACK */ + } + + envVarLen = strlen (envVar); + CHECKLEN (buf, envVarLen); + strcpy (bufptr, envVar); + bufptr += envVarLen; + src = end + 1; + break; + } +#endif +#ifndef WIN32 + case '$': + { + const char *end; + char *envName; + size_t envNameLen; + const char *envVar; + size_t envVarLen; + + src++; + if (*src == '{') + { + src++; + end = strchr(src, '}'); + if (end == NULL) + { + errno = EINVAL; + goto err; + } + envNameLen = end - src; + end++; // Skip the '}' + } + else + { + end = src; + while ((*end >= 'A' && *end <= 'Z') || + (*end >= 'a' && *end <= 'z') || + (*end >= '0' && *end <= '9') || + *end == '_') + end++; + envNameLen = end - src; + } + + envName = HMalloc (envNameLen + 1); + memcpy (envName, src, envNameLen + 1); + envName[envNameLen] = '\0'; + envVar = getenv (envName); + HFree (envName); + + if (envVar != NULL) + { + envVarLen = strlen (envVar); + CHECKLEN (buf, envVarLen); + memcpy (bufptr, envVar, envVarLen); + bufptr += envVarLen; + } + + src = end; + break; + } +#endif + default: + CHECKLEN(buf, 1); + *(bufptr++) = *(src++); + break; + } // switch + } // while + *bufptr = '\0'; + src = buf; + srcend = bufptr; + } // if (what & EP_ENVVARS) + else + srcend = src + strlen (src); + + if (what & EP_HOME) + { + if (src[0] == '~') + { + const char *home; + size_t homelen; + + if (src[1] != '/') + { + errno = EINVAL; + goto err; + } + + home = getHomeDir (); + if (home == NULL) + { + errno = ENOENT; + goto err; + } + homelen = strlen (home); + + if (what & EP_ABSOLUTE) { + size_t skip; + destptr = expandPathAbsolute (dest, destend - dest, + home, &skip, what); + if (destptr == NULL) + { + // errno is set + goto err; + } + home += skip; + what &= ~EP_ABSOLUTE; + // The part after the '~' should not be seen + // as absolute. + } + + CHECKLEN (dest, homelen); + memcpy (destptr, home, homelen); + destptr += homelen; + src++; /* skip the ~ */ + } + } + + if (what & EP_ABSOLUTE) + { + size_t skip; + destptr = expandPathAbsolute (destptr, destend - destptr, src, + &skip, what); + if (destptr == NULL) + { + // errno is set + goto err; + } + src += skip; + } + + CHECKLEN (dest, srcend - src); + memcpy (destptr, src, srcend - src + 1); + // The +1 is for the '\0'. It is already taken into account by + // CHECKLEN. + + if (what & EP_SLASHES) + { + /* Replacing backslashes in path by slashes. */ + destptr = dest; +#ifdef HAVE_UNC_PATHS + { + // A UNC path should always start with two backslashes + // and have a backslash in between the server and share part. + size_t skip = skipUNCServerShare (destptr); + if (skip != 0) + { + char *slash = (char *) memchr (destptr + 2, '/', skip - 2); + if (slash) + *slash = '\\'; + destptr += skip; + } + } +#endif /* HAVE_UNC_PATHS */ + while (*destptr != '\0') + { + if (*destptr == '\\') + *destptr = '/'; + destptr++; + } + } + + if (what & EP_DOTS) { + // At this point backslashes are already replaced by slashes if they + // are specified to be path seperators. + // Note that the path can only get smaller, so no size checks + // need to be done. + char *pathStart; + // Start of the first path component, after any + // leading slashes or drive letters. + char *startPart; + char *endPart; + + pathStart = dest; +#ifdef HAVE_DRIVE_LETTERS + if (isDriveLetter(pathStart[0]) && (pathStart[1] == ':')) + { + pathStart += 2; + } + else +#endif /* HAVE_DRIVE_LETTERS */ +#ifdef HAVE_UNC_PATHS + { + // Test for a Universal Naming Convention path. + pathStart += skipUNCServerShare(pathStart); + } +#else + { + // Making sure that there is an 'else' case if HAVE_DRIVE_LETTERS is + // defined. + } +#endif /* HAVE_UNC_PATHS */ + if (pathStart[0] == '/') + pathStart++; + + startPart = pathStart; + destptr = pathStart; + for (;;) + { + endPart = strchr(startPart, '/'); + if (endPart == NULL) + endPart = startPart + strlen(startPart); + + if (endPart - startPart == 1 && startPart[0] == '.') + { + // Found "." as path component. Ignore this component. + } + else if (endPart - startPart == 2 && + startPart[0] == '.' && startPart[1] == '.') + { + // Found ".." as path component. Remove the previous + // component, and ignore this one. + char *lastSlash; + lastSlash = strrchr2(pathStart, '/', destptr - 1); + if (lastSlash == NULL) + { + if (destptr == pathStart) + { + // We ran out of path components to back out of. + errno = EINVAL; + goto err; + } + destptr = pathStart; + } + else + { + destptr = lastSlash; + if (*endPart == '/') + destptr++; + } + } + else + { + // A normal path component; copy it. + // Using memmove as source and destination may overlap. + memmove(destptr, startPart, endPart - startPart); + destptr += (endPart - startPart); + if (*endPart == '/') + { + *destptr = '/'; + destptr++; + } + } + if (*endPart == '\0') + break; + startPart = endPart + 1; + } + *destptr = '\0'; + } + + if (what & EP_SINGLESEP) + { + char *srcptr; + srcptr = dest; + destptr = dest; + while (*srcptr != '\0') + { + char ch = *srcptr; + *(destptr++) = *(srcptr++); + if (ch == '/') + { + while (*srcptr == '/') + srcptr++; + } + } + *destptr = '\0'; + } + + HFree (buf); + return 0; + +err: + if (buf != NULL) { + int savedErrno = errno; + HFree (buf); + errno = savedErrno; + } + return -1; +} + +#if defined(HAVE_DRIVE_LETTERS) && defined(HAVE_CWD_PER_DRIVE) + // This code is only needed if we have a current working directory + // per drive. +// letter is 0 based: 0 = A, 1 = B, ... +static bool +driveLetterExists(int letter) +{ + unsigned long drives; + + drives = _getdrives (); + + return ((drives >> letter) & 1) != 0; +} +#endif /* if defined(HAVE_DRIVE_LETTERS) && defined(HAVE_CWD_PER_DRIVE) */ + +// helper for expandPath, expanding an absolute path +// returns a pointer to the end of the filled in part of dest. +static char * +expandPathAbsolute (char *dest, size_t destLen, const char *src, + size_t *skipSrc, int what) +{ + const char *orgSrc; + + if (src[0] == '/' || ((what & EP_SLASHES) && src[0] == '\\')) + { + // Path is already absolute; nothing to do + *skipSrc = 0; + return dest; + } + + orgSrc = src; +#ifdef HAVE_DRIVE_LETTERS + if (isDriveLetter(src[0]) && (src[1] == ':')) + { + int letter; + + if (src[2] == '/' || src[2] == '\\') + { + // Path is already absolute (of the form "d:/"); nothing to do + *skipSrc = 0; + return dest; + } + + // Path is of the form "d:path", without a (back)slash after the + // semicolon. + +#ifdef REJECT_DRIVE_PATH_WITHOUT_SLASH + // We reject paths of the form "d:foo/bar". + errno = EINVAL; + return NULL; +#elif defined(HAVE_CWD_PER_DRIVE) + // Paths of the form "d:foo/bar" are treated as "foo/bar" relative + // to the working directory of d:. + letter = tolower(src[0]) - 'a'; + + // _getdcwd() should only be called on drives that exist. + // This is weird though, because it means a race condition + // in between the existance check and the call to _getdcwd() + // cannot be avoided, unless a drive still exists for Windows + // when the physical drive is removed. + if (!driveLetterExists (letter)) + { + errno = ENOENT; + return NULL; + } + + // Get the working directory for a specific drive. + if (_getdcwd (letter + 1, dest, destLen) == NULL) + { + // errno is set + return NULL; + } + + src += 2; +#else /* if !defined(HAVE_CWD_PER_DRIVE) */ + // We treat paths of the form "d:foo/bar" as "d:/foo/bar". + if (destLen < 3) { + errno = ERANGE; + return NULL; + } + dest[0] = src[0]; + dest[1] = ':'; + dest[2] = '/'; + *skipSrc = 2; + dest += 3; + return dest; +#endif /* HAVE_CWD_PER_DRIVE */ + } + else +#endif /* HAVE_DRIVE_LETTERS */ + { + // Relative dir + if (getcwd (dest, destLen) == NULL) + { + // errno is set + return NULL; + } + } + + { + size_t tempLen; + tempLen = strlen (dest); + if (tempLen == 0) + { + // getcwd() or _getdcwd() returned a 0-length string. + errno = ENOENT; + return NULL; + } + dest += tempLen; + destLen -= tempLen; + } + if (dest[-1] != '/' +#ifdef BACKSLASH_IS_PATH_SEPARATOR + && dest[-1] != '\\' +#endif /* BACKSLASH_IS_PATH_SEPARATOR */ + ) + { + // Need to add a slash. + // There's always space, as we overwrite the '\0' that getcwd() + // always returns. + dest[0] = '/'; + dest++; + destLen--; + } + + *skipSrc = (size_t) (src - orgSrc); + return dest; +} + +// As strrchr, but starts searching from the indicated end of the string. +static char * +strrchr2(const char *start, int c, const char *end) { + for (;;) { + end--; + if (end < start) + return (char *) NULL; + if (*end == c) + return (char *) unconst(end); + } +} + +#ifdef HAVE_UNC_PATHS +// returns 0 if the path is not a valid UNC path. +// Does not skip trailing slashes. +size_t +skipUNCServerShare(const char *inPath) { + const char *path = inPath; + + // Skip the initial two backslashes. + if (path[0] != '\\' || path[1] != '\\') + return (size_t) 0; + path += 2; + + // Skip the server part. + while (*path != '\\' && *path != '/') { + if (*path == '\0') + return (size_t) 0; + path++; + } + + // Skip the seperator. + path++; + + // Skip the share part. + while (*path != '\0' && *path != '\\' && *path != '/') + path++; + + return (size_t) (path - inPath); +} +#endif /* HAVE_UNC_PATHS */ + + diff --git a/src/libs/file/files.c b/src/libs/file/files.c new file mode 100644 index 0000000..5ce7dac --- /dev/null +++ b/src/libs/file/files.c @@ -0,0 +1,165 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Contains code handling files + +#include +#include +#include +#include +#include +#include "port.h" +#include "libs/uio.h" +#include "config.h" +#include "types.h" +#include "filintrn.h" +#include "libs/memlib.h" +#include "libs/log.h" + +static int copyError(uio_Handle *srcHandle, uio_Handle *dstHandle, + uio_DirHandle *unlinkHandle, const char *unlinkPath, uint8 *buf); + +bool +fileExists (const char *name) +{ + return access (name, F_OK) == 0; +} + +bool +fileExists2(uio_DirHandle *dir, const char *fileName) +{ + uio_Stream *stream; + + stream = uio_fopen (dir, fileName, "rb"); + if (stream == NULL) + return 0; + + uio_fclose (stream); + return 1; +} + +/* + * Copy a file with path srcName to a file with name newName. + * If the destination already exists, the operation fails. + * Links are followed. + * Special files (fifos, char devices, block devices, etc) will be + * read as long as there is data available and the destination will be + * a regular file with that data. + * The new file will have the same permissions as the old. + * If an error occurs during copying, an attempt will be made to + * remove the copy. + */ +int +copyFile (uio_DirHandle *srcDir, const char *srcName, + uio_DirHandle *dstDir, const char *newName) +{ + uio_Handle *src, *dst; + struct stat sb; +#define BUFSIZE 65536 + uint8 *buf, *bufPtr; + ssize_t numInBuf, numWritten; + + src = uio_open (srcDir, srcName, O_RDONLY +#ifdef WIN32 + | O_BINARY +#endif + , 0); + if (src == NULL) + return -1; + + if (uio_fstat (src, &sb) == -1) + return copyError (src, NULL, NULL, NULL, NULL); + + dst = uio_open (dstDir, newName, O_WRONLY | O_CREAT | O_EXCL +#ifdef WIN32 + | O_BINARY +#endif + , sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + if (dst == NULL) + return copyError (src, NULL, NULL, NULL, NULL); + + buf = HMalloc(BUFSIZE); + // This was originally a statically allocated buffer, + // but as this function might be run from a thread with + // a small stack, this is better. + while (1) + { + numInBuf = uio_read (src, buf, BUFSIZE); + if (numInBuf == -1) + { + if (errno == EINTR) + continue; + return copyError (src, dst, dstDir, newName, buf); + } + if (numInBuf == 0) + break; + + bufPtr = buf; + do + { + numWritten = uio_write (dst, bufPtr, numInBuf); + if (numWritten == -1) + { + if (errno == EINTR) + continue; + return copyError (src, dst, dstDir, newName, buf); + } + numInBuf -= numWritten; + bufPtr += numWritten; + } while (numInBuf > 0); + } + + HFree (buf); + uio_close (src); + uio_close (dst); + errno = 0; + return 0; +} + +/* + * Closes srcHandle if it's not -1. + * Closes dstHandle if it's not -1. + * Removes unlinkpath from the unlinkHandle dir if it's not NULL. + * Frees 'buf' if not NULL. + * Always returns -1. + * errno is what was before the call. + */ +static int +copyError(uio_Handle *srcHandle, uio_Handle *dstHandle, + uio_DirHandle *unlinkHandle, const char *unlinkPath, uint8 *buf) +{ + int savedErrno; + + savedErrno = errno; + + log_add (log_Debug, "Error while copying: %s", strerror (errno)); + + if (srcHandle != NULL) + uio_close (srcHandle); + + if (dstHandle != NULL) + uio_close (dstHandle); + + if (unlinkPath != NULL) + uio_unlink (unlinkHandle, unlinkPath); + + if (buf != NULL) + HFree(buf); + + errno = savedErrno; + return -1; +} + diff --git a/src/libs/file/filintrn.h b/src/libs/file/filintrn.h new file mode 100644 index 0000000..2c6512a --- /dev/null +++ b/src/libs/file/filintrn.h @@ -0,0 +1,24 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Contains code handling temporary files and dirs + +#ifndef _FILEINTRN_H + +#include "../file.h" + +#endif /* _FILEINTRN_H */ + diff --git a/src/libs/file/temp.c b/src/libs/file/temp.c new file mode 100644 index 0000000..3253e0d --- /dev/null +++ b/src/libs/file/temp.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. + */ + +// Contains code handling temporary files and dirs + +#include +#include +#include +#ifdef WIN32 +# include +#endif +#include +#include +#include "filintrn.h" +#include "libs/timelib.h" +#include "port.h" +#include "libs/compiler.h" +#include "libs/log.h" +#include "libs/memlib.h" + +static char *tempDirName; +uio_DirHandle *tempDir; + +static void +removeTempDir (void) +{ + rmdir (tempDirName); +} + +// Try if the null-terminated path 'dir' to a directory is valid +// as temp path. +// On success, 'buf' will be filled with the path, with a trailing /, +// null-terminated, and 0 is returned. +// On failure, EINVAL, ENAMETOOLONG, or one of the errors access() can return +// is returned, and the contents of buf is unspecified. +static int +tryTempDir (char *buf, size_t buflen, const char *dir) +{ + size_t len; + int haveSlash; + + if (dir == NULL) + return EINVAL; + + if (dir[0] == '\0') + return EINVAL; + + len = strlen (dir); + haveSlash = (dir[len - 1] == '/' +#ifdef WIN32 + || dir[len - 1] == '\\' +#endif + ); + if ((haveSlash ? len : len + 1) >= buflen) + return ENAMETOOLONG; + + strcpy (buf, dir); +#if 0 + //def WIN32 + { + char *bufPtr; + for (bufPtr = buf; *bufPtr != '\0'; bufPtr++) + { + if (*bufPtr == '\\') + *bufPtr = '/'; + } + } +#endif + if (!haveSlash) + { + buf[len] = '/'; + len++; + buf[len] = '\0'; + } + if (access (buf, R_OK | W_OK) == -1) + return errno; + + return 0; +} + +static void +getTempDir (char *buf, size_t buflen) { + char cwd[PATH_MAX]; + + if (tryTempDir (buf, buflen, getenv("TMP")) && + tryTempDir (buf, buflen, getenv("TEMP")) && +#if !defined(WIN32) || defined (__CYGWIN__) + tryTempDir (buf, buflen, "/tmp/") && + tryTempDir (buf, buflen, "/var/tmp/") && +#endif + tryTempDir (buf, buflen, getcwd (cwd, sizeof cwd))) + { + log_add (log_Fatal, "Fatal Error: Cannot find a suitable location " + "to store temporary files."); + exit (EXIT_FAILURE); + } +} + +// Sets the global var 'tempDir' +static int +mountTempDir(const char *name) { + static uio_AutoMount *autoMount[] = { NULL }; + uio_MountHandle *tempHandle; + extern uio_Repository *repository; + + tempHandle = uio_mountDir (repository, "/tmp/", + uio_FSTYPE_STDIO, NULL, NULL, name, autoMount, + uio_MOUNT_TOP, NULL); + if (tempHandle == NULL) { + int saveErrno = errno; + log_add (log_Fatal, "Fatal error: Couldn't mount temp dir '%s': " + "%s", name, strerror (errno)); + errno = saveErrno; + return -1; + } + + tempDir = uio_openDir (repository, "/tmp", 0); + if (tempDir == NULL) { + int saveErrno = errno; + log_add (log_Fatal, "Fatal error: Could not open temp dir: %s", + strerror (errno)); + errno = saveErrno; + return -1; + } + return 0; +} + +#define NUM_TEMP_RETRIES 16 + // Number of files to try to open before giving up. +void +initTempDir (void) { + size_t len; + DWORD num; + int i; + char *tempPtr; + // Pointer to the location in the tempDirName string where the + // path to the temp dir ends and the dir starts. + + tempDirName = HMalloc (PATH_MAX); + getTempDir (tempDirName, PATH_MAX - 21); + // reserve 8 chars for dirname, 1 for slash, and 12 for filename + len = strlen(tempDirName); + + num = ((DWORD) time (NULL)); +// num = GetTimeCounter () % 0xffffffff; + tempPtr = tempDirName + len; + for (i = 0; i < NUM_TEMP_RETRIES; i++) + { + sprintf (tempPtr, "%08x", num + i); + if (createDirectory (tempDirName, 0700) == -1) + continue; + + // Success, we've got a temp dir. + tempDirName = HRealloc (tempDirName, len + 9); + atexit (removeTempDir); + if (mountTempDir (tempDirName) == -1) + exit (EXIT_FAILURE); + return; + } + + // Failure, could not make a temporary directory. + log_add (log_Fatal, "Fatal error: Cannot get a name for a temporary " + "directory."); + exit (EXIT_FAILURE); +} + +void +unInitTempDir (void) { + uio_closeDir(tempDir); + // the removing of the dir is handled via atexit +} + +// return the path to a file in the temp dir with the specified filename. +// returns a pointer to a static buffer. +char * +tempFilePath (const char *filename) { + static char file[PATH_MAX]; + + if (snprintf (file, PATH_MAX, "%s/%s", tempDirName, filename) == -1) { + log_add (log_Fatal, "Path to temp file too long."); + exit (EXIT_FAILURE); + } + return file; +} + + diff --git a/src/libs/gfxlib.h b/src/libs/gfxlib.h new file mode 100644 index 0000000..9a16a69 --- /dev/null +++ b/src/libs/gfxlib.h @@ -0,0 +1,474 @@ +//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 LIBS_GFXLIB_H_ +#define LIBS_GFXLIB_H_ + +#include "port.h" +#include "libs/compiler.h" + +typedef struct Color Color; +struct Color { + BYTE r; + BYTE g; + BYTE b; + BYTE a; +}; + +#include "libs/reslib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct context_desc CONTEXT_DESC; +typedef struct frame_desc FRAME_DESC; +typedef struct font_desc FONT_DESC; +typedef struct drawable_desc DRAWABLE_DESC; + +typedef CONTEXT_DESC *CONTEXT; +typedef FRAME_DESC *FRAME; +typedef FONT_DESC *FONT; +typedef DRAWABLE_DESC *DRAWABLE; + +typedef UWORD TIME_VALUE; + +#define TIME_SHIFT 8 +#define MAX_TIME_VALUE ((1 << TIME_SHIFT) + 1) + +typedef SWORD COORD; + +static inline bool +sameColor(Color c1, Color c2) +{ + return c1.r == c2.r && + c1.g == c2.g && + c1.b == c2.b && + c1.a == c2.a; +} + +// Transform a 5-bits color component to an 8-bits color component. +// Form 1, calculates '(r5 / 31.0) * 255.0, highest value is 0xff: +#define CC5TO8(c) (((c) << 3) | ((c) >> 2)) +// Form 2, calculates '(r5 / 32.0) * 256.0, highest value is 0xf8: +//#define CC5TO8(c) ((c) << 3) + +#define BUILD_COLOR(col, c256) col + // BUILD_COLOR used to combine a 15-bit RGB color tripple with a + // destination VGA palette index into a 32-bit value. + // Now, it is an empty wrapper which returns the first argument, + // which is of type Color, and ignores the second argument, + // the palette index. + // + // It is a remnant of 8bpp hardware paletted display (VGA). + // The palette index would be overwritten with the RGB value + // and the drawing op would use this index on screen. + // The palette indices 0-15, as used in DOS SC2, are unchanged + // from the standard VGA palette and are identical to 16-color EGA. + // Various frames, borders, menus, etc. frequently refer to these + // first 16 colors and normally do not change the RGB values from + // the standard ones (see colors.h; most likely unchanged from SC1) + // The palette index is meaningless in UQM for the most part. + // New code should just use index 0. + +// Turn a 15 bits color into a 24-bits color. +// r, g, and b are each 5-bits color components. +static inline Color +colorFromRgb15 (BYTE r, BYTE g, BYTE b) +{ + Color c; + c.r = CC5TO8 (r); + c.g = CC5TO8 (g); + c.b = CC5TO8 (b); + c.a = 0xff; + + return c; +} +#define MAKE_RGB15(r, g, b) colorFromRgb15 ((r), (g), (b)) + +#ifdef NOTYET /* Need C'99 support */ +#define MAKE_RGB15(r, g, b) (Color) { \ + .r = CC5TO8 (r), \ + .g = CC5TO8 (g), \ + .b = CC5TO8 (b), \ + .a = 0xff \ + } +#endif + +// Temporary, until we can use C'99 features. Then MAKE_RGB15 will be usable +// anywhere. +// This define is intended for global initialisations, where the +// expression must be constant. +#define MAKE_RGB15_INIT(r, g, b) { \ + CC5TO8 (r), \ + CC5TO8 (g), \ + CC5TO8 (b), \ + 0xff \ + } + +static inline Color +buildColorRgba (BYTE r, BYTE g, BYTE b, BYTE a) +{ + Color c; + c.r = r; + c.g = g; + c.b = b; + c.a = a; + + return c; +} +#define BUILD_COLOR_RGBA(r, g, b, a) \ + buildColorRgba ((r), (g), (b), (a)) + + +typedef BYTE CREATE_FLAGS; +// WANT_MASK is deprecated (and non-functional). It used to generate a bitmap +// of changed pixels for a target DRAWABLE, so that DRAW_SUBTRACTIVE could +// paint background pixels over them, i.e. a revert draw. The backgrounds +// are fully erased now instead. +#define WANT_MASK (CREATE_FLAGS)(1 << 0) +#define WANT_PIXMAP (CREATE_FLAGS)(1 << 1) +// MAPPED_TO_DISPLAY is deprecated but still checked by LoadDisplayPixmap(). +// Its former use was to indicate a pre-scaled graphic for the display. +#define MAPPED_TO_DISPLAY (CREATE_FLAGS)(1 << 2) +#define WANT_ALPHA (CREATE_FLAGS)(1 << 3) + +typedef struct extent +{ + COORD width, height; +} EXTENT; + +typedef struct point +{ + COORD x, y; +} POINT; + +typedef struct stamp +{ + POINT origin; + FRAME frame; +} STAMP; + +typedef struct rect +{ + POINT corner; + EXTENT extent; +} RECT; + +typedef struct line +{ + POINT first, second; +} LINE; + +static inline POINT +MAKE_POINT (COORD x, COORD y) +{ + POINT pt = {x, y}; + return pt; +} + +static inline bool +pointsEqual (POINT p1, POINT p2) +{ + return p1.x == p2.x && p1.y == p2.y; +} + +static inline bool +extentsEqual (EXTENT e1, EXTENT e2) +{ + return e1.width == e2.width && e1.height == e2.height; +} + +static inline bool +rectsEqual (RECT r1, RECT r2) +{ + return pointsEqual (r1.corner, r2.corner) + && extentsEqual (r1.extent, r2.extent); +} + +static inline bool +pointWithinRect (RECT r, POINT p) +{ + return p.x >= r.corner.x && p.y >= r.corner.y + && p.x < r.corner.x + r.extent.width + && p.y < r.corner.y + r.extent.height; +} + +typedef enum +{ + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT +} TEXT_ALIGN; + +typedef enum +{ + VALIGN_TOP, + VALIGN_MIDDLE, + VALIGN_BOTTOM +} TEXT_VALIGN; + +typedef struct text +{ + POINT baseline; + const UNICODE *pStr; + TEXT_ALIGN align; + COUNT CharCount; +} TEXT; + +#if defined(__cplusplus) +} +#endif + +#include "libs/strlib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef STRING_TABLE COLORMAP_REF; +typedef STRING COLORMAP; +// COLORMAPPTR is really a pointer to colortable entry structure +// which is documented in doc/devel/strtab, .ct files section +typedef void *COLORMAPPTR; + +#include "graphics/prim.h" + + +typedef BYTE BATCH_FLAGS; +// This flag is currently unused but it might make sense to restore it +#define BATCH_BUILD_PAGE (BATCH_FLAGS)(1 << 0) + +typedef struct +{ + TIME_VALUE last_time_val; + POINT EndPoint; + STAMP IntersectStamp; +} INTERSECT_CONTROL; + +typedef BYTE INTERSECT_CODE; + +#define INTERSECT_LEFT (INTERSECT_CODE)(1 << 0) +#define INTERSECT_TOP (INTERSECT_CODE)(1 << 1) +#define INTERSECT_RIGHT (INTERSECT_CODE)(1 << 2) +#define INTERSECT_BOTTOM (INTERSECT_CODE)(1 << 3) +#define INTERSECT_NOCLIP (INTERSECT_CODE)(1 << 7) +#define INTERSECT_ALL_SIDES (INTERSECT_CODE)(INTERSECT_LEFT | \ + INTERSECT_TOP | \ + INTERSECT_RIGHT | \ + INTERSECT_BOTTOM) + +typedef POINT HOT_SPOT; + +extern HOT_SPOT MAKE_HOT_SPOT (COORD, COORD); + +extern INTERSECT_CODE BoxIntersect (RECT *pr1, RECT *pr2, RECT *printer); +extern void BoxUnion (RECT *pr1, RECT *pr2, RECT *punion); + +typedef enum +{ + FadeAllToWhite = 250, + FadeSomeToWhite, + FadeAllToBlack, + FadeAllToColor, + FadeSomeToBlack, + FadeSomeToColor +} ScreenFadeType; + +typedef enum +{ + DRAW_REPLACE = 0, + // Pixels in the target FRAME are replaced entirely. + // Non-stamp primitives with Color.a < 255 to RGB targets are + // equivalent to DRAW_ALPHA with (DrawMode.factor = Color.a), + // except the Text primitives. + // DrawMode.factor: ignored + // Text: supported (except DRAW_ALPHA via Color.a) + // RGBA sources (WANT_ALPHA): per-pixel alpha blending performed + // RGBA targets (WANT_ALPHA): replace directly supported + DRAW_ADDITIVE, + // Pixel channels of the source FRAME or Color channels of + // a primitive are modulated by (DrawMode.factor / 255) and added + // to the pixel channels of the target FRAME. + // DrawMode.factor range: -32767..32767 (negative values make + // draw subtractive); 255 = 1:1 ratio + // Text: not yet supported + // RGBA sources (WANT_ALPHA): alpha channel ignored + // RGBA targets (WANT_ALPHA): not yet supported + DRAW_ALPHA, + // Pixel channels of the source FRAME or Color channels of + // a primitive are modulated by (DrawMode.factor / 255) and added + // to the pixel channels of the target FRAME, modulated by + // (1 - DrawMode.factor / 255) + // DrawMode.factor range: 0..255; 255 = fully opaque + // Text: supported + // RGBA sources (WANT_ALPHA): alpha channel ignored + // RGBA targets (WANT_ALPHA): not yet supported + + DRAW_DEFAULT = DRAW_REPLACE, +} DrawKind; + +typedef struct +{ + BYTE kind; + SWORD factor; +} DrawMode; + +#define DRAW_REPLACE_MODE MAKE_DRAW_MODE (DRAW_REPLACE, 0) +#define DRAW_FACTOR_1 0xff + +static inline DrawMode +MAKE_DRAW_MODE (DrawKind kind, SWORD factor) +{ + DrawMode mode; + mode.kind = kind; + mode.factor = factor; + return mode; +} + +extern CONTEXT SetContext (CONTEXT Context); +extern Color SetContextForeGroundColor (Color Color); +extern Color GetContextForeGroundColor (void); +extern Color SetContextBackGroundColor (Color Color); +extern Color GetContextBackGroundColor (void); +extern FRAME SetContextFGFrame (FRAME Frame); +extern FRAME GetContextFGFrame (void); +// Context cliprect defines the drawing bounds. Additionally, all +// drawing positions (x,y) are relative to the cliprect corner. +extern BOOLEAN SetContextClipRect (RECT *pRect); +// The returned rect is always filled in. If the context cliprect +// is undefined, the returned rect has foreground frame dimensions. +extern BOOLEAN GetContextClipRect (RECT *pRect); +// The actual origin will be orgOffset + context ClipRect.corner +extern POINT SetContextOrigin (POINT orgOffset); +extern DrawMode SetContextDrawMode (DrawMode); +extern DrawMode GetContextDrawMode (void); +// 'area' may be NULL to copy the entire CONTEXT cliprect +// 'area' is relative to the CONTEXT cliprect +extern DRAWABLE CopyContextRect (const RECT* area); + +extern TIME_VALUE DrawablesIntersect (INTERSECT_CONTROL *pControl0, + INTERSECT_CONTROL *pControl1, TIME_VALUE max_time_val); +extern void DrawStamp (STAMP *pStamp); +extern void DrawFilledStamp (STAMP *pStamp); +extern void DrawPoint (POINT *pPoint); +extern void DrawRectangle (RECT *pRect); +extern void DrawFilledRectangle (RECT *pRect); +extern void DrawLine (LINE *pLine); +extern void font_DrawText (TEXT *pText); +extern void font_DrawTracedText (TEXT *pText, Color text, Color trace); +extern void DrawBatch (PRIMITIVE *pBasePrim, PRIM_LINKS PrimLinks, + BATCH_FLAGS BatchFlags); +extern void BatchGraphics (void); +extern void UnbatchGraphics (void); +extern void FlushGraphics (void); +extern void ClearDrawable (void); +#ifdef DEBUG +extern CONTEXT CreateContextAux (const char *name); +#define CreateContext(name) CreateContextAux((name)) +#else /* if !defined(DEBUG) */ +extern CONTEXT CreateContextAux (void); +#define CreateContext(name) CreateContextAux() +#endif /* !defined(DEBUG) */ +extern BOOLEAN DestroyContext (CONTEXT ContextRef); +extern DRAWABLE CreateDisplay (CREATE_FLAGS CreateFlags, SIZE *pwidth, + SIZE *pheight); +extern DRAWABLE CreateDrawable (CREATE_FLAGS CreateFlags, SIZE width, + SIZE height, COUNT num_frames); +extern BOOLEAN DestroyDrawable (DRAWABLE Drawable); +extern BOOLEAN GetFrameRect (FRAME Frame, RECT *pRect); +#ifdef DEBUG +extern const char *GetContextName (CONTEXT context); +extern CONTEXT GetFirstContext (void); +extern CONTEXT GetNextContext (CONTEXT context); +extern size_t GetContextCount (void); +#endif /* DEBUG */ + +extern HOT_SPOT SetFrameHot (FRAME Frame, HOT_SPOT HotSpot); +extern HOT_SPOT GetFrameHot (FRAME Frame); +extern BOOLEAN InstallGraphicResTypes (void); +extern DRAWABLE LoadGraphicFile (const char *pStr); +extern FONT LoadFontFile (const char *pStr); +extern void *LoadGraphicInstance (RESOURCE res); +extern DRAWABLE LoadDisplayPixmap (const RECT *area, FRAME frame); +extern FRAME SetContextFontEffect (FRAME EffectFrame); +extern FONT SetContextFont (FONT Font); +extern BOOLEAN DestroyFont (FONT FontRef); +// The returned pRect is relative to the context drawing origin +extern BOOLEAN TextRect (TEXT *pText, RECT *pRect, BYTE *pdelta); +extern BOOLEAN GetContextFontLeading (SIZE *pheight); +extern BOOLEAN GetContextFontLeadingWidth (SIZE *pwidth); +extern COUNT GetFrameCount (FRAME Frame); +extern COUNT GetFrameIndex (FRAME Frame); +extern FRAME SetAbsFrameIndex (FRAME Frame, COUNT FrameIndex); +extern FRAME SetRelFrameIndex (FRAME Frame, SIZE FrameOffs); +extern FRAME SetEquFrameIndex (FRAME DstFrame, FRAME SrcFrame); +extern FRAME IncFrameIndex (FRAME Frame); +extern FRAME DecFrameIndex (FRAME Frame); +extern DRAWABLE CopyFrameRect (FRAME Frame, const RECT *area); +extern DRAWABLE CloneFrame (FRAME Frame); +extern DRAWABLE RotateFrame (FRAME Frame, int angle_deg); +extern DRAWABLE RescaleFrame (FRAME, int width, int height); +// This pair works for both paletted and trucolor frames +extern BOOLEAN ReadFramePixelColors (FRAME frame, Color *pixels, + int width, int height); +extern BOOLEAN WriteFramePixelColors (FRAME frame, const Color *pixels, + int width, int height); +// This pair only works for paletted frames +extern BOOLEAN ReadFramePixelIndexes (FRAME frame, BYTE *pixels, + int width, int height); +extern BOOLEAN WriteFramePixelIndexes (FRAME frame, const BYTE *pixels, + int width, int height); +extern void SetFrameTransparentColor (FRAME, Color); + +// If the frame is an active SCREEN_DRAWABLE, this call must be +// preceeded by FlushGraphics() for draw commands to have taken effect +extern Color GetFramePixel (FRAME, POINT pixelPt); + +extern FRAME CaptureDrawable (DRAWABLE Drawable); +extern DRAWABLE ReleaseDrawable (FRAME Frame); + +extern DRAWABLE GetFrameParentDrawable (FRAME Frame); + +extern BOOLEAN SetColorMap (COLORMAPPTR ColorMapPtr); +extern DWORD XFormColorMap (COLORMAPPTR ColorMapPtr, SIZE TimeInterval); +extern DWORD FadeScreen (ScreenFadeType fadeType, SIZE TimeInterval); +extern void FlushColorXForms (void); +#define InitColorMapResources InitStringTableResources +#define LoadColorMapFile LoadStringTableFile +#define LoadColorMapInstance LoadStringTableInstance +#define CaptureColorMap CaptureStringTable +#define ReleaseColorMap ReleaseStringTable +#define DestroyColorMap DestroyStringTable +#define GetColorMapRef GetStringTable +#define GetColorMapCount GetStringTableCount +#define GetColorMapIndex GetStringTableIndex +#define SetAbsColorMapIndex SetAbsStringTableIndex +#define SetRelColorMapIndex SetRelStringTableIndex +#define GetColorMapLength GetStringLengthBin + +extern COLORMAPPTR GetColorMapAddress (COLORMAP); + +void SetSystemRect (const RECT *pRect); +void ClearSystemRect (void); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_GFXLIB_H_ */ diff --git a/src/libs/graphics/Makeinfo b/src/libs/graphics/Makeinfo new file mode 100644 index 0000000..1a9f7ee --- /dev/null +++ b/src/libs/graphics/Makeinfo @@ -0,0 +1,12 @@ +if [ "$uqm_GFXMODULE" = "sdl" ]; then + uqm_SUBDIRS="sdl" +fi + +uqm_CFILES="boxint.c clipline.c cmap.c context.c drawable.c filegfx.c + bbox.c dcqueue.c gfxload.c + font.c frame.c gfx_common.c intersec.c loaddisp.c + pixmap.c resgfx.c tfb_draw.c tfb_prim.c widgets.c" + +uqm_HFILES="bbox.h cmap.h context.h dcqueue.h drawable.h drawcmd.h font.h + gfx_common.h gfxintrn.h prim.h tfb_draw.h tfb_prim.h widgets.h" + diff --git a/src/libs/graphics/bbox.c b/src/libs/graphics/bbox.c new file mode 100644 index 0000000..ce57d32 --- /dev/null +++ b/src/libs/graphics/bbox.c @@ -0,0 +1,133 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "libs/graphics/bbox.h" + +TFB_BoundingBox TFB_BBox; +int maxWidth; +int maxHeight; + +void +TFB_BBox_Init (int width, int height) +{ + maxWidth = width; + maxHeight = height; + TFB_BBox.clip.extent.width = width; + TFB_BBox.clip.extent.height = height; +} + +void +TFB_BBox_Reset (void) +{ + TFB_BBox.valid = 0; +} + +void +TFB_BBox_SetClipRect (const RECT *r) +{ + if (!r) + { /* No clipping -- full rect */ + TFB_BBox.clip.corner.x = 0; + TFB_BBox.clip.corner.y = 0; + TFB_BBox.clip.extent.width = maxWidth; + TFB_BBox.clip.extent.height = maxHeight; + return; + } + + TFB_BBox.clip = *r; + + /* Make sure the cliprect is sane */ + if (TFB_BBox.clip.corner.x < 0) + TFB_BBox.clip.corner.x = 0; + + if (TFB_BBox.clip.corner.y < 0) + TFB_BBox.clip.corner.y = 0; + + if (TFB_BBox.clip.corner.x + TFB_BBox.clip.extent.width > maxWidth) + TFB_BBox.clip.extent.width = maxWidth - TFB_BBox.clip.corner.x; + + if (TFB_BBox.clip.corner.y + TFB_BBox.clip.extent.height > maxHeight) + TFB_BBox.clip.extent.height = maxHeight - TFB_BBox.clip.corner.y; +} + +void +TFB_BBox_RegisterPoint (int x, int y) +{ + int x1 = TFB_BBox.clip.corner.x; + int y1 = TFB_BBox.clip.corner.y; + int x2 = TFB_BBox.clip.corner.x + TFB_BBox.clip.extent.width - 1; + int y2 = TFB_BBox.clip.corner.y + TFB_BBox.clip.extent.height - 1; + + /* Constrain coordinates */ + if (x < x1) x = x1; + if (x >= x2) x = x2; + if (y < y1) y = y1; + if (y >= y2) y = y2; + + /* Is this the first point? If so, set a pixel-region and return. */ + if (!TFB_BBox.valid) + { + TFB_BBox.valid = 1; + TFB_BBox.region.corner.x = x; + TFB_BBox.region.corner.y = y; + TFB_BBox.region.extent.width = 1; + TFB_BBox.region.extent.height = 1; + return; + } + + /* Otherwise expand the rectangle if necessary. */ + x1 = TFB_BBox.region.corner.x; + y1 = TFB_BBox.region.corner.y; + x2 = TFB_BBox.region.corner.x + TFB_BBox.region.extent.width - 1; + y2 = TFB_BBox.region.corner.y + TFB_BBox.region.extent.height - 1; + + if (x < x1) { + TFB_BBox.region.corner.x = x; + TFB_BBox.region.extent.width += x1 - x; + } + if (y < y1) { + TFB_BBox.region.corner.y = y; + TFB_BBox.region.extent.height += y1 - y; + } + if (x > x2) { + TFB_BBox.region.extent.width += x - x2; + } + if (y > y2) { + TFB_BBox.region.extent.height += y - y2; + } +} + +void +TFB_BBox_RegisterRect (const RECT *r) +{ + /* RECT will still register as a corner point of the cliprect even + * if it does not intersect with the cliprect at all. This is not + * a problem, as more is not less. */ + TFB_BBox_RegisterPoint (r->corner.x, r->corner.y); + TFB_BBox_RegisterPoint (r->corner.x + r->extent.width - 1, + r->corner.y + r->extent.height - 1); +} + +void +TFB_BBox_RegisterCanvas (TFB_Canvas c, int x, int y) +{ + RECT r; + r.corner.x = x; + r.corner.y = y; + TFB_DrawCanvas_GetExtent (c, &r.extent); + TFB_BBox_RegisterRect (&r); +} diff --git a/src/libs/graphics/bbox.h b/src/libs/graphics/bbox.h new file mode 100644 index 0000000..33d3000 --- /dev/null +++ b/src/libs/graphics/bbox.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 BBOX_H_INCL__ +#define BBOX_H_INCL__ + +#include "libs/gfxlib.h" +#include "libs/graphics/tfb_draw.h" + +/* Bounding Box operations. These operations are NOT synchronized. + * However, they should only be accessed by TFB_FlushGraphics and + * TFB_SwapBuffers, or the routines that they exclusively call -- all + * of which are only callable by the thread that is permitted to touch + * the screen. No explicit locks should therefore be required. */ + +typedef struct { + int valid; // If zero, the next point registered becomes the region + RECT region; // The actual modified rectangle + RECT clip; // Points outside of this rectangle are pushed to + // the closest border point +} TFB_BoundingBox; + +extern TFB_BoundingBox TFB_BBox; + +void TFB_BBox_RegisterPoint (int x, int y); +void TFB_BBox_RegisterRect (const RECT *r); +void TFB_BBox_RegisterCanvas (TFB_Canvas c, int x, int y); + +void TFB_BBox_Init (int width, int height); +void TFB_BBox_Reset (void); +void TFB_BBox_SetClipRect (const RECT *r); + +#endif /* BBOX_H_INCL__ */ diff --git a/src/libs/graphics/boxint.c b/src/libs/graphics/boxint.c new file mode 100644 index 0000000..0500311 --- /dev/null +++ b/src/libs/graphics/boxint.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 "gfxintrn.h" + +#undef MIN +#define MIN(a, b) (((a) <= (b)) ? (a) : (b)) +#undef MAX +#define MAX(a, b) (((a) >= (b)) ? (a) : (b)) + +INTERSECT_CODE +BoxIntersect (RECT *pr1, RECT *pr2, RECT *pinter) +{ + INTERSECT_CODE intersect_code; + COORD x1; + SIZE w1, w2, delta; + + intersect_code = INTERSECT_NOCLIP; + + x1 = pr1->corner.x - pr2->corner.x; + + w1 = pr1->extent.width; + w2 = pr2->extent.width; + if ((delta = w2 - x1) <= w1) + { + if (delta != w1) + { + w1 = delta; + intersect_code &= ~INTERSECT_NOCLIP; + } + intersect_code |= INTERSECT_RIGHT; + } + if (x1 <= 0) + { + if (x1 < 0) + { + w1 += x1; + x1 = 0; + intersect_code &= ~INTERSECT_NOCLIP; + } + intersect_code |= INTERSECT_LEFT; + } + + if (w1 > 0) + { +#define h2 w2 + COORD y1; + SIZE h1; + + y1 = pr1->corner.y - pr2->corner.y; + + h1 = pr1->extent.height; + h2 = pr2->extent.height; + if ((delta = h2 - y1) <= h1) + { + if (delta != h1) + { + h1 = delta; + intersect_code &= ~INTERSECT_NOCLIP; + } + intersect_code |= INTERSECT_BOTTOM; + } + if (y1 <= 0) + { + if (y1 < 0) + { + h1 += y1; + y1 = 0; + intersect_code &= ~INTERSECT_NOCLIP; + } + intersect_code |= INTERSECT_TOP; + } + + if (h1 > 0) + { + pinter->corner.x = x1 + pr2->corner.x; + pinter->corner.y = y1 + pr2->corner.y; + pinter->extent.width = w1; + pinter->extent.height = h1; + + return (intersect_code); + } +#undef h2 + } + + return ((INTERSECT_CODE)0); +} + +void +BoxUnion (RECT *pr1, RECT *pr2, RECT *punion) +{ +#if NEVER // Part of lower FIXME. + COORD x2, y2, w2, h2; +#endif // NEVER + + // Union is A AND B, put together, correct? Returns a bigger box that + // encompasses the two. + punion->corner.x = MIN(pr1->corner.x, pr2->corner.x); + punion->corner.y = MIN(pr1->corner.y, pr2->corner.y); + + punion->extent.width = MAX(pr1->corner.x + pr1->extent.width, + pr2->corner.x + pr2->extent.width) - punion->corner.x; + punion->extent.height = MAX(pr1->corner.y + pr1->extent.height, + pr2->corner.y + pr2->extent.height) - punion->corner.y; + + +#if NEVER // FIXME - I think this is broken, but keeping it around for reference + // FIXME - just in case. + +#if 1 /* alter based on 0 widths */ + + x2 = + (pr1->corner.x < pr2->corner.x)? pr1->corner.x : pr2->corner.x; + + y2 = + (pr1->corner.y < pr2->corner.y)? pr1->corner.y : pr2->corner.y; + + w2 = ( + ((pr1->corner.x + pr1->extent.width) > (pr2->corner.x + pr2->extent.width))? + (pr1->corner.x + pr1->extent.width) : (pr2->corner.x + pr2->extent.width) + ) - punion->corner.x; + + h2 = ( + ((pr1->corner.y + pr1->extent.height) > (pr2->corner.y + pr2->extent.height))? + (pr1->corner.y + pr1->extent.height) : (pr2->corner.y + pr2->extent.height) + ) - punion->corner.y; +#else + SIZE delta; + COORD x1, y1, w1, h1; + + x1 = pr1->corner.x; + w1 = pr1->extent.width; + x2 = pr2->corner.x; + w2 = pr2->extent.width; + if ((delta = x1 - x2) >= 0) + w1 += delta; + else + { + w2 -= delta; + x2 += delta; + } + + y1 = pr1->corner.y; + h1 = pr1->extent.height; + y2 = pr2->corner.y; + h2 = pr2->extent.height; + if ((delta = y1 - y2) >= 0) + h1 += delta; + else + { + h2 -= delta; + y2 += delta; + } + + if ((delta = w1 - w2) > 0) + w2 += delta; + if ((delta = h1 - h2) > 0) + h2 += delta; +#endif + + punion->corner.x = x2; + punion->corner.y = y2; + punion->extent.width = w2; + punion->extent.height = h2; + +#endif // NEVER +} + diff --git a/src/libs/graphics/clipline.c b/src/libs/graphics/clipline.c new file mode 100644 index 0000000..ab2d7dd --- /dev/null +++ b/src/libs/graphics/clipline.c @@ -0,0 +1,241 @@ +//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 "gfxintrn.h" + +INTERSECT_CODE +_clip_line (const RECT *pClipRect, BRESENHAM_LINE *pLine) +{ + COORD p; + COORD x0, y0, xmin, ymin, xmax, ymax; + SIZE abs_delta_x, abs_delta_y; + INTERSECT_CODE intersect_code; + + xmin = pClipRect->corner.x; + ymin = pClipRect->corner.y; + xmax = pClipRect->corner.x + pClipRect->extent.width - 1; + ymax = pClipRect->corner.y + pClipRect->extent.height - 1; + if (pLine->first.x <= pLine->second.x) + pLine->end_points_exchanged = FALSE; + else + { + p = pLine->first.x; + pLine->first.x = pLine->second.x; + pLine->second.x = p; + + p = pLine->first.y; + pLine->first.y = pLine->second.y; + pLine->second.y = p; + + pLine->end_points_exchanged = TRUE; + } + + if (pLine->first.x > xmax || pLine->second.x < xmin || + (pLine->first.y > ymax && pLine->second.y > ymax) || + (pLine->first.y < ymin && pLine->second.y < ymin)) + return ((INTERSECT_CODE)0); + + intersect_code = INTERSECT_NOCLIP; + x0 = y0 = 0; + abs_delta_x = (pLine->second.x - pLine->first.x) << 1; + abs_delta_y = (pLine->second.y - pLine->first.y) << 1; + pLine->abs_delta_x = abs_delta_x; + pLine->abs_delta_y = abs_delta_y; + if (abs_delta_y == 0) + { + if (pLine->first.x < xmin) + { + pLine->first.x = xmin; + intersect_code |= INTERSECT_LEFT; + } + if (pLine->second.x > xmax) + { + pLine->second.x = xmax; + intersect_code |= INTERSECT_RIGHT; + } + } + else if (abs_delta_x == 0) + { + if (abs_delta_y < 0) + { + p = pLine->first.y; + pLine->first.y = pLine->second.y; + pLine->second.y = p; + + pLine->abs_delta_y = + abs_delta_y = -abs_delta_y; + } + + if (pLine->first.y < ymin) + { + pLine->first.y = ymin; + intersect_code |= INTERSECT_TOP; + } + if (pLine->second.y > ymax) + { + pLine->second.y = ymax; + intersect_code |= INTERSECT_BOTTOM; + } + } + else + { + COORD x1, y1; + + p = pLine->first.x; + x1 = pLine->second.x - p; + xmin = xmin - p; + xmax = xmax - p; + + p = pLine->first.y; + if (abs_delta_y > 0) + { + y1 = pLine->second.y - p; + ymin = ymin - p; + ymax = ymax - p; + } + else + { + y1 = p - pLine->second.y; + ymin = p - ymin; + ymax = p - ymax; + + p = ymin; + ymin = ymax; + ymax = p; + abs_delta_y = -abs_delta_y; + } + + if (abs_delta_x > abs_delta_y) + { + SIZE half_dx; + + half_dx = abs_delta_x >> 1; + if (x0 < xmin) + { + if ((y0 = (COORD)(((long)abs_delta_y * + (x0 = xmin) + half_dx) / abs_delta_x)) > ymax) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_LEFT; + } + if (x1 > xmax) + { + if ((y1 = (COORD)(((long)abs_delta_y * + (x1 = xmax) + half_dx) / abs_delta_x)) < ymin) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_RIGHT; + } + if (y0 < ymin) + { + if ((x0 = (COORD)(((long)abs_delta_x * + (y0 = ymin) - half_dx + (abs_delta_y - 1)) / + abs_delta_y)) > xmax) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_TOP; + intersect_code &= ~INTERSECT_LEFT; + } + if (y1 > ymax) + { + if ((x1 = (COORD)(((long)abs_delta_x * + ((y1 = ymax) + 1) - half_dx + (abs_delta_y - 1)) / + abs_delta_y) - 1) < xmin) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_BOTTOM; + intersect_code &= ~INTERSECT_RIGHT; + } + } + else + { + SIZE half_dy; + + half_dy = abs_delta_y >> 1; + if (y0 < ymin) + { + if ((x0 = (COORD)(((long)abs_delta_x * + (y0 = ymin) + half_dy) / abs_delta_y)) > xmax) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_TOP; + } + if (y1 > ymax) + { + if ((x1 = (COORD)(((long)abs_delta_x * + (y1 = ymax) + half_dy) / abs_delta_y)) < xmin) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_BOTTOM; + } + if (x0 < xmin) + { + if ((y0 = (COORD)(((long)abs_delta_y * + (x0 = xmin) - half_dy + (abs_delta_x - 1)) / + abs_delta_x)) > ymax) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_LEFT; + intersect_code &= ~INTERSECT_TOP; + } + if (x1 > xmax) + { + if ((y1 = (COORD)(((long)abs_delta_y * + ((x1 = xmax) + 1) - half_dy + (abs_delta_x - 1)) / + abs_delta_x) - 1) < ymin) + return ((INTERSECT_CODE)0); + intersect_code |= INTERSECT_RIGHT; + intersect_code &= ~INTERSECT_BOTTOM; + } + } + + pLine->second.x = pLine->first.x + x1; + pLine->first.x += x0; + if (pLine->abs_delta_y > 0) + { + pLine->second.y = pLine->first.y + y1; + pLine->first.y += y0; + } + else + { + INTERSECT_CODE y_code; + + pLine->second.y = pLine->first.y - y1; + pLine->first.y -= y0; + + y_code = (INTERSECT_CODE)(intersect_code + & (INTERSECT_TOP | INTERSECT_BOTTOM)); + if (y_code && y_code != (INTERSECT_TOP | INTERSECT_BOTTOM)) + intersect_code ^= (INTERSECT_TOP | INTERSECT_BOTTOM); + } + } + + if (!(intersect_code & INTERSECT_ALL_SIDES)) + { + if (abs_delta_x > abs_delta_y) + pLine->error_term = -(SIZE)(abs_delta_x >> 1); + else + pLine->error_term = -(SIZE)(abs_delta_y >> 1); + } + else + { + intersect_code &= ~INTERSECT_NOCLIP; + if (abs_delta_x > abs_delta_y) + pLine->error_term = (SIZE)((x0 * (long)abs_delta_y) - + (y0 * (long)abs_delta_x)) - (abs_delta_x >> 1); + else + pLine->error_term = (SIZE)((y0 * (long)abs_delta_x) - + (x0 * (long)abs_delta_y)) - (abs_delta_y >> 1); + } + + return (pLine->intersect_code = intersect_code); +} + diff --git a/src/libs/graphics/cmap.c b/src/libs/graphics/cmap.c new file mode 100644 index 0000000..53cd13f --- /dev/null +++ b/src/libs/graphics/cmap.c @@ -0,0 +1,663 @@ +//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/graphics/cmap.h" +#include "libs/threadlib.h" +#include "libs/timelib.h" +#include "libs/inplib.h" +#include "libs/strlib.h" + // for GetStringAddress() +#include "libs/log.h" +#include +#include + + +typedef struct xform_control +{ + int CMapIndex; // -1 means unused + COLORMAPPTR CMapPtr; + SIZE Ticks; + DWORD StartTime; + DWORD EndTime; + Color OldCMap[NUMBER_OF_PLUTVALS]; +} XFORM_CONTROL; + +#define MAX_XFORMS 16 +static struct +{ + XFORM_CONTROL TaskControl[MAX_XFORMS]; + volatile int Highest; + // 'pending' is Highest >= 0 + Mutex Lock; +} XFormControl; + +static int fadeAmount = FADE_NORMAL_INTENSITY; +static int fadeDelta; +static TimeCount fadeStartTime; +static sint32 fadeInterval; +static Mutex fadeLock; + +#define SPARE_COLORMAPS 20 + +// Colormaps are rapidly replaced in some parts of the game, so +// it pays to have some spares on hand +static TFB_ColorMap *poolhead; +static int poolcount; + +static TFB_ColorMap * colormaps[MAX_COLORMAPS]; +static int mapcount; +static Mutex maplock; + + +static void release_colormap (TFB_ColorMap *map); +static void delete_colormap (TFB_ColorMap *map); + + +void +InitColorMaps (void) +{ + int i; + + // init colormaps + maplock = CreateMutex ("Colormaps Lock", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO); + + // init xform control + XFormControl.Highest = -1; + XFormControl.Lock = CreateMutex ("Transform Lock", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO); + for (i = 0; i < MAX_XFORMS; ++i) + XFormControl.TaskControl[i].CMapIndex = -1; + + fadeLock = CreateMutex ("Fade Lock", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO); +} + +void +UninitColorMaps (void) +{ + int i; + TFB_ColorMap *next; + + for (i = 0; i < MAX_COLORMAPS; ++i) + { + TFB_ColorMap *map = colormaps[i]; + if (!map) + continue; + release_colormap (map); + colormaps[i] = 0; + } + + // free spares + for ( ; poolhead; poolhead = next, --poolcount) + { + next = poolhead->next; + delete_colormap (poolhead); + } + + DestroyMutex (fadeLock); + // uninit xform control + DestroyMutex (XFormControl.Lock); + + // uninit colormaps + DestroyMutex (maplock); +} + +static inline TFB_ColorMap * +alloc_colormap (void) + // returns an addrefed object +{ + TFB_ColorMap *map; + + if (poolhead) + { // have some spares + map = poolhead; + poolhead = map->next; + --poolcount; + } + else + { // no spares, need a new one + map = HMalloc (sizeof (*map)); + map->palette = AllocNativePalette (); + if (!map->palette) + { + HFree (map); + return NULL; + } + } + map->next = NULL; + map->index = -1; + map->refcount = 1; + map->version = 0; + + return map; +} + +static TFB_ColorMap * +clone_colormap (TFB_ColorMap *from, int index) + // returns an addrefed object +{ + TFB_ColorMap *map; + + map = alloc_colormap (); + if (!map) + { + log_add (log_Warning, "FATAL: clone_colormap(): " + "could not allocate a map"); + exit (EXIT_FAILURE); + } + else + { // fresh new map + map->index = index; + if (from) + map->version = from->version; + } + map->version++; + + return map; +} + +static void +delete_colormap (TFB_ColorMap *map) +{ + FreeNativePalette (map->palette); + HFree (map); +} + +static inline void +free_colormap (TFB_ColorMap *map) +{ + if (!map) + { + log_add (log_Warning, "free_colormap(): tried to free a NULL map"); + return; + } + + if (poolcount < SPARE_COLORMAPS) + { // return to the spare pool + map->next = poolhead; + poolhead = map; + ++poolcount; + } + else + { // don't need any more spares + delete_colormap (map); + } +} + +static inline TFB_ColorMap * +get_colormap (int index) +{ + TFB_ColorMap *map; + + map = colormaps[index]; + if (!map) + { + log_add (log_Fatal, "BUG: get_colormap(): map not present"); + exit (EXIT_FAILURE); + } + + map->refcount++; + return map; +} + +static void +release_colormap (TFB_ColorMap *map) +{ + if (!map) + return; + + if (map->refcount <= 0) + { + log_add (log_Warning, "BUG: release_colormap(): refcount not >0"); + return; + } + + map->refcount--; + if (map->refcount == 0) + free_colormap (map); +} + +void +TFB_ReturnColorMap (TFB_ColorMap *map) +{ + LockMutex (maplock); + release_colormap (map); + UnlockMutex (maplock); +} + +TFB_ColorMap * +TFB_GetColorMap (int index) +{ + TFB_ColorMap *map; + + LockMutex (maplock); + map = get_colormap (index); + UnlockMutex (maplock); + + return map; +} + +void +GetColorMapColors (Color *colors, TFB_ColorMap *map) +{ + int i; + + if (!map) + return; + + for (i = 0; i < NUMBER_OF_PLUTVALS; ++i) + colors[i] = GetNativePaletteColor (map->palette, i); +} + +BOOLEAN +SetColorMap (COLORMAPPTR map) +{ + int start, end; + int total_size; + UBYTE *colors = (UBYTE*)map; + TFB_ColorMap **mpp; + + if (!map) + return TRUE; + + start = *colors++; + end = *colors++; + if (start > end) + { + log_add (log_Warning, "ERROR: SetColorMap(): " + "starting map (%d) not less or eq ending (%d)", + start, end); + return FALSE; + } + if (start >= MAX_COLORMAPS) + { + log_add (log_Warning, "ERROR: SetColorMap(): " + "starting map (%d) beyond range (0-%d)", + start, (int)MAX_COLORMAPS - 1); + return FALSE; + } + if (end >= MAX_COLORMAPS) + { + log_add (log_Warning, "SetColorMap(): " + "ending map (%d) beyond range (0-%d)\n", + end, (int)MAX_COLORMAPS - 1); + end = MAX_COLORMAPS - 1; + } + + total_size = end + 1; + + LockMutex (maplock); + + if (total_size > mapcount) + mapcount = total_size; + + // parse the supplied PLUTs into our colormaps + for (mpp = colormaps + start; start <= end; ++start, ++mpp) + { + int i; + TFB_ColorMap *newmap; + TFB_ColorMap *oldmap; + + oldmap = *mpp; + newmap = clone_colormap (oldmap, start); + + for (i = 0; i < NUMBER_OF_PLUTVALS; ++i, colors += PLUTVAL_BYTE_SIZE) + { + Color color; + + color.a = 0xff; + color.r = colors[PLUTVAL_RED]; + color.g = colors[PLUTVAL_GREEN]; + color.b = colors[PLUTVAL_BLUE]; + SetNativePaletteColor (newmap->palette, i, color); + } + + *mpp = newmap; + release_colormap (oldmap); + } + + UnlockMutex (maplock); + + return TRUE; +} + +/* Fade Transforms */ + +int +GetFadeAmount (void) +{ + int newAmount; + + LockMutex (fadeLock); + + if (fadeInterval) + { // have a pending fade + TimeCount Now = GetTimeCounter (); + sint32 elapsed; + + elapsed = Now - fadeStartTime; + if (elapsed > fadeInterval) + elapsed = fadeInterval; + + newAmount = fadeAmount + (long)fadeDelta * elapsed / fadeInterval; + + if (elapsed >= fadeInterval) + { // fade is over + fadeAmount = newAmount; + fadeInterval = 0; + } + } + else + { // no fade pending, return the current + newAmount = fadeAmount; + } + + UnlockMutex (fadeLock); + + return newAmount; +} + +static void +finishPendingFade (void) +{ + if (fadeInterval) + { // end the fade immediately + fadeAmount += fadeDelta; + fadeInterval = 0; + } +} + +static void +FlushFadeXForms (void) +{ + LockMutex (fadeLock); + finishPendingFade (); + UnlockMutex (fadeLock); +} + +DWORD +FadeScreen (ScreenFadeType fadeType, SIZE TimeInterval) +{ + TimeCount TimeOut; + int FadeEnd; + + switch (fadeType) + { + case FadeAllToBlack: + case FadeSomeToBlack: + FadeEnd = FADE_NO_INTENSITY; + break; + case FadeAllToColor: + case FadeSomeToColor: + FadeEnd = FADE_NORMAL_INTENSITY; + break; + case FadeAllToWhite: + case FadeSomeToWhite: + FadeEnd = FADE_FULL_INTENSITY; + break; + default: + return (GetTimeCounter ()); + } + + // Don't make users wait for fades + if (QuitPosted) + TimeInterval = 0; + + LockMutex (fadeLock); + + finishPendingFade (); + + if (TimeInterval <= 0) + { // end the fade immediately + fadeAmount = FadeEnd; + // cancel any pending fades + fadeInterval = 0; + TimeOut = GetTimeCounter (); + } + else + { + fadeInterval = TimeInterval; + fadeDelta = FadeEnd - fadeAmount; + fadeStartTime = GetTimeCounter (); + TimeOut = fadeStartTime + TimeInterval + 1; + } + + UnlockMutex (fadeLock); + + return TimeOut; +} + +/* Colormap Transforms */ + +static void +finish_colormap_xform (int which) +{ + SetColorMap (XFormControl.TaskControl[which].CMapPtr); + XFormControl.TaskControl[which].CMapIndex = -1; + // check Highest ptr + if (which == XFormControl.Highest) + { + do + --which; + while (which >= 0 && XFormControl.TaskControl[which].CMapIndex == -1); + + XFormControl.Highest = which; + } +} + +static inline BYTE +blendChan (BYTE c1, BYTE c2, int weight, int scale) +{ + return c1 + ((int)c2 - c1) * weight / scale; +} + +/* This gives the XFormColorMap task a timeslice to do its thing + * Only one thread should ever be allowed to be calling this at any time + */ +BOOLEAN +XFormColorMap_step (void) +{ + BOOLEAN Changed = FALSE; + int x; + DWORD Now = GetTimeCounter (); + + LockMutex (XFormControl.Lock); + + for (x = 0; x <= XFormControl.Highest; ++x) + { + XFORM_CONTROL *control = &XFormControl.TaskControl[x]; + int index = control->CMapIndex; + int TicksLeft = control->EndTime - Now; + TFB_ColorMap *curmap; + + if (index < 0) + continue; // unused slot + + LockMutex (maplock); + + curmap = colormaps[index]; + if (!curmap) + { + UnlockMutex (maplock); + log_add (log_Error, "BUG: XFormColorMap_step(): no current map"); + finish_colormap_xform (x); + continue; + } + + if (TicksLeft > 0) + { +#define XFORM_SCALE 0x10000 + TFB_ColorMap *newmap = NULL; + UBYTE *newClr; + Color *oldClr; + int frac; + int i; + + newmap = clone_colormap (curmap, index); + + oldClr = control->OldCMap; + newClr = (UBYTE*)control->CMapPtr + 2; + + frac = (int)(control->Ticks - TicksLeft) * XFORM_SCALE + / control->Ticks; + + for (i = 0; i < NUMBER_OF_PLUTVALS; ++i, ++oldClr, + newClr += PLUTVAL_BYTE_SIZE) + { + Color color; + + color.a = 0xff; + color.r = blendChan (oldClr->r, newClr[PLUTVAL_RED], + frac, XFORM_SCALE); + color.g = blendChan (oldClr->g, newClr[PLUTVAL_GREEN], + frac, XFORM_SCALE); + color.b = blendChan (oldClr->b, newClr[PLUTVAL_BLUE], + frac, XFORM_SCALE); + SetNativePaletteColor (newmap->palette, i, color); + } + + colormaps[index] = newmap; + release_colormap (curmap); + } + + UnlockMutex (maplock); + + if (TicksLeft <= 0) + { // asked for immediate xform or already done + finish_colormap_xform (x); + } + + Changed = TRUE; + } + + UnlockMutex (XFormControl.Lock); + + return Changed; +} + +static void +FlushPLUTXForms (void) +{ + int i; + + LockMutex (XFormControl.Lock); + + for (i = 0; i <= XFormControl.Highest; ++i) + { + if (XFormControl.TaskControl[i].CMapIndex >= 0) + finish_colormap_xform (i); + } + XFormControl.Highest = -1; // all gone + + UnlockMutex (XFormControl.Lock); +} + +static DWORD +XFormPLUT (COLORMAPPTR ColorMapPtr, SIZE TimeInterval) +{ + TFB_ColorMap *map; + XFORM_CONTROL *control; + int index; + int x; + int first_avail = -1; + DWORD EndTime; + DWORD Now; + + Now = GetTimeCounter (); + index = *(UBYTE*)ColorMapPtr; + + LockMutex (XFormControl.Lock); + // Find an available slot, or reuse if required + for (x = 0; x <= XFormControl.Highest + && index != XFormControl.TaskControl[x].CMapIndex; + ++x) + { + if (first_avail == -1 && XFormControl.TaskControl[x].CMapIndex == -1) + first_avail = x; + } + + if (index == XFormControl.TaskControl[x].CMapIndex) + { // already xforming this colormap -- cancel and reuse slot + finish_colormap_xform (x); + } + else if (first_avail >= 0) + { // picked up a slot along the way + x = first_avail; + } + else if (x >= MAX_XFORMS) + { // flush some xforms if the queue is full + log_add (log_Debug, "WARNING: XFormPLUT(): no slots available"); + x = XFormControl.Highest; + finish_colormap_xform (x); + } + // take next unused one + control = &XFormControl.TaskControl[x]; + if (x > XFormControl.Highest) + XFormControl.Highest = x; + + // make a copy of the current map + LockMutex (maplock); + map = colormaps[index]; + if (!map) + { + UnlockMutex (maplock); + UnlockMutex (XFormControl.Lock); + log_add (log_Warning, "BUG: XFormPLUT(): no current map"); + return (0); + } + GetColorMapColors (control->OldCMap, map); + UnlockMutex (maplock); + + control->CMapIndex = index; + control->CMapPtr = ColorMapPtr; + control->Ticks = TimeInterval; + if (control->Ticks < 0) + control->Ticks = 0; /* prevent negative fade */ + control->StartTime = Now; + control->EndTime = EndTime = Now + control->Ticks; + + UnlockMutex (XFormControl.Lock); + + return (EndTime); +} + +DWORD +XFormColorMap (COLORMAPPTR ColorMapPtr, SIZE TimeInterval) +{ + if (!ColorMapPtr) + return (0); + + // Don't make users wait for transforms + if (QuitPosted) + TimeInterval = 0; + + return XFormPLUT (ColorMapPtr, TimeInterval); +} + +void +FlushColorXForms (void) +{ + FlushFadeXForms (); + FlushPLUTXForms (); +} + +// The type conversions are implicit and will generate errors +// or warnings if types change imcompatibly +COLORMAPPTR +GetColorMapAddress (COLORMAP colormap) +{ + return GetStringAddress (colormap); +} diff --git a/src/libs/graphics/cmap.h b/src/libs/graphics/cmap.h new file mode 100644 index 0000000..f27f789 --- /dev/null +++ b/src/libs/graphics/cmap.h @@ -0,0 +1,77 @@ +//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 CMAP_H +#define CMAP_H + +#include "libs/gfxlib.h" + +#define MAX_COLORMAPS 250 + +// These are pertinent to colortable file format +// We load colormaps as binary and parse them when needed +#define PLUTVAL_BYTE_SIZE 3 +// Channel order in colormap tables +#define PLUTVAL_RED 0 +#define PLUTVAL_GREEN 1 +#define PLUTVAL_BLUE 2 + +#define NUMBER_OF_PLUTVALS 256 +// Size of the colormap in a colortable file +#define PLUT_BYTE_SIZE (PLUTVAL_BYTE_SIZE * NUMBER_OF_PLUTVALS) + +#define FADE_NO_INTENSITY 0 +#define FADE_NORMAL_INTENSITY 255 +#define FADE_FULL_INTENSITY 510 + +typedef struct NativePalette NativePalette; + +typedef struct tfb_colormap +{ + int index; + // Colormap index as the game sees it + int version; + // Version goes up every time the colormap changes. This may + // be due to SetColorMap() or at every transformation step + // of XFormColorMap(). Paletted TFB_Images track the last + // colormap version they were drawn with for optimization. + int refcount; + struct tfb_colormap *next; + // for spares linking + NativePalette *palette; +} TFB_ColorMap; + +extern int GetFadeAmount (void); + +extern void InitColorMaps (void); +extern void UninitColorMaps (void); + +extern void GetColorMapColors (Color *colors, TFB_ColorMap *); + +extern TFB_ColorMap * TFB_GetColorMap (int index); +extern void TFB_ReturnColorMap (TFB_ColorMap *map); + +extern BOOLEAN XFormColorMap_step (void); + +// Native +NativePalette* AllocNativePalette (void); +void FreeNativePalette (NativePalette *); +void SetNativePaletteColor (NativePalette *, int index, Color); +Color GetNativePaletteColor (NativePalette *, int index); + +#endif /* CMAP_H */ diff --git a/src/libs/graphics/context.c b/src/libs/graphics/context.c new file mode 100644 index 0000000..d609ded --- /dev/null +++ b/src/libs/graphics/context.c @@ -0,0 +1,404 @@ +//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 "gfxintrn.h" + +GRAPHICS_STATUS _GraphicsStatusFlags; +CONTEXT _pCurContext; + +#ifdef DEBUG +// We keep track of all contexts +CONTEXT firstContext; + // The first one in the list. +CONTEXT *contextEnd = &firstContext; + // Where to put the next context. +#endif + +PRIMITIVE _locPrim; + +FONT _CurFontPtr; + +#define DEFAULT_FORE_COLOR BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F) +#define DEFAULT_BACK_COLOR BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x00), 0x00) + +#define DEFAULT_DRAW_MODE MAKE_DRAW_MODE (DRAW_DEFAULT, 255) + +CONTEXT +SetContext (CONTEXT Context) +{ + CONTEXT LastContext; + + LastContext = _pCurContext; + if (Context != LastContext) + { + if (LastContext) + { + UnsetContextFlags ( + MAKE_WORD (0, GRAPHICS_ACTIVE | DRAWABLE_ACTIVE)); + SetContextFlags ( + MAKE_WORD (0, _GraphicsStatusFlags + & (GRAPHICS_ACTIVE | DRAWABLE_ACTIVE))); + + DeactivateContext (); + } + + _pCurContext = Context; + if (_pCurContext) + { + ActivateContext (); + + _GraphicsStatusFlags &= ~(GRAPHICS_ACTIVE | DRAWABLE_ACTIVE); + _GraphicsStatusFlags |= HIBYTE (_get_context_flags ()); + + SetPrimColor (&_locPrim, _get_context_fg_color ()); + + _CurFramePtr = _get_context_fg_frame (); + _CurFontPtr = _get_context_font (); + } + } + + return (LastContext); +} + +#ifdef DEBUG +CONTEXT +CreateContextAux (const char *name) +#else /* if !defined(DEBUG) */ +CONTEXT +CreateContextAux (void) +#endif /* !defined(DEBUG) */ +{ + CONTEXT NewContext; + + NewContext = AllocContext (); + if (NewContext) + { + /* initialize context */ +#ifdef DEBUG + NewContext->name = name; + NewContext->next = NULL; + *contextEnd = NewContext; + contextEnd = &NewContext->next; +#endif /* DEBUG */ + + NewContext->Mode = DEFAULT_DRAW_MODE; + NewContext->ForeGroundColor = DEFAULT_FORE_COLOR; + NewContext->BackGroundColor = DEFAULT_BACK_COLOR; + } + + return NewContext; +} + +#ifdef DEBUG +// Loop through the list of context to the pointer which points to the +// specified context. This is either 'firstContext' or the address of +// the 'next' field of some other context. +static CONTEXT * +FindContextPtr (CONTEXT context) { + CONTEXT *ptr; + + for (ptr = &firstContext; *ptr != NULL; ptr = &(*ptr)->next) { + if (*ptr == context) + break; + } + return ptr; +} +#endif /* DEBUG */ + +BOOLEAN +DestroyContext (CONTEXT ContextRef) +{ + TFB_Image *img; + + if (ContextRef == 0) + return (FALSE); + + if (_pCurContext && _pCurContext == ContextRef) + SetContext ((CONTEXT)0); + +#ifdef DEBUG + // Unlink the context. + { + CONTEXT *contextPtr = FindContextPtr (ContextRef); + if (contextEnd == &ContextRef->next) + contextEnd = contextPtr; + *contextPtr = ContextRef->next; + } +#endif /* DEBUG */ + + img = ContextRef->FontBacking; + if (img) + TFB_DrawImage_Delete (img); + + FreeContext (ContextRef); + return TRUE; +} + +Color +SetContextForeGroundColor (Color color) +{ + Color oldColor; + + if (!ContextActive ()) + return DEFAULT_FORE_COLOR; + + oldColor = _get_context_fg_color (); + if (!sameColor(oldColor, color)) + { + SwitchContextForeGroundColor (color); + + if (!(_get_context_fbk_flags () & FBK_IMAGE)) + { + SetContextFBkFlags (FBK_DIRTY); + } + } + SetPrimColor (&_locPrim, color); + + return (oldColor); +} + +Color +GetContextForeGroundColor (void) +{ + if (!ContextActive ()) + return DEFAULT_FORE_COLOR; + + return _get_context_fg_color (); +} + +Color +SetContextBackGroundColor (Color color) +{ + Color oldColor; + + if (!ContextActive ()) + return DEFAULT_BACK_COLOR; + + oldColor = _get_context_bg_color (); + if (!sameColor(oldColor, color)) + SwitchContextBackGroundColor (color); + + return oldColor; +} + +Color +GetContextBackGroundColor (void) +{ + if (!ContextActive ()) + return DEFAULT_BACK_COLOR; + + return _get_context_bg_color (); +} + +DrawMode +SetContextDrawMode (DrawMode mode) +{ + DrawMode oldMode; + + if (!ContextActive ()) + return DEFAULT_DRAW_MODE; + + oldMode = _get_context_draw_mode (); + SwitchContextDrawMode (mode); + + return oldMode; +} + +DrawMode +GetContextDrawMode (void) +{ + if (!ContextActive ()) + return DEFAULT_DRAW_MODE; + + return _get_context_draw_mode (); +} + +// Returns a rect based at 0,0 and the size of context foreground frame +static inline RECT +_get_context_fg_rect (void) +{ + RECT r = { {0, 0}, {0, 0} }; + if (_CurFramePtr) + r.extent = GetFrameBounds (_CurFramePtr); + return r; +} + +BOOLEAN +SetContextClipRect (RECT *lpRect) +{ + if (!ContextActive ()) + return (FALSE); + + if (lpRect) + { + if (rectsEqual (*lpRect, _get_context_fg_rect ())) + { // Cliprect is undefined to mirror GetContextClipRect() + _pCurContext->ClipRect.extent.width = 0; + } + else + { // We have a cliprect + _pCurContext->ClipRect = *lpRect; + } + } + else + { // Set cliprect as undefined + _pCurContext->ClipRect.extent.width = 0; + } + + return TRUE; +} + +BOOLEAN +GetContextClipRect (RECT *lpRect) +{ + if (!ContextActive ()) + return (FALSE); + + *lpRect = _pCurContext->ClipRect; + if (!_pCurContext->ClipRect.extent.width) + { // Though the cliprect is undefined, drawing will be clipped + // to the extent of the foreground frame + *lpRect = _get_context_fg_rect (); + } + + return (_pCurContext->ClipRect.extent.width != 0); +} + +POINT +SetContextOrigin (POINT orgOffset) +{ + // XXX: This is a hack, kind of. But that's what the original did. + return SetFrameHot (_CurFramePtr, orgOffset); +} + +FRAME +SetContextFontEffect (FRAME EffectFrame) +{ + FRAME LastEffect; + + if (!ContextActive ()) + return (NULL); + + LastEffect = _get_context_fonteff (); + if (EffectFrame != LastEffect) + { + SwitchContextFontEffect (EffectFrame); + + if (EffectFrame != 0) + { + SetContextFBkFlags (FBK_IMAGE); + } + else + { + UnsetContextFBkFlags (FBK_IMAGE); + } + } + + return LastEffect; +} + +void +FixContextFontEffect (void) +{ + SIZE w, h; + TFB_Image* img; + + if (!ContextActive () || (_get_context_font_backing () != 0 + && !(_get_context_fbk_flags () & FBK_DIRTY))) + return; + + if (!GetContextFontLeading (&h) || !GetContextFontLeadingWidth (&w)) + return; + + img = _pCurContext->FontBacking; + if (img) + TFB_DrawScreen_DeleteImage (img); + + img = TFB_DrawImage_CreateForScreen (w, h, TRUE); + if (_get_context_fbk_flags () & FBK_IMAGE) + { // image pattern backing + FRAME EffectFrame = _get_context_fonteff (); + + TFB_DrawImage_Image (EffectFrame->image, + -EffectFrame->HotSpot.x, -EffectFrame->HotSpot.y, + 0, 0, NULL, DRAW_REPLACE_MODE, img); + } + else + { // solid color backing + RECT r = { {0, 0}, {w, h} }; + Color color = _get_context_fg_color (); + + TFB_DrawImage_Rect (&r, color, DRAW_REPLACE_MODE, img); + } + + _pCurContext->FontBacking = img; + UnsetContextFBkFlags (FBK_DIRTY); +} + +// 'area' may be NULL to copy the entire CONTEXT cliprect +// 'area' is relative to the CONTEXT cliprect +DRAWABLE +CopyContextRect (const RECT* area) +{ + RECT clipRect; + RECT fgRect; + RECT r; + + if (!ContextActive () || !_CurFramePtr) + return NULL; + + fgRect = _get_context_fg_rect (); + GetContextClipRect (&clipRect); + r = clipRect; + if (area) + { // a portion of the context + r.corner.x += area->corner.x; + r.corner.y += area->corner.y; + r.extent = area->extent; + } + // TODO: Should this take CONTEXT origin into account too? + // validate the rect + if (!BoxIntersect (&r, &fgRect, &r)) + return NULL; + + if (_CurFramePtr->Type == SCREEN_DRAWABLE) + return LoadDisplayPixmap (&r, NULL); + else + return CopyFrameRect (_CurFramePtr, &r); +} + +#ifdef DEBUG +const char * +GetContextName (CONTEXT context) +{ + return context->name; +} + +CONTEXT +GetFirstContext (void) +{ + return firstContext; +} + +CONTEXT +GetNextContext (CONTEXT context) +{ + return context->next; +} +#endif /* DEBUG */ + diff --git a/src/libs/graphics/context.h b/src/libs/graphics/context.h new file mode 100644 index 0000000..09b50cf --- /dev/null +++ b/src/libs/graphics/context.h @@ -0,0 +1,147 @@ +//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 LIBS_GRAPHICS_CONTEXT_H_ +#define LIBS_GRAPHICS_CONTEXT_H_ + +#include "tfb_draw.h" +#include "libs/memlib.h" + +typedef UWORD FBK_FLAGS; +#define FBK_DIRTY (1 << 0) +#define FBK_IMAGE (1 << 1) + +struct context_desc +{ + UWORD Flags; + // Low nibble currently unused + // High nibble contains GRAPHICS_STATUS + + Color ForeGroundColor, BackGroundColor; + DrawMode Mode; + FRAME ForeGroundFrame; + FONT Font; + + RECT ClipRect; + + FRAME FontEffect; + TFB_Image *FontBacking; + FBK_FLAGS BackingFlags; + +#ifdef DEBUG + const char *name; + CONTEXT next; +#endif +}; + +#define AllocContext() HCalloc (sizeof (CONTEXT_DESC)) +#define FreeContext HFree + +extern CONTEXT _pCurContext; +extern PRIMITIVE _locPrim; + +#define _get_context_fg_color() (_pCurContext->ForeGroundColor) +#define _get_context_bg_color() (_pCurContext->BackGroundColor) +#define _get_context_flags() (_pCurContext->Flags) +#define _get_context_fg_frame() (_pCurContext->ForeGroundFrame) +#define _get_context_font() (_pCurContext->Font) +#define _get_context_fbk_flags() (_pCurContext->BackingFlags) +#define _get_context_fonteff() (_pCurContext->FontEffect) +#define _get_context_font_backing() (_pCurContext->FontBacking) +#define _get_context_draw_mode() (_pCurContext->Mode) + +#define SwitchContextDrawMode(m) \ +{ \ + _pCurContext->Mode = (m); \ +} +#define SwitchContextForeGroundColor(c) \ +{ \ + _pCurContext->ForeGroundColor = (c); \ +} +#define SwitchContextBackGroundColor(c) \ +{ \ + _pCurContext->BackGroundColor = (c); \ +} +#define SetContextFlags(f) \ +{ \ + _pCurContext->Flags |= (f); \ +} +#define UnsetContextFlags(f) \ +{ \ + _pCurContext->Flags &= ~(f); \ +} +#define SwitchContextFGFrame(f) \ +{ \ + _pCurContext->ForeGroundFrame = (f); \ +} +#define SwitchContextFont(f) \ +{ \ + _pCurContext->Font = (f); \ + SetContextFBkFlags (FBK_DIRTY); \ +} +#define SwitchContextBGFunc(f) \ +{ \ + _pCurContext->BackGroundFunc = (f); \ +} +#define SetContextFBkFlags(f) \ +{ \ + _pCurContext->BackingFlags |= (f); \ +} +#define UnsetContextFBkFlags(f) \ +{ \ + _pCurContext->BackingFlags &= ~(f); \ +} +#define SwitchContextFontEffect(f) \ +{ \ + _pCurContext->FontEffect = (f); \ + SetContextFBkFlags (FBK_DIRTY); \ +} + +typedef BYTE GRAPHICS_STATUS; + +extern GRAPHICS_STATUS _GraphicsStatusFlags; +#define GRAPHICS_ACTIVE (GRAPHICS_STATUS)(1 << 0) +#define GRAPHICS_VISIBLE (GRAPHICS_STATUS)(1 << 1) +#define CONTEXT_ACTIVE (GRAPHICS_STATUS)(1 << 2) +#define DRAWABLE_ACTIVE (GRAPHICS_STATUS)(1 << 3) +#define DeactivateGraphics() (_GraphicsStatusFlags &= ~GRAPHICS_ACTIVE) +#define ActivateGraphics() (_GraphicsStatusFlags |= GRAPHICS_ACTIVE) +#define GraphicsActive() (_GraphicsStatusFlags & GRAPHICS_ACTIVE) +#define DeactivateVisible() (_GraphicsStatusFlags &= ~GRAPHICS_VISIBLE) +#define ActivateVisible() (_GraphicsStatusFlags |= GRAPHICS_VISIBLE) +#define DeactivateContext() (_GraphicsStatusFlags &= ~CONTEXT_ACTIVE) +#define ActivateContext() (_GraphicsStatusFlags |= CONTEXT_ACTIVE) +#define ContextActive() (_GraphicsStatusFlags & CONTEXT_ACTIVE) +#define DeactivateDrawable() (_GraphicsStatusFlags &= ~DRAWABLE_ACTIVE) +#define ActivateDrawable() (_GraphicsStatusFlags |= DRAWABLE_ACTIVE) +#define DrawableActive() (_GraphicsStatusFlags & DRAWABLE_ACTIVE) + +#define SYSTEM_ACTIVE (GRAPHICS_STATUS)(CONTEXT_ACTIVE | DRAWABLE_ACTIVE) + +#define GraphicsSystemActive() \ + ((_GraphicsStatusFlags & SYSTEM_ACTIVE) == SYSTEM_ACTIVE) +#define GraphicsStatus() \ + (_GraphicsStatusFlags & (GRAPHICS_STATUS)(GRAPHICS_ACTIVE \ + | GRAPHICS_VISIBLE)) + +// pValidRect or origin may be NULL +BOOLEAN GetContextValidRect (RECT *pValidRect, POINT *origin); +extern void FixContextFontEffect (void); + +#endif /* LIBS_GRAPHICS_CONTEXT_H_ */ + diff --git a/src/libs/graphics/dcqueue.c b/src/libs/graphics/dcqueue.c new file mode 100644 index 0000000..70c0662 --- /dev/null +++ b/src/libs/graphics/dcqueue.c @@ -0,0 +1,670 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "libs/threadlib.h" +#include "libs/graphics/drawcmd.h" +#include "libs/graphics/drawable.h" +#include "libs/graphics/context.h" +#include "libs/graphics/dcqueue.h" +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/bbox.h" +#include "libs/timelib.h" +#include "libs/log.h" +#include "libs/misc.h" + // for TFB_DEBUG_HALT + + +static RecursiveMutex DCQ_Mutex; + +CondVar RenderingCond; + +TFB_DrawCommand DCQ[DCQ_MAX]; + +TFB_DrawCommandQueue DrawCommandQueue; + +#define FPS_PERIOD (ONE_SECOND / 100) +int RenderedFrames = 0; + + +// Wait for the queue to be emptied. +static void +TFB_WaitForSpace (int requested_slots) +{ + int old_depth, i; + log_add (log_Debug, "DCQ overload (Size = %d, FullSize = %d, " + "Requested = %d). Sleeping until renderer is done.", + DrawCommandQueue.Size, DrawCommandQueue.FullSize, + requested_slots); + // Restore the DCQ locking level. I *think* this is + // always 1, but... + TFB_BatchReset (); + old_depth = GetRecursiveMutexDepth (DCQ_Mutex); + for (i = 0; i < old_depth; i++) + UnlockRecursiveMutex (DCQ_Mutex); + WaitCondVar (RenderingCond); + for (i = 0; i < old_depth; i++) + LockRecursiveMutex (DCQ_Mutex); + log_add (log_Debug, "DCQ clear (Size = %d, FullSize = %d). Continuing.", + DrawCommandQueue.Size, DrawCommandQueue.FullSize); +} + +void +Lock_DCQ (int slots) +{ + LockRecursiveMutex (DCQ_Mutex); + while (DrawCommandQueue.FullSize >= DCQ_MAX - slots) + { + TFB_WaitForSpace (slots); + } +} + +void +Unlock_DCQ (void) +{ + UnlockRecursiveMutex (DCQ_Mutex); +} + +// Always have the DCQ locked when calling this. +static void +Synchronize_DCQ (void) +{ + if (!DrawCommandQueue.Batching) + { + int front = DrawCommandQueue.Front; + int back = DrawCommandQueue.InsertionPoint; + DrawCommandQueue.Back = DrawCommandQueue.InsertionPoint; + if (front <= back) + { + DrawCommandQueue.Size = (back - front); + } + else + { + DrawCommandQueue.Size = (back + DCQ_MAX - front); + } + DrawCommandQueue.FullSize = DrawCommandQueue.Size; + } +} + +void +TFB_BatchGraphics (void) +{ + LockRecursiveMutex (DCQ_Mutex); + DrawCommandQueue.Batching++; + UnlockRecursiveMutex (DCQ_Mutex); +} + +void +TFB_UnbatchGraphics (void) +{ + LockRecursiveMutex (DCQ_Mutex); + if (DrawCommandQueue.Batching) + { + DrawCommandQueue.Batching--; + } + Synchronize_DCQ (); + UnlockRecursiveMutex (DCQ_Mutex); +} + +// Cancel all pending batch operations, making them unbatched. This will +// cause a small amount of flicker when invoked, but prevents +// batching problems from freezing the game. +void +TFB_BatchReset (void) +{ + LockRecursiveMutex (DCQ_Mutex); + DrawCommandQueue.Batching = 0; + Synchronize_DCQ (); + UnlockRecursiveMutex (DCQ_Mutex); +} + + +// Draw Command Queue Stuff + +void +Init_DrawCommandQueue (void) +{ + DrawCommandQueue.Back = 0; + DrawCommandQueue.Front = 0; + DrawCommandQueue.InsertionPoint = 0; + DrawCommandQueue.Batching = 0; + DrawCommandQueue.FullSize = 0; + DrawCommandQueue.Size = 0; + + TFB_BBox_Init (ScreenWidth, ScreenHeight); + + DCQ_Mutex = CreateRecursiveMutex ("DCQ", + SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO); + + RenderingCond = CreateCondVar ("DCQ empty", + SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO); +} + +void +Uninit_DrawCommandQueue (void) +{ + if (RenderingCond) + { + DestroyCondVar (RenderingCond); + RenderingCond = 0; + } + + if (DCQ_Mutex) + { + DestroyRecursiveMutex (DCQ_Mutex); + DCQ_Mutex = 0; + } +} + +void +TFB_DrawCommandQueue_Push (TFB_DrawCommand* Command) +{ + Lock_DCQ (1); + DCQ[DrawCommandQueue.InsertionPoint] = *Command; + DrawCommandQueue.InsertionPoint = (DrawCommandQueue.InsertionPoint + 1) + % DCQ_MAX; + DrawCommandQueue.FullSize++; + Synchronize_DCQ (); + Unlock_DCQ (); +} + +int +TFB_DrawCommandQueue_Pop (TFB_DrawCommand *target) +{ + LockRecursiveMutex (DCQ_Mutex); + + if (DrawCommandQueue.Size == 0) + { + Unlock_DCQ (); + return (0); + } + + if (DrawCommandQueue.Front == DrawCommandQueue.Back && + DrawCommandQueue.Size != DCQ_MAX) + { + log_add (log_Debug, "Augh! Assertion failure in DCQ! " + "Front == Back, Size != DCQ_MAX"); + DrawCommandQueue.Size = 0; + Unlock_DCQ (); + return (0); + } + + *target = DCQ[DrawCommandQueue.Front]; + DrawCommandQueue.Front = (DrawCommandQueue.Front + 1) % DCQ_MAX; + + DrawCommandQueue.Size--; + DrawCommandQueue.FullSize--; + UnlockRecursiveMutex (DCQ_Mutex); + + return 1; +} + +void +TFB_DrawCommandQueue_Clear () +{ + LockRecursiveMutex (DCQ_Mutex); + DrawCommandQueue.Size = 0; + DrawCommandQueue.Front = 0; + DrawCommandQueue.Back = 0; + DrawCommandQueue.Batching = 0; + DrawCommandQueue.FullSize = 0; + DrawCommandQueue.InsertionPoint = 0; + UnlockRecursiveMutex (DCQ_Mutex); +} + +static void +checkExclusiveThread (TFB_DrawCommand* DrawCommand) +{ +#ifdef DEBUG_DCQ_THREADS + static uint32 exclusiveThreadId; + extern uint32 SDL_ThreadID(void); + + // Only one thread is currently allowed to enqueue commands + // This is not a technical limitation but rather a semantical one atm. + if (DrawCommand->Type == TFB_DRAWCOMMANDTYPE_REINITVIDEO) + { // TFB_DRAWCOMMANDTYPE_REINITVIDEO is an exception + // It is queued from the main() thread, which is safe to do + return; + } + + if (!exclusiveThreadId) + exclusiveThreadId = SDL_ThreadID(); + else + assert (SDL_ThreadID() == exclusiveThreadId); +#else + (void) DrawCommand; // suppress unused warning +#endif +} + +void +TFB_EnqueueDrawCommand (TFB_DrawCommand* DrawCommand) +{ + if (TFB_DEBUG_HALT) + { + return; + } + + checkExclusiveThread (DrawCommand); + + if (DrawCommand->Type <= TFB_DRAWCOMMANDTYPE_COPYTOIMAGE + && _CurFramePtr->Type == SCREEN_DRAWABLE) + { + static RECT scissor_rect; + + // Set the clipping region. + // We allow drawing with no current context set, so the whole screen + if ((_pCurContext && !rectsEqual (scissor_rect, _pCurContext->ClipRect)) + || (!_pCurContext && scissor_rect.extent.width != 0)) + { + // Enqueue command to set the glScissor spec + TFB_DrawCommand DC; + + if (_pCurContext) + scissor_rect = _pCurContext->ClipRect; + else + scissor_rect.extent.width = 0; + + if (scissor_rect.extent.width) + { + DC.Type = TFB_DRAWCOMMANDTYPE_SCISSORENABLE; + DC.data.scissor.rect = scissor_rect; + } + else + { + DC.Type = TFB_DRAWCOMMANDTYPE_SCISSORDISABLE; + } + + TFB_EnqueueDrawCommand(&DC); + } + } + + TFB_DrawCommandQueue_Push (DrawCommand); +} + +static void +computeFPS (void) +{ + static TimeCount last_time; + static TimePeriod fps_counter; + TimeCount current_time; + TimePeriod delta_time; + + current_time = GetTimeCounter (); + delta_time = current_time - last_time; + last_time = current_time; + + fps_counter += delta_time; + if (fps_counter > FPS_PERIOD) + { + log_add (log_User, "fps %.2f, effective %.2f", + (float)ONE_SECOND / delta_time, + (float)ONE_SECOND * RenderedFrames / fps_counter); + + fps_counter = 0; + RenderedFrames = 0; + } +} + +// Only call from main() thread!! +void +TFB_FlushGraphics (void) +{ + int commands_handled; + BOOLEAN livelock_deterrence; + + // This is technically a locking violation on DrawCommandQueue.Size, + // but it is likely to not be very destructive. + if (DrawCommandQueue.Size == 0) + { + static int last_fade = 255; + static int last_transition = 255; + int current_fade = GetFadeAmount (); + int current_transition = TransitionAmount; + + if ((current_fade != 255 && current_fade != last_fade) || + (current_transition != 255 && + current_transition != last_transition) || + (current_fade == 255 && last_fade != 255) || + (current_transition == 255 && last_transition != 255)) + { + TFB_SwapBuffers (TFB_REDRAW_FADING); + // if fading, redraw every frame + } + else + { + TaskSwitch (); + } + + last_fade = current_fade; + last_transition = current_transition; + BroadcastCondVar (RenderingCond); + return; + } + + if (GfxFlags & TFB_GFXFLAGS_SHOWFPS) + computeFPS (); + + commands_handled = 0; + livelock_deterrence = FALSE; + + if (DrawCommandQueue.FullSize > DCQ_FORCE_BREAK_SIZE) + { + TFB_BatchReset (); + } + + if (DrawCommandQueue.Size > DCQ_FORCE_SLOWDOWN_SIZE) + { + Lock_DCQ (-1); + livelock_deterrence = TRUE; + } + + TFB_BBox_Reset (); + + for (;;) + { + TFB_DrawCommand DC; + + if (!TFB_DrawCommandQueue_Pop (&DC)) + { + // the Queue is now empty. + break; + } + + ++commands_handled; + if (!livelock_deterrence && commands_handled + DrawCommandQueue.Size + > DCQ_LIVELOCK_MAX) + { + // log_add (log_Debug, "Initiating livelock deterrence!"); + livelock_deterrence = TRUE; + + Lock_DCQ (-1); + } + + switch (DC.Type) + { + case TFB_DRAWCOMMANDTYPE_SETMIPMAP: + { + TFB_DrawCommand_SetMipmap *cmd = &DC.data.setmipmap; + TFB_DrawImage_SetMipmap (cmd->image, cmd->mipmap, + cmd->hotx, cmd->hoty); + break; + } + + case TFB_DRAWCOMMANDTYPE_IMAGE: + { + TFB_DrawCommand_Image *cmd = &DC.data.image; + TFB_Image *DC_image = cmd->image; + const int x = cmd->x; + const int y = cmd->y; + + TFB_DrawCanvas_Image (DC_image, x, y, + cmd->scale, cmd->scaleMode, cmd->colormap, + cmd->drawMode, + TFB_GetScreenCanvas (cmd->destBuffer)); + + if (cmd->destBuffer == TFB_SCREEN_MAIN) + { + LockMutex (DC_image->mutex); + if (cmd->scale) + TFB_BBox_RegisterCanvas (DC_image->ScaledImg, + x - DC_image->last_scale_hs.x, + y - DC_image->last_scale_hs.y); + else + TFB_BBox_RegisterCanvas (DC_image->NormalImg, + x - DC_image->NormalHs.x, + y - DC_image->NormalHs.y); + UnlockMutex (DC_image->mutex); + } + + break; + } + + case TFB_DRAWCOMMANDTYPE_FILLEDIMAGE: + { + TFB_DrawCommand_FilledImage *cmd = &DC.data.filledimage; + TFB_Image *DC_image = cmd->image; + const int x = cmd->x; + const int y = cmd->y; + + TFB_DrawCanvas_FilledImage (DC_image, x, y, + cmd->scale, cmd->scaleMode, cmd->color, + cmd->drawMode, + TFB_GetScreenCanvas (cmd->destBuffer)); + + if (cmd->destBuffer == TFB_SCREEN_MAIN) + { + LockMutex (DC_image->mutex); + if (cmd->scale) + TFB_BBox_RegisterCanvas (DC_image->ScaledImg, + x - DC_image->last_scale_hs.x, + y - DC_image->last_scale_hs.y); + else + TFB_BBox_RegisterCanvas (DC_image->NormalImg, + x - DC_image->NormalHs.x, + y - DC_image->NormalHs.y); + UnlockMutex (DC_image->mutex); + } + + break; + } + + case TFB_DRAWCOMMANDTYPE_FONTCHAR: + { + TFB_DrawCommand_FontChar *cmd = &DC.data.fontchar; + TFB_Char *DC_char = cmd->fontchar; + const int x = cmd->x; + const int y = cmd->y; + + TFB_DrawCanvas_FontChar (DC_char, cmd->backing, x, y, + cmd->drawMode, TFB_GetScreenCanvas (cmd->destBuffer)); + + if (cmd->destBuffer == TFB_SCREEN_MAIN) + { + RECT r; + + r.corner.x = x - DC_char->HotSpot.x; + r.corner.y = y - DC_char->HotSpot.y; + r.extent.width = DC_char->extent.width; + r.extent.height = DC_char->extent.height; + + TFB_BBox_RegisterRect (&r); + } + + break; + } + + case TFB_DRAWCOMMANDTYPE_LINE: + { + TFB_DrawCommand_Line *cmd = &DC.data.line; + + if (cmd->destBuffer == TFB_SCREEN_MAIN) + { + TFB_BBox_RegisterPoint (cmd->x1, cmd->y1); + TFB_BBox_RegisterPoint (cmd->x2, cmd->y2); + } + TFB_DrawCanvas_Line (cmd->x1, cmd->y1, cmd->x2, cmd->y2, + cmd->color, cmd->drawMode, + TFB_GetScreenCanvas (cmd->destBuffer)); + break; + } + + case TFB_DRAWCOMMANDTYPE_RECTANGLE: + { + TFB_DrawCommand_Rect *cmd = &DC.data.rect; + + if (cmd->destBuffer == TFB_SCREEN_MAIN) + TFB_BBox_RegisterRect (&cmd->rect); + TFB_DrawCanvas_Rect (&cmd->rect, cmd->color, cmd->drawMode, + TFB_GetScreenCanvas (cmd->destBuffer)); + + break; + } + + case TFB_DRAWCOMMANDTYPE_SCISSORENABLE: + { + TFB_DrawCommand_Scissor *cmd = &DC.data.scissor; + + TFB_DrawCanvas_SetClipRect ( + TFB_GetScreenCanvas (TFB_SCREEN_MAIN), &cmd->rect); + TFB_BBox_SetClipRect (&DC.data.scissor.rect); + break; + } + + case TFB_DRAWCOMMANDTYPE_SCISSORDISABLE: + TFB_DrawCanvas_SetClipRect ( + TFB_GetScreenCanvas (TFB_SCREEN_MAIN), NULL); + TFB_BBox_SetClipRect (NULL); + break; + + case TFB_DRAWCOMMANDTYPE_COPYTOIMAGE: + { + TFB_DrawCommand_CopyToImage *cmd = &DC.data.copytoimage; + TFB_Image *DC_image = cmd->image; + const POINT dstPt = {0, 0}; + + if (DC_image == 0) + { + log_add (log_Debug, "DCQ ERROR: COPYTOIMAGE passed null " + "image ptr"); + break; + } + LockMutex (DC_image->mutex); + TFB_DrawCanvas_CopyRect ( + TFB_GetScreenCanvas (cmd->srcBuffer), &cmd->rect, + DC_image->NormalImg, dstPt); + UnlockMutex (DC_image->mutex); + break; + } + + case TFB_DRAWCOMMANDTYPE_COPY: + { + TFB_DrawCommand_Copy *cmd = &DC.data.copy; + const RECT r = cmd->rect; + + if (cmd->destBuffer == TFB_SCREEN_MAIN) + TFB_BBox_RegisterRect (&cmd->rect); + + TFB_DrawCanvas_CopyRect ( + TFB_GetScreenCanvas (cmd->srcBuffer), &r, + TFB_GetScreenCanvas (cmd->destBuffer), r.corner); + break; + } + + case TFB_DRAWCOMMANDTYPE_DELETEIMAGE: + { + TFB_Image *DC_image = DC.data.deleteimage.image; + TFB_DrawImage_Delete (DC_image); + break; + } + + case TFB_DRAWCOMMANDTYPE_DELETEDATA: + { + void *data = DC.data.deletedata.data; + HFree (data); + break; + } + + case TFB_DRAWCOMMANDTYPE_SENDSIGNAL: + ClearSemaphore (DC.data.sendsignal.sem); + break; + + case TFB_DRAWCOMMANDTYPE_REINITVIDEO: + { + TFB_DrawCommand_ReinitVideo *cmd = &DC.data.reinitvideo; + int oldDriver = GraphicsDriver; + int oldFlags = GfxFlags; + int oldWidth = ScreenWidthActual; + int oldHeight = ScreenHeightActual; + if (TFB_ReInitGraphics (cmd->driver, cmd->flags, + cmd->width, cmd->height)) + { + log_add (log_Error, "Could not provide requested mode: " + "reverting to last known driver."); + // We don't know what exactly failed, so roll it all back + if (TFB_ReInitGraphics (oldDriver, oldFlags, + oldWidth, oldHeight)) + { + log_add (log_Fatal, + "Couldn't reinit at that point either. " + "Your video has been somehow tied in knots."); + exit (EXIT_FAILURE); + } + } + TFB_SwapBuffers (TFB_REDRAW_YES); + break; + } + + case TFB_DRAWCOMMANDTYPE_CALLBACK: + { + DC.data.callback.callback (DC.data.callback.arg); + break; + } + } + } + + if (livelock_deterrence) + Unlock_DCQ (); + + TFB_SwapBuffers (TFB_REDRAW_NO); + RenderedFrames++; + BroadcastCondVar (RenderingCond); +} + +void +TFB_PurgeDanglingGraphics (void) +{ + Lock_DCQ (-1); + + for (;;) + { + TFB_DrawCommand DC; + + if (!TFB_DrawCommandQueue_Pop (&DC)) + { + // the Queue is now empty. + break; + } + + switch (DC.Type) + { + case TFB_DRAWCOMMANDTYPE_DELETEIMAGE: + { + TFB_Image *DC_image = DC.data.deleteimage.image; + TFB_DrawImage_Delete (DC_image); + break; + } + case TFB_DRAWCOMMANDTYPE_DELETEDATA: + { + void *data = DC.data.deletedata.data; + HFree (data); + break; + } + case TFB_DRAWCOMMANDTYPE_IMAGE: + { + TFB_ColorMap *cmap = DC.data.image.colormap; + if (cmap) + TFB_ReturnColorMap (cmap); + break; + } + case TFB_DRAWCOMMANDTYPE_SENDSIGNAL: + { + ClearSemaphore (DC.data.sendsignal.sem); + break; + } + } + } + Unlock_DCQ (); +} diff --git a/src/libs/graphics/dcqueue.h b/src/libs/graphics/dcqueue.h new file mode 100644 index 0000000..deed685 --- /dev/null +++ b/src/libs/graphics/dcqueue.h @@ -0,0 +1,55 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 DCQUEUE_H +#define DCQUEUE_H + +// Maximum size of the DCQ. The larger the DCQ, the larger frameskips +// become tolerable before initiating livelock deterrence and game +// slowdown. Other constants for controlling the frameskip/slowdown +// balance may be found in sdl_common.c near TFB_FlushGraphics. + +// Livelock deterrance constants. Because the entire screen is rarely +// refreshed, we may not drop draw commands on the floor with abandon. +// Furthermore, if the main program is queuing commands at a speed +// comparable to our processing of the commands, we never finish and +// the game freezes. Thus, if the queue starts out larger than +// DCQ_FORCE_SLOWDOWN_SIZE, or DCQ_LIVELOCK_MAX commands find +// themselves being processed in one go, livelock deterrence is +// enabled, and TFB_FlushGraphics locks the DCQ until it has processed +// all entries. If batched but pending commands exceed DCQ_FORCE_BREAK_SIZE, +// a continuity break is performed. This will effectively slow down the +// game logic, a fate we seek to avoid - however, it seems to be unavoidable +// on slower machines. Even there, it's seems nonexistent outside of +// communications screens. --Michael + +#ifdef DCQ_OF_DOOM +#define DCQ_MAX 512 +#define DCQ_FORCE_SLOWDOWN_SIZE 128 +#define DCQ_FORCE_BREAK_SIZE 512 +#define DCQ_LIVELOCK_MAX 256 +#else +#define DCQ_MAX 16384 +#define DCQ_FORCE_SLOWDOWN_SIZE 4096 +#define DCQ_FORCE_BREAK_SIZE 16384 +#define DCQ_LIVELOCK_MAX 4096 +#endif + +extern CondVar RenderingCond; + +#endif + + diff --git a/src/libs/graphics/drawable.c b/src/libs/graphics/drawable.c new file mode 100644 index 0000000..9766bc7 --- /dev/null +++ b/src/libs/graphics/drawable.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 "libs/gfxlib.h" +#include "libs/graphics/context.h" +#include "libs/graphics/drawable.h" +#include "libs/graphics/tfb_draw.h" +#include "libs/memlib.h" +#include "tfb_draw.h" +#include + +#ifndef M_PI +# define M_PI 3.14159265358979323846 +#endif + +FRAME _CurFramePtr; + +FRAME +SetContextFGFrame (FRAME Frame) +{ + FRAME LastFrame; + + if (Frame != (LastFrame = (FRAME)_CurFramePtr)) + { + if (LastFrame) + DeactivateDrawable (); + + _CurFramePtr = Frame; + if (_CurFramePtr) + ActivateDrawable (); + + if (ContextActive ()) + { + SwitchContextFGFrame (Frame); + } + } + + return (LastFrame); +} + +FRAME +GetContextFGFrame (void) +{ + return _CurFramePtr; +} + +static DRAWABLE +request_drawable (COUNT NumFrames, DRAWABLE_TYPE DrawableType, + CREATE_FLAGS flags, SIZE width, SIZE height) +{ + DRAWABLE Drawable; + COUNT i; + + Drawable = AllocDrawable (NumFrames); + if (!Drawable) + return NULL; + + Drawable->Flags = flags; + Drawable->MaxIndex = NumFrames - 1; + + for (i = 0; i < NumFrames; ++i) + { + FRAME FramePtr = &Drawable->Frame[i]; + + if (DrawableType == RAM_DRAWABLE && width > 0 && height > 0) + { + FramePtr->image = TFB_DrawImage_New (TFB_DrawCanvas_New_TrueColor ( + width, height, (flags & WANT_ALPHA) ? TRUE : FALSE)); + } + + FramePtr->Type = DrawableType; + FramePtr->Index = i; + SetFrameBounds (FramePtr, width, height); + } + + return Drawable; +} + +DRAWABLE +CreateDisplay (CREATE_FLAGS CreateFlags, SIZE *pwidth, SIZE *pheight) +{ + DRAWABLE Drawable; + + // TODO: ScreenWidth and ScreenHeight should be passed in + // instead of returned. + Drawable = request_drawable (1, SCREEN_DRAWABLE, + (CreateFlags & (WANT_PIXMAP | WANT_MASK)), + ScreenWidth, ScreenHeight); + if (Drawable) + { + FRAME F; + + F = CaptureDrawable (Drawable); + if (F == 0) + DestroyDrawable (Drawable); + else + { + *pwidth = GetFrameWidth (F); + *pheight = GetFrameHeight (F); + + ReleaseDrawable (F); + return (Drawable); + } + } + + *pwidth = *pheight = 0; + return (0); +} + +DRAWABLE +AllocDrawable (COUNT n) +{ + DRAWABLE Drawable; + Drawable = (DRAWABLE) HCalloc(sizeof (DRAWABLE_DESC)); + if (Drawable) + { + int i; + Drawable->Frame = (FRAME)HMalloc (sizeof (FRAME_DESC) * n); + if (Drawable->Frame == NULL) + { + HFree (Drawable); + return NULL; + } + + /* Zero out the newly allocated frames, since HMalloc doesn't have + * MEM_ZEROINIT. */ + for (i = 0; i < n; i++) { + FRAME F; + F = &Drawable->Frame[i]; + F->parent = Drawable; + F->Type = 0; + F->Index = 0; + F->image = 0; + F->Bounds.width = 0; + F->Bounds.height = 0; + F->HotSpot.x = 0; + F->HotSpot.y = 0; + } + } + return Drawable; +} + +DRAWABLE +CreateDrawable (CREATE_FLAGS CreateFlags, SIZE width, SIZE height, COUNT + num_frames) +{ + DRAWABLE Drawable; + + Drawable = request_drawable (num_frames, RAM_DRAWABLE, + (CreateFlags & (WANT_MASK | WANT_PIXMAP + | WANT_ALPHA | MAPPED_TO_DISPLAY)), + width, height); + if (Drawable) + { + FRAME F; + + F = CaptureDrawable (Drawable); + if (F) + { + ReleaseDrawable (F); + + return (Drawable); + } + } + + return (0); +} + +BOOLEAN +DestroyDrawable (DRAWABLE Drawable) +{ + if (_CurFramePtr && (Drawable == _CurFramePtr->parent)) + SetContextFGFrame ((FRAME)NULL); + + if (Drawable) + { + FreeDrawable (Drawable); + + return (TRUE); + } + + return (FALSE); +} + +BOOLEAN +GetFrameRect (FRAME FramePtr, RECT *pRect) +{ + if (FramePtr) + { + pRect->corner.x = -FramePtr->HotSpot.x; + pRect->corner.y = -FramePtr->HotSpot.y; + pRect->extent = GetFrameBounds (FramePtr); + + return (TRUE); + } + + return (FALSE); +} + +HOT_SPOT +SetFrameHot (FRAME FramePtr, HOT_SPOT HotSpot) +{ + if (FramePtr) + { + HOT_SPOT OldHot; + + OldHot = FramePtr->HotSpot; + FramePtr->HotSpot = HotSpot; + + return (OldHot); + } + + return (MAKE_HOT_SPOT (0, 0)); +} + +HOT_SPOT +GetFrameHot (FRAME FramePtr) +{ + if (FramePtr) + { + return FramePtr->HotSpot; + } + + return (MAKE_HOT_SPOT (0, 0)); +} + +DRAWABLE +RotateFrame (FRAME Frame, int angle_deg) +{ + DRAWABLE Drawable; + FRAME RotFramePtr; + double dx, dy; + double d; + double angle = angle_deg * M_PI / 180; + + if (!Frame) + return NULL; + + assert (Frame->Type != SCREEN_DRAWABLE); + + Drawable = request_drawable (1, RAM_DRAWABLE, WANT_PIXMAP, 0, 0); + if (!Drawable) + return 0; + RotFramePtr = CaptureDrawable (Drawable); + if (!RotFramePtr) + { + FreeDrawable (Drawable); + return 0; + } + + RotFramePtr->image = TFB_DrawImage_New_Rotated ( + Frame->image, angle_deg); + SetFrameBounds (RotFramePtr, RotFramePtr->image->extent.width, + RotFramePtr->image->extent.height); + + /* now we need to rotate the hot-spot, eww */ + dx = Frame->HotSpot.x - (GetFrameWidth (Frame) / 2); + dy = Frame->HotSpot.y - (GetFrameHeight (Frame) / 2); + d = sqrt ((double)dx*dx + (double)dy*dy); + if ((int)d != 0) + { + double organg = atan2 (-dy, dx); + dx = cos (organg + angle) * d; + dy = -sin (organg + angle) * d; + } + RotFramePtr->HotSpot.x = (GetFrameWidth (RotFramePtr) / 2) + (int)dx; + RotFramePtr->HotSpot.y = (GetFrameHeight (RotFramePtr) / 2) + (int)dy; + + ReleaseDrawable (RotFramePtr); + + return Drawable; +} + +// color.a is ignored +void +SetFrameTransparentColor (FRAME frame, Color color) +{ + TFB_Image *img; + + if (!frame) + return; + + assert (frame->Type != SCREEN_DRAWABLE); + + img = frame->image; + LockMutex (img->mutex); + + // TODO: This should defer to TFB_DrawImage instead + TFB_DrawCanvas_SetTransparentColor (img->NormalImg, color, FALSE); + + UnlockMutex (img->mutex); +} + +Color +GetFramePixel (FRAME frame, POINT pixelPt) +{ + TFB_Image *img; + Color ret; + + if (!frame) + return BUILD_COLOR_RGBA (0, 0, 0, 0); + + assert (frame->Type != SCREEN_DRAWABLE); + + img = frame->image; + LockMutex (img->mutex); + + // TODO: This should defer to TFB_DrawImage instead + ret = TFB_DrawCanvas_GetPixel (img->NormalImg, pixelPt.x, pixelPt.y); + + UnlockMutex (img->mutex); + + return ret; +} + +static FRAME +makeMatchingFrame (FRAME frame, int width, int height) +{ + DRAWABLE drawable; + FRAME newFrame; + CREATE_FLAGS flags; + + flags = GetFrameParentDrawable (frame)->Flags; + drawable = CreateDrawable (flags, width, height, 1); + if (!drawable) + return NULL; + newFrame = CaptureDrawable (drawable); + if (!newFrame) + { + FreeDrawable (drawable); + return NULL; + } + + return newFrame; +} + +// Creates an new DRAWABLE containing a copy of specified FRAME's rect +// Source FRAME must not be a SCREEN_DRAWABLE +DRAWABLE +CopyFrameRect (FRAME frame, const RECT *area) +{ + FRAME newFrame; + POINT nullPt = MAKE_POINT (0, 0); + + if (!frame) + return NULL; + + assert (frame->Type != SCREEN_DRAWABLE); + + newFrame = makeMatchingFrame (frame, area->extent.width, + area->extent.height); + if (!newFrame) + return NULL; + + TFB_DrawImage_CopyRect (frame->image, area, newFrame->image, nullPt); + + return ReleaseDrawable (newFrame); +} + +// Creates an new DRAWABLE mostly identical to specified FRAME +// Source FRAME must not be a SCREEN_DRAWABLE +DRAWABLE +CloneFrame (FRAME frame) +{ + FRAME newFrame; + RECT r; + + if (!frame) + return NULL; + + assert (frame->Type != SCREEN_DRAWABLE); + + GetFrameRect (frame, &r); + r.corner.x = 0; + r.corner.y = 0; + + newFrame = CaptureDrawable (CopyFrameRect (frame, &r)); + if (!newFrame) + return NULL; + + // copy the hot-spot + newFrame->HotSpot = frame->HotSpot; + + return ReleaseDrawable (newFrame); +} + +// Creates a new DRAWABLE of specified size and scales the passed +// frame onto it. The aspect ratio is not preserved. +DRAWABLE +RescaleFrame (FRAME frame, int width, int height) +{ + FRAME newFrame; + TFB_Image *img; + TFB_Canvas src, dst; + + if (!frame) + return NULL; + + assert (frame->Type != SCREEN_DRAWABLE); + + newFrame = makeMatchingFrame (frame, width, height); + if (!newFrame) + return NULL; + + // scale the hot-spot + newFrame->HotSpot.x = frame->HotSpot.x * width / frame->Bounds.width; + newFrame->HotSpot.y = frame->HotSpot.y * height / frame->Bounds.height; + + img = frame->image; + LockMutex (img->mutex); + // NOTE: We do not lock the target image because nothing has a + // reference to it yet! + src = img->NormalImg; + dst = newFrame->image->NormalImg; + TFB_DrawCanvas_Rescale_Nearest (src, dst, -1, NULL, NULL, NULL); + + UnlockMutex (img->mutex); + + return ReleaseDrawable (newFrame); +} + +BOOLEAN +ReadFramePixelColors (FRAME frame, Color *pixels, int width, int height) +{ + TFB_Image *img; + + if (!frame) + return FALSE; + + assert (frame->Type != SCREEN_DRAWABLE); + + // TODO: Do we need to lock the img->mutex here? + img = frame->image; + return TFB_DrawCanvas_GetPixelColors (img->NormalImg, pixels, + width, height); +} + +// Warning: this functions bypasses DCQ, which is why it is not a DrawXXX +BOOLEAN +WriteFramePixelColors (FRAME frame, const Color *pixels, int width, int height) +{ + TFB_Image *img; + + if (!frame) + return FALSE; + + assert (frame->Type != SCREEN_DRAWABLE); + + // TODO: Do we need to lock the img->mutex here? + img = frame->image; + return TFB_DrawCanvas_SetPixelColors (img->NormalImg, pixels, + width, height); +} + +BOOLEAN +ReadFramePixelIndexes (FRAME frame, BYTE *pixels, int width, int height) +{ + TFB_Image *img; + + if (!frame) + return FALSE; + + assert (frame->Type != SCREEN_DRAWABLE); + + // TODO: Do we need to lock the img->mutex here? + img = frame->image; + return TFB_DrawCanvas_GetPixelIndexes (img->NormalImg, pixels, + width, height); +} + +// Warning: this functions bypasses DCQ, which is why it is not a DrawXXX +BOOLEAN +WriteFramePixelIndexes (FRAME frame, const BYTE *pixels, int width, int height) +{ + TFB_Image *img; + + if (!frame) + return FALSE; + + assert (frame->Type != SCREEN_DRAWABLE); + + // TODO: Do we need to lock the img->mutex here? + img = frame->image; + return TFB_DrawCanvas_SetPixelIndexes (img->NormalImg, pixels, + width, height); +} diff --git a/src/libs/graphics/drawable.h b/src/libs/graphics/drawable.h new file mode 100644 index 0000000..98f5ada --- /dev/null +++ b/src/libs/graphics/drawable.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 LIBS_GRAPHICS_DRAWABLE_H_ +#define LIBS_GRAPHICS_DRAWABLE_H_ + +#include +#include "tfb_draw.h" + +#define ValidPrimType(pt) ((pt)Bounds.width) +#define GetFrameHeight(f) ((f)->Bounds.height) +#define GetFrameBounds(f) ((f)->Bounds) +#define SetFrameBounds(f,w,h) \ + ((f)->Bounds.width=(w), \ + ((f))->Bounds.height=(h)) + +#define DRAWABLE_PRIORITY DEFAULT_MEM_PRIORITY + +extern DRAWABLE AllocDrawable (COUNT num_frames); +#define FreeDrawable(D) _ReleaseCelData (D) + +typedef struct +{ + RECT Box; + FRAME FramePtr; +} IMAGE_BOX; + +extern INTERSECT_CODE _clip_line (const RECT *pClipRect, + BRESENHAM_LINE *pLine); + +extern void *_GetCelData (uio_Stream *fp, DWORD length); +extern BOOLEAN _ReleaseCelData (void *handle); + +extern FRAME _CurFramePtr; + +// ClipRect is relative to ctxOrigin +extern void _text_blt (RECT *pClipRect, TEXT *TextPtr, POINT ctxOrigin); + +#endif /* LIBS_GRAPHICS_DRAWABLE_H_ */ + diff --git a/src/libs/graphics/drawcmd.h b/src/libs/graphics/drawcmd.h new file mode 100644 index 0000000..6d44153 --- /dev/null +++ b/src/libs/graphics/drawcmd.h @@ -0,0 +1,202 @@ +//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 DRAWCMD_H +#define DRAWCMD_H + +#include "libs/graphics/tfb_draw.h" + +enum +{ + TFB_DRAWCOMMANDTYPE_LINE, + TFB_DRAWCOMMANDTYPE_RECTANGLE, + TFB_DRAWCOMMANDTYPE_IMAGE, + TFB_DRAWCOMMANDTYPE_FILLEDIMAGE, + TFB_DRAWCOMMANDTYPE_FONTCHAR, + + TFB_DRAWCOMMANDTYPE_COPY, + TFB_DRAWCOMMANDTYPE_COPYTOIMAGE, + + TFB_DRAWCOMMANDTYPE_SCISSORENABLE, + TFB_DRAWCOMMANDTYPE_SCISSORDISABLE, + + TFB_DRAWCOMMANDTYPE_SETMIPMAP, + TFB_DRAWCOMMANDTYPE_DELETEIMAGE, + TFB_DRAWCOMMANDTYPE_DELETEDATA, + TFB_DRAWCOMMANDTYPE_SENDSIGNAL, + TFB_DRAWCOMMANDTYPE_REINITVIDEO, + TFB_DRAWCOMMANDTYPE_CALLBACK, +}; + +typedef struct tfb_dc_line +{ + int x1, y1, x2, y2; + Color color; + DrawMode drawMode; + SCREEN destBuffer; +} TFB_DrawCommand_Line; + +typedef struct tfb_dc_rect +{ + RECT rect; + Color color; + DrawMode drawMode; + SCREEN destBuffer; +} TFB_DrawCommand_Rect; + +typedef struct tfb_dc_img +{ + TFB_Image *image; + int x, y; + SCREEN destBuffer; + TFB_ColorMap *colormap; + DrawMode drawMode; + int scale; + int scaleMode; +} TFB_DrawCommand_Image; + +typedef struct tfb_dc_filledimg +{ + TFB_Image *image; + int x, y; + Color color; + SCREEN destBuffer; + DrawMode drawMode; + int scale; + int scaleMode; +} TFB_DrawCommand_FilledImage; + +typedef struct tfb_dc_fontchar +{ + TFB_Char *fontchar; + TFB_Image *backing; + int x, y; + DrawMode drawMode; + SCREEN destBuffer; +} TFB_DrawCommand_FontChar; + +typedef struct tfb_dc_copy +{ + RECT rect; + SCREEN srcBuffer, destBuffer; +} TFB_DrawCommand_Copy; + +typedef struct tfb_dc_copyimg +{ + TFB_Image *image; + RECT rect; + SCREEN srcBuffer; +} TFB_DrawCommand_CopyToImage; + +typedef struct tfb_dc_scissor +{ + RECT rect; +} TFB_DrawCommand_Scissor; + +typedef struct tfb_dc_setmip +{ + TFB_Image *image; + TFB_Image *mipmap; + int hotx, hoty; +} TFB_DrawCommand_SetMipmap; + +typedef struct tfb_dc_delimg +{ + TFB_Image *image; +} TFB_DrawCommand_DeleteImage; + +typedef struct tfb_dc_deldata +{ + void *data; + // data must be a result of HXalloc() call +} TFB_DrawCommand_DeleteData; + +typedef struct tfb_dc_signal +{ + Semaphore sem; +} TFB_DrawCommand_SendSignal; + +typedef struct tfb_dc_reinit_video +{ + int driver, flags, width, height; +} TFB_DrawCommand_ReinitVideo; + +typedef struct tfb_dc_callback +{ + void (*callback)(void *arg); + void *arg; +} TFB_DrawCommand_Callback; + +typedef struct tfb_drawcommand +{ + int Type; + union { + TFB_DrawCommand_Line line; + TFB_DrawCommand_Rect rect; + TFB_DrawCommand_Image image; + TFB_DrawCommand_FilledImage filledimage; + TFB_DrawCommand_FontChar fontchar; + TFB_DrawCommand_Copy copy; + TFB_DrawCommand_CopyToImage copytoimage; + TFB_DrawCommand_Scissor scissor; + TFB_DrawCommand_SetMipmap setmipmap; + TFB_DrawCommand_DeleteImage deleteimage; + TFB_DrawCommand_DeleteData deletedata; + TFB_DrawCommand_SendSignal sendsignal; + TFB_DrawCommand_ReinitVideo reinitvideo; + TFB_DrawCommand_Callback callback; + } data; +} TFB_DrawCommand; + +// Queue Stuff + +typedef struct tfb_drawcommandqueue +{ + int Front; + int Back; + int InsertionPoint; + int Batching; + volatile int FullSize; + volatile int Size; +} TFB_DrawCommandQueue; + +void Init_DrawCommandQueue (void); + +void Uninit_DrawCommandQueue (void); + +void TFB_BatchGraphics (void); + +void TFB_UnbatchGraphics (void); + +void TFB_BatchReset (void); + +void TFB_DrawCommandQueue_Push (TFB_DrawCommand* Command); + +int TFB_DrawCommandQueue_Pop (TFB_DrawCommand* Command); + +void TFB_DrawCommandQueue_Clear (void); + +extern TFB_DrawCommandQueue DrawCommandQueue; + +void TFB_EnqueueDrawCommand (TFB_DrawCommand* DrawCommand); + +void Lock_DCQ (int slots); + +void Unlock_DCQ (void); + +#endif diff --git a/src/libs/graphics/filegfx.c b/src/libs/graphics/filegfx.c new file mode 100644 index 0000000..c6af7cd --- /dev/null +++ b/src/libs/graphics/filegfx.c @@ -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. + */ + +#include "gfxintrn.h" +#include "options.h" +#include "libs/reslib.h" + + +DRAWABLE +LoadGraphicFile (const char *pStr) +{ + uio_Stream *fp; + + // FIXME: this theoretically needs a mechanism to prevent races + if (_cur_resfile_name) + // something else is loading resources atm + return 0; + + fp = res_OpenResFile (contentDir, pStr, "rb"); + if (fp != NULL) + { + DRAWABLE hData; + + _cur_resfile_name = pStr; + hData = (DRAWABLE)_GetCelData (fp, LengthResFile (fp)); + _cur_resfile_name = 0; + res_CloseResFile (fp); + return hData; + } + + return (NULL); +} + +FONT +LoadFontFile (const char *pStr) +{ + uio_Stream *fp; + + // FIXME: this theoretically needs a mechanism to prevent races + if (_cur_resfile_name) + // something else is loading resources atm + return 0; + + fp = res_OpenResFile (contentDir, pStr, "rb"); + if (fp != NULL) + { + FONT hData; + + _cur_resfile_name = pStr; + hData = (FONT)_GetFontData (fp, LengthResFile (fp)); + _cur_resfile_name = 0; + res_CloseResFile (fp); + return hData; + } + + return (0); +} diff --git a/src/libs/graphics/font.c b/src/libs/graphics/font.c new file mode 100644 index 0000000..638c814 --- /dev/null +++ b/src/libs/graphics/font.c @@ -0,0 +1,334 @@ +//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 "gfxintrn.h" +#include "tfb_prim.h" +#include "libs/log.h" + +static inline TFB_Char *getCharFrame (FONT_DESC *fontPtr, UniChar ch); + + +FONT +SetContextFont (FONT Font) +{ + FONT LastFont; + + LastFont = _CurFontPtr; + _CurFontPtr = Font; + if (ContextActive ()) + SwitchContextFont (Font); + + return (LastFont); +} + +BOOLEAN +DestroyFont (FONT FontRef) +{ + if (FontRef == NULL) + return (FALSE); + + if (_CurFontPtr && _CurFontPtr == FontRef) + SetContextFont ((FONT)NULL); + + return (FreeFont (FontRef)); +} + +// XXX: Should be in frame.c (renamed to something decent?) +void +font_DrawText (TEXT *lpText) +{ + RECT ClipRect; + POINT origin; + TEXT text; + + FixContextFontEffect (); + if (!GraphicsSystemActive () || !GetContextValidRect (NULL, &origin)) + return; + + // TextRect() clobbers TEXT.CharCount so we have to make a copy + text = *lpText; + if (!TextRect (&text, &ClipRect, NULL)) + return; + // ClipRect is relative to origin + _text_blt (&ClipRect, &text, origin); +} + +/* Draw the stroke by drawing the same text in the + * background color one pixel shifted to all 4 directions. + */ +void +font_DrawTracedText (TEXT *pText, Color text, Color trace) +{ + // Preserve current foreground color for full correctness + Color oldfg = SetContextForeGroundColor (trace); + pText->baseline.x--; + font_DrawText (pText); + pText->baseline.x += 2; + font_DrawText (pText); + pText->baseline.x--; + pText->baseline.y--; + font_DrawText (pText); + pText->baseline.y += 2; + font_DrawText (pText); + pText->baseline.y--; + SetContextForeGroundColor (text); + font_DrawText (pText); + SetContextForeGroundColor (oldfg); +} + +BOOLEAN +GetContextFontLeading (SIZE *pheight) +{ + if (_CurFontPtr != 0) + { + *pheight = (SIZE)_CurFontPtr->Leading; + return (TRUE); + } + + *pheight = 0; + return (FALSE); +} + +BOOLEAN +GetContextFontLeadingWidth (SIZE *pwidth) +{ + if (_CurFontPtr != 0) + { + *pwidth = (SIZE)_CurFontPtr->LeadingWidth; + return (TRUE); + } + + *pwidth = 0; + return (FALSE); +} + +BOOLEAN +TextRect (TEXT *lpText, RECT *pRect, BYTE *pdelta) +{ + BYTE char_delta_array[MAX_DELTAS]; + FONT FontPtr; + + FontPtr = _CurFontPtr; + if (FontPtr != 0 && lpText->CharCount != 0) + { + COORD top_y, bot_y; + SIZE width; + UniChar next_ch = 0; + const char *pStr; + COUNT num_chars; + + num_chars = lpText->CharCount; + /* At this point lpText->CharCount contains the *maximum* number of + * characters that lpText->pStr may contain. + * After the while loop below, it will contain the actual number. + */ + if (pdelta == 0) + { + pdelta = char_delta_array; + if (num_chars > MAX_DELTAS) + { + num_chars = MAX_DELTAS; + lpText->CharCount = MAX_DELTAS; + } + } + + top_y = 0; + bot_y = 0; + width = 0; + pStr = lpText->pStr; + if (num_chars > 0) + { + next_ch = getCharFromString (&pStr); + if (next_ch == '\0') + num_chars = 0; + } + while (num_chars--) + { + UniChar ch; + SIZE last_width; + TFB_Char *charFrame; + + last_width = width; + + ch = next_ch; + if (num_chars > 0) + { + next_ch = getCharFromString (&pStr); + if (next_ch == '\0') + { + lpText->CharCount -= num_chars; + num_chars = 0; + } + } + + charFrame = getCharFrame (FontPtr, ch); + if (charFrame != NULL && charFrame->disp.width) + { + COORD y; + + y = -charFrame->HotSpot.y; + if (y < top_y) + top_y = y; + y += charFrame->disp.height; + if (y > bot_y) + bot_y = y; + + width += charFrame->disp.width; +#if 0 + if (num_chars && next_ch < (UNICODE) MAX_CHARS + && !(FontPtr->KernTab[ch] + & (FontPtr->KernTab[next_ch] >> 2))) + width -= FontPtr->KernAmount; +#endif + } + + *pdelta++ = (BYTE)(width - last_width); + } + + if (width > 0 && (bot_y -= top_y) > 0) + { + /* subtract off default character spacing */ + if (pdelta[-1] > 0) + { + --pdelta[-1]; + --width; + } + + if (lpText->align == ALIGN_LEFT) + pRect->corner.x = 0; + else if (lpText->align == ALIGN_CENTER) + pRect->corner.x = -(width >> 1); + else + pRect->corner.x = -width; + pRect->corner.y = top_y; + pRect->extent.width = width; + pRect->extent.height = bot_y; + + pRect->corner.x += lpText->baseline.x; + pRect->corner.y += lpText->baseline.y; + + return (TRUE); + } + } + + pRect->corner = lpText->baseline; + pRect->extent.width = 0; + pRect->extent.height = 0; + + return (FALSE); +} + +void +_text_blt (RECT *pClipRect, TEXT *TextPtr, POINT ctxOrigin) +{ + FONT FontPtr; + COUNT num_chars; + UniChar next_ch; + const char *pStr; + POINT origin; + TFB_Image *backing; + DrawMode mode = _get_context_draw_mode (); + + FontPtr = _CurFontPtr; + if (FontPtr == NULL) + return; + backing = _get_context_font_backing (); + if (!backing) + return; + + origin.x = pClipRect->corner.x; + origin.y = TextPtr->baseline.y; + num_chars = TextPtr->CharCount; + if (num_chars == 0) + return; + + pStr = TextPtr->pStr; + + next_ch = getCharFromString (&pStr); + if (next_ch == '\0') + num_chars = 0; + while (num_chars--) + { + UniChar ch; + TFB_Char* fontChar; + + ch = next_ch; + if (num_chars > 0) + { + next_ch = getCharFromString (&pStr); + if (next_ch == '\0') + num_chars = 0; + } + + fontChar = getCharFrame (FontPtr, ch); + if (fontChar != NULL && fontChar->disp.width) + { + RECT r; + + r.corner.x = origin.x - fontChar->HotSpot.x; + r.corner.y = origin.y - fontChar->HotSpot.y; + r.extent.width = fontChar->disp.width; + r.extent.height = fontChar->disp.height; + if (BoxIntersect (&r, pClipRect, &r)) + { + TFB_Prim_FontChar (origin, fontChar, backing, mode, + ctxOrigin); + } + + origin.x += fontChar->disp.width; +#if 0 + if (num_chars && next_ch < (UNICODE) MAX_CHARS + && !(FontPtr->KernTab[ch] + & (FontPtr->KernTab[next_ch] >> 2))) + origin.x -= FontPtr->KernAmount; +#endif + } + } +} + +static inline TFB_Char * +getCharFrame (FONT_DESC *fontPtr, UniChar ch) +{ + UniChar pageStart = ch & CHARACTER_PAGE_MASK; + size_t charIndex; + + FONT_PAGE *page = fontPtr->fontPages; + for (;;) + { + if (page == NULL) + return NULL; + + if (page->pageStart == pageStart) + break; + + page = page->next; + } + + charIndex = ch - page->firstChar; + if (ch >= page->firstChar && charIndex < page->numChars + && page->charDesc[charIndex].data) + { + return &page->charDesc[charIndex]; + } + else + { + //log_add (log_Debug, "Character %u not present", (unsigned int) ch); + return NULL; + } +} + diff --git a/src/libs/graphics/font.h b/src/libs/graphics/font.h new file mode 100644 index 0000000..b6bd13b --- /dev/null +++ b/src/libs/graphics/font.h @@ -0,0 +1,71 @@ +//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 LIBS_GRAPHICS_FONT_H_ +#define LIBS_GRAPHICS_FONT_H_ + +#include "libs/memlib.h" + +#define MAX_DELTAS 100 + +typedef struct FontPage +{ + struct FontPage *next; + UniChar pageStart; +#define CHARACTER_PAGE_MASK 0xfffff800 + UniChar firstChar; + size_t numChars; + TFB_Char *charDesc; +} FONT_PAGE; + +static inline FONT_PAGE * +AllocFontPage (int numChars) +{ + FONT_PAGE *result = HMalloc (sizeof (FONT_PAGE)); + result->charDesc = HCalloc (numChars * sizeof *result->charDesc); + return result; +} + +static inline void +FreeFontPage (FONT_PAGE *page) +{ + HFree (page->charDesc); + HFree (page); +} + +struct font_desc +{ + UWORD Leading; + UWORD LeadingWidth; + FONT_PAGE *fontPages; +}; + +#define CHAR_DESCPTR PCHAR_DESC + +#define FONT_PRIORITY DEFAULT_MEM_PRIORITY + +#define AllocFont(size) (FONT)HCalloc (sizeof (FONT_DESC) + (size)) +#define FreeFont _ReleaseFontData + +extern FONT _CurFontPtr; + +extern void *_GetFontData (uio_Stream *fp, DWORD length); +extern BOOLEAN _ReleaseFontData (void *handle); + +#endif /* LIBS_GRAPHICS_FONT_H_ */ + diff --git a/src/libs/graphics/frame.c b/src/libs/graphics/frame.c new file mode 100644 index 0000000..0539746 --- /dev/null +++ b/src/libs/graphics/frame.c @@ -0,0 +1,266 @@ +//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 "gfxintrn.h" +#include "gfx_common.h" +#include "tfb_draw.h" +#include "tfb_prim.h" + +HOT_SPOT +MAKE_HOT_SPOT (COORD x, COORD y) +{ + HOT_SPOT hs; + hs.x = x; + hs.y = y; + return hs; +} + +// XXX: INTERNAL_PRIMITIVE and INTERNAL_PRIM_DESC are not used +typedef union +{ + POINT Point; + STAMP Stamp; + BRESENHAM_LINE Line; + TEXT Text; + RECT Rect; +} INTERNAL_PRIM_DESC; + +typedef struct +{ + PRIM_LINKS Links; + GRAPHICS_PRIM Type; + Color Color; + INTERNAL_PRIM_DESC Object; +} INTERNAL_PRIMITIVE; + + +// pValidRect or origin may be NULL +BOOLEAN +GetContextValidRect (RECT *pValidRect, POINT *origin) +{ + RECT tempRect; + POINT tempPt; + + if (!pValidRect) + pValidRect = &tempRect; + if (!origin) + origin = &tempPt; + + // Start with a rect the size of foreground frame + pValidRect->corner.x = 0; + pValidRect->corner.y = 0; + pValidRect->extent = GetFrameBounds (_CurFramePtr); + *origin = _CurFramePtr->HotSpot; + + if (_pCurContext->ClipRect.extent.width) + { + // If the cliprect is completely outside of the valid frame + // bounds we have nothing to draw + if (!BoxIntersect (&_pCurContext->ClipRect, + pValidRect, pValidRect)) + return (FALSE); + + // Foreground frame hotspot defines a drawing position offset + // WRT the context cliprect + origin->x += _pCurContext->ClipRect.corner.x; + origin->y += _pCurContext->ClipRect.corner.y; + } + + return (TRUE); +} + +static void +ClearBackGround (RECT *pClipRect) +{ + RECT clearRect; + Color color = _get_context_bg_color (); + clearRect.corner.x = 0; + clearRect.corner.y = 0; + clearRect.extent = pClipRect->extent; + TFB_Prim_FillRect (&clearRect, color, DRAW_REPLACE_MODE, + pClipRect->corner); +} + +void +DrawBatch (PRIMITIVE *lpBasePrim, PRIM_LINKS PrimLinks, + BATCH_FLAGS BatchFlags) +{ + RECT ValidRect; + POINT origin; + + if (GraphicsSystemActive () && GetContextValidRect (&ValidRect, &origin)) + { + COUNT CurIndex; + PRIMITIVE *lpPrim; + DrawMode mode = _get_context_draw_mode (); + + BatchGraphics (); + + if (BatchFlags & BATCH_BUILD_PAGE) + { + ClearBackGround (&ValidRect); + } + + CurIndex = GetPredLink (PrimLinks); + + for (; CurIndex != END_OF_LIST; + CurIndex = GetSuccLink (GetPrimLinks (lpPrim))) + { + GRAPHICS_PRIM PrimType; + PRIMITIVE *lpWorkPrim; + RECT ClipRect; + Color color; + + lpPrim = &lpBasePrim[CurIndex]; + PrimType = GetPrimType (lpPrim); + if (!ValidPrimType (PrimType)) + continue; + + lpWorkPrim = lpPrim; + + switch (PrimType) + { + case POINT_PRIM: + color = GetPrimColor (lpWorkPrim); + TFB_Prim_Point (&lpWorkPrim->Object.Point, color, + mode, origin); + break; + case STAMP_PRIM: + TFB_Prim_Stamp (&lpWorkPrim->Object.Stamp, mode, origin); + break; + case STAMPFILL_PRIM: + color = GetPrimColor (lpWorkPrim); + TFB_Prim_StampFill (&lpWorkPrim->Object.Stamp, color, + mode, origin); + break; + case LINE_PRIM: + color = GetPrimColor (lpWorkPrim); + TFB_Prim_Line (&lpWorkPrim->Object.Line, color, + mode, origin); + break; + case TEXT_PRIM: + if (!TextRect (&lpWorkPrim->Object.Text, &ClipRect, NULL)) + continue; + // ClipRect is relative to origin + _text_blt (&ClipRect, &lpWorkPrim->Object.Text, origin); + break; + case RECT_PRIM: + color = GetPrimColor (lpWorkPrim); + TFB_Prim_Rect (&lpWorkPrim->Object.Rect, color, + mode, origin); + break; + case RECTFILL_PRIM: + color = GetPrimColor (lpWorkPrim); + TFB_Prim_FillRect (&lpWorkPrim->Object.Rect, color, + mode, origin); + break; + } + } + + UnbatchGraphics (); + } +} + +void +ClearDrawable (void) +{ + RECT ValidRect; + + if (GraphicsSystemActive () && GetContextValidRect (&ValidRect, NULL)) + { + ClearBackGround (&ValidRect); + } +} + +void +DrawPoint (POINT *lpPoint) +{ + POINT origin; + + if (GraphicsSystemActive () && GetContextValidRect (NULL, &origin)) + { + Color color = GetPrimColor (&_locPrim); + DrawMode mode = _get_context_draw_mode (); + TFB_Prim_Point (lpPoint, color, mode, origin); + } +} + +void +DrawRectangle (RECT *lpRect) +{ + POINT origin; + + if (GraphicsSystemActive () && GetContextValidRect (NULL, &origin)) + { + Color color = GetPrimColor (&_locPrim); + DrawMode mode = _get_context_draw_mode (); + TFB_Prim_Rect (lpRect, color, mode, origin); + } +} + +void +DrawFilledRectangle (RECT *lpRect) +{ + POINT origin; + + if (GraphicsSystemActive () && GetContextValidRect (NULL, &origin)) + { + Color color = GetPrimColor (&_locPrim); + DrawMode mode = _get_context_draw_mode (); + TFB_Prim_FillRect (lpRect, color, mode, origin); + } +} + +void +DrawLine (LINE *lpLine) +{ + POINT origin; + + if (GraphicsSystemActive () && GetContextValidRect (NULL, &origin)) + { + Color color = GetPrimColor (&_locPrim); + DrawMode mode = _get_context_draw_mode (); + TFB_Prim_Line (lpLine, color, mode, origin); + } +} + +void +DrawStamp (STAMP *stmp) +{ + POINT origin; + + if (GraphicsSystemActive () && GetContextValidRect (NULL, &origin)) + { + DrawMode mode = _get_context_draw_mode (); + TFB_Prim_Stamp (stmp, mode, origin); + } +} + +void +DrawFilledStamp (STAMP *stmp) +{ + POINT origin; + + if (GraphicsSystemActive () && GetContextValidRect (NULL, &origin)) + { + Color color = GetPrimColor (&_locPrim); + DrawMode mode = _get_context_draw_mode (); + TFB_Prim_StampFill (stmp, color, mode, origin); + } +} + diff --git a/src/libs/graphics/gfx_common.c b/src/libs/graphics/gfx_common.c new file mode 100644 index 0000000..405f614 --- /dev/null +++ b/src/libs/graphics/gfx_common.c @@ -0,0 +1,196 @@ +//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 "gfxintrn.h" +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/drawcmd.h" +#include "libs/timelib.h" +#include "libs/misc.h" + // for TFB_DEBUG_HALT + + +int ScreenWidth; +int ScreenHeight; +int ScreenWidthActual; +int ScreenHeightActual; +int ScreenColorDepth; +int GraphicsDriver; +int TFB_DEBUG_HALT = 0; + +volatile int TransitionAmount = 255; +RECT TransitionClipRect; + +static int gscale = GSCALE_IDENTITY; +static int gscale_mode = TFB_SCALE_NEAREST; + +void +DrawFromExtraScreen (RECT *r) +{ + TFB_DrawScreen_Copy(r, TFB_SCREEN_EXTRA, TFB_SCREEN_MAIN); +} + +void +LoadIntoExtraScreen (RECT *r) +{ + TFB_DrawScreen_Copy(r, TFB_SCREEN_MAIN, TFB_SCREEN_EXTRA); +} + +int +SetGraphicScale (int scale) +{ + int old_scale = gscale; + gscale = (scale ? scale : GSCALE_IDENTITY); + return old_scale; +} + +int +GetGraphicScale (void) +{ + return gscale; +} + +int +SetGraphicScaleMode (int mode) +{ + int old_mode = gscale_mode; + assert (mode >= TFB_SCALE_NEAREST && mode <= TFB_SCALE_TRILINEAR); + gscale_mode = mode; + return old_mode; +} + +int +GetGraphicScaleMode (void) +{ + return gscale_mode; +} + +/* Batching and Unbatching functions. A "Batch" is a collection of + DrawCommands that will never be flipped to the screen half-rendered. + BatchGraphics and UnbatchGraphics function vaguely like a non-blocking + recursive lock to do this respect. */ +void +BatchGraphics (void) +{ + TFB_BatchGraphics (); +} + +void +UnbatchGraphics (void) +{ + TFB_UnbatchGraphics (); +} + +/* Sleeps this thread until all Draw Commands queued by that thread have + been processed. */ + +void +FlushGraphics (void) +{ + TFB_DrawScreen_WaitForSignal (); +} + +static void +ExpandRect (RECT *rect, int expansion) +{ + if (rect->corner.x - expansion >= 0) + { + rect->extent.width += expansion; + rect->corner.x -= expansion; + } + else + { + rect->extent.width += rect->corner.x; + rect->corner.x = 0; + } + + if (rect->corner.y - expansion >= 0) + { + rect->extent.height += expansion; + rect->corner.y -= expansion; + } + else + { + rect->extent.height += rect->corner.y; + rect->corner.y = 0; + } + + if (rect->corner.x + rect->extent.width + expansion <= ScreenWidth) + rect->extent.width += expansion; + else + rect->extent.width = ScreenWidth - rect->corner.x; + + if (rect->corner.y + rect->extent.height + expansion <= ScreenHeight) + rect->extent.height += expansion; + else + rect->extent.height = ScreenHeight - rect->corner.y; +} + +void +SetTransitionSource (const RECT *pRect) +{ + RECT ActualRect; + + if (pRect) + { /* expand the rect to accomodate scalers in OpenGL mode */ + ActualRect = *pRect; + pRect = &ActualRect; + ExpandRect (&ActualRect, 2); + } + TFB_DrawScreen_Copy (pRect, TFB_SCREEN_MAIN, TFB_SCREEN_TRANSITION); +} + +// ScreenTransition() is synchronous (does not return until transition done) +void +ScreenTransition (int TransType, const RECT *pRect) +{ + const TimePeriod DURATION = ONE_SECOND * 31 / 60; + TimeCount startTime; + (void) TransType; /* dodge compiler warning */ + + if (pRect) + { + TransitionClipRect = *pRect; + } + else + { + TransitionClipRect.corner.x = 0; + TransitionClipRect.corner.y = 0; + TransitionClipRect.extent.width = ScreenWidth; + TransitionClipRect.extent.height = ScreenHeight; + } + + TFB_UploadTransitionScreen (); + + TransitionAmount = 0; + FlushGraphics (); + startTime = GetTimeCounter (); + while (TransitionAmount < 255) + { + TimePeriod deltaT; + int newAmount; + + SleepThread (ONE_SECOND / 100); + + deltaT = GetTimeCounter () - startTime; + newAmount = deltaT * 255 / DURATION; + if (newAmount > 255) + newAmount = 255; + + TransitionAmount = newAmount; + } +} diff --git a/src/libs/graphics/gfx_common.h b/src/libs/graphics/gfx_common.h new file mode 100644 index 0000000..e9e421a --- /dev/null +++ b/src/libs/graphics/gfx_common.h @@ -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. + */ + +#ifndef GFX_COMMON_H +#define GFX_COMMON_H + +#include +#include + +#include "libs/gfxlib.h" + +// driver for TFB_InitGraphics +enum +{ + TFB_GFXDRIVER_SDL_OPENGL, + TFB_GFXDRIVER_SDL_PURE, +}; + +// forced redraw +enum +{ + TFB_REDRAW_NO = 0, + TFB_REDRAW_FADING, + TFB_REDRAW_EXPOSE, + TFB_REDRAW_YES +}; + +// flags for TFB_InitGraphics +#define TFB_GFXFLAGS_FULLSCREEN (1<<0) +#define TFB_GFXFLAGS_SHOWFPS (1<<1) +#define TFB_GFXFLAGS_SCANLINES (1<<2) +#define TFB_GFXFLAGS_SCALE_BILINEAR (1<<3) +#define TFB_GFXFLAGS_SCALE_BIADAPT (1<<4) +#define TFB_GFXFLAGS_SCALE_BIADAPTADV (1<<5) +#define TFB_GFXFLAGS_SCALE_TRISCAN (1<<6) +#define TFB_GFXFLAGS_SCALE_HQXX (1<<7) +#define TFB_GFXFLAGS_SCALE_ANY \ + ( TFB_GFXFLAGS_SCALE_BILINEAR | \ + TFB_GFXFLAGS_SCALE_BIADAPT | \ + TFB_GFXFLAGS_SCALE_BIADAPTADV | \ + TFB_GFXFLAGS_SCALE_TRISCAN | \ + TFB_GFXFLAGS_SCALE_HQXX ) +#define TFB_GFXFLAGS_SCALE_SOFT_ONLY \ + ( TFB_GFXFLAGS_SCALE_ANY & ~TFB_GFXFLAGS_SCALE_BILINEAR ) + +// The flag variable itself +extern int GfxFlags; + +// The following functions are driver-defined +void TFB_PreInit (void); +int TFB_InitGraphics (int driver, int flags, const char *renderer, + int width, int height); +int TFB_ReInitGraphics (int driver, int flags, int width, int height); +void TFB_UninitGraphics (void); +void TFB_ProcessEvents (void); +bool TFB_SetGamma (float gamma); +void TFB_UploadTransitionScreen (void); +int TFB_SupportsHardwareScaling (void); +// This function should not be called directly +void TFB_SwapBuffers (int force_full_redraw); + +#define GSCALE_IDENTITY 256 + +typedef enum { + TFB_SCALE_STEP, /* not really a scaler */ + TFB_SCALE_NEAREST, + TFB_SCALE_BILINEAR, + TFB_SCALE_TRILINEAR +} SCALE; + +void LoadIntoExtraScreen (RECT *r); +void DrawFromExtraScreen (RECT *r); +int SetGraphicScale (int scale); +int GetGraphicScale (void); +int SetGraphicScaleMode (int mode /* enum SCALE */); +int GetGraphicScaleMode (void); +void SetTransitionSource (const RECT *pRect); +void ScreenTransition (int transition, const RECT *pRect); + +// TODO: there should be accessor functions for these +extern volatile int TransitionAmount; +extern RECT TransitionClipRect; + +extern float FrameRate; +extern int FrameRateTickBase; + +void TFB_FlushGraphics (void); // Only call from main thread!! +void TFB_PurgeDanglingGraphics (void); // Only call from main thread as part of shutdown. + +extern int ScreenWidth; +extern int ScreenHeight; +extern int ScreenWidthActual; +extern int ScreenHeightActual; +extern int ScreenColorDepth; +extern int GraphicsDriver; + +#endif diff --git a/src/libs/graphics/gfxintrn.h b/src/libs/graphics/gfxintrn.h new file mode 100644 index 0000000..72281e6 --- /dev/null +++ b/src/libs/graphics/gfxintrn.h @@ -0,0 +1,32 @@ +//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 LIBS_GRAPHICS_GFXINTRN_H_ +#define LIBS_GRAPHICS_GFXINTRN_H_ + +#include +#include + +#include "libs/gfxlib.h" +#include "libs/reslib.h" +#include "context.h" +#include "drawable.h" +#include "font.h" + +#endif /* LIBS_GRAPHICS_GFXINTRN_H_ */ + diff --git a/src/libs/graphics/gfxload.c b/src/libs/graphics/gfxload.c new file mode 100644 index 0000000..969a910 --- /dev/null +++ b/src/libs/graphics/gfxload.c @@ -0,0 +1,597 @@ +//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 +#include + +#include "options.h" +#include "port.h" +#include "libs/uio.h" +#include "libs/reslib.h" + // for _cur_resfile_name +#include "libs/log.h" +#include "libs/memlib.h" +#include "libs/graphics/tfb_draw.h" +#include "libs/graphics/drawable.h" +#include "libs/graphics/font.h" + + +typedef struct anidata +{ + int transparent_color; + int colormap_index; + int hotspot_x; + int hotspot_y; +} AniData; + +extern uio_Repository *repository; +static uio_AutoMount *autoMount[] = { NULL }; + +static void +process_image (FRAME FramePtr, TFB_Canvas img[], AniData *ani, int cel_ct) +{ + TFB_Image *tfbimg; + int hx, hy; + + FramePtr->Type = ROM_DRAWABLE; + FramePtr->Index = cel_ct; + + // handle transparency cases + if (TFB_DrawCanvas_IsPaletted (img[cel_ct])) + { // indexed color image + if (ani[cel_ct].transparent_color >= 0) + { + TFB_DrawCanvas_SetTransparentIndex (img[cel_ct], + ani[cel_ct].transparent_color, FALSE); + } + } + else + { // special transparency cases for truecolor images + if (ani[cel_ct].transparent_color == 0) + { // make RGB=0,0,0 transparent + Color color = {0, 0, 0, 0}; + TFB_DrawCanvas_SetTransparentColor (img[cel_ct], color, FALSE); + } + } + if (ani[cel_ct].transparent_color == -1) + { // enforce -1 to mean 'no transparency' + TFB_DrawCanvas_SetTransparentIndex (img[cel_ct], -1, FALSE); + // set transparent_color == -2 to use PNG tRNS transparency + } + + hx = ani[cel_ct].hotspot_x; + hy = ani[cel_ct].hotspot_y; + + FramePtr->image = TFB_DrawImage_New (img[cel_ct]); + + tfbimg = FramePtr->image; + tfbimg->colormap_index = ani[cel_ct].colormap_index; + img[cel_ct] = tfbimg->NormalImg; + + FramePtr->HotSpot = MAKE_HOT_SPOT (hx, hy); + SetFrameBounds (FramePtr, tfbimg->extent.width, tfbimg->extent.height); + +#ifdef CLIPDEBUG + { + /* for debugging clipping: + draws white (or most matching color from palette) pixels to + every corner of the image + */ + Color color = {0xff, 0xff, 0xff, 0xff}; + RECT r = {{0, 0}, {1, 1}}; + if (tfbimg->extent.width > 2 && tfbimg->extent.height > 2) + { + TFB_DrawImage_Rect (&r, color, tfbimg); + r.corner.x = tfbimg->extent.width - 1; + TFB_DrawImage_Rect (&r, color, tfbimg); + r.corner.y = tfbimg->extent.height - 1; + TFB_DrawImage_Rect (&r, color, tfbimg); + r.corner.x = 0; + TFB_DrawImage_Rect (&r, color, tfbimg); + } + } +#endif +} + +static void +processFontChar (TFB_Char* CharPtr, TFB_Canvas canvas) +{ + BYTE* newdata; + size_t dpitch; + + TFB_DrawCanvas_GetExtent (canvas, &CharPtr->extent); + + // Currently, each font char has its own separate data + // but that can change to common mem area + dpitch = CharPtr->extent.width; + newdata = HMalloc (dpitch * CharPtr->extent.height * sizeof (BYTE)); + TFB_DrawCanvas_GetFontCharData (canvas, newdata, dpitch); + + CharPtr->data = newdata; + CharPtr->pitch = dpitch; + CharPtr->disp.width = CharPtr->extent.width + 1; + CharPtr->disp.height = CharPtr->extent.height + 1; + // XXX: why the +1? + // I brought it into this function from the only calling + // function, but I don't know why it was there in the first + // place. + // XXX: the +1 appears to be for character and line spacing + // text_blt just adds the frame width to move to the next char + + { + // This tunes the font positioning to be about what it should + // TODO: prolly needs a little tweaking still + + int tune_amount = 0; + + if (CharPtr->extent.height == 8) + tune_amount = -1; + else if (CharPtr->extent.height == 9) + tune_amount = -2; + else if (CharPtr->extent.height > 9) + tune_amount = -3; + + CharPtr->HotSpot = MAKE_HOT_SPOT (0, + CharPtr->extent.height + tune_amount); + } +} + +void * +_GetCelData (uio_Stream *fp, DWORD length) +{ + int cel_total, cel_index, n; + DWORD opos; + char CurrentLine[1024], filename[PATH_MAX]; + TFB_Canvas *img; + AniData *ani; + DRAWABLE Drawable; + uio_MountHandle *aniMount = 0; + uio_DirHandle *aniDir = 0; + uio_Stream *aniFile = 0; + + opos = uio_ftell (fp); + + { + char *s1, *s2; + char aniDirName[PATH_MAX]; + const char *aniFileName; + uint8 buf[4] = { 0, 0, 0, 0 }; + uint32 header; + + if (_cur_resfile_name == 0 + || (((s2 = 0), (s1 = strrchr (_cur_resfile_name, '/')) == 0) + && (s2 = strrchr (_cur_resfile_name, '\\')) == 0)) + { + n = 0; + } + else + { + if (s2 > s1) + s1 = s2; + n = s1 - _cur_resfile_name + 1; + } + + uio_fread(buf, 4, 1, fp); + header = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + if (_cur_resfile_name && header == 0x04034b50) + { + // zipped ani file + if (n) + { + strncpy (aniDirName, _cur_resfile_name, n - 1); + aniDirName[n - 1] = 0; + aniFileName = _cur_resfile_name + n; + } + else + { + strcpy(aniDirName, "."); + aniFileName = _cur_resfile_name; + } + aniDir = uio_openDir (repository, aniDirName, 0); + aniMount = uio_mountDir (repository, aniDirName, uio_FSTYPE_ZIP, + aniDir, aniFileName, "/", autoMount, + uio_MOUNT_RDONLY | uio_MOUNT_TOP, + NULL); + aniFile = uio_fopen (aniDir, aniFileName, "r"); + opos = 0; + n = 0; + } + else + { + // unpacked ani file + strncpy (filename, _cur_resfile_name, n); + aniFile = fp; + aniDir = contentDir; + } + } + + cel_total = 0; + uio_fseek (aniFile, opos, SEEK_SET); + while (uio_fgets (CurrentLine, sizeof (CurrentLine), aniFile)) + { + ++cel_total; + } + + img = HMalloc (sizeof (TFB_Canvas) * cel_total); + ani = HMalloc (sizeof (AniData) * cel_total); + if (!img || !ani) + { + log_add (log_Warning, "Couldn't allocate space for '%s'", _cur_resfile_name); + if (aniMount) + { + uio_fclose(aniFile); + uio_closeDir(aniDir); + uio_unmountDir(aniMount); + } + HFree (img); + HFree (ani); + return NULL; + } + + cel_index = 0; + uio_fseek (aniFile, opos, SEEK_SET); + while (uio_fgets (CurrentLine, sizeof (CurrentLine), aniFile) && cel_index < cel_total) + { + sscanf (CurrentLine, "%s %d %d %d %d", &filename[n], + &ani[cel_index].transparent_color, &ani[cel_index].colormap_index, + &ani[cel_index].hotspot_x, &ani[cel_index].hotspot_y); + + img[cel_index] = TFB_DrawCanvas_LoadFromFile (aniDir, filename); + if (img[cel_index] == NULL) + { + const char *err; + + err = TFB_DrawCanvas_GetError (); + log_add (log_Warning, "_GetCelData: Unable to load image!"); + if (err != NULL) + log_add (log_Warning, "Gfx Driver reports: %s", err); + } + else + { + ++cel_index; + } + + if ((int)uio_ftell (aniFile) - (int)opos >= (int)length) + break; + } + + Drawable = NULL; + if (cel_index && (Drawable = AllocDrawable (cel_index))) + { + if (!Drawable) + { + while (cel_index--) + TFB_DrawCanvas_Delete (img[cel_index]); + + HFree (Drawable); + Drawable = NULL; + } + else + { + FRAME FramePtr; + + Drawable->Flags = WANT_PIXMAP; + Drawable->MaxIndex = cel_index - 1; + + FramePtr = &Drawable->Frame[cel_index]; + while (--FramePtr, cel_index--) + process_image (FramePtr, img, ani, cel_index); + } + } + + if (Drawable == NULL) + log_add (log_Warning, "Couldn't get cel data for '%s'", + _cur_resfile_name); + + if (aniMount) + { + uio_fclose(aniFile); + uio_closeDir(aniDir); + uio_unmountDir(aniMount); + } + + HFree (img); + HFree (ani); + return Drawable; +} + +BOOLEAN +_ReleaseCelData (void *handle) +{ + DRAWABLE DrawablePtr; + int cel_ct; + FRAME FramePtr = NULL; + + if ((DrawablePtr = handle) == 0) + return (FALSE); + + cel_ct = DrawablePtr->MaxIndex + 1; + FramePtr = DrawablePtr->Frame; + + HFree (handle); + if (FramePtr) + { + int i; + for (i = 0; i < cel_ct; i++) + { + TFB_Image *img = FramePtr[i].image; + if (img) + { + FramePtr[i].image = NULL; + TFB_DrawScreen_DeleteImage (img); + } + } + HFree (FramePtr); + } + + return (TRUE); +} + +typedef struct BuildCharDesc +{ + TFB_Canvas canvas; + UniChar index; +} BuildCharDesc; + +static int +compareBCDIndex (const void *arg1, const void *arg2) +{ + const BuildCharDesc *bcd1 = (const BuildCharDesc *) arg1; + const BuildCharDesc *bcd2 = (const BuildCharDesc *) arg2; + + return (int) bcd1->index - (int) bcd2->index; +} + +void * +_GetFontData (uio_Stream *fp, DWORD length) +{ + COUNT numDirEntries; + DIRENTRY fontDir = NULL; + BuildCharDesc *bcds = NULL; + size_t numBCDs = 0; + int dirEntryI; + uio_DirHandle *fontDirHandle = NULL; + uio_MountHandle *fontMount = NULL; + FONT fontPtr = NULL; + + if (_cur_resfile_name == 0) + goto err; + + if (fp != (uio_Stream*)~0) + { + // font is zipped instead of being in a directory + + char *s1, *s2; + int n; + const char *fontZipName; + char fontDirName[PATH_MAX]; + + if ((((s2 = 0), (s1 = strrchr (_cur_resfile_name, '/')) == 0) + && (s2 = strrchr (_cur_resfile_name, '\\')) == 0)) + { + strcpy(fontDirName, "."); + fontZipName = _cur_resfile_name; + } + else + { + if (s2 > s1) + s1 = s2; + n = s1 - _cur_resfile_name + 1; + strncpy (fontDirName, _cur_resfile_name, n - 1); + fontDirName[n - 1] = 0; + fontZipName = _cur_resfile_name + n; + } + + fontDirHandle = uio_openDir (repository, fontDirName, 0); + fontMount = uio_mountDir (repository, _cur_resfile_name, uio_FSTYPE_ZIP, + fontDirHandle, fontZipName, "/", autoMount, + uio_MOUNT_RDONLY | uio_MOUNT_TOP, + NULL); + uio_closeDir (fontDirHandle); + } + + fontDir = CaptureDirEntryTable (LoadDirEntryTable (contentDir, + _cur_resfile_name, ".", match_MATCH_SUBSTRING)); + if (fontDir == 0) + goto err; + numDirEntries = GetDirEntryTableCount (fontDir); + + fontDirHandle = uio_openDirRelative (contentDir, _cur_resfile_name, 0); + if (fontDirHandle == NULL) + goto err; + + bcds = HMalloc (numDirEntries * sizeof (BuildCharDesc)); + if (bcds == NULL) + goto err; + + // Load the surfaces for all dir Entries + for (dirEntryI = 0; dirEntryI < numDirEntries; dirEntryI++) + { + char *char_name; + unsigned int charIndex; + TFB_Canvas canvas; + EXTENT size; + + char_name = GetDirEntryAddress (SetAbsDirEntryTableIndex ( + fontDir, dirEntryI)); + if (sscanf (char_name, "%x.", &charIndex) != 1) + continue; + + if (charIndex > 0xffff) + continue; + + canvas = TFB_DrawCanvas_LoadFromFile (fontDirHandle, char_name); + if (canvas == NULL) + continue; + + TFB_DrawCanvas_GetExtent (canvas, &size); + if (size.width == 0 || size.height == 0) + { + TFB_DrawCanvas_Delete (canvas); + continue; + } + + bcds[numBCDs].canvas = canvas; + bcds[numBCDs].index = charIndex; + numBCDs++; + } + uio_closeDir (fontDirHandle); + DestroyDirEntryTable (ReleaseDirEntryTable (fontDir)); + if (fontMount != 0) + uio_unmountDir(fontMount); + +#if 0 + if (numBCDs == 0) + goto err; +#endif + + // sort on the character index + qsort (bcds, numBCDs, sizeof (BuildCharDesc), compareBCDIndex); + + fontPtr = AllocFont (0); + if (fontPtr == NULL) + goto err; + + fontPtr->Leading = 0; + fontPtr->LeadingWidth = 0; + + { + size_t startBCD = 0; + UniChar pageStart; + FONT_PAGE **pageEndPtr = &fontPtr->fontPages; + while (startBCD < numBCDs) + { + // Process one character page. + size_t endBCD; + pageStart = bcds[startBCD].index & CHARACTER_PAGE_MASK; + + endBCD = startBCD; + while (endBCD < numBCDs && + (bcds[endBCD].index & CHARACTER_PAGE_MASK) == pageStart) + endBCD++; + + { + size_t bcdI; + int numChars = bcds[endBCD - 1].index + 1 + - bcds[startBCD].index; + FONT_PAGE *page = AllocFontPage (numChars); + page->pageStart = pageStart; + page->firstChar = bcds[startBCD].index; + page->numChars = numChars; + *pageEndPtr = page; + pageEndPtr = &page->next; + + for (bcdI = startBCD; bcdI < endBCD; bcdI++) + { + // Process one character. + BuildCharDesc *bcd = &bcds[bcdI]; + TFB_Char *destChar = + &page->charDesc[bcd->index - page->firstChar]; + + if (destChar->data != NULL) + { + // There's already an image for this character. + log_add (log_Debug, "Duplicate image for character %d " + "for font %s.", (int) bcd->index, + _cur_resfile_name); + TFB_DrawCanvas_Delete (bcd->canvas); + continue; + } + + processFontChar (destChar, bcd->canvas); + TFB_DrawCanvas_Delete (bcd->canvas); + + if (destChar->disp.height > fontPtr->Leading) + fontPtr->Leading = destChar->disp.height; + if (destChar->disp.width > fontPtr->LeadingWidth) + fontPtr->LeadingWidth = destChar->disp.width; + } + } + + startBCD = endBCD; + } + *pageEndPtr = NULL; + } + + fontPtr->Leading++; + + HFree (bcds); + + (void) fp; /* Satisfying compiler (unused parameter) */ + (void) length; /* Satisfying compiler (unused parameter) */ + return fontPtr; + +err: + if (fontPtr != 0) + HFree (fontPtr); + + if (bcds != NULL) + { + size_t bcdI; + for (bcdI = 0; bcdI < numBCDs; bcdI++) + TFB_DrawCanvas_Delete (bcds[bcdI].canvas); + HFree (bcds); + } + + if (fontDirHandle != NULL) + uio_closeDir (fontDirHandle); + + if (fontDir != 0) + DestroyDirEntryTable (ReleaseDirEntryTable (fontDir)); + + if (fontMount != 0) + uio_unmountDir(fontMount); + + return 0; +} + +BOOLEAN +_ReleaseFontData (void *handle) +{ + FONT font = (FONT) handle; + if (font == NULL) + return FALSE; + + { + FONT_PAGE *page; + FONT_PAGE *nextPage; + + for (page = font->fontPages; page != NULL; page = nextPage) + { + size_t charI; + for (charI = 0; charI < page->numChars; charI++) + { + TFB_Char *c = &page->charDesc[charI]; + + if (c->data == NULL) + continue; + + // XXX: fix this if fonts get per-page data + // rather than per-char + TFB_DrawScreen_DeleteData (c->data); + } + + nextPage = page->next; + FreeFontPage (page); + } + } + + HFree (font); + + return TRUE; +} diff --git a/src/libs/graphics/intersec.c b/src/libs/graphics/intersec.c new file mode 100644 index 0000000..02a5226 --- /dev/null +++ b/src/libs/graphics/intersec.c @@ -0,0 +1,415 @@ +//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/graphics/context.h" +#include "libs/graphics/drawable.h" +#include "libs/graphics/tfb_draw.h" +#include "libs/log.h" + +//#define DEBUG_INTERSEC + +static inline BOOLEAN +images_intersect (IMAGE_BOX *box1, IMAGE_BOX *box2, const RECT *rect) +{ + return TFB_DrawImage_Intersect (box1->FramePtr->image, box1->Box.corner, + box2->FramePtr->image, box2->Box.corner, rect); +} + +static TIME_VALUE +frame_intersect (INTERSECT_CONTROL *pControl0, RECT *pr0, + INTERSECT_CONTROL *pControl1, RECT *pr1, TIME_VALUE t0, + TIME_VALUE t1) +{ + SIZE time_error0, time_error1; + SIZE cycle0, cycle1; + SIZE dx_0, dy_0, dx_1, dy_1; + SIZE xincr0, yincr0, xincr1, yincr1; + SIZE xerror0, xerror1, yerror0, yerror1; + RECT r_intersect; + IMAGE_BOX IB0, IB1; + BOOLEAN check0, check1; + + IB0.FramePtr = pControl0->IntersectStamp.frame; + IB0.Box.corner = pr0->corner; + IB0.Box.extent.width = GetFrameWidth (IB0.FramePtr); + IB0.Box.extent.height = GetFrameHeight (IB0.FramePtr); + IB1.FramePtr = pControl1->IntersectStamp.frame; + IB1.Box.corner = pr1->corner; + IB1.Box.extent.width = GetFrameWidth (IB1.FramePtr); + IB1.Box.extent.height = GetFrameHeight (IB1.FramePtr); + + dx_0 = pr0->extent.width; + dy_0 = pr0->extent.height; + if (dx_0 >= 0) + xincr0 = 1; + else + { + xincr0 = -1; + dx_0 = -dx_0; + } + if (dy_0 >= 0) + yincr0 = 1; + else + { + yincr0 = -1; + dy_0 = -dy_0; + } + if (dx_0 >= dy_0) + cycle0 = dx_0; + else + cycle0 = dy_0; + xerror0 = yerror0 = cycle0; + + dx_1 = pr1->extent.width; + dy_1 = pr1->extent.height; + if (dx_1 >= 0) + xincr1 = 1; + else + { + xincr1 = -1; + dx_1 = -dx_1; + } + if (dy_1 >= 0) + yincr1 = 1; + else + { + yincr1 = -1; + dy_1 = -dy_1; + } + if (dx_1 >= dy_1) + cycle1 = dx_1; + else + cycle1 = dy_1; + xerror1 = yerror1 = cycle1; + + check0 = check1 = FALSE; + if (t0 <= 1) + { + time_error0 = time_error1 = 0; + if (t0 == 0) + { + ++t0; + goto CheckFirstIntersection; + } + } + else + { + SIZE delta; + COUNT start; + long error; + + start = (COUNT)cycle0 * (COUNT)(t0 - 1); + time_error0 = start & ((1 << TIME_SHIFT) - 1); + if ((start >>= (COUNT)TIME_SHIFT) > 0) + { + if ((error = (long)xerror0 + - (long)dx_0 * (long)start) > 0) + xerror0 = (SIZE)error; + else + { + delta = -(SIZE)(error / (long)cycle0) + 1; + IB0.Box.corner.x += xincr0 * delta; + xerror0 = (SIZE)(error + (long)cycle0 * (long)delta); + } + if ((error = (long)yerror0 + - (long)dy_0 * (long)start) > 0) + yerror0 = (SIZE)error; + else + { + delta = -(SIZE)(error / (long)cycle0) + 1; + IB0.Box.corner.y += yincr0 * delta; + yerror0 = (SIZE)(error + (long)cycle0 * (long)delta); + } + pr0->corner = IB0.Box.corner; + } + + start = (COUNT)cycle1 * (COUNT)(t0 - 1); + time_error1 = start & ((1 << TIME_SHIFT) - 1); + if ((start >>= (COUNT)TIME_SHIFT) > 0) + { + if ((error = (long)xerror1 + - (long)dx_1 * (long)start) > 0) + xerror1 = (SIZE)error; + else + { + delta = -(SIZE)(error / (long)cycle1) + 1; + IB1.Box.corner.x += xincr1 * delta; + xerror1 = (SIZE)(error + (long)cycle1 * (long)delta); + } + if ((error = (long)yerror1 + - (long)dy_1 * (long)start) > 0) + yerror1 = (SIZE)error; + else + { + delta = -(SIZE)(error / (long)cycle1) + 1; + IB1.Box.corner.y += yincr1 * delta; + yerror1 = (SIZE)(error + (long)cycle1 * (long)delta); + } + pr1->corner = IB1.Box.corner; + } + } + + pControl0->last_time_val = pControl1->last_time_val = t0; + do + { + ++t0; + if ((time_error0 += cycle0) >= (1 << TIME_SHIFT)) + { + if ((xerror0 -= dx_0) <= 0) + { + IB0.Box.corner.x += xincr0; + xerror0 += cycle0; + } + if ((yerror0 -= dy_0) <= 0) + { + IB0.Box.corner.y += yincr0; + yerror0 += cycle0; + } + + check0 = TRUE; + time_error0 -= (1 << TIME_SHIFT); + } + + if ((time_error1 += cycle1) >= (1 << TIME_SHIFT)) + { + if ((xerror1 -= dx_1) <= 0) + { + IB1.Box.corner.x += xincr1; + xerror1 += cycle1; + } + if ((yerror1 -= dy_1) <= 0) + { + IB1.Box.corner.y += yincr1; + yerror1 += cycle1; + } + + check1 = TRUE; + time_error1 -= (1 << TIME_SHIFT); + } + + if (check0 || check1) + { /* if check0 && check1, this may not be quite right -- + * if shapes had a pixel's separation to begin with + * and both moved toward each other, you would actually + * get a pixel overlap but since the last positions were + * separated by a pixel, the shapes wouldn't be touching + * each other. + */ +CheckFirstIntersection: + if (BoxIntersect (&IB0.Box, &IB1.Box, &r_intersect) + && images_intersect (&IB0, &IB1, &r_intersect)) + return (t0); + + if (check0) + { + pr0->corner = IB0.Box.corner; + pControl0->last_time_val = t0; + check0 = FALSE; + } + if (check1) + { + pr1->corner = IB1.Box.corner; + pControl1->last_time_val = t0; + check1 = FALSE; + } + } + } while (t0 <= t1); + + return ((TIME_VALUE)0); +} + +TIME_VALUE +DrawablesIntersect (INTERSECT_CONTROL *pControl0, + INTERSECT_CONTROL *pControl1, TIME_VALUE max_time_val) +{ + SIZE dy; + SIZE time_y_0, time_y_1; + RECT r0, r1; + FRAME FramePtr0, FramePtr1; + + if (!ContextActive () || max_time_val == 0) + return ((TIME_VALUE)0); + else if (max_time_val > MAX_TIME_VALUE) + max_time_val = MAX_TIME_VALUE; + + pControl0->last_time_val = pControl1->last_time_val = 0; + + r0.corner = pControl0->IntersectStamp.origin; + r1.corner = pControl1->IntersectStamp.origin; + + r0.extent.width = pControl0->EndPoint.x - r0.corner.x; + r0.extent.height = pControl0->EndPoint.y - r0.corner.y; + r1.extent.width = pControl1->EndPoint.x - r1.corner.x; + r1.extent.height = pControl1->EndPoint.y - r1.corner.y; + + FramePtr0 = pControl0->IntersectStamp.frame; + if (FramePtr0 == 0) + return(0); + r0.corner.x -= FramePtr0->HotSpot.x; + r0.corner.y -= FramePtr0->HotSpot.y; + + FramePtr1 = pControl1->IntersectStamp.frame; + if (FramePtr1 == 0) + return(0); + r1.corner.x -= FramePtr1->HotSpot.x; + r1.corner.y -= FramePtr1->HotSpot.y; + + dy = r1.corner.y - r0.corner.y; + time_y_0 = dy - GetFrameHeight (FramePtr0) + 1; + time_y_1 = dy + GetFrameHeight (FramePtr1) - 1; + dy = r0.extent.height - r1.extent.height; + + 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; + + dx = r1.corner.x - r0.corner.x; + time_x_0 = dx - GetFrameWidth (FramePtr0) + 1; + time_x_1 = dx + GetFrameWidth (FramePtr1) - 1; + dx = r0.extent.width - r1.extent.width; + + 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)) + { + TIME_VALUE intersect_time; + + 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; + /* just to be safe, widen search area */ + --time_y_0; + ++time_y_1; + + 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; + /* just to be safe, widen search area */ + --time_x_0; + ++time_x_1; + +#ifdef DEBUG_INTERSEC + log_add (log_Debug, "FramePtr0<%d, %d> --> <%d, %d>", + GetFrameWidth (FramePtr0), GetFrameHeight (FramePtr0), + r0.corner.x, r0.corner.y); + log_add (log_Debug, "FramePtr1<%d, %d> --> <%d, %d>", + GetFrameWidth (FramePtr1), GetFrameHeight (FramePtr1), + r1.corner.x, r1.corner.y); + log_add (log_Debug, "time_x(%d, %d)-%d, time_y(%d, %d)-%d", + time_x_0, time_x_1, dx, time_y_0, time_y_1, dy); +#endif /* DEBUG_INTERSEC */ + 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 <<= TIME_SHIFT) < fract) + time_y_0 = 0; + else + time_y_0 = (SIZE)(time_beg / fract); + + if (time_end >= fract /* just in case of overflow */ + || (time_end <<= TIME_SHIFT) >= + fract * (long)max_time_val) + time_y_1 = max_time_val - 1; + else + time_y_1 = (SIZE)((time_end + fract - 1) / fract) - 1; + } + +#ifdef DEBUG_INTERSEC + log_add (log_Debug, "start_time = %d, end_time = %d", + time_y_0, time_y_1); +#endif /* DEBUG_INTERSEC */ + if (time_y_0 <= time_y_1 + && (intersect_time = frame_intersect ( + pControl0, &r0, pControl1, &r1, + (TIME_VALUE)time_y_0, (TIME_VALUE)time_y_1))) + { + FramePtr0 = pControl0->IntersectStamp.frame; + pControl0->EndPoint.x = r0.corner.x + FramePtr0->HotSpot.x; + pControl0->EndPoint.y = r0.corner.y + FramePtr0->HotSpot.y; + FramePtr1 = pControl1->IntersectStamp.frame; + pControl1->EndPoint.x = r1.corner.x + FramePtr1->HotSpot.x; + pControl1->EndPoint.y = r1.corner.y + FramePtr1->HotSpot.y; + + return (intersect_time); + } + } + } + + return ((TIME_VALUE)0); +} + diff --git a/src/libs/graphics/loaddisp.c b/src/libs/graphics/loaddisp.c new file mode 100644 index 0000000..ccc7919 --- /dev/null +++ b/src/libs/graphics/loaddisp.c @@ -0,0 +1,65 @@ +//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/graphics/drawable.h" +#include "libs/log.h" + + +// Reads a piece of screen into a passed FRAME or a newly created one +DRAWABLE +LoadDisplayPixmap (const RECT *area, FRAME frame) +{ + // TODO: This should just return a FRAME instead of DRAWABLE + DRAWABLE buffer = GetFrameParentDrawable (frame); + COUNT index; + + if (!buffer) + { // asked to create a new DRAWABLE instead + buffer = CreateDrawable (WANT_PIXMAP | MAPPED_TO_DISPLAY, + area->extent.width, area->extent.height, 1); + if (!buffer) + return NULL; + + index = 0; + } + else + { + index = GetFrameIndex (frame); + } + + frame = SetAbsFrameIndex (CaptureDrawable (buffer), index); + + if (_CurFramePtr->Type != SCREEN_DRAWABLE + || frame->Type == SCREEN_DRAWABLE + || !(GetFrameParentDrawable (frame)->Flags & MAPPED_TO_DISPLAY)) + { + log_add (log_Warning, "Unimplemented function activated: " + "LoadDisplayPixmap()"); + } + else + { + TFB_Image *img = frame->image; + TFB_DrawScreen_CopyToImage (img, area, TFB_SCREEN_MAIN); + } + + ReleaseDrawable (frame); + + return buffer; +} + diff --git a/src/libs/graphics/pixmap.c b/src/libs/graphics/pixmap.c new file mode 100644 index 0000000..6e68244 --- /dev/null +++ b/src/libs/graphics/pixmap.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 "gfxintrn.h" +#include "libs/log.h" + +DRAWABLE +GetFrameParentDrawable (FRAME f) +{ + if (f != NULL) + { + return f->parent; + } + return NULL; +} + +FRAME +CaptureDrawable (DRAWABLE DrawablePtr) +{ + if (DrawablePtr) + { + return &DrawablePtr->Frame[0]; + } + + return NULL; +} + +DRAWABLE +ReleaseDrawable (FRAME FramePtr) +{ + if (FramePtr != 0) + { + DRAWABLE Drawable; + + Drawable = GetFrameParentDrawable (FramePtr); + + return (Drawable); + } + + return NULL; +} + +COUNT +GetFrameCount (FRAME FramePtr) +{ + DRAWABLE_DESC *DrawablePtr; + + if (FramePtr == 0) + return (0); + + DrawablePtr = GetFrameParentDrawable (FramePtr); + return DrawablePtr->MaxIndex + 1; +} + +COUNT +GetFrameIndex (FRAME FramePtr) +{ + if (FramePtr == 0) + return (0); + + return FramePtr->Index; +} + +FRAME +SetAbsFrameIndex (FRAME FramePtr, COUNT FrameIndex) +{ + if (FramePtr != 0) + { + DRAWABLE_DESC *DrawablePtr; + + DrawablePtr = GetFrameParentDrawable (FramePtr); + + FrameIndex = FrameIndex % (DrawablePtr->MaxIndex + 1); + FramePtr = &DrawablePtr->Frame[FrameIndex]; + } + + return FramePtr; +} + +FRAME +SetRelFrameIndex (FRAME FramePtr, SIZE FrameOffs) +{ + if (FramePtr != 0) + { + COUNT num_frames; + DRAWABLE_DESC *DrawablePtr; + + DrawablePtr = GetFrameParentDrawable (FramePtr); + num_frames = DrawablePtr->MaxIndex + 1; + if (FrameOffs < 0) + { + while ((FrameOffs += num_frames) < 0) + ; + } + + FrameOffs = ((SWORD)FramePtr->Index + FrameOffs) % num_frames; + FramePtr = &DrawablePtr->Frame[FrameOffs]; + } + + return FramePtr; +} + +FRAME +SetEquFrameIndex (FRAME DstFramePtr, FRAME SrcFramePtr) +{ + COUNT Index; + + if (!DstFramePtr || !SrcFramePtr) + return 0; + + Index = GetFrameIndex (SrcFramePtr); +#ifdef DEBUG + { + DRAWABLE_DESC *DrawablePtr = GetFrameParentDrawable (DstFramePtr); + if (Index > DrawablePtr->MaxIndex) + log_add (log_Debug, "SetEquFrameIndex: source index (%d) beyond " + "destination range (%d)", (int)Index, + (int)DrawablePtr->MaxIndex); + } +#endif + + return SetAbsFrameIndex (DstFramePtr, Index); +} + +FRAME +IncFrameIndex (FRAME FramePtr) +{ + DRAWABLE_DESC *DrawablePtr; + + if (FramePtr == 0) + return (0); + + DrawablePtr = GetFrameParentDrawable (FramePtr); + if (FramePtr->Index < DrawablePtr->MaxIndex) + return ++FramePtr; + else + return DrawablePtr->Frame; +} + +FRAME +DecFrameIndex (FRAME FramePtr) +{ + if (FramePtr == 0) + return (0); + + if (FramePtr->Index > 0) + return --FramePtr; + else + { + DRAWABLE_DESC *DrawablePtr; + + DrawablePtr = GetFrameParentDrawable (FramePtr); + return &DrawablePtr->Frame[DrawablePtr->MaxIndex]; + } +} diff --git a/src/libs/graphics/prim.h b/src/libs/graphics/prim.h new file mode 100644 index 0000000..bce3c2a --- /dev/null +++ b/src/libs/graphics/prim.h @@ -0,0 +1,80 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_GRAPHICS_PRIM_H_ +#define LIBS_GRAPHICS_PRIM_H_ + +enum gfx_object +{ + POINT_PRIM = 0, + STAMP_PRIM, + STAMPFILL_PRIM, + LINE_PRIM, + TEXT_PRIM, + RECT_PRIM, + RECTFILL_PRIM, + + NUM_PRIMS +}; +typedef BYTE GRAPHICS_PRIM; + +typedef union +{ + POINT Point; + STAMP Stamp; + LINE Line; + TEXT Text; + RECT Rect; +} PRIM_DESC; + +typedef DWORD PRIM_LINKS; + +typedef struct +{ + PRIM_LINKS Links; + GRAPHICS_PRIM Type; + Color color; + PRIM_DESC Object; +} PRIMITIVE; + +#define END_OF_LIST ((COUNT)0xFFFF) + +#define GetPredLink(l) LOWORD(l) +#define GetSuccLink(l) HIWORD(l) +#define MakeLinks MAKE_DWORD +#define SetPrimLinks(pPrim,p,s) ((pPrim)->Links = MakeLinks (p, s)) +#define GetPrimLinks(pPrim) ((pPrim)->Links) +#define SetPrimType(pPrim,t) ((pPrim)->Type = t) +#define GetPrimType(pPrim) ((pPrim)->Type) +#define SetPrimColor(pPrim,c) ((pPrim)->color = c) +#define GetPrimColor(pPrim) ((pPrim)->color) + +static inline void +SetPrimNextLink (PRIMITIVE *pPrim, COUNT Link) +{ + SetPrimLinks (pPrim, END_OF_LIST, Link); +} + + +static inline COUNT +GetPrimNextLink (PRIMITIVE *pPrim) +{ + return GetSuccLink (GetPrimLinks (pPrim)); +} + + +#endif /* PRIM_H */ + + diff --git a/src/libs/graphics/resgfx.c b/src/libs/graphics/resgfx.c new file mode 100644 index 0000000..c0760dc --- /dev/null +++ b/src/libs/graphics/resgfx.c @@ -0,0 +1,54 @@ +//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 "gfxintrn.h" + +static void +GetCelFileData (const char *pathname, RESOURCE_DATA *resdata) +{ + resdata->ptr = LoadResourceFromPath (pathname, _GetCelData); +} + +static void +GetFontFileData (const char *pathname, RESOURCE_DATA *resdata) +{ + resdata->ptr = LoadResourceFromPath (pathname, _GetFontData); +} + + +BOOLEAN +InstallGraphicResTypes (void) +{ + InstallResTypeVectors ("GFXRES", GetCelFileData, _ReleaseCelData, NULL); + InstallResTypeVectors ("FONTRES", GetFontFileData, _ReleaseFontData, NULL); + return (TRUE); +} + +/* Needs to be void * because it could be either a DRAWABLE or a FONT. */ +void * +LoadGraphicInstance (RESOURCE res) +{ + void *hData; + + hData = res_GetResource (res); + if (hData) + res_DetachResource (res); + + return (hData); +} + diff --git a/src/libs/graphics/sdl/2xscalers.c b/src/libs/graphics/sdl/2xscalers.c new file mode 100644 index 0000000..a612d2c --- /dev/null +++ b/src/libs/graphics/sdl/2xscalers.c @@ -0,0 +1,260 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" + + +// Scaler function lookup table +// +const Scale_FuncDef_t +Scale_C_Functions[] = +{ + {TFB_GFXFLAGS_SCALE_BILINEAR, Scale_BilinearFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPT, Scale_BiAdaptFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPTADV, Scale_BiAdaptAdvFilter}, + {TFB_GFXFLAGS_SCALE_TRISCAN, Scale_TriScanFilter}, + {TFB_GFXFLAGS_SCALE_HQXX, Scale_HqFilter}, + // Default + {0, Scale_Nearest} +}; + +// See +// nearest2x.c -- Nearest Neighboor scaling +// bilinear2x.c -- Bilinear scaling +// biadv2x.c -- Advanced Biadapt scaling +// triscan2x.c -- Triscan scaling + +// Biadapt scaling to 2x +void +SCALE_(BiAdaptFilter) (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r) +{ + int x, y; + const int w = src->w, h = src->h; + int xend, yend; + int dsrc, ddst; + SDL_Rect *region = r; + SDL_Rect limits; + SDL_PixelFormat *fmt = dst->format; + const int sp = src->pitch, dp = dst->pitch; + const int bpp = fmt->BytesPerPixel; + const int slen = sp / bpp, dlen = dp / bpp; + Uint32 *src_p = (Uint32 *)src->pixels; + Uint32 *dst_p = (Uint32 *)dst->pixels; + Uint32 pixval_tl, pixval_tr, pixval_bl, pixval_br; + + // these macros are for clarity; they make the current pixel (0,0) + // and allow to access pixels in all directions + #define SRC(x, y) (src_p + (x) + ((y) * slen)) + + SCALE_(PlatInit) (); + + // expand updated region if necessary + // pixels neighbooring the updated region may + // change as a result of updates + limits.x = 0; + limits.y = 0; + limits.w = src->w; + limits.h = src->h; + Scale_ExpandRect (region, 2, &limits); + + xend = region->x + region->w; + yend = region->y + region->h; + dsrc = slen - region->w; + ddst = (dlen - region->w) * 2; + + // move ptrs to the first updated pixel + src_p += slen * region->y + region->x; + dst_p += (dlen * region->y + region->x) * 2; + + for (y = region->y; y < yend; ++y, dst_p += ddst, src_p += dsrc) + { + for (x = region->x; x < xend; ++x, ++src_p, ++dst_p) + { + pixval_tl = SCALE_GETPIX (SRC (0, 0)); + + SCALE_SETPIX (dst_p, pixval_tl); + + if (y + 1 < h) + { + // check pixel below the current one + pixval_bl = SCALE_GETPIX (SRC (0, 1)); + + if (pixval_tl == pixval_bl) + SCALE_SETPIX (dst_p + dlen, pixval_tl); + else + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + pixval_tl, pixval_bl) + ); + } + else + { + // last pixel in column - propagate + SCALE_SETPIX (dst_p + dlen, pixval_tl); + pixval_bl = pixval_tl; + } + ++dst_p; + + if (x + 1 >= w) + { + // last pixel in row - propagate + SCALE_SETPIX (dst_p, pixval_tl); + + if (pixval_tl == pixval_bl) + SCALE_SETPIX (dst_p + dlen, pixval_tl); + else + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + pixval_tl, pixval_bl) + ); + continue; + } + + // check pixel to the right from the current one + pixval_tr = SCALE_GETPIX (SRC (1, 0)); + + if (pixval_tl == pixval_tr) + SCALE_SETPIX (dst_p, pixval_tr); + else + SCALE_SETPIX (dst_p, Scale_Blend_11 ( + pixval_tl, pixval_tr) + ); + + if (y + 1 >= h) + { + // last pixel in column - propagate + SCALE_SETPIX (dst_p + dlen, pixval_tl); + continue; + } + + // check pixel to the bottom-right + pixval_br = SCALE_GETPIX (SRC (1, 1)); + + if (pixval_tl == pixval_br && pixval_tr == pixval_bl) + { + int cl, cr; + Uint32 clr; + + if (pixval_tl == pixval_tr) + { + // all 4 are equal - propagate + SCALE_SETPIX (dst_p + dlen, pixval_tl); + continue; + } + + // both pairs are equal, have to resolve the pixel + // race; we try detecting which color is + // the background by looking for a line or an edge + // examine 8 pixels surrounding the current quad + + cl = cr = 1; + + if (x > 0) + { + clr = SCALE_GETPIX (SRC (-1, 0)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + + clr = SCALE_GETPIX (SRC (-1, 1)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + } + + if (y > 0) + { + clr = SCALE_GETPIX (SRC (0, -1)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + + clr = SCALE_GETPIX (SRC (1, -1)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + } + + if (x + 2 < w) + { + clr = SCALE_GETPIX (SRC (2, 0)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + + clr = SCALE_GETPIX (SRC (2, 1)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + } + + if (y + 2 < h) + { + clr = SCALE_GETPIX (SRC (0, 2)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + + clr = SCALE_GETPIX (SRC (1, 2)); + if (clr == pixval_tl) + cl++; + else if (clr == pixval_tr) + cr++; + } + + // least count wins + if (cl > cr) + SCALE_SETPIX (dst_p + dlen, pixval_tr); + else if (cr > cl) + SCALE_SETPIX (dst_p + dlen, pixval_tl); + else + SCALE_SETPIX (dst_p + dlen, + Scale_Blend_11 (pixval_tl, pixval_tr)); + } + else if (pixval_tl == pixval_br) + { + // main diagonal is same color + // use its value + SCALE_SETPIX (dst_p + dlen, pixval_tl); + } + else if (pixval_tr == pixval_bl) + { + // 2nd diagonal is same color + // use its value + SCALE_SETPIX (dst_p + dlen, pixval_tr); + } + else + { + // blend all 4 + SCALE_SETPIX (dst_p + dlen, Scale_Blend_1111 ( + pixval_tl, pixval_bl, pixval_tr, pixval_br + )); + } + } + } + + SCALE_(PlatDone) (); +} + diff --git a/src/libs/graphics/sdl/2xscalers.h b/src/libs/graphics/sdl/2xscalers.h new file mode 100644 index 0000000..f004fcd --- /dev/null +++ b/src/libs/graphics/sdl/2xscalers.h @@ -0,0 +1,30 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_GRAPHICS_SDL_2XSCALERS_H_ +#define LIBS_GRAPHICS_SDL_2XSCALERS_H_ + +void Scale_Nearest (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_BilinearFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_BiAdaptFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_BiAdaptAdvFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_TriScanFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_HqFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); + +extern const Scale_FuncDef_t Scale_C_Functions[]; + + +#endif /* LIBS_GRAPHICS_SDL_2XSCALERS_H_ */ diff --git a/src/libs/graphics/sdl/2xscalers_3dnow.c b/src/libs/graphics/sdl/2xscalers_3dnow.c new file mode 100644 index 0000000..da34e82 --- /dev/null +++ b/src/libs/graphics/sdl/2xscalers_3dnow.c @@ -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. + */ + +#include "port.h" +#include "libs/platform.h" + +#if defined(MMX_ASM) + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" +#include "2xscalers_mmx.h" + +// 3DNow! name for all functions +#undef SCALE_ +#define SCALE_(name) Scale ## _3DNow_ ## name + +// Tell them which opcodes we want to support +#undef USE_MOVNTQ +#define USE_PREFETCH AMD_PREFETCH +#undef USE_PSADBW +// Bring in inline asm functions +#include "scalemmx.h" + + +// Scaler function lookup table +// +const Scale_FuncDef_t +Scale_3DNow_Functions[] = +{ + {TFB_GFXFLAGS_SCALE_BILINEAR, Scale_3DNow_BilinearFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPT, Scale_BiAdaptFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPTADV, Scale_MMX_BiAdaptAdvFilter}, + {TFB_GFXFLAGS_SCALE_TRISCAN, Scale_MMX_TriScanFilter}, + {TFB_GFXFLAGS_SCALE_HQXX, Scale_MMX_HqFilter}, + // Default + {0, Scale_3DNow_Nearest} +}; + + +void +Scale_3DNow_PrepPlatform (const SDL_PixelFormat* fmt) +{ + Scale_MMX_PrepPlatform (fmt); +} + +// Nearest Neighbor scaling to 2x +// void Scale_3DNow_Nearest (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "nearest2x.c" + + +// Bilinear scaling to 2x +// void Scale_3DNow_BilinearFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "bilinear2x.c" + + +#if 0 && NO_IMPROVEMENT + +// Advanced Biadapt scaling to 2x +// void Scale_3DNow_BiAdaptAdvFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "biadv2x.c" + + +// Triscan scaling to 2x +// derivative of scale2x -- scale2x.sf.net +// void Scale_3DNow_TriScanFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "triscan2x.c" + +// Hq2x scaling +// (adapted from 'hq2x' by Maxim Stepin -- www.hiend3d.com/hq2x.html) +// void Scale_3DNow_HqFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "hq2x.c" + +#endif /* NO_IMPROVEMENT */ + +#endif /* MMX_ASM */ + diff --git a/src/libs/graphics/sdl/2xscalers_mmx.c b/src/libs/graphics/sdl/2xscalers_mmx.c new file mode 100644 index 0000000..c321b16 --- /dev/null +++ b/src/libs/graphics/sdl/2xscalers_mmx.c @@ -0,0 +1,136 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "libs/platform.h" + +#if defined(MMX_ASM) + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" +#include "2xscalers_mmx.h" + +// MMX name for all functions +#undef SCALE_ +#define SCALE_(name) Scale ## _MMX_ ## name + +// Tell them which opcodes we want to support +#undef USE_MOVNTQ +#undef USE_PREFETCH +#undef USE_PSADBW +// And Bring in inline asm functions +#include "scalemmx.h" + + +// Scaler function lookup table +// +const Scale_FuncDef_t +Scale_MMX_Functions[] = +{ + {TFB_GFXFLAGS_SCALE_BILINEAR, Scale_MMX_BilinearFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPT, Scale_BiAdaptFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPTADV, Scale_MMX_BiAdaptAdvFilter}, + {TFB_GFXFLAGS_SCALE_TRISCAN, Scale_MMX_TriScanFilter}, + {TFB_GFXFLAGS_SCALE_HQXX, Scale_MMX_HqFilter}, + // Default + {0, Scale_MMX_Nearest} +}; + +// MMX transformation multipliers +Uint64 mmx_888to555_mult; +Uint64 mmx_Y_mult; +Uint64 mmx_U_mult; +Uint64 mmx_V_mult; +// Uint64 mmx_YUV_threshold = 0x00300706; original hq2x threshold +//Uint64 mmx_YUV_threshold = 0x0030100e; +Uint64 mmx_YUV_threshold = 0x0040120c; + +void +Scale_MMX_PrepPlatform (const SDL_PixelFormat* fmt) +{ + // prepare the channel-shuffle multiplier + mmx_888to555_mult = ((Uint64)0x0400) << (fmt->Rshift * 2) + | ((Uint64)0x0020) << (fmt->Gshift * 2) + | ((Uint64)0x0001) << (fmt->Bshift * 2); + + // prepare the RGB->YUV multipliers + mmx_Y_mult = ((Uint64)(uint16)YUV_matrix[YUV_XFORM_R][YUV_XFORM_Y]) + << (fmt->Rshift * 2) + | ((Uint64)(uint16)YUV_matrix[YUV_XFORM_G][YUV_XFORM_Y]) + << (fmt->Gshift * 2) + | ((Uint64)(uint16)YUV_matrix[YUV_XFORM_B][YUV_XFORM_Y]) + << (fmt->Bshift * 2); + + mmx_U_mult = ((Uint64)(uint16)YUV_matrix[YUV_XFORM_R][YUV_XFORM_U]) + << (fmt->Rshift * 2) + | ((Uint64)(uint16)YUV_matrix[YUV_XFORM_G][YUV_XFORM_U]) + << (fmt->Gshift * 2) + | ((Uint64)(uint16)YUV_matrix[YUV_XFORM_B][YUV_XFORM_U]) + << (fmt->Bshift * 2); + + mmx_V_mult = ((Uint64)(uint16)YUV_matrix[YUV_XFORM_R][YUV_XFORM_V]) + << (fmt->Rshift * 2) + | ((Uint64)(uint16)YUV_matrix[YUV_XFORM_G][YUV_XFORM_V]) + << (fmt->Gshift * 2) + | ((Uint64)(uint16)YUV_matrix[YUV_XFORM_B][YUV_XFORM_V]) + << (fmt->Bshift * 2); + + mmx_YUV_threshold = (SCALE_DIFFYUV_TY << 16) | (SCALE_DIFFYUV_TU << 8) + | SCALE_DIFFYUV_TV; +} + + +// Nearest Neighbor scaling to 2x +// void Scale_MMX_Nearest (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "nearest2x.c" + + +// Bilinear scaling to 2x +// void Scale_MMX_BilinearFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "bilinear2x.c" + + +// Advanced Biadapt scaling to 2x +// void Scale_MMX_BiAdaptAdvFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "biadv2x.c" + + +// Triscan scaling to 2x +// derivative of 'scale2x' -- scale2x.sf.net +// void Scale_MMX_TriScanFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "triscan2x.c" + +// Hq2x scaling +// (adapted from 'hq2x' by Maxim Stepin -- www.hiend3d.com/hq2x.html) +// void Scale_MMX_HqFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "hq2x.c" + + +#endif /* MMX_ASM */ + diff --git a/src/libs/graphics/sdl/2xscalers_mmx.h b/src/libs/graphics/sdl/2xscalers_mmx.h new file mode 100644 index 0000000..c8dba32 --- /dev/null +++ b/src/libs/graphics/sdl/2xscalers_mmx.h @@ -0,0 +1,56 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_GRAPHICS_SDL_2XSCALERS_MMX_H_ +#define LIBS_GRAPHICS_SDL_2XSCALERS_MMX_H_ + +// MMX versions +void Scale_MMX_PrepPlatform (const SDL_PixelFormat* fmt); + +void Scale_MMX_Nearest (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_MMX_BilinearFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_MMX_BiAdaptAdvFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_MMX_TriScanFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_MMX_HqFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); + +extern const Scale_FuncDef_t Scale_MMX_Functions[]; + + +// SSE (Intel)/MMX Ext (Athlon) versions +void Scale_SSE_PrepPlatform (const SDL_PixelFormat* fmt); + +void Scale_SSE_Nearest (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_SSE_BilinearFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_SSE_BiAdaptAdvFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_SSE_TriScanFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_SSE_HqFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); + +extern const Scale_FuncDef_t Scale_SSE_Functions[]; + + +// 3DNow (AMD K6/Athlon) versions +void Scale_3DNow_PrepPlatform (const SDL_PixelFormat* fmt); + +void Scale_3DNow_Nearest (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_3DNow_BilinearFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_3DNow_BiAdaptAdvFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_3DNow_TriScanFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); +void Scale_3DNow_HqFilter (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r); + +extern const Scale_FuncDef_t Scale_3DNow_Functions[]; + + +#endif /* LIBS_GRAPHICS_SDL_2XSCALERS_MMX_H_ */ diff --git a/src/libs/graphics/sdl/2xscalers_sse.c b/src/libs/graphics/sdl/2xscalers_sse.c new file mode 100644 index 0000000..a089744 --- /dev/null +++ b/src/libs/graphics/sdl/2xscalers_sse.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 "port.h" +#include "libs/platform.h" + +#if defined(MMX_ASM) + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" +#include "2xscalers_mmx.h" + +// SSE name for all functions +#undef SCALE_ +#define SCALE_(name) Scale ## _SSE_ ## name + +// Tell them which opcodes we want to support +#define USE_MOVNTQ +#define USE_PREFETCH INTEL_PREFETCH +#define USE_PSADBW +// Bring in inline asm functions +#include "scalemmx.h" + + +// Scaler function lookup table +// +const Scale_FuncDef_t +Scale_SSE_Functions[] = +{ + {TFB_GFXFLAGS_SCALE_BILINEAR, Scale_SSE_BilinearFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPT, Scale_BiAdaptFilter}, + {TFB_GFXFLAGS_SCALE_BIADAPTADV, Scale_SSE_BiAdaptAdvFilter}, + {TFB_GFXFLAGS_SCALE_TRISCAN, Scale_SSE_TriScanFilter}, + {TFB_GFXFLAGS_SCALE_HQXX, Scale_MMX_HqFilter}, + // Default + {0, Scale_SSE_Nearest} +}; + + +void +Scale_SSE_PrepPlatform (const SDL_PixelFormat* fmt) +{ + Scale_MMX_PrepPlatform (fmt); +} + +// Nearest Neighbor scaling to 2x +// void Scale_SSE_Nearest (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "nearest2x.c" + + +// Bilinear scaling to 2x +// void Scale_SSE_BilinearFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "bilinear2x.c" + + +// Advanced Biadapt scaling to 2x +// void Scale_SSE_BiAdaptAdvFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "biadv2x.c" + + +// Triscan scaling to 2x +// derivative of scale2x -- scale2x.sf.net +// void Scale_SSE_TriScanFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "triscan2x.c" + +#if 0 && NO_IMPROVEMENT +// Hq2x scaling +// (adapted from 'hq2x' by Maxim Stepin -- www.hiend3d.com/hq2x.html) +// void Scale_SSE_HqFilter (SDL_Surface *src, +// SDL_Surface *dst, SDL_Rect *r) + +#include "hq2x.c" +#endif + +#endif /* MMX_ASM */ + diff --git a/src/libs/graphics/sdl/Makeinfo b/src/libs/graphics/sdl/Makeinfo new file mode 100644 index 0000000..f940b76 --- /dev/null +++ b/src/libs/graphics/sdl/Makeinfo @@ -0,0 +1,9 @@ +uqm_CFILES="opengl.c palette.c primitives.c pure.c sdl2_pure.c + sdl_common.c sdl1_common.c sdl2_common.c + scalers.c 2xscalers.c + 2xscalers_mmx.c 2xscalers_sse.c 2xscalers_3dnow.c + nearest2x.c bilinear2x.c biadv2x.c triscan2x.c hq2x.c + canvas.c png2sdl.c sdluio.c rotozoom.c" +uqm_HFILES="2xscalers.h 2xscalers_mmx.h opengl.h palette.h png2sdl.h + primitives.h pure.h rotozoom.h scaleint.h scalemmx.h + scalers.h sdl_common.h sdluio.h" diff --git a/src/libs/graphics/sdl/biadv2x.c b/src/libs/graphics/sdl/biadv2x.c new file mode 100644 index 0000000..b38cdf7 --- /dev/null +++ b/src/libs/graphics/sdl/biadv2x.c @@ -0,0 +1,532 @@ +/* + * Portions Copyright (C) 2003-2005 Alex Volkov (codepro@usa.net) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Core algorithm of the Advanced BiAdaptive screen scaler +// Template +// When this file is built standalone is produces a plain C version +// Also #included by 2xscalers_mmx.c for an MMX version + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" + + +// Advanced biadapt scaling to 2x +// The name expands to either +// Scale_BiAdaptAdvFilter (for plain C) or +// Scale_MMX_BiAdaptAdvFilter (for MMX) +// [others when platforms are added] +void +SCALE_(BiAdaptAdvFilter) (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r) +{ + int x, y; + const int w = src->w, h = src->h; + int xend, yend; + int dsrc, ddst; + SDL_Rect *region = r; + SDL_Rect limits; + SDL_PixelFormat *fmt = dst->format; + const int sp = src->pitch, dp = dst->pitch; + const int bpp = fmt->BytesPerPixel; + const int slen = sp / bpp, dlen = dp / bpp; + // for clarity purposes, the 'pixels' array here is transposed + Uint32 pixels[4][4]; + static int resolve_coord[][2] = + { + {0, -1}, {1, -1}, { 2, 0}, { 2, 1}, + {1, 2}, {0, 2}, {-1, 1}, {-1, 0}, + {100, 100} // term + }; + Uint32 *src_p = (Uint32 *)src->pixels; + Uint32 *dst_p = (Uint32 *)dst->pixels; + + // these macros are for clarity; they make the current pixel (0,0) + // and allow to access pixels in all directions + #define PIX(x, y) (pixels[1 + (x)][1 + (y)]) + #define SRC(x, y) (src_p + (x) + ((y) * slen)) + // commonly used operations, for clarity also + // others are defined at their respective bpp levels + #define BIADAPT_RGBHIGH 8000 + #define BIADAPT_YUVLOW 30 + #define BIADAPT_YUVMED 70 + #define BIADAPT_YUVHIGH 130 + + // high tolerance pixel comparison + #define BIADAPT_CMPRGB_HIGH(p1, p2) \ + (p1 == p2 || SCALE_CMPRGB (p1, p2) <= BIADAPT_RGBHIGH) + + // low tolerance pixel comparison + #define BIADAPT_CMPYUV_LOW(p1, p2) \ + (p1 == p2 || SCALE_CMPYUV (p1, p2, BIADAPT_YUVLOW)) + // medium tolerance pixel comparison + #define BIADAPT_CMPYUV_MED(p1, p2) \ + (p1 == p2 || SCALE_CMPYUV (p1, p2, BIADAPT_YUVMED)) + // high tolerance pixel comparison + #define BIADAPT_CMPYUV_HIGH(p1, p2) \ + (p1 == p2 || SCALE_CMPYUV (p1, p2, BIADAPT_YUVHIGH)) + + SCALE_(PlatInit) (); + + // expand updated region if necessary + // pixels neighbooring the updated region may + // change as a result of updates + limits.x = 0; + limits.y = 0; + limits.w = src->w; + limits.h = src->h; + Scale_ExpandRect (region, 2, &limits); + + xend = region->x + region->w; + yend = region->y + region->h; + dsrc = slen - region->w; + ddst = (dlen - region->w) * 2; + + #define SCALE_GETPIX(p) ( *(Uint32 *)(p) ) + #define SCALE_SETPIX(p, c) ( *(Uint32 *)(p) = (c) ) + + // move ptrs to the first updated pixel + src_p += slen * region->y + region->x; + dst_p += (dlen * region->y + region->x) * 2; + + for (y = region->y; y < yend; ++y, dst_p += ddst, src_p += dsrc) + { + for (x = region->x; x < xend; ++x, ++src_p, ++dst_p) + { + // pixel equality counter + int cmatch; + + // most pixels will fall into 'all 4 equal' + // pattern, so we check it first + cmatch = 0; + + PIX (0, 0) = SCALE_GETPIX (SRC (0, 0)); + + SCALE_SETPIX (dst_p, PIX (0, 0)); + + if (y + 1 < h) + { + // check pixel below the current one + PIX (0, 1) = SCALE_GETPIX (SRC (0, 1)); + + if (PIX (0, 0) == PIX (0, 1)) + { + SCALE_SETPIX (dst_p + dlen, PIX (0, 0)); + cmatch |= 1; + } + } + else + { + // last pixel in column - propagate + PIX (0, 1) = PIX (0, 0); + SCALE_SETPIX (dst_p + dlen, PIX (0, 0)); + cmatch |= 1; + + } + + if (x + 1 < w) + { + // check pixel to the right from the current one + PIX (1, 0) = SCALE_GETPIX (SRC (1, 0)); + + if (PIX (0, 0) == PIX (1, 0)) + { + SCALE_SETPIX (dst_p + 1, PIX (0, 0)); + cmatch |= 2; + } + } + else + { + // last pixel in row - propagate + PIX (1, 0) = PIX (0, 0); + SCALE_SETPIX (dst_p + 1, PIX (0, 0)); + cmatch |= 2; + } + + if (cmatch == 3) + { + if (y + 1 >= h || x + 1 >= w) + { + // last pixel in row/column and nearest + // neighboor is identical + dst_p++; + SCALE_SETPIX (dst_p + dlen, PIX (0, 0)); + continue; + } + + // check pixel to the bottom-right + PIX (1, 1) = SCALE_GETPIX (SRC (1, 1)); + + if (PIX (0, 0) == PIX (1, 1)) + { + // all 4 are equal - propagate + dst_p++; + SCALE_SETPIX (dst_p + dlen, PIX (0, 0)); + continue; + } + } + + // some neighboors are different, lets check them + + if (x > 0) + PIX (-1, 0) = SCALE_GETPIX (SRC (-1, 0)); + else + PIX (-1, 0) = PIX (0, 0); + + if (x + 2 < w) + PIX (2, 0) = SCALE_GETPIX (SRC (2, 0)); + else + PIX (2, 0) = PIX (1, 0); + + if (y + 1 < h) + { + if (x > 0) + PIX (-1, 1) = SCALE_GETPIX (SRC (-1, 1)); + else + PIX (-1, 1) = PIX (0, 1); + + if (x + 2 < w) + { + PIX (1, 1) = SCALE_GETPIX (SRC (1, 1)); + PIX (2, 1) = SCALE_GETPIX (SRC (2, 1)); + } + else if (x + 1 < w) + { + PIX (1, 1) = SCALE_GETPIX (SRC (1, 1)); + PIX (2, 1) = PIX (1, 1); + } + else + { + PIX (1, 1) = PIX (0, 1); + PIX (2, 1) = PIX (0, 1); + } + } + else + { + // last pixel in column + PIX (-1, 1) = PIX (-1, 0); + PIX (1, 1) = PIX (1, 0); + PIX (2, 1) = PIX (2, 0); + } + + if (y + 2 < h) + { + PIX (0, 2) = SCALE_GETPIX (SRC (0, 2)); + + if (x > 0) + PIX (-1, 2) = SCALE_GETPIX (SRC (-1, 2)); + else + PIX (-1, 2) = PIX (0, 2); + + if (x + 2 < w) + { + PIX (1, 2) = SCALE_GETPIX (SRC (1, 2)); + PIX (2, 2) = SCALE_GETPIX (SRC (2, 2)); + } + else if (x + 1 < w) + { + PIX (1, 2) = SCALE_GETPIX (SRC (1, 2)); + PIX (2, 2) = PIX (1, 2); + } + else + { + PIX (1, 2) = PIX (0, 2); + PIX (2, 2) = PIX (0, 2); + } + } + else + { + // last pixel in column + PIX (-1, 2) = PIX (-1, 1); + PIX (0, 2) = PIX (0, 1); + PIX (1, 2) = PIX (1, 1); + PIX (2, 2) = PIX (2, 1); + } + + if (y > 0) + { + PIX (0, -1) = SCALE_GETPIX (SRC (0, -1)); + + if (x > 0) + PIX (-1, -1) = SCALE_GETPIX (SRC (-1, -1)); + else + PIX (-1, -1) = PIX (0, -1); + + if (x + 2 < w) + { + PIX (1, -1) = SCALE_GETPIX (SRC (1, -1)); + PIX (2, -1) = SCALE_GETPIX (SRC (2, -1)); + } + else if (x + 1 < w) + { + PIX (1, -1) = SCALE_GETPIX (SRC (1, -1)); + PIX (2, -1) = PIX (1, -1); + } + else + { + PIX (1, -1) = PIX (0, -1); + PIX (2, -1) = PIX (0, -1); + } + } + else + { + PIX (-1, -1) = PIX (-1, 0); + PIX (0, -1) = PIX (0, 0); + PIX (1, -1) = PIX (1, 0); + PIX (2, -1) = PIX (2, 0); + } + + // check pixel below the current one + if (!(cmatch & 1)) + { + if (SCALE_CMPYUV (PIX (0, 0), PIX (0, 1), BIADAPT_YUVLOW)) + { + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (0, 0), PIX (0, 1)) + ); + cmatch |= 1; + } + // detect a 2:1 line going across the current pixel + else if ( (PIX (0, 0) == PIX (-1, 0) + && PIX (0, 0) == PIX (1, 1) + && PIX (0, 0) == PIX (2, 1) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (0, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (1, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (2, 0))) || + (!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (1, 2)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (2, 2))))) || + + (PIX (0, 0) == PIX (1, 0) + && PIX (0, 0) == PIX (-1, 1) + && PIX (0, 0) == PIX (2, -1) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (0, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (1, -1))) || + (!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, 2)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (1, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (2, 0))))) ) + { + SCALE_SETPIX (dst_p + dlen, PIX (0, 0)); + } + // detect a 2:1 line going across the pixel below current + else if ( (PIX (0, 1) == PIX (-1, 0) + && PIX (0, 1) == PIX (1, 1) + && PIX (0, 1) == PIX (2, 2) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (-1, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (1, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (2, 1))) || + (!BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (-1, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (0, 2)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (1, 2))))) || + + (PIX (0, 1) == PIX (1, 0) + && PIX (0, 1) == PIX (-1, 1) + && PIX (0, 1) == PIX (2, 0) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (-1, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (1, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (2, -1))) || + (!BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (-1, 2)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (0, 2)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (1, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 1), PIX (2, 1))))) ) + { + SCALE_SETPIX (dst_p + dlen, PIX (0, 1)); + } + else + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (0, 0), PIX (0, 1)) + ); + } + + dst_p++; + + // check pixel to the right from the current one + if (!(cmatch & 2)) + { + if (SCALE_CMPYUV (PIX (0, 0), PIX (1, 0), BIADAPT_YUVLOW)) + { + SCALE_SETPIX (dst_p, Scale_Blend_11 ( + PIX (0, 0), PIX (1, 0)) + ); + cmatch |= 2; + } + // detect a 1:2 line going across the current pixel + else if ( (PIX (0, 0) == PIX (1, -1) + && PIX (0, 0) == PIX (0, 1) + && PIX (0, 0) == PIX (-1, 2) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (0, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, 1))) || + (!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (2, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (1, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (0, 2))))) || + + (PIX (0, 0) == PIX (0, -1) + && PIX (0, 0) == PIX (1, 1) + && PIX (0, 0) == PIX (1, 2) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (-1, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (0, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (0, 2))) || + (!BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (1, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (2, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (0, 0), PIX (2, 2))))) ) + + { + SCALE_SETPIX (dst_p, PIX (0, 0)); + } + // detect a 1:2 line going across the pixel to the right + else if ( (PIX (1, 0) == PIX (1, -1) + && PIX (1, 0) == PIX (0, 1) + && PIX (1, 0) == PIX (0, 2) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (0, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (-1, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (-1, 2))) || + (!BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (2, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (2, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (1, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (1, 2))))) || + + (PIX (1, 0) == PIX (0, -1) + && PIX (1, 0) == PIX (1, 1) + && PIX (1, 0) == PIX (2, 2) && + + ((!BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (-1, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (0, 1)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (1, 2))) || + (!BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (1, -1)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (2, 0)) + && !BIADAPT_CMPRGB_HIGH (PIX (1, 0), PIX (2, 1))))) ) + { + SCALE_SETPIX (dst_p, PIX (1, 0)); + } + else + SCALE_SETPIX (dst_p, Scale_Blend_11 ( + PIX (0, 0), PIX (1, 0)) + ); + } + + if (PIX (0, 0) == PIX (1, 1) && PIX (1, 0) == PIX (0, 1)) + { + // diagonals are equal + int *coord; + int cl, cr; + Uint32 clr; + + // both pairs are equal, have to resolve the pixel + // race; we try detecting which color is + // the background by looking for a line or an edge + // examine 8 pixels surrounding the current quad + + cl = cr = 2; + for (coord = resolve_coord[0]; *coord < 100; coord += 2) + { + clr = PIX (coord[0], coord[1]); + + if (BIADAPT_CMPYUV_MED (clr, PIX (0, 0))) + cl++; + else if (BIADAPT_CMPYUV_MED (clr, PIX (1, 0))) + cr++; + } + + // least count wins + if (cl > cr) + clr = PIX (1, 0); + else if (cr > cl) + clr = PIX (0, 0); + else + clr = Scale_Blend_11 (PIX (0, 0), PIX (1, 0)); + + SCALE_SETPIX (dst_p + dlen, clr); + continue; + } + + if (cmatch == 3 + || (BIADAPT_CMPYUV_LOW (PIX (1, 0), PIX (0, 1)) + && BIADAPT_CMPYUV_LOW (PIX (1, 0), PIX (1, 1)))) + { + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (0, 1), PIX (1, 0)) + ); + continue; + } + else if (cmatch && BIADAPT_CMPYUV_LOW (PIX (0, 0), PIX (1, 1))) + { + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (0, 0), PIX (1, 1)) + ); + continue; + } + + // check pixel to the bottom-right + if (BIADAPT_CMPYUV_HIGH (PIX (0, 0), PIX (1, 1)) + && BIADAPT_CMPYUV_HIGH (PIX (1, 0), PIX (0, 1))) + { + if (SCALE_GETY (PIX (0, 0)) > SCALE_GETY (PIX (1, 0))) + { + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (0, 0), PIX (1, 1)) + ); + } + else + { + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (1, 0), PIX (0, 1)) + ); + } + } + else if (BIADAPT_CMPYUV_HIGH (PIX (0, 0), PIX (1, 1))) + { + // main diagonal is same color + // use its value + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (0, 0), PIX (1, 1)) + ); + } + else if (BIADAPT_CMPYUV_HIGH (PIX (1, 0), PIX (0, 1))) + { + // 2nd diagonal is same color + // use its value + SCALE_SETPIX (dst_p + dlen, Scale_Blend_11 ( + PIX (1, 0), PIX (0, 1)) + ); + } + else + { + // blend all 4 + SCALE_SETPIX (dst_p + dlen, Scale_Blend_1111 ( + PIX (0, 0), PIX (0, 1), + PIX (1, 0), PIX (1, 1) + )); + } + } + } + + SCALE_(PlatDone) (); +} + diff --git a/src/libs/graphics/sdl/bilinear2x.c b/src/libs/graphics/sdl/bilinear2x.c new file mode 100644 index 0000000..a12eb93 --- /dev/null +++ b/src/libs/graphics/sdl/bilinear2x.c @@ -0,0 +1,112 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Core algorithm of the BiLinear screen scaler +// Template +// When this file is built standalone is produces a plain C version +// Also #included by 2xscalers_mmx.c for an MMX version + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" + + +// Bilinear scaling to 2x +// The name expands to either +// Scale_BilinearFilter (for plain C) or +// Scale_MMX_BilinearFilter (for MMX) +// Scale_SSE_BilinearFilter (for SSE) +// [others when platforms are added] +void +SCALE_(BilinearFilter) (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r) +{ + int x, y; + const int w = src->w, h = src->h; + int xend, yend; + int dsrc, ddst; + SDL_Rect *region = r; + SDL_Rect limits; + SDL_PixelFormat *fmt = dst->format; + const int pitch = src->pitch, dp = dst->pitch; + const int bpp = fmt->BytesPerPixel; + const int len = pitch / bpp, dlen = dp / bpp; + Uint32 p[4]; // influential pixels array + Uint32 *srow0 = (Uint32 *) src->pixels; + Uint32 *dst_p = (Uint32 *) dst->pixels; + + SCALE_(PlatInit) (); + + // expand updated region if necessary + // pixels neighbooring the updated region may + // change as a result of updates + limits.x = 0; + limits.y = 0; + limits.w = w; + limits.h = h; + Scale_ExpandRect (region, 1, &limits); + + xend = region->x + region->w; + yend = region->y + region->h; + dsrc = len - region->w; + ddst = (dlen - region->w) * 2; + + // move ptrs to the first updated pixel + srow0 += len * region->y + region->x; + dst_p += (dlen * region->y + region->x) * 2; + + for (y = region->y; y < yend; ++y, dst_p += ddst, srow0 += dsrc) + { + Uint32 *srow1; + + SCALE_(Prefetch) (srow0 + 16); + SCALE_(Prefetch) (srow0 + 32); + + if (y < h - 1) + srow1 = srow0 + len; + else + srow1 = srow0; + + SCALE_(Prefetch) (srow1 + 16); + SCALE_(Prefetch) (srow1 + 32); + + for (x = region->x; x < xend; ++x, ++srow0, ++srow1, dst_p += 2) + { + if (x < w - 1) + { // can blend directly from pixels + SCALE_BILINEAR_BLEND4 (srow0, srow1, dst_p, dlen); + } + else + { // need to make temp pixel rows + p[0] = srow0[0]; + p[1] = p[0]; + p[2] = srow1[0]; + p[3] = p[2]; + + SCALE_BILINEAR_BLEND4 (&p[0], &p[2], dst_p, dlen); + } + } + + SCALE_(Prefetch) (srow0 + dsrc); + SCALE_(Prefetch) (srow0 + dsrc + 16); + SCALE_(Prefetch) (srow1 + dsrc); + SCALE_(Prefetch) (srow1 + dsrc + 16); + } + + SCALE_(PlatDone) (); +} + diff --git a/src/libs/graphics/sdl/canvas.c b/src/libs/graphics/sdl/canvas.c new file mode 100644 index 0000000..ad7024d --- /dev/null +++ b/src/libs/graphics/sdl/canvas.c @@ -0,0 +1,2176 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + // for memcpy() + +#include SDL_INCLUDE(SDL.h) +#include "sdl_common.h" +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/tfb_draw.h" +#include "libs/graphics/cmap.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "primitives.h" +#include "palette.h" +#include "sdluio.h" +#include "rotozoom.h" +#include "options.h" +#include "types.h" + +typedef SDL_Surface *NativeCanvas; + +// BYTE x BYTE weight (mult >> 8) table +static Uint8 btable[256][256]; + +void +TFB_DrawCanvas_Initialize (void) +{ + int i, j; + for (i = 0; i < 256; ++i) + for (j = 0; j < 256; ++j) + btable[j][i] = (j * i + 0x80) >> 8; + // need error correction here +} + +const char * +TFB_DrawCanvas_GetError (void) +{ + const char *err = SDL_GetError (); + // TODO: Should we call SDL_ClearError() here so that it is not + // returned again later? + return err; +} + +static void +checkPrimitiveMode (SDL_Surface *surf, Color *color, DrawMode *mode) +{ + const SDL_PixelFormat *fmt = surf->format; + // Special case: We support DRAW_ALPHA mode to non-alpha surfaces + // for primitives via Color.a + if (mode->kind == DRAW_REPLACE && fmt->Amask == 0 && color->a != 0xff) + { + mode->kind = DRAW_ALPHA; + mode->factor = color->a; + color->a = 0xff; + } +} + +void +TFB_DrawCanvas_Line (int x1, int y1, int x2, int y2, Color color, + DrawMode mode, TFB_Canvas target) +{ + SDL_Surface *dst = target; + SDL_PixelFormat *fmt = dst->format; + Uint32 sdlColor; + RenderPixelFn plotFn; + + checkPrimitiveMode (dst, &color, &mode); + sdlColor = SDL_MapRGBA (fmt, color.r, color.g, color.b, color.a); + + plotFn = renderpixel_for (target, mode.kind); + if (!plotFn) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_Line " + "unsupported draw mode (%d)", (int)mode.kind); + return; + } + + SDL_LockSurface (dst); + line_prim (x1, y1, x2, y2, sdlColor, plotFn, mode.factor, dst); + SDL_UnlockSurface (dst); +} + +void +TFB_DrawCanvas_Rect (RECT *rect, Color color, DrawMode mode, TFB_Canvas target) +{ + SDL_Surface *dst = target; + SDL_PixelFormat *fmt = dst->format; + Uint32 sdlColor; + SDL_Rect sr; + sr.x = rect->corner.x; + sr.y = rect->corner.y; + sr.w = rect->extent.width; + sr.h = rect->extent.height; + + checkPrimitiveMode (dst, &color, &mode); + sdlColor = SDL_MapRGBA (fmt, color.r, color.g, color.b, color.a); + + if (mode.kind == DRAW_REPLACE) + { // Standard SDL fillrect rendering + Uint32 colorkey; + if (fmt->Amask && (TFB_GetColorKey (dst, &colorkey) == 0)) + { // special case -- alpha surface with colorkey + // colorkey rects are transparent + if ((sdlColor & ~fmt->Amask) == (colorkey & ~fmt->Amask)) + sdlColor &= ~fmt->Amask; // make transparent + } + SDL_FillRect (dst, &sr, sdlColor); + } + else + { // Custom fillrect rendering + RenderPixelFn plotFn = renderpixel_for (target, mode.kind); + if (!plotFn) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_Rect " + "unsupported draw mode (%d)", (int)mode.kind); + return; + } + + SDL_LockSurface (dst); + fillrect_prim (sr, sdlColor, plotFn, mode.factor, dst); + SDL_UnlockSurface (dst); + } +} + +static void +TFB_DrawCanvas_Blit (SDL_Surface *src, SDL_Rect *src_r, + SDL_Surface *dst, SDL_Rect *dst_r, DrawMode mode) +{ + SDL_PixelFormat *srcfmt = src->format; + + if (mode.kind == DRAW_REPLACE) + { // Standard SDL simple blit + SDL_BlitSurface (src, src_r, dst, dst_r); + } + else if (mode.kind == DRAW_ALPHA && srcfmt->Amask == 0) + { // Standard SDL surface-alpha blit + // Note that surface alpha and per-pixel alpha cannot work + // at the same time, which is why the Amask test + int hasAlpha = TFB_HasSurfaceAlphaMod (src); + assert (!hasAlpha); + // Set surface alpha temporarily + TFB_SetSurfaceAlphaMod (src, mode.factor); + SDL_BlitSurface (src, src_r, dst, dst_r); + TFB_DisableSurfaceAlphaMod (src); + } + else + { // Custom blit + SDL_Rect loc_src_r, loc_dst_r; + RenderPixelFn plotFn = renderpixel_for (dst, mode.kind); + if (!plotFn) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_Blit " + "unsupported draw mode (%d)", (int)mode.kind); + return; + } + + if (!src_r) + { // blit whole image; generate rect + loc_src_r.x = 0; + loc_src_r.y = 0; + loc_src_r.w = src->w; + loc_src_r.h = src->h; + src_r = &loc_src_r; + } + + if (!dst_r) + { // blit to 0,0; generate rect + loc_dst_r.x = 0; + loc_dst_r.y = 0; + loc_dst_r.w = dst->w; + loc_dst_r.h = dst->h; + dst_r = &loc_dst_r; + } + + SDL_LockSurface (dst); + blt_prim (src, *src_r, plotFn, mode.factor, dst, *dst_r); + SDL_UnlockSurface (dst); + } +} + +// XXX: If a colormap is passed in, it has to have been acquired via +// TFB_GetColorMap(). We release the colormap at the end. +void +TFB_DrawCanvas_Image (TFB_Image *img, int x, int y, int scale, + int scaleMode, TFB_ColorMap *cmap, DrawMode mode, TFB_Canvas target) +{ + SDL_Rect srcRect, targetRect, *pSrcRect; + SDL_Surface *surf; + SDL_Palette *NormalPal; + + if (img == 0) + { + log_add (log_Warning, + "ERROR: TFB_DrawCanvas_Image passed null image ptr"); + return; + } + + LockMutex (img->mutex); + + NormalPal = ((SDL_Surface *)img->NormalImg)->format->palette; + // only set the new palette if it changed + if (NormalPal && cmap && img->colormap_version != cmap->version) + TFB_SetColors (img->NormalImg, cmap->palette->colors, 0, 256); + + if (scale != 0 && scale != GSCALE_IDENTITY) + { + if (scaleMode == TFB_SCALE_TRILINEAR && img->MipmapImg) + { + // only set the new palette if it changed + if (TFB_DrawCanvas_IsPaletted (img->MipmapImg) + && cmap && img->colormap_version != cmap->version) + TFB_SetColors (img->MipmapImg, cmap->palette->colors, 0, 256); + } + else if (scaleMode == TFB_SCALE_TRILINEAR && !img->MipmapImg) + { // Do bilinear scaling instead when mipmap is unavailable + scaleMode = TFB_SCALE_BILINEAR; + } + + TFB_DrawImage_FixScaling (img, scale, scaleMode); + surf = img->ScaledImg; + if (TFB_DrawCanvas_IsPaletted (surf)) + { + // We may only get a paletted scaled image if the source is + // paletted. Currently, all scaling targets are truecolor. + assert (NormalPal && NormalPal->colors); + TFB_SetColors (surf, NormalPal->colors, 0, NormalPal->ncolors); + } + + srcRect.x = 0; + srcRect.y = 0; + srcRect.w = img->extent.width; + srcRect.h = img->extent.height; + pSrcRect = &srcRect; + + targetRect.x = x - img->last_scale_hs.x; + targetRect.y = y - img->last_scale_hs.y; + } + else + { + surf = img->NormalImg; + pSrcRect = NULL; + + targetRect.x = x - img->NormalHs.x; + targetRect.y = y - img->NormalHs.y; + } + + if (cmap) + { + img->colormap_version = cmap->version; + // TODO: Technically, this is not a proper place to release a + // colormap. As it stands now, the colormap must have been + // addrefed when passed to us. + TFB_ReturnColorMap (cmap); + } + + TFB_DrawCanvas_Blit (surf, pSrcRect, target, &targetRect, mode); + UnlockMutex (img->mutex); +} + +// Assumes the source and destination surfaces are in the same format +static void +TFB_DrawCanvas_Fill (SDL_Surface *src, Uint32 fillcolor, SDL_Surface *dst) +{ + const SDL_PixelFormat *srcfmt = src->format; + SDL_PixelFormat *dstfmt = dst->format; + const int width = src->w; + const int height = src->h; + const int bpp = dstfmt->BytesPerPixel; + const int sp = src->pitch, dp = dst->pitch; + const int slen = sp / bpp, dlen = dp / bpp; + const int dsrc = slen - width, ddst = dlen - width; + Uint32 *src_p; + Uint32 *dst_p; + int x, y; + Uint32 srckey = 0, dstkey = 0; // 0 means alpha=0 too + Uint32 amask = srcfmt->Amask; + int alpha = (fillcolor & amask) >> srcfmt->Ashift; + + if (srcfmt->BytesPerPixel != 4 || dstfmt->BytesPerPixel != 4) + { + log_add (log_Warning, "TFB_DrawCanvas_Fill: Unsupported surface " + "formats: %d bytes/pixel source, %d bytes/pixel destination", + (int)srcfmt->BytesPerPixel, (int)dstfmt->BytesPerPixel); + return; + } + + // Strip the alpha channel from fillcolor because we process the + // alpha separately + fillcolor &= ~amask; + + SDL_LockSurface(src); + SDL_LockSurface(dst); + + src_p = (Uint32 *)src->pixels; + dst_p = (Uint32 *)dst->pixels; + + if (dstkey == fillcolor) + { // color collision, must switch colorkey + // new colorkey is grey (1/2,1/2,1/2) + dstkey = SDL_MapRGBA (dstfmt, 127, 127, 127, 0); + } + + if (srcfmt->Amask) + { // alpha-based fill + for (y = 0; y < height; ++y, dst_p += ddst, src_p += dsrc) + { + for (x = 0; x < width; ++x, ++src_p, ++dst_p) + { + Uint32 p = *src_p & amask; + + if (p == 0) + { // fully transparent pixel + *dst_p = dstkey; + } + else if (alpha == 0xff) + { // not for DRAW_ALPHA; use alpha chan directly + *dst_p = p | fillcolor; + } + else + { // for DRAW_ALPHA; modulate the alpha channel + p >>= srcfmt->Ashift; + p = (p * alpha) >> 8; + p <<= srcfmt->Ashift; + *dst_p = p | fillcolor; + } + } + } + } + else if (TFB_GetColorKey (src, &srckey) == 0) + { // colorkey-based fill + + for (y = 0; y < height; ++y, dst_p += ddst, src_p += dsrc) + { + for (x = 0; x < width; ++x, ++src_p, ++dst_p) + { + Uint32 p = *src_p; + + *dst_p = (p == srckey) ? dstkey : fillcolor; + } + } + } + else + { + log_add (log_Warning, "TFB_DrawCanvas_Fill: Unsupported source" + "surface format\n"); + } + + SDL_UnlockSurface(dst); + SDL_UnlockSurface(src); + + // save the colorkey (dynamic image -- not using RLE coding here) + TFB_SetColorKey (dst, dstkey, 0); + // if the filled surface is RGBA, colorkey will only be used + // when SDL_SRCALPHA flag is cleared. this allows us to blit + // the surface in different ways to diff targets +} + +void +TFB_DrawCanvas_FilledImage (TFB_Image *img, int x, int y, int scale, + int scaleMode, Color color, DrawMode mode, TFB_Canvas target) +{ + SDL_Surface *dst = target; + SDL_Rect srcRect, targetRect, *pSrcRect; + SDL_Surface *surf; + SDL_Palette *palette; + int i; + bool force_fill = false; + + if (img == 0) + { + log_add (log_Warning, + "ERROR: TFB_DrawCanvas_FilledImage passed null image ptr"); + return; + } + + checkPrimitiveMode (dst, &color, &mode); + + LockMutex (img->mutex); + + if (scale != 0 && scale != GSCALE_IDENTITY) + { + if (scaleMode == TFB_SCALE_TRILINEAR) + scaleMode = TFB_SCALE_BILINEAR; + // no point in trilinear for filled images + + if (scale != img->last_scale || scaleMode != img->last_scale_type) + force_fill = true; + + TFB_DrawImage_FixScaling (img, scale, scaleMode); + surf = img->ScaledImg; + srcRect.x = 0; + srcRect.y = 0; + srcRect.w = img->extent.width; + srcRect.h = img->extent.height; + pSrcRect = &srcRect; + + targetRect.x = x - img->last_scale_hs.x; + targetRect.y = y - img->last_scale_hs.y; + } + else + { + if (img->last_scale != 0) + { + // Make sure we remember that the last fill was from + // an unscaled image + force_fill = true; + img->last_scale = 0; + } + + surf = img->NormalImg; + pSrcRect = NULL; + + targetRect.x = x - img->NormalHs.x; + targetRect.y = y - img->NormalHs.y; + } + + palette = surf->format->palette; + if (palette) + { // set palette for fill-stamp + // Calling TFB_SetColors() results in an expensive src -> dst + // color-mapping operation for an SDL blit, following the call. + // We want to avoid that as much as possible. + + // TODO: generate a 32bpp filled image? + + SDL_Color colors[256]; + + colors[0] = ColorToNative (color); + for (i = 1; i < palette->ncolors; i++) + colors[i] = colors[0]; + + TFB_SetColors (surf, colors, 0, palette->ncolors); + // reflect the change in *actual* image palette + img->colormap_version--; + } + else + { // fill the non-transparent parts of the image with fillcolor + SDL_Surface *newfill = img->FilledImg; + SDL_PixelFormat *fillfmt; + + if (newfill && (newfill->w < surf->w || newfill->h < surf->h)) + { + TFB_DrawCanvas_Delete (newfill); + newfill = NULL; + } + + // prepare the filled image + if (!newfill) + { + newfill = SDL_CreateRGBSurface (SDL_SWSURFACE, + surf->w, surf->h, + surf->format->BitsPerPixel, + surf->format->Rmask, + surf->format->Gmask, + surf->format->Bmask, + surf->format->Amask); + force_fill = true; + } + fillfmt = newfill->format; + + if (force_fill || !sameColor (img->last_fill, color)) + { // image or fillcolor changed - regenerate + Uint32 fillColor; + + if (mode.kind == DRAW_ALPHA && fillfmt->Amask) + { // Per-pixel alpha and surface alpha will not work together + // We have to handle DRAW_ALPHA differently by modulating + // the surface alpha channel ourselves. + color.a = mode.factor; + mode.kind = DRAW_REPLACE; + } + else + { // Make sure we do not modulate the alpha channel + color.a = 0xff; + } + fillColor = SDL_MapRGBA (newfill->format, color.r, color.g, + color.b, color.a); + TFB_DrawCanvas_Fill (surf, fillColor, newfill); + // cache filled image if possible + img->last_fill = color; + } + + img->FilledImg = newfill; + surf = newfill; + } + + TFB_DrawCanvas_Blit (surf, pSrcRect, dst, &targetRect, mode); + UnlockMutex (img->mutex); +} + +void +TFB_DrawCanvas_FontChar (TFB_Char *fontChar, TFB_Image *backing, + int x, int y, DrawMode mode, TFB_Canvas target) +{ + SDL_Surface *dst = target; + SDL_Rect srcRect, targetRect; + SDL_Surface *surf; + int w, h; + + if (fontChar == 0) + { + log_add (log_Warning, "ERROR: " + "TFB_DrawCanvas_FontChar passed null char ptr"); + return; + } + if (backing == 0) + { + log_add (log_Warning, "ERROR: " + "TFB_DrawCanvas_FontChar passed null backing ptr"); + return; + } + + w = fontChar->extent.width; + h = fontChar->extent.height; + + LockMutex (backing->mutex); + + surf = backing->NormalImg; + if (surf->format->BytesPerPixel != 4 + || surf->w < w || surf->h < h) + { + log_add (log_Warning, "ERROR: " + "TFB_DrawCanvas_FontChar bad backing surface: %dx%dx%d; " + "char: %dx%d", + surf->w, surf->h, (int)surf->format->BytesPerPixel, w, h); + UnlockMutex (backing->mutex); + return; + } + + srcRect.x = 0; + srcRect.y = 0; + srcRect.w = fontChar->extent.width; + srcRect.h = fontChar->extent.height; + + targetRect.x = x - fontChar->HotSpot.x; + targetRect.y = y - fontChar->HotSpot.y; + + // transfer the alpha channel to the backing surface + SDL_LockSurface (surf); + { + int x, y; + const int sskip = fontChar->pitch - w; + const int dskip = (surf->pitch / 4) - w; + const Uint32 dmask = ~surf->format->Amask; + const int ashift = surf->format->Ashift; + Uint8 *src_p = fontChar->data; + Uint32 *dst_p = (Uint32 *)surf->pixels; + + if (mode.kind == DRAW_ALPHA) + { // Per-pixel alpha and surface alpha will not work together + // We have to handle DRAW_ALPHA differently by modulating + // the backing surface alpha channel ourselves. + // The existing backing surface alpha channel is ignored. + int alpha = mode.factor; + mode.kind = DRAW_REPLACE; + + for (y = 0; y < h; ++y, src_p += sskip, dst_p += dskip) + { + for (x = 0; x < w; ++x, ++src_p, ++dst_p) + { + Uint32 p = *dst_p & dmask; + Uint32 a = *src_p; + + // we use >> 8 instead of / 255, and it does not handle + // alpha == 255 correctly + if (alpha != 0xff) + { // modulate the alpha channel + a = (a * alpha) >> 8; + } + *dst_p = p | (a << ashift); + } + } + } + else /* if (mode.kind != DRAW_ALPHA) */ + { // Transfer the alpha channel to the backing surface + // DRAW_REPLACE + Color.a is NOT supported right now + for (y = 0; y < h; ++y, src_p += sskip, dst_p += dskip) + { + for (x = 0; x < w; ++x, ++src_p, ++dst_p) + { + *dst_p = (*dst_p & dmask) | ((Uint32)*src_p << ashift); + } + } + } + } + SDL_UnlockSurface (surf); + + TFB_DrawCanvas_Blit (surf, &srcRect, dst, &targetRect, mode); + UnlockMutex (backing->mutex); +} + +TFB_Canvas +TFB_DrawCanvas_New_TrueColor (int w, int h, BOOLEAN hasalpha) +{ + SDL_Surface *new_surf; + SDL_PixelFormat* fmt = format_conv_surf->format; + + new_surf = SDL_CreateRGBSurface (SDL_SWSURFACE, w, h, + fmt->BitsPerPixel, fmt->Rmask, fmt->Gmask, fmt->Bmask, + hasalpha ? fmt->Amask : 0); + if (!new_surf) + { + log_add (log_Fatal, "INTERNAL PANIC: Failed to create TFB_Canvas: %s", + SDL_GetError()); + exit (EXIT_FAILURE); + } + return new_surf; +} + +TFB_Canvas +TFB_DrawCanvas_New_ForScreen (int w, int h, BOOLEAN withalpha) +{ + SDL_Surface *new_surf; + SDL_PixelFormat* fmt = SDL_Screen->format; + + if (fmt->palette) + { + log_add (log_Warning, "TFB_DrawCanvas_New_ForScreen() WARNING:" + "Paletted display format will be slow"); + + new_surf = TFB_DrawCanvas_New_TrueColor (w, h, withalpha); + } + else + { + if (withalpha && fmt->Amask == 0) + fmt = format_conv_surf->format; // Screen has no alpha and we need it + + new_surf = SDL_CreateRGBSurface (SDL_SWSURFACE, w, h, + fmt->BitsPerPixel, fmt->Rmask, fmt->Gmask, fmt->Bmask, + withalpha ? fmt->Amask : 0); + } + + if (!new_surf) + { + log_add (log_Fatal, "TFB_DrawCanvas_New_ForScreen() INTERNAL PANIC:" + "Failed to create TFB_Canvas: %s", SDL_GetError()); + exit (EXIT_FAILURE); + } + return new_surf; +} + +TFB_Canvas +TFB_DrawCanvas_New_Paletted (int w, int h, Color palette[256], + int transparent_index) +{ + SDL_Surface *new_surf; + new_surf = SDL_CreateRGBSurface (SDL_SWSURFACE, w, h, 8, 0, 0, 0, 0); + if (!new_surf) + { + log_add (log_Fatal, "INTERNAL PANIC: Failed to create TFB_Canvas: %s", + SDL_GetError()); + exit (EXIT_FAILURE); + } + if (palette != NULL) + { + TFB_DrawCanvas_SetPalette (new_surf, palette); + } + if (transparent_index >= 0) + { + TFB_SetColorKey (new_surf, transparent_index, 0); + } + else + { + TFB_DisableColorKey (new_surf); + } + return new_surf; +} + +TFB_Canvas +TFB_DrawCanvas_New_ScaleTarget (TFB_Canvas canvas, TFB_Canvas oldcanvas, int type, int last_type) +{ + SDL_Surface *src = canvas; + SDL_Surface *old = oldcanvas; + SDL_Surface *newsurf = NULL; + + // For the purposes of this function, bilinear == trilinear + if (type == TFB_SCALE_TRILINEAR) + type = TFB_SCALE_BILINEAR; + if (last_type == TFB_SCALE_TRILINEAR) + last_type = TFB_SCALE_BILINEAR; + + if (old && type != last_type) + { + TFB_DrawCanvas_Delete (old); + old = NULL; + } + if (old) + return old; /* can just reuse the old one */ + + if (type == TFB_SCALE_NEAREST) + { + newsurf = SDL_CreateRGBSurface (SDL_SWSURFACE, src->w, + src->h, + src->format->BitsPerPixel, + src->format->Rmask, + src->format->Gmask, + src->format->Bmask, + src->format->Amask); + TFB_DrawCanvas_CopyTransparencyInfo (src, newsurf); + } + else + { + // The scaled image may in fact be larger by 1 pixel than the source + // because of hotspot alignment and fractional edge pixels + if (SDL_Screen->format->BitsPerPixel == 32) + newsurf = TFB_DrawCanvas_New_ForScreen (src->w + 1, src->h + 1, TRUE); + else + newsurf = TFB_DrawCanvas_New_TrueColor (src->w + 1, src->h + 1, TRUE); + } + + return newsurf; +} + +TFB_Canvas +TFB_DrawCanvas_New_RotationTarget (TFB_Canvas src_canvas, int angle) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *newsurf; + EXTENT size; + + TFB_DrawCanvas_GetRotatedExtent (src_canvas, angle, &size); + + newsurf = SDL_CreateRGBSurface (SDL_SWSURFACE, + size.width, size.height, + src->format->BitsPerPixel, + src->format->Rmask, + src->format->Gmask, + src->format->Bmask, + src->format->Amask); + if (!newsurf) + { + log_add (log_Fatal, "TFB_DrawCanvas_New_RotationTarget()" + " INTERNAL PANIC: Failed to create TFB_Canvas: %s", + SDL_GetError()); + exit (EXIT_FAILURE); + } + TFB_DrawCanvas_CopyTransparencyInfo (src, newsurf); + + return newsurf; +} + +TFB_Canvas +TFB_DrawCanvas_LoadFromFile (void *dir, const char *fileName) +{ + SDL_Surface *surf = sdluio_loadImage (dir, fileName); + if (!surf) + return NULL; + + if (surf->format->BitsPerPixel < 8) + { + SDL_SetError ("unsupported image format (min 8bpp)"); + SDL_FreeSurface (surf); + surf = NULL; + } + + return surf; +} + +void +TFB_DrawCanvas_Delete (TFB_Canvas canvas) +{ + if (!canvas) + { + log_add (log_Warning, "INTERNAL PANIC: Attempted" + " to delete a NULL canvas!"); + /* Should we actually die here? */ + } + else + { + SDL_FreeSurface (canvas); + } +} + +BOOLEAN +TFB_DrawCanvas_GetFontCharData (TFB_Canvas canvas, BYTE *outData, + unsigned dataPitch) +{ + SDL_Surface *surf = canvas; + int x, y; + Uint8 r, g, b, a; + Uint32 p; + SDL_PixelFormat *fmt = surf->format; + GetPixelFn getpix; + + if (!surf || !outData) + return FALSE; + + SDL_LockSurface (surf); + + getpix = getpixel_for (surf); + + // produce an alpha-only image in internal BYTE[] format + // from the SDL surface + for (y = 0; y < surf->h; ++y) + { + BYTE *dst = outData + dataPitch * y; + + for (x = 0; x < surf->w; ++x, ++dst) + { + p = getpix (surf, x, y); + SDL_GetRGBA (p, fmt, &r, &g, &b, &a); + + if (!fmt->Amask) + { // produce alpha from intensity (Y component) + // using a fast approximation + // contributions to Y are: R=2, G=4, B=1 + a = ((r * 2) + (g * 4) + b) / 7; + } + + *dst = a; + } + } + + SDL_UnlockSurface (surf); + + return TRUE; +} + +Color * +TFB_DrawCanvas_ExtractPalette (TFB_Canvas canvas) +{ + int i; + Color *result; + SDL_Surface *surf = canvas; + SDL_Palette *palette = surf->format->palette; + + if (!palette) + return NULL; + + // There may be less colors in the surface than 256. Init to 0 first. + result = HCalloc (sizeof (Color) * 256); + assert (palette->ncolors <= 256); + for (i = 0; i < palette->ncolors; ++i) + result[i] = NativeToColor (palette->colors[i]); + + return result; +} + +TFB_Canvas +TFB_DrawCanvas_ToScreenFormat (TFB_Canvas canvas) +{ + SDL_Surface *result = TFB_DisplayFormatAlpha (canvas); + if (result == NULL) + { + log_add (log_Debug, "WARNING: Could not convert" + " sprite-canvas to display format."); + return canvas; + } + else if (result == canvas) + { // no conversion was necessary + return canvas; + } + else + { // converted + TFB_DrawCanvas_Delete (canvas); + return result; + } + + return canvas; +} + +BOOLEAN +TFB_DrawCanvas_IsPaletted (TFB_Canvas canvas) +{ + return ((SDL_Surface *)canvas)->format->palette != NULL; +} + +void +TFB_DrawCanvas_SetPalette (TFB_Canvas target, Color palette[256]) +{ + SDL_Color colors[256]; + int i; + + for (i = 0; i < 256; ++i) + colors[i] = ColorToNative (palette[i]); + + TFB_SetColors (target, colors, 0, 256); +} + +int +TFB_DrawCanvas_GetTransparentIndex (TFB_Canvas canvas) +{ + Uint32 colorkey; + if (TFB_GetColorKey (canvas, &colorkey)) + { + return colorkey; + } + return -1; +} + +void +TFB_DrawCanvas_SetTransparentIndex (TFB_Canvas canvas, int index, BOOLEAN rleaccel) +{ + if (index >= 0) + { + TFB_SetColorKey (canvas, index, rleaccel); + + if (!TFB_DrawCanvas_IsPaletted (canvas)) + { + // disables surface alpha so color key transparency actually works + TFB_DisableSurfaceAlphaMod (canvas); + } + } + else + { + TFB_DisableColorKey (canvas); + } +} + +void +TFB_DrawCanvas_CopyTransparencyInfo (TFB_Canvas src_canvas, + TFB_Canvas dst_canvas) +{ + SDL_Surface* src = src_canvas; + + if (src->format->palette) + { + int index; + index = TFB_DrawCanvas_GetTransparentIndex (src_canvas); + TFB_DrawCanvas_SetTransparentIndex (dst_canvas, index, FALSE); + } + else + { + Color color; + if (TFB_DrawCanvas_GetTransparentColor (src_canvas, &color)) + TFB_DrawCanvas_SetTransparentColor (dst_canvas, color, FALSE); + } +} + +BOOLEAN +TFB_DrawCanvas_GetTransparentColor (TFB_Canvas canvas, Color *color) +{ + Uint32 colorkey; + if (!TFB_DrawCanvas_IsPaletted (canvas) + && (TFB_GetColorKey ((SDL_Surface *)canvas, &colorkey) == 0)) + { + Uint8 ur, ug, ub; + SDL_GetRGB (colorkey, ((SDL_Surface *)canvas)->format, &ur, &ug, &ub); + color->r = ur; + color->g = ug; + color->b = ub; + color->a = 0xff; + return TRUE; + } + return FALSE; +} + +void +TFB_DrawCanvas_SetTransparentColor (TFB_Canvas canvas, Color color, + BOOLEAN rleaccel) +{ + Uint32 sdlColor; + sdlColor = SDL_MapRGBA (((SDL_Surface *)canvas)->format, + color.r, color.g, color.b, 0); + TFB_SetColorKey (canvas, sdlColor, rleaccel); + + if (!TFB_DrawCanvas_IsPaletted (canvas)) + { + // disables surface alpha so color key transparency actually works + TFB_DisableSurfaceAlphaMod (canvas); + } +} + +void +TFB_DrawCanvas_GetScaledExtent (TFB_Canvas src_canvas, HOT_SPOT* src_hs, + TFB_Canvas src_mipmap, HOT_SPOT* mm_hs, + int scale, int type, EXTENT *size, HOT_SPOT *hs) +{ + SDL_Surface *src = src_canvas; + sint32 x, y, w, h; + int frac; + + if (!src_mipmap) + { + w = src->w * scale; + h = src->h * scale; + x = src_hs->x * scale; + y = src_hs->y * scale; + } + else + { + // interpolates extents between src and mipmap to get smoother + // transition when surface changes + SDL_Surface *mipmap = src_mipmap; + int ratio = scale * 2 - GSCALE_IDENTITY; + + assert (scale >= GSCALE_IDENTITY / 2); + + w = mipmap->w * GSCALE_IDENTITY + (src->w - mipmap->w) * ratio; + h = mipmap->h * GSCALE_IDENTITY + (src->h - mipmap->h) * ratio; + + // Seems it is better to use mipmap hotspot because some + // source and mipmap images have the same dimensions! + x = mm_hs->x * GSCALE_IDENTITY + (src_hs->x - mm_hs->x) * ratio; + y = mm_hs->y * GSCALE_IDENTITY + (src_hs->y - mm_hs->y) * ratio; + } + + if (type != TFB_SCALE_NEAREST) + { + // align hotspot on an whole pixel + if (x & (GSCALE_IDENTITY - 1)) + { + frac = GSCALE_IDENTITY - (x & (GSCALE_IDENTITY - 1)); + x += frac; + w += frac; + } + if (y & (GSCALE_IDENTITY - 1)) + { + frac = GSCALE_IDENTITY - (y & (GSCALE_IDENTITY - 1)); + y += frac; + h += frac; + } + // pad the extent to accomodate fractional edge pixels + w += (GSCALE_IDENTITY - 1); + h += (GSCALE_IDENTITY - 1); + } + + size->width = w / GSCALE_IDENTITY; + size->height = h / GSCALE_IDENTITY; + hs->x = x / GSCALE_IDENTITY; + hs->y = y / GSCALE_IDENTITY; + + // Scaled image can be larger than the source by 1 pixel + // because of hotspot alignment and fractional edge pixels + assert (size->width <= src->w + 1 && size->height <= src->h + 1); + + if (!size->width && src->w) + size->width = 1; + if (!size->height && src->h) + size->height = 1; +} + +void +TFB_DrawCanvas_GetExtent (TFB_Canvas canvas, EXTENT *size) +{ + SDL_Surface *src = canvas; + + size->width = src->w; + size->height = src->h; +} + +void +TFB_DrawCanvas_Rescale_Nearest (TFB_Canvas src_canvas, TFB_Canvas dst_canvas, + int scale, HOT_SPOT* src_hs, EXTENT* size, HOT_SPOT* dst_hs) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *dst = dst_canvas; + int x, y; + int fsx = 0, fsy = 0; // source fractional dx and dy increments + int ssx = 0, ssy = 0; // source fractional x and y starting points + int w, h; + + if (scale > 0) + { + TFB_DrawCanvas_GetScaledExtent (src, src_hs, NULL, NULL, scale, + TFB_SCALE_NEAREST, size, dst_hs); + + w = size->width; + h = size->height; + } + else + { + // Just go with the dst surface dimensions + w = dst->w; + h = dst->h; + } + + if (w > dst->w || h > dst->h) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Nearest: Tried to scale" + " image to size %d %d when dest_canvas has only" + " dimensions of %d %d! Failing.", + w, h, dst->w, dst->h); + return; + } + + if (w > 1) + fsx = ((src->w - 1) << 16) / (w - 1); + if (h > 1) + fsy = ((src->h - 1) << 16) / (h - 1); + // We start with a value in 0..0.5 range to shift the bigger + // jumps towards the center of the image + ssx = 0x6000; + ssy = 0x6000; + + SDL_LockSurface (src); + SDL_LockSurface (dst); + + if (src->format->BytesPerPixel == 1 && dst->format->BytesPerPixel == 1) + { + Uint8 *sp, *csp, *dp, *cdp; + int sx, sy; // source fractional x and y positions + + sp = csp = (Uint8 *) src->pixels; + dp = cdp = (Uint8 *) dst->pixels; + + for (y = 0, sy = ssy; y < h; ++y) + { + csp += (sy >> 16) * src->pitch; + sp = csp; + dp = cdp; + for (x = 0, sx = ssx; x < w; ++x) + { + sp += (sx >> 16); + *dp = *sp; + sx &= 0xffff; + sx += fsx; + ++dp; + } + sy &= 0xffff; + sy += fsy; + cdp += dst->pitch; + } + } + else if (src->format->BytesPerPixel == 4 && dst->format->BytesPerPixel == 4) + { + Uint32 *sp, *csp, *dp, *cdp; + int sx, sy; // source fractional x and y positions + int sgap, dgap; + + sgap = src->pitch >> 2; + dgap = dst->pitch >> 2; + + sp = csp = (Uint32 *) src->pixels; + dp = cdp = (Uint32 *) dst->pixels; + + for (y = 0, sy = ssy; y < h; ++y) + { + csp += (sy >> 16) * sgap; + sp = csp; + dp = cdp; + for (x = 0, sx = ssx; x < w; ++x) + { + sp += (sx >> 16); + *dp = *sp; + sx &= 0xffff; + sx += fsx; + ++dp; + } + sy &= 0xffff; + sy += fsy; + cdp += dgap; + } + } + else + { + log_add (log_Warning, "Tried to deal with unknown BPP: %d -> %d", + src->format->BitsPerPixel, dst->format->BitsPerPixel); + } + SDL_UnlockSurface (dst); + SDL_UnlockSurface (src); +} + +typedef union +{ + Uint32 value; + Uint8 chan[4]; + struct + { + Uint8 r, g, b, a; + } c; +} pixel_t; + +static inline Uint8 +dot_product_8_4 (pixel_t* p, int c, Uint8* v) +{ // math expanded for speed +#if 0 + return ( + (Uint32)p[0].chan[c] * v[0] + (Uint32)p[1].chan[c] * v[1] + + (Uint32)p[2].chan[c] * v[2] + (Uint32)p[3].chan[c] * v[3] + ) >> 8; +#else + // mult-table driven version + return + btable[p[0].chan[c]][v[0]] + btable[p[1].chan[c]][v[1]] + + btable[p[2].chan[c]][v[2]] + btable[p[3].chan[c]][v[3]]; +#endif +} + +static inline Uint8 +weight_product_8_4 (pixel_t* p, int c, Uint8* w) +{ // math expanded for speed + return ( + (Uint32)p[0].chan[c] * w[0] + (Uint32)p[1].chan[c] * w[1] + + (Uint32)p[2].chan[c] * w[2] + (Uint32)p[3].chan[c] * w[3] + ) / (w[0] + w[1] + w[2] + w[3]); +} + +static inline Uint8 +blend_ratio_2 (Uint8 c1, Uint8 c2, int ratio) +{ // blend 2 color values according to ratio (0..256) + // identical to proper alpha blending + return (((c1 - c2) * ratio) >> 8) + c2; +} + +static inline Uint32 +scale_read_pixel (void* ppix, SDL_PixelFormat *fmt, SDL_Color *pal, + Uint32 mask, Uint32 key) +{ + pixel_t p; + + // start off with non-present pixel + p.value = 0; + + if (pal) + { // paletted pixel; mask not used + Uint32 c = *(Uint8 *)ppix; + + if (c != key) + { + p.c.r = pal[c].r; + p.c.g = pal[c].g; + p.c.b = pal[c].b; + p.c.a = SDL_ALPHA_OPAQUE; + } + } + else + { // RGB(A) pixel; (pix & mask) != key + Uint32 c = *(Uint32 *)ppix; + + if ((c & mask) != key) + { +#if 0 + SDL_GetRGBA (c, fmt, &p.c.r, &p.c.g, &p.c.b, &p.c.a); +#else + // Assume 8 bits/channel; a safe assumption with 32bpp surfaces + p.c.r = (c >> fmt->Rshift) & 0xff; + p.c.g = (c >> fmt->Gshift) & 0xff; + p.c.b = (c >> fmt->Bshift) & 0xff; + if (fmt->Amask) + p.c.a = (c >> fmt->Ashift) & 0xff; + else + p.c.a = SDL_ALPHA_OPAQUE; + } +#endif + } + + return p.value; +} + +static inline Uint32 +scale_get_pixel (SDL_Surface *src, Uint32 mask, Uint32 key, int x, int y) +{ + SDL_Color *pal = src->format->palette? src->format->palette->colors : 0; + + if (x < 0 || x >= src->w || y < 0 || y >= src->h) + return 0; + + return scale_read_pixel ((Uint8*)src->pixels + y * src->pitch + + x * src->format->BytesPerPixel, src->format, pal, mask, key); +} + +void +TFB_DrawCanvas_Rescale_Trilinear (TFB_Canvas src_canvas, TFB_Canvas src_mipmap, + TFB_Canvas dst_canvas, int scale, HOT_SPOT* src_hs, HOT_SPOT* mm_hs, + EXTENT* size, HOT_SPOT* dst_hs) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *dst = dst_canvas; + SDL_Surface *mm = src_mipmap; + SDL_PixelFormat *srcfmt = src->format; + SDL_PixelFormat *mmfmt = mm->format; + SDL_PixelFormat *dstfmt = dst->format; + SDL_Color *srcpal = srcfmt->palette? srcfmt->palette->colors : 0; + const int sbpp = srcfmt->BytesPerPixel; + const int mmbpp = mmfmt->BytesPerPixel; + const int slen = src->pitch; + const int mmlen = mm->pitch; + const int dst_has_alpha = (dstfmt->Amask != 0); + Uint32 transparent = 0; + const int alpha_threshold = dst_has_alpha ? 0 : 127; + // src v. mipmap importance factor + int ratio = scale * 2 - GSCALE_IDENTITY; + // source masks and keys + Uint32 mk0 = 0, ck0 = ~0, mk1 = 0, ck1 = ~0; + // source fractional x and y positions + int sx0, sy0, sx1, sy1; + // source fractional dx and dy increments + int fsx0 = 0, fsy0 = 0, fsx1 = 0, fsy1 = 0; + // source fractional x and y starting points + int ssx0 = 0, ssy0 = 0, ssx1 = 0, ssy1 = 0; + int x, y, w, h; + + TFB_GetColorKey (dst, &transparent); + + if (mmfmt->palette && !srcpal) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Trilinear: " + "Mipmap is paletted, but source is not! Failing."); + return; + } + + if (scale > 0) + { + int fw, fh; + + // Use (scale / GSCALE_IDENTITY) sizing factor + TFB_DrawCanvas_GetScaledExtent (src, src_hs, mm, mm_hs, scale, + TFB_SCALE_TRILINEAR, size, dst_hs); + + w = size->width; + h = size->height; + + fw = mm->w * GSCALE_IDENTITY + (src->w - mm->w) * ratio; + fh = mm->h * GSCALE_IDENTITY + (src->h - mm->h) * ratio; + + // This limits the effective source dimensions to 2048x2048, + // and we also lose 4 bits of precision out of 16 (no problem) + fsx0 = (src->w << 20) / fw; + fsx0 <<= 4; + fsy0 = (src->h << 20) / fh; + fsy0 <<= 4; + + fsx1 = (mm->w << 20) / fw; + fsx1 <<= 4; + fsy1 = (mm->h << 20) / fh; + fsy1 <<= 4; + + // position the hotspots directly over each other + ssx0 = (src_hs->x << 16) - fsx0 * dst_hs->x; + ssy0 = (src_hs->y << 16) - fsy0 * dst_hs->y; + + ssx1 = (mm_hs->x << 16) - fsx1 * dst_hs->x; + ssy1 = (mm_hs->y << 16) - fsy1 * dst_hs->y; + } + else + { + // Just go with the dst surface dimensions + w = dst->w; + h = dst->h; + + fsx0 = (src->w << 16) / w; + fsy0 = (src->h << 16) / h; + + fsx1 = (mm->w << 16) / w; + fsy1 = (mm->h << 16) / h; + + // give equal importance to both edges + ssx0 = (((src->w - 1) << 16) - fsx0 * (w - 1)) >> 1; + ssy0 = (((src->h - 1) << 16) - fsy0 * (h - 1)) >> 1; + + ssx1 = (((mm->w - 1) << 16) - fsx1 * (w - 1)) >> 1; + ssy1 = (((mm->h - 1) << 16) - fsy1 * (h - 1)) >> 1; + } + + if (w > dst->w || h > dst->h) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Trilinear: " + "Tried to scale image to size %d %d when dest_canvas" + " has only dimensions of %d %d! Failing.", + w, h, dst->w, dst->h); + return; + } + + if ((srcfmt->BytesPerPixel != 1 && srcfmt->BytesPerPixel != 4) || + (mmfmt->BytesPerPixel != 1 && mmfmt->BytesPerPixel != 4) || + (dst->format->BytesPerPixel != 4)) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Trilinear: " + "Tried to deal with unknown BPP: %d -> %d, mipmap %d", + srcfmt->BitsPerPixel, dst->format->BitsPerPixel, + mmfmt->BitsPerPixel); + return; + } + + // use colorkeys where appropriate + if (srcfmt->Amask) + { // alpha transparency + mk0 = srcfmt->Amask; + ck0 = 0; + } + else if (TFB_GetColorKey (src, &ck0) == 0) + { // colorkey transparency + mk0 = ~srcfmt->Amask; + ck0 &= mk0; + } + + if (mmfmt->Amask) + { // alpha transparency + mk1 = mmfmt->Amask; + ck1 = 0; + } + else if (TFB_GetColorKey (mm, &ck1) == 0) + { // colorkey transparency + mk1 = ~mmfmt->Amask; + ck1 &= mk1; + } + + SDL_LockSurface(src); + SDL_LockSurface(dst); + SDL_LockSurface(mm); + + for (y = 0, sy0 = ssy0, sy1 = ssy1; + y < h; + ++y, sy0 += fsy0, sy1 += fsy1) + { + Uint32 *dst_p = (Uint32 *) ((Uint8*)dst->pixels + y * dst->pitch); + const int py0 = (sy0 >> 16); + const int py1 = (sy1 >> 16); + Uint8 *src_a0 = (Uint8*)src->pixels + py0 * slen; + Uint8 *src_a1 = (Uint8*)mm->pixels + py1 * mmlen; + // retrieve the fractional portions of y + const Uint8 v0 = (sy0 >> 8) & 0xff; + const Uint8 v1 = (sy1 >> 8) & 0xff; + Uint8 w0[4], w1[4]; // pixel weight vectors + + for (x = 0, sx0 = ssx0, sx1 = ssx1; + x < w; + ++x, ++dst_p, sx0 += fsx0, sx1 += fsx1) + { + const int px0 = (sx0 >> 16); + const int px1 = (sx1 >> 16); + // retrieve the fractional portions of x + const Uint8 u0 = (sx0 >> 8) & 0xff; + const Uint8 u1 = (sx1 >> 8) & 0xff; + // pixels are examined and numbered in pattern + // 0 1 + // 2 3 + // the ideal pixel (4) is somewhere between these four + // and is calculated from these using weight vector (w) + // with a dot product + pixel_t p0[5], p1[5]; + Uint8 res_a; + + w0[0] = btable[255 - u0][255 - v0]; + w0[1] = btable[u0][255 - v0]; + w0[2] = btable[255 - u0][v0]; + w0[3] = btable[u0][v0]; + + w1[0] = btable[255 - u1][255 - v1]; + w1[1] = btable[u1][255 - v1]; + w1[2] = btable[255 - u1][v1]; + w1[3] = btable[u1][v1]; + + // Collect interesting pixels from src image + // Optimization: speed is criticial on larger images; + // most pixel reads fall completely inside the image + if (px0 >= 0 && px0 + 1 < src->w && py0 >= 0 && py0 + 1 < src->h) + { + Uint8 *src_p = src_a0 + px0 * sbpp; + + p0[0].value = scale_read_pixel (src_p, srcfmt, + srcpal, mk0, ck0); + p0[1].value = scale_read_pixel (src_p + sbpp, srcfmt, + srcpal, mk0, ck0); + p0[2].value = scale_read_pixel (src_p + slen, srcfmt, + srcpal, mk0, ck0); + p0[3].value = scale_read_pixel (src_p + sbpp + slen, srcfmt, + srcpal, mk0, ck0); + } + else + { + p0[0].value = scale_get_pixel (src, mk0, ck0, px0, py0); + p0[1].value = scale_get_pixel (src, mk0, ck0, px0 + 1, py0); + p0[2].value = scale_get_pixel (src, mk0, ck0, px0, py0 + 1); + p0[3].value = scale_get_pixel (src, mk0, ck0, + px0 + 1, py0 + 1); + } + + // Collect interesting pixels from mipmap image + if (px1 >= 0 && px1 + 1 < mm->w && py1 >= 0 && py1 + 1 < mm->h) + { + Uint8 *mm_p = src_a1 + px1 * mmbpp; + + p1[0].value = scale_read_pixel (mm_p, mmfmt, + srcpal, mk1, ck1); + p1[1].value = scale_read_pixel (mm_p + mmbpp, mmfmt, + srcpal, mk1, ck1); + p1[2].value = scale_read_pixel (mm_p + mmlen, mmfmt, + srcpal, mk1, ck1); + p1[3].value = scale_read_pixel (mm_p + mmbpp + mmlen, mmfmt, + srcpal, mk1, ck1); + } + else + { + p1[0].value = scale_get_pixel (mm, mk1, ck1, px1, py1); + p1[1].value = scale_get_pixel (mm, mk1, ck1, px1 + 1, py1); + p1[2].value = scale_get_pixel (mm, mk1, ck1, px1, py1 + 1); + p1[3].value = scale_get_pixel (mm, mk1, ck1, + px1 + 1, py1 + 1); + } + + p0[4].c.a = dot_product_8_4 (p0, 3, w0); + p1[4].c.a = dot_product_8_4 (p1, 3, w1); + + res_a = blend_ratio_2 (p0[4].c.a, p1[4].c.a, ratio); + + if (res_a <= alpha_threshold) + { + *dst_p = transparent; + } + else if (!dst_has_alpha) + { // RGB surface handling + p0[4].c.r = dot_product_8_4 (p0, 0, w0); + p0[4].c.g = dot_product_8_4 (p0, 1, w0); + p0[4].c.b = dot_product_8_4 (p0, 2, w0); + + p1[4].c.r = dot_product_8_4 (p1, 0, w1); + p1[4].c.g = dot_product_8_4 (p1, 1, w1); + p1[4].c.b = dot_product_8_4 (p1, 2, w1); + + p0[4].c.r = blend_ratio_2 (p0[4].c.r, p1[4].c.r, ratio); + p0[4].c.g = blend_ratio_2 (p0[4].c.g, p1[4].c.g, ratio); + p0[4].c.b = blend_ratio_2 (p0[4].c.b, p1[4].c.b, ratio); + + // TODO: we should handle alpha-blending here, but we do + // not know the destination color for blending! + + *dst_p = + (p0[4].c.r << dstfmt->Rshift) | + (p0[4].c.g << dstfmt->Gshift) | + (p0[4].c.b << dstfmt->Bshift); + } + else + { // RGBA surface handling + + // we do not want to blend with non-present pixels + // (pixels that have alpha == 0) as these will + // skew the result and make resulting alpha useless + if (p0[4].c.a != 0) + { + int i; + for (i = 0; i < 4; ++i) + if (p0[i].c.a == 0) + w0[i] = 0; + + p0[4].c.r = weight_product_8_4 (p0, 0, w0); + p0[4].c.g = weight_product_8_4 (p0, 1, w0); + p0[4].c.b = weight_product_8_4 (p0, 2, w0); + } + if (p1[4].c.a != 0) + { + int i; + for (i = 0; i < 4; ++i) + if (p1[i].c.a == 0) + w1[i] = 0; + + p1[4].c.r = weight_product_8_4 (p1, 0, w1); + p1[4].c.g = weight_product_8_4 (p1, 1, w1); + p1[4].c.b = weight_product_8_4 (p1, 2, w1); + } + + if (p0[4].c.a != 0 && p1[4].c.a != 0) + { // blend if both present + p0[4].c.r = blend_ratio_2 (p0[4].c.r, p1[4].c.r, ratio); + p0[4].c.g = blend_ratio_2 (p0[4].c.g, p1[4].c.g, ratio); + p0[4].c.b = blend_ratio_2 (p0[4].c.b, p1[4].c.b, ratio); + } + else if (p1[4].c.a != 0) + { // other pixel is present + p0[4].value = p1[4].value; + } + + // error-correct alpha to fully opaque to remove + // the often unwanted and unnecessary blending + if (res_a > 0xf8) + res_a = 0xff; + + *dst_p = + (p0[4].c.r << dstfmt->Rshift) | + (p0[4].c.g << dstfmt->Gshift) | + (p0[4].c.b << dstfmt->Bshift) | + (res_a << dstfmt->Ashift); + } + } + } + + SDL_UnlockSurface(mm); + SDL_UnlockSurface(dst); + SDL_UnlockSurface(src); +} + +void +TFB_DrawCanvas_Rescale_Bilinear (TFB_Canvas src_canvas, TFB_Canvas dst_canvas, + int scale, HOT_SPOT* src_hs, EXTENT* size, HOT_SPOT* dst_hs) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *dst = dst_canvas; + SDL_PixelFormat *srcfmt = src->format; + SDL_PixelFormat *dstfmt = dst->format; + SDL_Color *srcpal = srcfmt->palette? srcfmt->palette->colors : 0; + const int sbpp = srcfmt->BytesPerPixel; + const int slen = src->pitch; + const int dst_has_alpha = (dstfmt->Amask != 0); + Uint32 srckey = 0, transparent = 0; + const int alpha_threshold = dst_has_alpha ? 0 : 127; + // source masks and keys + Uint32 mk = 0, ck = ~0; + // source fractional x and y positions + int sx, sy; + // source fractional dx and dy increments + int fsx = 0, fsy = 0; + // source fractional x and y starting points + int ssx = 0, ssy = 0; + int x, y, w, h; + + // Get destination transparent color if it exists + TFB_GetColorKey (dst, &transparent); + + if (scale > 0) + { + // Use (scale / GSCALE_IDENTITY) sizing factor + TFB_DrawCanvas_GetScaledExtent (src, src_hs, NULL, NULL, scale, + TFB_SCALE_BILINEAR, size, dst_hs); + + w = size->width; + h = size->height; + fsx = (GSCALE_IDENTITY << 16) / scale; + fsy = (GSCALE_IDENTITY << 16) / scale; + + // position the hotspots directly over each other + ssx = (src_hs->x << 16) - fsx * dst_hs->x; + ssy = (src_hs->y << 16) - fsy * dst_hs->y; + } + else + { + // Just go with the dst surface dimensions + w = dst->w; + h = dst->h; + fsx = (src->w << 16) / w; + fsy = (src->h << 16) / h; + + // give equal importance to both edges + ssx = (((src->w - 1) << 16) - fsx * (w - 1)) >> 1; + ssy = (((src->h - 1) << 16) - fsy * (h - 1)) >> 1; + } + + if (w > dst->w || h > dst->h) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Bilinear: " + "Tried to scale image to size %d %d when dest_canvas" + " has only dimensions of %d %d! Failing.", + w, h, dst->w, dst->h); + return; + } + + if ((srcfmt->BytesPerPixel != 1 && srcfmt->BytesPerPixel != 4) || + (dst->format->BytesPerPixel != 4)) + { + log_add (log_Warning, "TFB_DrawCanvas_Rescale_Bilinear: " + "Tried to deal with unknown BPP: %d -> %d", + srcfmt->BitsPerPixel, dst->format->BitsPerPixel); + return; + } + + // use colorkeys where appropriate + if (srcfmt->Amask) + { // alpha transparency + mk = srcfmt->Amask; + ck = 0; + } + else if (TFB_GetColorKey (src, &srckey) == 0) + { // colorkey transparency + mk = ~srcfmt->Amask; + ck = srckey & mk; + } + + SDL_LockSurface(src); + SDL_LockSurface(dst); + + for (y = 0, sy = ssy; y < h; ++y, sy += fsy) + { + Uint32 *dst_p = (Uint32 *) ((Uint8*)dst->pixels + y * dst->pitch); + const int py = (sy >> 16); + Uint8 *src_a = (Uint8*)src->pixels + py * slen; + // retrieve the fractional portions of y + const Uint8 v = (sy >> 8) & 0xff; + Uint8 weight[4]; // pixel weight vectors + + for (x = 0, sx = ssx; x < w; ++x, ++dst_p, sx += fsx) + { + const int px = (sx >> 16); + // retrieve the fractional portions of x + const Uint8 u = (sx >> 8) & 0xff; + // pixels are examined and numbered in pattern + // 0 1 + // 2 3 + // the ideal pixel (4) is somewhere between these four + // and is calculated from these using weight vector (weight) + // with a dot product + pixel_t p[5]; + + weight[0] = btable[255 - u][255 - v]; + weight[1] = btable[u][255 - v]; + weight[2] = btable[255 - u][v]; + weight[3] = btable[u][v]; + + // Collect interesting pixels from src image + // Optimization: speed is criticial on larger images; + // most pixel reads fall completely inside the image + if (px >= 0 && px + 1 < src->w && py >= 0 && py + 1 < src->h) + { + Uint8 *src_p = src_a + px * sbpp; + + p[0].value = scale_read_pixel (src_p, srcfmt, srcpal, mk, ck); + p[1].value = scale_read_pixel (src_p + sbpp, srcfmt, + srcpal, mk, ck); + p[2].value = scale_read_pixel (src_p + slen, srcfmt, + srcpal, mk, ck); + p[3].value = scale_read_pixel (src_p + sbpp + slen, srcfmt, + srcpal, mk, ck); + } + else + { + p[0].value = scale_get_pixel (src, mk, ck, px, py); + p[1].value = scale_get_pixel (src, mk, ck, px + 1, py); + p[2].value = scale_get_pixel (src, mk, ck, px, py + 1); + p[3].value = scale_get_pixel (src, mk, ck, px + 1, py + 1); + } + + p[4].c.a = dot_product_8_4 (p, 3, weight); + + if (p[4].c.a <= alpha_threshold) + { + *dst_p = transparent; + } + else if (!dst_has_alpha) + { // RGB surface handling + p[4].c.r = dot_product_8_4 (p, 0, weight); + p[4].c.g = dot_product_8_4 (p, 1, weight); + p[4].c.b = dot_product_8_4 (p, 2, weight); + + // TODO: we should handle alpha-blending here, but we do + // not know the destination color for blending! + + *dst_p = + (p[4].c.r << dstfmt->Rshift) | + (p[4].c.g << dstfmt->Gshift) | + (p[4].c.b << dstfmt->Bshift); + } + else + { // RGBA surface handling + + // we do not want to blend with non-present pixels + // (pixels that have alpha == 0) as these will + // skew the result and make resulting alpha useless + int i; + for (i = 0; i < 4; ++i) + if (p[i].c.a == 0) + weight[i] = 0; + + p[4].c.r = weight_product_8_4 (p, 0, weight); + p[4].c.g = weight_product_8_4 (p, 1, weight); + p[4].c.b = weight_product_8_4 (p, 2, weight); + + // error-correct alpha to fully opaque to remove + // the often unwanted and unnecessary blending + if (p[4].c.a > 0xf8) + p[4].c.a = 0xff; + + *dst_p = + (p[4].c.r << dstfmt->Rshift) | + (p[4].c.g << dstfmt->Gshift) | + (p[4].c.b << dstfmt->Bshift) | + (p[4].c.a << dstfmt->Ashift); + } + } + } + + SDL_UnlockSurface(dst); + SDL_UnlockSurface(src); +} + +void +TFB_DrawCanvas_Lock (TFB_Canvas canvas) +{ + SDL_Surface *surf = canvas; + SDL_LockSurface (surf); +} + +void +TFB_DrawCanvas_Unlock (TFB_Canvas canvas) +{ + SDL_Surface *surf = canvas; + SDL_UnlockSurface (surf); +} + +void +TFB_DrawCanvas_GetScreenFormat (TFB_PixelFormat *fmt) +{ + SDL_PixelFormat *sdl = SDL_Screen->format; + + if (sdl->palette) + { + log_add (log_Warning, "TFB_DrawCanvas_GetScreenFormat() WARNING:" + "Paletted display format will be slow"); + + fmt->BitsPerPixel = 32; + fmt->Rmask = 0x000000ff; + fmt->Gmask = 0x0000ff00; + fmt->Bmask = 0x00ff0000; + fmt->Amask = 0xff000000; + } + else + { + fmt->BitsPerPixel = sdl->BitsPerPixel; + fmt->Rmask = sdl->Rmask; + fmt->Gmask = sdl->Gmask; + fmt->Bmask = sdl->Bmask; + fmt->Amask = sdl->Amask; + } +} + +int +TFB_DrawCanvas_GetStride (TFB_Canvas canvas) +{ + SDL_Surface *surf = canvas; + return surf->pitch; +} + +void* +TFB_DrawCanvas_GetLine (TFB_Canvas canvas, int line) +{ + SDL_Surface *surf = canvas; + return (uint8 *)surf->pixels + surf->pitch * line; +} + +Color +TFB_DrawCanvas_GetPixel (TFB_Canvas canvas, int x, int y) +{ + SDL_Surface* surf = canvas; + Uint32 pixel; + GetPixelFn getpixel; + Color c = {0, 0, 0, 0}; + + if (x < 0 || x >= surf->w || y < 0 || y >= surf->h) + { // outside bounds, return 0 + return c; + } + + SDL_LockSurface (surf); + + getpixel = getpixel_for(surf); + pixel = (*getpixel)(surf, x, y); + SDL_GetRGBA (pixel, surf->format, &c.r, &c.g, &c.b, &c.a); + + SDL_UnlockSurface (surf); + + return c; +} + +void +TFB_DrawCanvas_Rotate (TFB_Canvas src_canvas, TFB_Canvas dst_canvas, + int angle, EXTENT size) +{ + SDL_Surface *src = src_canvas; + SDL_Surface *dst = dst_canvas; + int ret; + Color color; + + if (size.width > dst->w || size.height > dst->h) + { + log_add (log_Warning, "TFB_DrawCanvas_Rotate: Tried to rotate" + " image to size %d %d when dst_canvas has only dimensions" + " of %d %d! Failing.", + size.width, size.height, dst->w, dst->h); + return; + } + + if (TFB_DrawCanvas_GetTransparentColor (src, &color)) + { + TFB_DrawCanvas_SetTransparentColor (dst, color, FALSE); + /* fill destination with transparent color before rotating */ + SDL_FillRect(dst, NULL, SDL_MapRGBA (dst->format, + color.r, color.g, color.b, 0)); + } + + ret = rotateSurface (src, dst, angle, 0); + if (ret != 0) + { + log_add (log_Warning, "TFB_DrawCanvas_Rotate: WARNING:" + " actual rotation func returned failure\n"); + } +} + +void +TFB_DrawCanvas_GetRotatedExtent (TFB_Canvas src_canvas, int angle, EXTENT *size) +{ + int dstw, dsth; + SDL_Surface *src = src_canvas; + + rotozoomSurfaceSize (src->w, src->h, angle, 1, &dstw, &dsth); + size->height = dsth; + size->width = dstw; +} + +void +TFB_DrawCanvas_CopyRect (TFB_Canvas source, const RECT *srcRect, + TFB_Canvas target, POINT dstPt) +{ + SDL_Rect sourceRect, targetRect; + + if (source == 0 || target == 0) + { + log_add (log_Warning, + "ERROR: TFB_DrawCanvas_CopyRect passed null canvas ptr"); + return; + } + + sourceRect.x = srcRect->corner.x; + sourceRect.y = srcRect->corner.y; + sourceRect.w = srcRect->extent.width; + sourceRect.h = srcRect->extent.height; + + targetRect.x = dstPt.x; + targetRect.y = dstPt.y; + // According to SDL docs, width and height are ignored, but + // we'll set them anyway, just in case. + targetRect.w = srcRect->extent.width; + targetRect.h = srcRect->extent.height; + + SDL_BlitSurface (source, &sourceRect, target, &targetRect); +} + +void +TFB_DrawCanvas_SetClipRect (TFB_Canvas canvas, const RECT *clipRect) +{ + if (canvas == 0) + { + log_add (log_Warning, + "ERROR: TFB_DrawCanvas_SetClipRect passed null canvas ptr"); + return; + } + + if (!clipRect) + { // clipping disabled + SDL_SetClipRect (canvas, NULL); + } + else + { + SDL_Rect r; + r.x = clipRect->corner.x; + r.y = clipRect->corner.y; + r.w = clipRect->extent.width; + r.h = clipRect->extent.height; + SDL_SetClipRect (canvas, &r); + } +} + +BOOLEAN +TFB_DrawCanvas_Intersect (TFB_Canvas canvas1, POINT c1org, + TFB_Canvas canvas2, POINT c2org, const RECT *interRect) +{ + BOOLEAN ret = FALSE; + SDL_Surface *surf1 = canvas1; + SDL_Surface *surf2 = canvas2; + int x, y; + Uint32 s1key, s2key; + Uint32 s1mask, s2mask; + GetPixelFn getpixel1, getpixel2; + + SDL_LockSurface (surf1); + SDL_LockSurface (surf2); + + getpixel1 = getpixel_for (surf1); + getpixel2 = getpixel_for (surf2); + + if (surf1->format->Amask) + { // use alpha transparency info + s1mask = surf1->format->Amask; + // consider any not fully transparent pixel collidable + s1key = 0; + } + else + { // colorkey transparency + Uint32 colorkey = 0; + TFB_GetColorKey(surf1, &colorkey); + s1mask = ~surf1->format->Amask; + s1key = colorkey & s1mask; + } + + if (surf2->format->Amask) + { // use alpha transparency info + s2mask = surf2->format->Amask; + // consider any not fully transparent pixel collidable + s2key = 0; + } + else + { // colorkey transparency + Uint32 colorkey = 0; + TFB_GetColorKey(surf2, &colorkey); + s2mask = ~surf2->format->Amask; + s2key = colorkey & s2mask; + } + + // convert surface origins to pixel offsets within + c1org.x = interRect->corner.x - c1org.x; + c1org.y = interRect->corner.y - c1org.y; + c2org.x = interRect->corner.x - c2org.x; + c2org.y = interRect->corner.y - c2org.y; + + for (y = 0; y < interRect->extent.height; ++y) + { + for (x = 0; x < interRect->extent.width; ++x) + { + Uint32 p1 = getpixel1 (surf1, x + c1org.x, y + c1org.y) & s1mask; + Uint32 p2 = getpixel2 (surf2, x + c2org.x, y + c2org.y) & s2mask; + + if (p1 != s1key && p2 != s2key) + { // pixel collision + ret = TRUE; + break; + } + } + } + + SDL_UnlockSurface (surf2); + SDL_UnlockSurface (surf1); + + return ret; +} + +// Read/write the canvas pixels in a Color format understood by the core. +// The pixels array is assumed to be at least width * height large. +// The pixels array can be wider/narrower or taller/shorter than the canvas, +// and in that case, only the relevant pixels will be transfered. +static BOOLEAN +TFB_DrawCanvas_TransferColors (TFB_Canvas canvas, BOOLEAN write, + Color *pixels, int width, int height) +{ + SDL_Surface *surf = canvas; + SDL_PixelFormat *fmt; + GetPixelFn getpix; + PutPixelFn putpix; + int x, y, w, h; + + if (canvas == 0) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_TransferColors " + "passed null canvas"); + return FALSE; + } + + fmt = surf->format; + getpix = getpixel_for (surf); + putpix = putpixel_for (surf); + + w = width < surf->w ? width : surf->w; + h = height < surf->h ? height : surf->h; + + SDL_LockSurface (surf); + + // This could be done faster if we assumed 32bpp surfaces + for (y = 0; y < h; ++y) + { + // pixels array pitch is width so as not to violate the interface + Color *c = pixels + y * width; + + for (x = 0; x < w; ++x, ++c) + { + if (write) + { // writing from data to surface + Uint32 p = SDL_MapRGBA (fmt, c->r, c->g, c->b, c->a); + putpix (surf, x, y, p); + } + else + { // reading from surface to data + Uint32 p = getpix (surf, x, y); + SDL_GetRGBA (p, fmt, &c->r, &c->g, &c->b, &c->a); + } + } + } + + SDL_UnlockSurface (surf); + + return TRUE; +} + +// Read the canvas pixels in a Color format understood by the core. +// See TFB_DrawCanvas_TransferColors() for pixels array info +BOOLEAN +TFB_DrawCanvas_GetPixelColors (TFB_Canvas canvas, Color *pixels, + int width, int height) +{ + return TFB_DrawCanvas_TransferColors (canvas, FALSE, pixels, + width, height); +} + +// Write the canvas pixels from a Color format understood by the core. +// See TFB_DrawCanvas_TransferColors() for pixels array info +BOOLEAN +TFB_DrawCanvas_SetPixelColors (TFB_Canvas canvas, const Color *pixels, + int width, int height) +{ + // unconst pixels, but it is safe -- it will not be written to + return TFB_DrawCanvas_TransferColors (canvas, TRUE, (Color *)pixels, + width, height); +} + +// Read/write the indexed canvas pixels as palette indexes. +// The data array is assumed to be at least width * height large. +// The data array can be wider/narrower or taller/shorter than the canvas, +// and in that case, only the relevant pixels will be transfered. +static BOOLEAN +TFB_DrawCanvas_TransferIndexes (TFB_Canvas canvas, BOOLEAN write, + BYTE *data, int width, int height) +{ + SDL_Surface *surf = canvas; + const SDL_PixelFormat *fmt; + int y, w, h; + + if (canvas == 0) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_TransferIndexes " + "passed null canvas"); + return FALSE; + } + fmt = surf->format; + if (!TFB_DrawCanvas_IsPaletted (canvas) || fmt->BitsPerPixel != 8) + { + log_add (log_Warning, "ERROR: TFB_DrawCanvas_TransferIndexes " + "unimplemeted function: not an 8bpp indexed canvas"); + return FALSE; + } + + w = width < surf->w ? width : surf->w; + h = height < surf->h ? height : surf->h; + + SDL_LockSurface (surf); + + for (y = 0; y < h; ++y) + { + Uint8 *surf_p = (Uint8 *)surf->pixels + y * surf->pitch; + // pixels array pitch is width so as not to violate the interface + BYTE *data_p = data + y * width; + + if (write) + { // writing from data to surface + memcpy (surf_p, data_p, w * sizeof (BYTE)); + } + else + { // reading from surface to data + memcpy (data_p, surf_p, w * sizeof (BYTE)); + } + } + + SDL_UnlockSurface (surf); + + return TRUE; +} + +// Read the indexed canvas pixels as palette indexes. +// See TFB_DrawCanvas_TransferIndexes() for data array info. +BOOLEAN +TFB_DrawCanvas_GetPixelIndexes (TFB_Canvas canvas, BYTE *data, + int width, int height) +{ + return TFB_DrawCanvas_TransferIndexes (canvas, FALSE, data, + width, height); +} + +// Write the indexed canvas pixels as palette indexes. +// See TFB_DrawCanvas_TransferIndexes() for data array info. +BOOLEAN +TFB_DrawCanvas_SetPixelIndexes (TFB_Canvas canvas, const BYTE *data, + int width, int height) +{ + // unconst data, but it is safe -- it will not be written to + return TFB_DrawCanvas_TransferIndexes (canvas, TRUE, (BYTE *)data, + width, height); +} diff --git a/src/libs/graphics/sdl/hq2x.c b/src/libs/graphics/sdl/hq2x.c new file mode 100644 index 0000000..02dc806 --- /dev/null +++ b/src/libs/graphics/sdl/hq2x.c @@ -0,0 +1,2888 @@ +//hq2x filter +//-------------------------------------------------------------------------- +//Copyright (C) 2003 MaxSt ( maxst@hiend3d.com ) - Original version +// +//Portions Copyright (C) 2005 Alex Volkov ( codepro@usa.net ) +// Modified Oct-2-2005 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Core algorithm of the HQ screen scaler +// adapted from hq2x -- www.hiend3d.com/hq2x.html +// Template +// When this file is built standalone is produces a plain C version +// Also #included by 2xscalers_mmx.c for an MMX version + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" + + +// Pixel blending/manipulation instructions +#define PIXEL00_0 dst_p[0] = pix[5]; +#define PIXEL00_10 dst_p[0] = Scale_Blend_31(pix[5], pix[1]); +#define PIXEL00_11 dst_p[0] = Scale_Blend_31(pix[5], pix[4]); +#define PIXEL00_12 dst_p[0] = Scale_Blend_31(pix[5], pix[2]); +#define PIXEL00_20 dst_p[0] = Scale_Blend_211(pix[5], pix[4], pix[2]); +#define PIXEL00_21 dst_p[0] = Scale_Blend_211(pix[5], pix[1], pix[2]); +#define PIXEL00_22 dst_p[0] = Scale_Blend_211(pix[5], pix[1], pix[4]); +#define PIXEL00_60 dst_p[0] = Scale_Blend_521(pix[5], pix[2], pix[4]); +#define PIXEL00_61 dst_p[0] = Scale_Blend_521(pix[5], pix[4], pix[2]); +#define PIXEL00_70 dst_p[0] = Scale_Blend_611(pix[5], pix[4], pix[2]); +#define PIXEL00_90 dst_p[0] = Scale_Blend_233(pix[5], pix[4], pix[2]); +#define PIXEL00_100 dst_p[0] = Scale_Blend_e11(pix[5], pix[4], pix[2]); +#define PIXEL01_0 dst_p[1] = pix[5]; +#define PIXEL01_10 dst_p[1] = Scale_Blend_31(pix[5], pix[3]); +#define PIXEL01_11 dst_p[1] = Scale_Blend_31(pix[5], pix[2]); +#define PIXEL01_12 dst_p[1] = Scale_Blend_31(pix[5], pix[6]); +#define PIXEL01_20 dst_p[1] = Scale_Blend_211(pix[5], pix[2], pix[6]); +#define PIXEL01_21 dst_p[1] = Scale_Blend_211(pix[5], pix[3], pix[6]); +#define PIXEL01_22 dst_p[1] = Scale_Blend_211(pix[5], pix[3], pix[2]); +#define PIXEL01_60 dst_p[1] = Scale_Blend_521(pix[5], pix[6], pix[2]); +#define PIXEL01_61 dst_p[1] = Scale_Blend_521(pix[5], pix[2], pix[6]); +#define PIXEL01_70 dst_p[1] = Scale_Blend_611(pix[5], pix[2], pix[6]); +#define PIXEL01_90 dst_p[1] = Scale_Blend_233(pix[5], pix[2], pix[6]); +#define PIXEL01_100 dst_p[1] = Scale_Blend_e11(pix[5], pix[2], pix[6]); +#define PIXEL10_0 dst_p[dlen] = pix[5]; +#define PIXEL10_10 dst_p[dlen] = Scale_Blend_31(pix[5], pix[7]); +#define PIXEL10_11 dst_p[dlen] = Scale_Blend_31(pix[5], pix[8]); +#define PIXEL10_12 dst_p[dlen] = Scale_Blend_31(pix[5], pix[4]); +#define PIXEL10_20 dst_p[dlen] = Scale_Blend_211(pix[5], pix[8], pix[4]); +#define PIXEL10_21 dst_p[dlen] = Scale_Blend_211(pix[5], pix[7], pix[4]); +#define PIXEL10_22 dst_p[dlen] = Scale_Blend_211(pix[5], pix[7], pix[8]); +#define PIXEL10_60 dst_p[dlen] = Scale_Blend_521(pix[5], pix[4], pix[8]); +#define PIXEL10_61 dst_p[dlen] = Scale_Blend_521(pix[5], pix[8], pix[4]); +#define PIXEL10_70 dst_p[dlen] = Scale_Blend_611(pix[5], pix[8], pix[4]); +#define PIXEL10_90 dst_p[dlen] = Scale_Blend_233(pix[5], pix[8], pix[4]); +#define PIXEL10_100 dst_p[dlen] = Scale_Blend_e11(pix[5], pix[8], pix[4]); +#define PIXEL11_0 dst_p[dlen + 1] = pix[5]; +#define PIXEL11_10 dst_p[dlen + 1] = Scale_Blend_31(pix[5], pix[9]); +#define PIXEL11_11 dst_p[dlen + 1] = Scale_Blend_31(pix[5], pix[6]); +#define PIXEL11_12 dst_p[dlen + 1] = Scale_Blend_31(pix[5], pix[8]); +#define PIXEL11_20 dst_p[dlen + 1] = Scale_Blend_211(pix[5], pix[6], pix[8]); +#define PIXEL11_21 dst_p[dlen + 1] = Scale_Blend_211(pix[5], pix[9], pix[8]); +#define PIXEL11_22 dst_p[dlen + 1] = Scale_Blend_211(pix[5], pix[9], pix[6]); +#define PIXEL11_60 dst_p[dlen + 1] = Scale_Blend_521(pix[5], pix[8], pix[6]); +#define PIXEL11_61 dst_p[dlen + 1] = Scale_Blend_521(pix[5], pix[6], pix[8]); +#define PIXEL11_70 dst_p[dlen + 1] = Scale_Blend_611(pix[5], pix[6], pix[8]); +#define PIXEL11_90 dst_p[dlen + 1] = Scale_Blend_233(pix[5], pix[6], pix[8]); +#define PIXEL11_100 dst_p[dlen + 1] = Scale_Blend_e11(pix[5], pix[6], pix[8]); + + +// HQ scaling to 2x +// The name expands to +// Scale_HqFilter (for plain C) +// Scale_MMX_HqFilter (for MMX) +// [others when platforms are added] +void +SCALE_(HqFilter) (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r) +{ + int x, y; + const int w = src->w, h = src->h; + int xend, yend; + int dsrc, ddst; + SDL_Rect *region = r; + SDL_Rect limits; + SDL_PixelFormat *fmt = dst->format; + const int sp = src->pitch, dp = dst->pitch; + const int bpp = fmt->BytesPerPixel; + const int slen = sp / bpp, dlen = dp / bpp; + Uint32 *src_p = (Uint32 *)src->pixels; + Uint32 *dst_p = (Uint32 *)dst->pixels; + + int prevline, nextline; + Uint32 pix[10]; + Uint32 yuv[10]; +// +----+----+----+ +// | | | | +// | p1 | p2 | p3 | +// +----+----+----+ +// | | | | +// | p4 | p5 | p6 | +// +----+----+----+ +// | | | | +// | p7 | p8 | p9 | +// +----+----+----+ + + // MMX code runs faster w/o branching + #define HQXX_DIFFYUV(p1, p2) \ + SCALE_DIFFYUV (p1, p2) + + SCALE_(PlatInit) (); + + // expand updated region if necessary + // pixels neighbooring the updated region may + // change as a result of updates + limits.x = 0; + limits.y = 0; + limits.w = src->w; + limits.h = src->h; + Scale_ExpandRect (region, 1, &limits); + + xend = region->x + region->w; + yend = region->y + region->h; + dsrc = slen - region->w; + ddst = (dlen - region->w) * 2; + + // move ptrs to the first updated pixel + src_p += slen * region->y + region->x; + dst_p += (dlen * region->y + region->x) * 2; + + for (y = region->y; y < yend; ++y, dst_p += ddst, src_p += dsrc) + { + if (y > 0) + prevline = -slen; + else + prevline = 0; + + if (y < h - 1) + nextline = slen; + else + nextline = 0; + + // prime the (tiny) sliding-window pixel arrays + pix[3] = src_p[prevline]; + pix[6] = src_p[0]; + pix[9] = src_p[nextline]; + + yuv[3] = SCALE_TOYUV (pix[3]); + yuv[6] = SCALE_TOYUV (pix[6]); + yuv[9] = SCALE_TOYUV (pix[9]); + + if (region->x > 0) + { + pix[2] = src_p[prevline - 1]; + pix[5] = src_p[-1]; + pix[8] = src_p[nextline - 1]; + + yuv[2] = SCALE_TOYUV (pix[2]); + yuv[5] = SCALE_TOYUV (pix[5]); + yuv[8] = SCALE_TOYUV (pix[8]); + } + else + { + pix[2] = pix[3]; + pix[5] = pix[6]; + pix[8] = pix[9]; + + yuv[2] = yuv[3]; + yuv[5] = yuv[6]; + yuv[8] = yuv[9]; + } + + for (x = region->x; x < xend; ++x, ++src_p, dst_p += 2) + { + int pattern = 0; + + // slide the window + pix[1] = pix[2]; + pix[4] = pix[5]; + pix[7] = pix[8]; + + yuv[1] = yuv[2]; + yuv[4] = yuv[5]; + yuv[7] = yuv[8]; + + pix[2] = pix[3]; + pix[5] = pix[6]; + pix[8] = pix[9]; + + yuv[2] = yuv[3]; + yuv[5] = yuv[6]; + yuv[8] = yuv[9]; + + if (x < w - 1) + { + pix[3] = src_p[prevline + 1]; + pix[6] = src_p[1]; + pix[9] = src_p[nextline + 1]; + + yuv[3] = SCALE_TOYUV (pix[3]); + yuv[6] = SCALE_TOYUV (pix[6]); + yuv[9] = SCALE_TOYUV (pix[9]); + } + else + { + pix[3] = pix[2]; + pix[6] = pix[5]; + pix[9] = pix[8]; + + yuv[3] = yuv[2]; + yuv[6] = yuv[5]; + yuv[9] = yuv[8]; + } + + // this runs much faster with branching removed + pattern |= HQXX_DIFFYUV (yuv[5], yuv[1]) & 0x0001; + pattern |= HQXX_DIFFYUV (yuv[5], yuv[2]) & 0x0002; + pattern |= HQXX_DIFFYUV (yuv[5], yuv[3]) & 0x0004; + pattern |= HQXX_DIFFYUV (yuv[5], yuv[4]) & 0x0008; + pattern |= HQXX_DIFFYUV (yuv[5], yuv[6]) & 0x0010; + pattern |= HQXX_DIFFYUV (yuv[5], yuv[7]) & 0x0020; + pattern |= HQXX_DIFFYUV (yuv[5], yuv[8]) & 0x0040; + pattern |= HQXX_DIFFYUV (yuv[5], yuv[9]) & 0x0080; + + switch (pattern) + { + case 0: + case 1: + case 4: + case 32: + case 128: + case 5: + case 132: + case 160: + case 33: + case 129: + case 36: + case 133: + case 164: + case 161: + case 37: + case 165: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_20 + PIXEL11_20 + break; + } + case 2: + case 34: + case 130: + case 162: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_20 + PIXEL11_20 + break; + } + case 16: + case 17: + case 48: + case 49: + { + PIXEL00_20 + PIXEL01_22 + PIXEL10_20 + PIXEL11_21 + break; + } + case 64: + case 65: + case 68: + case 69: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_21 + PIXEL11_22 + break; + } + case 8: + case 12: + case 136: + case 140: + { + PIXEL00_21 + PIXEL01_20 + PIXEL10_22 + PIXEL11_20 + break; + } + case 3: + case 35: + case 131: + case 163: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_20 + PIXEL11_20 + break; + } + case 6: + case 38: + case 134: + case 166: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_20 + PIXEL11_20 + break; + } + case 20: + case 21: + case 52: + case 53: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_20 + PIXEL11_21 + break; + } + case 144: + case 145: + case 176: + case 177: + { + PIXEL00_20 + PIXEL01_22 + PIXEL10_20 + PIXEL11_12 + break; + } + case 192: + case 193: + case 196: + case 197: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_21 + PIXEL11_11 + break; + } + case 96: + case 97: + case 100: + case 101: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_12 + PIXEL11_22 + break; + } + case 40: + case 44: + case 168: + case 172: + { + PIXEL00_21 + PIXEL01_20 + PIXEL10_11 + PIXEL11_20 + break; + } + case 9: + case 13: + case 137: + case 141: + { + PIXEL00_12 + PIXEL01_20 + PIXEL10_22 + PIXEL11_20 + break; + } + case 18: + case 50: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_20 + } + PIXEL10_20 + PIXEL11_21 + break; + } + case 80: + case 81: + { + PIXEL00_20 + PIXEL01_22 + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_20 + } + break; + } + case 72: + case 76: + { + PIXEL00_21 + PIXEL01_20 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 10: + case 138: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + PIXEL10_22 + PIXEL11_20 + break; + } + case 66: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_21 + PIXEL11_22 + break; + } + case 24: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_22 + PIXEL11_21 + break; + } + case 7: + case 39: + case 135: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_20 + PIXEL11_20 + break; + } + case 148: + case 149: + case 180: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_20 + PIXEL11_12 + break; + } + case 224: + case 228: + case 225: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_12 + PIXEL11_11 + break; + } + case 41: + case 169: + case 45: + { + PIXEL00_12 + PIXEL01_20 + PIXEL10_11 + PIXEL11_20 + break; + } + case 22: + case 54: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_20 + PIXEL11_21 + break; + } + case 208: + case 209: + { + PIXEL00_20 + PIXEL01_22 + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 104: + case 108: + { + PIXEL00_21 + PIXEL01_20 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 11: + case 139: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + PIXEL10_22 + PIXEL11_20 + break; + } + case 19: + case 51: + { + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL00_11 + PIXEL01_10 + } + else + { + PIXEL00_60 + PIXEL01_90 + } + PIXEL10_20 + PIXEL11_21 + break; + } + case 146: + case 178: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + PIXEL11_12 + } + else + { + PIXEL01_90 + PIXEL11_61 + } + PIXEL10_20 + break; + } + case 84: + case 85: + { + PIXEL00_20 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL01_11 + PIXEL11_10 + } + else + { + PIXEL01_60 + PIXEL11_90 + } + PIXEL10_21 + break; + } + case 112: + case 113: + { + PIXEL00_20 + PIXEL01_22 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL10_12 + PIXEL11_10 + } + else + { + PIXEL10_61 + PIXEL11_90 + } + break; + } + case 200: + case 204: + { + PIXEL00_21 + PIXEL01_20 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + PIXEL11_11 + } + else + { + PIXEL10_90 + PIXEL11_60 + } + break; + } + case 73: + case 77: + { + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL00_12 + PIXEL10_10 + } + else + { + PIXEL00_61 + PIXEL10_90 + } + PIXEL01_20 + PIXEL11_22 + break; + } + case 42: + case 170: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + PIXEL10_11 + } + else + { + PIXEL00_90 + PIXEL10_60 + } + PIXEL01_21 + PIXEL11_20 + break; + } + case 14: + case 142: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + PIXEL01_12 + } + else + { + PIXEL00_90 + PIXEL01_61 + } + PIXEL10_22 + PIXEL11_20 + break; + } + case 67: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_21 + PIXEL11_22 + break; + } + case 70: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_21 + PIXEL11_22 + break; + } + case 28: + { + PIXEL00_21 + PIXEL01_11 + PIXEL10_22 + PIXEL11_21 + break; + } + case 152: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_22 + PIXEL11_12 + break; + } + case 194: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_21 + PIXEL11_11 + break; + } + case 98: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_12 + PIXEL11_22 + break; + } + case 56: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_11 + PIXEL11_21 + break; + } + case 25: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_22 + PIXEL11_21 + break; + } + case 26: + case 31: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_22 + PIXEL11_21 + break; + } + case 82: + case 214: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 88: + case 248: + { + PIXEL00_21 + PIXEL01_22 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 74: + case 107: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 27: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + PIXEL10_22 + PIXEL11_21 + break; + } + case 86: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_21 + PIXEL11_10 + break; + } + case 216: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_10 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 106: + { + PIXEL00_10 + PIXEL01_21 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 30: + { + PIXEL00_10 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_22 + PIXEL11_21 + break; + } + case 210: + { + PIXEL00_22 + PIXEL01_10 + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 120: + { + PIXEL00_21 + PIXEL01_22 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 75: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + PIXEL10_10 + PIXEL11_22 + break; + } + case 29: + { + PIXEL00_12 + PIXEL01_11 + PIXEL10_22 + PIXEL11_21 + break; + } + case 198: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_21 + PIXEL11_11 + break; + } + case 184: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_11 + PIXEL11_12 + break; + } + case 99: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_12 + PIXEL11_22 + break; + } + case 57: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_11 + PIXEL11_21 + break; + } + case 71: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_21 + PIXEL11_22 + break; + } + case 156: + { + PIXEL00_21 + PIXEL01_11 + PIXEL10_22 + PIXEL11_12 + break; + } + case 226: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_12 + PIXEL11_11 + break; + } + case 60: + { + PIXEL00_21 + PIXEL01_11 + PIXEL10_11 + PIXEL11_21 + break; + } + case 195: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_21 + PIXEL11_11 + break; + } + case 102: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_12 + PIXEL11_22 + break; + } + case 153: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_22 + PIXEL11_12 + break; + } + case 58: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_11 + PIXEL11_21 + break; + } + case 83: + { + PIXEL00_11 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 92: + { + PIXEL00_21 + PIXEL01_11 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 202: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_21 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_11 + break; + } + case 78: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_12 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_22 + break; + } + case 154: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_22 + PIXEL11_12 + break; + } + case 114: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 89: + { + PIXEL00_12 + PIXEL01_22 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 90: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 55: + case 23: + { + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL00_11 + PIXEL01_0 + } + else + { + PIXEL00_60 + PIXEL01_90 + } + PIXEL10_20 + PIXEL11_21 + break; + } + case 182: + case 150: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + PIXEL11_12 + } + else + { + PIXEL01_90 + PIXEL11_61 + } + PIXEL10_20 + break; + } + case 213: + case 212: + { + PIXEL00_20 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL01_11 + PIXEL11_0 + } + else + { + PIXEL01_60 + PIXEL11_90 + } + PIXEL10_21 + break; + } + case 241: + case 240: + { + PIXEL00_20 + PIXEL01_22 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL10_12 + PIXEL11_0 + } + else + { + PIXEL10_61 + PIXEL11_90 + } + break; + } + case 236: + case 232: + { + PIXEL00_21 + PIXEL01_20 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + PIXEL11_11 + } + else + { + PIXEL10_90 + PIXEL11_60 + } + break; + } + case 109: + case 105: + { + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL00_12 + PIXEL10_0 + } + else + { + PIXEL00_61 + PIXEL10_90 + } + PIXEL01_20 + PIXEL11_22 + break; + } + case 171: + case 43: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + PIXEL10_11 + } + else + { + PIXEL00_90 + PIXEL10_60 + } + PIXEL01_21 + PIXEL11_20 + break; + } + case 143: + case 15: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + PIXEL01_12 + } + else + { + PIXEL00_90 + PIXEL01_61 + } + PIXEL10_22 + PIXEL11_20 + break; + } + case 124: + { + PIXEL00_21 + PIXEL01_11 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 203: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + PIXEL10_10 + PIXEL11_11 + break; + } + case 62: + { + PIXEL00_10 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_11 + PIXEL11_21 + break; + } + case 211: + { + PIXEL00_11 + PIXEL01_10 + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 118: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_12 + PIXEL11_10 + break; + } + case 217: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_10 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 110: + { + PIXEL00_10 + PIXEL01_12 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 155: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + PIXEL10_22 + PIXEL11_12 + break; + } + case 188: + { + PIXEL00_21 + PIXEL01_11 + PIXEL10_11 + PIXEL11_12 + break; + } + case 185: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_11 + PIXEL11_12 + break; + } + case 61: + { + PIXEL00_12 + PIXEL01_11 + PIXEL10_11 + PIXEL11_21 + break; + } + case 157: + { + PIXEL00_12 + PIXEL01_11 + PIXEL10_22 + PIXEL11_12 + break; + } + case 103: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_12 + PIXEL11_22 + break; + } + case 227: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_12 + PIXEL11_11 + break; + } + case 230: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_12 + PIXEL11_11 + break; + } + case 199: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_21 + PIXEL11_11 + break; + } + case 220: + { + PIXEL00_21 + PIXEL01_11 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 158: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_22 + PIXEL11_12 + break; + } + case 234: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_21 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_11 + break; + } + case 242: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 59: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_11 + PIXEL11_21 + break; + } + case 121: + { + PIXEL00_12 + PIXEL01_22 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 87: + { + PIXEL00_11 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 79: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_12 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_22 + break; + } + case 122: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 94: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 218: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 91: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 229: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_12 + PIXEL11_11 + break; + } + case 167: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_20 + PIXEL11_20 + break; + } + case 173: + { + PIXEL00_12 + PIXEL01_20 + PIXEL10_11 + PIXEL11_20 + break; + } + case 181: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_20 + PIXEL11_12 + break; + } + case 186: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_11 + PIXEL11_12 + break; + } + case 115: + { + PIXEL00_11 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 93: + { + PIXEL00_12 + PIXEL01_11 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 206: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_12 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_11 + break; + } + case 205: + case 201: + { + PIXEL00_12 + PIXEL01_20 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_11 + break; + } + case 174: + case 46: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_12 + PIXEL10_11 + PIXEL11_20 + break; + } + case 179: + case 147: + { + PIXEL00_11 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_20 + PIXEL11_12 + break; + } + case 117: + case 116: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 189: + { + PIXEL00_12 + PIXEL01_11 + PIXEL10_11 + PIXEL11_12 + break; + } + case 231: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_12 + PIXEL11_11 + break; + } + case 126: + { + PIXEL00_10 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 219: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + PIXEL10_10 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 125: + { + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL00_12 + PIXEL10_0 + } + else + { + PIXEL00_61 + PIXEL10_90 + } + PIXEL01_11 + PIXEL11_10 + break; + } + case 221: + { + PIXEL00_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL01_11 + PIXEL11_0 + } + else + { + PIXEL01_60 + PIXEL11_90 + } + PIXEL10_10 + break; + } + case 207: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + PIXEL01_12 + } + else + { + PIXEL00_90 + PIXEL01_61 + } + PIXEL10_10 + PIXEL11_11 + break; + } + case 238: + { + PIXEL00_10 + PIXEL01_12 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + PIXEL11_11 + } + else + { + PIXEL10_90 + PIXEL11_60 + } + break; + } + case 190: + { + PIXEL00_10 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + PIXEL11_12 + } + else + { + PIXEL01_90 + PIXEL11_61 + } + PIXEL10_11 + break; + } + case 187: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + PIXEL10_11 + } + else + { + PIXEL00_90 + PIXEL10_60 + } + PIXEL01_10 + PIXEL11_12 + break; + } + case 243: + { + PIXEL00_11 + PIXEL01_10 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL10_12 + PIXEL11_0 + } + else + { + PIXEL10_61 + PIXEL11_90 + } + break; + } + case 119: + { + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL00_11 + PIXEL01_0 + } + else + { + PIXEL00_60 + PIXEL01_90 + } + PIXEL10_12 + PIXEL11_10 + break; + } + case 237: + case 233: + { + PIXEL00_12 + PIXEL01_20 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + PIXEL11_11 + break; + } + case 175: + case 47: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + PIXEL01_12 + PIXEL10_11 + PIXEL11_20 + break; + } + case 183: + case 151: + { + PIXEL00_11 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_20 + PIXEL11_12 + break; + } + case 245: + case 244: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 250: + { + PIXEL00_10 + PIXEL01_10 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 123: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 95: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_10 + PIXEL11_10 + break; + } + case 222: + { + PIXEL00_10 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_10 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 252: + { + PIXEL00_21 + PIXEL01_11 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 249: + { + PIXEL00_12 + PIXEL01_22 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 235: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + PIXEL11_11 + break; + } + case 111: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + PIXEL01_12 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 63: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_11 + PIXEL11_21 + break; + } + case 159: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_22 + PIXEL11_12 + break; + } + case 215: + { + PIXEL00_11 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_21 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 246: + { + PIXEL00_22 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 254: + { + PIXEL00_10 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 253: + { + PIXEL00_12 + PIXEL01_11 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 251: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 239: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + PIXEL01_12 + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + PIXEL11_11 + break; + } + case 127: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 191: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_11 + PIXEL11_12 + break; + } + case 223: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_10 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 247: + { + PIXEL00_11 + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_12 + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 255: + { + if (HQXX_DIFFYUV (yuv[4], yuv[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + if (HQXX_DIFFYUV (yuv[2], yuv[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + if (HQXX_DIFFYUV (yuv[8], yuv[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + if (HQXX_DIFFYUV (yuv[6], yuv[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + } + } + } + + SCALE_(PlatDone) (); +} + diff --git a/src/libs/graphics/sdl/nearest2x.c b/src/libs/graphics/sdl/nearest2x.c new file mode 100644 index 0000000..42e6813 --- /dev/null +++ b/src/libs/graphics/sdl/nearest2x.c @@ -0,0 +1,207 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Core algorithm of the BiLinear screen scaler +// Template +// When this file is built standalone is produces a plain C version +// Also #included by 2xscalers_mmx.c for an MMX version + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" + +// Nearest Neighbor scaling to 2x +// The name expands to +// Scale_Nearest (for plain C) +// Scale_MMX_Nearest (for MMX) +// Scale_SSE_Nearest (for SSE) +// [others when platforms are added] +void +SCALE_(Nearest) (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r) +{ + int y; + const int rw = r->w, rh = r->h; + const int sp = src->pitch, dp = dst->pitch; + const int bpp = dst->format->BytesPerPixel; + const int slen = sp / bpp, dlen = dp / bpp; + const int dsrc = slen-rw, ddst = (dlen-rw) * 2; + + Uint32 *src_p = (Uint32 *)src->pixels; + Uint32 *dst_p = (Uint32 *)dst->pixels; + + // guard asm code against such atrocities + if (rw == 0 || rh == 0) + return; + + SCALE_(PlatInit) (); + + // move ptrs to the first updated pixel + src_p += slen * r->y + r->x; + dst_p += (dlen * r->y + r->x) * 2; + +#if defined(MMX_ASM) && defined(MSVC_ASM) + // Just about everything has to be done in asm for MSVC + // to actually take advantage of asm here + // MSVC does not support beautiful GCC-like asm templates + + y = rh; + __asm + { + // setup vars + mov esi, src_p + mov edi, dst_p + + PREFETCH (esi + 0x40) + PREFETCH (esi + 0x80) + PREFETCH (esi + 0xc0) + + mov edx, dlen + lea edx, [edx * 4] + mov eax, dsrc + lea eax, [eax * 4] + mov ebx, ddst + lea ebx, [ebx * 4] + + mov ecx, rw + loop_y: + test ecx, 1 + jz even_x + + // one-pixel transfer + movd mm1, [esi] + punpckldq mm1, mm1 // pix1 | pix1 -> mm1 + add esi, 4 + MOVNTQ (edi, mm1) + add edi, 8 + MOVNTQ (edi - 8 + edx, mm1) + + even_x: + shr ecx, 1 // x = rw / 2 + jz end_x // rw was 1 + + loop_x: + // two-pixel transfer + movq mm1, [esi] + movq mm2, mm1 + PREFETCH (esi + 0x100) + punpckldq mm1, mm1 // pix1 | pix1 -> mm1 + add esi, 8 + MOVNTQ (edi, mm1) + punpckhdq mm2, mm2 // pix2 | pix2 -> mm2 + MOVNTQ (edi + edx, mm1) + add edi, 16 + MOVNTQ (edi - 8, mm2) + MOVNTQ (edi - 8 + edx, mm2) + + dec ecx + jnz loop_x + + end_x: + // try to prefetch as early as possible to have it on time + PREFETCH (esi + eax) + + mov ecx, rw + add esi, eax + + PREFETCH (esi + 0x40) + PREFETCH (esi + 0x80) + PREFETCH (esi + 0xc0) + + add edi, ebx + + dec y + jnz loop_y + } + +#elif defined(MMX_ASM) && defined(GCC_ASM) + + SCALE_(Prefetch) (src_p + 16); + SCALE_(Prefetch) (src_p + 32); + SCALE_(Prefetch) (src_p + 48); + + for (y = rh; y; --y) + { + int x = rw; + + if (x & 1) + { // one-pixel transfer + __asm__ ( + "movd (%0), %%mm1 \n\t" + "punpckldq %%mm1, %%mm1 \n\t" + MOVNTQ (%%mm1, (%1)) "\n\t" + MOVNTQ (%%mm1, (%1,%2)) "\n\t" + + : /* nothing */ + : /*0*/"r" (src_p), /*1*/"r" (dst_p), /*2*/"r" (dlen*sizeof(Uint32)) + ); + + ++src_p; + dst_p += 2; + --x; + } + + for (x >>= 1; x; --x, src_p += 2, dst_p += 4) + { // two-pixel transfer + __asm__ ( + "movq (%0), %%mm1 \n\t" + "movq %%mm1, %%mm2 \n\t" + PREFETCH (0x100(%0)) "\n\t" + "punpckldq %%mm1, %%mm1 \n\t" + MOVNTQ (%%mm1, (%1)) "\n\t" + MOVNTQ (%%mm1, (%1,%2)) "\n\t" + "punpckhdq %%mm2, %%mm2 \n\t" + MOVNTQ (%%mm2, 8(%1)) "\n\t" + MOVNTQ (%%mm2, 8(%1,%2)) "\n\t" + + : /* nothing */ + : /*0*/"r" (src_p), /*1*/"r" (dst_p), /*2*/"r" (dlen*sizeof(Uint32)) + ); + } + + src_p += dsrc; + // try to prefetch as early as possible to have it on time + SCALE_(Prefetch) (src_p); + + dst_p += ddst; + + SCALE_(Prefetch) (src_p + 16); + SCALE_(Prefetch) (src_p + 32); + SCALE_(Prefetch) (src_p + 48); + } + +#else + // Plain C version + for (y = 0; y < rh; ++y) + { + int x; + for (x = 0; x < rw; ++x, ++src_p, dst_p += 2) + { + Uint32 pix = *src_p; + dst_p[0] = pix; + dst_p[1] = pix; + dst_p[dlen] = pix; + dst_p[dlen + 1] = pix; + } + dst_p += ddst; + src_p += dsrc; + } +#endif + + SCALE_(PlatDone) (); +} + diff --git a/src/libs/graphics/sdl/opengl.c b/src/libs/graphics/sdl/opengl.c new file mode 100644 index 0000000..d8fe33f --- /dev/null +++ b/src/libs/graphics/sdl/opengl.c @@ -0,0 +1,575 @@ +//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. + */ + +#ifdef HAVE_OPENGL + +#include "libs/graphics/sdl/opengl.h" +#include "libs/graphics/bbox.h" +#include "scalers.h" +#include "options.h" +#include "libs/log.h" + +#if SDL_MAJOR_VERSION == 1 + +typedef struct _gl_screeninfo { + SDL_Surface *scaled; + GLuint texture; + BOOLEAN dirty, active; + SDL_Rect updated; +} TFB_GL_SCREENINFO; + +static TFB_GL_SCREENINFO GL_Screens[TFB_GFX_NUMSCREENS]; + +static int ScreenFilterMode; + +static TFB_ScaleFunc scaler = NULL; +static BOOLEAN first_init = TRUE; + +#if SDL_BYTEORDER == SDL_BIG_ENDIAN +#define R_MASK 0xff000000 +#define G_MASK 0x00ff0000 +#define B_MASK 0x0000ff00 +#define A_MASK 0x000000ff +#else +#define R_MASK 0x000000ff +#define G_MASK 0x0000ff00 +#define B_MASK 0x00ff0000 +#define A_MASK 0xff000000 +#endif + +static void TFB_GL_Preprocess (int force_full_redraw, int transition_amount, int fade_amount); +static void TFB_GL_Postprocess (void); +static void TFB_GL_UploadTransitionScreen (void); +static void TFB_GL_Scaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect); +static void TFB_GL_Unscaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect); +static void TFB_GL_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect); + +static TFB_GRAPHICS_BACKEND opengl_scaled_backend = { + TFB_GL_Preprocess, + TFB_GL_Postprocess, + TFB_GL_UploadTransitionScreen, + TFB_GL_Scaled_ScreenLayer, + TFB_GL_ColorLayer }; + +static TFB_GRAPHICS_BACKEND opengl_unscaled_backend = { + TFB_GL_Preprocess, + TFB_GL_Postprocess, + TFB_GL_UploadTransitionScreen, + TFB_GL_Unscaled_ScreenLayer, + TFB_GL_ColorLayer }; + + +static int +AttemptColorDepth (int flags, int width, int height, int bpp) +{ + SDL_Surface *SDL_Video; + int videomode_flags; + ScreenColorDepth = bpp; + ScreenWidthActual = width; + ScreenHeightActual = height; + + switch (bpp) { + case 15: + SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 5); + SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 5); + break; + + case 16: + SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 6); + SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 5); + break; + + case 24: + SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 8); + break; + + case 32: + SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 8); + break; + default: + break; + } + + SDL_GL_SetAttribute (SDL_GL_DEPTH_SIZE, 0); + SDL_GL_SetAttribute (SDL_GL_DOUBLEBUFFER, 1); + + videomode_flags = SDL_OPENGL; + if (flags & TFB_GFXFLAGS_FULLSCREEN) + videomode_flags |= SDL_FULLSCREEN; + videomode_flags |= SDL_ANYFORMAT; + + SDL_Video = SDL_SetVideoMode (ScreenWidthActual, ScreenHeightActual, + bpp, videomode_flags); + if (SDL_Video == NULL) + { + log_add (log_Error, "Couldn't set OpenGL %ix%ix%i video mode: %s", + ScreenWidthActual, ScreenHeightActual, bpp, + SDL_GetError ()); + return -1; + } + else + { + log_add (log_Info, "Set the resolution to: %ix%ix%i" + " (surface reports %ix%ix%i)", + width, height, bpp, + SDL_GetVideoSurface()->w, SDL_GetVideoSurface()->h, + SDL_GetVideoSurface()->format->BitsPerPixel); + + log_add (log_Info, "OpenGL renderer: %s version: %s", + glGetString (GL_RENDERER), glGetString (GL_VERSION)); + } + return 0; +} + +int +TFB_GL_ConfigureVideo (int driver, int flags, int width, int height, int togglefullscreen) +{ + int i, texture_width, texture_height; + GraphicsDriver = driver; + + if (AttemptColorDepth (flags, width, height, 32) && + AttemptColorDepth (flags, width, height, 24) && + AttemptColorDepth (flags, width, height, 16)) + { + log_add (log_Error, "Couldn't set any OpenGL %ix%i video mode!", + width, height); + return -1; + } + + if (!togglefullscreen) + { + if (format_conv_surf) + SDL_FreeSurface (format_conv_surf); + format_conv_surf = SDL_CreateRGBSurface (SDL_SWSURFACE, 0, 0, 32, + R_MASK, G_MASK, B_MASK, A_MASK); + if (format_conv_surf == NULL) + { + log_add (log_Error, "Couldn't create format_conv_surf: %s", + SDL_GetError()); + return -1; + } + + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + if (0 != SDL1_ReInit_Screen (&SDL_Screens[i], format_conv_surf, + ScreenWidth, ScreenHeight)) + return -1; + } + + SDL_Screen = SDL_Screens[0]; + TransitionScreen = SDL_Screens[2]; + + if (first_init) + { + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + GL_Screens[i].scaled = NULL; + GL_Screens[i].dirty = TRUE; + GL_Screens[i].active = TRUE; + } + GL_Screens[1].active = FALSE; + first_init = FALSE; + } + } + + if (GfxFlags & TFB_GFXFLAGS_SCALE_SOFT_ONLY) + { + if (!togglefullscreen) + { + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + if (!GL_Screens[i].active) + continue; + if (0 != SDL1_ReInit_Screen (&GL_Screens[i].scaled, format_conv_surf, + ScreenWidth * 2, ScreenHeight * 2)) + return -1; + } + scaler = Scale_PrepPlatform (flags, SDL_Screen->format); + } + + texture_width = 1024; + texture_height = 512; + + graphics_backend = &opengl_scaled_backend; + } + else + { + texture_width = 512; + texture_height = 256; + + scaler = NULL; + graphics_backend = &opengl_unscaled_backend; + } + + + if (GfxFlags & TFB_GFXFLAGS_SCALE_ANY) + ScreenFilterMode = GL_LINEAR; + else + ScreenFilterMode = GL_NEAREST; + + glViewport (0, 0, ScreenWidthActual, ScreenHeightActual); + glClearColor (0,0,0,0); + glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); + SDL_GL_SwapBuffers (); + glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); + glDisable (GL_DITHER); + glDepthMask(GL_FALSE); + + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + if (!GL_Screens[i].active) + continue; + glGenTextures (1, &GL_Screens[i].texture); + glBindTexture (GL_TEXTURE_2D, GL_Screens[i].texture); + glPixelStorei (GL_UNPACK_ALIGNMENT, 1); + glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, texture_width, texture_height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + } + + return 0; +} + +int +TFB_GL_InitGraphics (int driver, int flags, int width, int height) +{ + char VideoName[256]; + + log_add (log_Info, "Initializing SDL with OpenGL support."); + + SDL_VideoDriverName (VideoName, sizeof (VideoName)); + log_add (log_Info, "SDL driver used: %s", VideoName); + log_add (log_Info, "SDL initialized."); + log_add (log_Info, "Initializing Screen."); + + ScreenWidth = 320; + ScreenHeight = 240; + + if (TFB_GL_ConfigureVideo (driver, flags, width, height, 0)) + { + log_add (log_Fatal, "Could not initialize video: " + "no fallback at start of program!"); + exit (EXIT_FAILURE); + } + + // Initialize scalers (let them precompute whatever) + Scale_Init (); + + return 0; +} + +void +TFB_GL_UninitGraphics (void) +{ + int i; + + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) { + UnInit_Screen (&GL_Screens[i].scaled); + } +} + +static void +TFB_GL_UploadTransitionScreen (void) +{ + GL_Screens[TFB_SCREEN_TRANSITION].updated.x = 0; + GL_Screens[TFB_SCREEN_TRANSITION].updated.y = 0; + GL_Screens[TFB_SCREEN_TRANSITION].updated.w = ScreenWidth; + GL_Screens[TFB_SCREEN_TRANSITION].updated.h = ScreenHeight; + GL_Screens[TFB_SCREEN_TRANSITION].dirty = TRUE; +} + +static void +TFB_GL_ScanLines (void) +{ + int y; + + glDisable (GL_TEXTURE_2D); + glEnable (GL_BLEND); + glBlendFunc (GL_DST_COLOR, GL_ZERO); + glColor3f (0.85f, 0.85f, 0.85f); + for (y = 0; y < ScreenHeightActual; y += 2) + { + glBegin (GL_LINES); + glVertex2i (0, y); + glVertex2i (ScreenWidthActual, y); + glEnd (); + } + + glBlendFunc (GL_DST_COLOR, GL_ONE); + glColor3f (0.2f, 0.2f, 0.2f); + for (y = 1; y < ScreenHeightActual; y += 2) + { + glBegin (GL_LINES); + glVertex2i (0, y); + glVertex2i (ScreenWidthActual, y); + glEnd (); + } +} + +static void +TFB_GL_DrawQuad (SDL_Rect *r) +{ + BOOLEAN keep_aspect_ratio = optKeepAspectRatio; + int x1 = 0, y1 = 0, x2 = ScreenWidthActual, y2 = ScreenHeightActual; + int sx = 0, sy = 0; + int sw, sh; + float sx_multiplier = 1; + float sy_multiplier = 1; + + if (keep_aspect_ratio) + { + float threshold = 0.75f; + float ratio = ScreenHeightActual / (float)ScreenWidthActual; + + if (ratio > threshold) + { + // screen is narrower than 4:3 + int height = (int)(ScreenWidthActual * threshold); + y1 = (ScreenHeightActual - height) / 2; + y2 = ScreenHeightActual - y1; + + if (r != NULL) + { + sx_multiplier = ScreenWidthActual / (float)ScreenWidth; + sy_multiplier = height / (float)ScreenHeight; + sx = (int)(r->x * sx_multiplier); + sy = (int)(((ScreenHeight - (r->y + r->h)) * sy_multiplier) + y1); + } + } + else if (ratio < threshold) + { + // screen is wider than 4:3 + int width = (int)(ScreenHeightActual / threshold); + x1 = (ScreenWidthActual - width) / 2; + x2 = ScreenWidthActual - x1; + + if (r != NULL) + { + sx_multiplier = width / (float)ScreenWidth; + sy_multiplier = ScreenHeightActual / (float)ScreenHeight; + sx = (int)((r->x * sx_multiplier) + x1); + sy = (int)((ScreenHeight - (r->y + r->h)) * sy_multiplier); + } + } + else + { + // screen is 4:3 + keep_aspect_ratio = 0; + } + } + + if (r != NULL) + { + if (!keep_aspect_ratio) + { + sx_multiplier = ScreenWidthActual / (float)ScreenWidth; + sy_multiplier = ScreenHeightActual / (float)ScreenHeight; + sx = (int)(r->x * sx_multiplier); + sy = (int)((ScreenHeight - (r->y + r->h)) * sy_multiplier); + } + sw = (int)(r->w * sx_multiplier); + sh = (int)(r->h * sy_multiplier); + glScissor (sx, sy, sw, sh); + glEnable (GL_SCISSOR_TEST); + } + + glBegin (GL_TRIANGLE_FAN); + glTexCoord2f (0, 0); + glVertex2i (x1, y1); + glTexCoord2f (ScreenWidth / 512.0f, 0); + glVertex2i (x2, y1); + glTexCoord2f (ScreenWidth / 512.0f, ScreenHeight / 256.0f); + glVertex2i (x2, y2); + glTexCoord2f (0, ScreenHeight / 256.0f); + glVertex2i (x1, y2); + glEnd (); + if (r != NULL) + { + glDisable (GL_SCISSOR_TEST); + } +} + +static void +TFB_GL_Preprocess (int force_full_redraw, int transition_amount, int fade_amount) +{ + glMatrixMode (GL_PROJECTION); + glLoadIdentity (); + glOrtho (0,ScreenWidthActual,ScreenHeightActual, 0, -1, 1); + glMatrixMode (GL_MODELVIEW); + glLoadIdentity (); + if (optKeepAspectRatio) + glClear (GL_COLOR_BUFFER_BIT); + + (void) transition_amount; + (void) fade_amount; + + if (force_full_redraw == TFB_REDRAW_YES) + { + GL_Screens[TFB_SCREEN_MAIN].updated.x = 0; + GL_Screens[TFB_SCREEN_MAIN].updated.y = 0; + GL_Screens[TFB_SCREEN_MAIN].updated.w = ScreenWidth; + GL_Screens[TFB_SCREEN_MAIN].updated.h = ScreenHeight; + GL_Screens[TFB_SCREEN_MAIN].dirty = TRUE; + } + else if (TFB_BBox.valid) + { + GL_Screens[TFB_SCREEN_MAIN].updated.x = TFB_BBox.region.corner.x; + GL_Screens[TFB_SCREEN_MAIN].updated.y = TFB_BBox.region.corner.y; + GL_Screens[TFB_SCREEN_MAIN].updated.w = TFB_BBox.region.extent.width; + GL_Screens[TFB_SCREEN_MAIN].updated.h = TFB_BBox.region.extent.height; + GL_Screens[TFB_SCREEN_MAIN].dirty = TRUE; + } +} + +static void +TFB_GL_Unscaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect) +{ + glBindTexture (GL_TEXTURE_2D, GL_Screens[screen].texture); + glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + if (GL_Screens[screen].dirty) + { + int PitchWords = SDL_Screens[screen]->pitch / 4; + glPixelStorei (GL_UNPACK_ROW_LENGTH, PitchWords); + /* Matrox OpenGL drivers do not handle GL_UNPACK_SKIP_* + correctly */ + glPixelStorei (GL_UNPACK_SKIP_ROWS, 0); + glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0); + SDL_LockSurface (SDL_Screens[screen]); + glTexSubImage2D (GL_TEXTURE_2D, 0, GL_Screens[screen].updated.x, + GL_Screens[screen].updated.y, + GL_Screens[screen].updated.w, + GL_Screens[screen].updated.h, + GL_RGBA, GL_UNSIGNED_BYTE, + (Uint32 *)SDL_Screens[screen]->pixels + + (GL_Screens[screen].updated.y * PitchWords + + GL_Screens[screen].updated.x)); + SDL_UnlockSurface (SDL_Screens[screen]); + GL_Screens[screen].dirty = FALSE; + } + + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ScreenFilterMode); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ScreenFilterMode); + glEnable (GL_TEXTURE_2D); + + if (a == 255) + { + glDisable (GL_BLEND); + glColor4f (1, 1, 1, 1); + } + else + { + float a_f = a / 255.0f; + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable (GL_BLEND); + glColor4f (1, 1, 1, a_f); + } + + TFB_GL_DrawQuad (rect); +} + +static void +TFB_GL_Scaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect) +{ + glBindTexture (GL_TEXTURE_2D, GL_Screens[screen].texture); + glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + if (GL_Screens[screen].dirty) + { + int PitchWords = GL_Screens[screen].scaled->pitch / 4; + scaler (SDL_Screens[screen], GL_Screens[screen].scaled, &GL_Screens[screen].updated); + glPixelStorei (GL_UNPACK_ROW_LENGTH, PitchWords); + + /* Matrox OpenGL drivers do not handle GL_UNPACK_SKIP_* + correctly */ + glPixelStorei (GL_UNPACK_SKIP_ROWS, 0); + glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0); + SDL_LockSurface (GL_Screens[screen].scaled); + glTexSubImage2D (GL_TEXTURE_2D, 0, GL_Screens[screen].updated.x * 2, + GL_Screens[screen].updated.y * 2, + GL_Screens[screen].updated.w * 2, + GL_Screens[screen].updated.h * 2, + GL_RGBA, GL_UNSIGNED_BYTE, + (Uint32 *)GL_Screens[screen].scaled->pixels + + (GL_Screens[screen].updated.y * 2 * PitchWords + + GL_Screens[screen].updated.x * 2)); + SDL_UnlockSurface (GL_Screens[screen].scaled); + GL_Screens[screen].dirty = FALSE; + } + + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ScreenFilterMode); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ScreenFilterMode); + glEnable (GL_TEXTURE_2D); + + if (a == 255) + { + glDisable (GL_BLEND); + glColor4f (1, 1, 1, 1); + } + else + { + float a_f = a / 255.0f; + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable (GL_BLEND); + glColor4f (1, 1, 1, a_f); + } + + TFB_GL_DrawQuad (rect); +} + +static void +TFB_GL_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect) +{ + float r_f = r / 255.0f; + float g_f = g / 255.0f; + float b_f = b / 255.0f; + float a_f = a / 255.0f; + glColor4f(r_f, g_f, b_f, a_f); + + glDisable (GL_TEXTURE_2D); + if (a != 255) + { + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable (GL_BLEND); + } + else + { + glDisable (GL_BLEND); + } + + TFB_GL_DrawQuad (rect); +} + +static void +TFB_GL_Postprocess (void) +{ + if (GfxFlags & TFB_GFXFLAGS_SCANLINES) + TFB_GL_ScanLines (); + + SDL_GL_SwapBuffers (); +} + +#endif +#endif diff --git a/src/libs/graphics/sdl/opengl.h b/src/libs/graphics/sdl/opengl.h new file mode 100644 index 0000000..6550bca --- /dev/null +++ b/src/libs/graphics/sdl/opengl.h @@ -0,0 +1,89 @@ +//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 OPENGL_H +#define OPENGL_H + +#include "libs/graphics/sdl/sdl_common.h" +#if SDL_MAJOR_VERSION == 1 + +int TFB_GL_InitGraphics (int driver, int flags, int width, int height); +void TFB_GL_UninitGraphics (void); +int TFB_GL_ConfigureVideo (int driver, int flags, int width, int height, int togglefullscreen); + +#ifdef HAVE_OPENGL +#ifdef WIN32 + +#ifdef _MSC_VER +#pragma comment (lib, "opengl32.lib") +#pragma comment (lib, "glu32.lib") +#endif + +/* To avoid including windows.h, + Win32's needs APIENTRY and WINGDIAPI defined properly. */ + +#ifndef APIENTRY +#define GLUT_APIENTRY_DEFINED +#if __MINGW32__ || (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED) +#define APIENTRY __stdcall +#else +#define APIENTRY +#endif +#endif + +#ifndef WINAPI +#define GLUT_WINAPI_DEFINED +#if __MINGW32__ || (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED) +#define WINAPI __stdcall +#else +#define WINAPI +#endif +#endif + +/* This is from Win32's */ +#ifndef CALLBACK +#if (defined(_M_MRX000) || defined(_M_IX86) || defined(_M_ALPHA) || defined(_M_PPC)) && !defined(MIDL_PASS) +#define CALLBACK __stdcall +#else +#define CALLBACK +#endif +#endif + +/* This is from Win32's and */ +#ifndef WINGDIAPI +#define GLUT_WINGDIAPI_DEFINED +#define WINGDIAPI __declspec(dllimport) +#endif + +/* This is from Win32's */ +#ifndef LIBS_GRAPHICS_SDL_OPENGL_H_ +typedef unsigned short wchar_t; +#define LIBS_GRAPHICS_SDL_OPENGL_H_ +#endif + +#include "GL/glu.h" + +#else /* !defined(WIN32) */ + +#include "port.h" +#include SDL_INCLUDE(SDL_opengl.h) + +#endif /* WIN32 */ +#endif /* SDL_MAJOR_VERSION == 1 */ +#endif /* HAVE_OPENGL */ +#endif diff --git a/src/libs/graphics/sdl/palette.c b/src/libs/graphics/sdl/palette.c new file mode 100644 index 0000000..18f4418 --- /dev/null +++ b/src/libs/graphics/sdl/palette.c @@ -0,0 +1,47 @@ +/* + * Copyright 2009 Alex Volkov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "palette.h" +#include "libs/memlib.h" +#include "libs/log.h" + +NativePalette * +AllocNativePalette (void) +{ + return HCalloc (sizeof (NativePalette)); +} + +void +FreeNativePalette (NativePalette *palette) +{ + HFree (palette); +} + +void +SetNativePaletteColor (NativePalette *palette, int index, Color color) +{ + assert (index < NUMBER_OF_PLUTVALS); + palette->colors[index] = ColorToNative (color); +} + +Color +GetNativePaletteColor (NativePalette *palette, int index) +{ + assert (index < NUMBER_OF_PLUTVALS); + return NativeToColor (palette->colors[index]); +} diff --git a/src/libs/graphics/sdl/palette.h b/src/libs/graphics/sdl/palette.h new file mode 100644 index 0000000..2cefe7c --- /dev/null +++ b/src/libs/graphics/sdl/palette.h @@ -0,0 +1,57 @@ +/* + * Copyright 2009 Alex Volkov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 PALETTE_H_INCL__ +#define PALETTE_H_INCL__ + +#include "port.h" +#include SDL_INCLUDE(SDL.h) +#include "libs/graphics/cmap.h" + +struct NativePalette +{ + SDL_Color colors[NUMBER_OF_PLUTVALS]; +}; + +static inline Color +NativeToColor (SDL_Color native) +{ + Color color; + color.r = native.r; + color.g = native.g; + color.b = native.b; + color.a = 0xff; // fully opaque + return color; +} + +static inline SDL_Color +ColorToNative (Color color) +{ + SDL_Color native; + native.r = color.r; + native.g = color.g; + native.b = color.b; +#if SDL_MAJOR_VERSION == 1 + native.unused = 0; +#else + native.a = color.a; +#endif + return native; +} + +#endif /* PALETTE_H_INCL__ */ diff --git a/src/libs/graphics/sdl/png2sdl.c b/src/libs/graphics/sdl/png2sdl.c new file mode 100644 index 0000000..1717886 --- /dev/null +++ b/src/libs/graphics/sdl/png2sdl.c @@ -0,0 +1,300 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 code is adapted from IMG_png.c in the SDL2_image library, which is + * available under the zlib license and is (c) 1997-2019 Sam Lantinga. The + * code itself is credited to Philippe Lavoie as the original author. It + * also shares some heritage with libpng's own example.c file, and + * ultimately inherits the GPL2+ license from the rest of UQM. + * + * Differences from SDL2_image are: + * - It is PNG-only + * - It directly links the relevant version of libpng at compile time + * - It always uses libpng and will never forward to alternative + * libraries such as ImageIO.framework. This means that palette + * information will always be preserved, as UQM requires. + * - It locks the surface as the API demands rather than using + * volatility markers + * - Palette assignment is done through the API rather than by + * directly editing the format contents + */ + +#include "png2sdl.h" +#include + +/* Link function between SDL_RWops and PNG's data source */ +static void +png_read_data(png_structp ctx, png_bytep area, png_size_t size) +{ + SDL_RWops *src = (SDL_RWops *)png_get_io_ptr (ctx); + SDL_RWread (src, area, size, 1); +} + +SDL_Surface * +TFB_png_to_sdl (SDL_RWops *src) +{ + Sint64 start; + const char *error; + SDL_Surface *surface; + png_structp png_ptr; + png_infop info_ptr; + png_uint_32 width, height; + int bit_depth, color_type, interlace_type, num_channels; + Uint32 Rmask, Gmask, Bmask, Amask; + png_bytep *row_pointers; + int row, i; + int ckey = -1; + png_color_16 *transv; + + if (!src) + { + /* The error message has been set in SDL_RWFromFile */ + return NULL; + } + start = SDL_RWtell (src); + + /* Initialize the data we will clean up when we're done */ + error = NULL; + png_ptr = NULL; + info_ptr = NULL; + row_pointers = NULL; + surface = NULL; + + /* Create the PNG loading context structure */ + png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (png_ptr == NULL) + { + error = "Couldn't allocate memory for PNG file"; + goto done; + } + + /* Allocate/initialize the memory for image information */ + info_ptr = png_create_info_struct (png_ptr); + if (info_ptr == NULL) + { + error = "Couldn't create image information for PNG file"; + goto done; + } + + /* Set error handling */ + if (setjmp (png_jmpbuf (png_ptr))) + { + error = "Error reading the PNG file."; + goto done; + } + + /* Set up the input control */ + png_set_read_fn (png_ptr, src, png_read_data); + + /* Read PNG header info */ + png_read_info (png_ptr, info_ptr); + png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth, + &color_type, &interlace_type, NULL, NULL); + + + /* Configure the decode based on what we know of the image + * already: strip 16 bit color down to 8 bit, automatically + * deinterlace, expand grayscale images or those with more + * than one transparent color or any translucent colors into + * full RGB or RGBA, and expand 1, 2, or 4-bpp paletted + * images to 8bpp. */ + png_set_strip_16 (png_ptr); + png_set_interlace_handling (png_ptr); + png_set_packing (png_ptr); + if (color_type == PNG_COLOR_TYPE_GRAY) + { + png_set_expand (png_ptr); + } + if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS)) + { + int num_trans; + Uint8 *trans; + png_get_tRNS (png_ptr, info_ptr, &trans, &num_trans, &transv); + if (color_type == PNG_COLOR_TYPE_PALETTE) + { + /* Check if all tRNS entries are opaque except one */ + int j, t = -1; + for (j = 0; j < num_trans; j++) + { + if (trans[j] == 0) + { + if (t >= 0) + { + break; + } + t = j; + } + else if (trans[j] != 255) + { + break; + } + } + if (j == num_trans) + { + /* exactly one transparent index */ + ckey = t; + } + else + { + /* more than one transparent index, or translucency */ + png_set_expand (png_ptr); + } + } + else + { + ckey = 0; /* actual value will be set later */ + } + } + + if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + png_set_gray_to_rgb (png_ptr); + } + + /* Register our changes with the reading machinery and refresh + * our ancillary data about the image */ + png_read_update_info (png_ptr, info_ptr); + png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth, + &color_type, &interlace_type, NULL, NULL); + + /* Allocate the SDL surface to hold the image */ + Rmask = Gmask = Bmask = Amask = 0; + num_channels = png_get_channels (png_ptr, info_ptr); + if (num_channels >= 3) + { +#if SDL_BYTEORDER == SDL_LIL_ENDIAN + Rmask = 0x000000FF; + Gmask = 0x0000FF00; + Bmask = 0x00FF0000; + Amask = (num_channels == 4) ? 0xFF000000 : 0; +#else + int s = (num_channels == 4) ? 0 : 8; + Rmask = 0xFF000000 >> s; + Gmask = 0x00FF0000 >> s; + Bmask = 0x0000FF00 >> s; + Amask = 0x000000FF >> s; +#endif + } + surface = SDL_CreateRGBSurface (SDL_SWSURFACE, width, height, + bit_depth * num_channels, + Rmask, Gmask, Bmask, Amask); + if (surface == NULL) + { + error = SDL_GetError (); + goto done; + } + + if (ckey != -1) + { + if (color_type != PNG_COLOR_TYPE_PALETTE) + { + /* FIXME: Should these be truncated or shifted down? */ + ckey = SDL_MapRGB(surface->format, + (Uint8)transv->red, + (Uint8)transv->green, + (Uint8)transv->blue); + } +#if SDL_MAJOR_VERSION >= 2 + SDL_SetColorKey (surface, SDL_TRUE, ckey); +#else + SDL_SetColorKey (surface, SDL_SRCCOLORKEY, ckey); +#endif + } + + SDL_LockSurface (surface); + /* Create the array of pointers to image data */ + row_pointers = (png_bytep *)SDL_malloc (sizeof (png_bytep) * height); + if (!row_pointers) + { + error = "Out of memory"; + goto done; + } + for (row = 0; row < (int)height; row++) + { + row_pointers[row] = (png_bytep) + (Uint8 *)surface->pixels + row*surface->pitch; + } + + /* Read the entire image in one go */ + png_read_image (png_ptr, row_pointers); + SDL_UnlockSurface (surface); + + /* and we're done! (png_read_end() can be omitted if no + * processing of post-IDAT text/time/etc. is desired) + * In some cases it can't read PNGs created by some popular + * programs (ACDSEE), we do not want to process comments, so + * we omit png_read_end */ + + /* Load the palette, if any */ + if (surface->format->palette) + { + SDL_Color palette[256]; + int png_num_palette; + png_colorp png_palette; + png_get_PLTE (png_ptr, info_ptr, &png_palette, &png_num_palette); + if (color_type == PNG_COLOR_TYPE_GRAY) + { + png_num_palette = 256; + for (i = 0; i < 256; i++) + { + palette[i].r = (Uint8)i; + palette[i].g = (Uint8)i; + palette[i].b = (Uint8)i; + } + } + else if (png_num_palette > 0) + { + for (i = 0; i < png_num_palette; ++i) + { + palette[i].b = png_palette[i].blue; + palette[i].g = png_palette[i].green; + palette[i].r = png_palette[i].red; + } + } +#if SDL_MAJOR_VERSION >= 2 + SDL_SetPaletteColors (surface->format->palette, palette, + 0, png_num_palette); +#else + SDL_SetPalette (surface, SDL_LOGPAL, palette, + 0, png_num_palette); +#endif + } + +done: /* Clean up and return */ + if (png_ptr) + { + png_destroy_read_struct (&png_ptr, + info_ptr ? &info_ptr : (png_infopp)0, + (png_infopp)0); + } + if (row_pointers) + { + SDL_free (row_pointers); + } + if (error) + { + SDL_RWseek(src, start, RW_SEEK_SET); + if (surface) + { + SDL_FreeSurface (surface); + surface = NULL; + } + fprintf (stderr, "%s", error); + } + return surface; +} diff --git a/src/libs/graphics/sdl/png2sdl.h b/src/libs/graphics/sdl/png2sdl.h new file mode 100644 index 0000000..c0a9ec3 --- /dev/null +++ b/src/libs/graphics/sdl/png2sdl.h @@ -0,0 +1,24 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 PNG2SDL_H_ +#define PNG2SDL_H_ + +#include + +SDL_Surface *TFB_png_to_sdl (SDL_RWops *src); + +#endif diff --git a/src/libs/graphics/sdl/primitives.c b/src/libs/graphics/sdl/primitives.c new file mode 100644 index 0000000..6dd539a --- /dev/null +++ b/src/libs/graphics/sdl/primitives.c @@ -0,0 +1,633 @@ +//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 "sdl_common.h" +#include "primitives.h" + + +// Pixel drawing routines + +static Uint32 +getpixel_8(SDL_Surface *surface, int x, int y) +{ + /* Here p is the address to the pixel we want to retrieve */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x; + return *p; +} + +static void +putpixel_8(SDL_Surface *surface, int x, int y, Uint32 pixel) +{ + /* Here p is the address to the pixel we want to set */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 1; + *p = pixel; +} + +static Uint32 +getpixel_16(SDL_Surface *surface, int x, int y) +{ + /* Here p is the address to the pixel we want to retrieve */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 2; + return *(Uint16 *)p; +} + +static void +putpixel_16(SDL_Surface *surface, int x, int y, Uint32 pixel) +{ + /* Here p is the address to the pixel we want to set */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 2; + *(Uint16 *)p = pixel; +} + +static Uint32 +getpixel_24_be(SDL_Surface *surface, int x, int y) +{ + /* Here p is the address to the pixel we want to retrieve */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 3; + return p[0] << 16 | p[1] << 8 | p[2]; +} + +static void +putpixel_24_be(SDL_Surface *surface, int x, int y, Uint32 pixel) +{ + /* Here p is the address to the pixel we want to set */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 3; + p[0] = (pixel >> 16) & 0xff; + p[1] = (pixel >> 8) & 0xff; + p[2] = pixel & 0xff; +} + +static Uint32 +getpixel_24_le(SDL_Surface *surface, int x, int y) +{ + /* Here p is the address to the pixel we want to retrieve */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 3; + return p[0] | p[1] << 8 | p[2] << 16; +} + +static void +putpixel_24_le(SDL_Surface *surface, int x, int y, Uint32 pixel) +{ + /* Here p is the address to the pixel we want to set */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 3; + p[0] = pixel & 0xff; + p[1] = (pixel >> 8) & 0xff; + p[2] = (pixel >> 16) & 0xff; +} + +static Uint32 +getpixel_32(SDL_Surface *surface, int x, int y) +{ + /* Here p is the address to the pixel we want to retrieve */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 4; + return *(Uint32 *)p; +} + +static void +putpixel_32(SDL_Surface *surface, int x, int y, Uint32 pixel) +{ + /* Here p is the address to the pixel we want to set */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * 4; + *(Uint32 *)p = pixel; +} + +GetPixelFn +getpixel_for(SDL_Surface *surface) +{ + int bpp = surface->format->BytesPerPixel; + switch (bpp) { + case 1: + return &getpixel_8; + case 2: + return &getpixel_16; + case 3: + if (SDL_BYTEORDER == SDL_BIG_ENDIAN) { + return &getpixel_24_be; + } else { + return &getpixel_24_le; + } + case 4: + return &getpixel_32; + } + return NULL; +} + +PutPixelFn +putpixel_for(SDL_Surface *surface) +{ + int bpp = surface->format->BytesPerPixel; + switch (bpp) { + case 1: + return &putpixel_8; + case 2: + return &putpixel_16; + case 3: + if (SDL_BYTEORDER == SDL_BIG_ENDIAN) { + return &putpixel_24_be; + } else { + return &putpixel_24_le; + } + case 4: + return &putpixel_32; + } + return NULL; +} + +static void +renderpixel_replace(SDL_Surface *surface, int x, int y, Uint32 pixel, + int factor) +{ + (void) factor; // ignored + putpixel_32(surface, x, y, pixel); +} + +static inline Uint8 +clip_channel(int c) +{ + if (c < 0) + c = 0; + else if (c > 255) + c = 255; + return c; +} + +static inline Uint8 +modulated_sum(Uint8 dc, Uint8 sc, int factor) +{ + // We use >> 8 instead of / 255 because it is faster, but it does + // not work 100% correctly. It should be safe because this should + // not be called for factor==255 + int b = dc + ((sc * factor) >> 8); + return clip_channel(b); +} + +static inline Uint8 +alpha_blend(Uint8 dc, Uint8 sc, int alpha) +{ + // We use >> 8 instead of / 255 because it is faster, but it does + // not work 100% correctly. It should be safe because this should + // not be called for alpha==255 + // No need to clip since we should never get values outside of 0..255 + // range, unless alpha is over 255, which is not supported. + return (((sc - dc) * alpha) >> 8) + dc; +} + +// Assumes 8 bits/channel, a safe assumption for 32bpp surfaces +#define UNPACK_PIXEL_32(p, fmt, r, g, b) \ + do { \ + (r) = ((p) >> (fmt)->Rshift) & 0xff; \ + (g) = ((p) >> (fmt)->Gshift) & 0xff; \ + (b) = ((p) >> (fmt)->Bshift) & 0xff; \ + } while (0) + +// Assumes the channels already clipped to 8 bits +static inline Uint32 +PACK_PIXEL_32(const SDL_PixelFormat *fmt, + Uint8 r, Uint8 g, Uint8 b) +{ + return ((Uint32)r << fmt->Rshift) | ((Uint32)g << fmt->Gshift) + | ((Uint32)b << fmt->Bshift); +} + +static void +renderpixel_additive(SDL_Surface *surface, int x, int y, Uint32 pixel, + int factor) +{ + const SDL_PixelFormat *fmt = surface->format; + Uint32 *p; + Uint32 sp; + Uint8 sr, sg, sb; + int r, g, b; + + p = (Uint32 *) ((Uint8 *)surface->pixels + y * surface->pitch + x * 4); + sp = *p; + UNPACK_PIXEL_32(sp, fmt, sr, sg, sb); + UNPACK_PIXEL_32(pixel, fmt, r, g, b); + + // TODO: We may need a special case for factor == -ADDITIVE_FACTOR_1 too, + // but it is not important enough right now to care ;) + if (factor == ADDITIVE_FACTOR_1) + { // no need to modulate the 'pixel', and modulation does not + // work correctly with factor==255 anyway + sr = clip_channel(sr + r); + sg = clip_channel(sg + g); + sb = clip_channel(sb + b); + } + else + { + sr = modulated_sum(sr, r, factor); + sg = modulated_sum(sg, g, factor); + sb = modulated_sum(sb, b, factor); + } + + *p = PACK_PIXEL_32(fmt, sr, sg, sb); +} + +static void +renderpixel_alpha(SDL_Surface *surface, int x, int y, Uint32 pixel, + int factor) +{ + const SDL_PixelFormat *fmt = surface->format; + Uint32 *p; + Uint32 sp; + Uint8 sr, sg, sb; + int r, g, b; + + if (factor == FULLY_OPAQUE_ALPHA) + { // alpha == 255 is equivalent to 'replace' and blending does not + // work correctly anyway because we use >> 8 instead of / 255 + putpixel_32(surface, x, y, pixel); + return; + } + + p = (Uint32 *) ((Uint8 *)surface->pixels + y * surface->pitch + x * 4); + sp = *p; + UNPACK_PIXEL_32(sp, fmt, sr, sg, sb); + UNPACK_PIXEL_32(pixel, fmt, r, g, b); + sr = alpha_blend(sr, r, factor); + sg = alpha_blend(sg, g, factor); + sb = alpha_blend(sb, b, factor); + *p = PACK_PIXEL_32(fmt, sr, sg, sb); +} + +RenderPixelFn +renderpixel_for(SDL_Surface *surface, RenderKind kind) +{ + const SDL_PixelFormat *fmt = surface->format; + + // The only supported rendering is to 32bpp surfaces + if (fmt->BytesPerPixel != 4) + return NULL; + + // Rendering other than REPLACE is not supported on RGBA surfaces + if (fmt->Amask != 0 && kind != renderReplace) + return NULL; + + switch (kind) + { + case renderReplace: + return &renderpixel_replace; + case renderAdditive: + return &renderpixel_additive; + case renderAlpha: + return &renderpixel_alpha; + } + // should not ever get here + return NULL; +} + +/* Line drawing routine + * Adapted from Paul Heckbert's implementation of Bresenham's algorithm, + * 3 Sep 85; taken from Graphics Gems I */ + +void +line_prim(int x1, int y1, int x2, int y2, Uint32 color, RenderPixelFn plot, + int factor, SDL_Surface *dst) +{ + int d, x, y, ax, ay, sx, sy, dx, dy; + SDL_Rect clip_r; + + SDL_GetClipRect (dst, &clip_r); + if (!clip_line (&x1, &y1, &x2, &y2, &clip_r)) + return; // line is completely outside clipping rectangle + + dx = x2-x1; + ax = ((dx < 0) ? -dx : dx) << 1; + sx = (dx < 0) ? -1 : 1; + dy = y2-y1; + ay = ((dy < 0) ? -dy : dy) << 1; + sy = (dy < 0) ? -1 : 1; + + x = x1; + y = y1; + if (ax > ay) { + d = ay - (ax >> 1); + for (;;) { + (*plot)(dst, x, y, color, factor); + if (x == x2) + return; + if (d >= 0) { + y += sy; + d -= ax; + } + x += sx; + d += ay; + } + } else { + d = ax - (ay >> 1); + for (;;) { + (*plot)(dst, x, y, color, factor); + if (y == y2) + return; + if (d >= 0) { + x += sx; + d -= ay; + } + y += sy; + d += ax; + } + } +} + + +// Clips line against rectangle using Cohen-Sutherland algorithm + +enum {C_TOP = 0x1, C_BOTTOM = 0x2, C_RIGHT = 0x4, C_LEFT = 0x8}; + +static int +compute_code (float x, float y, float xmin, float ymin, float xmax, float ymax) +{ + int c = 0; + if (y > ymax) + c |= C_TOP; + else if (y < ymin) + c |= C_BOTTOM; + if (x > xmax) + c |= C_RIGHT; + else if (x < xmin) + c |= C_LEFT; + return c; +} + +int +clip_line (int *lx1, int *ly1, int *lx2, int *ly2, const SDL_Rect *r) +{ + int C0, C1, C; + float x, y, x0, y0, x1, y1, xmin, ymin, xmax, ymax; + + x0 = (float)*lx1; + y0 = (float)*ly1; + x1 = (float)*lx2; + y1 = (float)*ly2; + + xmin = (float)r->x; + ymin = (float)r->y; + xmax = (float)r->x + r->w - 1; + ymax = (float)r->y + r->h - 1; + + C0 = compute_code (x0, y0, xmin, ymin, xmax, ymax); + C1 = compute_code (x1, y1, xmin, ymin, xmax, ymax); + + for (;;) { + /* trivial accept: both ends in rectangle */ + if ((C0 | C1) == 0) + { + *lx1 = (int)x0; + *ly1 = (int)y0; + *lx2 = (int)x1; + *ly2 = (int)y1; + return 1; + } + + /* trivial reject: both ends on the external side of the rectangle */ + if ((C0 & C1) != 0) + return 0; + + /* normal case: clip end outside rectangle */ + C = C0 ? C0 : C1; + if (C & C_TOP) + { + x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0); + y = ymax; + } + else if (C & C_BOTTOM) + { + x = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0); + y = ymin; + } + else if (C & C_RIGHT) + { + x = xmax; + y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0); + } + else + { + x = xmin; + y = y0 + (y1 - y0) * (xmin - x0) / (x1 - x0); + } + + /* set new end point and iterate */ + if (C == C0) + { + x0 = x; y0 = y; + C0 = compute_code (x0, y0, xmin, ymin, xmax, ymax); + } + else + { + x1 = x; y1 = y; + C1 = compute_code (x1, y1, xmin, ymin, xmax, ymax); + } + } +} + +void +fillrect_prim(SDL_Rect r, Uint32 color, RenderPixelFn plot, int factor, + SDL_Surface *dst) +{ + int x, y; + int x1, y1; + SDL_Rect clip_r; + + SDL_GetClipRect (dst, &clip_r); + if (!clip_rect (&r, &clip_r)) + return; // rect is completely outside clipping rectangle + + // TODO: calculate destination pointer directly instead of + // using the plot(x,y) version + x1 = r.x + r.w; + y1 = r.y + r.h; + for (y = r.y; y < y1; ++y) + { + for (x = r.x; x < x1; ++x) + plot(dst, x, y, color, factor); + } +} + +// clip the rectangle against the clip rectangle +int +clip_rect(SDL_Rect *r, const SDL_Rect *clip_r) +{ + // NOTE: the following clipping code is copied in part + // from SDL-1.2.4 sources + int dx, dy; + int w = r->w; + int h = r->h; + // SDL_Rect.w and .h are unsigned, we need signed + + dx = clip_r->x - r->x; + if (dx > 0) + { + w -= dx; + r->x += dx; + } + dx = r->x + w - clip_r->x - clip_r->w; + if (dx > 0) + w -= dx; + + dy = clip_r->y - r->y; + if (dy > 0) + { + h -= dy; + r->y += dy; + } + dy = r->y + h - clip_r->y - clip_r->h; + if (dy > 0) + h -= dy; + + if (w <= 0 || h <= 0) + { + r->w = 0; + r->h = 0; + return 0; + } + + r->w = w; + r->h = h; + return 1; +} + +void +blt_prim(SDL_Surface *src, SDL_Rect src_r, RenderPixelFn plot, int factor, + SDL_Surface *dst, SDL_Rect dst_r) +{ + SDL_PixelFormat *srcfmt = src->format; + SDL_Palette *srcpal = srcfmt->palette; + SDL_PixelFormat *dstfmt = dst->format; + Uint32 mask = 0; + Uint32 key = ~0; + GetPixelFn getpix = getpixel_for(src); + SDL_Rect clip_r; + int x, y; + + SDL_GetClipRect (dst, &clip_r); + if (!clip_blt_rects (&src_r, &dst_r, &clip_r)) + return; // rect is completely outside clipping rectangle + + if (src_r.x >= src->w || src_r.y >= src->h) + return; // rect is completely outside source bounds + + if (src_r.x + src_r.w > src->w) + src_r.w = src->w - src_r.x; + if (src_r.y + src_r.h > src->h) + src_r.h = src->h - src_r.y; + + // use colorkeys where appropriate + if (srcfmt->Amask) + { // alpha transparency + mask = srcfmt->Amask; + key = 0; + } + else if (TFB_GetColorKey (src, &key) == 0) + { + mask = ~0; + } + // TODO: calculate the source and destination pointers directly + // instead of using the plot(x,y) version + for (y = 0; y < src_r.h; ++y) + { + for (x = 0; x < src_r.w; ++x) + { + Uint8 r, g, b, a; + Uint32 p; + + p = getpix(src, src_r.x + x, src_r.y + y); + if (srcpal) + { // source is paletted, colorkey does not use mask + if (p == key) + continue; // transparent pixel + } + else + { // source is RGB(A), colorkey uses mask + if ((p & mask) == key) + continue; // transparent pixel + } + + // convert pixel format to destination + SDL_GetRGBA(p, srcfmt, &r, &g, &b, &a); + // TODO: handle source pixel alpha; plot() should probably + // get a source alpha parameter + p = SDL_MapRGBA(dstfmt, r, g, b, a); + + plot(dst, dst_r.x + x, dst_r.y + y, p, factor); + } + } +} + +// clip the source and destination rectangles against the clip rectangle +int +clip_blt_rects(SDL_Rect *src_r, SDL_Rect *dst_r, const SDL_Rect *clip_r) +{ + // NOTE: the following clipping code is copied in part + // from SDL-1.2.4 sources + int w, h; + int dx, dy; + + // clip the source rectangle to the source surface + w = src_r->w; + if (src_r->x < 0) + { + w += src_r->x; + dst_r->x -= src_r->x; + src_r->x = 0; + } + + h = src_r->h; + if (src_r->y < 0) + { + h += src_r->y; + dst_r->y -= src_r->y; + src_r->y = 0; + } + + // clip the destination rectangle against the clip rectangle, + // minding the source rectangle in the process + dx = clip_r->x - dst_r->x; + if (dx > 0) + { + w -= dx; + dst_r->x += dx; + src_r->x += dx; + } + dx = dst_r->x + w - clip_r->x - clip_r->w; + if (dx > 0) + w -= dx; + + dy = clip_r->y - dst_r->y; + if (dy > 0) + { + h -= dy; + dst_r->y += dy; + src_r->y += dy; + } + dy = dst_r->y + h - clip_r->y - clip_r->h; + if (dy > 0) + h -= dy; + + if (w <= 0 || h <= 0) + { + src_r->w = 0; + src_r->h = 0; + return 0; + } + + src_r->w = w; + src_r->h = h; + return 1; +} + diff --git a/src/libs/graphics/sdl/primitives.h b/src/libs/graphics/sdl/primitives.h new file mode 100644 index 0000000..9d8aff2 --- /dev/null +++ b/src/libs/graphics/sdl/primitives.h @@ -0,0 +1,62 @@ +//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 PRIMITIVES_H +#define PRIMITIVES_H + +/* Function types for the pixel functions */ + +typedef Uint32 (*GetPixelFn)(SDL_Surface *, int x, int y); +// 'pixel' is in destination surface format +typedef void (*PutPixelFn)(SDL_Surface *, int x, int y, Uint32 pixel); + +GetPixelFn getpixel_for(SDL_Surface *surface); +PutPixelFn putpixel_for(SDL_Surface *surface); + +// This currently matches gfxlib.h:DrawKind for simplicity +typedef enum +{ + renderReplace = 0, + renderAdditive, + renderAlpha, +} RenderKind; + +#define FULLY_OPAQUE_ALPHA 255 +#define ADDITIVE_FACTOR_1 255 + +// 'pixel' is in destination surface format +// See gfxlib.h:DrawKind for 'factor' spec +typedef void (*RenderPixelFn)(SDL_Surface *, int x, int y, Uint32 pixel, + int factor); + +RenderPixelFn renderpixel_for(SDL_Surface *surface, RenderKind); + +void line_prim(int x1, int y1, int x2, int y2, Uint32 color, + RenderPixelFn plot, int factor, SDL_Surface *dst); +void fillrect_prim(SDL_Rect r, Uint32 color, + RenderPixelFn plot, int factor, SDL_Surface *dst); +void blt_prim(SDL_Surface *src, SDL_Rect src_r, + RenderPixelFn plot, int factor, + SDL_Surface *dst, SDL_Rect dst_r); + +int clip_line(int *lx1, int *ly1, int *lx2, int *ly2, const SDL_Rect *clip_r); +int clip_rect(SDL_Rect *r, const SDL_Rect *clip_r); +int clip_blt_rects(SDL_Rect *src_r, SDL_Rect *dst_r, const SDL_Rect *clip_r); + + +#endif /* PRIMITIVES_H */ diff --git a/src/libs/graphics/sdl/pure.c b/src/libs/graphics/sdl/pure.c new file mode 100644 index 0000000..df4a329 --- /dev/null +++ b/src/libs/graphics/sdl/pure.c @@ -0,0 +1,474 @@ +//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 "pure.h" +#include "libs/graphics/bbox.h" +#include "scalers.h" +#include "libs/log.h" + +#if SDL_MAJOR_VERSION == 1 + +static SDL_Surface *SDL_Video = NULL; +static SDL_Surface *fade_color_surface = NULL; +static SDL_Surface *fade_temp = NULL; +static SDL_Surface *scaled_display = NULL; + +static TFB_ScaleFunc scaler = NULL; + +static Uint32 fade_color; + +static void TFB_Pure_Scaled_Preprocess (int force_full_redraw, int transition_amount, int fade_amount); +static void TFB_Pure_Scaled_Postprocess (void); +static void TFB_Pure_Unscaled_Preprocess (int force_full_redraw, int transition_amount, int fade_amount); +static void TFB_Pure_Unscaled_Postprocess (void); +static void TFB_Pure_UploadTransitionScreen (void); +static void TFB_Pure_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect); +static void TFB_Pure_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect); + +static TFB_GRAPHICS_BACKEND pure_scaled_backend = { + TFB_Pure_Scaled_Preprocess, + TFB_Pure_Scaled_Postprocess, + TFB_Pure_UploadTransitionScreen, + TFB_Pure_ScreenLayer, + TFB_Pure_ColorLayer }; + +static TFB_GRAPHICS_BACKEND pure_unscaled_backend = { + TFB_Pure_Unscaled_Preprocess, + TFB_Pure_Unscaled_Postprocess, + TFB_Pure_UploadTransitionScreen, + TFB_Pure_ScreenLayer, + TFB_Pure_ColorLayer }; + +// We cannot rely on SDL_DisplayFormatAlpha() anymore. It can return +// formats that we do not expect (SDL v1.2.14 on Mac OSX). Mac likes +// ARGB surfaces, but SDL_DisplayFormatAlpha thinks that only RGBA are fast. +// This is a generic replacement that gives what we want. +static void +CalcAlphaFormat (const SDL_PixelFormat* video, SDL_PixelFormat* ours) +{ + int valid = 0; + + // We use 32-bit surfaces internally + ours->BitsPerPixel = 32; + + // Try to get as close to the video format as possible + if (video->BitsPerPixel == 15 || video->BitsPerPixel == 16) + { // At least match the channel order + ours->Rshift = video->Rshift / 5 * 8; + ours->Gshift = video->Gshift / 5 * 8; + ours->Bshift = video->Bshift / 5 * 8; + valid = 1; + } + else if (video->BitsPerPixel == 24 || video->BitsPerPixel == 32) + { + // We can only use channels aligned on byte boundary + if (video->Rshift % 8 == 0 && video->Gshift % 8 == 0 + && video->Bshift % 8 == 0) + { // Match RGB in video + ours->Rshift = video->Rshift; + ours->Gshift = video->Gshift; + ours->Bshift = video->Bshift; + valid = 1; + } + } + + if (valid) + { // For alpha, use the unoccupied byte + ours->Ashift = 48 - (ours->Rshift + ours->Gshift + ours->Bshift); + // Set channels according to byte positions + ours->Rmask = 0xff << ours->Rshift; + ours->Gmask = 0xff << ours->Gshift; + ours->Bmask = 0xff << ours->Bshift; + ours->Amask = 0xff << ours->Ashift; + return; + } + + // Fallback case. It does not matter what we set, but SDL likes + // Alpha to be the highest. + ours->Rmask = 0x000000ff; + ours->Gmask = 0x0000ff00; + ours->Bmask = 0x00ff0000; + ours->Amask = 0xff000000; +} + +int +TFB_Pure_ConfigureVideo (int driver, int flags, int width, int height, int togglefullscreen) +{ + int i, videomode_flags; + SDL_PixelFormat conv_fmt; + + GraphicsDriver = driver; + + // must use SDL_SWSURFACE, HWSURFACE doesn't work properly + // with fades/scaling + if (width == 320 && height == 240) + { + videomode_flags = SDL_SWSURFACE; + ScreenWidthActual = 320; + ScreenHeightActual = 240; + graphics_backend = &pure_unscaled_backend; + } + else + { + videomode_flags = SDL_SWSURFACE; + ScreenWidthActual = 640; + ScreenHeightActual = 480; + graphics_backend = &pure_scaled_backend; + + if (width != 640 || height != 480) + log_add (log_Error, "Screen resolution of %dx%d not supported " + "under pure SDL, using 640x480", width, height); + } + + videomode_flags |= SDL_ANYFORMAT; + if (flags & TFB_GFXFLAGS_FULLSCREEN) + videomode_flags |= SDL_FULLSCREEN; + + /* We'll ask for a 32bpp frame, but it doesn't really matter, because we've set + SDL_ANYFORMAT */ + SDL_Video = SDL_SetVideoMode (ScreenWidthActual, ScreenHeightActual, + 32, videomode_flags); + + if (SDL_Video == NULL) + { + log_add (log_Error, "Couldn't set %ix%i video mode: %s", + ScreenWidthActual, ScreenHeightActual, + SDL_GetError ()); + return -1; + } + else + { + const SDL_Surface *video = SDL_GetVideoSurface (); + const SDL_PixelFormat* fmt = video->format; + + ScreenColorDepth = fmt->BitsPerPixel; + log_add (log_Info, "Set the resolution to: %ix%ix%i", + video->w, video->h, ScreenColorDepth); + log_add (log_Info, " Video: R %08x, G %08x, B %08x, A %08x", + fmt->Rmask, fmt->Gmask, fmt->Bmask, fmt->Amask); + + if (togglefullscreen) + { + // NOTE: We cannot change the format_conv_surf now because we + // have already loaded lots of graphics and changing it now + // will only lead to chaos. + // Just check if channel order has changed significantly + CalcAlphaFormat (fmt, &conv_fmt); + fmt = format_conv_surf->format; + if (conv_fmt.Rmask != fmt->Rmask || conv_fmt.Bmask != fmt->Bmask) + log_add (log_Warning, "Warning: pixel format has changed " + "significantly. Rendering will be slow."); + return 0; + } + } + + // Create a 32bpp surface in a compatible format which will supply + // the format information to all other surfaces used in the game + if (format_conv_surf) + { + SDL_FreeSurface (format_conv_surf); + format_conv_surf = NULL; + } + CalcAlphaFormat (SDL_Video->format, &conv_fmt); + format_conv_surf = SDL_CreateRGBSurface (SDL_SWSURFACE, 0, 0, + conv_fmt.BitsPerPixel, conv_fmt.Rmask, conv_fmt.Gmask, + conv_fmt.Bmask, conv_fmt.Amask); + if (!format_conv_surf) + { + log_add (log_Error, "Couldn't create format_conv_surf: %s", + SDL_GetError()); + return -1; + } + else + { + const SDL_PixelFormat* fmt = format_conv_surf->format; + log_add (log_Info, " Internal: R %08x, G %08x, B %08x, A %08x", + fmt->Rmask, fmt->Gmask, fmt->Bmask, fmt->Amask); + } + + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + if (0 != SDL1_ReInit_Screen (&SDL_Screens[i], format_conv_surf, + ScreenWidth, ScreenHeight)) + return -1; + } + + SDL_Screen = SDL_Screens[0]; + TransitionScreen = SDL_Screens[2]; + + if (0 != SDL1_ReInit_Screen (&fade_color_surface, format_conv_surf, + ScreenWidth, ScreenHeight)) + return -1; + fade_color = SDL_MapRGB (fade_color_surface->format, 0, 0, 0); + SDL_FillRect (fade_color_surface, NULL, fade_color); + + if (0 != SDL1_ReInit_Screen (&fade_temp, format_conv_surf, + ScreenWidth, ScreenHeight)) + return -1; + + if (ScreenWidthActual > ScreenWidth || ScreenHeightActual > ScreenHeight) + { + if (0 != SDL1_ReInit_Screen (&scaled_display, format_conv_surf, + ScreenWidthActual, ScreenHeightActual)) + return -1; + + scaler = Scale_PrepPlatform (flags, SDL_Screen->format); + } + else + { // no need to scale + scaler = NULL; + } + + return 0; +} + +int +TFB_Pure_InitGraphics (int driver, int flags, const char *renderer, int width, int height) +{ + char VideoName[256]; + + log_add (log_Info, "Initializing Pure-SDL graphics."); + + SDL_VideoDriverName (VideoName, sizeof (VideoName)); + log_add (log_Info, "SDL driver used: %s", VideoName); + (void) renderer; + // The "renderer" argument is ignored by SDL1. To control how SDL1 + // gets its pixmap, set the environment variable SDL_VIDEODRIVER. + // For Linux: x11 (default), dga, fbcon, directfb, svgalib, + // ggi, aalib + // For Windows: directx (default), windib + + log_add (log_Info, "SDL initialized."); + log_add (log_Info, "Initializing Screen."); + + ScreenWidth = 320; + ScreenHeight = 240; + + if (TFB_Pure_ConfigureVideo (driver, flags, width, height, 0)) + { + log_add (log_Fatal, "Could not initialize video: " + "no fallback at start of program!"); + exit (EXIT_FAILURE); + } + + // Initialize scalers (let them precompute whatever) + Scale_Init (); + + return 0; +} + +void +TFB_Pure_UninitGraphics (void) +{ + UnInit_Screen (&scaled_display); + UnInit_Screen (&fade_color_surface); + UnInit_Screen (&fade_temp); +} + +static void +ScanLines (SDL_Surface *dst, SDL_Rect *r) +{ + const int rw = r->w * 2; + const int rh = r->h * 2; + SDL_PixelFormat *fmt = dst->format; + const int pitch = dst->pitch; + const int len = pitch / fmt->BytesPerPixel; + int ddst; + Uint32 *p = (Uint32 *) dst->pixels; + int x, y; + + p += len * (r->y * 2) + (r->x * 2); + ddst = len + len - rw; + + for (y = rh; y; y -= 2, p += ddst) + { + for (x = rw; x; --x, ++p) + { + // we ignore the lower bits as the difference + // of 1 in 255 is negligible + *p = ((*p >> 1) & 0x7f7f7f7f) + ((*p >> 2) & 0x3f3f3f3f); + } + } +} + +static SDL_Surface *backbuffer = NULL, *scalebuffer = NULL; +static SDL_Rect updated; + +static void +TFB_Pure_Scaled_Preprocess (int force_full_redraw, int transition_amount, int fade_amount) +{ + if (force_full_redraw != TFB_REDRAW_NO) + { + updated.x = updated.y = 0; + updated.w = ScreenWidth; + updated.h = ScreenHeight; + } + else + { + updated.x = TFB_BBox.region.corner.x; + updated.y = TFB_BBox.region.corner.y; + updated.w = TFB_BBox.region.extent.width; + updated.h = TFB_BBox.region.extent.height; + } + + if (transition_amount == 255 && fade_amount == 255) + backbuffer = SDL_Screens[TFB_SCREEN_MAIN]; + else + backbuffer = fade_temp; + + // we can scale directly onto SDL_Video if video is compatible + if (SDL_Video->format->BitsPerPixel == SDL_Screen->format->BitsPerPixel + && SDL_Video->format->Rmask == SDL_Screen->format->Rmask + && SDL_Video->format->Bmask == SDL_Screen->format->Bmask) + scalebuffer = SDL_Video; + else + scalebuffer = scaled_display; + +} + +static void +TFB_Pure_Unscaled_Preprocess (int force_full_redraw, int transition_amount, int fade_amount) +{ + if (force_full_redraw != TFB_REDRAW_NO) + { + updated.x = updated.y = 0; + updated.w = ScreenWidth; + updated.h = ScreenHeight; + } + else + { + updated.x = TFB_BBox.region.corner.x; + updated.y = TFB_BBox.region.corner.y; + updated.w = TFB_BBox.region.extent.width; + updated.h = TFB_BBox.region.extent.height; + } + + backbuffer = SDL_Video; + (void)transition_amount; + (void)fade_amount; +} + +static void +TFB_Pure_Scaled_Postprocess (void) +{ + SDL_LockSurface (scalebuffer); + SDL_LockSurface (backbuffer); + + if (scaler) + scaler (backbuffer, scalebuffer, &updated); + + if (GfxFlags & TFB_GFXFLAGS_SCANLINES) + ScanLines (scalebuffer, &updated); + + SDL_UnlockSurface (backbuffer); + SDL_UnlockSurface (scalebuffer); + + updated.x *= 2; + updated.y *= 2; + updated.w *= 2; + updated.h *= 2; + if (scalebuffer != SDL_Video) + SDL_BlitSurface (scalebuffer, &updated, SDL_Video, &updated); + + SDL_UpdateRects (SDL_Video, 1, &updated); +} + +static void +TFB_Pure_Unscaled_Postprocess (void) +{ + SDL_UpdateRect (SDL_Video, updated.x, updated.y, + updated.w, updated.h); +} + +static void +TFB_Pure_UploadTransitionScreen (void) +{ + /* This is a no-op in SDL1 Pure mode */ +} + +static void +TFB_Pure_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect) +{ + if (SDL_Screens[screen] == backbuffer) + return; + SDL_SetAlpha (SDL_Screens[screen], SDL_SRCALPHA, a); + SDL_BlitSurface (SDL_Screens[screen], rect, backbuffer, rect); +} + +static void +TFB_Pure_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect) +{ + Uint32 col = SDL_MapRGB (fade_color_surface->format, r, g, b); + if (col != fade_color) + { + fade_color = col; + SDL_FillRect (fade_color_surface, NULL, fade_color); + } + SDL_SetAlpha (fade_color_surface, SDL_SRCALPHA, a); + SDL_BlitSurface (fade_color_surface, rect, backbuffer, rect); +} + +void +Scale_PerfTest (void) +{ + TimeCount TimeStart, TimeIn; + TimeCount Now = 0; + SDL_Rect updated = {0, 0, ScreenWidth, ScreenHeight}; + int i; + + if (!scaler) + { + log_add (log_Error, "No scaler configured! " + "Run with larger resolution, please"); + return; + } + if (!scaled_display) + { + log_add (log_Error, "Run scaler performance tests " + "in Pure mode, please"); + return; + } + + SDL_LockSurface (SDL_Screen); + SDL_LockSurface (scaled_display); + + TimeStart = TimeIn = SDL_GetTicks (); + + for (i = 1; i < 1001; ++i) // run for 1000 frames + { + scaler (SDL_Screen, scaled_display, &updated); + + if (GfxFlags & TFB_GFXFLAGS_SCANLINES) + ScanLines (scaled_display, &updated); + + if (i % 100 == 0) + { + Now = SDL_GetTicks (); + log_add (log_Debug, "%03d(%04u) ", 100*1000 / (Now - TimeIn), + Now - TimeIn); + TimeIn = Now; + } + } + + log_add (log_Debug, "Full frames scaled: %d; over %u ms; %d fps\n", + (i - 1), Now - TimeStart, i * 1000 / (Now - TimeStart)); + + SDL_UnlockSurface (scaled_display); + SDL_UnlockSurface (SDL_Screen); +} + +#endif diff --git a/src/libs/graphics/sdl/pure.h b/src/libs/graphics/sdl/pure.h new file mode 100644 index 0000000..50cf06f --- /dev/null +++ b/src/libs/graphics/sdl/pure.h @@ -0,0 +1,29 @@ +//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 PURE_H +#define PURE_H + +#include "libs/graphics/sdl/sdl_common.h" + +int TFB_Pure_InitGraphics (int driver, int flags, const char *renderer, int width, int height); +void TFB_Pure_UninitGraphics (void); +int TFB_Pure_ConfigureVideo (int driver, int flags, int width, int height, int togglefullscreen); +void Scale_PerfTest (void); + +#endif diff --git a/src/libs/graphics/sdl/rotozoom.c b/src/libs/graphics/sdl/rotozoom.c new file mode 100644 index 0000000..9f22769 --- /dev/null +++ b/src/libs/graphics/sdl/rotozoom.c @@ -0,0 +1,1038 @@ +/* + + rotozoom.c - rotozoomer for 32bit or 8bit surfaces + LGPL (c) A. Schiffler + + Note by sc2 developers: + Taken from SDL_gfx library and modified, original code can be downloaded + from http://www.ferzkopp.net/Software/SDL_gfx-2.0/ + +*/ + +#include +#include +#include "sdl_common.h" +#include "libs/memlib.h" +#include "port.h" +#include "rotozoom.h" + +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) + + + +/* + + 32bit Zoomer with optional anti-aliasing by bilinear interpolation. + + Zoomes 32bit RGBA/ABGR 'src' surface to 'dst' surface. + +*/ + +int zoomSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int smooth) +{ + int x, y, sx, sy, *sax, *say, *csax, *csay, csx, csy, ex, ey, t1, t2, sstep; + tColorRGBA *c00, *c01, *c10, *c11; + tColorRGBA *sp, *csp, *dp; + int sgap, dgap; + + /* + * Variable setup + */ + if (smooth) { + /* + * For interpolation: assume source dimension is one pixel + */ + /* + * smaller to avoid overflow on right and bottom edge. + */ + sx = (int) (65536.0 * (float) (src->w - 1) / (float) dst->w); + sy = (int) (65536.0 * (float) (src->h - 1) / (float) dst->h); + } else { + sx = (int) (65536.0 * (float) src->w / (float) dst->w); + sy = (int) (65536.0 * (float) src->h / (float) dst->h); + } + + /* + * Allocate memory for row increments + */ + +#ifndef __SYMBIAN32__ + if ((sax = (int *) alloca((dst->w + 1) * sizeof(Uint32))) == NULL) + return (-1); + if ((say = (int *) alloca((dst->h + 1) * sizeof(Uint32))) == NULL) + return (-1); +#else + if ((sax = (int *) HMalloc((dst->w + 1) * sizeof(Uint32))) == NULL) + return (-1); + if ((say = (int *) HMalloc((dst->h + 1) * sizeof(Uint32))) == NULL) + { + HFree(sax); + return (-1); + } +#endif + + + /* + * Precalculate row increments + */ + csx = 0; + csax = sax; + for (x = 0; x <= dst->w; x++) { + *csax = csx; + csax++; + csx &= 0xffff; + csx += sx; + } + csy = 0; + csay = say; + for (y = 0; y <= dst->h; y++) { + *csay = csy; + csay++; + csy &= 0xffff; + csy += sy; + } + + /* + * Pointer setup + */ + sp = csp = (tColorRGBA *) src->pixels; + dp = (tColorRGBA *) dst->pixels; + sgap = src->pitch - src->w * 4; + dgap = dst->pitch - dst->w * 4; + + /* + * Switch between interpolating and non-interpolating code + */ + if (smooth) { + + /* + * Interpolating Zoom + */ + + /* + * Scan destination + */ + csay = say; + for (y = 0; y < dst->h; y++) { + /* + * Setup color source pointers + */ + c00 = csp; + c01 = csp; + c01++; + c10 = (tColorRGBA *) ((Uint8 *) csp + src->pitch); + c11 = c10; + c11++; + csax = sax; + for (x = 0; x < dst->w; x++) { + + /* + * Interpolate colors + */ + ex = (*csax & 0xffff); + ey = (*csay & 0xffff); + t1 = ((((c01->r - c00->r) * ex) >> 16) + c00->r) & 0xff; + t2 = ((((c11->r - c10->r) * ex) >> 16) + c10->r) & 0xff; + dp->r = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01->g - c00->g) * ex) >> 16) + c00->g) & 0xff; + t2 = ((((c11->g - c10->g) * ex) >> 16) + c10->g) & 0xff; + dp->g = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01->b - c00->b) * ex) >> 16) + c00->b) & 0xff; + t2 = ((((c11->b - c10->b) * ex) >> 16) + c10->b) & 0xff; + dp->b = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01->a - c00->a) * ex) >> 16) + c00->a) & 0xff; + t2 = ((((c11->a - c10->a) * ex) >> 16) + c10->a) & 0xff; + dp->a = (((t2 - t1) * ey) >> 16) + t1; + + /* + * Advance source pointers + */ + csax++; + sstep = (*csax >> 16); + c00 += sstep; + c01 += sstep; + c10 += sstep; + c11 += sstep; + /* + * Advance destination pointer + */ + dp++; + } + /* + * Advance source pointer + */ + csay++; + csp = (tColorRGBA *) ((Uint8 *) csp + (*csay >> 16) * src->pitch); + /* + * Advance destination pointers + */ + dp = (tColorRGBA *) ((Uint8 *) dp + dgap); + } + + } else { + + /* + * Non-Interpolating Zoom + */ + + csay = say; + for (y = 0; y < dst->h; y++) { + sp = csp; + csax = sax; + for (x = 0; x < dst->w; x++) { + /* + * Draw + */ + *dp = *sp; + /* + * Advance source pointers + */ + csax++; + sp += (*csax >> 16); + /* + * Advance destination pointer + */ + dp++; + } + /* + * Advance source pointer + */ + csay++; + csp = (tColorRGBA *) ((Uint8 *) csp + (*csay >> 16) * src->pitch); + /* + * Advance destination pointers + */ + dp = (tColorRGBA *) ((Uint8 *) dp + dgap); + } + + } + +#ifdef __SYMBIAN32__ + HFree(sax); + HFree(say); +#endif + + return (0); +} + +/* + + 8bit Zoomer without smoothing. + + Zoomes 8bit palette/Y 'src' surface to 'dst' surface. + +*/ + +static +int zoomSurfaceY(SDL_Surface * src, SDL_Surface * dst) +{ + Uint32 sx, sy, *sax, *say, *csax, *csay, csx, csy; + int x, y; + Uint8 *sp, *dp, *csp; + int dgap; + + /* + * Variable setup + */ + sx = (Uint32) (65536.0 * (float) src->w / (float) dst->w); + sy = (Uint32) (65536.0 * (float) src->h / (float) dst->h); + + /* + * Allocate memory for row increments + */ +#ifndef __SYMBIAN32__ + if ((sax = (Uint32 *) alloca(dst->w * sizeof(Uint32))) == NULL) + return (-1); + if ((say = (Uint32 *) alloca(dst->h * sizeof(Uint32))) == NULL) + return (-1); +#else + if ((sax = (Uint32 *) HMalloc(dst->w * sizeof(Uint32))) == NULL) + return (-1); + if ((say = (Uint32 *) HMalloc(dst->h * sizeof(Uint32))) == NULL) + { + HFree(sax); + return (-1); + } +#endif + + /* + * Precalculate row increments + */ + csx = 0; + csax = sax; + for (x = 0; x < dst->w; x++) { + csx += sx; + *csax = (csx >> 16); + csx &= 0xffff; + csax++; + } + csy = 0; + csay = say; + for (y = 0; y < dst->h; y++) { + csy += sy; + *csay = (csy >> 16); + csy &= 0xffff; + csay++; + } + + csx = 0; + csax = sax; + for (x = 0; x < dst->w; x++) { + csx += (*csax); + csax++; + } + csy = 0; + csay = say; + for (y = 0; y < dst->h; y++) { + csy += (*csay); + csay++; + } + + /* + * Pointer setup + */ + sp = csp = (Uint8 *) src->pixels; + dp = (Uint8 *) dst->pixels; + dgap = dst->pitch - dst->w; + + /* + * Draw + */ + csay = say; + for (y = 0; y < dst->h; y++) { + csax = sax; + sp = csp; + for (x = 0; x < dst->w; x++) { + /* + * Draw + */ + *dp = *sp; + /* + * Advance source pointers + */ + sp += (*csax); + csax++; + /* + * Advance destination pointer + */ + dp++; + } + /* + * Advance source pointer (for row) + */ + csp += ((*csay) * src->pitch); + csay++; + /* + * Advance destination pointers + */ + dp += dgap; + } + +#ifdef __SYMBIAN32__ + HFree(sax); + HFree(say); +#endif + + return (0); +} + +/* + + 32bit Rotozoomer with optional anti-aliasing by bilinear interpolation. + + Rotates and zoomes 32bit RGBA/ABGR 'src' surface to 'dst' surface. + +*/ + +static +void transformSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int cx, int cy, int isin, int icos, int smooth) +{ + int x, y, t1, t2, dx, dy, xd, yd, sdx, sdy, ax, ay, ex, ey, sw, sh; + tColorRGBA c00, c01, c10, c11; + tColorRGBA *pc, *sp; + int gap; + + /* + * Variable setup + */ + xd = ((src->w - dst->w) << 15); + yd = ((src->h - dst->h) << 15); + ax = (cx << 16) - (icos * cx); + ay = (cy << 16) - (isin * cx); + sw = src->w - 1; + sh = src->h - 1; + pc = dst->pixels; + gap = dst->pitch - dst->w * 4; + + /* + * Switch between interpolating and non-interpolating code + */ + if (smooth) { + for (y = 0; y < dst->h; y++) { + dy = cy - y; + sdx = (ax + (isin * dy)) + xd; + sdy = (ay - (icos * dy)) + yd; + for (x = 0; x < dst->w; x++) { + dx = (sdx >> 16); + dy = (sdy >> 16); + if ((dx >= -1) && (dy >= -1) && (dx < src->w) && (dy < src->h)) { + if ((dx >= 0) && (dy >= 0) && (dx < sw) && (dy < sh)) { + sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy); + sp += dx; + c00 = *sp; + sp += 1; + c01 = *sp; + sp = (tColorRGBA *) ((Uint8 *) sp + src->pitch); + sp -= 1; + c10 = *sp; + sp += 1; + c11 = *sp; + } else if ((dx == sw) && (dy == sh)) { + sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy); + sp += dx; + c00 = *sp; + c01 = *sp; + c10 = *sp; + c11 = *sp; + } else if ((dx == -1) && (dy == -1)) { + sp = (tColorRGBA *) (src->pixels); + c00 = *sp; + c01 = *sp; + c10 = *sp; + c11 = *sp; + } else if ((dx == -1) && (dy == sh)) { + sp = (tColorRGBA *) (src->pixels); + sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy); + c00 = *sp; + c01 = *sp; + c10 = *sp; + c11 = *sp; + } else if ((dx == sw) && (dy == -1)) { + sp = (tColorRGBA *) (src->pixels); + sp += dx; + c00 = *sp; + c01 = *sp; + c10 = *sp; + c11 = *sp; + } else if (dx == -1) { + sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy); + c00 = *sp; + c01 = *sp; + c10 = *sp; + sp = (tColorRGBA *) ((Uint8 *) sp + src->pitch); + c11 = *sp; + } else if (dy == -1) { + sp = (tColorRGBA *) (src->pixels); + sp += dx; + c00 = *sp; + c01 = *sp; + c10 = *sp; + sp += 1; + c11 = *sp; + } else if (dx == sw) { + sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy); + sp += dx; + c00 = *sp; + c01 = *sp; + sp = (tColorRGBA *) ((Uint8 *) sp + src->pitch); + c10 = *sp; + c11 = *sp; + } else if (dy == sh) { + sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy); + sp += dx; + c00 = *sp; + sp += 1; + c01 = *sp; + c10 = *sp; + c11 = *sp; + } + /* + * Interpolate colors + */ + ex = (sdx & 0xffff); + ey = (sdy & 0xffff); + t1 = ((((c01.r - c00.r) * ex) >> 16) + c00.r) & 0xff; + t2 = ((((c11.r - c10.r) * ex) >> 16) + c10.r) & 0xff; + pc->r = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01.g - c00.g) * ex) >> 16) + c00.g) & 0xff; + t2 = ((((c11.g - c10.g) * ex) >> 16) + c10.g) & 0xff; + pc->g = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01.b - c00.b) * ex) >> 16) + c00.b) & 0xff; + t2 = ((((c11.b - c10.b) * ex) >> 16) + c10.b) & 0xff; + pc->b = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01.a - c00.a) * ex) >> 16) + c00.a) & 0xff; + t2 = ((((c11.a - c10.a) * ex) >> 16) + c10.a) & 0xff; + pc->a = (((t2 - t1) * ey) >> 16) + t1; + } + sdx += icos; + sdy += isin; + pc++; + } + pc = (tColorRGBA *) ((Uint8 *) pc + gap); + } + } else { + for (y = 0; y < dst->h; y++) { + dy = cy - y; + sdx = (ax + (isin * dy)) + xd; + sdy = (ay - (icos * dy)) + yd; + for (x = 0; x < dst->w; x++) { + dx = (short) (sdx >> 16); + dy = (short) (sdy >> 16); + if ((dx >= 0) && (dy >= 0) && (dx < src->w) && (dy < src->h)) { + sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy); + sp += dx; + *pc = *sp; + } + sdx += icos; + sdy += isin; + pc++; + } + pc = (tColorRGBA *) ((Uint8 *) pc + gap); + } + } +} + +/* + + 8bit Rotozoomer without smoothing + + Rotates and zoomes 8bit palette/Y 'src' surface to 'dst' surface. + +*/ + +static +void transformSurfaceY(SDL_Surface * src, SDL_Surface * dst, int cx, int cy, int isin, int icos) +{ + int x, y, dx, dy, xd, yd, sdx, sdy, ax, ay, sw, sh; + tColorY *pc, *sp; + int gap; + Uint32 colorkey = 0; + + /* + * Variable setup + */ + xd = ((src->w - dst->w) << 15); + yd = ((src->h - dst->h) << 15); + ax = (cx << 16) - (icos * cx); + ay = (cy << 16) - (isin * cx); + sw = src->w - 1; + sh = src->h - 1; + pc = dst->pixels; + gap = dst->pitch - dst->w; + /* + * Clear surface to colorkey + */ + TFB_GetColorKey (src, &colorkey); + memset(pc, (unsigned char) (colorkey & 0xff), dst->pitch * dst->h); + /* + * Iterate through destination surface + */ + for (y = 0; y < dst->h; y++) { + dy = cy - y; + sdx = (ax + (isin * dy)) + xd; + sdy = (ay - (icos * dy)) + yd; + for (x = 0; x < dst->w; x++) { + dx = (short) (sdx >> 16); + dy = (short) (sdy >> 16); + if ((dx >= 0) && (dy >= 0) && (dx < src->w) && (dy < src->h)) { + sp = (tColorY *) (src->pixels); + sp += (src->pitch * dy + dx); + *pc = *sp; + } + sdx += icos; + sdy += isin; + pc++; + } + pc += gap; + } +} + +/* + + rotozoomSurface() + + Rotates and zoomes a 32bit or 8bit 'src' surface to newly created 'dst' surface. + 'angle' is the rotation in degrees. 'zoom' a scaling factor. If 'smooth' is 1 + then the destination 32bit surface is anti-aliased. If the surface is not 8bit + or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly. + +*/ + +#define VALUE_LIMIT 0.001 + + +/* Local rotozoom-size function with trig result return */ + +static +void rotozoomSurfaceSizeTrig(int width, int height, double angle, double zoom, int *dstwidth, int *dstheight, + double *canglezoom, double *sanglezoom) +{ + double x, y, cx, cy, sx, sy; + double radangle; + int dstwidthhalf, dstheighthalf; + + /* + * Determine destination width and height by rotating a centered source box + */ + radangle = angle * (M_PI / 180.0); + *sanglezoom = sin(radangle); + *canglezoom = cos(radangle); + *sanglezoom *= zoom; + *canglezoom *= zoom; + x = width / 2; + y = height / 2; + cx = *canglezoom * x; + cy = *canglezoom * y; + sx = *sanglezoom * x; + sy = *sanglezoom * y; + dstwidthhalf = MAX(ceil(fabs(cx) + fabs(sy)), 1); + dstheighthalf = MAX(ceil(fabs(sx) + fabs(cy)), 1); + *dstwidth = 2 * dstwidthhalf; + *dstheight = 2 * dstheighthalf; +} + + +/* Publically available rotozoom-size function */ + +void rotozoomSurfaceSize(int width, int height, double angle, double zoom, int *dstwidth, int *dstheight) +{ + double dummy_sanglezoom, dummy_canglezoom; + + rotozoomSurfaceSizeTrig(width, height, angle, zoom, dstwidth, dstheight, &dummy_sanglezoom, &dummy_canglezoom); +} + + +/* Publically available rotozoom function */ + +SDL_Surface *rotozoomSurface(SDL_Surface * src, double angle, double zoom, int smooth) +{ + SDL_Surface *rz_src; + SDL_Surface *rz_dst; + double zoominv; + double sanglezoom, canglezoom, sanglezoominv, canglezoominv; + int dstwidthhalf, dstwidth, dstheighthalf, dstheight; + int is32bit; + int i, src_converted; + + /* + * Sanity check + */ + if (src == NULL) + return (NULL); + + /* + * Determine if source surface is 32bit or 8bit + */ + is32bit = (src->format->BitsPerPixel == 32); + if ((is32bit) || (src->format->BitsPerPixel == 8)) { + /* + * Use source surface 'as is' + */ + rz_src = src; + src_converted = 0; + } else { + /* + * New source surface is 32bit with a defined RGBA ordering + */ + rz_src = + SDL_CreateRGBSurface(SDL_SWSURFACE, src->w, src->h, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); + SDL_BlitSurface(src, NULL, rz_src, NULL); + src_converted = 1; + is32bit = 1; + } + + /* + * Sanity check zoom factor + */ + if (zoom < VALUE_LIMIT) { + zoom = VALUE_LIMIT; + } + zoominv = 65536.0 / (zoom * zoom); + + /* + * Check if we have a rotozoom or just a zoom + */ + if (fabs(angle) > VALUE_LIMIT) { + + /* + * Angle!=0: full rotozoom + */ + /* + * ----------------------- + */ + + /* Determine target size */ + rotozoomSurfaceSizeTrig(rz_src->w, rz_src->h, angle, zoom, &dstwidth, &dstheight, &canglezoom, &sanglezoom); + + /* + * Calculate target factors from sin/cos and zoom + */ + sanglezoominv = sanglezoom; + canglezoominv = canglezoom; + sanglezoominv *= zoominv; + canglezoominv *= zoominv; + + /* Calculate half size */ + dstwidthhalf = dstwidth / 2; + dstheighthalf = dstheight / 2; + + /* + * Alloc space to completely contain the rotated surface + */ + rz_dst = NULL; + if (is32bit) { + /* + * Target surface is 32bit with source RGBA/ABGR ordering + */ + rz_dst = + SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 32, + rz_src->format->Rmask, rz_src->format->Gmask, + rz_src->format->Bmask, rz_src->format->Amask); + } else { + /* + * Target surface is 8bit + */ + rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 8, 0, 0, 0, 0); + } + + /* + * Lock source surface + */ + SDL_LockSurface(rz_src); + /* + * Check which kind of surface we have + */ + if (is32bit) { + /* + * Call the 32bit transformation routine to do the rotation (using alpha) + */ + transformSurfaceRGBA(rz_src, rz_dst, dstwidthhalf, dstheighthalf, + (int) (sanglezoominv), (int) (canglezoominv), smooth); + /* + * Turn on source-alpha support + */ + TFB_SetSurfaceAlphaMod (rz_dst, 255); + } else { + /* + * Copy palette and colorkey info + */ + Uint32 srckey = 0; + TFB_GetColorKey (rz_src, &srckey); + for (i = 0; i < rz_src->format->palette->ncolors; i++) { + rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i]; + } + rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors; + /* + * Call the 8bit transformation routine to do the rotation + */ + transformSurfaceY(rz_src, rz_dst, dstwidthhalf, dstheighthalf, + (int) (sanglezoominv), (int) (canglezoominv)); + TFB_SetColorKey(rz_dst, srckey, 1); + } + /* + * Unlock source surface + */ + SDL_UnlockSurface(rz_src); + + } else { + + /* + * Angle=0: Just a zoom + */ + /* + * -------------------- + */ + + /* + * Calculate target size + */ + zoomSurfaceSize(rz_src->w, rz_src->h, zoom, zoom, &dstwidth, &dstheight); + + /* + * Alloc space to completely contain the zoomed surface + */ + rz_dst = NULL; + if (is32bit) { + /* + * Target surface is 32bit with source RGBA/ABGR ordering + */ + rz_dst = + SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 32, + rz_src->format->Rmask, rz_src->format->Gmask, + rz_src->format->Bmask, rz_src->format->Amask); + } else { + /* + * Target surface is 8bit + */ + rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 8, 0, 0, 0, 0); + } + + /* + * Lock source surface + */ + SDL_LockSurface(rz_src); + /* + * Check which kind of surface we have + */ + if (is32bit) { + /* + * Call the 32bit transformation routine to do the zooming (using alpha) + */ + zoomSurfaceRGBA(rz_src, rz_dst, smooth); + /* + * Turn on source-alpha support + */ + TFB_SetSurfaceAlphaMod (rz_dst, 255); + } else { + /* + * Copy palette and colorkey info + */ + Uint32 srckey = 0; + TFB_GetColorKey (rz_src, &srckey); + for (i = 0; i < rz_src->format->palette->ncolors; i++) { + rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i]; + } + rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors; + /* + * Call the 8bit transformation routine to do the zooming + */ + zoomSurfaceY(rz_src, rz_dst); + TFB_SetColorKey(rz_dst, srckey, 1); + } + /* + * Unlock source surface + */ + SDL_UnlockSurface(rz_src); + } + + /* + * Cleanup temp surface + */ + if (src_converted) { + SDL_FreeSurface(rz_src); + } + + /* + * Return destination surface + */ + return (rz_dst); +} + +/* Publically available rotate function */ + +int rotateSurface(SDL_Surface *src, SDL_Surface *dst, double angle, int smooth) +{ + double zoominv; + double sanglezoom, canglezoom, sanglezoominv, canglezoominv; + int dstwidthhalf, dstwidth, dstheighthalf, dstheight; + int is32bit; + int i; + + /* Sanity check */ + if (!src || !dst) + return -1; + if (src->format->BitsPerPixel != dst->format->BitsPerPixel) + return -1; + + /* Determine if source surface is 32bit or 8bit */ + is32bit = (src->format->BitsPerPixel == 32); + + zoominv = 65536.0; + + /* Check if we have to rotate anything */ + if (fabs(angle) <= VALUE_LIMIT) + { + SDL_BlitSurface(src, NULL, dst, NULL); + return 0; + } + + /* Determine target size */ + rotozoomSurfaceSizeTrig(src->w, src->h, angle, 1, &dstwidth, &dstheight, &canglezoom, &sanglezoom); + + /* + * Calculate target factors from sin/cos and zoom + */ + sanglezoominv = sanglezoom; + canglezoominv = canglezoom; + sanglezoominv *= zoominv; + canglezoominv *= zoominv; + + /* Calculate half size */ + dstwidthhalf = dstwidth / 2; + dstheighthalf = dstheight / 2; + + /* Check if the rotated surface will fit destination */ + if (dst->w < dstwidth || dst->h < dstheight) + return -1; + + /* Lock source surface */ + SDL_LockSurface(src); + SDL_LockSurface(dst); + /* Check which kind of surface we have */ + if (is32bit) + { /* Call the 32bit transformation routine to do the rotation (using alpha) */ + transformSurfaceRGBA(src, dst, dstwidthhalf, dstheighthalf, + (int) (sanglezoominv), (int) (canglezoominv), smooth); + } + else + { + /* Copy palette info */ + for (i = 0; i < src->format->palette->ncolors; i++) + dst->format->palette->colors[i] = src->format->palette->colors[i]; + dst->format->palette->ncolors = src->format->palette->ncolors; + /* Call the 8bit transformation routine to do the rotation */ + transformSurfaceY(src, dst, dstwidthhalf, dstheighthalf, + (int) (sanglezoominv), (int) (canglezoominv)); + } + /* Unlock source surface */ + SDL_UnlockSurface(dst); + SDL_UnlockSurface(src); + + return 0; +} + +/* + + zoomSurface() + + Zoomes a 32bit or 8bit 'src' surface to newly created 'dst' surface. + 'zoomx' and 'zoomy' are scaling factors for width and height. If 'smooth' is 1 + then the destination 32bit surface is anti-aliased. If the surface is not 8bit + or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly. + +*/ + +#define VALUE_LIMIT 0.001 + +void zoomSurfaceSize(int width, int height, double zoomx, double zoomy, int *dstwidth, int *dstheight) +{ + /* + * Sanity check zoom factors + */ + if (zoomx < VALUE_LIMIT) { + zoomx = VALUE_LIMIT; + } + if (zoomy < VALUE_LIMIT) { + zoomy = VALUE_LIMIT; + } + + /* + * Calculate target size + */ + *dstwidth = (int) ((double) width * zoomx); + *dstheight = (int) ((double) height * zoomy); + if (*dstwidth < 1) { + *dstwidth = 1; + } + if (*dstheight < 1) { + *dstheight = 1; + } +} + +SDL_Surface *zoomSurface(SDL_Surface * src, double zoomx, double zoomy, int smooth) +{ + SDL_Surface *rz_src; + SDL_Surface *rz_dst; + int dstwidth, dstheight; + int is32bit; + int i, src_converted; + + /* + * Sanity check + */ + if (src == NULL) + return (NULL); + + /* + * Determine if source surface is 32bit or 8bit + */ + is32bit = (src->format->BitsPerPixel == 32); + if ((is32bit) || (src->format->BitsPerPixel == 8)) { + /* + * Use source surface 'as is' + */ + rz_src = src; + src_converted = 0; + } else { + /* + * New source surface is 32bit with a defined RGBA ordering + */ + rz_src = + SDL_CreateRGBSurface(SDL_SWSURFACE, src->w, src->h, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); + SDL_BlitSurface(src, NULL, rz_src, NULL); + src_converted = 1; + is32bit = 1; + } + + /* Get size if target */ + zoomSurfaceSize(rz_src->w, rz_src->h, zoomx, zoomy, &dstwidth, &dstheight); + + /* + * Alloc space to completely contain the zoomed surface + */ + rz_dst = NULL; + if (is32bit) { + /* + * Target surface is 32bit with source RGBA/ABGR ordering + */ + rz_dst = + SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 32, + rz_src->format->Rmask, rz_src->format->Gmask, + rz_src->format->Bmask, rz_src->format->Amask); + } else { + /* + * Target surface is 8bit + */ + rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 8, 0, 0, 0, 0); + } + + /* + * Lock source surface + */ + SDL_LockSurface(rz_src); + /* + * Check which kind of surface we have + */ + if (is32bit) { + /* + * Call the 32bit transformation routine to do the zooming (using alpha) + */ + zoomSurfaceRGBA(rz_src, rz_dst, smooth); + /* + * Turn on source-alpha support + */ + TFB_SetSurfaceAlphaMod (rz_dst, 255); + } else { + /* + * Copy palette and colorkey info + */ + Uint32 srckey = 0; + TFB_GetColorKey (rz_src, &srckey); + for (i = 0; i < rz_src->format->palette->ncolors; i++) { + rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i]; + } + rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors; + /* + * Call the 8bit transformation routine to do the zooming + */ + zoomSurfaceY(rz_src, rz_dst); + TFB_SetColorKey(rz_dst, srckey, 0); + } + /* + * Unlock source surface + */ + SDL_UnlockSurface(rz_src); + + /* + * Cleanup temp surface + */ + if (src_converted) { + SDL_FreeSurface(rz_src); + } + + /* + * Return destination surface + */ + return (rz_dst); +} + diff --git a/src/libs/graphics/sdl/rotozoom.h b/src/libs/graphics/sdl/rotozoom.h new file mode 100644 index 0000000..728fd42 --- /dev/null +++ b/src/libs/graphics/sdl/rotozoom.h @@ -0,0 +1,96 @@ +/* + + rotozoom.h - rotozoomer for 32bit or 8bit surfaces + LGPL (c) A. Schiffler + + Note by sc2 developers: + Taken from SDL_gfx library and modified, original code can be downloaded + from http://www.ferzkopp.net/Software/SDL_gfx-2.0/ + +*/ + + +#ifndef ROTOZOOM_H +#define ROTOZOOM_H + +#include +#ifndef M_PI +#define M_PI 3.141592654 +#endif + +#include "port.h" +#include SDL_INCLUDE(SDL.h) + + +/* ---- Defines */ + +#define SMOOTHING_OFF 0 +#define SMOOTHING_ON 1 + +/* ---- Structures */ + +typedef struct tColorRGBA { + Uint8 r; + Uint8 g; + Uint8 b; + Uint8 a; +} tColorRGBA; + +typedef struct tColorY { + Uint8 y; +} tColorY; + + +/* ---- Prototypes */ + +/* + zoomSurfaceRGBA() + + Zoom the src surface into dst. The zoom amount is determined + by the dimensions of src and dst + +*/ +int zoomSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int smooth); + +/* + + rotozoomSurface() + + Rotates and zoomes a 32bit or 8bit 'src' surface to newly created 'dst' surface. + 'angle' is the rotation in degrees. 'zoom' a scaling factor. If 'smooth' is 1 + then the destination 32bit surface is anti-aliased. If the surface is not 8bit + or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly. + +*/ + +SDL_Surface *rotozoomSurface(SDL_Surface * src, double angle, double zoom, + int smooth); + +int rotateSurface(SDL_Surface * src, SDL_Surface * dst, double angle, + int smooth); + +/* Returns the size of the target surface for a rotozoomSurface() call */ + +void rotozoomSurfaceSize(int width, int height, double angle, double zoom, + int *dstwidth, int *dstheight); + +/* + + zoomSurface() + + Zoomes a 32bit or 8bit 'src' surface to newly created 'dst' surface. + 'zoomx' and 'zoomy' are scaling factors for width and height. If 'smooth' is 1 + then the destination 32bit surface is anti-aliased. If the surface is not 8bit + or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly. + +*/ + +SDL_Surface *zoomSurface(SDL_Surface * src, double zoomx, double zoomy, + int smooth); + +/* Returns the size of the target surface for a zoomSurface() call */ + +void zoomSurfaceSize(int width, int height, double zoomx, double zoomy, + int *dstwidth, int *dstheight); + +#endif diff --git a/src/libs/graphics/sdl/scaleint.h b/src/libs/graphics/sdl/scaleint.h new file mode 100644 index 0000000..e54de80 --- /dev/null +++ b/src/libs/graphics/sdl/scaleint.h @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2005 Alex Volkov (codepro@usa.net) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Scalers Internals + +#ifndef SCALEINT_H_ +#define SCALEINT_H_ + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" + + +// Plain C names +#define SCALE_(name) Scale ## _ ## name + +// These are defaults +#define SCALE_GETPIX(p) ( *(Uint32 *)(p) ) +#define SCALE_SETPIX(p, c) ( *(Uint32 *)(p) = (c) ) + +// Plain C defaults +#define SCALE_CMPRGB(p1, p2) \ + SCALE_(GetRGBDelta) (fmt, p1, p2) + +#define SCALE_TOYUV(p) \ + SCALE_(RGBtoYUV) (fmt, p) + +#define SCALE_CMPYUV(p1, p2, toler) \ + SCALE_(CmpYUV) (fmt, p1, p2, toler) + +#define SCALE_DIFFYUV(p1, p2) \ + SCALE_(DiffYUV) (p1, p2) +#define SCALE_DIFFYUV_TY 0x40 +#define SCALE_DIFFYUV_TU 0x12 +#define SCALE_DIFFYUV_TV 0x0c + +#define SCALE_GETY(p) \ + SCALE_(GetPixY) (fmt, p) + +#define SCALE_BILINEAR_BLEND4(r0, r1, dst, dlen) \ + SCALE_(Blend_bilinear) (r0, r1, dst, dlen) + +#define NO_PREFETCH 0 +#define INTEL_PREFETCH 1 +#define AMD_PREFETCH 2 + +typedef enum +{ + YUV_XFORM_R = 0, + YUV_XFORM_G = 1, + YUV_XFORM_B = 2, + YUV_XFORM_Y = 0, + YUV_XFORM_U = 1, + YUV_XFORM_V = 2 +} RGB_YUV_INDEX; + +extern const int YUV_matrix[3][3]; + +// pre-computed transformations for 8 bits per channel +extern int RGB_to_YUV[/*RGB*/ 3][/*YUV*/ 3][ /*mult-res*/ 256]; +extern sint16 dRGB_to_dYUV[/*RGB*/ 3][/*YUV*/ 3][ /*mult-res*/ 512]; + +typedef Uint32 YUV_VECTOR; +// pre-computed transformations for RGB555 +extern YUV_VECTOR RGB15_to_YUV[0x8000]; + + +// Platform+Scaler function lookups +// +typedef struct +{ + int flag; + TFB_ScaleFunc func; +} Scale_FuncDef_t; + + +// expands the given rectangle in all directions by 'expansion' +// guarded by 'limits' +extern void Scale_ExpandRect (SDL_Rect* rect, int expansion, + const SDL_Rect* limits); + + +// Standard plain C versions of support functions + +// Initialize various platform-specific features +static inline void +SCALE_(PlatInit) (void) +{ +} + +// Finish with various platform-specific features +static inline void +SCALE_(PlatDone) (void) +{ +} + +#if 0 +static inline void +SCALE_(Prefetch) (const void* p) +{ + /* no-op in pure C */ + (void)p; +} +#else +# define Scale_Prefetch(p) +#endif + +// compute the RGB distance squared between 2 pixels +// Plain C version +static inline int +SCALE_(GetRGBDelta) (const SDL_PixelFormat* fmt, Uint32 pix1, Uint32 pix2) +{ + int c; + int delta; + + c = ((pix1 >> fmt->Rshift) & 0xff) - ((pix2 >> fmt->Rshift) & 0xff); + delta = c * c; + + c = ((pix1 >> fmt->Gshift) & 0xff) - ((pix2 >> fmt->Gshift) & 0xff); + delta += c * c; + + c = ((pix1 >> fmt->Bshift) & 0xff) - ((pix2 >> fmt->Bshift) & 0xff); + delta += c * c; + + return delta; +} + +// retrieve the Y (intensity) component of pixel's YUV +// Plain C version +static inline int +SCALE_(GetPixY) (const SDL_PixelFormat* fmt, Uint32 pix) +{ + Uint32 r, g, b; + + r = (pix >> fmt->Rshift) & 0xff; + g = (pix >> fmt->Gshift) & 0xff; + b = (pix >> fmt->Bshift) & 0xff; + + return RGB_to_YUV [YUV_XFORM_R][YUV_XFORM_Y][r] + + RGB_to_YUV [YUV_XFORM_G][YUV_XFORM_Y][g] + + RGB_to_YUV [YUV_XFORM_B][YUV_XFORM_Y][b]; +} + +static inline YUV_VECTOR +SCALE_(RGBtoYUV) (const SDL_PixelFormat* fmt, Uint32 pix) +{ + return RGB15_to_YUV[ + (((pix >> (fmt->Rshift + 3)) & 0x1f) << 10) | + (((pix >> (fmt->Gshift + 3)) & 0x1f) << 5) | + (((pix >> (fmt->Bshift + 3)) & 0x1f) ) + ]; +} + +// compare 2 pixels with respect to their YUV representations +// tolerance set by toler arg +// returns true: close; false: distant (-gt toler) +// Plain C version +static inline bool +SCALE_(CmpYUV) (const SDL_PixelFormat* fmt, Uint32 pix1, Uint32 pix2, int toler) +#if 1 +{ + int dr, dg, db; + int delta; + + dr = ((pix1 >> fmt->Rshift) & 0xff) - ((pix2 >> fmt->Rshift) & 0xff) + 255; + dg = ((pix1 >> fmt->Gshift) & 0xff) - ((pix2 >> fmt->Gshift) & 0xff) + 255; + db = ((pix1 >> fmt->Bshift) & 0xff) - ((pix2 >> fmt->Bshift) & 0xff) + 255; + + // compute Y delta + delta = abs (dRGB_to_dYUV [YUV_XFORM_R][YUV_XFORM_Y][dr] + + dRGB_to_dYUV [YUV_XFORM_G][YUV_XFORM_Y][dg] + + dRGB_to_dYUV [YUV_XFORM_B][YUV_XFORM_Y][db]); + if (delta > toler) + return false; + + // compute U delta + delta += abs (dRGB_to_dYUV [YUV_XFORM_R][YUV_XFORM_U][dr] + + dRGB_to_dYUV [YUV_XFORM_G][YUV_XFORM_U][dg] + + dRGB_to_dYUV [YUV_XFORM_B][YUV_XFORM_U][db]); + if (delta > toler) + return false; + + // compute V delta + delta += abs (dRGB_to_dYUV [YUV_XFORM_R][YUV_XFORM_V][dr] + + dRGB_to_dYUV [YUV_XFORM_G][YUV_XFORM_V][dg] + + dRGB_to_dYUV [YUV_XFORM_B][YUV_XFORM_V][db]); + + return delta <= toler; +} +#else +{ + int delta; + Uint32 yuv1, yuv2; + + yuv1 = RGB15_to_YUV[ + (((pix1 >> (fmt->Rshift + 3)) & 0x1f) << 10) | + (((pix1 >> (fmt->Gshift + 3)) & 0x1f) << 5) | + (((pix1 >> (fmt->Bshift + 3)) & 0x1f) ) + ]; + + yuv2 = RGB15_to_YUV[ + (((pix2 >> (fmt->Rshift + 3)) & 0x1f) << 10) | + (((pix2 >> (fmt->Gshift + 3)) & 0x1f) << 5) | + (((pix2 >> (fmt->Bshift + 3)) & 0x1f) ) + ]; + + // compute Y delta + delta = abs ((yuv1 & 0xff0000) - (yuv2 & 0xff0000)) >> 16; + if (delta > toler) + return false; + + // compute U delta + delta += abs ((yuv1 & 0x00ff00) - (yuv2 & 0x00ff00)) >> 8; + if (delta > toler) + return false; + + // compute V delta + delta += abs ((yuv1 & 0x0000ff) - (yuv2 & 0x0000ff)); + + return delta <= toler; +} +#endif + +// Check if 2 pixels are different with respect to their +// YUV representations +// returns 0: close; ~0: distant +static inline int +SCALE_(DiffYUV) (Uint32 yuv1, Uint32 yuv2) +{ + // non-branching version -- assumes 2's complement integers + // delta math only needs 25 bits and we have 32 available; + // only interested in the sign bits after subtraction + sint32 delta, ret; + + if (yuv1 == yuv2) + return 0; + + // compute Y delta + delta = abs ((yuv1 & 0xff0000) - (yuv2 & 0xff0000)); + ret = (SCALE_DIFFYUV_TY << 16) - delta; // save sign bit + + // compute U delta + delta = abs ((yuv1 & 0x00ff00) - (yuv2 & 0x00ff00)); + ret |= (SCALE_DIFFYUV_TU << 8) - delta; // save sign bit + + // compute V delta + delta = abs ((yuv1 & 0x0000ff) - (yuv2 & 0x0000ff)); + ret |= SCALE_DIFFYUV_TV - delta; // save sign bit + + return (ret >> 31); +} + +// blends two pixels with 1:1 ratio +static inline Uint32 +SCALE_(Blend_11) (Uint32 pix1, Uint32 pix2) +{ + /* (pix1 + pix2) >> 1 */ + return + /* lower bits can be safely ignored - the error is minimal + expression that calcs them is left for posterity + (pix1 & pix2 & low_mask) + + */ + ((pix1 & 0xfefefefe) >> 1) + ((pix2 & 0xfefefefe) >> 1); +} + +// blends four pixels with 1:1:1:1 ratio +static inline Uint32 +SCALE_(Blend_1111) (Uint32 pix1, Uint32 pix2, + Uint32 pix3, Uint32 pix4) +{ + /* (pix1 + pix2 + pix3 + pix4) >> 2 */ + return + /* lower bits can be safely ignored - the error is minimal + expression that calcs them is left for posterity + ((((pix1 & low_mask) + (pix2 & low_mask) + + (pix3 & low_mask) + (pix4 & low_mask) + ) >> 2) & low_mask) + + */ + ((pix1 & 0xfcfcfcfc) >> 2) + ((pix2 & 0xfcfcfcfc) >> 2) + + ((pix3 & 0xfcfcfcfc) >> 2) + ((pix4 & 0xfcfcfcfc) >> 2); +} + +// blends pixels with 3:1 ratio +static inline Uint32 +Scale_Blend_31 (Uint32 pix1, Uint32 pix2) +{ + /* (pix1 * 3 + pix2) / 4 */ + /* lower bits can be safely ignored - the error is minimal */ + return ((pix1 & 0xfefefefe) >> 1) + ((pix1 & 0xfcfcfcfc) >> 2) + + ((pix2 & 0xfcfcfcfc) >> 2); +} + +// blends pixels with 2:1:1 ratio +static inline Uint32 +Scale_Blend_211 (Uint32 pix1, Uint32 pix2, Uint32 pix3) +{ + /* (pix1 * 2 + pix2 + pix3) / 4 */ + /* lower bits can be safely ignored - the error is minimal */ + return ((pix1 & 0xfefefefe) >> 1) + + ((pix2 & 0xfcfcfcfc) >> 2) + + ((pix3 & 0xfcfcfcfc) >> 2); +} + +// blends pixels with 5:2:1 ratio +static inline Uint32 +Scale_Blend_521 (Uint32 pix1, Uint32 pix2, Uint32 pix3) +{ + /* (pix1 * 5 + pix2 * 2 + pix3) / 8 */ + /* lower bits can be safely ignored - the error is minimal */ + return ((pix1 & 0xfefefefe) >> 1) + ((pix1 & 0xf8f8f8f8) >> 3) + + ((pix2 & 0xfcfcfcfc) >> 2) + + ((pix3 & 0xf8f8f8f8) >> 3) + + 0x02020202 /* half-error */; +} + +// blends pixels with 6:1:1 ratio +static inline Uint32 +Scale_Blend_611 (Uint32 pix1, Uint32 pix2, Uint32 pix3) +{ + /* (pix1 * 6 + pix2 + pix3) / 8 */ + /* lower bits can be safely ignored - the error is minimal */ + return ((pix1 & 0xfefefefe) >> 1) + ((pix1 & 0xfcfcfcfc) >> 2) + + ((pix2 & 0xf8f8f8f8) >> 3) + + ((pix3 & 0xf8f8f8f8) >> 3) + + 0x02020202 /* half-error */; +} + +// blends pixels with 2:3:3 ratio +static inline Uint32 +Scale_Blend_233 (Uint32 pix1, Uint32 pix2, Uint32 pix3) +{ + /* (pix1 * 2 + pix2 * 3 + pix3 * 3) / 8 */ + /* lower bits can be safely ignored - the error is minimal */ + return ((pix1 & 0xfcfcfcfc) >> 2) + + ((pix2 & 0xfcfcfcfc) >> 2) + ((pix2 & 0xf8f8f8f8) >> 3) + + ((pix3 & 0xfcfcfcfc) >> 2) + ((pix3 & 0xf8f8f8f8) >> 3) + + 0x02020202 /* half-error */; +} + +// blends pixels with 14:1:1 ratio +static inline Uint32 +Scale_Blend_e11 (Uint32 pix1, Uint32 pix2, Uint32 pix3) +{ + /* (pix1 * 14 + pix2 + pix3) >> 4 */ + /* lower bits can be safely ignored - the error is minimal */ + return ((pix1 & 0xfefefefe) >> 1) + ((pix1 & 0xfcfcfcfc) >> 2) + + ((pix1 & 0xf8f8f8f8) >> 3) + + ((pix2 & 0xf0f0f0f0) >> 4) + + ((pix3 & 0xf0f0f0f0) >> 4) + + 0x03030303 /* half-error */; +} + +// Halfs the pixel's intensity +static inline Uint32 +SCALE_(HalfPixel) (Uint32 pix) +{ + return ((pix & 0xfefefefe) >> 1); +} + + +// Bilinear weighted blend of four pixels +// Function produces 4 blended pixels and writes them +// out to the surface (in 2x2 matrix) +// Pixels are computed using expanded weight matrix like so: +// ('sp' - source pixel, 'dp' - destination pixel) +// dp[0] = (9*sp[0] + 3*sp[1] + 3*sp[2] + 1*sp[3]) / 16 +// dp[1] = (3*sp[0] + 9*sp[1] + 1*sp[2] + 3*sp[3]) / 16 +// dp[2] = (3*sp[0] + 1*sp[1] + 9*sp[2] + 3*sp[3]) / 16 +// dp[3] = (1*sp[0] + 3*sp[1] + 3*sp[2] + 9*sp[3]) / 16 +static inline void +SCALE_(Blend_bilinear) (const Uint32* row0, const Uint32* row1, + Uint32* dst_p, Uint32 dlen) +{ + // We loose some lower bits here and try to compensate for + // that by adding half-error values. + // In general, the error is minimal (+-7) + // The >>4 reduction is achieved gradually +# define BL_PACKED_HALF(p) \ + (((p) & 0xfefefefe) >> 1) +# define BL_SUM(p1, p2) \ + (BL_PACKED_HALF(p1) + BL_PACKED_HALF(p2)) +# define BL_HALF_ERR 0x01010101 +# define BL_SUM_WERR(p1, p2) \ + (BL_PACKED_HALF(p1) + BL_PACKED_HALF(p2) + BL_HALF_ERR) + + Uint32 sum1111, sum1331, sum3113; + + // cache p[0] + 3*(p[1] + p[2]) + p[3] in sum1331 + // cache p[1] + 3*(p[0] + p[3]) + p[2] in sum3113 + sum1331 = BL_SUM (row0[1], row1[0]); + sum3113 = BL_SUM (row0[0], row1[1]); + + // cache p[0] + p[1] + p[2] + p[3] in sum1111 + sum1111 = BL_SUM_WERR (sum1331, sum3113); + + sum1331 = BL_SUM_WERR (sum1331, sum1111); + sum1331 = BL_PACKED_HALF (sum1331); + sum3113 = BL_SUM_WERR (sum3113, sum1111); + sum3113 = BL_PACKED_HALF (sum3113); + + // pixel 0 math -- (9*p[0] + 3*(p[1] + p[2]) + p[3]) / 16 + dst_p[0] = BL_PACKED_HALF (row0[0]) + sum1331; + + // pixel 1 math -- (9*p[1] + 3*(p[0] + p[3]) + p[2]) / 16 + dst_p[1] = BL_PACKED_HALF (row0[1]) + sum3113; + + // pixel 2 math -- (9*p[2] + 3*(p[0] + p[3]) + p[1]) / 16 + dst_p[dlen] = BL_PACKED_HALF (row1[0]) + sum3113; + + // pixel 3 math -- (9*p[3] + 3*(p[1] + p[2]) + p[0]) / 16 + dst_p[dlen + 1] = BL_PACKED_HALF (row1[1]) + sum1331; + +# undef BL_PACKED_HALF +# undef BL_SUM +# undef BL_HALF_ERR +# undef BL_SUM_WERR +} + +#endif /* SCALEINT_H_ */ diff --git a/src/libs/graphics/sdl/scalemmx.h b/src/libs/graphics/sdl/scalemmx.h new file mode 100644 index 0000000..69c83fe --- /dev/null +++ b/src/libs/graphics/sdl/scalemmx.h @@ -0,0 +1,793 @@ +/* + * Copyright (C) 2005 Alex Volkov (codepro@usa.net) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 SCALEMMX_H_ +#define SCALEMMX_H_ + +#if !defined(SCALE_) +# error Please define SCALE_(name) before including scalemmx.h +#endif + +#if !defined(MSVC_ASM) && !defined(GCC_ASM) +# error Please define target assembler (MSVC_ASM, GCC_ASM) before including scalemmx.h +#endif + +// MMX defaults (no Format param) +#undef SCALE_CMPRGB +#define SCALE_CMPRGB(p1, p2) \ + SCALE_(GetRGBDelta) (p1, p2) + +#undef SCALE_TOYUV +#define SCALE_TOYUV(p) \ + SCALE_(RGBtoYUV) (p) + +#undef SCALE_CMPYUV +#define SCALE_CMPYUV(p1, p2, toler) \ + SCALE_(CmpYUV) (p1, p2, toler) + +#undef SCALE_GETY +#define SCALE_GETY(p) \ + SCALE_(GetPixY) (p) + +// MMX transformation multipliers +extern Uint64 mmx_888to555_mult; +extern Uint64 mmx_Y_mult; +extern Uint64 mmx_U_mult; +extern Uint64 mmx_V_mult; +extern Uint64 mmx_YUV_threshold; + +#define USE_YUV_LOOKUP + +#if defined(MSVC_ASM) +// MSVC inline assembly versions + +#if defined(USE_MOVNTQ) +# define MOVNTQ(addr, val) movntq [addr], val +#else +# define MOVNTQ(addr, val) movq [addr], val +#endif + +#if USE_PREFETCH == INTEL_PREFETCH +// using Intel SSE non-temporal prefetch +# define PREFETCH(addr) prefetchnta [addr] +# define HAVE_PREFETCH +#elif USE_PREFETCH == AMD_PREFETCH +// using AMD 3DNOW! prefetch +# define PREFETCH(addr) prefetch [addr] +# define HAVE_PREFETCH +#else +// no prefetch -- too bad for poor MMX-only souls +# define PREFETCH(addr) +# undef HAVE_PREFETCH +#endif + +#if defined(_MSC_VER) && (_MSC_VER >= 1300) +# pragma warning( disable : 4799 ) +#endif + +static inline void +SCALE_(PlatInit) (void) +{ + __asm + { + // mm0 will be kept == 0 throughout + // 0 is needed for bytes->words unpack instructions + pxor mm0, mm0 + } +} + +static inline void +SCALE_(PlatDone) (void) +{ + // finish with MMX registers and yield them to FPU + __asm + { + emms + } +} + +#if defined(HAVE_PREFETCH) +static inline void +SCALE_(Prefetch) (const void* p) +{ + __asm + { + mov eax, p + PREFETCH (eax) + } +} + +#else /* Not HAVE_PREFETCH */ + +static inline void +SCALE_(Prefetch) (const void* p) +{ + (void)p; // silence compiler + /* no-op */ +} + +#endif /* HAVE_PREFETCH */ + +// compute the RGB distance squared between 2 pixels +static inline int +SCALE_(GetRGBDelta) (Uint32 pix1, Uint32 pix2) +{ + __asm + { + // load pixels + movd mm1, pix1 + punpcklbw mm1, mm0 + movd mm2, pix2 + punpcklbw mm2, mm0 + // get the difference between RGBA components + psubw mm1, mm2 + // squared and sumed + pmaddwd mm1, mm1 + // finish suming the squares + movq mm2, mm1 + punpckhdq mm2, mm0 + paddd mm1, mm2 + // store result + movd eax, mm1 + } +} + +// retrieve the Y (intensity) component of pixel's YUV +static inline int +SCALE_(GetPixY) (Uint32 pix) +{ + __asm + { + // load pixel + movd mm1, pix + punpcklbw mm1, mm0 + // process + pmaddwd mm1, mmx_Y_mult // RGB * Yvec + movq mm2, mm1 // finish suming + punpckhdq mm2, mm0 // ditto + paddd mm1, mm2 // ditto + // store result + movd eax, mm1 + shr eax, 14 + } +} + +#ifdef USE_YUV_LOOKUP + +// convert pixel RGB vector into YUV representation vector +static inline YUV_VECTOR +SCALE_(RGBtoYUV) (Uint32 pix) +{ + __asm + { + // convert RGB888 to 555 + movd mm1, pix + punpcklbw mm1, mm0 + psrlw mm1, 3 // 8->5 bit + pmaddwd mm1, mmx_888to555_mult // shuffle into the right channel order + movq mm2, mm1 // finish shuffling + punpckhdq mm2, mm0 // ditto + por mm1, mm2 // ditto + + // lookup the YUV vector + movd eax, mm1 + mov eax, [RGB15_to_YUV + eax * 4] + } +} + +// compare 2 pixels with respect to their YUV representations +// tolerance set by toler arg +// returns true: close; false: distant (-gt toler) +static inline bool +SCALE_(CmpYUV) (Uint32 pix1, Uint32 pix2, int toler) +{ + __asm + { + // convert RGB888 to 555 + movd mm1, pix1 + punpcklbw mm1, mm0 + psrlw mm1, 3 // 8->5 bit + movd mm3, pix2 + punpcklbw mm3, mm0 + psrlw mm3, 3 // 8->5 bit + pmaddwd mm1, mmx_888to555_mult // shuffle into the right channel order + movq mm2, mm1 // finish shuffling + pmaddwd mm3, mmx_888to555_mult // shuffle into the right channel order + movq mm4, mm3 // finish shuffling + punpckhdq mm2, mm0 // ditto + por mm1, mm2 // ditto + punpckhdq mm4, mm0 // ditto + por mm3, mm4 // ditto + + // lookup the YUV vector + movd eax, mm1 + movd edx, mm3 + movd mm1, [RGB15_to_YUV + eax * 4] + movq mm4, mm1 + movd mm2, [RGB15_to_YUV + edx * 4] + + // get abs difference between YUV components +#ifdef USE_PSADBW + // we can use PSADBW and save us some grief + psadbw mm1, mm2 + movd edx, mm1 +#else + // no PSADBW -- have to do it the hard way + psubusb mm1, mm2 + psubusb mm2, mm4 + por mm1, mm2 + + // sum the differences + // XXX: technically, this produces a MAX diff of 510 + // but we do not need anything bigger, currently + movq mm2, mm1 + psrlq mm2, 8 + paddusb mm1, mm2 + psrlq mm2, 8 + paddusb mm1, mm2 + movd edx, mm1 + and edx, 0xff +#endif /* USE_PSADBW */ + xor eax, eax + shl edx, 1 + cmp edx, toler + // store result + setle al + } +} + +#else /* Not USE_YUV_LOOKUP */ + +// convert pixel RGB vector into YUV representation vector +static inline YUV_VECTOR +SCALE_(RGBtoYUV) (Uint32 pix) +{ + __asm + { + movd mm1, pix + punpcklbw mm1, mm0 + + movq mm2, mm1 + + // Y vector multiply + pmaddwd mm1, mmx_Y_mult + movq mm4, mm1 + punpckhdq mm4, mm0 + punpckldq mm1, mm0 // clear out the high dword + paddd mm1, mm4 + psrad mm1, 15 + + movq mm3, mm2 + + // U vector multiply + pmaddwd mm2, mmx_U_mult + psrad mm2, 10 + + // V vector multiply + pmaddwd mm3, mmx_V_mult + psrad mm3, 10 + + // load (1|1|1|1) into mm4 + pcmpeqw mm4, mm4 + psrlw mm4, 15 + + packssdw mm3, mm2 + pmaddwd mm3, mm4 + psrad mm3, 5 + + // load (64|64) into mm4 + punpcklwd mm4, mm0 + pslld mm4, 6 + paddd mm3, mm4 + + packssdw mm3, mm1 + packuswb mm3, mm0 + + movd eax, mm3 + } +} + +// compare 2 pixels with respect to their YUV representations +// tolerance set by toler arg +// returns true: close; false: distant (-gt toler) +static inline bool +SCALE_(CmpYUV) (Uint32 pix1, Uint32 pix2, int toler) +{ + __asm + { + movd mm1, pix1 + punpcklbw mm1, mm0 + movd mm2, pix2 + punpcklbw mm2, mm0 + + psubw mm1, mm2 + movq mm2, mm1 + + // Y vector multiply + pmaddwd mm1, mmx_Y_mult + movq mm4, mm1 + punpckhdq mm4, mm0 + paddd mm1, mm4 + // abs() + movq mm4, mm1 + psrad mm4, 31 + pxor mm4, mm1 + psubd mm1, mm4 + + movq mm3, mm2 + + // U vector multiply + pmaddwd mm2, mmx_U_mult + movq mm4, mm2 + punpckhdq mm4, mm0 + paddd mm2, mm4 + // abs() + movq mm4, mm2 + psrad mm4, 31 + pxor mm4, mm2 + psubd mm2, mm4 + + paddd mm1, mm2 + + // V vector multiply + pmaddwd mm3, mmx_V_mult + movq mm4, mm3 + punpckhdq mm3, mm0 + paddd mm3, mm4 + // abs() + movq mm4, mm3 + psrad mm4, 31 + pxor mm4, mm3 + psubd mm3, mm4 + + paddd mm1, mm3 + + movd edx, mm1 + xor eax, eax + shr edx, 14 + cmp edx, toler + // store result + setle al + } +} + +#endif /* USE_YUV_LOOKUP */ + +// Check if 2 pixels are different with respect to their +// YUV representations +// returns 0: close; ~0: distant +static inline int +SCALE_(DiffYUV) (Uint32 yuv1, Uint32 yuv2) +{ + __asm + { + // load YUV pixels + movd mm1, yuv1 + movq mm4, mm1 + movd mm2, yuv2 + // abs difference between channels + psubusb mm1, mm2 + psubusb mm2, mm4 + por mm1, mm2 + // compare to threshold + psubusb mm1, mmx_YUV_threshold + + movd edx, mm1 + // transform eax to 0 or ~0 + xor eax, eax + or edx, edx + setz al + dec eax + } +} + +// bilinear weighted blend of four pixels +// MSVC asm version +static inline void +SCALE_(Blend_bilinear) (const Uint32* row0, const Uint32* row1, + Uint32* dst_p, Uint32 dlen) +{ + __asm + { + // EL0: setup vars + mov ebx, row0 // EL0 + + // EL0: load pixels + movq mm1, [ebx] // EL0 + movq mm2, mm1 // EL0: p[1] -> mm2 + PREFETCH (ebx + 0x80) + punpckhbw mm2, mm0 // EL0: p[1] -> mm2 + mov ebx, row1 + punpcklbw mm1, mm0 // EL0: p[0] -> mm1 + movq mm3, [ebx] + movq mm4, mm3 // EL0: p[3] -> mm4 + movq mm6, mm2 // EL1.1: p[1] -> mm6 + PREFETCH (ebx + 0x80) + punpcklbw mm3, mm0 // EL0: p[2] -> mm3 + movq mm5, mm1 // EL1.1: p[0] -> mm5 + punpckhbw mm4, mm0 // EL0: p[3] -> mm4 + + mov edi, dst_p // EL0 + + // EL1: cache p[0] + 3*(p[1] + p[2]) + p[3] in mm6 + paddw mm6, mm3 // EL1.2: p[1] + p[2] -> mm6 + // EL1: cache p[0] + p[1] + p[2] + p[3] in mm7 + movq mm7, mm6 // EL1.3: p[1] + p[2] -> mm7 + // EL1: cache p[1] + 3*(p[0] + p[3]) + p[2] in mm5 + paddw mm5, mm4 // EL1.2: p[0] + p[3] -> mm5 + psllw mm6, 1 // EL1.4: 2*(p[1] + p[2]) -> mm6 + paddw mm7, mm5 // EL1.4: sum(p[]) -> mm7 + psllw mm5, 1 // EL1.5: 2*(p[0] + p[3]) -> mm5 + paddw mm6, mm7 // EL1.5: p[0] + 3*(p[1] + p[2]) + p[3] -> mm6 + paddw mm5, mm7 // EL1.6: p[1] + 3*(p[0] + p[3]) + p[2] -> mm5 + + // EL2: pixel 0 math -- (9*p[0] + 3*(p[1] + p[2]) + p[3]) / 16 + psllw mm1, 3 // EL2.1: 8*p[0] -> mm1 + paddw mm1, mm6 // EL2.2: 9*p[0] + 3*(p[1] + p[2]) + p[3] -> mm1 + psrlw mm1, 4 // EL2.3: sum[0]/16 -> mm1 + + mov edx, dlen // EL0 + + // EL3: pixel 1 math -- (9*p[1] + 3*(p[0] + p[3]) + p[2]) / 16 + psllw mm2, 3 // EL3.1: 8*p[1] -> mm2 + paddw mm2, mm5 // EL3.2: 9*p[1] + 3*(p[0] + p[3]) + p[2] -> mm2 + psrlw mm2, 4 // EL3.3: sum[1]/16 -> mm5 + + // EL2/3: store pixels 0 & 1 + packuswb mm1, mm2 // EL2/3: pack into bytes + MOVNTQ (edi, mm1) // EL2/3: store 2 pixels + + // EL4: pixel 2 math -- (9*p[2] + 3*(p[0] + p[3]) + p[1]) / 16 + psllw mm3, 3 // EL4.1: 8*p[2] -> mm3 + paddw mm3, mm5 // EL4.2: 9*p[2] + 3*(p[0] + p[3]) + p[1] -> mm3 + psrlw mm3, 4 // EL4.3: sum[2]/16 -> mm3 + + // EL5: pixel 3 math -- (9*p[3] + 3*(p[1] + p[2]) + p[0]) / 16 + psllw mm4, 3 // EL5.1: 8*p[3] -> mm4 + paddw mm4, mm6 // EL5.2: 9*p[3] + 3*(p[1] + p[2]) + p[0] -> mm4 + psrlw mm4, 4 // EL5.3: sum[3]/16 -> mm4 + + // EL4/5: store pixels 2 & 3 + packuswb mm3, mm4 // EL4/5: pack into bytes + MOVNTQ (edi + edx*4, mm3) // EL4/5: store 2 pixels + } +} +// End MSVC_ASM + +#elif defined(GCC_ASM) +// GCC inline assembly versions + +#if defined(USE_MOVNTQ) +# define MOVNTQ(val, addr) "movntq " #val "," #addr +#else +# define MOVNTQ(val, addr) "movq " #val "," #addr +#endif + +#if USE_PREFETCH == INTEL_PREFETCH +// using Intel SSE non-temporal prefetch +# define PREFETCH(addr) "prefetchnta " #addr +#elif USE_PREFETCH == AMD_PREFETCH +// using AMD 3DNOW! prefetch +# define PREFETCH(addr) "prefetch " #addr +#else +// no prefetch -- too bad for poor MMX-only souls +# define PREFETCH(addr) +#endif + +#if defined(__x86_64__) +# define A_REG "rax" +# define D_REG "rdx" +# define CLR_UPPER32(r) "xor " "%%" r "," "%%" r +#else +# define A_REG "eax" +# define D_REG "edx" +# define CLR_UPPER32(r) +#endif + +static inline void +SCALE_(PlatInit) (void) +{ + __asm__ ( + // mm0 will be kept == 0 throughout + // 0 is needed for bytes->words unpack instructions + "pxor %%mm0, %%mm0 \n\t" + + : /* nothing */ + : /* nothing */ + ); +} + +static inline void +SCALE_(PlatDone) (void) +{ + // finish with MMX registers and yield them to FPU + __asm__ ( + "emms \n\t" + : /* nothing */ : /* nothing */ + ); +} + +static inline void +SCALE_(Prefetch) (const void* p) +{ + __asm__ __volatile__ ("" PREFETCH (%0) : /*nothing*/ : "m" (p) ); +} + +// compute the RGB distance squared between 2 pixels +static inline int +SCALE_(GetRGBDelta) (Uint32 pix1, Uint32 pix2) +{ + int res; + + __asm__ ( + // load pixels + "movd %1, %%mm1 \n\t" + "punpcklbw %%mm0, %%mm1 \n\t" + "movd %2, %%mm2 \n\t" + "punpcklbw %%mm0, %%mm2 \n\t" + // get the difference between RGBA components + "psubw %%mm2, %%mm1 \n\t" + // squared and sumed + "pmaddwd %%mm1, %%mm1 \n\t" + // finish suming the squares + "movq %%mm1, %%mm2 \n\t" + "punpckhdq %%mm0, %%mm2 \n\t" + "paddd %%mm2, %%mm1 \n\t" + // store result + "movd %%mm1, %0 \n\t" + + : /*0*/"=rm" (res) + : /*1*/"rm" (pix1), /*2*/"rm" (pix2) + ); + + return res; +} + +// retrieve the Y (intensity) component of pixel's YUV +static inline int +SCALE_(GetPixY) (Uint32 pix) +{ + int ret; + + __asm__ ( + // load pixel + "movd %1, %%mm1 \n\t" + "punpcklbw %%mm0, %%mm1 \n\t" + // process + "pmaddwd %2, %%mm1 \n\t" // R,G,B * Yvec + "movq %%mm1, %%mm2 \n\t" // finish suming + "punpckhdq %%mm0, %%mm2 \n\t" // ditto + "paddd %%mm2, %%mm1 \n\t" // ditto + // store index + "movd %%mm1, %0 \n\t" + + : /*0*/"=r" (ret) + : /*1*/"rm" (pix), /*2*/"m" (mmx_Y_mult) + ); + return ret >> 14; +} + +#ifdef USE_YUV_LOOKUP + +// convert pixel RGB vector into YUV representation vector +static inline YUV_VECTOR +SCALE_(RGBtoYUV) (Uint32 pix) +{ + int i; + + __asm__ ( + // convert RGB888 to 555 + "movd %1, %%mm1 \n\t" + "punpcklbw %%mm0, %%mm1 \n\t" + "psrlw $3, %%mm1 \n\t" // 8->5 bit + "pmaddwd %2, %%mm1 \n\t" // shuffle into the right channel order + "movq %%mm1, %%mm2 \n\t" // finish shuffling + "punpckhdq %%mm0, %%mm2 \n\t" // ditto + "por %%mm2, %%mm1 \n\t" // ditto + "movd %%mm1, %0 \n\t" + + : /*0*/"=rm" (i) + : /*1*/"rm" (pix), /*2*/"m" (mmx_888to555_mult) + ); + return RGB15_to_YUV[i]; +} + +// compare 2 pixels with respect to their YUV representations +// tolerance set by toler arg +// returns true: close; false: distant (-gt toler) +static inline bool +SCALE_(CmpYUV) (Uint32 pix1, Uint32 pix2, int toler) +{ + int delta; + + __asm__ ( + "movd %1, %%mm1 \n\t" + "movd %2, %%mm3 \n\t" + + // convert RGB888 to 555 + // this is somewhat parallelized + "punpcklbw %%mm0, %%mm1 \n\t" + CLR_UPPER32 (A_REG) "\n\t" + "psrlw $3, %%mm1 \n\t" // 8->5 bit + "punpcklbw %%mm0, %%mm3 \n\t" + "psrlw $3, %%mm3 \n\t" // 8->5 bit + "pmaddwd %4, %%mm1 \n\t" // shuffle into the right channel order + "movq %%mm1, %%mm2 \n\t" // finish shuffling + "pmaddwd %4, %%mm3 \n\t" // shuffle into the right channel order + CLR_UPPER32 (D_REG) "\n\t" + "movq %%mm3, %%mm4 \n\t" // finish shuffling + "punpckhdq %%mm0, %%mm2 \n\t" // ditto + "por %%mm2, %%mm1 \n\t" // ditto + "punpckhdq %%mm0, %%mm4 \n\t" // ditto + "por %%mm4, %%mm3 \n\t" // ditto + + // lookup the YUV vector + "movd %%mm1, %%eax \n\t" + "movd %%mm3, %%edx \n\t" + "movd (%3, %%" A_REG ", 4), %%mm1 \n\t" + "movq %%mm1, %%mm4 \n\t" + "movd (%3, %%" D_REG ", 4), %%mm2 \n\t" + + // get abs difference between YUV components +#ifdef USE_PSADBW + // we can use PSADBW and save us some grief + "psadbw %%mm2, %%mm1 \n\t" + "movd %%mm1, %0 \n\t" +#else + // no PSADBW -- have to do it the hard way + "psubusb %%mm2, %%mm1 \n\t" + "psubusb %%mm4, %%mm2 \n\t" + "por %%mm2, %%mm1 \n\t" + + // sum the differences + // technically, this produces a MAX diff of 510 + // but we do not need anything bigger, currently + "movq %%mm1, %%mm2 \n\t" + "psrlq $8, %%mm2 \n\t" + "paddusb %%mm2, %%mm1 \n\t" + "psrlq $8, %%mm2 \n\t" + "paddusb %%mm2, %%mm1 \n\t" + // store intermediate delta + "movd %%mm1, %0 \n\t" + "andl $0xff, %0 \n\t" +#endif /* USE_PSADBW */ + : /*0*/"=rm" (delta) + : /*1*/"rm" (pix1), /*2*/"rm" (pix2), + /*3*/ "r" (RGB15_to_YUV), + /*4*/"m" (mmx_888to555_mult) + : "%" A_REG, "%" D_REG, "cc" + ); + + return (delta << 1) <= toler; +} + +#endif /* USE_YUV_LOOKUP */ + +// Check if 2 pixels are different with respect to their +// YUV representations +// returns 0: close; ~0: distant +static inline int +SCALE_(DiffYUV) (Uint32 yuv1, Uint32 yuv2) +{ + sint32 ret; + + __asm__ ( + // load YUV pixels + "movd %1, %%mm1 \n\t" + "movq %%mm1, %%mm4 \n\t" + "movd %2, %%mm2 \n\t" + // abs difference between channels + "psubusb %%mm2, %%mm1 \n\t" + "psubusb %%mm4, %%mm2 \n\t" + CLR_UPPER32(D_REG) "\n\t" + "por %%mm2, %%mm1 \n\t" + // compare to threshold + "psubusb %3, %%mm1 \n\t" + + "movd %%mm1, %%edx \n\t" + // transform eax to 0 or ~0 + "xor %%" A_REG ", %%" A_REG "\n\t" + "or %%" D_REG ", %%" D_REG "\n\t" + "setz %%al \n\t" + "dec %%" A_REG " \n\t" + + : /*0*/"=a" (ret) + : /*1*/"rm" (yuv1), /*2*/"rm" (yuv2), + /*3*/"m" (mmx_YUV_threshold) + : "%" D_REG, "cc" + ); + return ret; +} + +// Bilinear weighted blend of four pixels +// Function produces 4 blended pixels (in 2x2 matrix) and writes them +// out to the surface +// Last version +static inline void +SCALE_(Blend_bilinear) (const Uint32* row0, const Uint32* row1, + Uint32* dst_p, Uint32 dlen) +{ + __asm__ ( + // EL0: load pixels + "movq %0, %%mm1 \n\t" // EL0 + "movq %%mm1, %%mm2 \n\t" // EL0: p[1] -> mm2 + PREFETCH (0x80%0) "\n\t" + "punpckhbw %%mm0, %%mm2 \n\t" // EL0: p[1] -> mm2 + "punpcklbw %%mm0, %%mm1 \n\t" // EL0: p[0] -> mm1 + "movq %1, %%mm3 \n\t" + "movq %%mm3, %%mm4 \n\t" // EL0: p[3] -> mm4 + "movq %%mm2, %%mm6 \n\t" // EL1.1: p[1] -> mm6 + PREFETCH (0x80%1) "\n\t" + "punpcklbw %%mm0, %%mm3 \n\t" // EL0: p[2] -> mm3 + "movq %%mm1, %%mm5 \n\t" // EL1.1: p[0] -> mm5 + "punpckhbw %%mm0, %%mm4 \n\t" // EL0: p[3] -> mm4 + + // EL1: cache p[0] + 3*(p[1] + p[2]) + p[3] in mm6 + "paddw %%mm3, %%mm6 \n\t" // EL1.2: p[1] + p[2] -> mm6 + // EL1: cache p[0] + p[1] + p[2] + p[3] in mm7 + "movq %%mm6, %%mm7 \n\t" // EL1.3: p[1] + p[2] -> mm7 + // EL1: cache p[1] + 3*(p[0] + p[3]) + p[2] in mm5 + "paddw %%mm4, %%mm5 \n\t" // EL1.2: p[0] + p[3] -> mm5 + "psllw $1, %%mm6 \n\t" // EL1.4: 2*(p[1] + p[2]) -> mm6 + "paddw %%mm5, %%mm7 \n\t" // EL1.4: sum(p[]) -> mm7 + "psllw $1, %%mm5 \n\t" // EL1.5: 2*(p[0] + p[3]) -> mm5 + "paddw %%mm7, %%mm6 \n\t" // EL1.5: p[0] + 3*(p[1] + p[2]) + p[3] -> mm6 + "paddw %%mm7, %%mm5 \n\t" // EL1.6: p[1] + 3*(p[0] + p[3]) + p[2] -> mm5 + + // EL2: pixel 0 math -- (9*p[0] + 3*(p[1] + p[2]) + p[3]) / 16 + "psllw $3, %%mm1 \n\t" // EL2.1: 8*p[0] -> mm1 + "paddw %%mm6, %%mm1 \n\t" // EL2.2: 9*p[0] + 3*(p[1] + p[2]) + p[3] -> mm1 + "psrlw $4, %%mm1 \n\t" // EL2.3: sum[0]/16 -> mm1 + + // EL3: pixel 1 math -- (9*p[1] + 3*(p[0] + p[3]) + p[2]) / 16 + "psllw $3, %%mm2 \n\t" // EL3.1: 8*p[1] -> mm2 + "paddw %%mm5, %%mm2 \n\t" // EL3.2: 9*p[1] + 3*(p[0] + p[3]) + p[2] -> mm5 + "psrlw $4, %%mm2 \n\t" // EL3.3: sum[1]/16 -> mm5 + + // EL2/4: store pixels 0 & 1 + "packuswb %%mm2, %%mm1 \n\t" // EL2/4: pack into bytes + MOVNTQ (%%mm1, (%2)) "\n\t" // EL2/4: store 2 pixels + + // EL4: pixel 2 math -- (9*p[2] + 3*(p[0] + p[3]) + p[1]) / 16 + "psllw $3, %%mm3 \n\t" // EL4.1: 8*p[2] -> mm3 + "paddw %%mm5, %%mm3 \n\t" // EL4.2: 9*p[2] + 3*(p[0] + p[3]) + p[1] -> mm3 + "psrlw $4, %%mm3 \n\t" // EL4.3: sum[2]/16 -> mm3 + + // EL5: pixel 3 math -- (9*p[3] + 3*(p[1] + p[2]) + p[0]) / 16 + "psllw $3, %%mm4 \n\t" // EL5.1: 8*p[3] -> mm4 + "paddw %%mm6, %%mm4 \n\t" // EL5.2: 9*p[3] + 3*(p[1] + p[2]) + p[0] -> mm4 + "psrlw $4, %%mm4 \n\t" // EL5.3: sum[3]/16 -> mm4 + + // EL4/5: store pixels 2 & 3 + "packuswb %%mm4, %%mm3 \n\t" // EL4/5: pack into bytes + MOVNTQ (%%mm3, (%2,%3,4)) "\n\t" // EL4/5: store 2 pixels + + : /* nothing */ + : /*0*/"m" (*row0), /*1*/"m" (*row1), /*2*/"r" (dst_p), + /*3*/"r" ((unsigned long)dlen) /* 'long' is for proper reg alloc on amd64 */ + : "memory" + ); +} + +#undef A_REG +#undef D_REG +#undef CLR_UPPER32 + +#endif // GCC_ASM + +#endif /* SCALEMMX_H_ */ diff --git a/src/libs/graphics/sdl/scalers.c b/src/libs/graphics/sdl/scalers.c new file mode 100644 index 0000000..751dae3 --- /dev/null +++ b/src/libs/graphics/sdl/scalers.c @@ -0,0 +1,289 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "types.h" +#include "libs/graphics/sdl/sdl_common.h" +#include "libs/platform.h" +#include "libs/log.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" +#ifdef USE_PLATFORM_ACCEL +# ifndef __APPLE__ + // MacOS X framework has no SDL_cpuinfo.h for some reason +# include SDL_INCLUDE(SDL_cpuinfo.h) +# endif +# ifdef MMX_ASM +# include "2xscalers_mmx.h" +# endif /* MMX_ASM */ +#endif /* USE_PLATFORM_ACCEL */ + +#if SDL_MAJOR_VERSION == 1 +#define SDL_HasMMX SDL_HasMMXExt +#endif + +typedef enum +{ + SCALEPLAT_NULL = PLATFORM_NULL, + SCALEPLAT_C = PLATFORM_C, + SCALEPLAT_MMX = PLATFORM_MMX, + SCALEPLAT_SSE = PLATFORM_SSE, + SCALEPLAT_3DNOW = PLATFORM_3DNOW, + SCALEPLAT_ALTIVEC = PLATFORM_ALTIVEC, + + SCALEPLAT_C_RGBA, + SCALEPLAT_C_BGRA, + SCALEPLAT_C_ARGB, + SCALEPLAT_C_ABGR, + +} Scale_PlatType_t; + + +// RGB -> YUV transformation +// the RGB vector is multiplied by the transformation matrix +// to get the YUV vector +#if 0 +// original table -- not used +const int YUV_matrix[3][3] = +{ + /* Y U V */ + /* R */ {0.2989, -0.1687, 0.5000}, + /* G */ {0.5867, -0.3312, -0.4183}, + /* B */ {0.1144, 0.5000, -0.0816} +}; +#else +// scaled up by a 2^14 factor, with Y doubled +const int YUV_matrix[3][3] = +{ + /* Y U V */ + /* R */ { 9794, -2764, 8192}, + /* G */ {19224, -5428, -6853}, + /* B */ { 3749, 8192, -1339} +}; +#endif + +// pre-computed transformations for 8 bits per channel +int RGB_to_YUV[/*RGB*/ 3][/*YUV*/ 3][ /*mult-res*/ 256]; +sint16 dRGB_to_dYUV[/*RGB*/ 3][/*YUV*/ 3][ /*mult-res*/ 512]; + +// pre-computed transformations for RGB555 +YUV_VECTOR RGB15_to_YUV[0x8000]; + +PLATFORM_TYPE force_platform = PLATFORM_NULL; +Scale_PlatType_t Scale_Platform = SCALEPLAT_NULL; + + +// pre-compute the RGB->YUV transformations +void +Scale_Init (void) +{ + int i1, i2, i3; + + for (i1 = 0; i1 < 3; i1++) // enum R,G,B + for (i2 = 0; i2 < 3; i2++) // enum Y,U,V + for (i3 = 0; i3 < 256; i3++) // enum possible channel vals + { + RGB_to_YUV[i1][i2][i3] = + (YUV_matrix[i1][i2] * i3) >> 14; + } + + for (i1 = 0; i1 < 3; i1++) // enum R,G,B + for (i2 = 0; i2 < 3; i2++) // enum Y,U,V + for (i3 = -255; i3 < 256; i3++) // enum possible channel delta vals + { + dRGB_to_dYUV[i1][i2][i3 + 255] = + (YUV_matrix[i1][i2] * i3) >> 14; + } + + for (i1 = 0; i1 < 32; ++i1) + for (i2 = 0; i2 < 32; ++i2) + for (i3 = 0; i3 < 32; ++i3) + { + int y, u, v; + // adding upper bits halved for error correction + int r = (i1 << 3) | (i1 >> 3); + int g = (i2 << 3) | (i2 >> 3); + int b = (i3 << 3) | (i3 >> 3); + + y = ( r * YUV_matrix[YUV_XFORM_R][YUV_XFORM_Y] + + g * YUV_matrix[YUV_XFORM_G][YUV_XFORM_Y] + + b * YUV_matrix[YUV_XFORM_B][YUV_XFORM_Y] + ) >> 15; // we dont need Y doubled, need Y to fit 8 bits + + // U and V are half the importance of Y + u = 64+(( r * YUV_matrix[YUV_XFORM_R][YUV_XFORM_U] + + g * YUV_matrix[YUV_XFORM_G][YUV_XFORM_U] + + b * YUV_matrix[YUV_XFORM_B][YUV_XFORM_U] + ) >> 15); // halved + + v = 64+(( r * YUV_matrix[YUV_XFORM_R][YUV_XFORM_V] + + g * YUV_matrix[YUV_XFORM_G][YUV_XFORM_V] + + b * YUV_matrix[YUV_XFORM_B][YUV_XFORM_V] + ) >> 15); // halved + + RGB15_to_YUV[(i1 << 10) | (i2 << 5) | i3] = (y << 16) | (u << 8) | v; + } +} + + +// expands the given rectangle in all directions by 'expansion' +// guarded by 'limits' +void +Scale_ExpandRect (SDL_Rect* rect, int expansion, const SDL_Rect* limits) +{ + if (rect->x - expansion >= limits->x) + { + rect->w += expansion; + rect->x -= expansion; + } + else + { + rect->w += rect->x - limits->x; + rect->x = limits->x; + } + + if (rect->y - expansion >= limits->y) + { + rect->h += expansion; + rect->y -= expansion; + } + else + { + rect->h += rect->y - limits->y; + rect->y = limits->y; + } + + if (rect->x + rect->w + expansion <= limits->w) + rect->w += expansion; + else + rect->w = limits->w - rect->x; + + if (rect->y + rect->h + expansion <= limits->h) + rect->h += expansion; + else + rect->h = limits->h - rect->y; +} + + +// Platform+Scaler function lookups + +typedef struct +{ + Scale_PlatType_t platform; + const Scale_FuncDef_t* funcdefs; +} Scale_PlatDef_t; + + +static const Scale_PlatDef_t +Scale_PlatDefs[] = +{ +#if defined(MMX_ASM) + {SCALEPLAT_SSE, Scale_SSE_Functions}, + {SCALEPLAT_3DNOW, Scale_3DNow_Functions}, + {SCALEPLAT_MMX, Scale_MMX_Functions}, +#endif /* MMX_ASM */ + // Default + {SCALEPLAT_NULL, Scale_C_Functions} +}; + + +TFB_ScaleFunc +Scale_PrepPlatform (int flags, const SDL_PixelFormat* fmt) +{ + const Scale_PlatDef_t* pdef; + const Scale_FuncDef_t* fdef; + + (void)flags; + + Scale_Platform = SCALEPLAT_NULL; + + // first match wins + // add better platform techs to the top +#ifdef MMX_ASM + if ( (!force_platform && (SDL_HasSSE () || SDL_HasMMX ())) + || force_platform == PLATFORM_SSE) + { + log_add (log_Info, "Screen scalers are using SSE/MMX-Ext/MMX code"); + Scale_Platform = SCALEPLAT_SSE; + + Scale_SSE_PrepPlatform (fmt); + } + else + if ( (!force_platform && SDL_HasAltiVec ()) + || force_platform == PLATFORM_ALTIVEC) + { + log_add (log_Info, "Screen scalers would use AltiVec code " + "if someone actually wrote it"); + //Scale_Platform = SCALEPLAT_ALTIVEC; + } + else + if ( (!force_platform && SDL_Has3DNow ()) + || force_platform == PLATFORM_3DNOW) + { + log_add (log_Info, "Screen scalers are using 3DNow/MMX code"); + Scale_Platform = SCALEPLAT_3DNOW; + + Scale_3DNow_PrepPlatform (fmt); + } + else + if ( (!force_platform && SDL_HasMMX ()) + || force_platform == PLATFORM_MMX) + { + log_add (log_Info, "Screen scalers are using MMX code"); + Scale_Platform = SCALEPLAT_MMX; + + Scale_MMX_PrepPlatform (fmt); + } +#endif + + if (Scale_Platform == SCALEPLAT_NULL) + { // Plain C versions + if (fmt->Rmask == 0xff000000 && fmt->Bmask == 0x0000ff00) + Scale_Platform = SCALEPLAT_C_RGBA; + else if (fmt->Rmask == 0x00ff0000 && fmt->Bmask == 0x000000ff) + Scale_Platform = SCALEPLAT_C_ARGB; + else if (fmt->Rmask == 0x0000ff00 && fmt->Bmask == 0xff000000) + Scale_Platform = SCALEPLAT_C_BGRA; + else if (fmt->Rmask == 0x000000ff && fmt->Bmask == 0x00ff0000) + Scale_Platform = SCALEPLAT_C_ABGR; + else + { // use slowest default + log_add (log_Warning, "Scale_PrepPlatform(): unknown masks " + "(Red %08x, Blue %08x)", fmt->Rmask, fmt->Bmask); + Scale_Platform = SCALEPLAT_C; + } + + if (Scale_Platform == SCALEPLAT_C) + log_add (log_Info, "Screen scalers are using slow generic C code"); + else + log_add (log_Info, "Screen scalers are using optimized C code"); + } + + // Lookup the scaling function + // First find the right platform + for (pdef = Scale_PlatDefs; + pdef->platform != Scale_Platform && pdef->platform != SCALEPLAT_NULL; + ++pdef) + ; + // Next find the right function + for (fdef = pdef->funcdefs; + (flags & fdef->flag) != fdef->flag; + ++fdef) + ; + + return fdef->func; +} + diff --git a/src/libs/graphics/sdl/scalers.h b/src/libs/graphics/sdl/scalers.h new file mode 100644 index 0000000..cd36fe5 --- /dev/null +++ b/src/libs/graphics/sdl/scalers.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 SCALERS_H_ +#define SCALERS_H_ + +void Scale_Init (void); + +typedef void (* TFB_ScaleFunc) (SDL_Surface *src, SDL_Surface *dst, + SDL_Rect *r); + +TFB_ScaleFunc Scale_PrepPlatform (int flags, const SDL_PixelFormat* fmt); + +#endif /* SCALERS_H_ */ diff --git a/src/libs/graphics/sdl/sdl1_common.c b/src/libs/graphics/sdl/sdl1_common.c new file mode 100644 index 0000000..5c675e1 --- /dev/null +++ b/src/libs/graphics/sdl/sdl1_common.c @@ -0,0 +1,247 @@ +//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 "sdl_common.h" +#include "opengl.h" +#include "pure.h" +#include "primitives.h" +#include "options.h" +#include "uqmversion.h" +#include "libs/graphics/drawcmd.h" +#include "libs/graphics/dcqueue.h" +#include "libs/graphics/cmap.h" +#include "libs/input/sdl/input.h" + // for ProcessInputEvent() +#include "libs/graphics/bbox.h" +#include "port.h" +#include "libs/uio.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "libs/vidlib.h" + +#if SDL_MAJOR_VERSION == 1 + +static void TFB_PreQuit (void); + +void +TFB_PreInit (void) +{ + log_add (log_Info, "Initializing base SDL functionality."); + log_add (log_Info, "Using SDL version %d.%d.%d (compiled with " + "%d.%d.%d)", SDL_Linked_Version ()->major, + SDL_Linked_Version ()->minor, SDL_Linked_Version ()->patch, + SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL); +#if 0 + if (SDL_Linked_Version ()->major != SDL_MAJOR_VERSION || + SDL_Linked_Version ()->minor != SDL_MINOR_VERSION || + SDL_Linked_Version ()->patch != SDL_PATCHLEVEL) { + log_add (log_Warning, "The used SDL library is not the same version " + "as the one used to compile The Ur-Quan Masters with! " + "If you experience any crashes, this would be an excellent " + "suspect."); + } +#endif + + if ((SDL_Init (SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE) == -1)) + { + log_add (log_Fatal, "Could not initialize SDL: %s.", SDL_GetError ()); + exit (EXIT_FAILURE); + } + + atexit (TFB_PreQuit); +} + +static void +TFB_PreQuit (void) +{ + SDL_Quit (); +} + +int +TFB_ReInitGraphics (int driver, int flags, int width, int height) +{ + int result; + int togglefullscreen = 0; + char caption[200]; + + if (GfxFlags == (flags ^ TFB_GFXFLAGS_FULLSCREEN) && + driver == GraphicsDriver && + width == ScreenWidthActual && height == ScreenHeightActual) + { + togglefullscreen = 1; + } + + GfxFlags = flags; + + if (driver == TFB_GFXDRIVER_SDL_OPENGL) + { +#ifdef HAVE_OPENGL + result = TFB_GL_ConfigureVideo (driver, flags, width, height, + togglefullscreen); +#else + driver = TFB_GFXDRIVER_SDL_PURE; + log_add (log_Warning, "OpenGL support not compiled in," + " so using pure SDL driver"); + result = TFB_Pure_ConfigureVideo (driver, flags, width, height, + togglefullscreen); +#endif + } + else + { + result = TFB_Pure_ConfigureVideo (driver, flags, width, height, + togglefullscreen); + } + + sprintf (caption, "The Ur-Quan Masters v%d.%d.%d%s", + UQM_MAJOR_VERSION, UQM_MINOR_VERSION, + UQM_PATCH_VERSION, UQM_EXTRA_VERSION); + SDL_WM_SetCaption (caption, NULL); + + if (flags & TFB_GFXFLAGS_FULLSCREEN) + SDL_ShowCursor (SDL_DISABLE); + else + SDL_ShowCursor (SDL_ENABLE); + + return result; +} + +bool +TFB_SetGamma (float gamma) +{ + return (SDL_SetGamma (gamma, gamma, gamma) == 0); +} + +int +TFB_HasSurfaceAlphaMod (SDL_Surface *surface) +{ + if (!surface) + { + return 0; + } + return (surface->flags & SDL_SRCALPHA) ? 1 : 0; +} + +int +TFB_GetSurfaceAlphaMod (SDL_Surface *surface, Uint8 *alpha) +{ + if (!surface || !surface->format || !alpha) + { + return -1; + } + if (surface->flags & SDL_SRCALPHA) + { + *alpha = surface->format->alpha; + } + else + { + *alpha = 255; + } + return 0; +} + +int +TFB_SetSurfaceAlphaMod (SDL_Surface *surface, Uint8 alpha) +{ + if (!surface) + { + return -1; + } + return SDL_SetAlpha (surface, SDL_SRCALPHA, alpha); +} + +int +TFB_DisableSurfaceAlphaMod (SDL_Surface *surface) +{ + if (!surface) + { + return -1; + } + return SDL_SetAlpha (surface, 0, 255); +} + +int +TFB_GetColorKey (SDL_Surface *surface, Uint32 *key) +{ + if (surface && surface->format && key && + (surface->flags & SDL_SRCCOLORKEY)) + { + *key = surface->format->colorkey; + return 0; + } + return -1; +} + +int +TFB_SetColorKey (SDL_Surface *surface, Uint32 key, int rleaccel) +{ + if (!surface) + { + return -1; + } + return SDL_SetColorKey (surface, SDL_SRCCOLORKEY | (rleaccel ? SDL_RLEACCEL : 0), key); +} + +int +TFB_DisableColorKey (SDL_Surface *surface) +{ + if (!surface) + { + return -1; + } + return SDL_SetColorKey (surface, 0, 0); +} + +int +TFB_SetColors (SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors) +{ + return SDL_SetColors (surface, colors, firstcolor, ncolors); +} + +int +TFB_SupportsHardwareScaling (void) +{ +#ifdef HAVE_OPENGL + return 1; +#else + return 0; +#endif +} + +static SDL_Surface * +Create_Screen (SDL_Surface *templat, int w, int h) +{ + SDL_Surface *newsurf = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, + templat->format->BitsPerPixel, + templat->format->Rmask, templat->format->Gmask, + templat->format->Bmask, 0); + if (newsurf == 0) { + log_add (log_Error, "Couldn't create screen buffes: %s", + SDL_GetError()); + } + return newsurf; +} + +int +SDL1_ReInit_Screen (SDL_Surface **screen, SDL_Surface *templat, int w, int h) +{ + UnInit_Screen (screen); + *screen = Create_Screen (templat, w, h); + + return *screen == 0 ? -1 : 0; +} +#endif diff --git a/src/libs/graphics/sdl/sdl2_common.c b/src/libs/graphics/sdl/sdl2_common.c new file mode 100644 index 0000000..3eaf7af --- /dev/null +++ b/src/libs/graphics/sdl/sdl2_common.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 "sdl_common.h" +#include "opengl.h" +#include "pure.h" +#include "primitives.h" +#include "options.h" +#include "uqmversion.h" +#include "libs/graphics/drawcmd.h" +#include "libs/graphics/dcqueue.h" +#include "libs/graphics/cmap.h" +#include "libs/input/sdl/input.h" + // for ProcessInputEvent() +#include "libs/graphics/bbox.h" +#include "port.h" +#include "libs/uio.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "libs/vidlib.h" + +#if SDL_MAJOR_VERSION > 1 + +static void TFB_PreQuit (void); + +void +TFB_PreInit (void) +{ + SDL_version compiled, linked; + SDL_VERSION(&compiled); + SDL_GetVersion(&linked); + log_add (log_Info, "Initializing base SDL functionality."); + log_add (log_Info, "Using SDL version %d.%d.%d (compiled with " + "%d.%d.%d)", linked.major, linked.minor, linked.patch, + compiled.major, compiled.minor, compiled.patch); +#if 0 + if (compiled.major != linked.major || compiled.minor != linked.minor || + compiled.patch != linked.patch) + { + log_add (log_Warning, "The used SDL library is not the same version " + "as the one used to compile The Ur-Quan Masters with! " + "If you experience any crashes, this would be an excellent " + "suspect."); + } +#endif + + if ((SDL_Init (SDL_INIT_VIDEO) == -1)) + { + log_add (log_Fatal, "Could not initialize SDL: %s.", SDL_GetError ()); + exit (EXIT_FAILURE); + } + + atexit (TFB_PreQuit); +} + +static void +TFB_PreQuit (void) +{ + SDL_Quit (); +} + +int +TFB_ReInitGraphics (int driver, int flags, int width, int height) +{ + int result; + int togglefullscreen = 0; + + if (GfxFlags == (flags ^ TFB_GFXFLAGS_FULLSCREEN) && + driver == GraphicsDriver && + width == ScreenWidthActual && height == ScreenHeightActual) + { + togglefullscreen = 1; + } + + GfxFlags = flags; + + result = TFB_Pure_ConfigureVideo (TFB_GFXDRIVER_SDL_PURE, flags, + width, height, togglefullscreen); + + if (flags & TFB_GFXFLAGS_FULLSCREEN) + SDL_ShowCursor (SDL_DISABLE); + else + SDL_ShowCursor (SDL_ENABLE); + + return result; +} + +bool +TFB_SetGamma (float gamma) +{ + log_add (log_Warning, "Custom gamma correction is not available in the SDL2 engine."); + return 0; +} + +int +TFB_HasSurfaceAlphaMod (SDL_Surface *surface) +{ + SDL_BlendMode blend_mode; + if (!surface) + { + return 0; + } + if (SDL_GetSurfaceBlendMode (surface, &blend_mode) != 0) + { + return 0; + } + return blend_mode == SDL_BLENDMODE_BLEND; +} + +int +TFB_GetSurfaceAlphaMod (SDL_Surface *surface, Uint8 *alpha) +{ + SDL_BlendMode blend_mode; + if (!surface || !alpha) + { + return -1; + } + if (SDL_GetSurfaceBlendMode (surface, &blend_mode) == 0) + { + if (blend_mode == SDL_BLENDMODE_BLEND) + { + return SDL_GetSurfaceAlphaMod (surface, alpha); + } + } + *alpha = 255; + return 0; +} + +int +TFB_SetSurfaceAlphaMod (SDL_Surface *surface, Uint8 alpha) +{ + int result; + if (!surface) + { + return -1; + } + result = SDL_SetSurfaceBlendMode (surface, SDL_BLENDMODE_BLEND); + if (result == 0) + { + result = SDL_SetSurfaceAlphaMod (surface, alpha); + } + return result; +} + +int +TFB_DisableSurfaceAlphaMod (SDL_Surface *surface) +{ + if (!surface) + { + return -1; + } + SDL_SetSurfaceAlphaMod (surface, 255); + return SDL_SetSurfaceBlendMode (surface, SDL_BLENDMODE_NONE); +} + +int +TFB_GetColorKey (SDL_Surface *surface, Uint32 *key) +{ + if (!surface || !key) + { + return -1; + } + return SDL_GetColorKey (surface, key); +} + +int +TFB_SetColorKey (SDL_Surface *surface, Uint32 key, int rleaccel) +{ + if (!surface) + { + return -1; + } + SDL_SetSurfaceRLE (surface, rleaccel); + return SDL_SetColorKey (surface, SDL_TRUE, key); +} + +int +TFB_DisableColorKey (SDL_Surface *surface) +{ + if (!surface) + { + return -1; + } + return SDL_SetColorKey (surface, SDL_FALSE, 0); +} + +int +TFB_SetColors (SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors) +{ + if (!surface || !colors || !surface->format || !surface->format->palette) + { + return 0; + } + if (SDL_SetPaletteColors (surface->format->palette, colors, firstcolor, ncolors) == 0) + { + // SDL2's success code is opposite from SDL1's SDL_SetColors + return 1; + } + return 0; +} + +int +TFB_SupportsHardwareScaling (void) +{ + return 1; +} +#endif diff --git a/src/libs/graphics/sdl/sdl2_pure.c b/src/libs/graphics/sdl/sdl2_pure.c new file mode 100644 index 0000000..c6503db --- /dev/null +++ b/src/libs/graphics/sdl/sdl2_pure.c @@ -0,0 +1,465 @@ +//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 "pure.h" +#include "libs/graphics/bbox.h" +#include "libs/log.h" +#include "scalers.h" +#include "uqmversion.h" + +#if SDL_MAJOR_VERSION > 1 + +typedef struct tfb_sdl2_screeninfo_s { + SDL_Surface *scaled; + SDL_Texture *texture; + BOOLEAN dirty, active; + SDL_Rect updated; +} TFB_SDL2_SCREENINFO; + +static TFB_SDL2_SCREENINFO SDL2_Screens[TFB_GFX_NUMSCREENS]; + +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static const char *rendererBackend = NULL; + +static int ScreenFilterMode; + +static TFB_ScaleFunc scaler = NULL; + +#if SDL_BYTEORDER == SDL_BIG_ENDIAN +#define A_MASK 0xff000000 +#define B_MASK 0x00ff0000 +#define G_MASK 0x0000ff00 +#define R_MASK 0x000000ff +#else +#define A_MASK 0x000000ff +#define B_MASK 0x0000ff00 +#define G_MASK 0x00ff0000 +#define R_MASK 0xff000000 +#endif + +static void TFB_SDL2_Preprocess (int force_full_redraw, int transition_amount, int fade_amount); +static void TFB_SDL2_Postprocess (void); +static void TFB_SDL2_UploadTransitionScreen (void); +static void TFB_SDL2_Scaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect); +static void TFB_SDL2_Unscaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect); +static void TFB_SDL2_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect); + +static TFB_GRAPHICS_BACKEND sdl2_scaled_backend = { + TFB_SDL2_Preprocess, + TFB_SDL2_Postprocess, + TFB_SDL2_UploadTransitionScreen, + TFB_SDL2_Scaled_ScreenLayer, + TFB_SDL2_ColorLayer }; + +static TFB_GRAPHICS_BACKEND sdl2_unscaled_backend = { + TFB_SDL2_Preprocess, + TFB_SDL2_Postprocess, + TFB_SDL2_UploadTransitionScreen, + TFB_SDL2_Unscaled_ScreenLayer, + TFB_SDL2_ColorLayer }; + +static SDL_Surface * +Create_Screen (int w, int h) +{ + SDL_Surface *newsurf = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, + 32, R_MASK, G_MASK, B_MASK, 0); + if (newsurf == 0) + { + log_add (log_Error, "Couldn't create screen buffers: %s", + SDL_GetError()); + } + return newsurf; +} + +static int +ReInit_Screen (SDL_Surface **screen, int w, int h) +{ + if (*screen) + SDL_FreeSurface (*screen); + *screen = Create_Screen (w, h); + + return *screen == 0 ? -1 : 0; +} + +static int +FindBestRenderDriver (void) +{ + int i, n; + if (!rendererBackend) { + /* If the user has no preference, just let SDL2 choose */ + return -1; + } + n = SDL_GetNumRenderDrivers (); + log_add (log_Info, "Searching for render driver \"%s\".", rendererBackend); + + for (i = 0; i < n; i++) { + SDL_RendererInfo info; + if (SDL_GetRenderDriverInfo (i, &info) < 0) { + continue; + } + if (!strcmp(info.name, rendererBackend)) { + return i; + } + log_add (log_Info, "Skipping render driver \"%s\"", info.name); + } + /* We did not find any accelerated drivers that weren't D3D9. + * Return -1 to ask SDL2 to do its best. */ + log_add (log_Info, "Render driver \"%s\" not available, using system default", rendererBackend); + return -1; +} + +int +TFB_Pure_ConfigureVideo (int driver, int flags, int width, int height, int togglefullscreen) +{ + int i; + GraphicsDriver = driver; + (void) togglefullscreen; + if (window == NULL) + { + SDL_RendererInfo info; + char caption[200]; + + sprintf (caption, "The Ur-Quan Masters v%d.%d.%d%s", + UQM_MAJOR_VERSION, UQM_MINOR_VERSION, + UQM_PATCH_VERSION, UQM_EXTRA_VERSION); + window = SDL_CreateWindow (caption, + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + width, height, 0); + if (flags & TFB_GFXFLAGS_FULLSCREEN) + { + /* If we create the window fullscreen, it will have + * no icon if and when it becomes windowed. */ + SDL_SetWindowFullscreen (window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + if (!window) + { + return -1; + } + renderer = SDL_CreateRenderer (window, FindBestRenderDriver (), 0); + if (!renderer) + { + return -1; + } + if (SDL_GetRendererInfo (renderer, &info) == 0) + { + log_add (log_Info, "SDL2 renderer '%s' selected.\n", info.name); + } + else + { + log_add (log_Info, "SDL2 renderer had no name."); + } + SDL_RenderSetLogicalSize (renderer, ScreenWidth, ScreenHeight); + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + SDL2_Screens[i].scaled = NULL; + SDL2_Screens[i].texture = NULL; + SDL2_Screens[i].dirty = TRUE; + SDL2_Screens[i].active = TRUE; + if (0 != ReInit_Screen (&SDL_Screens[i], ScreenWidth, ScreenHeight)) + { + return -1; + } + } + SDL2_Screens[1].active = FALSE; + SDL_Screen = SDL_Screens[0]; + TransitionScreen = SDL_Screens[2]; + format_conv_surf = SDL_CreateRGBSurface(SDL_SWSURFACE, 0, 0, + 32, R_MASK, G_MASK, B_MASK, A_MASK); + if (!format_conv_surf) + { + return -1; + } + } + else + { + if (flags & TFB_GFXFLAGS_FULLSCREEN) + { + SDL_SetWindowFullscreen (window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + else + { + SDL_SetWindowFullscreen (window, 0); + SDL_SetWindowSize (window, width, height); + } + } + + if (GfxFlags & TFB_GFXFLAGS_SCALE_ANY) + { + /* Linear scaling */ + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); + } + else + { + /* Nearest-neighbor scaling */ + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); + } + + if (GfxFlags & TFB_GFXFLAGS_SCALE_SOFT_ONLY) + { + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + if (!SDL2_Screens[i].active) + { + continue; + } + if (0 != ReInit_Screen(&SDL2_Screens[i].scaled, + ScreenWidth * 2, ScreenHeight * 2)) + { + return -1; + } + if (SDL2_Screens[i].texture) + { + SDL_DestroyTexture (SDL2_Screens[i].texture); + SDL2_Screens[i].texture = NULL; + } + SDL2_Screens[i].texture = SDL_CreateTexture (renderer, SDL_PIXELFORMAT_RGBX8888, SDL_TEXTUREACCESS_STREAMING, ScreenWidth * 2, ScreenHeight * 2); + SDL_LockSurface (SDL2_Screens[i].scaled); + SDL_UpdateTexture (SDL2_Screens[i].texture, NULL, SDL2_Screens[i].scaled->pixels, SDL2_Screens[i].scaled->pitch); + SDL_UnlockSurface (SDL2_Screens[i].scaled); + } + scaler = Scale_PrepPlatform (flags, SDL2_Screens[0].scaled->format); + graphics_backend = &sdl2_scaled_backend; + } + else + { + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + if (SDL2_Screens[i].scaled) + { + SDL_FreeSurface (SDL2_Screens[i].scaled); + SDL2_Screens[i].scaled = NULL; + } + if (SDL2_Screens[i].texture) + { + SDL_DestroyTexture (SDL2_Screens[i].texture); + SDL2_Screens[i].texture = NULL; + } + SDL2_Screens[i].texture = SDL_CreateTexture (renderer, SDL_PIXELFORMAT_RGBX8888, SDL_TEXTUREACCESS_STREAMING, ScreenWidth, ScreenHeight); + SDL_LockSurface (SDL_Screens[i]); + SDL_UpdateTexture (SDL2_Screens[i].texture, NULL, SDL_Screens[i]->pixels, SDL_Screens[i]->pitch); + SDL_UnlockSurface (SDL_Screens[i]); + } + scaler = NULL; + graphics_backend = &sdl2_unscaled_backend; + } + + /* We succeeded, so alter the screen size to our new sizes */ + ScreenWidthActual = width; + ScreenHeightActual = height; + + return 0; +} + +int +TFB_Pure_InitGraphics (int driver, int flags, const char *renderer, int width, int height) +{ + log_add (log_Info, "Initializing SDL."); + log_add (log_Info, "SDL initialized."); + log_add (log_Info, "Initializing Screen."); + + ScreenWidth = 320; + ScreenHeight = 240; + rendererBackend = renderer; + + if (TFB_Pure_ConfigureVideo (driver, flags, width, height, 0)) + { + log_add (log_Fatal, "Could not initialize video: %s", + SDL_GetError ()); + exit (EXIT_FAILURE); + } + + /* Initialize scalers (let them precompute whatever) */ + Scale_Init (); + + return 0; +} + +void +TFB_Pure_UninitGraphics (void) +{ + if (renderer) { + SDL_DestroyRenderer (renderer); + } + if (window) { + SDL_DestroyWindow (window); + } +} + +static void +TFB_SDL2_UploadTransitionScreen (void) +{ + SDL2_Screens[TFB_SCREEN_TRANSITION].updated.x = 0; + SDL2_Screens[TFB_SCREEN_TRANSITION].updated.y = 0; + SDL2_Screens[TFB_SCREEN_TRANSITION].updated.w = ScreenWidth; + SDL2_Screens[TFB_SCREEN_TRANSITION].updated.h = ScreenHeight; + SDL2_Screens[TFB_SCREEN_TRANSITION].dirty = TRUE; +} + +static void +TFB_SDL2_UpdateTexture (SDL_Texture *dest, SDL_Surface *src, SDL_Rect *rect) +{ + char *srcBytes; + SDL_LockSurface (src); + srcBytes = src->pixels; + if (rect) + { + /* SDL2 screen surfaces are always 32bpp */ + srcBytes += (src->pitch * rect->y) + (rect->x * 4); + } + /* 2020-08-02: At time of writing, the documentation for + * SDL_UpdateTexture states this: "If the texture is intended to be + * updated often, it is preferred to create the texture as streaming + * and use [SDL_LockTexture and SDL_UnlockTexture]." Unfortunately, + * SDL_LockTexture will corrupt driver-space memory in the 32-bit + * Direct3D 9 driver on Intel Integrated graphics chips, resulting + * in an immediate crash with no detectable errors from the API up + * to that point. + * + * We also cannot simply forbid the Direct3D driver outright, because + * pre-Windows 10 machines appear to fail to initialize D3D11 even + * while claiming to support it. + * + * These bugs may be fixed in the future, but in the meantime we + * rely on this allegedly slower but definitely more reliable + * function. */ + SDL_UpdateTexture (dest, rect, srcBytes, src->pitch); + SDL_UnlockSurface (src); +} + +static void +TFB_SDL2_ScanLines (void) +{ + int y; + SDL_SetRenderDrawColor (renderer, 0, 0, 0, 64); + SDL_SetRenderDrawBlendMode (renderer, SDL_BLENDMODE_BLEND); + SDL_RenderSetLogicalSize (renderer, ScreenWidth * 2, ScreenHeight * 2); + for (y = 0; y < ScreenHeight * 2; y += 2) + { + SDL_RenderDrawLine (renderer, 0, y, ScreenWidth * 2 - 1, y); + } + SDL_RenderSetLogicalSize (renderer, ScreenWidth, ScreenHeight); +} + +static void +TFB_SDL2_Preprocess (int force_full_redraw, int transition_amount, int fade_amount) +{ + (void) transition_amount; + (void) fade_amount; + + if (force_full_redraw == TFB_REDRAW_YES) + { + SDL2_Screens[TFB_SCREEN_MAIN].updated.x = 0; + SDL2_Screens[TFB_SCREEN_MAIN].updated.y = 0; + SDL2_Screens[TFB_SCREEN_MAIN].updated.w = ScreenWidth; + SDL2_Screens[TFB_SCREEN_MAIN].updated.h = ScreenHeight; + SDL2_Screens[TFB_SCREEN_MAIN].dirty = TRUE; + } + else if (TFB_BBox.valid) + { + SDL2_Screens[TFB_SCREEN_MAIN].updated.x = TFB_BBox.region.corner.x; + SDL2_Screens[TFB_SCREEN_MAIN].updated.y = TFB_BBox.region.corner.y; + SDL2_Screens[TFB_SCREEN_MAIN].updated.w = TFB_BBox.region.extent.width; + SDL2_Screens[TFB_SCREEN_MAIN].updated.h = TFB_BBox.region.extent.height; + SDL2_Screens[TFB_SCREEN_MAIN].dirty = TRUE; + } + + SDL_SetRenderDrawBlendMode (renderer, SDL_BLENDMODE_NONE); + SDL_SetRenderDrawColor (renderer, 0, 0, 0, 255); + SDL_RenderClear (renderer); +} + +static void +TFB_SDL2_Unscaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect) +{ + SDL_Texture *texture = SDL2_Screens[screen].texture; + if (SDL2_Screens[screen].dirty) + { + TFB_SDL2_UpdateTexture (texture, SDL_Screens[screen], &SDL2_Screens[screen].updated); + } + if (a == 255) + { + SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_NONE); + } + else + { + SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_BLEND); + SDL_SetTextureAlphaMod (texture, a); + } + SDL_RenderCopy (renderer, texture, rect, rect); +} + +static void +TFB_SDL2_Scaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect) +{ + SDL_Texture *texture = SDL2_Screens[screen].texture; + SDL_Rect srcRect, *pSrcRect = NULL; + if (SDL2_Screens[screen].dirty) + { + SDL_Surface *src = SDL2_Screens[screen].scaled; + SDL_Rect scaled_update = SDL2_Screens[screen].updated; + scaler (SDL_Screens[screen], src, &SDL2_Screens[screen].updated); + scaled_update.x *= 2; + scaled_update.y *= 2; + scaled_update.w *= 2; + scaled_update.h *= 2; + TFB_SDL2_UpdateTexture (texture, src, &scaled_update); + } + if (a == 255) + { + SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_NONE); + } + else + { + SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_BLEND); + SDL_SetTextureAlphaMod (texture, a); + } + /* The texture has twice the resolution when scaled, but the + * screen's logical resolution has not changed, so the clip + * rectangle does not need to be scaled. The *source* clip + * rect, however, must be scaled to match. */ + if (rect) + { + srcRect = *rect; + srcRect.x *= 2; + srcRect.y *= 2; + srcRect.w *= 2; + srcRect.h *= 2; + pSrcRect = &srcRect; + } + SDL_RenderCopy (renderer, texture, pSrcRect, rect); +} + +static void +TFB_SDL2_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect) +{ + SDL_SetRenderDrawBlendMode (renderer, a == 255 ? SDL_BLENDMODE_NONE + : SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor (renderer, r, g, b, a); + SDL_RenderFillRect (renderer, rect); +} + +static void +TFB_SDL2_Postprocess (void) +{ + if (GfxFlags & TFB_GFXFLAGS_SCANLINES) + TFB_SDL2_ScanLines (); + + SDL_RenderPresent (renderer); +} + +#endif diff --git a/src/libs/graphics/sdl/sdl_common.c b/src/libs/graphics/sdl/sdl_common.c new file mode 100644 index 0000000..b699bf8 --- /dev/null +++ b/src/libs/graphics/sdl/sdl_common.c @@ -0,0 +1,308 @@ +//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 "sdl_common.h" +#include "opengl.h" +#include "pure.h" +#include "primitives.h" +#include "options.h" +#include "uqmversion.h" +#include "libs/graphics/drawcmd.h" +#include "libs/graphics/dcqueue.h" +#include "libs/graphics/cmap.h" +#include "libs/input/sdl/input.h" + // for ProcessInputEvent() +#include "libs/graphics/bbox.h" +#include "port.h" +#include "libs/uio.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "libs/vidlib.h" + +SDL_Surface *SDL_Screen; +SDL_Surface *TransitionScreen; + +SDL_Surface *SDL_Screens[TFB_GFX_NUMSCREENS]; + +SDL_Surface *format_conv_surf = NULL; + +static volatile BOOLEAN abortFlag = FALSE; + +int GfxFlags = 0; + +TFB_GRAPHICS_BACKEND *graphics_backend = NULL; + +volatile int QuitPosted = 0; +volatile int GameActive = 1; // Track the SDL_ACTIVEEVENT state SDL_APPACTIVE + +int +TFB_InitGraphics (int driver, int flags, const char *renderer, int width, int height) +{ + int result, i; + char caption[200]; + + /* Null out screen pointers the first time */ + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + SDL_Screens[i] = NULL; + } + + GfxFlags = flags; + + if (driver == TFB_GFXDRIVER_SDL_OPENGL) + { +#ifdef HAVE_OPENGL + result = TFB_GL_InitGraphics (driver, flags, width, height); +#else + driver = TFB_GFXDRIVER_SDL_PURE; + log_add (log_Warning, "OpenGL support not compiled in," + " so using pure SDL driver"); + result = TFB_Pure_InitGraphics (driver, flags, renderer, width, height); +#endif + } + else + { + result = TFB_Pure_InitGraphics (driver, flags, renderer, width, height); + } + +#if SDL_MAJOR_VERSION == 1 + /* Other versions do this when setting up the window */ + sprintf (caption, "The Ur-Quan Masters v%d.%d.%d%s", + UQM_MAJOR_VERSION, UQM_MINOR_VERSION, + UQM_PATCH_VERSION, UQM_EXTRA_VERSION); + SDL_WM_SetCaption (caption, NULL); +#endif + + if (flags & TFB_GFXFLAGS_FULLSCREEN) + SDL_ShowCursor (SDL_DISABLE); + + Init_DrawCommandQueue (); + + TFB_DrawCanvas_Initialize (); + + return 0; +} + +void +TFB_UninitGraphics (void) +{ + int i; + + Uninit_DrawCommandQueue (); + + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + UnInit_Screen (&SDL_Screens[i]); + + TFB_Pure_UninitGraphics (); +#ifdef HAVE_OPENGL + TFB_GL_UninitGraphics (); +#endif + + UnInit_Screen (&format_conv_surf); +} + +void +TFB_ProcessEvents () +{ + SDL_Event Event; + + while (SDL_PollEvent (&Event) > 0) + { + /* Run through the InputEvent filter. */ + ProcessInputEvent (&Event); + /* Handle graphics and exposure events. */ + switch (Event.type) { +#if 0 /* Currently disabled in mainline */ + case SDL_ACTIVEEVENT: /* Lose/gain visibility or focus */ + /* Up to three different state changes can occur in one event. */ + /* Here, disregard least significant change (mouse focus). */ + // This controls the automatic sleep/pause when minimized. + // On small displays (e.g. mobile devices), APPINPUTFOCUS would + // be an appropriate substitution for APPACTIVE: + // if (Event.active.state & SDL_APPINPUTFOCUS) + if (Event.active.state & SDL_APPACTIVE) + GameActive = Event.active.gain; + break; + case SDL_VIDEORESIZE: /* User resized video mode */ + // TODO + break; +#endif + case SDL_QUIT: + QuitPosted = 1; + break; +#if SDL_MAJOR_VERSION == 1 + case SDL_VIDEOEXPOSE: /* Screen needs to be redrawn */ + TFB_SwapBuffers (TFB_REDRAW_EXPOSE); + break; +#else + case SDL_WINDOWEVENT: + if (Event.window.event == SDL_WINDOWEVENT_EXPOSED) + { + /* Screen needs to be redrawn */ + TFB_SwapBuffers (TFB_REDRAW_EXPOSE); + } + break; +#endif + default: + break; + } + } +} + +static BOOLEAN system_box_active = 0; +static SDL_Rect system_box; + +void +SetSystemRect (const RECT *r) +{ + system_box_active = TRUE; + system_box.x = r->corner.x; + system_box.y = r->corner.y; + system_box.w = r->extent.width; + system_box.h = r->extent.height; +} + +void +ClearSystemRect (void) +{ + system_box_active = FALSE; +} + +void +TFB_SwapBuffers (int force_full_redraw) +{ + static int last_fade_amount = 255, last_transition_amount = 255; + static int fade_amount = 255, transition_amount = 255; + + fade_amount = GetFadeAmount (); + transition_amount = TransitionAmount; + + if (force_full_redraw == TFB_REDRAW_NO && !TFB_BBox.valid && + fade_amount == 255 && transition_amount == 255 && + last_fade_amount == 255 && last_transition_amount == 255) + return; + + if (force_full_redraw == TFB_REDRAW_NO && + (fade_amount != 255 || transition_amount != 255 || + last_fade_amount != 255 || last_transition_amount != 255)) + force_full_redraw = TFB_REDRAW_FADING; + + last_fade_amount = fade_amount; + last_transition_amount = transition_amount; + + graphics_backend->preprocess (force_full_redraw, transition_amount, + fade_amount); + graphics_backend->screen (TFB_SCREEN_MAIN, 255, NULL); + + if (transition_amount != 255) + { + SDL_Rect r; + r.x = TransitionClipRect.corner.x; + r.y = TransitionClipRect.corner.y; + r.w = TransitionClipRect.extent.width; + r.h = TransitionClipRect.extent.height; + graphics_backend->screen (TFB_SCREEN_TRANSITION, + 255 - transition_amount, &r); + } + + if (fade_amount != 255) + { + if (fade_amount < 255) + { + graphics_backend->color (0, 0, 0, 255 - fade_amount, NULL); + } + else + { + graphics_backend->color (255, 255, 255, + fade_amount - 255, NULL); + } + } + + if (system_box_active) + { + graphics_backend->screen (TFB_SCREEN_MAIN, 255, &system_box); + } + + graphics_backend->postprocess (); +} + +/* Probably ought to clean this away at some point. */ +SDL_Surface * +TFB_DisplayFormatAlpha (SDL_Surface *surface) +{ + SDL_Surface* newsurf; + SDL_PixelFormat* dstfmt; + const SDL_PixelFormat* srcfmt = surface->format; + + // figure out what format to use (alpha/no alpha) + if (surface->format->Amask) + dstfmt = format_conv_surf->format; + else + dstfmt = SDL_Screen->format; + + if (srcfmt->BytesPerPixel == dstfmt->BytesPerPixel && + srcfmt->Rmask == dstfmt->Rmask && + srcfmt->Gmask == dstfmt->Gmask && + srcfmt->Bmask == dstfmt->Bmask && + srcfmt->Amask == dstfmt->Amask) + return surface; // no conversion needed + + newsurf = SDL_ConvertSurface (surface, dstfmt, surface->flags); + // Colorkeys and surface-level alphas cannot work at the same time, + // so we need to disable one of them + if (TFB_HasColorKey (surface) && newsurf && + TFB_HasColorKey (newsurf) && + TFB_HasSurfaceAlphaMod (newsurf)) + { + TFB_DisableSurfaceAlphaMod (newsurf); + } + + return newsurf; +} + +// This function should only be called from the graphics thread, +// like from a TFB_DrawCommand_Callback command. +TFB_Canvas +TFB_GetScreenCanvas (SCREEN screen) +{ + return SDL_Screens[screen]; +} + +void +TFB_UploadTransitionScreen (void) +{ + graphics_backend->uploadTransitionScreen (); +} + +int +TFB_HasColorKey (SDL_Surface *surface) +{ + Uint32 key; + return TFB_GetColorKey (surface, &key) == 0; +} + +void +UnInit_Screen (SDL_Surface **screen) +{ + if (*screen == NULL) { + return; + } + + SDL_FreeSurface (*screen); + *screen = NULL; +} diff --git a/src/libs/graphics/sdl/sdl_common.h b/src/libs/graphics/sdl/sdl_common.h new file mode 100644 index 0000000..76d1cb6 --- /dev/null +++ b/src/libs/graphics/sdl/sdl_common.h @@ -0,0 +1,63 @@ +//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 SDL_COMMON_H +#define SDL_COMMON_H + +#include "port.h" +#include SDL_INCLUDE(SDL.h) + +#include "../gfxintrn.h" +#include "libs/graphics/tfb_draw.h" +#include "libs/graphics/gfx_common.h" + +// The Graphics Backend vtable +typedef struct _tfb_graphics_backend { + void (*preprocess) (int force_redraw, int transition_amount, int fade_amount); + void (*postprocess) (void); + void (*uploadTransitionScreen) (void); + void (*screen) (SCREEN screen, Uint8 alpha, SDL_Rect *rect); + void (*color) (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect); +} TFB_GRAPHICS_BACKEND; + +extern TFB_GRAPHICS_BACKEND *graphics_backend; + +extern SDL_Surface *SDL_Screen; +extern SDL_Surface *TransitionScreen; + +extern SDL_Surface *SDL_Screens[TFB_GFX_NUMSCREENS]; + +extern SDL_Surface *format_conv_surf; + +SDL_Surface* TFB_DisplayFormatAlpha (SDL_Surface *surface); +int TFB_HasSurfaceAlphaMod (SDL_Surface *surface); +int TFB_GetSurfaceAlphaMod (SDL_Surface *surface, Uint8 *alpha); +int TFB_SetSurfaceAlphaMod (SDL_Surface *surface, Uint8 alpha); +int TFB_DisableSurfaceAlphaMod (SDL_Surface *surface); +int TFB_HasColorKey (SDL_Surface *surface); +int TFB_GetColorKey (SDL_Surface *surface, Uint32 *key); +int TFB_SetColorKey (SDL_Surface *surface, Uint32 key, int rleaccel); +int TFB_DisableColorKey (SDL_Surface *surface); +int TFB_SetColors (SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors); + +#if SDL_MAJOR_VERSION == 1 +int SDL1_ReInit_Screen (SDL_Surface **screen, SDL_Surface *templat, int w, int h); +#endif +void UnInit_Screen (SDL_Surface **screen); + +#endif diff --git a/src/libs/graphics/sdl/sdluio.c b/src/libs/graphics/sdl/sdluio.c new file mode 100644 index 0000000..5e4554d --- /dev/null +++ b/src/libs/graphics/sdl/sdluio.c @@ -0,0 +1,153 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "sdluio.h" + +#include "port.h" +#include "libs/uio.h" +#include SDL_INCLUDE(SDL.h) +#include SDL_INCLUDE(SDL_error.h) +#include SDL_INCLUDE(SDL_rwops.h) +#include "libs/memlib.h" +#include "png2sdl.h" +#include +#include + + +static SDL_RWops *sdluio_makeRWops (uio_Stream *stream); + +#if 0 +// For use for initialisation, using structure assignment. +static SDL_RWops sdluio_templateRWops = +{ + .seek = sdluio_seek, + .read = sdluio_read, + .write = sdluio_write, + .close = sdluio_close, +}; +#endif + +SDL_Surface * +sdluio_loadImage (uio_DirHandle *dir, const char *fileName) { + uio_Stream *stream; + SDL_RWops *rwops; + SDL_Surface *result = NULL; + + stream = uio_fopen (dir, fileName, "rb"); + if (stream == NULL) + { + SDL_SetError ("Couldn't open '%s': %s", fileName, + strerror(errno)); + return NULL; + } + rwops = sdluio_makeRWops (stream); + if (rwops) { + result = TFB_png_to_sdl (rwops); + SDL_RWclose (rwops); + } + return result; +} + +#if SDL_MAJOR_VERSION == 1 +int +sdluio_seek (SDL_RWops *context, int offset, int whence) +#else +Sint64 +sdluio_seek (SDL_RWops *context, Sint64 offset, int whence) +#endif +{ + if (uio_fseek ((uio_Stream *) context->hidden.unknown.data1, offset, + whence) == -1) + { + SDL_SetError ("Error seeking in uio_Stream: %s", + strerror(errno)); + return -1; + } + return uio_ftell ((uio_Stream *) context->hidden.unknown.data1); +} + +#if SDL_MAJOR_VERSION == 1 +int +sdluio_read (SDL_RWops *context, void *ptr, int size, int maxnum) +#else +size_t +sdluio_read (SDL_RWops *context, void *ptr, size_t size, size_t maxnum) +#endif +{ + size_t numRead; + + numRead = uio_fread (ptr, (size_t) size, (size_t) maxnum, + (uio_Stream *) context->hidden.unknown.data1); + if (numRead == 0 && uio_ferror ((uio_Stream *) + context->hidden.unknown.data1)) + { + SDL_SetError ("Error reading from uio_Stream: %s", + strerror(errno)); + return 0; + } + return (int) numRead; +} + +#if SDL_MAJOR_VERSION == 1 +int +sdluio_write (SDL_RWops *context, const void *ptr, int size, int num) +#else +size_t +sdluio_write (SDL_RWops *context, const void *ptr, size_t size, size_t num) +#endif +{ + size_t numWritten; + + numWritten = uio_fwrite (ptr, (size_t) size, (size_t) num, + (uio_Stream *) context->hidden.unknown.data1); + if (numWritten == 0 && uio_ferror ((uio_Stream *) + context->hidden.unknown.data1)) + { + SDL_SetError ("Error writing to uio_Stream: %s", + strerror(errno)); + return 0; + } + return (size_t) numWritten; +} + +int +sdluio_close (SDL_RWops *context) { + int result; + + result = uio_fclose ((uio_Stream *) context->hidden.unknown.data1); + HFree (context); + return result; +} + +static SDL_RWops * +sdluio_makeRWops (uio_Stream *stream) { + SDL_RWops *result; + + result = HMalloc (sizeof (SDL_RWops)); +#if 0 + *(struct SDL_RWops *) result = sdluio_templateRWops; + // structure assignment +#endif + result->seek = sdluio_seek; + result->read = sdluio_read; + result->write = sdluio_write; + result->close = sdluio_close; + result->hidden.unknown.data1 = stream; + return result; +} + + + diff --git a/src/libs/graphics/sdl/sdluio.h b/src/libs/graphics/sdl/sdluio.h new file mode 100644 index 0000000..0f8c701 --- /dev/null +++ b/src/libs/graphics/sdl/sdluio.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 LIBS_GRAPHICS_SDL_SDLUIO_H_ +#define LIBS_GRAPHICS_SDL_SDLUIO_H_ + +#include "port.h" +#include "libs/uio.h" +#include SDL_INCLUDE(SDL.h) +#include SDL_INCLUDE(SDL_rwops.h) + +SDL_Surface *sdluio_loadImage (uio_DirHandle *dir, const char *fileName); +#if SDL_MAJOR_VERSION == 1 +int sdluio_seek (SDL_RWops *context, int offset, int whence); +int sdluio_read (SDL_RWops *context, void *ptr, int size, int maxnum); +int sdluio_write (SDL_RWops *context, const void *ptr, int size, int num); +#else +Sint64 sdlui_seek (SDL_RWops *context, Sint64 offset, int whence); +size_t sdlui_read (SDL_RWops *context, void *ptr, size_t size, size_t maxnum); +size_t sdlui_write (SDL_RWops *contex, const void *ptr, size_t size, size_t num); +#endif +int sdluio_close (SDL_RWops *context); + + +#endif /* LIBS_GRAPHICS_SDL_SDLUIO_H_ */ + diff --git a/src/libs/graphics/sdl/triscan2x.c b/src/libs/graphics/sdl/triscan2x.c new file mode 100644 index 0000000..830dc7c --- /dev/null +++ b/src/libs/graphics/sdl/triscan2x.c @@ -0,0 +1,155 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// Core algorithm of the Triscan screen scaler (based on Scale2x) +// (for scale2x please see http://scale2x.sf.net) +// Template +// When this file is built standalone is produces a plain C version +// Also #included by 2xscalers_mmx.c for an MMX version + +#include "libs/graphics/sdl/sdl_common.h" +#include "types.h" +#include "scalers.h" +#include "scaleint.h" +#include "2xscalers.h" + + +// Triscan scaling to 2x +// derivative of scale2x -- scale2x.sf.net +// The name expands to either +// Scale_TriScanFilter (for plain C) or +// Scale_MMX_TriScanFilter (for MMX) +// [others when platforms are added] +void +SCALE_(TriScanFilter) (SDL_Surface *src, SDL_Surface *dst, SDL_Rect *r) +{ + int x, y; + const int w = src->w, h = src->h; + int xend, yend; + int dsrc, ddst; + SDL_Rect *region = r; + SDL_Rect limits; + SDL_PixelFormat *fmt = dst->format; + const int sp = src->pitch, dp = dst->pitch; + const int bpp = fmt->BytesPerPixel; + const int slen = sp / bpp, dlen = dp / bpp; + // for clarity purposes, the 'pixels' array here is transposed + Uint32 pixels[3][3]; + Uint32 *src_p = (Uint32 *)src->pixels; + Uint32 *dst_p = (Uint32 *)dst->pixels; + + int prevline, nextline; + + // these macros are for clarity; they make the current pixel (0,0) + // and allow to access pixels in all directions + #define PIX(x, y) (pixels[1 + (x)][1 + (y)]) + + #define TRISCAN_YUV_MED 100 + // medium tolerance pixel comparison + #define TRISCAN_CMPYUV(p1, p2) \ + (PIX p1 == PIX p2 || SCALE_CMPYUV (PIX p1, PIX p2, TRISCAN_YUV_MED)) + + + SCALE_(PlatInit) (); + + // expand updated region if necessary + // pixels neighbooring the updated region may + // change as a result of updates + limits.x = 0; + limits.y = 0; + limits.w = src->w; + limits.h = src->h; + Scale_ExpandRect (region, 1, &limits); + + xend = region->x + region->w; + yend = region->y + region->h; + dsrc = slen - region->w; + ddst = (dlen - region->w) * 2; + + // move ptrs to the first updated pixel + src_p += slen * region->y + region->x; + dst_p += (dlen * region->y + region->x) * 2; + + for (y = region->y; y < yend; ++y, dst_p += ddst, src_p += dsrc) + { + if (y > 0) + prevline = -slen; + else + prevline = 0; + + if (y < h - 1) + nextline = slen; + else + nextline = 0; + + // prime the (tiny) sliding-window pixel arrays + PIX( 1, 0) = src_p[0]; + + if (region->x > 0) + PIX( 0, 0) = src_p[-1]; + else + PIX( 0, 0) = PIX( 1, 0); + + for (x = region->x; x < xend; ++x, ++src_p, dst_p += 2) + { + // slide the window + PIX(-1, 0) = PIX( 0, 0); + + PIX( 0, -1) = src_p[prevline]; + PIX( 0, 0) = PIX( 1, 0); + PIX( 0, 1) = src_p[nextline]; + + if (x < w - 1) + PIX( 1, 0) = src_p[1]; + else + PIX( 1, 0) = PIX( 0, 0); + + if (!TRISCAN_CMPYUV (( 0, -1), ( 0, 1)) && + !TRISCAN_CMPYUV ((-1, 0), ( 1, 0))) + { + if (TRISCAN_CMPYUV ((-1, 0), ( 0, -1))) + dst_p[0] = Scale_Blend_11 (PIX(-1, 0), PIX(0, -1)); + else + dst_p[0] = PIX(0, 0); + + if (TRISCAN_CMPYUV (( 1, 0), ( 0, -1))) + dst_p[1] = Scale_Blend_11 (PIX(1, 0), PIX(0, -1)); + else + dst_p[1] = PIX(0, 0); + + if (TRISCAN_CMPYUV ((-1, 0), ( 0, 1))) + dst_p[dlen] = Scale_Blend_11 (PIX(-1, 0), PIX(0, 1)); + else + dst_p[dlen] = PIX(0, 0); + + if (TRISCAN_CMPYUV (( 1, 0), ( 0, 1))) + dst_p[dlen+1] = Scale_Blend_11 (PIX(1, 0), PIX(0, 1)); + else + dst_p[dlen+1] = PIX(0, 0); + } + else + { + dst_p[0] = PIX(0, 0); + dst_p[1] = PIX(0, 0); + dst_p[dlen] = PIX(0, 0); + dst_p[dlen+1] = PIX(0, 0); + } + } + } + + SCALE_(PlatDone) (); +} + diff --git a/src/libs/graphics/tfb_draw.c b/src/libs/graphics/tfb_draw.c new file mode 100644 index 0000000..1ac3c34 --- /dev/null +++ b/src/libs/graphics/tfb_draw.c @@ -0,0 +1,493 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "gfx_common.h" +#include "tfb_draw.h" +#include "drawcmd.h" +#include "libs/log.h" +#include "libs/memlib.h" + + +static const HOT_SPOT NullHs = {0, 0}; + +void +TFB_DrawScreen_Line (int x1, int y1, int x2, int y2, Color color, + DrawMode mode, SCREEN dest) +{ + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_LINE; + DC.data.line.x1 = x1; + DC.data.line.y1 = y1; + DC.data.line.x2 = x2; + DC.data.line.y2 = y2; + DC.data.line.color = color; + DC.data.line.drawMode = mode; + DC.data.line.destBuffer = dest; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_Rect (RECT *rect, Color color, DrawMode mode, SCREEN dest) +{ + RECT locRect; + TFB_DrawCommand DC; + + if (!rect) + { + locRect.corner.x = locRect.corner.y = 0; + locRect.extent.width = ScreenWidth; + locRect.extent.height = ScreenHeight; + rect = &locRect; + } + + DC.Type = TFB_DRAWCOMMANDTYPE_RECTANGLE; + DC.data.rect.rect = *rect; + DC.data.rect.color = color; + DC.data.rect.drawMode = mode; + DC.data.rect.destBuffer = dest; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_Image (TFB_Image *img, int x, int y, int scale, + int scaleMode, TFB_ColorMap *cmap, DrawMode mode, SCREEN dest) +{ + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_IMAGE; + DC.data.image.image = img; + DC.data.image.colormap = cmap; + DC.data.image.x = x; + DC.data.image.y = y; + DC.data.image.scale = (scale == GSCALE_IDENTITY) ? 0 : scale; + DC.data.image.scaleMode = scaleMode; + DC.data.image.drawMode = mode; + DC.data.image.destBuffer = dest; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_FilledImage (TFB_Image *img, int x, int y, int scale, + int scaleMode, Color color, DrawMode mode, SCREEN dest) +{ + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_FILLEDIMAGE; + DC.data.filledimage.image = img; + DC.data.filledimage.x = x; + DC.data.filledimage.y = y; + DC.data.filledimage.scale = (scale == GSCALE_IDENTITY) ? 0 : scale; + DC.data.filledimage.scaleMode = scaleMode; + DC.data.filledimage.color = color; + DC.data.filledimage.drawMode = mode; + DC.data.filledimage.destBuffer = dest; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_FontChar (TFB_Char *fontChar, TFB_Image *backing, + int x, int y, DrawMode mode, SCREEN dest) +{ + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_FONTCHAR; + DC.data.fontchar.fontchar = fontChar; + DC.data.fontchar.backing = backing; + DC.data.fontchar.x = x; + DC.data.fontchar.y = y; + DC.data.fontchar.drawMode = mode; + DC.data.fontchar.destBuffer = dest; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_CopyToImage (TFB_Image *img, const RECT *r, SCREEN src) +{ + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_COPYTOIMAGE; + DC.data.copytoimage.rect = *r; + DC.data.copytoimage.image = img; + DC.data.copytoimage.srcBuffer = src; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_Copy (const RECT *r, SCREEN src, SCREEN dest) +{ + RECT locRect; + TFB_DrawCommand DC; + + if (!r) + { + locRect.corner.x = locRect.corner.y = 0; + locRect.extent.width = ScreenWidth; + locRect.extent.height = ScreenHeight; + r = &locRect; + } + + DC.Type = TFB_DRAWCOMMANDTYPE_COPY; + DC.data.copy.rect = *r; + DC.data.copy.srcBuffer = src; + DC.data.copy.destBuffer = dest; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_SetMipmap (TFB_Image *img, TFB_Image *mmimg, int hotx, int hoty) +{ + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_SETMIPMAP; + DC.data.setmipmap.image = img; + DC.data.setmipmap.mipmap = mmimg; + DC.data.setmipmap.hotx = hotx; + DC.data.setmipmap.hoty = hoty; + + TFB_EnqueueDrawCommand (&DC); +} + +void +TFB_DrawScreen_DeleteImage (TFB_Image *img) +{ + if (img) + { + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_DELETEIMAGE; + DC.data.deleteimage.image = img; + + TFB_EnqueueDrawCommand (&DC); + } +} + +void +TFB_DrawScreen_DeleteData (void *data) + // data must be a result of HXalloc() call +{ + if (data) + { + TFB_DrawCommand DC; + + DC.Type = TFB_DRAWCOMMANDTYPE_DELETEDATA; + DC.data.deletedata.data = data; + + TFB_EnqueueDrawCommand (&DC); + } +} + +void +TFB_DrawScreen_WaitForSignal (void) +{ + TFB_DrawCommand DrawCommand; + Semaphore s; + s = GetMyThreadLocal ()->flushSem; + DrawCommand.Type = TFB_DRAWCOMMANDTYPE_SENDSIGNAL; + DrawCommand.data.sendsignal.sem = s; + Lock_DCQ (1); + TFB_BatchReset (); + TFB_EnqueueDrawCommand (&DrawCommand); + Unlock_DCQ(); + SetSemaphore (s); +} + +void +TFB_DrawScreen_ReinitVideo (int driver, int flags, int width, int height) +{ + TFB_DrawCommand DrawCommand; + DrawCommand.Type = TFB_DRAWCOMMANDTYPE_REINITVIDEO; + DrawCommand.data.reinitvideo.driver = driver; + DrawCommand.data.reinitvideo.flags = flags; + DrawCommand.data.reinitvideo.width = width; + DrawCommand.data.reinitvideo.height = height; + TFB_EnqueueDrawCommand (&DrawCommand); +} + +void +TFB_DrawScreen_Callback (void (*callback) (void *arg), void *arg) +{ + TFB_DrawCommand DrawCommand; + DrawCommand.Type = TFB_DRAWCOMMANDTYPE_CALLBACK; + DrawCommand.data.callback.callback = callback; + DrawCommand.data.callback.arg = arg; + TFB_EnqueueDrawCommand(&DrawCommand); +} + +void +TFB_DrawImage_Line (int x1, int y1, int x2, int y2, Color color, + DrawMode mode, TFB_Image *target) +{ + LockMutex (target->mutex); + TFB_DrawCanvas_Line (x1, y1, x2, y2, color, mode, target->NormalImg); + target->dirty = TRUE; + UnlockMutex (target->mutex); +} + +void +TFB_DrawImage_Rect (RECT *rect, Color color, DrawMode mode, TFB_Image *target) +{ + LockMutex (target->mutex); + TFB_DrawCanvas_Rect (rect, color, mode, target->NormalImg); + target->dirty = TRUE; + UnlockMutex (target->mutex); +} + +void +TFB_DrawImage_Image (TFB_Image *img, int x, int y, int scale, + int scaleMode, TFB_ColorMap *cmap, DrawMode mode, TFB_Image *target) +{ + LockMutex (target->mutex); + TFB_DrawCanvas_Image (img, x, y, scale, scaleMode, cmap, + mode, target->NormalImg); + target->dirty = TRUE; + UnlockMutex (target->mutex); +} + +void +TFB_DrawImage_FilledImage (TFB_Image *img, int x, int y, int scale, + int scaleMode, Color color, DrawMode mode, TFB_Image *target) +{ + LockMutex (target->mutex); + TFB_DrawCanvas_FilledImage (img, x, y, scale, scaleMode, color, + mode, target->NormalImg); + target->dirty = TRUE; + UnlockMutex (target->mutex); +} + +void +TFB_DrawImage_FontChar (TFB_Char *fontChar, TFB_Image *backing, + int x, int y, DrawMode mode, TFB_Image *target) +{ + LockMutex (target->mutex); + TFB_DrawCanvas_FontChar (fontChar, backing, x, y, mode, target->NormalImg); + target->dirty = TRUE; + UnlockMutex (target->mutex); +} + + +TFB_Image * +TFB_DrawImage_New (TFB_Canvas canvas) +{ + TFB_Image *img = HMalloc (sizeof (TFB_Image)); + img->mutex = CreateMutex ("image lock", SYNC_CLASS_VIDEO); + img->ScaledImg = NULL; + img->MipmapImg = NULL; + img->FilledImg = NULL; + img->colormap_index = -1; + img->colormap_version = 0; + img->NormalHs = NullHs; + img->MipmapHs = NullHs; + img->last_scale_hs = NullHs; + img->last_scale_type = -1; + img->last_scale = 0; + img->dirty = FALSE; + TFB_DrawCanvas_GetExtent (canvas, &img->extent); + + if (TFB_DrawCanvas_IsPaletted (canvas)) + { + img->NormalImg = canvas; + } + else + { + img->NormalImg = TFB_DrawCanvas_ToScreenFormat (canvas); + } + + return img; +} + +TFB_Image* +TFB_DrawImage_CreateForScreen (int w, int h, BOOLEAN withalpha) +{ + TFB_Image* img = HMalloc (sizeof (TFB_Image)); + img->mutex = CreateMutex ("image lock", SYNC_CLASS_VIDEO); + img->ScaledImg = NULL; + img->MipmapImg = NULL; + img->FilledImg = NULL; + img->colormap_index = -1; + img->colormap_version = 0; + img->NormalHs = NullHs; + img->MipmapHs = NullHs; + img->last_scale_hs = NullHs; + img->last_scale_type = -1; + img->last_scale = 0; + img->extent.width = w; + img->extent.height = h; + + img->NormalImg = TFB_DrawCanvas_New_ForScreen (w, h, withalpha); + + return img; +} + +TFB_Image * +TFB_DrawImage_New_Rotated (TFB_Image *img, int angle) +{ + TFB_Canvas dst; + EXTENT size; + TFB_Image* newimg; + + /* sanity check */ + if (!img->NormalImg) + { + log_add (log_Warning, "TFB_DrawImage_New_Rotated: " + "source canvas is NULL! Failing."); + return NULL; + } + + TFB_DrawCanvas_GetRotatedExtent (img->NormalImg, angle, &size); + dst = TFB_DrawCanvas_New_RotationTarget (img->NormalImg, angle); + if (!dst) + { + log_add (log_Warning, "TFB_DrawImage_New_Rotated: " + "rotation target canvas not created! Failing."); + return NULL; + } + TFB_DrawCanvas_Rotate (img->NormalImg, dst, angle, size); + + newimg = TFB_DrawImage_New (dst); + return newimg; +} + +void +TFB_DrawImage_SetMipmap (TFB_Image *img, TFB_Image *mmimg, int hotx, int hoty) +{ + bool imgpal; + bool mmpal; + + if (!img || !mmimg) + return; + + LockMutex (img->mutex); + LockMutex (mmimg->mutex); + + // Either both images must be using the same colormap, or mipmap image + // must not be paletted. This restriction is due to the current + // implementation of fill-stamp, which replaces the palette with + // fill color. + imgpal = TFB_DrawCanvas_IsPaletted (img->NormalImg); + mmpal = TFB_DrawCanvas_IsPaletted (mmimg->NormalImg); + if (!mmpal || (mmpal && imgpal && + img->colormap_index == mmimg->colormap_index)) + { + img->MipmapImg = mmimg->NormalImg; + img->MipmapHs.x = hotx; + img->MipmapHs.y = hoty; + } + else + { + img->MipmapImg = NULL; + } + + UnlockMutex (mmimg->mutex); + UnlockMutex (img->mutex); +} + +void +TFB_DrawImage_Delete (TFB_Image *image) +{ + if (image == 0) + { + log_add (log_Warning, "INTERNAL ERROR: Tried to delete a null image!"); + /* Should we die here? */ + return; + } + LockMutex (image->mutex); + + TFB_DrawCanvas_Delete (image->NormalImg); + + if (image->ScaledImg) + { + TFB_DrawCanvas_Delete (image->ScaledImg); + image->ScaledImg = 0; + } + + if (image->FilledImg) + { + TFB_DrawCanvas_Delete (image->FilledImg); + image->FilledImg = 0; + } + + UnlockMutex (image->mutex); + DestroyMutex (image->mutex); + + HFree (image); +} + +void +TFB_DrawImage_FixScaling (TFB_Image *image, int target, int type) +{ + if (image->dirty || !image->ScaledImg || + target != image->last_scale || + type != image->last_scale_type) + { + image->dirty = FALSE; + image->ScaledImg = TFB_DrawCanvas_New_ScaleTarget (image->NormalImg, + image->ScaledImg, type, image->last_scale_type); + + if (type == TFB_SCALE_NEAREST) + TFB_DrawCanvas_Rescale_Nearest (image->NormalImg, + image->ScaledImg, target, &image->NormalHs, + &image->extent, &image->last_scale_hs); + else if (type == TFB_SCALE_BILINEAR) + TFB_DrawCanvas_Rescale_Bilinear (image->NormalImg, + image->ScaledImg, target, &image->NormalHs, + &image->extent, &image->last_scale_hs); + else + TFB_DrawCanvas_Rescale_Trilinear (image->NormalImg, + image->MipmapImg, image->ScaledImg, target, + &image->NormalHs, &image->MipmapHs, + &image->extent, &image->last_scale_hs); + + image->last_scale_type = type; + image->last_scale = target; + } +} + +BOOLEAN +TFB_DrawImage_Intersect (TFB_Image *img1, POINT img1org, + TFB_Image *img2, POINT img2org, const RECT *interRect) +{ + BOOLEAN ret; + + LockMutex (img1->mutex); + LockMutex (img2->mutex); + ret = TFB_DrawCanvas_Intersect (img1->NormalImg, img1org, + img2->NormalImg, img2org, interRect); + UnlockMutex (img2->mutex); + UnlockMutex (img1->mutex); + + return ret; +} + +void +TFB_DrawImage_CopyRect (TFB_Image *source, const RECT *srcRect, + TFB_Image *target, POINT dstPt) +{ + LockMutex (source->mutex); + LockMutex (target->mutex); + TFB_DrawCanvas_CopyRect (source->NormalImg, srcRect, + target->NormalImg, dstPt); + target->dirty = TRUE; + UnlockMutex (target->mutex); + UnlockMutex (source->mutex); +} diff --git a/src/libs/graphics/tfb_draw.h b/src/libs/graphics/tfb_draw.h new file mode 100644 index 0000000..11de5b0 --- /dev/null +++ b/src/libs/graphics/tfb_draw.h @@ -0,0 +1,199 @@ +//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 TFB_DRAW_H +#define TFB_DRAW_H + +#include "libs/threadlib.h" + + +typedef void *TFB_Canvas; + +typedef enum { + TFB_SCREEN_MAIN, + TFB_SCREEN_EXTRA, + TFB_SCREEN_TRANSITION, + + TFB_GFX_NUMSCREENS +} SCREEN; + +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/cmap.h" + +typedef struct tfb_image +{ + TFB_Canvas NormalImg; + TFB_Canvas ScaledImg; + TFB_Canvas MipmapImg; + TFB_Canvas FilledImg; + int colormap_index; + int colormap_version; + HOT_SPOT NormalHs; + HOT_SPOT MipmapHs; + HOT_SPOT last_scale_hs; + int last_scale; + int last_scale_type; + Color last_fill; + EXTENT extent; + Mutex mutex; + BOOLEAN dirty; +} TFB_Image; + +typedef struct tfb_char +{ + EXTENT extent; + EXTENT disp; + // Display extent + HOT_SPOT HotSpot; + BYTE* data; + DWORD pitch; + // Pitch is for storing all chars of a page + // in one rectangular pixel matrix +} TFB_Char; + +// we do not support paletted format for now +typedef struct tfb_pixelformat +{ + int BitsPerPixel; + int BytesPerPixel; + DWORD Rmask, Gmask, Bmask, Amask; + DWORD Rshift, Gshift, Bshift, Ashift; + DWORD Rloss, Gloss, Bloss, Aloss; +} TFB_PixelFormat; + +// Drawing commands + +void TFB_DrawScreen_Line (int x1, int y1, int x2, int y2, Color color, + DrawMode, SCREEN dest); +void TFB_DrawScreen_Rect (RECT *rect, Color, DrawMode, SCREEN dest); +void TFB_DrawScreen_Image (TFB_Image *img, int x, int y, int scale, + int scaleMode, TFB_ColorMap *, DrawMode, SCREEN dest); +void TFB_DrawScreen_Copy (const RECT *r, SCREEN src, SCREEN dest); +void TFB_DrawScreen_FilledImage (TFB_Image *img, int x, int y, int scale, + int scaleMode, Color, DrawMode, SCREEN dest); +void TFB_DrawScreen_FontChar (TFB_Char *, TFB_Image *backing, int x, int y, + DrawMode, SCREEN dest); + +void TFB_DrawScreen_CopyToImage (TFB_Image *img, const RECT *r, SCREEN src); +void TFB_DrawScreen_SetMipmap (TFB_Image *img, TFB_Image *mmimg, int hotx, + int hoty); +void TFB_DrawScreen_DeleteImage (TFB_Image *img); +void TFB_DrawScreen_DeleteData (void *); +void TFB_DrawScreen_WaitForSignal (void); +void TFB_DrawScreen_ReinitVideo (int driver, int flags, int width, int height); +void TFB_DrawScreen_Callback (void (*callback) (void *arg), void *arg); + +TFB_Image *TFB_DrawImage_New (TFB_Canvas canvas); +TFB_Image *TFB_DrawImage_CreateForScreen (int w, int h, BOOLEAN withalpha); +TFB_Image *TFB_DrawImage_New_Rotated (TFB_Image *img, int angle); +void TFB_DrawImage_SetMipmap (TFB_Image *img, TFB_Image *mmimg, int hotx, + int hoty); +void TFB_DrawImage_Delete (TFB_Image *image); +void TFB_DrawImage_FixScaling (TFB_Image *image, int target, int type); +BOOLEAN TFB_DrawImage_Intersect (TFB_Image *img1, POINT img1org, + TFB_Image *img2, POINT img2org, const RECT *interRect); +void TFB_DrawImage_CopyRect (TFB_Image *source, const RECT *srcRect, + TFB_Image *target, POINT dstPt); + +void TFB_DrawImage_Line (int x1, int y1, int x2, int y2, Color color, + DrawMode, TFB_Image *target); +void TFB_DrawImage_Rect (RECT *rect, Color, DrawMode, TFB_Image *target); +void TFB_DrawImage_Image (TFB_Image *img, int x, int y, int scale, + int scaleMode, TFB_ColorMap *, DrawMode, TFB_Image *target); +void TFB_DrawImage_FilledImage (TFB_Image *img, int x, int y, int scale, + int scaleMode, Color, DrawMode, TFB_Image *target); +void TFB_DrawImage_FontChar (TFB_Char *, TFB_Image *backing, int x, int y, + DrawMode, TFB_Image *target); + +TFB_Canvas TFB_DrawCanvas_LoadFromFile (void *dir, const char *fileName); +TFB_Canvas TFB_DrawCanvas_New_TrueColor (int w, int h, BOOLEAN hasalpha); +TFB_Canvas TFB_DrawCanvas_New_ForScreen (int w, int h, BOOLEAN withalpha); +TFB_Canvas TFB_DrawCanvas_New_Paletted (int w, int h, Color palette[256], + int transparent_index); +TFB_Canvas TFB_DrawCanvas_New_ScaleTarget (TFB_Canvas canvas, + TFB_Canvas oldcanvas, int type, int last_type); +TFB_Canvas TFB_DrawCanvas_New_RotationTarget (TFB_Canvas src, int angle); +TFB_Canvas TFB_DrawCanvas_ToScreenFormat (TFB_Canvas canvas); +BOOLEAN TFB_DrawCanvas_IsPaletted (TFB_Canvas canvas); +void TFB_DrawCanvas_Rescale_Nearest (TFB_Canvas src, TFB_Canvas dst, + int scale, HOT_SPOT* src_hs, EXTENT* size, HOT_SPOT* dst_hs); +void TFB_DrawCanvas_Rescale_Bilinear (TFB_Canvas src, TFB_Canvas dst, + int scale, HOT_SPOT* src_hs, EXTENT* size, HOT_SPOT* dst_hs); +void TFB_DrawCanvas_Rescale_Trilinear (TFB_Canvas src, TFB_Canvas mipmap, + TFB_Canvas dst, int scale, HOT_SPOT* src_hs, HOT_SPOT* mm_hs, + EXTENT* size, HOT_SPOT* dst_hs); +void TFB_DrawCanvas_GetScaledExtent (TFB_Canvas src_canvas, HOT_SPOT* src_hs, + TFB_Canvas src_mipmap, HOT_SPOT* mm_hs, + int scale, int type, EXTENT *size, HOT_SPOT *hs); +void TFB_DrawCanvas_Rotate (TFB_Canvas src, TFB_Canvas dst, int angle, + EXTENT size); +void TFB_DrawCanvas_GetRotatedExtent (TFB_Canvas src, int angle, EXTENT *size); +void TFB_DrawCanvas_GetExtent (TFB_Canvas canvas, EXTENT *size); +void TFB_DrawCanvas_SetClipRect (TFB_Canvas canvas, const RECT *clipRect); + +void TFB_DrawCanvas_Delete (TFB_Canvas canvas); + +void TFB_DrawCanvas_Line (int x1, int y1, int x2, int y2, Color color, + DrawMode, TFB_Canvas target); +void TFB_DrawCanvas_Rect (RECT *rect, Color, DrawMode, TFB_Canvas target); +void TFB_DrawCanvas_Image (TFB_Image *img, int x, int y, int scale, + int scaleMode, TFB_ColorMap *, DrawMode, TFB_Canvas target); +void TFB_DrawCanvas_FilledImage (TFB_Image *img, int x, int y, int scale, + int scaleMode, Color, DrawMode, TFB_Canvas target); +void TFB_DrawCanvas_FontChar (TFB_Char *, TFB_Image *backing, int x, int y, + DrawMode, TFB_Canvas target); +void TFB_DrawCanvas_CopyRect (TFB_Canvas source, const RECT *srcRect, + TFB_Canvas target, POINT dstPt); + +BOOLEAN TFB_DrawCanvas_GetFontCharData (TFB_Canvas canvas, BYTE *outData, + unsigned dataPitch); +Color *TFB_DrawCanvas_ExtractPalette (TFB_Canvas canvas); +void TFB_DrawCanvas_SetPalette (TFB_Canvas target, Color palette[256]); +int TFB_DrawCanvas_GetTransparentIndex (TFB_Canvas canvas); +void TFB_DrawCanvas_SetTransparentIndex (TFB_Canvas canvas, int i, + BOOLEAN rleaccel); +BOOLEAN TFB_DrawCanvas_GetTransparentColor (TFB_Canvas canvas, + Color *color); +void TFB_DrawCanvas_SetTransparentColor (TFB_Canvas canvas, + Color color, BOOLEAN rleaccel); +void TFB_DrawCanvas_CopyTransparencyInfo (TFB_Canvas src, TFB_Canvas dst); +void TFB_DrawCanvas_Initialize (void); +void TFB_DrawCanvas_Lock (TFB_Canvas canvas); +void TFB_DrawCanvas_Unlock (TFB_Canvas canvas); +void TFB_DrawCanvas_GetScreenFormat (TFB_PixelFormat *fmt); +int TFB_DrawCanvas_GetStride (TFB_Canvas canvas); +void *TFB_DrawCanvas_GetLine (TFB_Canvas canvas, int line); +Color TFB_DrawCanvas_GetPixel (TFB_Canvas canvas, int x, int y); +BOOLEAN TFB_DrawCanvas_Intersect (TFB_Canvas canvas1, POINT c1org, + TFB_Canvas canvas2, POINT c2org, const RECT *interRect); + +BOOLEAN TFB_DrawCanvas_GetPixelColors (TFB_Canvas, Color *pixels, + int width, int height); +BOOLEAN TFB_DrawCanvas_SetPixelColors (TFB_Canvas, const Color *pixels, + int width, int height); +BOOLEAN TFB_DrawCanvas_GetPixelIndexes (TFB_Canvas, BYTE *data, + int width, int height); +BOOLEAN TFB_DrawCanvas_SetPixelIndexes (TFB_Canvas, const BYTE *data, + int width, int height); + +const char *TFB_DrawCanvas_GetError (void); + +TFB_Canvas TFB_GetScreenCanvas (SCREEN screen); + +#endif + diff --git a/src/libs/graphics/tfb_prim.c b/src/libs/graphics/tfb_prim.c new file mode 100644 index 0000000..f8a2df4 --- /dev/null +++ b/src/libs/graphics/tfb_prim.c @@ -0,0 +1,237 @@ +// Copyright Michael Martin, 2003 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* The original Primitive routines do various elaborate checks to + * ensure we're within bounds for the clipping. Since clipping is + * handled by the underlying TFB_Canvas implementation, we need not + * worry about this. */ + +#include "gfxintrn.h" +#include "gfx_common.h" +#include "tfb_draw.h" +#include "tfb_prim.h" +#include "cmap.h" +#include "libs/log.h" + +void +TFB_Prim_Point (POINT *p, Color color, DrawMode mode, POINT ctxOrigin) +{ + RECT r; + + // The caller must scale the origin! + r.corner.x = p->x + ctxOrigin.x; + r.corner.y = p->y + ctxOrigin.y; + r.extent.width = r.extent.height = 1; + + if (_CurFramePtr->Type == SCREEN_DRAWABLE) + TFB_DrawScreen_Rect (&r, color, mode, TFB_SCREEN_MAIN); + else + TFB_DrawImage_Rect (&r, color, mode, _CurFramePtr->image); +} + +void +TFB_Prim_Rect (RECT *r, Color color, DrawMode mode, POINT ctxOrigin) +{ + RECT arm; + int gscale; + + // XXX: Rect prim scaling is currently unused + // We scale the rect size just to be consistent with stamp prim, + // which does same. The caller must scale the origin! + gscale = GetGraphicScale (); + arm = *r; + arm.extent.width = r->extent.width; + arm.extent.height = 1; + TFB_Prim_FillRect (&arm, color, mode, ctxOrigin); + arm.extent.height = r->extent.height; + arm.extent.width = 1; + TFB_Prim_FillRect (&arm, color, mode, ctxOrigin); + // rounding error correction here + arm.corner.x += ((r->extent.width * gscale + (GSCALE_IDENTITY >> 1)) + / GSCALE_IDENTITY) - 1; + TFB_Prim_FillRect (&arm, color, mode, ctxOrigin); + arm.corner.x = r->corner.x; + arm.corner.y += ((r->extent.height * gscale + (GSCALE_IDENTITY >> 1)) + / GSCALE_IDENTITY) - 1; + arm.extent.width = r->extent.width; + arm.extent.height = 1; + TFB_Prim_FillRect (&arm, color, mode, ctxOrigin); +} + +void +TFB_Prim_FillRect (RECT *r, Color color, DrawMode mode, POINT ctxOrigin) +{ + RECT rect; + int gscale; + + rect.corner.x = r->corner.x + ctxOrigin.x; + rect.corner.y = r->corner.y + ctxOrigin.y; + rect.extent.width = r->extent.width; + rect.extent.height = r->extent.height; + + // XXX: Rect prim scaling is currently unused + // We scale the rect size just to be consistent with stamp prim, + // which does same. The caller must scale the origin! + gscale = GetGraphicScale (); + if (gscale != GSCALE_IDENTITY) + { // rounding error correction here + rect.extent.width = (rect.extent.width * gscale + + (GSCALE_IDENTITY >> 1)) / GSCALE_IDENTITY; + rect.extent.height = (rect.extent.height * gscale + + (GSCALE_IDENTITY >> 1)) / GSCALE_IDENTITY; + } + + if (_CurFramePtr->Type == SCREEN_DRAWABLE) + TFB_DrawScreen_Rect (&rect, color, mode, TFB_SCREEN_MAIN); + else + TFB_DrawImage_Rect (&rect, color, mode, _CurFramePtr->image); +} + +void +TFB_Prim_Line (LINE *line, Color color, DrawMode mode, POINT ctxOrigin) +{ + int x1, y1, x2, y2; + + // The caller must scale the origins! + x1=line->first.x + ctxOrigin.x; + y1=line->first.y + ctxOrigin.y; + x2=line->second.x + ctxOrigin.x; + y2=line->second.y + ctxOrigin.y; + + if (_CurFramePtr->Type == SCREEN_DRAWABLE) + TFB_DrawScreen_Line (x1, y1, x2, y2, color, mode, TFB_SCREEN_MAIN); + else + TFB_DrawImage_Line (x1, y1, x2, y2, color, mode, _CurFramePtr->image); +} + +void +TFB_Prim_Stamp (STAMP *stmp, DrawMode mode, POINT ctxOrigin) +{ + int x, y; + FRAME SrcFramePtr; + TFB_Image *img; + TFB_ColorMap *cmap = NULL; + + SrcFramePtr = stmp->frame; + if (!SrcFramePtr) + { + log_add (log_Warning, "TFB_Prim_Stamp: Tried to draw a NULL frame" + " (Stamp address = %p)", (void *) stmp); + return; + } + img = SrcFramePtr->image; + + if (!img) + { + log_add (log_Warning, "Non-existent image to TFB_Prim_Stamp()"); + return; + } + + LockMutex (img->mutex); + + img->NormalHs = SrcFramePtr->HotSpot; + // We scale the image size here, but the caller must scale the origin! + x = stmp->origin.x + ctxOrigin.x; + y = stmp->origin.y + ctxOrigin.y; + + if (TFB_DrawCanvas_IsPaletted(img->NormalImg) && img->colormap_index != -1) + { + // returned cmap is addrefed, must release later + cmap = TFB_GetColorMap (img->colormap_index); + } + + UnlockMutex (img->mutex); + + if (_CurFramePtr->Type == SCREEN_DRAWABLE) + { + TFB_DrawScreen_Image (img, x, y, GetGraphicScale (), + GetGraphicScaleMode (), cmap, mode, TFB_SCREEN_MAIN); + } + else + { + TFB_DrawImage_Image (img, x, y, GetGraphicScale (), + GetGraphicScaleMode (), cmap, mode, _CurFramePtr->image); + } +} + +void +TFB_Prim_StampFill (STAMP *stmp, Color color, DrawMode mode, POINT ctxOrigin) +{ + int x, y; + FRAME SrcFramePtr; + TFB_Image *img; + + SrcFramePtr = stmp->frame; + if (!SrcFramePtr) + { + log_add (log_Warning, "TFB_Prim_StampFill: Tried to draw a NULL frame" + " (Stamp address = %p)", (void *) stmp); + return; + } + img = SrcFramePtr->image; + + if (!img) + { + log_add (log_Warning, "Non-existent image to TFB_Prim_StampFill()"); + return; + } + + LockMutex (img->mutex); + + img->NormalHs = SrcFramePtr->HotSpot; + // We scale the image size here, but the caller must scale the origin! + x = stmp->origin.x + ctxOrigin.x; + y = stmp->origin.y + ctxOrigin.y; + + UnlockMutex (img->mutex); + + if (_CurFramePtr->Type == SCREEN_DRAWABLE) + { + TFB_DrawScreen_FilledImage (img, x, y, GetGraphicScale (), + GetGraphicScaleMode (), color, mode, TFB_SCREEN_MAIN); + } + else + { + TFB_DrawImage_FilledImage (img, x, y, GetGraphicScale (), + GetGraphicScaleMode (), color, mode, _CurFramePtr->image); + } +} + +void +TFB_Prim_FontChar (POINT charOrigin, TFB_Char *fontChar, TFB_Image *backing, + DrawMode mode, POINT ctxOrigin) +{ + int x, y; + + // Text prim does not scale + x = charOrigin.x + ctxOrigin.x; + y = charOrigin.y + ctxOrigin.y; + + if (_CurFramePtr->Type == SCREEN_DRAWABLE) + { + TFB_DrawScreen_FontChar (fontChar, backing, x, y, mode, + TFB_SCREEN_MAIN); + } + else + { + TFB_DrawImage_FontChar (fontChar, backing, x, y, mode, + _CurFramePtr->image); + } +} + +// Text rendering is in font.c, under the name _text_blt diff --git a/src/libs/graphics/tfb_prim.h b/src/libs/graphics/tfb_prim.h new file mode 100644 index 0000000..6fcd546 --- /dev/null +++ b/src/libs/graphics/tfb_prim.h @@ -0,0 +1,30 @@ +// Copyright Michael Martin, 2003 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "tfb_draw.h" + + +void TFB_Prim_Line (LINE *, Color, DrawMode, POINT ctxOrigin); +void TFB_Prim_Point (POINT *, Color, DrawMode, POINT ctxOrigin); +void TFB_Prim_Rect (RECT *, Color, DrawMode, POINT ctxOrigin); +void TFB_Prim_FillRect (RECT *, Color, DrawMode, POINT ctxOrigin); +void TFB_Prim_Stamp (STAMP *, DrawMode, POINT ctxOrigin); +void TFB_Prim_StampFill (STAMP *, Color, DrawMode, POINT ctxOrigin); +void TFB_Prim_FontChar (POINT charOrigin, TFB_Char *fontChar, + TFB_Image *backing, DrawMode, POINT ctxOrigin); diff --git a/src/libs/graphics/widgets.c b/src/libs/graphics/widgets.c new file mode 100644 index 0000000..0c444bc --- /dev/null +++ b/src/libs/graphics/widgets.c @@ -0,0 +1,941 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "gfx_common.h" +#include "widgets.h" +#include "libs/strlib.h" + +WIDGET *widget_focus = NULL; + +/* Some basic color defines */ +#define WIDGET_ACTIVE_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x00), 0x0E) +#define WIDGET_INACTIVE_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x18, 0x18, 0x1F), 0x00) +#define WIDGET_INACTIVE_SELECTED_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F) +#define WIDGET_CURSOR_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x00), 0x00) +#define WIDGET_DIALOG_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x14, 0x14, 0x14), 0x07) +#define WIDGET_DIALOG_TEXT_COLOR \ + BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x00), 0x00) + +static Color win_bg_clr = + BUILD_COLOR (MAKE_RGB15_INIT (0x18, 0x18, 0x1F), 0x00); +static Color win_medium_clr = + BUILD_COLOR (MAKE_RGB15_INIT (0x10, 0x10, 0x18), 0x00); +static Color win_dark_clr = + BUILD_COLOR (MAKE_RGB15_INIT (0x08, 0x08, 0x10), 0x00); + +static FONT cur_font; + +void +DrawShadowedBox (RECT *r, Color bg, Color dark, Color medium) +{ + RECT t; + Color oldcolor; + + BatchGraphics (); + + t.corner.x = r->corner.x - 2; + t.corner.y = r->corner.y - 2; + t.extent.width = r->extent.width + 4; + t.extent.height = r->extent.height + 4; + oldcolor = SetContextForeGroundColor (dark); + DrawFilledRectangle (&t); + + t.corner.x += 2; + t.corner.y += 2; + t.extent.width -= 2; + t.extent.height -= 2; + SetContextForeGroundColor (medium); + DrawFilledRectangle (&t); + + t.corner.x -= 1; + t.corner.y += r->extent.height + 1; + t.extent.height = 1; + DrawFilledRectangle (&t); + + t.corner.x += r->extent.width + 2; + t.corner.y -= r->extent.height + 2; + t.extent.width = 1; + DrawFilledRectangle (&t); + + SetContextForeGroundColor (bg); + DrawFilledRectangle (r); + + SetContextForeGroundColor (oldcolor); + UnbatchGraphics (); +} + +// windowRect, if not NULL, will be filled with the dimensions of the +// window drawn. +void +DrawLabelAsWindow (WIDGET_LABEL *label, RECT *windowRect) +{ + Color oldfg = SetContextForeGroundColor (WIDGET_DIALOG_TEXT_COLOR); + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + RECT r; + TEXT t; + int i, win_w, win_h; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + /* Compute the dimensions of the label */ + win_h = label->height ((WIDGET *)label) + 16; + win_w = 0; + for (i = 0; i < label->line_count; i++) + { + int len = utf8StringCount (label->lines[i]); + if (len > win_w) + { + win_w = len; + } + } + win_w = (win_w * 6) + 16; + + BatchGraphics (); + r.corner.x = (ScreenWidth - win_w) >> 1; + r.corner.y = (ScreenHeight - win_h) >> 1; + r.extent.width = win_w; + r.extent.height = win_h; + DrawShadowedBox (&r, win_bg_clr, win_dark_clr, win_medium_clr); + + t.baseline.x = r.corner.x + (r.extent.width >> 1); + t.baseline.y = r.corner.y + 16; + for (i = 0; i < label->line_count; i++) + { + t.pStr = label->lines[i]; + t.align = ALIGN_CENTER; + t.CharCount = (COUNT)~0; + font_DrawText (&t); + t.baseline.y += 8; + } + + UnbatchGraphics (); + + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldfg); + + if (windowRect != NULL) { + // Add the outer border added by DrawShadowedBox. + // XXX: It may be nicer to add a border size parameter to + // DrawShadowedBox, instead of assuming 2 here. + windowRect->corner.x = r.corner.x - 2; + windowRect->corner.y = r.corner.y - 2; + windowRect->extent.width = r.extent.width + 4; + windowRect->extent.height = r.extent.height + 4; + } +} + +void +Widget_SetWindowColors (Color bg, Color dark, Color medium) +{ + win_bg_clr = bg; + win_dark_clr = dark; + win_medium_clr = medium; +} + +FONT +Widget_SetFont (FONT newFont) +{ + FONT oldFont = cur_font; + cur_font = newFont; + return oldFont; +} + +static void +Widget_DrawToolTips (int numlines, const char **tips) +{ + RECT r; + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + Color oldtext = SetContextForeGroundColor (WIDGET_INACTIVE_SELECTED_COLOR); + TEXT t; + int i; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + r.corner.x = 2; + r.corner.y = 2; + r.extent.width = ScreenWidth - 4; + r.extent.height = ScreenHeight - 4; + + t.align = ALIGN_CENTER; + t.CharCount = ~0; + t.baseline.x = r.corner.x + (r.extent.width >> 1); + t.baseline.y = r.corner.y + (r.extent.height - 8 - 8 * numlines); + + for (i = 0; i < numlines; i++) + { + t.pStr = tips[i]; + font_DrawText(&t); + t.baseline.y += 8; + } + + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); +} + +void +Widget_DrawMenuScreen (WIDGET *_self, int x, int y) +{ + RECT r; + Color title, oldtext; + Color inactive, default_color, selected; + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + TEXT t; + int widget_index, height, widget_y; + + WIDGET_MENU_SCREEN *self = (WIDGET_MENU_SCREEN *)_self; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + r.corner.x = 2; + r.corner.y = 2; + r.extent.width = ScreenWidth - 4; + r.extent.height = ScreenHeight - 4; + + title = WIDGET_INACTIVE_SELECTED_COLOR; + selected = WIDGET_ACTIVE_COLOR; + inactive = WIDGET_INACTIVE_COLOR; + default_color = title; + + DrawStamp (&self->bgStamp); + + oldtext = SetContextForeGroundColor (title); + t.baseline.x = r.corner.x + (r.extent.width >> 1); + t.baseline.y = r.corner.y + 8; + t.pStr = self->title; + t.align = ALIGN_CENTER; + t.CharCount = ~0; + font_DrawText (&t); + t.baseline.y += 8; + t.pStr = self->subtitle; + font_DrawText (&t); + + height = 0; + for (widget_index = 0; widget_index < self->num_children; widget_index++) + { + WIDGET *child = self->child[widget_index]; + height += (*child->height)(child); + height += 8; /* spacing */ + } + + height -= 8; + + widget_y = (ScreenHeight - height) >> 1; + for (widget_index = 0; widget_index < self->num_children; widget_index++) + { + WIDGET *c = self->child[widget_index]; + (*c->draw)(c, 0, widget_y); + widget_y += (*c->height)(c) + 8; + } + + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); + + (void) x; + (void) y; +} + +void +Widget_DrawChoice (WIDGET *_self, int x, int y) +{ + WIDGET_CHOICE *self = (WIDGET_CHOICE *)_self; + Color oldtext; + Color inactive, default_color, selected; + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + TEXT t; + int i, home_x, home_y; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + default_color = WIDGET_INACTIVE_SELECTED_COLOR; + selected = WIDGET_ACTIVE_COLOR; + inactive = WIDGET_INACTIVE_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); + + home_x = t.baseline.x + 3 * (ScreenWidth / ((self->maxcolumns + 1) * 2)); + home_y = t.baseline.y; + t.align = ALIGN_CENTER; + for (i = 0; i < self->numopts; i++) + { + t.baseline.x = home_x + ((i % 3) * + (ScreenWidth / (self->maxcolumns + 1))); + t.baseline.y = home_y + (8 * (i / 3)); + t.pStr = self->options[i].optname; + if ((widget_focus == _self) && + (self->highlighted == i)) + { + SetContextForeGroundColor (selected); + Widget_DrawToolTips (3, self->options[i].tooltip); + } + else if (i == self->selected) + { + SetContextForeGroundColor (default_color); + } + else + { + SetContextForeGroundColor (inactive); + } + font_DrawText (&t); + } + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); +} + +void +Widget_DrawButton (WIDGET *_self, int x, int y) +{ + WIDGET_BUTTON *self = (WIDGET_BUTTON *)_self; + Color oldtext; + Color inactive, selected; + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + TEXT t; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + selected = WIDGET_ACTIVE_COLOR; + inactive = WIDGET_INACTIVE_COLOR; + + t.baseline.x = 160; + t.baseline.y = y; + t.align = ALIGN_CENTER; + t.CharCount = ~0; + t.pStr = self->name; + if (widget_focus == _self) + { + Widget_DrawToolTips (3, self->tooltip); + oldtext = SetContextForeGroundColor (selected); + } + else + { + oldtext = SetContextForeGroundColor (inactive); + } + font_DrawText (&t); + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); + (void) x; +} + +void +Widget_DrawLabel (WIDGET *_self, int x, int y) +{ + WIDGET_LABEL *self = (WIDGET_LABEL *)_self; + Color oldtext = SetContextForeGroundColor (WIDGET_INACTIVE_SELECTED_COLOR); + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + TEXT t; + int i; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + t.baseline.x = 160; + t.baseline.y = y; + t.align = ALIGN_CENTER; + t.CharCount = ~0; + + for (i = 0; i < self->line_count; i++) + { + t.pStr = self->lines[i]; + font_DrawText (&t); + t.baseline.y += 8; + } + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); + (void) x; +} + +void +Widget_DrawSlider(WIDGET *_self, int x, int y) +{ + WIDGET_SLIDER *self = (WIDGET_SLIDER *)_self; + Color oldtext; + Color inactive, default_color, selected; + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + TEXT t; + RECT r; + int tick = (ScreenWidth - x) / 8; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + default_color = WIDGET_INACTIVE_SELECTED_COLOR; + selected = WIDGET_ACTIVE_COLOR; + inactive = WIDGET_INACTIVE_COLOR; + + t.baseline.x = x; + t.baseline.y = y; + t.align = ALIGN_LEFT; + t.CharCount = ~0; + t.pStr = self->category; + if (widget_focus == _self) + { + Widget_DrawToolTips (3, self->tooltip); + 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); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); +} + +void +Widget_Slider_DrawValue (WIDGET_SLIDER *self, int x, int y) +{ + TEXT t; + char buffer[16]; + + sprintf (buffer, "%d", self->value); + + t.baseline.x = x; + t.baseline.y = y; + t.align = ALIGN_CENTER; + t.CharCount = ~0; + t.pStr = buffer; + + font_DrawText (&t); +} + +void +Widget_DrawTextEntry (WIDGET *_self, int x, int y) +{ + WIDGET_TEXTENTRY *self = (WIDGET_TEXTENTRY *)_self; + Color oldtext; + Color inactive, default_color, selected; + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + TEXT t; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + default_color = WIDGET_INACTIVE_SELECTED_COLOR; + selected = WIDGET_ACTIVE_COLOR; + inactive = WIDGET_INACTIVE_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 = 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 = ScreenWidth - 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 (WIDGET_CURSOR_COLOR); + DrawFilledRectangle (&r); + + SetContextForeGroundColor (inactive); + font_DrawText (&t); + } + + UnbatchGraphics (); + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); +} + +void +Widget_DrawControlEntry (WIDGET *_self, int x, int y) +{ + WIDGET_CONTROLENTRY *self = (WIDGET_CONTROLENTRY *)_self; + Color oldtext; + Color inactive, default_color, selected; + FONT oldfont = 0; + FRAME oldFontEffect = SetContextFontEffect (NULL); + TEXT t; + int i, home_x, home_y; + + if (cur_font) + oldfont = SetContextFont (cur_font); + + default_color = WIDGET_INACTIVE_SELECTED_COLOR; + selected = WIDGET_ACTIVE_COLOR; + inactive = WIDGET_INACTIVE_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); + + // 3 * ScreenWidth / ((self->maxcolumns + 1) * 2)) as per CHOICE, but only two options. + home_x = t.baseline.x + (ScreenWidth / 2); + home_y = t.baseline.y; + t.align = ALIGN_CENTER; + for (i = 0; i < 2; i++) + { + t.baseline.x = home_x + ((i % 3) * (ScreenWidth / 3)); // self->maxcolumns + 1 as per CHOICE. + t.baseline.y = home_y + (8 * (i / 3)); + t.pStr = self->controlname[i]; + if (!t.pStr[0]) + { + t.pStr = "---"; + } + if ((widget_focus == _self) && + (self->highlighted == i)) + { + SetContextForeGroundColor (selected); + } + else + { + SetContextForeGroundColor (default_color); + } + font_DrawText (&t); + } + SetContextFontEffect (oldFontEffect); + if (oldfont) + SetContextFont (oldfont); + SetContextForeGroundColor (oldtext); +} + +int +Widget_HeightChoice (WIDGET *_self) +{ + return ((((WIDGET_CHOICE *)_self)->numopts + 2) / 3) * 8; +} + +int +Widget_HeightFullScreen (WIDGET *_self) +{ + (void)_self; + return ScreenHeight; +} + +int +Widget_HeightOneLine (WIDGET *_self) +{ + (void)_self; + return 8; +} + +int +Widget_HeightLabel (WIDGET *_self) +{ + WIDGET_LABEL *self = (WIDGET_LABEL *)_self; + return self->line_count * 8; +} + +int +Widget_WidthFullScreen (WIDGET *_self) +{ + (void)_self; + return ScreenWidth; +} + +int +Widget_ReceiveFocusSimple (WIDGET *_self, int event) +{ + widget_focus = _self; + (void)event; + return TRUE; +} + +int +Widget_ReceiveFocusChoice (WIDGET *_self, int event) +{ + WIDGET_CHOICE *self = (WIDGET_CHOICE *)_self; + widget_focus = _self; + self->highlighted = self->selected; + (void)event; + return TRUE; +} + +int +Widget_ReceiveFocusControlEntry (WIDGET *_self, int event) +{ + WIDGET_CONTROLENTRY *self = (WIDGET_CONTROLENTRY *)_self; + int oldval = 0; + if (widget_focus->tag == WIDGET_TYPE_CONTROLENTRY) + { + oldval = ((WIDGET_CONTROLENTRY *)widget_focus)->highlighted; + } + widget_focus = _self; + self->highlighted = oldval; + (void)event; + return TRUE; +} + +int +Widget_ReceiveFocusMenuScreen (WIDGET *_self, int event) +{ + WIDGET_MENU_SCREEN *self = (WIDGET_MENU_SCREEN *)_self; + int x, last_x, dx; + for (x = 0; x < self->num_children; x++) + { + self->child[x]->parent = _self; + } + if (event == WIDGET_EVENT_UP) + { + x = self->num_children - 1; + dx = -1; + last_x = -1; + } + else if (event == WIDGET_EVENT_DOWN) + { + x = 0; + dx = 1; + last_x = self->num_children; + } + else + { + /* Leave highlighted value the same */ + WIDGET *child = self->child[self->highlighted]; + child->receiveFocus (child, event); + return TRUE; + } + for ( ; x != last_x; x += dx) + { + WIDGET *child = self->child[x]; + if ((*child->receiveFocus)(child, event)) + { + self->highlighted = x; + return TRUE; + } + } + return FALSE; +} + +int +Widget_ReceiveFocusRefuseFocus (WIDGET *self, int event) +{ + (void)self; + (void)event; + return FALSE; +} + +int +Widget_HandleEventIgnoreAll (WIDGET *self, int event) +{ + (void)event; + (void)self; + return FALSE; +} + +int +Widget_HandleEventChoice (WIDGET *_self, int event) +{ + WIDGET_CHOICE *self = (WIDGET_CHOICE *)_self; + switch (event) + { + case WIDGET_EVENT_LEFT: + self->highlighted -= 1; + if (self->highlighted < 0) + self->highlighted = self->numopts - 1; + return TRUE; + case WIDGET_EVENT_RIGHT: + self->highlighted += 1; + if (self->highlighted >= self->numopts) + self->highlighted = 0; + return TRUE; + case WIDGET_EVENT_SELECT: + { + int oldval = self->selected; + self->selected = self->highlighted; + if (self->onChange) + { + (*(self->onChange))(self, oldval); + } + return TRUE; + } + default: + return FALSE; + } +} + +int +Widget_HandleEventSlider (WIDGET *_self, int event) +{ + WIDGET_SLIDER *self = (WIDGET_SLIDER *)_self; + switch (event) + { + case WIDGET_EVENT_LEFT: + self->value -= self->step; + if (self->value < self->min) + self->value = self->min; + return TRUE; + case WIDGET_EVENT_RIGHT: + self->value += self->step; + if (self->value > self->max) + self->value = self->max; + return TRUE; + default: + return FALSE; + } +} + +int +Widget_HandleEventMenuScreen (WIDGET *_self, int event) +{ + WIDGET_MENU_SCREEN *self = (WIDGET_MENU_SCREEN *)_self; + int x, last_x, dx; + switch (event) + { + case WIDGET_EVENT_UP: + dx = -1; + break; + case WIDGET_EVENT_DOWN: + dx = 1; + break; + case WIDGET_EVENT_CANCEL: + /* On cancel, shift focus to last element and send a SELECT. */ + self->highlighted = self->num_children - 1; + widget_focus = self->child[self->highlighted]; + return (widget_focus->handleEvent)(widget_focus, WIDGET_EVENT_SELECT); + default: + return FALSE; + } + last_x = self->highlighted; + x = self->highlighted + dx; + while (x != last_x) + { + WIDGET *child; + if (x == -1) + x = self->num_children - 1; + if (x == self->num_children) + x = 0; + child = self->child[x]; + if ((*child->receiveFocus)(child, event)) + { + self->highlighted = x; + return TRUE; + } + x += dx; + } + return FALSE; +} + +int +Widget_HandleEventTextEntry (WIDGET *_self, int event) +{ + WIDGET_TEXTENTRY *self = (WIDGET_TEXTENTRY *)_self; + if (event == WIDGET_EVENT_SELECT) { + if (!self->handleEventSelect) + return FALSE; + return (*self->handleEventSelect)(self); + } + return FALSE; +} + +int +Widget_HandleEventControlEntry (WIDGET *_self, int event) +{ + WIDGET_CONTROLENTRY *self = (WIDGET_CONTROLENTRY *)_self; + if (event == WIDGET_EVENT_SELECT) + { + if (self->onChange) + { + (self->onChange)(self); + return TRUE; + } + } + if (event == WIDGET_EVENT_DELETE) + { + if (self->onDelete) + { + (self->onDelete)(self); + return TRUE; + } + } + if ((event == WIDGET_EVENT_RIGHT) || + (event == WIDGET_EVENT_LEFT)) + { + self->highlighted = 1-self->highlighted; + return TRUE; + } + return FALSE; +} + +int +Widget_Event (int event) +{ + WIDGET *widget = widget_focus; + while (widget != NULL) + { + if ((*widget->handleEvent)(widget, event)) + return TRUE; + widget = widget->parent; + } + return FALSE; +} diff --git a/src/libs/graphics/widgets.h b/src/libs/graphics/widgets.h new file mode 100644 index 0000000..4548e35 --- /dev/null +++ b/src/libs/graphics/widgets.h @@ -0,0 +1,222 @@ +// 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 LIBS_GRAPHICS_WIDGETS_H_ +#define LIBS_GRAPHICS_WIDGETS_H_ + +#include "libs/gfxlib.h" + +enum { + WIDGET_EVENT_UP, + WIDGET_EVENT_DOWN, + WIDGET_EVENT_LEFT, + WIDGET_EVENT_RIGHT, + WIDGET_EVENT_SELECT, + WIDGET_EVENT_CANCEL, + WIDGET_EVENT_DELETE, + NUM_WIDGET_EVENTS +}; + +typedef enum { + WIDGET_TYPE_MENU_SCREEN, + WIDGET_TYPE_CHOICE, + WIDGET_TYPE_BUTTON, + WIDGET_TYPE_LABEL, + WIDGET_TYPE_SLIDER, + WIDGET_TYPE_TEXTENTRY, + WIDGET_TYPE_CONTROLENTRY, + NUM_WIDGET_TYPES +} WIDGET_TYPE; + +#define WIDGET_TEXTENTRY_WIDTH 50 +#define WIDGET_CONTROLENTRY_WIDTH 16 + +typedef struct _widget { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); +} WIDGET; + +typedef struct _widget_menu_screen { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); + const char *title; + const char *subtitle; + STAMP bgStamp; + int num_children; + struct _widget **child; + int highlighted; +} WIDGET_MENU_SCREEN; + +typedef struct { + const char *optname; + const char *tooltip[3]; +} CHOICE_OPTION; + +typedef struct _widget_choice { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); + const char *category; + int numopts; + int maxcolumns; + CHOICE_OPTION *options; + int selected, highlighted; + void (*onChange)(struct _widget_choice *self, int oldval); +} WIDGET_CHOICE; + +typedef struct _widget_button { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); + const char *name; + const char *tooltip[3]; +} WIDGET_BUTTON; + +typedef struct _widget_label { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); + int line_count; + const char **lines; +} WIDGET_LABEL; + +typedef struct _widget_slider { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); + void (*draw_value)(struct _widget_slider *self, int x, int y); + int min, max, step; + int value; + const char *category; + const char *tooltip[3]; +} WIDGET_SLIDER; + +typedef enum { + WTE_NORMAL = 0, + WTE_EDITING, + WTE_BLOCKCUR, + +} WIDGET_TEXTENTRY_STATE; + +typedef struct _widget_textentry { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); + int (*handleEventSelect)(struct _widget_textentry *self); + // handleEventSelect is an overridable callback event + // called by the default handleEvent implementation + // can be NULL, in which case SELECT is ignored + void (*onChange)(struct _widget_textentry *self); + const char *category; + char value[WIDGET_TEXTENTRY_WIDTH]; + int maxlen; + WIDGET_TEXTENTRY_STATE state; + int cursor_pos; +} WIDGET_TEXTENTRY; + +typedef struct _widget_controlentry { + WIDGET_TYPE tag; + struct _widget *parent; + int (*handleEvent)(struct _widget *self, int event); + int (*receiveFocus)(struct _widget *self, int event); + void (*draw)(struct _widget *self, int x, int y); + int (*height)(struct _widget *self); + int (*width)(struct _widget *self); + void (*onChange)(struct _widget_controlentry *self); + void (*onDelete)(struct _widget_controlentry *self); + const char *category; + int controlindex; + int highlighted; + char controlname[2][WIDGET_CONTROLENTRY_WIDTH]; +} WIDGET_CONTROLENTRY; + +void DrawShadowedBox (RECT *r, Color bg, Color dark, Color medium); +void DrawLabelAsWindow (WIDGET_LABEL *label, RECT *windowRect); +void Widget_SetWindowColors (Color bg, Color dark, Color medium); +FONT Widget_SetFont (FONT newFont); + + +int Widget_Event (int event); + +/* Methods for filling in widgets with */ + +int Widget_ReceiveFocusMenuScreen (WIDGET *_self, int event); +int Widget_ReceiveFocusChoice (WIDGET *_self, int event); +int Widget_ReceiveFocusSimple (WIDGET *_self, int event); +int Widget_ReceiveFocusSlider (WIDGET *_self, int event); +int Widget_ReceiveFocusControlEntry (WIDGET *_self, int event); +int Widget_ReceiveFocusRefuseFocus (WIDGET *_self, int event); + +int Widget_HandleEventMenuScreen (WIDGET *_self, int event); +int Widget_HandleEventChoice (WIDGET *_self, int event); +int Widget_HandleEventSlider (WIDGET *_self, int event); +int Widget_HandleEventTextEntry (WIDGET *_self, int event); +int Widget_HandleEventControlEntry (WIDGET *_self, int event); +int Widget_HandleEventIgnoreAll (WIDGET *_self, int event); + +int Widget_HeightChoice (WIDGET *_self); +int Widget_HeightFullScreen (WIDGET *_self); +int Widget_HeightOneLine (WIDGET *_self); +int Widget_HeightLabel (WIDGET *_self); + +int Widget_WidthFullScreen (WIDGET *_self); + +void Widget_DrawMenuScreen (WIDGET *_self, int x, int y); +void Widget_DrawChoice (WIDGET *_self, int x, int y); +void Widget_DrawButton (WIDGET *_self, int x, int y); +void Widget_DrawLabel (WIDGET *_self, int x, int y); +void Widget_DrawSlider (WIDGET *_self, int x, int y); +void Widget_DrawTextEntry (WIDGET *_self, int x, int y); +void Widget_DrawControlEntry (WIDGET *_self, int x, int y); + +void Widget_Slider_DrawValue (WIDGET_SLIDER *self, int x, int y); + +/* Other implementations will need these values */ +extern WIDGET *widget_focus; + +#endif /* LIBS_GRAPHICS_WIDGETS_H_ */ diff --git a/src/libs/heap.h b/src/libs/heap.h new file mode 100644 index 0000000..674649b --- /dev/null +++ b/src/libs/heap.h @@ -0,0 +1,9 @@ +#if defined(__cplusplus) +extern "C" { +#endif + +#include "heap/heap.h" + +#if defined(__cplusplus) +} +#endif diff --git a/src/libs/heap/Makeinfo b/src/libs/heap/Makeinfo new file mode 100644 index 0000000..1622e1c --- /dev/null +++ b/src/libs/heap/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="heap.c" +uqm_HFILES="heap.h" diff --git a/src/libs/heap/heap.c b/src/libs/heap/heap.c new file mode 100644 index 0000000..1f4c0f7 --- /dev/null +++ b/src/libs/heap/heap.c @@ -0,0 +1,197 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "heap.h" + +#include +#include +#include +#include "port.h" + +static inline size_t nextPower2(size_t x); + + +static void +Heap_resize(Heap *heap, size_t size) { + heap->entries = realloc(heap->entries, size * sizeof (HeapValue *)); + heap->size = size; +} + +// Heap inv: comparator(parent, child) <= 0 for every child of every parent. +Heap * +Heap_new(HeapValue_Comparator comparator, size_t initialSize, size_t minSize, + double minFillQuotient) { + Heap *heap; + + assert(minFillQuotient >= 0.0); + + heap = malloc(sizeof (Heap)); + + if (initialSize < minSize) + initialSize = minSize; + + heap->comparator = comparator; + heap->minSize = minSize; + heap->minFillQuotient = minFillQuotient; + heap->size = nextPower2(initialSize); + heap->minFill = ceil(((double) (heap->size >> 1)) + * heap->minFillQuotient); + heap->entries = malloc(heap->size * sizeof (HeapValue *)); + heap->numEntries = 0; + + return heap; +} + +void +Heap_delete(Heap *heap) { + free(heap->entries); + free(heap); +} + +void +Heap_add(Heap *heap, HeapValue *value) { + size_t i; + + if (heap->numEntries >= heap->size) + Heap_resize(heap, heap->size * 2); + + i = heap->numEntries; + heap->numEntries++; + + while (i > 0) { + size_t parentI = (i - 1) / 2; + if (heap->comparator(heap->entries[parentI], value) <= 0) + break; + + heap->entries[i] = heap->entries[parentI]; + heap->entries[i]->index = i; + i = parentI; + } + heap->entries[i] = value; + heap->entries[i]->index = i; +} + +HeapValue * +Heap_first(const Heap *heap) { + assert(heap->numEntries > 0); + + return heap->entries[0]; +} + +static void +Heap_removeByIndex(Heap *heap, size_t i) { + assert(heap->numEntries > i); + + heap->numEntries--; + + if (heap->numEntries != 0) { + // Restore the heap invariant. We're shifting entries into the + // gap that was created until we find the place where we can + // insert the last entry. + HeapValue *lastEntry = heap->entries[heap->numEntries]; + + for (;;) { + size_t childI = i * 2 + 1; + // The two children are childI and 'childI + 1'. + + if (childI + 1 >= heap->numEntries) { + // There is no right child. + + if (childI >= heap->numEntries) { + // There is no left child either. + break; + } + } else { + if (heap->comparator(heap->entries[childI + 1], + heap->entries[childI]) < 0) { + // The right child is the child with the lowest value. + childI++; + } + } + // childI is now the child with the lowest value. + + if (heap->comparator(lastEntry, heap->entries[childI]) <= 0) { + // The last entry goes here. + break; + } + + // Move the child into the gap. + heap->entries[i] = heap->entries[childI]; + heap->entries[i]->index = i; + + // and repeat for the child. + i = childI; + } + + // Fill the gap with the last entry. + heap->entries[i] = lastEntry; + heap->entries[i]->index = i; + } + + // Resize if necessary: + if (heap->numEntries < heap->minFill && + heap->numEntries > heap->minSize) + Heap_resize(heap, heap->size / 2); +} + +HeapValue * +Heap_pop(Heap *heap) { + HeapValue *result; + + assert(heap->numEntries > 0); + + result = heap->entries[0]; + Heap_removeByIndex(heap, 0); + + return result; +} + +size_t +Heap_count(const Heap *heap) { + return heap->numEntries; +} + +bool +Heap_hasMore(const Heap *heap) { + return heap->numEntries > 0; +} + +void +Heap_remove(Heap *heap, HeapValue *value) { + Heap_removeByIndex(heap, value->index); +} + +// Adapted from "Hackers Delight" +// Returns the smallest power of two greater or equal to x. +static inline size_t +nextPower2(size_t x) { + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; +# if (SIZE_MAX > 0xffff) + x |= x >> 16; +# if (SIZE_MAX > 0xffffffff) + x |= x >> 32; +# endif +# endif + return x + 1; +} + + diff --git a/src/libs/heap/heap.h b/src/libs/heap/heap.h new file mode 100644 index 0000000..9a3f829 --- /dev/null +++ b/src/libs/heap/heap.h @@ -0,0 +1,69 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_HEAP_HEAP_H_ +#define LIBS_HEAP_HEAP_H_ + +#include "types.h" + +#include +#include + + +typedef struct Heap Heap; +typedef struct HeapValue HeapValue; + +// The actual value stored should "inherit" from this. +struct HeapValue { + size_t index; +}; +typedef int (*HeapValue_Comparator)(HeapValue *v1, HeapValue *v2); + + +struct Heap { + HeapValue_Comparator comparator; + // Comparison function to determine the order of the + // elements. + size_t minSize; + // Never resize below this many number of entries. + double minFillQuotient; + // How much of half of the heap needs to be filled before + // resizing to size/2. + + HeapValue **entries; + // Actual values + size_t numEntries; + size_t size; + // Number of entries that fit in the heap as it is now. + size_t minFill; + // Resize to size/2 when below this size. +}; + + +Heap *Heap_new(HeapValue_Comparator comparator, size_t initialSize, + size_t minSize, double minFillQuotient); +void Heap_delete(Heap *heap); +void Heap_add(Heap *heap, HeapValue *value); +HeapValue *Heap_first(const Heap *heap); +HeapValue *Heap_pop(Heap *heap); +size_t Heap_count(const Heap *heap); +bool Heap_hasMore(const Heap *heap); +void Heap_remove(Heap *heap, HeapValue *value); + +#endif /* LIBS_HEAP_HEAP_H_ */ + diff --git a/src/libs/inplib.h b/src/libs/inplib.h new file mode 100644 index 0000000..2df1f79 --- /dev/null +++ b/src/libs/inplib.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 LIBS_INPLIB_H_ +#define LIBS_INPLIB_H_ + +#include +#include "libs/compiler.h" +#include "libs/uio.h" +#include "libs/unicode.h" + +#if defined(__cplusplus) +extern "C" { +#endif + + +extern BOOLEAN AnyButtonPress (BOOLEAN DetectSpecial); + +extern void TFB_ResetControls (void); + +/* + * Not used right now +extern BOOLEAN FindMouse (void); +extern void MoveMouse (SWORD x, SWORD y); +extern BYTE LocateMouse (SWORD *px, SWORD *py); +*/ + +extern volatile int MouseButtonDown; +extern volatile int QuitPosted; +extern volatile int GameActive; + +/* Functions for dealing with Character Mode */ + +void EnterCharacterMode (void); +void ExitCharacterMode (void); +UniChar GetNextCharacter (void); +UniChar GetLastCharacter (void); + +/* Interrogating the current key configuration */ + +void InterrogateInputState (int templat, int control, int index, char *buffer, int maxlen); +void RemoveInputState (int templat, int control, int index); +void RebindInputState (int templat, int control, int index); + +void SaveKeyConfiguration (uio_DirHandle *path, const char *fname); + +/* Separate inputs into frames for dealing with very fast inputs */ + +void BeginInputFrame (void); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_INPLIB_H */ diff --git a/src/libs/input/Makeinfo b/src/libs/input/Makeinfo new file mode 100644 index 0000000..b76604f --- /dev/null +++ b/src/libs/input/Makeinfo @@ -0,0 +1,6 @@ +if [ "$uqm_GFXMODULE" = "sdl" ]; then + uqm_SUBDIRS="sdl" +fi + +uqm_CFILES="input_common.c" +uqm_HFILES="inpintrn.h input_common.h" diff --git a/src/libs/input/inpintrn.h b/src/libs/input/inpintrn.h new file mode 100644 index 0000000..1d48ccc --- /dev/null +++ b/src/libs/input/inpintrn.h @@ -0,0 +1,25 @@ +//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 LIBS_INPUT_INPINTRN_H_ +#define LIBS_INPUT_INPINTRN_H_ + +#include "libs/inplib.h" +#include "libs/input/input_common.h" + +#endif /* LIBS_INPUT_INPINTRN_H_ */ diff --git a/src/libs/input/input_common.c b/src/libs/input/input_common.c new file mode 100644 index 0000000..7a0bbc1 --- /dev/null +++ b/src/libs/input/input_common.c @@ -0,0 +1,20 @@ +//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 "inpintrn.h" diff --git a/src/libs/input/input_common.h b/src/libs/input/input_common.h new file mode 100644 index 0000000..5979320 --- /dev/null +++ b/src/libs/input/input_common.h @@ -0,0 +1,39 @@ +//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 INPUT_COMMON_H +#define INPUT_COMMON_H + +// driver for TFB_InitInput +enum +{ + TFB_INPUTDRIVER_SDL +}; + +// flags for TFB_InitInput +//#define TFB_INPUTFLAGS_ETC (1<<0) + +extern int TFB_InitInput (int driver, int flags); +extern void TFB_UninitInput (void); + +#define MAX_FLIGHT_ALTERNATES 2 + +extern void TFB_SetInputVectors (volatile int menu[], int num_menu, + volatile int flight[], int num_templ, int num_flight); + +#endif diff --git a/src/libs/input/sdl/Makeinfo b/src/libs/input/sdl/Makeinfo new file mode 100644 index 0000000..427da53 --- /dev/null +++ b/src/libs/input/sdl/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="input.c keynames.c vcontrol.c" +uqm_HFILES="input.h keynames.h vcontrol.h" diff --git a/src/libs/input/sdl/input.c b/src/libs/input/sdl/input.c new file mode 100644 index 0000000..bff17aa --- /dev/null +++ b/src/libs/input/sdl/input.c @@ -0,0 +1,625 @@ +//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 +#include +#include +#include "input.h" +#include "../inpintrn.h" +#include "libs/threadlib.h" +#include "libs/input/sdl/vcontrol.h" +#include "libs/input/sdl/keynames.h" +#include "libs/memlib.h" +#include "libs/file.h" +#include "libs/log.h" +#include "libs/reslib.h" +#include "options.h" + + +#define KBDBUFSIZE (1 << 8) +static int kbdhead=0, kbdtail=0; +static UniChar kbdbuf[KBDBUFSIZE]; +static UniChar lastchar; +#if SDL_MAJOR_VERSION == 1 +static unsigned int num_keys = 0; +static int *kbdstate = NULL; + // Holds all SDL keys +1 for holding invalid values +#else // Later versions of SDL use the text input API instead +static BOOLEAN set_character_mode = FALSE; + // Records whether the UI thread has caught up with game thread + // on this setting +#endif + +static volatile int *menu_vec; +static int num_menu; +// The last vector element is the character repeat "key" +// This is only used in SDL1 input but it's mostly harmless everywhere else +#define KEY_MENU_ANY (num_menu - 1) +static volatile int *flight_vec; +static int num_templ; +static int num_flight; + +static BOOLEAN InputInitialized = FALSE; + +static BOOLEAN in_character_mode = FALSE; + +static const char *menu_res_names[] = { + "pause", + "exit", + "abort", + "debug", + "fullscreen", + "up", + "down", + "left", + "right", + "select", + "cancel", + "special", + "pageup", + "pagedown", + "home", + "end", + "zoomin", + "zoomout", + "delete", + "backspace", + "editcancel", + "search", + "next", + NULL +}; + +static const char *flight_res_names[] = { + "up", + "down", + "left", + "right", + "weapon", + "special", + "escape", + NULL +}; + +static void +register_menu_controls (int index) +{ + int i; + char buf[40]; + buf[39] = '\0'; + + i = 1; + while (TRUE) + { + VCONTROL_GESTURE g; + snprintf (buf, 39, "menu.%s.%d", menu_res_names[index], i); + if (!res_IsString (buf)) + break; + VControl_ParseGesture (&g, res_GetString (buf)); + VControl_AddGestureBinding (&g, (int *)&menu_vec[index]); + i++; + } +} + + +static VCONTROL_GESTURE *controls; +#define CONTROL_PTR(i, j, k) \ + (controls + ((i) * num_flight + (j)) * MAX_FLIGHT_ALTERNATES + (k)) + +static void +register_flight_controls (void) +{ + int i, j, k; + char buf[40]; + + buf[39] = '\0'; + + for (i = 0; i < num_templ; i++) + { + /* Copy in name */ + snprintf (buf, 39, "keys.%d.name", i+1); + if (res_IsString (buf)) + { + strncpy (input_templates[i].name, res_GetString (buf), 29); + input_templates[i].name[29] = '\0'; + } + else + { + input_templates[i].name[0] = '\0'; + } + for (j = 0; j < num_flight; j++) + { + for (k = 0; k < MAX_FLIGHT_ALTERNATES; k++) + { + VCONTROL_GESTURE *g = CONTROL_PTR(i, j, k); + snprintf (buf, 39, "keys.%d.%s.%d", i+1, flight_res_names[j], k+1); + if (!res_IsString (buf)) + { + g->type = VCONTROL_NONE; + continue; + } + VControl_ParseGesture (g, res_GetString (buf)); + VControl_AddGestureBinding (g, (int *)(flight_vec + i * num_flight + j)); + } + } + } +} + +static void +initKeyConfig (void) +{ + int i; + + if (!menu_vec || !flight_vec) + { + log_add (log_Fatal, "initKeyConfig(): invalid input vectors"); + exit (EXIT_FAILURE); + } + + controls = HCalloc (sizeof (*controls) * num_templ * num_flight + * MAX_FLIGHT_ALTERNATES); + + /* First, load in the menu keys */ + LoadResourceIndex (contentDir, "menu.key", "menu."); + LoadResourceIndex (configDir, "override.cfg", "menu."); + for (i = 0; i < num_menu; i++) + { + if (!menu_res_names[i]) + break; + register_menu_controls (i); + } + + LoadResourceIndex (configDir, "flight.cfg", "keys."); + if (!res_HasKey ("keys.1.name")) + { + /* Either flight.cfg doesn't exist, or we're using an old version + of flight.cfg, and thus we wound up loading untyped values into + 'keys.keys.1.name' and such. Load the defaults from the content + directory. */ + LoadResourceIndex (contentDir, "uqm.key", "keys."); + } + + register_flight_controls (); + + return; +} + +static void +resetKeyboardState (void) +{ +#if SDL_MAJOR_VERSION == 1 + memset (kbdstate, 0, sizeof (int) * num_keys); + menu_vec[KEY_MENU_ANY] = 0; +#endif +} + +void +TFB_SetInputVectors (volatile int menu[], int num_menu_, volatile int flight[], + int num_templ_, int num_flight_) +{ + if (num_menu_ < 0 || num_templ_ < 0 || num_flight_ < 0) + { + log_add (log_Fatal, "TFB_SetInputVectors(): invalid vector size"); + exit (EXIT_FAILURE); + } + menu_vec = menu; + num_menu = num_menu_; + flight_vec = flight; + num_templ = num_templ_; + num_flight = num_flight_; +} + +#ifdef HAVE_JOYSTICK + +static void +initJoystick (void) +{ + int nJoysticks; + + if ((SDL_InitSubSystem(SDL_INIT_JOYSTICK)) == -1) + { + log_add (log_Fatal, "Couldn't initialize joystick subsystem: %s", + SDL_GetError()); + exit (EXIT_FAILURE); + } + + log_add (log_Info, "%i joysticks were found.", SDL_NumJoysticks ()); + + nJoysticks = SDL_NumJoysticks (); + if (nJoysticks > 0) + { + int i; + + log_add (log_Info, "The names of the joysticks are:"); + for (i = 0; i < nJoysticks; i++) + { + log_add (log_Info, " %s", +#if SDL_MAJOR_VERSION == 1 + SDL_JoystickName (i)); +#else + SDL_JoystickNameForIndex (i)); +#endif + } + SDL_JoystickEventState (SDL_ENABLE); + } +} + +#endif /* HAVE_JOYSTICK */ + +int +TFB_InitInput (int driver, int flags) +{ + (void)driver; + (void)flags; + +#if SDL_MAJOR_VERSION == 1 + SDL_EnableUNICODE(1); + (void)SDL_GetKeyState (&num_keys); + kbdstate = (int *)HMalloc (sizeof (int) * (num_keys + 1)); +#endif + +#ifdef HAVE_JOYSTICK + initJoystick (); +#endif + + in_character_mode = FALSE; + resetKeyboardState (); + + /* Prepare the Virtual Controller system. */ + VControl_Init (); + + initKeyConfig (); + + VControl_ResetInput (); + InputInitialized = TRUE; + + return 0; +} + +void +TFB_UninitInput (void) +{ + VControl_Uninit (); + HFree (controls); +#if SDL_MAJOR_VERSION == 1 + HFree (kbdstate); +#endif +} + +void +EnterCharacterMode (void) +{ + kbdhead = kbdtail = 0; + lastchar = 0; + in_character_mode = TRUE; + VControl_ResetInput (); +} + +void +ExitCharacterMode (void) +{ + VControl_ResetInput (); + in_character_mode = FALSE; + kbdhead = kbdtail = 0; + lastchar = 0; +} + +UniChar +GetNextCharacter (void) +{ + UniChar result; + if (kbdhead == kbdtail) + return 0; + result = kbdbuf[kbdhead]; + kbdhead = (kbdhead + 1) & (KBDBUFSIZE - 1); + return result; +} + +UniChar +GetLastCharacter (void) +{ + return lastchar; +} + +volatile int MouseButtonDown = 0; + +static void +ProcessMouseEvent (const SDL_Event *e) +{ + switch (e->type) + { + case SDL_MOUSEBUTTONDOWN: + MouseButtonDown = 1; + break; + case SDL_MOUSEBUTTONUP: + MouseButtonDown = 0; + break; + default: + break; + } +} + +#if SDL_MAJOR_VERSION == 1 + +static inline int +is_numpad_char_event (const SDL_Event *Event) +{ + return in_character_mode && + (Event->type == SDL_KEYDOWN || Event->type == SDL_KEYUP) && + (Event->key.keysym.mod & KMOD_NUM) && /* NumLock is ON */ + Event->key.keysym.unicode > 0 && /* Printable char */ + Event->key.keysym.sym >= SDLK_KP0 && /* Keypad key */ + Event->key.keysym.sym <= SDLK_KP_PLUS; +} + +void +ProcessInputEvent (const SDL_Event *Event) +{ + if (!InputInitialized) + return; + + ProcessMouseEvent (Event); + + // In character mode with NumLock on, numpad chars bypass VControl + // so that menu arrow events are not produced + if (!is_numpad_char_event (Event)) + VControl_HandleEvent (Event); + + if (Event->type == SDL_KEYDOWN || Event->type == SDL_KEYUP) + { // process character input event, if any + // keysym.sym is an SDLKey type which is an enum and can be signed + // or unsigned on different platforms; we'll use a guaranteed type + int k = Event->key.keysym.sym; + UniChar map_key = Event->key.keysym.unicode; + + if (k < 0 || k > num_keys) + k = num_keys; // for unknown keys + + if (Event->type == SDL_KEYDOWN) + { + int newtail; + + // dont care about the non-printable, non-char + if (!map_key) + return; + + kbdstate[k]++; + + newtail = (kbdtail + 1) & (KBDBUFSIZE - 1); + // ignore the char if the buffer is full + if (newtail != kbdhead) + { + kbdbuf[kbdtail] = map_key; + kbdtail = newtail; + lastchar = map_key; + menu_vec[KEY_MENU_ANY]++; + } + } + else if (Event->type == SDL_KEYUP) + { + if (kbdstate[k] == 0) + { // something is fishy -- better to reset the + // repeatable state to avoid big problems + menu_vec[KEY_MENU_ANY] = 0; + } + else + { + kbdstate[k]--; + if (menu_vec[KEY_MENU_ANY] > 0) + menu_vec[KEY_MENU_ANY]--; + } + } + } +} +#else +void +ProcessInputEvent (const SDL_Event *Event) +{ + if (!InputInitialized) + return; + + ProcessMouseEvent (Event); + + if (in_character_mode && !set_character_mode) + { + set_character_mode = TRUE; + SDL_StartTextInput (); + } + + if (!in_character_mode && set_character_mode) + { + set_character_mode = FALSE; + SDL_StopTextInput (); + } + + /* TODO: Block numpad input when NUM_LOCK is on */ + VControl_HandleEvent (Event); + + if (Event->type == SDL_TEXTINPUT) + { + int newtail; + int i = 0; + + while (Event->text.text[i]) + { + UniChar map_key = Event->text.text[i++]; + + /* Decode any UTF-8 keys */ + if (map_key >= 0xC0 && map_key < 0xE0) + { + /* 2-byte UTF-8 */ + map_key = (map_key & 0x1f) << 6; + map_key |= Event->text.text[i++] & 0x3f; + } + else if (map_key >= 0xE0 && map_key < 0xF0) + { + /* 3-byte UTF-8 */ + map_key = (map_key & 0x0f) << 6; + map_key |= Event->text.text[i++] & 0x3f; + map_key <<= 6; + map_key |= Event->text.text[i++] & 0x3f; + } + else if (map_key >= 0xF0) + { + /* Out of the BMP, won't fit in a UniChar */ + /* Use the replacement character instead */ + map_key = 0xFFFD; + while ((UniChar)Event->text.text[i] > 0x7F) + { + ++i; + } + } + + /* dont care about the non-printable, non-char */ + if (!map_key) + return; + + newtail = (kbdtail + 1) & (KBDBUFSIZE - 1); + + /* ignore the char if the buffer is full */ + if (newtail != kbdhead) + { + kbdbuf[kbdtail] = map_key; + kbdtail = newtail; + lastchar = map_key; + } + + /* Loop back in case there are more chars in the + * text input buffer */ + } + } +} + +#endif + +void +TFB_ResetControls (void) +{ + VControl_ResetInput (); + resetKeyboardState (); + // flush character buffer + kbdhead = kbdtail = 0; + lastchar = 0; +} + +void +InterrogateInputState (int templat, int control, int index, char *buffer, int maxlen) +{ + VCONTROL_GESTURE *g = CONTROL_PTR(templat, control, index); + + if (templat >= num_templ || control >= num_flight + || index >= MAX_FLIGHT_ALTERNATES) + { + log_add (log_Warning, "InterrogateInputState(): invalid control index"); + buffer[0] = 0; + return; + } + + switch (g->type) + { + case VCONTROL_KEY: + snprintf (buffer, maxlen, "%s", VControl_code2name (g->gesture.key)); + buffer[maxlen-1] = 0; + break; + case VCONTROL_JOYBUTTON: + snprintf (buffer, maxlen, "[J%d B%d]", g->gesture.button.port, g->gesture.button.index + 1); + buffer[maxlen-1] = 0; + break; + case VCONTROL_JOYAXIS: + snprintf (buffer, maxlen, "[J%d A%d %c]", g->gesture.axis.port, g->gesture.axis.index, g->gesture.axis.polarity > 0 ? '+' : '-'); + break; + case VCONTROL_JOYHAT: + snprintf (buffer, maxlen, "[J%d H%d %d]", g->gesture.hat.port, g->gesture.hat.index, g->gesture.hat.dir); + break; + default: + /* Something we don't handle yet */ + buffer[0] = 0; + break; + } + return; +} + +void +RemoveInputState (int templat, int control, int index) +{ + VCONTROL_GESTURE *g = CONTROL_PTR(templat, control, index); + char keybuf[40]; + keybuf[39] = '\0'; + + if (templat >= num_templ || control >= num_flight + || index >= MAX_FLIGHT_ALTERNATES) + { + log_add (log_Warning, "RemoveInputState(): invalid control index"); + return; + } + + VControl_RemoveGestureBinding (g, + (int *)(flight_vec + templat * num_flight + control)); + g->type = VCONTROL_NONE; + + snprintf (keybuf, 39, "keys.%d.%s.%d", templat+1, flight_res_names[control], index+1); + res_Remove (keybuf); + + return; +} + +void +RebindInputState (int templat, int control, int index) +{ + VCONTROL_GESTURE g; + char keybuf[40], valbuf[40]; + keybuf[39] = valbuf[39] = '\0'; + + if (templat >= num_templ || control >= num_flight + || index >= MAX_FLIGHT_ALTERNATES) + { + log_add (log_Warning, "RebindInputState(): invalid control index"); + return; + } + + /* Remove the old binding on this spot */ + RemoveInputState (templat, control, index); + + /* Wait for the next interesting bit of user input */ + VControl_ClearGesture (); + while (!VControl_GetLastGesture (&g)) + { + TaskSwitch (); + } + + /* And now, add the new binding. */ + VControl_AddGestureBinding (&g, + (int *)(flight_vec + templat * num_flight + control)); + *CONTROL_PTR(templat, control, index) = g; + snprintf (keybuf, 39, "keys.%d.%s.%d", templat+1, flight_res_names[control], index+1); + VControl_DumpGesture (valbuf, 39, &g); + res_PutString (keybuf, valbuf); +} + +void +SaveKeyConfiguration (uio_DirHandle *path, const char *fname) +{ + SaveResourceIndex (path, fname, "keys.", TRUE); +} + +void +BeginInputFrame (void) +{ + VControl_BeginFrame (); +} + diff --git a/src/libs/input/sdl/input.h b/src/libs/input/sdl/input.h new file mode 100644 index 0000000..3c599aa --- /dev/null +++ b/src/libs/input/sdl/input.h @@ -0,0 +1,27 @@ +//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 INPUT_H +#define INPUT_H + +#include "port.h" +#include SDL_INCLUDE(SDL.h) + +extern void ProcessInputEvent (const SDL_Event *Event); + +#endif diff --git a/src/libs/input/sdl/keynames.c b/src/libs/input/sdl/keynames.c new file mode 100644 index 0000000..86c104a --- /dev/null +++ b/src/libs/input/sdl/keynames.c @@ -0,0 +1,229 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 SDL_INCLUDE(SDL.h) +#include +#include "keynames.h" + +/* This code is adapted from the code in SDL_keysym.h. Though this + * would almost certainly be fast if we were to use a direct char * + * array, this technique permits us to be independent of the actual + * character encoding to keysyms. */ + +/* These names are case-insensitive when compared, but we format + * them to look pretty when output */ + +/* This version of Virtual Controller does not support SDLK_WORLD_* + * keysyms or the Num/Caps/ScrollLock keys. SDL treats locking keys + * specially, and we cannot treat them as normal keys. Pain, + * tragedy. */ + +typedef struct vcontrol_keyname { + const char *name; + int code; +} keyname; + +static keyname keynames[] = { + {"Backspace", SDLK_BACKSPACE}, + {"Tab", SDLK_TAB}, + {"Clear", SDLK_CLEAR}, + {"Return", SDLK_RETURN}, + {"Pause", SDLK_PAUSE}, + {"Escape", SDLK_ESCAPE}, + {"Space", SDLK_SPACE}, + {"!", SDLK_EXCLAIM}, + {"\"", SDLK_QUOTEDBL}, + {"Hash", SDLK_HASH}, + {"$", SDLK_DOLLAR}, + {"&", SDLK_AMPERSAND}, + {"'", SDLK_QUOTE}, + {"(", SDLK_LEFTPAREN}, + {")", SDLK_RIGHTPAREN}, + {"*", SDLK_ASTERISK}, + {"+", SDLK_PLUS}, + {",", SDLK_COMMA}, + {"-", SDLK_MINUS}, + {".", SDLK_PERIOD}, + {"/", SDLK_SLASH}, + {"0", SDLK_0}, + {"1", SDLK_1}, + {"2", SDLK_2}, + {"3", SDLK_3}, + {"4", SDLK_4}, + {"5", SDLK_5}, + {"6", SDLK_6}, + {"7", SDLK_7}, + {"8", SDLK_8}, + {"9", SDLK_9}, + {":", SDLK_COLON}, + {";", SDLK_SEMICOLON}, + {"<", SDLK_LESS}, + {"=", SDLK_EQUALS}, + {">", SDLK_GREATER}, + {"?", SDLK_QUESTION}, + {"@", SDLK_AT}, + {"[", SDLK_LEFTBRACKET}, + {"\\", SDLK_BACKSLASH}, + {"]", SDLK_RIGHTBRACKET}, + {"^", SDLK_CARET}, + {"_", SDLK_UNDERSCORE}, + {"`", SDLK_BACKQUOTE}, + {"a", SDLK_a}, + {"b", SDLK_b}, + {"c", SDLK_c}, + {"d", SDLK_d}, + {"e", SDLK_e}, + {"f", SDLK_f}, + {"g", SDLK_g}, + {"h", SDLK_h}, + {"i", SDLK_i}, + {"j", SDLK_j}, + {"k", SDLK_k}, + {"l", SDLK_l}, + {"m", SDLK_m}, + {"n", SDLK_n}, + {"o", SDLK_o}, + {"p", SDLK_p}, + {"q", SDLK_q}, + {"r", SDLK_r}, + {"s", SDLK_s}, + {"t", SDLK_t}, + {"u", SDLK_u}, + {"v", SDLK_v}, + {"w", SDLK_w}, + {"x", SDLK_x}, + {"y", SDLK_y}, + {"z", SDLK_z}, + {"Delete", SDLK_DELETE}, +#if SDL_MAJOR_VERSION == 1 + {"Keypad-0", SDLK_KP0}, + {"Keypad-1", SDLK_KP1}, + {"Keypad-2", SDLK_KP2}, + {"Keypad-3", SDLK_KP3}, + {"Keypad-4", SDLK_KP4}, + {"Keypad-5", SDLK_KP5}, + {"Keypad-6", SDLK_KP6}, + {"Keypad-7", SDLK_KP7}, + {"Keypad-8", SDLK_KP8}, + {"Keypad-9", SDLK_KP9}, +#else + {"Keypad-0", SDLK_KP_0}, + {"Keypad-1", SDLK_KP_1}, + {"Keypad-2", SDLK_KP_2}, + {"Keypad-3", SDLK_KP_3}, + {"Keypad-4", SDLK_KP_4}, + {"Keypad-5", SDLK_KP_5}, + {"Keypad-6", SDLK_KP_6}, + {"Keypad-7", SDLK_KP_7}, + {"Keypad-8", SDLK_KP_8}, + {"Keypad-9", SDLK_KP_9}, +#endif + {"Keypad-.", SDLK_KP_PERIOD}, + {"Keypad-/", SDLK_KP_DIVIDE}, + {"Keypad-*", SDLK_KP_MULTIPLY}, + {"Keypad--", SDLK_KP_MINUS}, + {"Keypad-+", SDLK_KP_PLUS}, + {"Keypad-Enter", SDLK_KP_ENTER}, + {"Keypad-=", SDLK_KP_EQUALS}, + {"Up", SDLK_UP}, + {"Down", SDLK_DOWN}, + {"Right", SDLK_RIGHT}, + {"Left", SDLK_LEFT}, + {"Insert", SDLK_INSERT}, + {"Home", SDLK_HOME}, + {"End", SDLK_END}, + {"PageUp", SDLK_PAGEUP}, + {"PageDown", SDLK_PAGEDOWN}, + {"F1", SDLK_F1}, + {"F2", SDLK_F2}, + {"F3", SDLK_F3}, + {"F4", SDLK_F4}, + {"F5", SDLK_F5}, + {"F6", SDLK_F6}, + {"F7", SDLK_F7}, + {"F8", SDLK_F8}, + {"F9", SDLK_F9}, + {"F10", SDLK_F10}, + {"F11", SDLK_F11}, + {"F12", SDLK_F12}, + {"F13", SDLK_F13}, + {"F14", SDLK_F14}, + {"F15", SDLK_F15}, + {"RightShift", SDLK_RSHIFT}, + {"LeftShift", SDLK_LSHIFT}, + {"RightControl", SDLK_RCTRL}, + {"LeftControl", SDLK_LCTRL}, + {"RightAlt", SDLK_RALT}, + {"LeftAlt", SDLK_LALT}, +#if SDL_MAJOR_VERSION == 1 + {"RightMeta", SDLK_RMETA}, + {"LeftMeta", SDLK_LMETA}, + {"RightSuper", SDLK_RSUPER}, + {"LeftSuper", SDLK_LSUPER}, + {"AltGr", SDLK_MODE}, + {"Compose", SDLK_COMPOSE}, + {"Help", SDLK_HELP}, + {"Print", SDLK_PRINT}, + {"SysReq", SDLK_SYSREQ}, + {"Break", SDLK_BREAK}, + {"Menu", SDLK_MENU}, + {"Power", SDLK_POWER}, + {"Euro", SDLK_EURO}, + {"Undo", SDLK_UNDO}, +#ifdef _WIN32_WCE + {"App1", SDLK_APP1}, + {"App2", SDLK_APP2}, + {"App3", SDLK_APP3}, + {"App4", SDLK_APP4}, + {"App5", SDLK_APP5}, + {"App6", SDLK_APP6}, +#endif /* _WIN32_WCE */ +#endif /* SDL_MAJOR_VERSION == 1 */ + + {"Unknown", 0}}; +/* Last element must have code zero */ + +const char * +VControl_code2name (int code) +{ + int i = 0; + while (1) + { + int test = keynames[i].code; + if (test == code || !test) + { + return keynames[i].name; + } + ++i; + } +} + +int +VControl_name2code (const char *name) +{ + int i = 0; + while (1) + { + const char *test = keynames[i].name; + int code = keynames[i].code; + if (!strcasecmp(test, name) || !code) + { + return code; + } + ++i; + } +} diff --git a/src/libs/input/sdl/keynames.h b/src/libs/input/sdl/keynames.h new file mode 100644 index 0000000..affd854 --- /dev/null +++ b/src/libs/input/sdl/keynames.h @@ -0,0 +1,22 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_INPUT_SDL_KEYNAMES_H_ +#define LIBS_INPUT_SDL_KEYNAMES_H_ + +const char *VControl_code2name (int code); +int VControl_name2code (const char *code); +#endif diff --git a/src/libs/input/sdl/vcontrol.c b/src/libs/input/sdl/vcontrol.c new file mode 100644 index 0000000..9c226ae --- /dev/null +++ b/src/libs/input/sdl/vcontrol.c @@ -0,0 +1,1300 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 SDL_INCLUDE(SDL.h) +#include +#include +#include +#include "vcontrol.h" +#include "libs/memlib.h" +#include "keynames.h" +#include "libs/log.h" +#include "libs/reslib.h" + +/* How many binding slots are allocated at once. */ +#define POOL_CHUNK_SIZE 64 + +/* Total number of key input buckets. SDL1 keys are a simple enum, + * but SDL2 scatters key symbols through the entire 32-bit space, + * so we do not rely on being able to declare an array with one + * entry per key. */ +#define KEYBOARD_INPUT_BUCKETS 512 + +typedef struct vcontrol_keybinding { + int *target; + sdl_key_t keycode; + struct vcontrol_keypool *parent; + struct vcontrol_keybinding *next; +} keybinding; + +typedef struct vcontrol_keypool { + keybinding pool[POOL_CHUNK_SIZE]; + int remaining; + struct vcontrol_keypool *next; +} keypool; + + +#ifdef HAVE_JOYSTICK + +typedef struct vcontrol_joystick_axis { + keybinding *neg, *pos; + int polarity; +} axis_type; + +typedef struct vcontrol_joystick_hat { + keybinding *left, *right, *up, *down; + Uint8 last; +} hat_type; + +typedef struct vcontrol_joystick { + SDL_Joystick *stick; + int numaxes, numbuttons, numhats; + int threshold; + axis_type *axes; + keybinding **buttons; + hat_type *hats; +} joystick; + +static joystick *joysticks; + +#endif /* HAVE_JOYSTICK */ + +static unsigned int joycount; +static keybinding *bindings[KEYBOARD_INPUT_BUCKETS]; + +static keypool *pool; + +/* Last interesting event */ +static int event_ready; +static SDL_Event last_interesting; + +static keypool * +allocate_key_chunk (void) +{ + keypool *x = HMalloc (sizeof (keypool)); + if (x) + { + int i; + x->remaining = POOL_CHUNK_SIZE; + x->next = NULL; + for (i = 0; i < POOL_CHUNK_SIZE; i++) + { + x->pool[i].target = NULL; + x->pool[i].keycode = SDLK_UNKNOWN; + x->pool[i].next = NULL; + x->pool[i].parent = x; + } + } + return x; +} + +static void +free_key_pool (keypool *x) +{ + if (x) + { + free_key_pool (x->next); + HFree (x); + } +} + +#ifdef HAVE_JOYSTICK + +static void +create_joystick (int index) +{ + SDL_Joystick *stick; + int axes, buttons, hats; + if ((unsigned int) index >= joycount) + { + log_add (log_Warning, "VControl warning: Tried to open a non-existent joystick!"); + return; + } + if (joysticks[index].stick) + { + // Joystick is already created. Return. + return; + } + stick = SDL_JoystickOpen (index); + if (stick) + { + joystick *x = &joysticks[index]; + int j; +#if SDL_MAJOR_VERSION == 1 + log_add (log_Info, "VControl opened joystick: %s", SDL_JoystickName (index)); +#else + log_add (log_Info, "VControl opened joystick: %s", SDL_JoystickName (stick)); +#endif + axes = SDL_JoystickNumAxes (stick); + buttons = SDL_JoystickNumButtons (stick); + hats = SDL_JoystickNumHats (stick); + log_add (log_Info, "%d axes, %d buttons, %d hats.", axes, buttons, hats); + x->numaxes = axes; + x->numbuttons = buttons; + x->numhats = hats; + x->axes = HMalloc (sizeof (axis_type) * axes); + x->buttons = HMalloc (sizeof (keybinding *) * buttons); + x->hats = HMalloc (sizeof (hat_type) * hats); + for (j = 0; j < axes; j++) + { + x->axes[j].neg = x->axes[j].pos = NULL; + } + for (j = 0; j < hats; j++) + { + x->hats[j].left = x->hats[j].right = NULL; + x->hats[j].up = x->hats[j].down = NULL; + x->hats[j].last = SDL_HAT_CENTERED; + } + for (j = 0; j < buttons; j++) + { + x->buttons[j] = NULL; + } + x->stick = stick; + } + else + { + log_add (log_Warning, "VControl: Could not initialize joystick #%d", index); + } +} + +static void +destroy_joystick (int index) +{ + SDL_Joystick *stick = joysticks[index].stick; + if (stick) + { + SDL_JoystickClose (stick); + joysticks[index].stick = NULL; + HFree (joysticks[index].axes); + HFree (joysticks[index].buttons); + HFree (joysticks[index].hats); + joysticks[index].numaxes = joysticks[index].numbuttons = 0; + joysticks[index].axes = NULL; + joysticks[index].buttons = NULL; + joysticks[index].hats = NULL; + } +} + +#endif /* HAVE_JOYSTICK */ + +static void +key_init (void) +{ + unsigned int i; + int num_keys; // Temp to match type of param for SDL_GetKeyState(). + pool = allocate_key_chunk (); + for (i = 0; i < KEYBOARD_INPUT_BUCKETS; i++) + bindings[i] = NULL; + +#ifdef HAVE_JOYSTICK + /* Prepare for possible joystick controls. We don't actually + GRAB joysticks unless we're asked to make a joystick + binding, though. */ + joycount = SDL_NumJoysticks (); + if (joycount) + { + joysticks = HMalloc (sizeof (joystick) * joycount); + for (i = 0; i < joycount; i++) + { + joysticks[i].stick = NULL; + joysticks[i].numaxes = joysticks[i].numbuttons = 0; + joysticks[i].axes = NULL; + joysticks[i].buttons = NULL; + joysticks[i].threshold = 10000; + } + } + else + { + joysticks = NULL; + } +#else + joycount = 0; +#endif /* HAVE_JOYSTICK */ +} + +static void +key_uninit (void) +{ + unsigned int i; + free_key_pool (pool); + for (i = 0; i < KEYBOARD_INPUT_BUCKETS; i++) + bindings[i] = NULL; + pool = NULL; + +#ifdef HAVE_JOYSTICK + for (i = 0; i < joycount; i++) + destroy_joystick (i); + HFree (joysticks); +#endif /* HAVE_JOYSTICK */ +} + +void +VControl_Init (void) +{ + key_init (); +} + +void +VControl_Uninit (void) +{ + key_uninit (); +} + +int +VControl_SetJoyThreshold (int port, int threshold) +{ +#ifdef HAVE_JOYSTICK + if (port >= 0 && (unsigned int) port < joycount) + { + joysticks[port].threshold = threshold; + return 0; + } + else +#else + (void) port; + (void) threshold; +#endif /* HAVE_JOYSTICK */ + { + // log_add (log_Warning, "VControl_SetJoyThreshold passed illegal port %d", port); + return -1; + } +} + + +static void +add_binding (keybinding **newptr, int *target, sdl_key_t keycode) +{ + keybinding *newbinding; + keypool *searchbase; + int i; + + /* Acquire a pointer to the keybinding * that we'll be + * overwriting. Along the way, ensure we haven't already + * bound this symbol to this target. If we have, return.*/ + while (*newptr != NULL) + { + if (((*newptr)->target == target) && ((*newptr)->keycode == keycode)) + { + return; + } + newptr = &((*newptr)->next); + } + + /* Now hunt through the binding pool for a free binding. */ + + /* First, find a chunk with free spots in it */ + + searchbase = pool; + while (searchbase->remaining == 0) + { + /* If we're completely full, allocate a new chunk */ + if (searchbase->next == NULL) + { + searchbase->next = allocate_key_chunk (); + } + searchbase = searchbase->next; + } + + /* Now find a free binding within it */ + + newbinding = NULL; + for (i = 0; i < POOL_CHUNK_SIZE; i++) + { + if (searchbase->pool[i].target == NULL) + { + newbinding = &searchbase->pool[i]; + break; + } + } + + /* Sanity check. */ + if (!newbinding) + { + log_add (log_Warning, "add_binding failed to find a free binding slot!"); + return; + } + + newbinding->target = target; + newbinding->keycode = keycode; + newbinding->next = NULL; + *newptr = newbinding; + searchbase->remaining--; +} + +static void +remove_binding (keybinding **ptr, int *target, sdl_key_t keycode) +{ + if (!(*ptr)) + { + /* Nothing bound to symbol; return. */ + return; + } + else if (((*ptr)->target == target) && ((*ptr)->keycode == keycode)) + { + keybinding *todel = *ptr; + *ptr = todel->next; + todel->target = NULL; + todel->keycode = SDLK_UNKNOWN; + todel->next = NULL; + todel->parent->remaining++; + } + else + { + keybinding *prev = *ptr; + while (prev && prev->next != NULL) + { + if (prev->next->target == target) + { + keybinding *todel = prev->next; + prev->next = todel->next; + todel->target = NULL; + todel->keycode = SDLK_UNKNOWN; + todel->next = NULL; + todel->parent->remaining++; + } + prev = prev->next; + } + } +} + +static void +activate (keybinding *i, sdl_key_t keycode) +{ + while (i != NULL) + { + if (i->keycode == keycode) + { + *(i->target) = (*(i->target)+1) | VCONTROL_STARTBIT; + } + i = i->next; + } +} + +static void +deactivate (keybinding *i, sdl_key_t keycode) +{ + while (i != NULL) + { + int v = *(i->target) & VCONTROL_MASK; + if ((i->keycode == keycode) && (v > 0)) + { + *(i->target) = (v-1) | (*(i->target) & VCONTROL_STARTBIT); + } + i = i->next; + } +} + +static void +event2gesture (SDL_Event *e, VCONTROL_GESTURE *g) +{ + switch (e->type) + { + case SDL_KEYDOWN: + g->type = VCONTROL_KEY; + g->gesture.key = e->key.keysym.sym; + break; + case SDL_JOYAXISMOTION: + g->type = VCONTROL_JOYAXIS; + g->gesture.axis.port = e->jaxis.which; + g->gesture.axis.index = e->jaxis.axis; + g->gesture.axis.polarity = (e->jaxis.value < 0) ? -1 : 1; + break; + case SDL_JOYHATMOTION: + g->type = VCONTROL_JOYHAT; + g->gesture.hat.port = e->jhat.which; + g->gesture.hat.index = e->jhat.hat; + g->gesture.hat.dir = e->jhat.value; + break; + case SDL_JOYBUTTONDOWN: + g->type = VCONTROL_JOYBUTTON; + g->gesture.button.port = e->jbutton.which; + g->gesture.button.index = e->jbutton.button; + break; + + default: + g->type = VCONTROL_NONE; + break; + } +} + +int +VControl_AddGestureBinding (VCONTROL_GESTURE *g, int *target) +{ + int result = -1; + switch (g->type) + { + case VCONTROL_KEY: + result = VControl_AddKeyBinding (g->gesture.key, target); + break; + + case VCONTROL_JOYAXIS: +#ifdef HAVE_JOYSTICK + result = VControl_AddJoyAxisBinding (g->gesture.axis.port, g->gesture.axis.index, (g->gesture.axis.polarity < 0) ? -1 : 1, target); + break; +#endif + case VCONTROL_JOYHAT: +#ifdef HAVE_JOYSTICK + result = VControl_AddJoyHatBinding (g->gesture.hat.port, g->gesture.hat.index, g->gesture.hat.dir, target); + break; +#endif + case VCONTROL_JOYBUTTON: +#ifdef HAVE_JOYSTICK + result = VControl_AddJoyButtonBinding (g->gesture.button.port, g->gesture.button.index, target); + break; +#endif /* HAVE_JOYSTICK */ + case VCONTROL_NONE: + /* Do nothing */ + break; + + default: + log_add (log_Warning, "VControl_AddGestureBinding didn't understand argument gesture"); + result = -1; + break; + } + return result; +} + +void +VControl_RemoveGestureBinding (VCONTROL_GESTURE *g, int *target) +{ + switch (g->type) + { + case VCONTROL_KEY: + VControl_RemoveKeyBinding (g->gesture.key, target); + break; + + case VCONTROL_JOYAXIS: +#ifdef HAVE_JOYSTICK + VControl_RemoveJoyAxisBinding (g->gesture.axis.port, g->gesture.axis.index, (g->gesture.axis.polarity < 0) ? -1 : 1, target); + break; +#endif /* HAVE_JOYSTICK */ + case VCONTROL_JOYHAT: +#ifdef HAVE_JOYSTICK + VControl_RemoveJoyHatBinding (g->gesture.hat.port, g->gesture.hat.index, g->gesture.hat.dir, target); + break; +#endif /* HAVE_JOYSTICK */ + case VCONTROL_JOYBUTTON: +#ifdef HAVE_JOYSTICK + VControl_RemoveJoyButtonBinding (g->gesture.button.port, g->gesture.button.index, target); + break; +#endif /* HAVE_JOYSTICK */ + case VCONTROL_NONE: + break; + default: + log_add (log_Warning, "VControl_RemoveGestureBinding didn't understand argument gesture"); + break; + } +} + +int +VControl_AddKeyBinding (sdl_key_t symbol, int *target) +{ + add_binding(&bindings[symbol % KEYBOARD_INPUT_BUCKETS], target, symbol); + return 0; +} + +void +VControl_RemoveKeyBinding (sdl_key_t symbol, int *target) +{ + remove_binding (&bindings[symbol % KEYBOARD_INPUT_BUCKETS], target, symbol); +} + +int +VControl_AddJoyAxisBinding (int port, int axis, int polarity, int *target) +{ +#ifdef HAVE_JOYSTICK + if (port >= 0 && (unsigned int) port < joycount) + { + joystick *j = &joysticks[port]; + if (!(j->stick)) + create_joystick (port); + if ((axis >= 0) && (axis < j->numaxes)) + { + if (polarity < 0) + { + add_binding(&joysticks[port].axes[axis].neg, target, SDLK_UNKNOWN); + } + else if (polarity > 0) + { + add_binding(&joysticks[port].axes[axis].pos, target, SDLK_UNKNOWN); + } + else + { + log_add (log_Debug, "VControl: Attempted to bind to polarity zero"); + return -1; + } + } + else + { + // log_add (log_Debug, "VControl: Attempted to bind to illegal axis %d", axis); + return -1; + } + } + else +#else + (void) port; + (void) axis; + (void) polarity; + (void) target; +#endif /* HAVE_JOYSTICK */ + { + // log_add (log_Debug, "VControl: Attempted to bind to illegal port %d", port); + return -1; + } + return 0; +} + +void +VControl_RemoveJoyAxisBinding (int port, int axis, int polarity, int *target) +{ +#ifdef HAVE_JOYSTICK + if (port >= 0 && (unsigned int) port < joycount) + { + joystick *j = &joysticks[port]; + if (!(j->stick)) + create_joystick (port); + if ((axis >= 0) && (axis < j->numaxes)) + { + if (polarity < 0) + { + remove_binding(&joysticks[port].axes[axis].neg, target, SDLK_UNKNOWN); + } + else if (polarity > 0) + { + remove_binding(&joysticks[port].axes[axis].pos, target, SDLK_UNKNOWN); + } + else + { + log_add (log_Debug, "VControl: Attempted to unbind from polarity zero"); + } + } + else + { + log_add (log_Debug, "VControl: Attempted to unbind from illegal axis %d", axis); + } + } + else +#else + (void) port; + (void) axis; + (void) polarity; + (void) target; +#endif /* HAVE_JOYSTICK */ + { + log_add (log_Debug, "VControl: Attempted to unbind from illegal port %d", port); + } +} + +int +VControl_AddJoyButtonBinding (int port, int button, int *target) +{ +#ifdef HAVE_JOYSTICK + if (port >= 0 && (unsigned int) port < joycount) + { + joystick *j = &joysticks[port]; + if (!(j->stick)) + create_joystick (port); + if ((button >= 0) && (button < j->numbuttons)) + { + add_binding(&joysticks[port].buttons[button], target, SDLK_UNKNOWN); + return 0; + } + else + { + // log_add (log_Debug, "VControl: Attempted to bind to illegal button %d", button); + return -1; + } + } + else +#else + (void) port; + (void) button; + (void) target; +#endif /* HAVE_JOYSTICK */ + { + // log_add (log_Debug, "VControl: Attempted to bind to illegal port %d", port); + return -1; + } +} + +void +VControl_RemoveJoyButtonBinding (int port, int button, int *target) +{ +#ifdef HAVE_JOYSTICK + if (port >= 0 && (unsigned int) port < joycount) + { + joystick *j = &joysticks[port]; + if (!(j->stick)) + create_joystick (port); + if ((button >= 0) && (button < j->numbuttons)) + { + remove_binding (&joysticks[port].buttons[button], target, SDLK_UNKNOWN); + } + else + { + log_add (log_Debug, "VControl: Attempted to unbind from illegal button %d", button); + } + } + else +#else + (void) port; + (void) button; + (void) target; +#endif /* HAVE_JOYSTICK */ + { + log_add (log_Debug, "VControl: Attempted to unbind from illegal port %d", port); + } +} + +int +VControl_AddJoyHatBinding (int port, int which, Uint8 dir, int *target) +{ +#ifdef HAVE_JOYSTICK + if (port >= 0 && (unsigned int) port < joycount) + { + joystick *j = &joysticks[port]; + if (!(j->stick)) + create_joystick (port); + if ((which >= 0) && (which < j->numhats)) + { + if (dir == SDL_HAT_LEFT) + { + add_binding(&joysticks[port].hats[which].left, target, SDLK_UNKNOWN); + } + else if (dir == SDL_HAT_RIGHT) + { + add_binding(&joysticks[port].hats[which].right, target, SDLK_UNKNOWN); + } + else if (dir == SDL_HAT_UP) + { + add_binding(&joysticks[port].hats[which].up, target, SDLK_UNKNOWN); + } + else if (dir == SDL_HAT_DOWN) + { + add_binding(&joysticks[port].hats[which].down, target, SDLK_UNKNOWN); + } + else + { + // log_add (log_Debug, "VControl: Attempted to bind to illegal direction"); + return -1; + } + return 0; + } + else + { + // log_add (log_Debug, "VControl: Attempted to bind to illegal hat %d", which); + return -1; + } + } + else +#else + (void) port; + (void) which; + (void) dir; + (void) target; +#endif /* HAVE_JOYSTICK */ + { + // log_add (log_Debug, "VControl: Attempted to bind to illegal port %d", port); + return -1; + } +} + +void +VControl_RemoveJoyHatBinding (int port, int which, Uint8 dir, int *target) +{ +#ifdef HAVE_JOYSTICK + if (port >= 0 && (unsigned int) port < joycount) + { + joystick *j = &joysticks[port]; + if (!(j->stick)) + create_joystick (port); + if ((which >= 0) && (which < j->numhats)) + { + if (dir == SDL_HAT_LEFT) + { + remove_binding(&joysticks[port].hats[which].left, target, SDLK_UNKNOWN); + } + else if (dir == SDL_HAT_RIGHT) + { + remove_binding(&joysticks[port].hats[which].right, target, SDLK_UNKNOWN); + } + else if (dir == SDL_HAT_UP) + { + remove_binding(&joysticks[port].hats[which].up, target, SDLK_UNKNOWN); + } + else if (dir == SDL_HAT_DOWN) + { + remove_binding(&joysticks[port].hats[which].down, target, SDLK_UNKNOWN); + } + else + { + log_add (log_Debug, "VControl: Attempted to unbind from illegal direction"); + } + } + else + { + log_add (log_Debug, "VControl: Attempted to unbind from illegal hat %d", which); + } + } + else +#else + (void) port; + (void) which; + (void) dir; + (void) target; +#endif /* HAVE_JOYSTICK */ + { + log_add (log_Debug, "VControl: Attempted to unbind from illegal port %d", port); + } +} + +void +VControl_RemoveAllBindings (void) +{ + key_uninit (); + key_init (); +} + +void +VControl_ProcessKeyDown (sdl_key_t symbol) +{ + activate (bindings[symbol % KEYBOARD_INPUT_BUCKETS], symbol); +} + +void +VControl_ProcessKeyUp (sdl_key_t symbol) +{ + deactivate (bindings[symbol % KEYBOARD_INPUT_BUCKETS], symbol); +} + +void +VControl_ProcessJoyButtonDown (int port, int button) +{ +#ifdef HAVE_JOYSTICK + if (!joysticks[port].stick) + return; + activate (joysticks[port].buttons[button], SDLK_UNKNOWN); +#else + (void) port; + (void) button; +#endif /* HAVE_JOYSTICK */ +} + +void +VControl_ProcessJoyButtonUp (int port, int button) +{ +#ifdef HAVE_JOYSTICK + if (!joysticks[port].stick) + return; + deactivate (joysticks[port].buttons[button], SDLK_UNKNOWN); +#else + (void) port; + (void) button; +#endif /* HAVE_JOYSTICK */ +} + +void +VControl_ProcessJoyAxis (int port, int axis, int value) +{ +#ifdef HAVE_JOYSTICK + int t; + if (!joysticks[port].stick) + return; + t = joysticks[port].threshold; + if (value > t) + { + if (joysticks[port].axes[axis].polarity != 1) + { + if (joysticks[port].axes[axis].polarity == -1) + { + deactivate (joysticks[port].axes[axis].neg, SDLK_UNKNOWN); + } + joysticks[port].axes[axis].polarity = 1; + activate (joysticks[port].axes[axis].pos, SDLK_UNKNOWN); + } + } + else if (value < -t) + { + if (joysticks[port].axes[axis].polarity != -1) + { + if (joysticks[port].axes[axis].polarity == 1) + { + deactivate (joysticks[port].axes[axis].pos, SDLK_UNKNOWN); + } + joysticks[port].axes[axis].polarity = -1; + activate (joysticks[port].axes[axis].neg, SDLK_UNKNOWN); + } + } + else + { + if (joysticks[port].axes[axis].polarity == -1) + { + deactivate (joysticks[port].axes[axis].neg, SDLK_UNKNOWN); + } + else if (joysticks[port].axes[axis].polarity == 1) + { + deactivate (joysticks[port].axes[axis].pos, SDLK_UNKNOWN); + } + joysticks[port].axes[axis].polarity = 0; + } +#else + (void) port; + (void) axis; + (void) value; +#endif /* HAVE_JOYSTICK */ +} + +void +VControl_ProcessJoyHat (int port, int which, Uint8 value) +{ +#ifdef HAVE_JOYSTICK + Uint8 old; + if (!joysticks[port].stick) + return; + old = joysticks[port].hats[which].last; + if (!(old & SDL_HAT_LEFT) && (value & SDL_HAT_LEFT)) + activate (joysticks[port].hats[which].left, SDLK_UNKNOWN); + if (!(old & SDL_HAT_RIGHT) && (value & SDL_HAT_RIGHT)) + activate (joysticks[port].hats[which].right, SDLK_UNKNOWN); + if (!(old & SDL_HAT_UP) && (value & SDL_HAT_UP)) + activate (joysticks[port].hats[which].up, SDLK_UNKNOWN); + if (!(old & SDL_HAT_DOWN) && (value & SDL_HAT_DOWN)) + activate (joysticks[port].hats[which].down, SDLK_UNKNOWN); + if ((old & SDL_HAT_LEFT) && !(value & SDL_HAT_LEFT)) + deactivate (joysticks[port].hats[which].left, SDLK_UNKNOWN); + if ((old & SDL_HAT_RIGHT) && !(value & SDL_HAT_RIGHT)) + deactivate (joysticks[port].hats[which].right, SDLK_UNKNOWN); + if ((old & SDL_HAT_UP) && !(value & SDL_HAT_UP)) + deactivate (joysticks[port].hats[which].up, SDLK_UNKNOWN); + if ((old & SDL_HAT_DOWN) && !(value & SDL_HAT_DOWN)) + deactivate (joysticks[port].hats[which].down, SDLK_UNKNOWN); + joysticks[port].hats[which].last = value; +#else + (void) port; + (void) which; + (void) value; +#endif /* HAVE_JOYSTICK */ +} + +void +VControl_ResetInput (void) +{ + /* Step through every valid entry in the binding pool and zero + * them out. This will probably zero entries multiple times; + * oh well, no harm done. */ + + keypool *base = pool; + while (base != NULL) + { + int i; + for (i = 0; i < POOL_CHUNK_SIZE; i++) + { + if(base->pool[i].target) + { + *(base->pool[i].target) = 0; + } + } + base = base->next; + } +} + +void +VControl_BeginFrame (void) +{ + /* Step through every valid entry in the binding pool and zero + * out the frame-start bit. This will probably zero entries + * multiple times; oh well, no harm done. */ + + keypool *base = pool; + while (base != NULL) + { + int i; + for (i = 0; i < POOL_CHUNK_SIZE; i++) + { + if(base->pool[i].target) + { + *(base->pool[i].target) &= VCONTROL_MASK; + } + } + base = base->next; + } +} + +void +VControl_HandleEvent (const SDL_Event *e) +{ + switch (e->type) + { + case SDL_KEYDOWN: +#if SDL_MAJOR_VERSION > 1 + if (!e->key.repeat) +#endif + { + VControl_ProcessKeyDown (e->key.keysym.sym); + last_interesting = *e; + event_ready = 1; + } + break; + case SDL_KEYUP: + VControl_ProcessKeyUp (e->key.keysym.sym); + break; + +#ifdef HAVE_JOYSTICK + case SDL_JOYAXISMOTION: + VControl_ProcessJoyAxis (e->jaxis.which, e->jaxis.axis, e->jaxis.value); + if ((e->jaxis.value > 15000) || (e->jaxis.value < -15000)) + { + last_interesting = *e; + event_ready = 1; + } + break; + case SDL_JOYHATMOTION: + VControl_ProcessJoyHat (e->jhat.which, e->jhat.hat, e->jhat.value); + last_interesting = *e; + event_ready = 1; + break; + case SDL_JOYBUTTONDOWN: + VControl_ProcessJoyButtonDown (e->jbutton.which, e->jbutton.button); + last_interesting = *e; + event_ready = 1; + break; + case SDL_JOYBUTTONUP: + VControl_ProcessJoyButtonUp (e->jbutton.which, e->jbutton.button); + break; +#endif /* HAVE_JOYSTICK */ + + default: + break; + } +} + +/* Tracking the last interesting event */ + +void +VControl_ClearGesture (void) +{ + event_ready = 0; +} + +int +VControl_GetLastGesture (VCONTROL_GESTURE *g) +{ + if (event_ready && g != NULL) + { + event2gesture(&last_interesting, g); + } + return event_ready; +} + +/* Configuration file grammar is as follows: One command per line, + * hashes introduce comments that persist to end of line. Blank lines + * are ignored. + * + * Terminals are represented here as quoted strings, e.g. "foo" for + * the literal string foo. These are matched case-insensitively. + * Special terminals are: + * + * KEYNAME: This names a key, as defined in keynames.c. + * IDNAME: This is an arbitrary string of alphanumerics, + * case-insensitive, and ending with a colon. This + * names an application-specific control value. + * NUM: This is an unsigned integer. + * EOF: End of file + * + * Nonterminals (the grammar itself) have the following productions: + * + * configline <- IDNAME binding + * | "joystick" NUM "threshold" NUM + * | "version" NUM + * + * binding <- "key" KEYNAME + * | "joystick" NUM joybinding + * + * joybinding <- "axis" NUM polarity + * | "button" NUM + * | "hat" NUM direction + * + * polarity <- "positive" | "negative" + * + * dir <- "up" | "down" | "left" | "right" + * + * This grammar is amenable to simple recursive descent parsing; + * in fact, it's fully LL(1). */ + +/* Actual maximum line and token sizes are two less than this, since + * we need space for the \n\0 at the end */ +#define LINE_SIZE 256 +#define TOKEN_SIZE 64 + +typedef struct vcontrol_parse_state { + char line[LINE_SIZE]; + char token[TOKEN_SIZE]; + int index; + int error; + int linenum; +} parse_state; + +static void +next_token (parse_state *state) +{ + int index, base; + + state->token[0] = 0; + /* skip preceding whitespace */ + base = state->index; + while (state->line[base] && isspace (state->line[base])) + { + base++; + } + + index = 0; + while (index < (TOKEN_SIZE-1) && state->line[base+index] && !isspace (state->line[base+index])) + { + state->token[index] = state->line[base+index]; + index++; + } + state->token[index] = 0; + + /* If the token was too long, skip ahead until we get to whitespace */ + while (state->line[base+index] && !isspace (state->line[base+index])) + { + index++; + } + + state->index = base+index; +} + +static void +expected_error (parse_state *state, const char *expected) +{ + log_add (log_Warning, "VControl: Expected '%s' on config file line %d", + expected, state->linenum); + state->error = 1; +} + +static void +consume (parse_state *state, const char *expected) +{ + if (strcasecmp (expected, state->token)) + { + expected_error (state, expected); + } + next_token (state); +} + +static int +consume_keyname (parse_state *state) +{ + int keysym = VControl_name2code (state->token); + if (!keysym) + { + log_add (log_Warning, "VControl: Illegal key name '%s' on config file line %d", + state->token, state->linenum); + state->error = 1; + } + next_token (state); + return keysym; +} + +static int +consume_num (parse_state *state) +{ + char *end; + int result = strtol (state->token, &end, 10); + if (*end != '\0') + { + log_add (log_Warning, "VControl: Expected integer on config line %d", + state->linenum); + state->error = 1; + } + next_token (state); + return result; +} + +static int +consume_polarity (parse_state *state) +{ + int result = 0; + if (!strcasecmp (state->token, "positive")) + { + result = 1; + } + else if (!strcasecmp (state->token, "negative")) + { + result = -1; + } + else + { + expected_error (state, "positive' or 'negative"); + } + next_token (state); + return result; +} + +static Uint8 +consume_dir (parse_state *state) +{ + Uint8 result = 0; + if (!strcasecmp (state->token, "left")) + { + result = SDL_HAT_LEFT; + } + else if (!strcasecmp (state->token, "right")) + { + result = SDL_HAT_RIGHT; + } + else if (!strcasecmp (state->token, "up")) + { + result = SDL_HAT_UP; + } + else if (!strcasecmp (state->token, "down")) + { + result = SDL_HAT_DOWN; + } + else + { + expected_error (state, "left', 'right', 'up' or 'down"); + } + next_token (state); + return result; +} + +static void +parse_joybinding (parse_state *state, VCONTROL_GESTURE *gesture) +{ + int sticknum; + consume (state, "joystick"); + sticknum = consume_num (state); + if (!state->error) + { + if (!strcasecmp (state->token, "axis")) + { + int axisnum; + consume (state, "axis"); + axisnum = consume_num (state); + if (!state->error) + { + int polarity = consume_polarity (state); + if (!state->error) + { + gesture->type = VCONTROL_JOYAXIS; + gesture->gesture.axis.port = sticknum; + gesture->gesture.axis.index = axisnum; + gesture->gesture.axis.polarity = polarity; + } + } + } + else if (!strcasecmp (state->token, "button")) + { + int buttonnum; + consume (state, "button"); + buttonnum = consume_num (state); + if (!state->error) + { + gesture->type = VCONTROL_JOYBUTTON; + gesture->gesture.button.port = sticknum; + gesture->gesture.button.index = buttonnum; + } + } + else if (!strcasecmp (state->token, "hat")) + { + int hatnum; + consume (state, "hat"); + hatnum = consume_num (state); + if (!state->error) + { + Uint8 dir = consume_dir (state); + if (!state->error) + { + gesture->type = VCONTROL_JOYHAT; + gesture->gesture.hat.port = sticknum; + gesture->gesture.hat.index = hatnum; + gesture->gesture.hat.dir = dir; + } + } + } + else + { + expected_error (state, "axis', 'button', or 'hat"); + } + } +} + +static void +parse_gesture (parse_state *state, VCONTROL_GESTURE *gesture) +{ + gesture->type = VCONTROL_NONE; /* Default to error */ + if (!strcasecmp (state->token, "key")) + { + /* Parse key binding */ + int keysym; + consume (state, "key"); + keysym = consume_keyname (state); + if (!state->error) + { + gesture->type = VCONTROL_KEY; + gesture->gesture.key = keysym; + } + } + else if (!strcasecmp (state->token, "joystick")) + { + parse_joybinding (state, gesture); + } + else + { + expected_error (state, "key' or 'joystick"); + } +} + +void +VControl_ParseGesture (VCONTROL_GESTURE *g, const char *spec) +{ + parse_state ps; + + strncpy (ps.line, spec, LINE_SIZE); + ps.line[LINE_SIZE - 1] = '\0'; + ps.index = ps.error = 0; + ps.linenum = -1; + + next_token (&ps); + parse_gesture (&ps, g); + if (ps.error) + printf ("Error parsing %s\n", spec); +} + +int +VControl_DumpGesture (char *buf, int n, VCONTROL_GESTURE *g) +{ + switch (g->type) + { + case VCONTROL_KEY: + return snprintf (buf, n, "key %s", VControl_code2name (g->gesture.key)); + case VCONTROL_JOYAXIS: + return snprintf (buf, n, "joystick %d axis %d %s", g->gesture.axis.port, g->gesture.axis.index, + (g->gesture.axis.polarity > 0) ? "positive" : "negative"); + case VCONTROL_JOYBUTTON: + return snprintf (buf, n, "joystick %d button %d", g->gesture.button.port, g->gesture.button.index); + case VCONTROL_JOYHAT: + return snprintf (buf, n, "joystick %d hat %d %s", g->gesture.hat.port, g->gesture.hat.index, + (g->gesture.hat.dir == SDL_HAT_UP) ? "up" : + ((g->gesture.hat.dir == SDL_HAT_DOWN) ? "down" : + ((g->gesture.hat.dir == SDL_HAT_LEFT) ? "left" : "right"))); + default: + buf[0] = '\0'; + return 0; + } +} diff --git a/src/libs/input/sdl/vcontrol.h b/src/libs/input/sdl/vcontrol.h new file mode 100644 index 0000000..9c33445 --- /dev/null +++ b/src/libs/input/sdl/vcontrol.h @@ -0,0 +1,108 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_INPUT_SDL_VCONTROL_H_ +#define LIBS_INPUT_SDL_VCONTROL_H_ + +#include "port.h" +#include SDL_INCLUDE(SDL.h) + +#if SDL_MAJOR_VERSION == 1 +typedef SDLKey sdl_key_t; +#else +typedef SDL_Keycode sdl_key_t; +#endif + +/* Initialization routines */ +void VControl_Init (void); +void VControl_Uninit (void); + +/* Structures for representing actual VControl Inputs. Returned by + iterators and used to construct bindings. */ + +typedef enum { + VCONTROL_NONE, + VCONTROL_KEY, + VCONTROL_JOYAXIS, + VCONTROL_JOYBUTTON, + VCONTROL_JOYHAT, + NUM_VCONTROL_GESTURES +} VCONTROL_GESTURE_TYPE; + +typedef struct { + VCONTROL_GESTURE_TYPE type; + union { + sdl_key_t key; + struct { int port, index, polarity; } axis; + struct { int port, index; } button; + struct { int port, index; Uint8 dir; } hat; + } gesture; +} VCONTROL_GESTURE; + +/* Control of bindings */ +int VControl_AddGestureBinding (VCONTROL_GESTURE *g, int *target); +void VControl_RemoveGestureBinding (VCONTROL_GESTURE *g, int *target); + +int VControl_AddKeyBinding (sdl_key_t symbol, int *target); +void VControl_RemoveKeyBinding (sdl_key_t symbol, int *target); +int VControl_AddJoyAxisBinding (int port, int axis, int polarity, int *target); +void VControl_RemoveJoyAxisBinding (int port, int axis, int polarity, int *target); +int VControl_SetJoyThreshold (int port, int threshold); +int VControl_AddJoyButtonBinding (int port, int button, int *target); +void VControl_RemoveJoyButtonBinding (int port, int button, int *target); +int VControl_AddJoyHatBinding (int port, int which, Uint8 dir, int *target); +void VControl_RemoveJoyHatBinding (int port, int which, Uint8 dir, int *target); + +void VControl_RemoveAllBindings (void); + +/* Signal to VControl that a frame is about to begin. */ +void VControl_BeginFrame (void); + +/* The listener. Routines besides HandleEvent may be used to 'fake' inputs without + * fabricating an SDL_Event. + */ +void VControl_HandleEvent (const SDL_Event *e); +void VControl_ProcessKeyDown (sdl_key_t symbol); +void VControl_ProcessKeyUp (sdl_key_t symbol); +void VControl_ProcessJoyButtonDown (int port, int button); +void VControl_ProcessJoyButtonUp (int port, int button); +void VControl_ProcessJoyAxis (int port, int axis, int value); +void VControl_ProcessJoyHat (int port, int which, Uint8 value); + +/* Force the input into the blank state. For preventing "sticky" keys. */ +void VControl_ResetInput (void); + +/* Translate between gestures and string representations thereof. */ +void VControl_ParseGesture (VCONTROL_GESTURE *g, const char *spec); +int VControl_DumpGesture (char *buf, int n, VCONTROL_GESTURE *g); + +/* Tracking the "last interesting gesture." Used to poll to find new + control keys. */ + +void VControl_ClearGesture (void); +int VControl_GetLastGesture (VCONTROL_GESTURE *g); + +/* Constants for handling the "Start bit." If a gesture is made, and + * then ends, within a single frame, it will still, for one frame, + * have a nonzero value. This is because Bit 16 will be on for the + * first frame a gesture is struck. This bit is cleared when + * VControl_BeginFrame() is called. These constants are used to mask + * out results if necessary. */ + +#define VCONTROL_STARTBIT 0x10000 +#define VCONTROL_MASK 0x0FFFF + +#endif diff --git a/src/libs/list.h b/src/libs/list.h new file mode 100644 index 0000000..42a28d4 --- /dev/null +++ b/src/libs/list.h @@ -0,0 +1,29 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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(__cplusplus) +extern "C" { +#endif + +#include "list/list.h" + +#if defined(__cplusplus) +} +#endif + + diff --git a/src/libs/list/Makeinfo b/src/libs/list/Makeinfo new file mode 100644 index 0000000..c2bc72b --- /dev/null +++ b/src/libs/list/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="list.c" +uqm_HFILES="list.h" diff --git a/src/libs/list/list.c b/src/libs/list/list.c new file mode 100644 index 0000000..9d86538 --- /dev/null +++ b/src/libs/list/list.c @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2005 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIST_INTERNAL + // If list is already defined, this file is included + // as a template. In this case list.h has already been included. +# define LIST_INTERNAL +# include "list.h" +#endif + +#include "libs/memlib.h" +#define malloc HMalloc +#define free HFree +#define realloc HRealloc + +#include + +static inline LIST_(List) *LIST_(allocList)(void); +static inline void LIST_(freeList)(LIST_(List) *list); +static inline LIST_(Link) * LIST_(allocLink)(void); +static inline void LIST_(freeLink)(LIST_(Link) *link); + + +LIST_(List) * +LIST_(newList)(void) { + LIST_(List) *list; + + list = LIST_(allocList)(); + if (list == NULL) + return NULL; + + list->first = NULL; + list->end = &list->first; + return list; +} + +void +LIST_(deleteList)(LIST_(List) *list) +{ + LIST_(Link) *link; + LIST_(Link) *next; + + for (link = list->first; link != NULL; link = next) + { + next = link->next; + LIST_(freeLink)(link); + } + + LIST_(freeList)(list); +} + +void +LIST_(add)(LIST_(List) *list, LIST_(Entry) entry) { + LIST_(Link) *link; + + link = LIST_(allocLink)(); + link->entry = entry; + link->next = NULL; + *list->end = link; + list->end = &link->next; +} + +static inline LIST_(Link) ** +LIST_(findLink)(LIST_(List) *list, LIST_(Entry) entry) { + LIST_(Link) **linkPtr; + + for (linkPtr = &list->first; *linkPtr != NULL; + linkPtr = &(*linkPtr)->next) { + if ((*linkPtr)->entry == entry) + return linkPtr; + } + return NULL; +} + +static inline void +LIST_(removeLink)(LIST_(List) *list, LIST_(Link) **linkPtr) { + LIST_(Link) *link = *linkPtr; + + *linkPtr = link->next; + if (&link->next == list->end) + list->end = linkPtr; + LIST_(freeLink)(link); +} + +void +LIST_(remove)(LIST_(List) *list, LIST_(Entry) entry) { + LIST_(Link) **linkPtr; + + linkPtr = LIST_(findLink)(list, entry); + assert(linkPtr != NULL); + LIST_(removeLink)(list, linkPtr); +} + + +static inline LIST_(List) * +LIST_(allocList)(void) { + return malloc(sizeof (LIST_(List))); +} + +static inline void +LIST_(freeList)(LIST_(List) *list) { + free(list); +} + +static inline LIST_(Link) * +LIST_(allocLink)(void) { + return malloc(sizeof (LIST_(Link))); +} + +static inline void +LIST_(freeLink)(LIST_(Link) *link) { + free(link); +} + + diff --git a/src/libs/list/list.h b/src/libs/list/list.h new file mode 100644 index 0000000..2fd3332 --- /dev/null +++ b/src/libs/list/list.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2005 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// The 'already included' check must be done slightly more complicated +// than usually. This file may be included directly only once, +// but it may be included my derivative List definitions that use +// this file as a template more than once. +#if !defined(_LIST_H) || defined(LIST_GENERIC) +#if defined(LIST_) +# define LIST_GENERIC +#endif + +#include "types.h" +#include "port.h" + +// You can use inline lists, by using this file as a template. +// To do this, make a new .h and .c file. In the .h file, define the macros +// (and typedefs) from the LIST_ block below. +// In the .c file, #define LIST_INTERNAL, #include the .h file +// and list.c (in this order), and add the necessary functions. +#ifndef LIST_ +# define LIST_(identifier) List ## _ ## identifier + typedef void *List_Entry; +#endif + + +typedef struct LIST_(List) LIST_(List); +typedef struct LIST_(Link) LIST_(Link); + +struct LIST_(Link) { + LIST_(Entry) entry; + LIST_(Link) *next; +}; + +struct LIST_(List) { + LIST_(Link) *first; + LIST_(Link) **end; +}; + + +LIST_(List) *LIST_(newList)(void); +void LIST_(deleteList)(LIST_(List) *list); +void LIST_(add)(LIST_(List) *list, LIST_(Entry) entry); +void LIST_(remove)(LIST_(List) *list, LIST_(Entry) entry); + + +#ifndef LIST_INTERNAL +# undef LIST_ +#endif + +#endif /* !defined(_LIST_H) || defined(LIST_GENERIC) */ + + diff --git a/src/libs/log.h b/src/libs/log.h new file mode 100644 index 0000000..11537c7 --- /dev/null +++ b/src/libs/log.h @@ -0,0 +1,25 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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(__cplusplus) +extern "C" { +#endif + +#include "log/uqmlog.h" + +#if defined(__cplusplus) +} +#endif diff --git a/src/libs/log/Makeinfo b/src/libs/log/Makeinfo new file mode 100644 index 0000000..124260f --- /dev/null +++ b/src/libs/log/Makeinfo @@ -0,0 +1,15 @@ +uqm_CFILES="uqmlog.c" +uqm_HFILES="loginternal.h msgbox.h uqmlog.h" + +case "$HOST_SYSTEM" in + Darwin) + uqm_MFILES="msgbox_macosx.m" + ;; + MINGW32*|CYGWIN*) + uqm_CFILES="$uqm_CFILES msgbox_win.c" + ;; + *) + uqm_CFILES="$uqm_CFILES msgbox_stub.c" + ;; +esac + diff --git a/src/libs/log/loginternal.h b/src/libs/log/loginternal.h new file mode 100644 index 0000000..4457155 --- /dev/null +++ b/src/libs/log/loginternal.h @@ -0,0 +1,24 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_LOGINTERNAL_H_INCL__ +#define UQM_LOGINTERNAL_H_INCL__ + +#include + +extern FILE *streamOut; + +#endif /* UQM_LOGINTERNAL_H_INCL__ */ diff --git a/src/libs/log/msgbox.h b/src/libs/log/msgbox.h new file mode 100644 index 0000000..72934a5 --- /dev/null +++ b/src/libs/log/msgbox.h @@ -0,0 +1,23 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_MSGBOX_H_INCL__ +#define UQM_MSGBOX_H_INCL__ + +extern void log_displayBox (const /*UTF-8*/char *title, int isError, + const /*UTF-8*/char *msg); + +#endif /* UQM_MSGBOX_H_INCL__ */ diff --git a/src/libs/log/msgbox_macosx.m b/src/libs/log/msgbox_macosx.m new file mode 100644 index 0000000..eca32be --- /dev/null +++ b/src/libs/log/msgbox_macosx.m @@ -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. + */ + +#import +#import "msgbox.h" + +void +log_displayBox (const /*UTF-8*/char *title, int isError, + const /*UTF-8*/char *msg) +{ + @autoreleasepool { + NSAlert *alert = [[NSAlert alloc] init]; + NSString *titleStr = [NSString stringWithUTF8String:title]; + NSString *msgStr = [NSString stringWithUTF8String:msg]; + + if (alert && titleStr && msgStr) { + alert.alertStyle = isError ? NSAlertStyleCritical : NSAlertStyleInformational; + alert.messageText = titleStr; + alert.informativeText = msgStr; + + [alert runModal]; + } + + [msgStr release]; + [titleStr release]; + [alert release]; + } +} + diff --git a/src/libs/log/msgbox_stub.c b/src/libs/log/msgbox_stub.c new file mode 100644 index 0000000..8e0b6b6 --- /dev/null +++ b/src/libs/log/msgbox_stub.c @@ -0,0 +1,34 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "msgbox.h" +#include "loginternal.h" + +void +log_displayBox (const /*UTF-8*/char *title, int isError, + const /*UTF-8*/char *msg) +{ + // We do not know how to display a box. Perhaps it's done with a + // hefty dose of pixie dust, or perhaps with a hammer and nails. + // So just inform the user of our predicament + fprintf (streamOut, "Do not know how to display %s box\n", + isError ? "an error" : "a"); + + // Suppress the compiler warnings in any case. + (void)title; + (void)msg; +} + diff --git a/src/libs/log/msgbox_win.c b/src/libs/log/msgbox_win.c new file mode 100644 index 0000000..c4e0021 --- /dev/null +++ b/src/libs/log/msgbox_win.c @@ -0,0 +1,67 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "msgbox.h" +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + + +// Converts a UTF-8 string to Windows WideChar. +// Caller is responsible for free()ing the returned string. +static LPWSTR +toWideChar (const /*UTF-8*/char *str) +{ + int cch; + LPWSTR wstr; + + cch = MultiByteToWideChar (CP_UTF8, 0, str, -1, NULL, 0); + if (cch == 0) + return NULL; // failed, probably no UTF8 converter + + wstr = malloc (cch * sizeof (WCHAR)); + if (!wstr) + return NULL; // out of memory + + cch = MultiByteToWideChar (CP_UTF8, 0, str, -1, wstr, cch); + if (cch == 0) + { // Failed. It should not fail here if it succeeded just above, + // but it did. Not much can be done about it. + free (wstr); + return NULL; + } + + return wstr; +} + +void +log_displayBox (const /*UTF-8*/char *title, int isError, + const /*UTF-8*/char *msg) +{ + LPWSTR swTitle = toWideChar (title); + LPWSTR swMsg = toWideChar (msg); + UINT uType = isError ? MB_ICONWARNING : MB_ICONINFORMATION; + + if (swTitle && swMsg) + MessageBoxW (NULL, swMsg, swTitle, uType); + else // Could not convert; let's try ASCII, though it may look ugly + MessageBoxA (NULL, msg, title, uType); + + free (swTitle); + free (swMsg); +} + diff --git a/src/libs/log/uqmlog.c b/src/libs/log/uqmlog.c new file mode 100644 index 0000000..e054edb --- /dev/null +++ b/src/libs/log/uqmlog.c @@ -0,0 +1,331 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "uqmlog.h" +#include "loginternal.h" +#include "msgbox.h" +#include +#include +#include +#include +#include +#include +#include "libs/threadlib.h" + +#ifndef MAX_LOG_ENTRY_SIZE +# define MAX_LOG_ENTRY_SIZE 256 +#endif + +#ifndef MAX_LOG_ENTRIES +# define MAX_LOG_ENTRIES 128 +#endif + +typedef char log_Entry[MAX_LOG_ENTRY_SIZE]; + +// static buffers in case we run out of memory +static log_Entry queue[MAX_LOG_ENTRIES]; +static log_Entry msgNoThread; +static char msgBuf[16384]; + +static int maxLevel = log_Error; +static int maxStreamLevel = log_Debug; +static int maxDisp = 10; +static int qtotal = 0; +static int qhead = 0; +static int qtail = 0; +static volatile bool noThreadReady = false; +static bool showBox = true; +static bool errorBox = true; + +FILE *streamOut; + +static volatile int qlock = 0; +static Mutex qmutex; + +static void exitCallback (void); +static void displayLog (bool isError); + +static void +lockQueue (void) +{ + if (!qlock) + return; + + LockMutex (qmutex); +} + +static void +unlockQueue (void) +{ + if (!qlock) + return; + + UnlockMutex (qmutex); +} + +static void +removeExcess (int room) +{ + room = maxDisp - room; + if (room < 0) + room = 0; + + for ( ; qtotal > room; --qtotal, ++qtail) + ; + qtail %= MAX_LOG_ENTRIES; +} + +static int +acquireSlot (void) +{ + int slot; + + lockQueue (); + + removeExcess (1); + slot = qhead; + qhead = (qhead + 1) % MAX_LOG_ENTRIES; + ++qtotal; + + unlockQueue (); + + return slot; +} + +// queues the non-threaded message when present +static void +queueNonThreaded (void) +{ + int slot; + + // This is not perfect. A race condition still exists + // between buffering the no-thread message and setting + // the noThreadReady flag. Neither does this prevent + // the fully or partially overwritten message (by + // another competing thread). But it is 'good enough' + if (!noThreadReady) + return; + noThreadReady = false; + + slot = acquireSlot (); + memcpy (queue[slot], msgNoThread, sizeof (msgNoThread)); +} + +void +log_init (int max_lines) +{ + int i; + + maxDisp = max_lines; + streamOut = stderr; + + // pre-term queue strings + for (i = 0; i < MAX_LOG_ENTRIES; ++i) + queue[i][MAX_LOG_ENTRY_SIZE - 1] = '\0'; + + msgBuf[sizeof (msgBuf) - 1] = '\0'; + msgNoThread[sizeof (msgNoThread) - 1] = '\0'; + + // install exit handlers + atexit (exitCallback); +} + +void +log_initThreads (void) +{ + qmutex = CreateMutex ("Logging Lock", SYNC_CLASS_RESOURCE); + qlock = 1; +} + +int +log_exit (int code) +{ + showBox = false; + + if (qlock) + { + qlock = 0; + DestroyMutex (qmutex); + qmutex = 0; + } + + return code; +} + +void +log_setLevel (int level) +{ + maxLevel = level; + //maxStreamLevel = level; +} + +FILE * +log_setOutput (FILE *out) +{ + FILE *old = streamOut; + streamOut = out; + + return old; +} + +void +log_addV (log_Level level, const char *fmt, va_list list) +{ + log_Entry full_msg; + vsnprintf (full_msg, sizeof (full_msg) - 1, fmt, list); + full_msg[sizeof (full_msg) - 1] = '\0'; + + if ((int)level <= maxStreamLevel) + { + fprintf (streamOut, "%s\n", full_msg); + } + + if ((int)level <= maxLevel) + { + int slot; + + queueNonThreaded (); + + slot = acquireSlot (); + memcpy (queue[slot], full_msg, sizeof (queue[0])); + } +} + +void +log_add (log_Level level, const char *fmt, ...) +{ + va_list list; + + va_start (list, fmt); + log_addV (level, fmt, list); + va_end (list); +} + +// non-threaded version of 'add' +// uses single-instance static storage with entry into the +// queue delayed until the next threaded 'add' or 'exit' +void +log_add_nothreadV (log_Level level, const char *fmt, va_list list) +{ + log_Entry full_msg; + vsnprintf (full_msg, sizeof (full_msg) - 1, fmt, list); + full_msg[sizeof (full_msg) - 1] = '\0'; + + if ((int)level <= maxStreamLevel) + { + fprintf (streamOut, "%s\n", full_msg); + } + + if ((int)level <= maxLevel) + { + memcpy (msgNoThread, full_msg, sizeof (msgNoThread)); + noThreadReady = true; + } +} + +void +log_add_nothread (log_Level level, const char *fmt, ...) +{ + va_list list; + + va_start (list, fmt); + log_add_nothreadV (level, fmt, list); + va_end (list); +} + +void +log_showBox (bool show, bool err) +{ + showBox = show; + errorBox = err; +} + +// sets the maximum log lines captured for the final +// display to the user on failure exit +void +log_captureLines (int num) +{ + if (num > MAX_LOG_ENTRIES) + num = MAX_LOG_ENTRIES; + if (num < 1) + num = 1; + maxDisp = num; + + // remove any extra lines already on queue + lockQueue (); + removeExcess (0); + unlockQueue (); +} + +static void +exitCallback (void) +{ + if (showBox) + displayLog (errorBox); + + log_exit (0); +} + +static void +displayLog (bool isError) +{ + char *p = msgBuf; + int left = sizeof (msgBuf) - 1; + int len; + int ptr; + + if (isError) + { + strcpy (p, "The Ur-Quan Masters encountered a fatal error.\n" + "Part of the log follows:\n\n"); + len = strlen (p); + p += len; + left -= len; + } + + // Glue the log entries together + // Locking is not a good idea at this point and we do not + // really need it -- the worst that can happen is we get + // an extra or an incomplete message + for (ptr = qtail; ptr != qhead && left > 0; + ptr = (ptr + 1) % MAX_LOG_ENTRIES) + { + len = strlen (queue[ptr]) + 1; + if (len > left) + len = left; + memcpy (p, queue[ptr], len); + p[len - 1] = '\n'; + p += len; + left -= len; + } + + // Glue the non-threaded message if present + if (noThreadReady) + { + noThreadReady = false; + len = strlen (msgNoThread); + if (len > left) + len = left; + memcpy (p, msgNoThread, len); + p += len; + left -= len; + } + + *p = '\0'; + + log_displayBox ("The Ur-Quan Masters", isError, msgBuf); +} + diff --git a/src/libs/log/uqmlog.h b/src/libs/log/uqmlog.h new file mode 100644 index 0000000..38db089 --- /dev/null +++ b/src/libs/log/uqmlog.h @@ -0,0 +1,59 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 UQMLOG_H_INCL__ +#define UQMLOG_H_INCL__ + +#include "port.h" +#include "types.h" +#include +#include + +extern void log_init (int max_lines); +extern void log_initThreads (void); +extern int log_exit (int code); + +extern FILE * log_setOutput (FILE *out); + // sets the new output stream and returns the previous one +extern void log_setLevel (int level); +extern void log_showBox (bool show, bool err); +extern void log_captureLines (int num); +#define LOG_CAPTURE_ALL 1000000 // unreasonably big number + +typedef enum +{ + log_Nothing = 0, + log_User, + log_Fatal = log_User, + log_Error, + log_Warning, + log_Info, + log_Debug, + log_All, + +} log_Level; + +extern void log_add (log_Level, const char *fmt, ...) + PRINTF_FUNCTION(2, 3); +extern void log_addV (log_Level, const char *fmt, va_list) + VPRINTF_FUNCTION(2); +extern void log_add_nothread (log_Level, const char *fmt, ...) + PRINTF_FUNCTION(2, 3); +extern void log_add_nothreadV (log_Level, const char *fmt, va_list) + VPRINTF_FUNCTION(2); + + +#endif /* UQMLOG_H_INCL__ */ diff --git a/src/libs/math/Makeinfo b/src/libs/math/Makeinfo new file mode 100644 index 0000000..da10867 --- /dev/null +++ b/src/libs/math/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="random.c random2.c sqrt.c" +uqm_HFILES="mthintrn.h random.h" diff --git a/src/libs/math/mthintrn.h b/src/libs/math/mthintrn.h new file mode 100644 index 0000000..cfda767 --- /dev/null +++ b/src/libs/math/mthintrn.h @@ -0,0 +1,25 @@ +//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 LIBS_MATH_MTHINTRN_H_ +#define LIBS_MATH_MTHINTRN_H_ + +#include "libs/mathlib.h" + +#endif /* LIBS_MATH_MTHINTRN_H_ */ + diff --git a/src/libs/math/random.c b/src/libs/math/random.c new file mode 100644 index 0000000..83fcff8 --- /dev/null +++ b/src/libs/math/random.c @@ -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. + */ + + /**************************************************************************** +* FILE: random.c +* DESC: a library of random number generators for general purpose use. +* +* References: +* "Random Number Generators: Good ones are hard to find" S.K.Park & K.W.Miller +* Communications of the ACM, Vol31 Number 10, October 1988, Pp 1192-1201 +* +* HISTORY: Created 1/23/1989 +* LAST CHANGED: +* +* Copyright (c) 1989, Robert Leyland and Fred Ford +****************************************************************************/ + +/* ----------------------------INCLUDES----------------------------------- */ +#include "mthintrn.h" /* get the externs for error checking */ + +/* ----------------------------DEFINES------------------------------------ */ +/* constants for licongruential random number generator from CACM article + referenced above */ +#define A 16807 /* a relatively prime number -- also M div Q */ +#define M 2147483647L /* 0xFFFFFFFF / 2 */ +#define Q 127773L /* M div A */ +#define R 2836 /* M mod A */ + +/* ----------------------------STATIC DATA-------------------------------- */ + +static DWORD seed = 12345L; /* random number seed */ + +/* ----------------------------CODE--------------------------------------- */ + +/***************************************************************************** +* FUNC: DWORD TFB_Random() +* +* DESC: random number generator +* +* NOTES: +* +* HISTORY: Created By Robert leyland +* +*****************************************************************************/ + +DWORD +TFB_Random (void) +{ + seed = A * (seed % Q) - R * (seed / Q); + if (seed > M) + return (seed -= M); + else if (seed) + return (seed); + else + return (seed = 1L); +} + +/***************************************************************************** +* FUNC: DWORD TFB_SeedRandom(DWORD l) +* +* DESC: set the seed for the random number generator to parameter "l", and +* return the value of the previously active seed, to allow for multiple +* random number streams. +* +* NOTES: if the seed is not valid it will be coerced into a valid range +* +* HISTORY: Created By Robert leyland +* +*****************************************************************************/ + +DWORD +TFB_SeedRandom (DWORD new_seed) +{ + DWORD old_seed; + + /* coerce the seed to be in the range 1..M */ + if (new_seed == 0L) /* 0 becomes 1 */ + new_seed = 1; + else if (new_seed > M) /* and less than M */ + new_seed -= M; + + old_seed = seed; + seed = new_seed; + return (old_seed); +} + diff --git a/src/libs/math/random.h b/src/libs/math/random.h new file mode 100644 index 0000000..0e8d602 --- /dev/null +++ b/src/libs/math/random.h @@ -0,0 +1,56 @@ +//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. + */ + +/**************************************************************************** +* FILE: random.h +* DESC: definitions and externs for random number generators +* +* HISTORY: Created 6/ 6/1989 +* LAST CHANGED: +* +* Copyright (c) 1989, Robert Leyland and Scott Anderson +****************************************************************************/ + +#ifndef LIBS_MATH_RANDOM_H_ +#define LIBS_MATH_RANDOM_H_ + +/* ----------------------------GLOBALS/EXTERNS---------------------------- */ + +DWORD TFB_SeedRandom (DWORD seed); +DWORD TFB_Random (void); + + +typedef struct RandomContext RandomContext; + +#ifdef RANDOM2_INTERNAL +struct RandomContext { + DWORD seed; +}; +#endif + +RandomContext *RandomContext_New (void); +void RandomContext_Delete (RandomContext *context); +RandomContext *RandomContext_Copy (const RandomContext *source); +DWORD RandomContext_Random (RandomContext *context); +DWORD RandomContext_SeedRandom (RandomContext *context, DWORD new_seed); +DWORD RandomContext_GetSeed (RandomContext *context); + + +#endif /* LIBS_MATH_RANDOM_H_ */ + + diff --git a/src/libs/math/random2.c b/src/libs/math/random2.c new file mode 100644 index 0000000..9d354b4 --- /dev/null +++ b/src/libs/math/random2.c @@ -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. + */ + +// This file contains variants of the random functions in random.c +// that store the state of the RNG in a context, allowing for multiple +// independant RNGs to be used simultaneously. +// The RNG behavior itself is the same. + +#include "libs/compiler.h" + +#define RANDOM2_INTERNAL +#include "random.h" + +#include "libs/memlib.h" + + +#define A 16807 /* a relatively prime number -- also M div Q */ +#define M 2147483647 /* 0xFFFFFFFF / 2 */ +#define Q 127773 /* M div A */ +#define R 2836 /* M mod A */ + +RandomContext * +RandomContext_New (void) +{ + RandomContext *result = (RandomContext *) HMalloc (sizeof (RandomContext)); + result->seed = 12345; + return result; +} + +void +RandomContext_Delete (RandomContext *context) +{ + HFree ((void *) context); +} + +RandomContext * +RandomContext_Copy (const RandomContext *source) +{ + RandomContext *result = (RandomContext *) HMalloc (sizeof (RandomContext)); + *result = *source; + return result; +} + +DWORD +RandomContext_Random (RandomContext *context) +{ + context->seed = A * (context->seed % Q) - R * (context->seed / Q); + if (context->seed > M) { + context->seed -= M; + } else if (context->seed == 0) + context->seed = 1; + + return context->seed; +} + +DWORD +RandomContext_SeedRandom (RandomContext *context, DWORD new_seed) +{ + DWORD old_seed; + + /* coerce the seed to be in the range 1..M */ + if (new_seed == 0) /* 0 becomes 1 */ + new_seed = 1; + else if (new_seed > M) /* and less than M */ + new_seed -= M; + + old_seed = context->seed; + context->seed = new_seed; + return old_seed; +} + +DWORD +RandomContext_GetSeed (RandomContext *context) +{ + return context->seed; +} diff --git a/src/libs/math/sqrt.c b/src/libs/math/sqrt.c new file mode 100644 index 0000000..1f02cac --- /dev/null +++ b/src/libs/math/sqrt.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 "mthintrn.h" + +COUNT +square_root (DWORD value) +{ + UWORD sig_word, mask; + COUNT result, shift; + + if ((sig_word = HIWORD (value)) > 0) + { + DWORD mask_squared, result_shift; + + for (mask = 1 << 15, shift = 31; + !(mask & sig_word); mask >>= 1, --shift) + ; + shift >>= 1; + mask = 1 << shift; + + result = mask; + mask_squared = result_shift = (DWORD)mask << shift; + value -= mask_squared; + while (mask >>= 1) + { + DWORD remainder; + + mask_squared >>= 1; + mask_squared >>= 1; + if ((remainder = result_shift + mask_squared) > value) + result_shift >>= 1; + else + { + value -= remainder; + + result_shift = (result_shift >> 1) + mask_squared; + + result |= mask; + } + } + + return (result); + } + else if ((sig_word = LOWORD (value)) > 0) + { + UWORD mask_squared, result_shift; + + for (mask = 1 << 15, shift = 15; + !(mask & sig_word); mask >>= 1, --shift) + ; + shift >>= 1; + mask = 1 << shift; + + result = mask; + mask_squared = result_shift = mask << shift; + sig_word -= mask_squared; + while (mask >>= 1) + { + UWORD remainder; + + mask_squared >>= 1; + mask_squared >>= 1; + if ((remainder = result_shift + mask_squared) > sig_word) + result_shift >>= 1; + else + { + sig_word -= remainder; + + result_shift = (result_shift >> 1) + mask_squared; + + result |= mask; + } + } + + return (result); + } + + return (0); +} + + diff --git a/src/libs/mathlib.h b/src/libs/mathlib.h new file mode 100644 index 0000000..a7b27d1 --- /dev/null +++ b/src/libs/mathlib.h @@ -0,0 +1,36 @@ +//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 LIBS_MATHLIB_H_ +#define LIBS_MATHLIB_H_ + +#include "libs/compiler.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "math/random.h" + +extern COUNT square_root (DWORD value); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_MATHLIB_H_ */ diff --git a/src/libs/md5.h b/src/libs/md5.h new file mode 100644 index 0000000..f7732dc --- /dev/null +++ b/src/libs/md5.h @@ -0,0 +1,32 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_MD5_H_ +#define LIBS_MD5_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "md5/md5.h" + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_MD5_H_ */ diff --git a/src/libs/md5/Makeinfo b/src/libs/md5/Makeinfo new file mode 100644 index 0000000..7ba0a78 --- /dev/null +++ b/src/libs/md5/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="md5.c" +uqm_HFILES="md5.h" diff --git a/src/libs/md5/README b/src/libs/md5/README new file mode 100644 index 0000000..5ca6c5a --- /dev/null +++ b/src/libs/md5/README @@ -0,0 +1,6 @@ +The files md5.c and md5.h from this directory come from the GNU TLS +library (http://www.gnutls.org/), version 1.6.1. + +These files are unchanged, except for the replacement of the LGPL notices by +GPL notices. + diff --git a/src/libs/md5/md5.c b/src/libs/md5/md5.c new file mode 100644 index 0000000..d57b3df --- /dev/null +++ b/src/libs/md5/md5.c @@ -0,0 +1,452 @@ +/* Functions to compute MD5 message digest of files or memory blocks. + according to the definition of MD5 in RFC 1321 from April 1992. + Copyright (C) 1995,1996,1997,1999,2000,2001,2005,2006 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* Written by Ulrich Drepper , 1995. */ + +#include + +#include "md5.h" + +#include +#include +#include +#include + +#if USE_UNLOCKED_IO +# include "unlocked-io.h" +#endif + +#ifdef _LIBC +# include +# if __BYTE_ORDER == __BIG_ENDIAN +# define WORDS_BIGENDIAN 1 +# endif +/* We need to keep the namespace clean so define the MD5 function + protected using leading __ . */ +# define md5_init_ctx __md5_init_ctx +# define md5_process_block __md5_process_block +# define md5_process_bytes __md5_process_bytes +# define md5_finish_ctx __md5_finish_ctx +# define md5_read_ctx __md5_read_ctx +# define md5_stream __md5_stream +# define md5_buffer __md5_buffer +#endif + +#ifdef WORDS_BIGENDIAN +# define SWAP(n) \ + (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) +#else +# define SWAP(n) (n) +#endif + +#define BLOCKSIZE 4096 +#if BLOCKSIZE % 64 != 0 +# error "invalid BLOCKSIZE" +#endif + +/* This array contains the bytes used to pad the buffer to the next + 64-byte boundary. (RFC 1321, 3.1: Step 1) */ +static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ }; + + +/* Initialize structure containing state of computation. + (RFC 1321, 3.3: Step 3) */ +void +md5_init_ctx (struct md5_ctx *ctx) +{ + ctx->A = 0x67452301; + ctx->B = 0xefcdab89; + ctx->C = 0x98badcfe; + ctx->D = 0x10325476; + + ctx->total[0] = ctx->total[1] = 0; + ctx->buflen = 0; +} + +/* Put result from CTX in first 16 bytes following RESBUF. The result + must be in little endian byte order. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32-bit value. */ +void * +md5_read_ctx (const struct md5_ctx *ctx, void *resbuf) +{ + ((uint32_t *) resbuf)[0] = SWAP (ctx->A); + ((uint32_t *) resbuf)[1] = SWAP (ctx->B); + ((uint32_t *) resbuf)[2] = SWAP (ctx->C); + ((uint32_t *) resbuf)[3] = SWAP (ctx->D); + + return resbuf; +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32-bit value. */ +void * +md5_finish_ctx (struct md5_ctx *ctx, void *resbuf) +{ + /* Take yet unprocessed bytes into account. */ + uint32_t bytes = ctx->buflen; + size_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4; + + /* Now count remaining bytes. */ + ctx->total[0] += bytes; + if (ctx->total[0] < bytes) + ++ctx->total[1]; + + /* Put the 64-bit file length in *bits* at the end of the buffer. */ + ctx->buffer[size - 2] = SWAP (ctx->total[0] << 3); + ctx->buffer[size - 1] = SWAP ((ctx->total[1] << 3) | (ctx->total[0] >> 29)); + + memcpy (&((char *) ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes); + + /* Process last bytes. */ + md5_process_block (ctx->buffer, size * 4, ctx); + + return md5_read_ctx (ctx, resbuf); +} + +/* Compute MD5 message digest for bytes read from STREAM. The + resulting message digest number will be written into the 16 bytes + beginning at RESBLOCK. */ +int +md5_stream (FILE *stream, void *resblock) +{ + struct md5_ctx ctx; + char buffer[BLOCKSIZE + 72]; + size_t sum; + + /* Initialize the computation context. */ + md5_init_ctx (&ctx); + + /* Iterate over full file contents. */ + while (1) + { + /* We read the file in blocks of BLOCKSIZE bytes. One call of the + computation function processes the whole buffer so that with the + next round of the loop another block can be read. */ + size_t n; + sum = 0; + + /* Read block. Take care for partial reads. */ + while (1) + { + n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream); + + sum += n; + + if (sum == BLOCKSIZE) + break; + + if (n == 0) + { + /* Check for the error flag IFF N == 0, so that we don't + exit the loop after a partial read due to e.g., EAGAIN + or EWOULDBLOCK. */ + if (ferror (stream)) + return 1; + goto process_partial_block; + } + + /* We've read at least one byte, so ignore errors. But always + check for EOF, since feof may be true even though N > 0. + Otherwise, we could end up calling fread after EOF. */ + if (feof (stream)) + goto process_partial_block; + } + + /* Process buffer with BLOCKSIZE bytes. Note that + BLOCKSIZE % 64 == 0 + */ + md5_process_block (buffer, BLOCKSIZE, &ctx); + } + +process_partial_block: + + /* Process any remaining bytes. */ + if (sum > 0) + md5_process_bytes (buffer, sum, &ctx); + + /* Construct result in desired memory. */ + md5_finish_ctx (&ctx, resblock); + return 0; +} + +/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +void * +md5_buffer (const char *buffer, size_t len, void *resblock) +{ + struct md5_ctx ctx; + + /* Initialize the computation context. */ + md5_init_ctx (&ctx); + + /* Process whole buffer but last len % 64 bytes. */ + md5_process_bytes (buffer, len, &ctx); + + /* Put result in desired memory area. */ + return md5_finish_ctx (&ctx, resblock); +} + + +void +md5_process_bytes (const void *buffer, size_t len, struct md5_ctx *ctx) +{ + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + if (ctx->buflen != 0) + { + size_t left_over = ctx->buflen; + size_t add = 128 - left_over > len ? len : 128 - left_over; + + memcpy (&((char *) ctx->buffer)[left_over], buffer, add); + ctx->buflen += add; + + if (ctx->buflen > 64) + { + md5_process_block (ctx->buffer, ctx->buflen & ~63, ctx); + + ctx->buflen &= 63; + /* The regions in the following copy operation cannot overlap. */ + memcpy (ctx->buffer, + &((char *) ctx->buffer)[(left_over + add) & ~63], + ctx->buflen); + } + + buffer = (const char *) buffer + add; + len -= add; + } + + /* Process available complete blocks. */ + if (len >= 64) + { +#if !_STRING_ARCH_unaligned +# define alignof(type) offsetof (struct align_ ## type { char c; type x; }, x) +# define UNALIGNED_P(p) (((size_t) p) % alignof (uint32_t) != 0) + if (UNALIGNED_P (buffer)) + while (len > 64) + { + md5_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx); + buffer = (const char *) buffer + 64; + len -= 64; + } + else +#endif + { + md5_process_block (buffer, len & ~63, ctx); + buffer = (const char *) buffer + (len & ~63); + len &= 63; + } + } + + /* Move remaining bytes in internal buffer. */ + if (len > 0) + { + size_t left_over = ctx->buflen; + + memcpy (&((char *) ctx->buffer)[left_over], buffer, len); + left_over += len; + if (left_over >= 64) + { + md5_process_block (ctx->buffer, 64, ctx); + left_over -= 64; + memcpy (ctx->buffer, &ctx->buffer[16], left_over); + } + ctx->buflen = left_over; + } +} + + +/* These are the four functions used in the four steps of the MD5 algorithm + and defined in the RFC 1321. The first function is a little bit optimized + (as found in Colin Plumbs public domain implementation). */ +/* #define FF(b, c, d) ((b & c) | (~b & d)) */ +#define FF(b, c, d) (d ^ (b & (c ^ d))) +#define FG(b, c, d) FF (d, b, c) +#define FH(b, c, d) (b ^ c ^ d) +#define FI(b, c, d) (c ^ (b | ~d)) + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 64 == 0. */ + +void +md5_process_block (const void *buffer, size_t len, struct md5_ctx *ctx) +{ + uint32_t correct_words[16]; + const uint32_t *words = buffer; + size_t nwords = len / sizeof (uint32_t); + const uint32_t *endp = words + nwords; + uint32_t A = ctx->A; + uint32_t B = ctx->B; + uint32_t C = ctx->C; + uint32_t D = ctx->D; + + /* First increment the byte count. RFC 1321 specifies the possible + length of the file up to 2^64 bits. Here we only compute the + number of bytes. Do a double word increment. */ + ctx->total[0] += len; + if (ctx->total[0] < len) + ++ctx->total[1]; + + /* Process all bytes in the buffer with 64 bytes in each round of + the loop. */ + while (words < endp) + { + uint32_t *cwp = correct_words; + uint32_t A_save = A; + uint32_t B_save = B; + uint32_t C_save = C; + uint32_t D_save = D; + + /* First round: using the given function, the context and a constant + the next context is computed. Because the algorithms processing + unit is a 32-bit word and it is determined to work on words in + little endian byte order we perhaps have to change the byte order + before the computation. To reduce the work for the next steps + we store the swapped words in the array CORRECT_WORDS. */ + +#define OP(a, b, c, d, s, T) \ + do \ + { \ + a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T; \ + ++words; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* It is unfortunate that C does not provide an operator for + cyclic rotation. Hope the C compiler is smart enough. */ +#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s))) + + /* Before we start, one word to the strange constants. + They are defined in RFC 1321 as + + T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64 + + Here is an equivalent invocation using Perl: + + perl -e 'foreach(1..64){printf "0x%08x\n", int (4294967296 * abs (sin $_))}' + */ + + /* Round 1. */ + OP (A, B, C, D, 7, 0xd76aa478); + OP (D, A, B, C, 12, 0xe8c7b756); + OP (C, D, A, B, 17, 0x242070db); + OP (B, C, D, A, 22, 0xc1bdceee); + OP (A, B, C, D, 7, 0xf57c0faf); + OP (D, A, B, C, 12, 0x4787c62a); + OP (C, D, A, B, 17, 0xa8304613); + OP (B, C, D, A, 22, 0xfd469501); + OP (A, B, C, D, 7, 0x698098d8); + OP (D, A, B, C, 12, 0x8b44f7af); + OP (C, D, A, B, 17, 0xffff5bb1); + OP (B, C, D, A, 22, 0x895cd7be); + OP (A, B, C, D, 7, 0x6b901122); + OP (D, A, B, C, 12, 0xfd987193); + OP (C, D, A, B, 17, 0xa679438e); + OP (B, C, D, A, 22, 0x49b40821); + + /* For the second to fourth round we have the possibly swapped words + in CORRECT_WORDS. Redefine the macro to take an additional first + argument specifying the function to use. */ +#undef OP +#define OP(f, a, b, c, d, k, s, T) \ + do \ + { \ + a += f (b, c, d) + correct_words[k] + T; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* Round 2. */ + OP (FG, A, B, C, D, 1, 5, 0xf61e2562); + OP (FG, D, A, B, C, 6, 9, 0xc040b340); + OP (FG, C, D, A, B, 11, 14, 0x265e5a51); + OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa); + OP (FG, A, B, C, D, 5, 5, 0xd62f105d); + OP (FG, D, A, B, C, 10, 9, 0x02441453); + OP (FG, C, D, A, B, 15, 14, 0xd8a1e681); + OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8); + OP (FG, A, B, C, D, 9, 5, 0x21e1cde6); + OP (FG, D, A, B, C, 14, 9, 0xc33707d6); + OP (FG, C, D, A, B, 3, 14, 0xf4d50d87); + OP (FG, B, C, D, A, 8, 20, 0x455a14ed); + OP (FG, A, B, C, D, 13, 5, 0xa9e3e905); + OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8); + OP (FG, C, D, A, B, 7, 14, 0x676f02d9); + OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a); + + /* Round 3. */ + OP (FH, A, B, C, D, 5, 4, 0xfffa3942); + OP (FH, D, A, B, C, 8, 11, 0x8771f681); + OP (FH, C, D, A, B, 11, 16, 0x6d9d6122); + OP (FH, B, C, D, A, 14, 23, 0xfde5380c); + OP (FH, A, B, C, D, 1, 4, 0xa4beea44); + OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9); + OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60); + OP (FH, B, C, D, A, 10, 23, 0xbebfbc70); + OP (FH, A, B, C, D, 13, 4, 0x289b7ec6); + OP (FH, D, A, B, C, 0, 11, 0xeaa127fa); + OP (FH, C, D, A, B, 3, 16, 0xd4ef3085); + OP (FH, B, C, D, A, 6, 23, 0x04881d05); + OP (FH, A, B, C, D, 9, 4, 0xd9d4d039); + OP (FH, D, A, B, C, 12, 11, 0xe6db99e5); + OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8); + OP (FH, B, C, D, A, 2, 23, 0xc4ac5665); + + /* Round 4. */ + OP (FI, A, B, C, D, 0, 6, 0xf4292244); + OP (FI, D, A, B, C, 7, 10, 0x432aff97); + OP (FI, C, D, A, B, 14, 15, 0xab9423a7); + OP (FI, B, C, D, A, 5, 21, 0xfc93a039); + OP (FI, A, B, C, D, 12, 6, 0x655b59c3); + OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92); + OP (FI, C, D, A, B, 10, 15, 0xffeff47d); + OP (FI, B, C, D, A, 1, 21, 0x85845dd1); + OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f); + OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0); + OP (FI, C, D, A, B, 6, 15, 0xa3014314); + OP (FI, B, C, D, A, 13, 21, 0x4e0811a1); + OP (FI, A, B, C, D, 4, 6, 0xf7537e82); + OP (FI, D, A, B, C, 11, 10, 0xbd3af235); + OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb); + OP (FI, B, C, D, A, 9, 21, 0xeb86d391); + + /* Add the starting values of the context. */ + A += A_save; + B += B_save; + C += C_save; + D += D_save; + } + + /* Put checksum in context given as argument. */ + ctx->A = A; + ctx->B = B; + ctx->C = C; + ctx->D = D; +} diff --git a/src/libs/md5/md5.h b/src/libs/md5/md5.h new file mode 100644 index 0000000..0639525 --- /dev/null +++ b/src/libs/md5/md5.h @@ -0,0 +1,130 @@ +/* Declaration of functions and data types used for MD5 sum computing + library functions. + Copyright (C) 1995-1997,1999,2000,2001,2004,2005,2006 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 LIBS_MD5_MD5_H_ +#define LIBS_MD5_MD5_H_ 1 + +#include + +#ifdef _MSC_VER +typedef unsigned int uint32_t; +#else +#include +#endif + +#define MD5_DIGEST_SIZE 16 +#define MD5_BLOCK_SIZE 64 + +#ifndef __GNUC_PREREQ +# if defined __GNUC__ && defined __GNUC_MINOR__ +# define __GNUC_PREREQ(maj, min) \ + ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) +# else +# define __GNUC_PREREQ(maj, min) 0 +# endif +#endif + +#ifndef __THROW +# if defined __cplusplus && __GNUC_PREREQ (2,8) +# define __THROW throw () +# else +# define __THROW +# endif +#endif + +#ifndef _LIBC +# define __md5_buffer md5_buffer +# define __md5_finish_ctx md5_finish_ctx +# define __md5_init_ctx md5_init_ctx +# define __md5_process_block md5_process_block +# define __md5_process_bytes md5_process_bytes +# define __md5_read_ctx md5_read_ctx +# define __md5_stream md5_stream +#endif + +/* Structure to save state of computation between the single steps. */ +struct md5_ctx +{ + uint32_t A; + uint32_t B; + uint32_t C; + uint32_t D; + + uint32_t total[2]; + uint32_t buflen; + uint32_t buffer[32]; +}; + +/* + * The following three functions are build up the low level used in + * the functions `md5_stream' and `md5_buffer'. + */ + +/* Initialize structure containing state of computation. + (RFC 1321, 3.3: Step 3) */ +extern void __md5_init_ctx (struct md5_ctx *ctx) __THROW; + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is necessary that LEN is a multiple of 64!!! */ +extern void __md5_process_block (const void *buffer, size_t len, + struct md5_ctx *ctx) __THROW; + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of 64. */ +extern void __md5_process_bytes (const void *buffer, size_t len, + struct md5_ctx *ctx) __THROW; + +/* Process the remaining bytes in the buffer and put result from CTX + in first 16 bytes following RESBUF. The result is always in little + endian byte order, so that a byte-wise output yields to the wanted + ASCII representation of the message digest. + + IMPORTANT: On some systems, RESBUF must be aligned to a 32-bit + boundary. */ +extern void *__md5_finish_ctx (struct md5_ctx *ctx, void *resbuf) __THROW; + + +/* Put result from CTX in first 16 bytes following RESBUF. The result is + always in little endian byte order, so that a byte-wise output yields + to the wanted ASCII representation of the message digest. + + IMPORTANT: On some systems, RESBUF must be aligned to a 32-bit + boundary. */ +extern void *__md5_read_ctx (const struct md5_ctx *ctx, void *resbuf) __THROW; + + +/* Compute MD5 message digest for bytes read from STREAM. The + resulting message digest number will be written into the 16 bytes + beginning at RESBLOCK. */ +extern int __md5_stream (FILE *stream, void *resblock) __THROW; + +/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +extern void *__md5_buffer (const char *buffer, size_t len, + void *resblock) __THROW; + +#endif /* md5.h */ diff --git a/src/libs/memlib.h b/src/libs/memlib.h new file mode 100644 index 0000000..c4fa385 --- /dev/null +++ b/src/libs/memlib.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 LIBS_MEMLIB_H_ +#define LIBS_MEMLIB_H_ + +#include + +#include "types.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +extern bool mem_init (void); +extern bool mem_uninit (void); + +extern void *HMalloc (size_t size); +extern void HFree (void *p); +extern void *HCalloc (size_t size); +extern void *HRealloc (void *p, size_t size); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_MEMLIB_H_ */ + diff --git a/src/libs/memory/Makeinfo b/src/libs/memory/Makeinfo new file mode 100644 index 0000000..74dfc29 --- /dev/null +++ b/src/libs/memory/Makeinfo @@ -0,0 +1 @@ +uqm_CFILES="w_memlib.c" diff --git a/src/libs/memory/w_memlib.c b/src/libs/memory/w_memlib.c new file mode 100644 index 0000000..342e34e --- /dev/null +++ b/src/libs/memory/w_memlib.c @@ -0,0 +1,84 @@ +//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 +#include +#include +#include +#include "libs/memlib.h" +#include "libs/log.h" +#include "libs/misc.h" + + +bool +mem_init (void) +{ // This is a stub + return true; +} + +bool +mem_uninit (void) +{ // This is a stub + return true; +} + +void * +HMalloc (size_t size) +{ + void *p = malloc (size); + if (p == NULL && size > 0) + { + log_add (log_Fatal, "HMalloc() FATAL: out of memory."); + fflush (stderr); + explode (); + } + + return p; +} + +void +HFree (void *p) +{ + free (p); +} + +void * +HCalloc (size_t size) +{ + void *p; + + p = HMalloc (size); + memset (p, 0, size); + + return p; +} + +void * +HRealloc (void *p, size_t size) +{ + p = realloc (p, size); + if (p == NULL && size > 0) + { + log_add (log_Fatal, "HRealloc() FATAL: out of memory."); + fflush (stderr); + explode (); + } + + return p; +} + diff --git a/src/libs/mikmod/AUTHORS b/src/libs/mikmod/AUTHORS new file mode 100644 index 0000000..5752fdf --- /dev/null +++ b/src/libs/mikmod/AUTHORS @@ -0,0 +1,124 @@ +libmikmod main authors +---------------------- + +* Jean-Paul Mikkers (MikMak) + wrote MikMod and maintained it until version 3. +* Jake Stine (Air Richter) + [email doesn't work anymore...] + made decisive contributions to the code (esp. IT support) and + maintained MikMod version 3 until it was discontinued. He still works + on the WinAmp module plugin, roughly based on MikMod. +* Miod Vallat + current overbooked libmikmod maintainer (since version 3.0.4), made + an audit of the code resulting in many bugs fixed. + +Previous Unix maintainers +------------------------- + +* Steve McIntyre + maintained MikMod'Unix version 2. Used to maintain the Debian package + for MikMod. +* Peter Amstutz + maintained MikMod'Unix version 3 up to version 3.0.3. + +General contributors +-------------------- + +* Arne de Bruijn + wrote the compressed IT sample support. +* Shlomi Fish + wrote the Java port, bug fixes. +* Juan Linietsky + overall bug fixes. +* Claudio Matsuoka + wrote the STX loader and submitted bug fixes. +* Sebastiaan A. Megens + fixed various bugs (memory leaks, endianness issues, etc). +* ``UFO'' + wrote the OKT loader. +* Kev Vance + wrote the GDM loader. + +* Paul Fisher made decisive contributions and improvements. +* Alexander Kerkhove fixed an ULT panning effect bug. +* ``Kodiak'' helped on the interfaces of libmikmod. +* Sylvain Marchand make MikMod more portable and GCC compilable. + + +Contributors on the Unix side +----------------------------- + +* Douglas Carmichael + ported MikMod to FreeBSD. +* Chris Conn + wrote the OSS driver. +* Roine Gustaffson + wrote the Digital AudioFile driver. +* Stephan Kanthak + wrote the SGI driver. +* Lutz Vieweg + wrote the AIX and HP-UX drivers. +* Valtteri Vuorikoski + wrote the Sun driver. +* Andy Lo A Foe + wrote the Ultra driver (for the Gravis Ultrasound sound card). +* C Ray C + updated the Ultra driver to work with libmikmod 3. +* ``MenTaLguY'' + autoconfized the Unix libmikmod distribution. +* Tobias Gloth + created the new I/O interface, made the code MT-safe and submitted bug fixes. +* Simon Hosie + wrote the piped output driver, and submitted speed optimizations and bugfixes + for the software mixer. +* Gerd Rausch + wrote the sam9407 driver. +* Joseph Carter + maintains the Debian package for MikMod and libmikmod, submitted + bugfixes. + +Contributors on the Windows side +-------------------------------- + +* Brian McKinney + created the DirectSound driver. +* Bjornar Henden + created the Multimedia API windows driver. + +Contributors on the Dos side +---------------------------- + +Their code isn't there anymore, but they contributed to the success of +libmikmod... + +* Jean-Philippe Ajirent wrote the EMS memory routines. +* Peter Breitling ported MikMod to DJGPP. +* Arnout Cosman wrote the PAS driver. +* Mario Koeppen wrote the WSS driver. +* Mike Leibow wrote the GUS driver. +* Jeremy McDonald wrote a fast assembly-language mixer. +* Steffen Rusitschka and Vince Vu wrote the AWE driver. + +Contributors on the Macintosh side +---------------------------------- + +* Anders Bjoerklund + ported libmikmod to the Macintosh. + +Contributors on the OS/2 side +----------------------------- + +* Stefan Tibus + ported libmikmod to OS/2. +* Andrew Zabolotny + improved the existing OS/2 drivers. + +Contributors on the BeOS side +----------------------------- + +* Thomas Neumann + integrated libmikmod into his BeOS APlayer, and contributed many bug fixes. + +-- +If your name is missing, don't hesitate to remind me at + diff --git a/src/libs/mikmod/Makeinfo b/src/libs/mikmod/Makeinfo new file mode 100644 index 0000000..cd037f9 --- /dev/null +++ b/src/libs/mikmod/Makeinfo @@ -0,0 +1,5 @@ +uqm_CFILES="drv_nos.c load_it.c load_mod.c load_s3m.c load_stm.c load_xm.c + mdreg.c mdriver.c mloader.c + mlreg.c mlutil.c mmalloc.c mmerror.c mmio.c mplayer.c munitrk.c + mwav.c npertab.c sloader.c virtch.c virtch2.c virtch_common.c" +uqm_HFILES="mikmod_build.h mikmod.h mikmod_internals.h" diff --git a/src/libs/mikmod/README b/src/libs/mikmod/README new file mode 100644 index 0000000..c5c0be2 --- /dev/null +++ b/src/libs/mikmod/README @@ -0,0 +1,5 @@ +NOTE by UQM developers: this is a modified version of libmikmod. + +This version of the library is based on the official libmikmod library +version 3.1.11a with some internal and API changes backported from v3.2.2. +The official library is found at http://sourceforge.net/projects/mikmod. diff --git a/src/libs/mikmod/drv_nos.c b/src/libs/mikmod/drv_nos.c new file mode 100644 index 0000000..0ea3d1f --- /dev/null +++ b/src/libs/mikmod/drv_nos.c @@ -0,0 +1,107 @@ +/* MikMod sound library + (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Driver for no output + +==============================================================================*/ + +/* + + Written by Jean-Paul Mikkers + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "mikmod_internals.h" + +#define ZEROLEN 32768 + +static SBYTE *zerobuf=NULL; + +static BOOL NS_IsThere(void) +{ + return 1; +} + +static BOOL NS_Init(void) +{ + zerobuf=(SBYTE*)MikMod_malloc(ZEROLEN); + return VC_Init(); +} + +static void NS_Exit(void) +{ + VC_Exit(); + MikMod_free(zerobuf); +} + +static void NS_Update(void) +{ + if (zerobuf) + VC_WriteBytes(zerobuf,ZEROLEN); +} + +MIKMODAPI MDRIVER drv_nos={ + NULL, + "No Sound", + "Nosound Driver v3.0", + 255,255, + "nosound", + + NULL, + NS_IsThere, + VC_SampleLoad, + VC_SampleUnload, + VC_SampleSpace, + VC_SampleLength, + NS_Init, + NS_Exit, + NULL, + VC_SetNumVoices, + VC_PlayStart, + VC_PlayStop, + NS_Update, + NULL, + VC_VoiceSetVolume, + VC_VoiceGetVolume, + VC_VoiceSetFrequency, + VC_VoiceGetFrequency, + VC_VoiceSetPanning, + VC_VoiceGetPanning, + VC_VoicePlay, + VC_VoiceStop, + VC_VoiceStopped, + VC_VoiceGetPosition, + VC_VoiceRealVolume +}; + + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/load_it.c b/src/libs/mikmod/load_it.c new file mode 100644 index 0000000..747f23a --- /dev/null +++ b/src/libs/mikmod/load_it.c @@ -0,0 +1,1008 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file + AUTHORS for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Impulse tracker (IT) module loader + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include +#ifdef HAVE_MEMORY_H +#include +#endif +#include + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +extern int toupper(int); +#endif + +/*========== Module structure */ + +/* header */ +typedef struct ITHEADER { + CHAR songname[26]; + UBYTE blank01[2]; + UWORD ordnum; + UWORD insnum; + UWORD smpnum; + UWORD patnum; + UWORD cwt; /* Created with tracker (y.xx = 0x0yxx) */ + UWORD cmwt; /* Compatible with tracker ver > than val. */ + UWORD flags; + UWORD special; /* bit 0 set = song message attached */ + UBYTE globvol; + UBYTE mixvol; /* mixing volume [ignored] */ + UBYTE initspeed; + UBYTE inittempo; + UBYTE pansep; /* panning separation between channels */ + UBYTE zerobyte; + UWORD msglength; + ULONG msgoffset; + UBYTE blank02[4]; + UBYTE pantable[64]; + UBYTE voltable[64]; +} ITHEADER; + +/* sample information */ +typedef struct ITSAMPLE { + CHAR filename[12]; + UBYTE zerobyte; + UBYTE globvol; + UBYTE flag; + UBYTE volume; + UBYTE panning; + CHAR sampname[28]; + UWORD convert; /* sample conversion flag */ + ULONG length; + ULONG loopbeg; + ULONG loopend; + ULONG c5spd; + ULONG susbegin; + ULONG susend; + ULONG sampoffset; + UBYTE vibspeed; + UBYTE vibdepth; + UBYTE vibrate; + UBYTE vibwave; /* 0=sine, 1=rampdown, 2=square, 3=random (speed ignored) */ +} ITSAMPLE; + +/* instrument information */ + +#define ITENVCNT 25 +#define ITNOTECNT 120 +typedef struct ITINSTHEADER { + ULONG size; /* (dword) Instrument size */ + CHAR filename[12]; /* (char) Instrument filename */ + UBYTE zerobyte; /* (byte) Instrument type (always 0) */ + UBYTE volflg; + UBYTE volpts; + UBYTE volbeg; /* (byte) Volume loop start (node) */ + UBYTE volend; /* (byte) Volume loop end (node) */ + UBYTE volsusbeg; /* (byte) Volume sustain begin (node) */ + UBYTE volsusend; /* (byte) Volume Sustain end (node) */ + UBYTE panflg; + UBYTE panpts; + UBYTE panbeg; /* (byte) channel loop start (node) */ + UBYTE panend; /* (byte) channel loop end (node) */ + UBYTE pansusbeg; /* (byte) channel sustain begin (node) */ + UBYTE pansusend; /* (byte) channel Sustain end (node) */ + UBYTE pitflg; + UBYTE pitpts; + UBYTE pitbeg; /* (byte) pitch loop start (node) */ + UBYTE pitend; /* (byte) pitch loop end (node) */ + UBYTE pitsusbeg; /* (byte) pitch sustain begin (node) */ + UBYTE pitsusend; /* (byte) pitch Sustain end (node) */ + UWORD blank; + UBYTE globvol; + UBYTE chanpan; + UWORD fadeout; /* Envelope end / NNA volume fadeout */ + UBYTE dnc; /* Duplicate note check */ + UBYTE dca; /* Duplicate check action */ + UBYTE dct; /* Duplicate check type */ + UBYTE nna; /* New Note Action [0,1,2,3] */ + UWORD trkvers; /* tracker version used to save [files only] */ + UBYTE ppsep; /* Pitch-pan Separation */ + UBYTE ppcenter; /* Pitch-pan Center */ + UBYTE rvolvar; /* random volume varations */ + UBYTE rpanvar; /* random panning varations */ + UWORD numsmp; /* Number of samples in instrument [files only] */ + CHAR name[26]; /* Instrument name */ + UBYTE blank01[6]; + UWORD samptable[ITNOTECNT];/* sample for each note [note / samp pairs] */ + UBYTE volenv[200]; /* volume envelope (IT 1.x stuff) */ + UBYTE oldvoltick[ITENVCNT];/* volume tick position (IT 1.x stuff) */ + UBYTE volnode[ITENVCNT]; /* amplitude of volume nodes */ + UWORD voltick[ITENVCNT]; /* tick value of volume nodes */ + SBYTE pannode[ITENVCNT]; /* panenv - node points */ + UWORD pantick[ITENVCNT]; /* tick value of panning nodes */ + SBYTE pitnode[ITENVCNT]; /* pitchenv - node points */ + UWORD pittick[ITENVCNT]; /* tick value of pitch nodes */ +} ITINSTHEADER; + +/* unpacked note */ + +typedef struct ITNOTE { + UBYTE note,ins,volpan,cmd,inf; +} ITNOTE; + +/*========== Loader data */ + +static ULONG *paraptr=NULL; /* parapointer array (see IT docs) */ +static ITHEADER *mh=NULL; +static ITNOTE *itpat=NULL; /* allocate to space for one full pattern */ +static UBYTE *mask=NULL; /* arrays allocated to 64 elements and used for */ +static ITNOTE *last=NULL; /* uncompressing IT's pattern information */ +static int numtrk=0; +static unsigned int old_effect; /* if set, use S3M old-effects stuffs */ + +static CHAR* IT_Version[]={ + "ImpulseTracker . ", + "Compressed ImpulseTracker . ", + "ImpulseTracker 2.14p3", + "Compressed ImpulseTracker 2.14p3", + "ImpulseTracker 2.14p4", + "Compressed ImpulseTracker 2.14p4", +}; + +/* table for porta-to-note command within volume/panning column */ +static UBYTE portatable[10]= {0,1,4,8,16,32,64,96,128,255}; + +/*========== Loader code */ + +BOOL IT_Test(void) +{ + UBYTE id[4]; + + if(!_mm_read_UBYTES(id,4,modreader)) return 0; + if(!memcmp(id,"IMPM",4)) return 1; + return 0; +} + +BOOL IT_Init(void) +{ + if(!(mh=(ITHEADER*)MikMod_malloc(sizeof(ITHEADER)))) return 0; + if(!(poslookup=(UBYTE*)MikMod_malloc(256*sizeof(UBYTE)))) return 0; + if(!(itpat=(ITNOTE*)MikMod_malloc(200*64*sizeof(ITNOTE)))) return 0; + if(!(mask=(UBYTE*)MikMod_malloc(64*sizeof(UBYTE)))) return 0; + if(!(last=(ITNOTE*)MikMod_malloc(64*sizeof(ITNOTE)))) return 0; + + return 1; +} + +void IT_Cleanup(void) +{ + FreeLinear(); + + MikMod_free(mh); + MikMod_free(poslookup); + MikMod_free(itpat); + MikMod_free(mask); + MikMod_free(last); + MikMod_free(paraptr); + MikMod_free(origpositions); +} + +/* Because so many IT files have 64 channels as the set number used, but really + only use far less (usually from 8 to 24 still), I had to make this function, + which determines the number of channels that are actually USED by a pattern. + + NOTE: You must first seek to the file location of the pattern before calling + this procedure. + + Returns 1 on error +*/ +static BOOL IT_GetNumChannels(UWORD patrows) +{ + int row=0,flag,ch; + + do { + if((flag=_mm_read_UBYTE(modreader))==EOF) { + _mm_errno=MMERR_LOADING_PATTERN; + return 1; + } + if(!flag) + row++; + else { + ch=(flag-1)&63; + remap[ch]=0; + if(flag & 128) mask[ch]=_mm_read_UBYTE(modreader); + if(mask[ch]&1) _mm_read_UBYTE(modreader); + if(mask[ch]&2) _mm_read_UBYTE(modreader); + if(mask[ch]&4) _mm_read_UBYTE(modreader); + if(mask[ch]&8) { _mm_read_UBYTE(modreader);_mm_read_UBYTE(modreader); } + } + } while(rownote=n->note=_mm_read_UBYTE(modreader))==255) + l->note=n->note=253; + if(mask[ch]&2) + l->ins=n->ins=_mm_read_UBYTE(modreader); + if(mask[ch]&4) + l->volpan=n->volpan=_mm_read_UBYTE(modreader); + if(mask[ch]&8) { + l->cmd=n->cmd=_mm_read_UBYTE(modreader); + l->inf=n->inf=_mm_read_UBYTE(modreader); + } + if(mask[ch]&16) + n->note=l->note; + if(mask[ch]&32) + n->ins=l->ins; + if(mask[ch]&64) + n->volpan=l->volpan; + if(mask[ch]&128) { + n->cmd=l->cmd; + n->inf=l->inf; + } + } + } while(rowsongname,26,modreader); + _mm_read_UBYTES(mh->blank01,2,modreader); + mh->ordnum =_mm_read_I_UWORD(modreader); + mh->insnum =_mm_read_I_UWORD(modreader); + mh->smpnum =_mm_read_I_UWORD(modreader); + mh->patnum =_mm_read_I_UWORD(modreader); + mh->cwt =_mm_read_I_UWORD(modreader); + mh->cmwt =_mm_read_I_UWORD(modreader); + mh->flags =_mm_read_I_UWORD(modreader); + mh->special =_mm_read_I_UWORD(modreader); + mh->globvol =_mm_read_UBYTE(modreader); + mh->mixvol =_mm_read_UBYTE(modreader); + mh->initspeed =_mm_read_UBYTE(modreader); + mh->inittempo =_mm_read_UBYTE(modreader); + mh->pansep =_mm_read_UBYTE(modreader); + mh->zerobyte =_mm_read_UBYTE(modreader); + mh->msglength =_mm_read_I_UWORD(modreader); + mh->msgoffset =_mm_read_I_ULONG(modreader); + _mm_read_UBYTES(mh->blank02,4,modreader); + _mm_read_UBYTES(mh->pantable,64,modreader); + _mm_read_UBYTES(mh->voltable,64,modreader); + + if(_mm_eof(modreader)) { + _mm_errno=MMERR_LOADING_HEADER; + return 0; + } + + /* set module variables */ + of.songname = DupStr(mh->songname,26,0); /* make a cstr of songname */ + of.reppos = 0; + of.numpat = mh->patnum; + of.numins = mh->insnum; + of.numsmp = mh->smpnum; + of.initspeed = mh->initspeed; + of.inittempo = mh->inittempo; + of.initvolume = mh->globvol; + of.flags |= UF_BGSLIDES | UF_ARPMEM; + if (!(mh->flags & 1)) + of.flags |= UF_PANNING; + of.bpmlimit=32; + + if(mh->songname[25]) { + of.numvoices=1+mh->songname[25]; +#ifdef MIKMOD_DEBUG + fprintf(stderr,"Embedded IT limitation to %d voices\n",of.numvoices); +#endif + } + + /* set the module type */ + /* 2.17 : IT 2.14p4 */ + /* 2.16 : IT 2.14p3 with resonant filters */ + /* 2.15 : IT 2.14p3 (improved compression) */ + if((mh->cwt<=0x219)&&(mh->cwt>=0x217)) + of.modtype=strdup(IT_Version[mh->cmwt<0x214?4:5]); + else if (mh->cwt>=0x215) + of.modtype=strdup(IT_Version[mh->cmwt<0x214?2:3]); + else { + of.modtype = strdup(IT_Version[mh->cmwt<0x214?0:1]); + of.modtype[mh->cmwt<0x214?15:26] = (mh->cwt>>8)+'0'; + of.modtype[mh->cmwt<0x214?17:28] = ((mh->cwt>>4)&0xf)+'0'; + of.modtype[mh->cmwt<0x214?18:29] = ((mh->cwt)&0xf)+'0'; + } + + if(mh->flags&8) + of.flags |= UF_XMPERIODS | UF_LINEAR; + + if((mh->cwt>=0x106)&&(mh->flags&16)) + old_effect=S3MIT_OLDSTYLE; + else + old_effect=0; + + /* set panning positions */ + if (mh->flags & 1) + for(t=0;t<64;t++) { + mh->pantable[t]&=0x7f; + if(mh->pantable[t]<64) + of.panning[t]=mh->pantable[t]<<2; + else if(mh->pantable[t]==64) + of.panning[t]=255; + else if(mh->pantable[t]==100) + of.panning[t]=PAN_SURROUND; + else if(mh->pantable[t]==127) + of.panning[t]=PAN_CENTER; + else { + _mm_errno=MMERR_LOADING_HEADER; + return 0; + } + } + else + for(t=0;t<64;t++) + of.panning[t]=PAN_CENTER; + + /* set channel volumes */ + memcpy(of.chanvol,mh->voltable,64); + + /* read the order data */ + if(!AllocPositions(mh->ordnum)) return 0; + if(!(origpositions=MikMod_calloc(mh->ordnum,sizeof(UWORD)))) return 0; + + for(t=0;tordnum;t++) { + origpositions[t]=_mm_read_UBYTE(modreader); + if((origpositions[t]>mh->patnum)&&(origpositions[t]<254)) + origpositions[t]=255; + } + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + poslookupcnt=mh->ordnum; + S3MIT_CreateOrders(curious); + + if(!(paraptr=(ULONG*)MikMod_malloc((mh->insnum+mh->smpnum+of.numpat)* + sizeof(ULONG)))) return 0; + + /* read the instrument, sample, and pattern parapointers */ + _mm_read_I_ULONGS(paraptr,mh->insnum+mh->smpnum+of.numpat,modreader); + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* Check for and load midi information for resonant filters */ + if(mh->cmwt>=0x216) { + if(mh->special&8) { + IT_LoadMidiConfiguration(modreader); + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + } else + IT_LoadMidiConfiguration(NULL); + filters=1; + } + + /* Check for and load song comment */ + if((mh->special&1)&&(mh->cwt>=0x104)&&(mh->msglength)) { + _mm_fseek(modreader,(long)(mh->msgoffset),SEEK_SET); + if(!ReadComment(mh->msglength)) return 0; + } + + if(!(mh->flags&4)) of.numins=of.numsmp; + if(!AllocSamples()) return 0; + + if(!AllocLinear()) return 0; + + /* Load all samples */ + q = of.samples; + for(t=0;tsmpnum;t++) { + ITSAMPLE s; + + /* seek to sample position */ + _mm_fseek(modreader,(long)(paraptr[mh->insnum+t]+4),SEEK_SET); + + /* load sample info */ + _mm_read_string(s.filename,12,modreader); + s.zerobyte = _mm_read_UBYTE(modreader); + s.globvol = _mm_read_UBYTE(modreader); + s.flag = _mm_read_UBYTE(modreader); + s.volume = _mm_read_UBYTE(modreader); + _mm_read_string(s.sampname,26,modreader); + s.convert = _mm_read_UBYTE(modreader); + s.panning = _mm_read_UBYTE(modreader); + s.length = _mm_read_I_ULONG(modreader); + s.loopbeg = _mm_read_I_ULONG(modreader); + s.loopend = _mm_read_I_ULONG(modreader); + s.c5spd = _mm_read_I_ULONG(modreader); + s.susbegin = _mm_read_I_ULONG(modreader); + s.susend = _mm_read_I_ULONG(modreader); + s.sampoffset = _mm_read_I_ULONG(modreader); + s.vibspeed = _mm_read_UBYTE(modreader); + s.vibdepth = _mm_read_UBYTE(modreader); + s.vibrate = _mm_read_UBYTE(modreader); + s.vibwave = _mm_read_UBYTE(modreader); + + /* Generate an error if c5spd is > 512k, or samplelength > 256 megs + (nothing would EVER be that high) */ + + if(_mm_eof(modreader)||(s.c5spd>0x7ffffL)||(s.length>0xfffffffUL)) { + _mm_errno = MMERR_LOADING_SAMPLEINFO; + return 0; + } + + /* Reality check for sample loop information */ + if((s.flag&16)&& + ((s.loopbeg>0xfffffffUL)||(s.loopend>0xfffffffUL))) { + _mm_errno = MMERR_LOADING_SAMPLEINFO; + return 0; + } + + q->samplename = DupStr(s.sampname,26,0); + q->speed = s.c5spd / 2; + q->panning = ((s.panning&127)==64)?255:(s.panning&127)<<2; + q->length = s.length; + q->loopstart = s.loopbeg; + q->loopend = s.loopend; + q->volume = s.volume; + q->globvol = s.globvol; + q->seekpos = s.sampoffset; + + /* Convert speed to XM linear finetune */ + if(of.flags&UF_LINEAR) + q->speed=speed_to_finetune(s.c5spd,t); + + if(s.panning&128) q->flags|=SF_OWNPAN; + + if(s.vibrate) { + q->vibflags |= AV_IT; + q->vibtype = s.vibwave; + q->vibsweep = s.vibrate * 2; + q->vibdepth = s.vibdepth; + q->vibrate = s.vibspeed; + } + + if(s.flag&2) q->flags|=SF_16BITS; + if((s.flag&8)&&(mh->cwt>=0x214)) { + q->flags|=SF_ITPACKED; + compressed=1; + } + if(s.flag&16) q->flags|=SF_LOOP; + if(s.flag&64) q->flags|=SF_BIDI; + + if(mh->cwt>=0x200) { + if(s.convert&1) q->flags|=SF_SIGNED; + if(s.convert&4) q->flags|=SF_DELTA; + } + q++; + } + + /* Load instruments if instrument mode flag enabled */ + if(mh->flags&4) { + if(!AllocInstruments()) return 0; + d=of.instruments; + of.flags|=UF_NNA|UF_INST; + + for(t=0;tinsnum;t++) { + ITINSTHEADER ih; + + /* seek to instrument position */ + _mm_fseek(modreader,paraptr[t]+4,SEEK_SET); + + /* load instrument info */ + _mm_read_string(ih.filename,12,modreader); + ih.zerobyte = _mm_read_UBYTE(modreader); + if(mh->cwt<0x200) { + /* load IT 1.xx inst header */ + ih.volflg = _mm_read_UBYTE(modreader); + ih.volbeg = _mm_read_UBYTE(modreader); + ih.volend = _mm_read_UBYTE(modreader); + ih.volsusbeg = _mm_read_UBYTE(modreader); + ih.volsusend = _mm_read_UBYTE(modreader); + _mm_read_I_UWORD(modreader); + ih.fadeout = _mm_read_I_UWORD(modreader); + ih.nna = _mm_read_UBYTE(modreader); + ih.dnc = _mm_read_UBYTE(modreader); + } else { + /* Read IT200+ header */ + ih.nna = _mm_read_UBYTE(modreader); + ih.dct = _mm_read_UBYTE(modreader); + ih.dca = _mm_read_UBYTE(modreader); + ih.fadeout = _mm_read_I_UWORD(modreader); + ih.ppsep = _mm_read_UBYTE(modreader); + ih.ppcenter = _mm_read_UBYTE(modreader); + ih.globvol = _mm_read_UBYTE(modreader); + ih.chanpan = _mm_read_UBYTE(modreader); + ih.rvolvar = _mm_read_UBYTE(modreader); + ih.rpanvar = _mm_read_UBYTE(modreader); + } + + ih.trkvers = _mm_read_I_UWORD(modreader); + ih.numsmp = _mm_read_UBYTE(modreader); + _mm_read_UBYTE(modreader); + _mm_read_string(ih.name,26,modreader); + _mm_read_UBYTES(ih.blank01,6,modreader); + _mm_read_I_UWORDS(ih.samptable,ITNOTECNT,modreader); + if(mh->cwt<0x200) { + /* load IT 1xx volume envelope */ + _mm_read_UBYTES(ih.volenv,200,modreader); + for(lp=0;lpvolflg|=EF_VOLENV; + d->insname = DupStr(ih.name,26,0); + d->nnatype = ih.nna & NNA_MASK; + + if(mh->cwt<0x200) { + d->volfade=ih.fadeout<< 6; + if(ih.dnc) { + d->dct=DCT_NOTE; + d->dca=DCA_CUT; + } + + if(ih.volflg&1) d->volflg|=EF_ON; + if(ih.volflg&2) d->volflg|=EF_LOOP; + if(ih.volflg&4) d->volflg|=EF_SUSTAIN; + + /* XM conversion of IT envelope Array */ + d->volbeg = ih.volbeg; + d->volend = ih.volend; + d->volsusbeg = ih.volsusbeg; + d->volsusend = ih.volsusend; + + if(ih.volflg&1) { + for(u=0;uvolpts]!=0xff) { + d->volenv[d->volpts].val=(ih.volnode[d->volpts]<<2); + d->volenv[d->volpts].pos=ih.oldvoltick[d->volpts]; + d->volpts++; + } else + break; + } + } else { + d->panning=((ih.chanpan&127)==64)?255:(ih.chanpan&127)<<2; + if(!(ih.chanpan&128)) d->flags|=IF_OWNPAN; + + if(!(ih.ppsep & 128)) { + d->pitpansep=ih.ppsep<<2; + d->pitpancenter=ih.ppcenter; + d->flags|=IF_PITCHPAN; + } + d->globvol=ih.globvol>>1; + d->volfade=ih.fadeout<<5; + d->dct =ih.dct; + d->dca =ih.dca; + + if(mh->cwt>=0x204) { + d->rvolvar = ih.rvolvar; + d->rpanvar = ih.rpanvar; + } + +#if defined __STDC__ || defined _MSC_VER || defined MPW_C +#define IT_ProcessEnvelope(name) \ + if(ih. name##flg&1) d-> name##flg|=EF_ON; \ + if(ih. name##flg&2) d-> name##flg|=EF_LOOP; \ + if(ih. name##flg&4) d-> name##flg|=EF_SUSTAIN; \ + d-> name##pts=ih. name##pts; \ + d-> name##beg=ih. name##beg; \ + d-> name##end=ih. name##end; \ + d-> name##susbeg=ih. name##susbeg; \ + d-> name##susend=ih. name##susend; \ + \ + for(u=0;u name##env[u].pos=ih. name##tick[u]; \ + \ + if((d-> name##flg&EF_ON)&&(d-> name##pts<2)) \ + d-> name##flg&=~EF_ON +#else +#define IT_ProcessEnvelope(name) \ + if(ih. name/**/flg&1) d-> name/**/flg|=EF_ON; \ + if(ih. name/**/flg&2) d-> name/**/flg|=EF_LOOP; \ + if(ih. name/**/flg&4) d-> name/**/flg|=EF_SUSTAIN; \ + d-> name/**/pts=ih. name/**/pts; \ + d-> name/**/beg=ih. name/**/beg; \ + d-> name/**/end=ih. name/**/end; \ + d-> name/**/susbeg=ih. name/**/susbeg; \ + d-> name/**/susend=ih. name/**/susend; \ + \ + for(u=0;u name/**/env[u].pos=ih. name/**/tick[u]; \ + \ + if((d-> name/**/flg&EF_ON)&&(d-> name/**/pts<2)) \ + d-> name/**/flg&=~EF_ON +#endif + + IT_ProcessEnvelope(vol); + for(u=0;uvolenv[u].val=(ih.volnode[u]<<2); + + IT_ProcessEnvelope(pan); + for(u=0;upanenv[u].val= + ih.pannode[u]==32?255:(ih.pannode[u]+32)<<2; + + IT_ProcessEnvelope(pit); + for(u=0;upitenv[u].val=ih.pitnode[u]+32; +#undef IT_ProcessEnvelope + + if(ih.pitflg&0x80) { + /* filter envelopes not supported yet */ + d->pitflg&=~EF_ON; + ih.pitpts=ih.pitbeg=ih.pitend=0; +#ifdef MIKMOD_DEBUG + { + static int warn=0; + + if(!warn) + fprintf(stderr, "\rFilter envelopes not supported yet\n"); + warn=1; + } +#endif + } + } + + for(u=0;usamplenote[u]=(ih.samptable[u]&255); + d->samplenumber[u]= + (ih.samptable[u]>>8)?((ih.samptable[u]>>8)-1):0xffff; + if(d->samplenumber[u]>=of.numsmp) + d->samplenote[u]=255; + else if (of.flags&UF_LINEAR) { + int note=(int)d->samplenote[u]+noteindex[d->samplenumber[u]]; + d->samplenote[u]=(note<0)?0:(note>255?255:note); + } + } + + d++; + } + } else if(of.flags & UF_LINEAR) { + if(!AllocInstruments()) return 0; + d=of.instruments; + of.flags|=UF_INST; + + for(t=0;tsmpnum;t++,d++) + for(u=0;usamplenumber[u]>=of.numsmp) + d->samplenote[u]=255; + else { + int note=(int)d->samplenote[u]+noteindex[d->samplenumber[u]]; + d->samplenote[u]=(note<0)?0:(note>255?255:note); + } + } + } + + /* Figure out how many channels this song actually uses */ + of.numchn=0; + memset(remap,-1,UF_MAXCHAN*sizeof(UBYTE)); + for(t=0;tinsnum+mh->smpnum+t]) { /* 0 -> empty 64 row pattern */ + _mm_fseek(modreader,((long)paraptr[mh->insnum+mh->smpnum+t]),SEEK_SET); + _mm_read_I_UWORD(modreader); + /* read pattern length (# of rows) + Impulse Tracker never creates patterns with less than 32 rows, + but some other trackers do, so we only check for more than 256 + rows */ + packlen=_mm_read_I_UWORD(modreader); + if(packlen>256) { + _mm_errno=MMERR_LOADING_PATTERN; + return 0; + } + _mm_read_I_ULONG(modreader); + if(IT_GetNumChannels(packlen)) return 0; + } + } + + /* give each of them a different number */ + for(t=0;tinsnum+mh->smpnum+t]) { /* 0 -> empty 64 row pattern */ + of.pattrows[t]=64; + for(u=0;uinsnum+mh->smpnum+t]),SEEK_SET); + packlen=_mm_read_I_UWORD(modreader); + of.pattrows[t]=_mm_read_I_UWORD(modreader); + _mm_read_I_ULONG(modreader); + if(!IT_ReadPattern(of.pattrows[t])) return 0; + } + } + + return 1; +} + +CHAR *IT_LoadTitle(void) +{ + CHAR s[26]; + + _mm_fseek(modreader,4,SEEK_SET); + if(!_mm_read_UBYTES(s,26,modreader)) return NULL; + + return(DupStr(s,26,0)); +} + +/*========== Loader information */ + +MIKMODAPI MLOADER load_it={ + NULL, + "IT", + "IT (Impulse Tracker)", + IT_Init, + IT_Test, + IT_Load, + IT_Cleanup, + IT_LoadTitle +}; + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/load_mod.c b/src/libs/mikmod/load_mod.c new file mode 100644 index 0000000..40d4b9a --- /dev/null +++ b/src/libs/mikmod/load_mod.c @@ -0,0 +1,512 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file + AUTHORS for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Generic MOD loader (Protracker, StarTracker, FastTracker, etc) + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include +#ifdef HAVE_MEMORY_H +#include +#endif +#include + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + +/*========== Module structure */ + +typedef struct MSAMPINFO { + CHAR samplename[23]; /* 22 in module, 23 in memory */ + UWORD length; + UBYTE finetune; + UBYTE volume; + UWORD reppos; + UWORD replen; +} MSAMPINFO; + +typedef struct MODULEHEADER { + CHAR songname[21]; /* the songname.. 20 in module, 21 in memory */ + MSAMPINFO samples[31]; /* all sampleinfo */ + UBYTE songlength; /* number of patterns used */ + UBYTE magic1; /* should be 127 */ + UBYTE positions[128]; /* which pattern to play at pos */ + UBYTE magic2[4]; /* string "M.K." or "FLT4" or "FLT8" */ +} MODULEHEADER; + +typedef struct MODTYPE { + CHAR id[5]; + UBYTE channels; + CHAR *name; +} MODTYPE; + +typedef struct MODNOTE { + UBYTE a, b, c, d; +} MODNOTE; + +/*========== Loader variables */ + +#define MODULEHEADERSIZE 0x438 + +static CHAR protracker[] = "Protracker"; +static CHAR startrekker[] = "Startrekker"; +static CHAR fasttracker[] = "Fasttracker"; +static CHAR oktalyser[] = "Oktalyser"; +static CHAR oktalyzer[] = "Oktalyzer"; +static CHAR taketracker[] = "TakeTracker"; +static CHAR orpheus[] = "Imago Orpheus (MOD format)"; + +static MODULEHEADER *mh = NULL; +static MODNOTE *patbuf = NULL; +static int modtype, trekker; + +/*========== Loader code */ + +/* given the module ID, determine the number of channels and the tracker + description ; also alters modtype */ +static BOOL MOD_CheckType(UBYTE *id, UBYTE *numchn, CHAR **descr) +{ + modtype = trekker = 0; + + /* Protracker and variants */ + if ((!memcmp(id, "M.K.", 4)) || (!memcmp(id, "M!K!", 4))) { + *descr = protracker; + modtype = 0; + *numchn = 4; + return 1; + } + + /* Star Tracker */ + if (((!memcmp(id, "FLT", 3)) || (!memcmp(id, "EXO", 3))) && + (isdigit(id[3]))) { + *descr = startrekker; + modtype = trekker = 1; + *numchn = id[3] - '0'; + if (*numchn == 4 || *numchn == 8) + return 1; +#ifdef MIKMOD_DEBUG + else + fprintf(stderr, "\rUnknown FLT%d module type\n", *numchn); +#endif + return 0; + } + + /* Oktalyzer (Amiga) */ + if (!memcmp(id, "OKTA", 4)) { + *descr = oktalyzer; + modtype = 1; + *numchn = 8; + return 1; + } + + /* Oktalyser (Atari) */ + if (!memcmp(id, "CD81", 4)) { + *descr = oktalyser; + modtype = 1; + *numchn = 8; + return 1; + } + + /* Fasttracker */ + if ((!memcmp(id + 1, "CHN", 3)) && (isdigit(id[0]))) { + *descr = fasttracker; + modtype = 1; + *numchn = id[0] - '0'; + return 1; + } + /* Fasttracker or Taketracker */ + if (((!memcmp(id + 2, "CH", 2)) || (!memcmp(id + 2, "CN", 2))) + && (isdigit(id[0])) && (isdigit(id[1]))) { + if (id[3] == 'H') { + *descr = fasttracker; + modtype = 2; /* this can also be Imago Orpheus */ + } else { + *descr = taketracker; + modtype = 1; + } + *numchn = (id[0] - '0') * 10 + (id[1] - '0'); + return 1; + } + + return 0; +} + +static BOOL MOD_Test(void) +{ + UBYTE id[4], numchn; + CHAR *descr; + + _mm_fseek(modreader, MODULEHEADERSIZE, SEEK_SET); + if (!_mm_read_UBYTES(id, 4, modreader)) + return 0; + + if (MOD_CheckType(id, &numchn, &descr)) + return 1; + + return 0; +} + +static BOOL MOD_Init(void) +{ + if (!(mh = (MODULEHEADER *)MikMod_malloc(sizeof(MODULEHEADER)))) + return 0; + return 1; +} + +static void MOD_Cleanup(void) +{ + MikMod_free(mh); + MikMod_free(patbuf); +} + +/* +Old (amiga) noteinfo: + +_____byte 1_____ byte2_ _____byte 3_____ byte4_ +/ \ / \ / \ / \ +0000 0000-00000000 0000 0000-00000000 + +Upper four 12 bits for Lower four Effect command. +bits of sam- note period. bits of sam- +ple number. ple number. + +*/ + +static UBYTE ConvertNote(MODNOTE *n, UBYTE lasteffect) +{ + UBYTE instrument, effect, effdat, note; + UWORD period; + UBYTE lastnote = 0; + + /* extract the various information from the 4 bytes that make up a note */ + instrument = (n->a & 0x10) | (n->c >> 4); + period = (((UWORD)n->a & 0xf) << 8) + n->b; + effect = n->c & 0xf; + effdat = n->d; + + /* Convert the period to a note number */ + note = 0; + if (period) { + for (note = 0; note < 7 * OCTAVE; note++) + if (period >= npertab[note]) + break; + if (note == 7 * OCTAVE) + note = 0; + else + note++; + } + + if (instrument) { + /* if instrument does not exist, note cut */ + if ((instrument > 31) || (!mh->samples[instrument - 1].length)) { + UniPTEffect(0xc, 0); + if (effect == 0xc) + effect = effdat = 0; + } else { + /* Protracker handling */ + if (!modtype) { + /* if we had a note, then change instrument... */ + if (note) + UniInstrument(instrument - 1); + /* ...otherwise, only adjust volume... */ + else { + /* ...unless an effect was specified, which forces a new + note to be played */ + if (effect || effdat) { + UniInstrument(instrument - 1); + note = lastnote; + } else + UniPTEffect(0xc, + mh->samples[instrument - + 1].volume & 0x7f); + } + } else { + /* Fasttracker handling */ + UniInstrument(instrument - 1); + if (!note) + note = lastnote; + } + } + } + if (note) { + UniNote(note + 2 * OCTAVE - 1); + lastnote = note; + } + + /* Convert pattern jump from Dec to Hex */ + if (effect == 0xd) + effdat = (((effdat & 0xf0) >> 4) * 10) + (effdat & 0xf); + + /* Volume slide, up has priority */ + if ((effect == 0xa) && (effdat & 0xf) && (effdat & 0xf0)) + effdat &= 0xf0; + + /* Handle ``heavy'' volumes correctly */ + if ((effect == 0xc) && (effdat > 0x40)) + effdat = 0x40; + + /* An isolated 100, 200 or 300 effect should be ignored (no + "standalone" porta memory in mod files). However, a sequence such + as 1XX, 100, 100, 100 is fine. */ + if ((!effdat) && ((effect == 1)||(effect == 2)||(effect ==3)) && + (lasteffect < 0x10) && (effect != lasteffect)) + effect = 0; + + UniPTEffect(effect, effdat); + if (effect == 8) + of.flags |= UF_PANNING; + + return effect; +} + +static UBYTE *ConvertTrack(MODNOTE *n, int numchn) +{ + int t; + UBYTE lasteffect = 0x10; /* non existant effect */ + + UniReset(); + for (t = 0; t < 64; t++) { + lasteffect = ConvertNote(n,lasteffect); + UniNewline(); + n += numchn; + } + return UniDup(); +} + +/* Loads all patterns of a modfile and converts them into the 3 byte format. */ +static BOOL ML_LoadPatterns(void) +{ + int t, s, tracks = 0; + + if (!AllocPatterns()) + return 0; + if (!AllocTracks()) + return 0; + + /* Allocate temporary buffer for loading and converting the patterns */ + if (!(patbuf = (MODNOTE *)MikMod_calloc(64U * of.numchn, sizeof(MODNOTE)))) + return 0; + + if (trekker && of.numchn == 8) { + /* Startrekker module dual pattern */ + for (t = 0; t < of.numpat; t++) { + for (s = 0; s < (64 * 4); s++) { + patbuf[s].a = _mm_read_UBYTE(modreader); + patbuf[s].b = _mm_read_UBYTE(modreader); + patbuf[s].c = _mm_read_UBYTE(modreader); + patbuf[s].d = _mm_read_UBYTE(modreader); + } + for (s = 0; s < 4; s++) + if (!(of.tracks[tracks++] = ConvertTrack(patbuf + s, 4))) + return 0; + for (s = 0; s < (64 * 4); s++) { + patbuf[s].a = _mm_read_UBYTE(modreader); + patbuf[s].b = _mm_read_UBYTE(modreader); + patbuf[s].c = _mm_read_UBYTE(modreader); + patbuf[s].d = _mm_read_UBYTE(modreader); + } + for (s = 0; s < 4; s++) + if (!(of.tracks[tracks++] = ConvertTrack(patbuf + s, 4))) + return 0; + } + } else { + /* Generic module pattern */ + for (t = 0; t < of.numpat; t++) { + /* Load the pattern into the temp buffer and convert it */ + for (s = 0; s < (64 * of.numchn); s++) { + patbuf[s].a = _mm_read_UBYTE(modreader); + patbuf[s].b = _mm_read_UBYTE(modreader); + patbuf[s].c = _mm_read_UBYTE(modreader); + patbuf[s].d = _mm_read_UBYTE(modreader); + } + for (s = 0; s < of.numchn; s++) + if (!(of.tracks[tracks++] = ConvertTrack(patbuf + s, of.numchn))) + return 0; + } + } + return 1; +} + +static BOOL MOD_Load(BOOL curious) +{ + int t, scan; + SAMPLE *q; + MSAMPINFO *s; + CHAR *descr; + + /* try to read module header */ + _mm_read_string((CHAR *)mh->songname, 20, modreader); + mh->songname[20] = 0; /* just in case */ + + for (t = 0; t < 31; t++) { + s = &mh->samples[t]; + _mm_read_string(s->samplename, 22, modreader); + s->samplename[22] = 0; /* just in case */ + s->length = _mm_read_M_UWORD(modreader); + s->finetune = _mm_read_UBYTE(modreader); + s->volume = _mm_read_UBYTE(modreader); + s->reppos = _mm_read_M_UWORD(modreader); + s->replen = _mm_read_M_UWORD(modreader); + } + + mh->songlength = _mm_read_UBYTE(modreader); + + /* this fixes mods which declare more than 128 positions. + * eg: beatwave.mod */ + if (mh->songlength > 128) { mh->songlength = 128; } + + mh->magic1 = _mm_read_UBYTE(modreader); + _mm_read_UBYTES(mh->positions, 128, modreader); + _mm_read_UBYTES(mh->magic2, 4, modreader); + + if (_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* set module variables */ + of.initspeed = 6; + of.inittempo = 125; + if (!(MOD_CheckType(mh->magic2, &of.numchn, &descr))) { + _mm_errno = MMERR_NOT_A_MODULE; + return 0; + } + if (trekker && of.numchn == 8) + for (t = 0; t < 128; t++) + /* if module pretends to be FLT8, yet the order table + contains odd numbers, chances are it's a lying FLT4... */ + if (mh->positions[t] & 1) { + of.numchn = 4; + break; + } + if (trekker && of.numchn == 8) + for (t = 0; t < 128; t++) + mh->positions[t] >>= 1; + + of.songname = DupStr(mh->songname, 21, 1); + of.numpos = mh->songlength; + of.reppos = 0; + + /* Count the number of patterns */ + of.numpat = 0; + for (t = 0; t < of.numpos; t++) + if (mh->positions[t] > of.numpat) + of.numpat = mh->positions[t]; + + /* since some old modules embed extra patterns, we have to check the + whole list to get the samples' file offsets right - however we can find + garbage here, so check carefully */ + scan = 1; + for (t = of.numpos; t < 128; t++) + if (mh->positions[t] >= 0x80) + scan = 0; + if (scan) + for (t = of.numpos; t < 128; t++) { + if (mh->positions[t] > of.numpat) + of.numpat = mh->positions[t]; + if ((curious) && (mh->positions[t])) + of.numpos = t + 1; + } + of.numpat++; + of.numtrk = of.numpat * of.numchn; + + if (!AllocPositions(of.numpos)) + return 0; + for (t = 0; t < of.numpos; t++) + of.positions[t] = mh->positions[t]; + + /* Finally, init the sampleinfo structures */ + of.numins = of.numsmp = 31; + if (!AllocSamples()) + return 0; + s = mh->samples; + q = of.samples; + for (t = 0; t < of.numins; t++) { + /* convert the samplename */ + q->samplename = DupStr(s->samplename, 23, 1); + /* init the sampleinfo variables and convert the size pointers */ + q->speed = finetune[s->finetune & 0xf]; + q->volume = s->volume & 0x7f; + q->loopstart = (ULONG)s->reppos << 1; + q->loopend = q->loopstart + ((ULONG)s->replen << 1); + q->length = (ULONG)s->length << 1; + q->flags = SF_SIGNED; + /* Imago Orpheus creates MODs with 16 bit samples, check */ + if ((modtype == 2) && (s->volume & 0x80)) { + q->flags |= SF_16BITS; + descr = orpheus; + } + if (s->replen > 2) + q->flags |= SF_LOOP; + + s++; + q++; + } + + of.modtype = strdup(descr); + + if (!ML_LoadPatterns()) + return 0; + + return 1; +} + +static CHAR *MOD_LoadTitle(void) +{ + CHAR s[21]; + + _mm_fseek(modreader, 0, SEEK_SET); + if (!_mm_read_UBYTES(s, 20, modreader)) + return NULL; + s[20] = 0; /* just in case */ + + return (DupStr(s, 21, 1)); +} + +/*========== Loader information */ + +MIKMODAPI MLOADER load_mod = { + NULL, + "Standard module", + "MOD (31 instruments)", + MOD_Init, + MOD_Test, + MOD_Load, + MOD_Cleanup, + MOD_LoadTitle +}; + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/load_s3m.c b/src/libs/mikmod/load_s3m.c new file mode 100644 index 0000000..782ee23 --- /dev/null +++ b/src/libs/mikmod/load_s3m.c @@ -0,0 +1,470 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file + AUTHORS for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Screamtracker (S3M) module loader + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#ifdef HAVE_MEMORY_H +#include +#endif +#include + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + +/*========== Module structure */ + +/* header */ +typedef struct S3MHEADER { + CHAR songname[28]; + UBYTE t1a; + UBYTE type; + UBYTE unused1[2]; + UWORD ordnum; + UWORD insnum; + UWORD patnum; + UWORD flags; + UWORD tracker; + UWORD fileformat; + CHAR scrm[4]; + UBYTE mastervol; + UBYTE initspeed; + UBYTE inittempo; + UBYTE mastermult; + UBYTE ultraclick; + UBYTE pantable; + UBYTE unused2[8]; + UWORD special; + UBYTE channels[32]; +} S3MHEADER; + +/* sample information */ +typedef struct S3MSAMPLE { + UBYTE type; + CHAR filename[12]; + UBYTE memsegh; + UWORD memsegl; + ULONG length; + ULONG loopbeg; + ULONG loopend; + UBYTE volume; + UBYTE dsk; + UBYTE pack; + UBYTE flags; + ULONG c2spd; + UBYTE unused[12]; + CHAR sampname[28]; + CHAR scrs[4]; +} S3MSAMPLE; + +typedef struct S3MNOTE { + UBYTE note,ins,vol,cmd,inf; +} S3MNOTE; + +/*========== Loader variables */ + +static S3MNOTE *s3mbuf = NULL; /* pointer to a complete S3M pattern */ +static S3MHEADER *mh = NULL; +static UWORD *paraptr = NULL; /* parapointer array (see S3M docs) */ +static unsigned int tracker; /* tracker id */ + +/* tracker identifiers */ +#define NUMTRACKERS 4 +static CHAR* S3M_Version[] = { + "Screamtracker x.xx", + "Imago Orpheus x.xx (S3M format)", + "Impulse Tracker x.xx (S3M format)", + "Unknown tracker x.xx (S3M format)", + "Impulse Tracker 2.14p3 (S3M format)", + "Impulse Tracker 2.14p4 (S3M format)" +}; +/* version number position in above array */ +static int numeric[NUMTRACKERS]={14,14,16,16}; + +/*========== Loader code */ + +BOOL S3M_Test(void) +{ + UBYTE id[4]; + + _mm_fseek(modreader,0x2c,SEEK_SET); + if(!_mm_read_UBYTES(id,4,modreader)) return 0; + if(!memcmp(id,"SCRM",4)) return 1; + return 0; +} + +BOOL S3M_Init(void) +{ + if(!(s3mbuf=(S3MNOTE*)MikMod_malloc(32*64*sizeof(S3MNOTE)))) return 0; + if(!(mh=(S3MHEADER*)MikMod_malloc(sizeof(S3MHEADER)))) return 0; + if(!(poslookup=(UBYTE*)MikMod_malloc(sizeof(UBYTE)*256))) return 0; + memset(poslookup,-1,256); + + return 1; +} + +void S3M_Cleanup(void) +{ + MikMod_free(s3mbuf); + MikMod_free(paraptr); + MikMod_free(poslookup); + MikMod_free(mh); + MikMod_free(origpositions); +} + +/* Because so many s3m files have 16 channels as the set number used, but really + only use far less (usually 8 to 12 still), I had to make this function, which + determines the number of channels that are actually USED by a pattern. + + For every channel that's used, it sets the appropriate array entry of the + global variable 'remap' + + NOTE: You must first seek to the file location of the pattern before calling + this procedure. + + Returns 1 on fail. */ +static BOOL S3M_GetNumChannels(void) +{ + int row=0,flag,ch; + + while(row<64) { + flag=_mm_read_UBYTE(modreader); + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_PATTERN; + return 1; + } + + if(flag) { + ch=flag&31; + if(mh->channels[ch]<32) remap[ch] = 0; + if(flag&32) {_mm_read_UBYTE(modreader);_mm_read_UBYTE(modreader);} + if(flag&64) _mm_read_UBYTE(modreader); + if(flag&128){_mm_read_UBYTE(modreader);_mm_read_UBYTE(modreader);} + } else row++; + } + return 0; +} + +static BOOL S3M_ReadPattern(void) +{ + int row=0,flag,ch; + S3MNOTE *n,dummy; + + /* clear pattern data */ + memset(s3mbuf,255,32*64*sizeof(S3MNOTE)); + + while(row<64) { + flag=_mm_read_UBYTE(modreader); + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_PATTERN; + return 0; + } + + if(flag) { + ch=remap[flag&31]; + + if(ch!=-1) + n=&s3mbuf[(64U*ch)+row]; + else + n=&dummy; + + if(flag&32) { + n->note=_mm_read_UBYTE(modreader); + n->ins=_mm_read_UBYTE(modreader); + } + if(flag&64) { + n->vol=_mm_read_UBYTE(modreader); + if (n->vol>64) n->vol=64; + } + if(flag&128) { + n->cmd=_mm_read_UBYTE(modreader); + n->inf=_mm_read_UBYTE(modreader); + } + } else row++; + } + return 1; +} + +static UBYTE* S3M_ConvertTrack(S3MNOTE* tr) +{ + int t; + + UniReset(); + for(t=0;t<64;t++) { + UBYTE note,ins,vol; + + note=tr[t].note; + ins=tr[t].ins; + vol=tr[t].vol; + + if((ins)&&(ins!=255)) UniInstrument(ins-1); + if(note!=255) { + if(note==254) { + UniPTEffect(0xc,0); /* note cut command */ + vol=255; + } else + UniNote(((note>>4)*OCTAVE)+(note&0xf)); /* normal note */ + } + if(vol<255) UniPTEffect(0xc,vol); + + S3MIT_ProcessCmd(tr[t].cmd,tr[t].inf, + tracker == 1 ? S3MIT_OLDSTYLE | S3MIT_SCREAM : S3MIT_OLDSTYLE); + UniNewline(); + } + return UniDup(); +} + +BOOL S3M_Load(BOOL curious) +{ + int t,u,track = 0; + SAMPLE *q; + UBYTE pan[32]; + + /* try to read module header */ + _mm_read_string(mh->songname,28,modreader); + mh->t1a =_mm_read_UBYTE(modreader); + mh->type =_mm_read_UBYTE(modreader); + _mm_read_UBYTES(mh->unused1,2,modreader); + mh->ordnum =_mm_read_I_UWORD(modreader); + mh->insnum =_mm_read_I_UWORD(modreader); + mh->patnum =_mm_read_I_UWORD(modreader); + mh->flags =_mm_read_I_UWORD(modreader); + mh->tracker =_mm_read_I_UWORD(modreader); + mh->fileformat =_mm_read_I_UWORD(modreader); + _mm_read_string(mh->scrm,4,modreader); + mh->mastervol =_mm_read_UBYTE(modreader); + mh->initspeed =_mm_read_UBYTE(modreader); + mh->inittempo =_mm_read_UBYTE(modreader); + mh->mastermult =_mm_read_UBYTE(modreader); + mh->ultraclick =_mm_read_UBYTE(modreader); + mh->pantable =_mm_read_UBYTE(modreader); + _mm_read_UBYTES(mh->unused2,8,modreader); + mh->special =_mm_read_I_UWORD(modreader); + _mm_read_UBYTES(mh->channels,32,modreader); + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* then we can decide the module type */ + tracker=mh->tracker>>12; + if((!tracker)||(tracker>=NUMTRACKERS)) + tracker=NUMTRACKERS-1; /* unknown tracker */ + else { + if(mh->tracker>=0x3217) + tracker=NUMTRACKERS+1; /* IT 2.14p4 */ + else if(mh->tracker>=0x3216) + tracker=NUMTRACKERS; /* IT 2.14p3 */ + else tracker--; + } + of.modtype = strdup(S3M_Version[tracker]); + if(trackertracker>>8) &0xf)+'0'; + of.modtype[numeric[tracker]+2] = ((mh->tracker>>4)&0xf)+'0'; + of.modtype[numeric[tracker]+3] = ((mh->tracker)&0xf)+'0'; + } + /* set module variables */ + of.songname = DupStr(mh->songname,28,0); + of.numpat = mh->patnum; + of.reppos = 0; + of.numins = of.numsmp = mh->insnum; + of.initspeed = mh->initspeed; + of.inittempo = mh->inittempo; + of.initvolume = mh->mastervol<<1; + of.flags |= UF_ARPMEM | UF_PANNING; + if((mh->tracker==0x1300)||(mh->flags&64)) + of.flags|=UF_S3MSLIDES; + of.bpmlimit = 32; + + /* read the order data */ + if(!AllocPositions(mh->ordnum)) return 0; + if(!(origpositions=MikMod_calloc(mh->ordnum,sizeof(UWORD)))) return 0; + + for(t=0;tordnum;t++) { + origpositions[t]=_mm_read_UBYTE(modreader); + if((origpositions[t]>=mh->patnum)&&(origpositions[t]<254)) + origpositions[t]=255/*mh->patnum-1*/; + } + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + poslookupcnt=mh->ordnum; + S3MIT_CreateOrders(curious); + + if(!(paraptr=(UWORD*)MikMod_malloc((of.numins+of.numpat)*sizeof(UWORD)))) + return 0; + + /* read the instrument+pattern parapointers */ + _mm_read_I_UWORDS(paraptr,of.numins+of.numpat,modreader); + + if(mh->pantable==252) { + /* read the panning table (ST 3.2 addition. See below for further + portions of channel panning [past reampper]). */ + _mm_read_UBYTES(pan,32,modreader); + } + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* load samples */ + if(!AllocSamples()) return 0; + q = of.samples; + for(t=0;t 64000) + s.length = 64000; + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_SAMPLEINFO; + return 0; + } + + q->samplename = DupStr(s.sampname,28,0); + q->speed = s.c2spd; + q->length = s.length; + q->loopstart = s.loopbeg; + q->loopend = s.loopend; + q->volume = s.volume; + q->seekpos = (((long)s.memsegh)<<16|s.memsegl)<<4; + + if(s.flags&1) q->flags |= SF_LOOP; + if(s.flags&4) q->flags |= SF_16BITS; + if(mh->fileformat==1) q->flags |= SF_SIGNED; + + /* don't load sample if it doesn't have the SCRS tag */ + if(memcmp(s.scrs,"SCRS",4)) q->length = 0; + + q++; + } + + /* determine the number of channels actually used. */ + of.numchn = 0; + memset(remap,-1,32*sizeof(UBYTE)); + for(t=0;tchannels[t]<32)&&(remap[t]!=-1)) { + if(mh->channels[t]<8) + of.panning[remap[t]]=0x30; + else + of.panning[remap[t]]=0xc0; + } + if(mh->pantable==252) + /* set panning positions according to panning table (new for st3.2) */ + for(t=0;t<32;t++) + if((pan[t]&0x20)&&(mh->channels[t]<32)&&(remap[t]!=-1)) + of.panning[remap[t]]=(pan[t]&0xf)<<4; + + /* load pattern info */ + of.numtrk=of.numpat*of.numchn; + if(!AllocTracks()) return 0; + if(!AllocPatterns()) return 0; + + for(t=0;t +#endif + +#include +#ifdef HAVE_MEMORY_H +#include +#endif +#include + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + +/*========== Module structure */ + +/* sample information */ +typedef struct STMSAMPLE { + CHAR filename[12]; + UBYTE unused; /* 0x00 */ + UBYTE instdisk; /* Instrument disk */ + UWORD reserved; + UWORD length; /* Sample length */ + UWORD loopbeg; /* Loop start point */ + UWORD loopend; /* Loop end point */ + UBYTE volume; /* Volume */ + UBYTE reserved2; + UWORD c2spd; /* Good old c2spd */ + ULONG reserved3; + UWORD isa; +} STMSAMPLE; + +/* header */ +typedef struct STMHEADER { + CHAR songname[20]; + CHAR trackername[8]; /* !Scream! for ST 2.xx */ + UBYTE unused; /* 0x1A */ + UBYTE filetype; /* 1=song, 2=module */ + UBYTE ver_major; + UBYTE ver_minor; + UBYTE inittempo; /* initspeed= stm inittempo>>4 */ + UBYTE numpat; /* number of patterns */ + UBYTE globalvol; + UBYTE reserved[13]; + STMSAMPLE sample[31]; /* STM sample data */ + UBYTE patorder[128]; /* Docs say 64 - actually 128 */ +} STMHEADER; + +typedef struct STMNOTE { + UBYTE note,insvol,volcmd,cmdinf; +} STMNOTE; + +/*========== Loader variables */ + +static STMNOTE *stmbuf = NULL; +static STMHEADER *mh = NULL; + +/* tracker identifiers */ +static CHAR* STM_Version[STM_NTRACKERS] = { + "Screamtracker 2", + "Converted by MOD2STM (STM format)", + "Wuzamod (STM format)" +}; + +/*========== Loader code */ + +BOOL STM_Test(void) +{ + UBYTE str[44]; + int t; + + _mm_fseek(modreader,20,SEEK_SET); + _mm_read_UBYTES(str,44,modreader); + if(str[9]!=2) return 0; /* STM Module = filetype 2 */ + + /* Prevent false positives for S3M files */ + if(!memcmp(str+40,"SCRM",4)) + return 0; + + for (t=0;tnote; + ins = n->insvol>>3; + vol = (n->insvol&7)+((n->volcmd&0x70)>>1); + cmd = n->volcmd&15; + inf = n->cmdinf; + + if((ins)&&(ins<32)) UniInstrument(ins-1); + + /* special values of [SBYTE0] are handled here + we have no idea if these strange values will ever be encountered. + but it appears as those stms sound correct. */ + if((note==254)||(note==252)) { + UniPTEffect(0xc,0); /* note cut */ + n->volcmd|=0x80; + } else + /* if note < 251, then all three bytes are stored in the file */ + if(note<251) UniNote((((note>>4)+2)*OCTAVE)+(note&0xf)); + + if((!(n->volcmd&0x80))&&(vol<65)) UniPTEffect(0xc,vol); + if(cmd!=255) + switch(cmd) { + case 1: /* Axx set speed to xx */ + UniPTEffect(0xf,inf>>4); + break; + case 2: /* Bxx position jump */ + UniPTEffect(0xb,inf); + break; + case 3: /* Cxx patternbreak to row xx */ + UniPTEffect(0xd,(((inf&0xf0)>>4)*10)+(inf&0xf)); + break; + case 4: /* Dxy volumeslide */ + UniEffect(UNI_S3MEFFECTD,inf); + break; + case 5: /* Exy toneslide down */ + UniEffect(UNI_S3MEFFECTE,inf); + break; + case 6: /* Fxy toneslide up */ + UniEffect(UNI_S3MEFFECTF,inf); + break; + case 7: /* Gxx Tone portamento,speed xx */ + UniPTEffect(0x3,inf); + break; + case 8: /* Hxy vibrato */ + UniPTEffect(0x4,inf); + break; + case 9: /* Ixy tremor, ontime x, offtime y */ + UniEffect(UNI_S3MEFFECTI,inf); + break; + case 0: /* protracker arpeggio */ + if(!inf) break; + /* fall through */ + case 0xa: /* Jxy arpeggio */ + UniPTEffect(0x0,inf); + break; + case 0xb: /* Kxy Dual command H00 & Dxy */ + UniPTEffect(0x4,0); + UniEffect(UNI_S3MEFFECTD,inf); + break; + case 0xc: /* Lxy Dual command G00 & Dxy */ + UniPTEffect(0x3,0); + UniEffect(UNI_S3MEFFECTD,inf); + break; + /* Support all these above, since ST2 can LOAD these values but can + actually only play up to J - and J is only half-way implemented + in ST2 */ + case 0x18: /* Xxx amiga panning command 8xx */ + UniPTEffect(0x8,inf); + of.flags |= UF_PANNING; + break; + } +} + +static UBYTE *STM_ConvertTrack(STMNOTE *n) +{ + int t; + + UniReset(); + for(t=0;t<64;t++) { + STM_ConvertNote(n); + UniNewline(); + n+=of.numchn; + } + return UniDup(); +} + +static BOOL STM_LoadPatterns(void) +{ + int t,s,tracks=0; + + if(!AllocPatterns()) return 0; + if(!AllocTracks()) return 0; + + /* Allocate temporary buffer for loading and converting the patterns */ + for(t=0;tsongname,20,modreader); + _mm_read_string(mh->trackername,8,modreader); + mh->unused =_mm_read_UBYTE(modreader); + mh->filetype =_mm_read_UBYTE(modreader); + mh->ver_major =_mm_read_UBYTE(modreader); + mh->ver_minor =_mm_read_UBYTE(modreader); + mh->inittempo =_mm_read_UBYTE(modreader); + if(!mh->inittempo) { + _mm_errno=MMERR_NOT_A_MODULE; + return 0; + } + mh->numpat =_mm_read_UBYTE(modreader); + mh->globalvol =_mm_read_UBYTE(modreader); + _mm_read_UBYTES(mh->reserved,13,modreader); + + for(t=0;t<31;t++) { + STMSAMPLE *s=&mh->sample[t]; /* STM sample data */ + + _mm_read_string(s->filename,12,modreader); + s->unused =_mm_read_UBYTE(modreader); + s->instdisk =_mm_read_UBYTE(modreader); + s->reserved =_mm_read_I_UWORD(modreader); + s->length =_mm_read_I_UWORD(modreader); + s->loopbeg =_mm_read_I_UWORD(modreader); + s->loopend =_mm_read_I_UWORD(modreader); + s->volume =_mm_read_UBYTE(modreader); + s->reserved2=_mm_read_UBYTE(modreader); + s->c2spd =_mm_read_I_UWORD(modreader); + s->reserved3=_mm_read_I_ULONG(modreader); + s->isa =_mm_read_I_UWORD(modreader); + } + _mm_read_UBYTES(mh->patorder,128,modreader); + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* set module variables */ + for(t=0;ttrackername,STM_Signatures[t],8)) break; + of.modtype = strdup(STM_Version[t]); + of.songname = DupStr(mh->songname,20,1); /* make a cstr of songname */ + of.numpat = mh->numpat; + of.inittempo = 125; /* mh->inittempo+0x1c; */ + of.initspeed = mh->inittempo>>4; + of.numchn = 4; /* get number of channels */ + of.reppos = 0; + of.flags |= UF_S3MSLIDES; + of.bpmlimit = 32; + + t=0; + if(!AllocPositions(0x80)) return 0; + /* 99 terminates the patorder list */ + while((mh->patorder[t]<=99)&&(mh->patorder[t]numpat)) { + of.positions[t]=mh->patorder[t]; + t++; + } + if(mh->patorder[t]<=99) t++; + of.numpos=t; + of.numtrk=of.numpat*of.numchn; + of.numins=of.numsmp=31; + + if(!AllocSamples()) return 0; + if(!STM_LoadPatterns()) return 0; + MikMod_ISA=_mm_ftell(modreader); + MikMod_ISA=(MikMod_ISA+15)&0xfffffff0; /* normalize */ + + for(q=of.samples,t=0;tsamplename = DupStr(mh->sample[t].filename,12,1); + q->speed = (mh->sample[t].c2spd * 8363) / 8448; + q->volume = mh->sample[t].volume; + q->length = mh->sample[t].length; + if (/*(!mh->sample[t].volume)||*/(q->length==1)) q->length=0; + q->loopstart = mh->sample[t].loopbeg; + q->loopend = mh->sample[t].loopend; + q->seekpos = MikMod_ISA; + + MikMod_ISA+=q->length; + MikMod_ISA=(MikMod_ISA+15)&0xfffffff0; /* normalize */ + + /* contrary to the STM specs, sample data is signed */ + q->flags = SF_SIGNED; + + if(q->loopend && q->loopend != 0xffff) + q->flags|=SF_LOOP; + } + return 1; +} + +CHAR *STM_LoadTitle(void) +{ + CHAR s[20]; + + _mm_fseek(modreader,0,SEEK_SET); + if(!_mm_read_UBYTES(s,20,modreader)) return NULL; + + return(DupStr(s,20,1)); +} + +/*========== Loader information */ + +MIKMODAPI MLOADER load_stm={ + NULL, + "STM", + "STM (Scream Tracker)", + STM_Init, + STM_Test, + STM_Load, + STM_Cleanup, + STM_LoadTitle +}; + + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/load_xm.c b/src/libs/mikmod/load_xm.c new file mode 100644 index 0000000..97e0b73 --- /dev/null +++ b/src/libs/mikmod/load_xm.c @@ -0,0 +1,817 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file + AUTHORS for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Fasttracker (XM) module loader + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#ifdef HAVE_MEMORY_H +#include +#endif +#include + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + +/*========== Module structure */ + +typedef struct XMHEADER { + CHAR id[17]; /* ID text: 'Extended module: ' */ + CHAR songname[21]; /* Module name */ + CHAR trackername[20]; /* Tracker name */ + UWORD version; /* Version number */ + ULONG headersize; /* Header size */ + UWORD songlength; /* Song length (in patten order table) */ + UWORD restart; /* Restart position */ + UWORD numchn; /* Number of channels (2,4,6,8,10,...,32) */ + UWORD numpat; /* Number of patterns (max 256) */ + UWORD numins; /* Number of instruments (max 128) */ + UWORD flags; + UWORD tempo; /* Default tempo */ + UWORD bpm; /* Default BPM */ + UBYTE orders[256]; /* Pattern order table */ +} XMHEADER; + +typedef struct XMINSTHEADER { + ULONG size; /* Instrument size */ + CHAR name[22]; /* Instrument name */ + UBYTE type; /* Instrument type (always 0) */ + UWORD numsmp; /* Number of samples in instrument */ + ULONG ssize; +} XMINSTHEADER; + +#define XMENVCNT (12*2) +#define XMNOTECNT (8*OCTAVE) +typedef struct XMPATCHHEADER { + UBYTE what[XMNOTECNT]; /* Sample number for all notes */ + UWORD volenv[XMENVCNT]; /* Points for volume envelope */ + UWORD panenv[XMENVCNT]; /* Points for panning envelope */ + UBYTE volpts; /* Number of volume points */ + UBYTE panpts; /* Number of panning points */ + UBYTE volsus; /* Volume sustain point */ + UBYTE volbeg; /* Volume loop start point */ + UBYTE volend; /* Volume loop end point */ + UBYTE pansus; /* Panning sustain point */ + UBYTE panbeg; /* Panning loop start point */ + UBYTE panend; /* Panning loop end point */ + UBYTE volflg; /* Volume type: bit 0: On; 1: Sustain; 2: Loop */ + UBYTE panflg; /* Panning type: bit 0: On; 1: Sustain; 2: Loop */ + UBYTE vibflg; /* Vibrato type */ + UBYTE vibsweep; /* Vibrato sweep */ + UBYTE vibdepth; /* Vibrato depth */ + UBYTE vibrate; /* Vibrato rate */ + UWORD volfade; /* Volume fadeout */ +} XMPATCHHEADER; + +typedef struct XMWAVHEADER { + ULONG length; /* Sample length */ + ULONG loopstart; /* Sample loop start */ + ULONG looplength; /* Sample loop length */ + UBYTE volume; /* Volume */ + SBYTE finetune; /* Finetune (signed byte -128..+127) */ + UBYTE type; /* Loop type */ + UBYTE panning; /* Panning (0-255) */ + SBYTE relnote; /* Relative note number (signed byte) */ + UBYTE reserved; + CHAR samplename[22]; /* Sample name */ + UBYTE vibtype; /* Vibrato type */ + UBYTE vibsweep; /* Vibrato sweep */ + UBYTE vibdepth; /* Vibrato depth */ + UBYTE vibrate; /* Vibrato rate */ +} XMWAVHEADER; + +typedef struct XMPATHEADER { + ULONG size; /* Pattern header length */ + UBYTE packing; /* Packing type (always 0) */ + UWORD numrows; /* Number of rows in pattern (1..256) */ + SWORD packsize; /* Packed patterndata size */ +} XMPATHEADER; + +typedef struct XMNOTE { + UBYTE note,ins,vol,eff,dat; +} XMNOTE; + +/*========== Loader variables */ + +static XMNOTE *xmpat=NULL; +static XMHEADER *mh=NULL; + +/* increment unit for sample array reallocation */ +#define XM_SMPINCR 64 +static ULONG *nextwav=NULL; +static XMWAVHEADER *wh=NULL,*s=NULL; + +/*========== Loader code */ + +BOOL XM_Test(void) +{ + UBYTE id[38]; + + if(!_mm_read_UBYTES(id,38,modreader)) return 0; + if(memcmp(id,"Extended Module: ",17)) return 0; + if(id[37]==0x1a) return 1; + return 0; +} + +BOOL XM_Init(void) +{ + if(!(mh=(XMHEADER *)MikMod_malloc(sizeof(XMHEADER)))) return 0; + return 1; +} + +void XM_Cleanup(void) +{ + MikMod_free(mh); +} + +static int XM_ReadNote(XMNOTE* n) +{ + UBYTE cmp,result=1; + + memset(n,0,sizeof(XMNOTE)); + cmp=_mm_read_UBYTE(modreader); + + if(cmp&0x80) { + if(cmp&1) { result++;n->note = _mm_read_UBYTE(modreader); } + if(cmp&2) { result++;n->ins = _mm_read_UBYTE(modreader); } + if(cmp&4) { result++;n->vol = _mm_read_UBYTE(modreader); } + if(cmp&8) { result++;n->eff = _mm_read_UBYTE(modreader); } + if(cmp&16) { result++;n->dat = _mm_read_UBYTE(modreader); } + } else { + n->note = cmp; + n->ins = _mm_read_UBYTE(modreader); + n->vol = _mm_read_UBYTE(modreader); + n->eff = _mm_read_UBYTE(modreader); + n->dat = _mm_read_UBYTE(modreader); + result += 4; + } + return result; +} + +static UBYTE* XM_Convert(XMNOTE* xmtrack,UWORD rows) +{ + int t; + UBYTE note,ins,vol,eff,dat; + + UniReset(); + for(t=0;tnote; + ins = xmtrack->ins; + vol = xmtrack->vol; + eff = xmtrack->eff; + dat = xmtrack->dat; + + if(note) { + if(note>XMNOTECNT) + UniEffect(UNI_KEYFADE,0); + else + UniNote(note-1); + } + if(ins) UniInstrument(ins-1); + + switch(vol>>4) { + case 0x6: /* volslide down */ + if(vol&0xf) UniEffect(UNI_XMEFFECTA,vol&0xf); + break; + case 0x7: /* volslide up */ + if(vol&0xf) UniEffect(UNI_XMEFFECTA,vol<<4); + break; + + /* volume-row fine volume slide is compatible with protracker + EBx and EAx effects i.e. a zero nibble means DO NOT SLIDE, as + opposed to 'take the last sliding value'. */ + case 0x8: /* finevol down */ + UniPTEffect(0xe,0xb0|(vol&0xf)); + break; + case 0x9: /* finevol up */ + UniPTEffect(0xe,0xa0|(vol&0xf)); + break; + case 0xa: /* set vibrato speed */ + UniEffect(UNI_XMEFFECT4,vol<<4); + break; + case 0xb: /* vibrato */ + UniEffect(UNI_XMEFFECT4,vol&0xf); + break; + case 0xc: /* set panning */ + UniPTEffect(0x8,vol<<4); + break; + case 0xd: /* panning slide left (only slide when data not zero) */ + if(vol&0xf) UniEffect(UNI_XMEFFECTP,vol&0xf); + break; + case 0xe: /* panning slide right (only slide when data not zero) */ + if(vol&0xf) UniEffect(UNI_XMEFFECTP,vol<<4); + break; + case 0xf: /* tone porta */ + UniPTEffect(0x3,vol<<4); + break; + default: + if((vol>=0x10)&&(vol<=0x50)) + UniPTEffect(0xc,vol-0x10); + } + + switch(eff) { + case 0x4: + UniEffect(UNI_XMEFFECT4,dat); + break; + case 0x6: + UniEffect(UNI_XMEFFECT6,dat); + break; + case 0xa: + UniEffect(UNI_XMEFFECTA,dat); + break; + case 0xe: /* Extended effects */ + switch(dat>>4) { + case 0x1: /* XM fine porta up */ + UniEffect(UNI_XMEFFECTE1,dat&0xf); + break; + case 0x2: /* XM fine porta down */ + UniEffect(UNI_XMEFFECTE2,dat&0xf); + break; + case 0xa: /* XM fine volume up */ + UniEffect(UNI_XMEFFECTEA,dat&0xf); + break; + case 0xb: /* XM fine volume down */ + UniEffect(UNI_XMEFFECTEB,dat&0xf); + break; + default: + UniPTEffect(eff,dat); + } + break; + case 'G'-55: /* G - set global volume */ + UniEffect(UNI_XMEFFECTG,dat>64?128:dat<<1); + break; + case 'H'-55: /* H - global volume slide */ + UniEffect(UNI_XMEFFECTH,dat); + break; + case 'K'-55: /* K - keyOff and KeyFade */ + UniEffect(UNI_KEYFADE,dat); + break; + case 'L'-55: /* L - set envelope position */ + UniEffect(UNI_XMEFFECTL,dat); + break; + case 'P'-55: /* P - panning slide */ + UniEffect(UNI_XMEFFECTP,dat); + break; + case 'R'-55: /* R - multi retrig note */ + UniEffect(UNI_S3MEFFECTQ,dat); + break; + case 'T'-55: /* T - Tremor */ + UniEffect(UNI_S3MEFFECTI,dat); + break; + case 'X'-55: + switch(dat>>4) { + case 1: /* X1 - Extra Fine Porta up */ + UniEffect(UNI_XMEFFECTX1,dat&0xf); + break; + case 2: /* X2 - Extra Fine Porta down */ + UniEffect(UNI_XMEFFECTX2,dat&0xf); + break; + } + break; + default: + if(eff<=0xf) { + /* the pattern jump destination is written in decimal, + but it seems some poor tracker software writes them + in hexadecimal... (sigh) */ + if (eff==0xd) + /* don't change anything if we're sure it's in hexa */ + if ((((dat&0xf0)>>4)<=9)&&((dat&0xf)<=9)) + /* otherwise, convert from dec to hex */ + dat=(((dat&0xf0)>>4)*10)+(dat&0xf); + UniPTEffect(eff,dat); + } + break; + } + UniNewline(); + xmtrack++; + } + return UniDup(); +} + +static BOOL LoadPatterns(BOOL dummypat) +{ + int t,u,v,numtrk; + + if(!AllocTracks()) return 0; + if(!AllocPatterns()) return 0; + + numtrk=0; + for(t=0;tnumpat;t++) { + XMPATHEADER ph; + + ph.size =_mm_read_I_ULONG(modreader); + if (ph.size<(mh->version==0x0102?8:9)) { + _mm_errno=MMERR_LOADING_PATTERN; + return 0; + } + ph.packing =_mm_read_UBYTE(modreader); + if(ph.packing) { + _mm_errno=MMERR_LOADING_PATTERN; + return 0; + } + if(mh->version==0x0102) + ph.numrows =_mm_read_UBYTE(modreader)+1; + else + ph.numrows =_mm_read_I_UWORD(modreader); + ph.packsize =_mm_read_I_UWORD(modreader); + + ph.size-=(mh->version==0x0102?8:9); + if(ph.size) + _mm_fseek(modreader,ph.size,SEEK_CUR); + + of.pattrows[t]=ph.numrows; + + if(ph.numrows) { + if(!(xmpat=(XMNOTE*)MikMod_calloc(ph.numrows*of.numchn,sizeof(XMNOTE)))) + return 0; + + /* when packsize is 0, don't try to load a pattern.. it's empty. */ + if(ph.packsize) + for(u=0;upos; + + for (u = 1; u < pts; u++, prev++, cur++) { + if (cur->pos < prev->pos) { + if (cur->pos < 0x100) { + if (cur->pos > old) /* same hex century */ + tmp = cur->pos + (prev->pos - old); + else + tmp = cur->pos | ((prev->pos + 0x100) & 0xff00); + old = cur->pos; + cur->pos = tmp; +#ifdef MIKMOD_DEBUG + fprintf(stderr, "\rbroken envelope position(%d/%d), %d %d -> %d\n", + u, pts, prev->pos, old, cur->pos); +#endif + } else { +#ifdef MIKMOD_DEBUG + /* different brokenness style... fix unknown */ + fprintf(stderr, "\rbroken envelope position(%d/%d), %d %d\n", + u, pts, old, cur->pos); +#endif + old = cur->pos; + } + } else + old = cur->pos; + } +} + +static BOOL LoadInstruments(void) +{ + int t,u; + INSTRUMENT *d; + ULONG next=0; + UWORD wavcnt=0; + + if(!AllocInstruments()) return 0; + d=of.instruments; + for(t=0;tsamplenumber,0xff,INSTNOTES*sizeof(UWORD)); + + /* read instrument header */ + headend = _mm_ftell(modreader); + ih.size = _mm_read_I_ULONG(modreader); + headend += ih.size; + _mm_read_string(ih.name, 22, modreader); + ih.type = _mm_read_UBYTE(modreader); + ih.numsmp = _mm_read_I_UWORD(modreader); + + d->insname = DupStr(ih.name,22,1); + + if((SWORD)ih.size>29) { + ih.ssize = _mm_read_I_ULONG(modreader); + if(((SWORD)ih.numsmp>0)&&(ih.numsmp<=XMNOTECNT)) { + XMPATCHHEADER pth; + int p; + + _mm_read_UBYTES (pth.what,XMNOTECNT,modreader); + _mm_read_I_UWORDS (pth.volenv, XMENVCNT, modreader); + _mm_read_I_UWORDS (pth.panenv, XMENVCNT, modreader); + pth.volpts = _mm_read_UBYTE(modreader); + pth.panpts = _mm_read_UBYTE(modreader); + pth.volsus = _mm_read_UBYTE(modreader); + pth.volbeg = _mm_read_UBYTE(modreader); + pth.volend = _mm_read_UBYTE(modreader); + pth.pansus = _mm_read_UBYTE(modreader); + pth.panbeg = _mm_read_UBYTE(modreader); + pth.panend = _mm_read_UBYTE(modreader); + pth.volflg = _mm_read_UBYTE(modreader); + pth.panflg = _mm_read_UBYTE(modreader); + pth.vibflg = _mm_read_UBYTE(modreader); + pth.vibsweep = _mm_read_UBYTE(modreader); + pth.vibdepth = _mm_read_UBYTE(modreader); + pth.vibrate = _mm_read_UBYTE(modreader); + pth.volfade = _mm_read_I_UWORD(modreader); + + /* read the remainder of the header + (2 bytes for 1.03, 22 for 1.04) */ + for(u=headend-_mm_ftell(modreader);u;u--) _mm_read_UBYTE(modreader); + + /* we can't trust the envelope point count here, as some + modules have incorrect values (K_OSPACE.XM reports 32 volume + points, for example). */ + if(pth.volpts>XMENVCNT/2) pth.volpts=XMENVCNT/2; + if(pth.panpts>XMENVCNT/2) pth.panpts=XMENVCNT/2; + + if((_mm_eof(modreader))||(pth.volpts>XMENVCNT/2)||(pth.panpts>XMENVCNT/2)) { + if(nextwav) { MikMod_free(nextwav);nextwav=NULL; } + if(wh) { MikMod_free(wh);wh=NULL; } + _mm_errno = MMERR_LOADING_SAMPLEINFO; + return 0; + } + + for(u=0;usamplenumber[u]=pth.what[u]+of.numsmp; + d->volfade = pth.volfade; + +#if defined __STDC__ || defined _MSC_VER || defined MPW_C +#define XM_ProcessEnvelope(name) \ + for (u = 0; u < (XMENVCNT >> 1); u++) { \ + d-> name##env[u].pos = pth. name##env[u << 1]; \ + d-> name##env[u].val = pth. name##env[(u << 1)+ 1]; \ + } \ + if (pth. name##flg&1) d-> name##flg|=EF_ON; \ + if (pth. name##flg&2) d-> name##flg|=EF_SUSTAIN; \ + if (pth. name##flg&4) d-> name##flg|=EF_LOOP; \ + d-> name##susbeg=d-> name##susend=pth. name##sus; \ + d-> name##beg=pth. name##beg; \ + d-> name##end=pth. name##end; \ + d-> name##pts=pth. name##pts; \ + \ + /* scale envelope */ \ + for (p=0;p name##env[p].val<<=2; \ + \ + if ((d-> name##flg&EF_ON)&&(d-> name##pts<2)) \ + d-> name##flg&=~EF_ON +#else +#define XM_ProcessEnvelope(name) \ + for (u = 0; u < (XMENVCNT >> 1); u++) { \ + d-> name/**/env[u].pos = pth. name/**/env[u << 1]; \ + d-> name/**/env[u].val = pth. name/**/env[(u << 1)+ 1]; \ + } \ + if (pth. name/**/flg&1) d-> name/**/flg|=EF_ON; \ + if (pth. name/**/flg&2) d-> name/**/flg|=EF_SUSTAIN; \ + if (pth. name/**/flg&4) d-> name/**/flg|=EF_LOOP; \ + d-> name/**/susbeg=d-> name/**/susend= \ + pth. name/**/sus; \ + d-> name/**/beg=pth. name/**/beg; \ + d-> name/**/end=pth. name/**/end; \ + d-> name/**/pts=pth. name/**/pts; \ + \ + /* scale envelope */ \ + for (p=0;p name/**/env[p].val<<=2; \ + \ + if ((d-> name/**/flg&EF_ON)&&(d-> name/**/pts<2)) \ + d-> name/**/flg&=~EF_ON +#endif + + XM_ProcessEnvelope(vol); + XM_ProcessEnvelope(pan); +#undef XM_ProcessEnvelope + + if (d->volflg & EF_ON) + FixEnvelope(d->volenv, d->volpts); + if (d->panflg & EF_ON) + FixEnvelope(d->panenv, d->panpts); + + /* Samples are stored outside the instrument struct now, so we + have to load them all into a temp area, count the of.numsmp + along the way and then do an AllocSamples() and move + everything over */ + if(mh->version>0x0103) next = 0; + for(u=0;ulength =_mm_read_I_ULONG (modreader); + s->loopstart =_mm_read_I_ULONG (modreader); + s->looplength =_mm_read_I_ULONG (modreader); + s->volume =_mm_read_UBYTE (modreader); + s->finetune =_mm_read_SBYTE (modreader); + s->type =_mm_read_UBYTE (modreader); + s->panning =_mm_read_UBYTE (modreader); + s->relnote =_mm_read_SBYTE (modreader); + s->vibtype = pth.vibflg; + s->vibsweep = pth.vibsweep; + s->vibdepth = pth.vibdepth*4; + s->vibrate = pth.vibrate; + s->reserved =_mm_read_UBYTE (modreader); + _mm_read_string(s->samplename, 22, modreader); + + nextwav[of.numsmp+u]=next; + next+=s->length; + + if(_mm_eof(modreader)) { + MikMod_free(nextwav);MikMod_free(wh); + nextwav=NULL;wh=NULL; + _mm_errno = MMERR_LOADING_SAMPLEINFO; + return 0; + } + } + + if(mh->version>0x0103) { + for(u=0;uid,17,modreader); + _mm_read_string(mh->songname,21,modreader); + _mm_read_string(mh->trackername,20,modreader); + mh->version =_mm_read_I_UWORD(modreader); + if((mh->version<0x102)||(mh->version>0x104)) { + _mm_errno=MMERR_NOT_A_MODULE; + return 0; + } + mh->headersize =_mm_read_I_ULONG(modreader); + mh->songlength =_mm_read_I_UWORD(modreader); + mh->restart =_mm_read_I_UWORD(modreader); + mh->numchn =_mm_read_I_UWORD(modreader); + mh->numpat =_mm_read_I_UWORD(modreader); + mh->numins =_mm_read_I_UWORD(modreader); + mh->flags =_mm_read_I_UWORD(modreader); + mh->tempo =_mm_read_I_UWORD(modreader); + mh->bpm =_mm_read_I_UWORD(modreader); + if(!mh->bpm) { + _mm_errno=MMERR_NOT_A_MODULE; + return 0; + } + _mm_read_UBYTES(mh->orders,256,modreader); + + if(_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* set module variables */ + of.initspeed = mh->tempo; + of.inittempo = mh->bpm; + strncpy(tracker,mh->trackername,20);tracker[20]=0; + for(t=20;(tracker[t]<=' ')&&(t>=0);t--) tracker[t]=0; + + /* some modules have the tracker name empty */ + if (!tracker[0]) + strcpy(tracker,"Unknown tracker"); + +#ifdef HAVE_SNPRINTF + snprintf(modtype,60,"%s (XM format %d.%02d)", + tracker,mh->version>>8,mh->version&0xff); +#else + sprintf(modtype,"%s (XM format %d.%02d)", + tracker,mh->version>>8,mh->version&0xff); +#endif + of.modtype = strdup(modtype); + of.numchn = mh->numchn; + of.numpat = mh->numpat; + of.numtrk = (UWORD)of.numpat*of.numchn; /* get number of channels */ + of.songname = DupStr(mh->songname,20,1); + of.numpos = mh->songlength; /* copy the songlength */ + of.reppos = mh->restartsonglength?mh->restart:0; + of.numins = mh->numins; + of.flags |= UF_XMPERIODS | UF_INST | UF_NOWRAP | UF_FT2QUIRKS | + UF_PANNING; + if(mh->flags&1) of.flags |= UF_LINEAR; + of.bpmlimit = 32; + + memset(of.chanvol,64,of.numchn); /* store channel volumes */ + + if(!AllocPositions(of.numpos+1)) return 0; + for(t=0;torders[t]; + + /* We have to check for any pattern numbers in the order list greater than + the number of patterns total. If one or more is found, we set it equal to + the pattern total and make a dummy pattern to workaround the problem */ + for(t=0;t=of.numpat) { + of.positions[t]=of.numpat; + dummypat=1; + } + } + if(dummypat) { + of.numpat++;of.numtrk+=of.numchn; + } + + if(mh->version<0x0104) { + if(!LoadInstruments()) return 0; + if(!LoadPatterns(dummypat)) return 0; + for(t=0;tsamplename = DupStr(s->samplename,22,1); + q->length = s->length; + q->loopstart = s->loopstart; + q->loopend = s->loopstart+s->looplength; + q->volume = s->volume; + q->speed = s->finetune+128; + q->panning = s->panning; + q->seekpos = nextwav[u]; + q->vibtype = s->vibtype; + q->vibsweep = s->vibsweep; + q->vibdepth = s->vibdepth; + q->vibrate = s->vibrate; + + if(s->type & 0x10) { + q->length >>= 1; + q->loopstart >>= 1; + q->loopend >>= 1; + } + + q->flags|=SF_OWNPAN|SF_DELTA|SF_SIGNED; + if(s->type&0x3) q->flags|=SF_LOOP; + if(s->type&0x2) q->flags|=SF_BIDI; + if(s->type&0x10) q->flags|=SF_16BITS; + } + + d=of.instruments; + s=wh; + for(u=0;usamplenumber[t]>=of.numsmp) + d->samplenote[t]=255; + else { + int note=t+s[d->samplenumber[t]].relnote; + d->samplenote[t]=(note<0)?0:note; + } + } + + MikMod_free(wh);MikMod_free(nextwav); + wh=NULL;nextwav=NULL; + return 1; +} + +CHAR *XM_LoadTitle(void) +{ + CHAR s[21]; + + _mm_fseek(modreader,17,SEEK_SET); + if(!_mm_read_UBYTES(s,21,modreader)) return NULL; + + return(DupStr(s,21,1)); +} + +/*========== Loader information */ + +MIKMODAPI MLOADER load_xm={ + NULL, + "XM", + "XM (FastTracker 2)", + XM_Init, + XM_Test, + XM_Load, + XM_Cleanup, + XM_LoadTitle +}; + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mdreg.c b/src/libs/mikmod/mdreg.c new file mode 100644 index 0000000..2ad65c2 --- /dev/null +++ b/src/libs/mikmod/mdreg.c @@ -0,0 +1,47 @@ +/* MikMod sound library + (c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Routine for registering all drivers in libmikmod for the current platform. + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mikmod_internals.h" + +void _mm_registeralldrivers(void) +{ + _mm_registerdriver(&drv_nos); +} + +void MikMod_RegisterAllDrivers(void) +{ + MUTEX_LOCK(lists); + _mm_registeralldrivers(); + MUTEX_UNLOCK(lists); +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mdriver.c b/src/libs/mikmod/mdriver.c new file mode 100644 index 0000000..68a2e79 --- /dev/null +++ b/src/libs/mikmod/mdriver.c @@ -0,0 +1,935 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + These routines are used to access the available soundcard drivers. + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#if defined unix || (defined __APPLE__ && defined __MACH__) +#include +#include +#include +#include +#endif + +#include +#ifdef HAVE_STRINGS_H +#include +#endif + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + +static MDRIVER *firstdriver=NULL; +MIKMODAPI MDRIVER *md_driver=NULL; +extern MODULE *pf; /* modfile being played */ + +/* Initial global settings */ +MIKMODAPI UWORD md_device = 0; /* autodetect */ +MIKMODAPI UWORD md_mixfreq = 44100; +MIKMODAPI UWORD md_mode = DMODE_STEREO | DMODE_16BITS | + DMODE_SURROUND |DMODE_SOFT_MUSIC | + DMODE_SOFT_SNDFX; +MIKMODAPI UBYTE md_pansep = 128; /* 128 == 100% (full left/right) */ +MIKMODAPI UBYTE md_reverb = 0; /* no reverb */ +MIKMODAPI UBYTE md_volume = 128; /* global sound volume (0-128) */ +MIKMODAPI UBYTE md_musicvolume = 128; /* volume of song */ +MIKMODAPI UBYTE md_sndfxvolume = 128; /* volume of sound effects */ + UWORD md_bpm = 125; /* tempo */ + +/* Do not modify the numchn variables yourself! use MD_SetVoices() */ + UBYTE md_numchn=0,md_sngchn=0,md_sfxchn=0; + UBYTE md_hardchn=0,md_softchn=0; + + void (*md_player)(void) = Player_HandleTick; +static BOOL isplaying=0, initialized = 0; +static UBYTE *sfxinfo; +static int sfxpool; + +static SAMPLE **md_sample = NULL; + +/* Previous driver in use */ +static SWORD olddevice = -1; + +/* Limits the number of hardware voices to the specified amount. + This function should only be used by the low-level drivers. */ +static void LimitHardVoices(int limit) +{ + int t=0; + + if (!(md_mode & DMODE_SOFT_SNDFX) && (md_sfxchn>limit)) md_sfxchn=limit; + if (!(md_mode & DMODE_SOFT_MUSIC) && (md_sngchn>limit)) md_sngchn=limit; + + if (!(md_mode & DMODE_SOFT_SNDFX)) + md_hardchn=md_sfxchn; + else + md_hardchn=0; + + if (!(md_mode & DMODE_SOFT_MUSIC)) md_hardchn += md_sngchn; + + while (md_hardchn>limit) { + if (++t & 1) { + if (!(md_mode & DMODE_SOFT_SNDFX) && (md_sfxchn>4)) md_sfxchn--; + } else { + if (!(md_mode & DMODE_SOFT_MUSIC) && (md_sngchn>8)) md_sngchn--; + } + + if (!(md_mode & DMODE_SOFT_SNDFX)) + md_hardchn=md_sfxchn; + else + md_hardchn=0; + + if (!(md_mode & DMODE_SOFT_MUSIC)) + md_hardchn+=md_sngchn; + } + md_numchn=md_hardchn+md_softchn; +} + +/* Limits the number of hardware voices to the specified amount. + This function should only be used by the low-level drivers. */ +static void LimitSoftVoices(int limit) +{ + int t=0; + + if ((md_mode & DMODE_SOFT_SNDFX) && (md_sfxchn>limit)) md_sfxchn=limit; + if ((md_mode & DMODE_SOFT_MUSIC) && (md_sngchn>limit)) md_sngchn=limit; + + if (md_mode & DMODE_SOFT_SNDFX) + md_softchn=md_sfxchn; + else + md_softchn=0; + + if (md_mode & DMODE_SOFT_MUSIC) md_softchn+=md_sngchn; + + while (md_softchn>limit) { + if (++t & 1) { + if ((md_mode & DMODE_SOFT_SNDFX) && (md_sfxchn>4)) md_sfxchn--; + } else { + if ((md_mode & DMODE_SOFT_MUSIC) && (md_sngchn>8)) md_sngchn--; + } + + if (!(md_mode & DMODE_SOFT_SNDFX)) + md_softchn=md_sfxchn; + else + md_softchn=0; + + if (!(md_mode & DMODE_SOFT_MUSIC)) + md_softchn+=md_sngchn; + } + md_numchn=md_hardchn+md_softchn; +} + +/* Note: 'type' indicates whether the returned value should be for music or for + sound effects. */ +ULONG MD_SampleSpace(int type) +{ + if(type==MD_MUSIC) + type=(md_mode & DMODE_SOFT_MUSIC)?MD_SOFTWARE:MD_HARDWARE; + else if(type==MD_SNDFX) + type=(md_mode & DMODE_SOFT_SNDFX)?MD_SOFTWARE:MD_HARDWARE; + + return md_driver->FreeSampleSpace(type); +} + +ULONG MD_SampleLength(int type,SAMPLE* s) +{ + if(type==MD_MUSIC) + type=(md_mode & DMODE_SOFT_MUSIC)?MD_SOFTWARE:MD_HARDWARE; + else + if(type==MD_SNDFX) + type=(md_mode & DMODE_SOFT_SNDFX)?MD_SOFTWARE:MD_HARDWARE; + + return md_driver->RealSampleLength(type,s); +} + +MIKMODAPI CHAR* MikMod_InfoDriver(void) +{ + int t; + size_t len=0; + MDRIVER *l; + CHAR *list=NULL; + + MUTEX_LOCK(lists); + /* compute size of buffer */ + for(l=firstdriver;l;l=l->next) + len+=4+(l->next?1:0)+strlen(l->Version); + + if(len) + if((list=MikMod_malloc(len*sizeof(CHAR)))) { + list[0]=0; + /* list all registered device drivers : */ + for(t=1,l=firstdriver;l;l=l->next,t++) + sprintf(list,(l->next)?"%s%2d %s\n":"%s%2d %s", + list,t,l->Version); + } + MUTEX_UNLOCK(lists); + return list; +} + +void _mm_registerdriver(struct MDRIVER* drv) +{ + MDRIVER *cruise = firstdriver; + + /* don't register a MISSING() driver */ + if ((drv->Name) && (drv->Version)) { + if (cruise) { + while (cruise->next) cruise = cruise->next; + cruise->next = drv; + } else + firstdriver = drv; + } +} + +MIKMODAPI void MikMod_RegisterDriver(struct MDRIVER* drv) +{ + /* if we try to register an invalid driver, or an already registered driver, + ignore this attempt */ + if ((!drv)||(drv->next)||(!drv->Name)) + return; + + MUTEX_LOCK(lists); + _mm_registerdriver(drv); + MUTEX_UNLOCK(lists); +} + +MIKMODAPI int MikMod_DriverFromAlias(CHAR *alias) +{ + int rank=1; + MDRIVER *cruise; + + MUTEX_LOCK(lists); + cruise=firstdriver; + while(cruise) { + if (cruise->Alias) { + if (!(strcasecmp(alias,cruise->Alias))) break; + rank++; + } + cruise=cruise->next; + } + if(!cruise) rank=0; + MUTEX_UNLOCK(lists); + + return rank; +} + +SWORD MD_SampleLoad(SAMPLOAD* s, int type) +{ + SWORD result; + + if(type==MD_MUSIC) + type=(md_mode & DMODE_SOFT_MUSIC)?MD_SOFTWARE:MD_HARDWARE; + else if(type==MD_SNDFX) + type=(md_mode & DMODE_SOFT_SNDFX)?MD_SOFTWARE:MD_HARDWARE; + + SL_Init(s); + result=md_driver->SampleLoad(s,type); + SL_Exit(s); + + return result; +} + +void MD_SampleUnload(SWORD handle) +{ + md_driver->SampleUnload(handle); +} + +MIKMODAPI MikMod_player_t MikMod_RegisterPlayer(MikMod_player_t player) +{ + MikMod_player_t result; + + MUTEX_LOCK(vars); + result=md_player; + md_player=player; + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI void MikMod_Update(void) +{ + MUTEX_LOCK(vars); + if(isplaying) { + if((!pf)||(!pf->forbid)) + md_driver->Update(); + else { + if (md_driver->Pause) + md_driver->Pause(); + } + } + MUTEX_UNLOCK(vars); +} + +void Voice_SetVolume_internal(SBYTE voice,UWORD vol) +{ + ULONG tmp; + + if((voice<0)||(voice>=md_numchn)) return; + + /* range checks */ + if(md_musicvolume>128) md_musicvolume=128; + if(md_sndfxvolume>128) md_sndfxvolume=128; + if(md_volume>128) md_volume=128; + + tmp=(ULONG)vol*(ULONG)md_volume* + ((voiceVoiceSetVolume(voice,tmp/16384UL); +} + +MIKMODAPI void Voice_SetVolume(SBYTE voice,UWORD vol) +{ + MUTEX_LOCK(vars); + Voice_SetVolume_internal(voice,vol); + MUTEX_UNLOCK(vars); +} + +MIKMODAPI UWORD Voice_GetVolume(SBYTE voice) +{ + UWORD result=0; + + MUTEX_LOCK(vars); + if((voice>=0)&&(voiceVoiceGetVolume(voice); + MUTEX_UNLOCK(vars); + + return result; +} + +void Voice_SetFrequency_internal(SBYTE voice,ULONG frq) +{ + if((voice<0)||(voice>=md_numchn)) return; + if((md_sample[voice])&&(md_sample[voice]->divfactor)) + frq/=md_sample[voice]->divfactor; + md_driver->VoiceSetFrequency(voice,frq); +} + +MIKMODAPI void Voice_SetFrequency(SBYTE voice,ULONG frq) +{ + MUTEX_LOCK(vars); + Voice_SetFrequency_internal(voice,frq); + MUTEX_UNLOCK(vars); +} + +MIKMODAPI ULONG Voice_GetFrequency(SBYTE voice) +{ + ULONG result=0; + + MUTEX_LOCK(vars); + if((voice>=0)&&(voiceVoiceGetFrequency(voice); + MUTEX_UNLOCK(vars); + + return result; +} + +void Voice_SetPanning_internal(SBYTE voice,ULONG pan) +{ + if((voice<0)||(voice>=md_numchn)) return; + if(pan!=PAN_SURROUND) { + if(md_pansep>128) md_pansep=128; + if(md_mode & DMODE_REVERSE) pan=255-pan; + pan = (((SWORD)(pan-128)*md_pansep)/128)+128; + } + md_driver->VoiceSetPanning(voice, pan); +} + +MIKMODAPI void Voice_SetPanning(SBYTE voice,ULONG pan) +{ +#ifdef MIKMOD_DEBUG + if((pan!=PAN_SURROUND)&&((pan<0)||(pan>255))) + fprintf(stderr,"\rVoice_SetPanning called with pan=%ld\n",(long)pan); +#endif + + MUTEX_LOCK(vars); + Voice_SetPanning_internal(voice,pan); + MUTEX_UNLOCK(vars); +} + +MIKMODAPI ULONG Voice_GetPanning(SBYTE voice) +{ + ULONG result=PAN_CENTER; + + MUTEX_LOCK(vars); + if((voice>=0)&&(voiceVoiceGetPanning(voice); + MUTEX_UNLOCK(vars); + + return result; +} + +void Voice_Play_internal(SBYTE voice,SAMPLE* s,ULONG start) +{ + ULONG repend; + + if((voice<0)||(voice>=md_numchn)) return; + + md_sample[voice]=s; + repend=s->loopend; + + if(s->flags&SF_LOOP) + /* repend can't be bigger than size */ + if(repend>s->length) repend=s->length; + + md_driver->VoicePlay(voice,s->handle,start,s->length,s->loopstart,repend,s->flags); +} + +MIKMODAPI void Voice_Play(SBYTE voice,SAMPLE* s,ULONG start) +{ + if(start>s->length) return; + + MUTEX_LOCK(vars); + Voice_Play_internal(voice,s,start); + MUTEX_UNLOCK(vars); +} + +void Voice_Stop_internal(SBYTE voice) +{ + if((voice<0)||(voice>=md_numchn)) return; + if(voice>=md_sngchn) + /* It is a sound effects channel, so flag the voice as non-critical! */ + sfxinfo[voice-md_sngchn]=0; + md_driver->VoiceStop(voice); +} + +MIKMODAPI void Voice_Stop(SBYTE voice) +{ + MUTEX_LOCK(vars); + Voice_Stop_internal(voice); + MUTEX_UNLOCK(vars); +} + +BOOL Voice_Stopped_internal(SBYTE voice) +{ + if((voice<0)||(voice>=md_numchn)) return 0; + return(md_driver->VoiceStopped(voice)); +} + +MIKMODAPI BOOL Voice_Stopped(SBYTE voice) +{ + BOOL result; + + MUTEX_LOCK(vars); + result=Voice_Stopped_internal(voice); + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI SLONG Voice_GetPosition(SBYTE voice) +{ + SLONG result=0; + + MUTEX_LOCK(vars); + if((voice>=0)&&(voiceVoiceGetPosition) + result=(md_driver->VoiceGetPosition(voice)); + else + result=-1; + } + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI ULONG Voice_RealVolume(SBYTE voice) +{ + ULONG result=0; + + MUTEX_LOCK(vars); + if((voice>=0)&&(voiceVoiceRealVolume) + result=(md_driver->VoiceRealVolume(voice)); + MUTEX_UNLOCK(vars); + + return result; +} + +static BOOL _mm_init(CHAR *cmdline) +{ + UWORD t; + + _mm_critical = 1; + + /* if md_device==0, try to find a device number */ + if(!md_device) { + cmdline=NULL; + + for(t=1,md_driver=firstdriver;md_driver;md_driver=md_driver->next,t++) + if(md_driver->IsPresent()) break; + + if(!md_driver) { + _mm_errno = MMERR_DETECTING_DEVICE; + if(_mm_errorhandler) _mm_errorhandler(); + md_driver = &drv_nos; + return 1; + } + + md_device = t; + } else { + /* if n>0, use that driver */ + for(t=1,md_driver=firstdriver;(md_driver)&&(t!=md_device);md_driver=md_driver->next) + t++; + + if(!md_driver) { + _mm_errno = MMERR_INVALID_DEVICE; + if(_mm_errorhandler) _mm_errorhandler(); + md_driver = &drv_nos; + return 1; + } + + /* arguments here might be necessary for the presence check to succeed */ + if(cmdline&&(md_driver->CommandLine)) + md_driver->CommandLine(cmdline); + + if(!md_driver->IsPresent()) { + _mm_errno = MMERR_DETECTING_DEVICE; + if(_mm_errorhandler) _mm_errorhandler(); + md_driver = &drv_nos; + return 1; + } + } + + olddevice = md_device; + if(md_driver->Init()) { + MikMod_Exit_internal(); + if(_mm_errorhandler) _mm_errorhandler(); + return 1; + } + + initialized=1; + _mm_critical=0; + + return 0; +} + +MIKMODAPI BOOL MikMod_Init(CHAR *cmdline) +{ + BOOL result; + + MUTEX_LOCK(vars); + MUTEX_LOCK(lists); + result=_mm_init(cmdline); + MUTEX_UNLOCK(lists); + MUTEX_UNLOCK(vars); + + return result; +} + +void MikMod_Exit_internal(void) +{ + MikMod_DisableOutput_internal(); + md_driver->Exit(); + md_numchn = md_sfxchn = md_sngchn = 0; + md_driver = &drv_nos; + + if(sfxinfo) MikMod_free(sfxinfo); + if(md_sample) MikMod_free(md_sample); + md_sample = NULL; + sfxinfo = NULL; + + initialized = 0; +} + +MIKMODAPI void MikMod_Exit(void) +{ + MUTEX_LOCK(vars); + MUTEX_LOCK(lists); + MikMod_Exit_internal(); + MUTEX_UNLOCK(lists); + MUTEX_UNLOCK(vars); +} + +/* Reset the driver using the new global variable settings. + If the driver has not been initialized, it will be now. */ +static BOOL _mm_reset(CHAR *cmdline) +{ + BOOL wasplaying = 0; + + if(!initialized) return _mm_init(cmdline); + + if (isplaying) { + wasplaying = 1; + md_driver->PlayStop(); + } + + if((!md_driver->Reset)||(md_device != olddevice)) { + /* md_driver->Reset was NULL, or md_device was changed, so do a full + reset of the driver. */ + md_driver->Exit(); + if(_mm_init(cmdline)) { + MikMod_Exit_internal(); + if(_mm_errno) + if(_mm_errorhandler) _mm_errorhandler(); + return 1; + } + } else { + if(md_driver->Reset()) { + MikMod_Exit_internal(); + if(_mm_errno) + if(_mm_errorhandler) _mm_errorhandler(); + return 1; + } + } + + if (wasplaying) md_driver->PlayStart(); + return 0; +} + +MIKMODAPI BOOL MikMod_Reset(CHAR *cmdline) +{ + BOOL result; + + MUTEX_LOCK(vars); + MUTEX_LOCK(lists); + result=_mm_reset(cmdline); + MUTEX_UNLOCK(lists); + MUTEX_UNLOCK(vars); + + return result; +} + +/* If either parameter is -1, the current set value will be retained. */ +BOOL MikMod_SetNumVoices_internal(int music, int sfx) +{ + BOOL resume = 0; + int t, oldchn = 0; + + if((!music)&&(!sfx)) return 1; + _mm_critical = 1; + if(isplaying) { + MikMod_DisableOutput_internal(); + oldchn = md_numchn; + resume = 1; + } + + if(sfxinfo) MikMod_free(sfxinfo); + if(md_sample) MikMod_free(md_sample); + md_sample = NULL; + sfxinfo = NULL; + + if(music!=-1) md_sngchn = music; + if(sfx!=-1) md_sfxchn = sfx; + md_numchn = md_sngchn + md_sfxchn; + + LimitHardVoices(md_driver->HardVoiceLimit); + LimitSoftVoices(md_driver->SoftVoiceLimit); + + if(md_driver->SetNumVoices()) { + MikMod_Exit_internal(); + if(_mm_errno) + if(_mm_errorhandler!=NULL) _mm_errorhandler(); + md_numchn = md_softchn = md_hardchn = md_sfxchn = md_sngchn = 0; + return 1; + } + + if(md_sngchn+md_sfxchn) + md_sample=(SAMPLE**)MikMod_calloc(md_sngchn+md_sfxchn,sizeof(SAMPLE*)); + if(md_sfxchn) + sfxinfo = (UBYTE *)MikMod_calloc(md_sfxchn,sizeof(UBYTE)); + + /* make sure the player doesn't start with garbage */ + for(t=oldchn;tPlayStart()) return 1; + isplaying = 1; + } + _mm_critical = 0; + return 0; +} + +MIKMODAPI BOOL MikMod_EnableOutput(void) +{ + BOOL result; + + MUTEX_LOCK(vars); + result=MikMod_EnableOutput_internal(); + MUTEX_UNLOCK(vars); + + return result; +} + +void MikMod_DisableOutput_internal(void) +{ + if(isplaying && md_driver) { + isplaying = 0; + md_driver->PlayStop(); + } +} + +MIKMODAPI void MikMod_DisableOutput(void) +{ + MUTEX_LOCK(vars); + MikMod_DisableOutput_internal(); + MUTEX_UNLOCK(vars); +} + +BOOL MikMod_Active_internal(void) +{ + return isplaying; +} + +MIKMODAPI BOOL MikMod_Active(void) +{ + BOOL result; + + MUTEX_LOCK(vars); + result=MikMod_Active_internal(); + MUTEX_UNLOCK(vars); + + return result; +} + +/* Plays a sound effects sample. Picks a voice from the number of voices + allocated for use as sound effects (loops through voices, skipping all active + criticals). + + Returns the voice that the sound is being played on. */ +SBYTE Sample_Play_internal(SAMPLE *s,ULONG start,UBYTE flags) +{ + int orig=sfxpool;/* for cases where all channels are critical */ + int c; + + if(!md_sfxchn) return -1; + if(s->volume>64) s->volume = 64; + + /* check the first location after sfxpool */ + do { + if(sfxinfo[sfxpool]&SFX_CRITICAL) { + if(md_driver->VoiceStopped(c=sfxpool+md_sngchn)) { + sfxinfo[sfxpool]=flags; + Voice_Play_internal(c,s,start); + md_driver->VoiceSetVolume(c,s->volume<<2); + Voice_SetPanning_internal(c,s->panning); + md_driver->VoiceSetFrequency(c,s->speed); + sfxpool++; + if(sfxpool>=md_sfxchn) sfxpool=0; + return c; + } + } else { + sfxinfo[sfxpool]=flags; + Voice_Play_internal(c=sfxpool+md_sngchn,s,start); + md_driver->VoiceSetVolume(c,s->volume<<2); + Voice_SetPanning_internal(c,s->panning); + md_driver->VoiceSetFrequency(c,s->speed); + sfxpool++; + if(sfxpool>=md_sfxchn) sfxpool=0; + return c; + } + + sfxpool++; + if(sfxpool>=md_sfxchn) sfxpool = 0; + } while(sfxpool!=orig); + + return -1; +} + +MIKMODAPI SBYTE Sample_Play(SAMPLE *s,ULONG start,UBYTE flags) +{ + SBYTE result; + + MUTEX_LOCK(vars); + result=Sample_Play_internal(s,start,flags); + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI long MikMod_GetVersion(void) +{ + return LIBMIKMOD_VERSION; +} + +/*========== MT-safe stuff */ + +#ifdef HAVE_PTHREAD +#define INIT_MUTEX(name) \ + pthread_mutex_t _mm_mutex_##name=PTHREAD_MUTEX_INITIALIZER +#elif defined(__OS2__)||defined(__EMX__) +#define INIT_MUTEX(name) \ + HMTX _mm_mutex_##name +#elif defined(WIN32) +#define INIT_MUTEX(name) \ + HANDLE _mm_mutex_##name +#else +#define INIT_MUTEX(name) \ + void *_mm_mutex_##name = NULL +#endif + +INIT_MUTEX(vars); +INIT_MUTEX(lists); + +MIKMODAPI BOOL MikMod_InitThreads(void) +{ + static int firstcall=1; + static int result=0; + + if (firstcall) { + firstcall=0; +#ifdef HAVE_PTHREAD + result=1; +#elif defined(__OS2__)||defined(__EMX__) + if(DosCreateMutexSem((PSZ)NULL,&_mm_mutex_lists,0,0) || + DosCreateMutexSem((PSZ)NULL,&_mm_mutex_vars,0,0)) { + _mm_mutex_lists=_mm_mutex_vars=(HMTX)NULL; + result=0; + } else + result=1; +#elif defined(WIN32) + if((!(_mm_mutex_lists=CreateMutex(NULL,FALSE,"libmikmod(lists)")))|| + (!(_mm_mutex_vars=CreateMutex(NULL,FALSE,"libmikmod(vars)")))) + result=0; + else + result=1; +#endif + } + return result; +} + +MIKMODAPI void MikMod_Unlock(void) +{ + MUTEX_UNLOCK(lists); + MUTEX_UNLOCK(vars); +} + +MIKMODAPI void MikMod_Lock(void) +{ + MUTEX_LOCK(vars); + MUTEX_LOCK(lists); +} + +/*========== Parameter extraction helper */ + +CHAR *MD_GetAtom(CHAR *atomname,CHAR *cmdline,BOOL implicit) +{ + CHAR *ret=NULL; + + if(cmdline) { + CHAR *buf=strstr(cmdline,atomname); + + if((buf)&&((buf==cmdline)||(*(buf-1)==','))) { + CHAR *ptr=buf+strlen(atomname); + + if(*ptr=='=') { + for(buf=++ptr;(*ptr)&&((*ptr)!=',');ptr++); + ret=MikMod_malloc((1+ptr-buf)*sizeof(CHAR)); + if(ret) + strncpy(ret,buf,ptr-buf); + } else if((*ptr==',')||(!*ptr)) { + if(implicit) { + ret=MikMod_malloc((1+ptr-buf)*sizeof(CHAR)); + if(ret) + strncpy(ret,buf,ptr-buf); + } + } + } + } + return ret; +} + +#if defined unix || (defined __APPLE__ && defined __MACH__) + +/*========== Posix helper functions */ + +/* Check if the file is a regular or nonexistant file (or a link to a such a + file), and that, should the calling program be setuid, the access rights are + reasonable. Returns 1 if it is safe to rewrite the file, 0 otherwise. + The goal is to prevent a setuid root libmikmod application from overriding + files like /etc/passwd with digital sound... */ +BOOL MD_Access(CHAR *filename) +{ + struct stat buf; + + if(!stat(filename,&buf)) { + /* not a regular file ? */ + if(!S_ISREG(buf.st_mode)) return 0; + /* more than one hard link to the file ? */ + if(buf.st_nlink>1) return 0; + /* check access rights with the real user and group id */ + if(getuid()==buf.st_uid) { + if(!(buf.st_mode&S_IWUSR)) return 0; + } else if(getgid()==buf.st_gid) { + if(!(buf.st_mode&S_IWGRP)) return 0; + } else + if(!(buf.st_mode&S_IWOTH)) return 0; + } + + return 1; +} + +/* Drop all root privileges we might have */ +BOOL MD_DropPrivileges(void) +{ + if(!geteuid()) { + if(getuid()) { + /* we are setuid root -> drop setuid to become the real user */ + if(setuid(getuid())) return 1; + } else { + /* we are run as root -> drop all and become user 'nobody' */ + struct passwd *nobody; + int uid; + + if(!(nobody=getpwnam("nobody"))) return 1; /* no such user ? */ + uid=nobody->pw_uid; + if (!uid) /* user 'nobody' has root privileges ? weird... */ + return 1; + if (setuid(uid)) return 1; + } + } + return 0; +} + +#endif + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mikmod.h b/src/libs/mikmod/mikmod.h new file mode 100644 index 0000000..1fd5322 --- /dev/null +++ b/src/libs/mikmod/mikmod.h @@ -0,0 +1,730 @@ +/* MikMod sound library + (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + MikMod sound library include file + +==============================================================================*/ + +#ifndef LIBS_MIKMOD_MIKMOD_H_ +#define LIBS_MIKMOD_MIKMOD_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ========== Compiler magic for shared libraries + */ + +#if defined(LIBMIKMOD_DLL) +#if defined(DLL_EXPORTS) +#define MIKMODAPI __declspec(dllexport) +#else +#define MIKMODAPI __declspec(dllimport) +#endif +#else +#define MIKMODAPI +#endif + +/* + * ========== Library version + */ + +#define LIBMIKMOD_VERSION_MAJOR 3L +#define LIBMIKMOD_VERSION_MINOR 1L +#define LIBMIKMOD_REVISION 12L + +#define LIBMIKMOD_VERSION \ + ((LIBMIKMOD_VERSION_MAJOR<<16)| \ + (LIBMIKMOD_VERSION_MINOR<< 8)| \ + (LIBMIKMOD_REVISION)) + +MIKMODAPI extern long MikMod_GetVersion(void); + +/* + * ========== Platform independent-type definitions + */ + +#if defined(WIN32)||defined(WIN64) +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#endif + +#if defined(__OS2__)||defined(__EMX__) +#define INCL_DOSSEMAPHORES +#include +#else +typedef char CHAR; +#endif + + + +#if defined(__arch64__) || defined(__alpha) || defined(__x86_64) \ + || defined(__powerpc64__) || defined(_M_IA64) || defined(_M_AMD64) +/* 64 bit architectures */ + +typedef signed char SBYTE; /* 1 byte, signed */ +typedef unsigned char UBYTE; /* 1 byte, unsigned */ +typedef signed short SWORD; /* 2 bytes, signed */ +typedef unsigned short UWORD; /* 2 bytes, unsigned */ +typedef signed int SLONG; /* 4 bytes, signed */ +typedef unsigned int ULONG; /* 4 bytes, unsigned */ +typedef int BOOL; /* 0=false, <>0 true */ + +#else +/* 32 bit architectures */ + +typedef signed char SBYTE; /* 1 byte, signed */ +typedef unsigned char UBYTE; /* 1 byte, unsigned */ +typedef signed short SWORD; /* 2 bytes, signed */ +typedef unsigned short UWORD; /* 2 bytes, unsigned */ +typedef signed long SLONG; /* 4 bytes, signed */ +#if !defined(__OS2__)&&!defined(__EMX__)&&!defined(WIN32) +typedef unsigned long ULONG; /* 4 bytes, unsigned */ +typedef int BOOL; /* 0=false, <>0 true */ +#endif +#endif + +/* + * ========== Error codes + */ + +enum { + MMERR_OPENING_FILE = 1, + MMERR_OUT_OF_MEMORY, + MMERR_DYNAMIC_LINKING, + + MMERR_SAMPLE_TOO_BIG, + MMERR_OUT_OF_HANDLES, + MMERR_UNKNOWN_WAVE_TYPE, + + MMERR_LOADING_PATTERN, + MMERR_LOADING_TRACK, + MMERR_LOADING_HEADER, + MMERR_LOADING_SAMPLEINFO, + MMERR_NOT_A_MODULE, + MMERR_NOT_A_STREAM, + MMERR_MED_SYNTHSAMPLES, + MMERR_ITPACK_INVALID_DATA, + + MMERR_DETECTING_DEVICE, + MMERR_INVALID_DEVICE, + MMERR_INITIALIZING_MIXER, + MMERR_OPENING_AUDIO, + MMERR_8BIT_ONLY, + MMERR_16BIT_ONLY, + MMERR_STEREO_ONLY, + MMERR_ULAW, + MMERR_NON_BLOCK, + + MMERR_AF_AUDIO_PORT, + + MMERR_AIX_CONFIG_INIT, + MMERR_AIX_CONFIG_CONTROL, + MMERR_AIX_CONFIG_START, + + MMERR_GUS_SETTINGS, + MMERR_GUS_RESET, + MMERR_GUS_TIMER, + + MMERR_HP_SETSAMPLESIZE, + MMERR_HP_SETSPEED, + MMERR_HP_CHANNELS, + MMERR_HP_AUDIO_OUTPUT, + MMERR_HP_AUDIO_DESC, + MMERR_HP_BUFFERSIZE, + + MMERR_OSS_SETFRAGMENT, + MMERR_OSS_SETSAMPLESIZE, + MMERR_OSS_SETSTEREO, + MMERR_OSS_SETSPEED, + + MMERR_SGI_SPEED, + MMERR_SGI_16BIT, + MMERR_SGI_8BIT, + MMERR_SGI_STEREO, + MMERR_SGI_MONO, + + MMERR_SUN_INIT, + + MMERR_OS2_MIXSETUP, + MMERR_OS2_SEMAPHORE, + MMERR_OS2_TIMER, + MMERR_OS2_THREAD, + + MMERR_DS_PRIORITY, + MMERR_DS_BUFFER, + MMERR_DS_FORMAT, + MMERR_DS_NOTIFY, + MMERR_DS_EVENT, + MMERR_DS_THREAD, + MMERR_DS_UPDATE, + + MMERR_WINMM_HANDLE, + MMERR_WINMM_ALLOCATED, + MMERR_WINMM_DEVICEID, + MMERR_WINMM_FORMAT, + MMERR_WINMM_UNKNOWN, + + MMERR_MAC_SPEED, + MMERR_MAC_START, + + MMERR_MAX +}; + +/* + * ========== Error handling + */ + +typedef void (MikMod_handler)(void); +typedef MikMod_handler *MikMod_handler_t; + +MIKMODAPI extern int MikMod_errno; +MIKMODAPI extern BOOL MikMod_critical; +MIKMODAPI extern char *MikMod_strerror(int); + +MIKMODAPI extern MikMod_handler_t MikMod_RegisterErrorHandler(MikMod_handler_t); + +/* + * ========== Library initialization and core functions + */ + +struct MDRIVER; + +MIKMODAPI extern void MikMod_RegisterAllDrivers(void); + +MIKMODAPI extern CHAR* MikMod_InfoDriver(void); +MIKMODAPI extern void MikMod_RegisterDriver(struct MDRIVER*); +MIKMODAPI extern int MikMod_DriverFromAlias(CHAR*); + +MIKMODAPI extern BOOL MikMod_Init(CHAR*); +MIKMODAPI extern void MikMod_Exit(void); +MIKMODAPI extern BOOL MikMod_Reset(CHAR*); +MIKMODAPI extern BOOL MikMod_SetNumVoices(int,int); +MIKMODAPI extern BOOL MikMod_Active(void); +MIKMODAPI extern BOOL MikMod_EnableOutput(void); +MIKMODAPI extern void MikMod_DisableOutput(void); +MIKMODAPI extern void MikMod_Update(void); + +MIKMODAPI extern BOOL MikMod_InitThreads(void); +MIKMODAPI extern void MikMod_Lock(void); +MIKMODAPI extern void MikMod_Unlock(void); + +MIKMODAPI extern void* MikMod_malloc(size_t); +MIKMODAPI extern void* MikMod_realloc(void *, size_t); +MIKMODAPI extern void* MikMod_calloc(size_t,size_t); +MIKMODAPI extern void MikMod_free(void *); + +/* + * ========== Reader, Writer + */ + +typedef struct MREADER { + BOOL (*Seek)(struct MREADER*,long,int); + long (*Tell)(struct MREADER*); + BOOL (*Read)(struct MREADER*,void*,size_t); + int (*Get)(struct MREADER*); + BOOL (*Eof)(struct MREADER*); + long iobase; + long prev_iobase; +} MREADER; + +typedef struct MWRITER { + BOOL (*Seek)(struct MWRITER*,long,int); + long (*Tell)(struct MWRITER*); + BOOL (*Write)(struct MWRITER*,void*,size_t); + BOOL (*Put)(struct MWRITER*,int); +} MWRITER; + +/* + * ========== Samples + */ + +/* Sample playback should not be interrupted */ +#define SFX_CRITICAL 1 + +/* Sample format [loading and in-memory] flags: */ +#define SF_16BITS 0x0001 +#define SF_STEREO 0x0002 +#define SF_SIGNED 0x0004 +#define SF_BIG_ENDIAN 0x0008 +#define SF_DELTA 0x0010 +#define SF_ITPACKED 0x0020 + +#define SF_FORMATMASK 0x003F + +/* General Playback flags */ + +#define SF_LOOP 0x0100 +#define SF_BIDI 0x0200 +#define SF_REVERSE 0x0400 +#define SF_SUSTAIN 0x0800 + +#define SF_PLAYBACKMASK 0x0C00 + +/* Module-only Playback Flags */ + +#define SF_OWNPAN 0x1000 +#define SF_UST_LOOP 0x2000 + +#define SF_EXTRAPLAYBACKMASK 0x3000 + +/* Panning constants */ +#define PAN_LEFT 0 +#define PAN_HALFLEFT 64 +#define PAN_CENTER 128 +#define PAN_HALFRIGHT 192 +#define PAN_RIGHT 255 +#define PAN_SURROUND 512 /* panning value for Dolby Surround */ + +typedef struct SAMPLE { + SWORD panning; /* panning (0-255 or PAN_SURROUND) */ + ULONG speed; /* Base playing speed/frequency of note */ + UBYTE volume; /* volume 0-64 */ + UWORD inflags; /* sample format on disk */ + UWORD flags; /* sample format in memory */ + ULONG length; /* length of sample (in samples!) */ + ULONG loopstart; /* repeat position (relative to start, in samples) */ + ULONG loopend; /* repeat end */ + ULONG susbegin; /* sustain loop begin (in samples) \ Not Supported */ + ULONG susend; /* sustain loop end / Yet! */ + + /* Variables used by the module player only! (ignored for sound effects) */ + UBYTE globvol; /* global volume */ + UBYTE vibflags; /* autovibrato flag stuffs */ + UBYTE vibtype; /* Vibratos moved from INSTRUMENT to SAMPLE */ + UBYTE vibsweep; + UBYTE vibdepth; + UBYTE vibrate; + CHAR* samplename; /* name of the sample */ + + /* Values used internally only */ + UWORD avibpos; /* autovibrato pos [player use] */ + UBYTE divfactor; /* for sample scaling, maintains proper period slides */ + ULONG seekpos; /* seek position in file */ + SWORD handle; /* sample handle used by individual drivers */ +} SAMPLE; + +/* Sample functions */ + +MIKMODAPI extern SAMPLE *Sample_Load(CHAR*); +MIKMODAPI extern SAMPLE *Sample_LoadFP(FILE*); +MIKMODAPI extern SAMPLE *Sample_LoadMem(const char *buf, int len); +MIKMODAPI extern SAMPLE *Sample_LoadGeneric(MREADER*); +MIKMODAPI extern void Sample_Free(SAMPLE*); +MIKMODAPI extern SBYTE Sample_Play(SAMPLE*,ULONG,UBYTE); + +MIKMODAPI extern void Voice_SetVolume(SBYTE,UWORD); +MIKMODAPI extern UWORD Voice_GetVolume(SBYTE); +MIKMODAPI extern void Voice_SetFrequency(SBYTE,ULONG); +MIKMODAPI extern ULONG Voice_GetFrequency(SBYTE); +MIKMODAPI extern void Voice_SetPanning(SBYTE,ULONG); +MIKMODAPI extern ULONG Voice_GetPanning(SBYTE); +MIKMODAPI extern void Voice_Play(SBYTE,SAMPLE*,ULONG); +MIKMODAPI extern void Voice_Stop(SBYTE); +MIKMODAPI extern BOOL Voice_Stopped(SBYTE); +MIKMODAPI extern SLONG Voice_GetPosition(SBYTE); +MIKMODAPI extern ULONG Voice_RealVolume(SBYTE); + +/* + * ========== Internal module representation (UniMod) + */ + +/* + Instrument definition - for information only, the only field which may be + of use in user programs is the name field +*/ + +/* Instrument note count */ +#define INSTNOTES 120 + +/* Envelope point */ +typedef struct ENVPT { + SWORD pos; + SWORD val; +} ENVPT; + +/* Envelope point count */ +#define ENVPOINTS 32 + +/* Instrument structure */ +typedef struct INSTRUMENT { + CHAR* insname; + + UBYTE flags; + UWORD samplenumber[INSTNOTES]; + UBYTE samplenote[INSTNOTES]; + + UBYTE nnatype; + UBYTE dca; /* duplicate check action */ + UBYTE dct; /* duplicate check type */ + UBYTE globvol; + UWORD volfade; + SWORD panning; /* instrument-based panning var */ + + UBYTE pitpansep; /* pitch pan separation (0 to 255) */ + UBYTE pitpancenter; /* pitch pan center (0 to 119) */ + UBYTE rvolvar; /* random volume varations (0 - 100%) */ + UBYTE rpanvar; /* random panning varations (0 - 100%) */ + + /* volume envelope */ + UBYTE volflg; /* bit 0: on 1: sustain 2: loop */ + UBYTE volpts; + UBYTE volsusbeg; + UBYTE volsusend; + UBYTE volbeg; + UBYTE volend; + ENVPT volenv[ENVPOINTS]; + /* panning envelope */ + UBYTE panflg; /* bit 0: on 1: sustain 2: loop */ + UBYTE panpts; + UBYTE pansusbeg; + UBYTE pansusend; + UBYTE panbeg; + UBYTE panend; + ENVPT panenv[ENVPOINTS]; + /* pitch envelope */ + UBYTE pitflg; /* bit 0: on 1: sustain 2: loop */ + UBYTE pitpts; + UBYTE pitsusbeg; + UBYTE pitsusend; + UBYTE pitbeg; + UBYTE pitend; + ENVPT pitenv[ENVPOINTS]; +} INSTRUMENT; + +struct MP_CONTROL; +struct MP_VOICE; + +/* + Module definition +*/ + +/* maximum master channels supported */ +#define UF_MAXCHAN 64 + +/* Module flags */ +#define UF_XMPERIODS 0x0001 /* XM periods / finetuning */ +#define UF_LINEAR 0x0002 /* LINEAR periods (UF_XMPERIODS must be set) */ +#define UF_INST 0x0004 /* Instruments are used */ +#define UF_NNA 0x0008 /* IT: NNA used, set numvoices rather + than numchn */ +#define UF_S3MSLIDES 0x0010 /* uses old S3M volume slides */ +#define UF_BGSLIDES 0x0020 /* continue volume slides in the background */ +#define UF_HIGHBPM 0x0040 /* MED: can use >255 bpm */ +#define UF_NOWRAP 0x0080 /* XM-type (i.e. illogical) pattern break + semantics */ +#define UF_ARPMEM 0x0100 /* IT: need arpeggio memory */ +#define UF_FT2QUIRKS 0x0200 /* emulate some FT2 replay quirks */ +#define UF_PANNING 0x0400 /* module uses panning effects or have + non-tracker default initial panning */ + +typedef struct MODULE { + /* general module information */ + CHAR* songname; /* name of the song */ + CHAR* modtype; /* string type of module loaded */ + CHAR* comment; /* module comments */ + + UWORD flags; /* See module flags above */ + UBYTE numchn; /* number of module channels */ + UBYTE numvoices; /* max # voices used for full NNA playback */ + UWORD numpos; /* number of positions in this song */ + UWORD numpat; /* number of patterns in this song */ + UWORD numins; /* number of instruments */ + UWORD numsmp; /* number of samples */ +struct INSTRUMENT* instruments; /* all instruments */ +struct SAMPLE* samples; /* all samples */ + UBYTE realchn; /* real number of channels used */ + UBYTE totalchn; /* total number of channels used (incl NNAs) */ + + /* playback settings */ + UWORD reppos; /* restart position */ + UBYTE initspeed; /* initial song speed */ + UWORD inittempo; /* initial song tempo */ + UBYTE initvolume; /* initial global volume (0 - 128) */ + UWORD panning[UF_MAXCHAN]; /* panning positions */ + UBYTE chanvol[UF_MAXCHAN]; /* channel positions */ + UWORD bpm; /* current beats-per-minute speed */ + UWORD sngspd; /* current song speed */ + SWORD volume; /* song volume (0-128) (or user volume) */ + + BOOL extspd; /* extended speed flag (default enabled) */ + BOOL panflag; /* panning flag (default enabled) */ + BOOL wrap; /* wrap module ? (default disabled) */ + BOOL loop; /* allow module to loop ? (default enabled) */ + BOOL fadeout; /* volume fade out during last pattern */ + + UWORD patpos; /* current row number */ + SWORD sngpos; /* current song position */ + ULONG sngtime; /* current song time in 2^-10 seconds */ + + SWORD relspd; /* relative speed factor */ + + /* internal module representation */ + UWORD numtrk; /* number of tracks */ + UBYTE** tracks; /* array of numtrk pointers to tracks */ + UWORD* patterns; /* array of Patterns */ + UWORD* pattrows; /* array of number of rows for each pattern */ + UWORD* positions; /* all positions */ + + BOOL forbid; /* if true, no player update! */ + UWORD numrow; /* number of rows on current pattern */ + UWORD vbtick; /* tick counter (counts from 0 to sngspd) */ + UWORD sngremainder;/* used for song time computation */ + +struct MP_CONTROL* control; /* Effects Channel info (size pf->numchn) */ +struct MP_VOICE* voice; /* Audio Voice information (size md_numchn) */ + + UBYTE globalslide; /* global volume slide rate */ + UBYTE pat_repcrazy;/* module has just looped to position -1 */ + UWORD patbrk; /* position where to start a new pattern */ + UBYTE patdly; /* patterndelay counter (command memory) */ + UBYTE patdly2; /* patterndelay counter (real one) */ + SWORD posjmp; /* flag to indicate a jump is needed... */ + UWORD bpmlimit; /* threshold to detect bpm or speed values */ +} MODULE; + +/* + * ========== Module loaders + */ + +struct MLOADER; + +MIKMODAPI extern CHAR* MikMod_InfoLoader(void); +MIKMODAPI extern void MikMod_RegisterAllLoaders(void); +MIKMODAPI extern void MikMod_RegisterLoader(struct MLOADER*); + +MIKMODAPI extern struct MLOADER load_669; /* 669 and Extended-669 (by Tran/Renaissance) */ +MIKMODAPI extern struct MLOADER load_amf; /* DMP Advanced Module Format (by Otto Chrons) */ +MIKMODAPI extern struct MLOADER load_dsm; /* DSIK internal module format */ +MIKMODAPI extern struct MLOADER load_far; /* Farandole Composer (by Daniel Potter) */ +MIKMODAPI extern struct MLOADER load_gdm; /* General DigiMusic (by Edward Schlunder) */ +MIKMODAPI extern struct MLOADER load_it; /* Impulse Tracker (by Jeffrey Lim) */ +MIKMODAPI extern struct MLOADER load_imf; /* Imago Orpheus (by Lutz Roeder) */ +MIKMODAPI extern struct MLOADER load_med; /* Amiga MED modules (by Teijo Kinnunen) */ +MIKMODAPI extern struct MLOADER load_m15; /* Soundtracker 15-instrument */ +MIKMODAPI extern struct MLOADER load_mod; /* Standard 31-instrument Module loader */ +MIKMODAPI extern struct MLOADER load_mtm; /* Multi-Tracker Module (by Renaissance) */ +MIKMODAPI extern struct MLOADER load_okt; /* Amiga Oktalyzer */ +MIKMODAPI extern struct MLOADER load_stm; /* ScreamTracker 2 (by Future Crew) */ +MIKMODAPI extern struct MLOADER load_stx; /* STMIK 0.2 (by Future Crew) */ +MIKMODAPI extern struct MLOADER load_s3m; /* ScreamTracker 3 (by Future Crew) */ +MIKMODAPI extern struct MLOADER load_ult; /* UltraTracker (by MAS) */ +MIKMODAPI extern struct MLOADER load_uni; /* MikMod and APlayer internal module format */ +MIKMODAPI extern struct MLOADER load_xm; /* FastTracker 2 (by Triton) */ + +/* + * ========== Module player + */ + +MIKMODAPI extern MODULE* Player_Load(CHAR*,int,BOOL); +MIKMODAPI extern MODULE* Player_LoadFP(FILE*,int,BOOL); +MIKMODAPI extern MODULE* Player_LoadMem(const char *buffer,int len,int maxchan,BOOL curious); +MIKMODAPI extern MODULE* Player_LoadGeneric(MREADER*,int,BOOL); +MIKMODAPI extern CHAR* Player_LoadTitle(CHAR*); +MIKMODAPI extern CHAR* Player_LoadTitleFP(FILE*); +MIKMODAPI extern CHAR* Player_LoadTitleMem(const char *buffer,int len); +MIKMODAPI extern CHAR* Player_LoadTitleGeneric(MREADER*); + +MIKMODAPI extern void Player_Free(MODULE*); +MIKMODAPI extern void Player_Start(MODULE*); +MIKMODAPI extern BOOL Player_Active(void); +MIKMODAPI extern void Player_Stop(void); +MIKMODAPI extern void Player_TogglePause(void); +MIKMODAPI extern BOOL Player_Paused(void); +MIKMODAPI extern void Player_NextPosition(void); +MIKMODAPI extern void Player_PrevPosition(void); +MIKMODAPI extern void Player_SetPosition(UWORD); +MIKMODAPI extern BOOL Player_Muted(UBYTE); +MIKMODAPI extern void Player_SetVolume(SWORD); +MIKMODAPI extern MODULE* Player_GetModule(void); +MIKMODAPI extern void Player_SetSpeed(UWORD); +MIKMODAPI extern void Player_SetTempo(UWORD); +MIKMODAPI extern void Player_Unmute(SLONG,...); +MIKMODAPI extern void Player_Mute(SLONG,...); +MIKMODAPI extern void Player_ToggleMute(SLONG,...); +MIKMODAPI extern int Player_GetChannelVoice(UBYTE); +MIKMODAPI extern UWORD Player_GetChannelPeriod(UBYTE); + +typedef void (MikMod_player)(void); +typedef MikMod_player *MikMod_player_t; + +MIKMODAPI extern MikMod_player_t MikMod_RegisterPlayer(MikMod_player_t); + +#define MUTE_EXCLUSIVE 32000 +#define MUTE_INCLUSIVE 32001 + +/* + * ========== Drivers + */ + +enum { + MD_MUSIC = 0, + MD_SNDFX +}; + +enum { + MD_HARDWARE = 0, + MD_SOFTWARE +}; + +/* Mixing flags */ + +/* These ones take effect only after MikMod_Init or MikMod_Reset */ +#define DMODE_16BITS 0x0001 /* enable 16 bit output */ +#define DMODE_STEREO 0x0002 /* enable stereo output */ +#define DMODE_SOFT_SNDFX 0x0004 /* Process sound effects via software mixer */ +#define DMODE_SOFT_MUSIC 0x0008 /* Process music via software mixer */ +#define DMODE_HQMIXER 0x0010 /* Use high-quality (slower) software mixer */ +/* These take effect immediately. */ +#define DMODE_SURROUND 0x0100 /* enable surround sound */ +#define DMODE_INTERP 0x0200 /* enable interpolation */ +#define DMODE_REVERSE 0x0400 /* reverse stereo */ + +struct SAMPLOAD; +typedef struct MDRIVER { +struct MDRIVER* next; + CHAR* Name; + CHAR* Version; + + UBYTE HardVoiceLimit; /* Limit of hardware mixer voices */ + UBYTE SoftVoiceLimit; /* Limit of software mixer voices */ + + CHAR* Alias; + + void (*CommandLine) (CHAR*); + BOOL (*IsPresent) (void); + SWORD (*SampleLoad) (struct SAMPLOAD*,int); + void (*SampleUnload) (SWORD); + ULONG (*FreeSampleSpace) (int); + ULONG (*RealSampleLength) (int,struct SAMPLE*); + BOOL (*Init) (void); + void (*Exit) (void); + BOOL (*Reset) (void); + BOOL (*SetNumVoices) (void); + BOOL (*PlayStart) (void); + void (*PlayStop) (void); + void (*Update) (void); + void (*Pause) (void); + void (*VoiceSetVolume) (UBYTE,UWORD); + UWORD (*VoiceGetVolume) (UBYTE); + void (*VoiceSetFrequency)(UBYTE,ULONG); + ULONG (*VoiceGetFrequency)(UBYTE); + void (*VoiceSetPanning) (UBYTE,ULONG); + ULONG (*VoiceGetPanning) (UBYTE); + void (*VoicePlay) (UBYTE,SWORD,ULONG,ULONG,ULONG,ULONG,UWORD); + void (*VoiceStop) (UBYTE); + BOOL (*VoiceStopped) (UBYTE); + SLONG (*VoiceGetPosition) (UBYTE); + ULONG (*VoiceRealVolume) (UBYTE); +} MDRIVER; + +/* These variables can be changed at ANY time and results will be immediate */ +MIKMODAPI extern UBYTE md_volume; /* global sound volume (0-128) */ +MIKMODAPI extern UBYTE md_musicvolume; /* volume of song */ +MIKMODAPI extern UBYTE md_sndfxvolume; /* volume of sound effects */ +MIKMODAPI extern UBYTE md_reverb; /* 0 = none; 15 = chaos */ +MIKMODAPI extern UBYTE md_pansep; /* 0 = mono; 128 == 100% (full left/right) */ + +/* The variables below can be changed at any time, but changes will not be + implemented until MikMod_Reset is called. A call to MikMod_Reset may result + in a skip or pop in audio (depending on the soundcard driver and the settings + changed). */ +MIKMODAPI extern UWORD md_device; /* device */ +MIKMODAPI extern UWORD md_mixfreq; /* mixing frequency */ +MIKMODAPI extern UWORD md_mode; /* mode. See DMODE_? flags above */ + +/* The following variable should not be changed! */ +MIKMODAPI extern MDRIVER* md_driver; /* Current driver in use. */ + +/* Known drivers list */ + +MIKMODAPI extern struct MDRIVER drv_nos; /* no sound */ +MIKMODAPI extern struct MDRIVER drv_pipe; /* piped output */ +MIKMODAPI extern struct MDRIVER drv_raw; /* raw file disk writer [music.raw] */ +MIKMODAPI extern struct MDRIVER drv_stdout; /* output to stdout */ +MIKMODAPI extern struct MDRIVER drv_wav; /* RIFF WAVE file disk writer [music.wav] */ + +MIKMODAPI extern struct MDRIVER drv_ultra; /* Linux Ultrasound driver */ +MIKMODAPI extern struct MDRIVER drv_sam9407; /* Linux sam9407 driver */ + +MIKMODAPI extern struct MDRIVER drv_AF; /* Dec Alpha AudioFile */ +MIKMODAPI extern struct MDRIVER drv_aix; /* AIX audio device */ +MIKMODAPI extern struct MDRIVER drv_alsa; /* Advanced Linux Sound Architecture (ALSA) */ +MIKMODAPI extern struct MDRIVER drv_esd; /* Enlightened sound daemon (EsounD) */ +MIKMODAPI extern struct MDRIVER drv_hp; /* HP-UX audio device */ +MIKMODAPI extern struct MDRIVER drv_oss; /* OpenSound System (Linux,FreeBSD...) */ +MIKMODAPI extern struct MDRIVER drv_sgi; /* SGI audio library */ +MIKMODAPI extern struct MDRIVER drv_sun; /* Sun/NetBSD/OpenBSD audio device */ + +MIKMODAPI extern struct MDRIVER drv_dart; /* OS/2 Direct Audio RealTime */ +MIKMODAPI extern struct MDRIVER drv_os2; /* OS/2 MMPM/2 */ + +MIKMODAPI extern struct MDRIVER drv_ds; /* Win32 DirectSound driver */ +MIKMODAPI extern struct MDRIVER drv_win; /* Win32 multimedia API driver */ + +MIKMODAPI extern struct MDRIVER drv_mac; /* Macintosh Sound Manager driver */ + +/*========== Virtual channel mixer interface (for user-supplied drivers only) */ + +MIKMODAPI extern BOOL VC_Init(void); +MIKMODAPI extern void VC_Exit(void); +MIKMODAPI extern BOOL VC_SetNumVoices(void); +MIKMODAPI extern ULONG VC_SampleSpace(int); +MIKMODAPI extern ULONG VC_SampleLength(int,SAMPLE*); + +MIKMODAPI extern BOOL VC_PlayStart(void); +MIKMODAPI extern void VC_PlayStop(void); + +MIKMODAPI extern SWORD VC_SampleLoad(struct SAMPLOAD*,int); +MIKMODAPI extern void VC_SampleUnload(SWORD); + +MIKMODAPI extern ULONG VC_WriteBytes(SBYTE*,ULONG); +MIKMODAPI extern ULONG VC_SilenceBytes(SBYTE*,ULONG); + +MIKMODAPI extern void VC_VoiceSetVolume(UBYTE,UWORD); +MIKMODAPI extern UWORD VC_VoiceGetVolume(UBYTE); +MIKMODAPI extern void VC_VoiceSetFrequency(UBYTE,ULONG); +MIKMODAPI extern ULONG VC_VoiceGetFrequency(UBYTE); +MIKMODAPI extern void VC_VoiceSetPanning(UBYTE,ULONG); +MIKMODAPI extern ULONG VC_VoiceGetPanning(UBYTE); +MIKMODAPI extern void VC_VoicePlay(UBYTE,SWORD,ULONG,ULONG,ULONG,ULONG,UWORD); + +MIKMODAPI extern void VC_VoiceStop(UBYTE); +MIKMODAPI extern BOOL VC_VoiceStopped(UBYTE); +MIKMODAPI extern SLONG VC_VoiceGetPosition(UBYTE); +MIKMODAPI extern ULONG VC_VoiceRealVolume(UBYTE); + +#ifdef __cplusplus +} +#endif + +#endif + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mikmod_build.h b/src/libs/mikmod/mikmod_build.h new file mode 100644 index 0000000..29fe3b4 --- /dev/null +++ b/src/libs/mikmod/mikmod_build.h @@ -0,0 +1,9 @@ +#include "mikmod.h" + +#if defined(WIN32) && !defined(__STDC__) +# define __STDC__ 1 +#endif + +#if defined(WIN32) && defined(_MSC_VER) +# pragma warning(disable: 4018 4244) +#endif diff --git a/src/libs/mikmod/mikmod_internals.h b/src/libs/mikmod/mikmod_internals.h new file mode 100644 index 0000000..f84c4b7 --- /dev/null +++ b/src/libs/mikmod/mikmod_internals.h @@ -0,0 +1,679 @@ +/* MikMod sound library + (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + MikMod sound library internal definitions + +==============================================================================*/ + +#ifndef LIBS_MIKMOD_MIKMOD_INTERNALS_H_ +#define LIBS_MIKMOD_MIKMOD_INTERNALS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#if defined(__OS2__)||defined(__EMX__)||defined(WIN32)||defined(WIN64) +#define strcasecmp(s,t) stricmp(s,t) +#endif + +#define MIKMOD_INTERNAL +#include "mikmod_build.h" + +/*========== More type definitions */ + +/* SLONGLONG: 64bit, signed */ +#if defined(__arch64__) || defined(__alpha) || defined(__x86_64) \ + || defined(_M_IA64) || defined(_M_AMD64) +typedef long SLONGLONG; +#define NATIVE_64BIT_INT +#elif defined(__powerpc64__) +typedef long long SLONGLONG; +#define NATIVE_64BIT_INT +#elif defined(__WATCOMC__) +typedef __int64 SLONGLONG; +#elif defined(WIN32) && !defined(__MWERKS__) +typedef LONGLONG SLONGLONG; +#elif macintosh && !TYPE_LONGLONG +#include +typedef SInt64 SLONGLONG; +#else +typedef long long SLONGLONG; +#endif + +/*========== Error handling */ + +#define _mm_errno MikMod_errno +#define _mm_critical MikMod_critical +extern MikMod_handler_t _mm_errorhandler; + +/*========== MT stuff */ + +#ifdef HAVE_PTHREAD +#include +#define DECLARE_MUTEX(name) \ + extern pthread_mutex_t _mm_mutex_##name +#define MUTEX_LOCK(name) \ + pthread_mutex_lock(&_mm_mutex_##name) +#define MUTEX_UNLOCK(name) \ + pthread_mutex_unlock(&_mm_mutex_##name) +#elif defined(__OS2__)||defined(__EMX__) +#define DECLARE_MUTEX(name) \ + extern HMTX _mm_mutex_##name +#define MUTEX_LOCK(name) \ + if(_mm_mutex_##name) \ + DosRequestMutexSem(_mm_mutex_##name,SEM_INDEFINITE_WAIT) +#define MUTEX_UNLOCK(name) \ + if(_mm_mutex_##name) \ + DosReleaseMutexSem(_mm_mutex_##name) +#elif defined(WIN32) || defined(WIN64) +#include +#define DECLARE_MUTEX(name) \ + extern HANDLE _mm_mutex_##name +#define MUTEX_LOCK(name) \ + if(_mm_mutex_##name) \ + WaitForSingleObject(_mm_mutex_##name,INFINITE) +#define MUTEX_UNLOCK(name) \ + if(_mm_mutex_##name) \ + ReleaseMutex(_mm_mutex_##name) +#else +#define DECLARE_MUTEX(name) \ + extern void *_mm_mutex_##name +#define MUTEX_LOCK(name) +#define MUTEX_UNLOCK(name) +#endif + +DECLARE_MUTEX(lists); +DECLARE_MUTEX(vars); + +/*========== Portable file I/O */ + +extern MREADER* _mm_new_mem_reader(const void *buffer, int len); +extern void _mm_delete_mem_reader(MREADER *reader); + +extern MREADER* _mm_new_file_reader(FILE* fp); +extern void _mm_delete_file_reader(MREADER*); + +extern MWRITER* _mm_new_file_writer(FILE *fp); +extern void _mm_delete_file_writer(MWRITER*); + +extern BOOL _mm_FileExists(CHAR *fname); + +#define _mm_write_SBYTE(x,y) y->Put(y,(int)x) +#define _mm_write_UBYTE(x,y) y->Put(y,(int)x) + +#define _mm_read_SBYTE(x) (SBYTE)x->Get(x) +#define _mm_read_UBYTE(x) (UBYTE)x->Get(x) + +#define _mm_write_SBYTES(x,y,z) z->Write(z,(void *)x,y) +#define _mm_write_UBYTES(x,y,z) z->Write(z,(void *)x,y) +#define _mm_read_SBYTES(x,y,z) z->Read(z,(void *)x,y) +#define _mm_read_UBYTES(x,y,z) z->Read(z,(void *)x,y) + +#define _mm_fseek(x,y,z) x->Seek(x,y,z) +#define _mm_ftell(x) x->Tell(x) +#define _mm_rewind(x) _mm_fseek(x,0,SEEK_SET) + +#define _mm_eof(x) x->Eof(x) + +extern void _mm_iobase_setcur(MREADER*); +extern void _mm_iobase_revert(MREADER*); +extern FILE *_mm_fopen(CHAR*,CHAR*); +extern int _mm_fclose(FILE *); +extern void _mm_write_string(CHAR*,MWRITER*); +extern int _mm_read_string (CHAR*,int,MREADER*); + +extern SWORD _mm_read_M_SWORD(MREADER*); +extern SWORD _mm_read_I_SWORD(MREADER*); +extern UWORD _mm_read_M_UWORD(MREADER*); +extern UWORD _mm_read_I_UWORD(MREADER*); + +extern SLONG _mm_read_M_SLONG(MREADER*); +extern SLONG _mm_read_I_SLONG(MREADER*); +extern ULONG _mm_read_M_ULONG(MREADER*); +extern ULONG _mm_read_I_ULONG(MREADER*); + +extern int _mm_read_M_SWORDS(SWORD*,int,MREADER*); +extern int _mm_read_I_SWORDS(SWORD*,int,MREADER*); +extern int _mm_read_M_UWORDS(UWORD*,int,MREADER*); +extern int _mm_read_I_UWORDS(UWORD*,int,MREADER*); + +extern int _mm_read_M_SLONGS(SLONG*,int,MREADER*); +extern int _mm_read_I_SLONGS(SLONG*,int,MREADER*); +extern int _mm_read_M_ULONGS(ULONG*,int,MREADER*); +extern int _mm_read_I_ULONGS(ULONG*,int,MREADER*); + +extern void _mm_write_M_SWORD(SWORD,MWRITER*); +extern void _mm_write_I_SWORD(SWORD,MWRITER*); +extern void _mm_write_M_UWORD(UWORD,MWRITER*); +extern void _mm_write_I_UWORD(UWORD,MWRITER*); + +extern void _mm_write_M_SLONG(SLONG,MWRITER*); +extern void _mm_write_I_SLONG(SLONG,MWRITER*); +extern void _mm_write_M_ULONG(ULONG,MWRITER*); +extern void _mm_write_I_ULONG(ULONG,MWRITER*); + +extern void _mm_write_M_SWORDS(SWORD*,int,MWRITER*); +extern void _mm_write_I_SWORDS(SWORD*,int,MWRITER*); +extern void _mm_write_M_UWORDS(UWORD*,int,MWRITER*); +extern void _mm_write_I_UWORDS(UWORD*,int,MWRITER*); + +extern void _mm_write_M_SLONGS(SLONG*,int,MWRITER*); +extern void _mm_write_I_SLONGS(SLONG*,int,MWRITER*); +extern void _mm_write_M_ULONGS(ULONG*,int,MWRITER*); +extern void _mm_write_I_ULONGS(ULONG*,int,MWRITER*); + +/*========== Samples */ + +/* This is a handle of sorts attached to any sample registered with + SL_RegisterSample. Generally, this only need be used or changed by the + loaders and drivers of mikmod. */ +typedef struct SAMPLOAD { + struct SAMPLOAD *next; + + ULONG length; /* length of sample (in samples!) */ + ULONG loopstart; /* repeat position (relative to start, in samples) */ + ULONG loopend; /* repeat end */ + UWORD infmt,outfmt; + int scalefactor; + SAMPLE* sample; + MREADER* reader; +} SAMPLOAD; + +/*========== Sample and waves loading interface */ + +extern void SL_HalveSample(SAMPLOAD*,int); +extern void SL_Sample8to16(SAMPLOAD*); +extern void SL_Sample16to8(SAMPLOAD*); +extern void SL_SampleSigned(SAMPLOAD*); +extern void SL_SampleUnsigned(SAMPLOAD*); +extern BOOL SL_LoadSamples(void); +extern SAMPLOAD* SL_RegisterSample(SAMPLE*,int,MREADER*); +extern BOOL SL_Load(void*,SAMPLOAD*,ULONG); +extern BOOL SL_Init(SAMPLOAD*); +extern void SL_Exit(SAMPLOAD*); + +/*========== Internal module representation (UniMod) interface */ + +/* number of notes in an octave */ +#define OCTAVE 12 + +extern void UniSetRow(UBYTE*); +extern UBYTE UniGetByte(void); +extern UWORD UniGetWord(void); +extern UBYTE* UniFindRow(UBYTE*,UWORD); +extern void UniSkipOpcode(void); +extern void UniReset(void); +extern void UniWriteByte(UBYTE); +extern void UniWriteWord(UWORD); +extern void UniNewline(void); +extern UBYTE* UniDup(void); +extern BOOL UniInit(void); +extern void UniCleanup(void); +extern void UniEffect(UWORD,UWORD); +#define UniInstrument(x) UniEffect(UNI_INSTRUMENT,x) +#define UniNote(x) UniEffect(UNI_NOTE,x) +extern void UniPTEffect(UBYTE,UBYTE); +extern void UniVolEffect(UWORD,UBYTE); + +/*========== Module Commands */ + +enum { + /* Simple note */ + UNI_NOTE = 1, + /* Instrument change */ + UNI_INSTRUMENT, + /* Protracker effects */ + UNI_PTEFFECT0, /* arpeggio */ + UNI_PTEFFECT1, /* porta up */ + UNI_PTEFFECT2, /* porta down */ + UNI_PTEFFECT3, /* porta to note */ + UNI_PTEFFECT4, /* vibrato */ + UNI_PTEFFECT5, /* dual effect 3+A */ + UNI_PTEFFECT6, /* dual effect 4+A */ + UNI_PTEFFECT7, /* tremolo */ + UNI_PTEFFECT8, /* pan */ + UNI_PTEFFECT9, /* sample offset */ + UNI_PTEFFECTA, /* volume slide */ + UNI_PTEFFECTB, /* pattern jump */ + UNI_PTEFFECTC, /* set volume */ + UNI_PTEFFECTD, /* pattern break */ + UNI_PTEFFECTE, /* extended effects */ + UNI_PTEFFECTF, /* set speed */ + /* Scream Tracker effects */ + UNI_S3MEFFECTA, /* set speed */ + UNI_S3MEFFECTD, /* volume slide */ + UNI_S3MEFFECTE, /* porta down */ + UNI_S3MEFFECTF, /* porta up */ + UNI_S3MEFFECTI, /* tremor */ + UNI_S3MEFFECTQ, /* retrig */ + UNI_S3MEFFECTR, /* tremolo */ + UNI_S3MEFFECTT, /* set tempo */ + UNI_S3MEFFECTU, /* fine vibrato */ + UNI_KEYOFF, /* note off */ + /* Fast Tracker effects */ + UNI_KEYFADE, /* note fade */ + UNI_VOLEFFECTS, /* volume column effects */ + UNI_XMEFFECT4, /* vibrato */ + UNI_XMEFFECT6, /* dual effect 4+A */ + UNI_XMEFFECTA, /* volume slide */ + UNI_XMEFFECTE1, /* fine porta up */ + UNI_XMEFFECTE2, /* fine porta down */ + UNI_XMEFFECTEA, /* fine volume slide up */ + UNI_XMEFFECTEB, /* fine volume slide down */ + UNI_XMEFFECTG, /* set global volume */ + UNI_XMEFFECTH, /* global volume slide */ + UNI_XMEFFECTL, /* set envelope position */ + UNI_XMEFFECTP, /* pan slide */ + UNI_XMEFFECTX1, /* extra fine porta up */ + UNI_XMEFFECTX2, /* extra fine porta down */ + /* Impulse Tracker effects */ + UNI_ITEFFECTG, /* porta to note */ + UNI_ITEFFECTH, /* vibrato */ + UNI_ITEFFECTI, /* tremor (xy not incremented) */ + UNI_ITEFFECTM, /* set channel volume */ + UNI_ITEFFECTN, /* slide / fineslide channel volume */ + UNI_ITEFFECTP, /* slide / fineslide channel panning */ + UNI_ITEFFECTT, /* slide tempo */ + UNI_ITEFFECTU, /* fine vibrato */ + UNI_ITEFFECTW, /* slide / fineslide global volume */ + UNI_ITEFFECTY, /* panbrello */ + UNI_ITEFFECTZ, /* resonant filters */ + UNI_ITEFFECTS0, + /* UltraTracker effects */ + UNI_ULTEFFECT9, /* Sample fine offset */ + /* OctaMED effects */ + UNI_MEDSPEED, + UNI_MEDEFFECTF1, /* play note twice */ + UNI_MEDEFFECTF2, /* delay note */ + UNI_MEDEFFECTF3, /* play note three times */ + /* Oktalyzer effects */ + UNI_OKTARP, /* arpeggio */ + + UNI_LAST +}; + +extern UWORD unioperands[UNI_LAST]; + +/* IT / S3M Extended SS effects: */ +enum { + SS_GLISSANDO = 1, + SS_FINETUNE, + SS_VIBWAVE, + SS_TREMWAVE, + SS_PANWAVE, + SS_FRAMEDELAY, + SS_S7EFFECTS, + SS_PANNING, + SS_SURROUND, + SS_HIOFFSET, + SS_PATLOOP, + SS_NOTECUT, + SS_NOTEDELAY, + SS_PATDELAY +}; + +/* IT Volume column effects */ +enum { + VOL_VOLUME = 1, + VOL_PANNING, + VOL_VOLSLIDE, + VOL_PITCHSLIDEDN, + VOL_PITCHSLIDEUP, + VOL_PORTAMENTO, + VOL_VIBRATO +}; + +/* IT resonant filter information */ + +#define UF_MAXMACRO 0x10 +#define UF_MAXFILTER 0x100 + +#define FILT_CUT 0x80 +#define FILT_RESONANT 0x81 + +typedef struct FILTER { + UBYTE filter,inf; +} FILTER; + +/*========== Instruments */ + +/* Instrument format flags */ +#define IF_OWNPAN 1 +#define IF_PITCHPAN 2 + +/* Envelope flags: */ +#define EF_ON 1 +#define EF_SUSTAIN 2 +#define EF_LOOP 4 +#define EF_VOLENV 8 + +/* New Note Action Flags */ +#define NNA_CUT 0 +#define NNA_CONTINUE 1 +#define NNA_OFF 2 +#define NNA_FADE 3 + +#define NNA_MASK 3 + +#define DCT_OFF 0 +#define DCT_NOTE 1 +#define DCT_SAMPLE 2 +#define DCT_INST 3 + +#define DCA_CUT 0 +#define DCA_OFF 1 +#define DCA_FADE 2 + +#define KEY_KICK 0 +#define KEY_OFF 1 +#define KEY_FADE 2 +#define KEY_KILL (KEY_OFF|KEY_FADE) + +#define KICK_ABSENT 0 +#define KICK_NOTE 1 +#define KICK_KEYOFF 2 +#define KICK_ENV 4 + +#define AV_IT 1 /* IT vs. XM vibrato info */ + +/*========== Playing */ + +#define POS_NONE (-2) /* no loop position defined */ + +#define LAST_PATTERN (UWORD)(-1) /* special ``end of song'' pattern */ + +typedef struct ENVPR { + UBYTE flg; /* envelope flag */ + UBYTE pts; /* number of envelope points */ + UBYTE susbeg; /* envelope sustain index begin */ + UBYTE susend; /* envelope sustain index end */ + UBYTE beg; /* envelope loop begin */ + UBYTE end; /* envelope loop end */ + SWORD p; /* current envelope counter */ + UWORD a; /* envelope index a */ + UWORD b; /* envelope index b */ + ENVPT* env; /* envelope points */ +} ENVPR; + +typedef struct MP_CHANNEL { + INSTRUMENT* i; + SAMPLE* s; + UBYTE sample; /* which sample number */ + UBYTE note; /* the audible note as heard, direct rep of period */ + SWORD outvolume; /* output volume (vol + sampcol + instvol) */ + SBYTE chanvol; /* channel's "global" volume */ + UWORD fadevol; /* fading volume rate */ + SWORD panning; /* panning position */ + UBYTE kick; /* if true = sample has to be restarted */ + UWORD period; /* period to play the sample at */ + UBYTE nna; /* New note action type + master/slave flags */ + + UBYTE volflg; /* volume envelope settings */ + UBYTE panflg; /* panning envelope settings */ + UBYTE pitflg; /* pitch envelope settings */ + + UBYTE keyoff; /* if true = fade out and stuff */ + SWORD handle; /* which sample-handle */ + UBYTE notedelay; /* (used for note delay) */ + SLONG start; /* The starting byte index in the sample */ +} MP_CHANNEL; + +typedef struct MP_CONTROL { + struct MP_CHANNEL main; + + struct MP_VOICE *slave; /* Audio Slave of current effects control channel */ + + UBYTE slavechn; /* Audio Slave of current effects control channel */ + UBYTE muted; /* if set, channel not played */ + UWORD ultoffset; /* fine sample offset memory */ + UBYTE anote; /* the note that indexes the audible */ + UBYTE oldnote; + SWORD ownper; + SWORD ownvol; + UBYTE dca; /* duplicate check action */ + UBYTE dct; /* duplicate check type */ + UBYTE* row; /* row currently playing on this channel */ + SBYTE retrig; /* retrig value (0 means don't retrig) */ + ULONG speed; /* what finetune to use */ + SWORD volume; /* amiga volume (0 t/m 64) to play the sample at */ + + SWORD tmpvolume; /* tmp volume */ + UWORD tmpperiod; /* tmp period */ + UWORD wantedperiod; /* period to slide to (with effect 3 or 5) */ + + UBYTE arpmem; /* arpeggio command memory */ + UBYTE pansspd; /* panslide speed */ + UWORD slidespeed; + UWORD portspeed; /* noteslide speed (toneportamento) */ + + UBYTE s3mtremor; /* s3m tremor (effect I) counter */ + UBYTE s3mtronof; /* s3m tremor ontime/offtime */ + UBYTE s3mvolslide; /* last used volslide */ + SBYTE sliding; + UBYTE s3mrtgspeed; /* last used retrig speed */ + UBYTE s3mrtgslide; /* last used retrig slide */ + + UBYTE glissando; /* glissando (0 means off) */ + UBYTE wavecontrol; + + SBYTE vibpos; /* current vibrato position */ + UBYTE vibspd; /* "" speed */ + UBYTE vibdepth; /* "" depth */ + + SBYTE trmpos; /* current tremolo position */ + UBYTE trmspd; /* "" speed */ + UBYTE trmdepth; /* "" depth */ + + UBYTE fslideupspd; + UBYTE fslidednspd; + UBYTE fportupspd; /* fx E1 (extra fine portamento up) data */ + UBYTE fportdnspd; /* fx E2 (extra fine portamento dn) data */ + UBYTE ffportupspd; /* fx X1 (extra fine portamento up) data */ + UBYTE ffportdnspd; /* fx X2 (extra fine portamento dn) data */ + + ULONG hioffset; /* last used high order of sample offset */ + UWORD soffset; /* last used low order of sample-offset (effect 9) */ + + UBYTE sseffect; /* last used Sxx effect */ + UBYTE ssdata; /* last used Sxx data info */ + UBYTE chanvolslide; /* last used channel volume slide */ + + UBYTE panbwave; /* current panbrello waveform */ + UBYTE panbpos; /* current panbrello position */ + SBYTE panbspd; /* "" speed */ + UBYTE panbdepth; /* "" depth */ + + UWORD newsamp; /* set to 1 upon a sample / inst change */ + UBYTE voleffect; /* Volume Column Effect Memory as used by IT */ + UBYTE voldata; /* Volume Column Data Memory */ + + SWORD pat_reppos; /* patternloop position */ + UWORD pat_repcnt; /* times to loop */ +} MP_CONTROL; + +/* Used by NNA only player (audio control. AUDTMP is used for full effects + control). */ +typedef struct MP_VOICE { + struct MP_CHANNEL main; + + ENVPR venv; + ENVPR penv; + ENVPR cenv; + + UWORD avibpos; /* autovibrato pos */ + UWORD aswppos; /* autovibrato sweep pos */ + + ULONG totalvol; /* total volume of channel (before global mixings) */ + + BOOL mflag; + SWORD masterchn; + UWORD masterperiod; + + MP_CONTROL* master; /* index of "master" effects channel */ +} MP_VOICE; + +/*========== Loaders */ + +typedef struct MLOADER { +struct MLOADER* next; + CHAR* type; + CHAR* version; + BOOL (*Init)(void); + BOOL (*Test)(void); + BOOL (*Load)(BOOL); + void (*Cleanup)(void); + CHAR* (*LoadTitle)(void); +} MLOADER; + +/* internal loader variables */ +extern MREADER* modreader; +extern UWORD finetune[16]; +extern MODULE of; /* static unimod loading space */ +extern UWORD npertab[7*OCTAVE]; /* used by the original MOD loaders */ + +extern SBYTE remap[UF_MAXCHAN]; /* for removing empty channels */ +extern UBYTE* poslookup; /* lookup table for pattern jumps after + blank pattern removal */ +extern UBYTE poslookupcnt; +extern UWORD* origpositions; + +extern BOOL filters; /* resonant filters in use */ +extern UBYTE activemacro; /* active midi macro number for Sxx */ +extern UBYTE filtermacros[UF_MAXMACRO]; /* midi macro settings */ +extern FILTER filtersettings[UF_MAXFILTER]; /* computed filter settings */ + +extern int* noteindex; + +/*========== Internal loader interface */ + +extern BOOL ReadComment(UWORD); +extern BOOL ReadLinedComment(UWORD,UWORD); +extern BOOL AllocPositions(int); +extern BOOL AllocPatterns(void); +extern BOOL AllocTracks(void); +extern BOOL AllocInstruments(void); +extern BOOL AllocSamples(void); +extern CHAR* DupStr(CHAR*,UWORD,BOOL); + +/* loader utility functions */ +extern int* AllocLinear(void); +extern void FreeLinear(void); +extern int speed_to_finetune(ULONG,int); +extern void S3MIT_ProcessCmd(UBYTE,UBYTE,unsigned int); +extern void S3MIT_CreateOrders(BOOL); + +/* flags for S3MIT_ProcessCmd */ +#define S3MIT_OLDSTYLE 1 /* behave as old scream tracker */ +#define S3MIT_IT 2 /* behave as impulse tracker */ +#define S3MIT_SCREAM 4 /* enforce scream tracker specific limits */ + +/* used to convert c4spd to linear XM periods (IT and IMF loaders). */ +extern UWORD getlinearperiod(UWORD,ULONG); +extern ULONG getfrequency(UWORD,ULONG); + +/* loader shared data */ +#define STM_NTRACKERS 3 +extern CHAR *STM_Signatures[STM_NTRACKERS]; + +/*========== Player interface */ + +extern BOOL Player_Init(MODULE*); +extern void Player_Exit(MODULE*); +extern void Player_HandleTick(void); + +/*========== Drivers */ + +/* max. number of handles a driver has to provide. (not strict) */ +#define MAXSAMPLEHANDLES 384 + +/* These variables can be changed at ANY time and results will be immediate */ +extern UWORD md_bpm; /* current song / hardware BPM rate */ + +/* Variables below can be changed via MD_SetNumVoices at any time. However, a + call to MD_SetNumVoicess while the driver is active will cause the sound to + skip slightly. */ +extern UBYTE md_numchn; /* number of song + sound effects voices */ +extern UBYTE md_sngchn; /* number of song voices */ +extern UBYTE md_sfxchn; /* number of sound effects voices */ +extern UBYTE md_hardchn; /* number of hardware mixed voices */ +extern UBYTE md_softchn; /* number of software mixed voices */ + +/* This is for use by the hardware drivers only. It points to the registered + tickhandler function. */ +extern void (*md_player)(void); + +extern SWORD MD_SampleLoad(SAMPLOAD*,int); +extern void MD_SampleUnload(SWORD); +extern ULONG MD_SampleSpace(int); +extern ULONG MD_SampleLength(int,SAMPLE*); + +/* uLaw conversion */ +extern void unsignedtoulaw(char *,int); + +/* Parameter extraction helper */ +extern CHAR *MD_GetAtom(CHAR*,CHAR*,BOOL); + +/* Internal software mixer stuff */ +extern void VC_SetupPointers(void); +extern BOOL VC1_Init(void); +extern BOOL VC2_Init(void); + +#if defined(unix) || defined(__APPLE__) && defined(__MACH__) +/* POSIX helper functions */ +extern BOOL MD_Access(CHAR *); +extern BOOL MD_DropPrivileges(void); +#endif + +/* Macro to define a missing driver, yet allowing binaries to dynamically link + with the library without missing symbol errors */ +#define MISSING(a) MDRIVER a = { NULL, NULL, NULL, 0, 0 } + +/*========== Prototypes for non-MT safe versions of some public functions */ + +extern void _mm_registerdriver(struct MDRIVER*); +extern void _mm_registerloader(struct MLOADER*); +extern BOOL MikMod_Active_internal(void); +extern void MikMod_DisableOutput_internal(void); +extern BOOL MikMod_EnableOutput_internal(void); +extern void MikMod_Exit_internal(void); +extern BOOL MikMod_SetNumVoices_internal(int,int); +extern void Player_Exit_internal(MODULE*); +extern void Player_Stop_internal(void); +extern BOOL Player_Paused_internal(void); +extern void Sample_Free_internal(SAMPLE*); +extern void Voice_Play_internal(SBYTE,SAMPLE*,ULONG); +extern void Voice_SetFrequency_internal(SBYTE,ULONG); +extern void Voice_SetPanning_internal(SBYTE,ULONG); +extern void Voice_SetVolume_internal(SBYTE,UWORD); +extern void Voice_Stop_internal(SBYTE); +extern BOOL Voice_Stopped_internal(SBYTE); + +#ifdef __cplusplus +} +#endif + +#endif + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mloader.c b/src/libs/mikmod/mloader.c new file mode 100644 index 0000000..494334c --- /dev/null +++ b/src/libs/mikmod/mloader.c @@ -0,0 +1,607 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file + AUTHORS for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + These routines are used to access the available module loaders + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_MEMORY_H +#include +#endif +#include + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + + MREADER *modreader; + MODULE of; + +static MLOADER *firstloader=NULL; + +UWORD finetune[16]={ + 8363,8413,8463,8529,8581,8651,8723,8757, + 7895,7941,7985,8046,8107,8169,8232,8280 +}; + +MIKMODAPI CHAR* MikMod_InfoLoader(void) +{ + int len=0; + MLOADER *l; + CHAR *list=NULL; + + MUTEX_LOCK(lists); + /* compute size of buffer */ + for(l=firstloader;l;l=l->next) len+=1+(l->next?1:0)+strlen(l->version); + + if(len) + if((list=MikMod_malloc(len*sizeof(CHAR)))) { + list[0]=0; + /* list all registered module loders */ + for(l=firstloader;l;l=l->next) + sprintf(list,(l->next)?"%s%s\n":"%s%s",list,l->version); + } + MUTEX_UNLOCK(lists); + return list; +} + +void _mm_registerloader(MLOADER* ldr) +{ + MLOADER *cruise=firstloader; + + if(cruise) { + while(cruise->next) cruise = cruise->next; + cruise->next=ldr; + } else + firstloader=ldr; +} + +MIKMODAPI void MikMod_RegisterLoader(struct MLOADER* ldr) +{ + /* if we try to register an invalid loader, or an already registered loader, + ignore this attempt */ + if ((!ldr)||(ldr->next)) + return; + + MUTEX_LOCK(lists); + _mm_registerloader(ldr); + MUTEX_UNLOCK(lists); +} + +BOOL ReadComment(UWORD len) +{ + if(len) { + int i; + + if(!(of.comment=(CHAR*)MikMod_malloc(len+1))) return 0; + _mm_read_UBYTES(of.comment,len,modreader); + + /* translate IT linefeeds */ + for(i=0;i=0)&&(line[i]==' ');i--) line[i]=0; + for(i=0;ilines) { + if(!(of.comment=(CHAR*)MikMod_malloc(total+1))) { + MikMod_free(storage); + MikMod_free(tempcomment); + return 0; + } + + /* convert message */ + for(line=tempcomment,t=0;tlength) SL_RegisterSample(s,MD_MUSIC,modreader); + + return 1; +} + +/* Creates a CSTR out of a character buffer of 'len' bytes, but strips any + terminating non-printing characters like 0, spaces etc. */ +CHAR *DupStr(CHAR* s,UWORD len,BOOL strict) +{ + UWORD t; + CHAR *d=NULL; + + /* Scan for last printing char in buffer [includes high ascii up to 254] */ + while(len) { + if(s[len-1]>0x20) break; + len--; + } + + /* Scan forward for possible NULL character */ + if(strict) { + for(t=0;thandle>=0) + MD_SampleUnload(s->handle); + if(s->samplename) MikMod_free(s->samplename); +} + +static void ML_XFreeInstrument(INSTRUMENT *i) +{ + if(i->insname) MikMod_free(i->insname); +} + +static void ML_FreeEx(MODULE *mf) +{ + UWORD t; + + if(mf->songname) MikMod_free(mf->songname); + if(mf->comment) MikMod_free(mf->comment); + + if(mf->modtype) MikMod_free(mf->modtype); + if(mf->positions) MikMod_free(mf->positions); + if(mf->patterns) MikMod_free(mf->patterns); + if(mf->pattrows) MikMod_free(mf->pattrows); + + if(mf->tracks) { + for(t=0;tnumtrk;t++) + if(mf->tracks[t]) MikMod_free(mf->tracks[t]); + MikMod_free(mf->tracks); + } + if(mf->instruments) { + for(t=0;tnumins;t++) + ML_XFreeInstrument(&mf->instruments[t]); + MikMod_free(mf->instruments); + } + if(mf->samples) { + for(t=0;tnumsmp;t++) + if(mf->samples[t].length) ML_XFreeSample(&mf->samples[t]); + MikMod_free(mf->samples); + } + memset(mf,0,sizeof(MODULE)); + if(mf!=&of) MikMod_free(mf); +} + +static MODULE *ML_AllocUniMod(void) +{ + MODULE *mf; + + return (mf=MikMod_malloc(sizeof(MODULE))); +} + +void Player_Free_internal(MODULE *mf) +{ + if(mf) { + Player_Exit_internal(mf); + ML_FreeEx(mf); + } +} + +MIKMODAPI void Player_Free(MODULE *mf) +{ + MUTEX_LOCK(vars); + Player_Free_internal(mf); + MUTEX_UNLOCK(vars); +} + +static CHAR* Player_LoadTitle_internal(MREADER *reader) +{ + MLOADER *l; + + modreader=reader; + _mm_errno = 0; + _mm_critical = 0; + _mm_iobase_setcur(modreader); + + /* Try to find a loader that recognizes the module */ + for(l=firstloader;l;l=l->next) { + _mm_rewind(modreader); + if(l->Test()) break; + } + + if(!l) { + _mm_errno = MMERR_NOT_A_MODULE; + if(_mm_errorhandler) _mm_errorhandler(); + return NULL; + } + + return l->LoadTitle(); +} + +MIKMODAPI CHAR* Player_LoadTitleFP(FILE *fp) +{ + CHAR* result=NULL; + MREADER* reader; + + if(fp && (reader=_mm_new_file_reader(fp))) { + MUTEX_LOCK(lists); + result=Player_LoadTitle_internal(reader); + MUTEX_UNLOCK(lists); + _mm_delete_file_reader(reader); + } + return result; +} + +MIKMODAPI CHAR* Player_LoadTitleMem(const char *buffer,int len) +{ + CHAR *result=NULL; + MREADER* reader; + + if ((reader=_mm_new_mem_reader(buffer,len))) + { + MUTEX_LOCK(lists); + result=Player_LoadTitle_internal(reader); + MUTEX_UNLOCK(lists); + _mm_delete_mem_reader(reader); + } + + + return result; +} + +MIKMODAPI CHAR* Player_LoadTitleGeneric(MREADER *reader) +{ + CHAR *result=NULL; + + if (reader) { + MUTEX_LOCK(lists); + result=Player_LoadTitle_internal(reader); + MUTEX_UNLOCK(lists); + } + return result; +} + +MIKMODAPI CHAR* Player_LoadTitle(CHAR* filename) +{ + CHAR* result=NULL; + FILE* fp; + MREADER* reader; + + if((fp=_mm_fopen(filename,"rb"))) { + if((reader=_mm_new_file_reader(fp))) { + MUTEX_LOCK(lists); + result=Player_LoadTitle_internal(reader); + MUTEX_UNLOCK(lists); + _mm_delete_file_reader(reader); + } + _mm_fclose(fp); + } + return result; +} + +/* Loads a module given an reader */ +MODULE* Player_LoadGeneric_internal(MREADER *reader,int maxchan,BOOL curious) +{ + int t; + MLOADER *l; + BOOL ok; + MODULE *mf; + + modreader = reader; + _mm_errno = 0; + _mm_critical = 0; + _mm_iobase_setcur(modreader); + + /* Try to find a loader that recognizes the module */ + for(l=firstloader;l;l=l->next) { + _mm_rewind(modreader); + if(l->Test()) break; + } + + if(!l) { + _mm_errno = MMERR_NOT_A_MODULE; + if(_mm_errorhandler) _mm_errorhandler(); + _mm_rewind(modreader);_mm_iobase_revert(modreader); + return NULL; + } + + /* init unitrk routines */ + if(!UniInit()) { + if(_mm_errorhandler) _mm_errorhandler(); + _mm_rewind(modreader);_mm_iobase_revert(modreader); + return NULL; + } + + /* init the module structure with vanilla settings */ + memset(&of,0,sizeof(MODULE)); + of.bpmlimit = 33; + of.initvolume = 128; + for (t = 0; t < UF_MAXCHAN; t++) of.chanvol[t] = 64; + for (t = 0; t < UF_MAXCHAN; t++) + of.panning[t] = ((t + 1) & 2) ? PAN_RIGHT : PAN_LEFT; + + /* init module loader and load the header / patterns */ + if (!l->Init || l->Init()) { + _mm_rewind(modreader); + ok = l->Load(curious); + if (ok) { + /* propagate inflags=flags for in-module samples */ + for (t = 0; t < of.numsmp; t++) + if (of.samples[t].inflags == 0) + of.samples[t].inflags = of.samples[t].flags; + } + } else + ok = 0; + + /* free loader and unitrk allocations */ + if (l->Cleanup) l->Cleanup(); + UniCleanup(); + + if(!ok) { + ML_FreeEx(&of); + if(_mm_errorhandler) _mm_errorhandler(); + _mm_rewind(modreader);_mm_iobase_revert(modreader); + return NULL; + } + + if(!ML_LoadSamples()) { + ML_FreeEx(&of); + if(_mm_errorhandler) _mm_errorhandler(); + _mm_rewind(modreader);_mm_iobase_revert(modreader); + return NULL; + } + + if(!(mf=ML_AllocUniMod())) { + ML_FreeEx(&of); + _mm_rewind(modreader);_mm_iobase_revert(modreader); + if(_mm_errorhandler) _mm_errorhandler(); + return NULL; + } + + /* If the module doesn't have any specific panning, create a + MOD-like panning, with the channels half-separated. */ + if (!(of.flags & UF_PANNING)) + for (t = 0; t < of.numchn; t++) + of.panning[t] = ((t + 1) & 2) ? PAN_HALFRIGHT : PAN_HALFLEFT; + + /* Copy the static MODULE contents into the dynamic MODULE struct. */ + memcpy(mf,&of,sizeof(MODULE)); + + if(maxchan>0) { + if(!(mf->flags&UF_NNA)&&(mf->numchnnumchn; + else + if((mf->numvoices)&&(mf->numvoicesnumvoices; + + if(maxchannumchn) mf->flags |= UF_NNA; + + if(MikMod_SetNumVoices_internal(maxchan,-1)) { + _mm_iobase_revert(modreader); + Player_Free(mf); + return NULL; + } + } + if(SL_LoadSamples()) { + _mm_iobase_revert(modreader); + Player_Free_internal(mf); + return NULL; + } + if(Player_Init(mf)) { + _mm_iobase_revert(modreader); + Player_Free_internal(mf); + mf=NULL; + } + _mm_iobase_revert(modreader); + return mf; +} + +MIKMODAPI MODULE* Player_LoadGeneric(MREADER *reader,int maxchan,BOOL curious) +{ + MODULE* result; + + MUTEX_LOCK(vars); + MUTEX_LOCK(lists); + result=Player_LoadGeneric_internal(reader,maxchan,curious); + MUTEX_UNLOCK(lists); + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI MODULE* Player_LoadMem(const char *buffer,int len,int maxchan,BOOL curious) +{ + MODULE* result=NULL; + MREADER* reader; + + if ((reader=_mm_new_mem_reader(buffer, len))) { + result=Player_LoadGeneric(reader,maxchan,curious); + _mm_delete_mem_reader(reader); + } + return result; +} + +/* Loads a module given a file pointer. + File is loaded from the current file seek position. */ +MIKMODAPI MODULE* Player_LoadFP(FILE* fp,int maxchan,BOOL curious) +{ + MODULE* result=NULL; + struct MREADER* reader=_mm_new_file_reader (fp); + + if (reader) { + result=Player_LoadGeneric(reader,maxchan,curious); + _mm_delete_file_reader(reader); + } + return result; +} + +/* Open a module via its filename. The loader will initialize the specified + song-player 'player'. */ +MIKMODAPI MODULE* Player_Load(CHAR* filename,int maxchan,BOOL curious) +{ + FILE *fp; + MODULE *mf=NULL; + + if((fp=_mm_fopen(filename,"rb"))) { + mf=Player_LoadFP(fp,maxchan,curious); + _mm_fclose(fp); + } + return mf; +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mlreg.c b/src/libs/mikmod/mlreg.c new file mode 100644 index 0000000..14f2d7f --- /dev/null +++ b/src/libs/mikmod/mlreg.c @@ -0,0 +1,50 @@ +/* MikMod sound library + (c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Routine for registering all loaders in libmikmod for the current platform. + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mikmod_internals.h" + +void MikMod_RegisterAllLoaders_internal(void) +{ + _mm_registerloader(&load_it); + _mm_registerloader(&load_mod); + _mm_registerloader(&load_s3m); + _mm_registerloader(&load_stm); + _mm_registerloader(&load_xm); +} + +void MikMod_RegisterAllLoaders(void) +{ + MUTEX_LOCK(lists); + MikMod_RegisterAllLoaders_internal(); + MUTEX_UNLOCK(lists); +} +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mlutil.c b/src/libs/mikmod/mlutil.c new file mode 100644 index 0000000..2ecf64e --- /dev/null +++ b/src/libs/mikmod/mlutil.c @@ -0,0 +1,337 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Utility functions for the module loader + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_MEMORY_H +#include +#endif +#include + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + +/*========== Shared tracker identifiers */ + +CHAR *STM_Signatures[STM_NTRACKERS] = { + "!Scream!", + "BMOD2STM", + "WUZAMOD!" +}; + +CHAR *STM_Version[STM_NTRACKERS] = { + "Screamtracker 2", + "Converted by MOD2STM (STM format)", + "Wuzamod (STM format)" +}; + +/*========== Shared loader variables */ + +SBYTE remap[UF_MAXCHAN]; /* for removing empty channels */ +UBYTE* poslookup=NULL; /* lookup table for pattern jumps after blank + pattern removal */ +UBYTE poslookupcnt; +UWORD* origpositions=NULL; + +BOOL filters; /* resonant filters in use */ +UBYTE activemacro; /* active midi macro number for Sxx,xx<80h */ +UBYTE filtermacros[UF_MAXMACRO]; /* midi macro settings */ +FILTER filtersettings[UF_MAXFILTER]; /* computed filter settings */ + +/*========== Linear periods stuff */ + +int* noteindex=NULL; /* remap value for linear period modules */ +static int noteindexcount=0; + +int *AllocLinear(void) +{ + if(of.numsmp>noteindexcount) { + noteindexcount=of.numsmp; + noteindex=realloc(noteindex,noteindexcount*sizeof(int)); + } + return noteindex; +} + +void FreeLinear(void) +{ + if(noteindex) { + MikMod_free(noteindex); + noteindex=NULL; + } + noteindexcount=0; +} + +int speed_to_finetune(ULONG speed,int sample) +{ + int note=1,finetune=0; + ULONG ctmp=0,tmp; + + speed>>=1; + while((tmp=getfrequency(of.flags,getlinearperiod(note<<1,0)))speed) + tmp=getfrequency(of.flags,getlinearperiod(note<<1,--finetune)); + else { + note--; + while(ctmp>4; + + /* process S3M / IT specific command structure */ + + if(cmd!=255) { + switch(cmd) { + case 1: /* Axx set speed to xx */ + UniEffect(UNI_S3MEFFECTA,inf); + break; + case 2: /* Bxx position jump */ + if (inf>4)*10+(inf&0xf)); + else + UniPTEffect(0xd,inf); + break; + case 4: /* Dxy volumeslide */ + UniEffect(UNI_S3MEFFECTD,inf); + break; + case 5: /* Exy toneslide down */ + UniEffect(UNI_S3MEFFECTE,inf); + break; + case 6: /* Fxy toneslide up */ + UniEffect(UNI_S3MEFFECTF,inf); + break; + case 7: /* Gxx Tone portamento, speed xx */ + if (flags & S3MIT_OLDSTYLE) + UniPTEffect(0x3,inf); + else + UniEffect(UNI_ITEFFECTG,inf); + break; + case 8: /* Hxy vibrato */ + if (flags & S3MIT_OLDSTYLE) + UniPTEffect(0x4,inf); + else + UniEffect(UNI_ITEFFECTH,inf); + break; + case 9: /* Ixy tremor, ontime x, offtime y */ + if (flags & S3MIT_OLDSTYLE) + UniEffect(UNI_S3MEFFECTI,inf); + else + UniEffect(UNI_ITEFFECTI,inf); + break; + case 0xa: /* Jxy arpeggio */ + UniPTEffect(0x0,inf); + break; + case 0xb: /* Kxy Dual command H00 & Dxy */ + if (flags & S3MIT_OLDSTYLE) + UniPTEffect(0x4,0); + else + UniEffect(UNI_ITEFFECTH,0); + UniEffect(UNI_S3MEFFECTD,inf); + break; + case 0xc: /* Lxy Dual command G00 & Dxy */ + if (flags & S3MIT_OLDSTYLE) + UniPTEffect(0x3,0); + else + UniEffect(UNI_ITEFFECTG,0); + UniEffect(UNI_S3MEFFECTD,inf); + break; + case 0xd: /* Mxx Set Channel Volume */ + UniEffect(UNI_ITEFFECTM,inf); + break; + case 0xe: /* Nxy Slide Channel Volume */ + UniEffect(UNI_ITEFFECTN,inf); + break; + case 0xf: /* Oxx set sampleoffset xx00h */ + UniPTEffect(0x9,inf); + break; + case 0x10: /* Pxy Slide Panning Commands */ + UniEffect(UNI_ITEFFECTP,inf); + break; + case 0x11: /* Qxy Retrig (+volumeslide) */ + UniWriteByte(UNI_S3MEFFECTQ); + if(inf && !lo && !(flags & S3MIT_OLDSTYLE)) + UniWriteByte(1); + else + UniWriteByte(inf); + break; + case 0x12: /* Rxy tremolo speed x, depth y */ + UniEffect(UNI_S3MEFFECTR,inf); + break; + case 0x13: /* Sxx special commands */ + if (inf>=0xf0) { + /* change resonant filter settings if necessary */ + if((filters)&&((inf&0xf)!=activemacro)) { + activemacro=inf&0xf; + for(inf=0;inf<0x80;inf++) + filtersettings[inf].filter=filtermacros[activemacro]; + } + } else { + /* Scream Tracker does not have samples larger than + 64 Kb, thus doesn't need the SAx effect */ + if ((flags & S3MIT_SCREAM) && ((inf & 0xf0) == 0xa0)) + break; + + UniEffect(UNI_ITEFFECTS0,inf); + } + break; + case 0x14: /* Txx tempo */ + if(inf>=0x20) + UniEffect(UNI_S3MEFFECTT,inf); + else { + if(!(flags & S3MIT_OLDSTYLE)) + /* IT Tempo slide */ + UniEffect(UNI_ITEFFECTT,inf); + } + break; + case 0x15: /* Uxy Fine Vibrato speed x, depth y */ + if(flags & S3MIT_OLDSTYLE) + UniEffect(UNI_S3MEFFECTU,inf); + else + UniEffect(UNI_ITEFFECTU,inf); + break; + case 0x16: /* Vxx Set Global Volume */ + UniEffect(UNI_XMEFFECTG,inf); + break; + case 0x17: /* Wxy Global Volume Slide */ + UniEffect(UNI_ITEFFECTW,inf); + break; + case 0x18: /* Xxx amiga command 8xx */ + if(flags & S3MIT_OLDSTYLE) { + if(inf>128) + UniEffect(UNI_ITEFFECTS0,0x91); /* surround */ + else + UniPTEffect(0x8,(inf==128)?255:(inf<<1)); + } else + UniPTEffect(0x8,inf); + break; + case 0x19: /* Yxy Panbrello speed x, depth y */ + UniEffect(UNI_ITEFFECTY,inf); + break; + case 0x1a: /* Zxx midi/resonant filters */ + if(filtersettings[inf].filter) { + UniWriteByte(UNI_ITEFFECTZ); + UniWriteByte(filtersettings[inf].filter); + UniWriteByte(filtersettings[inf].inf); + } + break; + } + } +} + +/*========== Unitrk stuff */ + +/* Generic effect writing routine */ +void UniEffect(UWORD eff,UWORD dat) +{ + if((!eff)||(eff>=UNI_LAST)) return; + + UniWriteByte(eff); + if(unioperands[eff]==2) + UniWriteWord(dat); + else + UniWriteByte(dat); +} + +/* Appends UNI_PTEFFECTX opcode to the unitrk stream. */ +void UniPTEffect(UBYTE eff, UBYTE dat) +{ +#ifdef MIKMOD_DEBUG + if (eff>=0x10) + fprintf(stderr,"UniPTEffect called with incorrect eff value %d\n",eff); + else +#endif + if((eff)||(dat)||(of.flags&UF_ARPMEM)) UniEffect(UNI_PTEFFECT0+eff,dat); +} + +/* Appends UNI_VOLEFFECT + effect/dat to unistream. */ +void UniVolEffect(UWORD eff,UBYTE dat) +{ + if((eff)||(dat)) { /* don't write empty effect */ + UniWriteByte(UNI_VOLEFFECTS); + UniWriteByte(eff);UniWriteByte(dat); + } +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mmalloc.c b/src/libs/mikmod/mmalloc.c new file mode 100644 index 0000000..cc8f8d9 --- /dev/null +++ b/src/libs/mikmod/mmalloc.c @@ -0,0 +1,73 @@ +/* MikMod sound library + (c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Dynamic memory routines + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mikmod_internals.h" + +void* MikMod_realloc(void *data, size_t size) +{ + if (data) + return realloc(data, size); + else + return MikMod_malloc(size); +} + +/* Same as malloc, but sets error variable _mm_error when fails */ +void* MikMod_malloc(size_t size) +{ + void *d; + + if(!(d=calloc(1,size))) { + _mm_errno = MMERR_OUT_OF_MEMORY; + if(_mm_errorhandler) _mm_errorhandler(); + } + return d; +} + +/* Same as calloc, but sets error variable _mm_error when fails */ +void* MikMod_calloc(size_t nitems,size_t size) +{ + void *d; + + if(!(d=calloc(nitems,size))) { + _mm_errno = MMERR_OUT_OF_MEMORY; + if(_mm_errorhandler) _mm_errorhandler(); + } + return d; +} + +void MikMod_free(void *p) +{ + if (p) + free(p); +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mmerror.c b/src/libs/mikmod/mmerror.c new file mode 100644 index 0000000..efbde19 --- /dev/null +++ b/src/libs/mikmod/mmerror.c @@ -0,0 +1,197 @@ +/* MikMod sound library + (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Error handling functions. + Register an error handler with _mm_RegisterErrorHandler() and you're all set. + +==============================================================================*/ + +/* + + The global variables _mm_errno, and _mm_critical are set before the error + handler in called. See below for the values of these variables. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mikmod_internals.h" + +CHAR *_mm_errmsg[MMERR_MAX+1] = +{ +/* No error */ + + "No error", + +/* Generic errors */ + + "Could not open requested file", + "Out of memory", + "Dynamic linking failed", + +/* Sample errors */ + + "Out of memory to load sample", + "Out of sample handles to load sample", + "Sample format not recognized", + +/* Module errors */ + + "Failure loading module pattern", + "Failure loading module track", + "Failure loading module header", + "Failure loading sampleinfo", + "Module format not recognized", + "Module sample format not recognized", + "Synthsounds not supported in MED files", + "Compressed sample is invalid", + +/* Driver errors: */ + + "Sound device not detected", + "Device number out of range", + "Software mixer failure", + "Could not open sound device", + "This driver supports 8 bit linear output only", + "This driver supports 16 bit linear output only", + "This driver supports stereo output only", + "This driver supports uLaw output (8 bit mono, 8 kHz) only", + "Unable to set non-blocking mode for audio device", + +/* AudioFile driver errors */ + + "Cannot find suitable AudioFile audio port", + +/* AIX driver errors */ + + "Configuration (init step) of audio device failed", + "Configuration (control step) of audio device failed", + "Configuration (start step) of audio device failed", + +/* ALSA driver errors */ + +/* EsounD driver errors */ + +/* Ultrasound driver errors */ + + "Ultrasound driver only works in 16 bit stereo 44 KHz", + "Ultrasound card could not be reset", + "Could not start Ultrasound timer", + +/* HP driver errors */ + + "Unable to select 16bit-linear sample format", + "Could not select requested sample-rate", + "Could not select requested number of channels", + "Unable to select audio output", + "Unable to get audio description", + "Could not set transmission buffer size", + +/* Open Sound System driver errors */ + + "Could not set fragment size", + "Could not set sample size", + "Could not set mono/stereo setting", + "Could not set sample rate", + +/* SGI driver errors */ + + "Unsupported sample rate", + "Hardware does not support 16 bit sound", + "Hardware does not support 8 bit sound", + "Hardware does not support stereo sound", + "Hardware does not support mono sound", + +/* Sun driver errors */ + + "Sound device initialization failed", + +/* OS/2 drivers errors */ + + "Could not set mixing parameters", + "Could not create playback semaphores", + "Could not create playback timer", + "Could not create playback thread", + +/* DirectSound driver errors */ + + "Could not set playback priority", + "Could not create playback buffers", + "Could not set playback format", + "Could not register callback", + "Could not register event", + "Could not create playback thread", + "Could not initialize playback thread", + +/* Windows Multimedia API driver errors */ + + "Invalid device handle", + "The resource is already allocated", + "Invalid device identifier", + "Unsupported output format", + "Unknown error", + +/* Macintosh driver errors */ + + "Unsupported sample rate", + "Could not start playback", + +/* Invalid error */ + + "Invalid error code" +}; + +MIKMODAPI char *MikMod_strerror(int code) +{ + if ((code<0)||(code>MMERR_MAX)) code=MMERR_MAX+1; + return _mm_errmsg[code]; +} + +/* User installed error callback */ +MikMod_handler_t _mm_errorhandler = NULL; +MIKMODAPI int _mm_errno = 0; +MIKMODAPI BOOL _mm_critical = 0; + +MikMod_handler_t _mm_registererrorhandler(MikMod_handler_t proc) +{ + MikMod_handler_t oldproc=_mm_errorhandler; + + _mm_errorhandler = proc; + return oldproc; +} + +MIKMODAPI MikMod_handler_t MikMod_RegisterErrorHandler(MikMod_handler_t proc) +{ + MikMod_handler_t result; + + MUTEX_LOCK(vars); + result=_mm_registererrorhandler(proc); + MUTEX_UNLOCK(vars); + + return result; +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mmio.c b/src/libs/mikmod/mmio.c new file mode 100644 index 0000000..0f8a079 --- /dev/null +++ b/src/libs/mikmod/mmio.c @@ -0,0 +1,490 @@ +/* MikMod sound library + (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Portable file I/O routines + +==============================================================================*/ + +/* + + The way this module works: + + - _mm_fopen will call the errorhandler [see mmerror.c] in addition to + setting _mm_errno on exit. + - _mm_iobase is for internal use. It is used by Player_LoadFP to + ensure that it works properly with wad files. + - _mm_read_I_* and _mm_read_M_* differ : the first is for reading data + written by a little endian (intel) machine, and the second is for reading + big endian (Mac, RISC, Alpha) machine data. + - _mm_write functions work the same as the _mm_read functions. + - _mm_read_string is for reading binary strings. It is basically the same + as an fread of bytes. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include + +//#include "mikmod.h" +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fclose(FILE *); +extern int fgetc(FILE *); +extern int fputc(int, FILE *); +extern size_t fread(void *, size_t, size_t, FILE *); +extern int fseek(FILE *, long, int); +extern size_t fwrite(const void *, size_t, size_t, FILE *); +#endif + +#define COPY_BUFSIZE 1024 + +/* some prototypes */ +static BOOL _mm_MemReader_Eof(MREADER* reader); +static BOOL _mm_MemReader_Read(MREADER* reader,void* ptr,size_t size); +static int _mm_MemReader_Get(MREADER* reader); +static BOOL _mm_MemReader_Seek(MREADER* reader,long offset,int whence); +static long _mm_MemReader_Tell(MREADER* reader); + + +FILE* _mm_fopen(CHAR* fname,CHAR* attrib) +{ + FILE *fp; + + if(!(fp=fopen(fname,attrib))) { + _mm_errno = MMERR_OPENING_FILE; + if(_mm_errorhandler) _mm_errorhandler(); + } + return fp; +} + +BOOL _mm_FileExists(CHAR* fname) +{ + FILE *fp; + + if(!(fp=fopen(fname,"r"))) return 0; + fclose(fp); + + return 1; +} + +int _mm_fclose(FILE *fp) +{ + return fclose(fp); +} + +/* Sets the current file-position as the new iobase */ +void _mm_iobase_setcur(MREADER* reader) +{ + reader->prev_iobase=reader->iobase; /* store old value in case of revert */ + reader->iobase=reader->Tell(reader); +} + +/* Reverts to the last known iobase value. */ +void _mm_iobase_revert(MREADER* reader) +{ + reader->iobase=reader->prev_iobase; +} + +/*========== File Reader */ + +typedef struct MFILEREADER { + MREADER core; + FILE* file; +} MFILEREADER; + +static BOOL _mm_FileReader_Eof(MREADER* reader) +{ + return feof(((MFILEREADER*)reader)->file); +} + +static BOOL _mm_FileReader_Read(MREADER* reader,void* ptr,size_t size) +{ + return !!fread(ptr,size,1,((MFILEREADER*)reader)->file); +} + +static int _mm_FileReader_Get(MREADER* reader) +{ + return fgetc(((MFILEREADER*)reader)->file); +} + +static BOOL _mm_FileReader_Seek(MREADER* reader,long offset,int whence) +{ + return fseek(((MFILEREADER*)reader)->file, + (whence==SEEK_SET)?offset+reader->iobase:offset,whence); +} + +static long _mm_FileReader_Tell(MREADER* reader) +{ + return ftell(((MFILEREADER*)reader)->file)-reader->iobase; +} + +MREADER *_mm_new_file_reader(FILE* fp) +{ + MFILEREADER* reader=(MFILEREADER*)MikMod_malloc(sizeof(MFILEREADER)); + if (reader) { + reader->core.Eof =&_mm_FileReader_Eof; + reader->core.Read=&_mm_FileReader_Read; + reader->core.Get =&_mm_FileReader_Get; + reader->core.Seek=&_mm_FileReader_Seek; + reader->core.Tell=&_mm_FileReader_Tell; + reader->file=fp; + } + return (MREADER*)reader; +} + +void _mm_delete_file_reader (MREADER* reader) +{ + if(reader) MikMod_free(reader); +} + +/*========== File Writer */ + +typedef struct MFILEWRITER { + MWRITER core; + FILE* file; +} MFILEWRITER; + +static BOOL _mm_FileWriter_Seek(MWRITER* writer,long offset,int whence) +{ + return fseek(((MFILEWRITER*)writer)->file,offset,whence); +} + +static long _mm_FileWriter_Tell(MWRITER* writer) +{ + return ftell(((MFILEWRITER*)writer)->file); +} + +static BOOL _mm_FileWriter_Write(MWRITER* writer,void* ptr,size_t size) +{ + return (fwrite(ptr,size,1,((MFILEWRITER*)writer)->file)==size); +} + +static BOOL _mm_FileWriter_Put(MWRITER* writer,int value) +{ + return fputc(value,((MFILEWRITER*)writer)->file); +} + +MWRITER *_mm_new_file_writer(FILE* fp) +{ + MFILEWRITER* writer=(MFILEWRITER*)MikMod_malloc(sizeof(MFILEWRITER)); + if (writer) { + writer->core.Seek =&_mm_FileWriter_Seek; + writer->core.Tell =&_mm_FileWriter_Tell; + writer->core.Write=&_mm_FileWriter_Write; + writer->core.Put =&_mm_FileWriter_Put; + writer->file=fp; + } + return (MWRITER*) writer; +} + +void _mm_delete_file_writer (MWRITER* writer) +{ + if(writer) MikMod_free (writer); +} + +/*========== Memory Reader */ + + +typedef struct MMEMREADER { + MREADER core; + const void *buffer; + long len; + long pos; +} MMEMREADER; + +void _mm_delete_mem_reader(MREADER* reader) +{ + if (reader) { MikMod_free(reader); } +} + +MREADER *_mm_new_mem_reader(const void *buffer, int len) +{ + MMEMREADER* reader=(MMEMREADER*)MikMod_malloc(sizeof(MMEMREADER)); + if (reader) + { + reader->core.Eof =&_mm_MemReader_Eof; + reader->core.Read=&_mm_MemReader_Read; + reader->core.Get =&_mm_MemReader_Get; + reader->core.Seek=&_mm_MemReader_Seek; + reader->core.Tell=&_mm_MemReader_Tell; + reader->buffer = buffer; + reader->len = len; + reader->pos = 0; + } + return (MREADER*)reader; +} + +static BOOL _mm_MemReader_Eof(MREADER* reader) +{ + if (!reader) { return 1; } + if ( ((MMEMREADER*)reader)->pos > ((MMEMREADER*)reader)->len ) { + return 1; + } + return 0; +} + +static BOOL _mm_MemReader_Read(MREADER* reader,void* ptr,size_t size) +{ + unsigned char *d=ptr; + const unsigned char *s; + + if (!reader) { return 0; } + + if (reader->Eof(reader)) { return 0; } + + s = ((MMEMREADER*)reader)->buffer; + s += ((MMEMREADER*)reader)->pos; + + if ( ((MMEMREADER*)reader)->pos + (long)size > ((MMEMREADER*)reader)->len) + { + ((MMEMREADER*)reader)->pos = ((MMEMREADER*)reader)->len; + return 0; /* not enough remaining bytes */ + } + + ((MMEMREADER*)reader)->pos += (long)size; + + while (size--) + { + *d = *s; + s++; + d++; + } + + return 1; +} + +static int _mm_MemReader_Get(MREADER* reader) +{ + int pos; + + if (reader->Eof(reader)) { return 0; } + + pos = ((MMEMREADER*)reader)->pos; + ((MMEMREADER*)reader)->pos++; + + return ((unsigned char*)(((MMEMREADER*)reader)->buffer))[pos]; +} + +static BOOL _mm_MemReader_Seek(MREADER* reader,long offset,int whence) +{ + if (!reader) { return -1; } + + switch(whence) + { + case SEEK_CUR: + ((MMEMREADER*)reader)->pos += offset; + break; + case SEEK_SET: + ((MMEMREADER*)reader)->pos = offset; + break; + case SEEK_END: + ((MMEMREADER*)reader)->pos = ((MMEMREADER*)reader)->len - offset - 1; + break; + } + if ( ((MMEMREADER*)reader)->pos < 0) { ((MMEMREADER*)reader)->pos = 0; } + if ( ((MMEMREADER*)reader)->pos > ((MMEMREADER*)reader)->len ) { + ((MMEMREADER*)reader)->pos = ((MMEMREADER*)reader)->len; + } + return 0; +} + +static long _mm_MemReader_Tell(MREADER* reader) +{ + if (reader) { + return ((MMEMREADER*)reader)->pos; + } + return 0; +} + +/*========== Write functions */ + +void _mm_write_string(CHAR* data,MWRITER* writer) +{ + if(data) + _mm_write_UBYTES(data,strlen(data),writer); +} + +void _mm_write_M_UWORD(UWORD data,MWRITER* writer) +{ + _mm_write_UBYTE(data>>8,writer); + _mm_write_UBYTE(data&0xff,writer); +} + +void _mm_write_I_UWORD(UWORD data,MWRITER* writer) +{ + _mm_write_UBYTE(data&0xff,writer); + _mm_write_UBYTE(data>>8,writer); +} + +void _mm_write_M_ULONG(ULONG data,MWRITER* writer) +{ + _mm_write_M_UWORD(data>>16,writer); + _mm_write_M_UWORD(data&0xffff,writer); +} + +void _mm_write_I_ULONG(ULONG data,MWRITER* writer) +{ + _mm_write_I_UWORD(data&0xffff,writer); + _mm_write_I_UWORD(data>>16,writer); +} + +void _mm_write_M_SWORD(SWORD data,MWRITER* writer) +{ + _mm_write_M_UWORD((UWORD)data,writer); +} + +void _mm_write_I_SWORD(SWORD data,MWRITER* writer) +{ + _mm_write_I_UWORD((UWORD)data,writer); +} + +void _mm_write_M_SLONG(SLONG data,MWRITER* writer) +{ + _mm_write_M_ULONG((ULONG)data,writer); +} + +void _mm_write_I_SLONG(SLONG data,MWRITER* writer) +{ + _mm_write_I_ULONG((ULONG)data,writer); +} + +#if defined __STDC__ || defined _MSC_VER || defined MPW_C +#define DEFINE_MULTIPLE_WRITE_FUNCTION(type_name,type) \ +void _mm_write_##type_name##S (type *buffer,int number,MWRITER* writer) \ +{ \ + while(number-->0) \ + _mm_write_##type_name(*(buffer++),writer); \ +} +#else +#define DEFINE_MULTIPLE_WRITE_FUNCTION(type_name,type) \ +void _mm_write_/**/type_name/**/S (type *buffer,int number,MWRITER* writer) \ +{ \ + while(number-->0) \ + _mm_write_/**/type_name(*(buffer++),writer); \ +} +#endif + +DEFINE_MULTIPLE_WRITE_FUNCTION(M_SWORD,SWORD) +DEFINE_MULTIPLE_WRITE_FUNCTION(M_UWORD,UWORD) +DEFINE_MULTIPLE_WRITE_FUNCTION(I_SWORD,SWORD) +DEFINE_MULTIPLE_WRITE_FUNCTION(I_UWORD,UWORD) + +DEFINE_MULTIPLE_WRITE_FUNCTION(M_SLONG,SLONG) +DEFINE_MULTIPLE_WRITE_FUNCTION(M_ULONG,ULONG) +DEFINE_MULTIPLE_WRITE_FUNCTION(I_SLONG,SLONG) +DEFINE_MULTIPLE_WRITE_FUNCTION(I_ULONG,ULONG) + +/*========== Read functions */ + +int _mm_read_string(CHAR* buffer,int number,MREADER* reader) +{ + return reader->Read(reader,buffer,number); +} + +UWORD _mm_read_M_UWORD(MREADER* reader) +{ + UWORD result=((UWORD)_mm_read_UBYTE(reader))<<8; + result|=_mm_read_UBYTE(reader); + return result; +} + +UWORD _mm_read_I_UWORD(MREADER* reader) +{ + UWORD result=_mm_read_UBYTE(reader); + result|=((UWORD)_mm_read_UBYTE(reader))<<8; + return result; +} + +ULONG _mm_read_M_ULONG(MREADER* reader) +{ + ULONG result=((ULONG)_mm_read_M_UWORD(reader))<<16; + result|=_mm_read_M_UWORD(reader); + return result; +} + +ULONG _mm_read_I_ULONG(MREADER* reader) +{ + ULONG result=_mm_read_I_UWORD(reader); + result|=((ULONG)_mm_read_I_UWORD(reader))<<16; + return result; +} + +SWORD _mm_read_M_SWORD(MREADER* reader) +{ + return((SWORD)_mm_read_M_UWORD(reader)); +} + +SWORD _mm_read_I_SWORD(MREADER* reader) +{ + return((SWORD)_mm_read_I_UWORD(reader)); +} + +SLONG _mm_read_M_SLONG(MREADER* reader) +{ + return((SLONG)_mm_read_M_ULONG(reader)); +} + +SLONG _mm_read_I_SLONG(MREADER* reader) +{ + return((SLONG)_mm_read_I_ULONG(reader)); +} + +#if defined __STDC__ || defined _MSC_VER || defined MPW_C +#define DEFINE_MULTIPLE_READ_FUNCTION(type_name,type) \ +int _mm_read_##type_name##S (type *buffer,int number,MREADER* reader) \ +{ \ + while(number-->0) \ + *(buffer++)=_mm_read_##type_name(reader); \ + return !reader->Eof(reader); \ +} +#else +#define DEFINE_MULTIPLE_READ_FUNCTION(type_name,type) \ +int _mm_read_/**/type_name/**/S (type *buffer,int number,MREADER* reader) \ +{ \ + while(number-->0) \ + *(buffer++)=_mm_read_/**/type_name(reader); \ + return !reader->Eof(reader); \ +} +#endif + +DEFINE_MULTIPLE_READ_FUNCTION(M_SWORD,SWORD) +DEFINE_MULTIPLE_READ_FUNCTION(M_UWORD,UWORD) +DEFINE_MULTIPLE_READ_FUNCTION(I_SWORD,SWORD) +DEFINE_MULTIPLE_READ_FUNCTION(I_UWORD,UWORD) + +DEFINE_MULTIPLE_READ_FUNCTION(M_SLONG,SLONG) +DEFINE_MULTIPLE_READ_FUNCTION(M_ULONG,ULONG) +DEFINE_MULTIPLE_READ_FUNCTION(I_SLONG,SLONG) +DEFINE_MULTIPLE_READ_FUNCTION(I_ULONG,ULONG) + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mplayer.c b/src/libs/mikmod/mplayer.c new file mode 100644 index 0000000..812410a --- /dev/null +++ b/src/libs/mikmod/mplayer.c @@ -0,0 +1,3561 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + The Protracker Player Driver + + The protracker driver supports all base Protracker 3.x commands and features. + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#ifdef SRANDOM_IN_MATH_H +#include +#else +#include +#endif + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +extern long int random(void); +#endif + +/* The currently playing module */ +/* This variable should better be static, but it would break the ABI, so this + will wait */ +/*static*/ MODULE *pf = NULL; + +#define HIGH_OCTAVE 2 /* number of above-range octaves */ + +static UWORD oldperiods[OCTAVE*2]={ + 0x6b00, 0x6800, 0x6500, 0x6220, 0x5f50, 0x5c80, + 0x5a00, 0x5740, 0x54d0, 0x5260, 0x5010, 0x4dc0, + 0x4b90, 0x4960, 0x4750, 0x4540, 0x4350, 0x4160, + 0x3f90, 0x3dc0, 0x3c10, 0x3a40, 0x38b0, 0x3700 +}; + +static UBYTE VibratoTable[32]={ + 0, 24, 49, 74, 97,120,141,161,180,197,212,224,235,244,250,253, + 255,253,250,244,235,224,212,197,180,161,141,120, 97, 74, 49, 24 +}; + +static UBYTE avibtab[128]={ + 0, 1, 3, 4, 6, 7, 9,10,12,14,15,17,18,20,21,23, + 24,25,27,28,30,31,32,34,35,36,38,39,40,41,42,44, + 45,46,47,48,49,50,51,52,53,54,54,55,56,57,57,58, + 59,59,60,60,61,61,62,62,62,63,63,63,63,63,63,63, + 64,63,63,63,63,63,63,63,62,62,62,61,61,60,60,59, + 59,58,57,57,56,55,54,54,53,52,51,50,49,48,47,46, + 45,44,42,41,40,39,38,36,35,34,32,31,30,28,27,25, + 24,23,21,20,18,17,15,14,12,10, 9, 7, 6, 4, 3, 1 +}; + +/* Triton's linear periods to frequency translation table (for XM modules) */ +static ULONG lintab[768]={ + 535232,534749,534266,533784,533303,532822,532341,531861, + 531381,530902,530423,529944,529466,528988,528511,528034, + 527558,527082,526607,526131,525657,525183,524709,524236, + 523763,523290,522818,522346,521875,521404,520934,520464, + 519994,519525,519057,518588,518121,517653,517186,516720, + 516253,515788,515322,514858,514393,513929,513465,513002, + 512539,512077,511615,511154,510692,510232,509771,509312, + 508852,508393,507934,507476,507018,506561,506104,505647, + 505191,504735,504280,503825,503371,502917,502463,502010, + 501557,501104,500652,500201,499749,499298,498848,498398, + 497948,497499,497050,496602,496154,495706,495259,494812, + 494366,493920,493474,493029,492585,492140,491696,491253, + 490809,490367,489924,489482,489041,488600,488159,487718, + 487278,486839,486400,485961,485522,485084,484647,484210, + 483773,483336,482900,482465,482029,481595,481160,480726, + 480292,479859,479426,478994,478562,478130,477699,477268, + 476837,476407,475977,475548,475119,474690,474262,473834, + 473407,472979,472553,472126,471701,471275,470850,470425, + 470001,469577,469153,468730,468307,467884,467462,467041, + 466619,466198,465778,465358,464938,464518,464099,463681, + 463262,462844,462427,462010,461593,461177,460760,460345, + 459930,459515,459100,458686,458272,457859,457446,457033, + 456621,456209,455797,455386,454975,454565,454155,453745, + 453336,452927,452518,452110,451702,451294,450887,450481, + 450074,449668,449262,448857,448452,448048,447644,447240, + 446836,446433,446030,445628,445226,444824,444423,444022, + 443622,443221,442821,442422,442023,441624,441226,440828, + 440430,440033,439636,439239,438843,438447,438051,437656, + 437261,436867,436473,436079,435686,435293,434900,434508, + 434116,433724,433333,432942,432551,432161,431771,431382, + 430992,430604,430215,429827,429439,429052,428665,428278, + 427892,427506,427120,426735,426350,425965,425581,425197, + 424813,424430,424047,423665,423283,422901,422519,422138, + 421757,421377,420997,420617,420237,419858,419479,419101, + 418723,418345,417968,417591,417214,416838,416462,416086, + 415711,415336,414961,414586,414212,413839,413465,413092, + 412720,412347,411975,411604,411232,410862,410491,410121, + 409751,409381,409012,408643,408274,407906,407538,407170, + 406803,406436,406069,405703,405337,404971,404606,404241, + 403876,403512,403148,402784,402421,402058,401695,401333, + 400970,400609,400247,399886,399525,399165,398805,398445, + 398086,397727,397368,397009,396651,396293,395936,395579, + 395222,394865,394509,394153,393798,393442,393087,392733, + 392378,392024,391671,391317,390964,390612,390259,389907, + 389556,389204,388853,388502,388152,387802,387452,387102, + 386753,386404,386056,385707,385359,385012,384664,384317, + 383971,383624,383278,382932,382587,382242,381897,381552, + 381208,380864,380521,380177,379834,379492,379149,378807, + 378466,378124,377783,377442,377102,376762,376422,376082, + 375743,375404,375065,374727,374389,374051,373714,373377, + 373040,372703,372367,372031,371695,371360,371025,370690, + 370356,370022,369688,369355,369021,368688,368356,368023, + 367691,367360,367028,366697,366366,366036,365706,365376, + 365046,364717,364388,364059,363731,363403,363075,362747, + 362420,362093,361766,361440,361114,360788,360463,360137, + 359813,359488,359164,358840,358516,358193,357869,357547, + 357224,356902,356580,356258,355937,355616,355295,354974, + 354654,354334,354014,353695,353376,353057,352739,352420, + 352103,351785,351468,351150,350834,350517,350201,349885, + 349569,349254,348939,348624,348310,347995,347682,347368, + 347055,346741,346429,346116,345804,345492,345180,344869, + 344558,344247,343936,343626,343316,343006,342697,342388, + 342079,341770,341462,341154,340846,340539,340231,339924, + 339618,339311,339005,338700,338394,338089,337784,337479, + 337175,336870,336566,336263,335959,335656,335354,335051, + 334749,334447,334145,333844,333542,333242,332941,332641, + 332341,332041,331741,331442,331143,330844,330546,330247, + 329950,329652,329355,329057,328761,328464,328168,327872, + 327576,327280,326985,326690,326395,326101,325807,325513, + 325219,324926,324633,324340,324047,323755,323463,323171, + 322879,322588,322297,322006,321716,321426,321136,320846, + 320557,320267,319978,319690,319401,319113,318825,318538, + 318250,317963,317676,317390,317103,316817,316532,316246, + 315961,315676,315391,315106,314822,314538,314254,313971, + 313688,313405,313122,312839,312557,312275,311994,311712, + 311431,311150,310869,310589,310309,310029,309749,309470, + 309190,308911,308633,308354,308076,307798,307521,307243, + 306966,306689,306412,306136,305860,305584,305308,305033, + 304758,304483,304208,303934,303659,303385,303112,302838, + 302565,302292,302019,301747,301475,301203,300931,300660, + 300388,300117,299847,299576,299306,299036,298766,298497, + 298227,297958,297689,297421,297153,296884,296617,296349, + 296082,295815,295548,295281,295015,294749,294483,294217, + 293952,293686,293421,293157,292892,292628,292364,292100, + 291837,291574,291311,291048,290785,290523,290261,289999, + 289737,289476,289215,288954,288693,288433,288173,287913, + 287653,287393,287134,286875,286616,286358,286099,285841, + 285583,285326,285068,284811,284554,284298,284041,283785, + 283529,283273,283017,282762,282507,282252,281998,281743, + 281489,281235,280981,280728,280475,280222,279969,279716, + 279464,279212,278960,278708,278457,278206,277955,277704, + 277453,277203,276953,276703,276453,276204,275955,275706, + 275457,275209,274960,274712,274465,274217,273970,273722, + 273476,273229,272982,272736,272490,272244,271999,271753, + 271508,271263,271018,270774,270530,270286,270042,269798, + 269555,269312,269069,268826,268583,268341,268099,267857 +}; + +#define LOGFAC 2*16 +static UWORD logtab[104]={ + LOGFAC*907,LOGFAC*900,LOGFAC*894,LOGFAC*887, + LOGFAC*881,LOGFAC*875,LOGFAC*868,LOGFAC*862, + LOGFAC*856,LOGFAC*850,LOGFAC*844,LOGFAC*838, + LOGFAC*832,LOGFAC*826,LOGFAC*820,LOGFAC*814, + LOGFAC*808,LOGFAC*802,LOGFAC*796,LOGFAC*791, + LOGFAC*785,LOGFAC*779,LOGFAC*774,LOGFAC*768, + LOGFAC*762,LOGFAC*757,LOGFAC*752,LOGFAC*746, + LOGFAC*741,LOGFAC*736,LOGFAC*730,LOGFAC*725, + LOGFAC*720,LOGFAC*715,LOGFAC*709,LOGFAC*704, + LOGFAC*699,LOGFAC*694,LOGFAC*689,LOGFAC*684, + LOGFAC*678,LOGFAC*675,LOGFAC*670,LOGFAC*665, + LOGFAC*660,LOGFAC*655,LOGFAC*651,LOGFAC*646, + LOGFAC*640,LOGFAC*636,LOGFAC*632,LOGFAC*628, + LOGFAC*623,LOGFAC*619,LOGFAC*614,LOGFAC*610, + LOGFAC*604,LOGFAC*601,LOGFAC*597,LOGFAC*592, + LOGFAC*588,LOGFAC*584,LOGFAC*580,LOGFAC*575, + LOGFAC*570,LOGFAC*567,LOGFAC*563,LOGFAC*559, + LOGFAC*555,LOGFAC*551,LOGFAC*547,LOGFAC*543, + LOGFAC*538,LOGFAC*535,LOGFAC*532,LOGFAC*528, + LOGFAC*524,LOGFAC*520,LOGFAC*516,LOGFAC*513, + LOGFAC*508,LOGFAC*505,LOGFAC*502,LOGFAC*498, + LOGFAC*494,LOGFAC*491,LOGFAC*487,LOGFAC*484, + LOGFAC*480,LOGFAC*477,LOGFAC*474,LOGFAC*470, + LOGFAC*467,LOGFAC*463,LOGFAC*460,LOGFAC*457, + LOGFAC*453,LOGFAC*450,LOGFAC*447,LOGFAC*443, + LOGFAC*440,LOGFAC*437,LOGFAC*434,LOGFAC*431 +}; + +static SBYTE PanbrelloTable[256]={ + 0, 2, 3, 5, 6, 8, 9, 11, 12, 14, 16, 17, 19, 20, 22, 23, + 24, 26, 27, 29, 30, 32, 33, 34, 36, 37, 38, 39, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59, + 59, 60, 60, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 63, 63, 63, 62, 62, 62, 61, 61, 60, 60, + 59, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, + 45, 44, 43, 42, 41, 39, 38, 37, 36, 34, 33, 32, 30, 29, 27, 26, + 24, 23, 22, 20, 19, 17, 16, 14, 12, 11, 9, 8, 6, 5, 3, 2, + 0,- 2,- 3,- 5,- 6,- 8,- 9,-11,-12,-14,-16,-17,-19,-20,-22,-23, + -24,-26,-27,-29,-30,-32,-33,-34,-36,-37,-38,-39,-41,-42,-43,-44, + -45,-46,-47,-48,-49,-50,-51,-52,-53,-54,-55,-56,-56,-57,-58,-59, + -59,-60,-60,-61,-61,-62,-62,-62,-63,-63,-63,-64,-64,-64,-64,-64, + -64,-64,-64,-64,-64,-64,-63,-63,-63,-62,-62,-62,-61,-61,-60,-60, + -59,-59,-58,-57,-56,-56,-55,-54,-53,-52,-51,-50,-49,-48,-47,-46, + -45,-44,-43,-42,-41,-39,-38,-37,-36,-34,-33,-32,-30,-29,-27,-26, + -24,-23,-22,-20,-19,-17,-16,-14,-12,-11,- 9,- 8,- 6,- 5,- 3,- 2 +}; + +/* returns a random value between 0 and ceil-1, ceil must be a power of two */ +static int getrandom(int ceil) +{ +#ifdef HAVE_SRANDOM + return random()&(ceil-1); +#else + return (rand()*ceil)/(RAND_MAX+1.0); +#endif +} + +/* New Note Action Scoring System : + -------------------------------- + 1) total-volume (fadevol, chanvol, volume) is the main scorer. + 2) a looping sample is a bonus x2 + 3) a foreground channel is a bonus x4 + 4) an active envelope with keyoff is a handicap -x2 +*/ +static int MP_FindEmptyChannel(MODULE *mod) +{ + MP_VOICE *a; + ULONG t,k,tvol,pp; + + for (t=0;tvoice[t].main.kick==KICK_ABSENT)|| + (mod->voice[t].main.kick==KICK_ENV))&& + Voice_Stopped_internal(t)) + return t; + + tvol=0xffffffUL;t=-1;a=mod->voice; + for (k=0;kmain.s) + return k; + + if ((a->main.kick==KICK_ABSENT)||(a->main.kick==KICK_ENV)) { + pp=a->totalvol<<((a->main.s->flags&SF_LOOP)?1:0); + if ((a->master)&&(a==a->master->slave)) + pp<<=2; + + if (pp8000*7) return -1; + return t; +} + +static SWORD Interpolate(SWORD p,SWORD p1,SWORD p2,SWORD v1,SWORD v2) +{ + if ((p1==p2)||(p==p1)) return v1; + return v1+((SLONG)((p-p1)*(v2-v1))/(p2-p1)); +} + +UWORD getlinearperiod(UWORD note,ULONG fine) +{ + UWORD t; + + t=((20L+2*HIGH_OCTAVE)*OCTAVE+2-note)*32L-(fine>>1); + return t; +} + +static UWORD getlogperiod(UWORD note,ULONG fine) +{ + UWORD n,o; + UWORD p1,p2; + ULONG i; + + n=note%(2*OCTAVE); + o=note/(2*OCTAVE); + i=(n<<2)+(fine>>4); /* n*8 + fine/16 */ + + p1=logtab[i]; + p2=logtab[i+1]; + + return (Interpolate(fine>>4,0,15,p1,p2)>>o); +} + +static UWORD getoldperiod(UWORD note,ULONG speed) +{ + UWORD n,o; + + /* This happens sometimes on badly converted AMF, and old MOD */ + if (!speed) { +#ifdef MIKMOD_DEBUG + fprintf(stderr,"\rmplayer: getoldperiod() called with note=%d, speed=0 !\n",note); +#endif + return 4242; /* <- prevent divide overflow.. (42 hehe) */ + } + + n=note%(2*OCTAVE); + o=note/(2*OCTAVE); + return ((8363L*(ULONG)oldperiods[n])>>o)/speed; +} + +static UWORD GetPeriod(UWORD flags, UWORD note, ULONG speed) +{ + if (flags & UF_XMPERIODS) { + if (flags & UF_LINEAR) + return getlinearperiod(note, speed); + else + return getlogperiod(note, speed); + } else + return getoldperiod(note, speed); +} + +static SWORD InterpolateEnv(SWORD p,ENVPT *a,ENVPT *b) +{ + return (Interpolate(p,a->pos,b->pos,a->val,b->val)); +} + +static SWORD DoPan(SWORD envpan,SWORD pan) +{ + int newpan; + + newpan=pan+(((envpan-PAN_CENTER)*(128-abs(pan-PAN_CENTER)))/128); + + return (newpanPAN_RIGHT?PAN_RIGHT:newpan); +} + +static SWORD StartEnvelope(ENVPR *t,UBYTE flg,UBYTE pts,UBYTE susbeg,UBYTE susend,UBYTE beg,UBYTE end,ENVPT *p,UBYTE keyoff) +{ + t->flg=flg; + t->pts=pts; + t->susbeg=susbeg; + t->susend=susend; + t->beg=beg; + t->end=end; + t->env=p; + t->p=0; + t->a=0; + t->b=((t->flg&EF_SUSTAIN)&&(!(keyoff&KEY_OFF)))?0:1; + + /* Imago Orpheus sometimes stores an extra initial point in the envelope */ + if ((t->pts>=2)&&(t->env[0].pos==t->env[1].pos)) { + t->a++;t->b++; + } + + /* Fit in the envelope, still */ + if (t->a >= t->pts) + t->a = t->pts - 1; + if (t->b >= t->pts) + t->b = t->pts-1; + + return t->env[t->a].val; +} + +/* This procedure processes all envelope types, include volume, pitch, and + panning. Envelopes are defined by a set of points, each with a magnitude + [relating either to volume, panning position, or pitch modifier] and a tick + position. + + Envelopes work in the following manner: + + (a) Each tick the envelope is moved a point further in its progression. For + an accurate progression, magnitudes between two envelope points are + interpolated. + + (b) When progression reaches a defined point on the envelope, values are + shifted to interpolate between this point and the next, and checks for + loops or envelope end are done. + + Misc: + Sustain loops are loops that are only active as long as the keyoff flag is + clear. When a volume envelope terminates, so does the current fadeout. +*/ +static SWORD ProcessEnvelope(MP_VOICE *aout, ENVPR *t, SWORD v) +{ + if (t->flg & EF_ON) { + UBYTE a, b; /* actual points in the envelope */ + UWORD p; /* the 'tick counter' - real point being played */ + + a = t->a; + b = t->b; + p = t->p; + + /* + * Sustain loop on one point (XM type). + * Not processed if KEYOFF. + * Don't move and don't interpolate when the point is reached + */ + if ((t->flg & EF_SUSTAIN) && t->susbeg == t->susend && + (!(aout->main.keyoff & KEY_OFF) && p == t->env[t->susbeg].pos)) { + v = t->env[t->susbeg].val; + } else { + /* + * All following situations will require interpolation between + * two envelope points. + */ + + /* + * Sustain loop between two points (IT type). + * Not processed if KEYOFF. + */ + /* if we were on a loop point, loop now */ + if ((t->flg & EF_SUSTAIN) && !(aout->main.keyoff & KEY_OFF) && + a >= t->susend) { + a = t->susbeg; + b = (t->susbeg==t->susend)?a:a+1; + p = t->env[a].pos; + v = t->env[a].val; + } else + /* + * Regular loop. + * Be sure to correctly handle single point loops. + */ + if ((t->flg & EF_LOOP) && a >= t->end) { + a = t->beg; + b = t->beg == t->end ? a : a + 1; + p = t->env[a].pos; + v = t->env[a].val; + } else + /* + * Non looping situations. + */ + if (a != b) + v = InterpolateEnv(p, &t->env[a], &t->env[b]); + else + v = t->env[a].val; + + /* + * Start to fade if the volume envelope is finished. + */ + if (p >= t->env[t->pts - 1].pos) { + if (t->flg & EF_VOLENV) { + aout->main.keyoff |= KEY_FADE; + if (!v) + aout->main.fadevol = 0; + } + } else { + p++; + /* did pointer reach point b? */ + if (p >= t->env[b].pos) + a = b++; /* shift points a and b */ + } + t->a = a; + t->b = b; + t->p = p; + } + } + return v; +} + +/* XM linear period to frequency conversion */ +ULONG getfrequency(UWORD flags,ULONG period) +{ + if (flags & UF_LINEAR) { + SLONG shift = ((SLONG)period / 768) - HIGH_OCTAVE; + + if (shift >= 0) + return lintab[period % 768] >> shift; + else + return lintab[period % 768] << (-shift); + } else + return (8363L*1712L)/(period?period:1); +} + +/*========== Protracker effects */ + +static void DoArpeggio(UWORD tick, UWORD flags, MP_CONTROL *a, UBYTE style) +{ + UBYTE note=a->main.note; + + if (a->arpmem) { + switch (style) { + case 0: /* mod style: N, N+x, N+y */ + switch (tick % 3) { + /* case 0: unchanged */ + case 1: + note += (a->arpmem >> 4); + break; + case 2: + note += (a->arpmem & 0xf); + break; + } + break; + case 3: /* okt arpeggio 3: N-x, N, N+y */ + switch (tick % 3) { + case 0: + note -= (a->arpmem >> 4); + break; + /* case 1: unchanged */ + case 2: + note += (a->arpmem & 0xf); + break; + } + break; + case 4: /* okt arpeggio 4: N, N+y, N, N-x */ + switch (tick % 4) { + /* case 0, case 2: unchanged */ + case 1: + note += (a->arpmem & 0xf); + break; + case 3: + note -= (a->arpmem >> 4); + break; + } + break; + case 5: /* okt arpeggio 5: N-x, N+y, N, and nothing at tick 0 */ + if (!tick) + break; + switch (tick % 3) { + /* case 0: unchanged */ + case 1: + note -= (a->arpmem >> 4); + break; + case 2: + note += (a->arpmem & 0xf); + break; + } + break; + } + a->main.period = GetPeriod(flags, (UWORD)note << 1, a->speed); + a->ownper = 1; + } +} + +static int DoPTEffect0(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (!tick) { + if (!dat && (flags & UF_ARPMEM)) + dat=a->arpmem; + else + a->arpmem=dat; + } + if (a->main.period) + DoArpeggio(tick, flags, a, 0); + + return 0; +} + +static int DoPTEffect1(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (!tick && dat) + a->slidespeed = (UWORD)dat << 2; + if (a->main.period) + if (tick) + a->tmpperiod -= a->slidespeed; + + return 0; +} + +static int DoPTEffect2(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (!tick && dat) + a->slidespeed = (UWORD)dat << 2; + if (a->main.period) + if (tick) + a->tmpperiod += a->slidespeed; + + return 0; +} + +static void DoToneSlide(UWORD tick, MP_CONTROL *a) +{ + if (!a->main.fadevol) + a->main.kick = (a->main.kick == KICK_NOTE)? KICK_NOTE : KICK_KEYOFF; + else + a->main.kick = (a->main.kick == KICK_NOTE)? KICK_ENV : KICK_ABSENT; + + if (tick != 0) { + int dist; + + /* We have to slide a->main.period towards a->wantedperiod, so compute + the difference between those two values */ + dist=a->main.period-a->wantedperiod; + + /* if they are equal or if portamentospeed is too big ...*/ + if (dist == 0 || a->portspeed > abs(dist)) + /* ...make tmpperiod equal tperiod */ + a->tmpperiod=a->main.period=a->wantedperiod; + else if (dist>0) { + a->tmpperiod-=a->portspeed; + a->main.period-=a->portspeed; /* dist>0, slide up */ + } else { + a->tmpperiod+=a->portspeed; + a->main.period+=a->portspeed; /* dist<0, slide down */ + } + } else + a->tmpperiod=a->main.period; + a->ownper = 1; +} + +static int DoPTEffect3(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if ((!tick)&&(dat)) a->portspeed=(UWORD)dat<<2; + if (a->main.period) + DoToneSlide(tick, a); + + return 0; +} + +static void DoVibrato(UWORD tick, MP_CONTROL *a) +{ + UBYTE q; + UWORD temp = 0; /* silence warning */ + + if (!tick) + return; + + q=(a->vibpos>>2)&0x1f; + + switch (a->wavecontrol&3) { + case 0: /* sine */ + temp=VibratoTable[q]; + break; + case 1: /* ramp down */ + q<<=3; + if (a->vibpos<0) q=255-q; + temp=q; + break; + case 2: /* square wave */ + temp=255; + break; + case 3: /* random wave */ + temp=getrandom(256); + break; + } + + temp*=a->vibdepth; + temp>>=7;temp<<=2; + + if (a->vibpos>=0) + a->main.period=a->tmpperiod+temp; + else + a->main.period=a->tmpperiod-temp; + a->ownper = 1; + + if (tick != 0) + a->vibpos+=a->vibspd; +} + +static int DoPTEffect4(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (!tick) { + if (dat&0x0f) a->vibdepth=dat&0xf; + if (dat&0xf0) a->vibspd=(dat&0xf0)>>2; + } + if (a->main.period) + DoVibrato(tick, a); + + return 0; +} + +static void DoVolSlide(MP_CONTROL *a, UBYTE dat) +{ + if (dat&0xf) { + a->tmpvolume-=(dat&0x0f); + if (a->tmpvolume<0) + a->tmpvolume=0; + } else { + a->tmpvolume+=(dat>>4); + if (a->tmpvolume>64) + a->tmpvolume=64; + } +} + +static int DoPTEffect5(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (a->main.period) + DoToneSlide(tick, a); + + if (tick) + DoVolSlide(a, dat); + + return 0; +} + +/* DoPTEffect6 after DoPTEffectA */ + +static int DoPTEffect7(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + UBYTE q; + UWORD temp = 0; /* silence warning */ + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (!tick) { + if (dat&0x0f) a->trmdepth=dat&0xf; + if (dat&0xf0) a->trmspd=(dat&0xf0)>>2; + } + if (a->main.period) { + q=(a->trmpos>>2)&0x1f; + + switch ((a->wavecontrol>>4)&3) { + case 0: /* sine */ + temp=VibratoTable[q]; + break; + case 1: /* ramp down */ + q<<=3; + if (a->trmpos<0) q=255-q; + temp=q; + break; + case 2: /* square wave */ + temp=255; + break; + case 3: /* random wave */ + temp=getrandom(256); + break; + } + temp*=a->trmdepth; + temp>>=6; + + if (a->trmpos>=0) { + a->volume=a->tmpvolume+temp; + if (a->volume>64) a->volume=64; + } else { + a->volume=a->tmpvolume-temp; + if (a->volume<0) a->volume=0; + } + a->ownvol = 1; + + if (tick) + a->trmpos+=a->trmspd; + } + + return 0; +} + +static int DoPTEffect8(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + + dat = UniGetByte(); + if (mod->panflag) + a->main.panning = mod->panning[channel] = dat; + + return 0; +} + +static int DoPTEffect9(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (!tick) { + if (dat) a->soffset=(UWORD)dat<<8; + a->main.start=a->hioffset|a->soffset; + + if ((a->main.s)&&(a->main.start>a->main.s->length)) + a->main.start=a->main.s->flags&(SF_LOOP|SF_BIDI)? + a->main.s->loopstart:a->main.s->length; + } + + return 0; +} + +static int DoPTEffectA(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (tick) + DoVolSlide(a, dat); + + return 0; +} + +static int DoPTEffect6(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + if (a->main.period) + DoVibrato(tick, a); + DoPTEffectA(tick, flags, a, mod, channel); + + return 0; +} + +static int DoPTEffectB(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + + if (tick || mod->patdly2) + return 0; + + /* Vincent Voois uses a nasty trick in "Universal Bolero" */ + if (dat == mod->sngpos && mod->patbrk == mod->patpos) + return 0; + + if (!mod->loop && !mod->patbrk && + (dat < mod->sngpos || + (mod->sngpos == (mod->numpos - 1) && !mod->patbrk) || + (dat == mod->sngpos && (flags & UF_NOWRAP)) + )) { + /* if we don't loop, better not to skip the end of the + pattern, after all... so: + mod->patbrk=0; */ + mod->posjmp=3; + } else { + /* if we were fading, adjust... */ + if (mod->sngpos == (mod->numpos-1)) + mod->volume=mod->initvolume>128?128:mod->initvolume; + mod->sngpos=dat; + mod->posjmp=2; + mod->patpos=0; + } + + return 0; +} + +static int DoPTEffectC(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (tick) return 0; + if (dat==(UBYTE)-1) a->anote=dat=0; /* note cut */ + else if (dat>64) dat=64; + a->tmpvolume=dat; + + return 0; +} + +static int DoPTEffectD(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if ((tick)||(mod->patdly2)) return 0; + if ((mod->positions[mod->sngpos]!=LAST_PATTERN)&& + (dat>mod->pattrows[mod->positions[mod->sngpos]])) + dat=mod->pattrows[mod->positions[mod->sngpos]]; + mod->patbrk=dat; + if (!mod->posjmp) { + /* don't ask me to explain this code - it makes + backwards.s3m and children.xm (heretic's version) play + correctly, among others. Take that for granted, or write + the page of comments yourself... you might need some + aspirin - Miod */ + if ((mod->sngpos==mod->numpos-1)&&(dat)&&((mod->loop)|| + (mod->positions[mod->sngpos]==(mod->numpat-1) + && !(flags&UF_NOWRAP)))) { + mod->sngpos=0; + mod->posjmp=2; + } else + mod->posjmp=3; + } + + return 0; +} + +static void DoEEffects(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, + SWORD channel, UBYTE dat) +{ + UBYTE nib = dat & 0xf; + + switch (dat>>4) { + case 0x0: /* hardware filter toggle, not supported */ + break; + case 0x1: /* fineslide up */ + if (a->main.period) + if (!tick) + a->tmpperiod-=(nib<<2); + break; + case 0x2: /* fineslide dn */ + if (a->main.period) + if (!tick) + a->tmpperiod+=(nib<<2); + break; + case 0x3: /* glissando ctrl */ + a->glissando=nib; + break; + case 0x4: /* set vibrato waveform */ + a->wavecontrol&=0xf0; + a->wavecontrol|=nib; + break; + case 0x5: /* set finetune */ + if (a->main.period) { + if (flags&UF_XMPERIODS) + a->speed=nib+128; + else + a->speed=finetune[nib]; + a->tmpperiod=GetPeriod(flags, (UWORD)a->main.note<<1,a->speed); + } + break; + case 0x6: /* set patternloop */ + if (tick) + break; + if (nib) { /* set reppos or repcnt ? */ + /* set repcnt, so check if repcnt already is set, which means we + are already looping */ + if (a->pat_repcnt) + a->pat_repcnt--; /* already looping, decrease counter */ + else { +#if 0 + /* this would make walker.xm, shipped with Xsoundtracker, + play correctly, but it's better to remain compatible + with FT2 */ + if ((!(flags&UF_NOWRAP))||(a->pat_reppos!=POS_NONE)) +#endif + a->pat_repcnt=nib; /* not yet looping, so set repcnt */ + } + + if (a->pat_repcnt) { /* jump to reppos if repcnt>0 */ + if (a->pat_reppos==POS_NONE) + a->pat_reppos=mod->patpos-1; + if (a->pat_reppos==-1) { + mod->pat_repcrazy=1; + mod->patpos=0; + } else + mod->patpos=a->pat_reppos; + } else a->pat_reppos=POS_NONE; + } else + a->pat_reppos=mod->patpos-1; /* set reppos - can be (-1) */ + break; + case 0x7: /* set tremolo waveform */ + a->wavecontrol&=0x0f; + a->wavecontrol|=nib<<4; + break; + case 0x8: /* set panning */ + if (mod->panflag) { + if (nib<=8) nib<<=4; + else nib*=17; + a->main.panning=mod->panning[channel]=nib; + } + break; + case 0x9: /* retrig note */ + /* do not retrigger on tick 0, until we are emulating FT2 and effect + data is zero */ + if (!tick && !((flags & UF_FT2QUIRKS) && (!nib))) + break; + /* only retrigger if data nibble > 0, or if tick 0 (FT2 compat) */ + if (nib || !tick) { + if (!a->retrig) { + /* when retrig counter reaches 0, reset counter and restart + the sample */ + if (a->main.period) a->main.kick=KICK_NOTE; + a->retrig=nib; + } + a->retrig--; /* countdown */ + } + break; + case 0xa: /* fine volume slide up */ + if (tick) + break; + a->tmpvolume+=nib; + if (a->tmpvolume>64) a->tmpvolume=64; + break; + case 0xb: /* fine volume slide dn */ + if (tick) + break; + a->tmpvolume-=nib; + if (a->tmpvolume<0)a->tmpvolume=0; + break; + case 0xc: /* cut note */ + /* When tick reaches the cut-note value, turn the volume to + zero (just like on the amiga) */ + if (tick>=nib) + a->tmpvolume=0; /* just turn the volume down */ + break; + case 0xd: /* note delay */ + /* delay the start of the sample until tick==nib */ + if (!tick) + a->main.notedelay=nib; + else if (a->main.notedelay) + a->main.notedelay--; + break; + case 0xe: /* pattern delay */ + if (!tick) + if (!mod->patdly2) + mod->patdly=nib+1; /* only once, when tick=0 */ + break; + case 0xf: /* invert loop, not supported */ + break; + } +} + +static int DoPTEffectE(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + DoEEffects(tick, flags, a, mod, channel, UniGetByte()); + + return 0; +} + +static int DoPTEffectF(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (tick||mod->patdly2) return 0; + if (mod->extspd&&(dat>=mod->bpmlimit)) + mod->bpm=dat; + else + if (dat) { + mod->sngspd=(dat>=mod->bpmlimit)?mod->bpmlimit-1:dat; + mod->vbtick=0; + } + + return 0; +} + +/*========== Scream Tracker effects */ + +static int DoS3MEffectA(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE speed; + + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + speed = UniGetByte(); + + if (tick || mod->patdly2) + return 0; + + if (speed > 128) + speed -= 128; + if (speed) { + mod->sngspd = speed; + mod->vbtick = 0; + } + + return 0; +} + +static void DoS3MVolSlide(UWORD tick, UWORD flags, MP_CONTROL *a, UBYTE inf) +{ + UBYTE lo, hi; + + if (inf) + a->s3mvolslide=inf; + else + inf=a->s3mvolslide; + + lo=inf&0xf; + hi=inf>>4; + + if (!lo) { + if ((tick)||(flags&UF_S3MSLIDES)) a->tmpvolume+=hi; + } else + if (!hi) { + if ((tick)||(flags&UF_S3MSLIDES)) a->tmpvolume-=lo; + } else + if (lo==0xf) { + if (!tick) a->tmpvolume+=(hi?hi:0xf); + } else + if (hi==0xf) { + if (!tick) a->tmpvolume-=(lo?lo:0xf); + } else + return; + + if (a->tmpvolume<0) + a->tmpvolume=0; + else if (a->tmpvolume>64) + a->tmpvolume=64; +} + +static int DoS3MEffectD(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + DoS3MVolSlide(tick, flags, a, UniGetByte()); + + return 1; +} + +static void DoS3MSlideDn(UWORD tick, MP_CONTROL *a, UBYTE inf) +{ + UBYTE hi,lo; + + if (inf) + a->slidespeed=inf; + else + inf=a->slidespeed; + + hi=inf>>4; + lo=inf&0xf; + + if (hi==0xf) { + if (!tick) a->tmpperiod+=(UWORD)lo<<2; + } else + if (hi==0xe) { + if (!tick) a->tmpperiod+=lo; + } else { + if (tick) a->tmpperiod+=(UWORD)inf<<2; + } +} + +static int DoS3MEffectE(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (a->main.period) + DoS3MSlideDn(tick, a,dat); + + return 0; +} + +static void DoS3MSlideUp(UWORD tick, MP_CONTROL *a, UBYTE inf) +{ + UBYTE hi,lo; + + if (inf) a->slidespeed=inf; + else inf=a->slidespeed; + + hi=inf>>4; + lo=inf&0xf; + + if (hi==0xf) { + if (!tick) a->tmpperiod-=(UWORD)lo<<2; + } else + if (hi==0xe) { + if (!tick) a->tmpperiod-=lo; + } else { + if (tick) a->tmpperiod-=(UWORD)inf<<2; + } +} + +static int DoS3MEffectF(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (a->main.period) + DoS3MSlideUp(tick, a,dat); + + return 0; +} + +static int DoS3MEffectI(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf, on, off; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + if (inf) + a->s3mtronof = inf; + else { + inf = a->s3mtronof; + if (!inf) + return 0; + } + + if (!tick) + return 0; + + on=(inf>>4)+1; + off=(inf&0xf)+1; + a->s3mtremor%=(on+off); + a->volume=(a->s3mtremortmpvolume:0; + a->ownvol=1; + a->s3mtremor++; + + return 0; +} + +static int DoS3MEffectQ(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf; + + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + if (a->main.period) { + if (inf) { + a->s3mrtgslide=inf>>4; + a->s3mrtgspeed=inf&0xf; + } + + /* only retrigger if low nibble > 0 */ + if (a->s3mrtgspeed>0) { + if (!a->retrig) { + /* when retrig counter reaches 0, reset counter and restart the + sample */ + if (a->main.kick!=KICK_NOTE) a->main.kick=KICK_KEYOFF; + a->retrig=a->s3mrtgspeed; + + if ((tick)||(flags&UF_S3MSLIDES)) { + switch (a->s3mrtgslide) { + case 1: + case 2: + case 3: + case 4: + case 5: + a->tmpvolume-=(1<<(a->s3mrtgslide-1)); + break; + case 6: + a->tmpvolume=(2*a->tmpvolume)/3; + break; + case 7: + a->tmpvolume>>=1; + break; + case 9: + case 0xa: + case 0xb: + case 0xc: + case 0xd: + a->tmpvolume+=(1<<(a->s3mrtgslide-9)); + break; + case 0xe: + a->tmpvolume=(3*a->tmpvolume)>>1; + break; + case 0xf: + a->tmpvolume=a->tmpvolume<<1; + break; + } + if (a->tmpvolume<0) + a->tmpvolume=0; + else if (a->tmpvolume>64) + a->tmpvolume=64; + } + } + a->retrig--; /* countdown */ + } + } + + return 0; +} + +static int DoS3MEffectR(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat, q; + UWORD temp=0; /* silence warning */ + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (!tick) { + if (dat&0x0f) a->trmdepth=dat&0xf; + if (dat&0xf0) a->trmspd=(dat&0xf0)>>2; + } + + q=(a->trmpos>>2)&0x1f; + + switch ((a->wavecontrol>>4)&3) { + case 0: /* sine */ + temp=VibratoTable[q]; + break; + case 1: /* ramp down */ + q<<=3; + if (a->trmpos<0) q=255-q; + temp=q; + break; + case 2: /* square wave */ + temp=255; + break; + case 3: /* random */ + temp=getrandom(256); + break; + } + + temp*=a->trmdepth; + temp>>=7; + + if (a->trmpos>=0) { + a->volume=a->tmpvolume+temp; + if (a->volume>64) a->volume=64; + } else { + a->volume=a->tmpvolume-temp; + if (a->volume<0) a->volume=0; + } + a->ownvol = 1; + + if (tick) + a->trmpos+=a->trmspd; + + return 0; +} + +static int DoS3MEffectT(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE tempo; + + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + tempo = UniGetByte(); + + if (tick || mod->patdly2) + return 0; + + mod->bpm = (tempo < 32) ? 32 : tempo; + + return 0; +} + +static int DoS3MEffectU(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat, q; + UWORD temp = 0; /* silence warning */ + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (!tick) { + if (dat&0x0f) a->vibdepth=dat&0xf; + if (dat&0xf0) a->vibspd=(dat&0xf0)>>2; + } else + if (a->main.period) { + q=(a->vibpos>>2)&0x1f; + + switch (a->wavecontrol&3) { + case 0: /* sine */ + temp=VibratoTable[q]; + break; + case 1: /* ramp down */ + q<<=3; + if (a->vibpos<0) q=255-q; + temp=q; + break; + case 2: /* square wave */ + temp=255; + break; + case 3: /* random */ + temp=getrandom(256); + break; + } + + temp*=a->vibdepth; + temp>>=8; + + if (a->vibpos>=0) + a->main.period=a->tmpperiod+temp; + else + a->main.period=a->tmpperiod-temp; + a->ownper = 1; + + a->vibpos+=a->vibspd; + } + + return 0; +} + +/*========== Envelope helpers */ + +static int DoKeyOff(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + a->main.keyoff|=KEY_OFF; + if ((!(a->main.volflg&EF_ON))||(a->main.volflg&EF_LOOP)) + a->main.keyoff=KEY_KILL; + + return 0; +} + +static int DoKeyFade(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if ((tick>=dat)||(tick==mod->sngspd-1)) { + a->main.keyoff=KEY_KILL; + if (!(a->main.volflg&EF_ON)) + a->main.fadevol=0; + } + + return 0; +} + +/*========== Fast Tracker effects */ + +/* DoXMEffect6 after DoXMEffectA */ + +static int DoXMEffectA(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf, lo, hi; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + if (inf) + a->s3mvolslide = inf; + else + inf = a->s3mvolslide; + + if (tick) { + lo=inf&0xf; + hi=inf>>4; + + if (!hi) { + a->tmpvolume-=lo; + if (a->tmpvolume<0) a->tmpvolume=0; + } else { + a->tmpvolume+=hi; + if (a->tmpvolume>64) a->tmpvolume=64; + } + } + + return 0; +} + +static int DoXMEffect6(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + if (a->main.period) + DoVibrato(tick, a); + + return DoXMEffectA(tick, flags, a, mod, channel); +} + +static int DoXMEffectE1(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (!tick) { + if (dat) a->fportupspd=dat; + if (a->main.period) + a->tmpperiod-=(a->fportupspd<<2); + } + + return 0; +} + +static int DoXMEffectE2(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (!tick) { + if (dat) a->fportdnspd=dat; + if (a->main.period) + a->tmpperiod+=(a->fportdnspd<<2); + } + + return 0; +} + +static int DoXMEffectEA(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (!tick) + if (dat) a->fslideupspd=dat; + a->tmpvolume+=a->fslideupspd; + if (a->tmpvolume>64) a->tmpvolume=64; + + return 0; +} + +static int DoXMEffectEB(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if (!tick) + if (dat) a->fslidednspd=dat; + a->tmpvolume-=a->fslidednspd; + if (a->tmpvolume<0) a->tmpvolume=0; + + return 0; +} + +static int DoXMEffectG(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + mod->volume=UniGetByte()<<1; + if (mod->volume>128) mod->volume=128; + + return 0; +} + +static int DoXMEffectH(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf; + + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + + if (tick) { + if (inf) mod->globalslide=inf; + else inf=mod->globalslide; + if (inf & 0xf0) inf&=0xf0; + mod->volume=mod->volume+((inf>>4)-(inf&0xf))*2; + + if (mod->volume<0) + mod->volume=0; + else if (mod->volume>128) + mod->volume=128; + } + + return 0; +} + +static int DoXMEffectL(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat=UniGetByte(); + if ((!tick)&&(a->main.i)) { + UWORD points; + INSTRUMENT *i=a->main.i; + MP_VOICE *aout; + + if ((aout=a->slave)) { + if (aout->venv.env) { + points=i->volenv[i->volpts-1].pos; + aout->venv.p=aout->venv.env[(dat>points)?points:dat].pos; + } + if (aout->penv.env) { + points=i->panenv[i->panpts-1].pos; + aout->penv.p=aout->penv.env[(dat>points)?points:dat].pos; + } + } + } + + return 0; +} + +static int DoXMEffectP(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf, lo, hi; + SWORD pan; + + (void)flags; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + if (!mod->panflag) + return 0; + + if (inf) + a->pansspd = inf; + else + inf =a->pansspd; + + if (tick) { + lo=inf&0xf; + hi=inf>>4; + + /* slide right has absolute priority */ + if (hi) + lo = 0; + + pan=((a->main.panning==PAN_SURROUND)?PAN_CENTER:a->main.panning)+hi-lo; + a->main.panning=(panPAN_RIGHT?PAN_RIGHT:pan); + } + + return 0; +} + +static int DoXMEffectX1(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (dat) + a->ffportupspd = dat; + else + dat = a->ffportupspd; + + if (a->main.period) + if (!tick) { + a->main.period-=dat; + a->tmpperiod-=dat; + a->ownper = 1; + } + + return 0; +} + +static int DoXMEffectX2(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (dat) + a->ffportdnspd=dat; + else + dat = a->ffportdnspd; + + if (a->main.period) + if (!tick) { + a->main.period+=dat; + a->tmpperiod+=dat; + a->ownper = 1; + } + + return 0; +} + +/*========== Impulse Tracker effects */ + +static void DoITToneSlide(UWORD tick, MP_CONTROL *a, UBYTE dat) +{ + if (dat) + a->portspeed = dat; + + /* if we don't come from another note, ignore the slide and play the note + as is */ + if (!a->oldnote || !a->main.period) + return; + + if ((!tick)&&(a->newsamp)){ + a->main.kick=KICK_NOTE; + a->main.start=-1; + } else + a->main.kick=(a->main.kick==KICK_NOTE)?KICK_ENV:KICK_ABSENT; + + if (tick) { + int dist; + + /* We have to slide a->main.period towards a->wantedperiod, compute the + difference between those two values */ + dist=a->main.period-a->wantedperiod; + + /* if they are equal or if portamentospeed is too big... */ + if ((!dist)||((a->portspeed<<2)>abs(dist))) + /* ... make tmpperiod equal tperiod */ + a->tmpperiod=a->main.period=a->wantedperiod; + else + if (dist>0) { + a->tmpperiod-=a->portspeed<<2; + a->main.period-=a->portspeed<<2; /* dist>0 slide up */ + } else { + a->tmpperiod+=a->portspeed<<2; + a->main.period+=a->portspeed<<2; /* dist<0 slide down */ + } + } else + a->tmpperiod=a->main.period; + a->ownper=1; +} + +static int DoITEffectG(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + DoITToneSlide(tick, a, UniGetByte()); + + return 0; +} + +static void DoITVibrato(UWORD tick, MP_CONTROL *a, UBYTE dat) +{ + UBYTE q; + UWORD temp=0; + + if (!tick) { + if (dat&0x0f) a->vibdepth=dat&0xf; + if (dat&0xf0) a->vibspd=(dat&0xf0)>>2; + } + if (!a->main.period) + return; + + q=(a->vibpos>>2)&0x1f; + + switch (a->wavecontrol&3) { + case 0: /* sine */ + temp=VibratoTable[q]; + break; + case 1: /* square wave */ + temp=255; + break; + case 2: /* ramp down */ + q<<=3; + if (a->vibpos<0) q=255-q; + temp=q; + break; + case 3: /* random */ + temp=getrandom(256); + break; + } + + temp*=a->vibdepth; + temp>>=8; + temp<<=2; + + if (a->vibpos>=0) + a->main.period=a->tmpperiod+temp; + else + a->main.period=a->tmpperiod-temp; + a->ownper=1; + + a->vibpos+=a->vibspd; +} + +static int DoITEffectH(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + DoITVibrato(tick, a, UniGetByte()); + + return 0; +} + +static int DoITEffectI(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf, on, off; + + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + if (inf) + a->s3mtronof = inf; + else { + inf = a->s3mtronof; + if (!inf) + return 0; + } + + on=(inf>>4); + off=(inf&0xf); + + a->s3mtremor%=(on+off); + a->volume=(a->s3mtremortmpvolume:0; + a->ownvol = 1; + a->s3mtremor++; + + return 0; +} + +static int DoITEffectM(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + a->main.chanvol=UniGetByte(); + if (a->main.chanvol>64) + a->main.chanvol=64; + else if (a->main.chanvol<0) + a->main.chanvol=0; + + return 0; +} + +static int DoITEffectN(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf, lo, hi; + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + + if (inf) + a->chanvolslide = inf; + else + inf = a->chanvolslide; + + lo=inf&0xf; + hi=inf>>4; + + if (!hi) + a->main.chanvol-=lo; + else + if (!lo) { + a->main.chanvol+=hi; + } else + if (hi==0xf) { + if (!tick) a->main.chanvol-=lo; + } else + if (lo==0xf) { + if (!tick) a->main.chanvol+=hi; + } + + if (a->main.chanvol<0) + a->main.chanvol=0; + else if (a->main.chanvol>64) + a->main.chanvol=64; + + return 0; +} + +static int DoITEffectP(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf, lo, hi; + SWORD pan; + + (void)flags; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + if (inf) + a->pansspd = inf; + else + inf = a->pansspd; + + if (!mod->panflag) + return 0; + + lo=inf&0xf; + hi=inf>>4; + + pan=(a->main.panning==PAN_SURROUND)?PAN_CENTER:a->main.panning; + + if (!hi) + pan+=lo<<2; + else + if (!lo) { + pan-=hi<<2; + } else + if (hi==0xf) { + if (!tick) pan+=lo<<2; + } else + if (lo==0xf) { + if (!tick) pan-=hi<<2; + } + a->main.panning= + (panPAN_RIGHT?PAN_RIGHT:pan); + + return 0; +} + +static int DoITEffectT(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE tempo; + SWORD temp; + + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + tempo = UniGetByte(); + + if (mod->patdly2) + return 0; + + temp = mod->bpm; + if (tempo & 0x10) + temp += (tempo & 0x0f); + else + temp -= tempo; + + mod->bpm=(temp>255)?255:(temp<1?1:temp); + + return 0; +} + +static int DoITEffectU(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat, q; + UWORD temp = 0; /* silence warning */ + + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat = UniGetByte(); + if (!tick) { + if (dat&0x0f) a->vibdepth=dat&0xf; + if (dat&0xf0) a->vibspd=(dat&0xf0)>>2; + } + if (a->main.period) { + q=(a->vibpos>>2)&0x1f; + + switch (a->wavecontrol&3) { + case 0: /* sine */ + temp=VibratoTable[q]; + break; + case 1: /* square wave */ + temp=255; + break; + case 2: /* ramp down */ + q<<=3; + if (a->vibpos<0) q=255-q; + temp=q; + break; + case 3: /* random */ + temp=getrandom(256); + break; + } + + temp*=a->vibdepth; + temp>>=8; + + if (a->vibpos>=0) + a->main.period=a->tmpperiod+temp; + else + a->main.period=a->tmpperiod-temp; + a->ownper = 1; + + a->vibpos+=a->vibspd; + } + + return 0; +} + +static int DoITEffectW(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE inf, lo, hi; + + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + inf = UniGetByte(); + + if (inf) + mod->globalslide = inf; + else + inf = mod->globalslide; + + lo=inf&0xf; + hi=inf>>4; + + if (!lo) { + if (tick) mod->volume+=hi; + } else + if (!hi) { + if (tick) mod->volume-=lo; + } else + if (lo==0xf) { + if (!tick) mod->volume+=hi; + } else + if (hi==0xf) { + if (!tick) mod->volume-=lo; + } + + if (mod->volume<0) + mod->volume=0; + else if (mod->volume>128) + mod->volume=128; + + return 0; +} + +static int DoITEffectY(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat, q; + SLONG temp = 0; /* silence warning */ + + (void)flags; /* unused arg */ + + dat=UniGetByte(); + if (!tick) { + if (dat&0x0f) a->panbdepth=(dat&0xf); + if (dat&0xf0) a->panbspd=(dat&0xf0)>>4; + } + if (mod->panflag) { + q=a->panbpos; + + switch (a->panbwave) { + case 0: /* sine */ + temp=PanbrelloTable[q]; + break; + case 1: /* square wave */ + temp=(q<0x80)?64:0; + break; + case 2: /* ramp down */ + q<<=3; + temp=q; + break; + case 3: /* random */ + temp=getrandom(256); + break; + } + + temp*=a->panbdepth; + temp=(temp/8)+mod->panning[channel]; + + a->main.panning= + (tempPAN_RIGHT?PAN_RIGHT:temp); + a->panbpos+=a->panbspd; + + } + + return 0; +} + +static void DoNNAEffects(MODULE *, MP_CONTROL *, UBYTE); + +/* Impulse/Scream Tracker Sxx effects. + All Sxx effects share the same memory space. */ +static int DoITEffectS0(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat, inf, c; + + dat = UniGetByte(); + inf=dat&0xf; + c=dat>>4; + + if (!dat) { + c=a->sseffect; + inf=a->ssdata; + } else { + a->sseffect=c; + a->ssdata=inf; + } + + switch (c) { + case SS_GLISSANDO: /* S1x set glissando voice */ + DoEEffects(tick, flags, a, mod, channel, 0x30|inf); + break; + case SS_FINETUNE: /* S2x set finetune */ + DoEEffects(tick, flags, a, mod, channel, 0x50|inf); + break; + case SS_VIBWAVE: /* S3x set vibrato waveform */ + DoEEffects(tick, flags, a, mod, channel, 0x40|inf); + break; + case SS_TREMWAVE: /* S4x set tremolo waveform */ + DoEEffects(tick, flags, a, mod, channel, 0x70|inf); + break; + case SS_PANWAVE: /* S5x panbrello */ + a->panbwave=inf; + break; + case SS_FRAMEDELAY: /* S6x delay x number of frames (patdly) */ + DoEEffects(tick, flags, a, mod, channel, 0xe0|inf); + break; + case SS_S7EFFECTS: /* S7x instrument / NNA commands */ + DoNNAEffects(mod, a, inf); + break; + case SS_PANNING: /* S8x set panning position */ + DoEEffects(tick, flags, a, mod, channel, 0x80 | inf); + break; + case SS_SURROUND: /* S9x set surround sound */ + if (mod->panflag) + a->main.panning = mod->panning[channel] = PAN_SURROUND; + break; + case SS_HIOFFSET: /* SAy set high order sample offset yxx00h */ + if (!tick) { + a->hioffset=inf<<16; + a->main.start=a->hioffset|a->soffset; + + if ((a->main.s)&&(a->main.start>a->main.s->length)) + a->main.start=a->main.s->flags&(SF_LOOP|SF_BIDI)? + a->main.s->loopstart:a->main.s->length; + } + break; + case SS_PATLOOP: /* SBx pattern loop */ + DoEEffects(tick, flags, a, mod, channel, 0x60|inf); + break; + case SS_NOTECUT: /* SCx notecut */ + if (!inf) inf = 1; + DoEEffects(tick, flags, a, mod, channel, 0xC0|inf); + break; + case SS_NOTEDELAY: /* SDx notedelay */ + DoEEffects(tick, flags, a, mod, channel, 0xD0|inf); + break; + case SS_PATDELAY: /* SEx patterndelay */ + DoEEffects(tick, flags, a, mod, channel, 0xE0|inf); + break; + } + + return 0; +} + +/*========== Impulse Tracker Volume/Pan Column effects */ + +/* + * All volume/pan column effects share the same memory space. + */ + +static int DoVolEffects(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE c, inf; + + (void)channel; /* unused arg */ + + c = UniGetByte(); + inf = UniGetByte(); + + if ((!c)&&(!inf)) { + c=a->voleffect; + inf=a->voldata; + } else { + a->voleffect=c; + a->voldata=inf; + } + + if (c) + switch (c) { + case VOL_VOLUME: + if (tick) break; + if (inf>64) inf=64; + a->tmpvolume=inf; + break; + case VOL_PANNING: + if (mod->panflag) + a->main.panning=inf; + break; + case VOL_VOLSLIDE: + DoS3MVolSlide(tick, flags, a, inf); + return 1; + case VOL_PITCHSLIDEDN: + if (a->main.period) + DoS3MSlideDn(tick, a, inf); + break; + case VOL_PITCHSLIDEUP: + if (a->main.period) + DoS3MSlideUp(tick, a, inf); + break; + case VOL_PORTAMENTO: + DoITToneSlide(tick, a, inf); + break; + case VOL_VIBRATO: + DoITVibrato(tick, a, inf); + break; + } + + return 0; +} + +/*========== UltraTracker effects */ + +static int DoULTEffect9(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UWORD offset=UniGetWord(); + + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + if (offset) + a->ultoffset=offset; + + a->main.start=a->ultoffset<<2; + if ((a->main.s)&&(a->main.start>a->main.s->length)) + a->main.start=a->main.s->flags&(SF_LOOP|SF_BIDI)? + a->main.s->loopstart:a->main.s->length; + + return 0; +} + +/*========== OctaMED effects */ + +static int DoMEDSpeed(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UWORD speed=UniGetWord(); + + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)channel; /* unused arg */ + + mod->bpm=speed; + + return 0; +} + +static int DoMEDEffectF1(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + DoEEffects(tick, flags, a, mod, channel, 0x90|(mod->sngspd/2)); + + return 0; +} + +static int DoMEDEffectF2(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + DoEEffects(tick, flags, a, mod, channel, 0xd0|(mod->sngspd/2)); + + return 0; +} + +static int DoMEDEffectF3(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + DoEEffects(tick, flags, a, mod, channel, 0x90|(mod->sngspd/3)); + + return 0; +} + +/*========== Oktalyzer effects */ + +static int DoOktArp(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + UBYTE dat, dat2; + + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + dat2 = UniGetByte(); /* arpeggio style */ + dat = UniGetByte(); + if (!tick) { + if (!dat && (flags & UF_ARPMEM)) + dat=a->arpmem; + else + a->arpmem=dat; + } + if (a->main.period) + DoArpeggio(tick, flags, a, dat2); + + return 0; +} + +/*========== General player functions */ + +static int DoNothing(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel) +{ + (void)tick; /* unused arg */ + (void)flags; /* unused arg */ + (void)a; /* unused arg */ + (void)mod; /* unused arg */ + (void)channel; /* unused arg */ + + UniSkipOpcode(); + + return 0; +} + +typedef int (*effect_func) (UWORD, UWORD, MP_CONTROL *, MODULE *, SWORD); + +static effect_func effects[UNI_LAST] = { + DoNothing, /* 0 */ + DoNothing, /* UNI_NOTE */ + DoNothing, /* UNI_INSTRUMENT */ + DoPTEffect0, /* UNI_PTEFFECT0 */ + DoPTEffect1, /* UNI_PTEFFECT1 */ + DoPTEffect2, /* UNI_PTEFFECT2 */ + DoPTEffect3, /* UNI_PTEFFECT3 */ + DoPTEffect4, /* UNI_PTEFFECT4 */ + DoPTEffect5, /* UNI_PTEFFECT5 */ + DoPTEffect6, /* UNI_PTEFFECT6 */ + DoPTEffect7, /* UNI_PTEFFECT7 */ + DoPTEffect8, /* UNI_PTEFFECT8 */ + DoPTEffect9, /* UNI_PTEFFECT9 */ + DoPTEffectA, /* UNI_PTEFFECTA */ + DoPTEffectB, /* UNI_PTEFFECTB */ + DoPTEffectC, /* UNI_PTEFFECTC */ + DoPTEffectD, /* UNI_PTEFFECTD */ + DoPTEffectE, /* UNI_PTEFFECTE */ + DoPTEffectF, /* UNI_PTEFFECTF */ + DoS3MEffectA, /* UNI_S3MEFFECTA */ + DoS3MEffectD, /* UNI_S3MEFFECTD */ + DoS3MEffectE, /* UNI_S3MEFFECTE */ + DoS3MEffectF, /* UNI_S3MEFFECTF */ + DoS3MEffectI, /* UNI_S3MEFFECTI */ + DoS3MEffectQ, /* UNI_S3MEFFECTQ */ + DoS3MEffectR, /* UNI_S3MEFFECTR */ + DoS3MEffectT, /* UNI_S3MEFFECTT */ + DoS3MEffectU, /* UNI_S3MEFFECTU */ + DoKeyOff, /* UNI_KEYOFF */ + DoKeyFade, /* UNI_KEYFADE */ + DoVolEffects, /* UNI_VOLEFFECTS */ + DoPTEffect4, /* UNI_XMEFFECT4 */ + DoXMEffect6, /* UNI_XMEFFECT6 */ + DoXMEffectA, /* UNI_XMEFFECTA */ + DoXMEffectE1, /* UNI_XMEFFECTE1 */ + DoXMEffectE2, /* UNI_XMEFFECTE2 */ + DoXMEffectEA, /* UNI_XMEFFECTEA */ + DoXMEffectEB, /* UNI_XMEFFECTEB */ + DoXMEffectG, /* UNI_XMEFFECTG */ + DoXMEffectH, /* UNI_XMEFFECTH */ + DoXMEffectL, /* UNI_XMEFFECTL */ + DoXMEffectP, /* UNI_XMEFFECTP */ + DoXMEffectX1, /* UNI_XMEFFECTX1 */ + DoXMEffectX2, /* UNI_XMEFFECTX2 */ + DoITEffectG, /* UNI_ITEFFECTG */ + DoITEffectH, /* UNI_ITEFFECTH */ + DoITEffectI, /* UNI_ITEFFECTI */ + DoITEffectM, /* UNI_ITEFFECTM */ + DoITEffectN, /* UNI_ITEFFECTN */ + DoITEffectP, /* UNI_ITEFFECTP */ + DoITEffectT, /* UNI_ITEFFECTT */ + DoITEffectU, /* UNI_ITEFFECTU */ + DoITEffectW, /* UNI_ITEFFECTW */ + DoITEffectY, /* UNI_ITEFFECTY */ + DoNothing, /* UNI_ITEFFECTZ */ + DoITEffectS0, /* UNI_ITEFFECTS0 */ + DoULTEffect9, /* UNI_ULTEFFECT9 */ + DoMEDSpeed, /* UNI_MEDSPEED */ + DoMEDEffectF1, /* UNI_MEDEFFECTF1 */ + DoMEDEffectF2, /* UNI_MEDEFFECTF2 */ + DoMEDEffectF3, /* UNI_MEDEFFECTF3 */ + DoOktArp, /* UNI_OKTARP */ +}; + +static int pt_playeffects(MODULE *mod, SWORD channel, MP_CONTROL *a) +{ + UWORD tick = mod->vbtick; + UWORD flags = mod->flags; + UBYTE c; + int explicitslides = 0; + effect_func f; + + while((c=UniGetByte())) { + f = effects[c]; + if (f != DoNothing) + a->sliding = 0; + explicitslides |= f(tick, flags, a, mod, channel); + } + return explicitslides; +} + +static void DoNNAEffects(MODULE *mod, MP_CONTROL *a, UBYTE dat) +{ + int t; + MP_VOICE *aout; + + dat&=0xf; + aout=(a->slave)?a->slave:NULL; + + switch (dat) { + case 0x0: /* past note cut */ + for (t=0;tvoice[t].master==a) + mod->voice[t].main.fadevol=0; + break; + case 0x1: /* past note off */ + for (t=0;tvoice[t].master==a) { + mod->voice[t].main.keyoff|=KEY_OFF; + if ((!(mod->voice[t].venv.flg & EF_ON))|| + (mod->voice[t].venv.flg & EF_LOOP)) + mod->voice[t].main.keyoff=KEY_KILL; + } + break; + case 0x2: /* past note fade */ + for (t=0;tvoice[t].master==a) + mod->voice[t].main.keyoff|=KEY_FADE; + break; + case 0x3: /* set NNA note cut */ + a->main.nna=(a->main.nna&~NNA_MASK)|NNA_CUT; + break; + case 0x4: /* set NNA note continue */ + a->main.nna=(a->main.nna&~NNA_MASK)|NNA_CONTINUE; + break; + case 0x5: /* set NNA note off */ + a->main.nna=(a->main.nna&~NNA_MASK)|NNA_OFF; + break; + case 0x6: /* set NNA note fade */ + a->main.nna=(a->main.nna&~NNA_MASK)|NNA_FADE; + break; + case 0x7: /* disable volume envelope */ + if (aout) + aout->main.volflg&=~EF_ON; + break; + case 0x8: /* enable volume envelope */ + if (aout) + aout->main.volflg|=EF_ON; + break; + case 0x9: /* disable panning envelope */ + if (aout) + aout->main.panflg&=~EF_ON; + break; + case 0xa: /* enable panning envelope */ + if (aout) + aout->main.panflg|=EF_ON; + break; + case 0xb: /* disable pitch envelope */ + if (aout) + aout->main.pitflg&=~EF_ON; + break; + case 0xc: /* enable pitch envelope */ + if (aout) + aout->main.pitflg|=EF_ON; + break; + } +} + +void pt_UpdateVoices(MODULE *mod, int max_volume) +{ + SWORD envpan,envvol,envpit,channel; + UWORD playperiod; + SLONG vibval,vibdpt; + ULONG tmpvol; + + MP_VOICE *aout; + INSTRUMENT *i; + SAMPLE *s; + + mod->totalchn=mod->realchn=0; + for (channel=0;channelvoice[channel]; + i=aout->main.i; + s=aout->main.s; + + if (!s || !s->length) continue; + + if (aout->main.period<40) + aout->main.period=40; + else if (aout->main.period>50000) + aout->main.period=50000; + + if ((aout->main.kick==KICK_NOTE)||(aout->main.kick==KICK_KEYOFF)) { + Voice_Play_internal(channel,s,(aout->main.start==-1)? + ((s->flags&SF_UST_LOOP)?s->loopstart:0):aout->main.start); + aout->main.fadevol=32768; + aout->aswppos=0; + } + + envvol = 256; + envpan = PAN_CENTER; + envpit = 32; + if (i && ((aout->main.kick==KICK_NOTE)||(aout->main.kick==KICK_ENV))) { + if (aout->main.volflg & EF_ON) + envvol = StartEnvelope(&aout->venv,aout->main.volflg, + i->volpts,i->volsusbeg,i->volsusend, + i->volbeg,i->volend,i->volenv,aout->main.keyoff); + if (aout->main.panflg & EF_ON) + envpan = StartEnvelope(&aout->penv,aout->main.panflg, + i->panpts,i->pansusbeg,i->pansusend, + i->panbeg,i->panend,i->panenv,aout->main.keyoff); + if (aout->main.pitflg & EF_ON) + envpit = StartEnvelope(&aout->cenv,aout->main.pitflg, + i->pitpts,i->pitsusbeg,i->pitsusend, + i->pitbeg,i->pitend,i->pitenv,aout->main.keyoff); + + if (aout->cenv.flg & EF_ON) + aout->masterperiod=GetPeriod(mod->flags, + (UWORD)aout->main.note<<1, aout->master->speed); + } else { + if (aout->main.volflg & EF_ON) + envvol = ProcessEnvelope(aout,&aout->venv,256); + if (aout->main.panflg & EF_ON) + envpan = ProcessEnvelope(aout,&aout->penv,PAN_CENTER); + if (aout->main.pitflg & EF_ON) + envpit = ProcessEnvelope(aout,&aout->cenv,32); + } + aout->main.kick=KICK_ABSENT; + + tmpvol = aout->main.fadevol; /* max 32768 */ + tmpvol *= aout->main.chanvol; /* * max 64 */ + tmpvol *= aout->main.outvolume; /* * max 256 */ + tmpvol /= (256 * 64); /* tmpvol is max 32768 again */ + aout->totalvol = tmpvol >> 2; /* used to determine samplevolume */ + tmpvol *= envvol; /* * max 256 */ + tmpvol *= mod->volume; /* * max 128 */ + tmpvol /= (128 * 256 * 128); + + /* fade out */ + if (mod->sngpos>=mod->numpos) + tmpvol=0; + else + tmpvol=(tmpvol*max_volume)/128; + + if ((aout->masterchn!=-1)&& mod->control[aout->masterchn].muted) + Voice_SetVolume_internal(channel,0); + else { + Voice_SetVolume_internal(channel,tmpvol); + if ((tmpvol)&&(aout->master)&&(aout->master->slave==aout)) + mod->realchn++; + mod->totalchn++; + } + + if (aout->main.panning==PAN_SURROUND) + Voice_SetPanning_internal(channel,PAN_SURROUND); + else + if ((mod->panflag)&&(aout->penv.flg & EF_ON)) + Voice_SetPanning_internal(channel, + DoPan(envpan,aout->main.panning)); + else + Voice_SetPanning_internal(channel,aout->main.panning); + + if (aout->main.period && s->vibdepth) + switch (s->vibtype) { + case 0: + vibval=avibtab[s->avibpos&127]; + if (aout->avibpos & 0x80) vibval=-vibval; + break; + case 1: + vibval=64; + if (aout->avibpos & 0x80) vibval=-vibval; + break; + case 2: + vibval=63-(((aout->avibpos+128)&255)>>1); + break; + default: + vibval=(((aout->avibpos+128)&255)>>1)-64; + break; + } + else + vibval=0; + + if (s->vibflags & AV_IT) { + if ((aout->aswppos>>8)vibdepth) { + aout->aswppos += s->vibsweep; + vibdpt=aout->aswppos; + } else + vibdpt=s->vibdepth<<8; + vibval=(vibval*vibdpt)>>16; + if (aout->mflag) { + if (!(mod->flags&UF_LINEAR)) vibval>>=1; + aout->main.period-=vibval; + } + } else { + /* do XM style auto-vibrato */ + if (!(aout->main.keyoff & KEY_OFF)) { + if (aout->aswpposvibsweep) { + vibdpt=(aout->aswppos*s->vibdepth)/s->vibsweep; + aout->aswppos++; + } else + vibdpt=s->vibdepth; + } else { + /* keyoff -> depth becomes 0 if final depth wasn't reached or + stays at final level if depth WAS reached */ + if (aout->aswppos>=s->vibsweep) + vibdpt=s->vibdepth; + else + vibdpt=0; + } + vibval=(vibval*vibdpt)>>8; + aout->main.period-=vibval; + } + + /* update vibrato position */ + aout->avibpos=(aout->avibpos+s->vibrate)&0xff; + + /* process pitch envelope */ + playperiod=aout->main.period; + + if ((aout->main.pitflg&EF_ON)&&(envpit!=32)) { + long p1; + + envpit-=32; + if ((aout->main.note<<1)+envpit<=0) envpit=-(aout->main.note<<1); + + p1=GetPeriod(mod->flags, ((UWORD)aout->main.note<<1)+envpit, + aout->master->speed)-aout->masterperiod; + if (p1>0) { + if ((UWORD)(playperiod+p1)<=playperiod) { + p1=0; + aout->main.keyoff|=KEY_OFF; + } + } else if (p1<0) { + if ((UWORD)(playperiod+p1)>=playperiod) { + p1=0; + aout->main.keyoff|=KEY_OFF; + } + } + playperiod+=p1; + } + + if (!aout->main.fadevol) { /* check for a dead note (fadevol=0) */ + Voice_Stop_internal(channel); + mod->totalchn--; + if ((tmpvol)&&(aout->master)&&(aout->master->slave==aout)) + mod->realchn--; + } else { + Voice_SetFrequency_internal(channel, + getfrequency(mod->flags,playperiod)); + + /* if keyfade, start substracting fadeoutspeed from fadevol: */ + if ((i)&&(aout->main.keyoff&KEY_FADE)) { + if (aout->main.fadevol>=i->volfade) + aout->main.fadevol-=i->volfade; + else + aout->main.fadevol=0; + } + } + + md_bpm=mod->bpm+mod->relspd; + if (md_bpm<32) + md_bpm=32; + else if ((!(mod->flags&UF_HIGHBPM)) && md_bpm>255) + md_bpm=255; + } +} + +/* Handles new notes or instruments */ +void pt_Notes(MODULE *mod) +{ + SWORD channel; + MP_CONTROL *a; + UBYTE c,inst; + int tr,funky; /* funky is set to indicate note or instrument change */ + + for (channel=0;channelnumchn;channel++) { + a=&mod->control[channel]; + + if (mod->sngpos>=mod->numpos) { + tr=mod->numtrk; + mod->numrow=0; + } else { + tr=mod->patterns[(mod->positions[mod->sngpos]*mod->numchn)+channel]; + mod->numrow=mod->pattrows[mod->positions[mod->sngpos]]; + } + + a->row=(trnumtrk)?UniFindRow(mod->tracks[tr],mod->patpos):NULL; + a->newsamp=0; + if (!mod->vbtick) a->main.notedelay=0; + + if (!a->row) continue; + UniSetRow(a->row); + funky=0; + + while((c=UniGetByte())) + switch (c) { + case UNI_NOTE: + funky|=1; + a->oldnote=a->anote,a->anote=UniGetByte(); + a->main.kick =KICK_NOTE; + a->main.start=-1; + a->sliding=0; + + /* retrig tremolo and vibrato waves ? */ + if (!(a->wavecontrol & 0x80)) a->trmpos=0; + if (!(a->wavecontrol & 0x08)) a->vibpos=0; + if (!a->panbwave) a->panbpos=0; + break; + case UNI_INSTRUMENT: + inst=UniGetByte(); + if (inst>=mod->numins) break; /* safety valve */ + funky|=2; + a->main.i=(mod->flags & UF_INST)?&mod->instruments[inst]:NULL; + a->retrig=0; + a->s3mtremor=0; + a->ultoffset=0; + a->main.sample=inst; + break; + default: + UniSkipOpcode(); + break; + } + + if (funky) { + INSTRUMENT *i; + SAMPLE *s; + + if ((i=a->main.i)) { + if (i->samplenumber[a->anote] >= mod->numsmp) continue; + s=&mod->samples[i->samplenumber[a->anote]]; + a->main.note=i->samplenote[a->anote]; + } else { + a->main.note=a->anote; + s=&mod->samples[a->main.sample]; + } + + if (a->main.s!=s) { + a->main.s=s; + a->newsamp=a->main.period; + } + + /* channel or instrument determined panning ? */ + a->main.panning=mod->panning[channel]; + if (s->flags & SF_OWNPAN) + a->main.panning=s->panning; + else if ((i)&&(i->flags & IF_OWNPAN)) + a->main.panning=i->panning; + + a->main.handle=s->handle; + a->speed=s->speed; + + if (i) { + if ((mod->panflag)&&(i->flags & IF_PITCHPAN) + &&(a->main.panning!=PAN_SURROUND)){ + a->main.panning+= + ((a->anote-i->pitpancenter)*i->pitpansep)/8; + if (a->main.panningmain.panning=PAN_LEFT; + else if (a->main.panning>PAN_RIGHT) + a->main.panning=PAN_RIGHT; + } + a->main.pitflg=i->pitflg; + a->main.volflg=i->volflg; + a->main.panflg=i->panflg; + a->main.nna=i->nnatype; + a->dca=i->dca; + a->dct=i->dct; + } else { + a->main.pitflg=a->main.volflg=a->main.panflg=0; + a->main.nna=a->dca=0; + a->dct=DCT_OFF; + } + + if (funky&2) /* instrument change */ { + /* IT random volume variations: 0:8 bit fixed, and one bit for + sign. */ + a->volume=a->tmpvolume=s->volume; + if ((s)&&(i)) { + if (i->rvolvar) { + a->volume=a->tmpvolume=s->volume+ + ((s->volume*((SLONG)i->rvolvar*(SLONG)getrandom(512) + ))/25600); + if (a->volume<0) + a->volume=a->tmpvolume=0; + else if (a->volume>64) + a->volume=a->tmpvolume=64; + } + if ((mod->panflag)&&(a->main.panning!=PAN_SURROUND)) { + a->main.panning+=((a->main.panning*((SLONG)i->rpanvar* + (SLONG)getrandom(512)))/25600); + if (a->main.panningmain.panning=PAN_LEFT; + else if (a->main.panning>PAN_RIGHT) + a->main.panning=PAN_RIGHT; + } + } + } + + a->wantedperiod=a->tmpperiod= + GetPeriod(mod->flags, (UWORD)a->main.note<<1,a->speed); + a->main.keyoff=KEY_KICK; + } + } +} + +/* Handles effects */ +void pt_EffectsPass1(MODULE *mod) +{ + SWORD channel; + MP_CONTROL *a; + MP_VOICE *aout; + int explicitslides; + + for (channel=0;channelnumchn;channel++) { + a=&mod->control[channel]; + + if ((aout=a->slave)) { + a->main.fadevol=aout->main.fadevol; + a->main.period=aout->main.period; + if (a->main.kick==KICK_KEYOFF) + a->main.keyoff=aout->main.keyoff; + } + + if (!a->row) continue; + UniSetRow(a->row); + + a->ownper=a->ownvol=0; + explicitslides = pt_playeffects(mod, channel, a); + + /* continue volume slide if necessary for XM and IT */ + if (mod->flags&UF_BGSLIDES) { + if (!explicitslides && a->sliding) + DoS3MVolSlide(mod->vbtick, mod->flags, a, 0); + else if (a->tmpvolume) + a->sliding = explicitslides; + } + + if (!a->ownper) + a->main.period=a->tmpperiod; + if (!a->ownvol) + a->volume=a->tmpvolume; + + if (a->main.s) { + if (a->main.i) + a->main.outvolume= + (a->volume*a->main.s->globvol*a->main.i->globvol)>>10; + else + a->main.outvolume=(a->volume*a->main.s->globvol)>>4; + if (a->main.outvolume>256) + a->main.outvolume=256; + else if (a->main.outvolume<0) + a->main.outvolume=0; + } + } +} + +/* NNA management */ +void pt_NNA(MODULE *mod) +{ + SWORD channel; + MP_CONTROL *a; + + for (channel=0;channelnumchn;channel++) { + a=&mod->control[channel]; + + if (a->main.kick==KICK_NOTE) { + BOOL kill=0; + + if (a->slave) { + MP_VOICE *aout; + + aout=a->slave; + if (aout->main.nna & NNA_MASK) { + /* Make sure the old MP_VOICE channel knows it has no + master now ! */ + a->slave=NULL; + /* assume the channel is taken by NNA */ + aout->mflag=0; + + switch (aout->main.nna) { + case NNA_CONTINUE: /* continue note, do nothing */ + break; + case NNA_OFF: /* note off */ + aout->main.keyoff|=KEY_OFF; + if ((!(aout->main.volflg & EF_ON))|| + (aout->main.volflg & EF_LOOP)) + aout->main.keyoff=KEY_KILL; + break; + case NNA_FADE: + aout->main.keyoff |= KEY_FADE; + break; + } + } + } + + if (a->dct!=DCT_OFF) { + int t; + + for (t=0;tvoice[t].masterchn==channel)&& + (a->main.sample==mod->voice[t].main.sample)) { + kill=0; + switch (a->dct) { + case DCT_NOTE: + if (a->main.note==mod->voice[t].main.note) + kill=1; + break; + case DCT_SAMPLE: + if (a->main.handle==mod->voice[t].main.handle) + kill=1; + break; + case DCT_INST: + kill=1; + break; + } + if (kill) + switch (a->dca) { + case DCA_CUT: + mod->voice[t].main.fadevol=0; + break; + case DCA_OFF: + mod->voice[t].main.keyoff|=KEY_OFF; + if ((!(mod->voice[t].main.volflg&EF_ON))|| + (mod->voice[t].main.volflg&EF_LOOP)) + mod->voice[t].main.keyoff=KEY_KILL; + break; + case DCA_FADE: + mod->voice[t].main.keyoff|=KEY_FADE; + break; + } + } + } + } /* if (a->main.kick==KICK_NOTE) */ + } +} + +/* Setup module and NNA voices */ +void pt_SetupVoices(MODULE *mod) +{ + SWORD channel; + MP_CONTROL *a; + MP_VOICE *aout; + + for (channel=0;channelnumchn;channel++) { + a=&mod->control[channel]; + + if (a->main.notedelay) continue; + if (a->main.kick==KICK_NOTE) { + /* if no channel was cut above, find an empty or quiet channel + here */ + if (mod->flags&UF_NNA) { + if (!a->slave) { + int newchn; + + if ((newchn=MP_FindEmptyChannel(mod))!=-1) + a->slave=&mod->voice[a->slavechn=newchn]; + } + } else + a->slave=&mod->voice[a->slavechn=channel]; + + /* assign parts of MP_VOICE only done for a KICK_NOTE */ + if ((aout=a->slave)) { + if (aout->mflag && aout->master) aout->master->slave=NULL; + aout->master=a; + a->slave=aout; + aout->masterchn=channel; + aout->mflag=1; + } + } else + aout=a->slave; + + if (aout) + aout->main=a->main; + a->main.kick=KICK_ABSENT; + } +} + +/* second effect pass */ +void pt_EffectsPass2(MODULE *mod) +{ + SWORD channel; + MP_CONTROL *a; + UBYTE c; + + for (channel=0;channelnumchn;channel++) { + a=&mod->control[channel]; + + if (!a->row) continue; + UniSetRow(a->row); + + while((c=UniGetByte())) + if (c==UNI_ITEFFECTS0) { + c=UniGetByte(); + if ((c>>4)==SS_S7EFFECTS) + DoNNAEffects(mod, a, c&0xf); + } else + UniSkipOpcode(); + } +} + +void Player_HandleTick(void) +{ + SWORD channel; + int max_volume; + +#if 0 + /* don't handle the very first ticks, this allows the other hardware to + settle down so we don't loose any starting notes */ + if (isfirst) { + isfirst--; + return; + } +#endif + + if ((!pf)||(pf->forbid)||(pf->sngpos>=pf->numpos)) return; + + /* update time counter (sngtime is in milliseconds (in fact 2^-10)) */ + pf->sngremainder+=(1<<9)*5; /* thus 2.5*(1<<10), since fps=0.4xtempo */ + pf->sngtime+=pf->sngremainder/pf->bpm; + pf->sngremainder%=pf->bpm; + + if (++pf->vbtick>=pf->sngspd) { + if (pf->pat_repcrazy) + pf->pat_repcrazy=0; /* play 2 times row 0 */ + else + pf->patpos++; + pf->vbtick=0; + + /* process pattern-delay. pf->patdly2 is the counter and pf->patdly is + the command memory. */ + if (pf->patdly) + pf->patdly2=pf->patdly,pf->patdly=0; + if (pf->patdly2) { + /* patterndelay active */ + if (--pf->patdly2) + /* so turn back pf->patpos by 1 */ + if (pf->patpos) pf->patpos--; + } + + /* do we have to get a new patternpointer ? (when pf->patpos reaches the + pattern size, or when a patternbreak is active) */ + if (((pf->patpos>=pf->numrow)&&(pf->numrow>0))&&(!pf->posjmp)) + pf->posjmp=3; + + if (pf->posjmp) { + pf->patpos=pf->numrow?(pf->patbrk%pf->numrow):0; + pf->pat_repcrazy=0; + pf->sngpos+=(pf->posjmp-2); + for (channel=0;channelnumchn;channel++) + pf->control[channel].pat_reppos=-1; + + pf->patbrk=pf->posjmp=0; + /* handle the "---" (end of song) pattern since it can occur + *inside* the module in some formats */ + if ((pf->sngpos>=pf->numpos)|| + (pf->positions[pf->sngpos]==LAST_PATTERN)) { + if (!pf->wrap) return; + if (!(pf->sngpos=pf->reppos)) { + pf->volume=pf->initvolume>128?128:pf->initvolume; + if(pf->initspeed!=0) + pf->sngspd=pf->initspeed<32?pf->initspeed:32; + else + pf->sngspd=6; + pf->bpm=pf->inittempo<32?32:pf->inittempo; + } + } + if (pf->sngpos<0) pf->sngpos=pf->numpos-1; + } + + if (!pf->patdly2) + pt_Notes(pf); + } + + /* Fade global volume if enabled and we're playing the last pattern */ + if (((pf->sngpos==pf->numpos-1)|| + (pf->positions[pf->sngpos+1]==LAST_PATTERN))&& + (pf->fadeout)) + max_volume=pf->numrow?((pf->numrow-pf->patpos)*128)/pf->numrow:0; + else + max_volume=128; + + pt_EffectsPass1(pf); + if (pf->flags&UF_NNA) + pt_NNA(pf); + pt_SetupVoices(pf); + pt_EffectsPass2(pf); + + /* now set up the actual hardware channel playback information */ + pt_UpdateVoices(pf, max_volume); +} + +static void Player_Init_internal(MODULE* mod) +{ + int t; + + for (t=0;tnumchn;t++) { + mod->control[t].main.chanvol=mod->chanvol[t]; + mod->control[t].main.panning=mod->panning[t]; + } + + mod->sngtime=0; + mod->sngremainder=0; + + mod->pat_repcrazy=0; + mod->sngpos=0; + if(mod->initspeed!=0) + mod->sngspd=mod->initspeed<32?mod->initspeed:32; + else + mod->sngspd=6; + mod->volume=mod->initvolume>128?128:mod->initvolume; + + mod->vbtick=mod->sngspd; + mod->patdly=0; + mod->patdly2=0; + mod->bpm=mod->inittempo<32?32:mod->inittempo; + mod->realchn=0; + + mod->patpos=0; + mod->posjmp=2; /* make sure the player fetches the first note */ + mod->numrow=-1; + mod->patbrk=0; +} + +BOOL Player_Init(MODULE* mod) +{ + mod->extspd=1; + mod->panflag=1; + mod->wrap=0; + mod->loop=1; + mod->fadeout=0; + + mod->relspd=0; + + /* make sure the player doesn't start with garbage */ + if (!(mod->control=(MP_CONTROL*)MikMod_calloc(mod->numchn,sizeof(MP_CONTROL)))) + return 1; + if (!(mod->voice=(MP_VOICE*)MikMod_calloc(md_sngchn,sizeof(MP_VOICE)))) + return 1; + + Player_Init_internal(mod); + return 0; +} + +void Player_Exit_internal(MODULE* mod) +{ + if (!mod) + return; + + /* Stop playback if necessary */ + if (mod==pf) { + Player_Stop_internal(); + pf=NULL; + } + + if (mod->control) + MikMod_free(mod->control); + if (mod->voice) + MikMod_free(mod->voice); + mod->control=NULL; + mod->voice=NULL; +} + +void Player_Exit(MODULE* mod) +{ + MUTEX_LOCK(vars); + Player_Exit_internal(mod); + MUTEX_UNLOCK(vars); +} + +MIKMODAPI void Player_SetVolume(SWORD volume) +{ + MUTEX_LOCK(vars); + if (pf) + pf->volume=(volume<0)?0:(volume>128)?128:volume; + MUTEX_UNLOCK(vars); +} + +MIKMODAPI MODULE* Player_GetModule(void) +{ + MODULE* result; + + MUTEX_LOCK(vars); + result=pf; + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI void Player_Start(MODULE *mod) +{ + int t; + + if (!mod) + return; + + if (!MikMod_Active()) + MikMod_EnableOutput(); + + mod->forbid=0; + + MUTEX_LOCK(vars); + if (pf!=mod) { + /* new song is being started, so completely stop out the old one. */ + if (pf) pf->forbid=1; + for (t=0;tforbid=1; + pf=NULL; +} + +MIKMODAPI void Player_Stop(void) +{ + MUTEX_LOCK(vars); + Player_Stop_internal(); + MUTEX_UNLOCK(vars); +} + +MIKMODAPI BOOL Player_Active(void) +{ + BOOL result=0; + + MUTEX_LOCK(vars); + if (pf) + result=(!(pf->sngpos>=pf->numpos)); + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI void Player_NextPosition(void) +{ + MUTEX_LOCK(vars); + if (pf) { + int t; + + pf->forbid=1; + pf->posjmp=3; + pf->patbrk=0; + pf->vbtick=pf->sngspd; + + for (t=0;tvoice[t].main.i=NULL; + pf->voice[t].main.s=NULL; + } + for (t=0;tnumchn;t++) { + pf->control[t].main.i=NULL; + pf->control[t].main.s=NULL; + } + pf->forbid=0; + } + MUTEX_UNLOCK(vars); +} + +MIKMODAPI void Player_PrevPosition(void) +{ + MUTEX_LOCK(vars); + if (pf) { + int t; + + pf->forbid=1; + pf->posjmp=1; + pf->patbrk=0; + pf->vbtick=pf->sngspd; + + for (t=0;tvoice[t].main.i=NULL; + pf->voice[t].main.s=NULL; + } + for (t=0;tnumchn;t++) { + pf->control[t].main.i=NULL; + pf->control[t].main.s=NULL; + } + pf->forbid=0; + } + MUTEX_UNLOCK(vars); +} + +MIKMODAPI void Player_SetPosition(UWORD pos) +{ + MUTEX_LOCK(vars); + if (pf) { + int t; + + pf->forbid=1; + if (pos>=pf->numpos) pos=pf->numpos; + pf->posjmp=2; + pf->patbrk=0; + pf->sngpos=pos; + pf->vbtick=pf->sngspd; + + for (t=0;tvoice[t].main.i=NULL; + pf->voice[t].main.s=NULL; + } + for (t=0;tnumchn;t++) { + pf->control[t].main.i=NULL; + pf->control[t].main.s=NULL; + } + pf->forbid=0; + + if (!pos) + Player_Init_internal(pf); + } + MUTEX_UNLOCK(vars); +} + +static void Player_Unmute_internal(SLONG arg1,va_list ap) +{ + SLONG t,arg2,arg3=0; + + if (pf) { + switch (arg1) { + case MUTE_INCLUSIVE: + if (((!(arg2=va_arg(ap,SLONG)))&&(!(arg3=va_arg(ap,SLONG))))|| + (arg2>arg3)||(arg3>=pf->numchn)) + return; + for (;arg2numchn && arg2<=arg3;arg2++) + pf->control[arg2].muted=0; + break; + case MUTE_EXCLUSIVE: + if (((!(arg2=va_arg(ap,SLONG)))&&(!(arg3=va_arg(ap,SLONG))))|| + (arg2>arg3)||(arg3>=pf->numchn)) + return; + for (t=0;tnumchn;t++) { + if ((t>=arg2) && (t<=arg3)) + continue; + pf->control[t].muted=0; + } + break; + default: + if (arg1numchn) pf->control[arg1].muted=0; + break; + } + } +} + +MIKMODAPI void Player_Unmute(SLONG arg1, ...) +{ + va_list args; + + va_start(args,arg1); + MUTEX_LOCK(vars); + Player_Unmute_internal(arg1,args); + MUTEX_UNLOCK(vars); + va_end(args); +} + +static void Player_Mute_internal(SLONG arg1,va_list ap) +{ + SLONG t,arg2,arg3=0; + + if (pf) { + switch (arg1) { + case MUTE_INCLUSIVE: + if (((!(arg2=va_arg(ap,SLONG)))&&(!(arg3=va_arg(ap,SLONG))))|| + (arg2>arg3)||(arg3>=pf->numchn)) + return; + for (;arg2numchn && arg2<=arg3;arg2++) + pf->control[arg2].muted=1; + break; + case MUTE_EXCLUSIVE: + if (((!(arg2=va_arg(ap,SLONG)))&&(!(arg3=va_arg(ap,SLONG))))|| + (arg2>arg3)||(arg3>=pf->numchn)) + return; + for (t=0;tnumchn;t++) { + if ((t>=arg2) && (t<=arg3)) + continue; + pf->control[t].muted=1; + } + break; + default: + if (arg1numchn) + pf->control[arg1].muted=1; + break; + } + } +} + +MIKMODAPI void Player_Mute(SLONG arg1,...) +{ + va_list args; + + va_start(args,arg1); + MUTEX_LOCK(vars); + Player_Mute_internal(arg1,args); + MUTEX_UNLOCK(vars); + va_end(args); +} + +static void Player_ToggleMute_internal(SLONG arg1,va_list ap) +{ + SLONG arg2,arg3=0; + SLONG t; + + if (pf) { + switch (arg1) { + case MUTE_INCLUSIVE: + if (((!(arg2=va_arg(ap,SLONG)))&&(!(arg3=va_arg(ap,SLONG))))|| + (arg2>arg3)||(arg3>=pf->numchn)) + return; + for (;arg2numchn && arg2<=arg3;arg2++) + pf->control[arg2].muted=1-pf->control[arg2].muted; + break; + case MUTE_EXCLUSIVE: + if (((!(arg2=va_arg(ap,SLONG)))&&(!(arg3=va_arg(ap,SLONG))))|| + (arg2>arg3)||(arg3>=pf->numchn)) + return; + for (t=0;tnumchn;t++) { + if ((t>=arg2) && (t<=arg3)) + continue; + pf->control[t].muted=1-pf->control[t].muted; + } + break; + default: + if (arg1numchn) + pf->control[arg1].muted=1-pf->control[arg1].muted; + break; + } + } +} + +MIKMODAPI void Player_ToggleMute(SLONG arg1,...) +{ + va_list args; + + va_start(args,arg1); + MUTEX_LOCK(vars); + Player_ToggleMute_internal(arg1,args); + MUTEX_UNLOCK(vars); + va_end(args); +} + +MIKMODAPI BOOL Player_Muted(UBYTE chan) +{ + BOOL result=1; + + MUTEX_LOCK(vars); + if (pf) + result=(channumchn)?pf->control[chan].muted:1; + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI int Player_GetChannelVoice(UBYTE chan) +{ + int result=0; + + MUTEX_LOCK(vars); + if (pf) + result=(channumchn)?pf->control[chan].slavechn:-1; + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI UWORD Player_GetChannelPeriod(UBYTE chan) +{ + UWORD result=0; + + MUTEX_LOCK(vars); + if (pf) + result=(channumchn)?pf->control[chan].main.period:0; + MUTEX_UNLOCK(vars); + + return result; +} + +BOOL Player_Paused_internal(void) +{ + return pf?pf->forbid:1; +} + +MIKMODAPI BOOL Player_Paused(void) +{ + BOOL result; + + MUTEX_LOCK(vars); + result=Player_Paused_internal(); + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI void Player_TogglePause(void) +{ + MUTEX_LOCK(vars); + if (pf) + pf->forbid=1-pf->forbid; + MUTEX_UNLOCK(vars); +} + +MIKMODAPI void Player_SetSpeed(UWORD speed) +{ + MUTEX_LOCK(vars); + if (pf) + pf->sngspd=speed?(speed<32?speed:32):1; + MUTEX_UNLOCK(vars); +} + +MIKMODAPI void Player_SetTempo(UWORD tempo) +{ + if (tempo<32) tempo=32; + MUTEX_LOCK(vars); + if (pf) { + if ((!(pf->flags&UF_HIGHBPM))&&(tempo>255)) tempo=255; + pf->bpm=tempo; + } + MUTEX_UNLOCK(vars); +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/munitrk.c b/src/libs/mikmod/munitrk.c new file mode 100644 index 0000000..2818969 --- /dev/null +++ b/src/libs/mikmod/munitrk.c @@ -0,0 +1,303 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + All routines dealing with the manipulation of UNITRK streams + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mikmod_internals.h" + +#include + +/* Unibuffer chunk size */ +#define BUFPAGE 128 + +UWORD unioperands[UNI_LAST]={ + 0, /* not used */ + 1, /* UNI_NOTE */ + 1, /* UNI_INSTRUMENT */ + 1, /* UNI_PTEFFECT0 */ + 1, /* UNI_PTEFFECT1 */ + 1, /* UNI_PTEFFECT2 */ + 1, /* UNI_PTEFFECT3 */ + 1, /* UNI_PTEFFECT4 */ + 1, /* UNI_PTEFFECT5 */ + 1, /* UNI_PTEFFECT6 */ + 1, /* UNI_PTEFFECT7 */ + 1, /* UNI_PTEFFECT8 */ + 1, /* UNI_PTEFFECT9 */ + 1, /* UNI_PTEFFECTA */ + 1, /* UNI_PTEFFECTB */ + 1, /* UNI_PTEFFECTC */ + 1, /* UNI_PTEFFECTD */ + 1, /* UNI_PTEFFECTE */ + 1, /* UNI_PTEFFECTF */ + 1, /* UNI_S3MEFFECTA */ + 1, /* UNI_S3MEFFECTD */ + 1, /* UNI_S3MEFFECTE */ + 1, /* UNI_S3MEFFECTF */ + 1, /* UNI_S3MEFFECTI */ + 1, /* UNI_S3MEFFECTQ */ + 1, /* UNI_S3MEFFECTR */ + 1, /* UNI_S3MEFFECTT */ + 1, /* UNI_S3MEFFECTU */ + 0, /* UNI_KEYOFF */ + 1, /* UNI_KEYFADE */ + 2, /* UNI_VOLEFFECTS */ + 1, /* UNI_XMEFFECT4 */ + 1, /* UNI_XMEFFECT6 */ + 1, /* UNI_XMEFFECTA */ + 1, /* UNI_XMEFFECTE1 */ + 1, /* UNI_XMEFFECTE2 */ + 1, /* UNI_XMEFFECTEA */ + 1, /* UNI_XMEFFECTEB */ + 1, /* UNI_XMEFFECTG */ + 1, /* UNI_XMEFFECTH */ + 1, /* UNI_XMEFFECTL */ + 1, /* UNI_XMEFFECTP */ + 1, /* UNI_XMEFFECTX1 */ + 1, /* UNI_XMEFFECTX2 */ + 1, /* UNI_ITEFFECTG */ + 1, /* UNI_ITEFFECTH */ + 1, /* UNI_ITEFFECTI */ + 1, /* UNI_ITEFFECTM */ + 1, /* UNI_ITEFFECTN */ + 1, /* UNI_ITEFFECTP */ + 1, /* UNI_ITEFFECTT */ + 1, /* UNI_ITEFFECTU */ + 1, /* UNI_ITEFFECTW */ + 1, /* UNI_ITEFFECTY */ + 2, /* UNI_ITEFFECTZ */ + 1, /* UNI_ITEFFECTS0 */ + 2, /* UNI_ULTEFFECT9 */ + 2, /* UNI_MEDSPEED */ + 0, /* UNI_MEDEFFECTF1 */ + 0, /* UNI_MEDEFFECTF2 */ + 0, /* UNI_MEDEFFECTF3 */ + 2, /* UNI_OKTARP */ +}; + +/* Sparse description of the internal module format + ------------------------------------------------ + + A UNITRK stream is an array of bytes representing a single track of a pattern. +It's made up of 'repeat/length' bytes, opcodes and operands (sort of a assembly +language): + +rrrlllll +[REP/LEN][OPCODE][OPERAND][OPCODE][OPERAND] [REP/LEN][OPCODE][OPERAND].. +^ ^ ^ +|-------ROWS 0 - 0+REP of a track---------| |-------ROWS xx - xx+REP of a track... + + The rep/len byte contains the number of bytes in the current row, _including_ +the length byte itself (So the LENGTH byte of row 0 in the previous example +would have a value of 5). This makes it easy to search through a stream for a +particular row. A track is concluded by a 0-value length byte. + + The upper 3 bits of the rep/len byte contain the number of times -1 this row +is repeated for this track. (so a value of 7 means this row is repeated 8 times) + + Opcodes can range from 1 to 255 but currently only opcodes 1 to 62 are being +used. Each opcode can have a different number of operands. You can find the +number of operands to a particular opcode by using the opcode as an index into +the 'unioperands' table. + +*/ + +/*========== Reading routines */ + +static UBYTE *rowstart; /* startadress of a row */ +static UBYTE *rowend; /* endaddress of a row (exclusive) */ +static UBYTE *rowpc; /* current unimod(tm) programcounter */ + +static UBYTE lastbyte; /* for UniSkipOpcode() */ + +void UniSetRow(UBYTE* t) +{ + rowstart = t; + rowpc = rowstart; + rowend = t?rowstart+(*(rowpc++)&0x1f):t; +} + +UBYTE UniGetByte(void) +{ + return lastbyte = (rowpc end of track.. */ + l = (c>>5)+1; /* extract repeat value */ + if(l>row) break; /* reached wanted row? -> return pointer */ + row -= l; /* haven't reached row yet.. update row */ + t += c&0x1f; /* point t to the next row */ + } + return t; +} + +/*========== Writing routines */ + +static UBYTE *unibuf; /* pointer to the temporary unitrk buffer */ +static UWORD unimax; /* buffer size */ + +static UWORD unipc; /* buffer cursor */ +static UWORD unitt; /* current row index */ +static UWORD lastp; /* previous row index */ + +/* Resets index-pointers to create a new track. */ +void UniReset(void) +{ + unitt = 0; /* reset index to rep/len byte */ + unipc = 1; /* first opcode will be written to index 1 */ + lastp = 0; /* no previous row yet */ + unibuf[0] = 0; /* clear rep/len byte */ +} + +/* Expands the buffer */ +static BOOL UniExpand(int wanted) +{ + if ((unipc+wanted)>=unimax) { + UBYTE *newbuf; + + /* Expand the buffer by BUFPAGE bytes */ + newbuf=(UBYTE*)MikMod_realloc(unibuf,(unimax+BUFPAGE)*sizeof(UBYTE)); + + /* Check if realloc succeeded */ + if(newbuf) { + unibuf = newbuf; + unimax+=BUFPAGE; + return 1; + } else + return 0; + } + return 1; +} + +/* Appends one byte of data to the current row of a track. */ +void UniWriteByte(UBYTE data) +{ + if (UniExpand(1)) + /* write byte to current position and update */ + unibuf[unipc++]=data; +} + +void UniWriteWord(UWORD data) +{ + if (UniExpand(2)) { + unibuf[unipc++]=data>>8; + unibuf[unipc++]=data&0xff; + } +} + +static BOOL MyCmp(UBYTE* a,UBYTE* b,UWORD l) +{ + UWORD t; + + for(t=0;t>5)+1; /* repeat of previous row */ + l = (unibuf[lastp]&0x1f); /* length of previous row */ + + len = unipc-unitt; /* length of current row */ + + /* Now, check if the previous and the current row are identical.. when they + are, just increase the repeat field of the previous row */ + if(n<8 && len==l && MyCmp(&unibuf[lastp+1],&unibuf[unitt+1],len-1)) { + unibuf[lastp]+=0x20; + unipc = unitt+1; + } else { + if (UniExpand(unitt-unipc)) { + /* current and previous row aren't equal... update the pointers */ + unibuf[unitt] = len; + lastp = unitt; + unitt = unipc++; + } + } +} + +/* Terminates the current unitrk stream and returns a pointer to a copy of the + stream. */ +UBYTE* UniDup(void) +{ + UBYTE *d; + + if (!UniExpand(unitt-unipc)) return NULL; + unibuf[unitt] = 0; + + if(!(d=(UBYTE *)MikMod_malloc(unipc))) return NULL; + memcpy(d,unibuf,unipc); + + return d; +} + +BOOL UniInit(void) +{ + unimax = BUFPAGE; + + if(!(unibuf=(UBYTE*)MikMod_malloc(unimax*sizeof(UBYTE)))) return 0; + return 1; +} + +void UniCleanup(void) +{ + if(unibuf) MikMod_free(unibuf); + unibuf = NULL; +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/mwav.c b/src/libs/mikmod/mwav.c new file mode 100644 index 0000000..b629985 --- /dev/null +++ b/src/libs/mikmod/mwav.c @@ -0,0 +1,210 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + WAV sample loader + +==============================================================================*/ + +/* + FIXME: Stereo .WAV files are not yet supported as samples. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include + +#include "mikmod_internals.h" + +#ifdef SUNOS +extern int fprintf(FILE *, const char *, ...); +#endif + +typedef struct WAV { + CHAR rID[4]; + ULONG rLen; + CHAR wID[4]; + CHAR fID[4]; + ULONG fLen; + UWORD wFormatTag; + UWORD nChannels; + ULONG nSamplesPerSec; + ULONG nAvgBytesPerSec; + UWORD nBlockAlign; + UWORD nFormatSpecific; +} WAV; + +SAMPLE* Sample_LoadGeneric_internal(MREADER* reader) +{ + SAMPLE *si=NULL; + WAV wh; + BOOL have_fmt=0; + + /* read wav header */ + _mm_read_string(wh.rID,4,reader); + wh.rLen = _mm_read_I_ULONG(reader); + _mm_read_string(wh.wID,4,reader); + + /* check for correct header */ + if(_mm_eof(reader)|| memcmp(wh.rID,"RIFF",4) || memcmp(wh.wID,"WAVE",4)) { + _mm_errno = MMERR_UNKNOWN_WAVE_TYPE; + return NULL; + } + + /* scan all RIFF blocks until we find the sample data */ + for(;;) { + CHAR dID[4]; + ULONG len,start; + + _mm_read_string(dID,4,reader); + len = _mm_read_I_ULONG(reader); + /* truncated file ? */ + if (_mm_eof(reader)) { + _mm_errno=MMERR_UNKNOWN_WAVE_TYPE; + return NULL; + } + start = _mm_ftell(reader); + + /* sample format block + should be present only once and before a data block */ + if(!memcmp(dID,"fmt ",4)) { + wh.wFormatTag = _mm_read_I_UWORD(reader); + wh.nChannels = _mm_read_I_UWORD(reader); + wh.nSamplesPerSec = _mm_read_I_ULONG(reader); + wh.nAvgBytesPerSec = _mm_read_I_ULONG(reader); + wh.nBlockAlign = _mm_read_I_UWORD(reader); + wh.nFormatSpecific = _mm_read_I_UWORD(reader); + +#ifdef MIKMOD_DEBUG + fprintf(stderr,"\rwavloader : wFormatTag=%04x blockalign=%04x nFormatSpc=%04x\n", + wh.wFormatTag,wh.nBlockAlign,wh.nFormatSpecific); +#endif + + if((have_fmt)||(wh.nChannels>1)) { + _mm_errno=MMERR_UNKNOWN_WAVE_TYPE; + return NULL; + } + have_fmt=1; + } else + /* sample data block + should be present only once and after a format block */ + if(!memcmp(dID,"data",4)) { + if(!have_fmt) { + _mm_errno=MMERR_UNKNOWN_WAVE_TYPE; + return NULL; + } + if(!(si=(SAMPLE*)MikMod_malloc(sizeof(SAMPLE)))) return NULL; + si->speed = wh.nSamplesPerSec/wh.nChannels; + si->volume = 64; + si->length = len; + if(wh.nBlockAlign == 2) { + si->flags = SF_16BITS | SF_SIGNED; + si->length >>= 1; + } + si->inflags = si->flags; + SL_RegisterSample(si,MD_SNDFX,reader); + SL_LoadSamples(); + + /* skip any other remaining blocks - so in case of repeated sample + fragments, we'll return the first anyway instead of an error */ + break; + } + /* onto next block */ + _mm_fseek(reader,start+len,SEEK_SET); + if (_mm_eof(reader)) + break; + } + + return si; +} + +MIKMODAPI SAMPLE* Sample_LoadGeneric(MREADER* reader) +{ + SAMPLE* result; + + MUTEX_LOCK(vars); + result=Sample_LoadGeneric_internal(reader); + MUTEX_UNLOCK(vars); + + return result; +} + +MIKMODAPI extern SAMPLE *Sample_LoadMem(const char *buf, int len) +{ + SAMPLE* result=NULL; + MREADER* reader; + + if ((reader=_mm_new_mem_reader(buf, len))) { + result=Sample_LoadGeneric(reader); + _mm_delete_mem_reader(reader); + } + return result; +} + +MIKMODAPI SAMPLE* Sample_LoadFP(FILE *fp) +{ + SAMPLE* result=NULL; + MREADER* reader; + + if((reader=_mm_new_file_reader(fp))) { + result=Sample_LoadGeneric(reader); + _mm_delete_file_reader(reader); + } + return result; +} + +MIKMODAPI SAMPLE* Sample_Load(CHAR* filename) +{ + FILE *fp; + SAMPLE *si=NULL; + + if(!(md_mode & DMODE_SOFT_SNDFX)) return NULL; + if((fp=_mm_fopen(filename,"rb"))) { + si = Sample_LoadFP(fp); + _mm_fclose(fp); + } + return si; +} + +MIKMODAPI void Sample_Free(SAMPLE* si) +{ + if(si) { + MD_SampleUnload(si->handle); + MikMod_free(si); + } +} + +void Sample_Free_internal(SAMPLE *si) +{ + MUTEX_LOCK(vars); + Sample_Free(si); + MUTEX_UNLOCK(vars); +} + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/npertab.c b/src/libs/mikmod/npertab.c new file mode 100644 index 0000000..c6659e8 --- /dev/null +++ b/src/libs/mikmod/npertab.c @@ -0,0 +1,48 @@ +/* MikMod sound library + (c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for + complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + MOD format period table. Used by both the MOD and M15 (15-inst mod) Loaders. + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mikmod_internals.h" + +UWORD npertab[7 * OCTAVE] = { + /* Octaves 6 -> 0 */ + /* C C# D D# E F F# G G# A A# B */ + 0x6b0,0x650,0x5f4,0x5a0,0x54c,0x500,0x4b8,0x474,0x434,0x3f8,0x3c0,0x38a, + 0x358,0x328,0x2fa,0x2d0,0x2a6,0x280,0x25c,0x23a,0x21a,0x1fc,0x1e0,0x1c5, + 0x1ac,0x194,0x17d,0x168,0x153,0x140,0x12e,0x11d,0x10d,0x0fe,0x0f0,0x0e2, + 0x0d6,0x0ca,0x0be,0x0b4,0x0aa,0x0a0,0x097,0x08f,0x087,0x07f,0x078,0x071, + 0x06b,0x065,0x05f,0x05a,0x055,0x050,0x04b,0x047,0x043,0x03f,0x03c,0x038, + 0x035,0x032,0x02f,0x02d,0x02a,0x028,0x025,0x023,0x021,0x01f,0x01e,0x01c, + 0x01b,0x019,0x018,0x016,0x015,0x014,0x013,0x012,0x011,0x010,0x00f,0x00e +}; + +/* ex:set ts=4: */ + diff --git a/src/libs/mikmod/sloader.c b/src/libs/mikmod/sloader.c new file mode 100644 index 0000000..0b8c685 --- /dev/null +++ b/src/libs/mikmod/sloader.c @@ -0,0 +1,519 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS + for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Routines for loading samples. The sample loader utilizes the routines + provided by the "registered" sample loader. + +==============================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "mikmod_internals.h" + +static int sl_rlength; +static SWORD sl_old; +static SWORD *sl_buffer=NULL; +static SAMPLOAD *musiclist=NULL,*sndfxlist=NULL; + +/* size of the loader buffer in words */ +#define SLBUFSIZE 2048 + +/* IT-Compressed status structure */ +typedef struct ITPACK { + UWORD bits; /* current number of bits */ + UWORD bufbits; /* bits in buffer */ + SWORD last; /* last output */ + UBYTE buf; /* bit buffer */ +} ITPACK; + +BOOL SL_Init(SAMPLOAD* s) +{ + if(!sl_buffer) + if(!(sl_buffer=MikMod_malloc(SLBUFSIZE*sizeof(SWORD)))) return 0; + + sl_rlength = s->length; + if(s->infmt & SF_16BITS) sl_rlength>>=1; + sl_old = 0; + + return 1; +} + +void SL_Exit(SAMPLOAD *s) +{ + if(sl_rlength>0) _mm_fseek(s->reader,sl_rlength,SEEK_CUR); + if(sl_buffer) { + MikMod_free(sl_buffer); + sl_buffer=NULL; + } +} + +/* unpack a 8bit IT packed sample */ +static BOOL read_itcompr8(ITPACK* status,MREADER *reader,SWORD *sl_buffer,UWORD count,UWORD* incnt) +{ + SWORD *dest=sl_buffer,*end=sl_buffer+count; + UWORD x,y,needbits,havebits,new_count=0; + UWORD bits = status->bits; + UWORD bufbits = status->bufbits; + SBYTE last = status->last; + UBYTE buf = status->buf; + + while (dest>=y; + bufbits-=y; + needbits-=y; + havebits+=y; + } + if (new_count) { + new_count = 0; + if (++x >= bits) + x++; + bits = x; + continue; + } + if (bits<7) { + if (x==(1<<(bits-1))) { + new_count = 1; + continue; + } + } + else if (bits<9) { + y = (0xff >> (9-bits)) - 4; + if ((x>y)&&(x<=y+8)) { + if ((x-=y)>=bits) + x++; + bits = x; + continue; + } + } + else if (bits<10) { + if (x>=0x100) { + bits=x-0x100+1; + continue; + } + } else { + /* error in compressed data... */ + _mm_errno=MMERR_ITPACK_INVALID_DATA; + return 0; + } + + if (bits<8) /* extend sign */ + x = ((SBYTE)(x <<(8-bits))) >> (8-bits); + *(dest++)= (last+=x) << 8; /* convert to 16 bit */ + } + status->bits = bits; + status->bufbits = bufbits; + status->last = last; + status->buf = buf; + return !!(dest-sl_buffer); +} + +/* unpack a 16bit IT packed sample */ +static BOOL read_itcompr16(ITPACK *status,MREADER *reader,SWORD *sl_buffer,UWORD count,UWORD* incnt) +{ + SWORD *dest=sl_buffer,*end=sl_buffer+count; + SLONG x,y,needbits,havebits,new_count=0; + UWORD bits = status->bits; + UWORD bufbits = status->bufbits; + SWORD last = status->last; + UBYTE buf = status->buf; + + while (dest>=y; + bufbits-=y; + needbits-=y; + havebits+=y; + } + if (new_count) { + new_count = 0; + if (++x >= bits) + x++; + bits = x; + continue; + } + if (bits<7) { + if (x==(1<<(bits-1))) { + new_count=1; + continue; + } + } + else if (bits<17) { + y=(0xffff>>(17-bits))-8; + if ((x>y)&&(x<=y+16)) { + if ((x-=y)>=bits) + x++; + bits = x; + continue; + } + } + else if (bits<18) { + if (x>=0x10000) { + bits=x-0x10000+1; + continue; + } + } else { + /* error in compressed data... */ + _mm_errno=MMERR_ITPACK_INVALID_DATA; + return 0; + } + + if (bits<16) /* extend sign */ + x = ((SWORD)(x<<(16-bits)))>>(16-bits); + *(dest++)=(last+=x); + } + status->bits = bits; + status->bufbits = bufbits; + status->last = last; + status->buf = buf; + return !!(dest-sl_buffer); +} + +static BOOL SL_LoadInternal(void* buffer,UWORD infmt,UWORD outfmt,int scalefactor,ULONG length,MREADER* reader,BOOL dither) +{ + SBYTE *bptr = (SBYTE*)buffer; + SWORD *wptr = (SWORD*)buffer; + int stodo,t,u; + + int result,c_block=0; /* compression bytes until next block */ + ITPACK status; + UWORD incnt; + + while(length) { + stodo=(lengthRead(reader,sl_buffer,sizeof(SBYTE)*stodo); + src = (SBYTE*)sl_buffer; + dest = sl_buffer; + src += stodo;dest += stodo; + + for(t=0;t>1; + length-=2; + } + stodo = idx; + } + } + + if(outfmt & SF_16BITS) { + for(t=0;t>8; + } + } + return 0; +} + +BOOL SL_Load(void* buffer,SAMPLOAD *smp,ULONG length) +{ + return SL_LoadInternal(buffer,smp->infmt,smp->outfmt,smp->scalefactor, + length,smp->reader,0); +} + +/* Registers a sample for loading when SL_LoadSamples() is called. */ +SAMPLOAD* SL_RegisterSample(SAMPLE* s,int type,MREADER* reader) +{ + SAMPLOAD *news,**samplist,*cruise; + + if(type==MD_MUSIC) { + samplist = &musiclist; + cruise = musiclist; + } else + if (type==MD_SNDFX) { + samplist = &sndfxlist; + cruise = sndfxlist; + } else + return NULL; + + /* Allocate and add structure to the END of the list */ + if(!(news=(SAMPLOAD*)MikMod_malloc(sizeof(SAMPLOAD)))) return NULL; + + if(cruise) { + while(cruise->next) cruise=cruise->next; + cruise->next = news; + } else + *samplist = news; + + news->infmt = s->flags & SF_FORMATMASK; + news->outfmt = news->infmt; + news->reader = reader; + news->sample = s; + news->length = s->length; + news->loopstart = s->loopstart; + news->loopend = s->loopend; + + return news; +} + +static void FreeSampleList(SAMPLOAD* s) +{ + SAMPLOAD *old; + + while(s) { + old = s; + s = s->next; + MikMod_free(old); + } +} + +/* Returns the total amount of memory required by the samplelist queue. */ +static ULONG SampleTotal(SAMPLOAD* samplist,int type) +{ + int total = 0; + + while(samplist) { + samplist->sample->flags= + (samplist->sample->flags&~SF_FORMATMASK)|samplist->outfmt; + total += MD_SampleLength(type,samplist->sample); + samplist=samplist->next; + } + + return total; +} + +static ULONG RealSpeed(SAMPLOAD *s) +{ + return(s->sample->speed/(s->scalefactor?s->scalefactor:1)); +} + +static BOOL DitherSamples(SAMPLOAD* samplist,int type) +{ + SAMPLOAD *c2smp=NULL; + ULONG maxsize, speed; + SAMPLOAD *s; + + if(!samplist) return 0; + + if((maxsize=MD_SampleSpace(type)*1024)) + while(SampleTotal(samplist,type)>maxsize) { + /* First Pass - check for any 16 bit samples */ + s = samplist; + while(s) { + if(s->outfmt & SF_16BITS) { + SL_Sample16to8(s); + break; + } + s=s->next; + } + /* Second pass (if no 16bits found above) is to take the sample with + the highest speed and dither it by half. */ + if(!s) { + s = samplist; + speed = 0; + while(s) { + if((s->sample->length) && (RealSpeed(s)>speed)) { + speed=RealSpeed(s); + c2smp=s; + } + s=s->next; + } + if (c2smp) + SL_HalveSample(c2smp,2); + } + } + + /* Samples dithered, now load them ! */ + s = samplist; + while(s) { + /* sample has to be loaded ? -> increase number of samples, allocate + memory and load sample. */ + if(s->sample->length) { + if(s->sample->seekpos) + _mm_fseek(s->reader, s->sample->seekpos, SEEK_SET); + + /* Call the sample load routine of the driver module. It has to + return a 'handle' (>=0) that identifies the sample. */ + s->sample->handle = MD_SampleLoad(s, type); + s->sample->flags = (s->sample->flags & ~SF_FORMATMASK) | s->outfmt; + if(s->sample->handle<0) { + FreeSampleList(samplist); + if(_mm_errorhandler) _mm_errorhandler(); + return 1; + } + } + s = s->next; + } + + FreeSampleList(samplist); + return 0; +} + +BOOL SL_LoadSamples(void) +{ + BOOL ok; + + _mm_critical = 0; + + if((!musiclist)&&(!sndfxlist)) return 0; + ok=DitherSamples(musiclist,MD_MUSIC)||DitherSamples(sndfxlist,MD_SNDFX); + musiclist=sndfxlist=NULL; + + return ok; +} + +void SL_Sample16to8(SAMPLOAD* s) +{ + s->outfmt &= ~SF_16BITS; + s->sample->flags = (s->sample->flags&~SF_FORMATMASK) | s->outfmt; +} + +void SL_Sample8to16(SAMPLOAD* s) +{ + s->outfmt |= SF_16BITS; + s->sample->flags = (s->sample->flags&~SF_FORMATMASK) | s->outfmt; +} + +void SL_SampleSigned(SAMPLOAD* s) +{ + s->outfmt |= SF_SIGNED; + s->sample->flags = (s->sample->flags&~SF_FORMATMASK) | s->outfmt; +} + +void SL_SampleUnsigned(SAMPLOAD* s) +{ + s->outfmt &= ~SF_SIGNED; + s->sample->flags = (s->sample->flags&~SF_FORMATMASK) | s->outfmt; +} + +void SL_HalveSample(SAMPLOAD* s,int factor) +{ + s->scalefactor=factor>0?factor:2; + + s->sample->divfactor = s->scalefactor; + s->sample->length = s->length / s->scalefactor; + s->sample->loopstart = s->loopstart / s->scalefactor; + s->sample->loopend = s->loopend / s->scalefactor; +} + + +/* ex:set ts=4: */ diff --git a/src/libs/mikmod/virtch.c b/src/libs/mikmod/virtch.c new file mode 100644 index 0000000..0bbb470 --- /dev/null +++ b/src/libs/mikmod/virtch.c @@ -0,0 +1,935 @@ +/* MikMod sound library + (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file + AUTHORS for complete list. + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +/*============================================================================== + + $Id$ + + Sample mixing routines, using a 32 bits mixing buffer. + +==============================================================================*/ + +/* + + Optional features include: + (a) 4-step reverb (for 16 bit output only) + (b) Interpolation of sample data during mixing + (c) Dolby Surround Sound +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#ifdef HAVE_MEMORY_H +#include +#endif +#include + +#include "mikmod_internals.h" + +/* + Constant definitions + ==================== + + BITSHIFT + Controls the maximum volume of the sound output. All data is shifted + right by BITSHIFT after being mixed. Higher values result in quieter + sound and less chance of distortion. + + REVERBERATION + Controls the duration of the reverb. Larger values represent a shorter + reverb loop. Smaller values extend the reverb but can result in more of + an echo-ish sound. + +*/ + +#define BITSHIFT 9 +#define REVERBERATION 110000L + +#define FRACBITS 11 +#define FRACMASK ((1L< sample has to be restarted */ + UBYTE active; /* =1 -> sample is playing */ + UWORD flags; /* 16/8 bits looping/one-shot */ + SWORD handle; /* identifies the sample */ + ULONG start; /* start index */ + ULONG size; /* samplesize */ + ULONG reppos; /* loop start */ + ULONG repend; /* loop end */ + ULONG frq; /* current frequency */ + int vol; /* current volume */ + int pan; /* current panning position */ + + int rampvol; + int lvolsel,rvolsel; /* Volume factor in range 0-255 */ + int oldlvol,oldrvol; + + SLONGLONG current; /* current index in the sample */ + SLONGLONG increment; /* increment value */ +} VINFO; + +static SWORD **Samples; +static VINFO *vinf=NULL,*vnf; +static long tickleft,samplesthatfit,vc_memory=0; +static int vc_softchn; +static SLONGLONG idxsize,idxlpos,idxlend; +static SLONG *vc_tickbuf=NULL; +static UWORD vc_mode; + +/* Reverb control variables */ + +static int RVc1, RVc2, RVc3, RVc4, RVc5, RVc6, RVc7, RVc8; +static ULONG RVRindex; + +/* For Mono or Left Channel */ +static SLONG *RVbufL1=NULL,*RVbufL2=NULL,*RVbufL3=NULL,*RVbufL4=NULL, + *RVbufL5=NULL,*RVbufL6=NULL,*RVbufL7=NULL,*RVbufL8=NULL; + +/* For Stereo only (Right Channel) */ +static SLONG *RVbufR1=NULL,*RVbufR2=NULL,*RVbufR3=NULL,*RVbufR4=NULL, + *RVbufR5=NULL,*RVbufR6=NULL,*RVbufR7=NULL,*RVbufR8=NULL; + +#ifdef NATIVE_64BIT_INT +#define NATIVE SLONGLONG +#else +#define NATIVE SLONG +#endif + +/*========== 32 bit sample mixers - only for 32 bit platforms */ +#ifndef NATIVE_64BIT_INT + +static SLONG Mix32MonoNormal(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,SLONG todo) +{ + SWORD sample; + SLONG lvolsel = vnf->lvolsel; + + while(todo--) { + sample = srce[index >> FRACBITS]; + index += increment; + + *dest++ += lvolsel * sample; + } + return index; +} + +static SLONG Mix32StereoNormal(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,SLONG todo) +{ + SWORD sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + + while(todo--) { + sample=srce[index >> FRACBITS]; + index += increment; + + *dest++ += lvolsel * sample; + *dest++ += rvolsel * sample; + } + return index; +} + +static SLONG Mix32SurroundNormal(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,SLONG todo) +{ + SWORD sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + + if (lvolsel>=rvolsel) { + while(todo--) { + sample = srce[index >> FRACBITS]; + index += increment; + + *dest++ += lvolsel*sample; + *dest++ -= lvolsel*sample; + } + } else { + while(todo--) { + sample = srce[index >> FRACBITS]; + index += increment; + + *dest++ -= rvolsel*sample; + *dest++ += rvolsel*sample; + } + } + return index; +} + +static SLONG Mix32MonoInterp(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,SLONG todo) +{ + SLONG sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rampvol = vnf->rampvol; + + if (rampvol) { + SLONG oldlvol = vnf->oldlvol - lvolsel; + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += ((lvolsel << CLICK_SHIFT) + oldlvol * rampvol) + * sample >> CLICK_SHIFT; + if (!--rampvol) + break; + } + vnf->rampvol = rampvol; + if (todo < 0) + return index; + } + + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += lvolsel * sample; + } + return index; +} + +static SLONG Mix32StereoInterp(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,SLONG todo) +{ + SLONG sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + SLONG rampvol = vnf->rampvol; + + if (rampvol) { + SLONG oldlvol = vnf->oldlvol - lvolsel; + SLONG oldrvol = vnf->oldrvol - rvolsel; + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += ((lvolsel << CLICK_SHIFT) + oldlvol * rampvol) + * sample >> CLICK_SHIFT; + *dest++ += ((rvolsel << CLICK_SHIFT) + oldrvol * rampvol) + * sample >> CLICK_SHIFT; + if (!--rampvol) + break; + } + vnf->rampvol = rampvol; + if (todo < 0) + return index; + } + + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += lvolsel * sample; + *dest++ += rvolsel * sample; + } + return index; +} + +static SLONG Mix32SurroundInterp(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,SLONG todo) +{ + SLONG sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + SLONG rampvol = vnf->rampvol; + SLONG oldvol, vol; + + if (lvolsel >= rvolsel) { + vol = lvolsel; + oldvol = vnf->oldlvol; + } else { + vol = rvolsel; + oldvol = vnf->oldrvol; + } + + if (rampvol) { + oldvol -= vol; + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + sample=((vol << CLICK_SHIFT) + oldvol * rampvol) + * sample >> CLICK_SHIFT; + *dest++ += sample; + *dest++ -= sample; + + if (!--rampvol) + break; + } + vnf->rampvol = rampvol; + if (todo < 0) + return index; + } + + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += vol*sample; + *dest++ -= vol*sample; + } + return index; +} +#endif + +/*========== 64 bit sample mixers - all platforms */ + +static SLONGLONG MixMonoNormal(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,SLONG todo) +{ + SWORD sample; + SLONG lvolsel = vnf->lvolsel; + + while(todo--) { + sample = srce[index >> FRACBITS]; + index += increment; + + *dest++ += lvolsel * sample; + } + return index; +} + +static SLONGLONG MixStereoNormal(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,SLONG todo) +{ + SWORD sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + + while(todo--) { + sample=srce[index >> FRACBITS]; + index += increment; + + *dest++ += lvolsel * sample; + *dest++ += rvolsel * sample; + } + return index; +} + +static SLONGLONG MixSurroundNormal(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,SLONG todo) +{ + SWORD sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + + if(vnf->lvolsel>=vnf->rvolsel) { + while(todo--) { + sample = srce[index >> FRACBITS]; + index += increment; + + *dest++ += lvolsel*sample; + *dest++ -= lvolsel*sample; + } + } else { + while(todo--) { + sample = srce[index >> FRACBITS]; + index += increment; + + *dest++ -= rvolsel*sample; + *dest++ += rvolsel*sample; + } + } + return index; +} + +static SLONGLONG MixMonoInterp(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,SLONG todo) +{ + SLONG sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rampvol = vnf->rampvol; + + if (rampvol) { + SLONG oldlvol = vnf->oldlvol - lvolsel; + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += ((lvolsel << CLICK_SHIFT) + oldlvol * rampvol) + * sample >> CLICK_SHIFT; + if (!--rampvol) + break; + } + vnf->rampvol = rampvol; + if (todo < 0) + return index; + } + + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += lvolsel * sample; + } + return index; +} + +static SLONGLONG MixStereoInterp(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,SLONG todo) +{ + SLONG sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + SLONG rampvol = vnf->rampvol; + + if (rampvol) { + SLONG oldlvol = vnf->oldlvol - lvolsel; + SLONG oldrvol = vnf->oldrvol - rvolsel; + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ +=((lvolsel << CLICK_SHIFT) + oldlvol * rampvol) + * sample >> CLICK_SHIFT; + *dest++ +=((rvolsel << CLICK_SHIFT) + oldrvol * rampvol) + * sample >> CLICK_SHIFT; + if (!--rampvol) + break; + } + vnf->rampvol = rampvol; + if (todo < 0) + return index; + } + + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += lvolsel * sample; + *dest++ += rvolsel * sample; + } + return index; +} + +static SLONGLONG MixSurroundInterp(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,SLONG todo) +{ + SLONG sample; + SLONG lvolsel = vnf->lvolsel; + SLONG rvolsel = vnf->rvolsel; + SLONG rampvol = vnf->rampvol; + SLONG oldvol, vol; + + if (lvolsel >= rvolsel) { + vol = lvolsel; + oldvol = vnf->oldlvol; + } else { + vol = rvolsel; + oldvol = vnf->oldrvol; + } + + if (rampvol) { + oldvol -= vol; + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + sample=((vol << CLICK_SHIFT) + oldvol * rampvol) + * sample >> CLICK_SHIFT; + *dest++ += sample; + *dest++ -= sample; + if (!--rampvol) + break; + } + vnf->rampvol = rampvol; + if (todo < 0) + return index; + } + + while(todo--) { + sample=(SLONG)srce[index>>FRACBITS]+ + ((SLONG)(srce[(index>>FRACBITS)+1]-srce[index>>FRACBITS]) + *(index&FRACMASK)>>FRACBITS); + index += increment; + + *dest++ += vol*sample; + *dest++ -= vol*sample; + } + return index; +} + +static void (*MixReverb)(SLONG* srce,NATIVE count); + +/* Reverb macros */ +#define COMPUTE_LOC(n) loc##n = RVRindex % RVc##n +#define COMPUTE_LECHO(n) RVbufL##n [loc##n ]=speedup+((ReverbPct*RVbufL##n [loc##n ])>>7) +#define COMPUTE_RECHO(n) RVbufR##n [loc##n ]=speedup+((ReverbPct*RVbufR##n [loc##n ])>>7) + +static void MixReverb_Normal(SLONG* srce,NATIVE count) +{ + unsigned int speedup; + int ReverbPct; + unsigned int loc1,loc2,loc3,loc4; + unsigned int loc5,loc6,loc7,loc8; + + ReverbPct=58+(md_reverb<<2); + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + while(count--) { + /* Compute the left channel echo buffers */ + speedup = *srce >> 3; + + COMPUTE_LECHO(1); COMPUTE_LECHO(2); COMPUTE_LECHO(3); COMPUTE_LECHO(4); + COMPUTE_LECHO(5); COMPUTE_LECHO(6); COMPUTE_LECHO(7); COMPUTE_LECHO(8); + + /* Prepare to compute actual finalized data */ + RVRindex++; + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + /* left channel */ + *srce++ +=RVbufL1[loc1]-RVbufL2[loc2]+RVbufL3[loc3]-RVbufL4[loc4]+ + RVbufL5[loc5]-RVbufL6[loc6]+RVbufL7[loc7]-RVbufL8[loc8]; + } +} + +static void MixReverb_Stereo(SLONG* srce,NATIVE count) +{ + unsigned int speedup; + int ReverbPct; + unsigned int loc1, loc2, loc3, loc4; + unsigned int loc5, loc6, loc7, loc8; + + ReverbPct = 92+(md_reverb<<1); + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + while(count--) { + /* Compute the left channel echo buffers */ + speedup = *srce >> 3; + + COMPUTE_LECHO(1); COMPUTE_LECHO(2); COMPUTE_LECHO(3); COMPUTE_LECHO(4); + COMPUTE_LECHO(5); COMPUTE_LECHO(6); COMPUTE_LECHO(7); COMPUTE_LECHO(8); + + /* Compute the right channel echo buffers */ + speedup = srce[1] >> 3; + + COMPUTE_RECHO(1); COMPUTE_RECHO(2); COMPUTE_RECHO(3); COMPUTE_RECHO(4); + COMPUTE_RECHO(5); COMPUTE_RECHO(6); COMPUTE_RECHO(7); COMPUTE_RECHO(8); + + /* Prepare to compute actual finalized data */ + RVRindex++; + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + /* left channel then right channel */ + *srce++ +=RVbufL1[loc1]-RVbufL2[loc2]+RVbufL3[loc3]-RVbufL4[loc4]+ + RVbufL5[loc5]-RVbufL6[loc6]+RVbufL7[loc7]-RVbufL8[loc8]; + + *srce++ +=RVbufR1[loc1]-RVbufR2[loc2]+RVbufR3[loc3]-RVbufR4[loc4]+ + RVbufR5[loc5]-RVbufR6[loc6]+RVbufR7[loc7]-RVbufR8[loc8]; + } +} + +/* Mixing macros */ +#define EXTRACT_SAMPLE(var,size) var=*srce++>>(BITSHIFT+16-size) +#define CHECK_SAMPLE(var,bound) var=(var>=bound)?bound-1:(var<-bound)?-bound:var +#define PUT_SAMPLE(var) *dste++=var + +static void Mix32To16(SWORD* dste,const SLONG* srce,NATIVE count) +{ + SLONG x1,x2,x3,x4; + int remain; + + remain=count&3; + for(count>>=2;count;count--) { + EXTRACT_SAMPLE(x1,16); EXTRACT_SAMPLE(x2,16); + EXTRACT_SAMPLE(x3,16); EXTRACT_SAMPLE(x4,16); + + CHECK_SAMPLE(x1,32768); CHECK_SAMPLE(x2,32768); + CHECK_SAMPLE(x3,32768); CHECK_SAMPLE(x4,32768); + + PUT_SAMPLE(x1); PUT_SAMPLE(x2); PUT_SAMPLE(x3); PUT_SAMPLE(x4); + } + while(remain--) { + EXTRACT_SAMPLE(x1,16); + CHECK_SAMPLE(x1,32768); + PUT_SAMPLE(x1); + } +} + +static void Mix32To8(SBYTE* dste,const SLONG* srce,NATIVE count) +{ + SWORD x1,x2,x3,x4; + int remain; + + remain=count&3; + for(count>>=2;count;count--) { + EXTRACT_SAMPLE(x1,8); EXTRACT_SAMPLE(x2,8); + EXTRACT_SAMPLE(x3,8); EXTRACT_SAMPLE(x4,8); + + CHECK_SAMPLE(x1,128); CHECK_SAMPLE(x2,128); + CHECK_SAMPLE(x3,128); CHECK_SAMPLE(x4,128); + + PUT_SAMPLE(x1+128); PUT_SAMPLE(x2+128); + PUT_SAMPLE(x3+128); PUT_SAMPLE(x4+128); + } + while(remain--) { + EXTRACT_SAMPLE(x1,8); + CHECK_SAMPLE(x1,128); + PUT_SAMPLE(x1+128); + } +} + +static void AddChannel(SLONG* ptr,NATIVE todo) +{ + SLONGLONG end,done; + SWORD *s; + + if(!(s=Samples[vnf->handle])) { + vnf->current = vnf->active = 0; + return; + } + + /* update the 'current' index so the sample loops, or stops playing if it + reached the end of the sample */ + while(todo>0) { + SLONGLONG endpos; + + if(vnf->flags & SF_REVERSE) { + /* The sample is playing in reverse */ + if((vnf->flags&SF_LOOP)&&(vnf->currentflags & SF_BIDI) { + /* sample is doing bidirectional loops, so 'bounce' the + current index against the idxlpos */ + vnf->current = idxlpos+(idxlpos-vnf->current); + vnf->flags &= ~SF_REVERSE; + vnf->increment = -vnf->increment; + } else + /* normal backwards looping, so set the current position to + loopend index */ + vnf->current=idxlend-(idxlpos-vnf->current); + } else { + /* the sample is not looping, so check if it reached index 0 */ + if(vnf->current < 0) { + /* playing index reached 0, so stop playing this sample */ + vnf->current = vnf->active = 0; + break; + } + } + } else { + /* The sample is playing forward */ + if((vnf->flags & SF_LOOP) && + (vnf->current >= idxlend)) { + /* the sample is looping, check the loopend index */ + if(vnf->flags & SF_BIDI) { + /* sample is doing bidirectional loops, so 'bounce' the + current index against the idxlend */ + vnf->flags |= SF_REVERSE; + vnf->increment = -vnf->increment; + vnf->current = idxlend-(vnf->current-idxlend); + } else + /* normal backwards looping, so set the current position + to loopend index */ + vnf->current=idxlpos+(vnf->current-idxlend); + } else { + /* sample is not looping, so check if it reached the last + position */ + if(vnf->current >= idxsize) { + /* yes, so stop playing this sample */ + vnf->current = vnf->active = 0; + break; + } + } + } + + end=(vnf->flags&SF_REVERSE)?(vnf->flags&SF_LOOP)?idxlpos:0: + (vnf->flags&SF_LOOP)?idxlend:idxsize; + + /* if the sample is not blocked... */ + if((end==vnf->current)||(!vnf->increment)) + done=0; + else { + done=MIN((end-vnf->current)/vnf->increment+1,todo); + if(done<0) done=0; + } + + if(!done) { + vnf->active = 0; + break; + } + + endpos=vnf->current+done*vnf->increment; + + if(vnf->vol) { +#ifndef NATIVE_64BIT_INT + /* use the 32 bit mixers as often as we can (they're much faster) */ + if((vnf->current<0x7fffffff)&&(endpos<0x7fffffff)) { + if((md_mode & DMODE_INTERP)) { + if(vc_mode & DMODE_STEREO) { + if((vnf->pan==PAN_SURROUND)&&(md_mode&DMODE_SURROUND)) + vnf->current=Mix32SurroundInterp + (s,ptr,vnf->current,vnf->increment,done); + else + vnf->current=Mix32StereoInterp + (s,ptr,vnf->current,vnf->increment,done); + } else + vnf->current=Mix32MonoInterp + (s,ptr,vnf->current,vnf->increment,done); + } else if(vc_mode & DMODE_STEREO) { + if((vnf->pan==PAN_SURROUND)&&(md_mode&DMODE_SURROUND)) + vnf->current=Mix32SurroundNormal + (s,ptr,vnf->current,vnf->increment,done); + else + vnf->current=Mix32StereoNormal + (s,ptr,vnf->current,vnf->increment,done); + } else + vnf->current=Mix32MonoNormal + (s,ptr,vnf->current,vnf->increment,done); + } else +#endif + { + if((md_mode & DMODE_INTERP)) { + if(vc_mode & DMODE_STEREO) { + if((vnf->pan==PAN_SURROUND)&&(md_mode&DMODE_SURROUND)) + vnf->current=MixSurroundInterp + (s,ptr,vnf->current,vnf->increment,done); + else + vnf->current=MixStereoInterp + (s,ptr,vnf->current,vnf->increment,done); + } else + vnf->current=MixMonoInterp + (s,ptr,vnf->current,vnf->increment,done); + } else if(vc_mode & DMODE_STEREO) { + if((vnf->pan==PAN_SURROUND)&&(md_mode&DMODE_SURROUND)) + vnf->current=MixSurroundNormal + (s,ptr,vnf->current,vnf->increment,done); + else + vnf->current=MixStereoNormal + (s,ptr,vnf->current,vnf->increment,done); + } else + vnf->current=MixMonoNormal + (s,ptr,vnf->current,vnf->increment,done); + } + } else + /* update sample position */ + vnf->current=endpos; + + todo-=done; + ptr +=(vc_mode & DMODE_STEREO)?(done<<1):done; + } +} + +#define _IN_VIRTCH_ +#include "virtch_common.c" +#undef _IN_VIRTCH_ + +void VC1_WriteSamples(SBYTE* buf,ULONG todo) +{ + int left,portion=0,count; + SBYTE *buffer; + int t, pan, vol; + + while(todo) { + if(!tickleft) { + if(vc_mode & DMODE_SOFT_MUSIC) md_player(); + tickleft=(md_mixfreq*125L)/(md_bpm*50L); + } + left = MIN(tickleft, (long)todo); + buffer = buf; + tickleft -= left; + todo -= left; + buf += samples2bytes(left); + + while(left) { + portion = MIN(left, samplesthatfit); + count = (vc_mode & DMODE_STEREO)?(portion<<1):portion; + memset(vc_tickbuf, 0, count<<2); + for(t=0;tkick) { + vnf->current=((SLONGLONG)vnf->start)<kick =0; + vnf->active =1; + } + + if(!vnf->frq) vnf->active = 0; + + if(vnf->active) { + vnf->increment=((SLONGLONG)(vnf->frq<flags&SF_REVERSE) vnf->increment=-vnf->increment; + vol = vnf->vol; pan = vnf->pan; + + vnf->oldlvol=vnf->lvolsel;vnf->oldrvol=vnf->rvolsel; + if(vc_mode & DMODE_STEREO) { + if(pan != PAN_SURROUND) { + vnf->lvolsel=(vol*(PAN_RIGHT-pan))>>8; + vnf->rvolsel=(vol*pan)>>8; + } else + vnf->lvolsel=vnf->rvolsel=vol/2; + } else + vnf->lvolsel=vol; + + idxsize = (vnf->size)? ((SLONGLONG)vnf->size << FRACBITS)-1 : 0; + idxlend = (vnf->repend)? ((SLONGLONG)vnf->repend << FRACBITS)-1 : 0; + idxlpos = (SLONGLONG)vnf->reppos << FRACBITS; + AddChannel(vc_tickbuf, portion); + } + } + + if(md_reverb) { + if(md_reverb>15) md_reverb=15; + MixReverb(vc_tickbuf, portion); + } + + if(vc_mode & DMODE_16BITS) + Mix32To16((SWORD*) buffer, vc_tickbuf, count); + else + Mix32To8((SBYTE*) buffer, vc_tickbuf, count); + + buffer += samples2bytes(portion); + left -= portion; + } + } +} + +BOOL VC1_Init(void) +{ + VC_SetupPointers(); + + if (md_mode&DMODE_HQMIXER) + return VC2_Init(); + + if(!(Samples=(SWORD**)MikMod_calloc(MAXSAMPLEHANDLES,sizeof(SWORD*)))) { + _mm_errno = MMERR_INITIALIZING_MIXER; + return 1; + } + if(!vc_tickbuf) + if(!(vc_tickbuf=(SLONG*)MikMod_malloc((TICKLSIZE+32)*sizeof(SLONG)))) { + _mm_errno = MMERR_INITIALIZING_MIXER; + return 1; + } + + MixReverb=(md_mode&DMODE_STEREO)?MixReverb_Stereo:MixReverb_Normal; + vc_mode = md_mode; + return 0; +} + +BOOL VC1_PlayStart(void) +{ + samplesthatfit=TICKLSIZE; + if(vc_mode & DMODE_STEREO) samplesthatfit >>= 1; + tickleft = 0; + + RVc1 = (5000L * md_mixfreq) / REVERBERATION; + RVc2 = (5078L * md_mixfreq) / REVERBERATION; + RVc3 = (5313L * md_mixfreq) / REVERBERATION; + RVc4 = (5703L * md_mixfreq) / REVERBERATION; + RVc5 = (6250L * md_mixfreq) / REVERBERATION; + RVc6 = (6953L * md_mixfreq) / REVERBERATION; + RVc7 = (7813L * md_mixfreq) / REVERBERATION; + RVc8 = (8828L * md_mixfreq) / REVERBERATION; + + if(!(RVbufL1=(SLONG*)MikMod_calloc((RVc1+1),sizeof(SLONG)))) return 1; + if(!(RVbufL2=(SLONG*)MikMod_calloc((RVc2+1),sizeof(SLONG)))) return 1; + if(!(RVbufL3=(SLONG*)MikMod_calloc((RVc3+1),sizeof(SLONG)))) return 1; + if(!(RVbufL4=(SLONG*)MikMod_calloc((RVc4+1),sizeof(SLONG)))) return 1; + if(!(RVbufL5=(SLONG*)MikMod_calloc((RVc5+1),sizeof(SLONG)))) return 1; + if(!(RVbufL6=(SLONG*)MikMod_calloc((RVc6+1),sizeof(SLONG)))) return 1; + if(!(RVbufL7=(SLONG*)MikMod_calloc((RVc7+1),sizeof(SLONG)))) return 1; + if(!(RVbufL8=(SLONG*)MikMod_calloc((RVc8+1),sizeof(SLONG)))) return 1; + + if(!(RVbufR1=(SLONG*)MikMod_calloc((RVc1+1),sizeof(SLONG)))) return 1; + if(!(RVbufR2=(SLONG*)MikMod_calloc((RVc2+1),sizeof(SLONG)))) return 1; + if(!(RVbufR3=(SLONG*)MikMod_calloc((RVc3+1),sizeof(SLONG)))) return 1; + if(!(RVbufR4=(SLONG*)MikMod_calloc((RVc4+1),sizeof(SLONG)))) return 1; + if(!(RVbufR5=(SLONG*)MikMod_calloc((RVc5+1),sizeof(SLONG)))) return 1; + if(!(RVbufR6=(SLONG*)MikMod_calloc((RVc6+1),sizeof(SLONG)))) return 1; + if(!(RVbufR7=(SLONG*)MikMod_calloc((RVc7+1),sizeof(SLONG)))) return 1; + if(!(RVbufR8=(SLONG*)MikMod_calloc((RVc8+1),sizeof(SLONG)))) return 1; + + RVRindex = 0; + return 0; +} + +void VC1_PlayStop(void) +{ + if(RVbufL1) MikMod_free(RVbufL1); + if(RVbufL2) MikMod_free(RVbufL2); + if(RVbufL3) MikMod_free(RVbufL3); + if(RVbufL4) MikMod_free(RVbufL4); + if(RVbufL5) MikMod_free(RVbufL5); + if(RVbufL6) MikMod_free(RVbufL6); + if(RVbufL7) MikMod_free(RVbufL7); + if(RVbufL8) MikMod_free(RVbufL8); + RVbufL1=RVbufL2=RVbufL3=RVbufL4=RVbufL5=RVbufL6=RVbufL7=RVbufL8=NULL; + if(RVbufR1) MikMod_free(RVbufR1); + if(RVbufR2) MikMod_free(RVbufR2); + if(RVbufR3) MikMod_free(RVbufR3); + if(RVbufR4) MikMod_free(RVbufR4); + if(RVbufR5) MikMod_free(RVbufR5); + if(RVbufR6) MikMod_free(RVbufR6); + if(RVbufR7) MikMod_free(RVbufR7); + if(RVbufR8) MikMod_free(RVbufR8); + RVbufR1=RVbufR2=RVbufR3=RVbufR4=RVbufR5=RVbufR6=RVbufR7=RVbufR8=NULL; +} + +BOOL VC1_SetNumVoices(void) +{ + int t; + + if(!(vc_softchn=md_softchn)) return 0; + + if(vinf) MikMod_free(vinf); + if(!(vinf= MikMod_calloc(sizeof(VINFO),vc_softchn))) return 1; + + for(t=0;t +#endif +#include + +#include "mikmod_internals.h" + +/* + Constant Definitions + ==================== + + MAXVOL_FACTOR (was BITSHIFT in virtch.c) + Controls the maximum volume of the output data. All mixed data is + divided by this number after mixing, so larger numbers result in + quieter mixing. Smaller numbers will increase the likeliness of + distortion on loud modules. + + REVERBERATION + Larger numbers result in shorter reverb duration. Longer reverb + durations can cause unwanted static and make the reverb sound more + like a crappy echo. + + SAMPLING_SHIFT + Specified the shift multiplier which controls by how much the mixing + rate is multiplied while mixing. Higher values can improve quality by + smoothing the sound and reducing pops and clicks. Note, this is a shift + value, so a value of 2 becomes a mixing-rate multiplier of 4, and a + value of 3 = 8, etc. + + FRACBITS + The number of bits per integer devoted to the fractional part of the + number. Generally, this number should not be changed for any reason. + + !!! IMPORTANT !!! All values below MUST ALWAYS be greater than 0 + +*/ + +#define MAXVOL_FACTOR (1<<9) +#define REVERBERATION 11000L + +#define SAMPLING_SHIFT 2 +#define SAMPLING_FACTOR (1UL< sample has to be restarted */ + UBYTE active; /* =1 -> sample is playing */ + UWORD flags; /* 16/8 bits looping/one-shot */ + SWORD handle; /* identifies the sample */ + ULONG start; /* start index */ + ULONG size; /* samplesize */ + ULONG reppos; /* loop start */ + ULONG repend; /* loop end */ + ULONG frq; /* current frequency */ + int vol; /* current volume */ + int pan; /* current panning position */ + + int click; + int rampvol; + SLONG lastvalL,lastvalR; + int lvolsel,rvolsel; /* Volume factor in range 0-255 */ + int oldlvol,oldrvol; + + SLONGLONG current; /* current index in the sample */ + SLONGLONG increment; /* increment value */ +} VINFO; + +static SWORD **Samples; +static VINFO *vinf=NULL,*vnf; +static long tickleft,samplesthatfit,vc_memory=0; +static int vc_softchn; +static SLONGLONG idxsize,idxlpos,idxlend; +static SLONG *vc_tickbuf=NULL; +static UWORD vc_mode; + +/* Reverb control variables */ + +static int RVc1, RVc2, RVc3, RVc4, RVc5, RVc6, RVc7, RVc8; +static ULONG RVRindex; + +/* For Mono or Left Channel */ +static SLONG *RVbufL1=NULL,*RVbufL2=NULL,*RVbufL3=NULL,*RVbufL4=NULL, + *RVbufL5=NULL,*RVbufL6=NULL,*RVbufL7=NULL,*RVbufL8=NULL; + +/* For Stereo only (Right Channel) */ +static SLONG *RVbufR1=NULL,*RVbufR2=NULL,*RVbufR3=NULL,*RVbufR4=NULL, + *RVbufR5=NULL,*RVbufR6=NULL,*RVbufR7=NULL,*RVbufR8=NULL; + +#ifdef NATIVE_64BIT_INT +#define NATIVE SLONGLONG +#else +#define NATIVE SLONG +#endif + +/*========== 32 bit sample mixers - only for 32 bit platforms */ +#ifndef NATIVE_64BIT_INT + +static SLONG Mix32MonoNormal(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,SLONG todo) +{ + SWORD sample=0; + SLONG i,f; + + while(todo--) { + i=index>>FRACBITS,f=index&FRACMASK; + sample=(((SLONG)(srce[i]*(FRACMASK+1L-f)) + + ((SLONG)srce[i+1]*f)) >> FRACBITS); + index+=increment; + + if(vnf->rampvol) { + *dest++ += (long)( + ( ( (SLONG)(vnf->oldlvol*vnf->rampvol) + + (vnf->lvolsel*(CLICK_BUFFER-vnf->rampvol)) ) * + (SLONG)sample ) >> CLICK_SHIFT ); + vnf->rampvol--; + } else + if(vnf->click) { + *dest++ += (long)( + ( ( ((SLONG)vnf->lvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONG)sample ) + + (vnf->lastvalL*vnf->click) ) >> CLICK_SHIFT ); + vnf->click--; + } else + *dest++ +=vnf->lvolsel*sample; + } + vnf->lastvalL=vnf->lvolsel * sample; + + return index; +} + +static SLONG Mix32StereoNormal(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,ULONG todo) +{ + SWORD sample=0; + SLONG i,f; + + while(todo--) { + i=index>>FRACBITS,f=index&FRACMASK; + sample=((((SLONG)srce[i]*(FRACMASK+1L-f)) + + ((SLONG)srce[i+1] * f)) >> FRACBITS); + index += increment; + + if(vnf->rampvol) { + *dest++ += (long)( + ( ( ((SLONG)vnf->oldlvol*vnf->rampvol) + + (vnf->lvolsel*(CLICK_BUFFER-vnf->rampvol)) + ) * (SLONG)sample ) >> CLICK_SHIFT ); + *dest++ += (long)( + ( ( ((SLONG)vnf->oldrvol*vnf->rampvol) + + (vnf->rvolsel*(CLICK_BUFFER-vnf->rampvol)) + ) * (SLONG)sample ) >> CLICK_SHIFT ); + vnf->rampvol--; + } else + if(vnf->click) { + *dest++ += (long)( + ( ( (SLONG)(vnf->lvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONG)sample ) + (vnf->lastvalL * vnf->click) ) + >> CLICK_SHIFT ); + *dest++ += (long)( + ( ( ((SLONG)vnf->rvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONG)sample ) + (vnf->lastvalR * vnf->click) ) + >> CLICK_SHIFT ); + vnf->click--; + } else { + *dest++ +=vnf->lvolsel*sample; + *dest++ +=vnf->rvolsel*sample; + } + } + vnf->lastvalL=vnf->lvolsel*sample; + vnf->lastvalR=vnf->rvolsel*sample; + + return index; +} + +static SLONG Mix32StereoSurround(const SWORD* srce,SLONG* dest,SLONG index,SLONG increment,ULONG todo) +{ + SWORD sample=0; + long whoop; + SLONG i, f; + + while(todo--) { + i=index>>FRACBITS,f=index&FRACMASK; + sample=((((SLONG)srce[i]*(FRACMASK+1L-f)) + + ((SLONG)srce[i+1]*f)) >> FRACBITS); + index+=increment; + + if(vnf->rampvol) { + whoop=(long)( + ( ( (SLONG)(vnf->oldlvol*vnf->rampvol) + + (vnf->lvolsel*(CLICK_BUFFER-vnf->rampvol)) ) * + (SLONG)sample) >> CLICK_SHIFT ); + *dest++ +=whoop; + *dest++ -=whoop; + vnf->rampvol--; + } else + if(vnf->click) { + whoop = (long)( + ( ( ((SLONG)vnf->lvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONG)sample) + + (vnf->lastvalL * vnf->click) ) >> CLICK_SHIFT ); + *dest++ +=whoop; + *dest++ -=whoop; + vnf->click--; + } else { + *dest++ +=vnf->lvolsel*sample; + *dest++ -=vnf->lvolsel*sample; + } + } + vnf->lastvalL=vnf->lvolsel*sample; + vnf->lastvalR=vnf->lvolsel*sample; + + return index; +} +#endif + +/*========== 64 bit mixers */ + +static SLONGLONG MixMonoNormal(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,SLONG todo) +{ + SWORD sample=0; + SLONGLONG i,f; + + while(todo--) { + i=index>>FRACBITS,f=index&FRACMASK; + sample=(((SLONGLONG)(srce[i]*(FRACMASK+1L-f)) + + ((SLONGLONG)srce[i+1]*f)) >> FRACBITS); + index+=increment; + + if(vnf->rampvol) { + *dest++ += (long)( + ( ( (SLONGLONG)(vnf->oldlvol*vnf->rampvol) + + (vnf->lvolsel*(CLICK_BUFFER-vnf->rampvol)) ) * + (SLONGLONG)sample ) >> CLICK_SHIFT ); + vnf->rampvol--; + } else + if(vnf->click) { + *dest++ += (long)( + ( ( ((SLONGLONG)vnf->lvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONGLONG)sample ) + + (vnf->lastvalL*vnf->click) ) >> CLICK_SHIFT ); + vnf->click--; + } else + *dest++ +=vnf->lvolsel*sample; + } + vnf->lastvalL=vnf->lvolsel * sample; + + return index; +} + +static SLONGLONG MixStereoNormal(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,ULONG todo) +{ + SWORD sample=0; + SLONGLONG i,f; + + while(todo--) { + i=index>>FRACBITS,f=index&FRACMASK; + sample=((((SLONGLONG)srce[i]*(FRACMASK+1L-f)) + + ((SLONGLONG)srce[i+1] * f)) >> FRACBITS); + index += increment; + + if(vnf->rampvol) { + *dest++ += (long)( + ( ( ((SLONGLONG)vnf->oldlvol*vnf->rampvol) + + (vnf->lvolsel*(CLICK_BUFFER-vnf->rampvol)) + ) * (SLONGLONG)sample ) >> CLICK_SHIFT ); + *dest++ += (long)( + ( ( ((SLONGLONG)vnf->oldrvol*vnf->rampvol) + + (vnf->rvolsel*(CLICK_BUFFER-vnf->rampvol)) + ) * (SLONGLONG)sample ) >> CLICK_SHIFT ); + vnf->rampvol--; + } else + if(vnf->click) { + *dest++ += (long)( + ( ( (SLONGLONG)(vnf->lvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONGLONG)sample ) + (vnf->lastvalL * vnf->click) ) + >> CLICK_SHIFT ); + *dest++ += (long)( + ( ( ((SLONGLONG)vnf->rvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONGLONG)sample ) + (vnf->lastvalR * vnf->click) ) + >> CLICK_SHIFT ); + vnf->click--; + } else { + *dest++ +=vnf->lvolsel*sample; + *dest++ +=vnf->rvolsel*sample; + } + } + vnf->lastvalL=vnf->lvolsel*sample; + vnf->lastvalR=vnf->rvolsel*sample; + + return index; +} + +static SLONGLONG MixStereoSurround(const SWORD* srce,SLONG* dest,SLONGLONG index,SLONGLONG increment,ULONG todo) +{ + SWORD sample=0; + long whoop; + SLONGLONG i, f; + + while(todo--) { + i=index>>FRACBITS,f=index&FRACMASK; + sample=((((SLONGLONG)srce[i]*(FRACMASK+1L-f)) + + ((SLONGLONG)srce[i+1]*f)) >> FRACBITS); + index+=increment; + + if(vnf->rampvol) { + whoop=(long)( + ( ( (SLONGLONG)(vnf->oldlvol*vnf->rampvol) + + (vnf->lvolsel*(CLICK_BUFFER-vnf->rampvol)) ) * + (SLONGLONG)sample) >> CLICK_SHIFT ); + *dest++ +=whoop; + *dest++ -=whoop; + vnf->rampvol--; + } else + if(vnf->click) { + whoop = (long)( + ( ( ((SLONGLONG)vnf->lvolsel*(CLICK_BUFFER-vnf->click)) * + (SLONGLONG)sample) + + (vnf->lastvalL * vnf->click) ) >> CLICK_SHIFT ); + *dest++ +=whoop; + *dest++ -=whoop; + vnf->click--; + } else { + *dest++ +=vnf->lvolsel*sample; + *dest++ -=vnf->lvolsel*sample; + } + } + vnf->lastvalL=vnf->lvolsel*sample; + vnf->lastvalR=vnf->lvolsel*sample; + + return index; +} + +static void(*Mix32to16)(SWORD* dste,const SLONG* srce,NATIVE count); +static void(*Mix32to8)(SBYTE* dste,const SLONG* srce,NATIVE count); +static void(*MixReverb)(SLONG* srce,NATIVE count); + +/* Reverb macros */ +#define COMPUTE_LOC(n) loc##n = RVRindex % RVc##n +#define COMPUTE_LECHO(n) RVbufL##n [loc##n ]=speedup+((ReverbPct*RVbufL##n [loc##n ])>>7) +#define COMPUTE_RECHO(n) RVbufR##n [loc##n ]=speedup+((ReverbPct*RVbufR##n [loc##n ])>>7) + +static void MixReverb_Normal(SLONG* srce,NATIVE count) +{ + NATIVE speedup; + int ReverbPct; + unsigned int loc1,loc2,loc3,loc4,loc5,loc6,loc7,loc8; + + ReverbPct=58+(md_reverb*4); + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + while(count--) { + /* Compute the left channel echo buffers */ + speedup = *srce >> 3; + + COMPUTE_LECHO(1); COMPUTE_LECHO(2); COMPUTE_LECHO(3); COMPUTE_LECHO(4); + COMPUTE_LECHO(5); COMPUTE_LECHO(6); COMPUTE_LECHO(7); COMPUTE_LECHO(8); + + /* Prepare to compute actual finalized data */ + RVRindex++; + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + /* left channel */ + *srce++ +=RVbufL1[loc1]-RVbufL2[loc2]+RVbufL3[loc3]-RVbufL4[loc4]+ + RVbufL5[loc5]-RVbufL6[loc6]+RVbufL7[loc7]-RVbufL8[loc8]; + } +} + +static void MixReverb_Stereo(SLONG *srce,NATIVE count) +{ + NATIVE speedup; + int ReverbPct; + unsigned int loc1,loc2,loc3,loc4,loc5,loc6,loc7,loc8; + + ReverbPct=58+(md_reverb*4); + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + while(count--) { + /* Compute the left channel echo buffers */ + speedup = *srce >> 3; + + COMPUTE_LECHO(1); COMPUTE_LECHO(2); COMPUTE_LECHO(3); COMPUTE_LECHO(4); + COMPUTE_LECHO(5); COMPUTE_LECHO(6); COMPUTE_LECHO(7); COMPUTE_LECHO(8); + + /* Compute the right channel echo buffers */ + speedup = srce[1] >> 3; + + COMPUTE_RECHO(1); COMPUTE_RECHO(2); COMPUTE_RECHO(3); COMPUTE_RECHO(4); + COMPUTE_RECHO(5); COMPUTE_RECHO(6); COMPUTE_RECHO(7); COMPUTE_RECHO(8); + + /* Prepare to compute actual finalized data */ + RVRindex++; + + COMPUTE_LOC(1); COMPUTE_LOC(2); COMPUTE_LOC(3); COMPUTE_LOC(4); + COMPUTE_LOC(5); COMPUTE_LOC(6); COMPUTE_LOC(7); COMPUTE_LOC(8); + + /* left channel */ + *srce++ +=RVbufL1[loc1]-RVbufL2[loc2]+RVbufL3[loc3]-RVbufL4[loc4]+ + RVbufL5[loc5]-RVbufL6[loc6]+RVbufL7[loc7]-RVbufL8[loc8]; + + /* right channel */ + *srce++ +=RVbufR1[loc1]-RVbufR2[loc2]+RVbufR3[loc3]-RVbufR4[loc4]+ + RVbufR5[loc5]-RVbufR6[loc6]+RVbufR7[loc7]-RVbufR8[loc8]; + } +} + +/* Mixing macros */ +#define EXTRACT_SAMPLE(var,attenuation) var=*srce++/(MAXVOL_FACTOR*attenuation) +#define CHECK_SAMPLE(var,bound) var=(var>=bound)?bound-1:(var<-bound)?-bound:var + +static void Mix32To16_Normal(SWORD* dste,const SLONG* srce,NATIVE count) +{ + NATIVE x1,x2,tmpx; + int i; + + for(count/=SAMPLING_FACTOR;count;count--) { + tmpx=0; + + for(i=SAMPLING_FACTOR/2;i;i--) { + EXTRACT_SAMPLE(x1,1); EXTRACT_SAMPLE(x2,1); + + CHECK_SAMPLE(x1,32768); CHECK_SAMPLE(x2,32768); + + tmpx+=x1+x2; + } + *dste++ =tmpx/SAMPLING_FACTOR; + } +} + +static void Mix32To16_Stereo(SWORD* dste,const SLONG* srce,NATIVE count) +{ + NATIVE x1,x2,x3,x4,tmpx,tmpy; + int i; + + for(count/=SAMPLING_FACTOR;count;count--) { + tmpx=tmpy=0; + + for(i=SAMPLING_FACTOR/2;i;i--) { + EXTRACT_SAMPLE(x1,1); EXTRACT_SAMPLE(x2,1); + EXTRACT_SAMPLE(x3,1); EXTRACT_SAMPLE(x4,1); + + CHECK_SAMPLE(x1,32768); CHECK_SAMPLE(x2,32768); + CHECK_SAMPLE(x3,32768); CHECK_SAMPLE(x4,32768); + + tmpx+=x1+x3; + tmpy+=x2+x4; + } + *dste++ =tmpx/SAMPLING_FACTOR; + *dste++ =tmpy/SAMPLING_FACTOR; + } +} + +static void Mix32To8_Normal(SBYTE* dste,const SLONG* srce,NATIVE count) +{ + NATIVE x1,x2,tmpx; + int i; + + for(count/=SAMPLING_FACTOR;count;count--) { + tmpx = 0; + + for(i=SAMPLING_FACTOR/2;i;i--) { + EXTRACT_SAMPLE(x1,256); EXTRACT_SAMPLE(x2,256); + + CHECK_SAMPLE(x1,128); CHECK_SAMPLE(x2,128); + + tmpx+=x1+x2; + } + *dste++ =(tmpx/SAMPLING_FACTOR)+128; + } +} + +static void Mix32To8_Stereo(SBYTE* dste,const SLONG* srce,NATIVE count) +{ + NATIVE x1,x2,x3,x4,tmpx,tmpy; + int i; + + for(count/=SAMPLING_FACTOR;count;count--) { + tmpx=tmpy=0; + + for(i=SAMPLING_FACTOR/2;i;i--) { + EXTRACT_SAMPLE(x1,256); EXTRACT_SAMPLE(x2,256); + EXTRACT_SAMPLE(x3,256); EXTRACT_SAMPLE(x4,256); + + CHECK_SAMPLE(x1,128); CHECK_SAMPLE(x2,128); + CHECK_SAMPLE(x3,128); CHECK_SAMPLE(x4,128); + + tmpx+=x1+x3; + tmpy+=x2+x4; + } + *dste++ =(tmpx/SAMPLING_FACTOR)+128; + *dste++ =(tmpy/SAMPLING_FACTOR)+128; + } +} + +static void AddChannel(SLONG* ptr,NATIVE todo) +{ + SLONGLONG end,done; + SWORD *s; + + if(!(s=Samples[vnf->handle])) { + vnf->current = vnf->active = 0; + vnf->lastvalL = vnf->lastvalR = 0; + return; + } + + /* update the 'current' index so the sample loops, or stops playing if it + reached the end of the sample */ + while(todo>0) { + SLONGLONG endpos; + + if(vnf->flags & SF_REVERSE) { + /* The sample is playing in reverse */ + if((vnf->flags&SF_LOOP)&&(vnf->currentflags & SF_BIDI) { + /* sample is doing bidirectional loops, so 'bounce' the + current index against the idxlpos */ + vnf->current = idxlpos+(idxlpos-vnf->current); + vnf->flags &= ~SF_REVERSE; + vnf->increment = -vnf->increment; + } else + /* normal backwards looping, so set the current position to + loopend index */ + vnf->current=idxlend-(idxlpos-vnf->current); + } else { + /* the sample is not looping, so check if it reached index 0 */ + if(vnf->current < 0) { + /* playing index reached 0, so stop playing this sample */ + vnf->current = vnf->active = 0; + break; + } + } + } else { + /* The sample is playing forward */ + if((vnf->flags & SF_LOOP) && + (vnf->current >= idxlend)) { + /* the sample is looping, check the loopend index */ + if(vnf->flags & SF_BIDI) { + /* sample is doing bidirectional loops, so 'bounce' the + current index against the idxlend */ + vnf->flags |= SF_REVERSE; + vnf->increment = -vnf->increment; + vnf->current = idxlend-(vnf->current-idxlend); + } else + /* normal backwards looping, so set the current position + to loopend index */ + vnf->current=idxlpos+(vnf->current-idxlend); + } else { + /* sample is not looping, so check if it reached the last + position */ + if(vnf->current >= idxsize) { + /* yes, so stop playing this sample */ + vnf->current = vnf->active = 0; + break; + } + } + } + + end=(vnf->flags&SF_REVERSE)?(vnf->flags&SF_LOOP)?idxlpos:0: + (vnf->flags&SF_LOOP)?idxlend:idxsize; + + /* if the sample is not blocked... */ + if((end==vnf->current)||(!vnf->increment)) + done=0; + else { + done=MIN((end-vnf->current)/vnf->increment+1,todo); + if(done<0) done=0; + } + + if(!done) { + vnf->active = 0; + break; + } + + endpos=vnf->current+done*vnf->increment; + + if(vnf->vol || vnf->rampvol) { +#ifndef NATIVE_64BIT_INT + /* use the 32 bit mixers as often as we can (they're much faster) */ + if((vnf->current<0x7fffffff)&&(endpos<0x7fffffff)) { + if(vc_mode & DMODE_STEREO) { + if((vnf->pan==PAN_SURROUND)&&(vc_mode&DMODE_SURROUND)) + vnf->current=Mix32StereoSurround + (s,ptr,vnf->current,vnf->increment,done); + else + vnf->current=Mix32StereoNormal + (s,ptr,vnf->current,vnf->increment,done); + } else + vnf->current=Mix32MonoNormal + (s,ptr,vnf->current,vnf->increment,done); + } else +#endif + { + if(vc_mode & DMODE_STEREO) { + if((vnf->pan==PAN_SURROUND)&&(vc_mode&DMODE_SURROUND)) + vnf->current=MixStereoSurround + (s,ptr,vnf->current,vnf->increment,done); + else + vnf->current=MixStereoNormal + (s,ptr,vnf->current,vnf->increment,done); + } else + vnf->current=MixMonoNormal + (s,ptr,vnf->current,vnf->increment,done); + } + } else { + vnf->lastvalL = vnf->lastvalR = 0; + /* update sample position */ + vnf->current=endpos; + } + + todo -= done; + ptr +=(vc_mode & DMODE_STEREO)?(done<<1):done; + } +} + +#define _IN_VIRTCH_ + +#define VC1_SilenceBytes VC2_SilenceBytes +#define VC1_WriteSamples VC2_WriteSamples +#define VC1_WriteBytes VC2_WriteBytes +#define VC1_Exit VC2_Exit +#define VC1_VoiceSetVolume VC2_VoiceSetVolume +#define VC1_VoiceGetVolume VC2_VoiceGetVolume +#define VC1_VoiceSetPanning VC2_VoiceSetPanning +#define VC1_VoiceGetPanning VC2_VoiceGetPanning +#define VC1_VoiceSetFrequency VC2_VoiceSetFrequency +#define VC1_VoiceGetFrequency VC2_VoiceGetFrequency +#define VC1_VoicePlay VC2_VoicePlay +#define VC1_VoiceStop VC2_VoiceStop +#define VC1_VoiceStopped VC2_VoiceStopped +#define VC1_VoiceGetPosition VC2_VoiceGetPosition +#define VC1_SampleUnload VC2_SampleUnload +#define VC1_SampleLoad VC2_SampleLoad +#define VC1_SampleSpace VC2_SampleSpace +#define VC1_SampleLength VC2_SampleLength +#define VC1_VoiceRealVolume VC2_VoiceRealVolume + +#include "virtch_common.c" +#undef _IN_VIRTCH_ + +void VC2_WriteSamples(SBYTE* buf,ULONG todo) +{ + int left,portion=0; + SBYTE *buffer; + int t,pan,vol; + + todo*=SAMPLING_FACTOR; + + while(todo) { + if(!tickleft) { + if(vc_mode & DMODE_SOFT_MUSIC) md_player(); + tickleft=(md_mixfreq*125L*SAMPLING_FACTOR)/(md_bpm*50L); + tickleft&=~(SAMPLING_FACTOR-1); + } + left = MIN(tickleft, (long)todo); + buffer = buf; + tickleft -= left; + todo -= left; + buf += samples2bytes(left)/SAMPLING_FACTOR; + + while(left) { + portion = MIN(left, samplesthatfit); + memset(vc_tickbuf,0,portion<<((vc_mode&DMODE_STEREO)?3:2)); + for(t=0;tkick) { + vnf->current=((SLONGLONG)(vnf->start))<kick = 0; + vnf->active = 1; + vnf->click = CLICK_BUFFER; + vnf->rampvol = 0; + } + + if(!vnf->frq) vnf->active = 0; + + if(vnf->active) { + vnf->increment=((SLONGLONG)(vnf->frq)<<(FRACBITS-SAMPLING_SHIFT)) + /md_mixfreq; + if(vnf->flags&SF_REVERSE) vnf->increment=-vnf->increment; + vol = vnf->vol; pan = vnf->pan; + + vnf->oldlvol=vnf->lvolsel;vnf->oldrvol=vnf->rvolsel; + if(vc_mode & DMODE_STEREO) { + if(pan!=PAN_SURROUND) { + vnf->lvolsel=(vol*(PAN_RIGHT-pan))>>8; + vnf->rvolsel=(vol*pan)>>8; + } else { + vnf->lvolsel=vnf->rvolsel=(vol * 256L) / 480; + } + } else + vnf->lvolsel=vol; + + idxsize=(vnf->size)?((SLONGLONG)(vnf->size)<repend)?((SLONGLONG)(vnf->repend)<reppos)<15) md_reverb=15; + MixReverb(vc_tickbuf,portion); + } + + if(vc_mode & DMODE_16BITS) + Mix32to16((SWORD*)buffer,vc_tickbuf,portion); + else + Mix32to8((SBYTE*)buffer,vc_tickbuf,portion); + + buffer += samples2bytes(portion) / SAMPLING_FACTOR; + left -= portion; + } + } +} + +BOOL VC2_Init(void) +{ + VC_SetupPointers(); + + if (!(md_mode&DMODE_HQMIXER)) + return VC1_Init(); + + if(!(Samples=(SWORD**)MikMod_calloc(MAXSAMPLEHANDLES,sizeof(SWORD*)))) { + _mm_errno = MMERR_INITIALIZING_MIXER; + return 1; + } + if(!vc_tickbuf) + if(!(vc_tickbuf=(SLONG*)MikMod_malloc((TICKLSIZE+32)*sizeof(SLONG)))) { + _mm_errno = MMERR_INITIALIZING_MIXER; + return 1; + } + + if(md_mode & DMODE_STEREO) { + Mix32to16 = Mix32To16_Stereo; + Mix32to8 = Mix32To8_Stereo; + MixReverb = MixReverb_Stereo; + } else { + Mix32to16 = Mix32To16_Normal; + Mix32to8 = Mix32To8_Normal; + MixReverb = MixReverb_Normal; + } + md_mode |= DMODE_INTERP; + vc_mode = md_mode; + return 0; +} + +BOOL VC2_PlayStart(void) +{ + md_mode|=DMODE_INTERP; + + samplesthatfit = TICKLSIZE; + if(vc_mode & DMODE_STEREO) samplesthatfit >>= 1; + tickleft = 0; + + RVc1 = (5000L * md_mixfreq) / (REVERBERATION * 10); + RVc2 = (5078L * md_mixfreq) / (REVERBERATION * 10); + RVc3 = (5313L * md_mixfreq) / (REVERBERATION * 10); + RVc4 = (5703L * md_mixfreq) / (REVERBERATION * 10); + RVc5 = (6250L * md_mixfreq) / (REVERBERATION * 10); + RVc6 = (6953L * md_mixfreq) / (REVERBERATION * 10); + RVc7 = (7813L * md_mixfreq) / (REVERBERATION * 10); + RVc8 = (8828L * md_mixfreq) / (REVERBERATION * 10); + + if(!(RVbufL1=(SLONG*)MikMod_calloc((RVc1+1),sizeof(SLONG)))) return 1; + if(!(RVbufL2=(SLONG*)MikMod_calloc((RVc2+1),sizeof(SLONG)))) return 1; + if(!(RVbufL3=(SLONG*)MikMod_calloc((RVc3+1),sizeof(SLONG)))) return 1; + if(!(RVbufL4=(SLONG*)MikMod_calloc((RVc4+1),sizeof(SLONG)))) return 1; + if(!(RVbufL5=(SLONG*)MikMod_calloc((RVc5+1),sizeof(SLONG)))) return 1; + if(!(RVbufL6=(SLONG*)MikMod_calloc((RVc6+1),sizeof(SLONG)))) return 1; + if(!(RVbufL7=(SLONG*)MikMod_calloc((RVc7+1),sizeof(SLONG)))) return 1; + if(!(RVbufL8=(SLONG*)MikMod_calloc((RVc8+1),sizeof(SLONG)))) return 1; + + if(!(RVbufR1=(SLONG*)MikMod_calloc((RVc1+1),sizeof(SLONG)))) return 1; + if(!(RVbufR2=(SLONG*)MikMod_calloc((RVc2+1),sizeof(SLONG)))) return 1; + if(!(RVbufR3=(SLONG*)MikMod_calloc((RVc3+1),sizeof(SLONG)))) return 1; + if(!(RVbufR4=(SLONG*)MikMod_calloc((RVc4+1),sizeof(SLONG)))) return 1; + if(!(RVbufR5=(SLONG*)MikMod_calloc((RVc5+1),sizeof(SLONG)))) return 1; + if(!(RVbufR6=(SLONG*)MikMod_calloc((RVc6+1),sizeof(SLONG)))) return 1; + if(!(RVbufR7=(SLONG*)MikMod_calloc((RVc7+1),sizeof(SLONG)))) return 1; + if(!(RVbufR8=(SLONG*)MikMod_calloc((RVc8+1),sizeof(SLONG)))) return 1; + + RVRindex = 0; + return 0; +} + +void VC2_PlayStop(void) +{ + if(RVbufL1) MikMod_free(RVbufL1); + if(RVbufL2) MikMod_free(RVbufL2); + if(RVbufL3) MikMod_free(RVbufL3); + if(RVbufL4) MikMod_free(RVbufL4); + if(RVbufL5) MikMod_free(RVbufL5); + if(RVbufL6) MikMod_free(RVbufL6); + if(RVbufL7) MikMod_free(RVbufL7); + if(RVbufL8) MikMod_free(RVbufL8); + if(RVbufR1) MikMod_free(RVbufR1); + if(RVbufR2) MikMod_free(RVbufR2); + if(RVbufR3) MikMod_free(RVbufR3); + if(RVbufR4) MikMod_free(RVbufR4); + if(RVbufR5) MikMod_free(RVbufR5); + if(RVbufR6) MikMod_free(RVbufR6); + if(RVbufR7) MikMod_free(RVbufR7); + if(RVbufR8) MikMod_free(RVbufR8); + + RVbufL1=RVbufL2=RVbufL3=RVbufL4=RVbufL5=RVbufL6=RVbufL7=RVbufL8=NULL; + RVbufR1=RVbufR2=RVbufR3=RVbufR4=RVbufR5=RVbufR6=RVbufR7=RVbufR8=NULL; +} + +BOOL VC2_SetNumVoices(void) +{ + int t; + + md_mode|=DMODE_INTERP; + + if(!(vc_softchn=md_softchn)) return 0; + + if(vinf) MikMod_free(vinf); + if(!(vinf=MikMod_calloc(sizeof(VINFO),vc_softchn))) return 1; + + for(t=0;t>= 1; + if(vc_mode & DMODE_STEREO) bytes >>= 1; + return bytes; +} + +/* Fill the buffer with 'todo' bytes of silence (it depends on the mixing mode + how the buffer is filled) */ +ULONG VC1_SilenceBytes(SBYTE* buf,ULONG todo) +{ + todo=samples2bytes(bytes2samples(todo)); + + /* clear the buffer to zero (16 bits signed) or 0x80 (8 bits unsigned) */ + if(vc_mode & DMODE_16BITS) + memset(buf,0,todo); + else + memset(buf,0x80,todo); + + return todo; +} + +void VC1_WriteSamples(SBYTE*,ULONG); + +/* Writes 'todo' mixed SBYTES (!!) to 'buf'. It returns the number of SBYTES + actually written to 'buf' (which is rounded to number of samples that fit + into 'todo' bytes). */ +ULONG VC1_WriteBytes(SBYTE* buf,ULONG todo) +{ + if(!vc_softchn) + return VC1_SilenceBytes(buf,todo); + + todo = bytes2samples(todo); + VC1_WriteSamples(buf,todo); + + return samples2bytes(todo); +} + +void VC1_Exit(void) +{ + if(vc_tickbuf) MikMod_free(vc_tickbuf); + if(vinf) MikMod_free(vinf); + if(Samples) MikMod_free(Samples); + + vc_tickbuf = NULL; + vinf = NULL; + Samples = NULL; + + VC_SetupPointers(); +} + +UWORD VC1_VoiceGetVolume(UBYTE voice) +{ + return vinf[voice].vol; +} + +ULONG VC1_VoiceGetPanning(UBYTE voice) +{ + return vinf[voice].pan; +} + +void VC1_VoiceSetFrequency(UBYTE voice,ULONG frq) +{ + vinf[voice].frq=frq; +} + +ULONG VC1_VoiceGetFrequency(UBYTE voice) +{ + return vinf[voice].frq; +} + +void VC1_VoicePlay(UBYTE voice,SWORD handle,ULONG start,ULONG size,ULONG reppos,ULONG repend,UWORD flags) +{ + vinf[voice].flags = flags; + vinf[voice].handle = handle; + vinf[voice].start = start; + vinf[voice].size = size; + vinf[voice].reppos = reppos; + vinf[voice].repend = repend; + vinf[voice].kick = 1; +} + +void VC1_VoiceStop(UBYTE voice) +{ + vinf[voice].active = 0; +} + +BOOL VC1_VoiceStopped(UBYTE voice) +{ + return(vinf[voice].active==0); +} + +SLONG VC1_VoiceGetPosition(UBYTE voice) +{ + return(vinf[voice].current>>FRACBITS); +} + +void VC1_VoiceSetVolume(UBYTE voice,UWORD vol) +{ + /* protect against clicks if volume variation is too high */ + if(abs((int)vinf[voice].vol-(int)vol)>32) + vinf[voice].rampvol=CLICK_BUFFER; + vinf[voice].vol=vol; +} + +void VC1_VoiceSetPanning(UBYTE voice,ULONG pan) +{ + /* protect against clicks if panning variation is too high */ + if(abs((int)vinf[voice].pan-(int)pan)>48) + vinf[voice].rampvol=CLICK_BUFFER; + vinf[voice].pan=pan; +} + +/*========== External mixer interface */ + +void VC1_SampleUnload(SWORD handle) +{ + if (handlesample; + int handle; + ULONG t, length,loopstart,loopend; + + if(type==MD_HARDWARE) return -1; + + /* Find empty slot to put sample address in */ + for(handle=0;handleloopend > s->length) + s->loopend = s->length; + if (s->loopstart >= s->loopend) + s->flags &= ~SF_LOOP; + + length = s->length; + loopstart = s->loopstart; + loopend = s->loopend; + + SL_SampleSigned(sload); + SL_Sample8to16(sload); + + if(!(Samples[handle]=(SWORD*)MikMod_malloc((length+20)<<1))) { + _mm_errno = MMERR_SAMPLE_TOO_BIG; + return -1; + } + + /* read sample into buffer */ + if (SL_Load(Samples[handle],sload,length)) + return -1; + + /* Unclick sample */ + if(s->flags & SF_LOOP) { + if(s->flags & SF_BIDI) + for(t=0;t<16;t++) + Samples[handle][loopend+t]=Samples[handle][(loopend-t)-1]; + else + for(t=0;t<16;t++) + Samples[handle][loopend+t]=Samples[handle][t+loopstart]; + } else + for(t=0;t<16;t++) + Samples[handle][t+length]=0; + + return handle; +} + +ULONG VC1_SampleSpace(int type) +{ + (void)type; /* unused arg */ + + return vc_memory; +} + +ULONG VC1_SampleLength(int type,SAMPLE* s) +{ + (void)type; /* unused arg */ + + if (!s) return 0; + + return (s->length*((s->flags&SF_16BITS)?2:1))+16; +} + +ULONG VC1_VoiceRealVolume(UBYTE voice) +{ + ULONG i,s,size; + int k,j; + SWORD *smp; + SLONG t; + + t = vinf[voice].current>>FRACBITS; + if(!vinf[voice].active) return 0; + + s = vinf[voice].handle; + size = vinf[voice].size; + + i=64; t-=64; k=0; j=0; + if(i>size) i = size; + if(t<0) t = 0; + if(t+i > size) t = size-i; + + i &= ~1; /* make sure it's EVEN. */ + + smp = &Samples[s][t]; + for(;i;i--,smp++) { + if(k<*smp) k = *smp; + if(j>*smp) j = *smp; + } + return abs(k-j); +} + +#endif + +#endif + +/* ex:set ts=4: */ diff --git a/src/libs/misc.h b/src/libs/misc.h new file mode 100644 index 0000000..39333a2 --- /dev/null +++ b/src/libs/misc.h @@ -0,0 +1,66 @@ +//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 includes some misc things, previously in SDL_wrapper.h +// before modularization. -Mika + +#ifndef MISC_H +#define MISC_H + +#include +#include +#include "port.h" + +#if defined(__cplusplus) +extern "C" { +#endif + + +extern int TFB_DEBUG_HALT; + +static inline void explode (void) _NORETURN; + +static inline void explode (void) +{ +#ifdef DEBUG + // give debugger a chance to hook + abort (); +#else + exit (EXIT_FAILURE); +#endif +} + +/* Sometimes you just have to remove a 'const'. + * (for instance, when implementing a function like strchr) + */ +static inline void * +unconst(const void *arg) { + union { + void *c; + const void *cc; + } u; + u.cc = arg; + return u.c; +} + +#if defined(__cplusplus) +} +#endif + +#endif + diff --git a/src/libs/net.h b/src/libs/net.h new file mode 100644 index 0000000..ca5931e --- /dev/null +++ b/src/libs/net.h @@ -0,0 +1,36 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_NET_H_ +#define LIBS_NET_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "network/network.h" +#include "network/netmanager/netmanager.h" +#include "network/connect/connect.h" +#include "network/connect/listen.h" +#include "network/connect/resolve.h" + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_NET_H_ */ diff --git a/src/libs/network/FILES b/src/libs/network/FILES new file mode 100644 index 0000000..ddcd685 --- /dev/null +++ b/src/libs/network/FILES @@ -0,0 +1,26 @@ +In libs/network/: +netport.{c,h} Portability definitions. +bytesex.h Functions for endianness conversions. +network.h Functions for initialising the network subsystem. +network_bsd.c Network subsystem functions for BSD sockets. +network_win.c Network subsystem functions for Winsock sockets. +wspiapiwrap.{c,h} Hack to make sure is #included in exactly + one file. + +In libs/network/netmanager/: +ndesc.{c,h} Defines network descriptors. +netmanager.h Handles callbacks for network activity. +netmanager_bsd.{c,h} NetManager for systems with BSD sockets. +netmanager_win.{c,h} NetManager for Winsock systems. + +In libs/network/socket/: +socket.{c,h} Platform-independant socket layer. +socket_bsd.{c,h} Socket operations for BSD sockets. +socket_win.{c,h} Socket operations for Winsock sockets. + +In libs/network/connect/: +connect.{c,h} Routines for establishing outgoing connections. +listen.{c,h} Routines for receiving incoming connections. +resolve.{c,h} Routines for hostname lookups. + + diff --git a/src/libs/network/Makeinfo b/src/libs/network/Makeinfo new file mode 100644 index 0000000..40171fa --- /dev/null +++ b/src/libs/network/Makeinfo @@ -0,0 +1,14 @@ +uqm_SUBDIRS="connect netmanager socket" +uqm_CFILES="netport.c" +uqm_HFILES="bytesex.h netport.h network.h" + +if [ -n "$uqm_USE_WINSOCK" ]; then + uqm_CFILES="$uqm_CFILES network_win.c" + if [ -n "$MACRO___MINGW32__" ]; then + uqm_CFILES="$uqm_CFILES wspiapiwrap.c" + uqm_HFILES="$uqm_HFILES wspiapiwrap.h" + fi +else + uqm_CFILES="$uqm_CFILES network_bsd.c" +fi + diff --git a/src/libs/network/bytesex.h b/src/libs/network/bytesex.h new file mode 100644 index 0000000..4ee89d7 --- /dev/null +++ b/src/libs/network/bytesex.h @@ -0,0 +1,96 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Routines for changing the endianness of values. +// I'm not using ntohs() etc. as those would require include files that may +// have conflicting definitions. This is a problem on Windows, where these +// functions are in winsock2.h, which includes windows.h, which includes +// pretty much Microsoft's complete collection of .h files. + +#ifndef LIBS_NETWORK_BYTESEX_H_ +#define LIBS_NETWORK_BYTESEX_H_ + +#include "port.h" + // for inline +#include "endian_uqm.h" + // for WORDS_BIGENDIAN +#include "types.h" + +static inline uint16 +swapBytes16(uint16 x) { + return (x << 8) | (x >> 8); +} + +static inline uint32 +swapBytes32(uint32 x) { + return (x << 24) + | ((x & 0x0000ff00) << 8) + | ((x & 0x00ff0000) >> 8) + | (x >> 24); +} + +#ifdef WORDS_BIGENDIAN +// Already in network order. + +static inline uint16 +hton16(uint16 x) { + return x; +} + +static inline uint32 +hton32(uint32 x) { + return x; +} + +static inline uint16 +ntoh16(uint16 x) { + return x; +} + +static inline uint32 +ntoh32(uint32 x) { + return x; +} + +#else /* !defined(WORDS_BIGENDIAN) */ +// Need to swap bytes + +static inline uint16 +hton16(uint16 x) { + return swapBytes16(x); +} + +static inline uint32 +hton32(uint32 x) { + return swapBytes32(x); +} + +static inline uint16 +ntoh16(uint16 x) { + return swapBytes16(x); +} + +static inline uint32 +ntoh32(uint32 x) { + return swapBytes32(x); +} + +#endif /* defined(WORDS_BIGENDIAN) */ + +#endif /* LIBS_NETWORK_BYTESEX_H_ */ + diff --git a/src/libs/network/connect/Makeinfo b/src/libs/network/connect/Makeinfo new file mode 100644 index 0000000..5595270 --- /dev/null +++ b/src/libs/network/connect/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="connect.c listen.c resolve.c" +uqm_HFILES="connect.h listen.h resolve.h" diff --git a/src/libs/network/connect/connect.c b/src/libs/network/connect/connect.c new file mode 100644 index 0000000..4599b18 --- /dev/null +++ b/src/libs/network/connect/connect.c @@ -0,0 +1,490 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 CONNECT_INTERNAL +#define SOCKET_INTERNAL +#include "connect.h" + +#include "resolve.h" +#include "libs/alarm.h" +#include "../socket/socket.h" +#include "libs/misc.h" +#include "libs/log.h" + +#include +#include +#include +#include +#ifdef USE_WINSOCK +# include +# include +# include "../wspiapiwrap.h" +#else +# include +#endif + +#define DEBUG_CONNECT_REF +#ifdef DEBUG_CONNECT_REF +# include "types.h" +#endif + + +static void connectHostNext(ConnectState *connectState); +static void doConnectCallback(ConnectState *connectState, NetDescriptor *nd, + const struct sockaddr *addr, socklen_t addrLen); +static void doConnectErrorCallback(ConnectState *connectState, + const ConnectError *error); + + +static ConnectState * +ConnectState_alloc(void) { + return (ConnectState *) malloc(sizeof (ConnectState)); +} + +static void +ConnectState_free(ConnectState *connectState) { + free(connectState); +} + +static void +ConnectState_delete(ConnectState *connectState) { + assert(connectState->nd == NULL); + assert(connectState->alarm == NULL); + assert(connectState->info == NULL); + assert(connectState->infoPtr == NULL); + ConnectState_free(connectState); +} + +void +ConnectState_incRef(ConnectState *connectState) { + assert(connectState->refCount < REFCOUNT_MAX); + connectState->refCount++; +#ifdef DEBUG_CONNECT_REF + log_add(log_Debug, "ConnectState %08" PRIxPTR ": ref++ (%d)", + (uintptr_t) connectState, connectState->refCount); +#endif +} + +bool +ConnectState_decRef(ConnectState *connectState) { + assert(connectState->refCount > 0); + connectState->refCount--; +#ifdef DEBUG_CONNECT_REF + log_add(log_Debug, "ConnectState %08" PRIxPTR ": ref-- (%d)", + (uintptr_t) connectState, connectState->refCount); +#endif + if (connectState->refCount == 0) { + ConnectState_delete(connectState); + return true; + } + return false; +} + +// decrements ref count by 1 +void +ConnectState_close(ConnectState *connectState) { + if (connectState->resolveState != NULL) { + Resolve_close(connectState->resolveState); + connectState->resolveState = NULL; + } + if (connectState->alarm != NULL) { + Alarm_remove(connectState->alarm); + connectState->alarm = NULL; + } + if (connectState->nd != NULL) { + NetDescriptor_close(connectState->nd); + connectState->nd = NULL; + } + if (connectState->info != NULL) { + freeaddrinfo(connectState->info); + connectState->info = NULL; + connectState->infoPtr = NULL; + } + connectState->state = Connect_closed; + ConnectState_decRef(connectState); +} + +void +ConnectState_setExtra(ConnectState *connectState, void *extra) { + connectState->extra = extra; +} + +void * +ConnectState_getExtra(ConnectState *connectState) { + return connectState->extra; +} + +static void +connectCallback(NetDescriptor *nd) { + // Called by the NetManager when a connection has been established. + ConnectState *connectState = + (ConnectState *) NetDescriptor_getExtra(nd); + int err; + + if (connectState->alarm != NULL) { + Alarm_remove(connectState->alarm); + connectState->alarm = NULL; + } + + if (connectState->state == Connect_closed) { + // The connection attempt has been aborted. +#ifdef DEBUG + log_add(log_Debug, "Connection attempt was aborted."); +#endif + ConnectState_decRef(connectState); + return; + } + + if (Socket_getError(NetDescriptor_getSocket(nd), &err) == -1) { + log_add(log_Fatal, "Socket_getError() failed: %s.", + strerror(errno)); + explode(); + } + if (err != 0) { +#ifdef DEBUG + log_add(log_Debug, "connect() failed: %s.", strerror(err)); +#endif + NetDescriptor_close(nd); + connectState->nd = NULL; + connectState->infoPtr = connectState->infoPtr->ai_next; + connectHostNext(connectState); + return; + } + +#ifdef DEBUG + log_add(log_Debug, "Connection established."); +#endif + + // Notify the higher layer. + connectState->nd = NULL; + // The callback function takes over ownership of the + // NetDescriptor. + NetDescriptor_setWriteCallback(nd, NULL); + // Note that connectState->info and connectState->infoPtr are cleaned up + // when ConnectState_close() is called by the callback function. + + ConnectState_incRef(connectState); + doConnectCallback(connectState, nd, connectState->infoPtr->ai_addr, + connectState->infoPtr->ai_addrlen); + { + // The callback called should release the last reference to + // the connectState, by calling ConnectState_close(). + bool released = ConnectState_decRef(connectState); + assert(released); + (void) released; // In case assert() evaluates to nothing. + } +} + +static void +connectTimeoutCallback(ConnectState *connectState) { + connectState->alarm = NULL; + + NetDescriptor_close(connectState->nd); + connectState->nd = NULL; + + connectState->infoPtr = connectState->infoPtr->ai_next; + connectHostNext(connectState); +} + +static void +setConnectTimeout(ConnectState *connectState) { + assert(connectState->alarm == NULL); + + connectState->alarm = + Alarm_addRelativeMs(connectState->flags.timeout, + (AlarmCallback) connectTimeoutCallback, connectState); +} + +// Try connecting to the next address. +static Socket * +tryConnectHostNext(ConnectState *connectState) { + struct addrinfo *info; + Socket *sock; + int connectResult; + + assert(connectState->nd == NULL); + + info = connectState->infoPtr; + + sock = Socket_openNative(info->ai_family, info->ai_socktype, + info->ai_protocol); + if (sock == Socket_noSocket) { + int savedErrno = errno; + log_add(log_Error, "socket() failed: %s.", strerror(errno)); + errno = savedErrno; + return Socket_noSocket; + } + + if (Socket_setNonBlocking(sock) == -1) { + int savedErrno = errno; + log_add(log_Error, "Could not make socket non-blocking: %s.", + strerror(errno)); + errno = savedErrno; + return Socket_noSocket; + } + + (void) Socket_setReuseAddr(sock); + // Ignore errors; it's not a big deal. + (void) Socket_setInlineOOB(sock); + // Ignore errors; it's not a big deal as the other party is not + // not supposed to send any OOB data. + (void) Socket_setKeepAlive(sock); + // Ignore errors; it's not a big deal. + + connectResult = Socket_connect(sock, info->ai_addr, info->ai_addrlen); + if (connectResult == 0) { + // Connection has already succeeded. + // We just wait for the writability callback anyhow, so that + // we can use one code path. + return sock; + } + + switch (errno) { + case EINPROGRESS: + // Connection in progress; wait for the write callback. + return sock; + } + + // Connection failed immediately. This is just for one of the addresses, + // so this does not have to be final. + // Note that as the socket is non-blocking, most failed connection + // errors will usually not be reported immediately. + { + int savedErrno = errno; + Socket_close(sock); +#ifdef DEBUG + log_add(log_Debug, "connect() immediately failed for one address: " + "%s.", strerror(errno)); + // TODO: add the address in the status message. +#endif + errno = savedErrno; + } + return Socket_noSocket; +} + +static void +connectRetryCallback(ConnectState *connectState) { + connectState->alarm = NULL; + + connectState->infoPtr = connectState->info; + connectHostNext(connectState); +} + +static void +setConnectRetryAlarm(ConnectState *connectState) { + assert(connectState->alarm == NULL); + assert(connectState->flags.retryDelayMs != Connect_noRetry); + + connectState->alarm = + Alarm_addRelativeMs(connectState->flags.retryDelayMs, + (AlarmCallback) connectRetryCallback, connectState); +} + +static void +connectHostReportAllFailed(ConnectState *connectState) { + // Could not connect to any host. + ConnectError error; + freeaddrinfo(connectState->info); + connectState->info = NULL; + connectState->infoPtr = NULL; + connectState->state = Connect_closed; + error.state = Connect_connecting; + error.err = ETIMEDOUT; + // No errno code is exactly suitable. We have been unable + // to connect to any host, but the reasons may vary + // (unreachable, refused, ...). + // ETIMEDOUT is the least specific portable errno code that + // seems appropriate. + doConnectErrorCallback(connectState, &error); +} + +static void +connectHostNext(ConnectState *connectState) { + Socket *sock; + + while (connectState->infoPtr != NULL) { + sock = tryConnectHostNext(connectState); + + if (sock != Socket_noSocket) { + // Connection succeeded or connection in progress + connectState->nd = + NetDescriptor_new(sock, (void *) connectState); + if (connectState->nd == NULL) { + ConnectError error; + int savedErrno = errno; + + log_add(log_Error, "NetDescriptor_new() failed: %s.", + strerror(errno)); + Socket_close(sock); + freeaddrinfo(connectState->info); + connectState->info = NULL; + connectState->infoPtr = NULL; + connectState->state = Connect_closed; + error.state = Connect_connecting; + error.err = savedErrno; + doConnectErrorCallback(connectState, &error); + return; + } + + NetDescriptor_setWriteCallback(connectState->nd, connectCallback); + setConnectTimeout(connectState); + return; + } + + connectState->infoPtr = connectState->infoPtr->ai_next; + } + + // Connect failed to all addresses. + + if (connectState->flags.retryDelayMs == Connect_noRetry) { + connectHostReportAllFailed(connectState); + return; + } + + setConnectRetryAlarm(connectState); +} + +static void +connectHostResolveCallback(ResolveState *resolveState, + struct addrinfo *info) { + ConnectState *connectState = + (ConnectState *) ResolveState_getExtra(resolveState); + + connectState->state = Connect_connecting; + + Resolve_close(resolveState); + connectState->resolveState = NULL; + + if (connectState->flags.familyPrefer != PF_UNSPEC) { + // Reorganise the 'info' list to put the structures of the + // prefered family in front. + struct addrinfo *preferred; + struct addrinfo **preferredEnd; + struct addrinfo *rest; + struct addrinfo **restEnd; + splitAddrInfoOnFamily(info, connectState->flags.familyPrefer, + &preferred, &preferredEnd, &rest, &restEnd); + info = preferred; + *preferredEnd = rest; + } + + connectState->info = info; + connectState->infoPtr = info; + + connectHostNext(connectState); +} + +static void +connectHostResolveErrorCallback(ResolveState *resolveState, + const ResolveError *resolveError) { + ConnectState *connectState = + (ConnectState *) ResolveState_getExtra(resolveState); + ConnectError connectError; + + assert(resolveError->gaiRes != 0); + + Resolve_close(resolveState); + connectState->resolveState = NULL; + + connectError.state = Connect_resolving; + connectError.resolveError = resolveError; + connectError.err = resolveError->err; + doConnectErrorCallback(connectState, &connectError); +} + +ConnectState * +connectHostByName(const char *host, const char *service, Protocol proto, + const ConnectFlags *flags, ConnectConnectCallback connectCallback, + ConnectErrorCallback errorCallback, void *extra) { + struct addrinfo hints; + ConnectState *connectState; + ResolveFlags resolveFlags; + // Structure is empty (for now). + + assert(flags->familyDemand == PF_inet || + flags->familyDemand == PF_inet6 || + flags->familyDemand == PF_unspec); + assert(flags->familyPrefer == PF_inet || + flags->familyPrefer == PF_inet6 || + flags->familyPrefer == PF_unspec); + assert(proto == IPProto_tcp || proto == IPProto_udp); + + memset(&hints, '\0', sizeof hints); + hints.ai_family = protocolFamilyTranslation[flags->familyDemand]; + hints.ai_protocol = protocolTranslation[proto]; + + if (proto == IPProto_tcp) { + hints.ai_socktype = SOCK_STREAM; + } else { + assert(proto == IPProto_udp); + hints.ai_socktype = SOCK_DGRAM; + } + hints.ai_flags = 0; + + connectState = ConnectState_alloc(); + connectState->refCount = 1; +#ifdef DEBUG_CONNECT_REF + log_add(log_Debug, "ConnectState %08" PRIxPTR ": ref=1 (%d)", + (uintptr_t) connectState, connectState->refCount); +#endif + connectState->state = Connect_resolving; + connectState->flags = *flags; + connectState->connectCallback = connectCallback; + connectState->errorCallback = errorCallback; + connectState->extra = extra; + connectState->info = NULL; + connectState->infoPtr = NULL; + connectState->nd = NULL; + connectState->alarm = NULL; + + connectState->resolveState = getaddrinfoAsync( + host, service, &hints, &resolveFlags, + (ResolveCallback) connectHostResolveCallback, + (ResolveErrorCallback) connectHostResolveErrorCallback, + (ResolveCallbackArg) connectState); + + return connectState; +} + +// NB: The callback function becomes the owner of nd +static void +doConnectCallback(ConnectState *connectState, NetDescriptor *nd, + const struct sockaddr *addr, socklen_t addrLen) { + assert(connectState->connectCallback != NULL); + + ConnectState_incRef(connectState); + // No need to increment nd as the callback function takes over ownership. + (*connectState->connectCallback)(connectState, nd, addr, addrLen); + ConnectState_decRef(connectState); +} + +static void +doConnectErrorCallback(ConnectState *connectState, + const ConnectError *error) { + assert(connectState->errorCallback != NULL); + + ConnectState_incRef(connectState); + (*connectState->errorCallback)(connectState, error); + ConnectState_decRef(connectState); +} + + + diff --git a/src/libs/network/connect/connect.h b/src/libs/network/connect/connect.h new file mode 100644 index 0000000..77d7a44 --- /dev/null +++ b/src/libs/network/connect/connect.h @@ -0,0 +1,111 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_NETWORK_CONNECT_CONNECT_H_ +#define LIBS_NETWORK_CONNECT_CONNECT_H_ + + +typedef struct ConnectFlags ConnectFlags; +typedef struct ConnectError ConnectError; +typedef struct ConnectState ConnectState; + +typedef enum { + Connect_closed, + Connect_resolving, + Connect_connecting +} ConnectStateState; + + +#include "../netmanager/netmanager.h" +#include "../socket/socket.h" +#include "resolve.h" + + +// For connectHost() +struct ConnectFlags { + ProtocolFamily familyDemand; + // Only accept a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + ProtocolFamily familyPrefer; + // Prefer a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + int timeout; + /* Number of milliseconds before timing out a connection attempt. + * Note that if a host has multiple addresses, a connect to that + * host will have this timeout *per address*. */ + int retryDelayMs; + /* Retry connecting this many ms after connecting to the last + * address for the specified host fails. Set to Connect_noRetry + * to give up after one try. */ +#define Connect_noRetry -1 +}; + +struct ConnectError { + ConnectStateState state; + // State where the error occured. + int err; + // errno value. Not relevant if state == resolving unless + // gaiRes == EAI_SYSTEM. + const ResolveError *resolveError; + // Only relevant if state == resolving. +}; + +typedef void (*ConnectConnectCallback)(ConnectState *connectState, + NetDescriptor *nd, const struct sockaddr *addr, socklen_t addrLen); +typedef void (*ConnectErrorCallback)(ConnectState *connectState, + const ConnectError *error); + +#ifdef CONNECT_INTERNAL + +#include "libs/alarm.h" + +struct ConnectState { + RefCount refCount; + + ConnectStateState state; + ConnectFlags flags; + + ConnectConnectCallback connectCallback; + ConnectErrorCallback errorCallback; + void *extra; + + struct addrinfo *info; + struct addrinfo *infoPtr; + + ResolveState *resolveState; + + NetDescriptor *nd; + Alarm *alarm; + // Used for both the timeout for a connection attempt + // and to retry after all addresses have been tried. +}; +#endif /* CONNECT_INTERNAL */ + +ConnectState *connectHostByName(const char *host, const char *service, + Protocol proto, const ConnectFlags *flags, + ConnectConnectCallback connectCallback, + ConnectErrorCallback errorCallback, void *extra); +void ConnectState_incRef(ConnectState *connectState); +bool ConnectState_decRef(ConnectState *connectState); +void ConnectState_close(ConnectState *connectState); +void ConnectState_setExtra(ConnectState *connectState, void *extra); +void *ConnectState_getExtra(ConnectState *connectState); + +#endif /* LIBS_NETWORK_CONNECT_CONNECT_H_ */ + + diff --git a/src/libs/network/connect/listen.c b/src/libs/network/connect/listen.c new file mode 100644 index 0000000..4a7a65c --- /dev/null +++ b/src/libs/network/connect/listen.c @@ -0,0 +1,456 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "../netport.h" + +#define LISTEN_INTERNAL +#define SOCKET_INTERNAL +#include "listen.h" + +#include "resolve.h" +#include "../socket/socket.h" +#include "../netmanager/netmanager.h" +#include "libs/misc.h" +#include "libs/log.h" + +#include +#include +#ifdef USE_WINSOCK +# include +# include +# include "../wspiapiwrap.h" +#else +# include +#endif +#include +#include + +#define DEBUG_LISTEN_REF +#ifdef DEBUG_LISTEN_REF +# include "types.h" +#endif + + +static void acceptCallback(NetDescriptor *nd); +static void doListenErrorCallback(ListenState *listenState, + const ListenError *error); + + +static ListenState * +ListenState_alloc(void) { + return (ListenState *) malloc(sizeof (ListenState)); +} + +static void +ListenState_free(ListenState *listenState) { + free(listenState); +} + +static void +ListenState_delete(ListenState *listenState) { + assert(listenState->nds == NULL); + ListenState_free(listenState); +} + +void +ListenState_incRef(ListenState *listenState) { + assert(listenState->refCount < REFCOUNT_MAX); + listenState->refCount++; +#ifdef DEBUG_LISTEN_REF + log_add(log_Debug, "ListenState %08" PRIxPTR ": ref++ (%d)", + (uintptr_t) listenState, listenState->refCount); +#endif +} + +bool +ListenState_decRef(ListenState *listenState) { + assert(listenState->refCount > 0); + listenState->refCount--; +#ifdef DEBUG_LISTEN_REF + log_add(log_Debug, "ListenState %08" PRIxPTR ": ref-- (%d)", + (uintptr_t) listenState, listenState->refCount); +#endif + if (listenState->refCount == 0) { + ListenState_delete(listenState); + return true; + } + return false; +} + +// Decrements ref count byh 1 +void +ListenState_close(ListenState *listenState) { + if (listenState->resolveState != NULL) { + Resolve_close(listenState->resolveState); + listenState->resolveState = NULL; + } + if (listenState->nds != NULL) { + while (listenState->numNd > 0) { + listenState->numNd--; + NetDescriptor_close(listenState->nds[listenState->numNd]); + } + free(listenState->nds); + listenState->nds = NULL; + } + listenState->state = Listen_closed; + ListenState_decRef(listenState); +} + +void +ListenState_setExtra(ListenState *listenState, void *extra) { + listenState->extra = extra; +} + +void * +ListenState_getExtra(ListenState *listenState) { + return listenState->extra; +} + +static NetDescriptor * +listenPortSingle(struct ListenState *listenState, struct addrinfo *info) { + Socket *sock; + int bindResult; + int listenResult; + NetDescriptor *nd; + + sock = Socket_openNative(info->ai_family, info->ai_socktype, + info->ai_protocol); + if (sock == Socket_noSocket) { + int savedErrno = errno; + log_add(log_Error, "socket() failed: %s.", strerror(errno)); + errno = savedErrno; + return NULL; + } + + (void) Socket_setReuseAddr(sock); + // Ignore errors; it's not a big deal. + if (Socket_setNonBlocking(sock) == -1) { + int savedErrno = errno; + // Error message is already printed. + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + bindResult = Socket_bind(sock, info->ai_addr, info->ai_addrlen); + if (bindResult == -1) { + int savedErrno = errno; + if (errno == EADDRINUSE) { +#ifdef DEBUG + log_add(log_Warning, "bind() failed: %s.", strerror(errno)); +#endif + } else + log_add(log_Error, "bind() failed: %s.", strerror(errno)); + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + listenResult = Socket_listen(sock, listenState->flags.backlog); + if (listenResult == -1) { + int savedErrno = errno; + log_add(log_Error, "listen() failed: %s.", strerror(errno)); + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + nd = NetDescriptor_new(sock, (void *) listenState); + if (nd == NULL) { + int savedErrno = errno; + log_add(log_Error, "NetDescriptor_new() failed: %s.", + strerror(errno)); + Socket_close(sock); + errno = savedErrno; + return NULL; + } + + NetDescriptor_setReadCallback(nd, acceptCallback); + + return nd; +} + +static void +listenPortMulti(struct ListenState *listenState, struct addrinfo *info) { + struct addrinfo *addrPtr; + size_t addrCount; + size_t addrOkCount; + NetDescriptor **nds; + + // Count how many addresses we've got. + addrCount = 0; + for (addrPtr = info; addrPtr != NULL; addrPtr = addrPtr->ai_next) + addrCount++; + + // This is where we intend to store the file descriptors of the + // listening sockets. + nds = malloc(addrCount * sizeof listenState->nds[0]); + + // Bind to each address. + addrOkCount = 0; + for (addrPtr = info; addrPtr != NULL; addrPtr = addrPtr->ai_next) { + NetDescriptor *nd; + nd = listenPortSingle(listenState, addrPtr); + if (nd == NULL) { + // Failed. On to the next. + // An error message is already printed for serious errors. + // If the address is already in use, we here also print + // a message when we are not already listening on one of + // the other addresses. + // This is because on some IPv6 capabable systems (like Linux), + // IPv6 sockets also handle IPv4 connections, which means + // that a separate IPv4 socket won't be able to bind to the + // port. + // BUG: if the IPv4 socket is in the list before the + // IPv6 socket, it will be the IPv6 which will fail to bind, + // so only IPv4 connections will be handled, as v4 sockets can't + // accept v6 connections. + // In practice, on Linux, I haven't seen it happen, but + // it's a real possibility. + if (errno == EADDRINUSE && addrOkCount == 0) { + log_add(log_Error, "Error while preparing a network socket " + "for incoming connections: %s", strerror(errno)); + } + continue; + } + + nds[addrOkCount] = nd; + addrOkCount++; + } + + freeaddrinfo(info); + + listenState->nds = + realloc(nds, addrOkCount * sizeof listenState->nds[0]); + listenState->numNd = addrOkCount; + + if (addrOkCount == 0) { + // Could not listen on any port. + ListenError error; + error.state = Listen_listening; + error.err = EIO; + // Nothing better to offer. + doListenErrorCallback(listenState, &error); + return; + } +} + +static void +listenPortResolveCallback(ResolveState *resolveState, + struct addrinfo *result) { + ListenState *listenState = + (ListenState *) ResolveState_getExtra(resolveState); + Resolve_close(listenState->resolveState); + listenState->resolveState = NULL; + listenState->state = Listen_listening; + listenPortMulti(listenState, result); +} + +static void +listenPortResolveErrorCallback(ResolveState *resolveState, + const ResolveError *resolveError) { + ListenState *listenState = + (ListenState *) ResolveState_getExtra(resolveState); + ListenError listenError; + + assert(resolveError->gaiRes != 0); + + listenError.state = Listen_resolving; + listenError.resolveError = resolveError; + listenError.err = resolveError->err; + doListenErrorCallback(listenState, &listenError); +} + +// 'proto' is one of IPProto_tcp or IPProto_udp. +ListenState * +listenPort(const char *service, Protocol proto, const ListenFlags *flags, + ListenConnectCallback connectCallback, + ListenErrorCallback errorCallback, void *extra) { + struct addrinfo hints; + ListenState *listenState; + ResolveFlags resolveFlags; + // Structure is empty (for now). + + assert(flags->familyDemand == PF_inet || + flags->familyDemand == PF_inet6 || + flags->familyDemand == PF_unspec); + assert(flags->familyPrefer == PF_inet || + flags->familyPrefer == PF_inet6 || + flags->familyPrefer == PF_unspec); + assert(proto == IPProto_tcp || proto == IPProto_udp); + + // Acquire a list of addresses to bind to. + memset(&hints, '\0', sizeof hints); + hints.ai_family = protocolFamilyTranslation[flags->familyDemand]; + hints.ai_protocol = protocolTranslation[proto]; + + if (proto == IPProto_tcp) { + hints.ai_socktype = SOCK_STREAM; + } else { + assert(proto == IPProto_udp); + hints.ai_socktype = SOCK_DGRAM; + } + hints.ai_flags = AI_PASSIVE; + + listenState = ListenState_alloc(); + listenState->refCount = 1; +#ifdef DEBUG_LISTEN_REF + log_add(log_Debug, "ListenState %08" PRIxPTR ": ref=1 (%d)", + (uintptr_t) listenState, listenState->refCount); +#endif + listenState->state = Listen_resolving; + listenState->flags = *flags; + listenState->connectCallback = connectCallback; + listenState->errorCallback = errorCallback; + listenState->extra = extra; + listenState->nds = NULL; + listenState->numNd = 0; + + listenState->resolveState = getaddrinfoAsync(NULL, service, &hints, + &resolveFlags, listenPortResolveCallback, + listenPortResolveErrorCallback, + (ResolveCallbackArg) listenState); + + return listenState; +} + +// NB: The callback function becomes the owner of newNd. +static void +doListenConnectCallback(ListenState *listenState, NetDescriptor *listenNd, + NetDescriptor *newNd, + const struct sockaddr *addr, SOCKLEN_T addrLen) { + assert(listenState->connectCallback != NULL); + + ListenState_incRef(listenState); + // No need to increment listenNd, as there's guaranteed to be one + // reference from listenState. And no need to increment newNd, + // as the callback function takes over ownership. + (*listenState->connectCallback)(listenState, listenNd, newNd, + addr, (socklen_t) addrLen); + ListenState_decRef(listenState); +} + +static void +doListenErrorCallback(ListenState *listenState, const ListenError *error) { + assert(listenState->errorCallback != NULL); + + ListenState_incRef(listenState); + (*listenState->errorCallback)(listenState, error); + ListenState_decRef(listenState); +} + +static void +acceptSingleConnection(ListenState *listenState, NetDescriptor *nd) { + Socket *sock; + Socket *acceptResult; + struct sockaddr_storage addr; + socklen_t addrLen; + NetDescriptor *newNd; + + sock = NetDescriptor_getSocket(nd); + addrLen = sizeof (addr); + acceptResult = Socket_accept(sock, (struct sockaddr *) &addr, &addrLen); + if (acceptResult == Socket_noSocket) { + switch (errno) { + case EWOULDBLOCK: + case ECONNABORTED: + // Nothing serious. Keep listening. + return; + case EMFILE: + case ENFILE: + case ENOBUFS: + case ENOMEM: +#ifdef ENOSR + case ENOSR: +#endif + // Serious problems, but future connections may still + // be possible. + log_add(log_Warning, "accept() reported '%s'", + strerror(errno)); + return; + default: + // Should not happen. + log_add(log_Fatal, "Internal error: accept() reported " + "'%s'", strerror(errno)); + explode(); + } + } + + (void) Socket_setReuseAddr(acceptResult); + // Ignore errors; it's not a big deal. + if (Socket_setNonBlocking(acceptResult) == -1) { + int savedErrno = errno; + log_add(log_Error, "Could not make socket non-blocking: %s.", + strerror(errno)); + Socket_close(acceptResult); + errno = savedErrno; + return; + } + (void) Socket_setInlineOOB(acceptResult); + // Ignore errors; it's not a big deal as the other + // party is not not supposed to send any OOB data. + (void) Socket_setKeepAlive(sock); + // Ignore errors; it's not a big deal. + +#ifdef DEBUG + { + char hostname[NI_MAXHOST]; + int gniRes; + + gniRes = getnameinfo((struct sockaddr *) &addr, addrLen, + hostname, sizeof hostname, NULL, 0, 0); + if (gniRes != 0) { + log_add(log_Error, "Error while performing hostname " + "lookup for incoming connection: %s", + (gniRes == EAI_SYSTEM) ? strerror(errno) : + gai_strerror(gniRes)); + } else { + log_add(log_Debug, "Accepted incoming connection from '%s'.", + hostname); + } + } +#endif + + newNd = NetDescriptor_new(acceptResult, NULL); + if (newNd == NULL) { + int savedErrno = errno; + log_add(log_Error, "NetDescriptor_new() failed: %s.", + strerror(errno)); + Socket_close(acceptResult); + errno = savedErrno; + return; + } + + doListenConnectCallback(listenState, nd, newNd, + (struct sockaddr *) &addr, addrLen); + // NB: newNd is now handed over to the callback function, and should + // no longer be referenced from here. +} + +// Called when select() has indicated readability on a listening socket, +// i.e. when a connection is in the queue. +static void +acceptCallback(NetDescriptor *nd) { + ListenState *listenState = (ListenState *) NetDescriptor_getExtra(nd); + + acceptSingleConnection(listenState, nd); +} + + diff --git a/src/libs/network/connect/listen.h b/src/libs/network/connect/listen.h new file mode 100644 index 0000000..e44ef53 --- /dev/null +++ b/src/libs/network/connect/listen.h @@ -0,0 +1,106 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_NETWORK_CONNECT_LISTEN_H_ +#define LIBS_NETWORK_CONNECT_LISTEN_H_ + +typedef struct ListenFlags ListenFlags; +typedef enum { + Listen_closed, + Listen_resolving, + Listen_listening +} ListenStateState; +typedef struct ListenError ListenError; +typedef struct ListenState ListenState; + +#include "port.h" + +#ifdef USE_WINSOCK +// I do not want to include winsock2.h, because of possible conflicts with +// code that includes this file. +// Note that listen.c itself includes winsock2.h; SOCKLEN_T is used there +// only where necessary to keep the API consistent. +# define SOCKLEN_T size_t +struct sockaddr; +#else +# include +# define SOCKLEN_T socklen_t +#endif + +#include "resolve.h" +#include "../socket/socket.h" + +#include "../netmanager/netmanager.h" + +// For listenPort() +struct ListenFlags { + ProtocolFamily familyDemand; + // Only accept a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + ProtocolFamily familyPrefer; + // Prefer a protocol family of this type. + // One of PF_inet, PF_inet6, or PF_unspec. + int backlog; + // As the 2rd parameter to listen(); +}; + +struct ListenError { + ListenStateState state; + // State where the error occured. + int err; + // errno value. Not relevant if state == resolving unless + // gaiRes == EAI_SYSTEM. + const ResolveError *resolveError; + // Only relevant if state == resolving. +}; + +typedef void (*ListenConnectCallback)(ListenState *listenState, + NetDescriptor *listenNd, NetDescriptor *newNd, + const struct sockaddr *addr, SOCKLEN_T addrLen); +typedef void (*ListenErrorCallback)(ListenState *listenState, + const ListenError *error); + +#ifdef LISTEN_INTERNAL +struct ListenState { + RefCount refCount; + + ListenStateState state; + ListenFlags flags; + + ListenConnectCallback connectCallback; + ListenErrorCallback errorCallback; + void *extra; + + ResolveState *resolveState; + NetDescriptor **nds; + size_t numNd; + // INV: (numNd == NULL) == (nds == NULL) +}; +#endif /* defined(LISTEN_INTERNAL) */ + +ListenState *listenPort(const char *service, Protocol proto, + const ListenFlags *flags, ListenConnectCallback connectCallback, + ListenErrorCallback errorCallback, void *extra); +void ListenState_close(ListenState *listenState); +void ListenState_incRef(ListenState *listenState); +bool ListenState_decRef(ListenState *listenState); +void ListenState_setExtra(ListenState *listenState, void *extra); +void *ListenState_getExtra(ListenState *listenState); + +#endif /* LIBS_NETWORK_CONNECT_LISTEN_H_ */ + diff --git a/src/libs/network/connect/resolve.c b/src/libs/network/connect/resolve.c new file mode 100644 index 0000000..646e437 --- /dev/null +++ b/src/libs/network/connect/resolve.c @@ -0,0 +1,211 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 RESOLVE_INTERNAL +#include "resolve.h" + +#include +#include +#include + +#define DEBUG_RESOLVE_REF +#ifdef DEBUG_RESOLVE_REF +# include "types.h" +# include "libs/log.h" +# include +#endif + +static ResolveState * +ResolveState_new(void) { + return (ResolveState *) malloc(sizeof (ResolveState)); +} + +static void +ResolveState_free(ResolveState *resolveState) { + free(resolveState); +} + +static void +ResolveState_delete(ResolveState *resolveState) { + assert(resolveState->callbackID == CallbackID_invalid); + ResolveState_free(resolveState); +} + +void +ResolveState_incRef(ResolveState *resolveState) { + assert(resolveState->refCount < REFCOUNT_MAX); + resolveState->refCount++; +#ifdef DEBUG_RESOLVE_REF + log_add(log_Debug, "ResolveState %08" PRIxPTR ": ref++ (%d)", + (uintptr_t) resolveState, resolveState->refCount); +#endif +} + +bool +ResolveState_decRef(ResolveState *resolveState) { + assert(resolveState->refCount > 0); + resolveState->refCount--; +#ifdef DEBUG_RESOLVE_REF + log_add(log_Debug, "ResolveState %08" PRIxPTR ": ref-- (%d)", + (uintptr_t) resolveState, resolveState->refCount); +#endif + if (resolveState->refCount == 0) { + ResolveState_delete(resolveState); + return true; + } + return false; +} + +void +ResolveState_setExtra(ResolveState *resolveState, void *extra) { + resolveState->extra = extra; +} + +void * +ResolveState_getExtra(ResolveState *resolveState) { + return resolveState->extra; +} + +static void +doResolveCallback(ResolveState *resolveState) { + ResolveState_incRef(resolveState); + (*resolveState->callback)(resolveState, resolveState->result); + { + bool released = ResolveState_decRef(resolveState); + assert(released); + (void) released; // In case assert() evaluates to nothing. + } +} + +static void +doResolveErrorCallback(ResolveState *resolveState) { + ResolveState_incRef(resolveState); + resolveState->errorCallback(resolveState, &resolveState->error); + { + bool released = ResolveState_decRef(resolveState); + assert(released); + (void) released; // In case assert() evaluates to nothing. + } +} + +static void +resolveCallback(ResolveState *resolveState) { + resolveState->callbackID = CallbackID_invalid; + if (resolveState->error.gaiRes == 0) { + // Successful lookup. + doResolveCallback(resolveState); + } else { + // Lookup failed. + doResolveErrorCallback(resolveState); + } +} + +// Function that does getaddrinfo() and calls the callback function when +// the result is available. +// TODO: actually make this function asynchronous. Right now it just calls +// getaddrinfo() (which blocks) and then schedules the callback. +ResolveState * +getaddrinfoAsync(const char *node, const char *service, + const struct addrinfo *hints, ResolveFlags *flags, + ResolveCallback callback, ResolveErrorCallback errorCallback, + ResolveCallbackArg extra) { + ResolveState *resolveState; + + resolveState = ResolveState_new(); + resolveState->refCount = 1; +#ifdef DEBUG_RESOLVE_REF + log_add(log_Debug, "ResolveState %08" PRIxPTR ": ref=1 (%d)", + (uintptr_t) resolveState, resolveState->refCount); +#endif + resolveState->state = Resolve_resolving; + resolveState->flags = *flags; + resolveState->callback = callback; + resolveState->errorCallback = errorCallback; + resolveState->extra = extra; + resolveState->result = NULL; + + resolveState->error.gaiRes = + getaddrinfo(node, service, hints, &resolveState->result); + resolveState->error.err = errno; + + resolveState->callbackID = Callback_add( + (CallbackFunction) resolveCallback, (CallbackArg) resolveState); + + return resolveState; +} + +void +Resolve_close(ResolveState *resolveState) { + if (resolveState->callbackID != CallbackID_invalid) { + Callback_remove(resolveState->callbackID); + resolveState->callbackID = CallbackID_invalid; + } + resolveState->state = Resolve_closed; + ResolveState_decRef(resolveState); +} + +// Split an addrinfo list into two separate lists, one with structures with +// the specified value for the ai_family field, the other with the other +// structures. The order of entries in the resulting lists will remain +// the same as in the original list. +// info - the original list +// family - the family for the first list +// selected - pointer to where the list of selected structures should be +// stored +// selectedEnd - pointer to where the end of 'selected' should be stored +// rest - pointer to where the list of not-selected structures should be +// stored +// restEnd - pointer to where the end of 'rest' should be stored +// +// Yes, it is allowed to modify the ai_next field of addrinfo structures. +// Or at least, it's not disallowed by RFC 3493, and the following +// text from that RFC seems to suggest it should be possible: +// ] The freeaddrinfo() function must support the freeing of arbitrary +// ] sublists of an addrinfo list originally returned by getaddrinfo(). +void +splitAddrInfoOnFamily(struct addrinfo *info, int family, + struct addrinfo **selected, struct addrinfo ***selectedEnd, + struct addrinfo **rest, struct addrinfo ***restEnd) { + struct addrinfo *selectedFirst; + struct addrinfo **selectedNext; + struct addrinfo *restFirst; + struct addrinfo **restNext; + + selectedNext = &selectedFirst; + restNext = &restFirst; + while (info != NULL) { + if (info->ai_family == family) { + *selectedNext = info; + selectedNext = &(*selectedNext)->ai_next; + } else { + *restNext = info; + restNext = &(*restNext)->ai_next; + } + info = info->ai_next; + } + *selectedNext = NULL; + *restNext = NULL; + + // Fill in the result parameters. + *selected = selectedFirst; + *selectedEnd = selectedNext; + *rest = restFirst; + *restEnd = restNext; +} + + diff --git a/src/libs/network/connect/resolve.h b/src/libs/network/connect/resolve.h new file mode 100644 index 0000000..509c3b9 --- /dev/null +++ b/src/libs/network/connect/resolve.h @@ -0,0 +1,109 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_NETWORK_CONNECT_RESOLVE_H_ +#define LIBS_NETWORK_CONNECT_RESOLVE_H_ + + +typedef struct ResolveFlags ResolveFlags; +typedef struct ResolveError ResolveError; +typedef enum { + Resolve_closed, + Resolve_resolving +} ResolveStateState; +typedef struct ResolveState ResolveState; + +#include "port.h" +#include "../netport.h" + +// For addrinfo. +#ifdef USE_WINSOCK +// Not including because of possible conflicts with files +// including this file. +struct addrinfo; +#else +# include +#endif + +#include "libs/callback.h" +#include "../netmanager/netmanager.h" + + +struct ResolveFlags { + // Nothing yet. + + int dummy; // empty struct declarations are not allowed by C'99. +}; + +struct ResolveError { + int gaiRes; + int err; + // errno value. Only relevant if gaiRes == EAI_SYSTEM. +}; + +typedef void *ResolveCallbackArg; +typedef void (*ResolveCallback)(ResolveState *resolveState, + struct addrinfo *result); + // The receiver of the callback is owner of 'result' and + // should call freeaddrinfo(). +typedef void (*ResolveErrorCallback)(ResolveState *resolveState, + const ResolveError *error); + +#ifdef RESOLVE_INTERNAL +#ifdef USE_WINSOCK +# include +# include +# include "../wspiapiwrap.h" +#else /* !defined(USE_WINSOCK) */ +# include +#endif /* !defined(USE_WINSOCK) */ + +struct ResolveState { + RefCount refCount; + + ResolveStateState state; + ResolveFlags flags; + + ResolveCallback callback; + ResolveErrorCallback errorCallback; + void *extra; + + CallbackID callbackID; + ResolveError error; + struct addrinfo *result; +}; +#endif /* RESOLVE_INTERNAL */ + + +void ResolveState_incRef(ResolveState *resolveState); +bool ResolveState_decRef(ResolveState *resolveState); +void ResolveState_setExtra(ResolveState *resolveState, void *extra); +void *ResolveState_getExtra(ResolveState *resolveState); +ResolveState *getaddrinfoAsync(const char *node, const char *service, + const struct addrinfo *hints, ResolveFlags *flags, + ResolveCallback callback, ResolveErrorCallback errorCallback, + ResolveCallbackArg extra); +void Resolve_close(ResolveState *resolveState); + +void splitAddrInfoOnFamily(struct addrinfo *info, int family, + struct addrinfo **selected, struct addrinfo ***selectedEnd, + struct addrinfo **rest, struct addrinfo ***restEnd); + + +#endif /* LIBS_NETWORK_CONNECT_RESOLVE_H_ */ + diff --git a/src/libs/network/netmanager/Makeinfo b/src/libs/network/netmanager/Makeinfo new file mode 100644 index 0000000..ddf28b3 --- /dev/null +++ b/src/libs/network/netmanager/Makeinfo @@ -0,0 +1,11 @@ +uqm_CFILES="ndesc.c" +uqm_HFILES="ndesc.h netmanager.h" + +if [ -n "$uqm_USE_WINSOCK" ]; then + uqm_CFILES="$uqm_CFILES netmanager_win.c" + uqm_HFILES="$uqm_HFILES netmanager_win.h" +else + uqm_CFILES="$uqm_CFILES netmanager_bsd.c" + uqm_HFILES="$uqm_HFILES netmanager_bsd.h" +fi + diff --git a/src/libs/network/netmanager/ndesc.c b/src/libs/network/netmanager/ndesc.c new file mode 100644 index 0000000..e407ff9 --- /dev/null +++ b/src/libs/network/netmanager/ndesc.c @@ -0,0 +1,211 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 NETDESCRIPTOR_INTERNAL +#include "ndesc.h" + +#include "netmanager.h" +#include "libs/callback.h" + +#include +#include +#include + +#undef DEBUG_NETDESCRIPTOR_REF +#ifdef DEBUG_NETDESCRIPTOR_REF +# include "libs/log.h" +# include +#endif + + +static NetDescriptor * +NetDescriptor_alloc(void) { + return malloc(sizeof (NetDescriptor)); +} + +static void +NetDescriptor_free(NetDescriptor *nd) { + free(nd); +} + +// Sets the ref count to 1. +NetDescriptor * +NetDescriptor_new(Socket *socket, void *extra) { + NetDescriptor *nd; + + nd = NetDescriptor_alloc(); + nd->refCount = 1; +#ifdef DEBUG_NETDESCRIPTOR_REF + log_add(log_Debug, "NetDescriptor %08" PRIxPTR ": ref=1 (%d)", + (uintptr_t) nd, nd->refCount); +#endif + + nd->flags.closed = false; + nd->readCallback = NULL; + nd->writeCallback = NULL; + nd->exceptionCallback = NULL; + nd->closeCallback = NULL; + nd->socket = socket; + nd->smd = NULL; + nd->extra = extra; + + if (NetManager_addDesc(nd) == -1) { + int savedErrno = errno; + NetDescriptor_free(nd); + errno = savedErrno; + return NULL; + } + + return nd; +} + +static void +NetDescriptor_delete(NetDescriptor *nd) { + assert(nd->socket == Socket_noSocket); + assert(nd->smd == NULL); + + NetDescriptor_free(nd); +} + +// Called from the callback handler. +static void +NetDescriptor_closeCallback(NetDescriptor *nd) { + if (nd->closeCallback != NULL) { + // The check is necessary because the close callback may have + // been removed before it is triggered. + (*nd->closeCallback)(nd); + } + NetDescriptor_decRef(nd); +} + +void +NetDescriptor_close(NetDescriptor *nd) { + assert(!nd->flags.closed); + assert(nd->socket != Socket_noSocket); + + NetManager_removeDesc(nd); + (void) Socket_close(nd->socket); + nd->socket = Socket_noSocket; + nd->flags.closed = true; + if (nd->closeCallback != NULL) { + // Keep one reference around until the close callback has been + // called. + (void) Callback_add( + (CallbackFunction) NetDescriptor_closeCallback, + (CallbackArg) nd); + } else + NetDescriptor_decRef(nd); +} + +void +NetDescriptor_incRef(NetDescriptor *nd) { + assert(nd->refCount < REFCOUNT_MAX); + nd->refCount++; +#ifdef DEBUG_NETDESCRIPTOR_REF + log_add(log_Debug, "NetDescriptor %08" PRIxPTR ": ref++ (%d)", + (uintptr_t) nd, nd->refCount); +#endif +} + +// returns true iff the ref counter has reached 0. +bool +NetDescriptor_decRef(NetDescriptor *nd) { + assert(nd->refCount > 0); + nd->refCount--; +#ifdef DEBUG_NETDESCRIPTOR_REF + log_add(log_Debug, "NetDescriptor %08" PRIxPTR ": ref-- (%d)", + (uintptr_t) nd, nd->refCount); +#endif + if (nd->refCount == 0) { + NetDescriptor_delete(nd); + return true; + } + return false; +} + +// The socket will no longer be managed by the NetManager. +void +NetDescriptor_detach(NetDescriptor *nd) { + NetManager_removeDesc(nd); + nd->socket = Socket_noSocket; + nd->flags.closed = true; + NetDescriptor_decRef(nd); +} + +Socket * +NetDescriptor_getSocket(NetDescriptor *nd) { + return nd->socket; +} + +void +NetDescriptor_setExtra(NetDescriptor *nd, void *extra) { + nd->extra = extra; +} + +void * +NetDescriptor_getExtra(const NetDescriptor *nd) { + return nd->extra; +} + +void +NetDescriptor_setReadCallback(NetDescriptor *nd, + NetDescriptor_ReadCallback callback) { + nd->readCallback = callback; + if (!nd->flags.closed) { + if (nd->readCallback != NULL) { + NetManager_activateReadCallback(nd); + } else + NetManager_deactivateReadCallback(nd); + } +} + +void +NetDescriptor_setWriteCallback(NetDescriptor *nd, + NetDescriptor_WriteCallback callback) { + nd->writeCallback = callback; + if (!nd->flags.closed) { + if (nd->writeCallback != NULL) { + NetManager_activateWriteCallback(nd); + } else + NetManager_deactivateWriteCallback(nd); + } +} + +void +NetDescriptor_setExceptionCallback(NetDescriptor *nd, + NetDescriptor_ExceptionCallback callback) { + nd->exceptionCallback = callback; + if (!nd->flags.closed) { + if (nd->exceptionCallback != NULL) { + NetManager_activateExceptionCallback(nd); + } else + NetManager_deactivateExceptionCallback(nd); + } +} + +// The close callback is called as a result of a socket being closed, either +// because of a local command or a remote disconnect. +// The close callback will only be scheduled when this happens. The +// callback will not be called until the Callback_process() is called. +void +NetDescriptor_setCloseCallback(NetDescriptor *nd, + NetDescriptor_CloseCallback callback) { + nd->closeCallback = callback; +} + + diff --git a/src/libs/network/netmanager/ndesc.h b/src/libs/network/netmanager/ndesc.h new file mode 100644 index 0000000..8834db9 --- /dev/null +++ b/src/libs/network/netmanager/ndesc.h @@ -0,0 +1,82 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_NETWORK_NETMANAGER_NDESC_H_ +#define LIBS_NETWORK_NETMANAGER_NDESC_H_ + +#include "types.h" + + +typedef struct NetDescriptor NetDescriptor; + +typedef void (*NetDescriptor_ReadCallback)(NetDescriptor *nd); +typedef void (*NetDescriptor_WriteCallback)(NetDescriptor *nd); +typedef void (*NetDescriptor_ExceptionCallback)(NetDescriptor *nd); +typedef void (*NetDescriptor_CloseCallback)(NetDescriptor *nd); + +typedef uint32 RefCount; +#define REFCOUNT_MAX UINT32_MAX + +#include "../socket/socket.h" +#include "netmanager.h" + +#ifdef NETDESCRIPTOR_INTERNAL +// All created NetDescriptors are registered to the NetManager. +// They are unregisted when the NetDescriptor is closed. +// On creation the ref count is set to 1. On close it is decremented by 1. +struct NetDescriptor { + struct { + bool closed: 1; + } flags; + + RefCount refCount; + + NetDescriptor_ReadCallback readCallback; + NetDescriptor_WriteCallback writeCallback; + NetDescriptor_ExceptionCallback exceptionCallback; + NetDescriptor_CloseCallback closeCallback; + + Socket *socket; + SocketManagementData *smd; + + // Extra state-dependant information for the user. + void *extra; +}; +#endif + +NetDescriptor *NetDescriptor_new(Socket *socket, void *extra); +void NetDescriptor_close(NetDescriptor *nd); +void NetDescriptor_incRef(NetDescriptor *nd); +bool NetDescriptor_decRef(NetDescriptor *nd); +void NetDescriptor_detach(NetDescriptor *nd); +Socket *NetDescriptor_getSocket(NetDescriptor *nd); +void NetDescriptor_setExtra(NetDescriptor *nd, void *extra); +void *NetDescriptor_getExtra(const NetDescriptor *nd); +void NetDescriptor_setReadCallback(NetDescriptor *nd, + NetDescriptor_ReadCallback callback); +void NetDescriptor_setWriteCallback(NetDescriptor *nd, + NetDescriptor_WriteCallback callback); +void NetDescriptor_setExceptionCallback(NetDescriptor *nd, + NetDescriptor_ExceptionCallback callback); +void NetDescriptor_setCloseCallback(NetDescriptor *nd, + NetDescriptor_CloseCallback callback); + + +#endif /* LIBS_NETWORK_NETMANAGER_NDESC_H_ */ + + diff --git a/src/libs/network/netmanager/ndindex.ci b/src/libs/network/netmanager/ndindex.ci new file mode 100644 index 0000000..f922d8b --- /dev/null +++ b/src/libs/network/netmanager/ndindex.ci @@ -0,0 +1,103 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 netmanager_bsd.c, from where it is #included. +// Only used for BSD sockets. + +// This file provides a mapping of Sockets to NetDescriptors. + + +static NetDescriptor *netDescriptors[FD_SETSIZE]; + // INV: flags.closed is not set for entries in netDescriptors. +static size_t maxND; + // One past the largest used ND in netDescriptors, as used in + // the first argument of select(); + + +static inline void +NDIndex_init(void) { + size_t i; + size_t numND = sizeof (netDescriptors) / sizeof (netDescriptors[0]); + + for (i = 0; i < numND; i++) + netDescriptors[i] = NULL; +} + +static inline void +NDIndex_uninit(void) { + // Nothing to do. +} + +static inline int +NDIndex_registerNDWithSocket(Socket *sock, NetDescriptor *nd) { + if ((unsigned int) sock->fd >= FD_SETSIZE) { + errno = EMFILE; + return -1; + } + + netDescriptors[sock->fd] = nd; + + if ((size_t) sock->fd >= maxND) + maxND = (size_t) sock->fd + 1; + + return 0; +} + +static inline void +NDIndex_unregisterNDForSocket(Socket *sock) { + NetDescriptor **last; + + netDescriptors[sock->fd] = NULL; + + last = &netDescriptors[sock->fd]; + + if ((size_t) sock->fd + 1 == maxND) { + do { + maxND--; + if (last == &netDescriptors[0]) + break; + last--; + } while (*last == NULL); + } +} + +static inline NetDescriptor * +NDIndex_getNDForSocket(Socket *sock) { + assert((size_t) sock->fd < maxND); + return netDescriptors[sock->fd]; +} + +static inline NetDescriptor * +NDIndex_getNDForSocketFd(int fd) { + assert((size_t) fd < maxND); + return netDescriptors[fd]; +} + +static inline bool +NDIndex_socketRegistered(Socket *sock) { + return ((size_t) sock->fd < maxND) + && (NDIndex_getNDForSocket(sock) != NULL); +} + +// Get the first argument to be used in select(). +static inline size_t +NDIndex_getSelectNumND(void) { + return maxND; +} + + diff --git a/src/libs/network/netmanager/netmanager.h b/src/libs/network/netmanager/netmanager.h new file mode 100644 index 0000000..42be572 --- /dev/null +++ b/src/libs/network/netmanager/netmanager.h @@ -0,0 +1,48 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_NETWORK_NETMANAGER_NETMANAGER_H_ +#define LIBS_NETWORK_NETMANAGER_NETMANAGER_H_ + +#include "port.h" +#include "types.h" + +#ifdef USE_WINSOCK +# include "netmanager_win.h" +#else +# include "netmanager_bsd.h" +#endif + +#include "ndesc.h" + +void NetManager_init(void); +void NetManager_uninit(void); +int NetManager_process(uint32 *timeoutMs); + +// Only for internal use by the NetManager: +int NetManager_addDesc(NetDescriptor *nd); +void NetManager_removeDesc(NetDescriptor *nd); +void NetManager_activateReadCallback(NetDescriptor *nd); +void NetManager_deactivateReadCallback(NetDescriptor *nd); +void NetManager_activateWriteCallback(NetDescriptor *nd); +void NetManager_deactivateWriteCallback(NetDescriptor *nd); +void NetManager_activateExceptionCallback(NetDescriptor *nd); +void NetManager_deactivateExceptionCallback(NetDescriptor *nd); + +#endif /* LIBS_NETWORK_NETMANAGER_NETMANAGER_H_ */ + diff --git a/src/libs/network/netmanager/netmanager_bsd.c b/src/libs/network/netmanager/netmanager_bsd.c new file mode 100644 index 0000000..29159f8 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_bsd.c @@ -0,0 +1,223 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 SOCKET_INTERNAL +#define NETDESCRIPTOR_INTERNAL +#include "netmanager_bsd.h" +#include "ndesc.h" +#include "../socket/socket.h" + +#include "ndesc.h" +#include "types.h" +#include "libs/log.h" + +#include +#include +#include +#include + +#include "netmanager_common.ci" +#include "ndindex.ci" + + +// INV: The following sets only contain sockets present in the netDescriptor +// array. +static fd_set readSet; +static fd_set writeSet; +static fd_set exceptionSet; + + +void +NetManager_init(void) { + NDIndex_init(); + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_ZERO(&exceptionSet); +} + +void +NetManager_uninit(void) { + NDIndex_uninit(); +} + +// Register the NetDescriptor with the NetManager. +int +NetManager_addDesc(NetDescriptor *nd) { + int fd; + assert(nd->socket != Socket_noSocket); + assert(!NDIndex_socketRegistered(nd->socket)); + + if (NDIndex_registerNDWithSocket(nd->socket, nd) == -1) { + // errno is set + return -1; + } + + fd = nd->socket->fd; + if (nd->readCallback != NULL) + FD_SET(fd, &readSet); + if (nd->writeCallback != NULL) + FD_SET(fd, &writeSet); + if (nd->exceptionCallback != NULL) + FD_SET(fd, &exceptionSet); + return 0; +} + +void +NetManager_removeDesc(NetDescriptor *nd) { + int fd; + + assert(nd->socket != Socket_noSocket); + assert(NDIndex_getNDForSocket(nd->socket) == nd); + + fd = nd->socket->fd; + FD_CLR(fd, &readSet); + FD_CLR(fd, &writeSet); + FD_CLR(fd, &exceptionSet); + + NDIndex_unregisterNDForSocket(nd->socket); +} + +void +NetManager_activateReadCallback(NetDescriptor *nd) { + FD_SET(nd->socket->fd, &readSet); +} + +void +NetManager_deactivateReadCallback(NetDescriptor *nd) { + FD_CLR(nd->socket->fd, &readSet); +} + +void +NetManager_activateWriteCallback(NetDescriptor *nd) { + FD_SET(nd->socket->fd, &writeSet); +} + +void +NetManager_deactivateWriteCallback(NetDescriptor *nd) { + FD_CLR(nd->socket->fd, &writeSet); +} + +void +NetManager_activateExceptionCallback(NetDescriptor *nd) { + FD_SET(nd->socket->fd, &exceptionSet); +} + +void +NetManager_deactivateExceptionCallback(NetDescriptor *nd) { + FD_CLR(nd->socket->fd, &exceptionSet); +} + +// This function may be called again from inside a callback function +// triggered by this function. BUG: This may result in callbacks being +// called multiple times. +// This function should however not be called from multiple threads at once. +int +NetManager_process(uint32 *timeoutMs) { + struct timeval timeout; + size_t i; + int selectResult; + fd_set newReadSet; + fd_set newWriteSet; + fd_set newExceptionSet; + bool bitSet; + + timeout.tv_sec = *timeoutMs / 1000; + timeout.tv_usec = (*timeoutMs % 1000) * 1000; + + // Structure assignment: + newReadSet = readSet; + newWriteSet = writeSet; + newExceptionSet = exceptionSet; + + do { + selectResult = select(NDIndex_getSelectNumND(), + &newReadSet, &newWriteSet, &newExceptionSet, &timeout); + // BUG: If select() is restarted because of EINTR, the timeout + // may start over. (Linux changes 'timeout' to the time left, + // but most other platforms don't.) + } while (selectResult == -1 && errno == EINTR); + if (selectResult == -1) { + int savedErrno = errno; + log_add(log_Error, "select() failed: %s.", strerror(errno)); + errno = savedErrno; + *timeoutMs = (timeout.tv_sec * 1000) + (timeout.tv_usec / 1000); + // XXX: rounding microseconds down. Is that the correct + // thing to do? + return -1; + } + + for (i = 0; i < maxND; i++) { + NetDescriptor *nd; + + if (selectResult == 0) { + // No more bits set in the fd_sets + break; + } + + nd = NDIndex_getNDForSocketFd(i); + if (nd == NULL) + continue; + + bitSet = false; + // Is one of the bits in the fd_sets set? + + // A callback may cause a NetDescriptor to be closed. The deletion + // of the structure will be scheduled, but will still be + // available at least until this function returns. + + if (FD_ISSET(i, &newExceptionSet)) + { + bool closed; + bitSet = true; + closed = NetManager_doExceptionCallback(nd); + if (closed) + goto next; + } + + if (FD_ISSET(i, &newWriteSet)) + { + bool closed; + bitSet = true; + closed = NetManager_doWriteCallback(nd); + if (closed) + goto next; + } + + if (FD_ISSET(i, &newReadSet)) + { + bool closed; + bitSet = true; + closed = NetManager_doReadCallback(nd); + if (closed) + goto next; + } + +next: + if (bitSet) + selectResult--; + } + + *timeoutMs = (timeout.tv_sec * 1000) + (timeout.tv_usec / 1000); + // XXX: rounding microseconds down. Is that the correct + // thing to do? + return 0; +} + + + diff --git a/src/libs/network/netmanager/netmanager_bsd.h b/src/libs/network/netmanager/netmanager_bsd.h new file mode 100644 index 0000000..b94dd69 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_bsd.h @@ -0,0 +1,26 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_NETWORK_NETMANAGER_NETMANAGER_BSD_H_ +#define LIBS_NETWORK_NETMANAGER_NETMANAGER_BSD_H_ + +typedef struct SocketManagementDataBsd SocketManagementDataBsd; +typedef SocketManagementDataBsd SocketManagementData; + +#endif /* LIBS_NETWORK_NETMANAGER_NETMANAGER_BSD_H_ */ + diff --git a/src/libs/network/netmanager/netmanager_common.ci b/src/libs/network/netmanager/netmanager_common.ci new file mode 100644 index 0000000..058e739 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_common.ci @@ -0,0 +1,58 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 netmanager_bsd.ci and netmanager_win.ci, +// from where it is #included. + +static bool +NetManager_doReadCallback(NetDescriptor *nd) { + bool closed; + + assert(nd->readCallback != NULL); + NetDescriptor_incRef(nd); + (*nd->readCallback)(nd); + closed = nd->flags.closed; + NetDescriptor_decRef(nd); + return closed; +} + +static bool +NetManager_doWriteCallback(NetDescriptor *nd) { + bool closed; + + assert(nd->writeCallback != NULL); + NetDescriptor_incRef(nd); + (*nd->writeCallback)(nd); + closed = nd->flags.closed; + NetDescriptor_decRef(nd); + return closed; +} + +static bool +NetManager_doExceptionCallback(NetDescriptor *nd) { + bool closed; + + assert(nd->exceptionCallback != NULL); + NetDescriptor_incRef(nd); + (*nd->exceptionCallback)(nd); + closed = nd->flags.closed; + NetDescriptor_decRef(nd); + return closed; +} + + diff --git a/src/libs/network/netmanager/netmanager_win.c b/src/libs/network/netmanager/netmanager_win.c new file mode 100644 index 0000000..f146732 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_win.c @@ -0,0 +1,464 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "../netport.h" + +#define NETMANAGER_INTERNAL +#define NETDESCRIPTOR_INTERNAL +#define SOCKET_INTERNAL +#include "netmanager_win.h" +#include "../socket/socket.h" + +#include "ndesc.h" +#include "types.h" +#include "libs/misc.h" +#include "libs/log.h" + +#include +#include + +#include "netmanager_common.ci" + +int closeWSAEvent(WSAEVENT event); + + +// The elements of the following arrays with the same index belong to +// eachother. +#define MAX_SOCKETS WSA_MAXIMUM_WAIT_EVENTS + // We cannot have more sockets than we can have events, as + // all may need an event at some point. +static NetDescriptor *netDescriptors[MAX_SOCKETS]; +static WSAEVENT events[WSA_MAXIMUM_WAIT_EVENTS]; +static size_t numSockets; +static size_t numActiveEvents; +// For each NetDescriptor registered with the NetManager, an event +// is created. Only the first numActiveEvents will be processed though. +// Inv: numActiveEvents <= numSockets +// Inv: for all i: 0 <= i < numActiveEvents: events[i] is active +// (events[i] being active also means netDescriptor[i]->smd->eventMask +// != 0) +// Inv: for all i: 0 <= i < numSockets: netDescriptor[i]->smd->index == i + +void +NetManager_init(void) { + numActiveEvents = 0; +} + +void +NetManager_uninit(void) { + assert(numActiveEvents == 0); +} + +static inline SocketManagementDataWin * +SocketManagementData_alloc(void) { + return malloc(sizeof (SocketManagementDataWin)); +} + +static inline void +SocketManagementData_free(SocketManagementDataWin *smd) { + free(smd); +} + +// XXX: This function should be moved to some file with generic network +// functions. +int +closeWSAEvent(WSAEVENT event) { + for (;;) { + int error; + + if (WSACloseEvent(event)) + break; + + error = WSAGetLastError(); + if (error != WSAEINPROGRESS) { + log_add(log_Error, + "WSACloseEvent() failed with error code %d.", error); + errno = winsockErrorToErrno(error); + return -1; + } + } + return 0; +} + +// Register the NetDescriptor with the NetManager. +int +NetManager_addDesc(NetDescriptor *nd) { + long eventMask = 0; + WSAEVENT event; + + if (numSockets >= WSA_MAXIMUM_WAIT_EVENTS) { + errno = EMFILE; + return -1; + } + + if (nd->readCallback != NULL) + eventMask |= FD_READ | FD_ACCEPT; + + if (nd->writeCallback != NULL) + eventMask |= FD_WRITE /* | FD_CONNECT */; + + if (nd->exceptionCallback != NULL) + eventMask |= FD_OOB; + + eventMask |= FD_CLOSE; + + event = WSACreateEvent(); + if (event == WSA_INVALID_EVENT) { + errno = getWinsockErrno(); + return -1; + } + + nd->smd = SocketManagementData_alloc(); + if (eventMask != 0) { + // XXX: This guard is now always true, because of FD_CLOSE. + // This means that numActiveEvents will always be equal to + // numEvents. + // Once I'm convinced this is the right way to go, + // I can remove some unnecessary code. + if (WSAEventSelect(nd->socket->sock, event, eventMask) == + SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + int closeStatus = closeWSAEvent(event); + if (closeStatus == -1) { + log_add(log_Fatal, "closeWSAEvent() failed: %s.", + strerror(errno)); + explode(); + } + SocketManagementData_free(nd->smd); + errno = savedErrno; + return -1; + } + + // Move existing socket for which there exists no event, so + // so that all sockets for which there exists an event are at + // the front of the array of netdescriptors. + if (numActiveEvents < numSockets) { + netDescriptors[numSockets] = netDescriptors[numActiveEvents]; + netDescriptors[numSockets]->smd->index = numSockets; + } + + nd->smd->index = numActiveEvents; + numActiveEvents++; + } else { + nd->smd->index = numSockets; + } + nd->smd->eventMask = eventMask; + + netDescriptors[nd->smd->index] = nd; + events[nd->smd->index] = event; + numSockets++; + + return 0; +} + +void +NetManager_removeDesc(NetDescriptor *nd) { + assert(nd->smd != NULL); + assert(nd->smd->index < numSockets); + assert(nd == netDescriptors[nd->smd->index]); + + { + int closeStatus = closeWSAEvent(events[nd->smd->index]); + if (closeStatus == -1) + explode(); + } + + if (nd->smd->index < numActiveEvents) { + size_t index = nd->smd->index; + if (index + 1 != numActiveEvents) { + // Keep the list of active events consecutive by filling + // the new hole with the last active event. + events[index] = events[numActiveEvents - 1]; + netDescriptors[index] = netDescriptors[numActiveEvents - 1]; + netDescriptors[index]->smd->index = index; + } + numActiveEvents--; + } + + SocketManagementData_free(nd->smd); + nd->smd = NULL; + + numSockets--; +} + +static void +swapSockets(int index1, int index2) { + NetDescriptor *tempNd; + WSAEVENT tempEvent; + + tempNd = netDescriptors[index2]; + tempEvent = events[index2]; + + netDescriptors[index2] = netDescriptors[index1]; + events[index2] = events[index1]; + netDescriptors[index2]->smd->index = index2; + + netDescriptors[index1] = tempNd; + events[index1] = tempEvent; + netDescriptors[index1]->smd->index = index1; +} + +static int +NetManager_updateEvent(NetDescriptor *nd) { + assert(nd == netDescriptors[nd->smd->index]); + + if (WSAEventSelect(nd->socket->sock, + events[nd->smd->index], nd->smd->eventMask) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + int closeStatus = closeWSAEvent(events[nd->smd->index]); + if (closeStatus == -1) { + log_add(log_Fatal, "closeWSAEvent() failed: %s.", + strerror(errno)); + explode(); + } + errno = savedErrno; + return -1; + } + + if (nd->smd->eventMask != 0) { + // There are some events that we are interested in. + if (nd->smd->index >= numActiveEvents) { + // Event was not yet active. + if (nd->smd->index != numActiveEvents) { + // Need to keep the active nds and events in the front of + // their arrays. + swapSockets(nd->smd->index, numActiveEvents); + } + numActiveEvents++; + } + } else { + // There are no events that we are interested in. + if (nd->smd->index < numActiveEvents) { + // Event was active. + if (nd->smd->index != numActiveEvents - 1) { + // Need to keep the active nds and events in the front of + // their arrays. + swapSockets(nd->smd->index, numActiveEvents - 1); + } + } + numActiveEvents--; + } + + return 0; +} + +static void +activateSomeCallback(NetDescriptor *nd, long eventMask) { + nd->smd->eventMask |= eventMask; + { + int status = NetManager_updateEvent(nd); + if (status == -1) { + log_add(log_Fatal, "NetManager_updateEvent() failed: %s.", + strerror(errno)); + explode(); + // TODO: better error handling. + } + } +} + +static void +deactivateSomeCallback(NetDescriptor *nd, long eventMask) { + nd->smd->eventMask &= ~eventMask; + { + int status = NetManager_updateEvent(nd); + if (status == -1) { + log_add(log_Fatal, "NetManager_updateEvent() failed: %s.", + strerror(errno)); + explode(); + // TODO: better error handling + } + } +} + +void +NetManager_activateReadCallback(NetDescriptor *nd) { + activateSomeCallback(nd, FD_READ | FD_ACCEPT); +} + +void +NetManager_deactivateReadCallback(NetDescriptor *nd) { + deactivateSomeCallback(nd, FD_READ | FD_ACCEPT); +} + +void +NetManager_activateWriteCallback(NetDescriptor *nd) { + activateSomeCallback(nd, FD_WRITE /* | FD_CONNECT */); +} + +void +NetManager_deactivateWriteCallback(NetDescriptor *nd) { + deactivateSomeCallback(nd, FD_WRITE /* | FD_CONNECT */); +} + +void +NetManager_activateExceptionCallback(NetDescriptor *nd) { + activateSomeCallback(nd, FD_OOB); +} + +void +NetManager_deactivateExceptionCallback(NetDescriptor *nd) { + deactivateSomeCallback(nd, FD_OOB); +} + +static inline int +NetManager_processEvent(size_t index) { + WSANETWORKEVENTS networkEvents; + int enumRes; + + enumRes = WSAEnumNetworkEvents(netDescriptors[index]->socket->sock, + events[index], &networkEvents); + if (enumRes == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + if (networkEvents.lNetworkEvents & FD_READ) { + bool closed; + if (networkEvents.iErrorCode[FD_READ_BIT] != 0) { + // No special handling is required; the callback function + // will try to do a recv() and will get the error then. + } + + closed = NetManager_doReadCallback(netDescriptors[index]); + if (closed) + goto closed; + } + if (networkEvents.lNetworkEvents & FD_WRITE) { + bool closed; + if (networkEvents.iErrorCode[FD_WRITE_BIT] != 0) { + // No special handling is required; the callback function + // will try to do a send() and will get the error then. + } + + closed = NetManager_doWriteCallback(netDescriptors[index]); + if (closed) + goto closed; + } + if (networkEvents.lNetworkEvents & FD_OOB) { + bool closed; + if (networkEvents.iErrorCode[FD_OOB_BIT] != 0) { + // No special handling is required; the callback function + // will get the error then when it tries to do a recv(). + } + + closed = NetManager_doExceptionCallback(netDescriptors[index]); + if (closed) + goto closed; + } + if (networkEvents.lNetworkEvents & FD_ACCEPT) { + // There is no specific accept callback (because the BSD sockets + // don't work with specific notification for accept); we use + // the read callback instead. + bool closed; + if (networkEvents.iErrorCode[FD_READ_BIT] != 0) { + // No special handling is required; the callback function + // will try to do an accept() and will get the error then. + } + + closed = NetManager_doReadCallback(netDescriptors[index]); + if (closed) + goto closed; + } +#if 0 + // No need for this. Windows also sets FD_WRITE in this case, and + // writability is what we check for anyhow. + if (networkEvents.lNetworkEvents & FD_CONNECT) { + // There is no specific connect callback (because the BSD sockets + // don't work with specific notification for connect); we use + // the write callback instead. + bool closed; + if (networkEvents.iErrorCode[FD_WRITE_BIT] != 0) { + // No special handling is required; the callback function + // should do getsockopt() with SO_ERROR to get the error. + } + + closed = NetManager_doWriteCallback(netDescriptors[index]); + if (closed) + goto closed; + } +#endif + if (networkEvents.lNetworkEvents & FD_CLOSE) { + // The close event is handled last, in case there was still + // data in the buffers which could be processed. + NetDescriptor_close(netDescriptors[index]); + goto closed; + } + +closed: /* No special actions required for now. */ + + return 0; +} + +// This function may be called again from inside a callback function +// triggered by this function. BUG: This may result in callbacks being +// called multiple times. +// This function should however not be called from multiple threads at once. +int +NetManager_process(uint32 *timeoutMs) { + DWORD timeoutTemp; + DWORD waitResult; + DWORD startEvent; + + timeoutTemp = (DWORD) *timeoutMs; + + // WSAWaitForMultipleEvents only reports events for one socket at a + // time. In order to have each socket checked once, we call it + // again after it has reported an event, but passing only the + // events not yet processed. The second time, the timeout will be set + // to 0, so it won't wait. + startEvent = 0; + while (startEvent < numActiveEvents) { + waitResult = WSAWaitForMultipleEvents(numActiveEvents - startEvent, + &events[startEvent], FALSE, timeoutTemp, FALSE); + + if (waitResult == WSA_WAIT_IO_COMPLETION) + continue; + + if (waitResult == WSA_WAIT_TIMEOUT) { + // No events waiting. + *timeoutMs = 0; + return 0; + } + + if (waitResult == WSA_WAIT_FAILED) { + errno = getWinsockErrno(); + *timeoutMs = timeoutTemp; + return -1; + } + + { + DWORD eventIndex = waitResult - WSA_WAIT_EVENT_0; + if (NetManager_processEvent((size_t) eventIndex) == -1) { + // errno is set + *timeoutMs = timeoutTemp; + return -1; + } + + // Check the rest of the sockets, but don't wait anymore. + startEvent += eventIndex + 1; + timeoutTemp = 0; + } + } + + *timeoutMs = timeoutTemp; + return 0; +} + + diff --git a/src/libs/network/netmanager/netmanager_win.h b/src/libs/network/netmanager/netmanager_win.h new file mode 100644 index 0000000..03d65e4 --- /dev/null +++ b/src/libs/network/netmanager/netmanager_win.h @@ -0,0 +1,35 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_NETWORK_NETMANAGER_NETMANAGER_WIN_H_ +#define LIBS_NETWORK_NETMANAGER_NETMANAGER_WIN_H_ + +typedef struct SocketManagementDataWin SocketManagementDataWin; +typedef SocketManagementDataWin SocketManagementData; + +#ifdef NETMANAGER_INTERNAL +struct SocketManagementDataWin { + size_t index; + long eventMask; +}; +#endif /* NETMANAGER_INTERNAL */ + + +#endif /* LIBS_NETWORK_NETMANAGER_NETMANAGER_WIN_H_ */ + + diff --git a/src/libs/network/netport.c b/src/libs/network/netport.c new file mode 100644 index 0000000..bfd478c --- /dev/null +++ b/src/libs/network/netport.c @@ -0,0 +1,91 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "netport.h" + +#ifdef USE_WINSOCK +# include + +int +winsockErrorToErrno(int winsockError) { + switch (winsockError) { + case WSAEINTR: return EINTR; + case WSAEACCES: return EACCES; + case WSAEFAULT: return EFAULT; + case WSAEINVAL: return EINVAL; + case WSAEMFILE: return EMFILE; + case WSAEWOULDBLOCK: return EWOULDBLOCK; + case WSAEINPROGRESS: return EINPROGRESS; + case WSAEALREADY: return EALREADY; + case WSAENOTSOCK: return ENOTSOCK; + case WSAEDESTADDRREQ: return EDESTADDRREQ; + case WSAEMSGSIZE: return EMSGSIZE; + case WSAEPROTOTYPE: return EPROTOTYPE; + case WSAENOPROTOOPT: return ENOPROTOOPT; + case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT; + case WSAESOCKTNOSUPPORT: return ESOCKTNOSUPPORT; + case WSAEOPNOTSUPP: return EOPNOTSUPP; + case WSAEPFNOSUPPORT: return EPFNOSUPPORT; + case WSAEAFNOSUPPORT: return EAFNOSUPPORT; + case WSAEADDRINUSE: return EADDRINUSE; + case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL; + case WSAENETDOWN: return ENETDOWN; + case WSAENETUNREACH: return ENETUNREACH; + case WSAENETRESET: return ENETRESET; + case WSAECONNABORTED: return ECONNABORTED; + case WSAECONNRESET: return ECONNRESET; + case WSAENOBUFS: return ENOBUFS; + case WSAEISCONN: return EISCONN; + case WSAENOTCONN: return ENOTCONN; + case WSAESHUTDOWN: return ESHUTDOWN; + case WSAETIMEDOUT: return ETIMEDOUT; + case WSAECONNREFUSED: return ECONNREFUSED; + case WSAEHOSTDOWN: return EHOSTDOWN; + case WSAEHOSTUNREACH: return EHOSTUNREACH; + case WSAEPROCLIM: return EPROCLIM; + case WSASYSNOTREADY: return ENOSYS; + case WSAVERNOTSUPPORTED: return ENOSYS; + case WSANOTINITIALISED: return ENOSYS; + case WSAEDISCON: return ECONNRESET; + case WSATYPE_NOT_FOUND: return ENODATA; + case WSAHOST_NOT_FOUND: return ENODATA; + case WSATRY_AGAIN: return EAGAIN; + case WSANO_RECOVERY: return EIO; + case WSANO_DATA: return ENODATA; + case WSA_INVALID_HANDLE: return EBADF; + case WSA_INVALID_PARAMETER: return EINVAL; + case WSA_IO_INCOMPLETE: return EAGAIN; + case WSA_IO_PENDING: return EINPROGRESS; + case WSA_NOT_ENOUGH_MEMORY: return ENOMEM; + case WSA_OPERATION_ABORTED: return EINTR; + case WSAEINVALIDPROCTABLE: return ENOSYS; + case WSAEINVALIDPROVIDER: return ENOSYS; + case WSAEPROVIDERFAILEDINIT: return ENOSYS; + case WSASYSCALLFAILURE: return EIO; + default: return EIO; + } +} + +int +getWinsockErrno(void) { + return winsockErrorToErrno(WSAGetLastError()); +} +#endif /* defined (USE_WINSOCK) */ + diff --git a/src/libs/network/netport.h b/src/libs/network/netport.h new file mode 100644 index 0000000..ee99699 --- /dev/null +++ b/src/libs/network/netport.h @@ -0,0 +1,43 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_NETWORK_NETPORT_H_ +#define LIBS_NETWORK_NETPORT_H_ + +#include "port.h" + +#ifdef USE_WINSOCK +int winsockErrorToErrno(int winsockError); +int getWinsockErrno(void); +# define EAI_SYSTEM 0x02000001 + // Any value will do that doesn't conflict with an existing value. + +#ifdef __MINGW32__ +// MinGW does not have a working gai_strerror() yet. +static inline const char * +gai_strerror(int err) { + (void) err; + return "[gai_strerror() is not available on MinGW]"; +} +#endif /* defined(__MINGW32__) */ + +#endif + +#endif /* LIBS_NETWORK_NETPORT_H_ */ + + diff --git a/src/libs/network/network.h b/src/libs/network/network.h new file mode 100644 index 0000000..d8c596e --- /dev/null +++ b/src/libs/network/network.h @@ -0,0 +1,27 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_NETWORK_NETWORK_H_ +#define LIBS_NETWORK_NETWORK_H_ + +void Network_init(void); +void Network_uninit(void); + +#endif /* LIBS_NETWORK_NETWORK_H_ */ + + diff --git a/src/libs/network/network_bsd.c b/src/libs/network/network_bsd.c new file mode 100644 index 0000000..a213c4e --- /dev/null +++ b/src/libs/network/network_bsd.c @@ -0,0 +1,30 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "network.h" + +void +Network_init(void) { + // Nothing to do. +} + +void +Network_uninit(void) { + // Nothing to do. +} + diff --git a/src/libs/network/network_win.c b/src/libs/network/network_win.c new file mode 100644 index 0000000..a5c6abf --- /dev/null +++ b/src/libs/network/network_win.c @@ -0,0 +1,75 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "netport.h" + +#include "network.h" + +#include "libs/misc.h" +#include "libs/log.h" + +#include +#include + +void +Network_init(void) { + WSADATA data; + int startupResult; + WORD requestVersion = MAKEWORD(2, 2); + + startupResult = WSAStartup(requestVersion, &data); + if (startupResult != 0) { + int savedErrno = winsockErrorToErrno(startupResult); + log_add(log_Fatal, "WSAStartup failed."); + errno = savedErrno; + explode(); + } + +#ifdef DEBUG + log_add(log_Debug, "Winsock version %d.%d found: \"%s\".", + LOBYTE(data.wHighVersion), HIBYTE(data.wHighVersion), + data.szDescription); + log_add(log_Debug, "Requesting to use Winsock version %d.%d, got " + "version %d.%d.", + LOBYTE(requestVersion), HIBYTE(requestVersion), + LOBYTE(data.wVersion), HIBYTE(data.wVersion)); +#endif + if (data.wVersion != requestVersion) { + log_add(log_Fatal, "Winsock version %d.%d presented, requested " + "%d.%d.", LOBYTE(data.wVersion), HIBYTE(data.wVersion), + LOBYTE(requestVersion), HIBYTE(requestVersion)); + (void) WSACleanup(); + // Ignoring errors; we're going to abort anyhow. + explode(); + } +} + +void +Network_uninit(void) { + int cleanupResult; + + cleanupResult = WSACleanup(); + if (cleanupResult == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Fatal, "WSACleanup failed."); + errno = savedErrno; + explode(); + } +} + + diff --git a/src/libs/network/socket/Makeinfo b/src/libs/network/socket/Makeinfo new file mode 100644 index 0000000..19e1637 --- /dev/null +++ b/src/libs/network/socket/Makeinfo @@ -0,0 +1,11 @@ +uqm_CFILES="socket.c" +uqm_HFILES="socket.h" + +if [ -n "$uqm_USE_WINSOCK" ]; then + uqm_CFILES="$uqm_CFILES socket_win.c" + uqm_HFILES="$uqm_HFILES socket_win.h" +else + uqm_CFILES="$uqm_CFILES socket_bsd.c" + uqm_HFILES="$uqm_CFILES socket_bsd.h" +fi + diff --git a/src/libs/network/socket/socket.c b/src/libs/network/socket/socket.c new file mode 100644 index 0000000..be7d601 --- /dev/null +++ b/src/libs/network/socket/socket.c @@ -0,0 +1,61 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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" + +#define SOCKET_INTERNAL +#include "socket.h" + +#ifdef USE_WINSOCK +# include +#else +# include +# include +#endif + + +//////////////////////////////////////////////////////////////////////////// + + +const int protocolFamilyTranslation[] = { + /* .[PF_unspec] = */ PF_UNSPEC, + /* .[PF_inet] = */ PF_INET, + /* .[PF_inet6] = */ PF_INET6, +}; + +const int protocolTranslation[] = { + /* .[IPProto_tcp] = */ IPPROTO_TCP, + /* .[IPProto_udp] = */ IPPROTO_UDP, +}; + +const int socketTypeTranslation[] = { + /* .[Sock_stream] = */ SOCK_STREAM, + /* .[Sock_dgram] = */ SOCK_DGRAM, +}; + + +Socket * +Socket_open(ProtocolFamily domain, SocketType type, Protocol protocol) { + return Socket_openNative(protocolFamilyTranslation[domain], + socketTypeTranslation[type], protocolTranslation[protocol]); +} + + +//////////////////////////////////////////////////////////////////////////// + + diff --git a/src/libs/network/socket/socket.h b/src/libs/network/socket/socket.h new file mode 100644 index 0000000..5c75467 --- /dev/null +++ b/src/libs/network/socket/socket.h @@ -0,0 +1,99 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_NETWORK_SOCKET_SOCKET_H_ +#define LIBS_NETWORK_SOCKET_SOCKET_H_ + +typedef struct Socket Socket; +#define Socket_noSocket ((Socket *) NULL) + +#include "port.h" + +#ifdef USE_WINSOCK +# include "socket_win.h" +#else +# include "socket_bsd.h" +#endif + + +//////////////////////////////////////////////////////////////////////////// + + +// Defining our own types for protocol families and protocols instead of +// using the system defines, so that the layer using this API does not have +// to have anything to do with the system layer. + +typedef enum { + PF_unspec, + PF_inet, + PF_inet6, +} ProtocolFamily; +typedef ProtocolFamily AddressFamily; + +typedef enum { + IPProto_tcp, + IPProto_udp, +} Protocol; + +typedef enum { + Sock_stream, + Sock_dgram, +} SocketType; + +#ifdef SOCKET_INTERNAL +extern const int protocolFamilyTranslation[]; +#define addressFamilyTranslation protocolFamilyTranslation; +extern const int protocolTranslation[]; +extern const int socketTypeTranslation[]; +#endif + + +//////////////////////////////////////////////////////////////////////////// + + +Socket *Socket_open(ProtocolFamily domain, SocketType type, + Protocol protocol); +#ifdef SOCKET_INTERNAL +Socket *Socket_openNative(int domain, int type, int protocol); +#endif +int Socket_close(Socket *sock); + +int Socket_connect(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen); +int Socket_bind(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen); +int Socket_listen(Socket *sock, int backlog); +Socket *Socket_accept(Socket *sock, struct sockaddr *addr, socklen_t *addrLen); +ssize_t Socket_send(Socket *sock, const void *buf, size_t len, int flags); +ssize_t Socket_sendto(Socket *sock, const void *buf, size_t len, int flags, + const struct sockaddr *addr, socklen_t addrLen); +ssize_t Socket_recv(Socket *sock, void *buf, size_t len, int flags); +ssize_t Socket_recvfrom(Socket *sock, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromLen); + +int Socket_setNonBlocking(Socket *sock); +int Socket_setReuseAddr(Socket *sock); +int Socket_setNodelay(Socket *sock); +int Socket_setTOS(Socket *sock, int tos); +int Socket_setInteractive(Socket *sock); +int Socket_setInlineOOB(Socket *sock); +int Socket_setKeepAlive(Socket *sock); +int Socket_getError(Socket *sock, int *err); + +#endif /* LIBS_NETWORK_SOCKET_SOCKET_H_ */ + diff --git a/src/libs/network/socket/socket_bsd.c b/src/libs/network/socket/socket_bsd.c new file mode 100644 index 0000000..2bf25b8 --- /dev/null +++ b/src/libs/network/socket/socket_bsd.c @@ -0,0 +1,283 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Socket functions for BSD sockets. + +#define SOCKET_INTERNAL +#include "socket.h" + +#include "libs/log.h" + +#include +#include +#include +#include +#include +#include +#include +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) +# include +# include +#endif +#include +#include +#include +#include + + +static Socket * +Socket_alloc(void) { + return malloc(sizeof (Socket)); +} + +static void +Socket_free(Socket *sock) { + free(sock); +} + +Socket * +Socket_openNative(int domain, int type, int protocol) { + Socket *result; + int fd; + + fd = socket(domain, type, protocol); + if (fd == -1) { + // errno is set + return Socket_noSocket; + } + + result = Socket_alloc(); + result->fd = fd; + return result; +} + +int +Socket_close(Socket *sock) { + int closeResult; + + do { + closeResult = close(sock->fd); + if (closeResult == 0) { + Socket_free(sock); + return 0; + } + } while (errno == EINTR); + + return -1; +} + +int +Socket_connect(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen) { + int connectResult; + + do { + connectResult = connect(sock->fd, addr, addrLen); + } while (connectResult == -1 && errno == EINTR); + + return connectResult; +} + +int +Socket_bind(Socket *sock, const struct sockaddr *addr, socklen_t addrLen) { + return bind(sock->fd, addr, addrLen); +} + +int +Socket_listen(Socket *sock, int backlog) { + return listen(sock->fd, backlog); +} + +Socket * +Socket_accept(Socket *sock, struct sockaddr *addr, socklen_t *addrLen) { + int acceptResult; + socklen_t tempAddrLen; + + do { + tempAddrLen = *addrLen; + acceptResult = accept(sock->fd, addr, &tempAddrLen); + if (acceptResult != -1) { + Socket *result = Socket_alloc(); + result->fd = acceptResult; + *addrLen = tempAddrLen; + return result; + } + + } while (errno == EINTR); + + // errno is set + return Socket_noSocket; +} + +ssize_t +Socket_send(Socket *sock, const void *buf, size_t len, int flags) { + return send(sock->fd, buf, len, flags); +} + +ssize_t +Socket_sendto(Socket *sock, const void *buf, size_t len, int flags, + const struct sockaddr *addr, socklen_t addrLen) { + return sendto(sock->fd, buf, len, flags, addr, addrLen); +} + +ssize_t +Socket_recv(Socket *sock, void *buf, size_t len, int flags) { + return recv(sock->fd, buf, len, flags); +} + +ssize_t +Socket_recvfrom(Socket *sock, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromLen) { + return recvfrom(sock->fd, buf, len, flags, from, fromLen); +} + +int +Socket_setNonBlocking(Socket *sock) { + int flags; + + flags = fcntl(sock->fd, F_GETFL); + if (flags == -1) { + int savedErrno = errno; + log_add(log_Error, "Getting file descriptor flags of socket failed: " + "%s.", strerror(errno)); + errno = savedErrno; + return -1; + } + + if (fcntl(sock->fd, F_SETFL, flags | O_NONBLOCK) == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting non-blocking mode on socket failed: " + "%s.", strerror(errno)); + errno = savedErrno; + return -1; + } + + return 0; +} + +int +Socket_setReuseAddr(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof flag) + == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting socket reuse failed: %s.", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +// Send data as soon as it is available. Do not collect data to send at +// once. +int +Socket_setNodelay(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof flag) + == -1) { +#ifdef DEBUG + int savedErrno = errno; + log_add(log_Warning, "Disabling Nagle algorithm failed: %s.", + strerror(errno)); + errno = savedErrno; +#endif + return -1; + } + return 0; +} + +// 'tos' should be IPTOS_LOWDELAY, IPTOS_THROUGHPUT, IPTOS_THROUGHPUT, +// IPTOS_RELIABILITY, or IPTOS_MINCOST. +int +Socket_setTOS(Socket *sock, int tos) { + if (setsockopt(sock->fd, IPPROTO_IP, IP_TOS, &tos, sizeof tos) == -1) { +#ifdef DEBUG + int savedErrno = errno; + log_add(log_Warning, "Setting socket type-of-service failed: %s.", + strerror(errno)); + errno = savedErrno; +#endif + return -1; + } + return 0; +} + +// This function setups the socket for optimal configuration for an +// interactive connection. +int +Socket_setInteractive(Socket *sock) { + if (Socket_setNodelay(sock) == -1) { + // errno is set + return -1; + } + + if (Socket_setTOS(sock, IPTOS_LOWDELAY) == -1) { + // errno is set + return -1; + } + return 0; +} + +int +Socket_setInlineOOB(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, SOL_SOCKET, SO_OOBINLINE, &flag, sizeof flag) + == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting inline OOB on socket failed: %s", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_setKeepAlive(Socket *sock) { + int flag = 1; + + if (setsockopt(sock->fd, IPPROTO_TCP, SO_KEEPALIVE, &flag, sizeof flag) + == -1) { + int savedErrno = errno; + log_add(log_Error, "Setting keep-alive on socket failed: %s", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_getError(Socket *sock, int *err) { + socklen_t errLen = sizeof(*err); + + if (getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, err, &errLen) == -1) { + // errno is set + return -1; + } + + assert(errLen == sizeof(*err)); + // err is set + return 0; +} + + diff --git a/src/libs/network/socket/socket_bsd.h b/src/libs/network/socket/socket_bsd.h new file mode 100644 index 0000000..8019c01 --- /dev/null +++ b/src/libs/network/socket/socket_bsd.h @@ -0,0 +1,33 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_NETWORK_SOCKET_SOCKET_BSD_H_ +#define LIBS_NETWORK_SOCKET_SOCKET_BSD_H_ + +#include "types.h" +#include + +#ifdef SOCKET_INTERNAL +struct Socket { + int fd; +}; +#endif /* SOCKET_INTERNAL */ + + +#endif /* LIBS_NETWORK_SOCKET_SOCKET_BSD_H_ */ + diff --git a/src/libs/network/socket/socket_win.c b/src/libs/network/socket/socket_win.c new file mode 100644 index 0000000..e1ed002 --- /dev/null +++ b/src/libs/network/socket/socket_win.c @@ -0,0 +1,314 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Socket functions for Winsock sockets. + +#define PORT_WANT_ERRNO +#include "port.h" +#include "../netport.h" + +#define SOCKET_INTERNAL +#include "socket.h" + +#include "libs/log.h" + +#include +#include +#include + + +Socket * +Socket_alloc(void) { + return malloc(sizeof (Socket)); +} + +void +Socket_free(Socket *sock) { + free(sock); +} + +Socket * +Socket_openNative(int domain, int type, int protocol) { + Socket *result; + SOCKET sock; + + sock = socket(domain, type, protocol); + if (sock == INVALID_SOCKET) { + errno = getWinsockErrno(); + return Socket_noSocket; + } + + result = Socket_alloc(); + result->sock = sock; + return result; +} + +int +Socket_close(Socket *sock) { + int closeResult; + + do { + closeResult = closesocket(sock->sock); + if (closeResult != SOCKET_ERROR) { + Socket_free(sock); + return 0; + } + + errno = getWinsockErrno(); + } while (errno == EINTR); + + return -1; +} + +int +Socket_connect(Socket *sock, const struct sockaddr *addr, + socklen_t addrLen) { + int connectResult; + + do { + connectResult = connect(sock->sock, addr, addrLen); + if (connectResult == 0) + return 0; + + errno = getWinsockErrno(); + } while (errno == EINTR); + + if (errno == EWOULDBLOCK) { + // Windows returns (WSA)EWOULDBLOCK when a connection is being + // initiated on a non-blocking socket, while other platforms + // use EINPROGRESS in such cases. + errno = EINPROGRESS; + } + + return -1; +} + +int +Socket_bind(Socket *sock, const struct sockaddr *addr, socklen_t addrLen) { + int bindResult; + + bindResult = bind(sock->sock, addr, addrLen); + if (bindResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return 0; +} + +int +Socket_listen(Socket *sock, int backlog) { + int listenResult; + + listenResult = listen(sock->sock, backlog); + if (listenResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return 0; +} + +Socket * +Socket_accept(Socket *sock, struct sockaddr *addr, socklen_t *addrLen) { + SOCKET acceptResult; + socklen_t tempAddrLen; + + do { + tempAddrLen = *addrLen; + acceptResult = accept(sock->sock, addr, &tempAddrLen); + if (acceptResult != INVALID_SOCKET) { + Socket *result = Socket_alloc(); + result->sock = acceptResult; + *addrLen = tempAddrLen; + return result; + } + + errno = getWinsockErrno(); + } while (errno == EINTR); + + // errno is set + return Socket_noSocket; +} + +ssize_t +Socket_send(Socket *sock, const void *buf, size_t len, int flags) { + int sendResult; + + sendResult = send(sock->sock, buf, len, flags); + if (sendResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return sendResult; +} + +ssize_t +Socket_sendto(Socket *sock, const void *buf, size_t len, int flags, + const struct sockaddr *addr, socklen_t addrLen) { + int sendResult; + + sendResult = sendto(sock->sock, buf, len, flags, addr, addrLen); + if (sendResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return sendResult; +} + +ssize_t +Socket_recv(Socket *sock, void *buf, size_t len, int flags) { + int recvResult; + + recvResult = recv(sock->sock, buf, len, flags); + if (recvResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return recvResult; +} + +ssize_t +Socket_recvfrom(Socket *sock, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromLen) { + int recvResult; + + recvResult = recvfrom(sock->sock, buf, len, flags, from, fromLen); + if (recvResult == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + return recvResult; +} + +int +Socket_setNonBlocking(Socket *sock) { + unsigned long flag = 1; + + if (ioctlsocket(sock->sock, FIONBIO, &flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting non-block mode on socket failed: %s.", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_setReuseAddr(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, + (const char *) &flag, sizeof flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting socket reuse failed: %s.", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +// Send data as soon as it is available. Do not collect data to send at +// once. +int +Socket_setNodelay(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, IPPROTO_TCP, TCP_NODELAY, + (const char *) &flag, sizeof flag) == SOCKET_ERROR) { +#ifdef DEBUG + int savedErrno = getWinsockErrno(); + log_add(log_Warning, "Disabling Nagle algorithm failed: %s.", + strerror(errno)); + errno = savedErrno; +#endif + return -1; + } + return 0; +} + +// This function setups the socket for optimal configuration for an +// interactive connection. +int +Socket_setInteractive(Socket *sock) { + if (Socket_setNodelay(sock) == -1) { + // errno is set + return -1; + } + +#if 0 + if (Socket_setTOS(sock, IPTOS_LOWDELAY) == -1) { + // errno is set + return -1; + } +#endif + return 0; +} + +int +Socket_setInlineOOB(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, SOL_SOCKET, SO_OOBINLINE, (const char *) &flag, + sizeof flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting inline OOB on socket failed: %s", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_setKeepAlive(Socket *sock) { + BOOL flag = 1; + + if (setsockopt(sock->sock, IPPROTO_TCP, SO_KEEPALIVE, + (const char *) &flag, sizeof flag) == SOCKET_ERROR) { + int savedErrno = getWinsockErrno(); + log_add(log_Error, "Setting keep-alive on socket failed: %s", + strerror(errno)); + errno = savedErrno; + return -1; + } + return 0; +} + +int +Socket_getError(Socket *sock, int *err) { + int errLen = sizeof(*err); + + if (getsockopt(sock->sock, SOL_SOCKET, SO_ERROR, (char *) err, &errLen) + == SOCKET_ERROR) { + errno = getWinsockErrno(); + return -1; + } + + assert(errLen == sizeof(*err)); + // err is set + return 0; +} + + diff --git a/src/libs/network/socket/socket_win.h b/src/libs/network/socket/socket_win.h new file mode 100644 index 0000000..ed1674c --- /dev/null +++ b/src/libs/network/socket/socket_win.h @@ -0,0 +1,34 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_NETWORK_SOCKET_SOCKET_WIN_H_ +#define LIBS_NETWORK_SOCKET_SOCKET_WIN_H_ + +#ifdef SOCKET_INTERNAL +#include +struct Socket { + SOCKET sock; +}; +#endif /* SOCKET_INTERNAL */ + +typedef int socklen_t; +struct sockaddr; + +#endif /* LIBS_NETWORK_SOCKET_SOCKET_WIN_H_ */ + + diff --git a/src/libs/network/wspiapiwrap.c b/src/libs/network/wspiapiwrap.c new file mode 100644 index 0000000..2eddeb4 --- /dev/null +++ b/src/libs/network/wspiapiwrap.c @@ -0,0 +1,34 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Only used for MinGW + +// HACK. MinGW misses some functionality, so we're #including +// the actual Windows wspiapi.h file. Because that file includes +// inline functions that aren't static, it can only be included +// once per executable when using gcc (for MSVC this is apparently +// not a problem), so this file is it. The prototypes of these +// functions are added to wspiapiwrap.h + +#include "netport.h" + +#if defined(USE_WINSOCK) && defined(__MINGW32__) +# include +# include +#endif + diff --git a/src/libs/network/wspiapiwrap.h b/src/libs/network/wspiapiwrap.h new file mode 100644 index 0000000..23361a4 --- /dev/null +++ b/src/libs/network/wspiapiwrap.h @@ -0,0 +1,33 @@ +/* + * Copyright 2006 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_NETWORK_WSPIAPIWRAP_H_ +#define LIBS_NETWORK_WSPIAPIWRAP_H_ + +// HACK. See wspiapiwrap.c +# define getaddrinfo WspiapiGetAddrInfo +# define getnameinfo WspiapiGetNameInfo +# define freeaddrinfo WspiapiFreeAddrInfo +void WINAPI WspiapiFreeAddrInfo (struct addrinfo *ai); +int WINAPI WspiapiGetAddrInfo(const char *nodename, const char *servname, + const struct addrinfo *hints, struct addrinfo **res); +int WINAPI WspiapiGetNameInfo (const struct sockaddr *sa, socklen_t salen, + char *host, size_t hostlen, char *serv, size_t servlen, int flags); + +#endif /* LIBS_NETWORK_WSPIAPIWRAP_H_ */ + diff --git a/src/libs/platform.h b/src/libs/platform.h new file mode 100644 index 0000000..f17674d --- /dev/null +++ b/src/libs/platform.h @@ -0,0 +1,57 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 PLATFORM_H_ +#define PLATFORM_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(USE_PLATFORM_ACCEL) +# if defined(__GNUC__) && (defined(i386) || defined(__x86_64__)) +# define MMX_ASM +# define GCC_ASM +# elif (_MSC_VER >= 1100) && defined(_M_IX86) +// All 32bit AMD chips are IX86 equivalents +// We do not enable MSVC/MMX_ASM for _M_AMD64 as of now +# define MMX_ASM +# define MSVC_ASM +# endif +// other realistic possibilities for MSVC compiler are +// _M_AMD64 (AMD x86-64), _M_IA64 (Intel Arch 64) +#endif + +typedef enum +{ + PLATFORM_NULL = 0, + PLATFORM_C, + PLATFORM_MMX, + PLATFORM_SSE, + PLATFORM_3DNOW, + PLATFORM_ALTIVEC, + + PLATFORM_LAST = PLATFORM_ALTIVEC + +} PLATFORM_TYPE; + +extern PLATFORM_TYPE force_platform; + +#if defined(__cplusplus) +} +#endif + +#endif /* PLATFORM_H_ */ diff --git a/src/libs/reslib.h b/src/libs/reslib.h new file mode 100644 index 0000000..0b56262 --- /dev/null +++ b/src/libs/reslib.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 LIBS_RESLIB_H_ +#define LIBS_RESLIB_H_ + +//#include +#include "libs/compiler.h" +#include "port.h" +#include "libs/memlib.h" +#include "libs/uio.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct resource_index_desc RESOURCE_INDEX_DESC; +typedef RESOURCE_INDEX_DESC *RESOURCE_INDEX; + +typedef const char *RESOURCE; + +typedef union { + DWORD num; + void *ptr; + const char *str; +} RESOURCE_DATA; + +#define NULL_RESOURCE NULL + +extern const char *_cur_resfile_name; + +typedef void (ResourceLoadFun) (const char *pathname, RESOURCE_DATA *resdata); +typedef BOOLEAN (ResourceFreeFun) (void *handle); +typedef void (ResourceStringFun) (RESOURCE_DATA *handle, char *buf, unsigned int size); + +typedef void *(ResourceLoadFileFun) (uio_Stream *fp, DWORD len); + +void *LoadResourceFromPath(const char *pathname, ResourceLoadFileFun fn); + +uio_Stream *res_OpenResFile (uio_DirHandle *dir, const char *filename, const char *mode); +size_t ReadResFile (void *lpBuf, size_t size, size_t count, uio_Stream *fp); +size_t WriteResFile (const void *lpBuf, size_t size, size_t count, uio_Stream *fp); +int GetResFileChar (uio_Stream *fp); +int PutResFileChar (char ch, uio_Stream *fp); +int PutResFileNewline (uio_Stream *fp); +long SeekResFile (uio_Stream *fp, long offset, int whence); +long TellResFile (uio_Stream *fp); +size_t LengthResFile (uio_Stream *fp); +BOOLEAN res_CloseResFile (uio_Stream *fp); +BOOLEAN DeleteResFile (uio_DirHandle *dir, const char *filename); + +RESOURCE_INDEX InitResourceSystem (void); +void UninitResourceSystem (void); +BOOLEAN InstallResTypeVectors (const char *res_type, ResourceLoadFun *loadFun, ResourceFreeFun *freeFun, ResourceStringFun *stringFun); +void *res_GetResource (RESOURCE res); +void *res_DetachResource (RESOURCE res); +void res_FreeResource (RESOURCE res); +COUNT CountResourceTypes (void); +DWORD res_GetIntResource (RESOURCE res); +BOOLEAN res_GetBooleanResource (RESOURCE res); +const char *res_GetResourceType (RESOURCE res); + +void LoadResourceIndex (uio_DirHandle *dir, const char *filename, const char *prefix); +void SaveResourceIndex (uio_DirHandle *dir, const char *rmpfile, const char *root, BOOLEAN strip_root); + +void *GetResourceData (uio_Stream *fp, DWORD length); + +#define AllocResourceData HMalloc +BOOLEAN FreeResourceData (void *); + +#if defined(__cplusplus) +} +#endif + +#include "libs/strlib.h" +#include "libs/gfxlib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + // For Color + +typedef STRING_TABLE DIRENTRY_REF; +typedef STRING DIRENTRY; + +extern DIRENTRY_REF LoadDirEntryTable (uio_DirHandle *dirHandle, + const char *path, const char *pattern, match_MatchType matchType); +#define CaptureDirEntryTable CaptureStringTable +#define ReleaseDirEntryTable ReleaseStringTable +#define DestroyDirEntryTable DestroyStringTable +#define GetDirEntryTableRef GetStringTable +#define GetDirEntryTableCount GetStringTableCount +#define GetDirEntryTableIndex GetStringTableIndex +#define SetAbsDirEntryTableIndex SetAbsStringTableIndex +#define SetRelDirEntryTableIndex SetRelStringTableIndex +#define GetDirEntryLength GetStringLengthBin +#define GetDirEntryAddress GetStringAddress + +/* Key-Value resources */ + +BOOLEAN res_HasKey (const char *key); + +BOOLEAN res_IsString (const char *key); +const char *res_GetString (const char *key); +void res_PutString (const char *key, const char *value); + +BOOLEAN res_IsInteger (const char *key); +int res_GetInteger (const char *key); +void res_PutInteger (const char *key, int value); + +BOOLEAN res_IsBoolean (const char *key); +BOOLEAN res_GetBoolean (const char *key); +void res_PutBoolean (const char *key, BOOLEAN value); + +BOOLEAN res_IsColor (const char *key); +Color res_GetColor (const char *key); +void res_PutColor (const char *key, Color value); + +BOOLEAN res_Remove (const char *key); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_RESLIB_H_ */ diff --git a/src/libs/resource/Makeinfo b/src/libs/resource/Makeinfo new file mode 100644 index 0000000..ddac8e2 --- /dev/null +++ b/src/libs/resource/Makeinfo @@ -0,0 +1,3 @@ +uqm_CFILES="direct.c filecntl.c getres.c loadres.c stringbank.c + propfile.c resinit.c" +uqm_HFILES="index.h propfile.h resintrn.h stringbank.h" diff --git a/src/libs/resource/direct.c b/src/libs/resource/direct.c new file mode 100644 index 0000000..b3d3541 --- /dev/null +++ b/src/libs/resource/direct.c @@ -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. + */ + +#include "libs/strings/strintrn.h" +#include "libs/memlib.h" +#include "port.h" +#include "libs/uio.h" +#include + +DIRENTRY_REF +LoadDirEntryTable (uio_DirHandle *dirHandle, const char *path, + const char *pattern, match_MatchType matchType) +{ + uio_DirList *dirList; + COUNT num_entries; + COUNT i; + uio_DirHandle *dir; + STRING_TABLE StringTable; + STRING_TABLE_DESC *lpST; + STRING lpLastString; + + dir = uio_openDirRelative (dirHandle, path, 0); + assert(dir != NULL); + dirList = uio_getDirList (dir, "", pattern, matchType); + assert(dirList != NULL); + num_entries = 0; + + // First, count the amount of space needed + for (i = 0; i < dirList->numNames; i++) + { + struct stat sb; + + if (dirList->names[i][0] == '.') + { + dirList->names[i] = NULL; + continue; + } + if (uio_stat (dir, dirList->names[i], &sb) == -1) + { + dirList->names[i] = NULL; + continue; + } + if (!S_ISREG (sb.st_mode)) + { + dirList->names[i] = NULL; + continue; + } + num_entries++; + } + uio_closeDir (dir); + + if (num_entries == 0) { + uio_DirList_free(dirList); + return ((DIRENTRY_REF) 0); + } + + StringTable = AllocStringTable (num_entries, 0); + lpST = StringTable; + if (lpST == 0) + { + FreeStringTable (StringTable); + uio_DirList_free(dirList); + return ((DIRENTRY_REF) 0); + } + lpST->size = num_entries; + lpLastString = lpST->strings; + + for (i = 0; i < dirList->numNames; i++) + { + int size; + STRINGPTR target; + if (dirList->names[i] == NULL) + continue; + size = strlen (dirList->names[i]) + 1; + target = HMalloc (size); + memcpy (target, dirList->names[i], size); + lpLastString->data = target; + lpLastString->length = size; + lpLastString++; + } + + uio_DirList_free(dirList); + return StringTable; +} + + diff --git a/src/libs/resource/filecntl.c b/src/libs/resource/filecntl.c new file mode 100644 index 0000000..e2a81d9 --- /dev/null +++ b/src/libs/resource/filecntl.c @@ -0,0 +1,146 @@ +//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. + */ + +#ifdef WIN32 +#include +#endif +#include +#include +#include +#include +#include "port.h" +#include "resintrn.h" +#include "libs/uio.h" + +uio_Stream * +res_OpenResFile (uio_DirHandle *dir, const char *filename, const char *mode) +{ + uio_Stream *fp; + struct stat sb; + + if (uio_stat (dir, filename, &sb) == 0 && S_ISDIR(sb.st_mode)) + return ((uio_Stream *) ~0); + + fp = uio_fopen (dir, filename, mode); + + return (fp); +} + +BOOLEAN +res_CloseResFile (uio_Stream *fp) +{ + if (fp) + { + if (fp != (uio_Stream *)~0) + uio_fclose (fp); + return (TRUE); + } + + return (FALSE); +} + +BOOLEAN +DeleteResFile (uio_DirHandle *dir, const char *filename) +{ + return (uio_unlink (dir, filename) == 0); +} + +size_t +ReadResFile (void *lpBuf, size_t size, size_t count, uio_Stream *fp) +{ + int retval; + + retval = uio_fread (lpBuf, size, count, fp); + + return (retval); +} + +size_t +WriteResFile (const void *lpBuf, size_t size, size_t count, uio_Stream *fp) +{ + int retval; + + retval = uio_fwrite (lpBuf, size, count, fp); + + return (retval); +} + +int +GetResFileChar (uio_Stream *fp) +{ + int retval; + + retval = uio_getc (fp); + + return (retval); +} + +int +PutResFileChar (char ch, uio_Stream *fp) +{ + int retval; + + retval = uio_putc (ch, fp); + return (retval); +} + +int +PutResFileNewline (uio_Stream *fp) +{ + int retval; + +#ifdef WIN32 + PutResFileChar ('\r', fp); +#endif + retval = PutResFileChar ('\n', fp); + return (retval); +} + +long +SeekResFile (uio_Stream *fp, long offset, int whence) +{ + long retval; + + retval = uio_fseek (fp, offset, whence); + + return (retval); +} + +long +TellResFile (uio_Stream *fp) +{ + long retval; + + retval = uio_ftell (fp); + + return (retval); +} + +size_t +LengthResFile (uio_Stream *fp) +{ + struct stat sb; + + if (fp == (uio_Stream *)~0) + return (1); + if (uio_fstat(uio_streamHandle(fp), &sb) == -1) + return 1; + return sb.st_size; +} + + diff --git a/src/libs/resource/getres.c b/src/libs/resource/getres.c new file mode 100644 index 0000000..39e24a9 --- /dev/null +++ b/src/libs/resource/getres.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 "options.h" +#include "port.h" +#include "resintrn.h" +#include "libs/memlib.h" +#include "libs/log.h" +#include "libs/uio/charhashtable.h" + +const char *_cur_resfile_name; +// When a file is being loaded, _cur_resfile_name is set to its name. +// At other times, it is NULL. + +ResourceDesc * +lookupResourceDesc (RESOURCE_INDEX idx, RESOURCE res) +{ + return (ResourceDesc *) CharHashTable_find (idx->map, res); +} + +void +loadResourceDesc (ResourceDesc *desc) +{ + desc->vtable->loadFun (desc->fname, &desc->resdata); +} + +void * +LoadResourceFromPath (const char *path, ResourceLoadFileFun *loadFun) +{ + uio_Stream *stream; + unsigned long dataLen; + void *resdata; + + stream = res_OpenResFile (contentDir, path, "rb"); + if (stream == NULL) + { + log_add (log_Warning, "Warning: Can't open '%s'", path); + return NULL; + } + + dataLen = LengthResFile (stream); + log_add (log_Info, "\t'%s' -- %lu bytes", path, dataLen); + + if (dataLen == 0) + { + log_add (log_Warning, "Warning: Trying to load empty file '%s'.", path); + goto err; + } + + _cur_resfile_name = path; + resdata = (*loadFun) (stream, dataLen); + _cur_resfile_name = NULL; + res_CloseResFile (stream); + + return resdata; + +err: + res_CloseResFile (stream); + return NULL; +} + +const char * +res_GetResourceType (RESOURCE res) +{ + RESOURCE_INDEX resourceIndex; + ResourceDesc *desc; + + if (res == NULL_RESOURCE) + { + log_add (log_Warning, "Trying to get type of null resource"); + return NULL; + } + + resourceIndex = _get_current_index_header (); + desc = lookupResourceDesc (resourceIndex, res); + if (desc == NULL) + { + log_add (log_Warning, "Trying to get type of undefined resource '%s'", + res); + return NULL; + } + + return desc->vtable->resType; +} + + +// Get a resource by its resource ID. +void * +res_GetResource (RESOURCE res) +{ + RESOURCE_INDEX resourceIndex; + ResourceDesc *desc; + + if (res == NULL_RESOURCE) + { + log_add (log_Warning, "Trying to get null resource"); + return NULL; + } + + resourceIndex = _get_current_index_header (); + + desc = lookupResourceDesc (resourceIndex, res); + if (desc == NULL) + { + log_add (log_Warning, "Trying to get undefined resource '%s'", + res); + return NULL; + } + + if (desc->resdata.ptr == NULL) + loadResourceDesc (desc); + if (desc->resdata.ptr != NULL) + ++desc->refcount; + + return desc->resdata.ptr; + // May still be NULL, if the load failed. +} + +DWORD +res_GetIntResource (RESOURCE res) +{ + RESOURCE_INDEX resourceIndex; + ResourceDesc *desc; + + if (res == NULL_RESOURCE) + { + log_add (log_Warning, "Trying to get null resource"); + return 0; + } + + resourceIndex = _get_current_index_header (); + + desc = lookupResourceDesc (resourceIndex, res); + if (desc == NULL) + { + log_add (log_Warning, "Trying to get undefined resource '%s'", + res); + return 0; + } + + return desc->resdata.num; +} + +BOOLEAN +res_GetBooleanResource (RESOURCE res) +{ + return (res_GetIntResource (res) != 0); +} + +// NB: this function appears to be never called! +void +res_FreeResource (RESOURCE res) +{ + ResourceDesc *desc; + ResourceFreeFun *freeFun; + + desc = lookupResourceDesc (_get_current_index_header(), res); + if (desc == NULL) + { + log_add (log_Debug, "Warning: trying to free an unrecognised " + "resource."); + return; + } + + if (desc->refcount > 0) + --desc->refcount; + else + log_add (log_Debug, "Warning: freeing an unreferenced resource."); + if (desc->refcount > 0) + return; // Still references left + + freeFun = desc->vtable->freeFun; + if (freeFun == NULL) + { + log_add (log_Debug, "Warning: trying to free a non-heap resource."); + return; + } + + if (desc->resdata.ptr == NULL) + { + log_add (log_Debug, "Warning: trying to free not loaded " + "resource."); + return; + } + + (*freeFun) (desc->resdata.ptr); + desc->resdata.ptr = NULL; +} + +// By calling this function the caller will be responsible of unloading +// the resource. If res_GetResource() get called again for this +// resource, a NEW copy will be loaded, regardless of whether a detached +// copy still exists. +void * +res_DetachResource (RESOURCE res) +{ + ResourceDesc *desc; + ResourceFreeFun *freeFun; + void *result; + + desc = lookupResourceDesc (_get_current_index_header(), res); + if (desc == NULL) + { + log_add (log_Debug, "Warning: trying to detach from an unrecognised " + "resource."); + return NULL; + } + + freeFun = desc->vtable->freeFun; + if (freeFun == NULL) + { + log_add (log_Debug, "Warning: trying to detach from a non-heap resource."); + return NULL; + } + + if (desc->resdata.ptr == NULL) + { + log_add (log_Debug, "Warning: trying to detach from a not loaded " + "resource."); + return NULL; + } + + if (desc->refcount > 1) + { + log_add (log_Debug, "Warning: trying to detach a resource referenced " + "%u times", desc->refcount); + return NULL; + } + + result = desc->resdata.ptr; + desc->resdata.ptr = NULL; + desc->refcount = 0; + + return result; +} + +BOOLEAN +FreeResourceData (void *data) +{ + HFree (data); + return TRUE; +} diff --git a/src/libs/resource/index.h b/src/libs/resource/index.h new file mode 100644 index 0000000..bdbb162 --- /dev/null +++ b/src/libs/resource/index.h @@ -0,0 +1,54 @@ +//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 LIBS_RESOURCE_INDEX_H_ +#define LIBS_RESOURCE_INDEX_H_ + +typedef struct resource_handlers ResourceHandlers; +typedef struct resource_desc ResourceDesc; + +#include +#include "libs/reslib.h" +#include "libs/uio/charhashtable.h" + +struct resource_handlers +{ + const char *resType; + ResourceLoadFun *loadFun; + ResourceFreeFun *freeFun; + ResourceStringFun *toString; +}; + +struct resource_desc +{ + RESOURCE res_id; + char *fname; + ResourceHandlers *vtable; + RESOURCE_DATA resdata; + // refcount is rudimentary as nothing really frees the descriptors + unsigned refcount; +}; + +struct resource_index_desc +{ + CharHashTable_HashTable *map; + size_t numRes; +}; + +#endif /* LIBS_RESOURCE_INDEX_H_ */ + diff --git a/src/libs/resource/loadres.c b/src/libs/resource/loadres.c new file mode 100644 index 0000000..a9849e4 --- /dev/null +++ b/src/libs/resource/loadres.c @@ -0,0 +1,54 @@ +//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 "resintrn.h" +#include "libs/memlib.h" +#include "libs/log.h" + + +void * +GetResourceData (uio_Stream *fp, DWORD length) +{ + void *result; + DWORD compLen; + + // Resource data used to be prefixed by its length in package files. + // A valid length prefix indicated compressed data, and + // a length prefix ~0 meant uncompressed. + // Currently, .ct and .xlt files still carry a ~0 length prefix. + if (ReadResFile (&compLen, sizeof (compLen), 1, fp) != 1) + return NULL; + if (compLen != ~(DWORD)0) + { + log_add (log_Warning, "LZ-compressed binary data not supported"); + return NULL; + } + length -= sizeof (DWORD); + + result = AllocResourceData (length); + if (!result) + return NULL; + + if (ReadResFile (result, 1, length, fp) != length) + { + FreeResourceData (result); + result = NULL; + } + + return result; +} diff --git a/src/libs/resource/propfile.c b/src/libs/resource/propfile.c new file mode 100644 index 0000000..1784600 --- /dev/null +++ b/src/libs/resource/propfile.c @@ -0,0 +1,129 @@ +/* propfile.c, Copyright (c) 2008 Michael C. Martin */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope thta it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Se the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#include +#include "libs/log.h" +#include "propfile.h" +#include "libs/reslib.h" + +void +PropFile_from_string (char *d, PROPERTY_HANDLER handler, const char *prefix) +{ + int len, i; + + len = strlen(d); + i = 0; + while (i < len) { + int key_start, key_end, value_start, value_end; + /* Starting a line: search for non-whitespace */ + while ((i < len) && isspace (d[i])) i++; + if (i >= len) break; /* Done parsing! */ + /* If it was a comment, skip to end of comment/file */ + if (d[i] == '#') { + while ((i < len) && (d[i] != '\n')) i++; + if (i >= len) break; + continue; /* Back to keyword search */ + } + key_start = i; + /* Find the = on this line */ + while ((i < len) && (d[i] != '=') && + (d[i] != '\n') && (d[i] != '#')) i++; + if (i >= len) { /* Bare key at EOF */ + log_add (log_Warning, "Warning: Bare keyword at EOF"); + break; + } + /* Comments here mean incomplete line too */ + if (d[i] != '=') { + log_add (log_Warning, "Warning: Key without value"); + while ((i < len) && (d[i] != '\n')) i++; + if (i >= len) break; + continue; /* Back to keyword search */ + } + /* Key ends at first whitespace before = , or at key_start*/ + key_end = i; + while ((key_end > key_start) && isspace (d[key_end-1])) + key_end--; + + /* Consume the = */ + i++; + /* Value starts at first non-whitespace after = on line... */ + while ((i < len) && (d[i] != '#') && (d[i] != '\n') && + isspace (d[i])) + i++; + value_start = i; + /* Until first non-whitespace before terminator */ + while ((i < len) && (d[i] != '#') && (d[i] != '\n')) + i++; + value_end = i; + while ((value_end > value_start) && isspace (d[value_end-1])) + value_end--; + /* Skip past EOL or EOF */ + while ((i < len) && (d[i] != '\n')) + i++; + i++; + + /* We now have start and end values for key and value. + We terminate the strings for both by writing \0s, then + make a new map entry. */ + d[key_end] = '\0'; + d[value_end] = '\0'; + if (prefix) { + char buf[256]; + snprintf(buf, 255, "%s%s", prefix, d+key_start); + buf[255]=0; + handler(buf, d+value_start); + } else { + handler (d+key_start, d+value_start); + } + } +} + +void +PropFile_from_file (uio_Stream *f, PROPERTY_HANDLER handler, const char *prefix) +{ + size_t flen; + char *data; + + flen = LengthResFile (f); + + data = malloc (flen + 1); + if (!data) { + return; + } + + // We may end up with less bytes than we asked for due to the + // DOS->Unix newline conversion + flen = ReadResFile (data, 1, flen, f); + data[flen] = '\0'; + + PropFile_from_string (data, handler, prefix); + free (data); +} + +void +PropFile_from_filename (uio_DirHandle *path, const char *fname, PROPERTY_HANDLER handler, const char *prefix) +{ + uio_Stream *f = res_OpenResFile (path, fname, "rt"); + if (!f) { + return; + } + PropFile_from_file (f, handler, prefix); + res_CloseResFile(f); +} diff --git a/src/libs/resource/propfile.h b/src/libs/resource/propfile.h new file mode 100644 index 0000000..edc8c36 --- /dev/null +++ b/src/libs/resource/propfile.h @@ -0,0 +1,30 @@ +/* propfile.h, Copyright (c) 2008 Michael C. Martin */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope thta it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Se the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 PROPFILE_H_ +#define PROPFILE_H_ + +#include "libs/uio.h" + +typedef void (*PROPERTY_HANDLER) (const char *, const char *); + +void PropFile_from_string (char *d, PROPERTY_HANDLER handler, const char *prefix); +void PropFile_from_file (uio_Stream *f, PROPERTY_HANDLER handler, const char *prefix); +void PropFile_from_filename (uio_DirHandle *path, const char *fname, PROPERTY_HANDLER handler, const char *prefix); + +#endif diff --git a/src/libs/resource/resinit.c b/src/libs/resource/resinit.c new file mode 100644 index 0000000..dacbee4 --- /dev/null +++ b/src/libs/resource/resinit.c @@ -0,0 +1,651 @@ +//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 "resintrn.h" +#include "libs/memlib.h" +#include "options.h" +#include "types.h" +#include "libs/log.h" +#include "libs/gfxlib.h" +#include "libs/reslib.h" +#include "libs/sndlib.h" +#include "libs/vidlib.h" +#include "propfile.h" +#include +#include +// XXX: we should not include anything from uqm/ inside libs/ +#include "uqm/coderes.h" + +static RESOURCE_INDEX +allocResourceIndex (void) { + RESOURCE_INDEX ndx = HMalloc (sizeof (RESOURCE_INDEX_DESC)); + ndx->map = CharHashTable_newHashTable (NULL, NULL, NULL, NULL, NULL, + 0, 0.85, 0.9); + return ndx; +} + +static void +freeResourceIndex (RESOURCE_INDEX h) { + if (h != NULL) + { + /* TODO: This leaks the contents of h->map */ + CharHashTable_deleteHashTable (h->map); + HFree (h); + } +} + +#define TYPESIZ 32 + +static ResourceDesc * +newResourceDesc (const char *res_id, const char *resval) +{ + const char *path; + int pathlen; + ResourceHandlers *vtable; + ResourceDesc *result, *handlerdesc; + RESOURCE_INDEX idx = _get_current_index_header (); + char typestr[TYPESIZ]; + + path = strchr (resval, ':'); + if (path == NULL) + { + log_add (log_Warning, "Could not find type information for resource '%s'", res_id); + strncpy(typestr, "sys.UNKNOWNRES", TYPESIZ); + path = resval; + } + else + { + int n = path - resval; + + if (n >= TYPESIZ - 4) + { + n = TYPESIZ - 5; + } + strncpy (typestr, "sys.", TYPESIZ); + strncat (typestr+1, resval, n); + typestr[n+4] = '\0'; + path++; + } + pathlen = strlen (path); + + handlerdesc = lookupResourceDesc(idx, typestr); + if (handlerdesc == NULL) { + path = resval; + log_add (log_Warning, "Illegal type '%s' for resource '%s'; treating as UNKNOWNRES", typestr, res_id); + handlerdesc = lookupResourceDesc(idx, "sys.UNKNOWNRES"); + } + + vtable = (ResourceHandlers *)handlerdesc->resdata.ptr; + + if (vtable->loadFun == NULL) + { + log_add (log_Warning, "Warning: Unable to load '%s'; no handler " + "for type %s defined.", res_id, typestr); + return NULL; + } + + result = HMalloc (sizeof (ResourceDesc)); + if (result == NULL) + return NULL; + + result->fname = HMalloc (pathlen + 1); + strncpy (result->fname, path, pathlen); + result->fname[pathlen] = '\0'; + result->vtable = vtable; + result->refcount = 0; + + if (vtable->freeFun == NULL) + { + /* Non-heap resources are raw values. Work those out at load time. */ + vtable->loadFun (result->fname, &result->resdata); + } + else + { + result->resdata.ptr = NULL; + } + return result; +} + +static void +process_resource_desc (const char *key, const char *value) +{ + CharHashTable_HashTable *map = _get_current_index_header ()->map; + ResourceDesc *newDesc = newResourceDesc (key, value); + if (newDesc != NULL) + { + if (!CharHashTable_add (map, key, newDesc)) + { + res_Remove (key); + CharHashTable_add (map, key, newDesc); + } + } +} + +static void +UseDescriptorAsRes (const char *descriptor, RESOURCE_DATA *resdata) +{ + resdata->str = descriptor; +} + +static void +DescriptorToInt (const char *descriptor, RESOURCE_DATA *resdata) +{ + resdata->num = atoi (descriptor); +} + +static void +DescriptorToBoolean (const char *descriptor, RESOURCE_DATA *resdata) +{ + if (!strcasecmp (descriptor, "true")) + { + resdata->num = TRUE; + } + else + { + resdata->num = FALSE; + } +} + +static inline size_t +skipWhiteSpace (const char *start) +{ + const char *ptr = start; + while (isspace (*ptr)) + ptr++; + return (ptr - start); +} + +// On success, resdata->num will be filled with a 32-bits RGBA value. +static void +DescriptorToColor (const char *descriptor, RESOURCE_DATA *resdata) +{ + int bytesParsed; + int componentBits; + int maxComponentValue; + size_t componentCount; + size_t compI; + int comps[4]; + // One element for each of r, g, b, a. + + descriptor += skipWhiteSpace (descriptor); + +#if 0 + // Can't use this; '#' starts a comment. + if (*descriptor == '#') + { + // "#rrggbb" + int i; + DWORD value = 0; + + descriptor++; + for (i = 0; i < 6; i++) + { + BYTE nibbleValue; + if (*descriptor >= '0' && *descriptor <= '9') + nibbleValue = *descriptor - '0'; + else if (*descriptor >= 'a' && *descriptor <= 'f') + nibbleValue = 0xa + *descriptor - 'a'; + else if (*descriptor >= 'A' && *descriptor <= 'F') + nibbleValue = 0xa + *descriptor - 'A'; + else + goto fail; + + value = (value * 16) + nibbleValue; + descriptor++; + } + + descriptor += skipWhiteSpace (descriptor); + + if (*descriptor != '\0') + log_add (log_Warning, "Junk after color resource string."); + + resdata->num = (value << 8) | 0xff; + return; + } +#endif + + // Color is of the form "rgb(r, g, b)", "rgba(r, g, b, a)", + // or "rgb15(r, g, b)". + + if (sscanf (descriptor, "rgb ( %i , %i , %i ) %n", + &comps[0], &comps[1], &comps[2], &bytesParsed) >= 3) + { + componentBits = 8; + componentCount = 3; + comps[3] = 0xff; + } + else if (sscanf (descriptor, "rgba ( %i , %i , %i , %i ) %n", + &comps[0], &comps[1], &comps[2], &comps[3], &bytesParsed) >= 4) + { + componentBits = 8; + componentCount = 4; + } + else if (sscanf (descriptor, "rgb15 ( %i , %i , %i ) %n", + &comps[0], &comps[1], &comps[2], &bytesParsed) >= 3) + { + componentBits = 5; + componentCount = 3; + comps[3] = 0xff; + } + else + goto fail; + + if (descriptor[bytesParsed] != '\0') + log_add (log_Warning, "Junk after color resource string."); + + maxComponentValue = (1 << componentBits) - 1; + + // Check the range of the components. + for (compI = 0; compI < componentCount; compI++) + { + if (comps[compI] < 0) + { + comps[compI] = 0; + log_add (log_Warning, "Color component value too small; " + "value clipped."); + } + + if (comps[compI] > (long) maxComponentValue) + { + comps[compI] = maxComponentValue; + log_add (log_Warning, "Color component value too large; " + "value clipped."); + } + } + + if (componentBits == 5) + resdata->num = ((CC5TO8 (comps[0]) << 24) | + (CC5TO8 (comps[1]) << 16) | (CC5TO8 (comps[2]) << 8) | + comps[3]); + else + resdata->num = ((comps[0] << 24) | (comps[1] << 16) | + (comps[2] << 8) | comps[3]); + + return; + +fail: + log_add (log_Error, "Invalid color description string for resource.\n"); + resdata->num = 0x00000000; +} + +static void +RawDescriptor (RESOURCE_DATA *resdata, char *buf, unsigned int size) +{ + snprintf (buf, size, "%s", resdata->str); +} + +static void +IntToString (RESOURCE_DATA *resdata, char *buf, unsigned int size) +{ + snprintf (buf, size, "%d", resdata->num); +} + + +static void +BooleanToString (RESOURCE_DATA *resdata, char *buf, unsigned int size) +{ + snprintf (buf, size, "%s", resdata->num ? "true" : "false"); +} + +static void +ColorToString (RESOURCE_DATA *resdata, char *buf, unsigned int size) +{ + if ((resdata->num & 0xff) == 0xff) + { + // Opaque color, save as "rgb". + snprintf (buf, size, "rgb(0x%02x, 0x%02x, 0x%02x)", + (resdata->num >> 24), (resdata->num >> 16) & 0xff, + (resdata->num >> 8) & 0xff); + } + else + { + // (Partially) transparent color, save as "rgba". + snprintf (buf, size, "rgba(0x%02x, 0x%02x, 0x%02x, 0x%02x)", + (resdata->num >> 24), (resdata->num >> 16) & 0xff, + (resdata->num >> 8) & 0xff, resdata->num & 0xff); + } +} + +static RESOURCE_INDEX curResourceIndex; + +void +_set_current_index_header (RESOURCE_INDEX newResourceIndex) +{ + curResourceIndex = newResourceIndex; +} + +RESOURCE_INDEX +InitResourceSystem (void) +{ + RESOURCE_INDEX ndx; + if (curResourceIndex) { + return curResourceIndex; + } + ndx = allocResourceIndex (); + + _set_current_index_header (ndx); + + InstallResTypeVectors ("UNKNOWNRES", UseDescriptorAsRes, NULL, NULL); + InstallResTypeVectors ("STRING", UseDescriptorAsRes, NULL, RawDescriptor); + InstallResTypeVectors ("INT32", DescriptorToInt, NULL, IntToString); + InstallResTypeVectors ("BOOLEAN", DescriptorToBoolean, NULL, + BooleanToString); + InstallResTypeVectors ("COLOR", DescriptorToColor, NULL, ColorToString); + InstallGraphicResTypes (); + InstallStringTableResType (); + InstallAudioResTypes (); + InstallVideoResType (); + InstallCodeResType (); + + return ndx; +} + +RESOURCE_INDEX +_get_current_index_header (void) +{ + if (!curResourceIndex) { + InitResourceSystem (); + } + return curResourceIndex; +} + +void +LoadResourceIndex (uio_DirHandle *dir, const char *rmpfile, const char *prefix) +{ + PropFile_from_filename (dir, rmpfile, process_resource_desc, prefix); +} + +void +SaveResourceIndex (uio_DirHandle *dir, const char *rmpfile, const char *root, BOOLEAN strip_root) +{ + uio_Stream *f; + CharHashTable_Iterator *it; + unsigned int prefix_len; + + f = res_OpenResFile (dir, rmpfile, "wb"); + if (!f) { + /* TODO: Warning message */ + return; + } + prefix_len = root ? strlen (root) : 0; + for (it = CharHashTable_getIterator (_get_current_index_header ()->map); + !CharHashTable_iteratorDone (it); + it = CharHashTable_iteratorNext (it)) { + char *key = CharHashTable_iteratorKey (it); + if (!root || !strncmp (root, key, prefix_len)) { + ResourceDesc *value = CharHashTable_iteratorValue (it); + if (!value) { + log_add(log_Warning, "Resource %s had no value", key); + } else if (!value->vtable) { + log_add(log_Warning, "Resource %s had no type", key); + } else if (value->vtable->toString) { + char buf[256]; + value->vtable->toString (&value->resdata, buf, 256); + buf[255]=0; + if (root && strip_root) { + WriteResFile (key+prefix_len, 1, strlen (key) - prefix_len, f); + } else { + WriteResFile (key, 1, strlen (key), f); + } + PutResFileChar(' ', f); + PutResFileChar('=', f); + PutResFileChar(' ', f); + WriteResFile (value->vtable->resType, 1, strlen (value->vtable->resType), f); + PutResFileChar(':', f); + WriteResFile (buf, 1, strlen (buf), f); + PutResFileNewline(f); + } + } + } + res_CloseResFile (f); + CharHashTable_freeIterator (it); +} + +void +UninitResourceSystem (void) +{ + freeResourceIndex (_get_current_index_header ()); + _set_current_index_header (NULL); +} + +BOOLEAN +InstallResTypeVectors (const char *resType, ResourceLoadFun *loadFun, + ResourceFreeFun *freeFun, ResourceStringFun *stringFun) +{ + ResourceHandlers *handlers; + ResourceDesc *result; + char key[TYPESIZ]; + int typelen; + CharHashTable_HashTable *map; + + snprintf(key, TYPESIZ, "sys.%s", resType); + key[TYPESIZ-1] = '\0'; + typelen = strlen(resType); + + handlers = HMalloc (sizeof (ResourceHandlers)); + if (handlers == NULL) + { + return FALSE; + } + handlers->loadFun = loadFun; + handlers->freeFun = freeFun; + handlers->toString = stringFun; + handlers->resType = resType; + + result = HMalloc (sizeof (ResourceDesc)); + if (result == NULL) + return FALSE; + + result->fname = HMalloc (strlen(resType) + 1); + strncpy (result->fname, resType, typelen); + result->fname[typelen] = '\0'; + result->vtable = NULL; + result->resdata.ptr = handlers; + + map = _get_current_index_header ()->map; + return CharHashTable_add (map, key, result) != 0; +} + +/* These replace the mapres.c calls and probably should be split out at some point. */ +BOOLEAN +res_IsString (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + return desc && !strcmp(desc->vtable->resType, "STRING"); +} + +const char * +res_GetString (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + if (!desc || !desc->resdata.str || strcmp(desc->vtable->resType, "STRING")) + return ""; + /* TODO: Work out exact STRING semantics, specifically, the lifetime of + * the returned value. If caller is allowed to reference the returned + * value forever, STRING has to be ref-counted. */ + return desc->resdata.str; +} + +void +res_PutString (const char *key, const char *value) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + int srclen, dstlen; + if (!desc || !desc->resdata.str || strcmp(desc->vtable->resType, "STRING")) + { + /* TODO: This is kind of roundabout. We can do better by refactoring newResourceDesc */ + process_resource_desc(key, "STRING:undefined"); + desc = lookupResourceDesc (idx, key); + } + srclen = strlen (value); + dstlen = strlen (desc->fname); + if (srclen > dstlen) { + char *newValue = HMalloc(srclen + 1); + char *oldValue = desc->fname; + log_add(log_Warning, "Reallocating string space for '%s'", key); + strncpy (newValue, value, srclen + 1); + desc->resdata.str = newValue; + desc->fname = newValue; + HFree (oldValue); + } else { + strncpy (desc->fname, value, dstlen + 1); + } +} + +BOOLEAN +res_IsInteger (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + return desc && !strcmp(desc->vtable->resType, "INT32"); +} + +int +res_GetInteger (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + if (!desc || strcmp(desc->vtable->resType, "INT32")) + { + // TODO: Better error handling + return 0; + } + return desc->resdata.num; +} + +void +res_PutInteger (const char *key, int value) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + if (!desc || strcmp(desc->vtable->resType, "INT32")) + { + /* TODO: This is kind of roundabout. We can do better by refactoring newResourceDesc */ + process_resource_desc(key, "INT32:0"); + desc = lookupResourceDesc (idx, key); + } + desc->resdata.num = value; +} + +BOOLEAN +res_IsBoolean (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + return desc && !strcmp(desc->vtable->resType, "BOOLEAN"); +} + +BOOLEAN +res_GetBoolean (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + if (!desc || strcmp(desc->vtable->resType, "BOOLEAN")) + { + // TODO: Better error handling + return FALSE; + } + return desc->resdata.num ? TRUE : FALSE; +} + +void +res_PutBoolean (const char *key, BOOLEAN value) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + if (!desc || strcmp(desc->vtable->resType, "BOOLEAN")) + { + /* TODO: This is kind of roundabout. We can do better by refactoring newResourceDesc */ + process_resource_desc(key, "BOOLEAN:false"); + desc = lookupResourceDesc (idx, key); + } + desc->resdata.num = value; +} + +BOOLEAN +res_IsColor (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + return desc && !strcmp(desc->vtable->resType, "COLOR"); +} + +Color +res_GetColor (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + DWORD num; + if (!desc || strcmp(desc->vtable->resType, "COLOR")) + { + // TODO: Better error handling + return buildColorRgba (0, 0, 0, 0); + } + + num = desc->resdata.num; + return buildColorRgba (num >> 24, (num >> 16) & 0xff, + (desc->resdata.num >> 8) & 0xff, num & 0xff); +} + +void +res_PutColor (const char *key, Color value) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + ResourceDesc *desc = lookupResourceDesc (idx, key); + if (!desc || strcmp(desc->vtable->resType, "COLOR")) + { + /* TODO: This is kind of roundabout. We can do better by refactoring + * newResourceDesc */ + process_resource_desc(key, "COLOR:rgb(0, 0, 0)"); + desc = lookupResourceDesc (idx, key); + } + desc->resdata.num = + (value.r << 24) | (value.g << 16) | (value.b << 8) | value.a; +} + +BOOLEAN +res_HasKey (const char *key) +{ + RESOURCE_INDEX idx = _get_current_index_header (); + return (lookupResourceDesc(idx, key) != NULL); +} + +BOOLEAN +res_Remove (const char *key) +{ + CharHashTable_HashTable *map = _get_current_index_header ()->map; + ResourceDesc *oldDesc = (ResourceDesc *)CharHashTable_find (map, key); + if (oldDesc != NULL) + { + if (oldDesc->resdata.ptr != NULL) + { + if (oldDesc->refcount > 0) + log_add (log_Warning, "WARNING: Replacing '%s' while it is live", key); + if (oldDesc->vtable && oldDesc->vtable->freeFun) + { + oldDesc->vtable->freeFun(oldDesc->resdata.ptr); + } + } + HFree (oldDesc->fname); + HFree (oldDesc); + } + return CharHashTable_remove (map, key); +} diff --git a/src/libs/resource/resintrn.h b/src/libs/resource/resintrn.h new file mode 100644 index 0000000..e2255ea --- /dev/null +++ b/src/libs/resource/resintrn.h @@ -0,0 +1,34 @@ +//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 LIBS_RESOURCE_RESINTRN_H_ +#define LIBS_RESOURCE_RESINTRN_H_ + +#include +#include "libs/reslib.h" +#include "index.h" + +ResourceDesc *lookupResourceDesc (RESOURCE_INDEX idx, RESOURCE res); +void loadResourceDesc (ResourceDesc *desc); + +void _set_current_index_header (RESOURCE_INDEX newResourceIndex); +RESOURCE_INDEX _get_current_index_header (void); + + +#endif /* LIBS_RESOURCE_RESINTRN_H_ */ + diff --git a/src/libs/resource/stringbank.c b/src/libs/resource/stringbank.c new file mode 100644 index 0000000..a1b9576 --- /dev/null +++ b/src/libs/resource/stringbank.c @@ -0,0 +1,181 @@ +/* stringbank.c, Copyright (c) 2005 Michael C. Martin */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope thta it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Se the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#include + +#include "stringbank.h" + +typedef stringbank chunk; + +static stringbank * +add_chunk (stringbank *bank) +{ + stringbank *n = malloc (sizeof (stringbank)); + n->len = 0; + n->next = NULL; + if (bank) + { + while (bank->next) + bank = bank->next; + bank->next = n; + } + return n; +} + +stringbank * +StringBank_Create (void) +{ + return add_chunk (NULL); +} + +void +StringBank_Free (stringbank *bank) +{ + if (bank) + { + StringBank_Free (bank->next); + free (bank); + } +} + +const char * +StringBank_AddString (stringbank *bank, const char *str) +{ + unsigned int len = strlen (str) + 1; + stringbank *x = bank; + if (len > STRBANK_CHUNK_SIZE) + return NULL; + while (x) { + unsigned int remaining = STRBANK_CHUNK_SIZE - x->len; + if (len < remaining) { + char *result = x->data + x->len; + strcpy (result, str); + x->len += len; + return result; + } + x = x->next; + } + /* No room in any currently existing chunk */ + x = add_chunk (bank); + strcpy (x->data, str); + x->len += len; + return x->data; +} + +const char * +StringBank_AddOrFindString (stringbank *bank, const char *str) +{ + unsigned int len = strlen (str) + 1; + stringbank *x = bank; + if (len > STRBANK_CHUNK_SIZE) + return NULL; + while (x) { + int i = 0; + while (i < x->len) { + if (!strcmp (x->data + i, str)) + return x->data + i; + while (x->data[i]) i++; + i++; + } + x = x->next; + } + /* We didn't find it, so add it */ + return StringBank_AddString (bank, str); +} + +static char buffer[STRBANK_CHUNK_SIZE]; + +const char * +StringBank_AddSubstring (stringbank *bank, const char *str, unsigned int n) +{ + unsigned int len = strlen (str); + if (n > len) + { + return StringBank_AddString (bank, str); + } + if (n >= STRBANK_CHUNK_SIZE) + { + return NULL; + } + strncpy (buffer, str, n); + buffer[n] = '\0'; + return StringBank_AddString(bank, buffer); +} + +const char * +StringBank_AddOrFindSubstring (stringbank *bank, const char *str, unsigned int n) +{ + unsigned int len = strlen (str); + if (n > len) + { + return StringBank_AddOrFindString (bank, str); + } + if (n >= STRBANK_CHUNK_SIZE) + { + return NULL; + } + strncpy (buffer, str, n); + buffer[n] = '\0'; + return StringBank_AddOrFindString(bank, buffer); +} + +int +SplitString (const char *s, char splitchar, int n, const char **result, stringbank *bank) +{ + int i; + const char *index = s; + + for (i = 0; i < n-1; i++) + { + const char *next; + int len; + + next = strchr (index, splitchar); + if (!next) + { + break; + } + + len = next - index; + result[i] = StringBank_AddOrFindSubstring (bank, index, len); + index = next+1; + } + result[i] = StringBank_AddOrFindString (bank, index); + return i+1; +} + +#ifdef SB_DEBUG + +void +StringBank_Dump (stringbank *bank, FILE *s) +{ + stringbank *x = bank; + while (x) { + int i = 0; + while (i < x->len) { + fprintf (s, "\"%s\"\n", x->data + i); + while (x->data[i]) i++; + i++; + } + x = x->next; + } +} + +#endif diff --git a/src/libs/resource/stringbank.h b/src/libs/resource/stringbank.h new file mode 100644 index 0000000..e77105b --- /dev/null +++ b/src/libs/resource/stringbank.h @@ -0,0 +1,57 @@ +/* stringbank.h, Copyright (c) 2005 Michael C. Martin */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope thta it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Se the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_RESOURCE_STRINGBANK_H_ +#define LIBS_RESOURCE_STRINGBANK_H_ + +#ifdef SB_DEBUG +#include +#endif + +#define STRBANK_CHUNK_SIZE (1024 - sizeof (void *) - sizeof (int)) + +typedef struct _stringbank_chunk { + char data[STRBANK_CHUNK_SIZE]; + int len; + struct _stringbank_chunk *next; +} stringbank; + +/* Constructors and destructors */ +stringbank *StringBank_Create (void); +void StringBank_Free (stringbank *bank); + +/* Put str or n chars after str into the string bank. */ +const char *StringBank_AddString (stringbank *bank, const char *str); +const char *StringBank_AddSubstring (stringbank *bank, const char *str, unsigned int n); + +/* Put str or n chars after str into the string bank if it's not already + there. Much slower. */ +const char *StringBank_AddOrFindString (stringbank *bank, const char *str); +const char *StringBank_AddOrFindSubstring (stringbank *bank, const char *str, unsigned int n); + +/* Split a string s into at most n substrings, separated by splitchar. + Pointers to these substrings will be stored in result; the + substrings themselves will be filed in the specified stringbank. */ +int SplitString (const char *s, char splitchar, int n, const char **result, stringbank *bank); + +#ifdef SB_DEBUG +/* Print out a list of the contents of the string bank to the named stream. */ +void StringBank_Dump (stringbank *bank, FILE *s); +#endif /* SB_DEBUG */ + +#endif /* LIBS_RESOURCE_STRINGBANK_H_ */ diff --git a/src/libs/sndlib.h b/src/libs/sndlib.h new file mode 100644 index 0000000..e900707 --- /dev/null +++ b/src/libs/sndlib.h @@ -0,0 +1,107 @@ +//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 LIBS_SNDLIB_H_ +#define LIBS_SNDLIB_H_ + +#include "port.h" +#include "libs/strlib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef STRING_TABLE SOUND_REF; +typedef STRING SOUND; +// SOUNDPTR is really a TFB_SoundSample** +typedef void *SOUNDPTR; + +typedef struct soundposition +{ + BOOLEAN positional; + int x, y; +} SoundPosition; + +#define InitSoundResources InitStringTableResources +#define CaptureSound CaptureStringTable +#define ReleaseSound ReleaseStringTable +#define GetSoundRef GetStringTable +#define GetSoundCount GetStringTableCount +#define GetSoundIndex GetStringTableIndex +#define SetAbsSoundIndex SetAbsStringTableIndex +#define SetRelSoundIndex SetRelStringTableIndex + +extern SOUNDPTR GetSoundAddress (SOUND sound); + +typedef struct tfb_soundsample TFB_SoundSample; +typedef TFB_SoundSample **MUSIC_REF; + +extern BOOLEAN InitSound (int argc, char *argv[]); +extern void UninitSound (void); +extern SOUND_REF LoadSoundFile (const char *pStr); +extern MUSIC_REF LoadMusicFile (const char *pStr); +extern BOOLEAN InstallAudioResTypes (void); +extern SOUND_REF LoadSoundInstance (RESOURCE res); +extern MUSIC_REF LoadMusicInstance (RESOURCE res); +extern BOOLEAN DestroySound (SOUND_REF SoundRef); +extern BOOLEAN DestroyMusic (MUSIC_REF MusicRef); + +#define MAX_CHANNELS 8 +#define MAX_VOLUME 255 +#define NORMAL_VOLUME 160 + +#define FIRST_SFX_CHANNEL 0 +#define MIN_FX_CHANNEL 1 +#define NUM_FX_CHANNELS 4 +#define LAST_SFX_CHANNEL (MIN_FX_CHANNEL + NUM_FX_CHANNELS - 1) +#define NUM_SFX_CHANNELS (MIN_FX_CHANNEL + NUM_FX_CHANNELS) + +extern void PLRPlaySong (MUSIC_REF MusicRef, BOOLEAN Continuous, BYTE + Priority); +extern void PLRStop (MUSIC_REF MusicRef); +extern BOOLEAN PLRPlaying (MUSIC_REF MusicRef); +extern void PLRSeek (MUSIC_REF MusicRef, DWORD pos); +extern void PLRPause (MUSIC_REF MusicRef); +extern void PLRResume (MUSIC_REF MusicRef); +extern void snd_PlaySpeech (MUSIC_REF SpeechRef); +extern void snd_StopSpeech (void); +extern void PlayChannel (COUNT channel, SOUND snd, SoundPosition pos, + void *positional_object, unsigned char priority); +extern BOOLEAN ChannelPlaying (COUNT Channel); +extern void * GetPositionalObject (COUNT channel); +extern void SetPositionalObject (COUNT channel, void *positional_object); +extern void UpdateSoundPosition (COUNT channel, SoundPosition pos); +extern void StopChannel (COUNT Channel, BYTE Priority); +extern void SetMusicVolume (COUNT Volume); +extern void SetChannelVolume (COUNT Channel, COUNT Volume, BYTE + Priority); + +extern void StopSound (void); +extern BOOLEAN SoundPlaying (void); + +extern void WaitForSoundEnd (COUNT Channel); +#define TFBSOUND_WAIT_ALL ((COUNT)~0) + +extern DWORD FadeMusic (BYTE end_vol, SIZE TimeInterval); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_SNDLIB_H_ */ + diff --git a/src/libs/sound/Makeinfo b/src/libs/sound/Makeinfo new file mode 100644 index 0000000..28ee5cc --- /dev/null +++ b/src/libs/sound/Makeinfo @@ -0,0 +1,9 @@ +if [ "$uqm_SOUNDMODULE" = "openal" ]; then + uqm_SUBDIRS="openal mixer decoders" + uqm_CFLAGS="$uqm_CFLAGS -DHAVE_OPENAL" +else + uqm_SUBDIRS="mixer decoders" +fi + +uqm_CFILES="audiocore.c fileinst.c resinst.c sound.c sfx.c music.c stream.c trackplayer.c" +uqm_HFILES="audiocore.h sndintrn.h sound.h stream.h trackint.h trackplayer.h" diff --git a/src/libs/sound/audiocore.c b/src/libs/sound/audiocore.c new file mode 100644 index 0000000..440f63f --- /dev/null +++ b/src/libs/sound/audiocore.c @@ -0,0 +1,272 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Audio Core API (derived from OpenAL) + */ + +#include +#include +#include "audiocore.h" +#include "sound.h" +#include "libs/log.h" + +static audio_Driver audiodrv; + +/* The globals that control the sound drivers. */ +int snddriver, soundflags; + +volatile bool audio_inited = false; + +/* + * Declarations for driver init funcs + */ + +#ifdef HAVE_OPENAL +sint32 openAL_Init (audio_Driver *driver, sint32 flags); +#endif +sint32 mixSDL_Init (audio_Driver *driver, sint32 flags); +sint32 noSound_Init (audio_Driver *driver, sint32 flags); + + +/* + * Initialization + */ + +sint32 +initAudio (sint32 driver, sint32 flags) +{ + sint32 ret; + +#ifdef HAVE_OPENAL + if (driver == audio_DRIVER_MIXSDL) + ret = mixSDL_Init (&audiodrv, flags); + else if (driver == audio_DRIVER_OPENAL) + ret = openAL_Init (&audiodrv, flags); + else + ret = noSound_Init (&audiodrv, flags); +#else + if (driver == audio_DRIVER_OPENAL) + { + log_add (log_Warning, "OpenAL driver not compiled in, so using MixSDL"); + driver = audio_DRIVER_MIXSDL; + } + if (driver == audio_DRIVER_MIXSDL) + ret = mixSDL_Init (&audiodrv, flags); + else + ret = noSound_Init (&audiodrv, flags); +#endif + + if (ret != 0) + { + log_add (log_Fatal, "Sound driver initialization failed.\n" + "This may happen when a soundcard is " + "not present or not available.\n" + "NOTICE: Try running UQM with '--sound=none' option"); + exit (EXIT_FAILURE); + } + + SetSFXVolume (sfxVolumeScale); + SetSpeechVolume (speechVolumeScale); + SetMusicVolume (musicVolume); + + audio_inited = true; + + return ret; +} + +void +unInitAudio (void) +{ + if (!audio_inited) + return; + + audio_inited = false; + audiodrv.Uninitialize (); +} + + +/* + * General + */ + +sint32 +audio_GetError (void) +{ + return audiodrv.GetError (); +} + + +/* + * Sources + */ + +void +audio_GenSources (uint32 n, audio_Object *psrcobj) +{ + audiodrv.GenSources (n, psrcobj); +} + +void +audio_DeleteSources (uint32 n, audio_Object *psrcobj) +{ + audiodrv.DeleteSources (n, psrcobj); +} + +bool +audio_IsSource (audio_Object srcobj) +{ + return audiodrv.IsSource (srcobj); +} + +void +audio_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value) + +{ + audiodrv.Sourcei (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value) +{ + audiodrv.Sourcef (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + audiodrv.Sourcefv (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value) +{ + audiodrv.GetSourcei (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + audiodrv.GetSourcef (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_SourceRewind (audio_Object srcobj) +{ + audiodrv.SourceRewind (srcobj); +} + +void +audio_SourcePlay (audio_Object srcobj) +{ + audiodrv.SourcePlay (srcobj); +} + +void +audio_SourcePause (audio_Object srcobj) +{ + audiodrv.SourcePause (srcobj); +} + +void +audio_SourceStop (audio_Object srcobj) +{ + audiodrv.SourceStop (srcobj); +} + +void +audio_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + audiodrv.SourceQueueBuffers (srcobj, n, pbufobj); +} + +void +audio_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + audiodrv.SourceUnqueueBuffers (srcobj, n, pbufobj); +} + + +/* + * Buffers + */ + +void +audio_GenBuffers (uint32 n, audio_Object *pbufobj) +{ + audiodrv.GenBuffers (n, pbufobj); +} + +void +audio_DeleteBuffers (uint32 n, audio_Object *pbufobj) +{ + audiodrv.DeleteBuffers (n, pbufobj); +} + +bool +audio_IsBuffer (audio_Object bufobj) +{ + return audiodrv.IsBuffer (bufobj); +} + +void +audio_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value) +{ + audiodrv.GetBufferi (bufobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + audiodrv.BufferData (bufobj, audiodrv.EnumLookup[format], data, size, + freq); +} + +bool +audio_GetFormatInfo (uint32 format, int *channels, int *sample_size) +{ + switch (format) + { + case audio_FORMAT_MONO8: + *channels = 1; + *sample_size = sizeof (uint8); + return true; + + case audio_FORMAT_STEREO8: + *channels = 2; + *sample_size = sizeof (uint8); + return true; + + case audio_FORMAT_MONO16: + *channels = 1; + *sample_size = sizeof (sint16); + return true; + + case audio_FORMAT_STEREO16: + *channels = 2; + *sample_size = sizeof (sint16); + return true; + } + return false; +} diff --git a/src/libs/sound/audiocore.h b/src/libs/sound/audiocore.h new file mode 100644 index 0000000..6f48b26 --- /dev/null +++ b/src/libs/sound/audiocore.h @@ -0,0 +1,169 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Audio Core API (derived from OpenAL) + */ + +#ifndef LIBS_SOUND_AUDIOCORE_H_ +#define LIBS_SOUND_AUDIOCORE_H_ + +#include "config.h" +#include "types.h" + + +/* Available drivers */ +enum +{ + audio_DRIVER_MIXSDL, + audio_DRIVER_NOSOUND, + audio_DRIVER_OPENAL +}; + +/* Initialization flags */ +#define audio_QUALITY_HIGH (1 << 0) +#define audio_QUALITY_MEDIUM (1 << 1) +#define audio_QUALITY_LOW (1 << 2) + + +/* Interface Types */ +typedef uintptr_t audio_Object; +typedef intptr_t audio_IntVal; +typedef const sint32 audio_SourceProp; +typedef const sint32 audio_BufferProp; + +enum +{ + /* Errors */ + audio_NO_ERROR = 0, + audio_INVALID_NAME, + audio_INVALID_ENUM, + audio_INVALID_VALUE, + audio_INVALID_OPERATION, + audio_OUT_OF_MEMORY, + audio_DRIVER_FAILURE, + + /* Source properties */ + audio_POSITION, + audio_LOOPING, + audio_BUFFER, + audio_GAIN, + audio_SOURCE_STATE, + audio_BUFFERS_QUEUED, + audio_BUFFERS_PROCESSED, + + /* Source state information */ + audio_INITIAL, + audio_STOPPED, + audio_PLAYING, + audio_PAUSED, + + /* Sound buffer properties */ + audio_FREQUENCY, + audio_BITS, + audio_CHANNELS, + audio_SIZE, + audio_FORMAT_MONO16, + audio_FORMAT_STEREO16, + audio_FORMAT_MONO8, + audio_FORMAT_STEREO8, + audio_ENUM_SIZE +}; + +extern int snddriver, soundflags; + +typedef struct { + /* General */ + void (* Uninitialize) (void); + sint32 (* GetError) (void); + sint32 driverID; + sint32 EnumLookup[audio_ENUM_SIZE]; + + /* Sources */ + void (* GenSources) (uint32 n, audio_Object *psrcobj); + void (* DeleteSources) (uint32 n, audio_Object *psrcobj); + bool (* IsSource) (audio_Object srcobj); + void (* Sourcei) (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); + void (* Sourcef) (audio_Object srcobj, audio_SourceProp pname, + float value); + void (* Sourcefv) (audio_Object srcobj, audio_SourceProp pname, + float *value); + void (* GetSourcei) (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); + void (* GetSourcef) (audio_Object srcobj, audio_SourceProp pname, + float *value); + void (* SourceRewind) (audio_Object srcobj); + void (* SourcePlay) (audio_Object srcobj); + void (* SourcePause) (audio_Object srcobj); + void (* SourceStop) (audio_Object srcobj); + void (* SourceQueueBuffers) (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + void (* SourceUnqueueBuffers) (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + + /* Buffers */ + void (* GenBuffers) (uint32 n, audio_Object *pbufobj); + void (* DeleteBuffers) (uint32 n, audio_Object *pbufobj); + bool (* IsBuffer) (audio_Object bufobj); + void (* GetBufferi) (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); + void (* BufferData) (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); +} audio_Driver; + + +/* Initialization */ +sint32 initAudio (sint32 driver, sint32 flags); +void unInitAudio (void); + +/* General */ +sint32 audio_GetError (void); + +/* Sources */ +void audio_GenSources (uint32 n, audio_Object *psrcobj); +void audio_DeleteSources (uint32 n, audio_Object *psrcobj); +bool audio_IsSource (audio_Object srcobj); +void audio_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); +void audio_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value); +void audio_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value); +void audio_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); +void audio_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value); +void audio_SourceRewind (audio_Object srcobj); +void audio_SourcePlay (audio_Object srcobj); +void audio_SourcePause (audio_Object srcobj); +void audio_SourceStop (audio_Object srcobj); +void audio_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); +void audio_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + +/* Buffers */ +void audio_GenBuffers (uint32 n, audio_Object *pbufobj); +void audio_DeleteBuffers (uint32 n, audio_Object *pbufobj); +bool audio_IsBuffer (audio_Object bufobj); +void audio_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); +void audio_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + +bool audio_GetFormatInfo (uint32 format, int *channels, int *sample_size); + +#endif /* LIBS_SOUND_AUDIOCORE_H_ */ diff --git a/src/libs/sound/decoders/Makeinfo b/src/libs/sound/decoders/Makeinfo new file mode 100644 index 0000000..e1735a1 --- /dev/null +++ b/src/libs/sound/decoders/Makeinfo @@ -0,0 +1,8 @@ +uqm_CFILES="decoder.c aiffaud.c wav.c dukaud.c modaud.c" +uqm_HFILES="aiffaud.h decoder.h dukaud.h modaud.h wav.h" + +if [ "$uqm_OGGVORBIS" '!=' "none" ]; then + uqm_CFILES="$uqm_CFILES oggaud.c" + uqm_HFILES="$uqm_HFILES oggaud.h" +fi + diff --git a/src/libs/sound/decoders/aiffaud.c b/src/libs/sound/decoders/aiffaud.c new file mode 100644 index 0000000..102a78e --- /dev/null +++ b/src/libs/sound/decoders/aiffaud.c @@ -0,0 +1,650 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Portions (C) Serge van den Boom (svdb at stack.nl) */ +/* Portions (C) Alex Volkov (codepro at usa.net) */ + +/* AIFF decoder (.aif) + * + * Doesn't work on *all* aiff files in general, only 8/16 PCM and + * 16-bit AIFF-C SDX2-compressed. + */ + +#include +#include + // for abs() +#include +#ifndef _WIN32_WCE +# include +#endif +#include +#include "port.h" +#include "types.h" +#include "libs/uio.h" +#include "endian_uqm.h" +#include "libs/log.h" +#include "aiffaud.h" + +typedef uint32 aiff_ID; + +#define aiff_MAKE_ID(x1, x2, x3, x4) \ + (((x1) << 24) | ((x2) << 16) | ((x3) << 8) | (x4)) + +#define aiff_FormID aiff_MAKE_ID('F', 'O', 'R', 'M') +#define aiff_FormVersionID aiff_MAKE_ID('F', 'V', 'E', 'R') +#define aiff_CommonID aiff_MAKE_ID('C', 'O', 'M', 'M') +#define aiff_SoundDataID aiff_MAKE_ID('S', 'S', 'N', 'D') + +#define aiff_FormTypeAIFF aiff_MAKE_ID('A', 'I', 'F', 'F') +#define aiff_FormTypeAIFC aiff_MAKE_ID('A', 'I', 'F', 'C') + +#define aiff_CompressionTypeSDX2 aiff_MAKE_ID('S', 'D', 'X', '2') + + +typedef struct +{ + aiff_ID id; + uint32 size; +} aiff_ChunkHeader; + +#define AIFF_CHUNK_HDR_SIZE (4+4) + +typedef struct +{ + aiff_ChunkHeader chunk; + aiff_ID type; +} aiff_FileHeader; + +typedef struct +{ + uint32 version; /* format version, in Mac format */ +} aiff_FormatVersionChunk; + +typedef struct +{ + uint16 channels; /* number of channels */ + uint32 sampleFrames; /* number of sample frames */ + uint16 sampleSize; /* number of bits per sample */ + sint32 sampleRate; /* number of frames per second */ + /* this is actually stored as IEEE-754 80bit in files */ +} aiff_CommonChunk; + +#define AIFF_COMM_SIZE (2+4+2+10) + +typedef struct +{ + uint16 channels; /* number of channels */ + uint32 sampleFrames; /* number of sample frames */ + uint16 sampleSize; /* number of bits per sample */ + sint32 sampleRate; /* number of frames per second */ + aiff_ID extTypeID; /* compression type ID */ + char extName[32]; /* compression type name */ +} aiff_ExtCommonChunk; + +#define AIFF_EXT_COMM_SIZE (AIFF_COMM_SIZE+4) + +typedef struct +{ + uint32 offset; /* offset to sound data */ + uint32 blockSize; /* size of alignment blocks */ +} aiff_SoundDataChunk; + +#define AIFF_SSND_SIZE (4+4) + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* aifa_GetName (void); +static bool aifa_InitModule (int flags, const TFB_DecoderFormats*); +static void aifa_TermModule (void); +static uint32 aifa_GetStructSize (void); +static int aifa_GetError (THIS_PTR); +static bool aifa_Init (THIS_PTR); +static void aifa_Term (THIS_PTR); +static bool aifa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void aifa_Close (THIS_PTR); +static int aifa_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 aifa_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 aifa_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs aifa_DecoderVtbl = +{ + aifa_GetName, + aifa_InitModule, + aifa_TermModule, + aifa_GetStructSize, + aifa_GetError, + aifa_Init, + aifa_Term, + aifa_Open, + aifa_Close, + aifa_Decode, + aifa_Seek, + aifa_GetFrame, +}; + + +typedef enum +{ + aifc_None, + aifc_Sdx2, +} aiff_CompressionType; + +#define MAX_CHANNELS 4 + +typedef struct tfb_wavesounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + sint32 last_error; + uio_Stream *fp; + aiff_ExtCommonChunk fmtHdr; + aiff_CompressionType comp_type; + unsigned bits_per_sample; + unsigned block_align; + unsigned file_block; + uint32 data_ofs; + uint32 data_size; + uint32 max_pcm; + uint32 cur_pcm; + sint32 prev_val[MAX_CHANNELS]; + +} TFB_AiffSoundDecoder; + +static const TFB_DecoderFormats* aifa_formats = NULL; + +static int aifa_DecodePCM (TFB_AiffSoundDecoder*, void* buf, sint32 bufsize); +static int aifa_DecodeSDX2 (TFB_AiffSoundDecoder*, void* buf, sint32 bufsize); + + +static const char* +aifa_GetName (void) +{ + return "AIFF"; +} + +static bool +aifa_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + aifa_formats = fmts; + return true; + + (void)flags; // laugh at compiler warning +} + +static void +aifa_TermModule (void) +{ + // no specific module term +} + +static uint32 +aifa_GetStructSize (void) +{ + return sizeof (TFB_AiffSoundDecoder); +} + +static int +aifa_GetError (THIS_PTR) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + int ret = aifa->last_error; + aifa->last_error = 0; + return ret; +} + +static bool +aifa_Init (THIS_PTR) +{ + //TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + This->need_swap = !aifa_formats->want_big_endian; + return true; +} + +static void +aifa_Term (THIS_PTR) +{ + //TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + aifa_Close (This); // ensure cleanup +} + +static bool +read_be_16 (uio_Stream *fp, uint16 *v) +{ + if (!uio_fread (v, sizeof(*v), 1, fp)) + return false; + *v = UQM_SwapBE16 (*v); + return true; +} + +static bool +read_be_32 (uio_Stream *fp, uint32 *v) +{ + if (!uio_fread (v, sizeof(*v), 1, fp)) + return false; + *v = UQM_SwapBE32 (*v); + return true; +} + +// Read 80-bit IEEE 754 floating point number. +// We are only interested in values that we can work with, +// so using an sint32 here is fine. +static bool +read_be_f80 (uio_Stream *fp, sint32 *v) +{ + int sign, exp; + int shift; + uint16 se; + uint32 mant, mant_low; + if (!read_be_16 (fp, &se) || + !read_be_32 (fp, &mant) || !read_be_32 (fp, &mant_low)) + return false; + + sign = (se >> 15) & 1; // sign is the highest bit + exp = (se & ((1 << 15) - 1)); // exponent is next highest 15 bits +#if 0 // XXX: 80bit IEEE 754 used in AIFF uses explicit mantissa MS bit + // mantissa has an implied leading bit which is typically 1 + mant >>= 1; + if (exp != 0) + mant |= 0x80000000; +#endif + mant >>= 1; // we also need space for sign + exp -= (1 << 14) - 1; // exponent is biased by (2^(e-1) - 1) + shift = exp - 31 + 1; // mantissa is already 31 bits before decimal pt. + if (shift > 0) + mant = 0x7fffffff; // already too big + else if (shift < 0) + mant >>= -shift; + + *v = sign ? -(sint32)mant : (sint32)mant; + + return true; +} + +static bool +aifa_readFileHeader (TFB_AiffSoundDecoder* aifa, aiff_FileHeader* hdr) +{ + if (!read_be_32 (aifa->fp, &hdr->chunk.id) || + !read_be_32 (aifa->fp, &hdr->chunk.size) || + !read_be_32 (aifa->fp, &hdr->type)) + { + aifa->last_error = errno; + return false; + } + return true; +} + +static bool +aifa_readChunkHeader (TFB_AiffSoundDecoder* aifa, aiff_ChunkHeader* hdr) +{ + if (!read_be_32 (aifa->fp, &hdr->id) || + !read_be_32 (aifa->fp, &hdr->size)) + { + aifa->last_error = errno; + return false; + } + return true; +} + +static int +aifa_readCommonChunk (TFB_AiffSoundDecoder* aifa, uint32 size, + aiff_ExtCommonChunk* fmt) +{ + int bytes; + + memset(fmt, 0, sizeof(*fmt)); + if (size < AIFF_COMM_SIZE) + { + aifa->last_error = aifae_BadFile; + return 0; + } + + if (!read_be_16 (aifa->fp, &fmt->channels) || + !read_be_32 (aifa->fp, &fmt->sampleFrames) || + !read_be_16 (aifa->fp, &fmt->sampleSize) || + !read_be_f80 (aifa->fp, &fmt->sampleRate)) + { + aifa->last_error = errno; + return 0; + } + bytes = AIFF_COMM_SIZE; + + if (size >= AIFF_EXT_COMM_SIZE) + { + if (!read_be_32 (aifa->fp, &fmt->extTypeID)) + { + aifa->last_error = errno; + return 0; + } + bytes += sizeof(fmt->extTypeID); + } + + return bytes; +} + +static bool +aifa_readSoundDataChunk (TFB_AiffSoundDecoder* aifa, + aiff_SoundDataChunk* data) +{ + if (!read_be_32 (aifa->fp, &data->offset) || + !read_be_32 (aifa->fp, &data->blockSize)) + { + aifa->last_error = errno; + return false; + } + return true; +} + +static bool +aifa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + aiff_FileHeader fileHdr; + aiff_ChunkHeader chunkHdr; + sint32 remSize; + + aifa->fp = uio_fopen (dir, filename, "rb"); + if (!aifa->fp) + { + aifa->last_error = errno; + return false; + } + + aifa->data_size = 0; + aifa->max_pcm = 0; + aifa->data_ofs = 0; + memset(&aifa->fmtHdr, 0, sizeof(aifa->fmtHdr)); + memset(aifa->prev_val, 0, sizeof(aifa->prev_val)); + + // read wave header + if (!aifa_readFileHeader (aifa, &fileHdr)) + { + aifa->last_error = errno; + aifa_Close (This); + return false; + } + if (fileHdr.chunk.id != aiff_FormID) + { + log_add (log_Warning, "aifa_Open(): not an aiff file, ID 0x%08x", + fileHdr.chunk.id); + aifa_Close (This); + return false; + } + if (fileHdr.type != aiff_FormTypeAIFF && fileHdr.type != aiff_FormTypeAIFC) + { + log_add (log_Warning, "aifa_Open(): unsupported aiff file" + ", Type 0x%08x", fileHdr.type); + aifa_Close (This); + return false; + } + + for (remSize = fileHdr.chunk.size - sizeof(aiff_ID); remSize > 0; + remSize -= ((chunkHdr.size + 1) & ~1) + AIFF_CHUNK_HDR_SIZE) + { + if (!aifa_readChunkHeader (aifa, &chunkHdr)) + { + aifa_Close (This); + return false; + } + + if (chunkHdr.id == aiff_CommonID) + { + int read = aifa_readCommonChunk (aifa, chunkHdr.size, &aifa->fmtHdr); + if (!read) + { + aifa_Close (This); + return false; + } + uio_fseek (aifa->fp, chunkHdr.size - read, SEEK_CUR); + } + else if (chunkHdr.id == aiff_SoundDataID) + { + aiff_SoundDataChunk data; + if (!aifa_readSoundDataChunk (aifa, &data)) + { + aifa_Close (This); + return false; + } + aifa->data_ofs = uio_ftell (aifa->fp) + data.offset; + uio_fseek (aifa->fp, chunkHdr.size - AIFF_SSND_SIZE, SEEK_CUR); + } + else + { // skip uninteresting chunk + uio_fseek (aifa->fp, chunkHdr.size, SEEK_CUR); + } + + // 2-align the file ptr + uio_fseek (aifa->fp, chunkHdr.size & 1, SEEK_CUR); + } + + if (aifa->fmtHdr.sampleFrames == 0) + { + log_add (log_Warning, "aifa_Open(): aiff file has no sound data"); + aifa_Close (This); + return false; + } + + // make bits-per-sample a multiple of 8 + aifa->bits_per_sample = (aifa->fmtHdr.sampleSize + 7) & ~7; + if (aifa->bits_per_sample == 0 || aifa->bits_per_sample > 16) + { // XXX: for now we do not support 24 and 32 bps + log_add (log_Warning, "aifa_Open(): unsupported sample size %u", + aifa->bits_per_sample); + aifa_Close (This); + return false; + } + if (aifa->fmtHdr.channels != 1 && aifa->fmtHdr.channels != 2) + { + log_add (log_Warning, "aifa_Open(): unsupported number of channels %u", + (unsigned)aifa->fmtHdr.channels); + aifa_Close (This); + return false; + } + if (aifa->fmtHdr.sampleRate < 300 || aifa->fmtHdr.sampleRate > 128000) + { + log_add (log_Warning, "aifa_Open(): unsupported sampling rate %ld", + (long)aifa->fmtHdr.sampleRate); + aifa_Close (This); + return false; + } + + aifa->block_align = aifa->bits_per_sample / 8 * aifa->fmtHdr.channels; + aifa->file_block = aifa->block_align; + if (!aifa->data_ofs) + { + log_add (log_Warning, "aifa_Open(): bad aiff file," + " no SSND chunk found"); + aifa_Close (This); + return false; + } + + if (fileHdr.type == aiff_FormTypeAIFF) + { + if (aifa->fmtHdr.extTypeID != 0) + { + log_add (log_Warning, "aifa_Open(): unsupported extension 0x%08x", + aifa->fmtHdr.extTypeID); + aifa_Close (This); + return false; + } + aifa->comp_type = aifc_None; + } + else if (fileHdr.type == aiff_FormTypeAIFC) + { + if (aifa->fmtHdr.extTypeID != aiff_CompressionTypeSDX2) + { + log_add (log_Warning, "aifa_Open(): unsupported compression 0x%08x", + aifa->fmtHdr.extTypeID); + aifa_Close (This); + return false; + } + aifa->comp_type = aifc_Sdx2; + aifa->file_block /= 2; + assert(aifa->fmtHdr.channels <= MAX_CHANNELS); + // after decompression, we will get samples in machine byte order + This->need_swap = (aifa_formats->big_endian + != aifa_formats->want_big_endian); + } + + aifa->data_size = aifa->fmtHdr.sampleFrames * aifa->file_block; + + if (aifa->comp_type == aifc_Sdx2 && aifa->bits_per_sample != 16) + { + log_add (log_Warning, "aifa_Open(): unsupported sample size %u for SDX2", + (unsigned)aifa->fmtHdr.sampleSize); + aifa_Close (This); + return false; + } + + This->format = (aifa->fmtHdr.channels == 1 ? + (aifa->bits_per_sample == 8 ? + aifa_formats->mono8 : aifa_formats->mono16) + : + (aifa->bits_per_sample == 8 ? + aifa_formats->stereo8 : aifa_formats->stereo16) + ); + This->frequency = aifa->fmtHdr.sampleRate; + + uio_fseek (aifa->fp, aifa->data_ofs, SEEK_SET); + aifa->max_pcm = aifa->fmtHdr.sampleFrames; + aifa->cur_pcm = 0; + This->length = (float) aifa->max_pcm / aifa->fmtHdr.sampleRate; + aifa->last_error = 0; + + return true; +} + +static void +aifa_Close (THIS_PTR) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + + if (aifa->fp) + { + uio_fclose (aifa->fp); + aifa->fp = NULL; + } +} + +static int +aifa_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + switch (aifa->comp_type) + { + case aifc_None: + return aifa_DecodePCM (aifa, buf, bufsize); + case aifc_Sdx2: + return aifa_DecodeSDX2 (aifa, buf, bufsize); + default: + assert(false && "Unknown comp_type"); + return 0; + } +} + +static int +aifa_DecodePCM (TFB_AiffSoundDecoder* aifa, void* buf, sint32 bufsize) +{ + uint32 dec_pcm; + uint32 size; + + dec_pcm = bufsize / aifa->block_align; + if (dec_pcm > aifa->max_pcm - aifa->cur_pcm) + dec_pcm = aifa->max_pcm - aifa->cur_pcm; + + dec_pcm = uio_fread (buf, aifa->file_block, dec_pcm, aifa->fp); + aifa->cur_pcm += dec_pcm; + size = dec_pcm * aifa->block_align; + + if (aifa->bits_per_sample == 8) + { // AIFF files store 8-bit data as signed + // and we need it unsigned + uint8* ptr = (uint8*)buf; + uint32 left; + for (left = size; left > 0; --left, ++ptr) + *ptr += 128; + } + + return size; +} + +static int +aifa_DecodeSDX2 (TFB_AiffSoundDecoder* aifa, void* buf, sint32 bufsize) +{ + uint32 dec_pcm; + sint8 *src; + sint16 *dst = buf; + uint32 left; + + dec_pcm = bufsize / aifa->block_align; + if (dec_pcm > aifa->max_pcm - aifa->cur_pcm) + dec_pcm = aifa->max_pcm - aifa->cur_pcm; + + src = (sint8*)buf + bufsize - (dec_pcm * aifa->file_block); + dec_pcm = uio_fread (src, aifa->file_block, dec_pcm, aifa->fp); + aifa->cur_pcm += dec_pcm; + + for (left = dec_pcm; left > 0; --left) + { + int i; + sint32 *prev = aifa->prev_val; + for (i = aifa->fmtHdr.channels; i > 0; --i, ++prev, ++src, ++dst) + { + sint32 v = (*src * abs(*src)) << 1; + if (*src & 1) + v += *prev; + // saturate the value + if (v > 32767) + v = 32767; + else if (v < -32768) + v = -32768; + *prev = v; + *dst = v; + } + } + + return dec_pcm * aifa->block_align; +} + +static uint32 +aifa_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + + if (pcm_pos > aifa->max_pcm) + pcm_pos = aifa->max_pcm; + aifa->cur_pcm = pcm_pos; + uio_fseek (aifa->fp, + aifa->data_ofs + pcm_pos * aifa->file_block, + SEEK_SET); + + // reset previous values for SDX2 on seek ops + // the delta will recover faster with reset + memset(aifa->prev_val, 0, sizeof(aifa->prev_val)); + + return pcm_pos; +} + +static uint32 +aifa_GetFrame (THIS_PTR) +{ + //TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + return 0; // only 1 frame for now + + (void)This; // laugh at compiler warning +} diff --git a/src/libs/sound/decoders/aiffaud.h b/src/libs/sound/decoders/aiffaud.h new file mode 100644 index 0000000..36c6679 --- /dev/null +++ b/src/libs/sound/decoders/aiffaud.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. + */ + +/* AIFF decoder */ + +#ifndef AIFFAUD_H +#define AIFFAUD_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs aifa_DecoderVtbl; + +typedef enum +{ + // positive values are the same as in errno + aifae_None = 0, + aifae_Unknown = -1, + aifae_BadFile = -2, + aifae_BadArg = -3, + aifae_Other = -1000, +} aifa_Error; + +#endif /* AIFFAUD_H */ diff --git a/src/libs/sound/decoders/decoder.c b/src/libs/sound/decoders/decoder.c new file mode 100644 index 0000000..8c20877 --- /dev/null +++ b/src/libs/sound/decoders/decoder.c @@ -0,0 +1,936 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Sound file decoder for .wav, .mod, .ogg (to be used with OpenAL) + * API is heavily influenced by SDL_sound. + */ + +#include +#include +#include "port.h" +#include "libs/memlib.h" +#include "libs/file.h" +#include "libs/log.h" +#include "decoder.h" +#include "wav.h" +#include "dukaud.h" +#include "modaud.h" +#ifndef OVCODEC_NONE +# include "oggaud.h" +#endif /* OVCODEC_NONE */ +#include "aiffaud.h" + + +#define MAX_REG_DECODERS 31 + +#define THIS_PTR TFB_SoundDecoder* + +static const char* bufa_GetName (void); +static bool bufa_InitModule (int flags, const TFB_DecoderFormats*); +static void bufa_TermModule (void); +static uint32 bufa_GetStructSize (void); +static int bufa_GetError (THIS_PTR); +static bool bufa_Init (THIS_PTR); +static void bufa_Term (THIS_PTR); +static bool bufa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void bufa_Close (THIS_PTR); +static int bufa_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 bufa_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 bufa_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs bufa_DecoderVtbl = +{ + bufa_GetName, + bufa_InitModule, + bufa_TermModule, + bufa_GetStructSize, + bufa_GetError, + bufa_Init, + bufa_Term, + bufa_Open, + bufa_Close, + bufa_Decode, + bufa_Seek, + bufa_GetFrame, +}; + +typedef struct tfb_bufsounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + void* data; + uint32 max_pcm; + uint32 cur_pcm; + +} TFB_BufSoundDecoder; + +#define SD_MIN_SIZE (sizeof (TFB_BufSoundDecoder)) + +static const char* nula_GetName (void); +static bool nula_InitModule (int flags, const TFB_DecoderFormats*); +static void nula_TermModule (void); +static uint32 nula_GetStructSize (void); +static int nula_GetError (THIS_PTR); +static bool nula_Init (THIS_PTR); +static void nula_Term (THIS_PTR); +static bool nula_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void nula_Close (THIS_PTR); +static int nula_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 nula_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 nula_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs nula_DecoderVtbl = +{ + nula_GetName, + nula_InitModule, + nula_TermModule, + nula_GetStructSize, + nula_GetError, + nula_Init, + nula_Term, + nula_Open, + nula_Close, + nula_Decode, + nula_Seek, + nula_GetFrame, +}; + +typedef struct tfb_nullsounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + uint32 cur_pcm; + +} TFB_NullSoundDecoder; + +#undef THIS_PTR + + +struct TFB_RegSoundDecoder +{ + bool builtin; + bool used; // ever used indicator + const char* ext; + const TFB_SoundDecoderFuncs* funcs; +}; +static TFB_RegSoundDecoder sd_decoders[MAX_REG_DECODERS + 1] = +{ + {true, true, "wav", &wava_DecoderVtbl}, + {true, true, "mod", &moda_DecoderVtbl}, +#ifndef OVCODEC_NONE + {true, true, "ogg", &ova_DecoderVtbl}, +#endif /* OVCODEC_NONE */ + {true, true, "duk", &duka_DecoderVtbl}, + {true, true, "aif", &aifa_DecoderVtbl}, + {false, false, NULL, NULL}, // null term +}; + +static TFB_DecoderFormats decoder_formats; +static int sd_flags = 0; + +/* change endianness of 16bit words + * Only works optimal when 'data' is aligned on a 32 bits boundary. + */ +void +SoundDecoder_SwapWords (uint16* data, uint32 size) +{ + uint32 fsize = size & (~3U); + + size -= fsize; + fsize >>= 2; + for (; fsize; fsize--, data += 2) + { + uint32 v = *(uint32*)data; + *(uint32*)data = ((v & 0x00ff00ff) << 8) + | ((v & 0xff00ff00) >> 8); + } + if (size) + { + /* leftover word */ + *data = ((*data & 0x00ff) << 8) | ((*data & 0xff00) >> 8); + } +} + +const char* +SoundDecoder_GetName (TFB_SoundDecoder *decoder) +{ + if (!decoder || !decoder->funcs) + return "(Null)"; + return decoder->funcs->GetName (); +} + +sint32 +SoundDecoder_Init (int flags, TFB_DecoderFormats *formats) +{ + TFB_RegSoundDecoder* info; + sint32 ret = 0; + + if (!formats) + { + log_add (log_Error, "SoundDecoder_Init(): missing decoder formats"); + return 1; + } + decoder_formats = *formats; + + // init built-in decoders + for (info = sd_decoders; info->ext; info++) + { + if (!info->funcs->InitModule (flags, &decoder_formats)) + { + log_add (log_Error, "SoundDecoder_Init(): " + "%s audio decoder init failed", + info->funcs->GetName ()); + ret = 1; + } + } + + sd_flags = flags; + + return ret; +} + +void +SoundDecoder_Uninit (void) +{ + TFB_RegSoundDecoder* info; + + // uninit all decoders + // and unregister loaded decoders + for (info = sd_decoders; info->used; info++) + { + if (info->ext) // check if present + info->funcs->TermModule (); + + if (!info->builtin) + { + info->used = false; + info->ext = NULL; + } + } +} + +TFB_RegSoundDecoder* +SoundDecoder_Register (const char* fileext, TFB_SoundDecoderFuncs* decvtbl) +{ + TFB_RegSoundDecoder* info; + TFB_RegSoundDecoder* newslot = NULL; + + if (!decvtbl) + { + log_add (log_Warning, "SoundDecoder_Register(): Null decoder table"); + return NULL; + } + if (!fileext) + { + log_add (log_Warning, "SoundDecoder_Register(): Bad file type for %s", + decvtbl->GetName ()); + return NULL; + } + + // check if extension already registered + for (info = sd_decoders; info->used && + (!info->ext || strcmp (info->ext, fileext) != 0); + ++info) + { + // and pick up an empty slot (where available) + if (!newslot && !info->ext) + newslot = info; + } + + if (info >= sd_decoders + MAX_REG_DECODERS) + { + log_add (log_Warning, "SoundDecoder_Register(): Decoders limit reached"); + return NULL; + } + else if (info->ext) + { + log_add (log_Warning, "SoundDecoder_Register(): " + "'%s' decoder already registered (%s denied)", + fileext, decvtbl->GetName ()); + return NULL; + } + + if (!decvtbl->InitModule (sd_flags, &decoder_formats)) + { + log_add (log_Warning, "SoundDecoder_Register(): %s decoder init failed", + decvtbl->GetName ()); + return NULL; + } + + if (!newslot) + { + newslot = info; + newslot->used = true; + // make next one a term + info[1].builtin = false; + info[1].used = false; + info[1].ext = NULL; + } + + newslot->ext = fileext; + newslot->funcs = decvtbl; + + return newslot; +} + +void +SoundDecoder_Unregister (TFB_RegSoundDecoder* regdec) +{ + if (regdec < sd_decoders || regdec >= sd_decoders + MAX_REG_DECODERS || + !regdec->ext || !regdec->funcs) + { + log_add (log_Warning, "SoundDecoder_Unregister(): " + "Invalid or expired decoder passed"); + return; + } + + regdec->funcs->TermModule (); + regdec->ext = NULL; + regdec->funcs = NULL; +} + +const TFB_SoundDecoderFuncs* +SoundDecoder_Lookup (const char* fileext) +{ + TFB_RegSoundDecoder* info; + + for (info = sd_decoders; info->used && + (!info->ext || strcmp (info->ext, fileext) != 0); + ++info) + ; + return info->ext ? info->funcs : NULL; +} + +TFB_SoundDecoder* +SoundDecoder_Load (uio_DirHandle *dir, char *filename, + uint32 buffer_size, uint32 startTime, sint32 runTime) + // runTime < 0 specifies a default length for a nul decoder +{ + const char* pext; + TFB_RegSoundDecoder* info; + const TFB_SoundDecoderFuncs* funcs; + TFB_SoundDecoder* decoder; + uint32 struct_size; + + pext = strrchr (filename, '.'); + if (!pext) + { + log_add (log_Warning, "SoundDecoder_Load(): Unknown file type (%s)", + filename); + return NULL; + } + ++pext; + + for (info = sd_decoders; info->used && + (!info->ext || strcmp (info->ext, pext) != 0); + ++info) + ; + if (!info->ext) + { + log_add (log_Warning, "SoundDecoder_Load(): Unsupported file type (%s)", + filename); + + if (runTime) + { + runTime = abs (runTime); + startTime = 0; + funcs = &nula_DecoderVtbl; + } + else + { + return NULL; + } + } + else + { + funcs = info->funcs; + } + + if (!fileExists2 (dir, filename)) + { + if (runTime) + { + runTime = abs (runTime); + startTime = 0; + funcs = &nula_DecoderVtbl; + } + else + { + log_add (log_Warning, "SoundDecoder_Load(): %s does not exist", + filename); + return NULL; + } + } + + struct_size = funcs->GetStructSize (); + if (struct_size < SD_MIN_SIZE) + struct_size = SD_MIN_SIZE; + + decoder = (TFB_SoundDecoder*) HCalloc (struct_size); + decoder->funcs = funcs; + if (!decoder->funcs->Init (decoder)) + { + log_add (log_Warning, "SoundDecoder_Load(): " + "%s decoder instance failed init", + decoder->funcs->GetName ()); + HFree (decoder); + return NULL; + } + + if (!decoder->funcs->Open (decoder, dir, filename)) + { + log_add (log_Warning, "SoundDecoder_Load(): " + "%s decoder could not load %s", + decoder->funcs->GetName (), filename); + decoder->funcs->Term (decoder); + HFree (decoder); + return NULL; + } + + decoder->buffer = HMalloc (buffer_size); + decoder->buffer_size = buffer_size; + decoder->looping = false; + decoder->error = SOUNDDECODER_OK; + decoder->dir = dir; + decoder->filename = (char *) HMalloc (strlen (filename) + 1); + strcpy (decoder->filename, filename); + + if (decoder->is_null) + { // fake decoder, keeps voiceovers and etc. going + decoder->length = (float) (runTime / 1000.0); + } + + decoder->length -= startTime / 1000.0f; + if (decoder->length < 0) + decoder->length = 0; + else if (runTime > 0 && runTime / 1000.0 < decoder->length) + decoder->length = (float)(runTime / 1000.0); + + decoder->start_sample = (uint32)(startTime / 1000.0f * decoder->frequency); + decoder->end_sample = decoder->start_sample + + (unsigned long)(decoder->length * decoder->frequency); + if (decoder->start_sample != 0) + decoder->funcs->Seek (decoder, decoder->start_sample); + + if (decoder->format == decoder_formats.mono8) + decoder->bytes_per_samp = 1; + else if (decoder->format == decoder_formats.mono16) + decoder->bytes_per_samp = 2; + else if (decoder->format == decoder_formats.stereo8) + decoder->bytes_per_samp = 2; + else if (decoder->format == decoder_formats.stereo16) + decoder->bytes_per_samp = 4; + + decoder->pos = decoder->start_sample * decoder->bytes_per_samp; + + return decoder; +} + +uint32 +SoundDecoder_Decode (TFB_SoundDecoder *decoder) +{ + long decoded_bytes; + long rc; + long buffer_size; + uint32 max_bytes = UINT32_MAX; + uint8 *buffer; + + if (!decoder || !decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_Decode(): null or bad decoder"); + return 0; + } + + buffer = (uint8*) decoder->buffer; + buffer_size = decoder->buffer_size; + if (!decoder->looping && decoder->end_sample > 0) + { + max_bytes = decoder->end_sample * decoder->bytes_per_samp; + if (max_bytes - decoder->pos < decoder->buffer_size) + buffer_size = max_bytes - decoder->pos; + } + + if (buffer_size == 0) + { // nothing more to decode + decoder->error = SOUNDDECODER_EOF; + return 0; + } + + for (decoded_bytes = 0, rc = 1; rc > 0 && decoded_bytes < buffer_size; ) + { + rc = decoder->funcs->Decode (decoder, buffer + decoded_bytes, + buffer_size - decoded_bytes); + if (rc < 0) + { + log_add (log_Warning, "SoundDecoder_Decode(): " + "error decoding %s, code %ld", + decoder->filename, rc); + } + else if (rc == 0) + { // probably EOF + if (decoder->looping) + { + SoundDecoder_Rewind (decoder); + if (decoder->error) + { + log_add (log_Warning, "SoundDecoder_Decode(): " + "tried to loop %s but couldn't rewind, " + "error code %d", + decoder->filename, decoder->error); + } + else + { + log_add (log_Info, "SoundDecoder_Decode(): " + "looping %s", decoder->filename); + rc = 1; // prime the loop again + } + } + else + { + log_add (log_Info, "SoundDecoder_Decode(): eof for %s", + decoder->filename); + } + } + else + { // some bytes decoded + decoded_bytes += rc; + } + } + decoder->pos += decoded_bytes; + if (rc < 0) + decoder->error = SOUNDDECODER_ERROR; + else if (rc == 0 || decoder->pos >= max_bytes) + decoder->error = SOUNDDECODER_EOF; + else + decoder->error = SOUNDDECODER_OK; + + if (decoder->need_swap && decoded_bytes > 0 && + (decoder->format == decoder_formats.stereo16 || + decoder->format == decoder_formats.mono16)) + { + SoundDecoder_SwapWords ( + decoder->buffer, decoded_bytes); + } + + return decoded_bytes; +} + +uint32 +SoundDecoder_DecodeAll (TFB_SoundDecoder *decoder) +{ + uint32 decoded_bytes; + long rc; + uint32 reqbufsize; + + if (!decoder || !decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_DecodeAll(): null or bad decoder"); + return 0; + } + + reqbufsize = decoder->buffer_size; + + if (decoder->looping) + { + log_add (log_Warning, "SoundDecoder_DecodeAll(): " + "called for %s with looping", decoder->filename); + return 0; + } + + if (reqbufsize < 4096) + reqbufsize = 4096; + + for (decoded_bytes = 0, rc = 1; rc > 0; ) + { + if (decoded_bytes >= decoder->buffer_size) + { // need to grow buffer + decoder->buffer_size += reqbufsize; + decoder->buffer = HRealloc ( + decoder->buffer, decoder->buffer_size); + } + + rc = decoder->funcs->Decode (decoder, + (uint8*) decoder->buffer + decoded_bytes, + decoder->buffer_size - decoded_bytes); + + if (rc > 0) + decoded_bytes += rc; + } + decoder->buffer_size = decoded_bytes; + decoder->pos += decoded_bytes; + // Free up some unused memory + decoder->buffer = HRealloc (decoder->buffer, decoded_bytes); + + if (decoder->need_swap && decoded_bytes > 0 && + (decoder->format == decoder_formats.stereo16 || + decoder->format == decoder_formats.mono16)) + { + SoundDecoder_SwapWords ( + decoder->buffer, decoded_bytes); + } + + if (rc < 0) + { + decoder->error = SOUNDDECODER_ERROR; + log_add (log_Warning, "SoundDecoder_DecodeAll(): " + "error decoding %s, code %ld", + decoder->filename, rc); + return decoded_bytes; + } + + // switch to Buffer decoder + decoder->funcs->Close (decoder); + decoder->funcs->Term (decoder); + + decoder->funcs = &bufa_DecoderVtbl; + decoder->funcs->Init (decoder); + decoder->pos = 0; + decoder->start_sample = 0; + decoder->error = SOUNDDECODER_OK; + + return decoded_bytes; +} + +void +SoundDecoder_Rewind (TFB_SoundDecoder *decoder) +{ + SoundDecoder_Seek (decoder, 0); +} + +// seekTime is specified in mili-seconds +void +SoundDecoder_Seek (TFB_SoundDecoder *decoder, uint32 seekTime) +{ + uint32 pcm_pos; + + if (!decoder) + return; + if (!decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_Seek(): bad decoder passed"); + return; + } + + pcm_pos = (uint32) (seekTime / 1000.0f * decoder->frequency); + pcm_pos = decoder->funcs->Seek (decoder, + decoder->start_sample + pcm_pos); + decoder->pos = pcm_pos * decoder->bytes_per_samp; + decoder->error = SOUNDDECODER_OK; +} + +void +SoundDecoder_Free (TFB_SoundDecoder *decoder) +{ + if (!decoder) + return; + if (!decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_Free(): bad decoder passed"); + return; + } + + decoder->funcs->Close (decoder); + decoder->funcs->Term (decoder); + + HFree (decoder->buffer); + HFree (decoder->filename); + HFree (decoder); +} + +float +SoundDecoder_GetTime (TFB_SoundDecoder *decoder) +{ + if (!decoder) + return 0.0f; + if (!decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_GetTime(): bad decoder passed"); + return 0.0f; + } + + return (float) + ((decoder->pos / decoder->bytes_per_samp) + - decoder->start_sample + ) / decoder->frequency; +} + +uint32 +SoundDecoder_GetFrame (TFB_SoundDecoder *decoder) +{ + if (!decoder) + return 0; + if (!decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_GetFrame(): bad decoder passed"); + return 0; + } + + return decoder->funcs->GetFrame (decoder); +} + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* +bufa_GetName (void) +{ + return "Buffer"; +} + +static bool +bufa_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + // this should never be called + log_add (log_Debug, "bufa_InitModule(): dead function called"); + return false; + + (void)flags; (void)fmts; // laugh at compiler warning +} + +static void +bufa_TermModule (void) +{ + // this should never be called + log_add (log_Debug, "bufa_TermModule(): dead function called"); +} + +static uint32 +bufa_GetStructSize (void) +{ + return sizeof (TFB_BufSoundDecoder); +} + +static int +bufa_GetError (THIS_PTR) +{ + return 0; // error? what error?! + + (void)This; // laugh at compiler warning +} + +static bool +bufa_Init (THIS_PTR) +{ + TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + + This->need_swap = false; + // hijack the buffer + bufa->data = This->buffer; + bufa->max_pcm = This->buffer_size / This->bytes_per_samp; + bufa->cur_pcm = bufa->max_pcm; + + return true; +} + +static void +bufa_Term (THIS_PTR) +{ + //TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + bufa_Close (This); // ensure cleanup +} + +static bool +bufa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + // this should never be called + log_add (log_Debug, "bufa_Open(): dead function called"); + return false; + + // laugh at compiler warnings + (void)This; (void)dir; (void)filename; +} + +static void +bufa_Close (THIS_PTR) +{ + TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + + // restore the status quo + if (bufa->data) + { + This->buffer = bufa->data; + bufa->data = NULL; + } + bufa->cur_pcm = 0; +} + +static int +bufa_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + uint32 dec_pcm; + uint32 dec_bytes; + + dec_pcm = bufsize / This->bytes_per_samp; + if (dec_pcm > bufa->max_pcm - bufa->cur_pcm) + dec_pcm = bufa->max_pcm - bufa->cur_pcm; + dec_bytes = dec_pcm * This->bytes_per_samp; + + // Buffer decode is a hack + This->buffer = (uint8*) bufa->data + + bufa->cur_pcm * This->bytes_per_samp; + + if (dec_pcm > 0) + bufa->cur_pcm += dec_pcm; + + return dec_bytes; + + (void)buf; // laugh at compiler warning +} + +static uint32 +bufa_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + + if (pcm_pos > bufa->max_pcm) + pcm_pos = bufa->max_pcm; + bufa->cur_pcm = pcm_pos; + + return pcm_pos; +} + +static uint32 +bufa_GetFrame (THIS_PTR) +{ + return 0; // only 1 frame + + (void)This; // laugh at compiler warning +} + + +static const char* +nula_GetName (void) +{ + return "Null"; +} + +static bool +nula_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + // this should never be called + log_add (log_Debug, "nula_InitModule(): dead function called"); + return false; + + (void)flags; (void)fmts; // laugh at compiler warning +} + +static void +nula_TermModule (void) +{ + // this should never be called + log_add (log_Debug, "nula_TermModule(): dead function called"); +} + +static uint32 +nula_GetStructSize (void) +{ + return sizeof (TFB_NullSoundDecoder); +} + +static int +nula_GetError (THIS_PTR) +{ + return 0; // error? what error?! + + (void)This; // laugh at compiler warning +} + +static bool +nula_Init (THIS_PTR) +{ + TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + + This->need_swap = false; + nula->cur_pcm = 0; + return true; +} + +static void +nula_Term (THIS_PTR) +{ + //TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + nula_Close (This); // ensure cleanup +} + +static bool +nula_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + This->frequency = 11025; + This->format = decoder_formats.mono16; + This->is_null = true; + return true; + + // laugh at compiler warnings + (void)dir; (void)filename; +} + +static void +nula_Close (THIS_PTR) +{ + TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + + nula->cur_pcm = 0; +} + +static int +nula_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + uint32 max_pcm; + uint32 dec_pcm; + uint32 dec_bytes; + + max_pcm = (uint32) (This->length * This->frequency); + dec_pcm = bufsize / This->bytes_per_samp; + if (dec_pcm > max_pcm - nula->cur_pcm) + dec_pcm = max_pcm - nula->cur_pcm; + dec_bytes = dec_pcm * This->bytes_per_samp; + + if (dec_pcm > 0) + { + memset (buf, 0, dec_bytes); + nula->cur_pcm += dec_pcm; + } + + return dec_bytes; +} + +static uint32 +nula_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + uint32 max_pcm; + + max_pcm = (uint32) (This->length * This->frequency); + if (pcm_pos > max_pcm) + pcm_pos = max_pcm; + nula->cur_pcm = pcm_pos; + + return pcm_pos; +} + +static uint32 +nula_GetFrame (THIS_PTR) +{ + return 0; // only 1 frame + + (void)This; // laugh at compiler warning +} diff --git a/src/libs/sound/decoders/decoder.h b/src/libs/sound/decoders/decoder.h new file mode 100644 index 0000000..2d6983c --- /dev/null +++ b/src/libs/sound/decoders/decoder.h @@ -0,0 +1,129 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Sound file decoder for .wav, .mod, .ogg + * API is heavily influenced by SDL_sound. + */ + +#ifndef DECODER_H +#define DECODER_H + +#include "port.h" +#include "types.h" +#include "libs/uio.h" + +#ifndef OVCODEC_NONE +# ifdef _MSC_VER +# pragma comment (lib, "vorbisfile.lib") +# endif /* _MSC_VER */ +#endif /* OVCODEC_NONE */ + +typedef struct tfb_decoderformats +{ + bool big_endian; + bool want_big_endian; + uint32 mono8; + uint32 stereo8; + uint32 mono16; + uint32 stereo16; +} TFB_DecoderFormats; + +// forward-declare +typedef struct tfb_sounddecoder TFB_SoundDecoder; + +#define THIS_PTR TFB_SoundDecoder* + +typedef struct tfb_sounddecoderfunc +{ + const char* (* GetName) (void); + bool (* InitModule) (int flags, const TFB_DecoderFormats*); + void (* TermModule) (void); + uint32 (* GetStructSize) (void); + int (* GetError) (THIS_PTR); + bool (* Init) (THIS_PTR); + void (* Term) (THIS_PTR); + bool (* Open) (THIS_PTR, uio_DirHandle *dir, const char *filename); + void (* Close) (THIS_PTR); + int (* Decode) (THIS_PTR, void* buf, sint32 bufsize); + // returns <0 on error, ==0 when no more data, >0 bytes returned + uint32 (* Seek) (THIS_PTR, uint32 pcm_pos); + // returns the pcm position set + uint32 (* GetFrame) (THIS_PTR); + +} TFB_SoundDecoderFuncs; + +#undef THIS_PTR + +struct tfb_sounddecoder +{ + // decoder virtual funcs - R/O + const TFB_SoundDecoderFuncs *funcs; + + // public R/O, set by decoder + uint32 format; + uint32 frequency; + float length; // total length in seconds + bool is_null; + bool need_swap; + + // public R/O, set by wrapper + void *buffer; + uint32 buffer_size; + sint32 error; + uint32 bytes_per_samp; + + // public R/W + bool looping; + + // semi-private + uio_DirHandle *dir; + char *filename; + uint32 pos; + uint32 start_sample; + uint32 end_sample; + +}; + +// return values +enum +{ + SOUNDDECODER_OK, + SOUNDDECODER_ERROR, + SOUNDDECODER_EOF, +}; + +typedef struct TFB_RegSoundDecoder TFB_RegSoundDecoder; + +TFB_RegSoundDecoder* SoundDecoder_Register (const char* fileext, + TFB_SoundDecoderFuncs* decvtbl); +void SoundDecoder_Unregister (TFB_RegSoundDecoder* regdec); +const TFB_SoundDecoderFuncs* SoundDecoder_Lookup (const char* fileext); + +void SoundDecoder_SwapWords (uint16* data, uint32 size); +sint32 SoundDecoder_Init (int flags, TFB_DecoderFormats* formats); +void SoundDecoder_Uninit (void); +TFB_SoundDecoder* SoundDecoder_Load (uio_DirHandle *dir, + char *filename, uint32 buffer_size, uint32 startTime, sint32 runTime); +uint32 SoundDecoder_Decode (TFB_SoundDecoder *decoder); +uint32 SoundDecoder_DecodeAll (TFB_SoundDecoder *decoder); +float SoundDecoder_GetTime (TFB_SoundDecoder *decoder); +uint32 SoundDecoder_GetFrame (TFB_SoundDecoder *decoder); +void SoundDecoder_Seek (TFB_SoundDecoder *decoder, uint32 msecs); +void SoundDecoder_Rewind (TFB_SoundDecoder *decoder); +void SoundDecoder_Free (TFB_SoundDecoder *decoder); +const char* SoundDecoder_GetName (TFB_SoundDecoder *decoder); + +#endif diff --git a/src/libs/sound/decoders/dukaud.c b/src/libs/sound/decoders/dukaud.c new file mode 100644 index 0000000..aeff373 --- /dev/null +++ b/src/libs/sound/decoders/dukaud.c @@ -0,0 +1,546 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* .duk sound track decoder + */ + +#include +#include +#include +#include "libs/memlib.h" +#include "port.h" +#include "types.h" +#include "libs/uio.h" +#include "dukaud.h" +#include "decoder.h" +#include "endian_uqm.h" + +#define DATA_BUF_SIZE 0x8000 +#define DUCK_GENERAL_FPS 14.622f + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* duka_GetName (void); +static bool duka_InitModule (int flags, const TFB_DecoderFormats*); +static void duka_TermModule (void); +static uint32 duka_GetStructSize (void); +static int duka_GetError (THIS_PTR); +static bool duka_Init (THIS_PTR); +static void duka_Term (THIS_PTR); +static bool duka_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void duka_Close (THIS_PTR); +static int duka_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 duka_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 duka_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs duka_DecoderVtbl = +{ + duka_GetName, + duka_InitModule, + duka_TermModule, + duka_GetStructSize, + duka_GetError, + duka_Init, + duka_Term, + duka_Open, + duka_Close, + duka_Decode, + duka_Seek, + duka_GetFrame, +}; + +typedef struct tfb_ducksounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // public read-only + uint32 iframe; // current frame index + uint32 cframes; // total count of frames + uint32 channels; // number of channels + uint32 pcm_frame; // samples per frame + + // private + sint32 last_error; + uio_Stream* duk; + uint32* frames; + // buffer + void* data; + uint32 maxdata; + uint32 cbdata; + uint32 dataofs; + // decoder stuff + sint32 predictors[2]; + +} TFB_DuckSoundDecoder; + + +typedef struct +{ + uint32 audsize; + uint32 vidsize; +} DukAud_FrameHeader; + +typedef struct +{ + uint16 magic; // always 0xf77f + uint16 numsamples; + uint16 tag; + uint16 indices[2]; // initial indices for channels +} DukAud_AudSubframe; + +static const TFB_DecoderFormats* duka_formats = NULL; + +static sint32 +duka_readAudFrameHeader (TFB_DuckSoundDecoder* duka, uint32 iframe, + DukAud_AudSubframe* aud) +{ + DukAud_FrameHeader hdr; + + uio_fseek (duka->duk, duka->frames[iframe], SEEK_SET); + if (uio_fread (&hdr, sizeof(hdr), 1, duka->duk) != 1) + { + duka->last_error = errno; + return dukae_BadFile; + } + hdr.audsize = UQM_SwapBE32 (hdr.audsize); + + if (uio_fread (aud, sizeof(*aud), 1, duka->duk) != 1) + { + duka->last_error = errno; + return dukae_BadFile; + } + + aud->magic = UQM_SwapBE16 (aud->magic); + if (aud->magic != 0xf77f) + return duka->last_error = dukae_BadFile; + + aud->numsamples = UQM_SwapBE16 (aud->numsamples); + aud->tag = UQM_SwapBE16 (aud->tag); + aud->indices[0] = UQM_SwapBE16 (aud->indices[0]); + aud->indices[1] = UQM_SwapBE16 (aud->indices[1]); + + return 0; +} + +// This table is from one of the files that came with the original 3do source +// It's slightly different from the data used by MPlayer. +static int adpcm_step[89] = { + 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xF, + 0x10, 0x12, 0x13, 0x15, 0x17, 0x1A, 0x1C, 0x1F, + 0x22, 0x26, 0x29, 0x2E, 0x32, 0x37, 0x3D, 0x43, + 0x4A, 0x51, 0x59, 0x62, 0x6C, 0x76, 0x82, 0x8F, + 0x9E, 0xAD, 0xBF, 0xD2, 0xE7, 0xFE, 0x117, 0x133, + 0x152, 0x174, 0x199, 0x1C2, 0x1EF, 0x220, 0x256, 0x292, + 0x2D4, 0x31D, 0x36C, 0x3C4, 0x424, 0x48E, 0x503, 0x583, + 0x610, 0x6AC, 0x756, 0x812, 0x8E1, 0x9C4, 0xABE, 0xBD1, + 0xCFF, 0xE4C, 0xFBA, 0x114D, 0x1308, 0x14EF, 0x1707, 0x1954, + 0x1BDD, 0x1EA6, 0x21B7, 0x2516, + 0x28CB, 0x2CDF, 0x315C, 0x364C, + 0x3BBA, 0x41B2, 0x4844, 0x4F7E, + 0x5771, 0x6030, 0x69CE, 0x7463, + 0x7FFF + }; + + +// *** BEGIN part copied from MPlayer *** +// (some little changes) + +#if 0 +// pertinent tables for IMA ADPCM +static int adpcm_step[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 + }; +#endif + +static int adpcm_index[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + +// clamp a number between 0 and 88 +#define CLAMP_0_TO_88(x) \ + if ((x) < 0) (x) = 0; else if ((x) > 88) (x) = 88; + +// clamp a number within a signed 16-bit range +#define CLAMP_S16(x) \ + if ((x) < -32768) \ + (x) = -32768; \ + else if ((x) > 32767) \ + (x) = 32767; + +static void +decode_nibbles (sint16 *output, sint32 output_size, sint32 channels, + sint32* predictors, uint16* indices) +{ + sint32 step[2]; + sint32 index[2]; + sint32 diff; + sint32 i; + int sign; + sint32 delta; + int channel_number = 0; + + channels -= 1; + index[0] = indices[0]; + index[1] = indices[1]; + step[0] = adpcm_step[index[0]]; + step[1] = adpcm_step[index[1]]; + + for (i = 0; i < output_size; i++) + { + delta = output[i]; + + index[channel_number] += adpcm_index[delta]; + CLAMP_0_TO_88(index[channel_number]); + + sign = delta & 8; + delta = delta & 7; + +#if 0 + // fast approximation, used in most decoders + diff = step[channel_number] >> 3; + if (delta & 4) diff += step[channel_number]; + if (delta & 2) diff += step[channel_number] >> 1; + if (delta & 1) diff += step[channel_number] >> 2; +#else + // real thing +// diff = ((signed)delta + 0.5) * step[channel_number] / 4; + diff = (((delta << 1) + 1) * step[channel_number]) >> 3; +#endif + + if (sign) + predictors[channel_number] -= diff; + else + predictors[channel_number] += diff; + + CLAMP_S16(predictors[channel_number]); + output[i] = predictors[channel_number]; + step[channel_number] = adpcm_step[index[channel_number]]; + + // toggle channel + channel_number ^= channels; + } +} +// *** END part copied from MPlayer *** + +static sint32 +duka_decodeFrame (TFB_DuckSoundDecoder* duka, DukAud_AudSubframe* header, + uint8* input) +{ + uint8* inend; + sint16* output; + sint16* outptr; + sint32 outputsize; + + outputsize = header->numsamples * 2 * sizeof (sint16); + outptr = output = (sint16*) ((uint8*)duka->data + duka->cbdata); + + for (inend = input + header->numsamples; input < inend; ++input) + { + *(outptr++) = *input >> 4; + *(outptr++) = *input & 0x0f; + } + + decode_nibbles (output, header->numsamples * 2, duka->channels, + duka->predictors, header->indices); + + duka->cbdata += outputsize; + + return outputsize; +} + + +static sint32 +duka_readNextFrame (TFB_DuckSoundDecoder* duka) +{ + DukAud_FrameHeader hdr; + DukAud_AudSubframe* aud; + uint8* p; + + uio_fseek (duka->duk, duka->frames[duka->iframe], SEEK_SET); + if (uio_fread (&hdr, sizeof(hdr), 1, duka->duk) != 1) + { + duka->last_error = errno; + return dukae_BadFile; + } + hdr.audsize = UQM_SwapBE32 (hdr.audsize); + + // dump encoded data at the end of the buffer aligned on 8-byte + p = ((uint8*)duka->data + duka->maxdata - ((hdr.audsize + 7) & (-8))); + if (uio_fread (p, 1, hdr.audsize, duka->duk) != hdr.audsize) + { + duka->last_error = errno; + return dukae_BadFile; + } + aud = (DukAud_AudSubframe*) p; + p += sizeof(DukAud_AudSubframe); + + aud->magic = UQM_SwapBE16 (aud->magic); + if (aud->magic != 0xf77f) + return duka->last_error = dukae_BadFile; + + aud->numsamples = UQM_SwapBE16 (aud->numsamples); + aud->tag = UQM_SwapBE16 (aud->tag); + aud->indices[0] = UQM_SwapBE16 (aud->indices[0]); + aud->indices[1] = UQM_SwapBE16 (aud->indices[1]); + + duka->iframe++; + + return duka_decodeFrame (duka, aud, p); +} + +static sint32 +duka_stuffBuffer (TFB_DuckSoundDecoder* duka, void* buf, sint32 bufsize) +{ + sint32 dataleft; + + dataleft = duka->cbdata - duka->dataofs; + if (dataleft > 0) + { + if (dataleft > bufsize) + dataleft = bufsize & (-4); + memcpy (buf, (uint8*)duka->data + duka->dataofs, dataleft); + duka->dataofs += dataleft; + } + + if (duka->cbdata > 0 && duka->dataofs >= duka->cbdata) + duka->cbdata = duka->dataofs = 0; // reset for new data + + return dataleft; +} + + +static const char* +duka_GetName (void) +{ + return "DukAud"; +} + +static bool +duka_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + duka_formats = fmts; + return true; + + (void)flags; // laugh at compiler warning +} + +static void +duka_TermModule (void) +{ + // no specific module term +} + +static uint32 +duka_GetStructSize (void) +{ + return sizeof (TFB_DuckSoundDecoder); +} + +static int +duka_GetError (THIS_PTR) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + int ret = duka->last_error; + duka->last_error = dukae_None; + return ret; +} + +static bool +duka_Init (THIS_PTR) +{ + //TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + This->need_swap = + duka_formats->big_endian != duka_formats->want_big_endian; + return true; +} + +static void +duka_Term (THIS_PTR) +{ + //TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + duka_Close (This); // ensure cleanup +} + +static bool +duka_Open (THIS_PTR, uio_DirHandle *dir, const char *file) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + uio_Stream* duk; + uio_Stream* frm; + DukAud_AudSubframe aud; + char filename[256]; + uint32 filelen; + size_t cread; + uint32 i; + + filelen = strlen (file); + if (filelen > sizeof (filename) - 1) + return false; + strcpy (filename, file); + + duk = uio_fopen (dir, filename, "rb"); + if (!duk) + { + duka->last_error = errno; + return false; + } + + strcpy (filename + filelen - 3, "frm"); + frm = uio_fopen (dir, filename, "rb"); + if (!frm) + { + duka->last_error = errno; + uio_fclose (duk); + return false; + } + + duka->duk = duk; + + uio_fseek (frm, 0, SEEK_END); + duka->cframes = uio_ftell (frm) / sizeof (uint32); + uio_fseek (frm, 0, SEEK_SET); + if (!duka->cframes) + { + duka->last_error = dukae_BadFile; + uio_fclose (frm); + duka_Close (This); + return false; + } + + duka->frames = (uint32*) HMalloc (duka->cframes * sizeof (uint32)); + cread = uio_fread (duka->frames, sizeof (uint32), duka->cframes, frm); + uio_fclose (frm); + if (cread != duka->cframes) + { + duka->last_error = dukae_BadFile; + duka_Close (This); + return false; + } + + for (i = 0; i < duka->cframes; ++i) + duka->frames[i] = UQM_SwapBE32 (duka->frames[i]); + + if (duka_readAudFrameHeader (duka, 0, &aud) < 0) + { + duka_Close (This); + return false; + } + + This->frequency = 22050; + This->format = duka_formats->stereo16; + duka->channels = 2; + duka->pcm_frame = aud.numsamples; + duka->data = HMalloc (DATA_BUF_SIZE); + duka->maxdata = DATA_BUF_SIZE; + + // estimate + This->length = (float) duka->cframes / DUCK_GENERAL_FPS; + + duka->last_error = 0; + + return true; +} + +static void +duka_Close (THIS_PTR) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + + if (duka->data) + { + HFree (duka->data); + duka->data = NULL; + } + if (duka->frames) + { + HFree (duka->frames); + duka->frames = NULL; + } + if (duka->duk) + { + uio_fclose (duka->duk); + duka->duk = NULL; + } + duka->last_error = 0; +} + +static int +duka_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + sint32 stuffed; + sint32 total = 0; + + if (bufsize <= 0) + return duka->last_error = dukae_BadArg; + + do + { + stuffed = duka_stuffBuffer (duka, buf, bufsize); + buf = (uint8*)buf + stuffed; + bufsize -= stuffed; + total += stuffed; + + if (bufsize > 0 && duka->iframe < duka->cframes) + { + stuffed = duka_readNextFrame (duka); + if (stuffed <= 0) + return stuffed; + } + } while (bufsize > 0 && duka->iframe < duka->cframes); + + return total; +} + +static uint32 +duka_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + uint32 iframe; + + iframe = pcm_pos / duka->pcm_frame; + if (iframe < duka->cframes) + { + duka->iframe = iframe; + duka->cbdata = 0; + duka->dataofs = 0; + duka->predictors[0] = 0; + duka->predictors[1] = 0; + } + return duka->iframe * duka->pcm_frame; +} + +static uint32 +duka_GetFrame (THIS_PTR) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + + // if there is nothing buffered return the actual current frame + // otherwise return previous + return duka->dataofs == duka->cbdata ? + duka->iframe : duka->iframe - 1; +} diff --git a/src/libs/sound/decoders/dukaud.h b/src/libs/sound/decoders/dukaud.h new file mode 100644 index 0000000..23c4201 --- /dev/null +++ b/src/libs/sound/decoders/dukaud.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. + */ + +/* .duk sound track decoder */ + +#ifndef DUKAUD_H +#define DUKAUD_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs duka_DecoderVtbl; + +typedef enum +{ + // positive values are the same as in errno + dukae_None = 0, + dukae_Unknown = -1, + dukae_BadFile = -2, + dukae_BadArg = -3, + dukae_Other = -1000, +} DukAud_Error; + +#endif // DUKAUD_H diff --git a/src/libs/sound/decoders/modaud.c b/src/libs/sound/decoders/modaud.c new file mode 100644 index 0000000..18c29a2 --- /dev/null +++ b/src/libs/sound/decoders/modaud.c @@ -0,0 +1,430 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* MikMod decoder (.mod adapter) + */ + +#include +#include +#include +#include "libs/memlib.h" +#include "port.h" +#include "types.h" +#include "endian_uqm.h" +#include "libs/uio.h" +#include "decoder.h" +#include "libs/sound/audiocore.h" +#include "libs/log.h" +#include "modaud.h" + +#ifdef USE_INTERNAL_MIKMOD +# include "libs/mikmod/mikmod.h" +#else +# include +#endif + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* moda_GetName (void); +static bool moda_InitModule (int flags, const TFB_DecoderFormats*); +static void moda_TermModule (void); +static uint32 moda_GetStructSize (void); +static int moda_GetError (THIS_PTR); +static bool moda_Init (THIS_PTR); +static void moda_Term (THIS_PTR); +static bool moda_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void moda_Close (THIS_PTR); +static int moda_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 moda_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 moda_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs moda_DecoderVtbl = +{ + moda_GetName, + moda_InitModule, + moda_TermModule, + moda_GetStructSize, + moda_GetError, + moda_Init, + moda_Term, + moda_Open, + moda_Close, + moda_Decode, + moda_Seek, + moda_GetFrame, +}; + +typedef struct tfb_modsounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + sint32 last_error; + MODULE* module; + +} TFB_ModSoundDecoder; + + + +// MikMod Output driver +// we provide our own so that we can use MikMod as +// generic decoder + +static void* buffer; +static ULONG bufsize; +static ULONG written; + +static ULONG* +moda_mmout_SetOutputBuffer (void* buf, ULONG size) +{ + buffer = buf; + bufsize = size; + written = 0; + return &written; +} + +static BOOL +moda_mmout_IsThere (void) +{ + return 1; +} + +static BOOL +moda_mmout_Init (void) +{ + md_mode |= DMODE_SOFT_MUSIC | DMODE_SOFT_SNDFX; + return VC_Init (); +} + +static void +moda_mmout_Exit (void) +{ + VC_Exit (); +} + +static void +moda_mmout_Update (void) +{ + written = 0; + if (!buffer || bufsize == 0) + return; + + written = VC_WriteBytes (buffer, bufsize); +} + +static BOOL +moda_mmout_Reset (void) +{ + return 0; +} + +static char MDRIVER_name[] = "Mem Buffer"; +static char MDRIVER_version[] = "Mem Buffer driver v1.1"; +static char MDRIVER_alias[] = "membuf"; + +static MDRIVER moda_mmout_drv = +{ + NULL, + //xxx libmikmod does not declare these fields const; it probably should. + MDRIVER_name, // Name + MDRIVER_version, // Version + 0, 255, // Voice limits + MDRIVER_alias, // Alias + +// The minimum mikmod version we support is 3.1.8 +#if (LIBMIKMOD_VERSION_MAJOR > 3) || \ + ((LIBMIKMOD_VERSION_MAJOR == 3) && (LIBMIKMOD_VERSION_MINOR >= 2)) + NULL, // Cmdline help +#endif + + NULL, + moda_mmout_IsThere, + VC_SampleLoad, + VC_SampleUnload, + VC_SampleSpace, + VC_SampleLength, + moda_mmout_Init, + moda_mmout_Exit, + moda_mmout_Reset, + VC_SetNumVoices, + VC_PlayStart, + VC_PlayStop, + moda_mmout_Update, + NULL, /* FIXME: Pause */ + VC_VoiceSetVolume, + VC_VoiceGetVolume, + VC_VoiceSetFrequency, + VC_VoiceGetFrequency, + VC_VoiceSetPanning, + VC_VoiceGetPanning, + VC_VoicePlay, + VC_VoiceStop, + VC_VoiceStopped, + VC_VoiceGetPosition, + VC_VoiceRealVolume +}; + + +static const TFB_DecoderFormats* moda_formats = NULL; + +// MikMod READER interface +// we provide our own so that we can do loading via uio +// +typedef struct MUIOREADER +{ + MREADER core; + uio_Stream* file; + +} MUIOREADER; + +static BOOL +moda_uioReader_Eof (MREADER* reader) +{ + return uio_feof (((MUIOREADER*)reader)->file); +} + +static BOOL +moda_uioReader_Read (MREADER* reader, void* ptr, size_t size) +{ + return uio_fread (ptr, size, 1, ((MUIOREADER*)reader)->file); +} + +static int +moda_uioReader_Get (MREADER* reader) +{ + return uio_fgetc (((MUIOREADER*)reader)->file); +} + +static BOOL +moda_uioReader_Seek (MREADER* reader, long offset, int whence) +{ + return uio_fseek (((MUIOREADER*)reader)->file, offset, whence); +} + +static long +moda_uioReader_Tell (MREADER* reader) +{ + return uio_ftell (((MUIOREADER*)reader)->file); +} + +static MREADER* +moda_new_uioReader (uio_Stream* fp) +{ + MUIOREADER* reader = (MUIOREADER*) HMalloc (sizeof(MUIOREADER)); + if (reader) + { + reader->core.Eof = &moda_uioReader_Eof; + reader->core.Read = &moda_uioReader_Read; + reader->core.Get = &moda_uioReader_Get; + reader->core.Seek = &moda_uioReader_Seek; + reader->core.Tell = &moda_uioReader_Tell; + reader->file = fp; + } + return (MREADER*)reader; +} + +static void +moda_delete_uioReader (MREADER* reader) +{ + if (reader) + HFree (reader); +} + + +static const char* +moda_GetName (void) +{ + return "MikMod"; +} + +static bool +moda_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + MikMod_RegisterDriver (&moda_mmout_drv); + MikMod_RegisterAllLoaders (); + + if (flags & audio_QUALITY_HIGH) + { + md_mode = DMODE_HQMIXER|DMODE_STEREO|DMODE_16BITS|DMODE_INTERP|DMODE_SURROUND; + md_mixfreq = 44100; + md_reverb = 1; + } + else if (flags & audio_QUALITY_LOW) + { + md_mode = DMODE_SOFT_MUSIC|DMODE_STEREO|DMODE_16BITS; +#ifdef __SYMBIAN32__ + md_mixfreq = 11025; +#else + md_mixfreq = 22050; +#endif + md_reverb = 0; + } + else + { + md_mode = DMODE_SOFT_MUSIC|DMODE_STEREO|DMODE_16BITS|DMODE_INTERP; + md_mixfreq = 44100; + md_reverb = 0; + } + + md_pansep = 64; + + if (MikMod_Init (NULL)) + { + log_add (log_Error, "MikMod_Init() failed, %s", + MikMod_strerror (MikMod_errno)); + return false; + } + + moda_formats = fmts; + + return true; +} + +static void +moda_TermModule (void) +{ + MikMod_Exit (); +} + +static uint32 +moda_GetStructSize (void) +{ + return sizeof (TFB_ModSoundDecoder); +} + +static int +moda_GetError (THIS_PTR) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + int ret = moda->last_error; + moda->last_error = 0; + return ret; +} + +static bool +moda_Init (THIS_PTR) +{ + //TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + This->need_swap = + moda_formats->big_endian != moda_formats->want_big_endian; + return true; +} + +static void +moda_Term (THIS_PTR) +{ + //TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + moda_Close (This); // ensure cleanup +} + +static bool +moda_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + uio_Stream *fp; + MREADER* reader; + MODULE* mod; + + fp = uio_fopen (dir, filename, "rb"); + if (!fp) + { + moda->last_error = errno; + return false; + } + + reader = moda_new_uioReader (fp); + if (!reader) + { + moda->last_error = -1; + uio_fclose (fp); + return false; + } + + mod = Player_LoadGeneric (reader, 8, 0); + + // can already dispose of reader and fileh + moda_delete_uioReader (reader); + uio_fclose (fp); + if (!mod) + { + log_add (log_Warning, "moda_Open(): could not load %s", filename); + return false; + } + + moda->module = mod; + mod->extspd = 1; + mod->panflag = 1; + mod->wrap = 0; + mod->loop = 1; + + This->format = moda_formats->stereo16; + This->frequency = md_mixfreq; + This->length = 0; // FIXME way to obtain this from mikmod? + + moda->last_error = 0; + + return true; +} + +static void +moda_Close (THIS_PTR) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + + if (moda->module) + { + Player_Free (moda->module); + moda->module = NULL; + } +} + +static int +moda_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + volatile ULONG* poutsize; + + Player_Start (moda->module); + if (!Player_Active()) + return 0; + + poutsize = moda_mmout_SetOutputBuffer (buf, bufsize); + MikMod_Update (); + + return *poutsize; +} + +static uint32 +moda_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + + Player_Start (moda->module); + if (pcm_pos) + log_add (log_Debug, "moda_Seek(): " + "non-zero seek positions not supported for mod"); + Player_SetPosition (0); + + return 0; +} + +static uint32 +moda_GetFrame (THIS_PTR) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + return moda->module->sngpos; +} diff --git a/src/libs/sound/decoders/modaud.h b/src/libs/sound/decoders/modaud.h new file mode 100644 index 0000000..3b0eb86 --- /dev/null +++ b/src/libs/sound/decoders/modaud.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. + */ + +/* MikMod adapter */ + +#ifndef MODAUD_H +#define MODAUD_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs moda_DecoderVtbl; + +#endif // MODAUD_H diff --git a/src/libs/sound/decoders/oggaud.c b/src/libs/sound/decoders/oggaud.c new file mode 100644 index 0000000..6227120 --- /dev/null +++ b/src/libs/sound/decoders/oggaud.c @@ -0,0 +1,278 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Ogg Vorbis decoder (.ogg adapter) + */ + +#include +#include +#include +#include "libs/log.h" +#include "port.h" +#include "types.h" +#include "libs/uio.h" +#include "decoder.h" +#ifdef OVCODEC_TREMOR +# include +# include +#else +# include +# include +#endif /* OVCODEC_TREMOR */ +#include "oggaud.h" + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* ova_GetName (void); +static bool ova_InitModule (int flags, const TFB_DecoderFormats*); +static void ova_TermModule (void); +static uint32 ova_GetStructSize (void); +static int ova_GetError (THIS_PTR); +static bool ova_Init (THIS_PTR); +static void ova_Term (THIS_PTR); +static bool ova_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void ova_Close (THIS_PTR); +static int ova_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 ova_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 ova_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs ova_DecoderVtbl = +{ + ova_GetName, + ova_InitModule, + ova_TermModule, + ova_GetStructSize, + ova_GetError, + ova_Init, + ova_Term, + ova_Open, + ova_Close, + ova_Decode, + ova_Seek, + ova_GetFrame, +}; + +typedef struct tfb_oggsounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + sint32 last_error; + OggVorbis_File vf; + +} TFB_OggSoundDecoder; + +static const TFB_DecoderFormats* ova_formats = NULL; + +static size_t +ogg_read (void *ptr, size_t size, size_t nmemb, void *datasource) +{ + return uio_fread (ptr, size, nmemb, (uio_Stream *) datasource); +} + +static int +ogg_seek (void *datasource, ogg_int64_t offset, int whence) +{ + long off = (long) offset; + return uio_fseek ((uio_Stream *) datasource, off, whence); +} + +static int +ogg_close (void *datasource) +{ + return uio_fclose ((uio_Stream *) datasource); +} + +static long +ogg_tell (void *datasource) +{ + return uio_ftell ((uio_Stream *) datasource); +} + +static const ov_callbacks ogg_callbacks = +{ + ogg_read, + ogg_seek, + ogg_close, + ogg_tell, +}; + +static const char* +ova_GetName (void) +{ + return "Ogg Vorbis"; +} + +static bool +ova_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + ova_formats = fmts; + return true; + + (void)flags; // laugh at compiler warning +} + +static void +ova_TermModule (void) +{ + // no specific module term +} + +static uint32 +ova_GetStructSize (void) +{ + return sizeof (TFB_OggSoundDecoder); +} + +static int +ova_GetError (THIS_PTR) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + int ret = ova->last_error; + ova->last_error = 0; + return ret; +} + +static bool +ova_Init (THIS_PTR) +{ + //TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + This->need_swap = false; + return true; +} + +static void +ova_Term (THIS_PTR) +{ + //TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + ova_Close (This); // ensure cleanup +} + +static bool +ova_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + int rc; + uio_Stream *fp; + vorbis_info *vinfo; + + fp = uio_fopen (dir, filename, "rb"); + if (fp == NULL) + { + log_add (log_Warning, "ova_Open(): could not open %s", filename); + return false; + } + + rc = ov_open_callbacks (fp, &ova->vf, NULL, 0, ogg_callbacks); + if (rc != 0) + { + log_add (log_Warning, "ova_Open(): " + "ov_open_callbacks failed for %s, error code %d", + filename, rc); + uio_fclose (fp); + return false; + } + + vinfo = ov_info (&ova->vf, -1); + if (!vinfo) + { + log_add (log_Warning, "ova_Open(): " + "failed to retrieve ogg bitstream info for %s", + filename); + ov_clear (&ova->vf); + return false; + } + + This->frequency = vinfo->rate; +#ifdef OVCODEC_TREMOR + // With tremor ov_time_total returns an integer, in milliseconds. + This->length = ((float) ov_time_total (&ova->vf, -1)) / 1000.0f; +#else + // With libvorbis ov_time_total returns a double, in seconds. + This->length = (float) ov_time_total (&ova->vf, -1); +#endif /* OVCODEC_TREMOR */ + + if (vinfo->channels == 1) + This->format = ova_formats->mono16; + else + This->format = ova_formats->stereo16; + + ova->last_error = 0; + + return true; +} + +static void +ova_Close (THIS_PTR) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + + ov_clear (&ova->vf); +} + +static int +ova_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + long rc; + int bitstream; + +#ifdef OVCODEC_TREMOR + rc = ov_read (&ova->vf, buf, bufsize, &bitstream); +#else + rc = ov_read (&ova->vf, buf, bufsize, ova_formats->want_big_endian, + 2, 1, &bitstream); +#endif /* OVCODEC_TREMOR */ + + if (rc < 0) + ova->last_error = rc; + else + ova->last_error = 0; + + return rc; +} + +static uint32 +ova_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + int ret; + + ret = ov_pcm_seek (&ova->vf, pcm_pos); + if (ret != 0) + { + ova->last_error = ret; + return (uint32) ov_pcm_tell (&ova->vf); + } + else + return pcm_pos; +} + +static uint32 +ova_GetFrame (THIS_PTR) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + // this is the closest to a frame there is in ogg vorbis stream + // doesn't seem to be a func to retrive it +#ifdef OVCODEC_TREMOR + return ova->vf.os->pageno; +#else + return ova->vf.os.pageno; +#endif /* OVCODEC_TREMOR */ +} + diff --git a/src/libs/sound/decoders/oggaud.h b/src/libs/sound/decoders/oggaud.h new file mode 100644 index 0000000..4e443c4 --- /dev/null +++ b/src/libs/sound/decoders/oggaud.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. + */ + +/* Ogg Vorbis adapter */ + +#ifndef OGGAUD_H +#define OGGAUD_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs ova_DecoderVtbl; + +#endif // OGGAUD_H diff --git a/src/libs/sound/decoders/wav.c b/src/libs/sound/decoders/wav.c new file mode 100644 index 0000000..c22f63f --- /dev/null +++ b/src/libs/sound/decoders/wav.c @@ -0,0 +1,385 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Wave decoder (.wav adapter) + * Code is based on Creative's Win32 OpenAL implementation. + */ + +#include +#include +#include "port.h" +#include "types.h" +#include "libs/uio.h" +#include "endian_uqm.h" +#include "libs/log.h" +#include "wav.h" + +#define wave_MAKE_ID(x1, x2, x3, x4) \ + (((x4) << 24) | ((x3) << 16) | ((x2) << 8) | (x1)) + +#define wave_RiffID wave_MAKE_ID('R', 'I', 'F', 'F') +#define wave_WaveID wave_MAKE_ID('W', 'A', 'V', 'E') +#define wave_FmtID wave_MAKE_ID('f', 'm', 't', ' ') +#define wave_DataID wave_MAKE_ID('d', 'a', 't', 'a') + +typedef struct +{ + uint32 id; + uint32 size; + uint32 type; +} wave_FileHeader; + +typedef struct +{ + uint16 format; + uint16 channels; + uint32 samplesPerSec; + uint32 bytesPerSec; + uint16 blockAlign; + uint16 bitsPerSample; +} wave_FormatHeader; + +typedef struct +{ + uint32 id; + uint32 size; +} wave_ChunkHeader; + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* wava_GetName (void); +static bool wava_InitModule (int flags, const TFB_DecoderFormats*); +static void wava_TermModule (void); +static uint32 wava_GetStructSize (void); +static int wava_GetError (THIS_PTR); +static bool wava_Init (THIS_PTR); +static void wava_Term (THIS_PTR); +static bool wava_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void wava_Close (THIS_PTR); +static int wava_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 wava_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 wava_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs wava_DecoderVtbl = +{ + wava_GetName, + wava_InitModule, + wava_TermModule, + wava_GetStructSize, + wava_GetError, + wava_Init, + wava_Term, + wava_Open, + wava_Close, + wava_Decode, + wava_Seek, + wava_GetFrame, +}; + +typedef struct tfb_wavesounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + sint32 last_error; + uio_Stream *fp; + wave_FormatHeader fmtHdr; + uint32 data_ofs; + uint32 data_size; + uint32 max_pcm; + uint32 cur_pcm; + +} TFB_WaveSoundDecoder; + +static const TFB_DecoderFormats* wava_formats = NULL; + + +static const char* +wava_GetName (void) +{ + return "Wave"; +} + +static bool +wava_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + wava_formats = fmts; + return true; + + (void)flags; // laugh at compiler warning +} + +static void +wava_TermModule (void) +{ + // no specific module term +} + +static uint32 +wava_GetStructSize (void) +{ + return sizeof (TFB_WaveSoundDecoder); +} + +static int +wava_GetError (THIS_PTR) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + int ret = wava->last_error; + wava->last_error = 0; + return ret; +} + +static bool +wava_Init (THIS_PTR) +{ + //TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + This->need_swap = wava_formats->want_big_endian; + return true; +} + +static void +wava_Term (THIS_PTR) +{ + //TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + wava_Close (This); // ensure cleanup +} + +static bool +read_le_16 (uio_Stream *fp, uint16 *v) +{ + if (!uio_fread (v, sizeof(*v), 1, fp)) + return false; + *v = UQM_SwapLE16 (*v); + return true; +} + +static bool +read_le_32 (uio_Stream *fp, uint32 *v) +{ + if (!uio_fread (v, sizeof(*v), 1, fp)) + return false; + *v = UQM_SwapLE32 (*v); + return true; +} + +static bool +wava_readFileHeader (TFB_WaveSoundDecoder* wava, wave_FileHeader* hdr) +{ + if (!read_le_32 (wava->fp, &hdr->id) || + !read_le_32 (wava->fp, &hdr->size) || + !read_le_32 (wava->fp, &hdr->type)) + { + wava->last_error = errno; + return false; + } + return true; +} + +static bool +wava_readChunkHeader (TFB_WaveSoundDecoder* wava, wave_ChunkHeader* chunk) +{ + if (!read_le_32 (wava->fp, &chunk->id) || + !read_le_32 (wava->fp, &chunk->size)) + { + wava->last_error = errno; + return false; + } + return true; +} + +static bool +wava_readFormatHeader (TFB_WaveSoundDecoder* wava, wave_FormatHeader* fmt) +{ + if (!read_le_16 (wava->fp, &fmt->format) || + !read_le_16 (wava->fp, &fmt->channels) || + !read_le_32 (wava->fp, &fmt->samplesPerSec) || + !read_le_32 (wava->fp, &fmt->bytesPerSec) || + !read_le_16 (wava->fp, &fmt->blockAlign) || + !read_le_16 (wava->fp, &fmt->bitsPerSample)) + { + wava->last_error = errno; + return false; + } + return true; +} + +static bool +wava_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + wave_FileHeader fileHdr; + wave_ChunkHeader chunkHdr; + long dataLeft; + + wava->fp = uio_fopen (dir, filename, "rb"); + if (!wava->fp) + { + wava->last_error = errno; + return false; + } + + wava->data_size = 0; + wava->data_ofs = 0; + + // read wave header + if (!wava_readFileHeader (wava, &fileHdr)) + { + wava->last_error = errno; + wava_Close (This); + return false; + } + if (fileHdr.id != wave_RiffID || fileHdr.type != wave_WaveID) + { + log_add (log_Warning, "wava_Open(): " + "not a wave file, ID 0x%08x, Type 0x%08x", + fileHdr.id, fileHdr.type); + wava_Close (This); + return false; + } + + for (dataLeft = ((fileHdr.size + 1) & ~1) - 4; dataLeft > 0; + dataLeft -= (((chunkHdr.size + 1) & ~1) + 8)) + { + if (!wava_readChunkHeader (wava, &chunkHdr)) + { + wava_Close (This); + return false; + } + + if (chunkHdr.id == wave_FmtID) + { + if (!wava_readFormatHeader (wava, &wava->fmtHdr)) + { + wava_Close (This); + return false; + } + uio_fseek (wava->fp, chunkHdr.size - 16, SEEK_CUR); + } + else + { + if (chunkHdr.id == wave_DataID) + { + wava->data_size = chunkHdr.size; + wava->data_ofs = uio_ftell (wava->fp); + } + uio_fseek (wava->fp, chunkHdr.size, SEEK_CUR); + } + + // 2-align the file ptr + // XXX: I do not think this is necessary in WAVE files; + // possibly a remnant of ported AIFF reader + uio_fseek (wava->fp, chunkHdr.size & 1, SEEK_CUR); + } + + if (!wava->data_size || !wava->data_ofs) + { + log_add (log_Warning, "wava_Open(): bad wave file," + " no DATA chunk found"); + wava_Close (This); + return false; + } + + if (wava->fmtHdr.format != 0x0001) + { // not a PCM format + log_add (log_Warning, "wava_Open(): unsupported format %x", + wava->fmtHdr.format); + wava_Close (This); + return false; + } + if (wava->fmtHdr.channels != 1 && wava->fmtHdr.channels != 2) + { + log_add (log_Warning, "wava_Open(): unsupported number of channels %u", + (unsigned)wava->fmtHdr.channels); + wava_Close (This); + return false; + } + + if (dataLeft != 0) + log_add (log_Warning, "wava_Open(): bad or unsupported wave file, " + "size in header does not match read chunks"); + + This->format = (wava->fmtHdr.channels == 1 ? + (wava->fmtHdr.bitsPerSample == 8 ? + wava_formats->mono8 : wava_formats->mono16) + : + (wava->fmtHdr.bitsPerSample == 8 ? + wava_formats->stereo8 : wava_formats->stereo16) + ); + This->frequency = wava->fmtHdr.samplesPerSec; + + uio_fseek (wava->fp, wava->data_ofs, SEEK_SET); + wava->max_pcm = wava->data_size / wava->fmtHdr.blockAlign; + wava->cur_pcm = 0; + This->length = (float) wava->max_pcm / wava->fmtHdr.samplesPerSec; + wava->last_error = 0; + + return true; +} + +static void +wava_Close (THIS_PTR) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + + if (wava->fp) + { + uio_fclose (wava->fp); + wava->fp = NULL; + } +} + +static int +wava_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + uint32 dec_pcm; + + dec_pcm = bufsize / wava->fmtHdr.blockAlign; + if (dec_pcm > wava->max_pcm - wava->cur_pcm) + dec_pcm = wava->max_pcm - wava->cur_pcm; + + dec_pcm = uio_fread (buf, wava->fmtHdr.blockAlign, dec_pcm, wava->fp); + wava->cur_pcm += dec_pcm; + + return dec_pcm * wava->fmtHdr.blockAlign; +} + +static uint32 +wava_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + + if (pcm_pos > wava->max_pcm) + pcm_pos = wava->max_pcm; + wava->cur_pcm = pcm_pos; + uio_fseek (wava->fp, + wava->data_ofs + pcm_pos * wava->fmtHdr.blockAlign, + SEEK_SET); + + return pcm_pos; +} + +static uint32 +wava_GetFrame (THIS_PTR) +{ + //TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + return 0; // only 1 frame for now + + (void)This; // laugh at compiler warning +} diff --git a/src/libs/sound/decoders/wav.h b/src/libs/sound/decoders/wav.h new file mode 100644 index 0000000..9aaf347 --- /dev/null +++ b/src/libs/sound/decoders/wav.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. + */ + +/* Wave decoder */ + +#ifndef WAV_H +#define WAV_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs wava_DecoderVtbl; + +#endif diff --git a/src/libs/sound/fileinst.c b/src/libs/sound/fileinst.c new file mode 100644 index 0000000..cafbb8f --- /dev/null +++ b/src/libs/sound/fileinst.c @@ -0,0 +1,87 @@ +//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 "sound.h" +#include "sndintrn.h" +#include "options.h" +#include "libs/reslib.h" +#include + + +SOUND_REF +LoadSoundFile (const char *pStr) +{ + uio_Stream *fp; + + // FIXME: this theoretically needs a mechanism to prevent races + if (_cur_resfile_name) + // something else is loading resources atm + return 0; + + fp = res_OpenResFile (contentDir, pStr, "rb"); + if (fp) + { + SOUND_REF hData; + + _cur_resfile_name = pStr; + hData = (SOUND_REF)_GetSoundBankData (fp, LengthResFile (fp)); + _cur_resfile_name = 0; + + res_CloseResFile (fp); + + return hData; + } + + return NULL; +} + +MUSIC_REF +LoadMusicFile (const char *pStr) +{ + uio_Stream *fp; + char filename[256]; + + // FIXME: this theoretically needs a mechanism to prevent races + if (_cur_resfile_name) + // something else is loading resources atm + return 0; + + strncpy (filename, pStr, sizeof(filename) - 1); + filename[sizeof(filename) - 1] = '\0'; + CheckMusicResName (filename); + + // Opening the res file is not technically necessary right now + // since _GetMusicData() completely ignores the arguments + // But just for the sake of correctness + fp = res_OpenResFile (contentDir, filename, "rb"); + if (fp) + { + MUSIC_REF hData; + + _cur_resfile_name = filename; + hData = (MUSIC_REF)_GetMusicData (fp, LengthResFile (fp)); + _cur_resfile_name = 0; + + res_CloseResFile (fp); + + return hData; + } + + return (0); +} + diff --git a/src/libs/sound/mixer/Makeinfo b/src/libs/sound/mixer/Makeinfo new file mode 100644 index 0000000..66f960d --- /dev/null +++ b/src/libs/sound/mixer/Makeinfo @@ -0,0 +1,3 @@ +uqm_SUBDIRS="sdl nosound" +uqm_CFILES="mixer.c" +uqm_HFILES="mixer.h mixerint.h" diff --git a/src/libs/sound/mixer/mixer.c b/src/libs/sound/mixer/mixer.c new file mode 100644 index 0000000..3e14ddd --- /dev/null +++ b/src/libs/sound/mixer/mixer.c @@ -0,0 +1,1760 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Mixer for low-level sound output drivers + */ + +#include +#include +#include +#include "mixer.h" +#include "mixerint.h" +#include "libs/misc.h" +#include "libs/threadlib.h" +#include "libs/log.h" +#include "libs/memlib.h" + +static uint32 mixer_initialized = 0; +static uint32 mixer_format; +static uint32 mixer_chansize; +static uint32 mixer_sampsize; +static uint32 mixer_freq; +static uint32 mixer_channels; +static uint32 last_error = MIX_NO_ERROR; +static mixer_Quality mixer_quality; +static mixer_Resampling mixer_resampling; +static mixer_Flags mixer_flags; + +/* when locking more than one mutex + * you must lock them in this order + */ +static RecursiveMutex src_mutex; +static RecursiveMutex buf_mutex; +static RecursiveMutex act_mutex; + +#define MAX_SOURCES 8 +mixer_Source *active_sources[MAX_SOURCES]; + + +/************************************************* + * Internals + */ + +static void +mixer_SetError (uint32 error) +{ + last_error = error; +} + + +/************************************************* + * General interface + */ + +uint32 +mixer_GetError (void) +{ + uint32 error = last_error; + last_error = MIX_NO_ERROR; + return error; +} + +/* Initialize the mixer with a certain audio format */ +bool +mixer_Init (uint32 frequency, uint32 format, mixer_Quality quality, + mixer_Flags flags) +{ + if (mixer_initialized) + mixer_Uninit (); + + last_error = MIX_NO_ERROR; + memset (active_sources, 0, sizeof(mixer_Source*) * MAX_SOURCES); + + mixer_chansize = MIX_FORMAT_BPC (format); + mixer_channels = MIX_FORMAT_CHANS (format); + mixer_sampsize = MIX_FORMAT_SAMPSIZE (format); + mixer_freq = frequency; + mixer_quality = quality; + mixer_format = format; + mixer_flags = flags; + + mixer_resampling.None = mixer_ResampleNone; + mixer_resampling.Downsample = mixer_ResampleNearest; + if (mixer_quality == MIX_QUALITY_DEFAULT) + mixer_resampling.Upsample = mixer_UpsampleLinear; + else if (mixer_quality == MIX_QUALITY_HIGH) + mixer_resampling.Upsample = mixer_UpsampleCubic; + else + mixer_resampling.Upsample = mixer_ResampleNearest; + + src_mutex = CreateRecursiveMutex("mixer_SourceMutex", SYNC_CLASS_AUDIO); + buf_mutex = CreateRecursiveMutex("mixer_BufferMutex", SYNC_CLASS_AUDIO); + act_mutex = CreateRecursiveMutex("mixer_ActiveMutex", SYNC_CLASS_AUDIO); + + mixer_initialized = 1; + + return true; +} + +/* Uninitialize the mixer */ +void +mixer_Uninit (void) +{ + if (mixer_initialized) + { + DestroyRecursiveMutex (src_mutex); + DestroyRecursiveMutex (buf_mutex); + DestroyRecursiveMutex (act_mutex); + mixer_initialized = 0; + } +} + + +/********************************************************** + * THE mixer + * + */ + +void +mixer_MixChannels (void *userdata, uint8 *stream, sint32 len) +{ + uint8 *end_stream = stream + len; + bool left = true; + + /* keep this order or die */ + LockRecursiveMutex (src_mutex); + LockRecursiveMutex (buf_mutex); + LockRecursiveMutex (act_mutex); + + for (; stream < end_stream; stream += mixer_chansize) + { + uint32 i; + float fullsamp = 0; + + for (i = 0; i < MAX_SOURCES; i++) + { + mixer_Source *src; + float samp = 0; + + /* find next source */ + for (; i < MAX_SOURCES && ( + (src = active_sources[i]) == 0 + || src->state != MIX_PLAYING + || !mixer_SourceGetNextSample (src, &samp, left)); + i++) + ; + + if (i < MAX_SOURCES) + { + /* sample acquired */ + fullsamp += samp; + } + } + + /* clip the sample */ + if (mixer_chansize == 2) + { + /* check S16 clipping */ + if (fullsamp > SINT16_MAX) + fullsamp = SINT16_MAX; + else if (fullsamp < SINT16_MIN) + fullsamp = SINT16_MIN; + } + else + { + /* check S8 clipping */ + if (fullsamp > SINT8_MAX) + fullsamp = SINT8_MAX; + else if (fullsamp < SINT8_MIN) + fullsamp = SINT8_MIN; + } + + mixer_PutSampleExt (stream, mixer_chansize, (sint32)fullsamp); + if (mixer_channels == 2) + left = !left; + } + + /* keep this order or die */ + UnlockRecursiveMutex (act_mutex); + UnlockRecursiveMutex (buf_mutex); + UnlockRecursiveMutex (src_mutex); + + (void) userdata; // satisfying compiler - unused arg +} + +/* fake mixer -- only process buffer and source states */ +void +mixer_MixFake (void *userdata, uint8 *stream, sint32 len) +{ + uint8 *end_stream = stream + len; + bool left = true; + + /* keep this order or die */ + LockRecursiveMutex (src_mutex); + LockRecursiveMutex (buf_mutex); + LockRecursiveMutex (act_mutex); + + for (; stream < end_stream; stream += mixer_chansize) + { + uint32 i; + + for (i = 0; i < MAX_SOURCES; i++) + { + mixer_Source *src; + float samp; + + /* find next source */ + for (; i < MAX_SOURCES && ( + (src = active_sources[i]) == 0 + || src->state != MIX_PLAYING + || !mixer_SourceGetFakeSample (src, &samp, left)); + i++) + ; + } + if (mixer_channels == 2) + left = !left; + } + + /* keep this order or die */ + UnlockRecursiveMutex (act_mutex); + UnlockRecursiveMutex (buf_mutex); + UnlockRecursiveMutex (src_mutex); + + (void) userdata; // satisfying compiler - unused arg +} + + +/************************************************* + * Sources interface + */ + +/* generate n sources */ +void +mixer_GenSources (uint32 n, mixer_Object *psrcobj) +{ + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!psrcobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GenSources() called with null ptr"); + return; + } + for (; n; n--, psrcobj++) + { + mixer_Source *src; + + src = (mixer_Source *) HMalloc (sizeof (mixer_Source)); + src->magic = mixer_srcMagic; + src->locked = false; + src->state = MIX_INITIAL; + src->looping = false; + src->gain = MIX_GAIN_ADJ; + src->cqueued = 0; + src->cprocessed = 0; + src->firstqueued = 0; + src->nextqueued = 0; + src->prevqueued = 0; + src->lastqueued = 0; + src->pos = 0; + src->count = 0; + + *psrcobj = (mixer_Object) src; + } +} + +/* delete n sources */ +void +mixer_DeleteSources (uint32 n, mixer_Object *psrcobj) +{ + uint32 i; + mixer_Object *pcurobj; + + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!psrcobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteSources() called with null ptr"); + return; + } + + LockRecursiveMutex (src_mutex); + + /* check to make sure we can delete all sources */ + for (i = n, pcurobj = psrcobj; i && pcurobj; i--, pcurobj++) + { + mixer_Source *src = (mixer_Source *) *pcurobj; + + if (!src) + continue; + + if (src->magic != mixer_srcMagic) + break; + } + + if (i) + { /* some source failed */ + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteSources(): not a source"); + } + else + { /* all sources checked out */ + for (; n; n--, psrcobj++) + { + mixer_Source *src = (mixer_Source *) *psrcobj; + + if (!src) + continue; + + /* stopping should not be necessary + * under ideal circumstances + */ + if (src->state != MIX_INITIAL) + mixer_SourceStop_internal (src); + + /* unqueueing should not be necessary + * under ideal circumstances + */ + mixer_SourceUnqueueAll (src); + HFree (src); + *psrcobj = 0; + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* check if really is a source */ +bool +mixer_IsSource (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + bool ret; + + if (!src) + return false; + + LockRecursiveMutex (src_mutex); + ret = src->magic == mixer_srcMagic; + UnlockRecursiveMutex (src_mutex); + + return ret; +} + +/* set source integer property */ +void +mixer_Sourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcei() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcei(): not a source"); + } + else + { + switch (pname) + { + case MIX_LOOPING: + src->looping = value; + break; + case MIX_BUFFER: + { + mixer_Buffer *buf = (mixer_Buffer *) value; + + if (src->cqueued > 0) + mixer_SourceUnqueueAll (src); + + if (buf && !mixer_CheckBufferState (buf, "mixer_Sourcei")) + break; + + src->firstqueued = buf; + src->nextqueued = src->firstqueued; + src->prevqueued = 0; + src->lastqueued = src->nextqueued; + if (src->lastqueued) + src->lastqueued->next = 0; + src->cqueued = 1; + } + break; + case MIX_SOURCE_STATE: + if (value == MIX_INITIAL) + { + mixer_SourceRewind_internal (src); + } + else + { + log_add (log_Debug, "mixer_Sourcei(MIX_SOURCE_STATE): " + "unsupported state, call ignored"); + } + break; + default: + mixer_SetError (MIX_INVALID_ENUM); + log_add (log_Debug, "mixer_Sourcei() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* set source float property */ +void +mixer_Sourcef (mixer_Object srcobj, mixer_SourceProp pname, float value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcef() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcef(): not a source"); + } + else + { + switch (pname) + { + case MIX_GAIN: + src->gain = value * MIX_GAIN_ADJ; + break; + default: + log_add (log_Debug, "mixer_Sourcei() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* set source float array property (CURRENTLY NOT IMPLEMENTED) */ +void mixer_Sourcefv (mixer_Object srcobj, mixer_SourceProp pname, float *value) +{ + (void)srcobj; + (void)pname; + (void)value; +} + + +/* get source integer property */ +void +mixer_GetSourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal *value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src || !value) + { + mixer_SetError (src ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcei() called with null param"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcei(): not a source"); + } + else + { + switch (pname) + { + case MIX_LOOPING: + *value = src->looping; + break; + case MIX_BUFFER: + *value = (mixer_IntVal) src->firstqueued; + break; + case MIX_SOURCE_STATE: + *value = src->state; + break; + case MIX_BUFFERS_QUEUED: + *value = src->cqueued; + break; + case MIX_BUFFERS_PROCESSED: + *value = src->cprocessed; + break; + default: + mixer_SetError (MIX_INVALID_ENUM); + log_add (log_Debug, "mixer_GetSourcei() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* get source float property */ +void +mixer_GetSourcef (mixer_Object srcobj, mixer_SourceProp pname, + float *value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src || !value) + { + mixer_SetError (src ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcef() called with null param"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcef(): not a source"); + } + else + { + switch (pname) + { + case MIX_GAIN: + *value = src->gain / MIX_GAIN_ADJ; + break; + default: + log_add (log_Debug, "mixer_GetSourcef() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* start the source; add it to active array */ +void +mixer_SourcePlay (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePlay() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePlay(): not a source"); + } + else /* should make the source active */ + { + if (src->state < MIX_PLAYING) + { + if (src->firstqueued && !src->nextqueued) + mixer_SourceRewind_internal (src); + mixer_SourceActivate (src); + } + src->state = MIX_PLAYING; + } + + UnlockRecursiveMutex (src_mutex); +} + +/* stop the source; remove it from active array and requeue buffers */ +void +mixer_SourceRewind (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceRewind() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePlay(): not a source"); + } + else + { + mixer_SourceRewind_internal (src); + } + + UnlockRecursiveMutex (src_mutex); +} + +/* pause the source; keep in active array */ +void +mixer_SourcePause (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePause() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePause(): not a source"); + } + else /* should keep all buffers and offsets */ + { + if (src->state < MIX_PLAYING) + mixer_SourceActivate (src); + src->state = MIX_PAUSED; + } + + UnlockRecursiveMutex (src_mutex); +} + +/* stop the source; remove it from active array + * and unqueue 'queued' buffers + */ +void +mixer_SourceStop (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceStop() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceStop(): not a source"); + } + else /* should remove queued buffers */ + { + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + mixer_SourceStop_internal (src); + src->state = MIX_STOPPED; + } + + UnlockRecursiveMutex (src_mutex); +} + +/* queue buffers on the source */ +void +mixer_SourceQueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj) +{ + uint32 i; + mixer_Object* pobj; + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src || !pbufobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceQueueBuffers() called " + "with null param"); + return; + } + + LockRecursiveMutex (buf_mutex); + /* check to make sure we can safely queue all buffers */ + for (i = n, pobj = pbufobj; i; i--, pobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pobj; + if (!buf || !mixer_CheckBufferState (buf, + "mixer_SourceQueueBuffers")) + { + break; + } + } + UnlockRecursiveMutex (buf_mutex); + + if (i == 0) + { /* all buffers checked out */ + LockRecursiveMutex (src_mutex); + LockRecursiveMutex (buf_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceQueueBuffers(): not a source"); + } + else + { + for (i = n, pobj = pbufobj; i; i--, pobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pobj; + + /* add buffer to the chain */ + if (src->lastqueued) + src->lastqueued->next = buf; + src->lastqueued = buf; + + if (!src->firstqueued) + { + src->firstqueued = buf; + src->nextqueued = buf; + src->prevqueued = 0; + } + src->cqueued++; + buf->state = MIX_BUF_QUEUED; + } + } + + UnlockRecursiveMutex (buf_mutex); + UnlockRecursiveMutex (src_mutex); + } +} + +/* unqueue buffers from the source */ +void +mixer_SourceUnqueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj) +{ + uint32 i; + mixer_Source *src = (mixer_Source *) srcobj; + mixer_Buffer *curbuf = 0; + + if (!src || !pbufobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceUnqueueBuffers() called " + "with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceUnqueueBuffers(): not a source"); + } + else if (n > src->cqueued) + { + mixer_SetError (MIX_INVALID_OPERATION); + } + else + { + LockRecursiveMutex (buf_mutex); + + /* check to make sure we can unqueue all buffers */ + for (i = n, curbuf = src->firstqueued; + i && curbuf && curbuf->state != MIX_BUF_PLAYING; + i--, curbuf = curbuf->next) + ; + + if (i) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_SourceUnqueueBuffers(): " + "active buffer attempted"); + } + else + { /* all buffers checked out */ + for (i = n; i; i--, pbufobj++) + { + mixer_Buffer *buf = src->firstqueued; + + /* remove buffer from the chain */ + if (src->nextqueued == buf) + src->nextqueued = buf->next; + if (src->prevqueued == buf) + src->prevqueued = 0; + if (src->lastqueued == buf) + src->lastqueued = 0; + src->firstqueued = buf->next; + src->cqueued--; + + if (buf->state == MIX_BUF_PROCESSED) + src->cprocessed--; + + buf->state = MIX_BUF_FILLED; + buf->next = 0; + *pbufobj = (mixer_Object) buf; + } + } + + UnlockRecursiveMutex (buf_mutex); + } + + UnlockRecursiveMutex (src_mutex); +} + +/************************************************* + * Sources internals + */ + +static void +mixer_SourceUnqueueAll (mixer_Source *src) +{ + mixer_Buffer *buf; + mixer_Buffer *nextbuf; + + if (!src) + { + log_add (log_Debug, "mixer_SourceUnqueueAll() called " + "with null source"); + return; + } + + LockRecursiveMutex (buf_mutex); + + for (buf = src->firstqueued; buf; buf = nextbuf) + { + if (buf->state == MIX_BUF_PLAYING) + { + log_add (log_Debug, "mixer_SourceUnqueueAll(): " + "attempted on active buffer"); + } + nextbuf = buf->next; + buf->state = MIX_BUF_FILLED; + buf->next = 0; + } + + UnlockRecursiveMutex (buf_mutex); + + src->firstqueued = 0; + src->nextqueued = 0; + src->prevqueued = 0; + src->lastqueued = 0; + src->cqueued = 0; + src->cprocessed = 0; + src->pos = 0; + src->count = 0; +} + +/* add the source to the active array */ +static void +mixer_SourceActivate (mixer_Source* src) +{ + uint32 i; + + LockRecursiveMutex (act_mutex); + + /* check active sources, see if this source is there already */ + for (i = 0; i < MAX_SOURCES && active_sources[i] != src; i++) + ; + if (i < MAX_SOURCES) + { /* source found */ + log_add (log_Debug, "mixer_SourceActivate(): " + "source already active in slot %u", i); + UnlockRecursiveMutex (act_mutex); + return; + } + + /* find an empty slot */ + for (i = 0; i < MAX_SOURCES && active_sources[i] != 0; i++) + ; + if (i < MAX_SOURCES) + { /* slot found */ + active_sources[i] = src; + } + else + { + log_add (log_Debug, "mixer_SourceActivate(): " + "no more slots available (max=%d)", MAX_SOURCES); + } + + UnlockRecursiveMutex (act_mutex); +} + +/* remove the source from the active array */ +static void +mixer_SourceDeactivate (mixer_Source* src) +{ + uint32 i; + + LockRecursiveMutex (act_mutex); + + /* check active sources, see if this source is there */ + for (i = 0; i < MAX_SOURCES && active_sources[i] != src; i++) + ; + if (i < MAX_SOURCES) + { /* source found */ + active_sources[i] = 0; + } + else + { /* source not found */ + log_add (log_Debug, "mixer_SourceDeactivate(): source not active"); + } + + UnlockRecursiveMutex (act_mutex); +} + +static void +mixer_SourceStop_internal (mixer_Source *src) +{ + mixer_Buffer *buf; + mixer_Buffer *nextbuf; + + if (!src->firstqueued) + return; + + /* assert the source buffers state */ + if (!src->lastqueued) + { + log_add (log_Debug, "mixer_SourceStop_internal(): " + "desynced source state"); +#ifdef DEBUG + explode (); +#endif + } + + LockRecursiveMutex (buf_mutex); + + /* find last 'processed' buffer */ + for (buf = src->firstqueued; + buf && buf->next && buf->next != src->nextqueued; + buf = buf->next) + ; + src->lastqueued = buf; + if (buf) + buf->next = 0; /* break the chain */ + + /* unqueue all 'queued' buffers */ + for (buf = src->nextqueued; buf; buf = nextbuf) + { + nextbuf = buf->next; + buf->state = MIX_BUF_FILLED; + buf->next = 0; + src->cqueued--; + } + + if (src->cqueued == 0) + { /* all buffers were removed */ + src->firstqueued = 0; + src->lastqueued = 0; + } + src->nextqueued = 0; + src->prevqueued = 0; + src->pos = 0; + src->count = 0; + + UnlockRecursiveMutex (buf_mutex); +} + +static void +mixer_SourceRewind_internal (mixer_Source *src) +{ + /* should change the processed buffers to queued */ + mixer_Buffer *buf; + + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + + LockRecursiveMutex (buf_mutex); + + for (buf = src->firstqueued; + buf && buf->state != MIX_BUF_QUEUED; + buf = buf->next) + { + buf->state = MIX_BUF_QUEUED; + } + + UnlockRecursiveMutex (buf_mutex); + + src->pos = 0; + src->count = 0; + src->cprocessed = 0; + src->nextqueued = src->firstqueued; + src->prevqueued = 0; + src->state = MIX_INITIAL; +} + +/* get the sample next in queue in internal format */ +static inline bool +mixer_SourceGetNextSample (mixer_Source *src, float *psamp, bool left) +{ + /* fake the data if requested */ + if (mixer_flags & MIX_FAKE_DATA) + return mixer_SourceGetFakeSample (src, psamp, left); + + while (src->nextqueued) + { + mixer_Buffer *buf = src->nextqueued; + + if (!buf->data || buf->size < mixer_sampsize) + { + /* buffer invalid, go next */ + buf->state = MIX_BUF_PROCESSED; + src->pos = 0; + src->nextqueued = src->nextqueued->next; + src->cprocessed++; + continue; + } + + if (!left && buf->orgchannels == 1) + { + /* mono source so we can copy left channel to right */ + *psamp = src->samplecache; + } + else + { + *psamp = src->samplecache = buf->Resample(src, left) * src->gain; + } + + if (src->pos < buf->size || + (left && buf->sampsize != mixer_sampsize)) + { + buf->state = MIX_BUF_PLAYING; + } + else + { + /* buffer exhausted, go next */ + buf->state = MIX_BUF_PROCESSED; + src->pos = 0; + src->prevqueued = src->nextqueued; + src->nextqueued = src->nextqueued->next; + src->cprocessed++; + } + + return true; + } + + /* no more playable buffers */ + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + + src->state = MIX_STOPPED; + + return false; +} + +/* fake the next sample, but process buffers and states */ +static inline bool +mixer_SourceGetFakeSample (mixer_Source *src, float *psamp, bool left) +{ + while (src->nextqueued) + { + mixer_Buffer *buf = src->nextqueued; + + if (left || buf->orgchannels != 1) + { + if (mixer_freq == buf->orgfreq) + src->pos += mixer_chansize; + else + mixer_SourceAdvance(src, left); + } + *psamp = 0; + + if (src->pos < buf->size || + (left && buf->sampsize != mixer_sampsize)) + { + buf->state = MIX_BUF_PLAYING; + } + else + { + /* buffer exhausted, go next */ + buf->state = MIX_BUF_PROCESSED; + src->pos = 0; + src->prevqueued = src->nextqueued; + src->nextqueued = src->nextqueued->next; + src->cprocessed++; + } + + return true; + } + + /* no more playable buffers */ + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + + src->state = MIX_STOPPED; + + return false; +} + +/* advance position in currently queued buffer */ +static inline uint32 +mixer_SourceAdvance (mixer_Source *src, bool left) +{ + mixer_Buffer *curr = src->nextqueued; + if (curr->orgchannels == 2 && mixer_channels == 2) + { + if (!left) + { + src->pos += curr->high; + src->count += curr->low; + if (src->count > UINT16_MAX) + { + src->count -= UINT16_MAX; + src->pos += curr->sampsize; + } + return mixer_chansize; + } + } + else + { + src->pos += curr->high; + src->count += curr->low; + if (src->count > UINT16_MAX) + { + src->count -= UINT16_MAX; + src->pos += curr->sampsize; + } + } + return 0; +} + + +/************************************************* + * Buffers interface + */ + +/* generate n buffer objects */ +void +mixer_GenBuffers (uint32 n, mixer_Object *pbufobj) +{ + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!pbufobj) + { + mixer_SetError (MIX_INVALID_VALUE); + log_add (log_Debug, "mixer_GenBuffers() called with null ptr"); + return; + } + for (; n; n--, pbufobj++) + { + mixer_Buffer *buf; + + buf = (mixer_Buffer *) HMalloc (sizeof (mixer_Buffer)); + buf->magic = mixer_bufMagic; + buf->locked = false; + buf->state = MIX_BUF_INITIAL; + buf->data = 0; + buf->size = 0; + buf->next = 0; + buf->orgdata = 0; + buf->orgfreq = 0; + buf->orgsize = 0; + buf->orgchannels = 0; + buf->orgchansize = 0; + + *pbufobj = (mixer_Object) buf; + } +} + +/* delete n buffer objects */ +void +mixer_DeleteBuffers (uint32 n, mixer_Object *pbufobj) +{ + uint32 i; + mixer_Object *pcurobj; + + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!pbufobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteBuffers() called with null ptr"); + return; + } + + LockRecursiveMutex (buf_mutex); + + /* check to make sure we can delete all buffers */ + for (i = n, pcurobj = pbufobj; i && pcurobj; i--, pcurobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pcurobj; + + if (!buf) + continue; + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteBuffers(): not a buffer"); + break; + } + else if (buf->locked) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_DeleteBuffers(): locked buffer"); + break; + } + else if (buf->state >= MIX_BUF_QUEUED) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_DeleteBuffers(): " + "attempted on queued/active buffer"); + break; + } + } + + if (i == 0) + { + /* all buffers check out */ + for (; n; n--, pbufobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pbufobj; + + if (!buf) + continue; + + if (buf->data) + HFree (buf->data); + HFree (buf); + + *pbufobj = 0; + } + } + UnlockRecursiveMutex (buf_mutex); +} + +/* check if really a buffer object */ +bool +mixer_IsBuffer (mixer_Object bufobj) +{ + mixer_Buffer *buf = (mixer_Buffer *) bufobj; + bool ret; + + if (!buf) + return false; + + LockRecursiveMutex (buf_mutex); + ret = buf->magic == mixer_bufMagic; + UnlockRecursiveMutex (buf_mutex); + + return ret; +} + +/* get buffer property */ +void +mixer_GetBufferi (mixer_Object bufobj, mixer_BufferProp pname, + mixer_IntVal *value) +{ + mixer_Buffer *buf = (mixer_Buffer *) bufobj; + + if (!buf || !value) + { + mixer_SetError (buf ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetBufferi() called with null param"); + return; + } + + LockRecursiveMutex (buf_mutex); + + if (buf->locked) + { + UnlockRecursiveMutex (buf_mutex); + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_GetBufferi() called with locked buffer"); + return; + } + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetBufferi(): not a buffer"); + } + else + { + /* Return original buffer values + */ + switch (pname) + { + case MIX_FREQUENCY: + *value = buf->orgfreq; + break; + case MIX_BITS: + *value = buf->orgchansize << 3; + break; + case MIX_CHANNELS: + *value = buf->orgchannels; + break; + case MIX_SIZE: + *value = buf->orgsize; + break; + case MIX_DATA: + *value = (mixer_IntVal) buf->orgdata; + break; + default: + mixer_SetError (MIX_INVALID_ENUM); + log_add (log_Debug, "mixer_GetBufferi() called " + "with invalid property %u", pname); + } + } + + UnlockRecursiveMutex (buf_mutex); +} + +/* fill buffer with external data */ +void +mixer_BufferData (mixer_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + mixer_Buffer *buf = (mixer_Buffer *) bufobj; + mixer_Convertion conv; + uint32 dstsize; + + if (!buf || !data || !size) + { + mixer_SetError (buf ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_BufferData() called with bad param"); + return; + } + + LockRecursiveMutex (buf_mutex); + + if (buf->locked) + { + UnlockRecursiveMutex (buf_mutex); + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_BufferData() called " + "with locked buffer"); + return; + } + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_BufferData(): not a buffer"); + } + else if (buf->state > MIX_BUF_FILLED) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_BufferData() attempted " + "on in-use buffer"); + } + else + { + if (buf->data) + HFree (buf->data); + buf->data = 0; + buf->size = 0; + + /* Store original buffer values for OpenAL compatibility */ + buf->orgdata = data; + buf->orgfreq = freq; + buf->orgsize = size; + buf->orgchannels = MIX_FORMAT_CHANS (format); + buf->orgchansize = MIX_FORMAT_BPC (format); + + conv.srcsamples = conv.dstsamples = + size / MIX_FORMAT_SAMPSIZE (format); + + if (conv.dstsamples > + UINT32_MAX / MIX_FORMAT_SAMPSIZE (format)) + { + mixer_SetError (MIX_INVALID_VALUE); + } + else + { + if (MIX_FORMAT_CHANS (format) < MIX_FORMAT_CHANS (mixer_format)) + buf->sampsize = MIX_FORMAT_BPC (mixer_format) * + MIX_FORMAT_CHANS (format); + else + buf->sampsize = MIX_FORMAT_SAMPSIZE (mixer_format); + buf->size = dstsize = conv.dstsamples * buf->sampsize; + + /* only copy/convert the data if not faking */ + if (! (mixer_flags & MIX_FAKE_DATA)) + { + buf->data = HMalloc (dstsize); + + if (MIX_FORMAT_BPC (format) == MIX_FORMAT_BPC (mixer_format) && + MIX_FORMAT_CHANS (format) <= MIX_FORMAT_CHANS (mixer_format)) + { + /* format is compatible with internal */ + buf->locked = true; + UnlockRecursiveMutex (buf_mutex); + + memcpy (buf->data, data, size); + if (MIX_FORMAT_BPC (format) == 1) + { + /* convert buffer to S8 format internally */ + uint8* dst; + for (dst = buf->data; dstsize; dstsize--, dst++) + *dst ^= 0x80; + } + + LockRecursiveMutex (buf_mutex); + buf->locked = false; + } + else + { + /* needs convertion */ + conv.srcfmt = format; + conv.srcdata = data; + conv.srcsize = size; + + if (MIX_FORMAT_CHANS (format) < MIX_FORMAT_CHANS (mixer_format)) + conv.dstfmt = MIX_FORMAT_MAKE (mixer_chansize, + MIX_FORMAT_CHANS (format)); + else + conv.dstfmt = mixer_format; + conv.dstdata = buf->data; + conv.dstsize = dstsize; + + buf->locked = true; + UnlockRecursiveMutex (buf_mutex); + + mixer_ConvertBuffer_internal (&conv); + + LockRecursiveMutex (buf_mutex); + buf->locked = false; + } + } + + buf->state = MIX_BUF_FILLED; + buf->high = (buf->orgfreq / mixer_freq) * buf->sampsize; + buf->low = (((buf->orgfreq % mixer_freq) << 16) / mixer_freq); + if (mixer_freq == buf->orgfreq) + buf->Resample = mixer_resampling.None; + else if (mixer_freq > buf->orgfreq) + buf->Resample = mixer_resampling.Upsample; + else + buf->Resample = mixer_resampling.Downsample; + } + } + + UnlockRecursiveMutex (buf_mutex); +} + + +/************************************************* + * Buffer internals + */ + +static inline bool +mixer_CheckBufferState (mixer_Buffer *buf, const char* FuncName) +{ + if (!buf) + return false; + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "%s(): not a buffer", FuncName); + return false; + } + + if (buf->locked) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "%s(): locked buffer attempted", FuncName); + return false; + } + + if (buf->state != MIX_BUF_FILLED) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "%s: invalid buffer attempted", FuncName); + return false; + } + return true; +} + +static void +mixer_ConvertBuffer_internal (mixer_Convertion *conv) +{ + conv->srcbpc = MIX_FORMAT_BPC (conv->srcfmt); + conv->srcchans = MIX_FORMAT_CHANS (conv->srcfmt); + conv->dstbpc = MIX_FORMAT_BPC (conv->dstfmt); + conv->dstchans = MIX_FORMAT_CHANS (conv->dstfmt); + + conv->flags = 0; + if (conv->srcbpc > conv->dstbpc) + conv->flags |= mixConvSizeDown; + else if (conv->srcbpc < conv->dstbpc) + conv->flags |= mixConvSizeUp; + if (conv->srcchans > conv->dstchans) + conv->flags |= mixConvStereoDown; + else if (conv->srcchans < conv->dstchans) + conv->flags |= mixConvStereoUp; + + mixer_ResampleFlat (conv); +} + +/************************************************* + * Resampling routines + */ + +/* get a sample from external buffer + * in internal format + */ +static inline sint32 +mixer_GetSampleExt (void *src, uint32 bpc) +{ + if (bpc == 2) + return *(sint16 *)src; + else + return (*(uint8 *)src) - 128; +} + +/* get a sample from internal buffer */ +static inline sint32 +mixer_GetSampleInt (void *src, uint32 bpc) +{ + if (bpc == 2) + return *(sint16 *)src; + else + return *(sint8 *)src; +} + +/* put a sample into an external buffer + * from internal format + */ +static inline void +mixer_PutSampleExt (void *dst, uint32 bpc, sint32 samp) +{ + if (bpc == 2) + *(sint16 *)dst = samp; + else + *(uint8 *)dst = samp ^ 0x80; +} + +/* put a sample into an internal buffer + * in internal format + */ +static inline void +mixer_PutSampleInt (void *dst, uint32 bpc, sint32 samp) +{ + if (bpc == 2) + *(sint16 *)dst = samp; + else + *(sint8 *)dst = samp; +} + +/* get a sample from source */ +static float +mixer_ResampleNone (mixer_Source *src, bool left) +{ + uint8 *d0 = src->nextqueued->data + src->pos; + src->pos += mixer_chansize; + (void) left; // satisfying compiler - unused arg + return mixer_GetSampleInt (d0, mixer_chansize); +} + +/* get a resampled (up/down) sample from source (nearest neighbor) */ +static float +mixer_ResampleNearest (mixer_Source *src, bool left) +{ + uint8 *d0 = src->nextqueued->data + src->pos; + d0 += mixer_SourceAdvance (src, left); + return mixer_GetSampleInt (d0, mixer_chansize); +} + +/* get an upsampled sample from source (linear interpolation) */ +static float +mixer_UpsampleLinear (mixer_Source *src, bool left) +{ + mixer_Buffer *curr = src->nextqueued; + mixer_Buffer *next = src->nextqueued->next; + uint8 *d0, *d1; + float s0, s1, t; + + t = src->count / 65536.0f; + d0 = curr->data + src->pos; + d0 += mixer_SourceAdvance (src, left); + + if (d0 + curr->sampsize >= curr->data + curr->size) + { + if (next && next->data && next->size >= curr->sampsize) + { + d1 = next->data; + if (!left) + d1 += mixer_chansize; + } + else + d1 = d0; + } + else + d1 = d0 + curr->sampsize; + + s0 = mixer_GetSampleInt (d0, mixer_chansize); + s1 = mixer_GetSampleInt (d1, mixer_chansize); + return s0 + t * (s1 - s0); +} + +/* get an upsampled sample from source (cubic interpolation) */ +static float +mixer_UpsampleCubic (mixer_Source *src, bool left) +{ + mixer_Buffer *prev = src->prevqueued; + mixer_Buffer *curr = src->nextqueued; + mixer_Buffer *next = src->nextqueued->next; + uint8 *d0, *d1, *d2, *d3; /* prev, curr, next, next + 1 */ + float t, t2, a, b, c, s0, s1, s2, s3; + + t = src->count / 65536.0f; + t2 = t * t; + d1 = curr->data + src->pos; + d1 += mixer_SourceAdvance (src, left); + + if (d1 - curr->sampsize < curr->data) + { + if (prev && prev->data && prev->size >= curr->sampsize) + { + d0 = prev->data + prev->size - curr->sampsize; + if (!left) + d0 += mixer_chansize; + } + else + d0 = d1; + } + else + d0 = d1 - curr->sampsize; + + if (d1 + curr->sampsize >= curr->data + curr->size) + { + if (next && next->data && next->size >= curr->sampsize * 2) + { + d2 = next->data; + if (!left) + d2 += mixer_chansize; + d3 = d2 + curr->sampsize; + } + else + d2 = d3 = d1; + } + else + { + d2 = d1 + curr->sampsize; + if (d2 + curr->sampsize >= curr->data + curr->size) + { + if (next && next->data && next->size >= curr->sampsize) + { + d3 = next->data; + if (!left) + d3 += mixer_chansize; + } + else + d3 = d2; + } + else + d3 = d2 + curr->sampsize; + } + + s0 = mixer_GetSampleInt (d0, mixer_chansize); + s1 = mixer_GetSampleInt (d1, mixer_chansize); + s2 = mixer_GetSampleInt (d2, mixer_chansize); + s3 = mixer_GetSampleInt (d3, mixer_chansize); + + a = (3.0f * (s1 - s2) - s0 + s3) * 0.5f; + b = 2.0f * s2 + s0 - ((5.0f * s1 + s3) * 0.5f); + c = (s2 - s0) * 0.5f; + + return a * t2 * t + b * t2 + c * t + s1; +} + +/* get next sample from external buffer + * in internal format, while performing + * convertion if necessary + */ +static inline sint32 +mixer_GetConvSample (uint8 **psrc, uint32 bpc, uint32 flags) +{ + sint32 samp; + + samp = mixer_GetSampleExt (*psrc, bpc); + *psrc += bpc; + if (flags & mixConvStereoDown) + { + /* downmix to mono - average up channels */ + samp = (samp + mixer_GetSampleExt (*psrc, bpc)) / 2; + *psrc += bpc; + } + + if (flags & mixConvSizeUp) + { + /* convert S8 to S16 */ + samp <<= 8; + } + else if (flags & mixConvSizeDown) + { + /* convert S16 to S8 + * if arithmetic shift is available to the compiler + * it will use it to optimize this + */ + samp /= 0x100; + } + + return samp; +} + +/* put next sample into an internal buffer + * in internal format, while performing + * convertion if necessary + */ +static inline void +mixer_PutConvSample (uint8 **pdst, uint32 bpc, uint32 flags, sint32 samp) +{ + mixer_PutSampleInt (*pdst, bpc, samp); + *pdst += bpc; + if (flags & mixConvStereoUp) + { + mixer_PutSampleInt (*pdst, bpc, samp); + *pdst += bpc; + } +} + +/* resampling with respect to sample size only */ +static void +mixer_ResampleFlat (mixer_Convertion *conv) +{ + mixer_ConvFlags flags = conv->flags; + uint8 *src = conv->srcdata; + uint8 *dst = conv->dstdata; + uint32 srcbpc = conv->srcbpc; + uint32 dstbpc = conv->dstbpc; + uint32 samples; + + samples = conv->srcsamples; + if ( !(conv->flags & (mixConvStereoUp | mixConvStereoDown))) + samples *= conv->srcchans; + + for (; samples; samples--) + { + sint32 samp; + + samp = mixer_GetConvSample (&src, srcbpc, flags); + mixer_PutConvSample (&dst, dstbpc, flags, samp); + } +} diff --git a/src/libs/sound/mixer/mixer.h b/src/libs/sound/mixer/mixer.h new file mode 100644 index 0000000..71e7878 --- /dev/null +++ b/src/libs/sound/mixer/mixer.h @@ -0,0 +1,274 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Mixer for low-level sound output drivers + */ + +#ifndef LIBS_SOUND_MIXER_MIXER_H_ +#define LIBS_SOUND_MIXER_MIXER_H_ + +#include "config.h" +#include "types.h" +#include "endian_uqm.h" + +/** + * The interface heavily influenced by OpenAL + * to the point where you should use OpenAL's + * documentation when programming the mixer. + * (some source properties are not supported) + * + * EXCEPTION: You may not queue the same buffer + * on more than one source + */ + +#ifdef WORDS_BIGENDIAN +# define MIX_IS_BIG_ENDIAN true +# define MIX_WANT_BIG_ENDIAN true +#else +# define MIX_IS_BIG_ENDIAN false +# define MIX_WANT_BIG_ENDIAN false +#endif + +/** + * Mixer errors (see OpenAL errors) + */ +enum +{ + MIX_NO_ERROR = 0, + MIX_INVALID_NAME = 0xA001U, + MIX_INVALID_ENUM = 0xA002U, + MIX_INVALID_VALUE = 0xA003U, + MIX_INVALID_OPERATION = 0xA004U, + MIX_OUT_OF_MEMORY = 0xA005U, + + MIX_DRIVER_FAILURE = 0xA101U +}; + +/** + * Source properties (see OpenAL) + */ +typedef enum +{ + MIX_POSITION = 0x1004, + MIX_LOOPING = 0x1007, + MIX_BUFFER = 0x1009, + MIX_GAIN = 0x100A, + MIX_SOURCE_STATE = 0x1010, + + MIX_BUFFERS_QUEUED = 0x1015, + MIX_BUFFERS_PROCESSED = 0x1016 + +} mixer_SourceProp; + +/** + * Source state information + */ +typedef enum +{ + MIX_INITIAL = 0, + MIX_STOPPED, + MIX_PLAYING, + MIX_PAUSED, + +} mixer_SourceState; + +/** + * Sound buffer properties + */ +typedef enum +{ + MIX_FREQUENCY = 0x2001, + MIX_BITS = 0x2002, + MIX_CHANNELS = 0x2003, + MIX_SIZE = 0x2004, + MIX_DATA = 0x2005 + +} mixer_BufferProp; + +/** + * Buffer states: semi-private + */ +typedef enum +{ + MIX_BUF_INITIAL = 0, + MIX_BUF_FILLED, + MIX_BUF_QUEUED, + MIX_BUF_PLAYING, + MIX_BUF_PROCESSED + +} mixer_BufferState; + +/** Sound buffers: format specifier. + * bits 00..07: bytes per sample + * bits 08..15: channels + * bits 15..31: meaningless + */ +#define MIX_FORMAT_DUMMYID 0x00170000 +#define MIX_FORMAT_BPC(f) ((f) & 0xff) +#define MIX_FORMAT_CHANS(f) (((f) >> 8) & 0xff) +#define MIX_FORMAT_BPC_MAX 2 +#define MIX_FORMAT_CHANS_MAX 2 +#define MIX_FORMAT_MAKE(b, c) \ + ( MIX_FORMAT_DUMMYID | ((b) & 0xff) | (((c) & 0xff) << 8) ) + +#define MIX_FORMAT_SAMPSIZE(f) \ + ( MIX_FORMAT_BPC(f) * MIX_FORMAT_CHANS(f) ) + +typedef enum +{ + MIX_FORMAT_MONO8 = MIX_FORMAT_MAKE (1, 1), + MIX_FORMAT_STEREO8 = MIX_FORMAT_MAKE (1, 2), + MIX_FORMAT_MONO16 = MIX_FORMAT_MAKE (2, 1), + MIX_FORMAT_STEREO16 = MIX_FORMAT_MAKE (2, 2) + +} mixer_Format; + +typedef enum +{ + MIX_QUALITY_LOW = 0, + MIX_QUALITY_MEDIUM, + MIX_QUALITY_HIGH, + MIX_QUALITY_DEFAULT = MIX_QUALITY_MEDIUM, + MIX_QUALITY_COUNT + +} mixer_Quality; + +typedef enum +{ + MIX_NOFLAGS = 0, + MIX_FAKE_DATA = 1 +} mixer_Flags; + +/************************************************* + * Interface Types + */ + +typedef intptr_t mixer_Object; +typedef intptr_t mixer_IntVal; + +typedef struct _mixer_Source mixer_Source; + +typedef struct _mixer_Buffer +{ + uint32 magic; + bool locked; + mixer_BufferState state; + uint8 *data; + uint32 size; + uint32 sampsize; + uint32 high; + uint32 low; + float (* Resample) (mixer_Source *src, bool left); + /* original buffer values for OpenAL compat */ + void* orgdata; + uint32 orgfreq; + uint32 orgsize; + uint32 orgchannels; + uint32 orgchansize; + /* next buffer in chain */ + struct _mixer_Buffer *next; + +} mixer_Buffer; + +#define mixer_bufMagic 0x4258494DU /* MIXB in LSB */ + +struct _mixer_Source +{ + uint32 magic; + bool locked; + mixer_SourceState state; + bool looping; + float gain; + uint32 cqueued; + uint32 cprocessed; + mixer_Buffer *firstqueued; /* first buf in the queue */ + mixer_Buffer *nextqueued; /* next to play, or 0 */ + mixer_Buffer *prevqueued; /* previously played */ + mixer_Buffer *lastqueued; /* last in queue */ + uint32 pos; /* position in current buffer */ + uint32 count; /* fractional part of pos */ + + float samplecache; + +}; + +#define mixer_srcMagic 0x5358494DU /* MIXS in LSB */ + +/************************************************* + * General interface + */ +uint32 mixer_GetError (void); + +bool mixer_Init (uint32 frequency, uint32 format, mixer_Quality quality, + mixer_Flags flags); +void mixer_Uninit (void); +void mixer_MixChannels (void *userdata, uint8 *stream, sint32 len); +void mixer_MixFake (void *userdata, uint8 *stream, sint32 len); + +/************************************************* + * Sources + */ +void mixer_GenSources (uint32 n, mixer_Object *psrcobj); +void mixer_DeleteSources (uint32 n, mixer_Object *psrcobj); +bool mixer_IsSource (mixer_Object srcobj); +void mixer_Sourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal value); +void mixer_Sourcef (mixer_Object srcobj, mixer_SourceProp pname, + float value); +void mixer_Sourcefv (mixer_Object srcobj, mixer_SourceProp pname, + float *value); +void mixer_GetSourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal *value); +void mixer_GetSourcef (mixer_Object srcobj, mixer_SourceProp pname, + float *value); +void mixer_SourceRewind (mixer_Object srcobj); +void mixer_SourcePlay (mixer_Object srcobj); +void mixer_SourcePause (mixer_Object srcobj); +void mixer_SourceStop (mixer_Object srcobj); +void mixer_SourceQueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj); +void mixer_SourceUnqueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj); + +/************************************************* + * Buffers + */ +void mixer_GenBuffers (uint32 n, mixer_Object *pbufobj); +void mixer_DeleteBuffers (uint32 n, mixer_Object *pbufobj); +bool mixer_IsBuffer (mixer_Object bufobj); +void mixer_GetBufferi (mixer_Object bufobj, mixer_BufferProp pname, + mixer_IntVal *value); +void mixer_BufferData (mixer_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +/* Make sure the prop-value type is of suitable size + * it must be able to store both int and void* + * Adapted from SDL + * This will generate "negative subscript or subscript is too large" + * error during compile, if the actual size of a type is wrong + */ +#define MIX_COMPILE_TIME_ASSERT(name, x) \ + typedef int mixer_dummy_##name [(x) * 2 - 1] + +MIX_COMPILE_TIME_ASSERT (mixer_Object, + sizeof(mixer_Object) >= sizeof(void*)); +MIX_COMPILE_TIME_ASSERT (mixer_IntVal, + sizeof(mixer_IntVal) >= sizeof(mixer_Object)); + +#undef MIX_COMPILE_TIME_ASSERT + +#endif /* LIBS_SOUND_MIXER_MIXER_H_ */ diff --git a/src/libs/sound/mixer/mixerint.h b/src/libs/sound/mixer/mixerint.h new file mode 100644 index 0000000..0605161 --- /dev/null +++ b/src/libs/sound/mixer/mixerint.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. + */ + +/* Mixer for low-level sound output drivers + * Internals + */ + +#ifndef LIBS_SOUND_MIXER_MIXERINT_H_ +#define LIBS_SOUND_MIXER_MIXERINT_H_ + +#include "port.h" +#include "types.h" + +/************************************************* + * Internals + */ + +/* Conversion info types and funcs */ +typedef enum +{ + mixConvNone = 0, + mixConvStereoUp = 1, + mixConvStereoDown = 2, + mixConvSizeUp = 4, + mixConvSizeDown = 8 + +} mixer_ConvFlags; + +typedef struct +{ + uint32 srcfmt; + void *srcdata; + uint32 srcsize; + uint32 srcbpc; /* bytes/sample for 1 chan */ + uint32 srcchans; + uint32 srcsamples; + + uint32 dstfmt; + void *dstdata; + uint32 dstsize; + uint32 dstbpc; /* bytes/sample for 1 chan */ + uint32 dstchans; + uint32 dstsamples; + + mixer_ConvFlags flags; + +} mixer_Convertion; + +typedef struct +{ + float (* Upsample) (mixer_Source *src, bool left); + float (* Downsample) (mixer_Source *src, bool left); + float (* None) (mixer_Source *src, bool left); +} mixer_Resampling; + +static void mixer_ConvertBuffer_internal (mixer_Convertion *conv); +static void mixer_ResampleFlat (mixer_Convertion *conv); + +static inline sint32 mixer_GetSampleExt (void *src, uint32 bpc); +static inline sint32 mixer_GetSampleInt (void *src, uint32 bpc); +static inline void mixer_PutSampleInt (void *dst, uint32 bpc, + sint32 samp); +static inline void mixer_PutSampleExt (void *dst, uint32 bpc, + sint32 samp); + +static float mixer_ResampleNone (mixer_Source *src, bool left); +static float mixer_ResampleNearest (mixer_Source *src, bool left); +static float mixer_UpsampleLinear (mixer_Source *src, bool left); +static float mixer_UpsampleCubic (mixer_Source *src, bool left); + +/* Source manipulation */ +static void mixer_SourceUnqueueAll (mixer_Source *src); +static void mixer_SourceStop_internal (mixer_Source *src); +static void mixer_SourceRewind_internal (mixer_Source *src); +static void mixer_SourceActivate (mixer_Source* src); +static void mixer_SourceDeactivate (mixer_Source* src); + +static inline bool mixer_CheckBufferState (mixer_Buffer *buf, + const char* FuncName); + +/* Clipping boundaries */ +#define MIX_S16_MAX ((float) SINT16_MAX) +#define MIX_S16_MIN ((float) SINT16_MIN) +#define MIX_S8_MAX ((float) SINT8_MAX) +#define MIX_S8_MIN ((float) SINT8_MIN) + +/* Channel gain adjustment for clipping reduction */ +#define MIX_GAIN_ADJ (0.75f) + +/* The Mixer */ +static inline bool mixer_SourceGetNextSample (mixer_Source *src, + float *psamp, bool left); +static inline bool mixer_SourceGetFakeSample (mixer_Source *src, + float *psamp, bool left); +static inline uint32 mixer_SourceAdvance (mixer_Source *src, bool left); + +#endif /* LIBS_SOUND_MIXER_MIXERINT_H_ */ diff --git a/src/libs/sound/mixer/nosound/Makeinfo b/src/libs/sound/mixer/nosound/Makeinfo new file mode 100644 index 0000000..17f0c34 --- /dev/null +++ b/src/libs/sound/mixer/nosound/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="audiodrv_nosound.c" +uqm_HFILES="audiodrv_nosound.h" diff --git a/src/libs/sound/mixer/nosound/audiodrv_nosound.c b/src/libs/sound/mixer/nosound/audiodrv_nosound.c new file mode 100644 index 0000000..005bb44 --- /dev/null +++ b/src/libs/sound/mixer/nosound/audiodrv_nosound.c @@ -0,0 +1,410 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Nosound audio driver + */ + +#include "audiodrv_nosound.h" +#include "../../sndintrn.h" +#include "libs/tasklib.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include + + +static Task PlaybackTask; +static uint32 nosound_freq = 22050; + +static const audio_Driver noSound_Driver = +{ + noSound_Uninit, + noSound_GetError, + audio_DRIVER_NOSOUND, + { + /* Errors */ + MIX_NO_ERROR, + MIX_INVALID_NAME, + MIX_INVALID_ENUM, + MIX_INVALID_VALUE, + MIX_INVALID_OPERATION, + MIX_OUT_OF_MEMORY, + MIX_DRIVER_FAILURE, + + /* Source properties */ + MIX_POSITION, + MIX_LOOPING, + MIX_BUFFER, + MIX_GAIN, + MIX_SOURCE_STATE, + MIX_BUFFERS_QUEUED, + MIX_BUFFERS_PROCESSED, + + /* Source state information */ + MIX_INITIAL, + MIX_STOPPED, + MIX_PLAYING, + MIX_PAUSED, + + /* Sound buffer properties */ + MIX_FREQUENCY, + MIX_BITS, + MIX_CHANNELS, + MIX_SIZE, + MIX_FORMAT_MONO16, + MIX_FORMAT_STEREO16, + MIX_FORMAT_MONO8, + MIX_FORMAT_STEREO8 + }, + + /* Sources */ + noSound_GenSources, + noSound_DeleteSources, + noSound_IsSource, + noSound_Sourcei, + noSound_Sourcef, + noSound_Sourcefv, + noSound_GetSourcei, + noSound_GetSourcef, + noSound_SourceRewind, + noSound_SourcePlay, + noSound_SourcePause, + noSound_SourceStop, + noSound_SourceQueueBuffers, + noSound_SourceUnqueueBuffers, + + /* Buffers */ + noSound_GenBuffers, + noSound_DeleteBuffers, + noSound_IsBuffer, + noSound_GetBufferi, + noSound_BufferData +}; + + +/* + * Initialization + */ + +sint32 +noSound_Init (audio_Driver *driver, sint32 flags) +{ + int i; + TFB_DecoderFormats formats = + { + 0, 0, + audio_FORMAT_MONO8, audio_FORMAT_STEREO8, + audio_FORMAT_MONO16, audio_FORMAT_STEREO16 + }; + + log_add (log_Info, "Using nosound audio driver."); + log_add (log_Info, "Initializing mixer."); + + if (!mixer_Init (nosound_freq, MIX_FORMAT_MAKE (1, 1), + MIX_QUALITY_LOW, MIX_FAKE_DATA)) + { + log_add (log_Error, "Mixer initialization failed: %x", + mixer_GetError ()); + return -1; + } + log_add (log_Info, "Mixer initialized."); + + log_add (log_Info, "Initializing sound decoders."); + if (SoundDecoder_Init (flags, &formats)) + { + log_add (log_Error, "Sound decoders initialization failed."); + mixer_Uninit (); + return -1; + } + log_add (log_Info, "Sound decoders initialized."); + + *driver = noSound_Driver; + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + audio_GenSources (1, &soundSource[i].handle); + soundSource[i].stream_mutex = CreateMutex ("Nosound stream mutex", SYNC_CLASS_AUDIO); + } + + if (InitStreamDecoder ()) + { + log_add (log_Error, "Stream decoder initialization failed."); + // TODO: cleanup source mutexes [or is it "muti"? :) ] + SoundDecoder_Uninit (); + mixer_Uninit (); + return -1; + } + + PlaybackTask = AssignTask (PlaybackTaskFunc, 1024, + "nosound audio playback"); + + return 0; +} + +void +noSound_Uninit (void) +{ + int i; + + UninitStreamDecoder (); + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + if (soundSource[i].sample && soundSource[i].sample->decoder) + { + StopStream (i); + } + if (soundSource[i].sbuffer) + { + void *sbuffer = soundSource[i].sbuffer; + soundSource[i].sbuffer = NULL; + HFree (sbuffer); + } + DestroyMutex (soundSource[i].stream_mutex); + + noSound_DeleteSources (1, &soundSource[i].handle); + } + + if (PlaybackTask) + { + ConcludeTask (PlaybackTask); + PlaybackTask = 0; + } + + mixer_Uninit (); + SoundDecoder_Uninit (); +} + + +/* + * Playback task + */ + +int +PlaybackTaskFunc (void *data) +{ + Task task = (Task)data; + uint8 *stream; + uint32 entryTime; + sint32 period, delay; + uint32 len = 2048; + + stream = (uint8 *) HMalloc (len); + period = (sint32)((len / (double)nosound_freq) * ONE_SECOND); + + while (!Task_ReadState (task, TASK_EXIT)) + { + entryTime = GetTimeCounter (); + mixer_MixFake (NULL, stream, len); + delay = period - (GetTimeCounter () - entryTime); + if (delay > 0) + HibernateThread (delay); + } + + HFree (stream); + FinishTask (task); + return 0; +} + + +/* + * General + */ + +sint32 +noSound_GetError (void) +{ + sint32 value = mixer_GetError (); + switch (value) + { + case MIX_NO_ERROR: + return audio_NO_ERROR; + case MIX_INVALID_NAME: + return audio_INVALID_NAME; + case MIX_INVALID_ENUM: + return audio_INVALID_ENUM; + case MIX_INVALID_VALUE: + return audio_INVALID_VALUE; + case MIX_INVALID_OPERATION: + return audio_INVALID_OPERATION; + case MIX_OUT_OF_MEMORY: + return audio_OUT_OF_MEMORY; + default: + log_add (log_Debug, "noSound_GetError: unknown value %x", + value); + return audio_DRIVER_FAILURE; + break; + } +} + + +/* + * Sources + */ + +void +noSound_GenSources (uint32 n, audio_Object *psrcobj) +{ + mixer_GenSources (n, (mixer_Object *) psrcobj); +} + +void +noSound_DeleteSources (uint32 n, audio_Object *psrcobj) +{ + mixer_DeleteSources (n, (mixer_Object *) psrcobj); +} + +bool +noSound_IsSource (audio_Object srcobj) +{ + return mixer_IsSource ((mixer_Object) srcobj); +} + +void +noSound_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value) + +{ + mixer_Sourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal) value); +} + +void +noSound_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value) +{ + mixer_Sourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +noSound_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_Sourcefv ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +noSound_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value) +{ + mixer_GetSourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal *) value); + if (pname == MIX_SOURCE_STATE) + { + switch (*value) + { + case MIX_INITIAL: + *value = audio_INITIAL; + break; + case MIX_STOPPED: + *value = audio_STOPPED; + break; + case MIX_PLAYING: + *value = audio_PLAYING; + break; + case MIX_PAUSED: + *value = audio_PAUSED; + break; + default: + log_add (log_Debug, "noSound_GetSourcei(): unknown value %lx", + (long int) *value); + *value = audio_DRIVER_FAILURE; + } + } +} + +void +noSound_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_GetSourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +noSound_SourceRewind (audio_Object srcobj) +{ + mixer_SourceRewind ((mixer_Object) srcobj); +} + +void +noSound_SourcePlay (audio_Object srcobj) +{ + mixer_SourcePlay ((mixer_Object) srcobj); +} + +void +noSound_SourcePause (audio_Object srcobj) +{ + mixer_SourcePause ((mixer_Object) srcobj); +} + +void +noSound_SourceStop (audio_Object srcobj) +{ + mixer_SourceStop ((mixer_Object) srcobj); +} + +void +noSound_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceQueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + +void +noSound_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceUnqueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + + +/* + * Buffers + */ + +void +noSound_GenBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_GenBuffers (n, (mixer_Object *) pbufobj); +} + +void +noSound_DeleteBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_DeleteBuffers (n, (mixer_Object *) pbufobj); +} + +bool +noSound_IsBuffer (audio_Object bufobj) +{ + return mixer_IsBuffer ((mixer_Object) bufobj); +} + +void +noSound_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value) +{ + mixer_GetBufferi ((mixer_Object) bufobj, (mixer_BufferProp) pname, + (mixer_IntVal *) value); +} + +void +noSound_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + mixer_BufferData ((mixer_Object) bufobj, format, data, size, freq); +} diff --git a/src/libs/sound/mixer/nosound/audiodrv_nosound.h b/src/libs/sound/mixer/nosound/audiodrv_nosound.h new file mode 100644 index 0000000..173b706 --- /dev/null +++ b/src/libs/sound/mixer/nosound/audiodrv_nosound.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. + */ + +/* Nosound audio driver + */ + +#ifndef LIBS_SOUND_MIXER_NOSOUND_AUDIODRV_NOSOUND_H_ +#define LIBS_SOUND_MIXER_NOSOUND_AUDIODRV_NOSOUND_H_ + +#include "config.h" +#include "libs/sound/sound.h" +#include "libs/sound/mixer/mixer.h" + + +/* Playback task */ +int PlaybackTaskFunc (void *data); + +/* General */ +sint32 noSound_Init (audio_Driver *driver, sint32 flags); +void noSound_Uninit (void); +sint32 noSound_GetError (void); + +/* Sources */ +void noSound_GenSources (uint32 n, audio_Object *psrcobj); +void noSound_DeleteSources (uint32 n, audio_Object *psrcobj); +bool noSound_IsSource (audio_Object srcobj); +void noSound_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); +void noSound_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value); +void noSound_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value); +void noSound_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); +void noSound_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value); +void noSound_SourceRewind (audio_Object srcobj); +void noSound_SourcePlay (audio_Object srcobj); +void noSound_SourcePause (audio_Object srcobj); +void noSound_SourceStop (audio_Object srcobj); +void noSound_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); +void noSound_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + +/* Buffers */ +void noSound_GenBuffers (uint32 n, audio_Object *pbufobj); +void noSound_DeleteBuffers (uint32 n, audio_Object *pbufobj); +bool noSound_IsBuffer (audio_Object bufobj); +void noSound_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); +void noSound_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +#endif /* LIBS_SOUND_MIXER_NOSOUND_AUDIODRV_NOSOUND_H_ */ diff --git a/src/libs/sound/mixer/sdl/Makeinfo b/src/libs/sound/mixer/sdl/Makeinfo new file mode 100644 index 0000000..64c22c0 --- /dev/null +++ b/src/libs/sound/mixer/sdl/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="audiodrv_sdl.c" +uqm_HFILES="audiodrv_sdl.h" diff --git a/src/libs/sound/mixer/sdl/audiodrv_sdl.c b/src/libs/sound/mixer/sdl/audiodrv_sdl.c new file mode 100644 index 0000000..7ef522e --- /dev/null +++ b/src/libs/sound/mixer/sdl/audiodrv_sdl.c @@ -0,0 +1,486 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* SDL audio driver + */ + +#include "audiodrv_sdl.h" +#include "../../sndintrn.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include + + +/* SDL2 wants to talk to a specific device. We'll let SDL1 use the same + * function names and just throw the device argument away. */ +#if SDL_MAJOR_VERSION > 1 +static SDL_AudioDeviceID dev; +#else +#define SDL_CloseAudioDevice(x) SDL_CloseAudio () +#define SDL_PauseAudioDevice(x, y) SDL_PauseAudio (y) +#endif +static const audio_Driver mixSDL_Driver = +{ + mixSDL_Uninit, + mixSDL_GetError, + audio_DRIVER_MIXSDL, + { + /* Errors */ + MIX_NO_ERROR, + MIX_INVALID_NAME, + MIX_INVALID_ENUM, + MIX_INVALID_VALUE, + MIX_INVALID_OPERATION, + MIX_OUT_OF_MEMORY, + MIX_DRIVER_FAILURE, + + /* Source properties */ + MIX_POSITION, + MIX_LOOPING, + MIX_BUFFER, + MIX_GAIN, + MIX_SOURCE_STATE, + MIX_BUFFERS_QUEUED, + MIX_BUFFERS_PROCESSED, + + /* Source state information */ + MIX_INITIAL, + MIX_STOPPED, + MIX_PLAYING, + MIX_PAUSED, + + /* Sound buffer properties */ + MIX_FREQUENCY, + MIX_BITS, + MIX_CHANNELS, + MIX_SIZE, + MIX_FORMAT_MONO16, + MIX_FORMAT_STEREO16, + MIX_FORMAT_MONO8, + MIX_FORMAT_STEREO8 + }, + + /* Sources */ + mixSDL_GenSources, + mixSDL_DeleteSources, + mixSDL_IsSource, + mixSDL_Sourcei, + mixSDL_Sourcef, + mixSDL_Sourcefv, + mixSDL_GetSourcei, + mixSDL_GetSourcef, + mixSDL_SourceRewind, + mixSDL_SourcePlay, + mixSDL_SourcePause, + mixSDL_SourceStop, + mixSDL_SourceQueueBuffers, + mixSDL_SourceUnqueueBuffers, + + /* Buffers */ + mixSDL_GenBuffers, + mixSDL_DeleteBuffers, + mixSDL_IsBuffer, + mixSDL_GetBufferi, + mixSDL_BufferData +}; + + +static void audioCallback (void *userdata, Uint8 *stream, int len); + +/* + * Initialization + */ + +sint32 +mixSDL_Init (audio_Driver *driver, sint32 flags) +{ + int i; + SDL_AudioSpec desired, obtained; + mixer_Quality quality; + TFB_DecoderFormats formats = + { + MIX_IS_BIG_ENDIAN, MIX_WANT_BIG_ENDIAN, + audio_FORMAT_MONO8, audio_FORMAT_STEREO8, + audio_FORMAT_MONO16, audio_FORMAT_STEREO16 + }; + + log_add (log_Info, "Initializing SDL audio subsystem."); + if ((SDL_InitSubSystem(SDL_INIT_AUDIO)) == -1) + { + log_add (log_Error, "Couldn't initialize audio subsystem: %s", + SDL_GetError()); + return -1; + } + log_add (log_Info, "SDL audio subsystem initialized."); + + if (flags & audio_QUALITY_HIGH) + { + quality = MIX_QUALITY_HIGH; + desired.freq = 44100; + desired.samples = 4096; + } + else if (flags & audio_QUALITY_LOW) + { + quality = MIX_QUALITY_LOW; +#ifdef __SYMBIAN32__ + desired.freq = 11025; + desired.samples = 4096; +#else + desired.freq = 22050; + desired.samples = 2048; +#endif + } + else + { + quality = MIX_QUALITY_DEFAULT; + desired.freq = 44100; + desired.samples = 4096; + } + + desired.format = AUDIO_S16SYS; + desired.channels = 2; + desired.callback = audioCallback; + + log_add (log_Info, "Opening SDL audio device."); +#if SDL_MAJOR_VERSION > 1 + dev = SDL_OpenAudioDevice (NULL, 0, &desired, &obtained, + SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE +#ifdef SDL_AUDIO_ALLOW_SAMPLES_CHANGE + | SDL_AUDIO_ALLOW_SAMPLES_CHANGE +#endif + ); + if (dev != 0 && obtained.channels != 1 && obtained.channels != 2) + { + /* Try again without SDL_AUDIO_ALLOW_CHANNELS_CHANGE + * in case the device only supports >2 channels for some + * reason */ + SDL_CloseAudioDevice (dev); + dev = SDL_OpenAudioDevice (NULL, 0, &desired, &obtained, + SDL_AUDIO_ALLOW_FREQUENCY_CHANGE +#ifdef SDL_AUDIO_ALLOW_SAMPLES_CHANGE + | SDL_AUDIO_ALLOW_SAMPLES_CHANGE +#endif + ); + } + if (dev == 0) +#else + if (SDL_OpenAudio (&desired, &obtained) < 0) +#endif + { + log_add (log_Error, "Unable to open audio device: %s", + SDL_GetError ()); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + if (obtained.format != desired.format || + (obtained.channels != 1 && obtained.channels != 2)) + { + log_add (log_Error, "Unable to obtain desired audio format."); + SDL_CloseAudio (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + + { +#if SDL_MAJOR_VERSION == 1 + char devicename[256]; + SDL_AudioDriverName (devicename, sizeof (devicename)); +#else + const char *devicename = SDL_GetCurrentAudioDriver (); +#endif + log_add (log_Info, " using %s at %d Hz 16 bit %s, " + "%d samples audio buffer", + devicename, obtained.freq, + obtained.channels > 1 ? "stereo" : "mono", + obtained.samples); + } + + log_add (log_Info, "Initializing mixer."); + if (!mixer_Init (obtained.freq, MIX_FORMAT_MAKE (2, obtained.channels), + quality, 0)) + { + log_add (log_Error, "Mixer initialization failed: %x", + mixer_GetError ()); + SDL_CloseAudioDevice (dev); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + log_add (log_Info, "Mixer initialized."); + + log_add (log_Info, "Initializing sound decoders."); + if (SoundDecoder_Init (flags, &formats)) + { + log_add (log_Error, "Sound decoders initialization failed."); + SDL_CloseAudioDevice (dev); + mixer_Uninit (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + log_add (log_Info, "Sound decoders initialized."); + + *driver = mixSDL_Driver; + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + audio_GenSources (1, &soundSource[i].handle); + soundSource[i].stream_mutex = CreateMutex ("MixSDL stream mutex", SYNC_CLASS_AUDIO); + } + + if (InitStreamDecoder ()) + { + log_add (log_Error, "Stream decoder initialization failed."); + // TODO: cleanup source mutexes [or is it "muti"? :) ] + SDL_CloseAudioDevice (dev); + SoundDecoder_Uninit (); + mixer_Uninit (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + + atexit (unInitAudio); + + SetSFXVolume (sfxVolumeScale); + SetSpeechVolume (speechVolumeScale); + SetMusicVolume ((COUNT)musicVolume); + + SDL_PauseAudioDevice (dev, 0); + + return 0; +} + +void +mixSDL_Uninit (void) +{ + int i; + + UninitStreamDecoder (); + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + if (soundSource[i].sample && soundSource[i].sample->decoder) + { + StopStream (i); + } + if (soundSource[i].sbuffer) + { + void *sbuffer = soundSource[i].sbuffer; + soundSource[i].sbuffer = NULL; + HFree (sbuffer); + } + DestroyMutex (soundSource[i].stream_mutex); + soundSource[i].stream_mutex = 0; + + mixSDL_DeleteSources (1, &soundSource[i].handle); + } + + SDL_CloseAudioDevice (dev); + mixer_Uninit (); + SoundDecoder_Uninit (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); +} + +static void +audioCallback (void *userdata, Uint8 *stream, int len) +{ + mixer_MixChannels (userdata, stream, len); +} + +/* + * General + */ + +sint32 +mixSDL_GetError (void) +{ + sint32 value = mixer_GetError (); + switch (value) + { + case MIX_NO_ERROR: + return audio_NO_ERROR; + case MIX_INVALID_NAME: + return audio_INVALID_NAME; + case MIX_INVALID_ENUM: + return audio_INVALID_ENUM; + case MIX_INVALID_VALUE: + return audio_INVALID_VALUE; + case MIX_INVALID_OPERATION: + return audio_INVALID_OPERATION; + case MIX_OUT_OF_MEMORY: + return audio_OUT_OF_MEMORY; + default: + log_add (log_Debug, "mixSDL_GetError: unknown value %x", value); + return audio_DRIVER_FAILURE; + break; + } +} + + +/* + * Sources + */ + +void +mixSDL_GenSources (uint32 n, audio_Object *psrcobj) +{ + mixer_GenSources (n, (mixer_Object *) psrcobj); +} + +void +mixSDL_DeleteSources (uint32 n, audio_Object *psrcobj) +{ + mixer_DeleteSources (n, (mixer_Object *) psrcobj); +} + +bool +mixSDL_IsSource (audio_Object srcobj) +{ + return mixer_IsSource ((mixer_Object) srcobj); +} + +void +mixSDL_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value) + +{ + mixer_Sourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal) value); +} + +void +mixSDL_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value) +{ + mixer_Sourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +mixSDL_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_Sourcefv ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +mixSDL_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value) +{ + mixer_GetSourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal *) value); + if (pname == MIX_SOURCE_STATE) + { + switch (*value) + { + case MIX_INITIAL: + *value = audio_INITIAL; + break; + case MIX_STOPPED: + *value = audio_STOPPED; + break; + case MIX_PLAYING: + *value = audio_PLAYING; + break; + case MIX_PAUSED: + *value = audio_PAUSED; + break; + default: + *value = audio_DRIVER_FAILURE; + } + } +} + +void +mixSDL_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_GetSourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +mixSDL_SourceRewind (audio_Object srcobj) +{ + mixer_SourceRewind ((mixer_Object) srcobj); +} + +void +mixSDL_SourcePlay (audio_Object srcobj) +{ + mixer_SourcePlay ((mixer_Object) srcobj); +} + +void +mixSDL_SourcePause (audio_Object srcobj) +{ + mixer_SourcePause ((mixer_Object) srcobj); +} + +void +mixSDL_SourceStop (audio_Object srcobj) +{ + mixer_SourceStop ((mixer_Object) srcobj); +} + +void +mixSDL_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceQueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + +void +mixSDL_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceUnqueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + + +/* + * Buffers + */ + +void +mixSDL_GenBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_GenBuffers (n, (mixer_Object *) pbufobj); +} + +void +mixSDL_DeleteBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_DeleteBuffers (n, (mixer_Object *) pbufobj); +} + +bool +mixSDL_IsBuffer (audio_Object bufobj) +{ + return mixer_IsBuffer ((mixer_Object) bufobj); +} + +void +mixSDL_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value) +{ + mixer_GetBufferi ((mixer_Object) bufobj, (mixer_BufferProp) pname, + (mixer_IntVal *) value); +} + +void +mixSDL_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + mixer_BufferData ((mixer_Object) bufobj, format, data, size, freq); +} diff --git a/src/libs/sound/mixer/sdl/audiodrv_sdl.h b/src/libs/sound/mixer/sdl/audiodrv_sdl.h new file mode 100644 index 0000000..d74301b --- /dev/null +++ b/src/libs/sound/mixer/sdl/audiodrv_sdl.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. + */ + +/* SDL audio driver + */ + +#ifndef LIBS_SOUND_MIXER_SDL_AUDIODRV_SDL_H_ +#define LIBS_SOUND_MIXER_SDL_AUDIODRV_SDL_H_ + +#include "port.h" +#include "libs/sound/sound.h" +#include "libs/sound/mixer/mixer.h" +#include SDL_INCLUDE(SDL.h) + +/* General */ +sint32 mixSDL_Init (audio_Driver *driver, sint32 flags); +void mixSDL_Uninit (void); +sint32 mixSDL_GetError (void); + +/* Sources */ +void mixSDL_GenSources (uint32 n, audio_Object *psrcobj); +void mixSDL_DeleteSources (uint32 n, audio_Object *psrcobj); +bool mixSDL_IsSource (audio_Object srcobj); +void mixSDL_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); +void mixSDL_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value); +void mixSDL_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value); +void mixSDL_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); +void mixSDL_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value); +void mixSDL_SourceRewind (audio_Object srcobj); +void mixSDL_SourcePlay (audio_Object srcobj); +void mixSDL_SourcePause (audio_Object srcobj); +void mixSDL_SourceStop (audio_Object srcobj); +void mixSDL_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); +void mixSDL_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + +/* Buffers */ +void mixSDL_GenBuffers (uint32 n, audio_Object *pbufobj); +void mixSDL_DeleteBuffers (uint32 n, audio_Object *pbufobj); +bool mixSDL_IsBuffer (audio_Object bufobj); +void mixSDL_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); +void mixSDL_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +#endif /* LIBS_SOUND_MIXER_SDL_AUDIODRV_SDL_H_ */ diff --git a/src/libs/sound/music.c b/src/libs/sound/music.c new file mode 100644 index 0000000..c3f92f0 --- /dev/null +++ b/src/libs/sound/music.c @@ -0,0 +1,233 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include "libs/file.h" +#include "options.h" +#include "sound.h" +#include "sndintrn.h" +#include "libs/reslib.h" +#include "libs/log.h" +#include "libs/memlib.h" + + +static MUSIC_REF curMusicRef; +static MUSIC_REF curSpeechRef; + +void +PLRPlaySong (MUSIC_REF MusicRef, BOOLEAN Continuous, BYTE Priority) +{ + TFB_SoundSample **pmus = MusicRef; + + if (pmus) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + // Always scope the music data, we may need it + PlayStream ((*pmus), MUSIC_SOURCE, Continuous, true, true); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + + curMusicRef = MusicRef; + } + + (void) Priority; /* Satisfy compiler because of unused variable */ +} + +void +PLRStop (MUSIC_REF MusicRef) +{ + if (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + StopStream (MUSIC_SOURCE); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + + curMusicRef = 0; + } +} + +BOOLEAN +PLRPlaying (MUSIC_REF MusicRef) +{ + if (curMusicRef && (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0)) + { + BOOLEAN playing; + + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + playing = PlayingStream (MUSIC_SOURCE); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + + return playing; + } + + return FALSE; +} + +void +PLRSeek (MUSIC_REF MusicRef, DWORD pos) +{ + if (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + SeekStream (MUSIC_SOURCE, pos); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + } +} + +void +PLRPause (MUSIC_REF MusicRef) +{ + if (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + PauseStream (MUSIC_SOURCE); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + } +} + +void +PLRResume (MUSIC_REF MusicRef) +{ + if (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + ResumeStream (MUSIC_SOURCE); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + } +} + +void +snd_PlaySpeech (MUSIC_REF SpeechRef) +{ + TFB_SoundSample **pmus = SpeechRef; + + if (pmus) + { + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + // Do not need to scope the music-as-speech as of now + PlayStream (*pmus, SPEECH_SOURCE, false, false, true); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + curSpeechRef = SpeechRef; + } +} + +void +snd_StopSpeech (void) +{ + if (!curSpeechRef) + return; + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + StopStream (SPEECH_SOURCE); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + curSpeechRef = 0; +} + +BOOLEAN +DestroyMusic (MUSIC_REF MusicRef) +{ + return _ReleaseMusicData (MusicRef); +} + +void +SetMusicVolume (COUNT Volume) +{ + float f = (Volume / (float)MAX_VOLUME) * musicVolumeScale; + musicVolume = Volume; + audio_Sourcef (soundSource[MUSIC_SOURCE].handle, audio_GAIN, f); +} + +char* +CheckMusicResName (char* fileName) +{ + if (!fileExists2 (contentDir, fileName)) + log_add (log_Warning, "Requested track '%s' not found.", fileName); + return fileName; +} + +void * +_GetMusicData (uio_Stream *fp, DWORD length) +{ + MUSIC_REF h; + TFB_SoundSample *sample; + TFB_SoundDecoder *decoder; + char filename[256]; + + if (!_cur_resfile_name) + return NULL; + + strncpy (filename, _cur_resfile_name, sizeof(filename) - 1); + filename[sizeof(filename) - 1] = '\0'; + CheckMusicResName (filename); + + log_add (log_Info, "_GetMusicData(): loading %s", filename); + decoder = SoundDecoder_Load (contentDir, filename, 4096, 0, 0); + if (!decoder) + { + log_add (log_Warning, "_GetMusicData(): couldn't load %s", filename); + return NULL; + } + + h = AllocMusicData (sizeof (void *)); + if (!h) + { + SoundDecoder_Free (decoder); + return NULL; + } + + sample = TFB_CreateSoundSample (decoder, 64, NULL); + *h = sample; + + log_add (log_Info, " decoder: %s, rate %d format %x", + SoundDecoder_GetName (sample->decoder), + sample->decoder->frequency, sample->decoder->format); + + (void) fp; /* satisfy compiler (unused parameter) */ + (void) length; /* satisfy compiler (unused parameter) */ + return (h); +} + +BOOLEAN +_ReleaseMusicData (void *data) +{ + TFB_SoundSample **pmus = data; + TFB_SoundSample *sample; + + if (pmus == NULL) + return (FALSE); + + sample = *pmus; + assert (sample != 0); + if (sample->decoder) + { + TFB_SoundDecoder *decoder = sample->decoder; + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + if (soundSource[MUSIC_SOURCE].sample == sample) + { // Currently playing this sample! Not good. + StopStream (MUSIC_SOURCE); + } + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + + sample->decoder = NULL; + SoundDecoder_Free (decoder); + } + TFB_DestroySoundSample (sample); + FreeMusicData (data); + + return (TRUE); +} + diff --git a/src/libs/sound/openal/Makeinfo b/src/libs/sound/openal/Makeinfo new file mode 100644 index 0000000..1469461 --- /dev/null +++ b/src/libs/sound/openal/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="audiodrv_openal.c" +uqm_HFILES="audiodrv_openal.h" diff --git a/src/libs/sound/openal/audiodrv_openal.c b/src/libs/sound/openal/audiodrv_openal.c new file mode 100644 index 0000000..eee1cd1 --- /dev/null +++ b/src/libs/sound/openal/audiodrv_openal.c @@ -0,0 +1,420 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* OpenAL audio driver + */ + +#ifdef HAVE_OPENAL + + +#include "audiodrv_openal.h" +#include "../sndintrn.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include + + +ALCcontext *alcContext = NULL; +ALCdevice *alcDevice = NULL; +ALfloat defaultPos[] = {0.0f, 0.0f, -1.0f}; +ALfloat listenerPos[] = {0.0f, 0.0f, 0.0f}; +ALfloat listenerVel[] = {0.0f, 0.0f, 0.0f}; +ALfloat listenerOri[] = {0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f}; + +static const audio_Driver openAL_Driver = +{ + openAL_Uninit, + openAL_GetError, + audio_DRIVER_OPENAL, + { + /* Errors */ + AL_FALSE, + AL_INVALID_NAME, + AL_INVALID_ENUM, + AL_INVALID_VALUE, + AL_INVALID_OPERATION, + AL_OUT_OF_MEMORY, + audio_DRIVER_FAILURE, + + /* Source properties */ + AL_POSITION, + AL_LOOPING, + AL_BUFFER, + AL_GAIN, + AL_SOURCE_STATE, + AL_BUFFERS_QUEUED, + AL_BUFFERS_PROCESSED, + + /* Source state information */ + AL_INITIAL, + AL_STOPPED, + AL_PLAYING, + AL_PAUSED, + + /* Sound buffer properties */ + AL_FREQUENCY, + AL_BITS, + AL_CHANNELS, + AL_SIZE, + AL_FORMAT_MONO16, + AL_FORMAT_STEREO16, + AL_FORMAT_MONO8, + AL_FORMAT_STEREO8 + }, + + /* Sources */ + openAL_GenSources, + openAL_DeleteSources, + openAL_IsSource, + openAL_Sourcei, + openAL_Sourcef, + openAL_Sourcefv, + openAL_GetSourcei, + openAL_GetSourcef, + openAL_SourceRewind, + openAL_SourcePlay, + openAL_SourcePause, + openAL_SourceStop, + openAL_SourceQueueBuffers, + openAL_SourceUnqueueBuffers, + + /* Buffers */ + openAL_GenBuffers, + openAL_DeleteBuffers, + openAL_IsBuffer, + openAL_GetBufferi, + openAL_BufferData +}; + + +/* + * Initialization + */ + +sint32 +openAL_Init (audio_Driver *driver, sint32 flags) +{ + int i; + TFB_DecoderFormats formats = + { + MIX_IS_BIG_ENDIAN, MIX_WANT_BIG_ENDIAN, + audio_FORMAT_MONO8, audio_FORMAT_STEREO8, + audio_FORMAT_MONO16, audio_FORMAT_STEREO16 + }; + + log_add (log_Info, "Initializing OpenAL."); + alcDevice = alcOpenDevice (NULL); + + if (!alcDevice) + { + log_add (log_Error, "Couldn't initialize OpenAL: %d", + alcGetError (NULL)); + return -1; + } + + *driver = openAL_Driver; + + alcContext = alcCreateContext (alcDevice, NULL); + if (!alcContext) + { + log_add (log_Error, "Couldn't create OpenAL context: %d", + alcGetError (alcDevice)); + alcCloseDevice (alcDevice); + alcDevice = NULL; + return -1; + } + + alcMakeContextCurrent (alcContext); + + log_add (log_Info, "OpenAL initialized.\n" + " version: %s\n" + " vendor: %s\n" + " renderer: %s\n" + " device: %s", + alGetString (AL_VERSION), alGetString (AL_VENDOR), + alGetString (AL_RENDERER), + alcGetString (alcDevice, ALC_DEFAULT_DEVICE_SPECIFIER)); + //log_add (log_Info, " extensions: %s", alGetString (AL_EXTENSIONS)); + + log_add (log_Info, "Initializing sound decoders."); + if (SoundDecoder_Init (flags, &formats)) + { + log_add (log_Error, "Sound decoders initialization failed."); + alcMakeContextCurrent (NULL); + alcDestroyContext (alcContext); + alcContext = NULL; + alcCloseDevice (alcDevice); + alcDevice = NULL; + return -1; + } + log_add (log_Error, "Sound decoders initialized."); + + alListenerfv (AL_POSITION, listenerPos); + alListenerfv (AL_VELOCITY, listenerVel); + alListenerfv (AL_ORIENTATION, listenerOri); + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + float zero[3] = {0.0f, 0.0f, 0.0f}; + + alGenSources (1, &soundSource[i].handle); + alSourcei (soundSource[i].handle, AL_LOOPING, AL_FALSE); + alSourcefv (soundSource[i].handle, AL_POSITION, defaultPos); + alSourcefv (soundSource[i].handle, AL_VELOCITY, zero); + alSourcefv (soundSource[i].handle, AL_DIRECTION, zero); + + soundSource[i].stream_mutex = CreateMutex ("OpenAL stream mutex", SYNC_CLASS_AUDIO); + } + + if (InitStreamDecoder ()) + { + log_add (log_Error, "Stream decoder initialization failed."); + // TODO: cleanup source mutexes [or is it "muti"? :) ] + SoundDecoder_Uninit (); + alcMakeContextCurrent (NULL); + alcDestroyContext (alcContext); + alcContext = NULL; + alcCloseDevice (alcDevice); + alcDevice = NULL; + return -1; + } + + alDistanceModel (AL_INVERSE_DISTANCE); + + (void) driver; // eat compiler warning + + return 0; +} + +void +openAL_Uninit (void) +{ + int i; + + UninitStreamDecoder (); + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + if (soundSource[i].sample && soundSource[i].sample->decoder) + { + StopStream (i); + } + if (soundSource[i].sbuffer) + { + void *sbuffer = soundSource[i].sbuffer; + soundSource[i].sbuffer = NULL; + HFree (sbuffer); + } + DestroyMutex (soundSource[i].stream_mutex); + } + + alcMakeContextCurrent (NULL); + alcDestroyContext (alcContext); + alcContext = NULL; + alcCloseDevice (alcDevice); + alcDevice = NULL; + + SoundDecoder_Uninit (); +} + + +/* + * General + */ + +sint32 +openAL_GetError (void) +{ + ALint value = alGetError (); + switch (value) + { + case AL_FALSE: + return audio_NO_ERROR; + case AL_INVALID_NAME: + return audio_INVALID_NAME; + case AL_INVALID_ENUM: + return audio_INVALID_ENUM; + case AL_INVALID_VALUE: + return audio_INVALID_VALUE; + case AL_INVALID_OPERATION: + return audio_INVALID_OPERATION; + case AL_OUT_OF_MEMORY: + return audio_OUT_OF_MEMORY; + default: + log_add (log_Debug, "openAL_GetError: unknown value %x", value); + return audio_DRIVER_FAILURE; + break; + } +} + + +/* + * Sources + */ + +void +openAL_GenSources (uint32 n, audio_Object *psrcobj) +{ + alGenSources ((ALsizei) n, (ALuint *) psrcobj); +} + +void +openAL_DeleteSources (uint32 n, audio_Object *psrcobj) +{ + alDeleteSources ((ALsizei) n, (ALuint *) psrcobj); +} + +bool +openAL_IsSource (audio_Object srcobj) +{ + return alIsSource ((ALuint) srcobj); +} + +void +openAL_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value) + +{ + alSourcei ((ALuint) srcobj, (ALenum) pname, (ALint) value); +} + +void +openAL_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value) +{ + alSourcef ((ALuint) srcobj, (ALenum) pname, (ALfloat) value); +} + +void +openAL_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + alSourcefv ((ALuint) srcobj, (ALenum) pname, (ALfloat *) value); +} + +void +openAL_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value) +{ + alGetSourcei ((ALuint) srcobj, (ALenum) pname, (ALint *) value); + if (pname == AL_SOURCE_STATE) + { + switch (*value) + { + case AL_INITIAL: + *value = audio_INITIAL; + break; + case AL_STOPPED: + *value = audio_STOPPED; + break; + case AL_PLAYING: + *value = audio_PLAYING; + break; + case AL_PAUSED: + *value = audio_PAUSED; + break; + default: + log_add (log_Debug, "openAL_GetSourcei(): unknown value %x", + *value); + *value = audio_DRIVER_FAILURE; + } + } +} + +void +openAL_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + alGetSourcef ((ALuint) srcobj, (ALenum) pname, (ALfloat *) value); +} + +void +openAL_SourceRewind (audio_Object srcobj) +{ + alSourceRewind ((ALuint) srcobj); +} + +void +openAL_SourcePlay (audio_Object srcobj) +{ + alSourcePlay ((ALuint) srcobj); +} + +void +openAL_SourcePause (audio_Object srcobj) +{ + alSourcePause ((ALuint) srcobj); +} + +void +openAL_SourceStop (audio_Object srcobj) +{ + alSourceStop ((ALuint) srcobj); +} + +void +openAL_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + alSourceQueueBuffers ((ALuint) srcobj, (ALsizei) n, (ALuint *) pbufobj); +} + +void +openAL_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + alSourceUnqueueBuffers ((ALuint) srcobj, (ALsizei) n, (ALuint *) pbufobj); +} + + +/* + * Buffers + */ + +void +openAL_GenBuffers (uint32 n, audio_Object *pbufobj) +{ + alGenBuffers ((ALsizei) n, (ALuint *) pbufobj); +} + +void +openAL_DeleteBuffers (uint32 n, audio_Object *pbufobj) +{ + alDeleteBuffers ((ALsizei) n, (ALuint *) pbufobj); +} + +bool +openAL_IsBuffer (audio_Object bufobj) +{ + return alIsBuffer ((ALuint) bufobj); +} + +void +openAL_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value) +{ + alGetBufferi ((ALuint) bufobj, (ALenum) pname, (ALint *) value); +} + +void +openAL_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + alBufferData ((ALuint) bufobj, (ALenum) format, (ALvoid *) data, + (ALsizei) size, (ALsizei) freq); +} + +#endif diff --git a/src/libs/sound/openal/audiodrv_openal.h b/src/libs/sound/openal/audiodrv_openal.h new file mode 100644 index 0000000..c1a3eff --- /dev/null +++ b/src/libs/sound/openal/audiodrv_openal.h @@ -0,0 +1,86 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* OpenAL audio driver + */ + +#ifndef LIBS_SOUND_OPENAL_AUDIODRV_OPENAL_H_ +#define LIBS_SOUND_OPENAL_AUDIODRV_OPENAL_H_ + +#include "config.h" +#include "libs/sound/sound.h" +#include "endian_uqm.h" + +#if defined (__APPLE__) +# include +# include +#else +# include +# include +# ifdef _MSC_VER +# pragma comment (lib, "OpenAL32.lib") +# endif +#endif + +/* This is just a simple endianness setup for decoders */ +#ifdef WORDS_BIGENDIAN +# define MIX_IS_BIG_ENDIAN true +# define MIX_WANT_BIG_ENDIAN true +#else +# define MIX_IS_BIG_ENDIAN false +# define MIX_WANT_BIG_ENDIAN false +#endif + + +/* General */ +sint32 openAL_Init (audio_Driver *driver, sint32 flags); +void openAL_Uninit (void); +sint32 openAL_GetError (void); + +/* Sources */ +void openAL_GenSources (uint32 n, audio_Object *psrcobj); +void openAL_DeleteSources (uint32 n, audio_Object *psrcobj); +bool openAL_IsSource (audio_Object srcobj); +void openAL_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); +void openAL_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value); +void openAL_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value); +void openAL_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); +void openAL_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value); +void openAL_SourceRewind (audio_Object srcobj); +void openAL_SourcePlay (audio_Object srcobj); +void openAL_SourcePause (audio_Object srcobj); +void openAL_SourceStop (audio_Object srcobj); +void openAL_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); +void openAL_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + +/* Buffers */ +void openAL_GenBuffers (uint32 n, audio_Object *pbufobj); +void openAL_DeleteBuffers (uint32 n, audio_Object *pbufobj); +bool openAL_IsBuffer (audio_Object bufobj); +void openAL_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); +void openAL_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +#endif /* LIBS_SOUND_OPENAL_AUDIODRV_OPENAL_H_ */ diff --git a/src/libs/sound/resinst.c b/src/libs/sound/resinst.c new file mode 100644 index 0000000..dc44c49 --- /dev/null +++ b/src/libs/sound/resinst.c @@ -0,0 +1,65 @@ +//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 "sound.h" +#include "sndintrn.h" + +static void +GetSoundBankFileData (const char *pathname, RESOURCE_DATA *resdata) +{ + resdata->ptr = LoadResourceFromPath (pathname, _GetSoundBankData); +} + +static void +GetMusicFileData (const char *pathname, RESOURCE_DATA *resdata) +{ + resdata->ptr = LoadResourceFromPath (pathname, _GetMusicData); +} + +BOOLEAN +InstallAudioResTypes (void) +{ + InstallResTypeVectors ("SNDRES", GetSoundBankFileData, _ReleaseSoundBankData, NULL); + InstallResTypeVectors ("MUSICRES", GetMusicFileData, _ReleaseMusicData, NULL); + return (TRUE); +} + +SOUND_REF +LoadSoundInstance (RESOURCE res) +{ + void *hData; + + hData = res_GetResource (res); + if (hData) + res_DetachResource (res); + + return ((SOUND_REF)hData); +} + +MUSIC_REF +LoadMusicInstance (RESOURCE res) +{ + void *hData; + + hData = res_GetResource (res); + if (hData) + res_DetachResource (res); + + return ((MUSIC_REF)hData); +} + diff --git a/src/libs/sound/sfx.c b/src/libs/sound/sfx.c new file mode 100644 index 0000000..3060434 --- /dev/null +++ b/src/libs/sound/sfx.c @@ -0,0 +1,306 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "sound.h" +#include "sndintrn.h" +#include "libs/reslib.h" +#include "libs/log.h" +#include "libs/strlib.h" + // for GetStringAddress() +#include "libs/strings/strintrn.h" + // for AllocStringTable(), FreeStringTable() +#include "libs/memlib.h" +#include + + +static void CheckFinishedChannels (void); + +static const SoundPosition notPositional = {FALSE, 0, 0}; + +void +PlayChannel (COUNT channel, SOUND snd, SoundPosition pos, + void *positional_object, unsigned char priority) +{ + SOUNDPTR snd_ptr = GetSoundAddress (snd); + TFB_SoundSample *sample; + + StopSource (channel); + // all finished (stopped) channels can be cleaned up at this point + // since this is the only func that can initiate an sfx sound + CheckFinishedChannels (); + + if (!snd_ptr) + return; // nothing to play + + sample = *(TFB_SoundSample**) snd_ptr; + + soundSource[channel].sample = sample; + soundSource[channel].positional_object = positional_object; + + UpdateSoundPosition (channel, optStereoSFX ? pos : notPositional); + + audio_Sourcei (soundSource[channel].handle, audio_BUFFER, + sample->buffer[0]); + audio_SourcePlay (soundSource[channel].handle); + (void) priority; +} + +void +StopChannel (COUNT channel, unsigned char Priority) +{ + StopSource (channel); + (void)Priority; // ignored +} + +static void +CheckFinishedChannels (void) +{ + int i; + + for (i = FIRST_SFX_SOURCE; i <= LAST_SFX_SOURCE; ++i) + { + audio_IntVal state; + + audio_GetSourcei (soundSource[i].handle, audio_SOURCE_STATE, + &state); + if (state == audio_STOPPED) + { + CleanSource (i); + // and if it failed... we still dont care + audio_GetError(); + } + } +} + +BOOLEAN +ChannelPlaying (COUNT WhichChannel) +{ + audio_IntVal state; + + audio_GetSourcei (soundSource[WhichChannel].handle, + audio_SOURCE_STATE, &state); + if (state == audio_PLAYING) + return TRUE; + return FALSE; +} + +void * +GetPositionalObject (COUNT channel) +{ + return soundSource[channel].positional_object; +} + +void +SetPositionalObject (COUNT channel, void *positional_object) +{ + soundSource[channel].positional_object = positional_object; +} + +void +UpdateSoundPosition (COUNT channel, SoundPosition pos) +{ + const float ATTENUATION = 160.0f; + const float MIN_DISTANCE = 0.5f; + float fpos[3]; + + if (pos.positional) + { + float dist; + + fpos[0] = pos.x / ATTENUATION; + fpos[1] = 0.0f; + fpos[2] = pos.y / ATTENUATION; + dist = (float) sqrt (fpos[0] * fpos[0] + fpos[2] * fpos[2]); + if (dist < MIN_DISTANCE) + { // object is too close to listener + // move it away along the same vector + float scale = MIN_DISTANCE / dist; + fpos[0] *= scale; + fpos[2] *= scale; + } + + audio_Sourcefv (soundSource[channel].handle, audio_POSITION, fpos); + //log_add (log_Debug, "UpdateSoundPosition(): channel %d, pos %d %d, posobj %x", + // channel, pos.x, pos.y, (unsigned int)soundSource[channel].positional_object); + } + else + { + fpos[0] = fpos[1] = 0.0f; + fpos[2] = -1.0f; + audio_Sourcefv (soundSource[channel].handle, audio_POSITION, fpos); + } +} + +void +SetChannelVolume (COUNT channel, COUNT volume, BYTE priority) + // I wonder what this whole priority business is... + // I can probably ignore it. +{ + audio_Sourcef (soundSource[channel].handle, audio_GAIN, + (volume / (float)MAX_VOLUME) * sfxVolumeScale); + (void)priority; // ignored +} + +void * +_GetSoundBankData (uio_Stream *fp, DWORD length) +{ + int snd_ct, n; + DWORD opos; + char CurrentLine[1024], filename[1024]; +#define MAX_FX 256 + TFB_SoundSample *sndfx[MAX_FX]; + STRING_TABLE Snd; + STRING str; + int i; + + (void) length; // ignored + opos = uio_ftell (fp); + + { + char *s1, *s2; + + if (_cur_resfile_name == 0 + || (((s2 = 0), (s1 = strrchr (_cur_resfile_name, '/')) == 0) + && (s2 = strrchr (_cur_resfile_name, '\\')) == 0)) + n = 0; + else + { + if (s2 > s1) + s1 = s2; + n = s1 - _cur_resfile_name + 1; + strncpy (filename, _cur_resfile_name, n); + } + } + + snd_ct = 0; + while (uio_fgets (CurrentLine, sizeof (CurrentLine), fp) && + snd_ct < MAX_FX) + { + TFB_SoundSample* sample; + TFB_SoundDecoder* decoder; + uint32 decoded_bytes; + + if (sscanf (CurrentLine, "%s", &filename[n]) != 1) + { + log_add (log_Warning, "_GetSoundBankData: bad line: '%s'", + CurrentLine); + continue; + } + + log_add (log_Info, "_GetSoundBankData(): loading %s", filename); + + decoder = SoundDecoder_Load (contentDir, filename, 4096, 0, 0); + if (!decoder) + { + log_add (log_Warning, "_GetSoundBankData(): couldn't load %s", + filename); + continue; + } + + // SFX samples don't have decoders, everything is pre-decoded below + sample = TFB_CreateSoundSample (NULL, 1, NULL); + + // Decode everything and stash it in 1 buffer + decoded_bytes = SoundDecoder_DecodeAll (decoder); + log_add (log_Info, "_GetSoundBankData(): decoded bytes %d", + decoded_bytes); + + audio_BufferData (sample->buffer[0], decoder->format, + decoder->buffer, decoded_bytes, decoder->frequency); + // just for informational purposes + sample->length = decoder->length; + + SoundDecoder_Free (decoder); + + sndfx[snd_ct] = sample; + ++snd_ct; + } + + if (!snd_ct) + return NULL; // no sounds decoded + + Snd = AllocStringTable (snd_ct, 0); + if (!Snd) + { // Oops, have to delete everything now + while (snd_ct--) + TFB_DestroySoundSample (sndfx[snd_ct]); + + return NULL; + } + + // Populate the STRING_TABLE with ptrs to sample + for (i = 0, str = Snd->strings; i < snd_ct; ++i, ++str) + { + TFB_SoundSample **target = HMalloc (sizeof (sndfx[0])); + *target = sndfx[i]; + str->data = (STRINGPTR)target; + str->length = sizeof (sndfx[0]); + } + + return Snd; +} + +BOOLEAN +_ReleaseSoundBankData (void *Snd) +{ + STRING_TABLE fxTab = Snd; + int index; + + if (!fxTab) + return FALSE; + + for (index = 0; index < fxTab->size; ++index) + { + int i; + void **sptr = (void**)fxTab->strings[index].data; + TFB_SoundSample *sample = (TFB_SoundSample*)*sptr; + + // Check all sources and see if we are currently playing this sample + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + if (soundSource[i].sample == sample) + { // Playing this sample. Have to stop it. + StopSource (i); + soundSource[i].sample = NULL; + } + } + + if (sample->decoder) + SoundDecoder_Free (sample->decoder); + sample->decoder = NULL; + TFB_DestroySoundSample (sample); + // sptr will be deleted by FreeStringTable() below + } + + FreeStringTable (fxTab); + + return TRUE; +} + +BOOLEAN +DestroySound(SOUND_REF target) +{ + return _ReleaseSoundBankData (target); +} + +// The type conversions are implicit and will generate errors +// or warnings if types change imcompatibly +SOUNDPTR +GetSoundAddress (SOUND sound) +{ + return GetStringAddress (sound); +} diff --git a/src/libs/sound/sndintrn.h b/src/libs/sound/sndintrn.h new file mode 100644 index 0000000..179028c --- /dev/null +++ b/src/libs/sound/sndintrn.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 LIBS_SOUND_SNDINTRN_H_ +#define LIBS_SOUND_SNDINTRN_H_ + +#include +#include "types.h" +#include "libs/reslib.h" +#include "libs/memlib.h" + +#define PAD_SCOPE_BYTES 256 + +extern void *_GetMusicData (uio_Stream *fp, DWORD length); +extern BOOLEAN _ReleaseMusicData (void *handle); + +extern void *_GetSoundBankData (uio_Stream *fp, DWORD length); +extern BOOLEAN _ReleaseSoundBankData (void *handle); + +#define AllocMusicData HMalloc +#define FreeMusicData HFree + +extern char* CheckMusicResName (char* filename); + +// audio data +struct tfb_soundsample +{ + TFB_SoundDecoder *decoder; // decoder to read from + float length; // total length of decoder chain in seconds + audio_Object *buffer; + uint32 num_buffers; + TFB_SoundTag *buffer_tag; + sint32 offset; // initial offset + void* data; // user-defined data + TFB_SoundCallbacks callbacks; // user-defined callbacks +}; + +// equivalent to channel in legacy sound code +typedef struct tfb_soundsource +{ + TFB_SoundSample *sample; + audio_Object handle; + bool stream_should_be_playing; + Mutex stream_mutex; + sint32 start_time; // for tracks played-time math + uint32 pause_time; // keep track for paused tracks + void *positional_object; + + audio_Object last_q_buf; // for callbacks processing + + // Cyclic waveform buffer for oscilloscope + void *sbuffer; + uint32 sbuf_size; + uint32 sbuf_tail; + uint32 sbuf_head; + uint32 sbuf_lasttime; // timestamp of the first queued buffer +} TFB_SoundSource; + +extern TFB_SoundSource soundSource[]; + +#endif /* LIBS_SOUND_SNDINTRN_H_ */ diff --git a/src/libs/sound/sound.c b/src/libs/sound/sound.c new file mode 100644 index 0000000..f2a790e --- /dev/null +++ b/src/libs/sound/sound.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 "sound.h" +#include "sndintrn.h" +#include "libs/compiler.h" +#include "libs/inplib.h" +#include "libs/memlib.h" + + +int musicVolume = NORMAL_VOLUME; +float musicVolumeScale; +float sfxVolumeScale; +float speechVolumeScale; +TFB_SoundSource soundSource[NUM_SOUNDSOURCES]; + + +void +StopSound (void) +{ + int i; + + for (i = FIRST_SFX_SOURCE; i <= LAST_SFX_SOURCE; ++i) + { + StopSource (i); + } +} + +void +CleanSource (int iSource) +{ +#define MAX_STACK_BUFFERS 64 + audio_IntVal processed; + + soundSource[iSource].positional_object = NULL; + audio_GetSourcei (soundSource[iSource].handle, + audio_BUFFERS_PROCESSED, &processed); + if (processed != 0) + { + audio_Object stack_bufs[MAX_STACK_BUFFERS]; + audio_Object *bufs; + + if (processed > MAX_STACK_BUFFERS) + bufs = (audio_Object *) HMalloc ( + sizeof (audio_Object) * processed); + else + bufs = stack_bufs; + + audio_SourceUnqueueBuffers (soundSource[iSource].handle, + processed, bufs); + + if (processed > MAX_STACK_BUFFERS) + HFree (bufs); + } + // set the source state to 'initial' + audio_SourceRewind (soundSource[iSource].handle); +} + +void +StopSource (int iSource) +{ + audio_SourceStop (soundSource[iSource].handle); + CleanSource (iSource); +} + +BOOLEAN +SoundPlaying (void) +{ + int i; + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + TFB_SoundSample *sample; + sample = soundSource[i].sample; + if (sample && sample->decoder) + { + BOOLEAN result; + LockMutex (soundSource[i].stream_mutex); + result = PlayingStream (i); + UnlockMutex (soundSource[i].stream_mutex); + if (result) + return TRUE; + } + else + { + audio_IntVal state; + audio_GetSourcei (soundSource[i].handle, audio_SOURCE_STATE, &state); + if (state == audio_PLAYING) + return TRUE; + } + } + + return FALSE; +} + +// for now just spin in a sleep() loop +// perhaps later change to condvar implementation +void +WaitForSoundEnd (COUNT Channel) +{ + while (Channel == TFBSOUND_WAIT_ALL ? + SoundPlaying () : ChannelPlaying (Channel)) + { + SleepThread (ONE_SECOND / 20); + if (QuitPosted) // Don't make users wait for sounds to end + break; + } +} + + +// Status: Ignored +BOOLEAN +InitSound (int argc, char* argv[]) +{ + /* Quell compiler warnings */ + (void)argc; + (void)argv; + return TRUE; +} + +// Status: Ignored +void +UninitSound (void) +{ +} + +void +SetSFXVolume (float volume) +{ + int i; + for (i = FIRST_SFX_SOURCE; i <= LAST_SFX_SOURCE; ++i) + { + audio_Sourcef (soundSource[i].handle, audio_GAIN, volume); + } +} + +void +SetSpeechVolume (float volume) +{ + audio_Sourcef (soundSource[SPEECH_SOURCE].handle, audio_GAIN, volume); +} + +DWORD +FadeMusic (BYTE end_vol, SIZE TimeInterval) +{ + if (QuitPosted) // Don't make users wait for fades + TimeInterval = 0; + + if (TimeInterval < 0) + TimeInterval = 0; + + if (!SetMusicStreamFade (TimeInterval, end_vol)) + { // fade rejected, maybe due to TimeInterval==0 + SetMusicVolume (end_vol); + return GetTimeCounter (); + } + else + { + return GetTimeCounter () + TimeInterval + 1; + } +} + + diff --git a/src/libs/sound/sound.h b/src/libs/sound/sound.h new file mode 100644 index 0000000..2a4f447 --- /dev/null +++ b/src/libs/sound/sound.h @@ -0,0 +1,81 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_SOUND_SOUND_H_ // try avoiding collisions on id +#define LIBS_SOUND_SOUND_H_ + +#include "types.h" +#include "audiocore.h" +#include "decoders/decoder.h" +#include "libs/threadlib.h" +#include "libs/sndlib.h" + + +#define FIRST_SFX_SOURCE 0 +#define LAST_SFX_SOURCE (FIRST_SFX_SOURCE + NUM_SFX_CHANNELS - 1) +#define MUSIC_SOURCE (LAST_SFX_SOURCE + 1) +#define SPEECH_SOURCE (MUSIC_SOURCE + 1) +#define NUM_SOUNDSOURCES (SPEECH_SOURCE + 1) + +typedef struct +{ + int in_use; + audio_Object buf_name; + intptr_t data; // user-defined data +} TFB_SoundTag; + +typedef struct tfb_soundcallbacks +{ + // return TRUE to continue, FALSE to abort + bool (* OnStartStream) (TFB_SoundSample*); + // return TRUE to continue, FALSE to abort + bool (* OnEndChunk) (TFB_SoundSample*, audio_Object); + // return TRUE to continue, FALSE to abort + void (* OnEndStream) (TFB_SoundSample*); + // tagged buffer callback + void (* OnTaggedBuffer) (TFB_SoundSample*, TFB_SoundTag*); + // buffer just queued + void (* OnQueueBuffer) (TFB_SoundSample*, audio_Object); +} TFB_SoundCallbacks; + + +extern int musicVolume; +extern float musicVolumeScale; +extern float sfxVolumeScale; +extern float speechVolumeScale; + +void StopSource (int iSource); +void CleanSource (int iSource); + +void SetSFXVolume (float volume); +void SetSpeechVolume (float volume); + +TFB_SoundSample *TFB_CreateSoundSample (TFB_SoundDecoder*, uint32 num_buffers, + const TFB_SoundCallbacks* /* can be NULL */); +void TFB_DestroySoundSample (TFB_SoundSample*); +void TFB_SetSoundSampleData (TFB_SoundSample*, void* data); +void* TFB_GetSoundSampleData (TFB_SoundSample*); +void TFB_SetSoundSampleCallbacks (TFB_SoundSample*, + const TFB_SoundCallbacks* /* can be NULL */); +TFB_SoundDecoder* TFB_GetSoundSampleDecoder (TFB_SoundSample*); + +TFB_SoundTag* TFB_FindTaggedBuffer (TFB_SoundSample*, audio_Object buffer); +void TFB_ClearBufferTag (TFB_SoundTag*); +bool TFB_TagBuffer (TFB_SoundSample*, audio_Object buffer, intptr_t data); + +#include "stream.h" + +#endif // LIBS_SOUND_SOUND_H_ diff --git a/src/libs/sound/stream.c b/src/libs/sound/stream.c new file mode 100644 index 0000000..b7d2718 --- /dev/null +++ b/src/libs/sound/stream.c @@ -0,0 +1,814 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#include + // for abs() +#include "sound.h" +#include "sndintrn.h" +#include "libs/tasklib.h" +#include "libs/timelib.h" +#include "libs/threadlib.h" +#include "libs/log.h" +#include "libs/memlib.h" + + +static Task decoderTask; + +static TimeCount musicFadeStartTime; +static sint32 musicFadeInterval; +static int musicFadeStartVolume; +static int musicFadeDelta; +// Mutex protects fade structures +static Mutex fade_mutex; + +static void add_scope_data (TFB_SoundSource *source, uint32 bytes); + + +void +PlayStream (TFB_SoundSample *sample, uint32 source, bool looping, bool scope, + bool rewind) +{ + uint32 i; + sint32 offset; + TFB_SoundDecoder *decoder; + + if (!sample) + return; + + StopStream (source); + if (sample->callbacks.OnStartStream && + !sample->callbacks.OnStartStream (sample)) + return; // callback failed + + if (sample->buffer_tag) + memset (sample->buffer_tag, 0, + sample->num_buffers * sizeof (sample->buffer_tag[0])); + + decoder = sample->decoder; + offset = sample->offset; + if (rewind) + SoundDecoder_Rewind (decoder); + else + offset += (sint32)(SoundDecoder_GetTime (decoder) * ONE_SECOND); + + soundSource[source].sample = sample; + decoder->looping = looping; + audio_Sourcei (soundSource[source].handle, audio_LOOPING, false); + + if (scope) + { // Prealloc the scope buffer in advance so that we do not + // realloc it a zillion times + soundSource[source].sbuf_size = sample->num_buffers * + decoder->buffer_size + PAD_SCOPE_BYTES; + soundSource[source].sbuffer = HCalloc (soundSource[source].sbuf_size); + } + + for (i = 0; i < sample->num_buffers; ++i) + { + uint32 decoded_bytes; + + decoded_bytes = SoundDecoder_Decode (decoder); +#if 0 + log_add (log_Debug, "PlayStream(): source:%d filename:%s start:%d " + "position:%d bytes:%d\n", + source, decoder->filename, decoder->start_sample, + decoder->pos, decoded_bytes); +#endif + if (decoded_bytes == 0) + break; + + audio_BufferData (sample->buffer[i], decoder->format, + decoder->buffer, decoded_bytes, decoder->frequency); + audio_SourceQueueBuffers (soundSource[source].handle, 1, + &sample->buffer[i]); + if (sample->callbacks.OnQueueBuffer) + sample->callbacks.OnQueueBuffer (sample, sample->buffer[i]); + + if (scope) + add_scope_data (&soundSource[source], decoded_bytes); + + if (decoder->error != SOUNDDECODER_OK) + { + if (decoder->error != SOUNDDECODER_EOF || + !sample->callbacks.OnEndChunk || + !sample->callbacks.OnEndChunk (sample, sample->buffer[i])) + { // Decoder probably run out of data before we could fill + // all buffers, and OnEndChunk() did not set a new one + break; + } + else + { // OnEndChunk() probably set a new decoder, get it + decoder = sample->decoder; + } + } + } + + soundSource[source].sbuf_lasttime = GetTimeCounter (); + // Adjust the start time so it looks like the stream has been playing + // from the very beginning + soundSource[source].start_time = GetTimeCounter () - offset; + soundSource[source].pause_time = 0; + soundSource[source].stream_should_be_playing = TRUE; + audio_SourcePlay (soundSource[source].handle); +} + +void +StopStream (uint32 source) +{ + StopSource (source); + + soundSource[source].stream_should_be_playing = FALSE; + soundSource[source].sample = NULL; + + if (soundSource[source].sbuffer) + { + void *sbuffer = soundSource[source].sbuffer; + soundSource[source].sbuffer = NULL; + HFree (sbuffer); + } + soundSource[source].sbuf_size = 0; + soundSource[source].sbuf_head = 0; + soundSource[source].sbuf_tail = 0; + soundSource[source].pause_time = 0; +} + +void +PauseStream (uint32 source) +{ + soundSource[source].stream_should_be_playing = FALSE; + if (!soundSource[source].pause_time) + soundSource[source].pause_time = GetTimeCounter (); + audio_SourcePause (soundSource[source].handle); +} + +void +ResumeStream (uint32 source) +{ + if (soundSource[source].pause_time) + { // Adjust the start time so it looks like the stream has + // been playing all this time non-stop + soundSource[source].start_time += GetTimeCounter () + - soundSource[source].pause_time; + } + soundSource[source].pause_time = 0; + soundSource[source].stream_should_be_playing = TRUE; + audio_SourcePlay (soundSource[source].handle); +} + +void +SeekStream (uint32 source, uint32 pos) +{ + TFB_SoundSample* sample = soundSource[source].sample; + bool looping; + bool scope; + + if (!sample) + return; + looping = sample->decoder->looping; + scope = soundSource[source].sbuffer != NULL; + + StopSource (source); + SoundDecoder_Seek (sample->decoder, pos); + PlayStream (sample, source, looping, scope, false); +} + +BOOLEAN +PlayingStream (uint32 source) +{ + return soundSource[source].stream_should_be_playing; +} + + +TFB_SoundSample * +TFB_CreateSoundSample (TFB_SoundDecoder *decoder, uint32 num_buffers, + const TFB_SoundCallbacks *pcbs /* can be NULL */) +{ + TFB_SoundSample *sample; + + sample = HCalloc (sizeof (*sample)); + sample->decoder = decoder; + sample->num_buffers = num_buffers; + sample->buffer = HCalloc (sizeof (audio_Object) * num_buffers); + audio_GenBuffers (num_buffers, sample->buffer); + if (pcbs) + sample->callbacks = *pcbs; + + return sample; +} + +// Deletes all TFB_SoundSample data structures, except decoder +void +TFB_DestroySoundSample (TFB_SoundSample *sample) +{ + if (sample->buffer) + { + audio_DeleteBuffers (sample->num_buffers, sample->buffer); + HFree (sample->buffer); + } + HFree (sample->buffer_tag); + HFree (sample); +} + +void +TFB_SetSoundSampleData (TFB_SoundSample *sample, void* data) +{ + sample->data = data; +} + +void* +TFB_GetSoundSampleData (TFB_SoundSample *sample) +{ + return sample->data; +} + +void +TFB_SetSoundSampleCallbacks (TFB_SoundSample *sample, + const TFB_SoundCallbacks *pcbs /* can be NULL */) +{ + if (pcbs) + sample->callbacks = *pcbs; + else + memset (&sample->callbacks, 0, sizeof (sample->callbacks)); +} + +TFB_SoundDecoder* +TFB_GetSoundSampleDecoder (TFB_SoundSample *sample) +{ + return sample->decoder; +} + +TFB_SoundTag* +TFB_FindTaggedBuffer (TFB_SoundSample *sample, audio_Object buffer) +{ + uint32 buf_num; + + if (!sample->buffer_tag) + return NULL; // do not have any tags + + for (buf_num = 0; + buf_num < sample->num_buffers && + (!sample->buffer_tag[buf_num].in_use || + sample->buffer_tag[buf_num].buf_name != buffer + ); + buf_num++) + ; + + return buf_num < sample->num_buffers ? + &sample->buffer_tag[buf_num] : NULL; +} + +bool +TFB_TagBuffer (TFB_SoundSample *sample, audio_Object buffer, intptr_t data) +{ + uint32 buf_num; + + if (!sample->buffer_tag) + sample->buffer_tag = HCalloc (sizeof (TFB_SoundTag) * + sample->num_buffers); + + for (buf_num = 0; + buf_num < sample->num_buffers && + sample->buffer_tag[buf_num].in_use && + sample->buffer_tag[buf_num].buf_name != buffer; + buf_num++) + ; + + if (buf_num >= sample->num_buffers) + return false; // no empty slot + + sample->buffer_tag[buf_num].in_use = 1; + sample->buffer_tag[buf_num].buf_name = buffer; + sample->buffer_tag[buf_num].data = data; + + return true; +} + +void +TFB_ClearBufferTag (TFB_SoundTag *ptag) +{ + ptag->in_use = 0; + ptag->buf_name = 0; +} + +static void +remove_scope_data (TFB_SoundSource *source, audio_Object buffer) +{ + audio_IntVal buf_size; + + audio_GetBufferi (buffer, audio_SIZE, &buf_size); + source->sbuf_head += buf_size; + // the buffer is cyclic + source->sbuf_head %= source->sbuf_size; + + source->sbuf_lasttime = GetTimeCounter (); +} + +static void +add_scope_data (TFB_SoundSource *source, uint32 bytes) +{ + uint8 *sbuffer = source->sbuffer; + uint8 *dec_buf = source->sample->decoder->buffer; + uint32 tail_bytes; + uint32 wrap_bytes; + + if (source->sbuf_tail + bytes > source->sbuf_size) + { // does not fit at the tail, have to split it up + tail_bytes = source->sbuf_size - source->sbuf_tail; + wrap_bytes = bytes - tail_bytes; + } + else + { // all fits at the tail + tail_bytes = bytes; + wrap_bytes = 0; + } + + if (tail_bytes) + { + memcpy (sbuffer + source->sbuf_tail, dec_buf, tail_bytes); + source->sbuf_tail += tail_bytes; + } + + if (wrap_bytes) + { + memcpy (sbuffer, dec_buf + tail_bytes, wrap_bytes); + source->sbuf_tail = wrap_bytes; + } +} + +static void +process_stream (TFB_SoundSource *source) +{ + TFB_SoundSample *sample = source->sample; + TFB_SoundDecoder *decoder = sample->decoder; + bool end_chunk_failed = false; + audio_IntVal processed; + audio_IntVal queued; + + audio_GetSourcei (source->handle, audio_BUFFERS_PROCESSED, &processed); + audio_GetSourcei (source->handle, audio_BUFFERS_QUEUED, &queued); + + if (processed == 0) + { // Nothing was played + audio_IntVal state; + + audio_GetSourcei (source->handle, audio_SOURCE_STATE, &state); + if (state != audio_PLAYING) + { + if (queued == 0 && decoder->error == SOUNDDECODER_EOF) + { // The stream has reached the end + log_add (log_Info, "StreamDecoderTaskFunc(): " + "finished playing %s", decoder->filename); + source->stream_should_be_playing = FALSE; + + if (sample->callbacks.OnEndStream) + sample->callbacks.OnEndStream (sample); + } + else + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "buffer underrun playing %s", decoder->filename); + audio_SourcePlay (source->handle); + } + } + } + + // Unqueue processed buffers and replace them with new ones + for (; processed > 0; --processed) + { + uint32 error; + audio_Object buffer; + uint32 decoded_bytes; + + audio_GetError (); // clear error state + + // Get the buffer that finished playing + audio_SourceUnqueueBuffers (source->handle, 1, &buffer); + error = audio_GetError(); + if (error != audio_NO_ERROR) + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "error after audio_SourceUnqueueBuffers: %x, file %s", + error, decoder->filename); + break; + } + + // Process a callback on a tagged buffer, if any + if (sample->callbacks.OnTaggedBuffer) + { + TFB_SoundTag* tag = TFB_FindTaggedBuffer (sample, buffer); + if (tag) + sample->callbacks.OnTaggedBuffer (sample, tag); + } + + if (source->sbuffer) + remove_scope_data (source, buffer); + + // See what state the decoder was left in last time around + if (decoder->error != SOUNDDECODER_OK) + { + if (decoder->error == SOUNDDECODER_EOF) + { + if (end_chunk_failed) + continue; // should not do it again + + if (!sample->callbacks.OnEndChunk || + !sample->callbacks.OnEndChunk (sample, source->last_q_buf)) + { // Reached the end of the current stream and we did not + // get another sample to play (relevant for Trackplayer) + end_chunk_failed = true; + continue; + } + else + { // OnEndChunk succeeded, so someone (read: Trackplayer) + // wants to keep going, probably with a new decoder. + // Get the new decoder + decoder = sample->decoder; + } + } + else + { // Decoder returned a real error, keep going +#if 0 + log_add (log_Debug, "StreamDecoderTaskFunc(): " + "decoder->error is %d for %s", decoder->error, + decoder->filename); +#endif + continue; + } + } + + // Now replace the unqueued buffer with a new one + decoded_bytes = SoundDecoder_Decode (decoder); + if (decoder->error == SOUNDDECODER_ERROR) + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "SoundDecoder_Decode error %d, file %s", + decoder->error, decoder->filename); + source->stream_should_be_playing = FALSE; + continue; + } + + if (decoded_bytes == 0) + { // Nothing was decoded, keep going + continue; + // This loses a stream buffer, which we cannot get back + // w/o restarting the stream, but we should never get here. + } + + // And a new buffer is born + audio_BufferData (buffer, decoder->format, decoder->buffer, + decoded_bytes, decoder->frequency); + error = audio_GetError(); + if (error != audio_NO_ERROR) + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "error after audio_BufferData: %x, file %s, decoded %d", + error, decoder->filename, decoded_bytes); + continue; + } + + // Now queue the buffer + audio_SourceQueueBuffers (source->handle, 1, &buffer); + error = audio_GetError(); + if (error != audio_NO_ERROR) + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "error after audio_SourceQueueBuffers: %x, file %s, " + "decoded %d", error, decoder->filename, decoded_bytes); + continue; + } + + // Remember the last queued buffer so we can pass it to callbacks + source->last_q_buf = buffer; + if (sample->callbacks.OnQueueBuffer) + sample->callbacks.OnQueueBuffer (sample, buffer); + + if (source->sbuffer) + add_scope_data (source, decoded_bytes); + } +} + +static void +processMusicFade (void) +{ + TimeCount Now; + sint32 elapsed; + int newVolume; + + LockMutex (fade_mutex); + + if (!musicFadeInterval) + { // there is no fade set + UnlockMutex (fade_mutex); + return; + } + + Now = GetTimeCounter (); + elapsed = Now - musicFadeStartTime; + if (elapsed > musicFadeInterval) + elapsed = musicFadeInterval; + + newVolume = musicFadeStartVolume + (long)musicFadeDelta * elapsed + / musicFadeInterval; + SetMusicVolume (newVolume); + + if (elapsed >= musicFadeInterval) + musicFadeInterval = 0; // fade is over + + UnlockMutex (fade_mutex); +} + +static int +StreamDecoderTaskFunc (void *data) +{ + Task task = (Task)data; + int active_streams; + int i; + + while (!Task_ReadState (task, TASK_EXIT)) + { + active_streams = 0; + + processMusicFade (); + + for (i = MUSIC_SOURCE; i < NUM_SOUNDSOURCES; ++i) + { + TFB_SoundSource *source = &soundSource[i]; + + LockMutex (source->stream_mutex); + + if (!source->sample || + !source->sample->decoder || + !source->stream_should_be_playing || + source->sample->decoder->error == SOUNDDECODER_ERROR) + { + UnlockMutex (source->stream_mutex); + continue; + } + + process_stream (source); + active_streams++; + + UnlockMutex (source->stream_mutex); + } + + if (active_streams == 0) + { // Throttle down the thread when there are no active streams + HibernateThread (ONE_SECOND / 10); + } + else + TaskSwitch (); + } + + FinishTask (task); + return 0; +} + +static inline sint32 +readSoundSample (void *ptr, int sample_size) +{ + if (sample_size == sizeof (uint8)) + return (*(uint8*)ptr - 128) << 8; + else + return *(sint16*)ptr; +} + +// Graphs the current sound data for the oscilloscope. +// Includes a rudimentary automatic gain control (AGC) to properly graph +// the streams at different gain levels (based on running average). +// We use AGC because different pieces of music and speech can easily be +// at very different gain levels, because the game is moddable. +int +GraphForegroundStream (uint8 *data, sint32 width, sint32 height, + bool wantSpeech) +{ + int source_num; + TFB_SoundSource *source; + TFB_SoundDecoder *decoder; + int channels; + int sample_size; + int full_sample; + int step; + long played_time; + long delta; + uint8 *sbuffer; + unsigned long pos; + int scale; + sint32 i; + // AGC variables +#define DEF_PAGE_MAX 28000 +#define AGC_PAGE_COUNT 16 + static int page_sum = DEF_PAGE_MAX * AGC_PAGE_COUNT; + static int pages[AGC_PAGE_COUNT] = + { + DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, + DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, + DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, + DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, + }; + static int page_head; +#define AGC_FRAME_COUNT 8 + static int frame_sum; + static int frames; + static int avg_amp = DEF_PAGE_MAX; // running amplitude (sort of) average + int target_amp; + int max_a; +#define VAD_MIN_ENERGY 100 + long energy; + + + // Prefer speech to music + source_num = SPEECH_SOURCE; + source = &soundSource[source_num]; + LockMutex (source->stream_mutex); + if (wantSpeech && (!source->sample || + !source->sample->decoder || !source->sample->decoder->is_null)) + { // Use speech waveform, since it's available + // Step is picked experimentally. Using step of 1 sample at 11025Hz, + // because human speech is mostly in the low frequencies, and it looks + // better this way. + step = 1; + } + else + { // We do not have speech -- use music waveform + UnlockMutex (source->stream_mutex); + + source_num = MUSIC_SOURCE; + source = &soundSource[source_num]; + LockMutex (source->stream_mutex); + + // Step is picked experimentally. Using step of 4 samples at 11025Hz. + // It looks better this way. + step = 4; + } + + if (!PlayingStream (source_num) || !source->sample + || !source->sample->decoder || !source->sbuffer + || source->sbuf_size == 0) + { // We don't have data to return, oh well. + UnlockMutex (source->stream_mutex); + return 0; + } + decoder = source->sample->decoder; + + if (!audio_GetFormatInfo (decoder->format, &channels, &sample_size)) + { + UnlockMutex (source->stream_mutex); + log_add (log_Debug, "GraphForegroundStream(): uknown format %u", + (unsigned)decoder->format); + return 0; + } + full_sample = channels * sample_size; + + // See how far into the buffer we should be now + played_time = GetTimeCounter () - source->sbuf_lasttime; + delta = played_time * decoder->frequency * full_sample / ONE_SECOND; + // align delta to sample start + delta = delta & ~(full_sample - 1); + + if (delta < 0) + { + log_add (log_Debug, "GraphForegroundStream(): something is messed" + " with timing, delta %ld", delta); + delta = 0; + } + else if (delta > (long)source->sbuf_size) + { // Stream decoder task has just had a heart attack, not much we can do + delta = 0; + } + + // Step is in 11025 Hz units, so we need to adjust to source frequency + step = decoder->frequency * step / 11025; + if (step == 0) + step = 1; + step *= full_sample; + + sbuffer = source->sbuffer; + pos = source->sbuf_head + delta; + + // We are not basing the scaling factor on signal energy, because we + // want it to *look* pretty instead of sounding nice and even + target_amp = (height >> 1) >> 1; + scale = avg_amp / target_amp; + + max_a = 0; + energy = 0; + for (i = 0; i < width; ++i, pos += step) + { + sint32 s; + int t; + + pos %= source->sbuf_size; + + s = readSoundSample (sbuffer + pos, sample_size); + if (channels > 1) + s += readSoundSample (sbuffer + pos + sample_size, sample_size); + + energy += (s * s) / 0x10000; + t = abs(s); + if (t > max_a) + max_a = t; + + s = (s / scale) + (height >> 1); + if (s < 0) + s = 0; + else if (s > height - 1) + s = height - 1; + + data[i] = s; + } + energy /= width; + + // Very basic VAD. We don't want to count speech pauses in the average + if (energy > VAD_MIN_ENERGY) + { + // Record the maximum amplitude (sort of) + frame_sum += max_a; + ++frames; + if (frames == AGC_FRAME_COUNT) + { // Got a full page + frame_sum /= AGC_FRAME_COUNT; + // Record the page + page_sum -= pages[page_head]; + page_sum += frame_sum; + pages[page_head] = frame_sum; + page_head = (page_head + 1) % AGC_PAGE_COUNT; + + frame_sum = 0; + frames = 0; + + avg_amp = page_sum / AGC_PAGE_COUNT; + } + } + + UnlockMutex (source->stream_mutex); + return 1; +} + +// This function is normally called on the Starcon2Main thread +bool +SetMusicStreamFade (sint32 howLong, int endVolume) +{ + bool ret = true; + + LockMutex (fade_mutex); + + if (howLong < 0) + howLong = 0; + + musicFadeStartTime = GetTimeCounter (); + musicFadeInterval = howLong; + musicFadeStartVolume = musicVolume; + musicFadeDelta = endVolume - musicFadeStartVolume; + if (!musicFadeInterval) + ret = false; // reject + + UnlockMutex (fade_mutex); + + return ret; +} + +int +InitStreamDecoder (void) +{ + fade_mutex = CreateMutex ("Stream fade mutex", SYNC_CLASS_AUDIO); + if (!fade_mutex) + return -1; + + decoderTask = AssignTask (StreamDecoderTaskFunc, 1024, + "audio stream decoder"); + if (!decoderTask) + return -1; + + return 0; +} + +void +UninitStreamDecoder (void) +{ + if (decoderTask) + { + ConcludeTask (decoderTask); + decoderTask = NULL; + } + + if (fade_mutex) + { + DestroyMutex (fade_mutex); + fade_mutex = NULL; + } +} diff --git a/src/libs/sound/stream.h b/src/libs/sound/stream.h new file mode 100644 index 0000000..5766132 --- /dev/null +++ b/src/libs/sound/stream.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 STREAM_H +#define STREAM_H + +int InitStreamDecoder (void); +void UninitStreamDecoder (void); + +void PlayStream (TFB_SoundSample *sample, uint32 source, bool looping, + bool scope, bool rewind); +void StopStream (uint32 source); +void PauseStream (uint32 source); +void ResumeStream (uint32 source); +void SeekStream (uint32 source, uint32 pos); +BOOLEAN PlayingStream (uint32 source); + +int GraphForegroundStream (uint8 *data, sint32 width, sint32 height, + bool wantSpeech); + +// returns TRUE if the fade was accepted by stream decoder +bool SetMusicStreamFade (sint32 howLong, int endVolume); + +#endif diff --git a/src/libs/sound/trackint.h b/src/libs/sound/trackint.h new file mode 100644 index 0000000..fe39740 --- /dev/null +++ b/src/libs/sound/trackint.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 TRACKINT_H +#define TRACKINT_H + +#include "libs/callback.h" + +struct tfb_soundchunk +{ + TFB_SoundDecoder *decoder; // decoder for this chunk + float start_time; // relative time from track start + int tag_me; // set for chunks with subtitles + uint32 track_num; // logical track #, comm code needs this + UNICODE *text; // subtitle text + CallbackFunction callback; // comm callback, executed on chunk start + struct tfb_soundchunk *next; +}; + +typedef struct tfb_soundchunk TFB_SoundChunk; + +TFB_SoundChunk *create_SoundChunk (TFB_SoundDecoder *decoder, float start_time); +void destroy_SoundChunk_list (TFB_SoundChunk *chain); +TFB_SoundChunk *find_next_page (TFB_SoundChunk *cur); +TFB_SoundChunk *find_prev_page (TFB_SoundChunk *cur); + + +#endif // TRACKINT_H diff --git a/src/libs/sound/trackplayer.c b/src/libs/sound/trackplayer.c new file mode 100644 index 0000000..8068fc3 --- /dev/null +++ b/src/libs/sound/trackplayer.c @@ -0,0 +1,874 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "sound.h" +#include "sndintrn.h" +#include "libs/sound/trackplayer.h" +#include "trackint.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "options.h" +#include +#include +#include +#include + + +static int track_count; // total number of tracks +static int no_page_break; // set when combining several tracks into one + +// The one and only sample we play. Track switching is done by modifying +// this sample while it is playing. StreamDecoderTaskFunc() picks up the +// changes *mostly* seamlessly (keyword: mostly). +// This is technically a hack, but a decent one ;) +static TFB_SoundSample *sound_sample; + +static volatile uint32 tracks_length; // total length of tracks in game units + +static TFB_SoundChunk *chunks_head; // first decoder in linked list +static TFB_SoundChunk *chunks_tail; // last decoder in linked list +static TFB_SoundChunk *last_sub; // last chunk in the list with a subtitle + +static TFB_SoundChunk *cur_chunk; // currently playing chunk +static TFB_SoundChunk *cur_sub_chunk; // currently displayed subtitle chunk + +// Accesses to cur_chunk and cur_sub_chunk are guarded by stream_mutex, +// because these should only be accesses by the DoInput and the +// stream player threads. Any other accesses would go unguarded. +// Other data structures are unguarded and should only be accessed from +// the DoInput thread at certain times, i.e. nothing can be modified +// between StartTrack() and JumpTrack()/StopTrack() calls. +// Use caution when changing code, as you may need to guard other data +// structures the same way. + +static void seek_track (sint32 offset); + +// stream callbacks +static bool OnStreamStart (TFB_SoundSample* sample); +static bool OnChunkEnd (TFB_SoundSample* sample, audio_Object buffer); +static void OnStreamEnd (TFB_SoundSample* sample); +static void OnBufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag); + +static TFB_SoundCallbacks trackCBs = +{ + OnStreamStart, + OnChunkEnd, + OnStreamEnd, + OnBufferTag, + NULL +}; + +static inline sint32 +chunk_end_time (TFB_SoundChunk *chunk) +{ + return (sint32) ((chunk->start_time + chunk->decoder->length) + * ONE_SECOND); +} + +static inline sint32 +tracks_end_time (void) +{ + return chunk_end_time (chunks_tail); +} + +//JumpTrack currently aborts the current track. However, it doesn't clear the +//data-structures as StopTrack does. this allows for rewind even after the +//track has finished playing +//Question: Should 'abort' call StopTrack? +void +JumpTrack (void) +{ + if (!sound_sample) + return; // nothing to skip + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + seek_track (tracks_length + 1); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +// This should just start playing a stream +void +PlayTrack (void) +{ + if (!sound_sample) + return; // nothing to play + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + tracks_length = tracks_end_time (); + // decoder will be set in OnStreamStart() + cur_chunk = chunks_head; + // Always scope the speech data, we may need it + PlayStream (sound_sample, SPEECH_SOURCE, false, true, true); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +void +PauseTrack (void) +{ + if (!sound_sample) + return; // nothing to pause + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + PauseStream (SPEECH_SOURCE); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +// ResumeTrack should resume a paused track, and do nothing for a playing track +void +ResumeTrack (void) +{ + audio_IntVal state; + + if (!sound_sample) + return; // nothing to resume + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + if (!cur_chunk) + { // not playing anything, so no resuming + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + return; + } + + audio_GetSourcei (soundSource[SPEECH_SOURCE].handle, audio_SOURCE_STATE, &state); + if (state == audio_PAUSED) + ResumeStream (SPEECH_SOURCE); + + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +COUNT +PlayingTrack (void) +{ + // This ignores the paused state and simply returns what track + // *should* be playing + COUNT result = 0; // default is none + + if (!sound_sample) + return 0; // not playing anything + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + if (cur_chunk) + result = cur_chunk->track_num + 1; + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + return result; +} + +void +StopTrack (void) +{ + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + StopStream (SPEECH_SOURCE); + track_count = 0; + tracks_length = 0; + cur_chunk = NULL; + cur_sub_chunk = NULL; + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + if (chunks_head) + { + chunks_tail = NULL; + destroy_SoundChunk_list (chunks_head); + chunks_head = NULL; + last_sub = NULL; + } + if (sound_sample) + { + // We delete the decoders ourselves + sound_sample->decoder = NULL; + TFB_DestroySoundSample (sound_sample); + sound_sample = NULL; + } +} + +static void +DoTrackTag (TFB_SoundChunk *chunk) +{ + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + if (chunk->callback) + chunk->callback(0); + + cur_sub_chunk = chunk; + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +// This func is called by PlayStream() when stream is about +// to start. We have a chance to tweak the stream here. +// This is called on the DoInput thread. +static bool +OnStreamStart (TFB_SoundSample* sample) +{ + if (sample != sound_sample) + return false; // Huh? Why did we get called on this? + + if (!cur_chunk) + return false; // Stream shouldn't be playing at all + + // Adjust the sample to play what we want + sample->decoder = cur_chunk->decoder; + sample->offset = (sint32) (cur_chunk->start_time * ONE_SECOND); + + if (cur_chunk->tag_me) + DoTrackTag (cur_chunk); + + return true; +} + +// This func is called by StreamDecoderTaskFunc() when the last buffer +// of the current chunk has been decoded (not when it has been *played*). +// This is called on the stream task thread. +static bool +OnChunkEnd (TFB_SoundSample* sample, audio_Object buffer) +{ + if (sample != sound_sample) + return false; // Huh? Why did we get called on this? + + if (!cur_chunk || !cur_chunk->next) + { // all chunks and tracks are done + return false; + } + + // Move on to the next chunk + cur_chunk = cur_chunk->next; + // Adjust the sample to play what we want + sample->decoder = cur_chunk->decoder; + SoundDecoder_Rewind (sample->decoder); + + log_add (log_Info, "Switching to stream %s at pos %d", + sample->decoder->filename, sample->decoder->start_sample); + + if (cur_chunk->tag_me) + { // Tag the last buffer of the chunk with the next chunk + TFB_TagBuffer (sample, buffer, (intptr_t)cur_chunk); + } + + return true; +} + +// This func is called by StreamDecoderTaskFunc() when stream has ended +// This is called on the stream task thread. +static void +OnStreamEnd (TFB_SoundSample* sample) +{ + if (sample != sound_sample) + return; // Huh? Why did we get called on this? + + cur_chunk = NULL; + cur_sub_chunk = NULL; +} + +// This func is called by StreamDecoderTaskFunc() when a tagged buffer +// has finished playing. +// This is called on the stream task thread. +static void +OnBufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag) +{ + TFB_SoundChunk* chunk = (TFB_SoundChunk*) tag->data; + + assert (sizeof (tag->data) >= sizeof (chunk)); + + if (sample != sound_sample) + return; // Huh? Why did we get called on this? + + TFB_ClearBufferTag (tag); + DoTrackTag (chunk); +} + +// Parse the timestamps string into an int array. +// Rerturns number of timestamps parsed. +static int +GetTimeStamps (UNICODE *TimeStamps, sint32 *time_stamps) +{ + int pos; + int num = 0; + + while (*TimeStamps && (pos = strcspn (TimeStamps, ",\r\n"))) + { + UNICODE valStr[32]; + uint32 val; + + memcpy (valStr, TimeStamps, pos); + valStr[pos] = '\0'; + val = strtoul (valStr, NULL, 10); + if (val) + { + *time_stamps = val; + num++; + time_stamps++; + } + TimeStamps += pos; + TimeStamps += strspn (TimeStamps, ",\r\n"); + } + return (num); +} + +#define TEXT_SPEED 80 +// Returns number of parsed pages +static int +SplitSubPages (UNICODE *text, UNICODE *pages[], sint32 timestamp[], int size) +{ + int lead_ellips = 0; + COUNT page; + + for (page = 0; page < size && *text != '\0'; ++page) + { + int aft_ellips; + int pos; + + // seek to EOL or end of the string + pos = strcspn (text, "\r\n"); + // XXX: this will only work when ASCII punctuation and spaces + // are used exclusively + aft_ellips = 3 * (text[pos] != '\0' && pos > 0 && + !ispunct (text[pos - 1]) && !isspace (text[pos - 1])); + pages[page] = HMalloc (sizeof (UNICODE) * + (lead_ellips + pos + aft_ellips + 1)); + if (lead_ellips) + strcpy (pages[page], ".."); + memcpy (pages[page] + lead_ellips, text, pos); + pages[page][lead_ellips + pos] = '\0'; // string term + if (aft_ellips) + strcpy (pages[page] + lead_ellips + pos, "..."); + timestamp[page] = pos * TEXT_SPEED; + if (timestamp[page] < 1000) + timestamp[page] = 1000; + lead_ellips = aft_ellips ? 2 : 0; + text += pos; + // Skip any EOL + text += strspn (text, "\r\n"); + } + + return page; +} + +// decodes several tracks into one and adds it to queue +// track list is NULL-terminated +// May only be called after at least one SpliceTrack(). This is a limitation +// for the sake of timestamps, but it does not have to be so. +void +SpliceMultiTrack (UNICODE *TrackNames[], UNICODE *TrackText) +{ +#define MAX_MULTI_TRACKS 20 +#define MAX_MULTI_BUFFERS 100 + TFB_SoundDecoder* track_decs[MAX_MULTI_TRACKS + 1]; + int tracks; + int slen1, slen2; + + if (!TrackText) + { + log_add (log_Debug, "SpliceMultiTrack(): no track text"); + return; + } + + if (!sound_sample || !chunks_tail) + { + log_add (log_Warning, "SpliceMultiTrack(): Cannot be called before SpliceTrack()"); + return; + } + + log_add (log_Info, "SpliceMultiTrack(): loading..."); + for (tracks = 0; *TrackNames && tracks < MAX_MULTI_TRACKS; TrackNames++, tracks++) + { + track_decs[tracks] = SoundDecoder_Load (contentDir, *TrackNames, + 32768, 0, - 3 * TEXT_SPEED); + if (track_decs[tracks]) + { + log_add (log_Info, " track: %s, decoder: %s, rate %d format %x", + *TrackNames, + SoundDecoder_GetName (track_decs[tracks]), + track_decs[tracks]->frequency, + track_decs[tracks]->format); + SoundDecoder_DecodeAll (track_decs[tracks]); + + chunks_tail->next = create_SoundChunk (track_decs[tracks], sound_sample->length); + chunks_tail = chunks_tail->next; + chunks_tail->track_num = track_count - 1; + sound_sample->length += track_decs[tracks]->length; + } + else + { + log_add (log_Warning, "SpliceMultiTrack(): couldn't load %s\n", + *TrackNames); + tracks--; + } + } + track_decs[tracks] = 0; // term + + if (tracks == 0) + { + log_add (log_Warning, "SpliceMultiTrack(): no tracks loaded"); + return; + } + + slen1 = strlen (last_sub->text); + slen2 = strlen (TrackText); + last_sub->text = HRealloc (last_sub->text, slen1 + slen2 + 1); + strcpy (last_sub->text + slen1, TrackText); + + no_page_break = 1; +} + +// XXX: This code and the entire trackplayer are begging to be overhauled +void +SpliceTrack (UNICODE *TrackName, UNICODE *TrackText, UNICODE *TimeStamp, CallbackFunction cb) +{ + static UNICODE last_track_name[128] = ""; + static unsigned long dec_offset = 0; +#define MAX_PAGES 50 + UNICODE *pages[MAX_PAGES]; + sint32 time_stamps[MAX_PAGES]; + int num_pages; + int page; + + if (!TrackText) + return; + + if (!TrackName) + { // Appending a piece of subtitles to the last track + int slen1, slen2; + + if (track_count == 0) + { + log_add (log_Warning, "SpliceTrack(): Tried to append a subtitle," + " but no current track"); + return; + } + + if (!last_sub || !last_sub->text) + { + log_add (log_Warning, "SpliceTrack(): Tried to append a subtitle" + " to a NULL string"); + return; + } + + num_pages = SplitSubPages (TrackText, pages, time_stamps, MAX_PAGES); + if (num_pages == 0) + { + log_add (log_Warning, "SpliceTrack(): Failed to parse subtitles"); + return; + } + // The last page's stamp is a suggested value. The track should + // actually play to the end. + time_stamps[num_pages - 1] = -time_stamps[num_pages - 1]; + + // Add the first piece to the last subtitle page + slen1 = strlen (last_sub->text); + slen2 = strlen (pages[0]); + last_sub->text = HRealloc (last_sub->text, slen1 + slen2 + 1); + strcpy (last_sub->text + slen1, pages[0]); + HFree (pages[0]); + + // Add the rest of the pages + for (page = 1; page < num_pages; ++page) + { + TFB_SoundChunk *next_sub = find_next_page (last_sub); + if (next_sub) + { // nodes prepared by previous call, just fill in the subs + next_sub->text = pages[page]; + last_sub = next_sub; + } + else + { // probably no timestamps were provided, so need more work + TFB_SoundDecoder *decoder = SoundDecoder_Load (contentDir, + last_track_name, 4096, dec_offset, time_stamps[page]); + if (!decoder) + { + log_add (log_Warning, "SpliceTrack(): couldn't load %s", TrackName); + break; + } + dec_offset += (unsigned long)(decoder->length * 1000); + chunks_tail->next = create_SoundChunk (decoder, sound_sample->length); + chunks_tail = chunks_tail->next; + chunks_tail->tag_me = 1; + chunks_tail->track_num = track_count - 1; + chunks_tail->text = pages[page]; + chunks_tail->callback = cb; + // TODO: We may have to tag only one page with a callback + //cb = NULL; + last_sub = chunks_tail; + sound_sample->length += decoder->length; + } + } + } + else + { // Adding a new track + int num_timestamps = 0; + + utf8StringCopy (last_track_name, sizeof (last_track_name), TrackName); + + num_pages = SplitSubPages (TrackText, pages, time_stamps, MAX_PAGES); + if (num_pages == 0) + { + log_add (log_Warning, "SpliceTrack(): Failed to parse sutitles"); + return; + } + // The last page's stamp is a suggested value. The track should + // actually play to the end. + time_stamps[num_pages - 1] = -time_stamps[num_pages - 1]; + + if (no_page_break && track_count) + { + int slen1, slen2; + + slen1 = strlen (last_sub->text); + slen2 = strlen (pages[0]); + last_sub->text = HRealloc (last_sub->text, slen1 + slen2 + 1); + strcpy (last_sub->text + slen1, pages[0]); + HFree (pages[0]); + } + else + track_count++; + + log_add (log_Info, "SpliceTrack(): loading %s", TrackName); + + if (TimeStamp) + { + num_timestamps = GetTimeStamps (TimeStamp, time_stamps) + 1; + if (num_timestamps < num_pages) + { + log_add (log_Warning, "SpliceTrack(): number of timestamps" + " doesn't match number of pages!"); + } + else if (num_timestamps > num_pages) + { // We most likely will get more subtitles appended later + // Set the last page to the rest of the track + time_stamps[num_timestamps - 1] = -100000; + } + } + else + { + num_timestamps = num_pages; + } + + // Reset the offset for the new track + dec_offset = 0; + for (page = 0; page < num_timestamps; ++page) + { + TFB_SoundDecoder *decoder = SoundDecoder_Load (contentDir, + TrackName, 4096, dec_offset, time_stamps[page]); + if (!decoder) + { + log_add (log_Warning, "SpliceTrack(): couldn't load %s", TrackName); + break; + } + + if (!sound_sample) + { + sound_sample = TFB_CreateSoundSample (NULL, 8, &trackCBs); + chunks_head = create_SoundChunk (decoder, 0.0); + chunks_tail = chunks_head; + } + else + { + chunks_tail->next = create_SoundChunk (decoder, sound_sample->length); + chunks_tail = chunks_tail->next; + } + dec_offset += (unsigned long)(decoder->length * 1000); +#if 0 + log_add (log_Debug, "page (%d of %d): %d ts: %d", + page, num_pages, + dec_offset, time_stamps[page]); +#endif + sound_sample->length += decoder->length; + chunks_tail->track_num = track_count - 1; + if (!no_page_break) + { + chunks_tail->tag_me = 1; + if (page < num_pages) + { + chunks_tail->text = pages[page]; + last_sub = chunks_tail; + } + chunks_tail->callback = cb; + // TODO: We may have to tag only one page with a callback + //cb = NULL; + } + no_page_break = 0; + } + } +} + +// This function figures out the chunk that should be playing based on +// 'offset' into the total playing time of all tracks. It then sets +// the speech source's sample to the necessary decoder and seeks the +// decoder to the proper point. +// XXX: This means that whatever speech has already been queued on the +// source will continue playing, so we may need some small timing +// adjustments. It may be simpler to just call PlayStream(). +static void +seek_track (sint32 offset) +{ + TFB_SoundChunk *cur; + TFB_SoundChunk *last_tag = NULL; + + if (!sound_sample) + return; // nothing to recompute + + if (offset < 0) + offset = 0; + else if ((uint32)offset > tracks_length) + offset = tracks_length + 1; + + // Adjusting the stream start time is the only way we can arbitrarily + // seek the stream right now + soundSource[SPEECH_SOURCE].start_time = GetTimeCounter () - offset; + + // Find the chunk that should be playing at this time offset + for (cur = chunks_head; cur && offset >= chunk_end_time (cur); + cur = cur->next) + { + // .. looking for the last callback as we go along + // XXX: this effectively set the last point where Fot is looking at. + // TODO: this should be somehow changed if we implement more + // callbacks, like Melnorme trading, offloading at Starbase, etc. + if (cur->tag_me) + last_tag = cur; + } + + if (cur) + { + cur_chunk = cur; + SoundDecoder_Seek (cur->decoder, (uint32) (((float)offset / ONE_SECOND + - cur->start_time) * 1000)); + sound_sample->decoder = cur->decoder; + + if (cur->tag_me) + last_tag = cur; + if (last_tag) + DoTrackTag (last_tag); + } + else + { // The offset is beyond the length of all tracks + StopStream (SPEECH_SOURCE); + cur_chunk = NULL; + cur_sub_chunk = NULL; + } +} + +static sint32 +get_current_track_pos (void) +{ + sint32 start_time = soundSource[SPEECH_SOURCE].start_time; + sint32 pos = GetTimeCounter () - start_time; + if (pos < 0) + pos = 0; + else if ((uint32)pos > tracks_length) + pos = tracks_length; + return pos; +} + +void +FastReverse_Smooth (void) +{ + sint32 offset; + + if (!sound_sample) + return; // nothing is playing, so.. bye! + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + offset = get_current_track_pos (); + offset -= ACCEL_SCROLL_SPEED; + seek_track (offset); + + // Restart the stream in case it ended previously + if (!PlayingStream (SPEECH_SOURCE)) + PlayStream (sound_sample, SPEECH_SOURCE, false, true, false); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +void +FastForward_Smooth (void) +{ + sint32 offset; + + if (!sound_sample) + return; // nothing is playing, so.. bye! + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + offset = get_current_track_pos (); + offset += ACCEL_SCROLL_SPEED; + seek_track (offset); + + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +void +FastReverse_Page (void) +{ + TFB_SoundChunk *prev; + + if (!sound_sample) + return; // nothing is playing, so.. bye! + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + prev = find_prev_page (cur_sub_chunk); + if (prev) + { // Set the chunk to be played + cur_chunk = prev; + cur_sub_chunk = prev; + // Decoder will be set in OnStreamStart() + PlayStream (sound_sample, SPEECH_SOURCE, false, true, true); + } + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +void +FastForward_Page (void) +{ + TFB_SoundChunk *next; + + if (!sound_sample) + return; // nothing is playing, so.. bye! + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + next = find_next_page (cur_sub_chunk); + if (next) + { // Set the chunk to be played + cur_chunk = next; + cur_sub_chunk = next; + // Decoder will be set in OnStreamStart() + PlayStream (sound_sample, SPEECH_SOURCE, false, true, true); + } + else + { // End of the tracks (pun intended) + seek_track (tracks_length + 1); + } + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +// Tells current position of streaming speech in the units +// specified by the caller. +// This is normally called on the ambient_anim_task thread. +int +GetTrackPosition (int in_units) +{ + uint32 offset; + uint32 length = tracks_length; + // detach from the static one, otherwise, we can race for + // it and thus divide by 0 + + if (!sound_sample || length == 0) + return 0; // nothing is playing + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + offset = get_current_track_pos (); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + return in_units * offset / length; +} + +TFB_SoundChunk * +create_SoundChunk (TFB_SoundDecoder *decoder, float start_time) +{ + TFB_SoundChunk *chunk; + chunk = HCalloc (sizeof (*chunk)); + chunk->decoder = decoder; + chunk->start_time = start_time; + return chunk; +} + +void +destroy_SoundChunk_list (TFB_SoundChunk *chunk) +{ + TFB_SoundChunk *next = NULL; + for ( ; chunk; chunk = next) + { + next = chunk->next; + if (chunk->decoder) + SoundDecoder_Free (chunk->decoder); + HFree (chunk->text); + HFree (chunk); + } +} + +// Returns the next chunk with a subtitle +TFB_SoundChunk * +find_next_page (TFB_SoundChunk *cur) +{ + if (!cur) + return NULL; + for (cur = cur->next; cur && !cur->tag_me; cur = cur->next) + ; + return cur; +} + +// Returns the previous chunk with a subtitle. +// cur == 0 is treated as end of the list. +TFB_SoundChunk * +find_prev_page (TFB_SoundChunk *cur) +{ + TFB_SoundChunk *prev; + TFB_SoundChunk *last_valid = chunks_head; + + if (cur == chunks_head) + return cur; // cannot go below the first track + + for (prev = chunks_head; prev && prev != cur; prev = prev->next) + { + if (prev->tag_me) + last_valid = prev; + } + return last_valid; +} + + +// External access to the chunks list +SUBTITLE_REF +GetFirstTrackSubtitle (void) +{ + return chunks_head; +} + +// External access to the chunks list +SUBTITLE_REF +GetNextTrackSubtitle (SUBTITLE_REF LastRef) +{ + if (!LastRef) + return NULL; // enumeration already ended + + return find_next_page (LastRef); +} + +// External access to the chunk subtitles +const UNICODE * +GetTrackSubtitleText (SUBTITLE_REF SubRef) +{ + if (!SubRef) + return NULL; + + return SubRef->text; +} + +// External access to currently active subtitle text +// Returns NULL is none is active +const UNICODE * +GetTrackSubtitle (void) +{ + const UNICODE *cur_sub = NULL; + + if (!sound_sample) + return NULL; // not playing anything + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + if (cur_sub_chunk) + cur_sub = cur_sub_chunk->text; + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + return cur_sub; +} diff --git a/src/libs/sound/trackplayer.h b/src/libs/sound/trackplayer.h new file mode 100644 index 0000000..5964e65 --- /dev/null +++ b/src/libs/sound/trackplayer.h @@ -0,0 +1,52 @@ +//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 TRACKPLAYER_H +#define TRACKPLAYER_H + +#include "libs/compiler.h" +#include "libs/callback.h" + +#define ACCEL_SCROLL_SPEED 300 + +extern void PlayTrack (void); +extern void StopTrack (void); +extern void JumpTrack (void); +extern void PauseTrack (void); +extern void ResumeTrack (void); +extern COUNT PlayingTrack (void); + +extern void FastReverse_Smooth (void); +extern void FastForward_Smooth (void); +extern void FastReverse_Page (void); +extern void FastForward_Page (void); + +extern void SpliceTrack (UNICODE *filespec, UNICODE *textspec, UNICODE *TimeStamp, CallbackFunction cb); +extern void SpliceMultiTrack (UNICODE *TrackNames[], UNICODE *TrackText); + +extern int GetTrackPosition (int in_units); + +typedef struct tfb_soundchunk *SUBTITLE_REF; + +extern SUBTITLE_REF GetFirstTrackSubtitle (void); +extern SUBTITLE_REF GetNextTrackSubtitle (SUBTITLE_REF LastRef); +extern const UNICODE *GetTrackSubtitleText (SUBTITLE_REF SubRef); + +extern const UNICODE *GetTrackSubtitle (void); + +#endif diff --git a/src/libs/strings/Makeinfo b/src/libs/strings/Makeinfo new file mode 100644 index 0000000..f1e4a9e --- /dev/null +++ b/src/libs/strings/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="getstr.c sfileins.c sresins.c stringhashtable.c strings.c unicode.c" +uqm_HFILES="stringhashtable.c strintrn.h" diff --git a/src/libs/strings/getstr.c b/src/libs/strings/getstr.c new file mode 100644 index 0000000..ba428cf --- /dev/null +++ b/src/libs/strings/getstr.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 "options.h" +#include "strintrn.h" +#include "libs/graphics/gfx_common.h" +#include "libs/reslib.h" +#include "libs/log.h" +#include "libs/memlib.h" + + +#define MAX_STRINGS 2048 +#define POOL_SIZE 4096 + +static void +dword_convert (DWORD *dword_array, COUNT num_dwords) +{ + BYTE *p = (BYTE*)dword_array; + + do + { + *dword_array++ = MAKE_DWORD ( + MAKE_WORD (p[3], p[2]), + MAKE_WORD (p[1], p[0]) + ); + p += 4; + } while (--num_dwords); +} + +static STRING +set_strtab_entry (STRING_TABLE_DESC *strtab, int index, const char *value, + int len) +{ + STRING str = &strtab->strings[index]; + + if (str->data) + { + HFree (str->data); + str->data = NULL; + str->length = 0; + } + if (len) + { + str->data = HMalloc (len); + str->length = len; + memcpy (str->data, value, len); + } + return str; +} + +static void +copy_strings_to_strtab (STRING_TABLE_DESC *strtab, size_t firstIndex, + size_t count, const char *data, const DWORD *lens) +{ + size_t stringI; + const char *off = data; + + for (stringI = 0; stringI < count; stringI++) + { + set_strtab_entry(strtab, firstIndex + stringI, + off, lens[stringI]); + off += lens[stringI]; + } +} + +// Check whether a buffer has a certain minimum size, and enlarge it +// if necessary. +// buf: pointer to the pointer to the buffer. May be NULL. +// curSize: pointer to the current size (multiple of 'increment') +// minSize: required minimum size +// increment: size to increment the buffer with if necessary +// On success, *buf and *curSize are updated. On failure, they are +// unchanged. +// returns FALSE if and only if the buffer needs to be enlarged but +// memory allocation failed. +static BOOLEAN +ensureBufSize (char **buf, size_t *curSize, size_t minSize, size_t increment) +{ + char *newBuf; + size_t newSize; + + if (minSize <= *curSize) + { + // Buffer is large enough as it is. + return TRUE; + } + + newSize = ((minSize + (increment - 1)) / increment) * increment; + // Smallest multiple of 'increment' larger or equal to minSize. + newBuf = HRealloc (*buf, newSize); + if (newBuf == NULL) + return FALSE; + + // Success + *buf = newBuf; + *curSize = newSize; + return TRUE; +} + +void +_GetConversationData (const char *path, RESOURCE_DATA *resdata) +{ + unsigned long dataLen; + void *result; + int stringI; + int path_len; + int num_data_sets; + DWORD opos; + + char *namedata = NULL; + // Contains the names (indexes) of the dialogs. + DWORD nlen[MAX_STRINGS]; + // Length of each of the names. + DWORD NameOffs; + size_t tot_name_size; + + char *strdata = NULL; + // Contains the dialog strings. + DWORD slen[MAX_STRINGS]; + // Length of each of the dialog strings. + DWORD StringOffs; + size_t tot_string_size; + + char *clipdata = NULL; + // Contains the file names of the speech files. + DWORD clen[MAX_STRINGS]; + // Length of each of the speech file names. + DWORD ClipOffs; + size_t tot_clip_size; + + char *ts_data = NULL; + // Contains the timestamp data for synching the text with the + // speech. + DWORD tslen[MAX_STRINGS]; + // Length of each of the timestamp strings. + DWORD TSOffs; + size_t tot_ts_size = 0; + + char CurrentLine[1024]; + char paths[1024]; + char *clip_path; + char *ts_path; + + uio_Stream *fp = NULL; + uio_Stream *timestamp_fp = NULL; + StringHashTable_HashTable *nameHashTable = NULL; + // Hash table of string names (such as "GLAD_WHEN_YOU_COME_BACK") + // to a STRING. + + /* Parse out the conversation components. */ + strncpy (paths, path, 1023); + paths[1023] = '\0'; + clip_path = strchr (paths, ':'); + if (clip_path == NULL) + { + ts_path = NULL; + } + else + { + *clip_path = '\0'; + clip_path++; + + ts_path = strchr (clip_path, ':'); + if (ts_path != NULL) + { + *ts_path = '\0'; + ts_path++; + } + } + + fp = res_OpenResFile (contentDir, paths, "rb"); + if (fp == NULL) + { + log_add (log_Warning, "Warning: Can't open '%s'", paths); + resdata->ptr = NULL; + return; + } + + dataLen = LengthResFile (fp); + log_add (log_Info, "\t'%s' -- conversation phrases -- %lu bytes", paths, + dataLen); + if (clip_path) + log_add (log_Info, "\t'%s' -- voice clip directory", clip_path); + else + log_add (log_Info, "\tNo associated voice clips"); + if (ts_path) + log_add (log_Info, "\t'%s' -- timestamps", ts_path); + else + log_add (log_Info, "\tNo associated timestamp file"); + + if (dataLen == 0) + { + log_add (log_Warning, "Warning: Trying to load empty file '%s'.", + path); + goto err; + } + + tot_string_size = POOL_SIZE; + strdata = HMalloc (tot_string_size); + if (strdata == 0) + goto err; + + tot_name_size = POOL_SIZE; + namedata = HMalloc (tot_name_size); + if (namedata == 0) + goto err; + + tot_clip_size = POOL_SIZE; + clipdata = HMalloc (tot_clip_size); + if (clipdata == 0) + goto err; + ts_data = NULL; + + nameHashTable = StringHashTable_newHashTable( + NULL, NULL, NULL, NULL, NULL, 0, 0.85, 0.9); + if (nameHashTable == NULL) + goto err; + + path_len = clip_path ? strlen (clip_path) : 0; + + if (ts_path) + { + timestamp_fp = uio_fopen (contentDir, ts_path, "rb"); + if (timestamp_fp != NULL) + { + tot_ts_size = POOL_SIZE; + ts_data = HMalloc (tot_ts_size); + if (ts_data == 0) + goto err; + } + } + + opos = uio_ftell (fp); + stringI = -1; + NameOffs = 0; + StringOffs = 0; + ClipOffs = 0; + TSOffs = 0; + for (;;) + { + int l; + + if (uio_fgets (CurrentLine, sizeof (CurrentLine), fp) == NULL) + { + // EOF or read error. + break; + } + + if (stringI >= MAX_STRINGS - 1) + { + // Too many strings. + break; + } + + if (CurrentLine[0] == '#') + { + // String header, of the following form: + // #(GLAD_WHEN_YOU_COME_BACK) commander-000.ogg + char CopyLine[1024]; + char *name; + char *ts; + + strcpy (CopyLine, CurrentLine); + name = strtok (&CopyLine[1], "()"); + if (name) + { + if (stringI >= 0) + { + while (slen[stringI] > 1 && + (strdata[StringOffs - 2] == '\n' || + strdata[StringOffs - 2] == '\r')) + { + --slen[stringI]; + --StringOffs; + strdata[StringOffs - 1] = '\0'; + } + } + + slen[++stringI] = 0; + + // Store the string name. + l = strlen (name) + 1; + if (!ensureBufSize (&namedata, &tot_name_size, + NameOffs + l, POOL_SIZE)) + goto err; + strcpy (&namedata[NameOffs], name); + NameOffs += l; + nlen[stringI] = l; + + // now lets check for timestamp data + if (timestamp_fp) + { + // We have a time stamp file. + char TimeStampLine[1024]; + char *tsptr; + BOOLEAN ts_ok = FALSE; + uio_fgets (TimeStampLine, sizeof (TimeStampLine), timestamp_fp); + if (TimeStampLine[0] == '#') + { + // Line is of the following form: + // #(GIVE_FUEL_AGAIN) 3304,3255 + tslen[stringI] = 0; + tsptr = strstr (TimeStampLine, name); + if (tsptr) + { + tsptr += strlen(name) + 1; + ts_ok = TRUE; + while (! strcspn(tsptr," \t\r\n") && *tsptr) + tsptr++; + if (*tsptr) + { + l = strlen (tsptr) + 1; + if (!ensureBufSize (&ts_data, &tot_ts_size, TSOffs + l, + POOL_SIZE)) + goto err; + + strcpy (&ts_data[TSOffs], tsptr); + TSOffs += l; + tslen[stringI] = l; + } + } + } + if (!ts_ok) + { + // timestamp data is invalid, remove all of it + log_add (log_Warning, "Invalid timestamp data " + "for '%s'. Disabling timestamps", name); + HFree (ts_data); + ts_data = NULL; + uio_fclose (timestamp_fp); + timestamp_fp = NULL; + TSOffs = 0; + } + } + clen[stringI] = 0; + ts = strtok (NULL, " \t\r\n)"); + if (ts) + { + l = path_len + strlen (ts) + 1; + if (!ensureBufSize (&clipdata, &tot_clip_size, + ClipOffs + l, POOL_SIZE)) + goto err; + + if (clip_path) + strcpy (&clipdata[ClipOffs], clip_path); + strcpy (&clipdata[ClipOffs + path_len], ts); + ClipOffs += l; + clen[stringI] = l; + } + } + } + else if (stringI >= 0) + { + char *s; + l = strlen (CurrentLine) + 1; + + if (!ensureBufSize (&strdata, &tot_string_size, StringOffs + l, + POOL_SIZE)) + goto err; + + if (slen[stringI]) + { + --slen[stringI]; + --StringOffs; + } + s = &strdata[StringOffs]; + slen[stringI] += l; + StringOffs += l; + + strcpy (s, CurrentLine); + } + + if ((int)uio_ftell (fp) - (int)opos >= (int)dataLen) + break; + } + if (stringI >= 0) + { + while (slen[stringI] > 1 && (strdata[StringOffs - 2] == '\n' + || strdata[StringOffs - 2] == '\r')) + { + --slen[stringI]; + --StringOffs; + strdata[StringOffs - 1] = '\0'; + } + } + + if (timestamp_fp) + uio_fclose (timestamp_fp); + + result = NULL; + num_data_sets = (ClipOffs ? 1 : 0) + (TSOffs ? 1 : 0) + 1; + if (++stringI) + { + int flags = 0; + int stringCount = stringI; + + if (ClipOffs) + flags |= HAS_SOUND_CLIPS; + if (TSOffs) + flags |= HAS_TIMESTAMP; + flags |= HAS_NAMEINDEX; + + result = AllocStringTable (stringCount, flags); + if (result) + { + // Copy all the gatherered data in a STRING_TABLE + STRING_TABLE_DESC *lpST = (STRING_TABLE) result; + STRING str; + stringI = 0; + + // Store the dialog string. + copy_strings_to_strtab ( + lpST, stringI, stringCount, strdata, slen); + stringI += stringCount; + + // Store the dialog names. + copy_strings_to_strtab ( + lpST, stringI, stringCount, namedata, nlen); + stringI += stringCount; + + // Store sound clip file names. + if (lpST->flags & HAS_SOUND_CLIPS) + { + copy_strings_to_strtab ( + lpST, stringI, stringCount, clipdata, clen); + stringI += stringCount; + } + + // Store time stamp data. + if (lpST->flags & HAS_TIMESTAMP) + { + copy_strings_to_strtab ( + lpST, stringI, stringCount, ts_data, tslen); + //stringI += stringCount; + } + + // Store the STRING in the hash table indexed by the dialog + // name. + str = &lpST->strings[stringCount]; + for (stringI = 0; stringI < stringCount; stringI++) + { + StringHashTable_add (nameHashTable, str[stringI].data, + &str[stringI]); + } + + lpST->nameIndex = nameHashTable; + } + } + HFree (strdata); + if (clipdata != NULL) + HFree (clipdata); + if (ts_data != NULL) + HFree (ts_data); + + resdata->ptr = result; + return; + +err: + if (nameHashTable != NULL) + StringHashTable_deleteHashTable (nameHashTable); + if (ts_data != NULL) + HFree (ts_data); + if (clipdata != NULL) + HFree (clipdata); + if (strdata != NULL) + HFree (strdata); + res_CloseResFile (fp); + resdata->ptr = NULL; +} + +void * +_GetStringData (uio_Stream *fp, DWORD length) +{ + void *result; + + int stringI; + DWORD opos; + DWORD slen[MAX_STRINGS]; + DWORD StringOffs; + size_t tot_string_size; + char CurrentLine[1024]; + char *strdata = NULL; + + tot_string_size = POOL_SIZE; + strdata = HMalloc (tot_string_size); + if (strdata == 0) + goto err; + + opos = uio_ftell (fp); + stringI = -1; + StringOffs = 0; + for (;;) + { + int l; + + if (uio_fgets (CurrentLine, sizeof (CurrentLine), fp) == NULL) + { + // EOF or read error. + break; + } + + if (stringI >= MAX_STRINGS - 1) + { + // Too many strings. + break; + } + + if (CurrentLine[0] == '#') + { + char CopyLine[1024]; + char *s; + + strcpy (CopyLine, CurrentLine); + s = strtok (&CopyLine[1], "()"); + if (s) + { + if (stringI >= 0) + { + while (slen[stringI] > 1 && + (strdata[StringOffs - 2] == '\n' || + strdata[StringOffs - 2] == '\r')) + { + --slen[stringI]; + --StringOffs; + strdata[StringOffs - 1] = '\0'; + } + } + + slen[++stringI] = 0; + } + } + else if (stringI >= 0) + { + char *s; + l = strlen (CurrentLine) + 1; + + if (!ensureBufSize (&strdata, &tot_string_size, StringOffs + l, + POOL_SIZE)) + goto err; + + if (slen[stringI]) + { + --slen[stringI]; + --StringOffs; + } + s = &strdata[StringOffs]; + slen[stringI] += l; + StringOffs += l; + + strcpy (s, CurrentLine); + } + + if ((int)uio_ftell (fp) - (int)opos >= (int)length) + break; + } + if (stringI >= 0) + { + while (slen[stringI] > 1 && (strdata[StringOffs - 2] == '\n' + || strdata[StringOffs - 2] == '\r')) + { + --slen[stringI]; + --StringOffs; + strdata[StringOffs - 1] = '\0'; + } + } + + result = NULL; + if (++stringI) + { + int flags = 0; + int stringCount = stringI; + + result = AllocStringTable (stringI, flags); + if (result) + { + STRING_TABLE_DESC *lpST = (STRING_TABLE) result; + copy_strings_to_strtab (lpST, 0, stringCount, strdata, slen); + } + } + HFree (strdata); + + return result; + +err: + if (strdata != NULL) + HFree (strdata); + return 0; +} + + +void * +_GetBinaryTableData (uio_Stream *fp, DWORD length) +{ + void *result; + result = GetResourceData (fp, length); + + if (result) + { + DWORD *fileData; + STRING_TABLE lpST; + + fileData = (DWORD *)result; + + dword_convert (fileData, 1); /* Length */ + + lpST = AllocStringTable (fileData[0], 0); + if (lpST) + { + int i, size; + BYTE *stringptr; + + size = lpST->size; + + dword_convert (fileData+1, size + 1); + stringptr = (BYTE *)(fileData + 2 + size + fileData[1]); + for (i = 0; i < size; i++) + { + set_strtab_entry (lpST, i, (char *)stringptr, fileData[2+i]); + stringptr += fileData[2+i]; + } + } + HFree (result); + result = lpST; + } + + return result; +} + diff --git a/src/libs/strings/sfileins.c b/src/libs/strings/sfileins.c new file mode 100644 index 0000000..6ff4422 --- /dev/null +++ b/src/libs/strings/sfileins.c @@ -0,0 +1,50 @@ +//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 "strintrn.h" +#include "libs/uio.h" +#include "libs/reslib.h" + + +STRING_TABLE +LoadStringTableFile (uio_DirHandle *dir, const char *fileName) +{ + uio_Stream *fp; + + // FIXME: this theoretically needs a mechanism to prevent races + if (_cur_resfile_name) + // something else is loading resources atm + return 0; + + fp = res_OpenResFile (dir, fileName, "rb"); + if (fp) + { + STRING_TABLE data; + + _cur_resfile_name = fileName; + data = (STRING_TABLE) _GetStringData (fp, LengthResFile (fp)); + _cur_resfile_name = 0; + res_CloseResFile (fp); + + return data; + } + + return (0); +} + diff --git a/src/libs/strings/sresins.c b/src/libs/strings/sresins.c new file mode 100644 index 0000000..af2de79 --- /dev/null +++ b/src/libs/strings/sresins.c @@ -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. + */ + +#include "strintrn.h" + +static void +GetStringTableFileData (const char *pathname, RESOURCE_DATA *resdata) +{ + resdata->ptr = LoadResourceFromPath (pathname, _GetStringData); +} + +static void +GetBinaryTableFileData (const char *pathname, RESOURCE_DATA *resdata) +{ + resdata->ptr = LoadResourceFromPath (pathname, _GetBinaryTableData); +} + +BOOLEAN +InstallStringTableResType (void) +{ + InstallResTypeVectors ("STRTAB", GetStringTableFileData, FreeResourceData, NULL); + InstallResTypeVectors ("BINTAB", GetBinaryTableFileData, FreeResourceData, NULL); + InstallResTypeVectors ("CONVERSATION", _GetConversationData, FreeResourceData, NULL); + return TRUE; +} + +STRING_TABLE +LoadStringTableInstance (RESOURCE res) +{ + void *data; + + data = res_GetResource (res); + if (data) + { + res_DetachResource (res); + } + + return (STRING_TABLE)data; +} + diff --git a/src/libs/strings/stringhashtable.c b/src/libs/strings/stringhashtable.c new file mode 100644 index 0000000..ac4b4f4 --- /dev/null +++ b/src/libs/strings/stringhashtable.c @@ -0,0 +1,67 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 HASHTABLE_INTERNAL +#include "stringhashtable.h" +#include "types.h" +#include "libs/misc.h" + // For unconst() +#include "libs/uio/uioport.h" + +static inline uio_uint32 StringHashTable_hash( + StringHashTable_HashTable *hashTable, const char *string); +static inline uio_bool StringHashTable_equal( + StringHashTable_HashTable *hashTable, + const char *key1, const char *key2); +static inline char *StringHashTable_copy( + StringHashTable_HashTable *hashTable, const char *key); + +#include "libs/uio/hashtable.c" + + +static inline uio_uint32 +StringHashTable_hash(StringHashTable_HashTable *hashTable, const char *key) { + uio_uint32 hash; + + (void) hashTable; + // Rotating hash, variation of something on the web which + // wasn't original itself. + hash = 0; + // Hash was on that web page initialised as the length, + // but that isn't known at this time. + while (*key != '\0') { + hash = (hash << 4) ^ (hash >> 28) ^ *key; + key++; + } + return hash ^ (hash >> 10) ^ (hash >> 20); +} + +static inline uio_bool +StringHashTable_equal(StringHashTable_HashTable *hashTable, + const char *key1, const char *key2) { + (void) hashTable; + return strcmp(key1, key2) == 0; +} + +static inline char * +StringHashTable_copy(StringHashTable_HashTable *hashTable, + const char *key) { + (void) hashTable; + return unconst(key); +} + diff --git a/src/libs/strings/stringhashtable.h b/src/libs/strings/stringhashtable.h new file mode 100644 index 0000000..36f9e47 --- /dev/null +++ b/src/libs/strings/stringhashtable.h @@ -0,0 +1,43 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 _STRINGHASHTABLE_H +#define _STRINGHASHTABLE_H + +// HashTable from 'char *' to STRING. +// We don't actually copy the index, which means that the caller is +// responsible for keeping them unchanged during the time that it is used in +// the hash table. + +#include "libs/strlib.h" + +#define HASHTABLE_(identifier) StringHashTable ## _ ## identifier +typedef char HASHTABLE_(Key); +typedef STRING_TABLE_ENTRY_DESC HASHTABLE_(Value); +#define StringHashTable_HASH StringHashTable_hash +#define StringHashTable_EQUAL StringHashTable_equal +#define StringHashTable_COPY StringHashTable_copy +#define StringHashTable_FREEKEY(hashTable, key) \ + ((void) (hashTable), (void) (key)) +#define StringHashTable_FREEVALUE(hashTable, value) \ + ((void) (hashTable), (void) (value)) + +#include "libs/uio/hashtable.h" + + +#endif /* _STRINGHASHTABLE_H */ diff --git a/src/libs/strings/strings.c b/src/libs/strings/strings.c new file mode 100644 index 0000000..7f8d5e4 --- /dev/null +++ b/src/libs/strings/strings.c @@ -0,0 +1,347 @@ +//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 "strintrn.h" +#include "libs/memlib.h" + +STRING_TABLE +AllocStringTable (int num_entries, int flags) +{ + STRING_TABLE strtab = HMalloc (sizeof (STRING_TABLE_DESC)); + int i, multiplier = 1; + + if (flags & HAS_NAMEINDEX) + { + multiplier++; + } + if (flags & HAS_SOUND_CLIPS) + { + multiplier++; + } + if (flags & HAS_TIMESTAMP) + { + multiplier++; + } + strtab->flags = flags; + strtab->size = num_entries; + num_entries *= multiplier; + strtab->strings = HMalloc (sizeof (STRING_TABLE_ENTRY_DESC) * num_entries); + for (i = 0; i < num_entries; i++) + { + strtab->strings[i].data = NULL; + strtab->strings[i].length = 0; + strtab->strings[i].parent = strtab; + strtab->strings[i].index = i; + } + strtab->nameIndex = NULL; + return strtab; +} + +void +FreeStringTable (STRING_TABLE strtab) +{ + int i, multiplier = 1; + + if (strtab == NULL) + { + return; + } + + if (strtab->flags & HAS_SOUND_CLIPS) + { + multiplier++; + } + if (strtab->flags & HAS_TIMESTAMP) + { + multiplier++; + } + + for (i = 0; i < strtab->size * multiplier; i++) + { + if (strtab->strings[i].data != NULL) + { + HFree (strtab->strings[i].data); + } + } + + HFree (strtab->strings); + HFree (strtab); +} + +BOOLEAN +DestroyStringTable (STRING_TABLE StringTable) +{ + FreeStringTable (StringTable); + return TRUE; +} + +STRING +CaptureStringTable (STRING_TABLE StringTable) +{ + if ((StringTable != 0) && (StringTable->size > 0)) + { + return StringTable->strings; + } + + return NULL; +} + +STRING_TABLE +ReleaseStringTable (STRING String) +{ + STRING_TABLE StringTable; + + StringTable = GetStringTable (String); + + return (StringTable); +} + +STRING_TABLE +GetStringTable (STRING String) +{ + if (String && String->parent) + { + return String->parent; + } + return NULL; +} + +COUNT +GetStringTableCount (STRING String) +{ + if (String && String->parent) + { + return String->parent->size; + } + return 0; +} + +COUNT +GetStringTableIndex (STRING String) +{ + if (String) + { + return String->index; + } + return 0; +} + +STRING +SetAbsStringTableIndex (STRING String, COUNT StringTableIndex) +{ + STRING_TABLE StringTablePtr; + + if (!String) + return NULL; + + StringTablePtr = String->parent; + + if (StringTablePtr == NULL) + { + String = NULL; + } + else + { + StringTableIndex = StringTableIndex % StringTablePtr->size; + String = &StringTablePtr->strings[StringTableIndex]; + } + + return (String); +} + +STRING +SetRelStringTableIndex (STRING String, SIZE StringTableOffs) +{ + STRING_TABLE StringTablePtr; + + if (!String) + return NULL; + + StringTablePtr = String->parent; + + if (StringTablePtr == NULL) + { + String = NULL; + } + else + { + COUNT StringTableIndex; + + while (StringTableOffs < 0) + StringTableOffs += StringTablePtr->size; + StringTableIndex = (String->index + StringTableOffs) + % StringTablePtr->size; + + String = &StringTablePtr->strings[StringTableIndex]; + } + + return (String); +} + +COUNT +GetStringLength (STRING String) +{ + if (String == NULL) + { + return 0; + } + return utf8StringCountN(String->data, String->data + String->length); +} + +COUNT +GetStringLengthBin (STRING String) +{ + if (String == NULL) + { + return 0; + } + return String->length; +} + +STRINGPTR +GetStringName (STRING String) +{ + STRING_TABLE StringTablePtr; + COUNT StringIndex; + + if (String == NULL) + { + return NULL; + } + + StringTablePtr = String->parent; + if (StringTablePtr == NULL) + { + return NULL; + } + + StringIndex = String->index; + + if (!(StringTablePtr->flags & HAS_NAMEINDEX)) + { + return NULL; + } + StringIndex += StringTablePtr->size; + + String = &StringTablePtr->strings[StringIndex]; + if (String->length == 0) + { + return NULL; + } + + return String->data; +} + +STRINGPTR +GetStringSoundClip (STRING String) +{ + STRING_TABLE StringTablePtr; + COUNT StringIndex; + + if (String == NULL) + { + return NULL; + } + + StringTablePtr = String->parent; + if (StringTablePtr == NULL) + { + return NULL; + } + + StringIndex = String->index; + if (!(StringTablePtr->flags & HAS_SOUND_CLIPS)) + { + return NULL; + } + StringIndex += StringTablePtr->size; + + if (StringTablePtr->flags & HAS_NAMEINDEX) + { + StringIndex += StringTablePtr->size; + } + + String = &StringTablePtr->strings[StringIndex]; + if (String->length == 0) + { + return NULL; + } + + return String->data; +} + +STRINGPTR +GetStringTimeStamp (STRING String) +{ + STRING_TABLE StringTablePtr; + COUNT StringIndex; + + if (String == NULL) + { + return NULL; + } + + StringTablePtr = String->parent; + if (StringTablePtr == NULL) + { + return NULL; + } + + StringIndex = String->index; + if (!(StringTablePtr->flags & HAS_TIMESTAMP)) + { + return NULL; + } + StringIndex += StringTablePtr->size; + + if (StringTablePtr->flags & HAS_NAMEINDEX) + { + StringIndex += StringTablePtr->size; + } + + if (StringTablePtr->flags & HAS_SOUND_CLIPS) + { + StringIndex += StringTablePtr->size; + } + + String = &StringTablePtr->strings[StringIndex]; + if (String->length == 0) + { + return NULL; + } + + return String->data; +} + +STRINGPTR +GetStringAddress (STRING String) +{ + if (String == NULL) + { + return NULL; + } + return String->data; +} + +STRING +GetStringByName (STRING_TABLE StringTable, const char *index) +{ + return (STRING) StringHashTable_find (StringTable->nameIndex, index); +} + + diff --git a/src/libs/strings/strintrn.h b/src/libs/strings/strintrn.h new file mode 100644 index 0000000..0c41fb0 --- /dev/null +++ b/src/libs/strings/strintrn.h @@ -0,0 +1,56 @@ +//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 LIBS_STRINGS_STRINTRN_H_ +#define LIBS_STRINGS_STRINTRN_H_ + +#include +#include +#include "libs/strlib.h" +#include "libs/reslib.h" +#include "stringhashtable.h" + +struct string_table_entry +{ + STRINGPTR data; + int length; /* Internal NULs are allowed */ + int index; + struct string_table *parent; +}; + +struct string_table +{ + unsigned short flags; + int size; + STRING_TABLE_ENTRY_DESC *strings; + StringHashTable_HashTable *nameIndex; +}; + +#define HAS_SOUND_CLIPS (1 << 0) +#define HAS_TIMESTAMP (1 << 1) +#define HAS_NAMEINDEX (1 << 2) + +STRING_TABLE AllocStringTable (int num_entries, int flags); +void FreeStringTable (STRING_TABLE strtab); + +void *_GetStringData (uio_Stream *fp, DWORD length); +void *_GetBinaryTableData (uio_Stream *fp, DWORD length); +void _GetConversationData (const char *path, RESOURCE_DATA *resdata); + +#endif /* LIBS_STRINGS_STRINTRN_H_ */ + diff --git a/src/libs/strings/unicode.c b/src/libs/strings/unicode.c new file mode 100644 index 0000000..1750507 --- /dev/null +++ b/src/libs/strings/unicode.c @@ -0,0 +1,541 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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" + +#define UNICODE_INTERNAL +#include "libs/unicode.h" + +#include +#include +#include +#include +#include "libs/log.h" +#include "libs/misc.h" + + +// Resynchronise (skip everything starting with 0x10xxxxxx): +static inline void +resyncUTF8(const unsigned char **ptr) { + while ((**ptr & 0xc0) == 0x80) + (*ptr)++; +} + +// Get one character from a UTF-8 encoded string. +// *ptr will point to the start of the next character. +// Returns 0 if the encoding is bad. This can be distinguished from the +// '\0' character by checking whether **ptr == '\0' before calling this +// function. +UniChar +getCharFromString(const unsigned char **ptr) { + UniChar result; + + if (**ptr < 0x80) { + // 0xxxxxxx, regular ASCII + result = **ptr; + (*ptr)++; + + return result; + } + + if ((**ptr & 0xe0) == 0xc0) { + // 110xxxxx; 10xxxxxx must follow + // Value between 0x00000080 and 0x000007ff (inclusive) + result = **ptr & 0x1f; + (*ptr)++; + + if ((**ptr & 0xc0) != 0x80) + goto err; + result = (result << 6) | ((**ptr) & 0x3f); + (*ptr)++; + + if (result < 0x00000080) { + // invalid encoding - must reject + goto err; + } + return result; + } + + if ((**ptr & 0xf0) == 0xe0) { + // 1110xxxx; 10xxxxxx 10xxxxxx must follow + // Value between 0x00000800 and 0x0000ffff (inclusive) + result = **ptr & 0x0f; + (*ptr)++; + + if ((**ptr & 0xc0) != 0x80) + goto err; + result = (result << 6) | ((**ptr) & 0x3f); + (*ptr)++; + + if ((**ptr & 0xc0) != 0x80) + goto err; + result = (result << 6) | ((**ptr) & 0x3f); + (*ptr)++; + + if (result < 0x00000800) { + // invalid encoding - must reject + goto err; + } + return result; + } + + if ((**ptr & 0xf8) == 0xf0) { + // 11110xxx; 10xxxxxx 10xxxxxx 10xxxxxx must follow + // Value between 0x00010000 and 0x0010ffff (inclusive) + result = **ptr & 0x07; + (*ptr)++; + + if ((**ptr & 0xc0) != 0x80) + goto err; + result = (result << 6) | ((**ptr) & 0x3f); + (*ptr)++; + + if ((**ptr & 0xc0) != 0x80) + goto err; + result = (result << 6) | ((**ptr) & 0x3f); + (*ptr)++; + + if ((**ptr & 0xc0) != 0x80) + goto err; + result = (result << 6) | ((**ptr) & 0x3f); + (*ptr)++; + + if (result < 0x00010000) { + // invalid encoding - must reject + goto err; + } + return result; + } + +err: + log_add(log_Warning, "Warning: Invalid UTF8 sequence."); + + // Resynchronise (skip everything starting with 0x10xxxxxx): + resyncUTF8(ptr); + + return 0; +} + +UniChar +getCharFromStringN(const unsigned char **ptr, const unsigned char *end) { + size_t numBytes; + + if (*ptr == end) + goto err; + + if (**ptr < 0x80) { + numBytes = 1; + } else if ((**ptr & 0xe0) == 0xc0) { + numBytes = 2; + } else if ((**ptr & 0xf0) == 0xe0) { + numBytes = 3; + } else if ((**ptr & 0xf8) == 0xf0) { + numBytes = 4; + } else + goto err; + + if (*ptr + numBytes > end) + goto err; + + return getCharFromString(ptr); + +err: + *ptr = end; + return 0; +} + +// Get one line from a string. +// A line is terminated with either CRLF (DOS/Windows), +// LF (Unix, MacOS X), or CR (old MacOS). +// The end of the string is reached when **startNext == '\0'. +// NULL is returned if the string is not valid UTF8. In this case +// *end points to the first invalid character (or the character before if +// it was a LF), and *startNext to the start of the next (possibly invalid +// too) character. +unsigned char * +getLineFromString(const unsigned char *start, const unsigned char **end, + const unsigned char **startNext) { + const unsigned char *ptr = start; + const unsigned char *lastPtr; + UniChar ch; + + // Search for the first newline. + for (;;) { + if (*ptr == '\0') { + *end = ptr; + *startNext = ptr; + return (unsigned char *) unconst(start); + } + lastPtr = ptr; + ch = getCharFromString(&ptr); + if (ch == '\0') { + // Bad string + *end = lastPtr; + *startNext = ptr; + return NULL; + } + if (ch == '\n') { + *end = lastPtr; + if (*ptr == '\0'){ + // LF at the end of the string. + *startNext = ptr; + return (unsigned char *) unconst(start); + } + ch = getCharFromString(&ptr); + if (ch == '\0') { + // Bad string + return NULL; + } + if (ch == '\r') { + // LFCR + *startNext = ptr; + } else { + // LF + *startNext = *end; + } + return (unsigned char *) unconst(start); + } else if (ch == '\r') { + *end = lastPtr; + *startNext = ptr; + return (unsigned char *) unconst(start); + } // else: a normal character + } +} + +size_t +utf8StringCount(const unsigned char *start) { + size_t count = 0; + UniChar ch; + + for (;;) { + ch = getCharFromString(&start); + if (ch == '\0') + return count; + count++; + } +} + +size_t +utf8StringCountN(const unsigned char *start, const unsigned char *end) { + size_t count = 0; + UniChar ch; + + for (;;) { + ch = getCharFromStringN(&start, end); + if (ch == '\0') + return count; + count++; + } +} + +// Locates a unicode character (ch) in a UTF-8 string (pStr) +// returns the char positions when found +// -1 when not found +int +utf8StringPos (const unsigned char *pStr, UniChar ch) +{ + int pos; + + for (pos = 0; *pStr != '\0'; ++pos) + { + if (getCharFromString (&pStr) == ch) + return pos; + } + + if (ch == '\0' && *pStr == '\0') + return pos; + + return -1; +} + +// Safe version of strcpy(), somewhat analogous to strncpy() +// except it guarantees a 0-term when size > 0 +// when size == 0, returns NULL +// BUG: this may result in the last character being only partially in the +// buffer +unsigned char * +utf8StringCopy (unsigned char *dst, size_t size, const unsigned char *src) +{ + if (size == 0) + return 0; + + strncpy ((char *) dst, (const char *) src, size); + dst[size - 1] = '\0'; + + return dst; +} + +// TODO: this is not implemented with respect to collating order +int +utf8StringCompare (const unsigned char *str1, const unsigned char *str2) +{ +#if 0 + // UniChar comparing version + UniChar ch1; + UniChar ch2; + + for (;;) + { + int cmp; + + ch1 = getCharFromString(&str1); + ch2 = getCharFromString(&str2); + if (ch1 == '\0' || ch2 == '\0') + break; + + cmp = utf8CompareChar (ch1, ch2); + if (cmp != 0) + return cmp; + } + + if (ch1 != '\0') + { + // ch2 == '\0' + // str2 ends, str1 continues + return 1; + } + + if (ch2 != '\0') + { + // ch1 == '\0' + // str1 ends, str2 continues + return -1; + } + + // ch1 == '\0' && ch2 == '\0'. + // Strings match completely. + return 0; +#else + // this will do for now + return strcmp ((const char *) str1, (const char *) str2); +#endif +} + +unsigned char * +skipUTF8Chars(const unsigned char *ptr, size_t num) { + UniChar ch; + const unsigned char *oldPtr; + + while (num--) { + oldPtr = ptr; + ch = getCharFromString(&ptr); + if (ch == '\0') + return (unsigned char *) unconst(oldPtr); + } + return (unsigned char *) unconst(ptr); +} + +// Decodes a UTF-8 string (start) into a unicode character string (wstr) +// returns number of chars decoded and stored, not counting 0-term +// any chars that do not fit are truncated +// wide string term 0 is always appended, unless the destination +// buffer is 0 chars long +size_t +getUniCharFromStringN(UniChar *wstr, size_t maxcount, + const unsigned char *start, const unsigned char *end) +{ + UniChar *next; + + if (maxcount == 0) + return 0; + + // always leave room for 0-term + --maxcount; + + for (next = wstr; maxcount > 0; ++next, --maxcount) + { + *next = getCharFromStringN(&start, end); + if (*next == 0) + break; + } + + *next = 0; // term + + return next - wstr; +} + +// See getStringFromWideN() for functionality +// the only difference is that the source string (start) length is +// calculated by searching for 0-term +size_t +getUniCharFromString(UniChar *wstr, size_t maxcount, + const unsigned char *start) +{ + UniChar *next; + + if (maxcount == 0) + return 0; + + // always leave room for 0-term + --maxcount; + + for (next = wstr; maxcount > 0; ++next, --maxcount) + { + *next = getCharFromString(&start); + if (*next == 0) + break; + } + + *next = 0; // term + + return next - wstr; +} + +// Encode one wide character into UTF-8 +// returns number of bytes used in the buffer, +// 0 : invalid or unsupported char +// <0 : negative of bytes needed if buffer too small +// string term '\0' is *not* appended or counted +int +getStringFromChar(unsigned char *ptr, size_t size, UniChar ch) +{ + int i; + static const struct range_def + { + UniChar lim; + int marker; + int mask; + } + ranges[] = + { + {0x0000007f, 0x00, 0x7f}, + {0x000007ff, 0xc0, 0x1f}, + {0x0000ffff, 0xe0, 0x0f}, + {0x001fffff, 0xf0, 0x07}, + {0x03ffffff, 0xf8, 0x03}, + {0x7fffffff, 0xfc, 0x01}, + {0x00000000, 0x00, 0x00} // term + }; + const struct range_def *def; + + // lookup the range + for (i = 0, def = ranges; ch > def->lim && def->mask != 0; ++i, ++def) + ; + if (def->mask == 0) + { // invalid or unsupported char + log_add(log_Warning, "Warning: Invalid or unsupported unicode " + "char (%lu)", (unsigned long) ch); + return 0; + } + + if ((size_t)i + 1 > size) + return -(i + 1); + + // unrolled for speed + switch (i) + { + case 5: ptr[5] = (ch & 0x3f) | 0x80; + ch >>= 6; + case 4: ptr[4] = (ch & 0x3f) | 0x80; + ch >>= 6; + case 3: ptr[3] = (ch & 0x3f) | 0x80; + ch >>= 6; + case 2: ptr[2] = (ch & 0x3f) | 0x80; + ch >>= 6; + case 1: ptr[1] = (ch & 0x3f) | 0x80; + ch >>= 6; + case 0: ptr[0] = (ch & def->mask) | def->marker; + } + + return i + 1; +} + +// Encode a wide char string (wstr) into a UTF-8 string (ptr) +// returns number of bytes used in the buffer (includes 0-term) +// any chars that do not fit are truncated +// string term '\0' is always appended, unless the destination +// buffer is 0 bytes long +size_t +getStringFromWideN(unsigned char *ptr, size_t size, + const UniChar *wstr, size_t count) +{ + unsigned char *next; + int used; + + if (size == 0) + return 0; + + // always leave room for 0-term + --size; + + for (next = ptr; size > 0 && count > 0; + size -= used, next += used, --count, ++wstr) + { + used = getStringFromChar(next, size, *wstr); + if (used < 0) + break; // not enough room + if (used == 0) + { // bad char? + *next = '?'; + used = 1; + } + } + + *next = '\0'; // term + + return next - ptr + 1; +} + +// See getStringFromWideN() for functionality +// the only difference is that the source string (wstr) length is +// calculated by searching for 0-term +size_t +getStringFromWide(unsigned char *ptr, size_t size, const UniChar *wstr) +{ + const UniChar *end; + + for (end = wstr; *end != 0; ++end) + ; + + return getStringFromWideN(ptr, size, wstr, (end - wstr)); +} + +int +UniChar_isGraph(UniChar ch) +{ // this is not technically sufficient, but close enough for us + // we'll consider all non-control (CO and C1) chars in 'graph' class + // except for the "Private Use Area" (0xE000 - 0xF8FF) + + // TODO: The private use area is really only glommed by OS X, + // and even there, not all of it. (Delete and Backspace both + // end up producing characters there -- see bug #942 for the + // gory details.) + return (ch > 0xa0 && (ch < 0xE000 || ch > 0xF8FF)) || + (ch > 0x20 && ch < 0x7f); +} + +int +UniChar_isPrint(UniChar ch) +{ // this is not technically sufficient, but close enough for us + // chars in 'print' class are 'graph' + 'space' classes + // the only space we currently have defined is 0x20 + return (ch == 0x20) || UniChar_isGraph(ch); +} + +UniChar +UniChar_toUpper(UniChar ch) +{ // this is a very basic Latin-1 implementation + // just to get things going + return (ch < 0x100) ? (UniChar) toupper((int) ch) : ch; +} + +UniChar +UniChar_toLower(UniChar ch) +{ // this is a very basic Latin-1 implementation + // just to get things going + return (ch < 0x100) ? (UniChar) tolower((int) ch) : ch; +} + diff --git a/src/libs/strlib.h b/src/libs/strlib.h new file mode 100644 index 0000000..c313f7f --- /dev/null +++ b/src/libs/strlib.h @@ -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. + */ + +#ifndef LIBS_STRLIB_H_ +#define LIBS_STRLIB_H_ + +#include "libs/compiler.h" +#include "port.h" +#include "libs/uio.h" +#include "libs/unicode.h" + +#include + +typedef struct string_table_entry STRING_TABLE_ENTRY_DESC; +typedef struct string_table STRING_TABLE_DESC; + +typedef STRING_TABLE_DESC *STRING_TABLE; +typedef STRING_TABLE_ENTRY_DESC *STRING; +typedef char *STRINGPTR; + +/* This has to go here because reslib requires the above typedefs. */ +#include "libs/reslib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +extern BOOLEAN InstallStringTableResType (void); +extern STRING_TABLE LoadStringTableInstance (RESOURCE res); +extern STRING_TABLE LoadStringTableFile (uio_DirHandle *dir, + const char *fileName); +extern BOOLEAN DestroyStringTable (STRING_TABLE StringTable); +extern STRING CaptureStringTable (STRING_TABLE StringTable); +extern STRING_TABLE ReleaseStringTable (STRING String); +extern STRING_TABLE GetStringTable (STRING String); +extern COUNT GetStringTableCount (STRING String); +extern COUNT GetStringTableIndex (STRING String); +extern STRING SetAbsStringTableIndex (STRING String, COUNT + StringTableIndex); +extern STRING SetRelStringTableIndex (STRING String, SIZE + StringTableOffs); +extern COUNT GetStringLength (STRING String); +extern COUNT GetStringLengthBin (STRING String); +extern STRINGPTR GetStringAddress (STRING String); +extern STRINGPTR GetStringName (STRING String); +extern STRINGPTR GetStringSoundClip (STRING String); +extern STRINGPTR GetStringTimeStamp (STRING String); +extern STRING GetStringByName (STRING_TABLE StringTable, const char *index); + +#define UNICHAR_DEGREE_SIGN 0x00b0 +#define STR_DEGREE_SIGN "\xC2\xB0" +#define UNICHAR_INFINITY_SIGN 0x221e +#define STR_INFINITY_SIGN "\xE2\x88\x9E" +#define UNICHAR_EARTH_SIGN 0x2641 +#define STR_EARTH_SIGN "\xE2\x99\x81" +#define UNICHAR_MIDDLE_DOT 0x00b7 +#define STR_MIDDLE_DOT "\xC2\xB7" +#define UNICHAR_BULLET 0x2022 +#define STR_BULLET "\xE2\x80\xA2" + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_STRLIB_H_ */ diff --git a/src/libs/task/Makeinfo b/src/libs/task/Makeinfo new file mode 100644 index 0000000..f780c46 --- /dev/null +++ b/src/libs/task/Makeinfo @@ -0,0 +1 @@ +uqm_CFILES="tasklib.c" diff --git a/src/libs/task/tasklib.c b/src/libs/task/tasklib.c new file mode 100644 index 0000000..81f77af --- /dev/null +++ b/src/libs/task/tasklib.c @@ -0,0 +1,139 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* By Michael Martin, 2002-09-21 + */ + +#include +#include +#include "libs/tasklib.h" +#include "libs/log.h" + +#define TASK_MAX 64 + +static struct taskstruct task_array[TASK_MAX]; + +Task +AssignTask (ThreadFunction task_func, SDWORD stackSize, const char *name) +{ + int i; + for (i = 0; i < TASK_MAX; ++i) + { + if (!Task_SetState (task_array+i, TASK_INUSE)) + { + // log_add (log_Debug, "Assigning Task #%i: %s", i+1, name); + Task_ClearState (task_array+i, ~TASK_INUSE); + task_array[i].name = name; + task_array[i].thread = CreateThread (task_func, task_array+i, + stackSize, name); + return task_array+i; + } + } + log_add (log_Error, "Task error! Task array exhausted. Check for thread leaks."); + return NULL; +} + +void +FinishTask (Task task) +{ + // log_add (log_Debug, "Releasing Task: %s", task->name); + task->thread = 0; + if (!Task_ClearState (task, TASK_INUSE)) + { + log_add (log_Debug, "Task error! Attempted to FinishTask '%s'... " + "but it was already done!", task->name); + } +} + +/* This could probably be done better with a condition variable of some kind. */ +void +ConcludeTask (Task task) +{ + Thread old = task->thread; + // log_add (log_Debug, "Awaiting conclusion of %s", task->name); + if (old) + { + Task_SetState (task, TASK_EXIT); + while (task->thread == old) + { + TaskSwitch (); + } + } +} + +DWORD +Task_SetState (Task task, DWORD state_mask) +{ + DWORD old_state; + LockMutex (task->state_mutex); + old_state = task->state; + task->state |= state_mask; + UnlockMutex (task->state_mutex); + old_state &= state_mask; + return old_state; +} + +DWORD +Task_ClearState (Task task, DWORD state_mask) +{ + DWORD old_state; + LockMutex (task->state_mutex); + old_state = task->state; + task->state &= ~state_mask; + UnlockMutex (task->state_mutex); + old_state &= state_mask; + return old_state; +} + +DWORD +Task_ToggleState (Task task, DWORD state_mask) +{ + DWORD old_state; + LockMutex (task->state_mutex); + old_state = task->state; + task->state ^= state_mask; + UnlockMutex (task->state_mutex); + old_state &= state_mask; + return old_state; +} + +DWORD +Task_ReadState (Task task, DWORD state_mask) +{ + return task->state & state_mask; +} + +void +InitTaskSystem (void) +{ + int i; + for (i = 0; i < TASK_MAX; ++i) + { + task_array[i].state_mutex = CreateMutex ("task manager lock", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_RESOURCE); + } +} + +void +CleanupTaskSystem (void) +{ + int i; + for (i = 0; i < TASK_MAX; ++i) + { + DestroyMutex (task_array[i].state_mutex); + task_array[i].state_mutex = 0; + } +} + diff --git a/src/libs/tasklib.h b/src/libs/tasklib.h new file mode 100644 index 0000000..41544db --- /dev/null +++ b/src/libs/tasklib.h @@ -0,0 +1,62 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* By Michael Martin, 2002-09-21 + */ + +/* The task libraries are a set of facilities for controlling synchronous + * processes. They are built on top of threads, but add the ability to + * modify a "state" variable to pass messages back and forth. */ + +#ifndef LIBS_TASKLIB_H_ +#define LIBS_TASKLIB_H_ + +#include "libs/threadlib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/* Bitmasks for setting task state. */ +#define TASK_INUSE 1 +#define TASK_EXIT 2 + +struct taskstruct { + Mutex state_mutex; + volatile DWORD state; // Protected by state_mutex + const char *name; + volatile Thread thread; +}; + +typedef struct taskstruct *Task; + +extern void InitTaskSystem (void); +extern void CleanupTaskSystem (void); + +extern Task AssignTask (ThreadFunction task_func, SDWORD Stacksize, const char *name); +extern DWORD Task_SetState (Task task, DWORD state_mask); +extern DWORD Task_ClearState (Task task, DWORD state_mask); +extern DWORD Task_ToggleState (Task task, DWORD state_mask); +extern DWORD Task_ReadState (Task task, DWORD state_mask); +extern void FinishTask (Task task); +extern void ConcludeTask (Task task); + +#if defined(__cplusplus) +} +#endif + +#endif + diff --git a/src/libs/threadlib.h b/src/libs/threadlib.h new file mode 100644 index 0000000..6586d7f --- /dev/null +++ b/src/libs/threadlib.h @@ -0,0 +1,186 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* By Serge van den Boom, 2002-09-12 + */ + +#ifndef LIBS_THREADLIB_H_ +#define LIBS_THREADLIB_H_ + +#define NAMED_SYNCHRO /* Should synchronizable objects have names? */ +#define TRACK_CONTENTION /* Should we report when a thread sleeps on synchronize? */ + +/* TRACK_CONTENTION implies NAMED_SYNCHRO. */ +#ifdef TRACK_CONTENTION +# ifndef NAMED_SYNCHRO +# define NAMED_SYNCHRO +# endif +#endif /* TRACK_CONTENTION */ + +#ifdef DEBUG +# ifndef DEBUG_THREADS +# define DEBUG_THREADS +# endif +#endif /* DEBUG */ + +#ifdef DEBUG_THREADS +//# ifndef PROFILE_THREADS +//# define PROFILE_THREADS +//# endif +#endif /* DEBUG_THREADS */ + +#include +#include "libs/timelib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined (PROFILE_THREADS) || defined (DEBUG_THREADS) +#define THREAD_NAMES +#endif + +void InitThreadSystem (void); +void UnInitThreadSystem (void); + +typedef int (*ThreadFunction) (void *); + +typedef void *Thread; +typedef void *Mutex; +typedef void *Semaphore; +typedef void *RecursiveMutex; +typedef void *CondVar; + +/* Local data associated with each thread */ +typedef struct _threadLocal { + Semaphore flushSem; +} ThreadLocal; + +/* The classes of synchronization objects */ + +enum +{ + SYNC_CLASS_TOPLEVEL = (1 << 0), /* Exposed to the game logic */ + SYNC_CLASS_AUDIO = (1 << 1), /* Involves the audio system */ + SYNC_CLASS_VIDEO = (1 << 2), /* Involves the video system. Very noisy because of FlushGraphics(). */ + SYNC_CLASS_RESOURCE = (1 << 3) /* Involves system resources (_MemoryLock) */ +}; + +/* Note. NEVER call CreateThread from the main thread, or deadlocks + are guaranteed. Use StartThread instead (which doesn't wait around + for the main thread to actually create the thread and return + it). */ + +#ifdef NAMED_SYNCHRO +/* Logical OR of all classes we want to track. */ +#define TRACK_CONTENTION_CLASSES (SYNC_CLASS_TOPLEVEL) + +/* Prototypes with the "name" field */ + +Thread CreateThread_Core (ThreadFunction func, void *data, SDWORD stackSize, const char *name); +void StartThread_Core (ThreadFunction func, void *data, SDWORD stackSize, const char *name); +Semaphore CreateSemaphore_Core (DWORD initial, const char *name, DWORD syncClass); +Mutex CreateMutex_Core (const char *name, DWORD syncClass); +RecursiveMutex CreateRecursiveMutex_Core (const char *name, DWORD syncClass); +CondVar CreateCondVar_Core (const char *name, DWORD syncClass); + +/* Preprocessor directives to forward to the appropriate routines */ + +#define CreateThread(func, data, stackSize, name) \ + CreateThread_Core ((func), (data), (stackSize), (name)) +#define StartThread(func, data, stackSize, name) \ + StartThread_Core ((func), (data), (stackSize), (name)) +#define CreateSemaphore(initial, name, syncClass) \ + CreateSemaphore_Core ((initial), (name), (syncClass)) +#define CreateMutex(name, syncClass) \ + CreateMutex_Core ((name), (syncClass)) +#define CreateRecursiveMutex(name, syncClass) \ + CreateRecursiveMutex_Core((name), (syncClass)) +#define CreateCondVar(name, syncClass) \ + CreateCondVar_Core ((name), (syncClass)) + +#else + +/* Prototypes without the "name" field. */ +Thread CreateThread_Core (ThreadFunction func, void *data, SDWORD stackSize); +void StartThread_Core (ThreadFunction func, void *data, SDWORD stackSize); +Semaphore CreateSemaphore_Core (DWORD initial); +Mutex CreateMutex_Core (void); +RecursiveMutex CreateRecursiveMutex_Core (void); +CondVar CreateCondVar_Core (void); + + +/* Preprocessor directives to forward to the appropriate routines. + The "name" field is stripped away in preprocessing. */ + +#define CreateThread(func, data, stackSize, name) \ + CreateThread_Core ((func), (data), (stackSize)) +#define StartThread(func, data, stackSize, name) \ + StartThread_Core ((func), (data), (stackSize)) +#define CreateSemaphore(initial, name, syncClass) \ + CreateSemaphore_Core ((initial)) +#define CreateMutex(name, syncClass) \ + CreateMutex_Core () +#define CreateRecursiveMutex(name, syncClass) \ + CreateRecursiveMutex_Core() +#define CreateCondVar(name, syncClass) \ + CreateCondVar_Core () + +#endif + +ThreadLocal *CreateThreadLocal (void); +void DestroyThreadLocal (ThreadLocal *tl); +ThreadLocal *GetMyThreadLocal (void); + +void HibernateThread (TimePeriod timePeriod); +void HibernateThreadUntil (TimeCount wakeTime); +void SleepThread (TimePeriod timePeriod); +void SleepThreadUntil (TimeCount wakeTime); +void DestroyThread (Thread); +void TaskSwitch (void); +void WaitThread (Thread thread, int *status); + +void FinishThread (Thread); +void ProcessThreadLifecycles (void); + +#ifdef PROFILE_THREADS +void PrintThreadsStats (void); +#endif /* PROFILE_THREADS */ + + +void DestroySemaphore (Semaphore sem); +void SetSemaphore (Semaphore sem); +void ClearSemaphore (Semaphore sem); + +void DestroyMutex (Mutex sem); +void LockMutex (Mutex sem); +void UnlockMutex (Mutex sem); + +void DestroyRecursiveMutex (RecursiveMutex m); +void LockRecursiveMutex (RecursiveMutex m); +void UnlockRecursiveMutex (RecursiveMutex m); +int GetRecursiveMutexDepth (RecursiveMutex m); + +void DestroyCondVar (CondVar); +void WaitCondVar (CondVar); +void SignalCondVar (CondVar); +void BroadcastCondVar (CondVar); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_THREADLIB_H_ */ diff --git a/src/libs/threads/Makeinfo b/src/libs/threads/Makeinfo new file mode 100644 index 0000000..57f56a8 --- /dev/null +++ b/src/libs/threads/Makeinfo @@ -0,0 +1,11 @@ +case "$uqm_THREADLIB" in + SDL) + uqm_SUBDIRS="sdl" + ;; + PTHREAD) + uqm_SUBDIRS="pthread" + ;; +esac + +uqm_CFILES="thrcommon.c" +uqm_HFILES="thrcommon.h" diff --git a/src/libs/threads/pthread/Makeinfo b/src/libs/threads/pthread/Makeinfo new file mode 100644 index 0000000..251db7b --- /dev/null +++ b/src/libs/threads/pthread/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="posixthreads.c" +uqm_HFILES="posixthreads.h" diff --git a/src/libs/threads/pthread/posixthreads.c b/src/libs/threads/pthread/posixthreads.c new file mode 100644 index 0000000..4f0d2e8 --- /dev/null +++ b/src/libs/threads/pthread/posixthreads.c @@ -0,0 +1,672 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include "libs/misc.h" +#include "libs/memlib.h" +#include "posixthreads.h" +#include +#include + +#include + +#include "libs/log/uqmlog.h" + +typedef struct _thread { + pthread_t native; +#ifdef NAMED_SYNCHRO + const char *name; +#endif + ThreadLocal *localData; + struct _thread *next; +} *TrueThread; + +static volatile TrueThread threadQueue = NULL; +static pthread_mutex_t threadQueueMutex; + +struct ThreadStartInfo +{ + ThreadFunction func; + void *data; + sem_t sem; + TrueThread thread; +}; + +void +InitThreadSystem_PT (void) +{ + pthread_mutex_init (&threadQueueMutex, NULL); +} + +void +UnInitThreadSystem_PT (void) +{ + pthread_mutex_destroy (&threadQueueMutex); +} + +static void +QueueThread (TrueThread thread) +{ + pthread_mutex_lock (&threadQueueMutex); + thread->next = threadQueue; + threadQueue = thread; + pthread_mutex_unlock (&threadQueueMutex); +} + +static void +UnQueueThread (TrueThread thread) +{ + volatile TrueThread *ptr; + + pthread_mutex_lock (&threadQueueMutex); + ptr = &threadQueue; + while (*ptr != thread) + { +#ifdef DEBUG_THREADS + if (*ptr == NULL) + { + // Should not happen. + log_add (log_Debug, "Error: Trying to remove non-present thread " + "from thread queue."); + fflush (stderr); + explode (); + } +#endif /* DEBUG_THREADS */ + ptr = &(*ptr)->next; + } + *ptr = (*ptr)->next; + pthread_mutex_unlock (&threadQueueMutex); +} + +static TrueThread +FindThreadInfo (pthread_t threadID) +{ + TrueThread ptr; + + pthread_mutex_lock (&threadQueueMutex); + ptr = threadQueue; + while (ptr) + { + if (ptr->native == threadID) + { + pthread_mutex_unlock (&threadQueueMutex); + return ptr; + } + ptr = ptr->next; + } + pthread_mutex_unlock (&threadQueueMutex); + return NULL; +} + +#ifdef NAMED_SYNCHRO +static const char * +MyThreadName (void) +{ + TrueThread t = FindThreadInfo (pthread_self()); + return t ? t->name : "Unknown (probably renderer)"; +} +#endif + +static void * +ThreadHelper (void *startInfo) { + ThreadFunction func; + void *data; + sem_t *sem; + TrueThread thread; + int result; + + //log_add (log_Debug, "ThreadHelper()"); + + func = ((struct ThreadStartInfo *) startInfo)->func; + data = ((struct ThreadStartInfo *) startInfo)->data; + sem = &((struct ThreadStartInfo *) startInfo)->sem; + + // Wait until the Thread structure is available. + if (sem_wait (sem)) + { + log_add(log_Fatal, "ThreadHelper sem_wait fail"); + exit(EXIT_FAILURE); + } + if (sem_destroy (sem)) + { + log_add(log_Fatal, "ThreadHelper sem_destroy fail"); + exit(EXIT_FAILURE); + } + + thread = ((struct ThreadStartInfo *) startInfo)->thread; + HFree (startInfo); + + result = (*func) (data); + +#ifdef DEBUG_THREADS + log_add (log_Debug, "Thread '%s' done (returned %d).", + thread->name, result); + fflush (stderr); +#endif + + UnQueueThread (thread); + DestroyThreadLocal (thread->localData); + FinishThread (thread); + /* Destroying the thread is the responsibility of ProcessThreadLifecycles() */ + return (void*)result; +} + +void +DestroyThread_PT (Thread t) +{ + HFree (t); +} + +Thread +CreateThread_PT (ThreadFunction func, void *data, SDWORD stackSize +#ifdef NAMED_SYNCHRO + , const char *name +#endif + ) +{ + TrueThread thread; + struct ThreadStartInfo *startInfo; + pthread_attr_t attr; + + + //log_add (log_Debug, "CreateThread_PT '%s'", name); + + thread = (struct _thread *) HMalloc (sizeof *thread); +#ifdef NAMED_SYNCHRO + thread->name = name; +#endif + + thread->localData = CreateThreadLocal (); + + startInfo = (struct ThreadStartInfo *) HMalloc (sizeof (*startInfo)); + startInfo->func = func; + startInfo->data = data; + if (sem_init(&startInfo->sem, 0, 0) < 0) + { + log_add (log_Fatal, "createthread seminit fail"); + exit(EXIT_FAILURE); + } + startInfo->thread = thread; + + pthread_attr_init(&attr); + if (pthread_attr_setstacksize(&attr, 75000)) + { + log_add (log_Debug, "pthread stacksize fail"); + } + if (pthread_create(&thread->native, &attr, ThreadHelper, (void *)startInfo)) + { + log_add (log_Debug, "pthread create fail"); + DestroyThreadLocal (thread->localData); + HFree (startInfo); + HFree (thread); + return NULL; + } + // The responsibility to free 'startInfo' and 'thread' is now by the new + // thread. + + QueueThread (thread); + +#ifdef DEBUG_THREADS +//#if 0 + log_add (log_Debug, "Thread '%s' created.", thread->name); + fflush (stderr); +//#endif +#endif + + // Signal to the new thread that the thread structure is ready + // and it can begin to use it. + if (sem_post (&startInfo->sem)) + { + log_add(log_Fatal, "CreateThread sem_post fail"); + exit(EXIT_FAILURE); + } + + (void) stackSize; /* Satisfying compiler (unused parameter) */ + return thread; +} + +void +SleepThread_PT (TimeCount sleepTime) +{ + usleep (sleepTime * 1000000 / ONE_SECOND); +} + +void +SleepThreadUntil_PT (TimeCount wakeTime) { + TimeCount now; + + now = GetTimeCounter (); + if (wakeTime <= now) + TaskSwitch_PT (); + else + usleep ((wakeTime - now) * 1000000 / ONE_SECOND); +} + +void +TaskSwitch_PT (void) { + usleep (1000); +} + +void +WaitThread_PT (Thread thread, int *status) { + //log_add(log_Debug, "WaitThread_PT '%s', status %x", ((TrueThread)thread)->name, status); + //pthread_join(((TrueThread)thread)->native, status); + pthread_join(((TrueThread)thread)->native, NULL); + //log_add(log_Debug, "WaitThread_PT '%s' complete", ((TrueThread)thread)->name); +} + +ThreadLocal * +GetMyThreadLocal_PT (void) +{ + TrueThread t = FindThreadInfo (pthread_self()); + return t ? t->localData : NULL; +} + +/* These are the pthread implementations of the UQM synchronization objects. */ + +/* Mutexes. */ +/* TODO. The w_memlib uses Mutexes right now, so we can't use HMalloc + * or HFree. Once that goes, this needs to change. */ + +typedef struct _mutex { + pthread_mutex_t mutex; +#ifdef TRACK_CONTENTION + pthread_t owner; +#endif +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} Mut; + + +Mutex +#ifdef NAMED_SYNCHRO +CreateMutex_PT (const char *name, DWORD syncClass) +#else +CreateMutex_PT (void) +#endif +{ + Mut *mutex = malloc (sizeof (Mut)); + + if (mutex != NULL) + { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + if (pthread_mutex_init(&mutex->mutex, &attr)) + { +#ifdef NAMED_SYNCHRO + /* logging depends on Mutexes, so we have to use the + * non-threaded version instead */ + log_add_nothread (log_Fatal, "Could not initialize mutex '%s':" + "aborting.", name); +#else + log_add_nothread (log_Fatal, "Could not initialize mutex:" + "aborting."); +#endif + exit (EXIT_FAILURE); + } + pthread_mutexattr_destroy(&attr); + +#ifdef TRACK_CONTENTION + mutex->owner = 0; +#endif +#ifdef NAMED_SYNCHRO + mutex->name = name; + mutex->syncClass = syncClass; +#endif + } + + return mutex; +} + +void +DestroyMutex_PT (Mutex m) +{ + Mut *mutex = (Mut *)m; + //log_add_nothread(log_Debug, "Destroying mutex '%s'", mutex->name); + pthread_mutex_destroy (&mutex->mutex); + free (mutex); +} + +void +LockMutex_PT (Mutex m) +{ + Mut *mutex = (Mut *)m; +#ifdef TRACK_CONTENTION + /* This code isn't really quite right; race conditions between + * check and lock remain and can produce reports of contention + * where the thread never sleeps, or fail to report in + * situations where it does. If tracking with perfect + * accuracy becomes important, the TRACK_CONTENTION mutex will + * need to handle its own wake/sleep cycles with condition + * variables (check the history of this file for the + * CrossThreadMutex code). This almost-measure is being added + * because for the most part it should suffice. */ + if (mutex->owner && (mutex->syncClass & TRACK_CONTENTION_CLASSES)) + { /* logging depends on Mutexes, so we have to use the + * non-threaded version instead */ + log_add_nothread (log_Debug, "Thread '%s' blocking on mutex '%s'", + MyThreadName (), mutex->name); + } +#endif + + while (pthread_mutex_lock (&mutex->mutex) != 0) + { + //log_add_nothread (log_Debug, "Attempt to acquire mutex '%s' failretry", mutex->name); + TaskSwitch_PT (); + } +#ifdef TRACK_CONTENTION + mutex->owner = pthread_self(); +#endif +} + +void +UnlockMutex_PT (Mutex m) +{ + Mut *mutex = (Mut *)m; +#ifdef TRACK_CONTENTION + mutex->owner = 0; +#endif + while (pthread_mutex_unlock (&mutex->mutex) != 0) + { + TaskSwitch_PT (); + } +} + +/* Semaphores. */ + +typedef struct _sem { + sem_t sem; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} Sem; + +Semaphore +CreateSemaphore_PT (DWORD initial +#ifdef NAMED_SYNCHRO + , const char *name, DWORD syncClass +#endif + ) +{ + Sem *sem = (Sem *) HMalloc (sizeof (struct _sem)); +#ifdef NAMED_SYNCHRO + sem->name = name; + sem->syncClass = syncClass; +#endif + + //log_add (log_Debug, "Creating semaphore '%s'", sem->name); + + if (sem_init(&sem->sem, 0, initial) < 0) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize semaphore '%s':" + " aborting.", name); +#else + log_add (log_Fatal, "Could not initialize semaphore:" + " aborting."); +#endif + exit (EXIT_FAILURE); + } + //log_add (log_Debug, "Creating semaphore '%s' success", sem->name); + return sem; +} + +void +DestroySemaphore_PT (Semaphore s) +{ + Sem *sem = (Sem *)s; + //log_add (log_Debug, "Destroying semaphore '%s'", sem->name); + if (sem_destroy (&sem->sem)) + { + log_add (log_Debug, "Destroying semaphore '%s' failed", sem->name); + } + HFree (sem); +} + +void +SetSemaphore_PT (Semaphore s) +{ + Sem *sem = (Sem *)s; +#ifdef TRACK_CONTENTION + int contention = 0; + sem_getvalue(&sem->sem, &contention); + contention = !contention; + if (contention && (sem->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' blocking on semaphore '%s'", + MyThreadName (), sem->name); + } +#endif + //log_add (log_Debug, "Attempt to set semaphore '%s'", sem->name); + while (sem_wait (&sem->sem) == -1) + { + //log_add (log_Debug, "Attempt to set semaphore '%s' failretry", sem->name); + TaskSwitch_PT (); + } + //log_add (log_Debug, "Attempt to set semaphore '%s' success", sem->name); +#ifdef TRACK_CONTENTION + if (contention && (sem->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' awakens," + " released from semaphore '%s'", MyThreadName (), sem->name); + } +#endif +} + +void +ClearSemaphore_PT (Semaphore s) +{ + Sem *sem = (Sem *)s; + //log_add (log_Debug, "Attempt to clear semaphore '%s' %x", sem->name, sem); + while (sem_post (&sem->sem) == -1) + { + //log_add (log_Debug, "Attempt to clear semaphore %x failretry", sem); + TaskSwitch_PT (); + } + //log_add (log_Debug, "Attempt to clear semaphore %x success", sem); +} + +/* Recursive mutexes. Adapted from mixSDL code, which was adapted from + the original DCQ code. */ + +typedef struct _recm { + pthread_mutex_t mutex; + pthread_t thread_id; + unsigned int locks; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} RecM; + +RecursiveMutex +#ifdef NAMED_SYNCHRO +CreateRecursiveMutex_PT (const char *name, DWORD syncClass) +#else +CreateRecursiveMutex_PT (void) +#endif +{ + RecM *mtx = (RecM *) HMalloc (sizeof (struct _recm)); + + mtx->thread_id = 0; + if (pthread_mutex_init(&mtx->mutex, NULL)) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize recursive " + "mutex '%s': aborting.", name); +#else + log_add (log_Fatal, "Could not initialize recursive " + "mutex: aborting."); +#endif + exit (EXIT_FAILURE); + } +#ifdef NAMED_SYNCHRO + mtx->name = name; + mtx->syncClass = syncClass; +#endif + mtx->locks = 0; + return (RecursiveMutex) mtx; +} + +void +DestroyRecursiveMutex_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + pthread_mutex_destroy(&mtx->mutex); + HFree (mtx); +} + +void +LockRecursiveMutex_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + pthread_t thread_id = pthread_self(); + if (!mtx->locks || mtx->thread_id != thread_id) + { +#ifdef TRACK_CONTENTION + if (mtx->thread_id && (mtx->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' blocking on '%s'", + MyThreadName (), mtx->name); + } +#endif + while (pthread_mutex_lock (&mtx->mutex)) + TaskSwitch_PT (); + mtx->thread_id = thread_id; + } + mtx->locks++; +} + +void +UnlockRecursiveMutex_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + pthread_t thread_id = pthread_self(); + if (!mtx->locks || mtx->thread_id != thread_id) + { +#ifdef NAMED_SYNCHRO + log_add (log_Debug, "'%s' attempted to unlock %s when it " + "didn't hold it", MyThreadName (), mtx->name); +#endif + } + else + { + mtx->locks--; + if (!mtx->locks) + { + mtx->thread_id = 0; + pthread_mutex_unlock (&mtx->mutex); + } + } +} + +int +GetRecursiveMutexDepth_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + return mtx->locks; +} + +typedef struct _cond { + pthread_cond_t cond; + pthread_mutex_t mutex; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} cvar; + +CondVar +#ifdef NAMED_SYNCHRO +CreateCondVar_PT (const char *name, DWORD syncClass) +#else +CreateCondVar_PT (void) +#endif +{ + int err1, err2; + cvar *cv = (cvar *) HMalloc (sizeof (cvar)); + err1 = pthread_cond_init(&cv->cond, NULL); + err2 = pthread_mutex_init(&cv->mutex, NULL); + if (err1 || err2) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize condition variable '%s':" + " aborting.", name); +#else + log_add (log_Fatal, "Could not initialize condition variable:" + " aborting."); +#endif + exit (EXIT_FAILURE); + } +#ifdef NAMED_SYNCHRO + cv->name = name; + cv->syncClass = syncClass; +#endif + return cv; +} + +void +DestroyCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_cond_destroy(&cv->cond); + pthread_mutex_destroy(&cv->mutex); + HFree (cv); +} + +void +WaitCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_mutex_lock (&cv->mutex); +#ifdef TRACK_CONTENTION + if (cv->syncClass & TRACK_CONTENTION_CLASSES) + { + log_add (log_Debug, "Thread '%s' waiting for signal from '%s'", + MyThreadName (), cv->name); + } +#endif + while (pthread_cond_wait (&cv->cond, &cv->mutex) != 0) + { + TaskSwitch_PT (); + } +#ifdef TRACK_CONTENTION + if (cv->syncClass & TRACK_CONTENTION_CLASSES) + { + log_add (log_Debug, "Thread '%s' received signal from '%s'," + " awakening.", MyThreadName (), cv->name); + } +#endif + pthread_mutex_unlock (&cv->mutex); +} + +void +SignalCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_cond_signal(&cv->cond); +} + +void +BroadcastCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_cond_broadcast(&cv->cond); +} diff --git a/src/libs/threads/pthread/posixthreads.h b/src/libs/threads/pthread/posixthreads.h new file mode 100644 index 0000000..9945b53 --- /dev/null +++ b/src/libs/threads/pthread/posixthreads.h @@ -0,0 +1,103 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_THREADS_PTHREAD_POSIXTHREADS_H_ +#define LIBS_THREADS_PTHREAD_POSIXTHREADS_H_ + +#include "port.h" +#include "libs/threadlib.h" + +void InitThreadSystem_PT (void); +void UnInitThreadSystem_PT (void); + +#ifdef NAMED_SYNCHRO +/* Prototypes with the "name" field */ +Thread CreateThread_PT (ThreadFunction func, void *data, SDWORD stackSize, const char *name); +Mutex CreateMutex_PT (const char *name, DWORD syncClass); +Semaphore CreateSemaphore_PT (DWORD initial, const char *name, DWORD syncClass); +RecursiveMutex CreateRecursiveMutex_PT (const char *name, DWORD syncClass); +CondVar CreateCondVar_PT (const char *name, DWORD syncClass); +#else +/* Prototypes without the "name" field. */ +Thread CreateThread_PT (ThreadFunction func, void *data, SDWORD stackSize); +Mutex CreateMutex_PT (void); +Semaphore CreateSemaphore_PT (DWORD initial); +RecursiveMutex CreateRecursiveMutex_PT (void); +CondVar CreateCondVar_PT (void); +#endif + +ThreadLocal *GetMyThreadLocal_PT (void); + +void SleepThread_PT (TimeCount sleepTime); +void SleepThreadUntil_PT (TimeCount wakeTime); +void TaskSwitch_PT (void); +void WaitThread_PT (Thread thread, int *status); +void DestroyThread_PT (Thread thread); + +void DestroyMutex_PT (Mutex m); +void LockMutex_PT (Mutex m); +void UnlockMutex_PT (Mutex m); + +void DestroySemaphore_PT (Semaphore sem); +void SetSemaphore_PT (Semaphore sem); +void ClearSemaphore_PT (Semaphore sem); + +void DestroyCondVar_PT (CondVar c); +void WaitCondVar_PT (CondVar c); +void SignalCondVar_PT (CondVar c); +void BroadcastCondVar_PT (CondVar c); + +void DestroyRecursiveMutex_PT (RecursiveMutex m); +void LockRecursiveMutex_PT (RecursiveMutex m); +void UnlockRecursiveMutex_PT (RecursiveMutex m); +int GetRecursiveMutexDepth_PT (RecursiveMutex m); + +#define NativeInitThreadSystem InitThreadSystem_PT +#define NativeUnInitThreadSystem UnInitThreadSystem_PT + +#define NativeGetMyThreadLocal GetMyThreadLocal_PT + +#define NativeCreateThread CreateThread_PT +#define NativeSleepThread SleepThread_PT +#define NativeSleepThreadUntil SleepThreadUntil_PT +#define NativeTaskSwitch TaskSwitch_PT +#define NativeWaitThread WaitThread_PT +#define NativeDestroyThread DestroyThread_PT + +#define NativeCreateMutex CreateMutex_PT +#define NativeDestroyMutex DestroyMutex_PT +#define NativeLockMutex LockMutex_PT +#define NativeUnlockMutex UnlockMutex_PT + +#define NativeCreateSemaphore CreateSemaphore_PT +#define NativeDestroySemaphore DestroySemaphore_PT +#define NativeSetSemaphore SetSemaphore_PT +#define NativeClearSemaphore ClearSemaphore_PT + +#define NativeCreateCondVar CreateCondVar_PT +#define NativeDestroyCondVar DestroyCondVar_PT +#define NativeWaitCondVar WaitCondVar_PT +#define NativeSignalCondVar SignalCondVar_PT +#define NativeBroadcastCondVar BroadcastCondVar_PT + +#define NativeCreateRecursiveMutex CreateRecursiveMutex_PT +#define NativeDestroyRecursiveMutex DestroyRecursiveMutex_PT +#define NativeLockRecursiveMutex LockRecursiveMutex_PT +#define NativeUnlockRecursiveMutex UnlockRecursiveMutex_PT +#define NativeGetRecursiveMutexDepth GetRecursiveMutexDepth_PT + +#endif /* _PTTHREAD_H */ + diff --git a/src/libs/threads/sdl/Makeinfo b/src/libs/threads/sdl/Makeinfo new file mode 100644 index 0000000..7a7ad76 --- /dev/null +++ b/src/libs/threads/sdl/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="sdlthreads.c" +uqm_HFILES="sdlthreads.h" diff --git a/src/libs/threads/sdl/sdlthreads.c b/src/libs/threads/sdl/sdlthreads.c new file mode 100644 index 0000000..118951d --- /dev/null +++ b/src/libs/threads/sdl/sdlthreads.c @@ -0,0 +1,706 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include "libs/misc.h" +#include "libs/memlib.h" +#include "sdlthreads.h" +#ifdef PROFILE_THREADS +#include +#include +#endif +#include "libs/log.h" + +#if defined(PROFILE_THREADS) && !defined(WIN32) +#include +#include +#endif + +#if SDL_MAJOR_VERSION == 1 +typedef Uint32 SDL_threadID; +#endif + +typedef struct _thread { + void *native; +#ifdef NAMED_SYNCHRO + const char *name; +#endif +#ifdef PROFILE_THREADS + int startTime; +#endif /* PROFILE_THREADS */ + ThreadLocal *localData; + struct _thread *next; +} *TrueThread; + +static volatile TrueThread threadQueue = NULL; +static SDL_mutex *threadQueueMutex; + +struct ThreadStartInfo +{ + ThreadFunction func; + void *data; + SDL_sem *sem; + TrueThread thread; +}; + +#ifdef PROFILE_THREADS +static void +SigUSR1Handler (int signr) { + if (getpgrp () != getpid ()) + { + // Only act for the main process + return; + } + PrintThreadsStats (); + // It's not a good idea in general to do many things in a signal + // handler, (and especially the locking) but I guess it will + // have to do for now (and it's only for debugging). + (void) signr; /* Satisfying compiler (unused parameter) */ +} + +static void +LocalStats (SDL_Thread *thread) { +#if defined (WIN32) || !defined(SDL_PTHREADS) + fprintf (stderr, "Thread ID %08lx\n", (Uint64)SDL_GetThreadID (thread)); +#else /* !defined (WIN32) && defined(SDL_PTHREADS) */ + // This only works if SDL implements threads as processes + pid_t pid; + struct rusage ru; + long seconds; + + pid = (pid_t) SDL_GetThreadID (thread); + fprintf (stderr, "Pid %d\n", (int) pid); + getrusage(RUSAGE_SELF, &ru); + seconds = ru.ru_utime.tv_sec + ru.ru_utime.tv_sec; + fprintf (stderr, "Used %ld.%ld minutes of processor time.\n", + seconds / 60, seconds % 60); +#endif /* defined (WIN32) && defined(SDL_PTHREADS) */ +} + +void +PrintThreadsStats_SDL (void) +{ + TrueThread ptr; + int now; + + now = GetTimeCounter (); + SDL_mutexP (threadQueueMutex); + fprintf(stderr, "--- Active threads ---\n"); + for (ptr = threadQueue; ptr != NULL; ptr = ptr->next) { + fprintf (stderr, "Thread named '%s'.\n", ptr->name); + fprintf (stderr, "Started %d.%d minutes ago.\n", + (now - ptr->startTime) / 60000, + ((now - ptr->startTime) / 1000) % 60); + LocalStats (ptr->native); + if (ptr->next != NULL) + fprintf(stderr, "\n"); + } + SDL_mutexV (threadQueueMutex); + fprintf(stderr, "----------------------\n"); + fflush (stderr); +} +#endif /* PROFILE_THREADS */ + +void +InitThreadSystem_SDL (void) +{ + threadQueueMutex = SDL_CreateMutex (); +#ifdef PROFILE_THREADS + signal(SIGUSR1, SigUSR1Handler); +#endif +} + +void +UnInitThreadSystem_SDL (void) +{ +#ifdef PROFILE_THREADS + signal(SIGUSR1, SIG_DFL); +#endif + SDL_DestroyMutex (threadQueueMutex); +} + +static void +QueueThread (TrueThread thread) +{ + SDL_mutexP (threadQueueMutex); + thread->next = threadQueue; + threadQueue = thread; + SDL_mutexV (threadQueueMutex); +} + +static void +UnQueueThread (TrueThread thread) +{ + volatile TrueThread *ptr; + + SDL_mutexP (threadQueueMutex); + ptr = &threadQueue; + while (*ptr != thread) + { +#ifdef DEBUG_THREADS + if (*ptr == NULL) + { + // Should not happen. + log_add (log_Debug, "Error: Trying to remove non-present thread " + "from thread queue."); + fflush (stderr); + explode (); + } +#endif /* DEBUG_THREADS */ + ptr = &(*ptr)->next; + } + *ptr = (*ptr)->next; + SDL_mutexV (threadQueueMutex); +} + +static TrueThread +FindThreadInfo (SDL_threadID threadID) +{ + TrueThread ptr; + + SDL_mutexP (threadQueueMutex); + ptr = threadQueue; + while (ptr) + { + if (SDL_GetThreadID (ptr->native) == threadID) + { + SDL_mutexV (threadQueueMutex); + return ptr; + } + ptr = ptr->next; + } + SDL_mutexV (threadQueueMutex); + return NULL; +} + +#ifdef NAMED_SYNCHRO +static const char * +MyThreadName (void) +{ + TrueThread t = FindThreadInfo (SDL_ThreadID ()); + return t ? t->name : "Unknown (probably renderer)"; +} +#endif + +static int +ThreadHelper (void *startInfo) { + ThreadFunction func; + void *data; + SDL_sem *sem; + TrueThread thread; + int result; + + func = ((struct ThreadStartInfo *) startInfo)->func; + data = ((struct ThreadStartInfo *) startInfo)->data; + sem = ((struct ThreadStartInfo *) startInfo)->sem; + + // Wait until the Thread structure is available. + SDL_SemWait (sem); + SDL_DestroySemaphore (sem); + thread = ((struct ThreadStartInfo *) startInfo)->thread; + HFree (startInfo); + + result = (*func) (data); + +#ifdef DEBUG_THREADS + log_add (log_Debug, "Thread '%s' done (returned %d).", + thread->name, result); + fflush (stderr); +#endif + + UnQueueThread (thread); + DestroyThreadLocal (thread->localData); + FinishThread (thread); + /* Destroying the thread is the responsibility of ProcessThreadLifecycles() */ + return result; +} + +void +DestroyThread_SDL (Thread t) +{ + HFree (t); +} + +Thread +CreateThread_SDL (ThreadFunction func, void *data, SDWORD stackSize +#ifdef NAMED_SYNCHRO + , const char *name +#endif + ) +{ + TrueThread thread; + struct ThreadStartInfo *startInfo; + + thread = (struct _thread *) HMalloc (sizeof *thread); +#ifdef NAMED_SYNCHRO + thread->name = name; +#endif +#ifdef PROFILE_THREADS + thread->startTime = GetTimeCounter (); +#endif + + thread->localData = CreateThreadLocal (); + + startInfo = (struct ThreadStartInfo *) HMalloc (sizeof (*startInfo)); + startInfo->func = func; + startInfo->data = data; + startInfo->sem = SDL_CreateSemaphore (0); + startInfo->thread = thread; + +#if SDL_MAJOR_VERSION == 1 + // SDL1 case + thread->native = SDL_CreateThread (ThreadHelper, (void *) startInfo); +#elif defined(NAMED_SYNCHRO) + // SDL2 with UQM-aware named threads case + thread->native = SDL_CreateThread (ThreadHelper, thread->name, (void *) startInfo); +#else + // SDL2 without UQM-aware named threads; use a placeholder for debuggers + thread->native = SDL_CreateThread (ThreadHelper, "UQM worker thread", (void *)startInfo); +#endif + if (!(thread->native)) + { + DestroyThreadLocal (thread->localData); + HFree (startInfo); + HFree (thread); + return NULL; + } + // The responsibility to free 'startInfo' and 'thread' is now by the new + // thread. + + QueueThread (thread); + +#ifdef DEBUG_THREADS +#if 0 + log_add (log_Debug, "Thread '%s' created.", ThreadName (thread)); + fflush (stderr); +#endif +#endif + + // Signal to the new thread that the thread structure is ready + // and it can begin to use it. + SDL_SemPost (startInfo->sem); + + (void) stackSize; /* Satisfying compiler (unused parameter) */ + return thread; +} + +void +SleepThread_SDL (TimeCount sleepTime) +{ + SDL_Delay (sleepTime * 1000 / ONE_SECOND); +} + +void +SleepThreadUntil_SDL (TimeCount wakeTime) { + TimeCount now; + + now = GetTimeCounter (); + if (wakeTime <= now) + TaskSwitch_SDL (); + else + SDL_Delay ((wakeTime - now) * 1000 / ONE_SECOND); +} + +void +TaskSwitch_SDL (void) { + SDL_Delay (1); +} + +void +WaitThread_SDL (Thread thread, int *status) { + SDL_WaitThread (((TrueThread)thread)->native, status); +} + +ThreadLocal * +GetMyThreadLocal_SDL (void) +{ + TrueThread t = FindThreadInfo (SDL_ThreadID ()); + return t ? t->localData : NULL; +} + +/* These are the SDL implementations of the UQM synchronization objects. */ + +/* Mutexes. */ +/* TODO. The w_memlib uses Mutexes right now, so we can't use HMalloc + * or HFree. Once that goes, this needs to change. */ + +typedef struct _mutex { + SDL_mutex *mutex; +#ifdef TRACK_CONTENTION + SDL_threadID owner; +#endif +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} Mut; + + +Mutex +#ifdef NAMED_SYNCHRO +CreateMutex_SDL (const char *name, DWORD syncClass) +#else +CreateMutex_SDL (void) +#endif +{ + Mut *mutex = malloc (sizeof (Mut)); + if (mutex != NULL) + { + mutex->mutex = SDL_CreateMutex(); +#ifdef TRACK_CONTENTION + mutex->owner = 0; +#endif +#ifdef NAMED_SYNCHRO + mutex->name = name; + mutex->syncClass = syncClass; +#endif + } + + if ((mutex == NULL) || (mutex->mutex == NULL)) + { +#ifdef NAMED_SYNCHRO + /* logging depends on Mutexes, so we have to use the + * non-threaded version instead */ + log_add_nothread (log_Fatal, "Could not initialize mutex '%s':" + "aborting.", name); +#else + log_add_nothread (log_Fatal, "Could not initialize mutex:" + "aborting."); +#endif + exit (EXIT_FAILURE); + } + + return mutex; +} + +void +DestroyMutex_SDL (Mutex m) +{ + Mut *mutex = (Mut *)m; + SDL_DestroyMutex (mutex->mutex); + free (mutex); +} + +void +LockMutex_SDL (Mutex m) +{ + Mut *mutex = (Mut *)m; +#ifdef TRACK_CONTENTION + /* This code isn't really quite right; race conditions between + * check and lock remain and can produce reports of contention + * where the thread never sleeps, or fail to report in + * situations where it does. If tracking with perfect + * accuracy becomes important, the TRACK_CONTENTION mutex will + * need to handle its own wake/sleep cycles with condition + * variables (check the history of this file for the + * CrossThreadMutex code). This almost-measure is being added + * because for the most part it should suffice. */ + if (mutex->owner && (mutex->syncClass & TRACK_CONTENTION_CLASSES)) + { /* logging depends on Mutexes, so we have to use the + * non-threaded version instead */ + log_add_nothread (log_Debug, "Thread '%s' blocking on mutex '%s'", + MyThreadName (), mutex->name); + } +#endif + while (SDL_mutexP (mutex->mutex) != 0) + { + TaskSwitch_SDL (); + } +#ifdef TRACK_CONTENTION + mutex->owner = SDL_ThreadID (); +#endif +} + +void +UnlockMutex_SDL (Mutex m) +{ + Mut *mutex = (Mut *)m; +#ifdef TRACK_CONTENTION + mutex->owner = 0; +#endif + while (SDL_mutexV (mutex->mutex) != 0) + { + TaskSwitch_SDL (); + } +} + +/* Semaphores. */ + +typedef struct _sem { + SDL_sem *sem; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} Sem; + +Semaphore +CreateSemaphore_SDL (DWORD initial +#ifdef NAMED_SYNCHRO + , const char *name, DWORD syncClass +#endif + ) +{ + Sem *sem = (Sem *) HMalloc (sizeof (struct _sem)); +#ifdef NAMED_SYNCHRO + sem->name = name; + sem->syncClass = syncClass; +#endif + sem->sem = SDL_CreateSemaphore (initial); + if (sem->sem == NULL) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize semaphore '%s':" + " aborting.", name); +#else + log_add (log_Fatal, "Could not initialize semaphore:" + " aborting."); +#endif + exit (EXIT_FAILURE); + } + return sem; +} + +void +DestroySemaphore_SDL (Semaphore s) +{ + Sem *sem = (Sem *)s; + SDL_DestroySemaphore (sem->sem); + HFree (sem); +} + +void +SetSemaphore_SDL (Semaphore s) +{ + Sem *sem = (Sem *)s; +#ifdef TRACK_CONTENTION + BOOLEAN contention = !(SDL_SemValue (sem->sem)); + if (contention && (sem->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' blocking on semaphore '%s'", + MyThreadName (), sem->name); + } +#endif + while (SDL_SemWait (sem->sem) == -1) + { + TaskSwitch_SDL (); + } +#ifdef TRACK_CONTENTION + if (contention && (sem->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' awakens," + " released from semaphore '%s'", MyThreadName (), sem->name); + } +#endif +} + +void +ClearSemaphore_SDL (Semaphore s) +{ + Sem *sem = (Sem *)s; + while (SDL_SemPost (sem->sem) == -1) + { + TaskSwitch_SDL (); + } +} + +/* Recursive mutexes. Adapted from mixSDL code, which was adapted from + the original DCQ code. */ + +typedef struct _recm { + SDL_mutex *mutex; + SDL_threadID thread_id; + Uint32 locks; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} RecM; + +RecursiveMutex +#ifdef NAMED_SYNCHRO +CreateRecursiveMutex_SDL (const char *name, DWORD syncClass) +#else +CreateRecursiveMutex_SDL (void) +#endif +{ + RecM *mtx = (RecM *) HMalloc (sizeof (struct _recm)); + + mtx->thread_id = 0; + mtx->mutex = SDL_CreateMutex (); + if (mtx->mutex == NULL) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize recursive " + "mutex '%s': aborting.", name); +#else + log_add (log_Fatal, "Could not initialize recursive " + "mutex: aborting."); +#endif + exit (EXIT_FAILURE); + } +#ifdef NAMED_SYNCHRO + mtx->name = name; + mtx->syncClass = syncClass; +#endif + mtx->locks = 0; + return (RecursiveMutex) mtx; +} + +void +DestroyRecursiveMutex_SDL (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + SDL_DestroyMutex (mtx->mutex); + HFree (mtx); +} + +void +LockRecursiveMutex_SDL (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + SDL_threadID thread_id = SDL_ThreadID(); + if (!mtx->locks || mtx->thread_id != thread_id) + { +#ifdef TRACK_CONTENTION + if (mtx->thread_id && (mtx->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' blocking on '%s'", + MyThreadName (), mtx->name); + } +#endif + while (SDL_mutexP (mtx->mutex)) + TaskSwitch_SDL (); + mtx->thread_id = thread_id; + } + mtx->locks++; +} + +void +UnlockRecursiveMutex_SDL (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + SDL_threadID thread_id = SDL_ThreadID(); + if (!mtx->locks || mtx->thread_id != thread_id) + { +#ifdef NAMED_SYNCHRO + log_add (log_Debug, "'%s' attempted to unlock %s when it " + "didn't hold it", MyThreadName (), mtx->name); +#endif + } + else + { + mtx->locks--; + if (!mtx->locks) + { + mtx->thread_id = 0; + SDL_mutexV (mtx->mutex); + } + } +} + +int +GetRecursiveMutexDepth_SDL (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + return mtx->locks; +} + +typedef struct _cond { + SDL_cond *cond; + SDL_mutex *mutex; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} cvar; + +CondVar +#ifdef NAMED_SYNCHRO +CreateCondVar_SDL (const char *name, DWORD syncClass) +#else +CreateCondVar_SDL (void) +#endif +{ + cvar *cv = (cvar *) HMalloc (sizeof (cvar)); + cv->cond = SDL_CreateCond (); + cv->mutex = SDL_CreateMutex (); + if ((cv->cond == NULL) || (cv->mutex == NULL)) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize condition variable '%s':" + " aborting.", name); +#else + log_add (log_Fatal, "Could not initialize condition variable:" + " aborting."); +#endif + exit (EXIT_FAILURE); + } +#ifdef NAMED_SYNCHRO + cv->name = name; + cv->syncClass = syncClass; +#endif + return cv; +} + +void +DestroyCondVar_SDL (CondVar c) +{ + cvar *cv = (cvar *) c; + SDL_DestroyCond (cv->cond); + SDL_DestroyMutex (cv->mutex); + HFree (cv); +} + +void +WaitCondVar_SDL (CondVar c) +{ + cvar *cv = (cvar *) c; + SDL_mutexP (cv->mutex); +#ifdef TRACK_CONTENTION + if (cv->syncClass & TRACK_CONTENTION_CLASSES) + { + log_add (log_Debug, "Thread '%s' waiting for signal from '%s'", + MyThreadName (), cv->name); + } +#endif + while (SDL_CondWait (cv->cond, cv->mutex) != 0) + { + TaskSwitch_SDL (); + } +#ifdef TRACK_CONTENTION + if (cv->syncClass & TRACK_CONTENTION_CLASSES) + { + log_add (log_Debug, "Thread '%s' received signal from '%s'," + " awakening.", MyThreadName (), cv->name); + } +#endif + SDL_mutexV (cv->mutex); +} + +void +SignalCondVar_SDL (CondVar c) +{ + cvar *cv = (cvar *) c; + SDL_CondSignal (cv->cond); +} + +void +BroadcastCondVar_SDL (CondVar c) +{ + cvar *cv = (cvar *) c; + SDL_CondBroadcast (cv->cond); +} diff --git a/src/libs/threads/sdl/sdlthreads.h b/src/libs/threads/sdl/sdlthreads.h new file mode 100644 index 0000000..c93c947 --- /dev/null +++ b/src/libs/threads/sdl/sdlthreads.h @@ -0,0 +1,106 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_THREADS_SDL_SDLTHREADS_H_ +#define LIBS_THREADS_SDL_SDLTHREADS_H_ + +#include "port.h" +#include SDL_INCLUDE(SDL.h) +#include SDL_INCLUDE(SDL_thread.h) +#include "libs/threadlib.h" +#include "libs/timelib.h" + +void InitThreadSystem_SDL (void); +void UnInitThreadSystem_SDL (void); + +#ifdef NAMED_SYNCHRO +/* Prototypes with the "name" field */ +Thread CreateThread_SDL (ThreadFunction func, void *data, SDWORD stackSize, const char *name); +Mutex CreateMutex_SDL (const char *name, DWORD syncClass); +Semaphore CreateSemaphore_SDL (DWORD initial, const char *name, DWORD syncClass); +RecursiveMutex CreateRecursiveMutex_SDL (const char *name, DWORD syncClass); +CondVar CreateCondVar_SDL (const char *name, DWORD syncClass); +#else +/* Prototypes without the "name" field. */ +Thread CreateThread_SDL (ThreadFunction func, void *data, SDWORD stackSize); +Mutex CreateMutex_SDL (void); +Semaphore CreateSemaphore_SDL (DWORD initial); +RecursiveMutex CreateRecursiveMutex_SDL (void); +CondVar CreateCondVar_SDL (void); +#endif + +ThreadLocal *GetMyThreadLocal_SDL (void); + +void SleepThread_SDL (TimeCount sleepTime); +void SleepThreadUntil_SDL (TimeCount wakeTime); +void TaskSwitch_SDL (void); +void WaitThread_SDL (Thread thread, int *status); +void DestroyThread_SDL (Thread thread); + +void DestroyMutex_SDL (Mutex m); +void LockMutex_SDL (Mutex m); +void UnlockMutex_SDL (Mutex m); + +void DestroySemaphore_SDL (Semaphore sem); +void SetSemaphore_SDL (Semaphore sem); +void ClearSemaphore_SDL (Semaphore sem); + +void DestroyCondVar_SDL (CondVar c); +void WaitCondVar_SDL (CondVar c); +void SignalCondVar_SDL (CondVar c); +void BroadcastCondVar_SDL (CondVar c); + +void DestroyRecursiveMutex_SDL (RecursiveMutex m); +void LockRecursiveMutex_SDL (RecursiveMutex m); +void UnlockRecursiveMutex_SDL (RecursiveMutex m); +int GetRecursiveMutexDepth_SDL (RecursiveMutex m); + +#define NativeInitThreadSystem InitThreadSystem_SDL +#define NativeUnInitThreadSystem UnInitThreadSystem_SDL + +#define NativeGetMyThreadLocal GetMyThreadLocal_SDL + +#define NativeCreateThread CreateThread_SDL +#define NativeSleepThread SleepThread_SDL +#define NativeSleepThreadUntil SleepThreadUntil_SDL +#define NativeTaskSwitch TaskSwitch_SDL +#define NativeWaitThread WaitThread_SDL +#define NativeDestroyThread DestroyThread_SDL + +#define NativeCreateMutex CreateMutex_SDL +#define NativeDestroyMutex DestroyMutex_SDL +#define NativeLockMutex LockMutex_SDL +#define NativeUnlockMutex UnlockMutex_SDL + +#define NativeCreateSemaphore CreateSemaphore_SDL +#define NativeDestroySemaphore DestroySemaphore_SDL +#define NativeSetSemaphore SetSemaphore_SDL +#define NativeClearSemaphore ClearSemaphore_SDL + +#define NativeCreateCondVar CreateCondVar_SDL +#define NativeDestroyCondVar DestroyCondVar_SDL +#define NativeWaitCondVar WaitCondVar_SDL +#define NativeSignalCondVar SignalCondVar_SDL +#define NativeBroadcastCondVar BroadcastCondVar_SDL + +#define NativeCreateRecursiveMutex CreateRecursiveMutex_SDL +#define NativeDestroyRecursiveMutex DestroyRecursiveMutex_SDL +#define NativeLockRecursiveMutex LockRecursiveMutex_SDL +#define NativeUnlockRecursiveMutex UnlockRecursiveMutex_SDL +#define NativeGetRecursiveMutexDepth GetRecursiveMutexDepth_SDL + +#endif /* LIBS_THREADS_SDL_SDLTHREADS_H_ */ + diff --git a/src/libs/threads/thrcommon.c b/src/libs/threads/thrcommon.c new file mode 100644 index 0000000..bc54751 --- /dev/null +++ b/src/libs/threads/thrcommon.c @@ -0,0 +1,451 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#include "libs/threadlib.h" +#include "libs/timelib.h" +#include "libs/log.h" +#include "libs/async.h" +#include "libs/memlib.h" +#include "thrcommon.h" + +#define LIFECYCLE_SIZE 8 +typedef struct { + ThreadFunction func; + void *data; + SDWORD stackSize; + Semaphore sem; + Thread value; +#ifdef NAMED_SYNCHRO + const char *name; +#endif +} SpawnRequest_struct; + +typedef SpawnRequest_struct *SpawnRequest; + +static Mutex lifecycleMutex; +static SpawnRequest pendingBirth[LIFECYCLE_SIZE]; +static Thread pendingDeath[LIFECYCLE_SIZE]; + +void +InitThreadSystem (void) +{ + int i; + NativeInitThreadSystem (); + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + pendingBirth[i] = NULL; + pendingDeath[i] = NULL; + } + lifecycleMutex = CreateMutex ("Thread Lifecycle Mutex", SYNC_CLASS_RESOURCE); +} + +void +UnInitThreadSystem (void) +{ + NativeUnInitThreadSystem (); + DestroyMutex (lifecycleMutex); +} + +static Thread +FlagStartThread (SpawnRequest s) +{ + int i; + LockMutex (lifecycleMutex); + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + if (pendingBirth[i] == NULL) + { + pendingBirth[i] = s; + UnlockMutex (lifecycleMutex); + if (s->sem) + { + Thread result; + SetSemaphore (s->sem); + DestroySemaphore (s->sem); + result = s->value; + HFree (s); + return result; + } + return NULL; + } + } + log_add (log_Fatal, "Thread Lifecycle array filled. This is a fatal error! Make LIFECYCLE_SIZE something larger than %d.", LIFECYCLE_SIZE); + exit (EXIT_FAILURE); +} + +void +FinishThread (Thread thread) +{ + int i; + LockMutex (lifecycleMutex); + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + if (pendingDeath[i] == NULL) + { + pendingDeath[i] = thread; + UnlockMutex (lifecycleMutex); + return; + } + } + log_add (log_Fatal, "Thread Lifecycle array filled. This is a fatal error! Make LIFECYCLE_SIZE something larger than %d.", LIFECYCLE_SIZE); + exit (EXIT_FAILURE); +} + +/* Only call from main thread! */ +void +ProcessThreadLifecycles (void) +{ + int i; + LockMutex (lifecycleMutex); + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + SpawnRequest s = pendingBirth[i]; + if (s != NULL) + { +#ifdef NAMED_SYNCHRO + s->value = NativeCreateThread (s->func, s->data, s->stackSize, s->name); +#else + s->value = NativeCreateThread (s->func, s->data, s->stackSize); +#endif + if (s->sem) + { + ClearSemaphore (s->sem); + /* The spawning thread's FlagStartThread will clean up s */ + } + else + { + /* The thread value has been lost to the game logic. We must + clean up s ourself. */ + HFree (s); + } + pendingBirth[i] = NULL; + } + } + + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + Thread t = pendingDeath[i]; + if (t != NULL) + { + WaitThread (t, NULL); + pendingDeath[i] = NULL; + DestroyThread (t); + } + } + UnlockMutex (lifecycleMutex); +} + + +/* The Create routines look different based on whether NAMED_SYNCHRO + is defined or not. */ + +#ifdef NAMED_SYNCHRO +Thread +CreateThread_Core (ThreadFunction func, void *data, SDWORD stackSize, const char *name) +{ + SpawnRequest s = HMalloc(sizeof (SpawnRequest_struct)); + s->func = func; + s->data = data; + s->stackSize = stackSize; + s->name = name; + s->sem = CreateSemaphore (0, "SpawnRequest semaphore", SYNC_CLASS_RESOURCE); + return FlagStartThread (s); +} + +void +StartThread_Core (ThreadFunction func, void *data, SDWORD stackSize, const char *name) +{ + SpawnRequest s = HMalloc(sizeof (SpawnRequest_struct)); + s->func = func; + s->data = data; + s->stackSize = stackSize; + s->name = name; + s->sem = NULL; + FlagStartThread (s); +} + +Mutex +CreateMutex_Core (const char *name, DWORD syncClass) +{ + return NativeCreateMutex (name, syncClass); +} + +Semaphore +CreateSemaphore_Core (DWORD initial, const char *name, DWORD syncClass) +{ + return NativeCreateSemaphore (initial, name, syncClass); +} + +RecursiveMutex +CreateRecursiveMutex_Core (const char *name, DWORD syncClass) +{ + return NativeCreateRecursiveMutex (name, syncClass); +} + +CondVar +CreateCondVar_Core (const char *name, DWORD syncClass) +{ + return NativeCreateCondVar (name, syncClass); +} + +#else +/* These are the versions of Create* without the names. */ +Thread +CreateThread_Core (ThreadFunction func, void *data, SDWORD stackSize) +{ + SpawnRequest s = HMalloc(sizeof (SpawnRequest_struct)); + s->func = func; + s->data = data; + s->stackSize = stackSize; + s->sem = CreateSemaphore (0, "SpawnRequest semaphore", SYNC_CLASS_RESOURCE); + return FlagStartThread (s); +} + +void +StartThread_Core (ThreadFunction func, void *data, SDWORD stackSize) +{ + SpawnRequest s = HMalloc(sizeof (SpawnRequest_struct)); + s->func = func; + s->data = data; + s->stackSize = stackSize; + s->sem = NULL; + FlagStartThread (s); +} + +Mutex +CreateMutex_Core (void) +{ + return NativeCreateMutex (); +} + +Semaphore +CreateSemaphore_Core (DWORD initial) +{ + return NativeCreateSemaphore (initial); +} + +RecursiveMutex +CreateRecursiveMutex_Core (void) +{ + return NativeCreateRecursiveMutex (); +} + +CondVar +CreateCondVar_Core (void) +{ + return NativeCreateCondVar (); +} +#endif + +void +DestroyThread (Thread t) +{ + NativeDestroyThread (t); +} + +ThreadLocal * +CreateThreadLocal (void) +{ + ThreadLocal *tl = HMalloc (sizeof (ThreadLocal)); + tl->flushSem = CreateSemaphore (0, "FlushGraphics", SYNC_CLASS_VIDEO); + return tl; +} + +void +DestroyThreadLocal (ThreadLocal *tl) +{ + DestroySemaphore (tl->flushSem); + HFree (tl); +} + +ThreadLocal * +GetMyThreadLocal (void) +{ + return NativeGetMyThreadLocal (); +} + +void +WaitThread (Thread thread, int *status) +{ + NativeWaitThread (thread, status); +} + +#ifdef DEBUG_SLEEP +extern uint32 mainThreadId; +extern uint32 SDL_ThreadID(void); +#endif /* DEBUG_SLEEP */ + +void +HibernateThread (TimePeriod timePeriod) +{ +#ifdef DEBUG_SLEEP + if (SDL_ThreadID() == mainThreadId) + log_add (log_Debug, "HibernateThread called from main thread.\n"); +#endif /* DEBUG_SLEEP */ + + NativeSleepThread (timePeriod); +} + +void +HibernateThreadUntil (TimeCount wakeTime) +{ +#ifdef DEBUG_SLEEP + if (SDL_ThreadID() == mainThreadId) + log_add (log_Debug, "HibernateThreadUntil called from main " + "thread.\n"); +#endif /* DEBUG_SLEEP */ + + NativeSleepThreadUntil (wakeTime); +} + +void +SleepThread (TimePeriod timePeriod) +{ + TimeCount now; + +#ifdef DEBUG_SLEEP + if (SDL_ThreadID() != mainThreadId) + log_add (log_Debug, "SleepThread called from non-main " + "thread.\n"); +#endif /* DEBUG_SLEEP */ + + now = GetTimeCounter (); + SleepThreadUntil (now + timePeriod); +} + +// Sleep until wakeTime, but call asynchrounous operations until then. +void +SleepThreadUntil (TimeCount wakeTime) +{ +#ifdef DEBUG_SLEEP + if (SDL_ThreadID() != mainThreadId) + log_add (log_Debug, "SleepThreadUntil called from non-main " + "thread.\n"); +#endif /* DEBUG_SLEEP */ + + for (;;) { + uint32 nextTimeMs; + TimeCount nextTime; + TimeCount now; + + Async_process (); + + now = GetTimeCounter (); + if (wakeTime <= now) + return; + + nextTimeMs = Async_timeBeforeNextMs (); + nextTime = (nextTimeMs / 1000) * ONE_SECOND + + ((nextTimeMs % 1000) * ONE_SECOND / 1000); + // Overflow-safe conversion. + if (wakeTime < nextTime) + nextTime = wakeTime; + + NativeSleepThreadUntil (nextTime); + } +} + +void +TaskSwitch (void) +{ + NativeTaskSwitch (); +} + +void +DestroyMutex (Mutex sem) +{ + NativeDestroyMutex (sem); +} + +void +LockMutex (Mutex sem) +{ + NativeLockMutex (sem); +} + +void +UnlockMutex (Mutex sem) +{ + NativeUnlockMutex (sem); +} + +void +DestroySemaphore (Semaphore sem) +{ + NativeDestroySemaphore (sem); +} + +void +SetSemaphore (Semaphore sem) +{ + NativeSetSemaphore (sem); +} + +void +ClearSemaphore (Semaphore sem) +{ + NativeClearSemaphore (sem); +} + +void +DestroyCondVar (CondVar cv) +{ + NativeDestroyCondVar (cv); +} + +void +WaitCondVar (CondVar cv) +{ + NativeWaitCondVar (cv); +} + +void +SignalCondVar (CondVar cv) +{ + NativeSignalCondVar (cv); +} + +void +BroadcastCondVar (CondVar cv) +{ + NativeBroadcastCondVar (cv); +} + +void +DestroyRecursiveMutex (RecursiveMutex mutex) +{ + NativeDestroyRecursiveMutex (mutex); +} + +void +LockRecursiveMutex (RecursiveMutex mutex) +{ + NativeLockRecursiveMutex (mutex); +} + +void +UnlockRecursiveMutex (RecursiveMutex mutex) +{ + NativeUnlockRecursiveMutex (mutex); +} + +int +GetRecursiveMutexDepth (RecursiveMutex mutex) +{ + return NativeGetRecursiveMutexDepth (mutex); +} diff --git a/src/libs/threads/thrcommon.h b/src/libs/threads/thrcommon.h new file mode 100644 index 0000000..d6d6d41 --- /dev/null +++ b/src/libs/threads/thrcommon.h @@ -0,0 +1,28 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_THREADS_THRCOMMON_H_ +#define LIBS_THREADS_THRCOMMON_H_ + + +#if defined(THREADLIB_SDL) +# include "sdl/sdlthreads.h" +#elif defined(THREADLIB_PTHREAD) +# include "pthread/posixthreads.h" +#endif /* defined(THREADLIB_PTHREAD) */ + + +#endif /* _THR_COMMON_H */ diff --git a/src/libs/time/Makeinfo b/src/libs/time/Makeinfo new file mode 100644 index 0000000..14a7ca8 --- /dev/null +++ b/src/libs/time/Makeinfo @@ -0,0 +1,3 @@ +uqm_SUBDIRS="sdl" +uqm_CFILES="timecommon.c" +uqm_HFILES="timecommon.h" diff --git a/src/libs/time/sdl/Makeinfo b/src/libs/time/sdl/Makeinfo new file mode 100644 index 0000000..47180ad --- /dev/null +++ b/src/libs/time/sdl/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="sdltime.c" +uqm_HFILES="sdltime.h" diff --git a/src/libs/time/sdl/sdltime.c b/src/libs/time/sdl/sdltime.c new file mode 100644 index 0000000..35ab856 --- /dev/null +++ b/src/libs/time/sdl/sdltime.c @@ -0,0 +1,30 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* By Serge van den Boom + */ + +#include "sdltime.h" +#include "libs/timelib.h" + +Uint32 +SDLWrapper_GetTimeCounter (void) +{ + Uint32 ticks = SDL_GetTicks (); + return (ticks / 1000) * ONE_SECOND + ((ticks % 1000) * ONE_SECOND / 1000); + // Use the following instead when confirming "random" lockup bugs (see #668) + //return ticks * ONE_SECOND / 1000; +} diff --git a/src/libs/time/sdl/sdltime.h b/src/libs/time/sdl/sdltime.h new file mode 100644 index 0000000..ee72dc9 --- /dev/null +++ b/src/libs/time/sdl/sdltime.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. + */ + +/* By Serge van den Boom, 2002-09-12 + */ + +#ifndef LIBS_TIME_SDL_SDLTIME_H_ +#define LIBS_TIME_SDL_SDLTIME_H_ + +#include "port.h" +#include SDL_INCLUDE(SDL.h) +#include "../timecommon.h" + +#define NativeInitTimeSystem() +#define NativeUnInitTimeSystem() +extern Uint32 SDLWrapper_GetTimeCounter (void); +#define NativeGetTimeCounter() \ + SDLWrapper_GetTimeCounter () + + +#endif /* LIBS_TIME_SDL_SDLTIME_H_ */ + diff --git a/src/libs/time/timecommon.c b/src/libs/time/timecommon.c new file mode 100644 index 0000000..a281efe --- /dev/null +++ b/src/libs/time/timecommon.c @@ -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. + */ + +/* By Serge van den Boom, 2002-09-12 + */ + +#include +#include "libs/timelib.h" +#include "timecommon.h" + +void +InitTimeSystem (void) +{ + NativeInitTimeSystem (); +} + +void +UnInitTimeSystem (void) +{ + NativeUnInitTimeSystem (); +} + +TimeCount +GetTimeCounter (void) +{ + return NativeGetTimeCounter (); +} + diff --git a/src/libs/time/timecommon.h b/src/libs/time/timecommon.h new file mode 100644 index 0000000..ec5892c --- /dev/null +++ b/src/libs/time/timecommon.h @@ -0,0 +1,30 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* By Serge van den Boom, 2002-09-12 + */ + +#ifndef LIBS_TIME_TIMECOMMON_H_ +#define LIBS_TIME_TIMECOMMON_H_ + + +#if TIMELIB == SDL +#include "sdl/sdltime.h" +#endif /* TIMELIB == SDL */ + + +#endif /* LIBS_TIME_TIMECOMMON_H_ */ + diff --git a/src/libs/timelib.h b/src/libs/timelib.h new file mode 100644 index 0000000..8fc6522 --- /dev/null +++ b/src/libs/timelib.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 LIBS_TIMELIB_H_ +#define LIBS_TIMELIB_H_ + +#define TIMELIB SDL + +#include "libs/compiler.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/* ONE_SECOND is the LCM of all the fractions of a second the game uses. + * Battle is 24 FPS, Landers are 35 FPS, most UI-level things are 15 FPS, + * The Interplanetary flight is 30 FPS, Comm ambient animation is 40 FPS, + * (also Comm Oscilloscope is 32 FPS, but it does not require a stable + * timer and currently runs within the Comm ambient anim paradigm anyway) + * Thus, the minimum value for ONE_SECOND is 840. */ +#if TIMELIB == SDL +# define ONE_SECOND 840 +#endif + +typedef DWORD TimeCount; +typedef DWORD TimePeriod; + +extern void InitTimeSystem (void); +extern void UnInitTimeSystem (void); +extern TimeCount GetTimeCounter (void); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_TIMLIB_H_ */ diff --git a/src/libs/uio.h b/src/libs/uio.h new file mode 100644 index 0000000..f455049 --- /dev/null +++ b/src/libs/uio.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_H_ +#define LIBS_UIO_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "uio/io.h" + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_UIO_H_ */ diff --git a/src/libs/uio/COPYING b/src/libs/uio/COPYING new file mode 100644 index 0000000..2774fef --- /dev/null +++ b/src/libs/uio/COPYING @@ -0,0 +1,350 @@ +Below the text of the GNU General Public License is quoted verbatim. +It applies to all files in and below this directory that say so. +Note that most of these files will say only version 2 of the +GNU General Public License applies, not any later version. + + +--------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + + diff --git a/src/libs/uio/Makeinfo b/src/libs/uio/Makeinfo new file mode 100644 index 0000000..9d127fc --- /dev/null +++ b/src/libs/uio/Makeinfo @@ -0,0 +1,22 @@ +uqm_SUBDIRS="stdio" +uqm_CFILES="charhashtable.c defaultfs.c fileblock.c fstypes.c gphys.c io.c + ioaux.c match.c mount.c mounttree.c paths.c physical.c uiostream.c + uioutils.c utils.c" +uqm_HFILES="charhashtable.h defaultfs.h fileblock.h fstypes.h getint.h + gphys.h ioaux.h io.h iointrn.h match.h mem.h mount.h mounttree.h + paths.h physical.h types.h uioport.h uiostream.h uioutils.h utils.h" + +if [ -n "$uqm_USE_ZIP_IO" ]; then + uqm_SUBDIRS="$uqm_SUBDIRS zip" +fi + +#if [ -n "$DEBUG" -o -n "$uqm_UIO_DEBUG" ]; then + uqm_CFILES="$uqm_CFILES debug.c" + uqm_HFILES="$uqm_HFILES debug.h" +#fi + +if [ -n "$MEMDEBUG" ]; then + uqm_CFILES="$uqm_CFILES hashtable.c memdebug.c" + uqm_HFILES="$uqm_HFILES hashtable.h memdebug.h" +fi + diff --git a/src/libs/uio/charhashtable.c b/src/libs/uio/charhashtable.c new file mode 100644 index 0000000..df10b2a --- /dev/null +++ b/src/libs/uio/charhashtable.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 HASHTABLE_INTERNAL +#include "charhashtable.h" +#include "types.h" +#include "uioport.h" + +static inline uio_uint32 CharHashTable_hash(CharHashTable_HashTable *hashTable, + const char *string); +static inline uio_bool CharHashTable_equal(CharHashTable_HashTable *hashTable, + const char *key1, const char *key2); +static inline char *CharHashTable_copy(CharHashTable_HashTable *hashTable, + const char *key); +static inline void CharHashTable_freeKey(CharHashTable_HashTable *hashTable, + char *key); + +#include "hashtable.c" + + +static inline uio_uint32 +CharHashTable_hash(CharHashTable_HashTable *hashTable, const char *key) { + uio_uint32 hash; + + (void) hashTable; + // Rotating hash, variation of something on the web which + // wasn't original itself. + hash = 0; + // Hash was on that web page initialised as the length, + // but that isn't known at this time. + while (*key != '\0') { + hash = (hash << 4) ^ (hash >> 28) ^ *key; + key++; + } + return hash ^ (hash >> 10) ^ (hash >> 20); +} + +static inline uio_bool +CharHashTable_equal(CharHashTable_HashTable *hashTable, + const char *key1, const char *key2) { + (void) hashTable; + return strcmp(key1, key2) == 0; +} + +static inline char * +CharHashTable_copy(CharHashTable_HashTable *hashTable, + const char *key) { + (void) hashTable; + return uio_strdup(key); +} + +static inline void +CharHashTable_freeKey(CharHashTable_HashTable *hashTable, + char *key) { + (void) hashTable; + uio_free(key); +} + + diff --git a/src/libs/uio/charhashtable.h b/src/libs/uio/charhashtable.h new file mode 100644 index 0000000..3cd0add --- /dev/null +++ b/src/libs/uio/charhashtable.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_CHARHASHTABLE_H_ +#define LIBS_UIO_CHARHASHTABLE_H_ + + +#define HASHTABLE_(identifier) CharHashTable ## _ ## identifier +typedef char HASHTABLE_(Key); +typedef void HASHTABLE_(Value); +#define CharHashTable_HASH CharHashTable_hash +#define CharHashTable_EQUAL CharHashTable_equal +#define CharHashTable_COPY CharHashTable_copy +#define CharHashTable_FREEKEY CharHashTable_freeKey +#define CharHashTable_FREEVALUE(hashTable, value) \ + ((void) (hashTable), (void) (value)) + +#include "hashtable.h" + + +#endif /* _CHARHASHTABLE_H */ + diff --git a/src/libs/uio/debug.c b/src/libs/uio/debug.c new file mode 100644 index 0000000..66bab47 --- /dev/null +++ b/src/libs/uio/debug.c @@ -0,0 +1,914 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#include +#include +#include +#include +#include +#if defined(__unix__) && !defined(_WIN32_WCE) +# include +#endif + +#include "debug.h" +#include "uioport.h" +#include "io.h" +#include "utils.h" +#include "types.h" +#include "mem.h" +#include "uioutils.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + +#define LINEBUFLEN 1024 + +typedef struct DebugContext { + uio_bool exit; + FILE *in; + FILE *out; + FILE *err; + uio_DirHandle *cwd; +} DebugContext; + +typedef struct { + const char *name; + int (*fun)(DebugContext *, int, char *[]); +} DebugCommand; + +#ifdef STAND_ALONE +void initRepository(void); +void unInitRepository(void); +#endif + +static int debugCmdCat(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdCd(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdExec(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdExit(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdLs(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdMem(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdMkDir(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdMount(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdMv(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdPwd(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdRm(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdRmDir(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdStat(DebugContext *debugContext, int argc, char *argv[]); +static int debugCmdWriteTest(DebugContext *debugContext, int argc, + char *argv[]); +static int debugCmdFwriteTest(DebugContext *debugContext, int argc, + char *argv[]); + +static void makeArgs(char *lineBuf, int *argc, char ***argv); +static int debugCallCommand(DebugContext *debugContext, + int argc, char *argv[]); +uio_MountHandle * +debugMountOne(uio_Repository *destRep, const char *mountPoint, + uio_FileSystemID fsType, + uio_DirHandle *sourceDir, const char *sourcePath, + const char *inPath, uio_AutoMount **autoMount, int flags, + uio_MountHandle *relative); + +// In alphabetic order: +DebugCommand debugCommands[] = { + { "cat", debugCmdCat }, + { "cd", debugCmdCd }, + { "exec", debugCmdExec }, + { "exit", debugCmdExit }, + { "fwritetest", debugCmdFwriteTest }, + { "ls", debugCmdLs }, + { "mem", debugCmdMem }, + { "mkdir", debugCmdMkDir }, + { "mount", debugCmdMount }, + { "mv", debugCmdMv }, + { "pwd", debugCmdPwd }, + { "rm", debugCmdRm }, + { "rmdir", debugCmdRmDir }, + { "stat", debugCmdStat }, + { "writetest", debugCmdWriteTest }, +}; + + +#ifndef STAND_ALONE +extern uio_Repository *repository; +#else +uio_Repository *repository; +uio_MountHandle *mountHandles[9]; // TODO: remove (this is just a test) + +int +main(int argc, char *argv[]) { + initRepository(); + uio_debugInteractive(stdin, stdout, stderr); + unInitRepository(); + (void) argc; + (void) argv; + return EXIT_SUCCESS; +} + +uio_MountHandle * +debugMountOne(uio_Repository *destRep, const char *mountPoint, + uio_FileSystemID fsType, + uio_DirHandle *sourceDir, const char *sourcePath, + const char *inPath, uio_AutoMount **autoMount, int flags, + uio_MountHandle *relative) { + uio_MountHandle *mountHandle; + + mountHandle = uio_mountDir(destRep, mountPoint, fsType, sourceDir, + sourcePath, inPath, autoMount, flags, relative); + if (mountHandle == NULL) { + int savedErrno = errno; + fprintf(stderr, "Could not mount '%s' and graft '%s' from that " + "into the repository at '%s': %s\n", + sourcePath, inPath, mountPoint, strerror(errno)); + errno = savedErrno; + } + return mountHandle; +} + +void +initRepository(void) { + static uio_AutoMount autoMountZip = { + .pattern = "*.zip", + .matchType = match_MATCH_SUFFIX, + .fileSystemID = uio_FSTYPE_ZIP, + .mountFlags = uio_MOUNT_BELOW | uio_MOUNT_RDONLY + }; + static uio_AutoMount *autoMount[] = { + &autoMountZip, + NULL + }; + + uio_init(); + repository = uio_openRepository(0); + + memset(&mountHandles, '\0', sizeof mountHandles); +#if 1 + mountHandles[0] = debugMountOne(repository, "/", uio_FSTYPE_STDIO, + NULL, NULL, "/home/svdb/cvs/sc2/content", autoMount, + uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); +#endif +#if 1 + mountHandles[1] = debugMountOne(repository, "/", uio_FSTYPE_STDIO, + NULL, NULL, "/home/svdb/cvs/sc2/src/sc2code/ships", + autoMount, uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); +#endif +#if 1 + mountHandles[2] = debugMountOne(repository, "/", uio_FSTYPE_STDIO, + NULL, NULL, "/tmp/vfstest", autoMount, uio_MOUNT_TOP, NULL); +#endif +#if 1 + mountHandles[3] = debugMountOne(repository, "/", uio_FSTYPE_STDIO, + NULL, NULL, "/tmp/vfstest2", autoMount, uio_MOUNT_TOP, NULL); +#endif + + // TODO: should work too: +#if 0 + mountHandle[4] = debugMountOne(repository, "/zip/", uio_FSTYPE_ZIP, NULL, + NULL, "/ziptest/foo.zip", autoMount, uio_MOUNT_TOP, NULL); +#endif + { + uio_DirHandle *rootDir; + rootDir = uio_openDir(repository, "/", 0); + if (rootDir == NULL) { + fprintf(stderr, "Could not open '/' dir.\n"); + } else { +#if 1 + mountHandles[4] = debugMountOne(repository, "/example/", + uio_FSTYPE_ZIP, rootDir, "/example2.zip", "/", autoMount, + uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); +#endif +#if 1 + mountHandles[5] = debugMountOne(repository, "/example/", + uio_FSTYPE_ZIP, rootDir, "/example/example.zip", "/", + autoMount, uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); +#endif +#if 1 + mountHandles[6] = debugMountOne(repository, "/zip/", + uio_FSTYPE_ZIP, rootDir, "/voice.zip", "/", autoMount, + uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); +#endif +#if 1 + mountHandles[7] = debugMountOne(repository, "/foo/", + uio_FSTYPE_ZIP, rootDir, "/foo2.zip", "/", autoMount, + uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); +#endif + uio_closeDir(rootDir); + } + } + mountHandles[8] = debugMountOne(repository, "/tmp/", + uio_FSTYPE_STDIO, NULL, NULL, "/tmp/", autoMount, + uio_MOUNT_TOP, NULL); + +#if 1 + mountHandles[8] = debugMountOne(repository, "/root/root/", + uio_FSTYPE_STDIO, NULL, NULL, "/", autoMount, + uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); +#endif + +} + +void +unInitRepository(void) { +#if 1 + int i; +// uio_printMountTree(stderr, repository->mountTree, 0); +// fprintf(stderr, "\n"); + for (i = 7; i >= 0; i--) { + if (mountHandles[i] != NULL) + uio_unmountDir(mountHandles[i]); +// uio_printMountTree(stderr, repository->mountTree, 0); +// uio_printMounts(stderr, repository); +// fprintf(stderr, "\n"); + } +#endif + uio_closeRepository(repository); + uio_unInit(); +} +#endif /* STAND_ALONE */ + +void +uio_debugInteractive(FILE *in, FILE *out, FILE *err) { + char lineBuf[LINEBUFLEN]; + size_t lineLen; + int argc; + char **argv; + DebugContext debugContext; + uio_bool interactive; + + memset(&debugContext, '\0', sizeof (DebugContext)); + debugContext.exit = false; + debugContext.in = in; + debugContext.out = out; + debugContext.err = err; + debugContext.cwd = uio_openDir(repository, "/", 0); + if (debugContext.cwd == NULL) { + fprintf(err, "Fatal: Could not open working dir.\n"); + abort(); + } + + interactive = isatty(fileno(in)); + do { + if (interactive) + fprintf(out, "> "); + if (fgets(lineBuf, LINEBUFLEN, in) == NULL) { + if (feof(in)) { + // user pressed ^D + break; + } + // error occured + clearerr(in); + continue; + } + lineLen = strlen(lineBuf); + if (lineBuf[lineLen - 1] != '\n' && lineBuf[lineLen - 1] != '\r') { + fprintf(err, "Too long command line.\n"); + // TODO: read until EOL + continue; + } + makeArgs(lineBuf, &argc, &argv); + if (argc == 0) { + uio_free(argv); + continue; + } + debugCallCommand(&debugContext, argc, argv); + uio_free(argv); + } while (!debugContext.exit); + if (interactive) + fprintf(out, "\n"); + uio_closeDir(debugContext.cwd); +} + +static void +makeArgs(char *lineBuf, int *argc, char ***argv) { + int numArg; + char **args; + char *ptr; + + numArg = 0; + ptr = lineBuf; + while(true) { + while (isspace((int) *ptr)) + ptr++; + if (*ptr == '\0') + break; + numArg++; + while (!isspace((int) *ptr)) + ptr++; + } + + args = uio_malloc((numArg + 1) * sizeof (char *)); + numArg = 0; + ptr = lineBuf; + while(true) { + while (isspace((int) *ptr)) + ptr++; + if (*ptr == '\0') + break; + args[numArg] = ptr; + numArg++; + while (!isspace((int) *ptr)) + ptr++; + if (*ptr == '\0') + break; + *ptr = '\0'; + ptr++; + } + args[numArg] = NULL; + *argv = args; + *argc = numArg; +} + +static int +debugCallCommand(DebugContext *debugContext, int argc, char *argv[]) { + int i; + int numDebugCommands; + + i = 0; + numDebugCommands = sizeof debugCommands / sizeof debugCommands[0]; + // could be improved with binary search + while(1) { + if (i == numDebugCommands) { + fprintf(debugContext->err, "Invalid command.\n"); + return 1; + } + if (strcmp(argv[0], debugCommands[i].name) == 0) + break; + i++; + } + return debugCommands[i].fun(debugContext, argc, argv); +} + +static int +debugCmdCat(DebugContext *debugContext, int argc, char *argv[]) { + uio_Handle *handle; +#define READBUFSIZE 0x10000 + char readBuf[READBUFSIZE]; + char *bufPtr; + ssize_t numInBuf, numWritten; + size_t totalWritten; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + handle = uio_open(debugContext->cwd, argv[1], O_RDONLY +#ifdef WIN32 + | O_BINARY +#endif + , 0); + if (handle == NULL) { + fprintf(debugContext->err, "Could not open file: %s\n", + strerror(errno)); + return 1; + } + + totalWritten = 0; + while (1) { + numInBuf = uio_read(handle, readBuf, READBUFSIZE); + if (numInBuf == -1) { + if (errno == EINTR) + continue; + fprintf(debugContext->err, "Could not read from file: %s\n", + strerror(errno)); + uio_close(handle); + return 1; + } + if (numInBuf == 0) + break; + bufPtr = readBuf; + do { + numWritten = write(fileno(debugContext->out), bufPtr, numInBuf); + if (numWritten == -1) + { + if (errno == EINTR) + continue; + fprintf(debugContext->err, "Could not read from file: %s\n", + strerror(errno)); + uio_close(handle); + } + numInBuf -= numWritten; + bufPtr += numWritten; + totalWritten += numWritten; + } while (numInBuf > 0); + } + fprintf(debugContext->out, "[%u bytes]\n", (unsigned int) totalWritten); + + uio_close(handle); + return 0; +} + +static int +debugCmdCd(DebugContext *debugContext, int argc, char *argv[]) { + uio_DirHandle *newWd; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + newWd = uio_openDirRelative(debugContext->cwd, argv[1], 0); + if (newWd == NULL) { + fprintf(debugContext->err, "Could not access new dir: %s\n", + strerror(errno)); + return 1; + } + uio_closeDir(debugContext->cwd); + debugContext->cwd = newWd; + return 0; +} + +static int +debugCmdExec(DebugContext *debugContext, int argc, char *argv[]) { + int i; + const char **newArgs; + int errCode = 0; + uio_StdioAccessHandle **handles; + uio_DirHandle *tempDir; + + if (argc < 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + tempDir = uio_openDirRelative(debugContext->cwd, "/tmp", 0); + if (tempDir == 0) { + fprintf(debugContext->err, "Could not open temp dir: %s.\n", + strerror(errno)); + return 1; + } + + newArgs = uio_malloc(argc * sizeof (char *)); + newArgs[0] = argv[1]; + handles = uio_malloc(argc * sizeof (uio_StdioAccessHandle *)); + handles[0] = NULL; + + for (i = 2; i < argc; i++) { +#if 0 + if (argv[i][0] == '-') { + // Don't try to parse arguments that start with '-'. + // They are probably option flags. + newArgs[i - 1] = argv[i]; + } +#endif + handles[i - 1] = uio_getStdioAccess(debugContext->cwd, argv[i], + O_RDONLY, tempDir); + if (handles[i - 1] == NULL) { + if (errno == ENOENT) { + // No match; we keep what's typed literally. + newArgs[i - 1] = argv[i]; + continue; + } + + // error + fprintf(debugContext->err, + "Cannot execute: Cannot get stdio access to %s: %s.\n", + argv[i], strerror(errno)); + errCode = 1; + argc = i + 1; + goto err; + } + + newArgs[i - 1] = uio_StdioAccessHandle_getPath(handles[i - 1]); + } + newArgs[argc - 1] = NULL; + + fprintf(debugContext->err, "Executing: %s", newArgs[0]); + for (i = 1; i < argc - 1; i++) + fprintf(debugContext->err, " %s", newArgs[i]); + fprintf(debugContext->err, "\n"); + +#if defined(__unix__) && !defined(_WIN32_WCE) + { + pid_t pid; + + pid = fork(); + switch (pid) { + case -1: + fprintf(debugContext->err, "Error: fork() failed: %s.\n", + strerror(errno)); + break; + case 0: + // child + execvp(newArgs[0], (char * const *) newArgs); + fprintf(debugContext->err, "Error: execvp() failed: %s.\n", + strerror(errno)); + _exit(EXIT_FAILURE); + break; + default: { + // parent + int status; + pid_t retVal; + + while (1) { + retVal = waitpid(pid, &status, 0); + if (retVal != -1) + break; + if (errno != EINTR) { + fprintf(debugContext->err, "Error: waitpid() " + "failed: %s\n", strerror(errno)); + break; + } + } + if (retVal == -1) + break; + + if (WIFEXITED(status)) { + fprintf(debugContext->err, "Exit status: %d\n", + WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + fprintf(debugContext->err, "Terminated on signal %d.\n", + WTERMSIG(status)); + } else { + fprintf(debugContext->err, "Error: weird exit status.\n"); + } + break; + } + } + } +#else + fprintf(debugContext->err, "Cannot execute: not supported on this " + "platform.\n"); +#endif + +err: + for (i = 1; i < argc - 1; i++) { + if (handles[i] != NULL) + uio_releaseStdioAccess(handles[i]); + } + uio_free(handles); + uio_free((void *) newArgs); + uio_closeDir(tempDir); + + return errCode; +} + +static int +debugCmdExit(DebugContext *debugContext, int argc, char *argv[]) { + debugContext->exit = true; + (void) argc; + (void) argv; + return 0; +} + +static int +debugCmdFwriteTest(DebugContext *debugContext, int argc, char *argv[]) { + uio_Stream *stream; + const char testString[] = "0123456789\n"; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + stream = uio_fopen(debugContext->cwd, argv[1], "w+b"); + if (stream == NULL) { + fprintf(debugContext->err, "Could not open file: %s\n", + strerror(errno)); + goto err; + } + + if (uio_fwrite(testString, strlen(testString), 1, stream) != 1) { + fprintf(debugContext->err, "uio_fwrite() failed: %s\n", + strerror(errno)); + goto err; + } + if (uio_fputs(testString, stream) == EOF) { + fprintf(debugContext->err, "uio_fputs() failed: %s\n", + strerror(errno)); + goto err; + } + if (uio_fseek(stream, 15, SEEK_SET) != 0) { + fprintf(debugContext->err, "uio_fseek() failed: %s\n", + strerror(errno)); + goto err; + } + if (uio_fputc('A', stream) != 'A') { + fprintf(debugContext->err, "uio_fputc() failed: %s\n", + strerror(errno)); + goto err; + } + if (uio_fseek(stream, 0, SEEK_SET) != 0) { + fprintf(debugContext->err, "uio_fseek() failed: %s\n", + strerror(errno)); + goto err; + } + { + char buf[6]; + char *ptr; + int i; + i = 1; + while (1) { + ptr = uio_fgets(buf, sizeof buf, stream); + if (ptr == NULL) + break; + fprintf(debugContext->out, "%d: [%s]\n", i, ptr); + i++; + } + if (uio_ferror(stream)) { + fprintf(debugContext->err, "uio_fgets() failed: %s\n", + strerror(errno)); + goto err; + } + uio_clearerr(stream); + } + if (uio_fseek(stream, 4, SEEK_END) != 0) { + fprintf(debugContext->err, "uio_fseek() failed: %s\n", + strerror(errno)); + goto err; + } + { + char buf[2000]; + memset(buf, 'Q', sizeof buf); + if (uio_fwrite(buf, 100, 20, stream) != 20) { + fprintf(debugContext->err, "uio_fwrite() failed: %s\n", + strerror(errno)); + goto err; + } + } + if (uio_fseek(stream, 5, SEEK_SET) != 0) { + fprintf(debugContext->err, "uio_fseek() failed: %s\n", + strerror(errno)); + goto err; + } + if (uio_fputc('B', stream) != 'B') { + fprintf(debugContext->err, "uio_fputc() failed: %s\n", + strerror(errno)); + goto err; + } + uio_fclose(stream); + return 0; +err: + uio_fclose(stream); + return 1; +} + +static int +listOneDir(DebugContext *debugContext, const char *arg) { + uio_DirList *dirList; + int i; + const char *pattern; + const char *cpath; + char *buf = NULL; + + if (arg[0] == '\0') { + cpath = arg; + pattern = "*"; + } else { + pattern = strrchr(arg, '/'); + if (pattern == NULL) { + // No directory component in 'arg'. + cpath = ""; + pattern = arg; + } else if (pattern[1] == '\0') { + // 'arg' ends on / + cpath = arg; + pattern = "*"; + } else { + if (pattern == arg) { + cpath = "/"; + } else { + buf = uio_malloc(pattern - arg + 1); + memcpy(buf, arg, pattern - arg); + buf[pattern - arg] = '\0'; + cpath = buf; + } + pattern++; + } + } +#ifdef HAVE_GLOB + dirList = uio_getDirList(debugContext->cwd, cpath, pattern, + match_MATCH_GLOB); +#else + if (pattern[0] == '*' && pattern[1] == '\0') { + dirList = uio_getDirList(debugContext->cwd, cpath, "", + match_MATCH_PREFIX); + } else { + dirList = uio_getDirList(debugContext->cwd, cpath, pattern, + match_MATCH_LITERAL); + } +#endif + if (dirList == NULL) { + fprintf(debugContext->out, "Error in uio_getDirList(): %s.\n", + strerror(errno)); + if (buf != NULL) + uio_free(buf); + return 1; + } + for (i = 0; i < dirList->numNames; i++) + fprintf(debugContext->out, "%s\n", dirList->names[i]); + uio_DirList_free(dirList); + if (buf != NULL) + uio_free(buf); + return 0; +} + +static int +debugCmdLs(DebugContext *debugContext, int argc, char *argv[]) { + int argI; + int retVal; + + if (argc == 1) + return listOneDir(debugContext, unconst("")); + + for (argI = 1; argI < argc; argI++) { + retVal = listOneDir(debugContext, argv[argI]); + if (retVal != 0) + return retVal; + } + + return 0; +} + +static int +debugCmdMem(DebugContext *debugContext, int argc, char *argv[]) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_printPointers(debugContext->out); +#else + fprintf(debugContext->out, "Memory debugging not compiled in.\n"); +#endif + (void) argc; + (void) argv; + return 0; +} + +static int +debugCmdMkDir(DebugContext *debugContext, int argc, char *argv[]) { + int retVal; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + retVal = uio_mkdir(debugContext->cwd, argv[1], 0777); + if (retVal == -1) { + fprintf(debugContext->err, "Could not create directory: %s\n", + strerror(errno)); + return 1; + } + return 0; +} + +static int +debugCmdMount(DebugContext *debugContext, int argc, char *argv[]) { + if (argc == 1) { + uio_printMounts(debugContext->out, repository); +// uio_printMountTree(debugContext->out, repository->mountTree, 0); + } + (void) argv; + return 0; +} + +static int +debugCmdMv(DebugContext *debugContext, int argc, char *argv[]) { + int retVal; + + if (argc != 3) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + retVal = uio_rename(debugContext->cwd, argv[1], + debugContext->cwd, argv[2]); + if (retVal == -1) { + fprintf(debugContext->err, "Could not rename: %s\n", strerror(errno)); + return 1; + } + return 0; +} + +static int +debugCmdPwd(DebugContext *debugContext, int argc, char *argv[]) { + uio_DirHandle_print(debugContext->cwd, debugContext->out); + (void) argc; + (void) argv; + return 0; +} + +static int +debugCmdRm(DebugContext *debugContext, int argc, char *argv[]) { + int retVal; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + retVal = uio_unlink(debugContext->cwd, argv[1]); + if (retVal == -1) { + fprintf(debugContext->err, "Could not remove file: %s\n", + strerror(errno)); + return 1; + } + return 0; +} + +static int +debugCmdRmDir(DebugContext *debugContext, int argc, char *argv[]) { + int retVal; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + retVal = uio_rmdir(debugContext->cwd, argv[1]); + if (retVal == -1) { + fprintf(debugContext->err, "Could not remove directory: %s\n", + strerror(errno)); + return 1; + } + return 0; +} + +static int +debugCmdStat(DebugContext *debugContext, int argc, char *argv[]) { + struct stat statBuf; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + if (uio_stat(debugContext->cwd, argv[1], &statBuf) == -1) { + // errno is set + int savedErrno; + savedErrno = errno; + fprintf(debugContext->err, "Could not stat file: %s\n", + strerror(errno)); + errno = savedErrno; + return 1; + } + + fprintf(debugContext->out, + "size %ld bytes\n" + "uid %d gid %d mode 0%o\n", + (unsigned long) statBuf.st_size, + (unsigned int) statBuf.st_uid, + (unsigned int) statBuf.st_gid, + (unsigned int) statBuf.st_mode & + (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID)); + // Can't do these next three in one fprintf, as ctime uses a static buffer + // that is overwritten with each call. + fprintf(debugContext->out, + "last access: %s", ctime(&statBuf.st_atime)); + fprintf(debugContext->out, + "last modification: %s", ctime(&statBuf.st_mtime)); + fprintf(debugContext->out, + "last status change: %s", ctime(&statBuf.st_ctime)); + + return 0; +} + +static int +debugCmdWriteTest(DebugContext *debugContext, int argc, char *argv[]) { + uio_Handle *handle; + const char testString[] = "Hello world!\n"; + + if (argc != 2) { + fprintf(debugContext->err, "Invalid number of arguments.\n"); + return 1; + } + + handle = uio_open(debugContext->cwd, argv[1], + O_WRONLY | O_CREAT | O_EXCL +#ifdef WIN32 + | O_BINARY +#endif + , 0644); + if (handle == NULL) { + fprintf(debugContext->err, "Could not open file: %s\n", + strerror(errno)); + return 1; + } + + if (uio_write(handle, testString, sizeof testString) == -1) { + fprintf(debugContext->err, "Write failed: %s\n", + strerror(errno)); + return 1; + } + + uio_close(handle); + return 0; +} + + diff --git a/src/libs/uio/debug.h b/src/libs/uio/debug.h new file mode 100644 index 0000000..fd6a3ff --- /dev/null +++ b/src/libs/uio/debug.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 UIO_DEBUG_H +#define UIO_DEBUG_H + +void uio_debugInteractive(FILE *in, FILE *out, FILE *err); + + +#endif /* _DEBUG_H */ + + diff --git a/src/libs/uio/defaultfs.c b/src/libs/uio/defaultfs.c new file mode 100644 index 0000000..bf6d247 --- /dev/null +++ b/src/libs/uio/defaultfs.c @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "defaultfs.h" +#include "uioport.h" + +extern uio_FileSystemHandler stdio_fileSystemHandler; +#ifdef HAVE_ZIP +extern uio_FileSystemHandler zip_fileSystemHandler; +#endif + +const uio_DefaultFileSystemSetup defaultFileSystems[] = { + { uio_FSTYPE_STDIO, "stdio", &stdio_fileSystemHandler }, +#ifdef HAVE_ZIP + { uio_FSTYPE_ZIP, "zip", &zip_fileSystemHandler }, +#endif +}; + +int +uio_numDefaultFileSystems(void) { + return sizeof defaultFileSystems / sizeof defaultFileSystems[0]; +} + + diff --git a/src/libs/uio/defaultfs.h b/src/libs/uio/defaultfs.h new file mode 100644 index 0000000..711ac0c --- /dev/null +++ b/src/libs/uio/defaultfs.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 uio_DEFAULTFS_H +#define uio_DEFAULTFS_H + +typedef struct uio_DefaultFileSystemSetup uio_DefaultFileSystemSetup; + +#include "iointrn.h" +#include "uioport.h" +#include "fstypes.h" + +struct uio_DefaultFileSystemSetup { + uio_FileSystemID id; + const char *name; + uio_FileSystemHandler *handler; +}; + +extern const uio_DefaultFileSystemSetup defaultFileSystems[]; + +int uio_numDefaultFileSystems(void); + +#endif /* uio_DEFAULTFS_H */ + diff --git a/src/libs/uio/doc/basics b/src/libs/uio/doc/basics new file mode 100644 index 0000000..e04afd8 --- /dev/null +++ b/src/libs/uio/doc/basics @@ -0,0 +1,178 @@ +-= Introduction =- + +The file io system I present here provides the user with a virtual file +systems on which normal POSIX-like file operations can be performed. + +A virtual file system consists of a number of underlying filesystems merged +together in one directory structure. +Underlying file systems can be POSIX filesystems, or zip archives, but +others can be added easilly. +Filesystems are grafted into the virtual filesystem either explicitely, by +mounting, or implicitely, by having files of a specific type (for instance +those ending on .zip) mounted automatically. +When a filesystem is mounted to a directory which already exists in an +earlier mounted file system, files in the later mounted file system hide +files in an earlier mounted file system. Files present in a filesystem +mounted earlier that don't exist in a filesystem mounted later will remain +visible. +Accessing compressed files inside compressed files is possible, though slow. + + +-= Nomenclature =- +'repository' +A collection of files from various sources as a virtual file system. + +'physical directory structure' +An underlying filesystem, such as a POSIX filesystem, or a .zip archive. + +'logical directory structure' +The merger of one or more Physical directory structures. + +'mounting' +Grafting a physical directory structure in a logical directory system. + +When mounting several dirs on top of eachother, I refer to later mounted +dirs as 'higher' dirs, and earlier mounted dirs as 'lower' dirs. + +'directory entry' +A file or subdirectory within a directory. + + +-= API =- + +Types: +uio_Repository - A struct describing a repository +uio_Handle - A handle for working on files +uio_DirHandle - A handle for working on directories + + +TODO: functions + + +-= Behaviour relating directory structures =- + +The design of the virtual filesystem is guided by the following rules: +- Combined directories contain the entries of each directory. + If some of the source directories contain an entry with the same name, + the combined directory will contain the entry of the topmost directory. + (this means a directory can hide a file and the other way around) +- Entries hidden in this way are never accessable. +- Where possible, actions on directory entries within a combined directory + are done as in a normal file system. +Because of these, some design decisions have been made: +- New entries are created in the topmost writable filesystem. If the path to + the directory where the entry is to be created does not exist, it is + created [1]. +- When a file is to be opened for writing, and the file in the combined + filesystem exists, but is not writable, and there exists a writable + filesystem, mounted higher than the filesystem where the existing file + resides, the file is first copied to the topmost writable filesystem. + +[1] I could have decided to use a pre-existing writable directory if one + is available. + With this choice, when no such pre-existing directory exists, + it would make sense to complete the path in the writable filesystem + in which the part of the path that does exist is the longest. + As you can't create a directory inside a specific filesystem, + this would complicate and confuse things for the user. + + +In specific, for various actions: + +opening a file: +- if O_RDONLY: + - open the file in the highest dir. +- if O_WRONLY or O_RDWR: + - if file already exists: + - if O_CREAT and O_EXCL: return EEXIST + - if file is writable, use that one + - if file is not writable, copy it to the highest writable location + higher than the location of that file (don't bother if O_TRUNC) and use + the new file. If necessary, create the path leading upto it. + If no such location exists, return EACCESS. + - if file does not exist: + - if not O_CREAT: return ENOENT + - if the path to the file does not exists, return ENOENT. + - find the highest writable dir and open the file there, creating + the path leading upto it if necessary. + +removing a file: +- try removing the specified file from all physical directory structures + for a repository. +- once a file is encountered that can't be removed, return an error for + that and don't try the rest. + +creating a directory: +- as for opening a file with O_WRONLY | O_CREAT | O_EXCL + +removing a directory: +- as for removing a file + (but a physical directory not being empty is a reason for failure) + + +-= Limitations =- + +There's no limit to the length of a path in the logical file system. +Paths in the underlying physical filesystem can be limited though. + +At the moment, the system is not thread safe. Only one thread should access +a repository at once. Seperate threads working on seperate repositories is +no problem, as long as the repositories don't overlap. + + +-= Internals =- + +Types: + +uio_MountTree + A node in a data structure describing the mounted directories. + +uio_PRoot + A struct describing the a physical file system. + +uio_PRootExtra + Filesystem-dependant extra data for a PRoot. + +uio_GPRoot + Generic filesystem-dependant data for a PRoot, used as uio_PRootExtra. + +uio_GPRootExtra + Extra filesystem-dependant data for a PRoot, when using uio_GPRoot for + generic filesystem-dependant data. + +uio_GPDir + Generic structure representing a node in a physical directory structure + describing one directory. + +uio_GPFile + Generic structure describing a file in a physical file system. + + +Helper functions (defined in ioaux.c): + +uio_copyFilePhysical + Copy a file from one physical directory to another. + +uio_getPathPhysicalDirs + Get handles to the (existing) physical dirs that are effective in a + path 'path' relative from 'dirHandle' + +uio_getPhysicalAccess + Find PDirHandle and MountInfo structures for reading and writing for a path + from a DirHandle. + +uio_makePath + Create a directory inside a physical directory. All non-existant + parent directories will be created as well. + +uio_resolvePath + Determine the absolute path given a path relative to a given directory. + +uio_verifyPath + Test whether a path is valid and exists. + +uio_walkPhysicalPath + Follow a path starting from a specified physical dir for as long as + possible. + + diff --git a/src/libs/uio/doc/conventions b/src/libs/uio/doc/conventions new file mode 100644 index 0000000..f156101 --- /dev/null +++ b/src/libs/uio/doc/conventions @@ -0,0 +1,30 @@ +This file describes the various conventions used in the source. + + +-= Naming of constructors and destructors =- + +uio_Thing *uio_Thing_new(args) + Allocates the structure, and initialises it from the arguments. + Arguments are always used as-is; if they are reference-counted, + and the caller still needs a reference, it is up to the caller + to increment the reference counter. +void uio_Thing_delete(uio_Thing *thing) + Frees the structure and all sub-structures. + Decrements reference counters to shared structures. +uio_Thing *uio_Thing_alloc() + Allocates memory for a new structure. + Could be omited for standard allocators. + Particularly relevant when a non-standard allocation scheme is + used (for instance, allocation from a pool of pre-allocated strucures). +void uio_Thing_free(Thing *thing) + Frees the memory allocated for thing (reverse of uio_Thing_alloc). + Could be omited for standard deallocators. +void uio_Thing_ref(Thing *thing) + Increments the reference counter to the structure. +void uio_Thing_unref(Thing *thing) + Decrements the reference counter to the structure. + Calls uio_Thing_Delete() if the refcounter becomes 0. + +These functions are always declared and defined in this order. + + diff --git a/src/libs/uio/doc/todo b/src/libs/uio/doc/todo new file mode 100644 index 0000000..b9f232c --- /dev/null +++ b/src/libs/uio/doc/todo @@ -0,0 +1,144 @@ +Needed for use in UQM: +- documentation +- configuring for GLOB +- when doing uio_getStdioPhysical(), if write access is required, but + not available on the original location, also copy the file to the + temporary dir. +- Call fsync() at appropriate times. + +Documentation: +- use doxygen +- Warning: getting (part of) the contents of a directory is fairly slow, + as dirs need to be merged. +- It would be (theoretically) possible to add HTTP and FTP support for + remote file systems. +- on adding extra file system types: + open, mkdir, rmdir, and unlink should themselves make sure that + the physical structure is kept up to date when an entry is + removed or added. +- stream stuff is not thread safe +- uio_fflush() does not accept NULL as argument to flush all streams, + like stdio fflush() does. +- uio_close() does never fail +- The physical open function should call uio_Handle_new and store its own + data in it. + The physical close function should delete its own data. It's called + Will cleanup the general Handle. + Analogous for mount/unmount with PRoot +- No need to store MountHandles. They will be automatically freed + when the repository is closed. +- You can use mount stuff from other repositories. +- ".." works by nullifying one path component, not by following the ".." link + in the directory. "/a/../b" will always be functionally equivalent to + "/b", even when "/a" is a symlink. + +Testing: +- Test mounting an UNC directory + +Bugs: +- 'openDir(repository, "dir/")' will have the trailing '/' + in the dirHandle, which will cause problems. +- 'openDirRelative(repository, "/")' causes segfaults later on +- uio_rename() doesn't work on directories +- uio_getPhysicalAccess() needs to be changed so that it works the same on + dirs as on files. stat() can be cleaned up too then. +- uio_getPhysicalAccess() will not return ENOENT when (only) the last + component does not exist, even when O_RDONLY is used. + For O_RDRW, O_CREAT should probably be checked too (function description + needs to be updated too then). +- A lot of inlining is not possible because of the order of function + definitions. +- sizeof(size_t) may be less than 4 on some platforms. Check what problems + this may cause. At least the size of zip_INPUT_BUFFER_SIZE is an + issue. SIZE_MAX can be used to check for sizeof(size_t) at runtime. +- remove() is probably more compatible than unlink(). remove() is part + of the C standard (as well as POSIX), unlink() is just POSIX. +- seeking in files opened as "text" on Windows goes wrong. +- Network paths on Windows are not accepted. +- No CRLF translation (and ^Z recognition) is done for files read from + zip files, even though that may be expected on Windows. +- The order in which "uio_unmountAllDirs() unmounts the directories, may lead + it to unmount a dir while there still is a file descriptor for another + dir open, causing a warning. + +Extra features (not necessary for UQM): +- Make functions to use for uio_malloc, uio_free and uio_realloc + configurable at init. +- add uio_mmap() +- add match_MATCH_ALL and match_MATCH_NONE +- automounting + - Unmount automounted filesystems when the originating filesystem + is unmounted. +- when doing stat() on a directory, merge the stat info of the underlying + physical dirs. +- read directory information from zip files. +- implement ungetc() +- make fileblocks public +- setmode() for windows? +- prevent aliasing of files/dirs, both between two physical file + systems (where possible), as within the same filesystem (in particular + stdio). On Windows, aliasing can occur easilly when a file or dir + is accessed with the long vs the short ("PROGRA~1") name, or when + capitalisation is involved. +- accept non-dir, non-regular-file dir entries in stdio. +- Add non-merging mounts. Make 'merging' an option for the mount. +- make uio_rename() work cross-fs, or provide a wrapper which does that. + (the system rename() doesn't work cross-fs either, so keeping uio_rename() + as it is makes sense, as I'm trying to stay close to the system functions, + even though hiding file systems from the user would be nicer) +- Add a readdir_r(). Right now, for Symbian readdir_r() is defined in + uioport.h, but the actual implementation is out of the uio tree. + +Optimisations (not necessary for UQM): +- use mmap for fileBlocks +- Use a pre-allocated pool of hash table entries for allocHashEntry. +- optimise certain strings (specifically directory entries) by making + a string type which has pointers to a shared char array. + Invariant: if two strings are the same, they point to the same + character array. Consequence: string comparison is pointer comparison. + Making a new string would imply checking the existing strings. + Existing strings should be in a hash table. +- optimise paths + Encode all paths internally as (double-linked) chains of path components + This way, operations like going up one path component, are cheap. + Each path component could either be represented by a pointer to a string, + (possibly shared as above), or as a pair of begin and end pointers + (or begin pointer and length). In the latter case, there's no need for + copying strings when parsing a user-supplied string, but sharing of strings + would not be possible. + It's probably possible to use both methods next to eachother; + shared strings for stored paths, and pointers into a path for user-supplied + strings (which would be freed when the call returns). + Instead of a linked list, perhaps an array can be used, as paths rarely + change. It would be a problem if for some reason, recursively path + components are added to a path. +- (maybe) keep track of already issued handles, so that the ref counter + could just be incremented, instead of issuing a new one. + (uio_getPDirEntryHandle) +- Make it possible for people to add their own file system, without giving + away the internals. (no wanton including of .h files) +- Don't cache stdio dir structure + (and don't read the dir leading up to the dir that is actually needed) +- mounting foo to /Foo and foo/bar to /Foo/Bar should result in only one + physical structure. Preferably, it shouldn't even lead to two mountInfo + structures for /Foo/Bar, so that merging the dirs is not unnecessarilly + expensive. +- add uio_access() + (uqm fileExists can be redone then) +- implement the physical function cleanup() to clean up the physical + structure. Int argument that specifies how thoroughly. + On it depends whether cache is cleaned, whether stuff is realloc'ed + to defragment memory, etc. + +Cleanups (not necessary for UQM): +- rename function names to be more like class names for as far as that's + not already done. + uio_PRoot_unref etc +- Add uio_fatal(), uio_error(), uio_warning() +- Clean up the include structure +- use stdint.h and stdbool.h types directly, instead of using uio_int16 etc. + Remove types.h, and instead, if these types are missing on some platforms, + put the fixes in port.h. + + + diff --git a/src/libs/uio/fileblock.c b/src/libs/uio/fileblock.c new file mode 100644 index 0000000..bb4f466 --- /dev/null +++ b/src/libs/uio/fileblock.c @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 uio_INTERNAL_FILEBLOCK + +#include "iointrn.h" +#include "fileblock.h" +#include "uioport.h" + +#include + +static uio_FileBlock *uio_FileBlock_new(uio_Handle *handle, int flags, + off_t offset, size_t blockSize, char *buffer, size_t bufSize, + off_t bufOffset, size_t bufFill, size_t readAheadBufSize); +static inline uio_FileBlock *uio_FileBlock_alloc(void); +static void uio_FileBlock_free(uio_FileBlock *block); + +// caller should uio_Handle_ref(handle) (unless it doesn't need it's own +// reference anymore). +static uio_FileBlock * +uio_FileBlock_new(uio_Handle *handle, int flags, off_t offset, + size_t blockSize, char *buffer, size_t bufSize, off_t bufOffset, + size_t bufFill, size_t readAheadBufSize) { + uio_FileBlock *result; + + result = uio_FileBlock_alloc(); + result->handle = handle; + result->flags = flags; + result->offset = offset; + result->blockSize = blockSize; + result->buffer = buffer; + result->bufSize = bufSize; + result->bufOffset = bufOffset; + result->bufFill = bufFill; + result->readAheadBufSize = readAheadBufSize; + return result; +} + +static inline uio_FileBlock * +uio_FileBlock_alloc(void) { + return uio_malloc(sizeof (uio_FileBlock)); +} + +static inline void +uio_FileBlock_free(uio_FileBlock *block) { + uio_free(block); +} + +uio_FileBlock * +uio_openFileBlock(uio_Handle *handle) { + // TODO: if mmap support is available, and it is available natively + // for the filesystem (make some sort of sysctl for filesystems + // to check this?), use mmap. + // mmap the entire file if it's small enough. + // N.B. Keep in mind streams of which the size is not known in + // advance. + struct stat statBuf; + if (uio_fstat(handle, &statBuf) == -1) { + // errno is set + return NULL; + } + uio_Handle_ref(handle); + return uio_FileBlock_new(handle, 0, 0, statBuf.st_size, NULL, 0, 0, 0, 0); +} + +uio_FileBlock * +uio_openFileBlock2(uio_Handle *handle, off_t offset, size_t size) { + // TODO: mmap (see uio_openFileBlock) + + // TODO: check if offset and size are acceptable. + // Need to handle streams of which the size is unknown. +#if 0 + if (uio_stat(handle, &statBuf) == -1) { + // errno is set + return NULL; + } + if (statBuf.st_size > offset || (statBuf.st_size - offset > size)) { + // NOT: 'if (statBuf.st_size > offset + size)', to protect + // against overflow. + + } +#endif + uio_Handle_ref(handle); + return uio_FileBlock_new(handle, 0, offset, size, NULL, 0, 0, 0, 0); +} + +static inline ssize_t +uio_accessFileBlockMmap(uio_FileBlock *block, off_t offset, size_t length, + char **buffer) { + // TODO + errno = ENOSYS; + (void) block; + (void) offset; + (void) length; + (void) buffer; + return -1; +} + +static inline ssize_t +uio_accessFileBlockNoMmap(uio_FileBlock *block, off_t offset, size_t length, + char **buffer) { + ssize_t numRead; + off_t start; + off_t end; + size_t bufSize; + char *oldBuffer; + //size_t oldBufSize; + + // Don't go beyond the end of the block. + if (offset > (off_t) block->blockSize) { + *buffer = block->buffer; + return 0; + } + if (length > block->blockSize - offset) + length = block->blockSize - offset; + + if (block->buffer != NULL) { + // Check whether the requested data is already in the buffer. + if (offset >= block->bufOffset && + (offset - block->bufOffset) + length < block->bufFill) { + *buffer = block->buffer + (offset - block->bufOffset); + return length; + } + } + + if (length < block->readAheadBufSize && + (block->flags & uio_FB_USAGE_MASK) != 0) { + // We can buffer more data. + switch (block->flags & uio_FB_USAGE_MASK) { + case uio_FB_USAGE_FORWARD: + // Read extra data after the requested data. + start = offset; + end = (block->readAheadBufSize > block->blockSize - offset) ? + block->blockSize : offset + block->readAheadBufSize; + break; + case uio_FB_USAGE_BACKWARD: + // Read extra data before the requested data. + end = offset + length; + start = (end <= (off_t) block->blockSize) ? + 0 : end - block->bufSize; + break; + case uio_FB_USAGE_FORWARD | uio_FB_USAGE_BACKWARD: { + // Read extra data both before and after the requested data. + off_t extraBefore = (block->readAheadBufSize - length) / 2; + start = (offset < extraBefore) ? 0 : offset - extraBefore; + + end = (block->readAheadBufSize > block->blockSize - start) ? + block->blockSize : start + block->readAheadBufSize; + break; + } + } + } else { + start = offset; + end = offset + length; + } + bufSize = (length > block->readAheadBufSize) ? + length : block->readAheadBufSize; + + // Start contains the start index in the block of the data we're going + // to read. + // End contains the end index. + // bufSize contains the size of the buffer. bufSize >= end - start. + + oldBuffer = block->buffer; + //oldBufSize = block->bufSize; + if (block->buffer != NULL || block->bufSize != bufSize) { + // We don't have a buffer, or we have one, but of the wrong size. + block->buffer = uio_malloc(bufSize); + block->bufSize = bufSize; + } + + if (oldBuffer != NULL) { + // TODO: If we have part of the data still in the old buffer, we + // can keep that. + // memmove(...) + + if (oldBuffer != block->buffer) + uio_free(oldBuffer); + } + block->bufFill = 0; + block->bufOffset = start; + + // TODO: lock handle + if (uio_lseek(block->handle, block->offset + start, SEEK_SET) == + (off_t) -1) { + // errno is set + return -1; + } + + numRead = uio_read(block->handle, block->buffer, end - start); + if (numRead == -1) { + // errno is set + // TODO: unlock handle + return -1; + } + // TODO: unlock handle + + block->bufFill = numRead; + *buffer = block->buffer + (offset - block->bufOffset); + if (numRead <= (offset - block->bufOffset)) + return 0; + if ((size_t) numRead >= length) + return length; + return numRead - offset; +} + +// block remains usable until the next call to uio_accessFileBlock +// with the same block as argument, or until uio_closeFileBlock with +// that block as argument. +// The 'offset' parameter is wrt. the start of the block. +// Requesting access to data beyond the file block is not an error. The +// FileBlock is meant to be used as a replacement of seek() and read(), and +// as with those functions, trying to go beyond the end of a file just +// goes to the end. The return value is the number of bytes in the buffer. +ssize_t +uio_accessFileBlock(uio_FileBlock *block, off_t offset, size_t length, + char **buffer) { + if (block->flags & uio_FB_USE_MMAP) { + return uio_accessFileBlockMmap(block, offset, length, buffer); + } else { + return uio_accessFileBlockNoMmap(block, offset, length, buffer); + } +} + +int +uio_copyFileBlock(uio_FileBlock *block, off_t offset, char *buffer, + size_t length) { + if (block->flags & uio_FB_USE_MMAP) { + // TODO + errno = ENOSYS; + return -1; + } else { + ssize_t numCopied = 0; + ssize_t readResult; + + // Don't go beyond the end of the block. + if (offset > (off_t) block->blockSize) + return 0; + if (length > block->blockSize - offset) + length = block->blockSize - offset; + + // Check whether (part of) the requested data is already in our + // own buffer. + if (block->buffer != NULL && offset >= block->bufOffset + && offset < block->bufOffset + (off_t) block->bufFill) { + size_t toCopy = block->bufFill - offset; + if (toCopy > length) + toCopy = length; + memcpy(buffer, block->buffer + (offset - block->bufOffset), + toCopy); + numCopied += toCopy; + length -= toCopy; + if (length == 0) + return numCopied; + buffer += toCopy; + offset += toCopy; + } + + // TODO: lock handle + if (uio_lseek(block->handle, block->offset + offset, SEEK_SET) == + (off_t) -1) { + // errno is set + return -1; + } + + readResult = uio_read(block->handle, buffer, length); + // TODO: unlock handle + if (readResult == -1) { + // errno is set + return -1; + } + numCopied += readResult; + + return numCopied; + } +} + +int +uio_closeFileBlock(uio_FileBlock *block) { + if (block->flags & uio_FB_USE_MMAP) { +#if 0 + if (block->buffer != NULL) + uio_mmunmap(block->buffer); +#endif + } else { + if (block->buffer != NULL) + uio_free(block->buffer); + } + uio_Handle_unref(block->handle); + uio_FileBlock_free(block); + return 0; +} + +// Usage is the or'ed value of zero or more of uio_FB_USAGE_FORWARD, +// and uio_FB_USAGE_BACKWARD. +void +uio_setFileBlockUsageHint(uio_FileBlock *block, int usage, + size_t readAheadBufSize) { + block->flags = (block->flags & ~uio_FB_USAGE_MASK) | + (usage & uio_FB_USAGE_MASK); + block->readAheadBufSize = readAheadBufSize; +} + +// Call if you want the memory used by the fileblock to be released, but +// still want to use the fileblock later. If you don't need the fileblock, +// call uio_closeFileBlock() instead. +void +uio_clearFileBlockBuffers(uio_FileBlock *block) { + if (block->buffer != NULL) { + uio_free(block->buffer); + block->buffer = NULL; + } +} + + diff --git a/src/libs/uio/fileblock.h b/src/libs/uio/fileblock.h new file mode 100644 index 0000000..97a81e1 --- /dev/null +++ b/src/libs/uio/fileblock.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_FILEBLOCK_H_ +#define LIBS_UIO_FILEBLOCK_H_ + +typedef struct uio_FileBlock uio_FileBlock; + +#include "io.h" +#include "uioport.h" + +#include + +#define uio_FB_USAGE_FORWARD 1 +#define uio_FB_USAGE_BACKWARD 2 +#define uio_FB_USAGE_MASK (uio_FB_USAGE_FORWARD | uio_FB_USAGE_BACKWARD) + +#ifdef uio_INTERNAL_FILEBLOCK + +// A fileblock represents a contiguous block of data from a file. +// It's purpose is to avoid needless copying of data, while enabling +// buffering. + +struct uio_FileBlock { + uio_Handle *handle; + int flags; + // See above for uio_FB_USAGE_FORWARD, uio_FB_USAGE_BACKWARD. +#define uio_FB_USE_MMAP 4 + off_t offset; + // Offset to the start of the block in the file. + size_t blockSize; + // Size of the block of data represented by this FileBlock. + char *buffer; + // either allocated buffer, or buffer to mmap'ed area. + size_t bufSize; + // Size of the buffer. + off_t bufOffset; + // Offset of the start of the buffer into the block. + size_t bufFill; + // Part of 'buffer' which is in use. + size_t readAheadBufSize; + // Try to read up to this many bytes at a time, even when less + // is immediately needed. +}; +// INV: The FileBlock represents 'fileData[offset..(offset + blockSize - 1)]' +// where 'fileData' is the contents of the file. +// INV: If buf != NULL then: +// bufFill <= bufSize +// bufFill <= blockSize +// buffer[0..bufFill - 1] == fileData[ +// (offset + bufOffset)..(offset + bufOffset + bufFill - 1)] + + +#endif /* uio_INTERNAL_FILEBLOCK */ + +uio_FileBlock *uio_openFileBlock(uio_Handle *handle); +uio_FileBlock *uio_openFileBlock2(uio_Handle *handle, off_t offset, + size_t size); +ssize_t uio_accessFileBlock(uio_FileBlock *block, off_t offset, size_t length, + char **buffer); +int uio_copyFileBlock(uio_FileBlock *block, off_t offset, char *buffer, + size_t length); +int uio_closeFileBlock(uio_FileBlock *block); +#define uio_FB_READAHEAD_BUFSIZE_MAX ((size_t) -1) +void uio_setFileBlockUsageHint(uio_FileBlock *block, int usage, + size_t readAheadBufSize); +void uio_clearFileBlockBuffers(uio_FileBlock *block); + +#endif /* LIBS_UIO_FILEBLOCK_H_ */ + + diff --git a/src/libs/uio/fstypes.c b/src/libs/uio/fstypes.c new file mode 100644 index 0000000..d940e8f --- /dev/null +++ b/src/libs/uio/fstypes.c @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include + +#include "iointrn.h" +#include "uioport.h" +#include "fstypes.h" +#include "mem.h" +#include "defaultfs.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + + +static uio_bool uio_validFileSystemHandler(uio_FileSystemHandler *handler); +static uio_FileSystemInfo *uio_FileSystemInfo_new(uio_FileSystemID id, + uio_FileSystemHandler *handler, char *name); +static uio_FileSystemInfo **uio_getFileSystemInfoPtr(uio_FileSystemID id); + +static inline uio_FileSystemInfo *uio_FileSystemInfo_alloc(void); + +static inline void uio_FileSystemInfo_free( + uio_FileSystemInfo *fileSystemInfo); + + +uio_FileSystemInfo *uio_fileSystems = NULL; + // list sorted by id + + + +void +uio_registerDefaultFileSystems(void) { + int i; + int num; + uio_FileSystemID registerResult; + + num = uio_numDefaultFileSystems(); + for (i = 0; i < num; i++) { + registerResult = uio_registerFileSystem( + defaultFileSystems[i].id, + defaultFileSystems[i].name, + defaultFileSystems[i].handler); + switch (registerResult) { + case 0: + fprintf(stderr, "Warning: Default file system '%s' is " + "already registered.\n", + defaultFileSystems[i].name); + break; + case -1: + fprintf(stderr, "Error: Could not register '%s' file \n" + "system: %s\n", defaultFileSystems[i].name, + strerror(errno)); + break; + default: + assert(registerResult == defaultFileSystems[i].id); + break; + } + } +} + +void +uio_unRegisterDefaultFileSystems(void) { + int i; + int num; + + num = uio_numDefaultFileSystems(); + for (i = 0; i < num; i++) { + if (uio_unRegisterFileSystem(defaultFileSystems[i].id) == -1) { + fprintf(stderr, "Could not unregister '%s' file system: %s\n", + defaultFileSystems[i].name, strerror(errno)); + } + } +} + +// if wantedID = 0, just pick one +// if wantedID != 0, 0 will be returned if that id wasn't available +// a copy of 'name' is made +uio_FileSystemID +uio_registerFileSystem(uio_FileSystemID wantedID, const char *name, + uio_FileSystemHandler *handler) { + uio_FileSystemInfo **ptr; + + if (!uio_validFileSystemHandler(handler)) { + errno = EINVAL; + return -1; + } + if (wantedID == 0) { + // Search for the first free id >= uio_FIRST_CUSTOM_ID + // it is put in wantedID + + for (ptr = &uio_fileSystems; *ptr != NULL; ptr = &(*ptr)->next) + if ((*ptr)->id >= uio_FS_FIRST_CUSTOM_ID) + break; + + wantedID = uio_FS_FIRST_CUSTOM_ID; + while (*ptr != NULL) { + if ((*ptr)->id != wantedID) { + // wantedID is not in use + break; + } + wantedID++; + ptr = &(*ptr)->next; + } + // wantedID contains the new ID + } else { + // search for the place in the list where to insert the wanted + // id, keeping the list sorted + for (ptr = &uio_fileSystems; *ptr != NULL; ptr = &(*ptr)->next) { + if ((*ptr)->id <= wantedID) { + if ((*ptr)->id == wantedID) + return 0; + break; + } + } + + } + // ptr points to the place where the new link can inserted + + if (handler->init != NULL && handler->init() == -1) { + // errno is set + return -1; + } + + { + uio_FileSystemInfo *newInfo; + + newInfo = uio_FileSystemInfo_new(wantedID, handler, uio_strdup(name)); + newInfo->next = *ptr; + *ptr = newInfo; + return wantedID; + } +} + +int +uio_unRegisterFileSystem(uio_FileSystemID id) { + uio_FileSystemInfo **ptr; + uio_FileSystemInfo *temp; + + ptr = uio_getFileSystemInfoPtr(id); + if (ptr == NULL) { + errno = EINVAL; + return -1; + } + if ((*ptr)->ref > 1) { + errno = EBUSY; + return -1; + } + + if ((*ptr)->handler->unInit != NULL && + ((*ptr)->handler->unInit() == -1)) { + // errno is set + return -1; + } + + temp = *ptr; + *ptr = (*ptr)->next; + +// uio_FileSystemHandler_unref(temp->handler); + uio_free(temp->name); + uio_FileSystemInfo_free(temp); + + return 0; +} + +static uio_bool +uio_validFileSystemHandler(uio_FileSystemHandler *handler) { + // Check for the essentials + if (handler->mount == NULL || + handler->umount == NULL || + handler->open == NULL || + handler->close == NULL || + handler->read == NULL || + handler->openEntries == NULL || + handler->readEntries == NULL || + handler->closeEntries == NULL) { +#ifdef DEBUG + fprintf(stderr, "Invalid file system handler.\n"); +#endif + return false; + } + return true; +} + +uio_FileSystemHandler * +uio_getFileSystemHandler(uio_FileSystemID id) { + uio_FileSystemInfo *ptr; + + for (ptr = uio_fileSystems; ptr != NULL; ptr = ptr->next) { + if (ptr->id == id) + return ptr->handler; + } + return NULL; +} + +uio_FileSystemInfo * +uio_getFileSystemInfo(uio_FileSystemID id) { + uio_FileSystemInfo *ptr; + + for (ptr = uio_fileSystems; ptr != NULL; ptr = ptr->next) { + if (ptr->id == id) + return ptr; + } + return NULL; +} + +static uio_FileSystemInfo ** +uio_getFileSystemInfoPtr(uio_FileSystemID id) { + uio_FileSystemInfo **ptr; + + for (ptr = &uio_fileSystems; *ptr != NULL; ptr = &(*ptr)->next) { + if ((*ptr)->id == id) + return ptr; + } + return NULL; +} + +// sets ref to 1 +static uio_FileSystemInfo * +uio_FileSystemInfo_new(uio_FileSystemID id, uio_FileSystemHandler *handler, + char *name) { + uio_FileSystemInfo *result; + + result = uio_FileSystemInfo_alloc(); + result->id = id; + result->handler = handler; + result->name = name; + result->ref = 1; + return result; +} + +// *** Allocators *** + +static inline uio_FileSystemInfo * +uio_FileSystemInfo_alloc(void) { + uio_FileSystemInfo *result = uio_malloc(sizeof (uio_FileSystemInfo)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_FileSystemInfo, (void *) result); +#endif + return result; +} + + +// *** Deallocators *** + +static inline void +uio_FileSystemInfo_free(uio_FileSystemInfo *fileSystemInfo) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_FileSystemInfo, (void *) fileSystemInfo); +#endif + uio_free(fileSystemInfo); +} + + diff --git a/src/libs/uio/fstypes.h b/src/libs/uio/fstypes.h new file mode 100644 index 0000000..3ff01a2 --- /dev/null +++ b/src/libs/uio/fstypes.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_FSTYPES_H_ +#define LIBS_UIO_FSTYPES_H_ + +typedef int uio_FileSystemID; +#define uio_FSTYPE_STDIO 1 +#define uio_FSTYPE_ZIP 2 + + +#ifdef uio_INTERNAL + +#define uio_FS_FIRST_CUSTOM_ID 0x10 + +#ifndef uio_INTERNAL_PHYSICAL +typedef void *uio_NativeHandle; +#endif + +// 'forward' declarations +typedef struct uio_FileSystemHandler uio_FileSystemHandler; +typedef struct uio_FileSystemInfo uio_FileSystemInfo; + +#include +#include +#include "physical.h" +#include "uioport.h" + + +/* Structure describing how to access files _in_ a directory of a certain + * type (not files _of_ a certain type.) Except for mount(). + * in open(), the first arg points to the dir where the file should be + * in, and the second arg is the name of that file (no path) + */ +struct uio_FileSystemHandler { + int (*init) (void); + int (*unInit) (void); + void (*cleanup) (uio_PRoot *, int); + // Called to cleanup memory. The second argument specifies + // how thoroughly. + + struct uio_PRoot * (*mount) (uio_Handle *, int); + int (*umount) (uio_PRoot *); + + int (*access) (uio_PDirHandle *, const char *, int mode); + void (*close) (uio_Handle *); + // called when the last reference is closed, not + // necessarilly each time when uio_close() is called + int (*fstat) (uio_Handle *, struct stat *); + int (*stat) (uio_PDirHandle *, const char *, + struct stat *); + uio_PDirHandle * (*mkdir) (uio_PDirHandle *, const char *, mode_t); + uio_Handle * (*open) (uio_PDirHandle *, const char *, int, + mode_t); + ssize_t (*read) (uio_Handle *, void *, size_t); + int (*rename) (uio_PDirHandle *, const char *, + uio_PDirHandle *, const char *); + int (*rmdir) (uio_PDirHandle *, const char *); + off_t (*seek) (uio_Handle *, off_t, int); + ssize_t (*write) (uio_Handle *, const void *, size_t); + int (*unlink) (uio_PDirHandle *, const char *); + + uio_NativeEntriesContext (*openEntries) (uio_PDirHandle *); + int (*readEntries) (uio_NativeEntriesContext *, char *, + size_t); + void (*closeEntries) (uio_NativeEntriesContext); + + uio_PDirEntryHandle * (*getPDirEntryHandle) ( + const uio_PDirHandle *, const char *); + void (*deletePRootExtra) (uio_PRootExtra pRootExtra); + void (*deletePDirHandleExtra) ( + uio_PDirHandleExtra pDirHandleExtra); + void (*deletePFileHandleExtra) ( + uio_PFileHandleExtra pFileHandleExtra); +}; + +struct uio_FileSystemInfo { + int ref; + uio_FileSystemID id; + char *name; // name of the file system + uio_FileSystemHandler *handler; + struct uio_FileSystemInfo *next; +}; + +void uio_registerDefaultFileSystems(void); +void uio_unRegisterDefaultFileSystems(void); +uio_FileSystemID uio_registerFileSystem(uio_FileSystemID wantedID, + const char *name, uio_FileSystemHandler *handler); +int uio_unRegisterFileSystem(uio_FileSystemID id); +uio_FileSystemHandler *uio_getFileSystemHandler(uio_FileSystemID id); +uio_FileSystemInfo *uio_getFileSystemInfo(uio_FileSystemID id); + +#endif /* uio_INTERNAL */ + +#endif /* LIBS_UIO_FSTYPES_H_ */ + diff --git a/src/libs/uio/getint.h b/src/libs/uio/getint.h new file mode 100644 index 0000000..ad6e810 --- /dev/null +++ b/src/libs/uio/getint.h @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2007 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_GETINT_H_ +#define LIBS_UIO_GETINT_H_ + +/* All these functions return true on success, or false on failure */ + +#include "types.h" +#include "uioport.h" + +static inline uio_bool +uio_getU8(uio_Stream *stream, uio_uint8 *result) { + int val = uio_getc(stream); + if (val == EOF) + return false; + + *result = (uio_uint8) val; + return true; +} + +static inline uio_bool +uio_getS8(uio_Stream *stream, uio_sint8 *result) { + int val = uio_getc(stream); + if (val == EOF) + return false; + + *result = (uio_sint8) val; + return true; +} + +static inline uio_bool +uio_getU16LE(uio_Stream *stream, uio_uint16 *result) { + uio_uint8 buf[2]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (buf[1] << 8) | buf[0]; + return true; +} + +static inline uio_bool +uio_getU16BE(uio_Stream *stream, uio_uint16 *result) { + uio_uint8 buf[2]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (buf[0] << 8) | buf[1]; + return true; +} + +static inline uio_bool +uio_getS16LE(uio_Stream *stream, uio_sint16 *result) { + uio_uint8 buf[2]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (uio_sint16) ((buf[1] << 8) | buf[0]); + return true; +} + +static inline uio_bool +uio_getS16BE(uio_Stream *stream, uio_sint16 *result) { + uio_uint8 buf[2]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (uio_sint16) ((buf[0] << 8) | buf[1]); + return true; +} + +static inline uio_bool +uio_getU32LE(uio_Stream *stream, uio_uint32 *result) { + uio_uint8 buf[4]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; + return true; +} + +static inline uio_bool +uio_getU32BE(uio_Stream *stream, uio_uint32 *result) { + uio_uint8 buf[4]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + return true; +} + +static inline uio_bool +uio_getS32LE(uio_Stream *stream, uio_sint32 *result) { + uio_uint8 buf[4]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (uio_sint32) ((buf[3] << 24) | (buf[2] << 16) | + (buf[1] << 8) | buf[0]); + return true; +} + +static inline uio_bool +uio_getS32BE(uio_Stream *stream, uio_sint32 *result) { + uio_uint8 buf[4]; + + if (uio_fread(buf, sizeof buf, 1, stream) != 1) + return false; + + *result = (uio_sint32) ((buf[0] << 24) | (buf[1] << 16) | + (buf[2] << 8) | buf[3]); + return true; +} + + +#endif /* LIBS_UIO_GETINT_H_ */ + diff --git a/src/libs/uio/gphys.c b/src/libs/uio/gphys.c new file mode 100644 index 0000000..f25f557 --- /dev/null +++ b/src/libs/uio/gphys.c @@ -0,0 +1,620 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 uio_INTERNAL_PHYSICAL +#define uio_INTERNAL_GPHYS +typedef void *uio_NativeHandle; +typedef void *uio_GPRootExtra; +typedef void *uio_GPDirExtra; +typedef void *uio_GPFileExtra; + +#include +#include + +#include "gphys.h" +#include "paths.h" +#include "uioport.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + +static void uio_GPDir_deepPersistentUnref(uio_GPDir *gPDir); +static uio_GPRoot *uio_GPRoot_alloc(void); + +static void uio_GPRoot_free(uio_GPRoot *gPRoot); + +static inline uio_GPDir *uio_GPDir_alloc(void); +void uio_GPDir_delete(uio_GPDir *gPDir); +static inline void uio_GPDir_free(uio_GPDir *gPDir); + +static inline uio_GPFile *uio_GPFile_alloc(void); +void uio_GPFile_delete(uio_GPFile *gPFile); +static inline void uio_GPFile_free(uio_GPFile *gPFile); + +// Call this when you need to edit a file 'dirName' in the GPDir 'gPDir'. +// a new entry is created when necessary. +// uio_gPDirCommitSubDir should be called when you're done with it. +// +// a copy of dirName is made if needed; the caller remains responsible for +// freeing the original +// Allocates a new dir if necessary. +uio_GPDir * +uio_GPDir_prepareSubDir(uio_GPDir *gPDir, const char *dirName) { + uio_GPDir *subDir; + uio_GPDirEntry *entry; + + entry = uio_GPDirEntries_find(gPDir->entries, dirName); + if (entry != NULL) { + if (uio_GPDirEntry_isDir(entry)) { + // Return existing subdir. + uio_GPDir_ref((uio_GPDir *) entry); + return (uio_GPDir *) entry; + } else { + // There already exists a file with the same name. + // This should not happen within one file system. + fprintf(stderr, "Directory %s shadows file with the same name " + "from the same filesystem.\n", dirName); + } + } + + // return new subdir + subDir = uio_GPDir_new(gPDir->pRoot, NULL, uio_GPDir_DETACHED); + // subDir->ref is initialised at 1 + return subDir; +} + +// call this when you're done with a dir acquired by a call to +// uio_gPDirPrepareSubDir +void +uio_GPDir_commitSubDir(uio_GPDir *gPDir, const char *dirName, + uio_GPDir *subDir) { + if (subDir->flags & uio_GPDir_DETACHED) { + // New dir. + // reference to the subDir is passed along to the upDir, + // so subDir->ref should not be changed. + uio_GPDirEntries_add(gPDir->entries, dirName, subDir); + subDir->flags &= ~uio_GPDir_DETACHED; + if (!(subDir->flags & uio_GPDir_PERSISTENT)) { + // Persistent dirs have an extra reference. + uio_GPDir_unref(subDir); + } + } else { + uio_GPDir_unref(subDir); + } +} + +// a copy of fileName is made if needed; the caller remains responsible for +// freeing the original +void +uio_GPDir_addFile(uio_GPDir *gPDir, const char *fileName, uio_GPFile *file) { + // A file will never already exist in a dir. There can only be + // one entry in a physical dir with the same name. + uio_GPDirEntries_add(gPDir->entries, fileName, (uio_GPDirEntry *) file); +} + +// Pre: 'fileName' exists in 'gPDir' and is a dir. +void +uio_GPDir_removeFile(uio_GPDir *gPDir, const char *fileName) { + uio_GPDirEntry *entry; + uio_GPFile *file; + uio_bool retVal; + + entry = uio_GPDirEntries_find(gPDir->entries, fileName); + if (entry == NULL) { + // This means the file has no associated GPFile. + // This can happen when the GPFile structure is only used for caching. + return; + } + + assert(!uio_GPDirEntry_isDir(entry)); + file = (uio_GPFile *) entry; + uio_GPFile_unref(file); + retVal = uio_GPDirEntries_remove(gPDir->entries, fileName); + assert(retVal); +} + +// Pre: 'dirName' exists in 'gPDir' and is a dir. +void +uio_GPDir_removeSubDir(uio_GPDir *gPDir, const char *dirName) { + uio_GPDirEntry *entry; + uio_GPDir *subDir; + uio_bool retVal; + + entry = uio_GPDirEntries_find(gPDir->entries, dirName); + if (entry == NULL) { + // This means the directory has no associated GPDir. + // This can happen when the GPDir structure is only used for caching. + return; + } + + assert(uio_GPDirEntry_isDir(entry)); + subDir = (uio_GPDir *) entry; + if (subDir->flags & uio_GPDir_PERSISTENT) { + // Persistent dirs have an extra reference. + uio_GPDir_unref(subDir); + } + retVal = uio_GPDirEntries_remove(gPDir->entries, dirName); + assert(retVal); +} + +void +uio_GPDir_setComplete(uio_GPDir *gPDir, uio_bool flag) { + if (flag) { + gPDir->flags |= uio_GPDir_COMPLETE; + } else + gPDir->flags &= ~uio_GPDir_COMPLETE; +} + +int +uio_GPDir_entryCount(const uio_GPDir *gPDir) { + return uio_GPDirEntries_count(gPDir->entries); +} + +static void +uio_GPDir_access(uio_GPDir *gPDir) { + if (!(gPDir->flags & uio_GPDir_COMPLETE)) + uio_GPDir_fill(gPDir); +} + +// The ref counter for the dir entry is not incremented. +uio_GPDirEntry * +uio_GPDir_getGPDirEntry(uio_GPDir *gPDir, const char *name) { + uio_GPDir_access(gPDir); + return uio_GPDirEntries_find(gPDir->entries, name); +} + +// The ref counter for the dir entry is not incremented. +uio_PDirEntryHandle * +uio_GPDir_getPDirEntryHandle(const uio_PDirHandle *pDirHandle, + const char *name) { + uio_GPDirEntry *gPDirEntry; + + gPDirEntry = uio_GPDir_getGPDirEntry(pDirHandle->extra, name); + if (gPDirEntry == NULL) + return NULL; + uio_GPDirEntry_ref(gPDirEntry); + if (uio_GPDirEntry_isDir(gPDirEntry)) { + return (uio_PDirEntryHandle *) uio_PDirHandle_new(pDirHandle->pRoot, + (uio_GPDir *) gPDirEntry); + } else { + return (uio_PDirEntryHandle *) uio_PFileHandle_new(pDirHandle->pRoot, + (uio_GPFile *) gPDirEntry); + } +} + +/* + * Follow a path starting from a specified physical dir as long as possible. + * When you can get no further, 'endGPDir' will be filled in with the dir + * where you ended up, and 'pathRest' will point into the original path. to + * the beginning of the part that was not matched. + * It is allowed to have endGPDir point to gPDir and/or restPath + * point to path when calling this function. + * returns: 0 if the complete path was matched + * ENOENT if some component (the next one) didn't exists + * ENODIR if a component (the next one) exists but isn't a dir + * See also uio_walkPhysicalPath. The difference is that this one works + * directly on the GPDirs and is faster because of that. + */ +int +uio_walkGPPath(uio_GPDir *startGPDir, const char *path, + size_t pathLen, uio_GPDir **endGPDir, const char **pathRest) { + const char *pathEnd; + const char *partStart, *partEnd; + char *tempBuf; + uio_GPDir *gPDir; + uio_GPDirEntry *entry; + int retVal; + + gPDir = startGPDir; + tempBuf = uio_malloc(strlen(path) + 1); + // XXX: Use a dynamically allocated array when moving to C99. + pathEnd = path + pathLen; + getFirstPathComponent(path, pathEnd, &partStart, &partEnd); + while (1) { + if (partStart == pathEnd) { + retVal = 0; + break; + } + memcpy(tempBuf, partStart, partEnd - partStart); + tempBuf[partEnd - partStart] = '\0'; + + entry = uio_GPDir_getGPDirEntry(gPDir, tempBuf); + if (entry == NULL) { + retVal = ENOENT; + break; + } + if (!uio_GPDirEntry_isDir(entry)) { + retVal = ENOTDIR; + break; + } + gPDir = (uio_GPDir *) entry; + getNextPathComponent(pathEnd, &partStart, &partEnd); + } + + uio_free(tempBuf); + *pathRest = partStart; + *endGPDir = gPDir; + return retVal; +} + +uio_GPDirEntries_Iterator * +uio_GPDir_openEntries(uio_PDirHandle *pDirHandle) { + uio_GPDir_access(pDirHandle->extra); + return uio_GPDirEntries_getIterator(pDirHandle->extra->entries); +} + +// the start of 'buf' will be filled with pointers to strings +// those strings are stored elsewhere in buf. +// The function returns the number of strings passed along, or -1 for error. +// If there are no more entries, the last pointer will be NULL. +// (this pointer counts towards the return value) +int +uio_GPDir_readEntries(uio_GPDirEntries_Iterator **iterator, + char *buf, size_t len) { + char *end; + char **start; + int num; + const char *name; + size_t nameLen; + + // buf will be filled like this: + // The start of buf will contain pointers to char *, + // the end will contain the actual char[] that those pointers point to. + // The start and the end will grow towards eachother. + start = (char **) buf; + end = buf + len; + num = 0; + while (!uio_GPDirEntries_iteratorDone(*iterator)) { + name = uio_GPDirEntries_iteratorName(*iterator); + nameLen = strlen(name) + 1; + + // Does this work with systems that need memory access to be + // aligned on a certain number of bytes? + if ((size_t) (sizeof (char *) + nameLen) > + (size_t) (end - (char *) start)) { + // Not enough room to fit the pointer (at the beginning) and + // the string (at the end). + return num; + } + end -= nameLen; + memcpy(end, name, nameLen); + *start = end; + start++; + num++; + *iterator = uio_GPDirEntries_iteratorNext(*iterator); + } + if (sizeof (char *) > (size_t) (end - (char *) start)) { + // not enough room to fit the NULL pointer. + // It will have to be reported seperately the next time. + return num; + } + *start = NULL; + num++; + return num; +} + +void +uio_GPDir_closeEntries(uio_GPDirEntries_Iterator *iterator) { + uio_GPDirEntries_freeIterator(iterator); +} + +void +uio_GPDir_fill(uio_GPDir *gPDir) { + if ((gPDir->flags & uio_GPDir_COMPLETE) && + !(gPDir->flags & uio_GPDir_NOCACHE)) + return; + assert(gPDir->pRoot->extra->ops->fillGPDir != NULL); + gPDir->pRoot->extra->ops->fillGPDir(gPDir); +} + +void +uio_GPRoot_deleteGPRootExtra(uio_GPRoot *gPRoot) { + if (gPRoot->extra == NULL) + return; + assert(gPRoot->ops->deleteGPRootExtra != NULL); + gPRoot->ops->deleteGPRootExtra(gPRoot->extra); +} + +void +uio_GPDir_deleteGPDirExtra(uio_GPDir *gPDir) { + if (gPDir->extra == NULL) + return; + assert(gPDir->pRoot->extra->ops->deleteGPDirExtra != NULL); + gPDir->pRoot->extra->ops->deleteGPDirExtra(gPDir->extra); +} + +void +uio_GPFile_deleteGPFileExtra(uio_GPFile *gPFile) { + if (gPFile->extra == NULL) + return; + assert(gPFile->pRoot->extra->ops->deleteGPFileExtra != NULL); + gPFile->pRoot->extra->ops->deleteGPFileExtra(gPFile->extra); +} + +int +uio_gPDirFlagsFromPRootFlags(int flags) { + int newFlags; + + newFlags = 0; + if (flags & uio_PRoot_NOCACHE) + newFlags |= uio_GPDir_NOCACHE; + + return newFlags; +} + +int +uio_gPFileFlagsFromPRootFlags(int flags) { + int newFlags; + + newFlags = 0; + if (flags & uio_PRoot_NOCACHE) + newFlags |= uio_GPFile_NOCACHE; + + return newFlags; +} + +// This function is to be called from the physical layer. +// uio_GPDirHandle is the extra information for an uio_PDirHandle. +// This is in practice a pointer to the uio_GPDir. +void +uio_GPDirHandle_delete(uio_GPDirHandle *gPDirHandle) { + uio_GPDir_unref((uio_GPDir *) gPDirHandle); + (void) gPDirHandle; +} + +// This function is to be called from the physical layer. +// uio_GPFileHandle is the extra information for an uio_PFileHandle. +// This is in practice a pointer to the uio_GPFile. +void +uio_GPFileHandle_delete(uio_GPFileHandle *gPFileHandle) { + uio_GPFile_unref((uio_GPFile *) gPFileHandle); + (void) gPFileHandle; +} + +void +uio_GPDirEntry_delete(uio_GPDirEntry *gPDirEntry) { + if (uio_GPDirEntry_isDir(gPDirEntry)) { + uio_GPDir_delete((uio_GPDir *) gPDirEntry); + } else { + uio_GPFile_delete((uio_GPFile *) gPDirEntry); + } +} + +// note: sets ref count to 1 +uio_GPDir * +uio_GPDir_new(uio_PRoot *pRoot, uio_GPDirExtra extra, int flags) { + uio_GPDir *gPDir; + + gPDir = uio_GPDir_alloc(); + gPDir->ref = 1; + gPDir->pRoot = pRoot; + gPDir->entries = uio_GPDirEntries_new(); + gPDir->extra = extra; + flags |= uio_gPDirFlagsFromPRootFlags(gPDir->pRoot->flags); + if (pRoot->extra->flags & uio_GPRoot_PERSISTENT) + flags |= uio_GPDir_PERSISTENT; + gPDir->flags = flags | uio_GPDirEntry_TYPE_DIR; + return gPDir; +} + +// pre: There are no more references to within the gPDir, and +// gPDir is the last reference to the gPDir itself. +void +uio_GPDir_delete(uio_GPDir *gPDir) { +#if 0 + uio_GPDirEntry *entry; + + uio_GPDirEntries_Iterator *iterator; + + iterator = uio_GPDirEntries_getIterator(gPDir->entries); + while (!uio_GPDirEntries_iteratorDone(iterator)) { + entry = uio_GPDirEntries_iteratorItem(iterator); + assert(entry->ref == 0); + if (uio_GPDirEntry_isDir(entry)) { + uio_GPDir_delete((uio_GPDir *) entry); + } else { + uio_GPFile_delete((uio_GPFile *) entry); + } + iterator = uio_GPDirEntries_iteratorNext(iterator); + } +#endif + + assert(gPDir->ref == 0); + uio_GPDirEntries_deleteHashTable(gPDir->entries); + uio_GPDir_deleteGPDirExtra(gPDir); + uio_GPDir_free(gPDir); +} + +static void +uio_GPDir_deepPersistentUnref(uio_GPDir *gPDir) { + uio_GPDirEntry *entry; + uio_GPDirEntries_Iterator *iterator; + + iterator = uio_GPDirEntries_getIterator(gPDir->entries); + while (!uio_GPDirEntries_iteratorDone(iterator)) { + entry = uio_GPDirEntries_iteratorItem(iterator); + if (uio_GPDirEntry_isDir(entry)) { + uio_GPDir_deepPersistentUnref((uio_GPDir *) entry); + } else { + uio_GPFile_unref((uio_GPFile *) entry); + } + iterator = uio_GPDirEntries_iteratorNext(iterator); + } + uio_GPDirEntries_freeIterator(iterator); + if (gPDir->flags & uio_GPDir_PERSISTENT) { + uio_GPDir_unref(gPDir); + } else { + gPDir->flags &= ~uio_GPDir_COMPLETE; + } +} + +static inline uio_GPDir * +uio_GPDir_alloc(void) { + uio_GPDir *result = uio_malloc(sizeof (uio_GPDir)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_GPDir, (void *) result); +#endif + return result; +} + +static inline void +uio_GPDir_free(uio_GPDir *gPDir) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_GPDir, (void *) gPDir); +#endif + uio_free(gPDir); +} + +// note: sets ref count to 1 +uio_GPFile * +uio_GPFile_new(uio_PRoot *pRoot, uio_GPFileExtra extra, int flags) { + uio_GPFile *gPFile; + + gPFile = uio_GPFile_alloc(); + gPFile->ref = 1; + gPFile->pRoot = pRoot; + gPFile->extra = extra; + gPFile->flags = flags; + return gPFile; +} + +void +uio_GPFile_delete(uio_GPFile *gPFile) { + assert(gPFile->ref == 0); + uio_GPFile_deleteGPFileExtra(gPFile); + uio_GPFile_free(gPFile); +} + +static inline uio_GPFile * +uio_GPFile_alloc(void) { + uio_GPFile *result = uio_malloc(sizeof (uio_GPFile)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_GPFile, (void *) result); +#endif + return result; +} + +static inline void +uio_GPFile_free(uio_GPFile *gPFile) { + uio_free(gPFile); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_GPFile, (void *) gPFile); +#endif +} + +// The ref counter to 'handle' is not incremented. +uio_PRoot * +uio_GPRoot_makePRoot(uio_FileSystemHandler *handler, int pRootFlags, + uio_GPRoot_Operations *ops, uio_GPRootExtra gPRootExtra, int gPRootFlags, + uio_Handle *handle, uio_GPDirExtra gPDirExtra, int gPDirFlags) { + uio_PRoot *result; + uio_GPDir *gPTopDir; + uio_GPRoot *gPRoot; + + gPRoot = uio_GPRoot_new(ops, gPRootExtra, gPRootFlags); + result = uio_PRoot_new(NULL, handler, handle, gPRoot, pRootFlags); + + gPTopDir = uio_GPDir_new(result, gPDirExtra, gPDirFlags); + if (gPRoot->flags & uio_GPRoot_PERSISTENT) + uio_GPDir_ref(gPTopDir); + result->rootDir = uio_GPDir_makePDirHandle(gPTopDir); + + return result; +} + +// Pre: there are no more references to PRoot or anything inside it. +int +uio_GPRoot_umount(uio_PRoot *pRoot) { + uio_PDirHandle *topDirHandle; + uio_GPDir *topDir; + + topDirHandle = uio_PRoot_getRootDirHandle(pRoot); + topDir = topDirHandle->extra; + if (pRoot->extra->flags & uio_GPRoot_PERSISTENT) + uio_GPDir_deepPersistentUnref(topDir); + uio_PDirHandle_unref(topDirHandle); + (void) pRoot; + return 0; +} + +uio_GPRoot * +uio_GPRoot_new(uio_GPRoot_Operations *ops, uio_GPRootExtra extra, int flags) { + uio_GPRoot *result; + + result = uio_GPRoot_alloc(); + result->ops = ops; + result->extra = extra; + result->flags = flags; + return result; +} + +void +uio_GPRoot_delete(uio_GPRoot *gPRoot) { + uio_GPRoot_deleteGPRootExtra(gPRoot); + uio_GPRoot_free(gPRoot); +} + +static uio_GPRoot * +uio_GPRoot_alloc(void) { + uio_GPRoot *result = uio_malloc(sizeof (uio_GPRoot)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_GPRoot, (void *) result); +#endif + return result; +} + +static void +uio_GPRoot_free(uio_GPRoot *gPRoot) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_GPRoot, (void *) gPRoot); +#endif + uio_free(gPRoot); +} + +// The ref counter to the gPDir is not inremented. +uio_PDirHandle * +uio_GPDir_makePDirHandle(uio_GPDir *gPDir) { + return uio_PDirHandle_new(gPDir->pRoot, gPDir); +} + +#ifdef DEBUG +void +uio_GPDirEntry_print(FILE *outStream, uio_GPDirEntry *gPDirEntry) { + if (uio_GPDirEntry_isDir(gPDirEntry)) { + uio_GPDir_print(outStream, (uio_GPDir *) gPDirEntry); + } else { + uio_GPFile_print(outStream, (uio_GPFile *) gPDirEntry); + } +} + +void +uio_GPDir_print(FILE *outStream, uio_GPDir *gPDir) { + (void) outStream; + (void) gPDir; +} + +void +uio_GPFile_print(FILE *outStream, uio_GPFile *gPFile) { + (void) outStream; + (void) gPFile; +} +#endif + + diff --git a/src/libs/uio/gphys.h b/src/libs/uio/gphys.h new file mode 100644 index 0000000..7a9d6be --- /dev/null +++ b/src/libs/uio/gphys.h @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_GPHYS_H_ +#define LIBS_UIO_GPHYS_H_ + +#include "uioport.h" + +#ifndef uio_INTERNAL_PHYSICAL +typedef void *uio_GPRootExtra; +typedef void *uio_GPDirExtra; +typedef void *uio_GPFileExtra; +#endif + +typedef struct CharHashTable_HashTable uio_GPDirEntries; + +#define uio_GPDirEntries_new() \ + ((uio_GPDirEntries *) CharHashTable_newHashTable(NULL, NULL, NULL, \ + NULL, NULL, 0, 0.85, 0.9)) +#define uio_GPDirEntries_add(hashTable, name, item) \ + CharHashTable_add((CharHashTable_HashTable *) hashTable, name, \ + (void *) item) +#define uio_GPDirEntries_remove(hashTable, name) \ + CharHashTable_remove((CharHashTable_HashTable *) hashTable, name) +#define uio_GPDirEntries_count(hashTable) \ + CharHashTable_count((CharHashTable_HashTable *) hashTable) +#define uio_GPDirEntries_find(hashTable, name) \ + ((uio_GPDirEntry *) CharHashTable_find( \ + (CharHashTable_HashTable *) hashTable, name)) +#define uio_GPDirEntries_deleteHashTable(hashTable) \ + CharHashTable_deleteHashTable((CharHashTable_HashTable *) hashTable) +//#define uio_GPDirEntries_clear(hashTable) +// CharHashTable_clear((CharHashTable_HashTable *) hashTable) +#define uio_GPDirEntries_getIterator(hashTable) \ + ((uio_GPDirEntries_Iterator *) CharHashTable_getIterator( \ + (const CharHashTable_HashTable *) hashTable)) +#define uio_GPDirEntries_iteratorDone(iterator) \ + CharHashTable_iteratorDone((const CharHashTable_Iterator *) iterator) +#define uio_GPDirEntries_iteratorName(iterator) \ + CharHashTable_iteratorKey((CharHashTable_Iterator *) iterator) +#define uio_GPDirEntries_iteratorItem(iterator) \ + ((uio_GPDirEntry *) CharHashTable_iteratorValue( \ + (CharHashTable_Iterator *) iterator)) +#define uio_GPDirEntries_iteratorNext(iterator) \ + ((uio_GPDirEntries_Iterator *) CharHashTable_iteratorNext( \ + (CharHashTable_Iterator *) iterator)) +#define uio_GPDirEntries_freeIterator(iterator) \ + CharHashTable_freeIterator(iterator) + +// 'forward' declarations +typedef struct uio_GPDirEntry uio_GPDirEntry; +typedef struct uio_GPDir uio_GPDir; +typedef struct uio_GPFile uio_GPFile; +typedef struct uio_GPRoot_Operations uio_GPRoot_Operations; +typedef struct uio_GPRoot uio_GPRoot; + +#include "charhashtable.h" +typedef CharHashTable_Iterator uio_GPDirEntries_Iterator; + +#ifdef uio_INTERNAL_GPHYS +typedef uio_GPDirEntries_Iterator *uio_NativeEntriesContext; +#endif +typedef struct uio_GPRoot *uio_PRootExtra; +typedef struct uio_GPDir uio_GPDirHandle; +typedef uio_GPDirHandle *uio_PDirHandleExtra; +typedef struct uio_GPFile uio_GPFileHandle; +typedef uio_GPFileHandle *uio_PFileHandleExtra; + + +#ifdef DEBUG +# include +#endif +#include "iointrn.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + +struct uio_GPRoot_Operations { + void (*fillGPDir)(uio_GPDir *); + void (*deleteGPRootExtra)(uio_GPRootExtra); + void (*deleteGPDirExtra)(uio_GPDirExtra); + void (*deleteGPFileExtra)(uio_GPFileExtra); +}; + +struct uio_GPRoot { + int flags; +#define uio_GPRoot_PERSISTENT 0x4000 + /* Set if directories in this file system should not be deleted + * as long as the file system is mounted. If this flag is not + * set, the GPDir structure is only a cache. + */ + uio_GPRoot_Operations *ops; + uio_GPRootExtra extra; +}; + +#define uio_GPDirEntry_COMMON \ + int flags; \ + int ref; \ + /* Number of times this structure is referenced from the \ + * outside (so not counting the references from subdirs \ + * or files when the entry is a directory) \ + */ + +#define uio_GPDirEntry_NOCACHE uio_PRoot_NOCACHE + +/* + * uio_GPDirEntry + * super-'class' of uio_GPDir and uio_GPFile + */ +struct uio_GPDirEntry { + uio_GPDirEntry_COMMON + void *extra; +}; + +#define uio_GPDirEntry_TYPE_REG 0x0000 +#define uio_GPDirEntry_TYPE_DIR 0x0001 +#define uio_GPDirEntry_TYPEMASK 0x0001 + +/* + * uio_GPDir + * Represents a directory in a physical directory structure. + * sub-'class' of uio_GPDirEntry + */ +struct uio_GPDir { + uio_GPDirEntry_COMMON +# define uio_GPDir_NOCACHE uio_GPDirEntry_NOCACHE + /* This directory info will not be cached. + * PDIR_COMPLETE is irrelevant in this case */ +# define uio_GPDir_COMPLETE 0x1000 + /* Set if fillDir should not be called if an entry does not + * exist in a directory. Usually set if the entire dir has been + * completely read in. + */ +# define uio_GPDir_DETACHED 0x2000 + /* Set if this dir is not linked to from elsewhere in the physical + * structure */ +# define uio_GPDir_PERSISTENT 0x4000 + /* Set if this dir should not be deleted as long as the file + * system is mounted. If this flag is not set, the GPDir + * structure is only a cache. + */ + uio_GPDirExtra extra; + /* extra internal data for some filesystem types */ + uio_PRoot *pRoot; + uio_GPDirEntries *entries; +}; + + +/* + * uio_GPFile + * Represents a file in a physical directory structure. + * sub-'class' of uio_GPDirEntry + */ +struct uio_GPFile { + uio_GPDirEntry_COMMON +# define uio_GPFile_NOCACHE uio_GPDirEntry_NOCACHE + /* Info on this file will not be cached. */ + uio_GPFileExtra extra; + /* extra internal data for some filesystem types */ + uio_PRoot *pRoot; +}; + + +static inline uio_bool +uio_GPDirEntry_isReg(uio_GPDirEntry *gPDirEntry) { + return (gPDirEntry->flags & uio_GPDirEntry_TYPEMASK) == + uio_GPDirEntry_TYPE_REG; +} + +static inline uio_bool +uio_GPDirEntry_isDir(uio_GPDirEntry *gPDirEntry) { + return (gPDirEntry->flags & uio_GPDirEntry_TYPEMASK) == + uio_GPDirEntry_TYPE_DIR; +} + + +#ifdef DEBUG +void uio_GPDirEntry_print(FILE *outStream, uio_GPDirEntry *gPDirEntry); +void uio_GPDir_print(FILE *outStream, uio_GPDir *gPDir); +void uio_GPFile_print(FILE *outStream, uio_GPFile *pFile); +#endif + +uio_NativeEntriesContext uio_GPDir_openEntries(uio_PDirHandle *pDirHandle); +int uio_GPDir_readEntries(uio_NativeEntriesContext *iterator, + char *buf, size_t len); +void uio_GPDir_closeEntries(uio_NativeEntriesContext iterator); +int uio_GPDir_entryCount(const uio_GPDir *gPDir); + +int uio_gPDirFlagsFromPRootFlags(int flags); +int uio_gPFileFlagsFromPRootFlags(int flags); +uio_PRoot *uio_GPRoot_makePRoot(uio_FileSystemHandler *handler, int pRootFlags, + uio_GPRoot_Operations *ops, uio_GPRootExtra gPRootExtra, int gPRootFlags, + uio_Handle *handle, uio_GPDirExtra gPDirExtra, int gPDirFlags); +int uio_GPRoot_umount(uio_PRoot *pRoot); + +uio_GPDir *uio_GPDir_prepareSubDir(uio_GPDir *gPDir, const char *dirName); +void uio_GPDir_commitSubDir(uio_GPDir *gPDir, const char *dirName, + uio_GPDir *subDir); +void uio_GPDir_addFile(uio_GPDir *gPDir, const char *fileName, + uio_GPFile *file); +void uio_GPDir_removeFile(uio_GPDir *gPDir, const char *fileName); +void uio_GPDir_removeSubDir(uio_GPDir *gPDir, const char *dirName); +void uio_GPDir_setComplete(uio_GPDir *gPDir, uio_bool flag); +uio_GPDirEntry *uio_GPDir_getGPDirEntry(uio_GPDir *gPDir, + const char *name); +uio_PDirEntryHandle *uio_GPDir_getPDirEntryHandle( + const uio_PDirHandle *pDirHandle, const char *name); +int uio_walkGPPath(uio_GPDir *startGPDir, const char *path, + size_t pathLen, uio_GPDir **endGPDir, const char **pathRest); +uio_PDirHandle *uio_GPDir_makePDirHandle(uio_GPDir *gPDir); + +void uio_GPDir_fill(uio_GPDir *gPDir); +void uio_GPRoot_deleteGPRootExtra(uio_GPRoot *gPRoot); +void uio_GPDir_deleteGPDirExtra(uio_GPDir *gPDir); +void uio_GPFile_deleteGPFileExtra(uio_GPFile *gPFile); + +void uio_GPDirHandle_delete(uio_GPDirHandle *gPDirHandle); +void uio_GPFileHandle_delete(uio_GPFileHandle *gPFileHandle); +void uio_GPDirEntry_delete(uio_GPDirEntry *gPDirEntry); +uio_GPRoot *uio_GPRoot_new(uio_GPRoot_Operations *ops, uio_GPRootExtra extra, + int flags); +void uio_GPRoot_delete(uio_GPRoot *gPRoot); +uio_GPDir *uio_GPDir_new(uio_PRoot *pRoot, uio_GPDirExtra extra, int flags); +void uio_GPDir_delete(uio_GPDir *gPDir); +uio_GPFile *uio_GPFile_new(uio_PRoot *pRoot, uio_GPFileExtra extra, int flags); +void uio_GPFile_delete(uio_GPFile *gPFile); + + +static inline void +uio_GPDirEntry_ref(uio_GPDirEntry *gPDirEntry) { +#ifdef uio_MEM_DEBUG + if (uio_GPDirEntry_isDir(gPDirEntry)) { + uio_MemDebug_debugRef(uio_GPDir, (void *) gPDirEntry); + } else { + uio_MemDebug_debugRef(uio_GPFile, (void *) gPDirEntry); + } +#endif + gPDirEntry->ref++; +} + +static inline void +uio_GPDirEntry_unref(uio_GPDirEntry *gPDirEntry) { + assert(gPDirEntry->ref > 0); +#ifdef uio_MEM_DEBUG + if (uio_GPDirEntry_isDir(gPDirEntry)) { + uio_MemDebug_debugUnref(uio_GPDir, (void *) gPDirEntry); + } else { + uio_MemDebug_debugUnref(uio_GPFile, (void *) gPDirEntry); + } +#endif + gPDirEntry->ref--; + if (gPDirEntry->ref == 0) + uio_GPDirEntry_delete(gPDirEntry); +} + +static inline void +uio_GPDir_ref(uio_GPDir *gPDir) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugRef(uio_GPDir, (void *) gPDir); +#endif + gPDir->ref++; +} + +static inline void +uio_GPDir_unref(uio_GPDir *gPDir) { + assert(gPDir->ref > 0); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugUnref(uio_GPDir, (void *) gPDir); +#endif + gPDir->ref--; + if (gPDir->ref == 0) + uio_GPDir_delete(gPDir); +} + +static inline void +uio_GPFile_ref(uio_GPFile *gPFile) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugRef(uio_GPFile, (void *) gPFile); +#endif + gPFile->ref++; +} + +static inline void +uio_GPFile_unref(uio_GPFile *gPFile) { + assert(gPFile->ref > 0); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugUnref(uio_GPFile, (void *) gPFile); +#endif + gPFile->ref--; + if (gPFile->ref == 0) + uio_GPFile_delete(gPFile); +} + + +#endif /* LIBS_UIO_GPHYS_H_ */ + diff --git a/src/libs/uio/hashtable.c b/src/libs/uio/hashtable.c new file mode 100644 index 0000000..1f376e1 --- /dev/null +++ b/src/libs/uio/hashtable.c @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 HASHTABLE_INTERNAL + // If HASHTABLE_INTERNAL is already defined, this file is included + // as a template. In this case hashtable.h has already been included. +# define HASHTABLE_INTERNAL +# include "hashtable.h" +#endif + +#include +#include +#include +#include + +#include "mem.h" +#include "uioport.h" + +static void HASHTABLE_(setup)(HASHTABLE_(HashTable) *HashTable, + uio_uint32 size); +static void HASHTABLE_(resize)(HASHTABLE_(HashTable) *hashTable); +static inline uio_uint32 nextPower2(uio_uint32 x); + +static inline HASHTABLE_(HashTable) *HASHTABLE_(allocHashTable)(void); +static inline HASHTABLE_(HashEntry) *HASHTABLE_(newHashEntry)(uio_uint32 hash, + HASHTABLE_(Key) *key, HASHTABLE_(Value) *value, + HASHTABLE_(HashEntry) *next); +static inline HASHTABLE_(HashEntry) *HASHTABLE_(allocHashEntry)(void); +static inline void HASHTABLE_(freeHashEntry)( + HASHTABLE_(HashEntry) *entry); + +// Create a new HashTable. +HASHTABLE_(HashTable) * +HASHTABLE_(newHashTable)( + HASHTABLE_(HashFunction) hashFunction, + HASHTABLE_(EqualFunction) equalFunction, + HASHTABLE_(CopyFunction) copyFunction, + HASHTABLE_(FreeKeyFunction) freeKeyFunction, + HASHTABLE_(FreeValueFunction) freeValueFunction, + uio_uint32 initialSize, + double minFillQuotient, + double maxFillQuotient) { + HASHTABLE_(HashTable) *hashTable; + + assert(maxFillQuotient >= minFillQuotient); + + hashTable = HASHTABLE_(allocHashTable)(); + hashTable->hashFunction = hashFunction; + hashTable->equalFunction = equalFunction; + hashTable->copyFunction = copyFunction; + hashTable->freeKeyFunction = freeKeyFunction; + hashTable->freeValueFunction = freeValueFunction; + + hashTable->minFillQuotient = minFillQuotient; + hashTable->maxFillQuotient = maxFillQuotient; + HASHTABLE_(setup)(hashTable, initialSize); + + return hashTable; +} + +// Add an entry to the HashTable. +uio_bool +HASHTABLE_(add)(HASHTABLE_(HashTable) *hashTable, + const HASHTABLE_(Key) *key, HASHTABLE_(Value) *value) { + uio_uint32 hash; + struct HASHTABLE_(HashEntry) *entry; + + hash = HASHTABLE_(HASH)(hashTable, key); + entry = hashTable->entries[hash & hashTable->hashMask]; + while (entry != NULL) { + if (HASHTABLE_(EQUAL)(hashTable, key, entry->key)) { + // key is already present + return false; + } + entry = entry->next; + } + +#ifdef HashTable_PROFILE + if (hashTable->entries[hash & hashTable->hashMask] != NULL) + hashTable->numCollisions++; +#endif + hashTable->entries[hash & hashTable->hashMask] = + HASHTABLE_(newHashEntry)(hash, + HASHTABLE_(COPY)(hashTable, key), value, + hashTable->entries[hash & hashTable->hashMask]); + + hashTable->numEntries++; + if (hashTable->numEntries > hashTable->maxSize) + HASHTABLE_(resize)(hashTable); + + return true; +} + +// Remove an entry with a specified Key from the HashTable. +uio_bool +HASHTABLE_(remove)(HASHTABLE_(HashTable) *hashTable, + const HASHTABLE_(Key) *key) { + uio_uint32 hash; + struct HASHTABLE_(HashEntry) **entry, *next; + + hash = HASHTABLE_(HASH)(hashTable, key); + entry = &hashTable->entries[hash & hashTable->hashMask]; + while (1) { + if (*entry == NULL) + return false; + if (HASHTABLE_(EQUAL)(hashTable, key, (*entry)->key)) { + // found the key + break; + } + entry = &(*entry)->next; + } + next = (*entry)->next; + HASHTABLE_(FREEKEY)(hashTable, (*entry)->key); + HASHTABLE_(FREEVALUE)(hashTable, (*entry)->value); + HASHTABLE_(freeHashEntry)(*entry); + *entry = next; + + hashTable->numEntries--; + if (hashTable->numEntries < hashTable->minSize) + HASHTABLE_(resize)(hashTable); + + return true; +} + +// Find the Value stored for some Key. +HASHTABLE_(Value) * +HASHTABLE_(find)(HASHTABLE_(HashTable) *hashTable, + const HASHTABLE_(Key) *key) { + uio_uint32 hash; + struct HASHTABLE_(HashEntry) *entry; + + hash = HASHTABLE_(HASH)(hashTable, key); + entry = hashTable->entries[hash & hashTable->hashMask]; + while (entry != NULL) { + if (HASHTABLE_(EQUAL)(hashTable, key, entry->key)) { + // found the key + return entry->value; + } + entry = entry->next; + } + return NULL; +} + +// Returns the number of entries in the HashTable. +uio_uint32 +HASHTABLE_(count)(const HASHTABLE_(HashTable) *hashTable) { + return hashTable->numEntries; +} + +// Auxiliary function to (re)initialise the buckets in the HashTable. +static void +HASHTABLE_(setup)(HASHTABLE_(HashTable) *hashTable, uio_uint32 initialSize) { + if (initialSize < 4) + initialSize = 4; + hashTable->size = nextPower2(ceil(((double) initialSize) / + hashTable->maxFillQuotient)); + hashTable->hashMask = hashTable->size - 1; + hashTable->minSize = ceil(((double) (hashTable->size >> 1)) + * hashTable->minFillQuotient); + hashTable->maxSize = floor(((double) hashTable->size) + * hashTable->maxFillQuotient); + hashTable->entries = uio_calloc(hashTable->size, + sizeof (HASHTABLE_(HashEntry) *)); + hashTable->numEntries = 0; +#ifdef HashTable_PROFILE + hashTable->numCollisions = 0; +#endif +} + +// Resize the buckets in the HashTable. +static void +HASHTABLE_(resize)(HASHTABLE_(HashTable) *hashTable) { + HASHTABLE_(HashEntry) **oldEntries; + HASHTABLE_(HashEntry) *entry, *next; + HASHTABLE_(HashEntry) **newLocation; + uio_uint32 oldNumEntries; + uio_uint32 i; + + oldNumEntries = hashTable->numEntries; + oldEntries = hashTable->entries; + + HASHTABLE_(setup)(hashTable, hashTable->numEntries); + hashTable->numEntries = oldNumEntries; + + i = 0; + while (oldNumEntries > 0) { + entry = oldEntries[i]; + while (entry != NULL) { + next = entry->next; + newLocation = &hashTable->entries[entry->hash & + hashTable->hashMask]; +#ifdef HashTable_PROFILE + if (*newLocation != NULL) + hashTable->numCollisions++; +#endif + entry->next = *newLocation; + *newLocation = entry; + oldNumEntries--; + entry = next; + } + i++; + } + + uio_free(oldEntries); +} + +// Adapted from "Hackers Delight" +// Returns the smallest power of two greater or equal to x. +static inline uio_uint32 +nextPower2(uio_uint32 x) { + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x + 1; +} + +// Get an iterator to iterate through all the entries in the HashTable. +// NB: Iterator should be considered invalid if the HashTable is changed. +// TODO: change this (make it thread-safe) +// this can be done by only marking items as deleted when +// there are outstanding iterators. +HASHTABLE_(Iterator) * +HASHTABLE_(getIterator)(const HASHTABLE_(HashTable) *hashTable) { + HASHTABLE_(Iterator) *iterator; + uio_uint32 i; + + iterator = uio_malloc(sizeof (HASHTABLE_(Iterator))); + iterator->hashTable = hashTable; + + // Look for the first used bucket. + for (i = 0; i < iterator->hashTable->size; i++) { + if (iterator->hashTable->entries[i] != NULL) { + // Found a used bucket. + iterator->bucketNr = i; + iterator->entry = iterator->hashTable->entries[i]; + return iterator; + } + } + + // No entries were found. + iterator->bucketNr = i; + iterator->entry = NULL; + return iterator; +} + +// Returns true if and only if there are no more entries in the hash table +// for the Iterator to find. +int +HASHTABLE_(iteratorDone)(const HASHTABLE_(Iterator) *iterator) { + return iterator->bucketNr >= iterator->hashTable->size; +} + +// Get the Key of the entry pointed to by an Iterator. +HASHTABLE_(Key) * +HASHTABLE_(iteratorKey)(HASHTABLE_(Iterator) *iterator) { + return iterator->entry->key; +} + +// Get the Value of the entry pointed to by an Iterator. +HASHTABLE_(Value) * +HASHTABLE_(iteratorValue)(HASHTABLE_(Iterator) *iterator) { + return iterator->entry->value; +} + +// Move the Iterator to the next entry in the HashTable. +// Should not be called if the iterator is already past the last entry. +HASHTABLE_(Iterator) * +HASHTABLE_(iteratorNext)(HASHTABLE_(Iterator) *iterator) { + uio_uint32 i; + + // If there's another entry in this bucket, use that. + iterator->entry = iterator->entry->next; + if (iterator->entry != NULL) + return iterator; + + // Look for the next used bucket. + for (i = iterator->bucketNr + 1; i < iterator->hashTable->size; i++) { + if (iterator->hashTable->entries[i] != NULL) { + // Found another used bucket. + iterator->bucketNr = i; + iterator->entry = iterator->hashTable->entries[i]; + return iterator; + } + } + + // No more entries were found. + iterator->bucketNr = i; + iterator->entry = NULL; + return iterator; +} + +// Free the Iterator. +void +HASHTABLE_(freeIterator)(HASHTABLE_(Iterator) *iterator) { + uio_free(iterator); +} + +// Auxiliary function to allocate a HashTable. +static inline HASHTABLE_(HashTable) * +HASHTABLE_(allocHashTable)(void) { + return uio_malloc(sizeof (HASHTABLE_(HashTable))); +} + +// Auxiliary function to create a HashEntry. +static inline HASHTABLE_(HashEntry) * +HASHTABLE_(newHashEntry)(uio_uint32 hash, HASHTABLE_(Key) *key, + HASHTABLE_(Value) *value, HASHTABLE_(HashEntry) *next) { + HASHTABLE_(HashEntry) *result; + + result = HASHTABLE_(allocHashEntry)(); + result->hash = hash; + result->key = key; + result->value = value; + result->next = next; + return result; +} + +// Allocate a new HashEntry. +static inline HASHTABLE_(HashEntry) * +HASHTABLE_(allocHashEntry)(void) { + return uio_malloc(sizeof (HASHTABLE_(HashEntry))); +} + +// Delete the HashTable. +void +HASHTABLE_(deleteHashTable)(HASHTABLE_(HashTable) *hashTable) { + uio_uint32 i; + HASHTABLE_(HashEntry) *entry, *next; + HASHTABLE_(HashEntry) **bucketPtr; + + i = hashTable->numEntries; + bucketPtr = hashTable->entries; + while (i > 0) { + entry = *bucketPtr; + while (entry != NULL) { + next = entry->next; + HASHTABLE_(FREEKEY)(hashTable, entry->key); + HASHTABLE_(FREEVALUE)(hashTable, entry->value); + HASHTABLE_(freeHashEntry)(entry); + entry = next; + i--; + } + bucketPtr++; + } + uio_free(hashTable->entries); + uio_free(hashTable); +} + +// Auxiliary function to deallocate a HashEntry. +static inline void +HASHTABLE_(freeHashEntry)(HASHTABLE_(HashEntry) *entry) { + uio_free(entry); +} + diff --git a/src/libs/uio/hashtable.h b/src/libs/uio/hashtable.h new file mode 100644 index 0000000..eb4437f --- /dev/null +++ b/src/libs/uio/hashtable.h @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// The 'already included' check must be done slightly more complicated +// than usually. This file may be included directly only once, +// but it may be included my derivative HashTable definitions that use +// this file as a template more than once. +#if !defined(_HASHTABLE_H) || defined(HASHTABLE_GENERIC) +#if defined(HASHTABLE_) +# define HASHTABLE_GENERIC +#endif + +#include "types.h" +#include "uioport.h" + +// Define to enable profiling. +#define HashTable_PROFILE + +// You can use inline hash functions for extra speed, by using this file as +// a template. +// To do this, make a new .h and .c file. In the .h file, define the macros +// (and typedefs) from the HASHTABLE_ block below. +// In the .c file, #define HASHTABLE_INTERNAL, #include the .h file +// and hashtable.c (in this order), and add the necessary functions. +// If you do not need to free the Value, you can define HashTable_FREEVALUE +// as a no-op. +#ifndef HASHTABLE_ +# define HASHTABLE_(identifier) HashTable ## _ ## identifier + typedef void HashTable_Key; + typedef void HashTable_Value; +# define HashTable_HASH(hashTable, hashValue) \ + (hashTable)->hashFunction(hashValue) +# define HashTable_EQUAL(hashTable, hashKey1, hashKey2) \ + (hashTable)->equalFunction(hashKey1, hashKey2) +# define HashTable_COPY(hashTable, hashKey) \ + (hashTable)->copyFunction(hashKey) +# define HashTable_FREEKEY(hashTable, hashKey) \ + (hashTable)->freeKeyFunction(hashKey) +# define HashTable_FREEVALUE(hashTable, hashValue) \ + (hashTable)->freeValueFunction(hashValue) +#endif + + + +typedef uio_uint32 (*HASHTABLE_(HashFunction))(const HASHTABLE_(Key) *); +typedef uio_bool (*HASHTABLE_(EqualFunction))(const HASHTABLE_(Key) *, + const HASHTABLE_(Key) *); +typedef HASHTABLE_(Value) *(*HASHTABLE_(CopyFunction))( + const HASHTABLE_(Key) *); +typedef void (*HASHTABLE_(FreeKeyFunction))(HASHTABLE_(Key) *); +typedef void (*HASHTABLE_(FreeValueFunction))(HASHTABLE_(Value) *); + +typedef struct HASHTABLE_(HashTable) HASHTABLE_(HashTable); +typedef struct HASHTABLE_(HashEntry) HASHTABLE_(HashEntry); +typedef struct HASHTABLE_(Iterator) HASHTABLE_(Iterator); + +struct HASHTABLE_(HashTable) { + HASHTABLE_(HashFunction) hashFunction; + // Function creating a uio_uint32 hash of a key. + HASHTABLE_(EqualFunction) equalFunction; + // Function used to compare two keys. + HASHTABLE_(CopyFunction) copyFunction; + // Function used to copy a key. + HASHTABLE_(FreeKeyFunction) freeKeyFunction; + // Function used to free a key. + HASHTABLE_(FreeValueFunction) freeValueFunction; + // Function used to free a value. Called when an entry is + // removed using the remove function, or for entries + // still in the HashTable when the HashTable is deleted. + + double minFillQuotient; + // How much of half of the hashtable needs to be filled + // before resizing to size/2. + double maxFillQuotient; + // How much of the hashTable needs to be filled before + // resizing to size*2. + uio_uint32 minSize; + // Resize to size/2 when below this size. + uio_uint32 maxSize; + // Resize to size*2 when above this size. + uio_uint32 size; + // The number of buckets in the hash table. + uio_uint32 hashMask; + // Mask to take on a the calculated hash value, to make it + // fit into the table. + + HASHTABLE_(HashEntry) **entries; + // The actual entries + + uio_uint32 numEntries; +#ifdef HashTable_PROFILE + uio_uint32 numCollisions; +#endif +}; + +struct HASHTABLE_(HashEntry) { + uio_uint32 hash; + HASHTABLE_(Key) *key; + HASHTABLE_(Value) *value; + HASHTABLE_(HashEntry) *next; +}; + +struct HASHTABLE_(Iterator) { + const HASHTABLE_(HashTable) *hashTable; + uio_uint32 bucketNr; + HASHTABLE_(HashEntry) *entry; +}; + +HASHTABLE_(HashTable) *HASHTABLE_(newHashTable)( + HASHTABLE_(HashFunction) hashFunction, + HASHTABLE_(EqualFunction) equalFunction, + HASHTABLE_(CopyFunction) copyFunction, + HASHTABLE_(FreeKeyFunction) freeKeyFunction, + HASHTABLE_(FreeValueFunction) freeValueFunction, + uio_uint32 initialSize, + double minFillQuotient, double maxFillQuotient); +uio_bool HASHTABLE_(add)(HASHTABLE_(HashTable) *hashTable, + const HASHTABLE_(Key) *key, HASHTABLE_(Value) *value); +uio_bool HASHTABLE_(remove)(HASHTABLE_(HashTable) *hashTable, + const HASHTABLE_(Key) *key); +HASHTABLE_(Value) *HASHTABLE_(find)( + HASHTABLE_(HashTable) *hashTable, const HASHTABLE_(Key) *key); +uio_uint32 HASHTABLE_(count)(const HASHTABLE_(HashTable) *hashTable); +void HASHTABLE_(deleteHashTable)(HASHTABLE_(HashTable) *hashTable); +HASHTABLE_(Iterator) *HASHTABLE_(getIterator)( + const HASHTABLE_(HashTable) *hashTable); +int HASHTABLE_(iteratorDone)(const HASHTABLE_(Iterator) *iterator); +HASHTABLE_(Key) *HASHTABLE_(iteratorKey)(HASHTABLE_(Iterator) *iterator); +HASHTABLE_(Value) *HASHTABLE_(iteratorValue)(HASHTABLE_(Iterator) *iterator); +HASHTABLE_(Iterator) *HASHTABLE_(iteratorNext)(HASHTABLE_(Iterator) *iterator); +void HASHTABLE_(freeIterator)(HASHTABLE_(Iterator) *iterator); + +#ifndef HASHTABLE_INTERNAL +# undef HASHTABLE_ +#endif + +#endif /* !defined(_HASHTABLE_H) || defined(HASHTABLE_GENERIC) */ + + + diff --git a/src/libs/uio/io.c b/src/libs/uio/io.c new file mode 100644 index 0000000..247d1e2 --- /dev/null +++ b/src/libs/uio/io.c @@ -0,0 +1,1859 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#include +#include +#include +#include + +#include "iointrn.h" +#include "ioaux.h" +#include "mount.h" +#include "fstypes.h" +#include "mounttree.h" +#include "physical.h" +#include "paths.h" +#include "mem.h" +#include "uioutils.h" +#include "uioport.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + +#if 0 +static int uio_accessDir(uio_DirHandle *dirHandle, const char *path, + int mode); +#endif +static int uio_statDir(uio_DirHandle *dirHandle, const char *path, + struct stat *statBuf); +static int uio_statOneDir(uio_PDirHandle *pDirHandle, struct stat *statBuf); + +static void uio_PDirHandles_delete(uio_PDirHandle *pDirHandles[], + int numPDirHandles); + +static inline uio_PDirHandle *uio_PDirHandle_alloc(void); +static inline void uio_PDirHandle_free(uio_PDirHandle *pDirHandle); +static inline uio_PFileHandle *uio_PFileHandle_alloc(void); +static inline void uio_PFileHandle_free(uio_PFileHandle *pFileHandle); + +static uio_DirHandle *uio_DirHandle_new(uio_Repository *repository, char *path, + char *rootEnd); +static inline uio_DirHandle *uio_DirHandle_alloc(void); +static inline void uio_DirHandle_free(uio_DirHandle *dirHandle); + +static inline uio_Handle *uio_Handle_alloc(void); +static inline void uio_Handle_free(uio_Handle *handle); + +static uio_MountHandle *uio_MountHandle_new(uio_Repository *repository, + uio_MountInfo *mountInfo); +static inline void uio_MountHandle_delete(uio_MountHandle *mountHandle); +static inline uio_MountHandle *uio_MountHandle_alloc(void); +static inline void uio_MountHandle_free(uio_MountHandle *mountHandle); + + + +void +uio_init(void) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_init(); +#endif + uio_registerDefaultFileSystems(); +} + +void +uio_unInit(void) { + uio_unRegisterDefaultFileSystems(); +#ifdef uio_MEM_DEBUG +# ifdef DEBUG + uio_MemDebug_printPointers(stderr); + fflush(stderr); +# endif + uio_MemDebug_unInit(); +#endif +} + +uio_Repository * +uio_openRepository(int flags) { + return uio_Repository_new(flags); +} + +void +uio_closeRepository(uio_Repository *repository) { + uio_unmountAllDirs(repository); + uio_Repository_unref(repository); +} + +/* + * Function name: uio_mountDir + * Description: Grafts a directory from inside a physical fileSystem + * into the locical filesystem, at a specified directory. + * Arguments: destRep - the repository where the newly mounted dir + * is to be grafted. + * mountPoint - the path to the directory where the dir + * is to be grafted. + * fsType - the file system type of physical fileSystem + * pointed to by sourcePath. + * sourceDir - the directory to which 'sourcePath' is to + * be taken relative. + * sourcePath - a path relative to sourceDir, which contains + * the file/directory to be mounted. + * If sourceDir and sourcePath are NULL, the file + * system of the operating system will be used. + * inPath - the location relative to the root of the newly + * mounted fileSystem, pointing to the directory + * that is to be grafted. + * Note: If fsType is uio_FSTYPE_STDIO, inPath is + * relative to the root of the filesystem, NOT to + * the current working dir. + * autoMount - array of automount options in function + * in this mountPoint. + * flags - one of uio_MOUNT_TOP, uio_MOUNT_BOTTOM, + * uio_MOUNT_BELOW, uio_MOUNT_ABOVE, specifying + * the precedence of this mount, OR'ed with + * one or more of the following flags: + * uio_MOUNT_RDONLY (no writing is allowed) + * relative - If 'flags' includes uio_MOUNT_BELOW or + * uio_MOUNT_ABOVE, this is the mount handle + * where the new mount is relative to. + * Otherwise, it should be NULL. + * Returns: a handle suitable for uio_unmountDir() + * NULL if an error occured. In this case 'errno' is set. + */ +uio_MountHandle * +uio_mountDir(uio_Repository *destRep, const char *mountPoint, + uio_FileSystemID fsType, + uio_DirHandle *sourceDir, const char *sourcePath, + const char *inPath, uio_AutoMount **autoMount, int flags, + uio_MountHandle *relative) { + uio_PRoot *pRoot; + uio_Handle *handle; + uio_FileSystemHandler *handler; + uio_MountInfo *relativeInfo; + + switch (flags & uio_MOUNT_LOCATION_MASK) { + case uio_MOUNT_TOP: + case uio_MOUNT_BOTTOM: + if (relative != NULL) { + errno = EINVAL; + return NULL; + } + relativeInfo = NULL; + break; + case uio_MOUNT_BELOW: + case uio_MOUNT_ABOVE: + if (relative == NULL) { + errno = EINVAL; + return NULL; + } + relativeInfo = relative->mountInfo; + break; + default: + abort(); + } + + if (mountPoint[0] == '/') + mountPoint++; + if (!validPathName(mountPoint, strlen(mountPoint))) { + errno = EINVAL; + return NULL; + } + + // TODO: check if the filesystem is already mounted, and if so, reuse it. + // A RO filedescriptor will need to be replaced though if the + // filesystem needs to be remounted RW now. + if (sourceDir == NULL) { + if (sourcePath != NULL) { + // bad: sourceDir is NULL, but sourcePath isn't + errno = EINVAL; + return NULL; + } + handle = NULL; + } else { + if (sourcePath == NULL) { + // bad: sourcePath is NULL, but sourceDir isn't + errno = EINVAL; + return NULL; + } + handle = uio_open(sourceDir, sourcePath, + ((flags & uio_MOUNT_RDONLY) == uio_MOUNT_RDONLY ? + O_RDONLY : O_RDWR) +#ifdef WIN32 + | O_BINARY +#endif + , 0); + if (handle == NULL) { + // errno is set + return NULL; + } + } + + handler = uio_getFileSystemHandler(fsType); + if (handler == NULL) { + if (handle) + uio_close(handle); + errno = ENODEV; + return NULL; + } + + assert(handler->mount != NULL); + pRoot = (handler->mount)(handle, flags); + if (pRoot == NULL) { + int savedErrno; + + savedErrno = errno; + if (handle) + uio_close(handle); + errno = savedErrno; + return NULL; + } + + if (handle) { + // Close this reference to handle. + // The physical layer may store the link in pRoot, in which it + // will be cleaned up from uio_unmount(). + uio_close(handle); + } + + // The new file system is ready, now we need to find the specified + // dir inside it and put it in its place in the mountTree. + { + uio_PDirHandle *endDirHandle; + const char *endInPath; + char *dirName; + uio_MountInfo *mountInfo; + uio_MountTree *mountTree; + uio_PDirHandle *pRootHandle; +#ifdef BACKSLASH_IS_PATH_SEPARATOR + char *unixPath; + + unixPath = dosToUnixPath(inPath); + inPath = unixPath; +#endif /* BACKSLASH_IS_PATH_SEPARATOR */ + + if (inPath[0] == '/') + inPath++; + pRootHandle = uio_PRoot_getRootDirHandle(pRoot); + uio_walkPhysicalPath(pRootHandle, inPath, strlen(inPath), + &endDirHandle, &endInPath); + if (*endInPath != '\0') { + // Path inside the filesystem to mount does not exist. +#ifdef BACKSLASH_IS_PATH_SEPARATOR + uio_free(unixPath); +#endif /* BACKSLASH_IS_PATH_SEPARATOR */ + uio_PDirHandle_unref(endDirHandle); + uio_PRoot_unrefMount(pRoot); + errno = ENOENT; + return NULL; + } + + dirName = uio_malloc(endInPath - inPath + 1); + memcpy(dirName, inPath, endInPath - inPath); + dirName[endInPath - inPath] = '\0'; +#ifdef BACKSLASH_IS_PATH_SEPARATOR + // InPath is a copy with the paths fixed. + uio_free(unixPath); +#endif /* BACKSLASH_IS_PATH_SEPARATOR */ + mountInfo = uio_MountInfo_new(fsType, NULL, endDirHandle, dirName, + autoMount, NULL, flags); + uio_repositoryAddMount(destRep, mountInfo, + flags & uio_MOUNT_LOCATION_MASK, relativeInfo); + mountTree = uio_mountTreeAddMountInfo(destRep, destRep->mountTree, + mountInfo, mountPoint, flags & uio_MOUNT_LOCATION_MASK, + relativeInfo); + // mountTree is the node in destRep->mountTree where mountInfo + // leads to. + mountInfo->mountTree = mountTree; + mountInfo->mountHandle = uio_MountHandle_new(destRep, mountInfo); + return mountInfo->mountHandle; + } +} + +// Mount a repository directory into same repository at a different location +// From fossil. +uio_MountHandle * +uio_transplantDir(const char *mountPoint, uio_DirHandle *sourceDir, int flags, + uio_MountHandle *relative) { + uio_MountInfo *relativeInfo; + int numPDirHandles; + uio_PDirHandle **pDirHandles; + uio_MountTreeItem **treeItems; + int i; + uio_MountHandle *handle = NULL; + + if ((flags & uio_MOUNT_RDONLY) != uio_MOUNT_RDONLY) { + // Only read-only transplants supported atm + errno = ENOSYS; + return NULL; + } + + switch (flags & uio_MOUNT_LOCATION_MASK) { + case uio_MOUNT_TOP: + case uio_MOUNT_BOTTOM: + if (relative != NULL) { + errno = EINVAL; + return NULL; + } + relativeInfo = NULL; + break; + case uio_MOUNT_BELOW: + case uio_MOUNT_ABOVE: + if (relative == NULL) { + errno = EINVAL; + return NULL; + } + relativeInfo = relative->mountInfo; + break; + default: + abort(); + } + + if (mountPoint[0] == '/') + mountPoint++; + if (!validPathName(mountPoint, strlen(mountPoint))) { + errno = EINVAL; + return NULL; + } + + if (uio_getPathPhysicalDirs(sourceDir, "", 0, + &pDirHandles, &numPDirHandles, &treeItems) == -1) { + // errno is set + return NULL; + } + if (numPDirHandles == 0) { + errno = ENOENT; + return NULL; + } + + // TODO: We only transplant the first read-only physical dir that we find + // Maybe transplant all of them? We would then have several + // uio_MountHandles to return. + for (i = 0; i < numPDirHandles; ++i) { + uio_PDirHandle *pDirHandle = pDirHandles[i]; + uio_MountInfo *oldMountInfo = treeItems[i]->mountInfo; + uio_Repository *rep = oldMountInfo->mountHandle->repository; + uio_MountInfo *mountInfo; + uio_MountTree *mountTree; + + // Only interested in read-only dirs in this incarnation + if (!uio_mountInfoIsReadOnly(oldMountInfo)) + continue; + + mountInfo = uio_MountInfo_new(oldMountInfo->fsID, NULL, pDirHandle, + uio_strdup(""), oldMountInfo->autoMount, NULL, flags); + // New mount references the same handles + uio_PDirHandle_ref(pDirHandle); + uio_PRoot_refMount(pDirHandle->pRoot); + + uio_repositoryAddMount(rep, mountInfo, + flags & uio_MOUNT_LOCATION_MASK, relativeInfo); + mountTree = uio_mountTreeAddMountInfo(rep, rep->mountTree, + mountInfo, mountPoint, flags & uio_MOUNT_LOCATION_MASK, + relativeInfo); + // mountTree is the node in rep->mountTree where mountInfo leads to + mountInfo->mountTree = mountTree; + mountInfo->mountHandle = uio_MountHandle_new(rep, mountInfo); + handle = mountInfo->mountHandle; + break; + } + + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + uio_free(treeItems); + + if (handle == NULL) + errno = ENOENT; + + return handle; +} + +int +uio_unmountDir(uio_MountHandle *mountHandle) { + uio_PRoot *pRoot; + + pRoot = mountHandle->mountInfo->pDirHandle->pRoot; + + // check if it's in use +#ifdef DEBUG + if (pRoot->mountRef == 1 && pRoot->handleRef > 0) { + fprintf(stderr, "Warning: File system to be unmounted still " + "has file descriptors open. The file system will not " + "be deallocated until these are all closed.\n"); + } +#endif + + // TODO: lock (and furtheron unlock) repository + + // remove from mount tree + uio_mountTreeRemoveMountInfo(mountHandle->repository, + mountHandle->mountInfo->mountTree, + mountHandle->mountInfo); + + // remove from mount list. + uio_repositoryRemoveMount(mountHandle->repository, + mountHandle->mountInfo); + + uio_MountInfo_delete(mountHandle->mountInfo); + + uio_MountHandle_delete(mountHandle); + uio_PRoot_unrefMount(pRoot); + return 0; +} + +int +uio_unmountAllDirs(uio_Repository *repository) { + int i; + + i = repository->numMounts; + while (i--) + uio_unmountDir(repository->mounts[i]->mountHandle); + return 0; +} + +uio_FileSystemID +uio_getMountFileSystemType(uio_MountHandle *mountHandle) { + return mountHandle->mountInfo->fsID; +} + +int +uio_close(uio_Handle *handle) { + uio_Handle_unref(handle); + return 0; +} + +int +uio_rename(uio_DirHandle *oldDir, const char *oldPath, + uio_DirHandle *newDir, const char *newPath) { + uio_PDirHandle *oldPReadDir, *newPReadDir, *newPWriteDir; + uio_MountInfo *oldReadMountInfo, *newReadMountInfo, *newWriteMountInfo; + char *oldName, *newName; + int retVal; + + if (uio_getPhysicalAccess(oldDir, oldPath, O_RDONLY, 0, + &oldReadMountInfo, &oldPReadDir, NULL, + NULL, NULL, NULL, &oldName) == -1) { + // errno is set + return -1; + } + + if (uio_getPhysicalAccess(newDir, newPath, O_WRONLY | O_CREAT | O_EXCL, + uio_GPA_NOWRITE, &newReadMountInfo, &newPReadDir, NULL, + &newWriteMountInfo, &newPWriteDir, NULL, &newName) == -1) { + int savedErrno = errno; + uio_PDirHandle_unref(oldPReadDir); + uio_free(oldName); + errno = savedErrno; + return -1; + } + + if (oldReadMountInfo != newWriteMountInfo) { + uio_PDirHandle_unref(oldPReadDir); + uio_PDirHandle_unref(newPReadDir); + uio_PDirHandle_unref(newPWriteDir); + uio_free(oldName); + uio_free(newName); + errno = EXDEV; + return -1; + } + + if (uio_mountInfoIsReadOnly(oldReadMountInfo)) { + // XXX: Doesn't uio_getPhysicalAccess already handle this? + // It doesn't return EROFS though; perhaps it should. + uio_PDirHandle_unref(oldPReadDir); + uio_PDirHandle_unref(newPReadDir); + uio_PDirHandle_unref(newPWriteDir); + uio_free(oldName); + uio_free(newName); + errno = EROFS; + return -1; + } + + if (oldReadMountInfo->pDirHandle->pRoot->handler->rename == NULL) { + uio_PDirHandle_unref(oldPReadDir); + uio_PDirHandle_unref(newPReadDir); + uio_PDirHandle_unref(newPWriteDir); + uio_free(oldName); + uio_free(newName); + errno = ENOSYS; + return -1; + } + retVal = (oldReadMountInfo->pDirHandle->pRoot->handler->rename)( + oldPReadDir, oldName, newPWriteDir, newName); + if (retVal == -1) { + int savedErrno = errno; + uio_PDirHandle_unref(oldPReadDir); + uio_PDirHandle_unref(newPReadDir); + uio_PDirHandle_unref(newPWriteDir); + uio_free(oldName); + uio_free(newName); + errno = savedErrno; + return -1; + } + + uio_PDirHandle_unref(oldPReadDir); + uio_PDirHandle_unref(newPReadDir); + uio_PDirHandle_unref(newPWriteDir); + uio_free(oldName); + uio_free(newName); + return 0; +} + +int +uio_access(uio_DirHandle *dir, const char *path, int mode) { + (void) dir; + (void) path; + (void) mode; + errno = ENOSYS; // Not implemented. + return -1; + +#if 0 + uio_PDirHandle *pReadDir; + uio_MountInfo *readMountInfo; + char *name; + int result; + + if (uio_getPhysicalAccess(dir, path, O_RDONLY, 0, + &readMountInfo, &pReadDir, NULL, + NULL, NULL, NULL, &name) == -1) { + // XXX: I copied this part from uio_stat(). Is this what I need? + if (uio_accessDir(dir, path, statBuf) == -1) { + // errno is set + return -1; + } + return 0; + } + + if (pReadDir->pRoot->handler->access == NULL) { + uio_PDirHandle_unref(pReadDir); + uio_free(name); + errno = ENOSYS; + return -1; + } + + result = (pReadDir->pRoot->handler->access)(pReadDir, name, mode); + if (result == -1) { + int savedErrno = errno; + uio_PDirHandle_unref(pReadDir); + uio_free(name); + errno = savedErrno; + return -1; + } + + uio_PDirHandle_unref(pReadDir); + uio_free(name); + return result; +#endif +} + +#if 0 +// auxiliary function to uio_access +static int +uio_accessDir(uio_DirHandle *dirHandle, const char *path, int mode) { + int numPDirHandles; + uio_PDirHandle **pDirHandles; + + if (mode & R_OK) + { + // Read permission is always granted. Nothing to check here. + } + + if (uio_getPathPhysicalDirs(dirHandle, path, strlen(path), + &pDirHandles, &numPDirHandles, NULL) == -1) { + // errno is set + return -1; + } + + if (numPDirHandles == 0) { + errno = ENOENT; + return -1; + } + + if (mode & F_OK) + { + // We need to check whether each of the directories is complete + + // WORK + } + + if (mode & W_OK) { + // If there is any directory where writing is allowed, then + // we can write. + + // WORK + errno = ENOENT; + return -1; + +#if 0 + if (uio_statOneDir(pDirHandles[0], statBuf) == -1) { + int savedErrno = errno; + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + errno = savedErrno; + return -1; + } + // TODO: atm, fstat'ing a dir will show the info for the topmost + // dir. Maybe it would make sense of merging the bits. (How?) + +#if 0 + for (i = 1; i < numPDirHandles; i++) { + struct stat statOne; + uio_PDirHandle *pDirHandle; + + if (statOneDir(pDirHandles[i], &statOne) == -1) { + // errno is set + int savedErrno = errno; + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + errno = savedErrno; + return -1; + } + + // Merge dirs: + + + } +#endif +#endif + } + + if (mode & X_OK) { + // XXX: Not implemented. + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + errno = ENOSYS; + return -1; + } + + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + return 0; +} +#endif + +int +uio_fstat(uio_Handle *handle, struct stat *statBuf) { + if (handle->root->handler->fstat == NULL) { + errno = ENOSYS; + return -1; + } + return (handle->root->handler->fstat)(handle, statBuf); +} + +int +uio_stat(uio_DirHandle *dir, const char *path, struct stat *statBuf) { + uio_PDirHandle *pReadDir; + uio_MountInfo *readMountInfo; + char *name; + int result; + + if (uio_getPhysicalAccess(dir, path, O_RDONLY, 0, + &readMountInfo, &pReadDir, NULL, + NULL, NULL, NULL, &name) == -1) { + if (uio_statDir(dir, path, statBuf) == -1) { + // errno is set + return -1; + } + return 0; + } + + if (pReadDir->pRoot->handler->stat == NULL) { + uio_PDirHandle_unref(pReadDir); + uio_free(name); + errno = ENOSYS; + return -1; + } + + result = (pReadDir->pRoot->handler->stat)(pReadDir, name, statBuf); + if (result == -1) { + int savedErrno = errno; + uio_PDirHandle_unref(pReadDir); + uio_free(name); + errno = savedErrno; + return -1; + } + + uio_PDirHandle_unref(pReadDir); + uio_free(name); + return result; +} + +// auxiliary function to uio_stat +static int +uio_statDir(uio_DirHandle *dirHandle, const char *path, + struct stat *statBuf) { + int numPDirHandles; + uio_PDirHandle **pDirHandles; + + if (uio_getPathPhysicalDirs(dirHandle, path, strlen(path), + &pDirHandles, &numPDirHandles, NULL) == -1) { + // errno is set + return -1; + } + + if (numPDirHandles == 0) { + errno = ENOENT; + return -1; + } + + if (uio_statOneDir(pDirHandles[0], statBuf) == -1) { + int savedErrno = errno; + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + errno = savedErrno; + return -1; + } + // TODO: atm, fstat'ing a dir will show the info for the topmost + // dir. Maybe it would make sense of merging the bits. (How?) + +#if 0 + for (i = 1; i < numPDirHandles; i++) { + struct stat statOne; + uio_PDirHandle *pDirHandle; + + if (statOneDir(pDirHandles[i], &statOne) == -1) { + // errno is set + int savedErrno = errno; + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + errno = savedErrno; + return -1; + } + + // Merge dirs: + + + } +#endif + + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + return 0; +} + +static int +uio_statOneDir(uio_PDirHandle *pDirHandle, struct stat *statBuf) { + if (pDirHandle->pRoot->handler->stat == NULL) { + errno = ENOSYS; + return -1; + } + return (pDirHandle->pRoot->handler->stat)(pDirHandle, ".", statBuf); + // sets errno on error +} + +int +uio_mkdir(uio_DirHandle *dir, const char *path, mode_t mode) { + uio_PDirHandle *pReadDir, *pWriteDir; + uio_MountInfo *readMountInfo, *writeMountInfo; + char *name; + uio_PDirHandle *newDirHandle; + + if (uio_getPhysicalAccess(dir, path, O_WRONLY | O_CREAT | O_EXCL, 0, + &readMountInfo, &pReadDir, NULL, + &writeMountInfo, &pWriteDir, NULL, &name) == -1) { + // errno is set + if (errno == EISDIR) + errno = EEXIST; + return -1; + } + uio_PDirHandle_unref(pReadDir); + + if (pWriteDir->pRoot->handler->mkdir == NULL) { + uio_free(name); + uio_PDirHandle_unref(pWriteDir); + errno = ENOSYS; + return -1; + } + + newDirHandle = (pWriteDir->pRoot->handler->mkdir)(pWriteDir, name, mode); + if (newDirHandle == NULL) { + int savedErrno = errno; + uio_free(name); + uio_PDirHandle_unref(pWriteDir); + errno = savedErrno; + return -1; + } + + uio_PDirHandle_unref(pWriteDir); + uio_PDirHandle_unref(newDirHandle); + uio_free(name); + return 0; +} + +uio_Handle * +uio_open(uio_DirHandle *dir, const char *path, int flags, mode_t mode) { + uio_PDirHandle *readPDirHandle, *writePDirHandle, *pDirHandle; + uio_MountInfo *readMountInfo, *writeMountInfo; + char *name; + uio_Handle *handle; + + if (uio_getPhysicalAccess(dir, path, flags, 0, + &readMountInfo, &readPDirHandle, NULL, + &writeMountInfo, &writePDirHandle, NULL, &name) == -1) { + // errno is set + return NULL; + } + + if ((flags & O_ACCMODE) == O_RDONLY) { + // WritePDirHandle is not filled in. + pDirHandle = readPDirHandle; + } else if (readPDirHandle == writePDirHandle) { + // In general, the dirs can be the same even when the handles are + // not the same. But here it works, because uio_getPhysicalAccess + // guarantees it. + uio_PDirHandle_unref(writePDirHandle); + pDirHandle = readPDirHandle; + } else { + // need to write + uio_PDirEntryHandle *entry; + + entry = uio_getPDirEntryHandle(readPDirHandle, name); + if (entry != NULL) { + // file already exists + uio_PDirEntryHandle_unref(entry); + if ((flags & O_CREAT) == O_CREAT && + (flags & O_EXCL) == O_EXCL) { + uio_free(name); + uio_PDirHandle_unref(readPDirHandle); + uio_PDirHandle_unref(writePDirHandle); + errno = EEXIST; + return NULL; + } + if ((flags & O_TRUNC) == O_TRUNC) { + // No use copying the file to the writable dir. + // As it doesn't exists there, O_TRUNC needs to be turned off + // though. + flags &= ~O_TRUNC; + } else { + // file needs to be copied + if (uio_copyFilePhysical(readPDirHandle, name, writePDirHandle, + name) == -1) { + int savedErrno = errno; + uio_free(name); + uio_PDirHandle_unref(readPDirHandle); + uio_PDirHandle_unref(writePDirHandle); + errno = savedErrno; + return NULL; + } + } + } else { + // file does not exist + if (((flags & O_ACCMODE) == O_RDONLY) || + (flags & O_CREAT) != O_CREAT) { + uio_free(name); + uio_PDirHandle_unref(readPDirHandle); + uio_PDirHandle_unref(writePDirHandle); + errno = ENOENT; + return NULL; + } + } + uio_PDirHandle_unref(readPDirHandle); + pDirHandle = writePDirHandle; + } + + handle = (pDirHandle->pRoot->handler->open)(pDirHandle, name, flags, mode); + // Also adds a new entry to the physical dir if appropriate. + if (handle == NULL) { + int savedErrno = errno; + uio_free(name); + uio_PDirHandle_unref(pDirHandle); + errno = savedErrno; + return NULL; + } + + uio_free(name); + uio_PDirHandle_unref(pDirHandle); + return handle; +} + +uio_DirHandle * +uio_openDir(uio_Repository *repository, const char *path, int flags) { + uio_DirHandle *dirHandle; + const char * const rootStr = ""; + + dirHandle = uio_DirHandle_new(repository, + unconst(rootStr), unconst(rootStr)); + // dirHandle->path will be replaced before uio_openDir() + // exits() + if (uio_verifyPath(dirHandle, path, &dirHandle->path) == -1) { + int savedErrno = errno; + uio_DirHandle_free(dirHandle); + errno = savedErrno; + return NULL; + } + // dirHandle->path is no longer equal to 'path' at this point. + // TODO: increase ref in repository? + dirHandle->rootEnd = dirHandle->path; + if (flags & uio_OD_ROOT) + dirHandle->rootEnd += strlen(dirHandle->path); + return dirHandle; +} + +uio_DirHandle * +uio_openDirRelative(uio_DirHandle *base, const char *path, int flags) { + uio_DirHandle *dirHandle; + char *newPath; + + if (uio_verifyPath(base, path, &newPath) == -1) { + // errno is set + return NULL; + } + if (flags & uio_OD_ROOT) { + dirHandle = uio_DirHandle_new(base->repository, + newPath, newPath + strlen(newPath)); + // TODO: increase ref in base->repository? + } else { + // use the root of the base dir + dirHandle = uio_DirHandle_new(base->repository, + newPath, newPath + (base->rootEnd - base->path)); + } + return dirHandle; +} + +int +uio_closeDir(uio_DirHandle *dirHandle) { + uio_DirHandle_unref(dirHandle); + return 0; +} + +ssize_t +uio_read(uio_Handle *handle, void *buf, size_t count) { + return (handle->root->handler->read)(handle, buf, count); +} + +int +uio_rmdir(uio_DirHandle *dirHandle, const char *path) { + int numPDirHandles; + uio_PDirHandle *pDirHandle, **pDirHandles; + const char *pathEnd, *name; + uio_PDirEntryHandle *entry; + uio_MountTreeItem **items; + int i; + int numDeleted; + + pathEnd = strrchr(path, '/'); + if (pathEnd == NULL) { + pathEnd = path; + name = path; + } else + name = pathEnd + 1; + + if (uio_getPathPhysicalDirs(dirHandle, path, pathEnd - path, + &pDirHandles, &numPDirHandles, &items) == -1) { + // errno is set + return -1; + } + + entry = NULL; + // Should be set before a possible goto. + + if (name[0] == '\0') { + // path was of the form "foo/bar/" or "/foo/bar/" + // These are intentionally not accepted. + // I see this as a path and not as a directory identifier. + errno = ENOENT; + goto err; + } + + numDeleted = 0; + for (i = 0; i < numPDirHandles; i++) { + pDirHandle = pDirHandles[i]; + entry = uio_getPDirEntryHandle(pDirHandle, name); + + if (entry == NULL) + continue; + + if (!uio_PDirEntryHandle_isDir(entry)) { + errno = ENOTDIR; + goto err; + } + + if (uio_mountInfoIsReadOnly(items[i]->mountInfo)) { + errno = EROFS; + goto err; + } + + if (pDirHandle->pRoot->handler->rmdir == NULL) { + errno = ENOSYS; + goto err; + } + + if ((pDirHandle->pRoot->handler->rmdir)(pDirHandle, name) == -1) { + // errno is set + goto err; + } + numDeleted++; + uio_PDirEntryHandle_unref(entry); + } + entry = NULL; + + if (numDeleted == 0) { + errno = ENOENT; + goto err; + } + + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + uio_free(items); + return 0; + +err: + { + int savedErrno = errno; + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + uio_free(items); + if (entry != NULL) + uio_PDirEntryHandle_unref(entry); + errno = savedErrno; + return -1; + } +} + +static void +uio_PDirHandles_delete(uio_PDirHandle *pDirHandles[], int numPDirHandles) { + while (numPDirHandles--) + uio_PDirHandle_unref(pDirHandles[numPDirHandles]); + uio_free(pDirHandles); +} + +int +uio_lseek(uio_Handle *handle, off_t offset, int whence) { + if (handle->root->handler->seek == NULL) { + errno = ENOSYS; + return -1; + } + return (handle->root->handler->seek)(handle, offset, whence); +} + +ssize_t +uio_write(uio_Handle *handle, const void *buf, size_t count) { + if (handle->root->handler->write == NULL) { + errno = ENOSYS; + return -1; + } + return (handle->root->handler->write)(handle, buf, count); +} + +int +uio_unlink(uio_DirHandle *dirHandle, const char *path) { + int numPDirHandles; + uio_PDirHandle *pDirHandle, **pDirHandles; + const char *pathEnd, *name; + uio_PDirEntryHandle *entry; + uio_MountTreeItem **items; + int i; + int numDeleted; + + pathEnd = strrchr(path, '/'); + if (pathEnd == NULL) { + pathEnd = path; + name = path; + } else + name = pathEnd + 1; + + if (uio_getPathPhysicalDirs(dirHandle, path, pathEnd - path, + &pDirHandles, &numPDirHandles, &items) == -1) { + // errno is set + return -1; + } + + entry = NULL; + // Should be set before a possible goto. + + if (name[0] == '\0') { + // path was of the form "foo/bar/" or "/foo/bar/" + errno = ENOENT; + goto err; + } + + numDeleted = 0; + for (i = 0; i < numPDirHandles; i++) { + pDirHandle = pDirHandles[i]; + entry = uio_getPDirEntryHandle(pDirHandle, name); + + if (entry == NULL) + continue; + + if (uio_PDirEntryHandle_isDir(entry)) { + errno = EISDIR; + goto err; + } + + if (uio_mountInfoIsReadOnly(items[i]->mountInfo)) { + errno = EROFS; + goto err; + } + + if (pDirHandle->pRoot->handler->unlink == NULL) { + errno = ENOSYS; + goto err; + } + + if ((pDirHandle->pRoot->handler->unlink)(pDirHandle, name) == -1) { + // errno is set + goto err; + } + numDeleted++; + uio_PDirEntryHandle_unref(entry); + } + entry = NULL; + + if (numDeleted == 0) { + errno = ENOENT; + goto err; + } + + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + uio_free(items); + return 0; + +err: + { + int savedErrno = errno; + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + uio_free(items); + if (entry != NULL) + uio_PDirEntryHandle_unref(entry); + errno = savedErrno; + return -1; + } +} + +// inPath and *outPath may point to the same location +int +uio_getFileLocation(uio_DirHandle *dir, const char *inPath, + int flags, uio_MountHandle **mountHandle, char **outPath) { + uio_PDirHandle *readPDirHandle, *writePDirHandle; + uio_MountInfo *readMountInfo, *writeMountInfo, *mountInfo; + char *name; + char *readPRootPath, *writePRootPath, *pRootPath; + + if (uio_getPhysicalAccess(dir, inPath, flags, 0, + &readMountInfo, &readPDirHandle, &readPRootPath, + &writeMountInfo, &writePDirHandle, &writePRootPath, + &name) == -1) { + // errno is set + return -1; + } + + // TODO: This code is partly the same as the code in uio_open(). + // probably some code could be put in a seperate function. + if ((flags & O_ACCMODE) == O_RDONLY) { + // WritePDirHandle is not filled in. + uio_PDirHandle_unref(readPDirHandle); + pRootPath = readPRootPath; + mountInfo = readMountInfo; + } else if (readPDirHandle == writePDirHandle) { + // In general, the dirs can be the same even when the handles are + // not the same. But here it works, because uio_getPhysicalAccess + // guarantees it. + uio_PDirHandle_unref(readPDirHandle); + uio_PDirHandle_unref(writePDirHandle); + pRootPath = readPRootPath; + mountInfo = readMountInfo; + uio_free(writePRootPath); + } else { + // need to write + uio_PDirEntryHandle *entry; + + entry = uio_getPDirEntryHandle(readPDirHandle, name); + if (entry != NULL) { + // file already exists + uio_PDirEntryHandle_unref(entry); + if ((flags & O_CREAT) == O_CREAT && + (flags & O_EXCL) == O_EXCL) { + uio_free(name); + uio_PDirHandle_unref(readPDirHandle); + uio_free(readPRootPath); + uio_PDirHandle_unref(writePDirHandle); + uio_free(writePRootPath); + errno = EEXIST; + return -1; + } + if ((flags & O_TRUNC) == O_TRUNC) { + // No use copying the file to the writable dir. + // As it doesn't exists there, O_TRUNC needs to be turned off + // though. + flags &= ~O_TRUNC; + } else { + // file needs to be copied + if (uio_copyFilePhysical(readPDirHandle, name, writePDirHandle, + name) == -1) { + int savedErrno = errno; + uio_free(name); + uio_PDirHandle_unref(readPDirHandle); + uio_free(readPRootPath); + uio_PDirHandle_unref(writePDirHandle); + uio_free(writePRootPath); + errno = savedErrno; + return -1; + } + } + } else { + // file does not exist + if (((flags & O_ACCMODE) == O_RDONLY) || + (flags & O_CREAT) != O_CREAT) { + uio_free(name); + uio_PDirHandle_unref(readPDirHandle); + uio_free(readPRootPath); + uio_PDirHandle_unref(writePDirHandle); + uio_free(writePRootPath); + errno = ENOENT; + return -1; + } + } + uio_PDirHandle_unref(readPDirHandle); + uio_PDirHandle_unref(writePDirHandle); + pRootPath = writePRootPath; + mountInfo = writeMountInfo; + uio_free(readPRootPath); + } + + uio_free(name); + + *mountHandle = mountInfo->mountHandle; + *outPath = pRootPath; + return 0; +} + + +// *** begin dirList stuff *** // + +#define uio_DIR_BUFFER_SIZE 2048 + // Large values will give significantly better speed for large + // directories. What is stored in the buffer is file names + // plus one pointer per file name, so with an average size of 16 + // characters per file (including \0), a buffer of size 2048 will + // store approximately 100 files. + // It should be at least big enough to store one entry (NAME_MAX is + // 255 on POSIX systems). + // TODO: add a compile-time check for this + +typedef struct uio_DirBufferLink { + char *buffer; + int numEntries; + struct uio_DirBufferLink *next; +} uio_DirBufferLink; + +static int strPtrCmp(const char * const *ptr1, const char * const *ptr2); +static void uio_DirBufferLink_free(uio_DirBufferLink *sdbl); +static void uio_DirBufferChain_free(uio_DirBufferLink *dirBufferLink); +static uio_DirList *uio_getDirListMulti(uio_PDirHandle **pDirHandles, + int numPDirHandles, const char *pattern, match_MatchType matchType); +static uio_DirList *uio_makeDirList(const char **newNames, + const char * const *names, int numNames); +static uio_DirList *uio_DirList_new(const char **names, int numNames, + char *buffer); +static void uio_collectDirEntries(uio_PDirHandle *pDirHandle, + uio_DirBufferLink **linkPtr, int *numEntries); +static inline uio_DirList *uio_DirList_alloc(void); +static void uio_filterNames(const char * const *names, int numNames, + const char **newNames, int *numNewNames, + match_MatchContext *matchContext); + +static uio_EntriesContext *uio_openEntriesPhysical(uio_PDirHandle *dirHandle); +static int uio_readEntriesPhysical(uio_EntriesContext *iterator, char *buf, + size_t len); +static void uio_closeEntriesPhysical(uio_EntriesContext *iterator); +static uio_EntriesContext *uio_EntriesContext_new(uio_PRoot *pRoot, + uio_NativeEntriesContext *native); +static inline uio_EntriesContext *uio_EntriesContext_alloc(void); +static inline void uio_EntriesContext_delete(uio_EntriesContext *entriesContext); +static inline void uio_EntriesContext_free(uio_EntriesContext + *entriesContext); + +// The caller may modify the elements of the .names field of the result, but +// .names itself, and the rest of the elements of dirList should be left +// alone, so that they will be freed by uio_DirList_free(). +uio_DirList * +uio_getDirList(uio_DirHandle *dirHandle, const char *path, const char *pattern, + match_MatchType matchType) { + int numPDirHandles; + uio_PDirHandle **pDirHandles; + uio_DirList *result; + + if (uio_getPathPhysicalDirs(dirHandle, path, strlen(path), + &pDirHandles, &numPDirHandles, NULL) == -1) { + // errno is set + return NULL; + } + + if (numPDirHandles == 0) { + assert(pDirHandles == NULL); + // nothing to free + return uio_DirList_new(NULL, 0, NULL); + } + + result = uio_getDirListMulti(pDirHandles, numPDirHandles, pattern, + matchType); + + { + int savedErrno; + savedErrno = errno; + uio_PDirHandles_delete(pDirHandles, numPDirHandles); + errno = savedErrno; + } + return result; +} + +// Names and newNames may point to the same location. +// numNewNames may point to &numNames. +static void +uio_filterDoubleNames(const char * const *names, int numNames, + const char **newNames, int *numNewNames) { + const char * const *endNames; + const char *prevName; + int newNum; + + if (numNames == 0) { + *numNewNames = 0; + return; + } + + endNames = names + numNames; + prevName = *names; + *newNames = *names; + newNames++; + names++; + newNum = 1; + while (names < endNames) { + if (strcmp(prevName, *names) != 0) { + *newNames = *names; + newNum++; + prevName = *names; + newNames++; + } + names++; + } + *numNewNames = newNum; +} + +static uio_DirList * +uio_getDirListMulti(uio_PDirHandle **pDirHandles, + int numPDirHandles, const char *pattern, match_MatchType matchType) { + int pDirI; // physical dir iterator + uio_DirBufferLink **links; // array of bufferLinks for each physical dir + uio_DirBufferLink *linkPtr; + int *numNames; // number of entries in each physical dir + int totalNumNames; + const char **bigNameBuffer; // buffer where all names will end up together + const char **destPtr; + uio_DirList *result; + match_Result matchResult; + match_MatchContext *matchContext; + + matchResult = match_prepareContext(pattern, &matchContext, matchType); + if (matchResult != match_OK) { +#ifdef DEBUG + fprintf(stderr, "Error compiling match function: %s.\n", + match_errorString(matchContext, matchResult)); +#endif + match_freeContext(matchContext); + errno = EIO; + // I actually want to signal an internal error. + // EIO comes closes + return NULL; + } + + // first get the directory listings for all seperate relevant dirs. + totalNumNames = 0; + links = uio_malloc(numPDirHandles * sizeof (uio_DirBufferLink *)); + numNames = uio_malloc(numPDirHandles * sizeof (int)); + for (pDirI = 0; pDirI < numPDirHandles; pDirI++) { + uio_collectDirEntries(pDirHandles[pDirI], &links[pDirI], + &numNames[pDirI]); + totalNumNames += numNames[pDirI]; + } + + bigNameBuffer = uio_malloc(totalNumNames * sizeof (uio_DirBufferLink *)); + + // Fill the bigNameBuffer with all the names from all the DirBufferLinks + // of all the physical dirs. + destPtr = bigNameBuffer; + totalNumNames = 0; + for (pDirI = 0; pDirI < numPDirHandles; pDirI++) { + for (linkPtr = links[pDirI]; linkPtr != NULL; + linkPtr = linkPtr->next) { + int numNewNames; + uio_filterNames((const char * const *) linkPtr->buffer, + linkPtr->numEntries, destPtr, + &numNewNames, matchContext); + totalNumNames += numNewNames; + destPtr += numNewNames; + } + } + + match_freeContext(matchContext); + + // Sort the bigNameBuffer + // Necessary for removing doubles. + // Not really necessary if the big list was the result of only one + // physical dir, but let's output a sorted list anyhow. + qsort((void *) bigNameBuffer, totalNumNames, sizeof (char *), + (int (*)(const void *, const void *)) strPtrCmp); + + // remove doubles + // (unnecessary if the big list was the result of only one physical dir) + if (numPDirHandles > 1) { + uio_filterDoubleNames(bigNameBuffer, totalNumNames, + bigNameBuffer, &totalNumNames); + } + + // resize the bigNameBuffer + bigNameBuffer = uio_realloc((void *) bigNameBuffer, + totalNumNames * sizeof (char *)); + + // put the lot in a DirList, copying the strings themselves + result = uio_makeDirList(bigNameBuffer, bigNameBuffer, + totalNumNames); + + // free the old junk + for (pDirI = 0; pDirI < numPDirHandles; pDirI++) + uio_DirBufferChain_free(links[pDirI]); + uio_free(links); + uio_free(numNames); + + return result; +} + +// 'buffer' and 'names' may be the same dir +// 'names' contains an array of 'numNames' pointers. +// 'newNames', if non-NULL, will be used as the array of new pointers +// (to a copy of the strings) in the DirList. +static uio_DirList * +uio_makeDirList(const char **newNames, const char * const *names, + int numNames) { + int i; + size_t len, totLen; + char *bufPtr; + uio_DirList *result; + + if (newNames == NULL) + newNames = uio_malloc(numNames * sizeof (char *)); + + totLen = 0; + for (i = 0; i < numNames; i++) + totLen += strlen(names[i]); + totLen += numNames; + // for the \0's + + result = uio_DirList_new(newNames, numNames, uio_malloc(totLen)); + + bufPtr = result->buffer; + for (i = 0; i < numNames; i++) { + len = strlen(names[i]) + 1; + memcpy(bufPtr, names[i], len); + newNames[i] = bufPtr; + bufPtr += len; + } + return result; +} + +static void +uio_collectDirEntries(uio_PDirHandle *pDirHandle, uio_DirBufferLink **linkPtr, + int *numEntries) { + uio_EntriesContext *entriesContext; + uio_DirBufferLink **linkEndPtr; // where to attach the next link + int numRead; + int totalEntries; + char *buffer; + + entriesContext = uio_openEntriesPhysical(pDirHandle); + if (entriesContext == NULL) { +#ifdef DEBUG + fprintf(stderr, "Error: uio_openEntriesPhysical() failed: %s\n", + strerror(errno)); +#endif + *linkPtr = NULL; + *numEntries = 0; + return; + } + linkEndPtr = linkPtr; + totalEntries = 0; + while (1) { + *linkEndPtr = uio_malloc(sizeof (uio_DirBufferLink)); + buffer = uio_malloc(uio_DIR_BUFFER_SIZE); + (*linkEndPtr)->buffer = buffer; + numRead = uio_readEntriesPhysical(entriesContext, buffer, + uio_DIR_BUFFER_SIZE); + if (numRead == 0) { + fprintf(stderr, "Warning: uio_DIR_BUFFER_SIZE is too small to " + "hold a certain large entry on its own!\n"); + uio_DirBufferLink_free(*linkEndPtr); + break; + } + totalEntries += numRead; + (*linkEndPtr)->numEntries = numRead; + if (((char **) buffer)[numRead - 1] == NULL) { + // The entry being NULL means this is the last buffer + // Decrement the amount of queries to get the real number. + (*linkEndPtr)->numEntries--; + totalEntries--; + linkEndPtr = &(*linkEndPtr)->next; + break; + } + linkEndPtr = &(*linkEndPtr)->next; + } + *linkEndPtr = NULL; + uio_closeEntriesPhysical(entriesContext); + *numEntries = totalEntries; +} + +static void +uio_filterNames(const char * const *names, int numNames, + const char **newNames, int *numNewNames, + match_MatchContext *matchContext) { + int newNum; + const char * const *namesEnd; + match_Result matchResult; + + newNum = 0; + namesEnd = names + numNames; + while (names < namesEnd) { + matchResult = match_matchPattern(matchContext, *names); + if (matchResult == match_MATCH) { + *newNames = *names; + newNames++; + newNum++; + } else if (matchResult != match_NOMATCH) { + fprintf(stderr, "Error trying to match pattern: %s.\n", + match_errorString(matchContext, matchResult)); + } + names++; + } + *numNewNames = newNum; +} + +static int +strPtrCmp(const char * const *ptr1, const char * const *ptr2) { + return strcmp(*ptr1, *ptr2); +} + +static uio_EntriesContext * +uio_openEntriesPhysical(uio_PDirHandle *dirHandle) { + uio_NativeEntriesContext *native; + uio_PRoot *pRoot; + + pRoot = dirHandle->pRoot; + + assert(pRoot->handler->openEntries != NULL); + native = pRoot->handler->openEntries(dirHandle); + if (native == NULL) + return NULL; + uio_PRoot_refHandle(pRoot); + return uio_EntriesContext_new(pRoot, native); +} + +static int +uio_readEntriesPhysical(uio_EntriesContext *iterator, char *buf, size_t len) { + assert(iterator->pRoot->handler->readEntries != NULL); + return iterator->pRoot->handler->readEntries(&iterator->native, buf, len); +} + +static void +uio_closeEntriesPhysical(uio_EntriesContext *iterator) { + assert(iterator->pRoot->handler->closeEntries != NULL); + iterator->pRoot->handler->closeEntries(iterator->native); + uio_PRoot_unrefHandle(iterator->pRoot); + uio_EntriesContext_delete(iterator); +} + +static uio_EntriesContext * +uio_EntriesContext_new(uio_PRoot *pRoot, uio_NativeEntriesContext *native) { + uio_EntriesContext *result; + result = uio_EntriesContext_alloc(); + result->pRoot = pRoot; + result->native = native; + return result; +} + +static inline uio_EntriesContext * +uio_EntriesContext_alloc(void) { + return uio_malloc(sizeof (uio_EntriesContext)); +} + +static inline void +uio_EntriesContext_delete(uio_EntriesContext *entriesContext) { + uio_EntriesContext_free(entriesContext); +} + +static inline void +uio_EntriesContext_free(uio_EntriesContext *entriesContext) { + uio_free(entriesContext); +} + +static void +uio_DirBufferLink_free(uio_DirBufferLink *dirBufferLink) { + uio_free(dirBufferLink->buffer); + uio_free(dirBufferLink); +} + +static void +uio_DirBufferChain_free(uio_DirBufferLink *dirBufferLink) { + uio_DirBufferLink *next; + + while (dirBufferLink != NULL) { + next = dirBufferLink->next; + uio_DirBufferLink_free(dirBufferLink); + dirBufferLink = next; + } +} + +static uio_DirList * +uio_DirList_new(const char **names, int numNames, char *buffer) { + uio_DirList *result; + + result = uio_DirList_alloc(); + result->names = names; + result->numNames = numNames; + result->buffer = buffer; + return result; +} + +static uio_DirList * +uio_DirList_alloc(void) { + return uio_malloc(sizeof (uio_DirList)); +} + +void +uio_DirList_free(uio_DirList *dirList) { + if (dirList->buffer) + uio_free(dirList->buffer); + if (dirList->names) + uio_free((void *) dirList->names); + uio_free(dirList); +} + +// *** end DirList stuff *** // + + +// *** PDirEntryHandle stuff *** // + +uio_PDirEntryHandle * +uio_getPDirEntryHandle(const uio_PDirHandle *dirHandle, + const char *name) { + assert(dirHandle->pRoot->handler != NULL); + return dirHandle->pRoot->handler->getPDirEntryHandle(dirHandle, name); +} + +void +uio_PDirHandle_deletePDirHandleExtra(uio_PDirHandle *pDirHandle) { + if (pDirHandle->extra == NULL) + return; + assert(pDirHandle->pRoot->handler->deletePDirHandleExtra != NULL); + pDirHandle->pRoot->handler->deletePDirHandleExtra(pDirHandle->extra); +} + +void +uio_PFileHandle_deletePFileHandleExtra(uio_PFileHandle *pFileHandle) { + if (pFileHandle->extra == NULL) + return; + assert(pFileHandle->pRoot->handler->deletePFileHandleExtra != NULL); + pFileHandle->pRoot->handler->deletePFileHandleExtra(pFileHandle->extra); +} + +uio_PDirHandle * +uio_PDirHandle_new(uio_PRoot *pRoot, uio_PDirHandleExtra extra) { + uio_PDirHandle *result; + + result = uio_PDirHandle_alloc(); + result->flags = uio_PDirEntryHandle_TYPE_DIR; + result->ref = 1; + result->pRoot = pRoot; + result->extra = extra; + return result; +} + +void +uio_PDirEntryHandle_delete(uio_PDirEntryHandle *pDirEntryHandle) { + if (uio_PDirEntryHandle_isDir(pDirEntryHandle)) { + uio_PDirHandle_delete((uio_PDirHandle *) pDirEntryHandle); + } else { + uio_PFileHandle_delete((uio_PFileHandle *) pDirEntryHandle); + } +} + +void +uio_PDirHandle_delete(uio_PDirHandle *pDirHandle) { + uio_PDirHandle_deletePDirHandleExtra(pDirHandle); + uio_PDirHandle_free(pDirHandle); +} + +static inline uio_PDirHandle * +uio_PDirHandle_alloc(void) { + uio_PDirHandle *result = uio_malloc(sizeof (uio_PDirHandle)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_PDirHandle, (void *) result); +#endif + return result; +} + +static inline void +uio_PDirHandle_free(uio_PDirHandle *pDirHandle) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_PDirHandle, (void *) pDirHandle); +#endif + uio_free(pDirHandle); +} + +uio_PFileHandle * +uio_PFileHandle_new(uio_PRoot *pRoot, uio_PFileHandleExtra extra) { + uio_PFileHandle *result; + + result = uio_PFileHandle_alloc(); + result->flags = uio_PDirEntryHandle_TYPE_REG; + result->ref = 1; + result->pRoot = pRoot; + result->extra = extra; + return result; +} + +void +uio_PFileHandle_delete(uio_PFileHandle *pFileHandle) { + uio_PFileHandle_deletePFileHandleExtra(pFileHandle); + uio_PFileHandle_free(pFileHandle); +} + +static inline uio_PFileHandle * +uio_PFileHandle_alloc(void) { + uio_PFileHandle *result = uio_malloc(sizeof (uio_PFileHandle)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_PFileHandle, (void *) result); +#endif + return result; +} + +static inline void +uio_PFileHandle_free(uio_PFileHandle *pFileHandle) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_PFileHandle, (void *) pFileHandle); +#endif + uio_free(pFileHandle); +} + +// *** PDirEntryHandle stuff *** // + + +// ref count set to 1 +uio_Handle * +uio_Handle_new(uio_PRoot *root, uio_NativeHandle native, int openFlags) { + uio_Handle *handle; + + handle = uio_Handle_alloc(); + handle->ref = 1; + uio_PRoot_refHandle(root); + handle->root = root; + handle->native = native; + handle->openFlags = openFlags; + return handle; +} + +void +uio_Handle_delete(uio_Handle *handle) { + (handle->root->handler->close)(handle); + uio_PRoot_unrefHandle(handle->root); + uio_Handle_free(handle); +} + +static inline uio_Handle * +uio_Handle_alloc(void) { + uio_Handle *result = uio_malloc(sizeof (uio_Handle)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_Handle, (void *) result); +#endif + return result; +} + +static inline void +uio_Handle_free(uio_Handle *handle) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_Handle, (void *) handle); +#endif + uio_free(handle); +} + +// ref count set to 1 +static uio_DirHandle * +uio_DirHandle_new(uio_Repository *repository, char *path, char *rootEnd) { + uio_DirHandle *result; + + result = uio_DirHandle_alloc(); + result->ref = 1; + result->repository = repository; + result->path = path; + result->rootEnd = rootEnd; + return result; +} + +void +uio_DirHandle_delete(uio_DirHandle *dirHandle) { + uio_free(dirHandle->path); + uio_DirHandle_free(dirHandle); +} + +static inline uio_DirHandle * +uio_DirHandle_alloc(void) { + uio_DirHandle *result = uio_malloc(sizeof (uio_DirHandle)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_DirHandle, (void *) result); +#endif + return result; +} + +static inline void +uio_DirHandle_free(uio_DirHandle *dirHandle) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_DirHandle, (void *) dirHandle); +#endif + uio_free(dirHandle); +} + +void +uio_DirHandle_print(const uio_DirHandle *dirHandle, FILE *out) { + fprintf(out, "["); + fwrite(dirHandle->path, dirHandle->path - dirHandle->rootEnd, 1, out); + fprintf(out, "]%s", dirHandle->rootEnd); +} + +static uio_MountHandle * +uio_MountHandle_new(uio_Repository *repository, uio_MountInfo *mountInfo) { + uio_MountHandle *result; + + result = uio_MountHandle_alloc(); + result->repository = repository; + result->mountInfo = mountInfo; + return result; +} + +static inline void +uio_MountHandle_delete(uio_MountHandle *mountHandle) { + uio_MountHandle_free(mountHandle); +} + +static inline uio_MountHandle * +uio_MountHandle_alloc(void) { + uio_MountHandle *result = uio_malloc(sizeof (uio_MountHandle)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_MountHandle, (void *) result); +#endif + return result; +} + +static inline void +uio_MountHandle_free(uio_MountHandle *mountHandle) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_MountHandle, (void *) mountHandle); +#endif + uio_free(mountHandle); +} + + + diff --git a/src/libs/uio/io.h b/src/libs/uio/io.h new file mode 100644 index 0000000..813a2c3 --- /dev/null +++ b/src/libs/uio/io.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_IO_H_ +#define LIBS_UIO_IO_H_ + +#include +#include + +typedef struct uio_Handle uio_Handle; +typedef struct uio_DirHandle uio_DirHandle; +typedef struct uio_DirList uio_DirList; +typedef struct uio_MountHandle uio_MountHandle; + +typedef enum { + uio_MOUNT_BOTTOM = (0 << 2), + uio_MOUNT_TOP = (1 << 2), + uio_MOUNT_BELOW = (2 << 2), + uio_MOUNT_ABOVE = (3 << 2) +} uio_MountLocation; + +#include "match.h" +#include "fstypes.h" +#include "mount.h" +#include "mounttree.h" +#include "uiostream.h" +#include "getint.h" +#include "debug.h" + +struct uio_AutoMount { + const char *pattern; + match_MatchType matchType; + uio_FileSystemID fileSystemID; + int mountFlags; +// uio_AutoMount **autoMount; + // automount rules to apply to file systems automounted + // because of this automount rule. +}; + +#ifndef uio_INTERNAL +struct uio_DirList { + const char **names; + int numNames; + // The rest of the fields are not visible from the outside +}; +#endif + + +// Initialise the resource system +void uio_init(void); + +// Uninitialise the resource system +void uio_unInit(void); + +// Open a repository. +uio_Repository *uio_openRepository(int flags); + +// Close a repository opened by uio_openRepository(). +void uio_closeRepository(uio_Repository *repository); + +// Mount a directory into a repository +uio_MountHandle *uio_mountDir(uio_Repository *destRep, const char *mountPoint, + uio_FileSystemID fsType, + uio_DirHandle *sourceDir, const char *sourcePath, + const char *inPath, uio_AutoMount **autoMount, int flags, + uio_MountHandle *relative); + +// Mount a repository directory into same repository at a different +// location. +// From fossil. +uio_MountHandle *uio_transplantDir(const char *mountPoint, + uio_DirHandle *sourceDir, int flags, uio_MountHandle *relative); + +// Unmount a previously mounted dir. +int uio_unmountDir(uio_MountHandle *mountHandle); + +// Unmount all previously mounted dirs. +int uio_unmountAllDirs(uio_Repository *repository); + +// Get the filesystem identifier for a mounted directory. +uio_FileSystemID uio_getMountFileSystemType(uio_MountHandle *mountHandle); + +// Open a file +uio_Handle *uio_open(uio_DirHandle *dir, const char *file, int flags, + mode_t mode); + +// Close a file descriptor for a file opened by uio_open +int uio_close(uio_Handle *handle); + +// Rename or move a file or directory. +int uio_rename(uio_DirHandle *oldDir, const char *oldPath, + uio_DirHandle *newDir, const char *newPath); + +// Test permissions on a file or directory. +int uio_access(uio_DirHandle *dir, const char *path, int mode); + +// Fstat a file descriptor +int uio_fstat(uio_Handle *handle, struct stat *statBuf); + +int uio_stat(uio_DirHandle *dir, const char *path, struct stat *statBuf); + +int uio_mkdir(uio_DirHandle *dir, const char *name, mode_t mode); + +ssize_t uio_read(uio_Handle *handle, void *buf, size_t count); + +int uio_rmdir(uio_DirHandle *dirHandle, const char *path); + +int uio_lseek(uio_Handle *handle, off_t offset, int whence); + +ssize_t uio_write(uio_Handle *handle, const void *buf, size_t count); + +int uio_unlink(uio_DirHandle *dirHandle, const char *path); + +int uio_getFileLocation(uio_DirHandle *dir, const char *inPath, + int flags, uio_MountHandle **mountHandle, char **outPath); + +// Get a directory handle. +uio_DirHandle *uio_openDir(uio_Repository *repository, const char *path, + int flags); +#define uio_OD_ROOT 1 + +// Get a directory handle using a path relative to another handle. +uio_DirHandle *uio_openDirRelative(uio_DirHandle *base, const char *path, + int flags); + +// Release a directory handle +int uio_closeDir(uio_DirHandle *dirHandle); + +uio_DirList *uio_getDirList(uio_DirHandle *dirHandle, const char *path, + const char *pattern, match_MatchType matchType); +void uio_DirList_free(uio_DirList *dirList); + +// For debugging purposes +void uio_DirHandle_print(const uio_DirHandle *dirHandle, FILE *out); + + +#ifdef DEBUG +# define uio_DEBUG +#endif + +#endif /* LIBS_UIO_IO_H_ */ + diff --git a/src/libs/uio/ioaux.c b/src/libs/uio/ioaux.c new file mode 100644 index 0000000..3dc0880 --- /dev/null +++ b/src/libs/uio/ioaux.c @@ -0,0 +1,930 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// auxiliary functions for use by io.c + +#include +#include + +#include "ioaux.h" +#include "iointrn.h" +#include "uioport.h" +#include "paths.h" + +static int copyError(int error, + uio_FileSystemHandler *fromHandler, uio_Handle *fromHandle, + uio_FileSystemHandler *toHandler, uio_Handle *toHandle, + uio_PDirHandle *toDir, const char *toName, char *buf); + +/** + * Follow a path starting from a specified physical dir for as long as + * possible. + * + * @param[in] startPDirHandle The physical dir to start from. + * @param[in] path The path to follow, relative to + * 'startPDirHandle'. + * @param[in] pathLen The string length of 'path'. + * @param[out] endPDirHandle The physical dir where you end up after + * following 'path' for as long as possible. Unmodified if an error + * occurs. + * @param[out] pathRest '*pathRest' will point into 'path' to the + * start the part that was not matched. Unmodified if an error occurs. + * + * @retval 0 if the complete path was matched + * @retval ENOENT if some component (the next one in '*pathRest') didn't + * exist. + * @retval ENODIR if a component (the next one in '*pathRest') did exist, + * but wasn't a dir. + * + * @note It is allowed to have 'endPDirHandle' point to pDirHandle, but + * care should be taken to keep a reference to the original so its + * reference counter can be decremented. + * @note It is allowed to have 'pathRest' point to 'path'. + */ +int +uio_walkPhysicalPath(uio_PDirHandle *startPDirHandle, const char *path, + size_t pathLen, uio_PDirHandle **endPDirHandle, + const char **pathRest) { + const char *pathEnd; + const char *partStart, *partEnd; + char *tempBuf; + uio_PDirHandle *pDirHandle; + uio_PDirEntryHandle *entry; + int retVal; + + uio_PDirHandle_ref(startPDirHandle); + pDirHandle = startPDirHandle; + tempBuf = uio_malloc(strlen(path) + 1); + // XXX: Use a dynamically allocated array when moving to C99. + pathEnd = path + pathLen; + getFirstPathComponent(path, pathEnd, &partStart, &partEnd); + for (;;) { + if (partStart == pathEnd) { + retVal = 0; + break; + } + memcpy(tempBuf, partStart, partEnd - partStart); + tempBuf[partEnd - partStart] = '\0'; + + entry = uio_getPDirEntryHandle(pDirHandle, tempBuf); + if (entry == NULL) { + retVal = ENOENT; + break; + } + if (!uio_PDirEntryHandle_isDir(entry)) { + uio_PDirEntryHandle_unref(entry); + retVal = ENOTDIR; + break; + } + uio_PDirHandle_unref(pDirHandle); + pDirHandle = (uio_PDirHandle *) entry; + getNextPathComponent(pathEnd, &partStart, &partEnd); + } + + uio_free(tempBuf); + *pathRest = partStart; + *endPDirHandle = pDirHandle; + return retVal; +} + +/** + * Create a directory inside a physical directory. All non-existant + * parent directories will be created as well. + * + * @param[in] pDirHandle The physical directory to which 'path' is relative + * @param[in] path The path to the directory to create, relative to + * 'pDirHandle' + * @param[in] pathLen The string length of 'path'. + * @param[in] mode The access mode for the newly created directories. + * + * @returns the new (physical) directory, or NULL if an error occurs, in + * which case #errno will be set. + */ +uio_PDirHandle * +uio_makePath(uio_PDirHandle *pDirHandle, const char *path, size_t pathLen, + mode_t mode) { + const char *rest, *start, *end; + uio_PDirHandle *(*mkdirFun)(uio_PDirHandle *, const char *, mode_t); + char *buf; + const char *pathEnd; + uio_PDirHandle *newPDirHandle; + + mkdirFun = pDirHandle->pRoot->handler->mkdir; + if (mkdirFun == NULL) { + errno = ENOSYS; + return NULL; + } + + pathEnd = path + pathLen; + + buf = uio_malloc(pathLen + 1); + // worst case length + // XXX: Use a dynamically allocated array when moving to C99. + + uio_walkPhysicalPath(pDirHandle, path, pathLen, &pDirHandle, &rest); + // The reference to the original pDirHandle is still kept + // by the calling function; uio_PDirHandle_unref should not + // be called for it. + // Rest now points into 'path' to the part from where no physical + // dir exists. + + getFirstPathComponent(rest, pathEnd, &start, &end); + while (start < pathEnd) { + memcpy(buf, start, end - start); + buf[end - start] = '\0'; + + newPDirHandle = mkdirFun(pDirHandle, buf, mode); + if (newPDirHandle == NULL) { + int savedErrno = errno; + uio_PDirHandle_unref(pDirHandle); + errno = savedErrno; + uio_free(buf); + return NULL; + } + uio_PDirHandle_unref(pDirHandle); + pDirHandle = newPDirHandle; + getNextPathComponent(pathEnd, &start, &end); + } + uio_free(buf); + return pDirHandle; +} + + +/** + * Copy a file from one physical directory to another. + * The copy will have the same file permissions as the original. + * + * @param[in] fromDir The physical directory where the file to copy is + * located. + * @param[in] fromName The name of the file to copy. + * @param[in] toDir The physical directory where to put the copy. + * @param[in] toName The name to use for the copy. + * + * @note It is up to the caller to make any relevant permissions checks. + * + * @note This function will fail if a file with the name in 'toName' already + * exists, leaving the original file intact. If an error occurs during + * copying, an attempt is made to remove the file that was to be the + * copy. + */ +int +uio_copyFilePhysical(uio_PDirHandle *fromDir, const char *fromName, + uio_PDirHandle *toDir, const char *toName) { + uio_FileSystemHandler *fromHandler, *toHandler; + uio_Handle *fromHandle; + uio_Handle *toHandle; +#define BUFSIZE 0x10000 + struct stat statBuf; + char *buf, *bufPtr; + ssize_t numInBuf, numWritten; + + fromHandler = fromDir->pRoot->handler; + toHandler = toDir->pRoot->handler; + if (toHandler->write == NULL || fromHandler->fstat == NULL || + toHandler->unlink == NULL) { + errno = ENOSYS; + return -1; + } + + fromHandle = (fromHandler->open)(fromDir, fromName, O_RDONLY, 0); + if (fromHandle == NULL) { + // errno is set + return -1; + } + + if ((fromHandler->fstat)(fromHandle, &statBuf) == -1) + return copyError(errno, fromHandler, fromHandle, + toHandler, NULL, NULL, NULL, NULL); + + toHandle = (toHandler->open)(toDir, toName, O_WRONLY | O_CREAT | O_EXCL, + statBuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + if (toHandle == NULL) + return copyError(errno, fromHandler, fromHandle, + toHandler, NULL, NULL, NULL, NULL); + + buf = uio_malloc(BUFSIZE); + // not allocated on the stack, as this function may be called + // from a thread with little stack space. + while (1) { + numInBuf = (fromHandler->read)(fromHandle, buf, BUFSIZE); + if (numInBuf == -1) + { + if (errno == EINTR) + continue; + return copyError (errno, fromHandler, fromHandle, + toHandler, toHandle, toDir, toName, buf); + } + if (numInBuf == 0) + break; + + bufPtr = buf; + do { + numWritten = (toHandler->write)(toHandle, bufPtr, numInBuf); + if (numWritten == -1) + { + if (errno == EINTR) + continue; + return copyError (errno, fromHandler, fromHandle, + toHandler, toHandle, toDir, toName, buf); + } + numInBuf -= numWritten; + bufPtr += numWritten; + } while (numInBuf > 0); + } + + uio_free(buf); + (toHandler->close)(toHandle); + (fromHandler->close)(fromHandle); + return 0; +} + +/* + * Closes fromHandle if it's not -1. + * Closes fromHandle if it's not -1. + * Removes 'toName' from 'toDir' if it's not NULL. + * Frees 'buf' if not NULL. + * Always returns -1, setting errno to 'error'. + */ +static int +copyError(int error, + uio_FileSystemHandler *fromHandler, uio_Handle *fromHandle, + uio_FileSystemHandler *toHandler, uio_Handle *toHandle, + uio_PDirHandle *toDir, const char *toName, char *buf) { +#ifdef DEBUG + fprintf(stderr, "Error while copying: %s\n", strerror(error)); +#endif + + if (fromHandle != NULL) + (fromHandler->close)(fromHandle); + + if (toHandle != NULL) + (toHandler->close)(toHandle); + + if (toName != NULL) + (toHandler->unlink)(toDir, toName); + + if (buf != NULL) + uio_free(buf); + + errno = error; + return -1; +} + +/* + * Description: find PDirHandle and MountInfo structures for reading and + * writing for a path from a DirHandle. + * This can be used for opening a file and creating directories. + * Arguments: dirHandle - the directory to which the path is relative. + * path - the path to the component that is to be accessed. + * The last component does not have to exist. + * flags - used to specify what kind of access is requested + * Either O_RDONLY, O_RDWR, O_WRONLY. They may be + * OR'd with other values accepted by open(). These + * are ignored. + * XXX: this is no longer true. + * TODO: update this doc, and check uio_open() and + * perhaps others (uio_mkdir()) for unnecessary + * checks on O_CREAT and O_EXCL. + * extraFlags - either 0 or uio_GPA_NOWRITE + * When 0, the path will be created if it doesn't + * exist in the writing location, but does exist + * in the reading location. With uio_GPA_NOWRITE, it + * won't be created, and -1 will be returned and errno + * will be set to ENOENT. + * mountInfoReadPtr - pointer to location where the pointer + * to the MountInfo structure for the reading location + * should be stored. + * readPDirHandlePtr - pointer to the location where the pointer + * to the PDirHandle used for reading should be stored. + * readPRootPath - pointer to the location where the pointer + * to the physical path to the reading location + * is to be stored. + * The caller is responsible for freeing this. + * Ignored if NULL. + * mountInfoWritePtr - pointer to location where the pointer + * to the MountInfo structure for the writing location + * should be stored. + * writePDirHandlePtr - pointer to the location where the pointer + * to the PDirHandle used for writing should be stored. + * NULL if O_RDONLY was specified. + * If this is the same dir as the one refered + * to by readPDirHandlePtr, the handles will be the + * same too. + * writePRootPath - pointer to the location where the pointer + * to the physical path to the writing location + * is to be stored. + * The caller is responsible for freeing this. + * Ignored if NULL. + * restPtr - pointer to the location where a newly created + * string with as contents the last component of 'path' + * is to be stored. + * The caller is responsible for freeing this. + * Ignored if NULL. + * Returns: 0 - success + * -1 - failure (errno set) + * NB: This is the function that would most benefit from + * the introduction of LDirs. + * This is also the most messy function. It could + * use some more comments, or a cleanup (if possible). + */ +int +uio_getPhysicalAccess(uio_DirHandle *dirHandle, const char *path, + int flags, int extraFlags, + uio_MountInfo **mountInfoReadPtr, uio_PDirHandle **readPDirHandlePtr, + char **readPRootPathPtr, + uio_MountInfo **mountInfoWritePtr, uio_PDirHandle **writePDirHandlePtr, + char **writePRootPathPtr, + char **restPtr) { + char *fullPath; // path from dirHandle with 'path' added + const char *pRootPath; // path from the pRoot of a physical tree + const char *readPRootPath, *writePRootPath; + const char *rest, *readRest; + uio_MountTree *tree; + uio_MountTreeItem *item; + uio_MountTreeItem *readItem, *writeItem; + uio_PDirHandle *readPDirHandle, *writePDirHandle, *pDirHandle; + int retVal; + uio_bool entryExists; + // Set if the entry pointed to by path exists (including + // the last component of the path) + + // 'path' is relative to dirHandle. + // Fill 'fullPath' with the absolute path. + if (uio_resolvePath(dirHandle, path, strlen(path), &fullPath) == -1) { + // errno is set + return -1; + } + + // Walk the tree of mount points along 'fullPath'. + // 'tree' will be the part of the tree where we end up when we can go + // no further. tree->pLocs are all the mounts relevant there. + // 'rest' will point within 'fullPath' to what is left, after we can + // walk the tree of mountpoints no further. + uio_findMountTree(dirHandle->repository->mountTree, fullPath, + &tree, &rest); + + readItem = NULL; + readPDirHandle = NULL; + readPRootPath = NULL; + writeItem = NULL; + writePDirHandle = NULL; + writePRootPath = NULL; + readRest = NULL; + // Satisfy compiler. + entryExists = false; + // try all the MountInfo structures in effect for this MountTree + for (item = tree->pLocs; item != NULL; item = item->next) { + pRootPath = uio_mountTreeItemRestPath(item, tree->lastComp, fullPath); + retVal = uio_walkPhysicalPath(item->mountInfo->pDirHandle, pRootPath, + strlen(pRootPath), &pDirHandle, &rest); + // rest points inside fullPath + if (retVal == 0) { + // Even the last component appeared to be a dir. + // As the last component did exist, we don't go on. + uio_free(fullPath); + uio_PDirHandle_unref(pDirHandle); + if (readPDirHandle != NULL) + uio_PDirHandle_unref(readPDirHandle); + errno = EISDIR; + return -1; + } + // check if this MountTreeItem is suitable for writing + if (writeItem == NULL && !uio_mountInfoIsReadOnly(item->mountInfo)) + writeItem = item; + if (strchr(rest, '/') == NULL) { + // There's only one dir component that was not matched. + uio_PDirEntryHandle *entry; + + // This MountInfo will do for reading, if the file from the last + // component is present in this dir. + entry = uio_getPDirEntryHandle(pDirHandle, rest); + if (entry != NULL) { + // 'rest' exists, and it is not a dir, as otherwise + // uio_getPDirEntryHandle wouldn't have stopped where it did. + uio_PDirEntryHandle_unref(entry); + readItem = item; + if (readPDirHandle != NULL) + uio_PDirHandle_unref(readPDirHandle); + readPDirHandle = pDirHandle; + readPRootPath = pRootPath; + readRest = rest; + entryExists = true; + break; + } else { + // 'rest' doesn't exist + // We're only interested in this dir if we want to write too. + if (((flags & O_ACCMODE) != O_RDONLY) && readItem == NULL) { + // Keep the first one. + readItem = item; + assert(readPDirHandle == NULL); + readPDirHandle = pDirHandle; + readPRootPath = pRootPath; + readRest = rest; + continue; + } + } + } else { + // There is more than one dir component that was not matched. + // If the first component of the non-matched part is a file, + // stop here and don't check lower dirs. + if (retVal == ENOTDIR) { + uio_free(fullPath); + uio_PDirHandle_unref(pDirHandle); + if (readPDirHandle != NULL) + uio_PDirHandle_unref(readPDirHandle); + errno = ENOTDIR; + return -1; + } + } + uio_PDirHandle_unref(pDirHandle); + } // for + // readItem is set to the first readItem for which the path completely + // exists (including the final component). If there's no such readItem, + // it is set to the first path for which the path exists, but without + // the final component (entryExists is false in this case). If there's + // no such path either, it's set to NULL. + + if (readItem == NULL) { + uio_free(fullPath); + errno = ENOENT; + return -1; + } + if ((flags & O_ACCMODE) == O_RDONLY) { + // write access is not needed + *mountInfoReadPtr = readItem->mountInfo; + *readPDirHandlePtr = readPDirHandle; + if (readPRootPathPtr != NULL) + *readPRootPathPtr = joinPathsAbsolute( + readItem->mountInfo->dirName, readPRootPath); + // Don't touch mountInfoWritePtr and writePDirHandlePtr. + // they'd be NULL. + *restPtr = uio_strdup(readRest); + uio_free(fullPath); + return 0; + } else { + if (entryExists) { + if ((flags & O_CREAT) && (flags & O_EXCL)) { + // An entry should be created, but it already exists and + // it may not be overwritten. + uio_PDirHandle_unref(readPDirHandle); + uio_free(fullPath); + errno = EEXIST; + return -1; + } + } else { + // Though the path to the entry existed (readPDirHandle is + // set to it), the entry itself doesn't, so we can't use it + // unless we intend to create it. + if (flags & O_CREAT) { + // The entry does not exist, but we can create it. + // Handled below. + } else { + // O_CREAT was not specified, so we cannot create + // this entry. + uio_PDirHandle_unref(readPDirHandle); + uio_free(fullPath); + errno = ENOENT; + return -1; + } + + } + } + if (writeItem == readItem) { + // The read directory is usable as write directory too. + *mountInfoReadPtr = readItem->mountInfo; + *readPDirHandlePtr = readPDirHandle; + if (readPRootPathPtr != NULL) + *readPRootPathPtr = joinPathsAbsolute( + readItem->mountInfo->dirName, readPRootPath); + *mountInfoWritePtr = writeItem->mountInfo; + // writeItem == readItem + uio_PDirHandle_ref(readPDirHandle); + *writePDirHandlePtr = readPDirHandle; + // No copy&paste error, the read PDirHandle is the write + // pDirHandle too. + if (writePRootPathPtr != NULL) + *writePRootPathPtr = joinPathsAbsolute( + writeItem->mountInfo->dirName, writePRootPath); + if (restPtr != NULL) + *restPtr = uio_strdup(readRest); + uio_free(fullPath); + return 0; + } + if (writeItem == NULL) { + uio_free(fullPath); + uio_PDirHandle_unref(readPDirHandle); + errno = EPERM; + // readItem is not NULL, so ENOENT would not be correct here. + return -1; + } + + // Left is the case where the write location is different from the + // read location. + + pRootPath = uio_mountTreeItemRestPath(writeItem, tree->lastComp, + fullPath); + + rest = strrchr(pRootPath, '/'); + // rest points inside fullPath + if (rest == NULL) { + rest = pRootPath; + uio_PDirHandle_ref(writeItem->mountInfo->pDirHandle); + writePDirHandle = writeItem->mountInfo->pDirHandle; + } else { + // There exists no path for a write dir, so it will have to be created. + // writeMountInfo indicates the physical tree where it should end up. + + if (extraFlags & uio_GPA_NOWRITE) { + // The caller has specified that the path should not be created. + uio_PDirHandle_unref(readPDirHandle); + uio_free(fullPath); + errno = ENOENT; + return -1; + } + + writePDirHandle = uio_makePath(writeItem->mountInfo->pDirHandle, + pRootPath, rest - pRootPath, 0777); + if (writePDirHandle == NULL) { + int savedErrno; + if (errno == ENOSYS) { + // mkdir not supported. We want to report that we failed + // because of an error in the underlying layer. + // EIO sounds like the best choice. + errno = EIO; + } + savedErrno = errno; + uio_PDirHandle_unref(readPDirHandle); + uio_free(fullPath); + errno = savedErrno; + return -1; + } + rest++; // skip the '/' + } + + if (!entryExists) { + // The path to the read dir exists, but the entry itself doesn't. + // After we created the write dir, the same thing holds for + // the write dir. As it occurs in an earlier MountItem, we'll use + // the writeItem (and writePDirHandle) for reading too. + readItem = writeItem; + uio_PDirHandle_ref(writePDirHandle); + uio_PDirHandle_unref(readPDirHandle); + readPDirHandle = writePDirHandle; + } + + *mountInfoReadPtr = readItem->mountInfo; + *readPDirHandlePtr = readPDirHandle; + if (readPRootPathPtr != NULL) + *readPRootPathPtr = joinPathsAbsolute( + readItem->mountInfo->dirName, readPRootPath); + *mountInfoWritePtr = writeItem->mountInfo; + *writePDirHandlePtr = writePDirHandle; + if (writePRootPathPtr != NULL) + *writePRootPathPtr = joinPathsAbsolute( + writeItem->mountInfo->dirName, writePRootPath); + if (restPtr != NULL) + *restPtr = uio_strdup(rest); + uio_free(fullPath); + return 0; +} + + +/** + * Get handles to the (existing) physical dirs that are effective in a + * path 'path' relative from 'dirHandle' + * + * @param[in] pDirHandle The physical directory to which 'path' is + * relative. + * @param[in] path The path to get the physical dirs for, relative to + * 'pDirHandle' + * @param[in] pathLen The string length of 'path'. + * @param[out] resPDirHandles *resPDirHandles is set to the handles to the + * (existing) physical dirs that are effective in 'path' (relative to + * pDirHandle), or NULL if there are none. + * @param[out] resNumPDirHandles The number of PDirHandles found. + * @param[out] resItems If 'resItems' != NULL, *resItems is set to the + * MountTreeItems belonging to $pDirHandles, or NULL if none were found. + * + * @retval 0 if everything went ok. + * @retval -1 if an error occurred; #errno is set. + */ +int +uio_getPathPhysicalDirs(uio_DirHandle *dirHandle, const char *path, + size_t pathLen, uio_PDirHandle ***resPDirHandles, + int *resNumPDirHandles, uio_MountTreeItem ***resItems) { + uio_PDirHandle **pDirHandles; + char *fullPath; // path from dirHandle with 'path' added + uio_MountTree *tree; + const char *rest; + uio_MountTreeItem *item, **items; + const char *pRootPath; // path from the pRoot of a physical tree + int numPDirHandles; + int pDirI; // PDirHandle iterator + + // Determine the absolute path from 'path', which is relative to dirHandle. + if (uio_resolvePath(dirHandle, path, pathLen, &fullPath) == -1) { + // errno is set + return -1; + } + + // get the MountTree effective for the path + uio_findMountTree(dirHandle->repository->mountTree, fullPath, + &tree, &rest); + + // fill pDirHandles with all the PDirHandles for the path + numPDirHandles = uio_mountTreeCountPLocs(tree); + pDirHandles = uio_malloc(numPDirHandles * sizeof (uio_PDirHandle *)); + if (resItems != NULL) { + items = uio_malloc(numPDirHandles * sizeof (uio_MountTreeItem *)); + } else { + items = NULL; // satisfy compiler + } + pDirI = 0; + for (item = tree->pLocs; item != NULL; item = item->next) { + uio_PDirHandle *pDirHandle; + + pRootPath = uio_mountTreeItemRestPath(item, tree->lastComp, fullPath); + switch (uio_walkPhysicalPath(item->mountInfo->pDirHandle, pRootPath, + strlen(pRootPath), &pDirHandle, &rest)) { + case 0: + // complete path was matched + pDirHandles[pDirI] = pDirHandle; + if (resItems != NULL) + items[pDirI] = item; + pDirI++; + continue; + case ENOENT: + // some component couldn't be matched + uio_PDirHandle_unref(pDirHandle); + continue; + case ENOTDIR: + // next component was not a dir + // Don't look further at other mount Items. + uio_PDirHandle_unref(pDirHandle); + break; + default: + assert(false); + uio_PDirHandle_unref(pDirHandle); + continue; + } + break; + } + numPDirHandles = pDirI; + + uio_free(fullPath); + + *resPDirHandles = uio_realloc(pDirHandles, + numPDirHandles * sizeof (uio_PDirHandle *)); + if (resItems != NULL) + *resItems = uio_realloc(items, + numPDirHandles * sizeof (uio_MountTreeItem *)); + *resNumPDirHandles = numPDirHandles; + + return 0; +} + +// returns 0 if the path is valid and exists +// returns -1 if the path is not valid or does not exist. +// in this case errno will be set to: +// ENOENT if some component didn't exist +// ENOTDIR is some component exists, but is not a dir +// something else (like EPATHTOOLONG) for internal errors +// On success, 'resolvedPath' will be set to the absolute path as returned by +// uio_resolvePath. +int +uio_verifyPath(uio_DirHandle *dirHandle, const char *path, + char **resolvedPath) { + uio_MountTree *tree; + uio_MountTreeItem *item; + const char *rest; + int retVal; + + // TODO: "////", "/somedir//", and "//somedir" are accepted as valid + + // Determine the absolute path from 'path' which is relative to dirHandle. + if (uio_resolvePath(dirHandle, path, strlen(path), resolvedPath) == -1) { + // errno is set + return -1; + } + + // get the MountTree effective for the path + uio_findMountTree(dirHandle->repository->mountTree, *resolvedPath, + &tree, &rest); + + if (rest[0] == '\0') { + // Complete match. Even if there are no pLocs in effect here + // (which can only happen in case the mount Tree is empty and + // we're viewing '/'). + return 0; + } + + // Try all the MountInfo structures in effect for this MountTree. + for (item = tree->pLocs; item != NULL; item = item->next) { + const char *pRootPath; + uio_PDirHandle *pDirHandle; + + pRootPath = uio_mountTreeItemRestPath(item, tree->lastComp, + *resolvedPath); + retVal = uio_walkPhysicalPath(item->mountInfo->pDirHandle, pRootPath, + strlen(pRootPath), &pDirHandle, &rest); + uio_PDirHandle_unref(pDirHandle); + switch (retVal) { + case 0: + // Complete match. We're done. + return 0; + case ENOTDIR: + // A component is matched, but not as a dir. Failed. + uio_free(*resolvedPath); + errno = ENOTDIR; + return -1; + case ENOENT: + // No match; try the next pLoc. + continue; + default: + // Unknown error. Let's bail out just to be safe. +#ifdef DEBUG + fprintf(stderr, "Warning: Unknown error from " + "uio_walkPhysicalPath: %s\n", strerror(retVal)); +#endif + uio_free(*resolvedPath); + errno = retVal; + return -1; + } + } + + // No match, exit with ENOENT. + uio_free(*resolvedPath); + errno = ENOENT; + return -1; +} + +/** + * Determine the absolute path given a path relative to a given directory. + * + * @param[in] dirHandle The directory to which 'path' is relative. + * @param[in] path The path, relative to 'dirHandle', to make + * absolute. + * @param[in] pathLen The string length of 'path'. + * @param[out] destPath Filled with a newly allocated string containing + * the sought absolute path. It will not contain a '/' as the first + * or last character. It should be freed with uio_free(). + * Unmodified if an error occurs. + * + * @returns the length of '*destPath', or -1 if an error occurs, in which + * case #errno will be set. + */ +ssize_t +uio_resolvePath(uio_DirHandle *dirHandle, const char *path, size_t pathLen, + char **destPath) { + size_t len; + const char *pathEnd, *start, *end; + int numUp; // number of ".." dirs still need to be matched. + char *buffer; + char *endBufPtr; + uio_bool absolute; + + absolute = path[0] == '/'; + pathEnd = path + pathLen; + + // Pass 1: count the amount of space needed + len = 0; + numUp = 0; + for (getLastPathComponent(path, pathEnd, &start, &end); + end > path; + getPreviousPathComponent(path, &start, &end)) { + if (start[0] == '.') { + if (start + 1 == end) { + // "." matched + continue; + } + if (start[1] == '.' && start + 2 == end) { + // ".." matched + numUp++; + continue; + } + } + if (numUp > 0) { + // last 'numUp' components were ".." + numUp--; + continue; + } + len += (end - start) + 1; + // the 1 is for the '/' + } + + // The part from 'dirHandle->path' to 'dirHandle->rootEnd' is + // always copied (for a valid path). The rest we'll have to count. + // (Note the 'rootEnd' in the initialiser of the for loop.) + len += (dirHandle->rootEnd - dirHandle->path); + if (!absolute) { + for (getLastPath0Component(dirHandle->rootEnd, &start, &end); + end > dirHandle->rootEnd; + getPreviousPath0Component(dirHandle->rootEnd, &start, &end)) { + if (numUp > 0) { + numUp--; + continue; + } + len += (end - start) + 1; + // the 1 is for the '/' + } + } + if (numUp > 0) { + // too many ".." + errno = ENOENT; + return -1; + } + + if (len == 0) { + *destPath = uio_malloc(1); + (*destPath)[0] = '\0'; + return 0; + } + + // len--; // we don't want a '/' at the start + // len++; // for the terminating '\0' + buffer = uio_malloc(len); + + // Pass 2: fill the buffer + endBufPtr = buffer + len - 1; + *endBufPtr = '\0'; + numUp = 0; + for (getLastPathComponent(path, pathEnd, &start, &end); + end > path; + getPreviousPathComponent(path, &start, &end)) { + if (start[0] == '.') { + if (start + 1 == end) { + // "." matched + continue; + } + if (start[1] == '.' && start + 2 == end) { + // ".." matched + numUp++; + continue; + } + } + if (numUp > 0) { + // last 'numUp' components were ".." + numUp--; + continue; + } + endBufPtr -= (end - start); + memcpy(endBufPtr, start, end - start); + if (endBufPtr != buffer) { + // We want no '/' at the beginning + endBufPtr--; + *endBufPtr = '/'; + } else { + // We're already done. We might as well take advantage of + // the fact that we know that and exit immediatly: + *destPath = buffer; + return len; + } + } + // copy the part from dirHandle->path to dirHandle->rootEnd + endBufPtr -= (dirHandle->rootEnd - dirHandle->path); + memcpy(endBufPtr, dirHandle->path, dirHandle->rootEnd - dirHandle->path); + if (!absolute) { + // copy (some of) the components from dirHandle->rootEnd on. + for (getLastPath0Component(dirHandle->rootEnd, &start, &end); + end > dirHandle->rootEnd; + getPreviousPath0Component(dirHandle->rootEnd, &start, &end)) { + if (numUp > 0) { + numUp--; + continue; + } + endBufPtr -= (end - start); + memcpy(endBufPtr, start, end - start); + if (endBufPtr != buffer) { + // We want no '/' at the beginning + endBufPtr--; + *endBufPtr = '/'; + } else { + // We're already done. We might as well take advantage of + // the fact that we know that and exit immediatly: + break; + } + } + } + + *destPath = buffer; + return len; +} + + diff --git a/src/libs/uio/ioaux.h b/src/libs/uio/ioaux.h new file mode 100644 index 0000000..41c7e39 --- /dev/null +++ b/src/libs/uio/ioaux.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_IOAUX_H_ +#define LIBS_UIO_IOAUX_H_ + +#include "iointrn.h" +#include "physical.h" +#include "uioport.h" + +int uio_walkPhysicalPath(uio_PDirHandle *startPDirHandle, const char *path, + size_t pathLen, uio_PDirHandle **endPDirHandle, + const char **pathRest); +uio_PDirHandle *uio_makePath(uio_PDirHandle *pDirHandle, const char *path, + size_t pathLen, mode_t mode); +int uio_copyFilePhysical(uio_PDirHandle *fromDir, const char *fromName, + uio_PDirHandle *toDir, const char *toName); +int uio_getPhysicalAccess(uio_DirHandle *dirHandle, const char *path, + int flags, int extraFlags, + uio_MountInfo **mountInfoReadPtr, uio_PDirHandle **readPDirHandlePtr, + char **readPRootPathPtr, + uio_MountInfo **mountInfoWritePtr, uio_PDirHandle **writePDirHandlePtr, + char **writePRootPathPtr, + char **restPtr); +#define uio_GPA_NOWRITE 1 +int uio_getPathPhysicalDirs(uio_DirHandle *dirHandle, const char *path, + size_t pathLen, uio_PDirHandle ***resPDirHandles, + int *resNumPDirHandles, uio_MountTreeItem ***resItems); +int uio_verifyPath(uio_DirHandle *dirHandle, const char *path, + char **resolvedPath); +ssize_t uio_resolvePath(uio_DirHandle *dirHandle, const char *path, + size_t pathLen, char **destPath); + + +#endif /* LIBS_UIO_IOAUX_H_ */ + diff --git a/src/libs/uio/iointrn.h b/src/libs/uio/iointrn.h new file mode 100644 index 0000000..522ce18 --- /dev/null +++ b/src/libs/uio/iointrn.h @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_IOINTRN_H_ +#define LIBS_UIO_IOINTRN_H_ + +#define uio_INTERNAL + +typedef struct uio_PDirEntryHandle uio_PDirEntryHandle; +typedef struct uio_PDirHandle uio_PDirHandle; +typedef struct uio_PFileHandle uio_PFileHandle; +typedef struct uio_EntriesContext uio_EntriesContext; + +#ifndef uio_INTERNAL_PHYSICAL +typedef void *uio_PDirHandleExtra; +typedef void *uio_PFileHandleExtra; +#endif + +#include "io.h" +#include "uioport.h" +#include "physical.h" +#include "mount.h" +#include "mounttree.h" +#include "match.h" +#include "mem.h" + +struct uio_Handle { + int ref; + struct uio_PRoot *root; + uio_NativeHandle native; + int openFlags; + // need to know whether the handle is a RO or RW handle. +}; + +struct uio_DirHandle { + int ref; + struct uio_Repository *repository; + char *path; + // does not contain any '.' or '..'; does not start or end + // with a / + char *rootEnd; + // points to the end of the part of path that is considered + // the root dir. (you can't use '..' to get above the root dir) +}; + +struct uio_MountHandle { + struct uio_Repository *repository; + struct uio_MountInfo *mountInfo; +}; + +struct uio_DirList { + const char **names; + int numNames; + char *buffer; +}; + + +#define uio_PHandle_COMMON \ + int flags; \ + int ref; \ + uio_PRoot *pRoot; + +#define uio_PDirEntryHandle_TYPE_REG 0x0000 +#define uio_PDirEntryHandle_TYPE_DIR 0x0001 +#define uio_PDirEntryHandle_TYPEMASK 0x0001 + +struct uio_PDirEntryHandle { + uio_PHandle_COMMON +}; + +struct uio_PDirHandle { + uio_PHandle_COMMON + uio_PDirHandleExtra extra; +}; + +struct uio_PFileHandle { + uio_PHandle_COMMON + uio_PFileHandleExtra extra; +}; + +struct uio_EntriesContext { + uio_PRoot *pRoot; + uio_NativeEntriesContext native; +}; + + +uio_Handle *uio_Handle_new(uio_PRoot *root, uio_NativeHandle native, + int openFlags); +void uio_Handle_delete(uio_Handle *handle); +void uio_DirHandle_delete(uio_DirHandle *dirHandle); + +uio_PDirEntryHandle *uio_getPDirEntryHandle( + const uio_PDirHandle *dirHandle, const char *name); +void uio_PDirHandle_deletePDirHandleExtra(uio_PDirHandle *pDirHandle); +void uio_PFileHandle_deletePFileHandleExtra(uio_PFileHandle *pFileHandle); + +uio_PDirHandle *uio_PDirHandle_new(uio_PRoot *pRoot, + uio_PDirHandleExtra extra); +uio_PFileHandle *uio_PFileHandle_new(uio_PRoot *pRoot, + uio_PFileHandleExtra extra); +void uio_PDirEntryHandle_delete(uio_PDirEntryHandle *pDirEntryHandle); +void uio_PDirHandle_delete(uio_PDirHandle *pDirHandle); +void uio_PFileHandle_delete(uio_PFileHandle *pFileHandle); + + +static inline void +uio_Handle_ref(uio_Handle *handle) { + handle->ref++; +} + +static inline void +uio_Handle_unref(uio_Handle *handle) { + assert(handle->ref > 0); + handle->ref--; + if (handle->ref == 0) + uio_Handle_delete(handle); +} + +static inline void +uio_DirHandle_ref(uio_DirHandle *dirHandle) { + dirHandle->ref++; +} + +static inline void +uio_DirHandle_unref(uio_DirHandle *dirHandle) { + assert(dirHandle->ref > 0); + dirHandle->ref--; + if (dirHandle->ref == 0) + uio_DirHandle_delete(dirHandle); +} + +static inline uio_bool +uio_PDirEntryHandle_isDir(const uio_PDirEntryHandle *handle) { + return (handle->flags & uio_PDirEntryHandle_TYPEMASK) == + uio_PDirEntryHandle_TYPE_DIR; +} + +static inline void +uio_PDirEntryHandle_ref(uio_PDirEntryHandle *handle) { + handle->ref++; +} + +static inline void +uio_PDirEntryHandle_unref(uio_PDirEntryHandle *handle) { + assert(handle->ref > 0); + handle->ref--; + if (handle->ref == 0) + uio_PDirEntryHandle_delete(handle); +} + +static inline void +uio_PDirHandle_ref(uio_PDirHandle *handle) { + handle->ref++; +} + +static inline void +uio_PDirHandle_unref(uio_PDirHandle *handle) { + assert(handle->ref > 0); + handle->ref--; + if (handle->ref == 0) + uio_PDirHandle_delete(handle); +} + +static inline void +uio_PFileHandle_ref(uio_PFileHandle *handle) { + handle->ref++; +} + +static inline void +uio_PFileHandle_unref(uio_PFileHandle *handle) { + assert(handle->ref > 0); + handle->ref--; + if (handle->ref == 0) + uio_PFileHandle_delete(handle); +} + + +#endif /* LIBS_UIO_IOINTRN_H_ */ + + diff --git a/src/libs/uio/match.c b/src/libs/uio/match.c new file mode 100644 index 0000000..68e6897 --- /dev/null +++ b/src/libs/uio/match.c @@ -0,0 +1,569 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#ifdef DEBUG +# include +#endif + +#define match_INTERNAL + +#include "match.h" +#include "mem.h" +#include "uioport.h" + +#ifdef HAVE_GLOB +# include +#endif + + +static inline match_MatchContext *match_allocMatchContext(void); +static inline void match_freeMatchContext(match_MatchContext *context); + +static inline match_LiteralContext *match_newLiteralContext(char *pattern); +static inline match_LiteralContext *match_allocLiteralContext(void); +static inline void match_freeLiteralContext(match_LiteralContext *context); +static inline match_PrefixContext *match_newPrefixContext(char *pattern); +static inline match_PrefixContext *match_allocPrefixContext(void); +static inline void match_freePrefixContext(match_PrefixContext *context); +static inline match_SuffixContext *match_newSuffixContext( + char *pattern, size_t len); +static inline match_SuffixContext *match_allocSuffixContext(void); +static inline void match_freeSuffixContext(match_SuffixContext *context); +static inline match_SubStringContext *match_newSubStringContext( + char *pattern); +static inline match_SubStringContext *match_allocSubStringContext(void); +static inline void match_freeSubStringContext(match_SubStringContext *context); +#ifdef HAVE_GLOB +static inline match_GlobContext *match_newGlobContext(char *pattern); +static inline match_GlobContext *match_allocGlobContext(void); +static inline void match_freeGlobContext(match_GlobContext *context); +#endif /* HAVE_GLOB */ +#ifdef HAVE_REGEX +static inline match_RegexContext *match_newRegexContext(void); +static inline match_RegexContext *match_allocRegexContext(void); +static inline void match_freeRegexContext(match_RegexContext *context); +#endif /* HAVE_REGEX */ + + +// *** General part *** + +static inline match_MatchContext * +match_allocMatchContext(void) { + return uio_malloc(sizeof (match_MatchContext)); +} + +static inline void +match_freeMatchContext(match_MatchContext *context) { + uio_free(context); +} + +// NB: Even if this function fails, *contextPtr contains a context +// which needs to be freed. +match_Result +match_prepareContext(const char *pattern, match_MatchContext **contextPtr, + match_MatchType type) { + match_Result result; + + *contextPtr = match_allocMatchContext(); + (*contextPtr)->type = type; + switch (type) { + case match_MATCH_LITERAL: + result = match_prepareLiteral(pattern, + &(*contextPtr)->u.literal); + break; + case match_MATCH_PREFIX: + result = match_preparePrefix(pattern, &(*contextPtr)->u.prefix); + break; + case match_MATCH_SUFFIX: + result = match_prepareSuffix(pattern, &(*contextPtr)->u.suffix); + break; + case match_MATCH_SUBSTRING: + result = match_prepareSubString(pattern, + &(*contextPtr)->u.subString); + break; +#ifdef HAVE_GLOB + case match_MATCH_GLOB: + result = match_prepareGlob(pattern, &(*contextPtr)->u.glob); + break; +#endif +#ifdef HAVE_REGEX + case match_MATCH_REGEX: + result = match_prepareRegex(pattern, &(*contextPtr)->u.regex); + break; +#endif + default: +#ifdef DEBUG + fprintf(stderr, "match_prepareContext called with unsupported " + "type %d matching.\n", type); +#endif + return match_ENOSYS; + } + return result; +} + +match_Result +match_matchPattern(match_MatchContext *context, const char *string) { + switch (context->type) { + case match_MATCH_LITERAL: + return match_matchLiteral(context->u.literal, string); + case match_MATCH_PREFIX: + return match_matchPrefix(context->u.prefix, string); + case match_MATCH_SUFFIX: + return match_matchSuffix(context->u.suffix, string); + case match_MATCH_SUBSTRING: + return match_matchSubString(context->u.subString, string); +#ifdef HAVE_GLOB + case match_MATCH_GLOB: + return match_matchGlob(context->u.glob, string); +#endif +#ifdef HAVE_REGEX + case match_MATCH_REGEX: + return match_matchRegex(context->u.regex, string); +#endif + default: + abort(); + } +} + +// context may be NULL +const char * +match_errorString(match_MatchContext *context, match_Result result) { + switch (result) { + case match_OK: + case match_MATCH: +// case match_NOMATCH: // same value as match_OK + return "Success"; + case match_ENOSYS: + return "Not implemented"; + case match_ENOTINIT: + return "Uninitialised use"; + case match_ECUSTOM: + // Depends on match type. Printed below. + break; + default: + return "Unknown error"; + } + + if (context == NULL) + return "Unknown match-type specific error."; + // We can't be any more specific if no 'context' is supplied. + + switch (context->type) { +#if 0 + case match_MATCH_LITERAL: + return match_errorStringLiteral(context->u.literal, result); + case match_MATCH_PREFIX: + return match_errorStringPrefix(context->u.prefix, result); + case match_MATCH_SUFFIX: + return match_errorStringSuffix(context->u.suffix, result); + case match_MATCH_SUBSTRING: + return match_errorStringSubString(context->u.subString, result); +#ifdef HAVE_GLOB + case match_MATCH_GLOB: + return match_errorStringGlob(context->u.glob, result); +#endif +#endif +#ifdef HAVE_REGEX + case match_MATCH_REGEX: + return match_errorStringRegex(context->u.regex, result); +#endif + default: + abort(); + } +} + +void +match_freeContext(match_MatchContext *context) { + switch (context->type) { + case match_MATCH_LITERAL: + match_freeLiteral(context->u.literal); + break; + case match_MATCH_PREFIX: + match_freePrefix(context->u.prefix); + break; + case match_MATCH_SUFFIX: + match_freeSuffix(context->u.suffix); + break; + case match_MATCH_SUBSTRING: + match_freeSubString(context->u.subString); + break; +#ifdef HAVE_GLOB + case match_MATCH_GLOB: + match_freeGlob(context->u.glob); + break; +#endif +#ifdef HAVE_REGEX + case match_MATCH_REGEX: + match_freeRegex(context->u.regex); + break; +#endif + default: + abort(); + } + match_freeMatchContext(context); +} + +match_Result +match_matchPatternOnce(const char *pattern, match_MatchType type, + const char *string) { + match_MatchContext *context; + match_Result result; + + result = match_prepareContext(pattern, &context, type); + if (result != match_OK) + goto out; + + result = match_matchPattern(context, string); + +out: + match_freeContext(context); + return result; +} + + +// *** Literal part *** + +match_Result +match_prepareLiteral(const char *pattern, + match_LiteralContext **contextPtr) { + *contextPtr = match_newLiteralContext(uio_strdup(pattern)); + return match_OK; +} + +match_Result +match_matchLiteral(match_LiteralContext *context, const char *string) { + return (strcmp(context->pattern, string) == 0) ? + match_MATCH : match_NOMATCH; +} + +void +match_freeLiteral(match_LiteralContext *context) { + uio_free(context->pattern); + match_freeLiteralContext(context); +} + +static inline match_LiteralContext * +match_newLiteralContext(char *pattern) { + match_LiteralContext *result; + + result = match_allocLiteralContext(); + result->pattern = pattern; + return result; +} + +static inline match_LiteralContext * +match_allocLiteralContext(void) { + return uio_malloc(sizeof (match_LiteralContext)); +} + +static inline void +match_freeLiteralContext(match_LiteralContext *context) { + uio_free(context); +} + + +// *** Prefix part *** + +match_Result +match_preparePrefix(const char *pattern, + match_PrefixContext **contextPtr) { + *contextPtr = match_newPrefixContext(uio_strdup(pattern)); + return match_OK; +} + +match_Result +match_matchPrefix(match_PrefixContext *context, const char *string) { + char *patPtr; + + patPtr = context->pattern; + while (1) { + if (*patPtr == '\0') { + // prefix has completely matched + return match_MATCH; + } + if (*string == '\0') { + // no more string left, and still prefix to match + return match_NOMATCH; + } + if (*patPtr != *string) + return match_NOMATCH; + patPtr++; + string++; + } +} + +void +match_freePrefix(match_PrefixContext *context) { + uio_free(context->pattern); + match_freePrefixContext(context); +} + +static inline match_PrefixContext * +match_newPrefixContext(char *pattern) { + match_PrefixContext *result; + + result = match_allocPrefixContext(); + result->pattern = pattern; + return result; +} + +static inline match_PrefixContext * +match_allocPrefixContext(void) { + return uio_malloc(sizeof (match_PrefixContext)); +} + +static inline void +match_freePrefixContext(match_PrefixContext *context) { + uio_free(context); +} + + +// *** Suffix part *** + +match_Result +match_prepareSuffix(const char *pattern, + match_SuffixContext **contextPtr) { + *contextPtr = match_newSuffixContext( + uio_strdup(pattern), strlen(pattern)); + return match_OK; +} + +match_Result +match_matchSuffix(match_SuffixContext *context, const char *string) { + size_t stringLen; + + stringLen = strlen(string); + if (stringLen < context->len) { + // Supplied suffix is larger than string + return match_NOMATCH; + } + + return memcmp(string + stringLen - context->len, context->pattern, + context->len) == 0 ? match_MATCH : match_NOMATCH; +} + +void +match_freeSuffix(match_SuffixContext *context) { + uio_free(context->pattern); + match_freeSuffixContext(context); +} + +static inline match_SuffixContext * +match_newSuffixContext(char *pattern, size_t len) { + match_SuffixContext *result; + + result = match_allocSuffixContext(); + result->pattern = pattern; + result->len = len; + return result; +} + +static inline match_SuffixContext * +match_allocSuffixContext(void) { + return uio_malloc(sizeof (match_SuffixContext)); +} + +static inline void +match_freeSuffixContext(match_SuffixContext *context) { + uio_free(context); +} + + +// *** SubString part *** + +match_Result +match_prepareSubString(const char *pattern, + match_SubStringContext **contextPtr) { + *contextPtr = match_newSubStringContext(uio_strdup(pattern)); + return match_OK; +} + +match_Result +match_matchSubString(match_SubStringContext *context, const char *string) { + return strstr(string, context->pattern) != NULL; +} + +void +match_freeSubString(match_SubStringContext *context) { + uio_free(context->pattern); + match_freeSubStringContext(context); +} + +static inline match_SubStringContext * +match_newSubStringContext(char *pattern) { + match_SubStringContext *result; + + result = match_allocSubStringContext(); + result->pattern = pattern; + return result; +} + +static inline match_SubStringContext * +match_allocSubStringContext(void) { + return uio_malloc(sizeof (match_SubStringContext)); +} + +static inline void +match_freeSubStringContext(match_SubStringContext *context) { + uio_free(context); +} + + +// *** Glob part *** + +#ifdef HAVE_GLOB +match_Result +match_prepareGlob(const char *pattern, match_GlobContext **contextPtr) { + *contextPtr = match_newGlobContext(uio_strdup(pattern)); + return match_OK; +} + +match_Result +match_matchGlob(match_GlobContext *context, const char *string) { + int retval; + + retval = fnmatch(context->pattern, string, 0); + switch (retval) { + case 0: + return match_MATCH; + case FNM_NOMATCH: + return match_NOMATCH; +#if 0 + case FNM_NOSYS: + return match_ENOSYS; +#endif + default: + return match_EUNKNOWN; + } +} + +void +match_freeGlob(match_GlobContext *context) { + uio_free(context->pattern); + match_freeGlobContext(context); +} + +static inline match_GlobContext * +match_newGlobContext(char *pattern) { + match_GlobContext *result; + + result = match_allocGlobContext(); + result->pattern = pattern; + return result; +} + +static inline match_GlobContext * +match_allocGlobContext(void) { + return uio_malloc(sizeof (match_GlobContext)); +} + +static inline void +match_freeGlobContext(match_GlobContext *context) { + uio_free(context); +} +#endif /* HAVE_GLOB */ + + +// *** Regex part *** + +#ifdef HAVE_REGEX +match_Result +match_prepareRegex(const char *pattern, match_RegexContext **contextPtr) { + *contextPtr = match_newRegexContext(); + (*contextPtr)->errorCode = regcomp(&(*contextPtr)->native, pattern, + REG_EXTENDED | REG_NOSUB); + if ((*contextPtr)->errorCode == 0) { + (*contextPtr)->flags = match_REGEX_INITIALISED; + return match_OK; + } + return match_ECUSTOM; +} + +match_Result +match_matchRegex(match_RegexContext *context, const char *string) { + int retval; + + if ((context->flags & match_REGEX_INITIALISED) != + match_REGEX_INITIALISED) { + return match_ENOTINIT; + } + if (context->errorString) { + uio_free(context->errorString); + context->errorString = NULL; + } + retval = regexec(&context->native, string, 0, NULL, 0); + switch (retval) { + case 0: + return match_MATCH; + case REG_NOMATCH: + return match_NOMATCH; + default: + context->errorCode = retval; + return match_ECUSTOM; + } +} + +const char * +match_errorStringRegex(match_RegexContext *context, int errorCode) { + size_t errorStringLength; + + if (context->errorString != NULL) + uio_free(context->errorString); + + errorStringLength = regerror(context->errorCode, &context->native, + NULL, 0); + context->errorString = uio_malloc(errorStringLength); + regerror(context->errorCode, &context->native, + context->errorString, errorStringLength); + + (void) errorCode; + return context->errorString; +} + +void +match_freeRegex(match_RegexContext *context) { + regfree(&context->native); + if (context->errorString) + uio_free(context->errorString); + match_freeRegexContext(context); +} + +static inline match_RegexContext * +match_newRegexContext(void) { + match_RegexContext *result; + result = match_allocRegexContext(); + result->errorString = NULL; + result->errorCode = 0; + result->flags = 0; + return result; +} + +static inline match_RegexContext * +match_allocRegexContext(void) { + return uio_malloc(sizeof (match_RegexContext)); +} + +static inline void +match_freeRegexContext(match_RegexContext *context) { + uio_free(context); +} +#endif /* HAVE_REGEX */ + diff --git a/src/libs/uio/match.h b/src/libs/uio/match.h new file mode 100644 index 0000000..0557e7d --- /dev/null +++ b/src/libs/uio/match.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_MATCH_H_ +#define LIBS_UIO_MATCH_H_ + +typedef struct match_MatchContext match_MatchContext; + +#include + +// TODO: make this into a configurable option +//#define HAVE_GLOB +#define HAVE_REGEX + + +typedef enum { + match_MATCH_LITERAL = 0, + match_MATCH_PREFIX, + match_MATCH_SUFFIX, + match_MATCH_SUBSTRING, +#ifdef HAVE_GLOB + match_MATCH_GLOB, +#endif +#ifdef HAVE_REGEX + match_MATCH_REGEX, +#endif +} match_MatchType; + +typedef int match_Result; +#define match_NOMATCH 0 +#define match_MATCH 1 +#define match_OK 0 +#define match_EUNKNOWN -1 +#define match_ENOSYS -2 +#define match_ECUSTOM -3 +#define match_ENOTINIT -4 + +typedef struct match_LiteralContext match_LiteralContext; +typedef struct match_PrefixContext match_PrefixContext; +typedef struct match_SuffixContext match_SuffixContext; +typedef struct match_SubStringContext match_SubStringContext; +#ifdef HAVE_GLOB +typedef struct match_GlobContext match_GlobContext; +#endif +#ifdef HAVE_REGEX +typedef struct match_RegexContext match_RegexContext; +#endif + +match_Result match_prepareContext(const char *pattern, + match_MatchContext **contextPtr, match_MatchType type); +match_Result match_matchPattern(match_MatchContext *context, + const char *string); +const char *match_errorString(match_MatchContext *context, + match_Result result); +void match_freeContext(match_MatchContext *context); +match_Result match_matchPatternOnce(const char *pattern, match_MatchType type, + const char *string); + + +/* *** Internal definitions follow *** */ +#ifdef match_INTERNAL + +#include +#ifdef HAVE_REGEX +# include +#endif + +#include "uioport.h" + +struct match_MatchContext { + match_MatchType type; + union { + match_LiteralContext *literal; + match_PrefixContext *prefix; + match_SuffixContext *suffix; + match_SubStringContext *subString; +#ifdef HAVE_GLOB + match_GlobContext *glob; +#endif +#ifdef HAVE_REGEX + match_RegexContext *regex; +#endif + } u; +}; + +struct match_LiteralContext { + char *pattern; +}; + +struct match_PrefixContext { + char *pattern; +}; + +struct match_SuffixContext { + char *pattern; + size_t len; + // for speed +}; + +struct match_SubStringContext { + char *pattern; +}; + +#ifdef HAVE_GLOB +struct match_GlobContext { + char *pattern; +}; +#endif + +#ifdef HAVE_REGEX +struct match_RegexContext { + regex_t native; + char *errorString; + int errorCode; + int flags; +#define match_REGEX_INITIALISED 1 +}; +#endif + +match_Result match_prepareLiteral(const char *pattern, + match_LiteralContext **contextPtr); +match_Result match_matchLiteral(match_LiteralContext *context, + const char *string); +void match_freeLiteral(match_LiteralContext *context); + +match_Result match_preparePrefix(const char *pattern, + match_PrefixContext **contextPtr); +match_Result match_matchPrefix(match_PrefixContext *context, + const char *string); +void match_freePrefix(match_PrefixContext *context); + +match_Result match_prepareSuffix(const char *pattern, + match_SuffixContext **contextPtr); +match_Result match_matchSuffix(match_SuffixContext *context, + const char *string); +void match_freeSuffix(match_SuffixContext *context); + +match_Result match_prepareSubString(const char *pattern, + match_SubStringContext **contextPtr); +match_Result match_matchSubString(match_SubStringContext *context, + const char *string); +void match_freeSubString(match_SubStringContext *context); + +#ifdef HAVE_GLOB +match_Result match_prepareGlob(const char *pattern, + match_GlobContext **contextPtr); +match_Result match_matchGlob(match_GlobContext *context, + const char *string); +void match_freeGlob(match_GlobContext *context); +#endif /* HAVE_GLOB */ + +#ifdef HAVE_REGEX +match_Result match_prepareRegex(const char *pattern, + match_RegexContext **contextPtr); +match_Result match_matchRegex(match_RegexContext *context, + const char *string); +const char *match_errorStringRegex(match_RegexContext *context, + int errorCode); +void match_freeRegex(match_RegexContext *context); +#endif + +#endif /* match_INTERNAL */ + +#endif /* LIBS_UIO_MATCH_H_ */ + diff --git a/src/libs/uio/mem.h b/src/libs/uio/mem.h new file mode 100644 index 0000000..915837e --- /dev/null +++ b/src/libs/uio/mem.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_MEM_H_ +#define LIBS_UIO_MEM_H_ + +#include +#include +#include "uioport.h" + +#define uio_malloc malloc +#define uio_realloc realloc +#define uio_free free +#define uio_calloc calloc + +#ifdef uio_MEM_DEBUG +// When uio_strdup is defined to the libc strdup, there's no opportunity +// to intercept the alloc. Hence this function here. +static inline char * +uio_strdup(const char *s) { + char *result; + size_t size; + + size = strlen(s) + 1; + result = uio_malloc(size); + memcpy(result, s, size); + return result; +} +#else +# define uio_strdup strdup +#endif + +// Allocates new memory, copies 'len' characters from 'src', and adds a '\0'. +static inline char * +uio_memdup0(const char *src, size_t len) { + char *dst = uio_malloc(len + 1); + memcpy(dst, src, len); + dst[len] = '\0'; + return dst; +} + +#endif + + diff --git a/src/libs/uio/memdebug.c b/src/libs/uio/memdebug.c new file mode 100644 index 0000000..b6bd1b6 --- /dev/null +++ b/src/libs/uio/memdebug.c @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include + +#include "memdebug.h" +#include "hashtable.h" +#include "mem.h" +#include "uioutils.h" +#include "types.h" +#include "uioport.h" + +// I'm intentionally not including the .h files. It would only make things +// messy, because the second arguments would be other pointers, which +// would require typecasts of the functions in uio_MemDebug_logTypeInfo. +extern void uio_DirHandle_print(FILE *out, const void *); +extern void uio_printMountInfo(FILE *out, const void *); +extern void uio_printMountTreeItem(FILE *out, const void *); +extern void uio_printPathComp(FILE *out, const void *); + +// Must be in the same order as in uio_MemDebug_LogTypes +// Change the third field to have debug info printed for specific actions. +// See memdebug.h file the possible values. +const uio_MemDebug_LogTypeInfo uio_MemDebug_logTypeInfo[] = { + { "uio_DirHandle", uio_DirHandle_print, 0 }, + { "uio_FileSystemInfo", NULL, 0 }, + { "uio_GPDir", NULL, 0 }, + { "uio_GPFile", NULL, 0 }, + { "uio_GPRoot", NULL, 0 }, + { "uio_Handle", NULL, 0 }, + { "uio_MountHandle", NULL, 0 }, + { "uio_MountInfo", uio_printMountInfo, 0 }, + { "uio_MountTree", NULL, 0 }, + { "uio_MountTreeItem", uio_printMountTreeItem, 0 }, + { "uio_PathComp", uio_printPathComp, 0 }, + { "uio_PFileHandle", NULL, 0 }, + { "uio_PDirHandle", NULL, 0 }, + { "uio_PRoot", NULL, 0 }, + { "uio_Repository", NULL, 0 }, + { "uio_Stream", NULL, 0 }, + { "stdio_GPDirData", NULL, 0 }, +#ifdef HAVE_ZIP + { "zip_GPFileData", NULL, 0 }, + { "zip_GPDirData", NULL, 0 }, +#endif +}; + +HashTable_HashTable **uio_MemDebug_logs; + + +static uio_uint32 uio_MemDebug_pointerHash(const void *ptr); +static uio_bool uio_MemDebug_pointerCompare(const void *ptr1, const void *ptr2); +static void *uio_MemDebug_pointerCopy(const void *ptr); +static void uio_MemDebug_pointerFree(void *ptr); + +static inline uio_MemDebug_PointerInfo *uio_MemDebug_PointerInfo_new(int ref); +static inline void uio_MemDebug_PointerInfo_delete( + uio_MemDebug_PointerInfo *pointerInfo); +static inline uio_MemDebug_PointerInfo *uio_MemDebug_PointerInfo_alloc(void); +static inline void uio_MemDebug_PointerInfo_free( + uio_MemDebug_PointerInfo *pointerInfo); + +void +uio_MemDebug_init(void) { + int i; + + assert((int) uio_MemDebug_numLogTypes == + (sizeof uio_MemDebug_logTypeInfo / + sizeof uio_MemDebug_logTypeInfo[0])); + uio_MemDebug_logs = uio_malloc(uio_MemDebug_numLogTypes * + sizeof (HashTable_HashTable *)); + + for (i = 0; i < uio_MemDebug_numLogTypes; i++) { + uio_MemDebug_logs[i] = HashTable_newHashTable( + uio_MemDebug_pointerHash, + uio_MemDebug_pointerCompare, + uio_MemDebug_pointerCopy, + uio_MemDebug_pointerFree, + 4, 0.85, 0.90); + } +} + +void +uio_MemDebug_unInit(void) { + int i; + + for (i = 0; i < uio_MemDebug_numLogTypes; i++) + HashTable_deleteHashTable(uio_MemDebug_logs[i]); + + uio_free(uio_MemDebug_logs); +} + +static uio_uint32 +uio_MemDebug_pointerHash(const void *ptr) { + uio_uintptr ptrInt; + + ptrInt = (uio_uintptr) ptr; + return (uio_uint32) (ptrInt ^ (ptrInt >> 10) ^ (ptrInt >> 20)); +} + +static uio_bool +uio_MemDebug_pointerCompare(const void *ptr1, const void *ptr2) { + return ptr1 == ptr2; +} + +static void * +uio_MemDebug_pointerCopy(const void *ptr) { + return unconst(ptr); +} + +static void +uio_MemDebug_pointerFree(void *ptr) { + (void) ptr; +} + +void +uio_MemDebug_logAllocation(uio_MemDebug_LogType type, void *ptr) { + uio_MemDebug_PointerInfo *pointerInfo; + + if (ptr == NULL) { + fprintf(stderr, "Fatal: Allocated pointer is (%s *) NULL.\n", + uio_MemDebug_logTypeInfo[(int) type].name); + abort(); + } + if (uio_MemDebug_logTypeInfo[(int) type].flags & uio_MemDebug_PRINT_ALLOC) { + fprintf(stderr, "Alloc "); + uio_MemDebug_printPointer(stderr, type, ptr); + fprintf(stderr, "\n"); + } + pointerInfo = uio_MemDebug_PointerInfo_new(1); + HashTable_add(uio_MemDebug_logs[type], ptr, (void *) pointerInfo); +} + +void +uio_MemDebug_logDeallocation(uio_MemDebug_LogType type, void *ptr) { + uio_MemDebug_PointerInfo *pointerInfo; + + if (ptr == NULL) { + fprintf(stderr, "Fatal: Attempt to free (%s *) NULL pointer.\n", + uio_MemDebug_logTypeInfo[(int) type].name); + abort(); + } + if (uio_MemDebug_logTypeInfo[(int) type].flags & uio_MemDebug_PRINT_FREE) { + fprintf(stderr, "Free "); + uio_MemDebug_printPointer(stderr, type, ptr); + fprintf(stderr, "\n"); + } + pointerInfo = HashTable_find(uio_MemDebug_logs[type], ptr); + if (pointerInfo == NULL) { + fprintf(stderr, "Fatal: Attempt to free unallocated pointer " + "(%s *) %p.\n", + uio_MemDebug_logTypeInfo[(int) type].name, ptr); + abort(); + } +#if 0 + if (pointerInfo->ref != 0) { + fprintf(stderr, "Fatal: Attempt to free pointer with references " + "left (%s *) %p.\n", + uio_MemDebug_logTypeInfo[(int) type].name, ptr); + abort(); + } +#endif + uio_MemDebug_PointerInfo_free(pointerInfo); + HashTable_remove(uio_MemDebug_logs[type], ptr); +} + +void +uio_MemDebug_logRef(uio_MemDebug_LogType type, void *ptr) { + uio_MemDebug_PointerInfo *pointerInfo; + + if (ptr == NULL) { + fprintf(stderr, "Fatal: Attempt to increment reference to a " + "(%s *) NULL pointer.\n", + uio_MemDebug_logTypeInfo[(int) type].name); + abort(); + } + pointerInfo = HashTable_find(uio_MemDebug_logs[type], ptr); + if (pointerInfo == NULL) { + fprintf(stderr, "Fatal: Attempt to increment reference to " + "unallocated pointer (%s *) %p.\n", + uio_MemDebug_logTypeInfo[(int) type].name, ptr); + abort(); + } + pointerInfo->pointerRef++; + if (uio_MemDebug_logTypeInfo[(int) type].flags & uio_MemDebug_PRINT_REF) { + fprintf(stderr, "Ref++ to %d, ", pointerInfo->pointerRef); + uio_MemDebug_printPointer(stderr, type, ptr); + fprintf(stderr, "\n"); + } +} + +void +uio_MemDebug_logUnref(uio_MemDebug_LogType type, void *ptr) { + uio_MemDebug_PointerInfo *pointerInfo; + + if (ptr == NULL) { + fprintf(stderr, "Fatal: Attempt to decrement reference to a " + "(%s *) NULL pointer.\n", + uio_MemDebug_logTypeInfo[(int) type].name); + abort(); + } + pointerInfo = HashTable_find(uio_MemDebug_logs[type], ptr); + if (pointerInfo == NULL) { + fprintf(stderr, "Fatal: Attempt to decrement reference to " + "unallocated pointer (%s *) %p.\n", + uio_MemDebug_logTypeInfo[(int) type].name, ptr); + abort(); + } + if (pointerInfo->pointerRef == 0) { + fprintf(stderr, "Fatal: Attempt to decrement reference below 0 for " + "pointer (%s *) %p.\n", + uio_MemDebug_logTypeInfo[(int) type].name, ptr); + abort(); + } + pointerInfo->pointerRef--; + if (uio_MemDebug_logTypeInfo[(int) type].flags & uio_MemDebug_PRINT_UNREF) { + fprintf(stderr, "Ref-- to %d, ", pointerInfo->pointerRef); + uio_MemDebug_printPointer(stderr, type, ptr); + fprintf(stderr, "\n"); + } +} + +void +uio_MemDebug_printPointer(FILE *out, uio_MemDebug_LogType type, void *ptr) { + fprintf(out, "(%s *) %p", uio_MemDebug_logTypeInfo[(int) type].name, ptr); + if (uio_MemDebug_logTypeInfo[(int) type].printFunction != NULL) { + fprintf(out, ": "); + uio_MemDebug_logTypeInfo[(int) type].printFunction(out, ptr); + } +} + +void +uio_MemDebug_printPointersType(FILE *out, uio_MemDebug_LogType type) { + HashTable_Iterator *iterator; + + for (iterator = HashTable_getIterator(uio_MemDebug_logs[type]); + !HashTable_iteratorDone(iterator); + iterator = HashTable_iteratorNext(iterator)) { + uio_MemDebug_printPointer(out, type, HashTable_iteratorKey(iterator)); + fprintf(out, "\n"); + } + HashTable_freeIterator(iterator); +} + +void +uio_MemDebug_printPointers(FILE *out) { + int i; + + for (i = 0; i < uio_MemDebug_numLogTypes; i++) + uio_MemDebug_printPointersType(out, i); +} + +static inline uio_MemDebug_PointerInfo * +uio_MemDebug_PointerInfo_new(int ref) { + uio_MemDebug_PointerInfo *result; + result = uio_MemDebug_PointerInfo_alloc(); + result->pointerRef = ref; + return result; +} + +static inline void +uio_MemDebug_PointerInfo_delete(uio_MemDebug_PointerInfo *pointerInfo) { + uio_MemDebug_PointerInfo_free(pointerInfo); +} + +static inline uio_MemDebug_PointerInfo * +uio_MemDebug_PointerInfo_alloc(void) { + return uio_malloc(sizeof (uio_MemDebug_PointerInfo)); +} + +static inline void +uio_MemDebug_PointerInfo_free(uio_MemDebug_PointerInfo *pointerInfo) { + uio_free(pointerInfo); +} + diff --git a/src/libs/uio/memdebug.h b/src/libs/uio/memdebug.h new file mode 100644 index 0000000..e04a8d2 --- /dev/null +++ b/src/libs/uio/memdebug.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_MEMDEBUG_H_ +#define LIBS_UIO_MEMDEBUG_H_ + +#include +#include "uioport.h" + +// Important: if you add an item here, add it to uio_MemDebug_LogTypeInfo +// too. The order should be the same. +typedef enum { + uio_MemDebug_LogType_uio_DirHandle, + uio_MemDebug_LogType_uio_FileSystemInfo, + uio_MemDebug_LogType_uio_GPDir, + uio_MemDebug_LogType_uio_GPFile, + uio_MemDebug_LogType_uio_GPRoot, + uio_MemDebug_LogType_uio_Handle, + uio_MemDebug_LogType_uio_MountHandle, + uio_MemDebug_LogType_uio_MountInfo, + uio_MemDebug_LogType_uio_MountTree, + uio_MemDebug_LogType_uio_MountTreeItem, + uio_MemDebug_LogType_uio_PathComp, + uio_MemDebug_LogType_uio_PFileHandle, + uio_MemDebug_LogType_uio_PDirHandle, + uio_MemDebug_LogType_uio_PRoot, + uio_MemDebug_LogType_uio_Repository, + uio_MemDebug_LogType_uio_Stream, + uio_MemDebug_LogType_stdio_GPDirData, + uio_MemDebug_LogType_zip_GPFileData, + uio_MemDebug_LogType_zip_GPDirData, + + uio_MemDebug_numLogTypes, /* This needs to be the last entry */ +} uio_MemDebug_LogType; + +typedef void (uio_MemDebug_PrintFunction)(FILE *out, const void *arg); + +typedef struct uio_MemDebug_LogTypeInfo { + const char *name; + uio_MemDebug_PrintFunction *printFunction; + int flags; +#define uio_MemDebug_PRINT_ALLOC 0x1 +#define uio_MemDebug_PRINT_FREE 0x2 +#define uio_MemDebug_PRINT_REF 0x4 +#define uio_MemDebug_PRINT_UNREF 0x8 +#define uio_MemDebug_PRINT_ALL \ + (uio_MemDebug_PRINT_ALLOC | uio_MemDebug_PRINT_FREE | \ + uio_MemDebug_PRINT_REF | uio_MemDebug_PRINT_UNREF) +} uio_MemDebug_LogTypeInfo; + +extern const uio_MemDebug_LogTypeInfo uio_MemDebug_logTypeInfo[]; + +typedef struct uio_MemDebug_PointerInfo { + int pointerRef; + // Ref counter for the associated pointer, not the pointerInfo + // itself. +} uio_MemDebug_PointerInfo; + +void uio_MemDebug_init(void); +void uio_MemDebug_unInit(void); +void uio_MemDebug_logAllocation(uio_MemDebug_LogType type, void *ptr); +void uio_MemDebug_logDeallocation(uio_MemDebug_LogType type, void *ptr); +void uio_MemDebug_logRef(uio_MemDebug_LogType type, void *ptr); +void uio_MemDebug_logUnref(uio_MemDebug_LogType type, void *ptr); +void uio_MemDebug_printPointer(FILE *out, uio_MemDebug_LogType type, + void *ptr); +void uio_MemDebug_printPointersType(FILE *out, uio_MemDebug_LogType type); +void uio_MemDebug_printPointers(FILE *out); + +#define uio_MemDebug_debugAlloc(type, pointer) \ + uio_MemDebug_logAllocation(uio_MemDebug_LogType_ ## type, pointer) +#define uio_MemDebug_debugFree(type, pointer) \ + uio_MemDebug_logDeallocation(uio_MemDebug_LogType_ ## type, pointer) +#define uio_MemDebug_debugRef(type, pointer) \ + uio_MemDebug_logRef(uio_MemDebug_LogType_ ## type, pointer) +#define uio_MemDebug_debugUnref(type, pointer) \ + uio_MemDebug_logUnref(uio_MemDebug_LogType_ ## type, pointer) + +#endif /* LIBS_UIO_MEMDEBUG_H_ */ + diff --git a/src/libs/uio/mount.c b/src/libs/uio/mount.c new file mode 100644 index 0000000..f095d3f --- /dev/null +++ b/src/libs/uio/mount.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include + +#include "iointrn.h" +#include "uioport.h" +#include "mount.h" +#include "mounttree.h" +#include "mem.h" +#include "uioutils.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + +static void uio_Repository_delete(uio_Repository *repository); +static uio_Repository *uio_Repository_alloc(void); +static void uio_Repository_free(uio_Repository *repository); + + +void +uio_repositoryAddMount(uio_Repository *repository, + uio_MountInfo *mountInfo, uio_MountLocation location, + uio_MountInfo *relative) { + lockRepository(repository, uio_LOCK_WRITE); + switch (location) { + case uio_MOUNT_TOP: { + uio_MountInfo **newMounts; + + newMounts = uio_malloc( + (repository->numMounts + 2) * sizeof (uio_MountInfo *)); + newMounts[0] = mountInfo; + memcpy(&newMounts[1], repository->mounts, + (repository->numMounts + 1) * sizeof (uio_MountInfo *)); + uio_free(repository->mounts); + repository->mounts = newMounts; + repository->numMounts++; + break; + } + case uio_MOUNT_BOTTOM: + repository->mounts = uio_realloc(repository->mounts, + (repository->numMounts + 2) * sizeof (uio_MountInfo *)); + repository->mounts[repository->numMounts] = mountInfo; + repository->numMounts++; + break; + case uio_MOUNT_ABOVE: { + int i; + uio_MountInfo **newMounts; + + i = 0; + while (repository->mounts[i] != relative) + i++; + newMounts = (uio_MountInfo **) insertArrayPointer( + (const void **) repository->mounts, + repository->numMounts + 1, i, (void *) mountInfo); + uio_free(repository->mounts); + repository->mounts = newMounts; + repository->numMounts++; + break; + } + case uio_MOUNT_BELOW: { + int i; + uio_MountInfo **newMounts; + + i = 0; + while (repository->mounts[i] != relative) + i++; + i++; + newMounts = (uio_MountInfo **) insertArrayPointer( + (const void **) repository->mounts, + repository->numMounts + 1, i, (void *) mountInfo); + uio_free(repository->mounts); + repository->mounts = newMounts; + repository->numMounts++; + break; + } + default: + assert(false); + } + unlockRepository(repository); +} + +void +uio_repositoryRemoveMount(uio_Repository *repository, uio_MountInfo *mountInfo) { + int i; + uio_MountInfo **newMounts; + + lockRepository(repository, uio_LOCK_WRITE); + + i = 0; + while (repository->mounts[i] != mountInfo) + i++; + newMounts = (uio_MountInfo **) excludeArrayPointer( + (const void **) repository->mounts, repository->numMounts + 1, + i, 1); + uio_free(repository->mounts); + repository->mounts = newMounts; + repository->numMounts--; + unlockRepository(repository); +} + +// sets ref to 1 +uio_Repository * +uio_Repository_new(int flags) { + uio_Repository *result; + + result = uio_Repository_alloc(); + result->ref = 1; + result->flags = flags; + result->numMounts = 0; + result->mounts = uio_malloc(1 * sizeof (uio_MountInfo *)); + result->mounts[0] = NULL; + result->mountTree = uio_makeRootMountTree(); + return result; +} + +void +uio_Repository_unref(uio_Repository *repository) { + assert(repository->ref > 0); + repository->ref--; + if (repository->ref == 0) + uio_Repository_delete(repository); +} + +static uio_Repository * +uio_Repository_alloc(void) { + uio_Repository *result = uio_malloc(sizeof (uio_Repository)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_Repository, (void *) result); +#endif + return result; +} + +static void +uio_Repository_delete(uio_Repository *repository) { + assert(repository->numMounts == 0); + uio_free(repository->mounts); + uio_MountTree_delete(repository->mountTree); + uio_Repository_free(repository); +} + +static void +uio_Repository_free(uio_Repository *repository) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_Repository, (void *) repository); +#endif + uio_free(repository); +} + + diff --git a/src/libs/uio/mount.h b/src/libs/uio/mount.h new file mode 100644 index 0000000..237f54b --- /dev/null +++ b/src/libs/uio/mount.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_MOUNT_H_ +#define LIBS_UIO_MOUNT_H_ + + +typedef struct uio_Repository uio_Repository; +typedef struct uio_AutoMount uio_AutoMount; +#define uio_MOUNT_RDONLY (1 << 1) + +/* *** Internal definitions follow */ +#ifdef uio_INTERNAL + +#define uio_MOUNT_LOCATION_MASK (3 << 2) + +#include "uioport.h" +#include "fstypes.h" +#include "mounttree.h" +#include "match.h" + +struct uio_Repository { + int ref; /* reference counter */ + int flags; + int numMounts; + struct uio_MountInfo **mounts; + // first in the list is considered the top + // last entry is NULL + struct uio_MountTree *mountTree; +}; + +#define lockRepository(repository, prot) +#define unlockRepository(repository) + +uio_Repository *uio_Repository_new(int flags); +void uio_Repository_unref(uio_Repository *repository); +void uio_repositoryAddMount(uio_Repository *repository, + uio_MountInfo *mountInfo, uio_MountLocation location, + uio_MountInfo *relative); +void uio_repositoryRemoveMount(uio_Repository *repository, + uio_MountInfo *mountInfo); + + +#endif /* uio_INTERNAL */ + +#endif /* LIBS_UIO_MOUNT_H_ */ + diff --git a/src/libs/uio/mounttree.c b/src/libs/uio/mounttree.c new file mode 100644 index 0000000..eb5bdec --- /dev/null +++ b/src/libs/uio/mounttree.c @@ -0,0 +1,814 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#ifdef DEBUG +# include +#endif +#include + +#include "iointrn.h" +#include "uioport.h" +#include "mounttree.h" +#include "paths.h" +#include "types.h" +#include "mem.h" +#include "uioutils.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + +static uio_MountTree *uio_mountTreeAddMountInfoRecTree( + uio_Repository *repository, uio_MountTree *tree, + uio_MountInfo *mountInfo, const char *start, const char *end, + uio_PathComp *upComp, uio_MountLocation location, + const uio_MountInfo *relative); +static inline uio_MountTree *uio_mountTreeAddMountInfoRecTreeSub( + uio_Repository *repository, uio_MountTree **tree, + uio_MountInfo *mountInfo, const char *start, + const char *end, uio_MountLocation location, + const uio_MountInfo *relative); +static void uio_mountTreeAddMountInfoLocAll(uio_Repository *repository, + uio_MountTree *tree, uio_MountInfo *mountInfo, int depth, + uio_MountLocation location, const uio_MountInfo *relative); +static uio_MountTreeItem *uio_copyMountTreeItems(uio_MountTreeItem *item, + int extraDepth); +static void uio_addMountTreeItem(uio_Repository *repository, + uio_MountTreeItem **pLocs, uio_MountTreeItem *item, + uio_MountLocation location, const uio_MountInfo *relative); +static uio_MountTree *uio_mountTreeAddNewSubTree(uio_Repository *repository, + uio_MountTree *tree, const char *path, uio_MountInfo *mountInfo, + uio_PathComp *upComp, uio_MountLocation location, + const uio_MountInfo *relative); +static void uio_mountTreeAddSub(uio_MountTree *tree, uio_MountTree *sub); +static uio_MountTree * uio_splitMountTree(uio_MountTree **tree, uio_PathComp + *lastComp, int depth); +static void uio_mountTreeRemoveMountInfoRec(uio_MountTree *mountTree, + uio_MountInfo *mountInfo); +static void uio_printMount(FILE *outStream, const uio_MountInfo *mountInfo); + +static inline uio_MountTree * uio_MountTree_new(uio_MountTree *subTrees, + uio_MountTreeItem *pLocs, uio_MountTree *upTree, uio_PathComp + *comps, uio_PathComp *lastComp, uio_MountTree *next); +static inline uio_MountTreeItem *uio_MountTree_newItem( + uio_MountInfo *mountInfo, int depth, uio_MountTreeItem *next); + +static inline void uio_MountTreeItem_delete(uio_MountTreeItem *item); + +static inline uio_MountTree *uio_MountTree_alloc(void); +static inline uio_MountTreeItem *uio_MountTreeItem_alloc(void); +static inline uio_MountInfo *uio_MountInfo_alloc(void); + +static inline void uio_MountTree_free(uio_MountTree *mountTree); +static inline void uio_MountTreeItem_free(uio_MountTreeItem *mountTreeItem); +static inline void uio_MountInfo_free(uio_MountInfo *mountInfo); + + +// make the root mount Tree +uio_MountTree * +uio_makeRootMountTree(void) { + return uio_MountTree_new(NULL, NULL, NULL, NULL, NULL, NULL); +} + +// Add a MountInfo structure to a MountTree in the place pointed +// to by 'path'. +// returns the MountTree for the location at the end of the path +uio_MountTree * +uio_mountTreeAddMountInfo(uio_Repository *repository, uio_MountTree *mountTree, + uio_MountInfo *mountInfo, const char *path, uio_MountLocation location, + const uio_MountInfo *relative) { + const char *start, *end; + + getFirstPath0Component(path, &start, &end); + return uio_mountTreeAddMountInfoRecTree(repository, mountTree, mountInfo, + start, end, NULL, location, relative); +} + +// recursive helper for uio_mountTreeAddMountInfo +// returns the MountTree for the location at the end of the path +static uio_MountTree * +uio_mountTreeAddMountInfoRecTree(uio_Repository *repository, uio_MountTree *tree, + uio_MountInfo *mountInfo, const char *start, const char *end, + uio_PathComp *upComp, + uio_MountLocation location, const uio_MountInfo *relative) { + uio_MountTree **sub; + + if (*start == '\0') { + // End of the path. Put the MountInfo here and on all subtrees below + // this level. + uio_mountTreeAddMountInfoLocAll(repository, tree, mountInfo, 0, + location, relative); + return tree; + } + + // Check if sub trees match the path. + for (sub = &tree->subTrees; *sub != NULL; sub = &(*sub)->next) { + uio_MountTree *resTree; + + resTree = uio_mountTreeAddMountInfoRecTreeSub(repository, sub, + mountInfo, start, end, location, relative); + if (resTree != NULL) { + // handled + return resTree; + } + } + // No subtree found matching (part of) 'path'. + + // Need to add a new tree sub + return uio_mountTreeAddNewSubTree(repository, tree, start, mountInfo, + upComp, location, relative); +} + +// recursive helper for uio_mountTreeAddMountInfo +// Pre: *start != '\0' +// returns the MountTree for the location at the end of the path, if +// that falls within this tree. If not, returns NULL. +static inline uio_MountTree * +uio_mountTreeAddMountInfoRecTreeSub(uio_Repository *repository, + uio_MountTree **tree, uio_MountInfo *mountInfo, + const char *start, const char *end, uio_MountLocation location, + const uio_MountInfo *relative) { + uio_PathComp *comp, *lastComp; + int depth; + + comp = (*tree)->comps; + if (strncmp(comp->name, start, end - start) != 0 || + comp->name[end - start] != '\0') { + // first component does not match; this is not the correct subTree + return NULL; + } + + depth = 1; + // try to match all components of the directory path to this subTree. + while (1) { + getNextPath0Component(&start, &end); + lastComp = comp; + comp = comp->next; + + if (comp == NULL) + break; + + if (*start == '\0') { + // end of the path reached + // We need to split up the components and insert a new + // MountTree here. + uio_MountTree *newTree; + newTree = uio_splitMountTree(tree, lastComp, depth); + + // Add mountInfo to each of the MountTrees below newTree. + uio_mountTreeAddMountInfoLocAll(repository, newTree, mountInfo, 0, + location, relative); + + return newTree; + } + if (strncmp(comp->name, start, end - start) != 0 || + comp->name[end - start] != '\0') { + // Some, but not all components matched; we need to split + // up the components and add a new subTree here for the + // (non-matching) rest of the path. + uio_MountTree *newTree; + + newTree = uio_splitMountTree(tree, lastComp, depth); + + // A new Tree is added at the split-point. + return uio_mountTreeAddNewSubTree(repository, newTree, start, + mountInfo, lastComp, location, relative); + } + getNextPath0Component(&start, &end); + depth++; + } + + // All components matched. We can recurse to the next subdir. + return uio_mountTreeAddMountInfoRecTree(repository, *tree, mountInfo, + start, end, lastComp, location, relative); +} + +// Add a MountInfo struct 'mountInfo' to the pLocs fields of all subTrees +// starting with 'tree'. +// 'depth' is the distance to the MountTree where the MountInfo is located. +static void +uio_mountTreeAddMountInfoLocAll(uio_Repository *repository, uio_MountTree *tree, + uio_MountInfo *mountInfo, int depth, uio_MountLocation location, + const uio_MountInfo *relative) { + uio_MountTreeItem *newPLoc; + uio_MountTree *subTree; + int compCount; + + // Add a new PLoc to this mountTree + newPLoc = uio_MountTree_newItem(mountInfo, depth, NULL); + uio_addMountTreeItem(repository, &tree->pLocs, newPLoc, location, relative); + + // Recurse for subtrees + for (subTree = tree->subTrees; subTree != NULL; + subTree = subTree->next) { + compCount = uio_countPathComps(subTree->comps); + uio_mountTreeAddMountInfoLocAll( + repository, subTree, mountInfo, depth + compCount, + location, relative); + } +} + +// pre: repository->mounts is already updated +// pre: if location is uio_MOUNT_BELOW or uio_MOUNT_ABOVE, 'relative' +// exists in repository->mounts +static void +uio_addMountTreeItem(uio_Repository *repository, uio_MountTreeItem **pLocs, + uio_MountTreeItem *item, + uio_MountLocation location, const uio_MountInfo *relative) { + switch (location) { + case uio_MOUNT_TOP: + item->next = *pLocs; + *pLocs = item; + break; + case uio_MOUNT_BOTTOM: + while (*pLocs != NULL) + pLocs = &(*pLocs)->next; + item->next = NULL; + *pLocs = item; + break; + case uio_MOUNT_ABOVE: { + uio_MountInfo **mountInfo; + mountInfo = repository->mounts; + while (*mountInfo != relative) { + assert(*mountInfo != NULL); + if ((*pLocs)->mountInfo == *mountInfo) + pLocs = &(*pLocs)->next; + mountInfo++; + } + item->next = *pLocs; + *pLocs = item; + break; + } + case uio_MOUNT_BELOW: { + uio_MountInfo **mountInfo; + mountInfo = repository->mounts; + while (*mountInfo != relative) { + assert(*mountInfo != NULL); + if ((*pLocs)->mountInfo == *mountInfo) + pLocs = &(*pLocs)->next; + mountInfo++; + } + item->next = (*pLocs)->next; + (*pLocs)->next = item; + break; + } + default: + assert(false); + } +} + +// Copy a chain of MountTreeItems, but increase the depth by 'extraDepth'. +static uio_MountTreeItem * +uio_copyMountTreeItems(uio_MountTreeItem *item, int extraDepth) { + uio_MountTreeItem *result, **resPtr; + uio_MountTreeItem *newItem; + + resPtr = &result; + while (item != NULL) { + newItem = uio_MountTree_newItem( + item->mountInfo, item->depth + extraDepth, NULL); + *resPtr = newItem; + resPtr = &newItem->next; + item = item->next; + } + *resPtr = NULL; + return result; +} + +// add a new sub tree under a tree 'tree'. +// 'path' is the part leading up to the new tree and +// 'mountInfo' is the MountInfo structure to at there. +// 'upComp' points to the last path component that lead to 'tree'. +static uio_MountTree * +uio_mountTreeAddNewSubTree(uio_Repository *repository, uio_MountTree *tree, + const char *path, uio_MountInfo *mountInfo, uio_PathComp *upComp, + uio_MountLocation location, const uio_MountInfo *relative) { + uio_MountTreeItem *item, *items; + uio_MountTree *newTree; + uio_PathComp *compList, *lastComp; + int compCount; + + compList = uio_makePathComps(path, upComp); + compCount = uio_countPathComps(compList); + lastComp = uio_lastPathComp(compList); + item = uio_MountTree_newItem(mountInfo, 0, NULL); + item->next = NULL; + items = uio_copyMountTreeItems(tree->pLocs, compCount); + uio_addMountTreeItem(repository, &items, item, location, + relative); + newTree = uio_MountTree_new( + NULL /* subTrees */, + items /* pLocs */, + tree /* upTree */, + compList /* comps */, + lastComp /* lastComp */, + NULL /* next */); + uio_mountTreeAddSub(tree, newTree); + return newTree; +} + +// add a sub structure to the end of the 'subTrees' list of a tree. +static void +uio_mountTreeAddSub(uio_MountTree *tree, uio_MountTree *sub) { + uio_MountTree **subPtr; + + for (subPtr = &tree->subTrees; *subPtr != NULL; + subPtr = &(*subPtr)->next) { + // Nothing to do here. + } + *subPtr = sub; +} + +// Add a new MountTree structure in between two MountTrees. +// Tree points to the pointer for the tree in front of which the new +// tree needs to be placed (at depth 'depth'). +// 'lastComp' is the last pathComp of the part before the splitting point +// It returns the new MountTree. +static uio_MountTree * +uio_splitMountTree(uio_MountTree **tree, uio_PathComp *lastComp, int depth) { + uio_MountTree *newTree; + uio_MountTreeItem *items; + + items = uio_copyMountTreeItems((*tree)->upTree->pLocs, depth); + newTree = uio_MountTree_new( + *tree /* subTrees */, + items /* pLocs */, + (*tree)->upTree /* upTree */, + (*tree)->comps /* comps */, + lastComp /* lastComp */, + NULL /* next */); + (*tree)->upTree = newTree; + (*tree)->comps = lastComp->next; + lastComp->next = NULL; + *tree = newTree; + return newTree; +} + +void +uio_mountTreeRemoveMountInfo(uio_Repository *repository, + uio_MountTree *mountTree, uio_MountInfo *mountInfo) { + uio_MountTree **subTreePtr; + uio_MountTree *upTree; + + // If the tree has no sub-trees and it has the same items as the + // upTree, with 'mountInfo' added, then the tree is a dead end + // and can be removed entirely. + // First we handle the other case. + // Note that if the upTree has exactly one item less than the tree + // itself, these items must be the same, plus mountInfo for the + // tree itself, as each tree has at least the items of its upTree. + if (mountTree->upTree == NULL || mountTree->subTrees != NULL || + uio_mountTreeCountPLocs(mountTree) != + uio_mountTreeCountPLocs(mountTree->upTree) + 1) { + // We can't remove the tree itself. + // We need to remove the mountInfo from the tree, and all subTrees. + // Then we're done. + uio_mountTreeRemoveMountInfoRec(mountTree, mountInfo); + return; + } + + // mountTree itself can be removed. + // First remove the tree from the list of subtrees of the upTree. + subTreePtr = &mountTree->upTree->subTrees; + while (1) { + assert(*subTreePtr != NULL); + if (*subTreePtr == mountTree) + break; + subTreePtr = &(*subTreePtr)->next; + } + *subTreePtr = mountTree->next; + + // Save the upTree for later. + upTree = mountTree->upTree; + + // Remove the tree itself. + uio_MountTree_delete(mountTree); + + // The upTree itself could have become unnecessary now. + // This is the case when upTree now only has one subTree, and upTree + // and the subTree have the same items. + // Again, same item count implies same items. + if (upTree->subTrees == NULL || upTree->subTrees->next != NULL || + uio_mountTreeCountPLocs(upTree) != + uio_mountTreeCountPLocs(upTree->subTrees)) { + // upTree is still necessary. We're done. + return; + } + + // Merge upTree and upTree->subTrees. + // It would be easiest to keep upTree, and throw upTree->subTrees away, + // but that's not possible as external links point to upTree->subTrees. + // First merge the path components: + assert(upTree->subTrees->lastComp != NULL); + upTree->subTrees->lastComp->next = upTree->subTrees->comps; + upTree->subTrees->lastComp = upTree->lastComp; + upTree->subTrees->comps = upTree->comps; + // Now let the pointer that pointed to upTree, point to upTree->subTrees. + // Change upTree->next accordingly. + if (upTree->upTree == NULL) { + assert(repository->mountTree == upTree); + repository->mountTree = upTree->subTrees; + // upTree->subTrees->next is already NULL + } else { + uio_MountTree *next; + subTreePtr = &upTree->upTree->subTrees; + while (1) { + assert(*subTreePtr != NULL); + if (*subTreePtr == upTree) + break; + subTreePtr = &(*subTreePtr)->next; + } + next = (*subTreePtr)->next; + *subTreePtr = upTree->subTrees; + upTree->subTrees->next = next; + } + + // Now delete the tree itself + upTree->subTrees = NULL; + upTree->comps = NULL; + uio_MountTree_delete(upTree); +} + +// pre: mountInfo exists in mountTree->pLocs (and hence in pLocs for +// every sub-tree) +static void +uio_mountTreeRemoveMountInfoRec(uio_MountTree *mountTree, + uio_MountInfo *mountInfo) { + uio_MountTree *subTree; + uio_MountTreeItem **itemPtr, *item; + + // recurse for all subTrees + for (subTree = mountTree->subTrees; subTree != NULL; + subTree = subTree->next) + uio_mountTreeRemoveMountInfoRec(subTree, mountInfo); + + // Find the mount info in this tree. + itemPtr = &mountTree->pLocs; + while (1) { + assert(*itemPtr != NULL); + // We know an item with the specified mountInfo + // must be here somewhere. + if ((*itemPtr)->mountInfo == mountInfo) { + // Found it. + break; + } + itemPtr = &(*itemPtr)->next; + } + + item = *itemPtr; + *itemPtr = item->next; + uio_MountTreeItem_delete(item); +} + +// Count the number of pLocs in a tree that leads to. +int +uio_mountTreeCountPLocs(const uio_MountTree *tree) { + int count; + uio_MountTreeItem *item; + + count = 0; + for (item = tree->pLocs; item != NULL; item = item->next) + count++; + return count; +} + +// resTree may point to top +// pPath may point to path +void +uio_findMountTree(uio_MountTree *top, const char *path, + uio_MountTree **resTree, const char **pPath) { + const char *start, *end, *pathFromTree; + uio_MountTree *tree, *sub; + uio_PathComp *comp; + + getFirstPath0Component(path, &start, &end); + tree = top; + while(1) { + if (*start == '\0') { + *resTree = tree; + *pPath = start; + return; + } + + pathFromTree = start; + sub = tree->subTrees; + while(1) { + if (sub == NULL) { + // No matching sub Dirs found. So we report back the current + // dir. + *resTree = tree; + *pPath = pathFromTree; + return; + } + comp = sub->comps; + if (strncmp(comp->name, start, end - start) == 0 && + comp->name[end - start] == '\0') + break; + sub = sub->next; + } + // Found a Sub dir which matches at least partially. + + while (1) { + getNextPath0Component(&start, &end); + comp = comp->next; + if (comp == NULL) + break; + if (*start == '\0' || + strncmp(comp->name, start, end - start) != 0 || + comp->name[end - start] != '\0') { + // either the path ends here, or the path in the tree does. + // either way, the last Tree is the one we want. + *resTree = tree; + *pPath = pathFromTree; + return; + } + } + // all components matched until the next MountTree + tree = sub; + } +} + +// finds the path to the MountInfo associated with a mountTreeItem +// given a path to the 'item' itself. +// 'item' is the mountTreeItem +// 'endComp' is the last PathComp leading to 'item' +// 'start' is the start of the path to the item +char * +uio_mountTreeItemRestPath(const uio_MountTreeItem *item, + uio_PathComp *endComp, const char *path) { + int i; + const char *pathPtr; + + i = item->depth; + while (i--) + endComp = endComp->up; + + pathPtr = path; + if (endComp != NULL) { + while (1) { + pathPtr += endComp->nameLen; + endComp = endComp->up; + if (endComp == NULL) + break; + pathPtr++; + // for a '/' + } + } + if (*path == '/') { + // / at the beginning of the path + pathPtr++; + } + if (*pathPtr == '/') { + // / at the end of the path + pathPtr++; + } +// return (char *) pathPtr; + // gives warning +// return *((char **)((void *) &pathPtr)); + // not portable + return (char *) unconst((const void *) pathPtr); +} + +void +uio_printMountTree(FILE *outStream, const uio_MountTree *tree, int indent) { + uio_MountTree *sub; + uio_PathComp *comp; + + fprintf(outStream, "("); + uio_printMountTreeItems(outStream, tree->pLocs); + fprintf(outStream, ")\n"); + for (sub = tree->subTrees; sub != NULL; sub = sub->next) { + int newIndent; + + newIndent = indent; + fprintf(outStream, "%*s", indent, ""); + for (comp = sub->comps; comp != NULL; comp = comp->next) { + fprintf(outStream, "/%s", comp->name); + newIndent += 1 + comp->nameLen; + } + fprintf(outStream, " "); + newIndent += 1; + uio_printMountTree(outStream, sub, newIndent); + } +} + +void +uio_printMountTreeItem(FILE *outStream, const uio_MountTreeItem *item) { + uio_printMountInfo(outStream, item->mountInfo); + fprintf(outStream, ":%d", item->depth); +} + +void +uio_printMountTreeItems(FILE *outStream, const uio_MountTreeItem *item) { + if (!item) + return; + while(1) { + uio_printMountTreeItem(outStream, item); + item = item->next; + if (item == NULL) + break; + fprintf(outStream, ", "); + } +} + +void +uio_printPathToMountTree(FILE *outStream, const uio_MountTree *tree) { + if (tree->upTree == NULL) { + fprintf(outStream, "/"); + } else + uio_printPathToComp(outStream, tree->lastComp); +} + +void +uio_printMountInfo(FILE *outStream, const uio_MountInfo *mountInfo) { + uio_FileSystemInfo *fsInfo; + + fsInfo = uio_getFileSystemInfo(mountInfo->fsID); + fprintf(outStream, "%s:/%s", fsInfo->name, mountInfo->dirName); +} + +static void +uio_printMount(FILE *outStream, const uio_MountInfo *mountInfo) { + uio_FileSystemInfo *fsInfo; + + fsInfo = uio_getFileSystemInfo(mountInfo->fsID); + fprintf(outStream, "???:%s on ", mountInfo->dirName); + uio_printPathToMountTree(outStream, mountInfo->mountTree); + fprintf(outStream, " type %s (", fsInfo->name); + if (mountInfo->flags & uio_MOUNT_RDONLY) { + fprintf(outStream, "ro"); + } else + fprintf(outStream, "rw"); + fprintf(outStream, ")\n"); +} + +void +uio_printMounts(FILE *outStream, const uio_Repository *repository) { + int i; + + for (i = 0; i < repository->numMounts; i++) { + uio_printMount(outStream, repository->mounts[i]); + } +} + + +// *** uio_MountTree*** // + +static inline uio_MountTree * +uio_MountTree_new(uio_MountTree *subTrees, uio_MountTreeItem *pLocs, + uio_MountTree *upTree, uio_PathComp *comps, uio_PathComp *lastComp, + uio_MountTree *next) { + uio_MountTree *result; + + result = uio_MountTree_alloc(); + result->subTrees = subTrees; + result->pLocs = pLocs; + result->upTree = upTree; + result->comps = comps; + result->lastComp = lastComp; + result->next = next; + return result; +} + +void +uio_MountTree_delete(uio_MountTree *tree) { + uio_MountTree *subTree, *nextTree; + uio_MountTreeItem *item, *nextItem; + + subTree = tree->subTrees; + while (subTree != NULL) { + nextTree = subTree->next; + uio_MountTree_delete(subTree); + subTree = nextTree; + } + + item = tree->pLocs; + while (item != NULL) { + nextItem = item->next; + uio_MountTreeItem_delete(item); + item = nextItem; + } + + if (tree->comps != NULL) + uio_PathComp_delete(tree->comps); + + uio_MountTree_free(tree); +} + +static inline uio_MountTree * +uio_MountTree_alloc(void) { + uio_MountTree *result = uio_malloc(sizeof (uio_MountTree)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_MountTree, (void *) result); +#endif + return result; +} + +static inline void +uio_MountTree_free(uio_MountTree *mountTree) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_MountTree, (void *) mountTree); +#endif + uio_free(mountTree); +} + + +// *** uio_MountTreeItem *** // + +static inline uio_MountTreeItem * +uio_MountTree_newItem(uio_MountInfo *mountInfo, int depth, + uio_MountTreeItem *next) { + uio_MountTreeItem *result; + + result = uio_MountTreeItem_alloc(); + result->mountInfo = mountInfo; + result->depth = depth; + result->next = next; + return result; +} + +static inline void +uio_MountTreeItem_delete(uio_MountTreeItem *item) { + uio_MountTreeItem_free(item); +} + +static inline uio_MountTreeItem * +uio_MountTreeItem_alloc(void) { + uio_MountTreeItem *result = uio_malloc(sizeof (uio_MountTreeItem)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_MountTreeItem, (void *) result); +#endif + return result; +} + +static inline void +uio_MountTreeItem_free(uio_MountTreeItem *mountTreeItem) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_MountTreeItem, (void *) mountTreeItem); +#endif + uio_free(mountTreeItem); +} + + +// *** uio_MountInfo *** // + +uio_MountInfo * +uio_MountInfo_new(uio_FileSystemID fsID, uio_MountTree *mountTree, + uio_PDirHandle *pDirHandle, char *dirName, uio_AutoMount **autoMount, + uio_MountHandle *mountHandle, int flags) { + uio_MountInfo *result; + + result = uio_MountInfo_alloc(); + result->fsID = fsID; + result->mountTree = mountTree; + result->pDirHandle = pDirHandle; + result->dirName = dirName; + result->autoMount = autoMount; + result->mountHandle = mountHandle; + result->flags = flags; + return result; +} + +void +uio_MountInfo_delete(uio_MountInfo *mountInfo) { + uio_free(mountInfo->dirName); + uio_PDirHandle_unref(mountInfo->pDirHandle); + uio_MountInfo_free(mountInfo); +} + +static inline uio_MountInfo * +uio_MountInfo_alloc(void) { + uio_MountInfo *result = uio_malloc(sizeof (uio_MountInfo)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_MountInfo, (void *) result); +#endif + return result; +} + +static inline void +uio_MountInfo_free(uio_MountInfo *mountInfo) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_MountInfo, (void *) mountInfo); +#endif + uio_free(mountInfo); +} + + diff --git a/src/libs/uio/mounttree.h b/src/libs/uio/mounttree.h new file mode 100644 index 0000000..b0041bc --- /dev/null +++ b/src/libs/uio/mounttree.h @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_MOUNTTREE_H_ +#define LIBS_UIO_MOUNTTREE_H_ + +#include +#include "mount.h" + +void uio_printMounts(FILE *outStream, const uio_Repository *repository); + +/* *** Internal definitions follow *** */ +#ifdef uio_INTERNAL + +#include + +typedef struct uio_MountTreeItem uio_MountTreeItem; +typedef struct uio_MountTree uio_MountTree; +typedef struct uio_MountInfo uio_MountInfo; + +#include "physical.h" +#include "types.h" +#include "uioport.h" +#include "paths.h" + + +/* + * A MountTree describes the relation between dirs (PRoot structures) + * mounted in a directory structure. + * A MountTree structure represents a node (a dir) in this directory + * structure. + * It describes what MountInfo structures apply in that dir and below (if + * not overrided in subnodes) (the 'pLocs' field). + * These path components are also linked together 'up'-wards (towards the + * root of the tree) by the 'up' field. + */ + +struct uio_MountTreeItem { + struct uio_MountInfo *mountInfo; + int depth; + // 'mountInfo->pDirHandle' and 'depth' together point to a + // location in a physical tree. An uio_pDirHandle alone can't be + // used as the directory might not exist. + // So pDirHandle points to the top dir that was mounted, and + // 'depth' indicates how many directory names of the path to + // this point in the Mount Tree need to be followed. + // Example: + // This MountTreeItem is somewhere in a tree /foo/bar/bla + // and depth = 1. Then this MountTreeItem points to + // /bla in the specified root. + struct uio_MountTreeItem *next; + // The next MountTreeItem in a MountTree +}; + +struct uio_MountTree { + struct uio_MountTree *subTrees; + // Trees for subdirs in this MountTree + struct uio_MountTreeItem *pLocs; + // The physical locations that have effect in this MountTree. + struct uio_MountTree *upTree; + // the MountTree that pointed to this MountTree + struct uio_PathComp *comps; + // the names of the path components that lead to the tree. + // Not necessary every PathComp is connected to a MountTree. + // If you have /foo and /foo/bar/zut mounted, then + // there are MountTrees for /, /foo and /foo/bar/zut, + // but there are PathComps for 'foo', 'bar' and 'zut'. + struct uio_PathComp *lastComp; + // The last PathComp of comps that pointed to this MountTree. + // This can be used to trace the path back to the top. + struct uio_MountTree *next; + // If this tree is a subTree of a tree, 'next' points to the + // next subTree of that tree. +}; + +/* + * A MountInfo structure describes how a physical structure was mounted. + * A physical structure can be used by several MountInfo structures. + */ +struct uio_MountInfo { + int flags; + /* Mount flags */ +# define uio_MOUNTINFO_RDONLY uio_MOUNT_RDONLY + uio_FileSystemID fsID; + char *dirName; + /* The path inside the mounted fs leading to pDirHandle */ + uio_PDirHandle *pDirHandle; + /* The pDirHandle belonging to this mount */ + uio_MountTree *mountTree; + /* The MountTree node for the mountpoint */ + uio_AutoMount **autoMount; + uio_MountHandle *mountHandle; +}; + + +/* + * Say we've got mounted (in order): + * Bla -> / + * Bar -> /foo/bar + * Foo -> /foo + * Zut -> /zut/123 + * Fop -> /zut/123 + * + * This will build a tree that looks like this: + * (the strings between brackets are the mounted filesystems that have effect + * in a dir, in order) + * + * / (Bar) + * foo (Foo, Bla) + * bar (Foo, Bar, Bla) + * zut/123 (Fop, Zut, Bla) + * + * The MountTree will look like: + * / = { + * sub = { + * /foo, + * /zut/123 + * }, + * pLocs = { + * BlaDir:/ (0) + * } + * } + * /foo = { + * sub = { + * /foo/bar + * }, + * pLocs = { + * BlaDir:/foo (1), + * FooDir:/ (0) + * } + * } + * /foo/bar = { + * sub = { }, + * pLocs = { + * BlaDir:/foo/bar (2), + * BarDir:/ (0), + * FooDir:/bar (1) + * } + * } + * /zut/123 = { + * sub = { }, + * pLocs = { + * FooDir:/ (0) + * ZutDir:/ (0) + * BlaDir:/zut/123 (2) + * } + * } + * + * 'BlaDir:/zut/123 (2)' means the pDirHandle is 'Bla', and the dir into + * that directory is '/zut/123', but as this is always a postfix of the + * path where we are, the number of dirs (the '(2)') is enough to store + * (apart from the pDirHandle). + */ + +uio_MountTree *uio_makeRootMountTree(void); +void uio_MountTree_delete(uio_MountTree *tree); +uio_MountTree *uio_mountTreeAddMountInfo(uio_Repository *repository, + uio_MountTree *mountTree, uio_MountInfo *mountInfo, const char *path, + uio_MountLocation location, const uio_MountInfo *relative); +void uio_mountTreeRemoveMountInfo(uio_Repository *repository, + uio_MountTree *mountTree, uio_MountInfo *mountInfo); +void uio_findMountTree(uio_MountTree *top, const char *path, + uio_MountTree **resTree, const char **pPath); +char *uio_mountTreeItemRestPath(const uio_MountTreeItem *item, + uio_PathComp *endComp, const char *path); +int uio_mountTreeCountPLocs(const uio_MountTree *tree); +uio_MountInfo *uio_MountInfo_new(uio_FileSystemID fsID, + uio_MountTree *mountTree, uio_PDirHandle *pDirHandle, + char *dirName, uio_AutoMount **autoMount, + uio_MountHandle *mountHandle, int flags); +void uio_MountInfo_delete(uio_MountInfo *mountInfo); +void uio_printMountTree(FILE *outStream, const uio_MountTree *tree, + int indent); +void uio_printMountTreeItem(FILE *outStream, const uio_MountTreeItem *item); +void uio_printMountTreeItems(FILE *outStream, const uio_MountTreeItem *item); +void uio_printPathToMountTree(FILE *outStream, const uio_MountTree *tree); +void uio_printMountInfo(FILE *outStream, const uio_MountInfo *mountInfo); + +static inline uio_bool +uio_mountInfoIsReadOnly(uio_MountInfo *mountInfo) { + return (mountInfo->flags & uio_MOUNTINFO_RDONLY) != 0; +} + +#endif /* uio_INTERNAL */ + +#endif /* LIBS_UIO_MOUNTTREE_H_ */ + diff --git a/src/libs/uio/paths.c b/src/libs/uio/paths.c new file mode 100644 index 0000000..f8411cd --- /dev/null +++ b/src/libs/uio/paths.c @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#include +#include + +#include "paths.h" +#include "uioport.h" +#include "mem.h" + +static inline uio_PathComp *uio_PathComp_alloc(void); +static inline void uio_PathComp_free(uio_PathComp *pathComp); + +// gets the first dir component of a path +// sets '*start' to the start of the first component +// sets '*end' to the end of the first component +// if *start >= dirEnd, then the end has been reached. +void +getFirstPathComponent(const char *dir, const char *dirEnd, + const char **startComp, const char **endComp) { + assert(dir <= dirEnd); + *startComp = dir; + if (*startComp == dirEnd) { + *endComp = *startComp; + return; + } + *endComp = memchr(*startComp, '/', dirEnd - *startComp); + if (*endComp == NULL) + *endComp = dirEnd; +} + +// gets the first dir component of a path +// sets '*start' to the start of the first component +// sets '*end' to the end of the first component +// if **start == '\0', then the end has been reached. +void +getFirstPath0Component(const char *dir, + const char **startComp, const char **endComp) { + *startComp = dir; + if (**startComp == '\0') { + *endComp = *startComp; + return; + } + *endComp = strchr(*startComp, '/'); + if (*endComp == NULL) + *endComp = *startComp + strlen(*startComp); +} + +// gets the next component of a path +// '*start' should be set to the start of the last component +// '*end' should be set to the end of the last component +// '*start' will be set to the start of the next component +// '*end' will be set to the end of the next component +// if *start >= dirEnd, then the end has been reached. +void +getNextPathComponent(const char *dirEnd, + const char **startComp, const char **endComp) { + assert(*endComp <= dirEnd); + if (*endComp == dirEnd) { + *startComp = *endComp; + return; + } + assert(**endComp == '/'); + *startComp = *endComp + 1; + *endComp = memchr(*startComp, '/', dirEnd - *startComp); + if (*endComp == NULL) + *endComp = dirEnd; +} + +// gets the next component of a path +// '*start' should be set to the start of the last component +// '*end' should be set to the end of the last component +// '*start' will be set to the start of the next component +// '*end' will be set to the end of the next component +// if **start == '\0', then the end has been reached. +void +getNextPath0Component(const char **startComp, const char **endComp) { + if (**endComp == '\0') { + *startComp = *endComp; + return; + } + assert(**endComp == '/'); + *startComp = *endComp + 1; + *endComp = strchr(*startComp, '/'); + if (*endComp == NULL) + *endComp = *startComp + strlen(*startComp); +} + +// if *end == dir, the beginning has been reached +void +getLastPathComponent(const char *dir, const char *endDir, + const char **startComp, const char **endComp) { + *endComp = endDir; +// if (*(*endComp - 1) == '/') +// (*endComp)--; + *startComp = *endComp; + // TODO: use memrchr where available + while ((*startComp) - 1 >= dir && *(*startComp - 1) != '/') + (*startComp)--; +} + +// if *end == dir, the beginning has been reached +// pre: dir is \0-terminated +void +getLastPath0Component(const char *dir, + const char **startComp, const char **endComp) { + *endComp = dir + strlen(dir); +// if (*(*endComp - 1) == '/') +// (*endComp)--; + *startComp = *endComp; + // TODO: use memrchr where available + while ((*startComp) - 1 >= dir && *(*startComp - 1) != '/') + (*startComp)--; +} + +// if *end == dir, the beginning has been reached +void +getPreviousPathComponent(const char *dir, + const char **startComp, const char **endComp) { + if (*startComp == dir) { + *endComp = *startComp; + return; + } + *endComp = *startComp - 1; + *startComp = *endComp; + while ((*startComp) - 1 >= dir && *(*startComp - 1) != '/') + (*startComp)--; +} + +// Combine two parts of a paths into a new path. +// The new path starts with a '/' only when the first part does. +// The first path may (but doesn't have to) end on a '/', or may be empty. +// Pre: the second path doesn't start with a '/' +char * +joinPaths(const char *first, const char *second) { + char *result, *resPtr; + size_t firstLen, secondLen; + + if (first[0] == '\0') + return uio_strdup(second); + + firstLen = strlen(first); + if (first[firstLen - 1] == '/') + firstLen--; + secondLen = strlen(second); + result = uio_malloc(firstLen + secondLen + 2); + resPtr = result; + + memcpy(resPtr, first, firstLen); + resPtr += firstLen; + + *resPtr = '/'; + resPtr++; + + memcpy(resPtr, second, secondLen); + resPtr += secondLen; + + *resPtr = '\0'; + return result; +} + +// Combine two parts of a paths into a new path. +// The new path will always start with a '/'. +// The first path may (but doesn't have to) end on a '/', or may be empty. +// Pre: the second path doesn't start with a '/' +char * +joinPathsAbsolute(const char *first, const char *second) { + char *result, *resPtr; + size_t firstLen, secondLen; + + if (first[0] == '\0') { + secondLen = strlen(second); + result = uio_malloc(secondLen + 2); + result[0] = '/'; + memcpy(&result[1], second, secondLen); + result[secondLen + 1] = '\0'; + return result; + } + + firstLen = strlen(first); + if (first[firstLen - 1] == '/') + firstLen--; + secondLen = strlen(second); + result = uio_malloc(firstLen + secondLen + 3); + resPtr = result; + + *resPtr = '/'; + resPtr++; + + memcpy(resPtr, first, firstLen); + resPtr += firstLen; + + *resPtr = '/'; + resPtr++; + + memcpy(resPtr, second, secondLen); + resPtr += secondLen; + + *resPtr = '\0'; + return result; +} + +// Returns 'false' if +// - one of the path components is empty, or +// - one of the path components is ".", or +// - one of the path components is ".." +// and 'true' otherwise. +uio_bool +validPathName(const char *path, size_t len) { + const char *pathEnd; + const char *start, *end; + + pathEnd = path + len; + getFirstPathComponent(path, pathEnd, &start, &end); + while (start < pathEnd) { + if (end == start || (end - start == 1 && start[0] == '.') || + (end - start == 2 && start[0] == '.' && start[1] == '.')) + return false; + getNextPathComponent(pathEnd, &start, &end); + } + return true; +} + +// returns 0 if the path is not a valid UNC path. +// Does not skip trailing slashes. +size_t +uio_skipUNCServerShare(const char *inPath) { + const char *path = inPath; + + // Skip the initial two backslashes. + if (path[0] != '\\' || path[1] != '\\') + return (size_t) 0; + path += 2; + + // Skip the server part. + while (*path != '\\' && *path != '/') { + if (*path == '\0') + return (size_t) 0; + path++; + } + + // Skip the seperator. + path++; + + // Skip the share part. + while (*path != '\0' && *path != '\\' && *path != '/') + path++; + + return (size_t) (path - inPath); +} + +/** + * Find the server and share part of a Windows "Universal Naming Convention" + * path (a path of the form '\\server\share\path\file'). + * The path must contain at least a server and share path to be valid. + * The initial two slashes must be backslashes, the other slashes may each + * be either a forward slash or a backslash. + * + * @param[in] inPath The path to parse. + * @param[out] outPath Will contain a newly allocated string (to be + * freed using uio_free(), containing the server and share part + * of inPath, separated by a backslash, or NULL if 'inPath' was + * not a valid UNC path. + * @param[out] outLen If not NULL on entry, it will contain the string + * length of '*outPath', or 0 if 'inPath' was not a valid UNC path. + * + * @returns The number of characters to add to 'inPath' to get to the first + * path component past the server and share part, or 0 if 'inPath' + * was not a valid UNC path. + */ +size_t +uio_getUNCServerShare(const char *inPath, char **outPath, size_t *outLen) { + const char *ptr; + const char *server; + const char *serverEnd; + const char *share; + const char *shareEnd; + char *name; + char *nameEnd; + size_t nameLen; + + ptr = inPath; + + if (ptr[0] != '\\' || ptr[1] != '\\') + goto noMatch; + + // Parse the server part. + server = ptr + 2; + serverEnd = server; + for (;;) { + if (*serverEnd == '\0') + goto noMatch; + if (isPathDelimiter(*serverEnd)) + break; + serverEnd++; + } + if (serverEnd == server) + goto noMatch; + + // Parse the share part. + share = serverEnd + 1; + shareEnd = share; + while (*shareEnd != '\0') { + if (isPathDelimiter(*shareEnd)) + break; + serverEnd++; + } + + // Skip any trailing path delimiters. + ptr = shareEnd; + while (isPathDelimiter(*ptr)) + ptr++; + + // Allocate a new string and fill it. + nameLen = (serverEnd - server) + (shareEnd - share) + 3; + name = uio_malloc(nameLen + 1); + nameEnd = name; + *(nameEnd++) = '\\'; + *(nameEnd++) = '\\'; + memcpy(nameEnd, server, serverEnd - server); + *(nameEnd++) = '\\'; + memcpy(nameEnd, share, shareEnd - share); + *nameEnd = '\0'; + + *outPath = name; + if (outLen != NULL) + *outLen = nameLen; + return (size_t) (ptr - inPath); + +noMatch: + *outPath = NULL; + if (outLen != NULL) + *outLen = 0; + return (size_t) 0; +} + +// Decomposes a path into its components. +// If isAbsolute is not NULL, *isAbsolute will be set to true +// iff the path is absolute. +// As POSIX considers multiple consecutive slashes to be equivalent to +// a single slash, so will uio (but not in the "\\MACHINE\share" part +// of a Windows UNC path). +int +decomposePath(const char *path, uio_PathComp **pathComp, + uio_bool *isAbsolute) { + uio_PathComp *result; + uio_PathComp *last; + uio_PathComp **endResult = &result; + uio_bool absolute = false; + char *name; +#ifdef HAVE_UNC_PATHS + size_t nameLen; +#endif /* HAVE_UNC_PATHS */ + + if (path[0] == '\0') { + errno = ENOENT; + return -1; + } + + last = NULL; +#ifdef HAVE_UNC_PATHS + path += uio_getUNCServerShare(path, &name, &nameLen); + if (name != NULL) { + // UNC path + *endResult = uio_PathComp_new(name, nameLen, last); + last = *endResult; + endResult = &last->next; + + absolute = true; + } else +#endif /* HAVE_UNC_PATHS */ +#ifdef HAVE_DRIVE_LETTERS + if (isDriveLetter(path[0]) && path[1] == ':') { + // DOS/Windows drive letter. + if (path[2] != '\0' && !isPathDelimiter(path[2])) { + errno = ENOENT; + return -1; + } + name = uio_memdup0(path, 2); + *endResult = uio_PathComp_new(name, 2, last); + last = *endResult; + endResult = &last->next; + absolute = true; + } else +#endif /* HAVE_DRIVE_LETTERS */ + { + if (isPathDelimiter(*path)) { + absolute = true; + do { + path++; + } while (isPathDelimiter(*path)); + } + } + + while (*path != '\0') { + const char *start = path; + while (*path != '\0' && !isPathDelimiter(*path)) + path++; + + name = uio_memdup0(path, path - start); + *endResult = uio_PathComp_new(name, path - start, last); + last = *endResult; + endResult = &last->next; + + while (isPathDelimiter(*path)) + path++; + } + + *endResult = NULL; + *pathComp = result; + if (isAbsolute != NULL) + *isAbsolute = absolute; + return 0; +} + +// Pre: pathComp forms a valid path for the platform. +void +composePath(const uio_PathComp *pathComp, uio_bool absolute, + char **path, size_t *pathLen) { + size_t len; + const uio_PathComp *ptr; + char *result; + char *pathPtr; + + assert(pathComp != NULL); + + // First determine how much space is required. + len = 0; + if (absolute) + len++; + ptr = pathComp; + while (ptr != NULL) { + len += ptr->nameLen; + ptr = ptr->next; + } + + // Allocate the required space. + result = (char *) uio_malloc(len + 1); + + // Fill the path. + pathPtr = result; + ptr = pathComp; + if (absolute) { +#ifdef HAVE_UNC_PATHS + if (ptr->name[0] == '\\') { + // UNC path + assert(ptr->name[1] == '\\'); + // Nothing to do. + } else +#endif /* HAVE_UNC_PATHS */ +#ifdef HAVE_DRIVE_LETTERS + if (ptr->nameLen == 2 && ptr->name[1] == ':' + && isDriveLetter(ptr->name[0])) { + // Nothing to do. + } + else +#endif /* HAVE_DRIVE_LETTERS */ + { + *(pathPtr++) = '/'; + } + } + + for (;;) { + memcpy(pathPtr, ptr->name, ptr->nameLen); + pathPtr += ptr->nameLen; + + ptr = ptr->next; + if (ptr == NULL) + break; + + *(pathPtr++) = '/'; + } + + *path = result; + *pathLen = len; +} + + +// *** uio_PathComp *** // + +static inline uio_PathComp * +uio_PathComp_alloc(void) { + uio_PathComp *result = uio_malloc(sizeof (uio_PathComp)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_PathComp, (void *) result); +#endif + return result; +} + +static inline void +uio_PathComp_free(uio_PathComp *pathComp) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_PathComp, (void *) pathComp); +#endif + uio_free(pathComp); +} + +// 'name' should be a null terminated string. It is stored in the PathComp, +// no copy is made. +// 'namelen' should be the length of 'name' +uio_PathComp * +uio_PathComp_new(char *name, size_t nameLen, uio_PathComp *upComp) { + uio_PathComp *result; + + result = uio_PathComp_alloc(); + result->name = name; + result->nameLen = nameLen; + result->up = upComp; + return result; +} + +void +uio_PathComp_delete(uio_PathComp *pathComp) { + uio_PathComp *next; + + while (pathComp != NULL) { + next = pathComp->next; + uio_free(pathComp->name); + uio_PathComp_free(pathComp); + pathComp = next; + } +} + +// Count the number of path components that 'comp' leads to. +int +uio_countPathComps(const uio_PathComp *comp) { + int count; + + count = 0; + for (; comp != NULL; comp = comp->next) + count++; + return count; +} + +uio_PathComp * +uio_lastPathComp(uio_PathComp *comp) { + if (comp == NULL) + return NULL; + + while (comp->next != NULL) + comp = comp->next; + return comp; +} + +// make a list of uio_PathComps from a path string +uio_PathComp * +uio_makePathComps(const char *path, uio_PathComp *upComp) { + const char *start, *end; + char *str; + uio_PathComp *result; + uio_PathComp **compPtr; // Where to put the next PathComp + + compPtr = &result; + getFirstPath0Component(path, &start, &end); + while (*start != '\0') { + str = uio_malloc(end - start + 1); + memcpy(str, start, end - start); + str[end - start] = '\0'; + + *compPtr = uio_PathComp_new(str, end - start, upComp); + upComp = *compPtr; + compPtr = &(*compPtr)->next; + getNextPath0Component(&start, &end); + } + *compPtr = NULL; + return result; +} + +void +uio_printPathComp(FILE *outStream, const uio_PathComp *comp) { + fprintf(outStream, "%s", comp->name); +} + +void +uio_printPathToComp(FILE *outStream, const uio_PathComp *comp) { + if (comp == NULL) + return; + uio_printPathToComp(outStream, comp->up); + fprintf(outStream, "/"); + uio_printPathComp(outStream, comp); +} + + diff --git a/src/libs/uio/paths.h b/src/libs/uio/paths.h new file mode 100644 index 0000000..bb5090d --- /dev/null +++ b/src/libs/uio/paths.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_PATHS_H_ +#define LIBS_UIO_PATHS_H_ + +typedef struct uio_PathComp uio_PathComp; + +#include "types.h" +#include "uioport.h" + +#include + +struct uio_PathComp { + char *name; + // The name of this path component, 0-terminated + size_t nameLen; + // The length of the 'name' field, for fast lookups. + struct uio_PathComp *next; + // Next component in the path. + struct uio_PathComp *up; + // Previous component in the path. +}; + +void getFirstPathComponent(const char *dir, const char *dirEnd, + const char **startComp, const char **endComp); +void getFirstPath0Component(const char *dir, const char **startComp, + const char **endComp); +void getNextPathComponent(const char *dirEnd, + const char **startComp, const char **endComp); +void getNextPath0Component(const char **startComp, const char **endComp); +void getLastPathComponent(const char *dir, const char *dirEnd, + const char **startComp, const char **endComp); +void getLastPath0Component(const char *dir, const char **startComp, + const char **endComp); +void getPreviousPathComponent(const char *dir, const char **startComp, + const char **endComp); +#define getPreviousPath0Component getPreviousPathComponent +char *joinPaths(const char *first, const char *second); +char *joinPathsAbsolute(const char *first, const char *second); + +uio_bool validPathName(const char *path, size_t len); +size_t uio_skipUNCServerShare(const char *inPath); +size_t uio_getUNCServerShare(const char *inPath, char **outPath, + size_t *outLen); + +#ifdef HAVE_DRIVE_LETTERS +static inline int +isDriveLetter(int c) +{ + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +} +#endif /* HAVE_DRIVE_LETTERS */ + +static inline int +isPathDelimiter(int c) +{ +#ifdef BACKSLASH_IS_PATH_SEPARATOR + return c == '/' || c == '\\'; +#else + return c == '/'; +#endif /* BACKSLASH_IS_PATH_SEPARATOR */ +} + +int decomposePath(const char *path, uio_PathComp **pathComp, + uio_bool *isAbsolute); +void composePath(const uio_PathComp *pathComp, uio_bool absolute, + char **path, size_t *pathLen); +uio_PathComp *uio_PathComp_new(char *name, size_t nameLen, + uio_PathComp *upComp); +void uio_PathComp_delete(uio_PathComp *pathComp); +int uio_countPathComps(const uio_PathComp *comp); +uio_PathComp *uio_lastPathComp(uio_PathComp *comp); +uio_PathComp *uio_makePathComps(const char *path, uio_PathComp *upComp); +void uio_printPathComp(FILE *outStream, const uio_PathComp *comp); +void uio_printPathToComp(FILE *outStream, const uio_PathComp *comp); + +#endif /* LIBS_UIO_PATHS_H_ */ + diff --git a/src/libs/uio/physical.c b/src/libs/uio/physical.c new file mode 100644 index 0000000..18f96d1 --- /dev/null +++ b/src/libs/uio/physical.c @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#include +#include +#include + +#include "physical.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif +#include "uioport.h" + +static inline uio_PRoot *uio_PRoot_alloc(void); +static inline void uio_PRoot_free(uio_PRoot *pRoot); + +// NB: ref counter is not incremented +uio_PDirHandle * +uio_PRoot_getRootDirHandle(uio_PRoot *pRoot) { + return pRoot->rootDir; +} + +void +uio_PRoot_deletePRootExtra(uio_PRoot *pRoot) { + if (pRoot->extra == NULL) + return; + assert(pRoot->handler->deletePRootExtra != NULL); + pRoot->handler->deletePRootExtra(pRoot->extra); +} + +// note: sets refMount count to 1 +// set handlerRef count to 0 +uio_PRoot * +uio_PRoot_new(uio_PDirHandle *topDirHandle, + uio_FileSystemHandler *handler, uio_Handle *handle, + uio_PRootExtra extra, int flags) { + uio_PRoot *pRoot; + + pRoot = uio_PRoot_alloc(); + pRoot->mountRef = 1; + pRoot->handleRef = 0; + pRoot->rootDir = topDirHandle; + pRoot->handler = handler; + pRoot->handle = handle; + pRoot->extra = extra; + pRoot->flags = flags; +#ifdef uio_PROOT_HAVE_CLOSE_HANDLERS + pRoot->numCloseHandlers = 0; + pRoot->closeHandlers = NULL; +#endif + + return pRoot; +} + +#ifdef uio_PROOT_HAVE_CLOSE_HANDLERS +// Closehandlers code disabled. +// It was only meant for internal use, but I don't need it any more. +// Keeping it around for a while until I'm confident I won't need it in the +// future. + +void +uio_PRoot_addCloseHandler(uio_PRoot *pRoot, void (*fun)(void *), void *arg) { + pRoot->numCloseHandlers++; + pRoot->closeHandlers = uio_realloc(pRoot->closeHandlers, + pRoot->numCloseHandlers * sizeof (uio_PRoot_CloseHandler)); + pRoot->closeHandlers[pRoot->numCloseHandlers - 1].fun = fun; + pRoot->closeHandlers[pRoot->numCloseHandlers - 1].arg = arg; +} + +void +uio_PRoot_callCloseHandlers(uio_PRoot *pRoot) { + int i; + + i = pRoot->numCloseHandlers; + while (i--) { + uio_PRoot_CloseHandler *closeHandler; + + closeHandler = &pRoot->closeHandlers[i]; + (closeHandler->fun)(closeHandler->arg); + } +} + +void +uio_PRoot_removeCloseHandlers(uio_PRoot *pRoot) { + pRoot->numCloseHandlers = 0; + if (pRoot->closeHandlers != NULL) + uio_free(pRoot->closeHandlers); + pRoot->closeHandlers = NULL; +} +#endif + +static inline void +uio_PRoot_delete(uio_PRoot *pRoot) { +#ifdef uio_PROOT_HAVE_CLOSE_HANDLERS + uio_PRoot_callCloseHandlers(pRoot); + uio_PRoot_removeCloseHandlers(pRoot); +#endif + assert(pRoot->handler->umount != NULL); + pRoot->handler->umount(pRoot); + if (pRoot->handle) + uio_Handle_unref(pRoot->handle); + uio_PRoot_deletePRootExtra(pRoot); + uio_PRoot_free(pRoot); +} + +static inline uio_PRoot * +uio_PRoot_alloc(void) { + uio_PRoot *result = uio_malloc(sizeof (uio_PRoot)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_PRoot, (void *) result); +#endif + return result; +} + +static inline void +uio_PRoot_free(uio_PRoot *pRoot) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_PRoot, (void *) pRoot); +#endif + uio_free(pRoot); +} + +void +uio_PRoot_refHandle(uio_PRoot *pRoot) { + pRoot->handleRef++; +} + +void +uio_PRoot_unrefHandle(uio_PRoot *pRoot) { + assert(pRoot->handleRef > 0); + pRoot->handleRef--; + if (pRoot->handleRef == 0 && pRoot->mountRef == 0) + uio_PRoot_delete(pRoot); +} + +void +uio_PRoot_refMount(uio_PRoot *pRoot) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugRef(uio_PRoot, (void *) pRoot); +#endif + pRoot->mountRef++; +} + +void +uio_PRoot_unrefMount(uio_PRoot *pRoot) { + assert(pRoot->mountRef > 0); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugUnref(uio_PRoot, (void *) pRoot); +#endif + pRoot->mountRef--; + if (pRoot->mountRef == 0 && pRoot->handleRef == 0) + uio_PRoot_delete(pRoot); +} + + diff --git a/src/libs/uio/physical.h b/src/libs/uio/physical.h new file mode 100644 index 0000000..71bc8e6 --- /dev/null +++ b/src/libs/uio/physical.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_PHYSICAL_H_ +#define LIBS_UIO_PHYSICAL_H_ + +#ifndef uio_INTERNAL_PHYSICAL +typedef void *uio_PRootExtra; +typedef void *uio_NativeEntriesContext; +#endif + +// 'forward' declarations +typedef struct uio_PRoot uio_PRoot; +typedef struct uio_PRoot_CloseHandler uio_PRoot_CloseHandler; + + +#include "iointrn.h" +#include "uioport.h" +#include "fstypes.h" + + +/* + * Represents the root of a physical directory structure. + */ +struct uio_PRoot { + int mountRef; + /* Number of times this structure is referenced from + * mount trees. */ + int handleRef; + /* Number of file or directory handles that point inside the + * physical directory strucure of this pRoot. + */ + struct uio_PDirHandle *rootDir; + struct uio_FileSystemHandler *handler; + /* How to handle files in this PRoot tree */ + int flags; +# define uio_PRoot_NOCACHE 0x0002 + struct uio_Handle *handle; + /* The handle through which this PRoot is opened, + * this is NULL for the top PRoot */ + // TODO: move this to extra? +#ifdef uio_PROOT_HAVE_CLOSE_HANDLERS + int numCloseHandlers; + uio_PRoot_CloseHandler *closeHandlers; +#endif + uio_PRootExtra extra; + /* extra internal data for some filesystem types */ +}; + +#ifdef uio_PROOT_HAVE_CLOSE_HANDLERS +struct uio_PRoot_CloseHandler { + void (*fun)(void *); + void *arg; +}; +#endif + +void uio_PRoot_deletePRootExtra(uio_PRoot *pRoot); +uio_PRoot *uio_PRoot_new(uio_PDirHandle *topDirHandle, + uio_FileSystemHandler *handler, uio_Handle *handle, + uio_PRootExtra extra, int flags); +#ifdef uio_PROOT_HAVE_CLOSE_HANDLERS +void uio_PRoot_addCloseHandler(uio_PRoot *pRoot, void (*fun)(void *), + void *arg); +void uio_PRoot_callCloseHandlers(uio_PRoot *pRoot); +void uio_PRoot_removeCloseHandlers(uio_PRoot *pRoot); +#endif +uio_PDirHandle *uio_PRoot_getRootDirHandle(uio_PRoot *pRoot); +void uio_PRoot_refHandle(uio_PRoot *pRoot); +void uio_PRoot_unrefHandle(uio_PRoot *pRoot); +void uio_PRoot_refMount(uio_PRoot *pRoot); +void uio_PRoot_unrefMount(uio_PRoot *pRoot); + +#endif /* LIBS_UIO_PHYSICAL_H_ */ + + diff --git a/src/libs/uio/stdio/Makeinfo b/src/libs/uio/stdio/Makeinfo new file mode 100644 index 0000000..2d99c66 --- /dev/null +++ b/src/libs/uio/stdio/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="stdio.c" +uqm_HFILES="stdio.h" diff --git a/src/libs/uio/stdio/stdio.c b/src/libs/uio/stdio/stdio.c new file mode 100644 index 0000000..a4421d1 --- /dev/null +++ b/src/libs/uio/stdio/stdio.c @@ -0,0 +1,854 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// The GPDir structures and functions are used for caching only. + +#ifdef __svr4__ +# define _POSIX_PTHREAD_SEMANTICS + // For the POSIX variant of readdir_r() +#endif + +#include "./stdio.h" + +#ifdef WIN32 +# include +#else +# include +# include +# include +#endif +#include +#include +#include +#include +#include +#include + +#include "../uioport.h" +#include "../paths.h" +#include "../mem.h" +#include "../physical.h" +#ifdef uio_MEM_DEBUG +# include "../memdebug.h" +#endif + +static inline uio_GPFile *stdio_addFile(uio_GPDir *gPDir, + const char *fileName); +static inline uio_GPDir *stdio_addDir(uio_GPDir *gPDir, const char *dirName); +static char *stdio_getPath(uio_GPDir *gPDir); +static stdio_GPDirData *stdio_GPDirData_new(char *name, char *cachedPath, + uio_GPDir *upDir); +static void stdio_GPDirData_delete(stdio_GPDirData *gPDirData); +static inline stdio_GPDirData *stdio_GPDirData_alloc(void); +static inline void stdio_GPDirData_free(stdio_GPDirData *gPDirData); +static inline stdio_EntriesIterator *stdio_EntriesIterator_alloc(void); +static inline void stdio_EntriesIterator_free( + stdio_EntriesIterator *iterator); + +uio_FileSystemHandler stdio_fileSystemHandler = { + /* .init = */ NULL, + /* .unInit = */ NULL, + /* .cleanup = */ NULL, + + /* .mount = */ stdio_mount, + /* .umount = */ uio_GPRoot_umount, + + /* .access = */ stdio_access, + /* .close = */ stdio_close, + /* .fstat = */ stdio_fstat, + /* .stat = */ stdio_stat, + /* .mkdir = */ stdio_mkdir, + /* .open = */ stdio_open, + /* .read = */ stdio_read, + /* .rename = */ stdio_rename, + /* .rmdir = */ stdio_rmdir, + /* .seek = */ stdio_seek, + /* .write = */ stdio_write, + /* .unlink = */ stdio_unlink, + + /* .openEntries = */ stdio_openEntries, + /* .readEntries = */ stdio_readEntries, + /* .closeEntries = */ stdio_closeEntries, + + /* .getPDirEntryHandle = */ stdio_getPDirEntryHandle, + /* .deletePRootExtra = */ uio_GPRoot_delete, + /* .deletePDirHandleExtra = */ uio_GPDirHandle_delete, + /* .deletePFileHandleExtra = */ uio_GPFileHandle_delete, +}; + +uio_GPRoot_Operations stdio_GPRootOperations = { + /* .fillGPDir = */ NULL, + /* .deleteGPRootExtra = */ NULL, + /* .deleteGPDirExtra = */ stdio_GPDirData_delete, + /* .deleteGPFileExtra = */ NULL, +}; + + +void +stdio_close(uio_Handle *handle) { + int fd; + int result; + + fd = handle->native->fd; + uio_free(handle->native); + + while (1) { + result = close(fd); + if (result == 0) + break; + if (errno != EINTR) { + fprintf(stderr, "Warning: Error while closing socket: %s\n", + strerror(errno)); + break; + } + } +} + +int +stdio_access(uio_PDirHandle *pDirHandle, const char *name, int mode) { + char *path; + int result; + + path = joinPaths(stdio_getPath(pDirHandle->extra), name); + if (path == NULL) { + // errno is set + return -1; + } + + result = access(path, mode); + if (result == -1) { + int savedErrno = errno; + uio_free(path); + errno = savedErrno; + return -1; + } + + uio_free(path); + return result; +} + +int +stdio_fstat(uio_Handle *handle, struct stat *statBuf) { + return fstat(handle->native->fd, statBuf); +} + +int +stdio_stat(uio_PDirHandle *pDirHandle, const char *name, + struct stat *statBuf) { + char *path; + int result; + + path = joinPaths(stdio_getPath(pDirHandle->extra), name); + if (path == NULL) { + // errno is set + return -1; + } + + result = stat(path, statBuf); + if (result == -1) { + int savedErrno = errno; + uio_free(path); + errno = savedErrno; + return -1; + } + + uio_free(path); + return result; +} + +uio_PDirHandle * +stdio_mkdir(uio_PDirHandle *pDirHandle, const char *name, mode_t mode) { + char *path; + uio_GPDir *newGPDir; + + path = joinPaths(stdio_getPath(pDirHandle->extra), name); + if (path == NULL) { + // errno is set + return NULL; + } + + if (MKDIR(path, mode) == -1) { + int savedErrno = errno; + uio_free(path); + errno = savedErrno; + return NULL; + } + uio_free(path); + + newGPDir = stdio_addDir(pDirHandle->extra, name); + uio_GPDir_ref(newGPDir); + return uio_PDirHandle_new(pDirHandle->pRoot, newGPDir); +} + +/* + * Function name: stdio_open + * Description: open a file from a normal stdio environment + * Arguments: gPDir - the dir where to open the file + * file - the name of the file to open + * flags - flags, as to stdio open() + * mode - mode, as to stdio open() + * Returns: handle, for use in functions accessing the opened file. + * If failed, errno is set and handle is -1. + */ +uio_Handle * +stdio_open(uio_PDirHandle *pDirHandle, const char *file, int flags, + mode_t mode) { + stdio_Handle *handle; + char *path; + int fd; + + path = joinPaths(stdio_getPath(pDirHandle->extra), file); + if (path == NULL) { + // errno is set + return NULL; + } + + fd = open(path, flags, mode); + if (fd == -1) { + int save_errno; + + save_errno = errno; + uio_free(path); + errno = save_errno; + return NULL; + } + uio_free(path); + +#if 0 + if (flags & O_CREAT) { + if (uio_GPDir_getGPDirEntry(pDirHandle->extra, file) == NULL) + stdio_addFile(pDirHandle->extra, file); + } +#endif + + handle = uio_malloc(sizeof (stdio_Handle)); + handle->fd = fd; + + return uio_Handle_new(pDirHandle->pRoot, handle, flags); +} + +ssize_t +stdio_read(uio_Handle *handle, void *buf, size_t count) { + return read(handle->native->fd, buf, count); +} + +int +stdio_rename(uio_PDirHandle *oldPDirHandle, const char *oldName, + uio_PDirHandle *newPDirHandle, const char *newName) { + char *newPath, *oldPath; + int result; + + oldPath = joinPaths(stdio_getPath(oldPDirHandle->extra), oldName); + if (oldPath == NULL) { + // errno is set + return -1; + } + + newPath = joinPaths(stdio_getPath(newPDirHandle->extra), newName); + if (newPath == NULL) { + // errno is set + uio_free(oldPath); + return -1; + } + + result = rename(oldPath, newPath); + if (result == -1) { + int savedErrno = errno; + uio_free(oldPath); + uio_free(newPath); + errno = savedErrno; + return -1; + } + + uio_free(oldPath); + uio_free(newPath); + + { + // update the GPDir structure + uio_GPDirEntry *entry; + + // TODO: add locking + entry = uio_GPDir_getGPDirEntry(oldPDirHandle->extra, oldName); + if (entry != NULL) { + uio_GPDirEntries_remove(oldPDirHandle->extra->entries, oldName); + uio_GPDirEntries_add(newPDirHandle->extra->entries, newName, + entry); + } + } + + return result; +} + +int +stdio_rmdir(uio_PDirHandle *pDirHandle, const char *name) { + char *path; + int result; + + path = joinPaths(stdio_getPath(pDirHandle->extra), name); + if (path == NULL) { + // errno is set + return -1; + } + + result = rmdir(path); + if (result == -1) { + int savedErrno = errno; + uio_free(path); + errno = savedErrno; + return -1; + } + + uio_free(path); + + uio_GPDir_removeSubDir(pDirHandle->extra, name); + + return result; +} + +off_t +stdio_seek(uio_Handle *handle, off_t offset, int whence) { + return lseek(handle->native->fd, offset, whence); +} + +ssize_t +stdio_write(uio_Handle *handle, const void *buf, size_t count) { + return write(handle->native->fd, buf, count); +} + +int +stdio_unlink(uio_PDirHandle *pDirHandle, const char *name) { + char *path; + int result; + + path = joinPaths(stdio_getPath(pDirHandle->extra), name); + if (path == NULL) { + // errno is set + return -1; + } + + result = unlink(path); + if (result == -1) { + int savedErrno = errno; + uio_free(path); + errno = savedErrno; + return -1; + } + + uio_free(path); + + uio_GPDir_removeFile(pDirHandle->extra, name); + + return result; +} + +uio_PDirEntryHandle * +stdio_getPDirEntryHandle(const uio_PDirHandle *pDirHandle, const char *name) { + uio_PDirEntryHandle *result; + const char *pathUpTo; + char *path; + struct stat statBuf; +#ifdef HAVE_DRIVE_LETTERS + char driveName[3]; +#endif /* HAVE_DRIVE_LETTERS */ + +#if defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) + if (pDirHandle->extra->extra->upDir == NULL) { + // Top dir. Contains only drive letters and UNC \\server\share + // parts. +#ifdef HAVE_DRIVE_LETTERS + if (isDriveLetter(name[0]) && name[1] == ':' && name[2] == '\0') { + driveName[0] = tolower(name[0]); + driveName[1] = ':'; + driveName[2] = '\0'; + name = driveName; + } else +#endif /* HAVE_DRIVE_LETTERS */ +#ifdef HAVE_UNC_PATHS + { + size_t uncLen; + + uncLen = uio_skipUNCServerShare(name); + if (name[uncLen] != '\0') { + // 'name' contains neither a drive letter, nor the + // first part of a UNC path. + return NULL; + } + } +#else /* !defined(HAVE_UNC_PATHS) */ + { + // Make sure that there is an 'else' case if HAVE_DRIVE_LETTERS + // is defined. + } +#endif /* HAVE_UNC_PATHS */ + } +#endif /* defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) */ + + result = uio_GPDir_getPDirEntryHandle(pDirHandle, name); + if (result != NULL) + return result; + +#if defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) + if (pDirHandle->extra->extra->upDir == NULL) { + // Need to create a 'directory' for the drive letter or UNC + // "\\server\share" part. + // It's no problem if we happen to create a dir for a non-existing + // drive. It should just produce an empty dir. + uio_GPDir *gPDir; + + gPDir = stdio_addDir(pDirHandle->extra, name); + uio_GPDir_ref(gPDir); + return (uio_PDirEntryHandle *) uio_PDirHandle_new( + pDirHandle->pRoot, gPDir); + } +#endif /* defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) */ + + pathUpTo = stdio_getPath(pDirHandle->extra); + if (pathUpTo == NULL) { + // errno is set + return NULL; + } + path = joinPaths(pathUpTo, name); + if (path == NULL) { + // errno is set + return NULL; + } + + if (stat(path, &statBuf) == -1) { +#ifdef __SYMBIAN32__ + // XXX: HACK: If we don't have access to a directory, we can still + // have access to the underlying entries. We don't actually know + // whether the entry is a directory, but I know of no way to find + // out. We just pretend that it is; worst case, a file which we can't + // access shows up as a directory which we can't access. + if (errno == EACCES) { + statBuf.st_mode = S_IFDIR; + // Fake a directory; the other fields of the stat + // structure are unused. + } else +#endif + { + // errno is set. + int savedErrno = errno; + uio_free(path); + errno = savedErrno; + return NULL; + } + } + uio_free(path); + + if (S_ISREG(statBuf.st_mode)) { + uio_GPFile *gPFile; + + gPFile = stdio_addFile(pDirHandle->extra, name); + uio_GPFile_ref(gPFile); + return (uio_PDirEntryHandle *) uio_PFileHandle_new( + pDirHandle->pRoot, gPFile); + } else if (S_ISDIR(statBuf.st_mode)) { + uio_GPDir *gPDir; + + gPDir = stdio_addDir(pDirHandle->extra, name); + uio_GPDir_ref(gPDir); + return (uio_PDirEntryHandle *) uio_PDirHandle_new( + pDirHandle->pRoot, gPDir); + } else { +#ifdef DEBUG + fprintf(stderr, "Warning: Attempt to access '%s' from '%s', " + "which is not a regular file, nor a directory.\n", name, + pathUpTo); +#endif + return NULL; + } +} + +uio_PRoot * +stdio_mount(uio_Handle *handle, int flags) { + uio_PRoot *result; + stdio_GPDirData *extra; + + assert (handle == NULL); + extra = stdio_GPDirData_new( + uio_strdup("") /* name */, +#if defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) + // Full paths start with a drive letter or \\server\share + uio_strdup("") /* cached path */, +#else + uio_strdup("/") /* cached path */, +#endif /* HAVE_DRIVE_LETTERS */ + NULL /* parent dir */); + + result = uio_GPRoot_makePRoot( + uio_getFileSystemHandler(uio_FSTYPE_STDIO), flags, + &stdio_GPRootOperations, NULL, uio_GPRoot_PERSISTENT, + handle, extra, 0); + + uio_GPDir_setComplete(result->rootDir->extra, true); + + return result; +} + +#ifdef WIN32 +stdio_EntriesIterator * +stdio_openEntries(uio_PDirHandle *pDirHandle) { + const char *dirPath; + char path[PATH_MAX]; + char *pathEnd; + size_t dirPathLen; + stdio_EntriesIterator *iterator; + +// uio_GPDir_access(pDirHandle->extra); + + dirPath = stdio_getPath(pDirHandle->extra); + if (dirPath == NULL) { + // errno is set + return NULL; + } + + dirPathLen = strlen(dirPath); + if (dirPathLen > PATH_MAX - 3) { + // dirPath ++ '/' ++ '*' ++ '\0' + errno = ENAMETOOLONG; + return NULL; + } + memcpy(path, dirPath, dirPathLen); + pathEnd = path + dirPathLen; + pathEnd[0] = '/'; + pathEnd[1] = '*'; + pathEnd[2] = '\0'; + iterator = stdio_EntriesIterator_new(0); + iterator->dirHandle = _findfirst(path, &iterator->findData); + if (iterator->dirHandle == 1) { + if (errno != ENOENT) { + stdio_EntriesIterator_delete(iterator); + return NULL; + } + iterator->status = 1; + } else + iterator->status = 0; + return iterator; +} +#endif + +#ifndef WIN32 +stdio_EntriesIterator * +stdio_openEntries(uio_PDirHandle *pDirHandle) { + const char *dirPath; + DIR *dirHandle; + stdio_EntriesIterator *result; + +// uio_GPDir_access(pDirHandle->extra); + + dirPath = stdio_getPath(pDirHandle->extra); + if (dirPath == NULL) { + // errno is set + return NULL; + } + + dirHandle = opendir(dirPath); + if (dirHandle == NULL) { + // errno is set; + return NULL; + } + + result = stdio_EntriesIterator_new(dirHandle); + result->status = readdir_r(dirHandle, result->direntBuffer, + &result->entry); +#ifndef WIN32 +# ifdef DEBUG + if (result->status != 0) { + fprintf(stderr, "Warning: readdir_r() failed: %s\n", + strerror(result->status)); + } +# endif +#endif + return result; +} +#endif + +// the start of 'buf' will be filled with pointers to strings +// those strings are stored elsewhere in buf. +// The function returns the number of strings passed along, or -1 for error. +// If there are no more entries, the last pointer will be NULL. +// (this pointer counts towards the return value) +int +stdio_readEntries(stdio_EntriesIterator **iteratorPtr, + char *buf, size_t len) { + char *end; + char **start; + int num; + const char *name; + size_t nameLen; + stdio_EntriesIterator *iterator; + + iterator = *iteratorPtr; + + // buf will be filled like this: + // The start of buf will contain pointers to char *, + // the end will contain the actual char[] that those pointers point to. + // The start and the end will grow towards eachother. + start = (char **) buf; + end = buf + len; + num = 0; +#ifdef WIN32 + for (; iterator->status == 0; + iterator->status = _findnext(iterator->dirHandle, + &iterator->findData)) +#else + for (; iterator->status == 0 && iterator->entry != NULL; + iterator->status = readdir_r(iterator->dirHandle, + iterator->direntBuffer, &iterator->entry)) +#endif + { +#ifdef WIN32 + name = iterator->findData.name; +#else + name = iterator->entry->d_name; +#endif + if (name[0] == '.' && + (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0'))) { + // skip directories "." and ".." + continue; + } + nameLen = strlen(name) + 1; + + // Does this work with systems that need memory access to be + // aligned on a certain number of bytes? + if ((size_t) (sizeof (char *) + nameLen) > + (size_t) (end - (char *) start)) { + // Not enough room to fit the pointer (at the beginning) and + // the string (at the end). + return num; + } + end -= nameLen; + memcpy(end, name, nameLen); + *start = end; + start++; + num++; + } +#ifndef WIN32 +# ifdef DEBUG + if (iterator->status != 0) { + fprintf(stderr, "Warning: readdir_r() failed: %s\n", + strerror(iterator->status)); + } +# endif +#endif + if (sizeof (char *) > (size_t) (end - (char *) start)) { + // not enough room to fit the NULL pointer. + // It will have to be reported seperately the next time. + return num; + } + *start = NULL; + num++; + return num; +} + +void +stdio_closeEntries(stdio_EntriesIterator *iterator) { +#ifdef WIN32 + _findclose(iterator->dirHandle); +#else + closedir(iterator->dirHandle); +#endif + stdio_EntriesIterator_delete(iterator); +} + +#ifdef WIN32 +stdio_EntriesIterator * +stdio_EntriesIterator_new(long dirHandle) { + stdio_EntriesIterator *result; + + result = stdio_EntriesIterator_alloc(); + result->dirHandle = dirHandle; + return result; +} +#else +stdio_EntriesIterator * +stdio_EntriesIterator_new(DIR *dirHandle) { + stdio_EntriesIterator *result; + size_t bufferSize; + + result = stdio_EntriesIterator_alloc(); + result->dirHandle = dirHandle; + + // Linux's and FreeBSD's struct dirent are defined with a + // maximum d_name field (NAME_MAX). + // However, POSIX doesn't require this, and in fact + // at least QNX defines struct dirent with an empty d_name field. + // Solaris defineds it with a d_name field of length 1. + // This should take care of it: + bufferSize = sizeof (struct dirent) + - sizeof (((struct dirent *) 0)->d_name) + (NAME_MAX + 1); + // Take the length of the dirent structure as it is defined, + // subtract the length of the d_name field, and add the length + // of the maximum length d_name field (NAME_MAX plus 1 for + // the '\0'). + // XXX: Could this give problems with weird alignments? + result->direntBuffer = uio_malloc(bufferSize); + return result; +} +#endif + +void +stdio_EntriesIterator_delete(stdio_EntriesIterator *iterator) { +#ifndef WIN32 + uio_free(iterator->direntBuffer); +#endif + stdio_EntriesIterator_free(iterator); +} + +static inline stdio_EntriesIterator * +stdio_EntriesIterator_alloc(void) { + return uio_malloc(sizeof (stdio_EntriesIterator)); +} + +static inline void +stdio_EntriesIterator_free(stdio_EntriesIterator *iterator) { + uio_free(iterator); +} + +static inline uio_GPFile * +stdio_addFile(uio_GPDir *gPDir, const char *fileName) { + uio_GPFile *file; + + file = uio_GPFile_new(gPDir->pRoot, NULL, + uio_gPFileFlagsFromPRootFlags(gPDir->pRoot->flags)); + uio_GPDir_addFile(gPDir, fileName, file); + return file; +} + +// called by fillGPDir when a subdir is found +static inline uio_GPDir * +stdio_addDir(uio_GPDir *gPDir, const char *dirName) { + uio_GPDir *subDir; + + subDir = uio_GPDir_prepareSubDir(gPDir, dirName); + if (subDir->extra == NULL) { + // It's a new dir, we'll need to add our own data. + uio_GPDir_ref(gPDir); + subDir->extra = stdio_GPDirData_new(uio_strdup(dirName), + NULL, gPDir); + uio_GPDir_setComplete(subDir, true); + // fillPDir should not be called. + } + uio_GPDir_commitSubDir(gPDir, dirName, subDir); + return subDir; +} + +// returns a pointer to gPDir->extra->cachedPath +// pointer should not be stored, the memory it points to can be freed +// lateron. TODO: not threadsafe. +static char * +stdio_getPath(uio_GPDir *gPDir) { + if (gPDir->extra->cachedPath == NULL) { + char *upPath; + size_t upPathLen, nameLen; + + if (gPDir->extra->upDir == NULL) { +#if defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) + // Drive letter or UNC \\server\share still needs to follow. + gPDir->extra->cachedPath = uio_malloc(1); + gPDir->extra->cachedPath[0] = '\0'; +#else + gPDir->extra->cachedPath = uio_malloc(2); + gPDir->extra->cachedPath[0] = '/'; + gPDir->extra->cachedPath[1] = '\0'; +#endif /* defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) */ + return gPDir->extra->cachedPath; + } + + upPath = stdio_getPath(gPDir->extra->upDir); + if (upPath == NULL) { + // errno is set + return NULL; + } + +#if defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) + if (upPath[0] == '\0') { + // The up dir is the root dir. Directly below the root dir are + // only dirs for drive letters and UNC \\share\server parts. + // No '/' needs to be attached. + gPDir->extra->cachedPath = uio_strdup(gPDir->extra->name); + return gPDir->extra->cachedPath; + } +#endif /* defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) */ + upPathLen = strlen(upPath); +#if !defined(HAVE_DRIVE_LETTERS) && !defined(HAVE_UNC_PATHS) + if (upPath[upPathLen - 1] == '/') { + // should only happen for "/" + upPathLen--; + } +#endif /* !defined(HAVE_DRIVE_LETTERS) && !defined(HAVE_UNC_PATHS) */ + nameLen = strlen(gPDir->extra->name); + if (upPathLen + nameLen + 1 >= PATH_MAX) { + errno = ENAMETOOLONG; + return NULL; + } + gPDir->extra->cachedPath = uio_malloc(upPathLen + nameLen + 2); + memcpy(gPDir->extra->cachedPath, upPath, upPathLen); + gPDir->extra->cachedPath[upPathLen] = '/'; + memcpy(gPDir->extra->cachedPath + upPathLen + 1, + gPDir->extra->name, nameLen); + gPDir->extra->cachedPath[upPathLen + nameLen + 1] = '\0'; + } + return gPDir->extra->cachedPath; +} + +static stdio_GPDirData * +stdio_GPDirData_new(char *name, char *cachedPath, uio_GPDir *upDir) { + stdio_GPDirData *result; + + result = stdio_GPDirData_alloc(); + result->name = name; + result->cachedPath = cachedPath; + result->upDir = upDir; + return result; +} + +static void +stdio_GPDirData_delete(stdio_GPDirData *gPDirData) { + if (gPDirData->upDir != NULL) + uio_GPDir_unref(gPDirData->upDir); + stdio_GPDirData_free(gPDirData); +} + +static inline stdio_GPDirData * +stdio_GPDirData_alloc(void) { + stdio_GPDirData *result; + + result = uio_malloc(sizeof (stdio_GPDirData)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(stdio_GPDirData, (void *) result); +#endif + return result; +} + +static inline void +stdio_GPDirData_free(stdio_GPDirData *gPDirData) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(stdio_GPDirData, (void *) gPDirData); +#endif + uio_free(gPDirData->name); + if (gPDirData->cachedPath != NULL) + uio_free(gPDirData->cachedPath); + uio_free(gPDirData); +} + + diff --git a/src/libs/uio/stdio/stdio.h b/src/libs/uio/stdio/stdio.h new file mode 100644 index 0000000..914a1d7 --- /dev/null +++ b/src/libs/uio/stdio/stdio.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +typedef struct stdio_Handle *uio_NativeHandle; +typedef void *uio_GPRootExtra; +typedef struct stdio_GPDirData *uio_GPDirExtra; +typedef void *uio_GPFileExtra; +typedef struct stdio_EntriesIterator stdio_EntriesIterator; +typedef stdio_EntriesIterator *uio_NativeEntriesContext; + + +#define uio_INTERNAL_PHYSICAL + +#include "../gphys.h" +#include "../iointrn.h" +#include "../uioport.h" +#include "../fstypes.h" +#include "../physical.h" + +#include +#ifndef WIN32 +# include +#endif + + +typedef struct stdio_GPDirData { + // The reason that names are stored is that in the system filesystem + // you need names to refer to files and directories. + // (you could keep a file descriptor to each one, but that would + // mean a lot of open file descriptors, and for some it won't even + // be enough). + // This is not needed for all filesystems; therefor this info is not + // in uio_GPDir itself. + // The reasons for including upDir here are similar. + char *name; + char *cachedPath; + uio_GPDir *upDir; +} stdio_GPDirData; + +typedef struct stdio_Handle { + int fd; +} stdio_Handle; + +#ifdef WIN32 +struct stdio_EntriesIterator { + long dirHandle; + struct _finddata_t findData; + int status; +}; +#endif + +#ifndef WIN32 +struct stdio_EntriesIterator { + DIR *dirHandle; + struct dirent *entry; + struct dirent *direntBuffer; + int status; +}; +#endif + + +uio_PRoot *stdio_mount(uio_Handle *handle, int flags); +int stdio_umount(uio_PRoot *); +uio_PDirHandle *stdio_mkdir(uio_PDirHandle *pDirHandle, const char *name, + mode_t mode); +uio_Handle *stdio_open(uio_PDirHandle *pDirHandle, const char *file, int flags, + mode_t mode); +void stdio_close(uio_Handle *handle); +int zip_access(uio_PDirHandle *pDirHandle, const char *name, int mode); +int stdio_access(uio_PDirHandle *pDirHandle, const char *name, int mode); +int stdio_fstat(uio_Handle *handle, struct stat *statBuf); +int stdio_stat(uio_PDirHandle *pDirHandle, const char *name, + struct stat *statBuf); +ssize_t stdio_read(uio_Handle *handle, void *buf, size_t count); +int stdio_rename(uio_PDirHandle *oldPDirHandle, const char *oldName, + uio_PDirHandle *newPDirHandle, const char *newName); +int stdio_rmdir(uio_PDirHandle *pDirHandle, const char *name); +off_t stdio_seek(uio_Handle *handle, off_t offset, int whence); +ssize_t stdio_write(uio_Handle *handle, const void *buf, size_t count); +int stdio_unlink(uio_PDirHandle *pDirHandle, const char *name); + +stdio_EntriesIterator *stdio_openEntries(uio_PDirHandle *pDirHandle); +int stdio_readEntries(stdio_EntriesIterator **iterator, + char *buf, size_t len); +void stdio_closeEntries(stdio_EntriesIterator *iterator); +#ifdef WIN32 +stdio_EntriesIterator *stdio_EntriesIterator_new(long dirHandle); +#else +stdio_EntriesIterator *stdio_EntriesIterator_new(DIR *dirHandle); +#endif +void stdio_EntriesIterator_delete(stdio_EntriesIterator *iterator); +uio_PDirEntryHandle *stdio_getPDirEntryHandle( + const uio_PDirHandle *pDirHandle, const char *name); + diff --git a/src/libs/uio/types.h b/src/libs/uio/types.h new file mode 100644 index 0000000..b92f7a4 --- /dev/null +++ b/src/libs/uio/types.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 _uio_TYPES_H +#define _uio_TYPES_H + +#include "config.h" + +// ISO C99 compatible boolean types. The ISO C99 standard defines: +// - An object declared as type _Bool, large enough to store the values 0 +// and 1, the rank of which is less than the rank of all other standard +// integer types. +// - A macro "bool", which expands to "_Bool". +// - A macro "true", which expands to the integer constant 1, suitable for +// use in #if preprocessing directives. +// - A macro "false", which expands to the integer constant 0, suitable for +// use in #if preprocessing directives. +// - A macro "__bool_true_false_are_defined", which expands to the integer +// constant 1, suitable for use in #if preprocessing directives. +#ifndef __bool_true_false_are_defined +#undef bool +#undef false +#undef true +#ifndef HAVE__BOOL +typedef unsigned char _Bool; +#endif /* HAVE_BOOL */ +#define bool _Bool +#define true 1 +#define false 0 +#define __bool_true_false_are_defined +#endif /* __bool_true_false_are_defined */ + +typedef bool uio_bool; + +typedef unsigned char uio_uint8; +typedef signed char uio_sint8; +typedef unsigned short uio_uint16; +typedef signed short uio_sint16; +typedef unsigned int uio_uint32; +typedef signed int uio_sint32; + +typedef unsigned long uio_uintptr; + // Needs to be adapted for 64 bits systems + +#endif /* _uio_TYPES_H */ + + diff --git a/src/libs/uio/uioport.h b/src/libs/uio/uioport.h new file mode 100644 index 0000000..69d7e8e --- /dev/null +++ b/src/libs/uio/uioport.h @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_UIOPORT_H_ +#define LIBS_UIO_UIOPORT_H_ + +#ifdef _MSC_VER +# include +#else +# include +#endif + + +// Compilation related +#ifndef inline +# ifdef _MSC_VER +# define inline __inline +# else +# define inline __inline__ +# endif +#endif + +// Paths +#ifdef WIN32 +# include +# define PATH_MAX _MAX_PATH +# define NAME_MAX _MAX_FNAME + // _MAX_DIR and FILENAME_MAX could also be candidates. + // If anyone can tell me which one matches NAME_MAX, please + // let me know. +#elif defined(_WIN32_WCE) +# include +#else +# include + /* PATH_MAX is per POSIX defined in , but: + * "A definition of one of the values from Table 2.6 shall bea + * omitted from on specific implementations where the + * corresponding value is equal to or greater than the + * stated minimum, but where the value can vary depending + * on the file to which it is applied. The actual value supported + * for a specific pathname shall be provided by the pathconf() + * function." + * _POSIX_NAME_MAX will provide a minimum (14). + * This is relevant (at least) for Solaris. + */ +# ifndef NAME_MAX +# define NAME_MAX _POSIX_NAME_MAX +# endif +#endif + +// Variations in path handling +#if defined(WIN32) || defined(__SYMBIAN32__) + // HAVE_DRIVE_LETTERS is defined to signify that DOS/Windows style drive + // letters are to be recognised on this platform. +# define HAVE_DRIVE_LETTERS + // BACKSLASH_IS_PATH_SEPARATOR is defined to signify that the backslash + // character is to be recognised as a path separator on this platform. + // This does not affect the acceptance of forward slashes as path + // separators. +# define BACKSLASH_IS_PATH_SEPARATOR +#endif +#if defined(WIN32) + // HAVE_UNC_PATHS is defined to signify that Universal Naming Convention + // style paths are to be recognised on this platform. +# define HAVE_UNC_PATHS +#endif + +// User ids +#ifdef WIN32 +typedef short uid_t; +typedef short gid_t; +#endif + +// Some types +#ifdef _MSC_VER +typedef int ssize_t; +typedef unsigned short mode_t; +#endif + +// Directories +#include +#ifdef WIN32 +# ifdef _MSC_VER +# define MKDIR(name, mode) ((void) mode, _mkdir(name)) +# else +# define MKDIR(name, mode) ((void) mode, mkdir(name)) +# endif +#else +# define MKDIR mkdir +#endif +#ifdef _MSC_VER +# include +# define chdir _chdir +# define getcwd _getcwd +# define chdir _chdir +# define getcwd _getcwd +# define access _access +# define F_OK 0 +# define W_OK 2 +# define R_OK 4 +# define open _open +# define read _read +# define rmdir _rmdir +# define lseek _lseek +# define lstat _lstat +# define fstat _fstat +# define S_IRUSR S_IREAD +# define S_IWUSR S_IWRITE +# define S_IXUSR S_IEXEC +# define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) +# define S_IRGRP 0 +# define S_IWGRP 0 +# define S_IXGRP 0 +# define S_IROTH 0 +# define S_IWOTH 0 +# define S_IXOTH 0 +# define S_IRWXG 0 +# define S_IRWXO 0 +# define S_ISUID 0 +# define S_ISGID 0 +# define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR) +# define S_IFMT _S_IFMT +# define S_IFREG _S_IFREG +# define S_IFCHR _S_IFCHR +# define S_IFDIR _S_IFDIR +# define S_ISDIR(mode) (((mode) & _S_IFMT) == _S_IFDIR) +# define S_ISREG(mode) (((mode) & _S_IFMT) == _S_IFREG) +# define write _write +# define stat _stat +# define unlink _unlink +#elif defined (__MINGW32__) +# define S_IRGRP 0 +# define S_IWGRP 0 +# define S_IXGRP 0 +# define S_IROTH 0 +# define S_IWOTH 0 +# define S_IXOTH 0 +# define S_IRWXG 0 +# define S_IRWXO 0 +# define S_ISUID 0 +# define S_ISGID 0 +# define S_IFMT _S_IFMT +# define S_IFREG _S_IFREG +# define S_IFCHR _S_IFCHR +# define S_IFDIR _S_IFDIR +#endif +#ifdef __SYMBIAN32__ + // TODO: Symbian doesn't have readdir_r(). If uio is to be usable + // outside of uqm (which defines its own backup readdir_r()), an + // implementation of that function needs to be added to uio. +# include + int readdir_r (DIR *dirp, struct dirent *entry, struct dirent **result); +#endif + +#endif /* LIBS_UIO_UIOPORT_H_ */ + diff --git a/src/libs/uio/uiostream.c b/src/libs/uio/uiostream.c new file mode 100644 index 0000000..eb101a5 --- /dev/null +++ b/src/libs/uio/uiostream.c @@ -0,0 +1,603 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "uioport.h" +#include "iointrn.h" +#include "uiostream.h" + +#include +#include +#include + +#include "uioutils.h" +#include "utils.h" +#ifdef uio_MEM_DEBUG +# include "memdebug.h" +#endif + +#define uio_Stream_BLOCK_SIZE 1024 + +static inline uio_Stream *uio_Stream_new(uio_Handle *handle, int openFlags); +static inline void uio_Stream_delete(uio_Stream *stream); +static inline uio_Stream *uio_Stream_alloc(void); +static inline void uio_Stream_free(uio_Stream *stream); +#ifdef NDEBUG +# define uio_assertReadSanity(stream) +# define uio_assertWriteSanity(stream) +#else +static void uio_assertReadSanity(uio_Stream *stream); +static void uio_assertWriteSanity(uio_Stream *stream); +#endif +static int uio_Stream_fillReadBuffer(uio_Stream *stream); +static int uio_Stream_flushWriteBuffer(uio_Stream *stream); +static void uio_Stream_discardReadBuffer(uio_Stream *stream); + + +uio_Stream * +uio_fopen(uio_DirHandle *dir, const char *path, const char *mode) { + int openFlags; + uio_Handle *handle; + uio_Stream *stream; + int i; + + switch (*mode) { + case 'r': + openFlags = O_RDONLY; + break; + case 'w': + openFlags = O_WRONLY | O_CREAT | O_TRUNC; + break; + case 'a': + openFlags = O_WRONLY| O_CREAT | O_APPEND; + default: + errno = EINVAL; + fprintf(stderr, "Invalid mode string in call to uio_fopen().\n"); + return NULL; + } + mode++; + + // C'89 says 'b' may either be the second or the third character. + // If someone specifies both 'b' and 't', he/she is out of luck. + i = 2; + while (i-- && (*mode != '\0')) { + switch (*mode) { + case 'b': +#ifdef WIN32 + openFlags |= O_BINARY; +#endif + break; + case 't': +#ifdef WIN32 + openFlags |= O_TEXT; +#endif + break; + case '+': + openFlags = (openFlags & ~O_ACCMODE) | O_RDWR; + break; + default: + i = 0; + // leave the while loop + break; + } + mode++; + } + + // Any characters in the mode string that might follow are ignored. + + handle = uio_open(dir, path, openFlags, S_IRUSR | S_IWUSR | + S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (handle == NULL) { + // errno is set + return NULL; + } + + stream = uio_Stream_new(handle, openFlags); + return stream; +} + +int +uio_fclose(uio_Stream *stream) { + if (stream->operation == uio_StreamOperation_write) + uio_Stream_flushWriteBuffer(stream); + uio_close(stream->handle); + uio_Stream_delete(stream); + return 0; +} + +// "The file position indicator for the stream (if defined) is advanced by +// the number of characters successfully read. If an error occurs, the +// resulting value of the file position indicator for the stream is +// indeterminate. If a partial element is read, its value is +// indeterminate." (from POSIX for fread()). +size_t +uio_fread(void *buf, size_t size, size_t nmemb, uio_Stream *stream) { + size_t bytesToRead; + size_t bytesRead; + + bytesToRead = size * nmemb; + bytesRead = 0; + + uio_assertReadSanity(stream); + stream->operation = uio_StreamOperation_read; + + if (stream->dataEnd > stream->dataStart) { + // First use what's in the buffer. + size_t numRead; + + numRead = minu(stream->dataEnd - stream->dataStart, bytesToRead); + memcpy(buf, stream->dataStart, numRead); + buf = (void *) ((char *) buf + numRead); + stream->dataStart += numRead; + bytesToRead -= numRead; + bytesRead += numRead; + } + if (bytesToRead == 0) { + // Done already + return nmemb; + } + + { + // Read the rest directly into the caller's buffer. + ssize_t numRead; + numRead = uio_read(stream->handle, buf, bytesToRead); + if (numRead == -1) { + stream->status = uio_Stream_STATUS_ERROR; + goto out; + } + bytesRead += numRead; + if ((size_t) numRead < bytesToRead) { + // End of file + stream->status = uio_Stream_STATUS_EOF; + stream->operation = uio_StreamOperation_none; + goto out; + } + } + +out: + if (bytesToRead == 0) + return nmemb; + return bytesRead / size; +} + +char * +uio_fgets(char *s, int size, uio_Stream *stream) { + int orgSize; + char *buf; + + uio_assertReadSanity(stream); + stream->operation = uio_StreamOperation_read; + + size--; + orgSize = size; + buf = s; + while (size > 0) { + size_t maxRead; + const char *newLinePos; + + // Fill buffer if empty. + if (stream->dataStart == stream->dataEnd) { + if (uio_Stream_fillReadBuffer(stream) == -1) { + // errno is set + stream->status = uio_Stream_STATUS_ERROR; + return NULL; + } + if (stream->dataStart == stream->dataEnd) { + // End-of-file + stream->status = uio_Stream_STATUS_EOF; + stream->operation = uio_StreamOperation_none; + if (size == orgSize) { + // Nothing was read. + return NULL; + } + break; + } + } + + // Search in buffer + maxRead = minu(stream->dataEnd - stream->dataStart, size); + newLinePos = memchr(stream->dataStart, '\n', maxRead); + if (newLinePos != NULL) { + // Newline found. + maxRead = newLinePos + 1 - stream->dataStart; + memcpy(buf, stream->dataStart, maxRead); + stream->dataStart += maxRead; + buf[maxRead] = '\0'; + return buf; + } + // No newline present. + memcpy(buf, stream->dataStart, maxRead); + stream->dataStart += maxRead; + buf += maxRead; + size -= maxRead; + } + + *buf = '\0'; + return s; +} + +int +uio_fgetc(uio_Stream *stream) { + int result; + + uio_assertReadSanity(stream); + stream->operation = uio_StreamOperation_read; + + if (stream->dataStart == stream->dataEnd) { + // Buffer is empty + if (uio_Stream_fillReadBuffer(stream) == -1) { + stream->status = uio_Stream_STATUS_ERROR; + return (int) EOF; + } + if (stream->dataStart == stream->dataEnd) { + // End-of-file + stream->status = uio_Stream_STATUS_EOF; + stream->operation = uio_StreamOperation_none; + return (int) EOF; + } + } + + result = (int) *((unsigned char *) stream->dataStart); + stream->dataStart++; + return result; +} + +// Only one character pushback is guaranteed, just like with stdio ungetc(). +int +uio_ungetc(int c, uio_Stream *stream) { + assert((stream->openFlags & O_ACCMODE) != O_WRONLY); + assert(c >= 0 && c <= 255); + + return (int) EOF; + // not implemented +// return c; +} + +// NB. POSIX allows errno to be set for vsprintf(), but does not require it: +// "The value of errno may be set to nonzero by a library function call +// whether or not there is an error, provided the use of errno is not +// documented in the description of the function in this International +// Standard." The latter is the case for vsprintf(). +int +uio_vfprintf(uio_Stream *stream, const char *format, va_list args) { + // This could be done faster, but going through snprintf() is easiest, + // and is fast enough for now. + char *buf; + int putResult; + int savedErrno; + + buf = uio_vasprintf(format, args); + if (buf == NULL) { + // errno may or may not be set + return -1; + } + + putResult = uio_fputs(buf, stream); + savedErrno = errno; + + uio_free(buf); + + errno = savedErrno; + return putResult; +} + +int +uio_fprintf(uio_Stream *stream, const char *format, ...) { + va_list args; + int result; + + va_start(args, format); + result = uio_vfprintf(stream, format, args); + va_end(args); + + return result; +} + +int +uio_fputc(int c, uio_Stream *stream) { + assert((stream->openFlags & O_ACCMODE) != O_RDONLY); + assert(c >= 0 && c <= 255); + + uio_assertWriteSanity(stream); + stream->operation = uio_StreamOperation_write; + + if (stream->dataEnd == stream->bufEnd) { + // The buffer is full. Flush it out. + if (uio_Stream_flushWriteBuffer(stream) == -1) { + // errno is set + // Error status (for ferror()) is set. + return EOF; + } + } + + *(unsigned char *) stream->dataEnd = (unsigned char) c; + stream->dataEnd++; + return c; +} + +int +uio_fputs(const char *s, uio_Stream *stream) { + int result; + + result = uio_fwrite(s, strlen(s), 1, stream); + if (result != 1) + return EOF; + return 0; +} + +int +uio_fseek(uio_Stream *stream, long offset, int whence) { + int newPos; + + if (stream->operation == uio_StreamOperation_read) { + uio_Stream_discardReadBuffer(stream); + } else if (stream->operation == uio_StreamOperation_write) { + if (uio_Stream_flushWriteBuffer(stream) == -1) { + // errno is set + return -1; + } + } + assert(stream->dataStart == stream->buf); + assert(stream->dataEnd == stream->buf); + stream->operation = uio_StreamOperation_none; + + newPos = uio_lseek(stream->handle, offset, whence); + if (newPos == -1) { + // errno is set + return -1; + } + stream->status = uio_Stream_STATUS_OK; + // Clear error or end-of-file flag. + + return 0; +} + +long +uio_ftell(uio_Stream *stream) { + off_t newPos; + + newPos = uio_lseek(stream->handle, 0, SEEK_CUR); + if (newPos == (off_t) -1) { + // errno is set + return (long) -1; + } + + if (stream->operation == uio_StreamOperation_write) { + newPos += stream->dataEnd - stream->dataStart; + } else if (stream->operation == uio_StreamOperation_read) { + newPos -= stream->dataEnd - stream->dataStart; + } + + return (long) newPos; +} + +// If less that nmemb elements could be written, or an error occurs, the +// file pointer is undefined. clearerr() followed by fseek() need to be +// called before attempting to read or write again. +// I don't have the C standard myself, but I suspect this is the official +// behaviour for fread() and fwrite(). +size_t +uio_fwrite(const void *buf, size_t size, size_t nmemb, uio_Stream *stream) { + ssize_t bytesToWrite; + ssize_t bytesWritten; + + uio_assertWriteSanity(stream); + stream->operation = uio_StreamOperation_write; + + // NB. If a file is opened in append mode, the file position indicator + // is moved to the end of the file before writing. + // We leave that up to the physical layer. + + bytesToWrite = size * nmemb; + if (bytesToWrite < stream->bufEnd - stream->dataEnd) { + // There's enough space in the write buffer to store everything. + memcpy(stream->dataEnd, buf, bytesToWrite); + stream->dataEnd += bytesToWrite; + return nmemb; + } + + // Not enough space in the write buffer to write everything. + // Flush what's left in the write buffer first. + if (uio_Stream_flushWriteBuffer(stream) == -1) { + // errno is set + // Error status (for ferror()) is set. + return 0; + } + + if (bytesToWrite < stream->bufEnd - stream->dataEnd) { + // The now empty write buffer is large enough to store everything. + memcpy(stream->dataEnd, buf, bytesToWrite); + stream->dataEnd += bytesToWrite; + return nmemb; + } + + // There is more data to write than fits in the (empty) write buffer. + // The data is written directly, in its entirety, without going + // through the write buffer. + bytesWritten = uio_write(stream->handle, buf, bytesToWrite); + if (bytesWritten != bytesToWrite) { + stream->status = uio_Stream_STATUS_ERROR; + if (bytesWritten == -1) + return 0; + } + + if (bytesWritten == bytesToWrite) + return nmemb; + return (size_t) bytesWritten / size; +} + +// NB: stdio fflush() accepts NULL to flush all streams. uio_flush() does +// not. +int +uio_fflush(uio_Stream *stream) { + assert(stream != NULL); + + if (stream->operation == uio_StreamOperation_write) { + if (uio_Stream_flushWriteBuffer(stream) == -1) { + // errno is set + return (int) EOF; + } + stream->operation = uio_StreamOperation_none; + } + + return 0; +} + +int +uio_feof(uio_Stream *stream) { + return stream->status == uio_Stream_STATUS_EOF; +} + +int +uio_ferror(uio_Stream *stream) { + return stream->status == uio_Stream_STATUS_ERROR; +} + +void +uio_clearerr(uio_Stream *stream) { + stream->status = uio_Stream_STATUS_OK; +} + +// Counterpart of fileno() +uio_Handle * +uio_streamHandle(uio_Stream *stream) { + return stream->handle; +} + +#ifndef NDEBUG +static void +uio_assertReadSanity(uio_Stream *stream) { + assert((stream->openFlags & O_ACCMODE) != O_WRONLY); + + if (stream->operation == uio_StreamOperation_write) { + // "[...] output shall not be directly followed by input without an + // intervening call to the fflush function or to a file positioning + // function (fseek, fsetpos, or rewind), and input shall not be + // directly followed by output without an intervening call to a file + // positioning function, unless the input operation encounters + // end-of-file." (POSIX, C) + fprintf(stderr, "Error: Reading on a file directly after writing, " + "without an intervening call to fflush() or a file " + "positioning function.\n"); + abort(); + } +} +#endif + +#ifndef NDEBUG +static void +uio_assertWriteSanity(uio_Stream *stream) { + assert((stream->openFlags & O_ACCMODE) != O_RDONLY); + + if (stream->operation == uio_StreamOperation_read) { + // "[...] output shall not be directly followed by input without an + // intervening call to the fflush function or to a file positioning + // function (fseek, fsetpos, or rewind), and input shall not be + // directly followed by output without an intervening call to a file + // positioning function, unless the input operation encounters + // end-of-file." (POSIX, C) + fprintf(stderr, "Error: Writing on a file directly after reading, " + "without an intervening call to a file positioning " + "function.\n"); + abort(); + } + assert(stream->dataStart == stream->buf); +} +#endif + +static int +uio_Stream_flushWriteBuffer(uio_Stream *stream) { + ssize_t bytesWritten; + + assert(stream->operation == uio_StreamOperation_write); + + bytesWritten = uio_write(stream->handle, stream->dataStart, + stream->dataEnd - stream->dataStart); + if (bytesWritten != stream->dataEnd - stream->dataStart) { + stream->status = uio_Stream_STATUS_ERROR; + return -1; + } + assert(stream->dataStart == stream->buf); + stream->dataEnd = stream->buf; + + return 0; +} + +static void +uio_Stream_discardReadBuffer(uio_Stream *stream) { + assert(stream->operation == uio_StreamOperation_read); + stream->dataStart = stream->buf; + stream->dataEnd = stream->buf; + // TODO: when implementing pushback: throw away pushback buffer. +} + +static int +uio_Stream_fillReadBuffer(uio_Stream *stream) { + ssize_t numRead; + + assert(stream->operation == uio_StreamOperation_read); + + numRead = uio_read(stream->handle, stream->buf, + uio_Stream_BLOCK_SIZE); + if (numRead == -1) + return -1; + stream->dataStart = stream->buf; + stream->dataEnd = stream->buf + numRead; + return 0; +} + +static inline uio_Stream * +uio_Stream_new(uio_Handle *handle, int openFlags) { + uio_Stream *result; + + result = uio_Stream_alloc(); + result->handle = handle; + result->openFlags = openFlags; + result->status = uio_Stream_STATUS_OK; + result->operation = uio_StreamOperation_none; + result->buf = uio_malloc(uio_Stream_BLOCK_SIZE); + result->dataStart = result->buf; + result->dataEnd = result->buf; + result->bufEnd = result->buf + uio_Stream_BLOCK_SIZE; + return result; +} + +static inline uio_Stream * +uio_Stream_alloc(void) { + uio_Stream *result = uio_malloc(sizeof (uio_Stream)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(uio_Stream, (void *) result); +#endif + return result; +} + +static inline void +uio_Stream_delete(uio_Stream *stream) { + uio_free(stream->buf); + uio_Stream_free(stream); +} + +static inline void +uio_Stream_free(uio_Stream *stream) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(uio_Stream, (void *) stream); +#endif + uio_free(stream); +} + diff --git a/src/libs/uio/uiostream.h b/src/libs/uio/uiostream.h new file mode 100644 index 0000000..f65487e --- /dev/null +++ b/src/libs/uio/uiostream.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_UIOSTREAM_H_ +#define LIBS_UIO_UIOSTREAM_H_ + + +typedef struct uio_Stream uio_Stream; + +#include "io.h" + +#include + + +uio_Stream *uio_fopen(uio_DirHandle *dir, const char *path, const char *mode); +int uio_fclose(uio_Stream *stream); +size_t uio_fread(void *buf, size_t size, size_t nmemb, uio_Stream *stream); +char *uio_fgets(char *buf, int size, uio_Stream *stream); +int uio_fgetc(uio_Stream *stream); +#define uio_getc uio_fgetc +int uio_ungetc(int c, uio_Stream *stream); +int uio_vfprintf(uio_Stream *stream, const char *format, va_list args); +int uio_fprintf(uio_Stream *stream, const char *format, ...); +int uio_fputc(int c, uio_Stream *stream); +#define uio_putc uio_fputc +int uio_fputs(const char *s, uio_Stream *stream); +int uio_fseek(uio_Stream *stream, long offset, int whence); +long uio_ftell(uio_Stream *stream); +size_t uio_fwrite(const void *buf, size_t size, size_t nmemb, + uio_Stream *stream); +int uio_fflush(uio_Stream *stream); +int uio_feof(uio_Stream *stream); +int uio_ferror(uio_Stream *stream); +void uio_clearerr(uio_Stream *stream); +uio_Handle *uio_streamHandle(uio_Stream *stream); + + +/* *** Internal definitions follow *** */ +#ifdef uio_INTERNAL + +#include +#include +#include "iointrn.h" + +typedef enum { + uio_StreamOperation_none, + uio_StreamOperation_read, + uio_StreamOperation_write +} uio_StreamOperation; + +struct uio_Stream { + char *buf; + // Start of the buffer. + char *dataStart; + // Start of the part of the buffer that is in use. + char *dataEnd; + // Start of the unused part of the buffer. + char *bufEnd; + // End of the buffer. + // INV: buf <= dataStart <= dataEnd <= bufEnd + // INV: if 'operation == uio_StreamOperation_write' then buf == dataStart + + uio_Handle *handle; + int status; +#define uio_Stream_STATUS_OK 0 +#define uio_Stream_STATUS_EOF 1 +#define uio_Stream_STATUS_ERROR 2 + uio_StreamOperation operation; + // What was the last action (reading or writing). This + // determines whether the buffer is a read or write buffer. + int openFlags; + // Flags used for opening the file. +}; + + +#endif /* uio_INTERNAL */ + +#endif /* LIBS_UIO_UIOSTREAM_H_ */ + + diff --git a/src/libs/uio/uioutils.c b/src/libs/uio/uioutils.c new file mode 100644 index 0000000..fbe3cd4 --- /dev/null +++ b/src/libs/uio/uioutils.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include + +#include "uioutils.h" +#include "mem.h" +#include "paths.h" +#include "uioport.h" + +/** + * Concatenate two strings into a newly allocated buffer. + * + * @param[in] first The first (left) string, '\0' terminated. + * @param[in] second The second (right) string, '\0' terminated. + * + * @returns A newly allocated string consisting of the concatenation of + * 'first' and 'second', to be freed using uio_free(). + */ +char * +strcata(const char *first, const char *second) { + char *result, *resPtr; + size_t firstLen, secondLen; + + firstLen = strlen(first); + secondLen = strlen(second); + result = uio_malloc(firstLen + secondLen + 1); + resPtr = result; + + memcpy(resPtr, first, firstLen); + resPtr += firstLen; + + memcpy(resPtr, second, secondLen); + resPtr += secondLen; + + *resPtr = '\0'; + return result; +} + +// returns a copy of a generic array 'array' with 'element' inserted in +// position 'insertPos' +void * +insertArray(const void *array, size_t oldNumElements, int insertPos, + const void *element, size_t elementSize) { + void *newArray, *newArrayPtr; + const void *arrayPtr; + size_t preInsertSize; + + newArray = uio_malloc((oldNumElements + 1) * elementSize); + preInsertSize = insertPos * elementSize; + memcpy(newArray, array, preInsertSize); + newArrayPtr = (char *) newArray + preInsertSize; + arrayPtr = (const char *) array + preInsertSize; + memcpy(newArrayPtr, element, elementSize); + newArrayPtr = (char *) newArrayPtr + elementSize; + memcpy(newArrayPtr, arrayPtr, + (oldNumElements - insertPos) * elementSize); + return newArray; +} + +// returns a copy of a pointer array 'array' with 'element' inserted in +// position 'insertPos' +void ** +insertArrayPointer(const void **array, size_t oldNumElements, int insertPos, + const void *element) { + void **newArray, **newArrayPtr; + const void **arrayPtr; + size_t preInsertSize; + + newArray = uio_malloc((oldNumElements + 1) * sizeof (void *)); + preInsertSize = insertPos * sizeof (void *); + memcpy(newArray, array, preInsertSize); + newArrayPtr = newArray + insertPos; + arrayPtr = array + insertPos; + *newArrayPtr = unconst(element); + newArrayPtr++; + memcpy(newArrayPtr, arrayPtr, + (oldNumElements - insertPos) * sizeof (void *)); + return newArray; +} + +// returns a copy of a generic array 'array' with 'numExclude' elements, +// starting from startpos, removed. +void * +excludeArray(const void *array, size_t oldNumElements, int startPos, + int numExclude, size_t elementSize) { + void *newArray, *newArrayPtr; + const void *arrayPtr; + size_t preExcludeSize; + + newArray = uio_malloc((oldNumElements - numExclude) * elementSize); + preExcludeSize = startPos * elementSize; + memcpy(newArray, array, preExcludeSize); + newArrayPtr = (char *) newArray + preExcludeSize; + arrayPtr = (const char *) array + + (startPos + numExclude) * sizeof (elementSize); + memcpy(newArrayPtr, arrayPtr, + (oldNumElements - startPos - numExclude) * elementSize); + return newArray; +} + +// returns a copy of a pointer array 'array' with 'numExclude' elements, +// starting from startpos, removed. +void ** +excludeArrayPointer(const void **array, size_t oldNumElements, int startPos, + int numExclude) { + void **newArray; + + newArray = uio_malloc((oldNumElements - numExclude) * sizeof (void *)); + memcpy(newArray, array, startPos * sizeof (void *)); + memcpy(&newArray[startPos], &array[startPos + numExclude], + (oldNumElements - startPos - numExclude) * sizeof (void *)); + return newArray; +} + +// If the given DOS date/time is invalid, the result is unspecified, +// but the function won't crash. +time_t +dosToUnixTime(uio_uint16 date, uio_uint16 tm) { + // DOS date has the following format: + // bits 0-4 specify the number of the day in the month (1-31). + // bits 5-8 specify the number of the month in the year (1-12). + // bits 9-15 specify the year number since 1980 (0-127) + // DOS time has the fillowing format: + // bits 0-4 specify the number of seconds/2 in the minute (0-29) + // (only accurate on 2 seconds) + // bits 5-10 specify the number of minutes in the hour (0-59) + // bits 11-15 specify the number of hours since midnight (0-23) + + int year, month, day; + int hours, minutes, seconds; + long result; + + static const int daysUntilMonth[] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, + 334, 334, 334, 334 }; + // The last 4 entries are there so that there's no + // invalid memory access if the date is invalid. + + year = date >> 9; + month = ((date >> 5) - 1) & 0x0f; // Number in [0..15] + day = (date - 1) & 0x1f; // Number in [0..31] + hours = tm >> 11; + minutes = (tm >> 5) & 0x3f; + seconds = (tm & 0x1f) * 2; // Even number in [0..62] + + result = year * 365 + daysUntilMonth[month] + day; + // Count the (non-leap) days in all those years + + // Add a leapday for each 4th year + if (year % 4 == 0 && month <= 2) { + // The given date is a leap-year but the leapday hasn't occured yet. + result += year / 4; + } else { + result += 1 + year / 4; + } + // result now is the number of days between 1980-01-01 and the given day. + + // Add the days between 1970-01-01 and 1980-01-01 + // (2 leapdays in this period) + result += 365 * 10 + 2; + + result = (result * 24) + hours; // days to hours + result = (result * 60) + minutes; // hours to minutes + result = (result * 60) + seconds; // minutes to seconds + + return (time_t) result; +} + +char * +dosToUnixPath(const char *path) { + const char *srcPtr; + char *result, *dstPtr; + size_t skip; + + result = uio_malloc(strlen(path) + 1); + srcPtr = path; + dstPtr = result; + + // A UNC path will look like this: "\\server\share/..."; the first two + // characters will be backslashes, and the separator between the server + // and the share too. The rest will be slashes. + // The goal is that at every forward slash, the path should be + // stat()'able. + skip = uio_skipUNCServerShare(srcPtr); + if (skip != 0) { + char *slash; + memcpy(dstPtr, srcPtr, skip); + + slash = memchr(srcPtr + 2, '/', skip - 2); + if (slash != NULL) + *slash = '\\'; + + srcPtr += skip; + dstPtr += skip; + } + + while (*srcPtr != '\0') { + if (*srcPtr == '\\') { + *dstPtr = '/'; + } else + *dstPtr = *srcPtr; + srcPtr++; + dstPtr++; + } + *dstPtr = '\0'; + return result; +} + + diff --git a/src/libs/uio/uioutils.h b/src/libs/uio/uioutils.h new file mode 100644 index 0000000..6e04843 --- /dev/null +++ b/src/libs/uio/uioutils.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_UIOUTILS_H_ +#define LIBS_UIO_UIOUTILS_H_ + +#include + +#include "types.h" +#include "uioport.h" + +char *strcata(const char *first, const char *second); +void *insertArray(const void *array, size_t oldNumElements, int insertPos, + const void *element, size_t elementSize); +void **insertArrayPointer(const void **array, size_t oldNumElements, + int insertPos, const void *element); +void *excludeArray(const void *array, size_t oldNumElements, int startPos, + int numExclude, size_t elementSize); +void **excludeArrayPointer(const void **array, size_t oldNumElements, + int startPos, int numExclude); +time_t dosToUnixTime(uio_uint16 date, uio_uint16 tm); +char *dosToUnixPath(const char *path); + +/* Sometimes you just have to remove a 'const'. + * (for instance, when implementing a function like strchr) + */ +static inline void * +unconst(const void *arg) { + union { + void *c; + const void *cc; + } u; + u.cc = arg; + return u.c; +} + +// byte1 is the lowest byte, byte4 the highest +static inline uio_uint32 +makeUInt32(uio_uint8 byte1, uio_uint8 byte2, uio_uint8 byte3, uio_uint8 byte4) { + return byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24); +} + +static inline uio_uint16 +makeUInt16(uio_uint8 byte1, uio_uint8 byte2) { + return byte1 | (byte2 << 8); +} + +static inline uio_sint32 +makeSInt32(uio_uint8 byte1, uio_uint8 byte2, uio_uint8 byte3, uio_uint8 byte4) { + return byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24); +} + +static inline uio_sint16 +makeSInt16(uio_uint8 byte1, uio_uint8 byte2) { + return byte1 | (byte2 << 8); +} + +static inline uio_bool +isBitSet(uio_uint32 bitField, int bit) { + return ((bitField >> bit) & 1) == 1; +} + +static inline int +mins(int i1, int i2) { + return i1 <= i2 ? i1 : i2; +} + +static inline unsigned int +minu(unsigned int i1, unsigned int i2) { + return i1 <= i2 ? i1 : i2; +} + + +#endif /* LIBS_UIO_UIOUTILS_H_ */ + diff --git a/src/libs/uio/utils.c b/src/libs/uio/utils.c new file mode 100644 index 0000000..1f705bb --- /dev/null +++ b/src/libs/uio/utils.c @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#include +#ifdef _MSC_VER +# include +#endif /* _MSC_VER */ + +#include "iointrn.h" +#include "ioaux.h" +#include "utils.h" + +static int uio_copyError(uio_Handle *srcHandle, uio_Handle *dstHandle, + uio_DirHandle *unlinkHandle, const char *unlinkPath, uio_uint8 *buf); + +struct uio_StdioAccessHandle { + uio_DirHandle *tempRoot; + char *tempDirName; + uio_DirHandle *tempDir; + char *fileName; + char *stdioPath; +}; + +static inline uio_StdioAccessHandle *uio_StdioAccessHandle_new( + uio_DirHandle *tempRoot, char *tempDirName, + uio_DirHandle *tempDir, char *fileName, + char *stdioPath); +static inline void uio_StdioAccessHandle_delete( + uio_StdioAccessHandle *handle); +static inline uio_StdioAccessHandle *uio_StdioAccessHandle_alloc(void); +static inline void uio_StdioAccessHandle_free(uio_StdioAccessHandle *handle); + +/* + * Copy a file with path srcName to a file with name newName. + * If the destination already exists, the operation fails. + * Links are followed. + * Special files (fifos, char devices, block devices, etc) will be + * read as long as there is data available and the destination will be + * a regular file with that data. + * The new file will have the same permissions as the old. + * If an error occurs during copying, an attempt will be made to + * remove the copy. + */ +int +uio_copyFile(uio_DirHandle *srcDir, const char *srcName, + uio_DirHandle *dstDir, const char *newName) { + uio_Handle *src, *dst; + struct stat sb; +#define BUFSIZE 65536 + uio_uint8 *buf, *bufPtr; + ssize_t numInBuf, numWritten; + + src = uio_open(srcDir, srcName, O_RDONLY +#ifdef WIN32 + | O_BINARY +#endif + , 0); + if (src == NULL) + return -1; + + if (uio_fstat(src, &sb) == -1) + return uio_copyError(src, NULL, NULL, NULL, NULL); + + dst = uio_open(dstDir, newName, O_WRONLY | O_CREAT | O_EXCL +#ifdef WIN32 + | O_BINARY +#endif + , sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + if (dst == NULL) + return uio_copyError(src, NULL, NULL, NULL, NULL); + + buf = uio_malloc(BUFSIZE); + // This was originally a statically allocated buffer, + // but as this function might be run from a thread with + // a small Stack, this is better. + while (1) { + numInBuf = uio_read(src, buf, BUFSIZE); + if (numInBuf == -1) { + if (errno == EINTR) + continue; + return uio_copyError(src, dst, dstDir, newName, buf); + } + if (numInBuf == 0) + break; + + bufPtr = buf; + do + { + numWritten = uio_write(dst, bufPtr, numInBuf); + if (numWritten == -1) { + if (errno == EINTR) + continue; + return uio_copyError(src, dst, dstDir, newName, buf); + } + numInBuf -= numWritten; + bufPtr += numWritten; + } while (numInBuf > 0); + } + + uio_free(buf); + uio_close(src); + uio_close(dst); + errno = 0; + return 0; +} + +/* + * Closes srcHandle if it's not -1. + * Closes dstHandle if it's not -1. + * Removes unlinkpath from the unlinkHandle dir if it's not NULL. + * Frees 'buf' if not NULL. + * Always returns -1. + * errno is what was before the call. + */ +static int +uio_copyError(uio_Handle *srcHandle, uio_Handle *dstHandle, + uio_DirHandle *unlinkHandle, const char *unlinkPath, uio_uint8 *buf) { + int savedErrno; + + savedErrno = errno; + +#ifdef DEBUG + fprintf(stderr, "Error while copying: %s\n", strerror(errno)); +#endif + + if (srcHandle != NULL) + uio_close(srcHandle); + + if (dstHandle != NULL) + uio_close(dstHandle); + + if (unlinkPath != NULL) + uio_unlink(unlinkHandle, unlinkPath); + + if (buf != NULL) + uio_free(buf); + + errno = savedErrno; + return -1; +} + +#define NUM_TEMP_RETRIES 16 + // Retry this many times to create a temporary dir, before giving + // up. If undefined, keep trying indefinately. + +uio_StdioAccessHandle * +uio_getStdioAccess(uio_DirHandle *dir, const char *path, int flags, + uio_DirHandle *tempDir) { + int res; + uio_MountHandle *mountHandle; + const char *name; + char *newPath; + char *tempDirName; + uio_DirHandle *newDir; + uio_FileSystemID fsID; + + res = uio_getFileLocation(dir, path, flags, &mountHandle, &newPath); + if (res == -1) { + // errno is set + return NULL; + } + + fsID = uio_getMountFileSystemType(mountHandle); + if (fsID == uio_FSTYPE_STDIO) { + // Current location is usable. + return uio_StdioAccessHandle_new(NULL, NULL, NULL, NULL, newPath); + } + uio_free(newPath); + + { + uio_uint32 dirNum; + int i; + + // Current location is not usable. Create a directory with a + // generated name, as a temporary location to store a copy of + // the file. + dirNum = (uio_uint32) time(NULL); + tempDirName = uio_malloc(sizeof "01234567"); + for (i = 0; ; i++) { +#ifdef NUM_TEMP_RETRIES + if (i >= NUM_TEMP_RETRIES) { + // Using ENOSPC to report that we couldn't create a + // temporary dir, getting EEXIST. + uio_free(tempDirName); + errno = ENOSPC; + return NULL; + } +#endif + + sprintf(tempDirName, "%08lx", (unsigned long) dirNum + i); + + res = uio_mkdir(tempDir, tempDirName, 0700); + if (res == -1) { + int savedErrno; + if (errno == EEXIST) + continue; + savedErrno = errno; +#ifdef DEBUG + fprintf(stderr, "Error: Could not create temporary dir: %s\n", + strerror(errno)); +#endif + uio_free(tempDirName); + errno = savedErrno; + return NULL; + } + break; + } + + newDir = uio_openDirRelative(tempDir, tempDirName, 0); + if (newDir == NULL) { +#ifdef DEBUG + fprintf(stderr, "Error: Could not open temporary dir: %s\n", + strerror(errno)); +#endif + res = uio_rmdir(tempDir, tempDirName); +#ifdef DEBUG + if (res == -1) + fprintf(stderr, "Warning: Could not remove temporary dir: " + "%s.\n", strerror(errno)); +#endif + uio_free(tempDirName); + errno = EIO; + return NULL; + } + + // Get the last component of path. This should be the file to + // access. + name = strrchr(path, '/'); + if (name == NULL) + name = path; + + // Copy the file + res = uio_copyFile(dir, path, newDir, name); + if (res == -1) { + int savedErrno = errno; +#ifdef DEBUG + fprintf(stderr, "Error: Could not copy file to temporary dir: " + "%s\n", strerror(errno)); +#endif + uio_closeDir(newDir); + uio_free(tempDirName); + errno = savedErrno; + return NULL; + } + } + + res = uio_getFileLocation(newDir, name, flags, &mountHandle, &newPath); + if (res == -1) { + int savedErrno = errno; + fprintf(stderr, "Error: uio_getStdioAccess: Could not get location " + "of temporary dir: %s.\n", strerror(errno)); + uio_closeDir(newDir); + uio_free(tempDirName); + errno = savedErrno; + return NULL; + } + + fsID = uio_getMountFileSystemType(mountHandle); + if (fsID != uio_FSTYPE_STDIO) { + // Temp dir isn't on a stdio fs either. + fprintf(stderr, "Error: uio_getStdioAccess: Temporary file location " + "isn't on a stdio filesystem.\n"); + uio_closeDir(newDir); + uio_free(tempDirName); + uio_free(newPath); +// errno = EXDEV; + errno = EINVAL; + return NULL; + } + + uio_DirHandle_ref(tempDir); + return uio_StdioAccessHandle_new(tempDir, tempDirName, newDir, + uio_strdup(name), newPath); +} + +void +uio_releaseStdioAccess(uio_StdioAccessHandle *handle) { + if (handle->tempDir != NULL) { + if (uio_unlink(handle->tempDir, handle->fileName) == -1) { +#ifdef DEBUG + fprintf(stderr, "Error: Could not remove temporary file: " + "%s\n", strerror(errno)); +#endif + } + + // Need to free this handle in advance. There should be no handles + // to a dir left when removing it. + uio_DirHandle_unref(handle->tempDir); + handle->tempDir = NULL; + + if (uio_rmdir(handle->tempRoot, handle->tempDirName) == -1) { +#ifdef DEBUG + fprintf(stderr, "Error: Could not remove temporary directory: " + "%s\n", strerror(errno)); +#endif + } + } + + uio_StdioAccessHandle_delete(handle); +} + +const char * +uio_StdioAccessHandle_getPath(uio_StdioAccessHandle *handle) { + return (const char *) handle->stdioPath; +} + +// references to tempRoot and tempDir are not increased. +// no copies of arguments are made. +// By calling this function control of the values is transfered to +// the handle. +static inline uio_StdioAccessHandle * +uio_StdioAccessHandle_new( + uio_DirHandle *tempRoot, char *tempDirName, + uio_DirHandle *tempDir, char *fileName, char *stdioPath) { + uio_StdioAccessHandle *result; + + result = uio_StdioAccessHandle_alloc(); + result->tempRoot = tempRoot; + result->tempDirName = tempDirName; + result->tempDir = tempDir; + result->fileName = fileName; + result->stdioPath = stdioPath; + + return result; +} + +static inline void +uio_StdioAccessHandle_delete(uio_StdioAccessHandle *handle) { + if (handle->tempDir != NULL) + uio_DirHandle_unref(handle->tempDir); + if (handle->fileName != NULL) + uio_free(handle->fileName); + if (handle->tempRoot != NULL) + uio_DirHandle_unref(handle->tempRoot); + if (handle->tempDirName != NULL) + uio_free(handle->tempDirName); + uio_free(handle->stdioPath); + uio_StdioAccessHandle_free(handle); +} + +static inline uio_StdioAccessHandle * +uio_StdioAccessHandle_alloc(void) { + return uio_malloc(sizeof (uio_StdioAccessHandle)); +} + +static inline void +uio_StdioAccessHandle_free(uio_StdioAccessHandle *handle) { + uio_free(handle); +} + +#ifdef _MSC_VER +# include +#if 0 /* Unneeded for now */ +// MSVC does not have snprintf(). It does have a _snprintf(), but it does +// not \0-terminate a truncated string as the C standard prescribes. +static inline int +snprintf(char *str, size_t size, const char *format, ...) +{ + int result; + va_list args; + + va_start (args, format); + result = _vsnprintf (str, size, format, args); + if (str != NULL && size != 0) + str[size - 1] = '\0'; + va_end (args); + + return result; +} +#endif + +// MSVC does not have vsnprintf(). It does have a _vsnprintf(), but it does +// not \0-terminate a truncated string as the C standard prescribes. +static inline int +vsnprintf(char *str, size_t size, const char *format, va_list args) +{ + int result = _vsnprintf (str, size, format, args); + if (str != NULL && size != 0) + str[size - 1] = '\0'; + return result; +} +#endif /* _MSC_VER */ + +// The result should be freed using uio_free(). +// NB. POSIX allows errno to be set for vsprintf(), but does not require it: +// "The value of errno may be set to nonzero by a library function call +// whether or not there is an error, provided the use of errno is not +// documented in the description of the function in this International +// Standard." The latter is the case for vsprintf(). +char * +uio_vasprintf(const char *format, va_list args) { + // TODO: If there is a system vasprintf, use that. + // XXX: That would mean that the allocation would always go through + // malloc() or so, instead of uio_malloc(), which may not be + // desirable. + + char *buf; + size_t bufSize = 128; + // Start with enough for one screen line, and a power of 2, + // which might give faster result with allocations. + + buf = uio_malloc(bufSize); + if (buf == NULL) { + // errno is set. + return NULL; + } + + for (;;) { + int printResult = vsnprintf(buf, bufSize, format, args); + if (printResult < 0) { + // This means the buffer was not large enough, but vsnprintf() + // does not give us any clue on how large it should be. + // Note that this does not happen with a C'99 compliant + // vsnprintf(), but it will happen on MS Windows, and on + // glibc before version 2.1. + bufSize *= 2; + } else if ((unsigned int) printResult >= bufSize) { + // The buffer was too small, but printResult contains the size + // that the buffer needs to be (excluding the '\0' character). + bufSize = printResult + 1; + } else { + // Success. + if ((unsigned int) printResult + 1 != bufSize) { + // Shorten the resulting buffer to the size that was + // actually needed. + char *newBuf = uio_realloc(buf, printResult + 1); + if (newBuf == NULL) { + // We could have returned the (overly large) original + // buffer, but the unused memory might not be + // acceptable, and the program would be likely to run + // into problems sooner or later anyhow. + int savedErrno = errno; + uio_free(buf); + errno = savedErrno; + return NULL; + } + return newBuf; + } + + return buf; + } + + { + char *newBuf = uio_realloc(buf, bufSize); + if (newBuf == NULL) + { + int savedErrno = errno; + uio_free(buf); + errno = savedErrno; + return NULL; + } + buf = newBuf; + } + } +} + +// As uio_vasprintf(), but with an argument list. +char * +uio_asprintf(const char *format, ...) { + // TODO: If there is a system asprintf, use that. + // XXX: That would mean that the allocation would always go through + // malloc() or so, instead of uio_malloc(), which may not be + // desirable. + + va_list args; + char *result; + int savedErrno; + + va_start(args, format); + result = uio_vasprintf(format, args); + savedErrno = errno; + va_end(args); + + errno = savedErrno; + return result; +} + + diff --git a/src/libs/uio/utils.h b/src/libs/uio/utils.h new file mode 100644 index 0000000..313bf67 --- /dev/null +++ b/src/libs/uio/utils.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIO_UTILS_H_ +#define LIBS_UIO_UTILS_H_ + +#include + +#ifdef uio_INTERNAL +typedef struct uio_StdioAccessHandle uio_StdioAccessHandle; +#else +typedef void uio_StdioAccessHandle; +#endif + +int uio_copyFile(uio_DirHandle *srcDir, const char *srcName, + uio_DirHandle *dstDir, const char *newName); +uio_StdioAccessHandle *uio_getStdioAccess(uio_DirHandle *dir, + const char *path, int flags, uio_DirHandle *tempDir); +const char *uio_StdioAccessHandle_getPath(uio_StdioAccessHandle *handle); +void uio_releaseStdioAccess(uio_StdioAccessHandle *handle); + +char *uio_vasprintf(const char *format, va_list args); +char *uio_asprintf(const char *format, ...); + +#endif /* LIBS_UIO_UTILS_H_ */ + diff --git a/src/libs/uio/zip/Makeinfo b/src/libs/uio/zip/Makeinfo new file mode 100644 index 0000000..64ae8d5 --- /dev/null +++ b/src/libs/uio/zip/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="zip.c" +uqm_HFILES="zip.h" diff --git a/src/libs/uio/zip/zip.c b/src/libs/uio/zip/zip.c new file mode 100644 index 0000000..6da462b --- /dev/null +++ b/src/libs/uio/zip/zip.c @@ -0,0 +1,1680 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 makes use of zlib (http://www.gzip.org/zlib/) + * + * References: + * The .zip format description from PKWare: + * http://www.pkware.com/products/enterprise/white_papers/appnote.html + * The .zip format description from InfoZip: + * ftp://ftp.info-zip.org/pub/infozip/doc/appnote-011203-iz.zip + */ + +#include +#include +#include +#include + +#include "zip.h" +#include "../physical.h" +#include "../uioport.h" +#include "../paths.h" +#include "../uioutils.h" +#ifdef uio_MEM_DEBUG +# include "../memdebug.h" +#endif + + +#define DIR_STRUCTURE_READ_BUFSIZE 0x10000 + +static int zip_badFile(zip_GPFileData *gPFileData, char *fileName); +static int zip_fillDirStructure(uio_GPDir *top, uio_Handle *handle); +#if zip_USE_HEADERS == zip_USE_LOCAL_HEADERS +static int zip_fillDirStructureLocal(uio_GPDir *top, uio_Handle *handle); +static int zip_fillDirStructureLocalProcessEntry(uio_GPDir *topGPDir, + uio_FileBlock *fileBlock, off_t *pos); +#endif +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +static off_t zip_findEndOfCentralDirectoryRecord(uio_Handle *handle, + uio_FileBlock *fileBlock); +static int zip_fillDirStructureCentral(uio_GPDir *top, uio_Handle *handle); +static int zip_fillDirStructureCentralProcessEntry(uio_GPDir *topGPDir, + uio_FileBlock *fileBlock, off_t *pos); +static int zip_updatePFileDataFromLocalFileHeader(zip_GPFileData *gPFileData, + uio_FileBlock *fileBlock, int pos); +int zip_updateFileDataFromLocalHeader(uio_Handle *handle, + zip_GPFileData *gPFileData); +#endif +static int zip_fillDirStructureProcessExtraFields( + uio_FileBlock *fileBlock, off_t extraFieldLength, + zip_GPFileData *gPFileData, const char *path, off_t pos, + uio_bool central); +static inline int zip_foundFile(uio_GPDir *gPDir, const char *path, + zip_GPFileData *gPFileData); +static inline int zip_foundDir(uio_GPDir *gPDir, const char *dirName, + zip_GPDirData *gPDirData); +static int zip_initZipStream(z_stream *zipStream); +static int zip_unInitZipStream(z_stream *zipStream); +static int zip_reInitZipStream(z_stream *zipStream); + +static voidpf zip_alloc(voidpf opaque, uInt items, uInt size); +static void zip_free(voidpf opaque, voidpf address); + +static inline zip_GPFileData * zip_GPFileData_new(void); +static inline void zip_GPFileData_delete(zip_GPFileData *gPFileData); +static inline zip_GPFileData *zip_GPFileData_alloc(void); +static inline void zip_GPFileData_free(zip_GPFileData *gPFileData); +static inline void zip_GPDirData_delete(zip_GPDirData *gPDirData); +static inline void zip_GPDirData_free(zip_GPDirData *gPDirData); + +static ssize_t zip_readStored(uio_Handle *handle, void *buf, size_t count); +static ssize_t zip_readDeflated(uio_Handle *handle, void *buf, size_t count); +static off_t zip_seekStored(uio_Handle *handle, off_t offset); +static off_t zip_seekDeflated(uio_Handle *handle, off_t offset); + +uio_FileSystemHandler zip_fileSystemHandler = { + /* .init = */ NULL, + /* .unInit = */ NULL, + /* .cleanup = */ NULL, + + /* .mount = */ zip_mount, + /* .umount = */ uio_GPRoot_umount, + + /* .access = */ zip_access, + /* .close = */ zip_close, + /* .fstat = */ zip_fstat, + /* .stat = */ zip_stat, + /* .mkdir = */ NULL, + /* .open = */ zip_open, + /* .read = */ zip_read, + /* .rename = */ NULL, + /* .rmdir = */ NULL, + /* .seek = */ zip_seek, + /* .write = */ NULL, + /* .unlink = */ NULL, + + /* .openEntries = */ uio_GPDir_openEntries, + /* .readEntries = */ uio_GPDir_readEntries, + /* .closeEntries = */ uio_GPDir_closeEntries, + + /* .getPDirEntryHandle = */ uio_GPDir_getPDirEntryHandle, + /* .deletePRootExtra = */ uio_GPRoot_delete, + /* .deletePDirHandleExtra = */ uio_GPDirHandle_delete, + /* .deletePFileHandleExtra = */ uio_GPFileHandle_delete, +}; + +uio_GPRoot_Operations zip_GPRootOperations = { + /* .fillGPDir = */ NULL, + /* .deleteGPRootExtra = */ NULL, + /* .deleteGPDirExtra = */ zip_GPDirData_delete, + /* .deleteGPFileExtra = */ zip_GPFileData_delete, +}; + + +#define NUM_COMPRESSION_METHODS 11 +/* + * [0] = stored uncompressed + * [1] = Shrunk + * [2] = Reduced with compression factor 1 + * [3] = Reduced with compression factor 2 + * [4] = Reduced with compression factor 3 + * [5] = Reduced with compression factor 4 + * [6] = Imploded + * [7] = Reserved for Tokenizing + * [8] = Deflated + * [9] = Deflate64 + * [10] = "PKWARE Data Compression Library Imploding" + */ +static const uio_bool + zip_compressionMethodSupported[NUM_COMPRESSION_METHODS] = { + true, false, false, false, false, false, false, false, + true, false, false }; + +typedef ssize_t (*zip_readFunctionType)(uio_Handle *handle, + void *buf, size_t count); +zip_readFunctionType zip_readMethods[NUM_COMPRESSION_METHODS] = { + zip_readStored, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + zip_readDeflated, NULL, NULL +}; +typedef off_t (*zip_seekFunctionType)(uio_Handle *handle, off_t offset); +zip_seekFunctionType zip_seekMethods[NUM_COMPRESSION_METHODS] = { + zip_seekStored, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + zip_seekDeflated, NULL, NULL +}; + + +typedef enum { + zip_OSType_FAT, + zip_OSType_Amiga, + zip_OSType_OpenVMS, + zip_OSType_UNIX, + zip_OSType_VMCMS, + zip_OSType_AtariST, + zip_OSType_HPFS, + zip_OSType_HFS, + zip_OSType_ZSystem, + zip_OSType_CPM, + zip_OSType_TOPS20, + zip_OSType_NTFS, + zip_OSType_QDOS, + zip_OSType_Acorn, + zip_OSType_VFAT, + zip_OSType_MVS, + zip_OSType_BeOS, + zip_OSType_Tandem, + + zip_numOSTypes +} zip_OSType; + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +static mode_t zip_makeFileMode(zip_OSType creatorOS, uio_uint32 modeBytes); +#endif + +#define zip_INPUT_BUFFER_SIZE 0x10000 + // TODO: make this configurable a la sysctl? +#define zip_SEEK_BUFFER_SIZE zip_INPUT_BUFFER_SIZE + + +void +zip_close(uio_Handle *handle) { + zip_Handle *zip_handle; + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "zip_close - handle=%p\n", (void *) handle); +#endif + zip_handle = handle->native; + uio_GPFile_unref(zip_handle->file); + zip_unInitZipStream(&zip_handle->zipStream); + uio_closeFileBlock(zip_handle->fileBlock); + uio_free(zip_handle); +} + +static void +zip_fillStat(struct stat *statBuf, const zip_GPFileData *gPFileData) { + memset(statBuf, '\0', sizeof (struct stat)); + statBuf->st_size = gPFileData->uncompressedSize; + statBuf->st_uid = gPFileData->uid; + statBuf->st_gid = gPFileData->gid; + statBuf->st_mode = gPFileData->mode; + statBuf->st_atime = gPFileData->atime; + statBuf->st_mtime = gPFileData->mtime; + statBuf->st_ctime = gPFileData->ctime; +} + +int +zip_access(uio_PDirHandle *pDirHandle, const char *name, int mode) { + errno = ENOSYS; // Not implemented. + (void) pDirHandle; + (void) name; + (void) mode; + return -1; + +#if 0 + uio_GPDirEntry *entry; + + if (name[0] == '.' && name[1] == '\0') { + entry = (uio_GPDirEntry *) pDirHandle->extra; + } else { + entry = uio_GPDir_getGPDirEntry(pDirHandle->extra, name); + if (entry == NULL) { + errno = ENOENT; + return -1; + } + } + + if (mode & R_OK) + { + // Read permission is always granted. Nothing to check here. + } + + if (mode & W_OK) { + errno = EACCES; + return -1; + } + + if (mode & X_OK) { + if (uio_GPDirEntry_isDir(entry)) { + // Search permission on directories is always granted. + } else { + // WORK +#error + } + } + + if (mode & F_OK) { + // WORK +#error + } + + return 0; +#endif +} + +int +zip_fstat(uio_Handle *handle, struct stat *statBuf) { +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS + if (handle->native->file->extra->fileOffset == -1) { + // The local header wasn't read in yet. + if (zip_updateFileDataFromLocalHeader(handle->root->handle, + handle->native->file->extra) == -1) { + // errno is set + return -1; + } + } +#endif /* zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS */ + zip_fillStat(statBuf, handle->native->file->extra); + return 0; +} + +int +zip_stat(uio_PDirHandle *pDirHandle, const char *name, struct stat *statBuf) { + uio_GPDirEntry *entry; + + if (name[0] == '.' && name[1] == '\0') { + entry = (uio_GPDirEntry *) pDirHandle->extra; + } else { + entry = uio_GPDir_getGPDirEntry(pDirHandle->extra, name); + if (entry == NULL) { + errno = ENOENT; + return -1; + } + } + + if (uio_GPDirEntry_isDir(entry) && entry->extra == NULL) { + // No information about this directory was stored. + // We'll have to make something up. + memset(statBuf, '\0', sizeof (struct stat)); + statBuf->st_mode = S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR | + S_IRGRP | S_IWGRP | S_IXOTH | + S_IROTH | S_IWOTH | S_IXOTH; + statBuf->st_uid = 0; + statBuf->st_gid = 0; + return 0; + } + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +#ifndef zip_INCOMPLETE_STAT + if (((zip_GPFileData *) entry->extra)->fileOffset == -1) { + // The local header wasn't read in yet. + if (zip_updateFileDataFromLocalHeader(pDirHandle->pRoot->handle, + (zip_GPFileData *) entry->extra) == -1) { + // errno is set + return -1; + } + } +#endif /* !defined(zip_INCOMPLETE_STAT) */ +#endif /* zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS */ + + zip_fillStat(statBuf, (zip_GPFileData *) entry->extra); + return 0; +} + +/* + * Function name: zip_open + * Description: open a file in zip file + * Arguments: pDirHandle - handle to the dir where to open the file + * name - the name of the file to open + * flags - flags, as to stdio open() + * mode - mode, as to stdio open() + * Returns: handle, as from stdio open() + * If failed, errno is set and handle is -1. + */ +uio_Handle * +zip_open(uio_PDirHandle *pDirHandle, const char *name, int flags, + mode_t mode) { + zip_Handle *handle; + uio_GPFile *gPFile; + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "zip_open - pDirHandle=%p name=%s flags=%d mode=0%o\n", + (void *) pDirHandle, name, flags, mode); +#endif + + if ((flags & O_ACCMODE) != O_RDONLY) { + errno = EACCES; + return NULL; + } + + gPFile = (uio_GPFile *) uio_GPDir_getGPDirEntry(pDirHandle->extra, name); + if (gPFile == NULL) { + errno = ENOENT; + return NULL; + } + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS + if (gPFile->extra->fileOffset == -1) { + // The local header wasn't read in yet. + if (zip_updateFileDataFromLocalHeader(pDirHandle->pRoot->handle, + gPFile->extra) == -1) { + // errno is set + return NULL; + } + } +#endif + + handle = uio_malloc(sizeof (zip_Handle)); + uio_GPFile_ref(gPFile); + handle->file = gPFile; + handle->fileBlock = uio_openFileBlock2(pDirHandle->pRoot->handle, + gPFile->extra->fileOffset, gPFile->extra->compressedSize); + if (handle->fileBlock == NULL) { + // errno is set + return NULL; + } + + if (zip_initZipStream(&handle->zipStream) == -1) { + uio_GPFile_unref(gPFile); + uio_closeFileBlock(handle->fileBlock); + return NULL; + } + handle->compressedOffset = 0; + handle->uncompressedOffset = 0; + + (void) mode; + return uio_Handle_new(pDirHandle->pRoot, handle, flags); +} + +ssize_t +zip_read(uio_Handle *handle, void *buf, size_t count) { + ssize_t result; + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "zip_read - handle=%p buf=%p count=%d: ", (void *) handle, + (void *) buf, count); +#endif + result = zip_readMethods[handle->native->file->extra->compressionMethod] + (handle, buf, count); +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "%d\n", result); +#endif + return result; +} + +static ssize_t +zip_readStored(uio_Handle *handle, void *buf, size_t count) { + int numBytes; + zip_Handle *zipHandle; + + zipHandle = handle->native; + numBytes = uio_copyFileBlock(zipHandle->fileBlock, + zipHandle->uncompressedOffset, buf, count); + if (numBytes == -1) { + // errno is set + return -1; + } + zipHandle->uncompressedOffset += numBytes; + zipHandle->compressedOffset += numBytes; + return numBytes; +} + +static ssize_t +zip_readDeflated(uio_Handle *handle, void *buf, size_t count) { + zip_Handle *zipHandle; + int inflateResult; + + zipHandle = handle->native; + + if (count > ((zip_GPFileData *) (zipHandle->file->extra))-> + uncompressedSize - zipHandle->zipStream.total_out) + count = ((zip_GPFileData *) (zipHandle->file->extra))-> + uncompressedSize - zipHandle->zipStream.total_out; + + zipHandle->zipStream.next_out = (Bytef *) buf; + zipHandle->zipStream.avail_out = count; + while (zipHandle->zipStream.avail_out > 0) { + if (zipHandle->zipStream.avail_in == 0) { + ssize_t numBytes; + numBytes = uio_accessFileBlock(zipHandle->fileBlock, + zipHandle->compressedOffset, zip_INPUT_BUFFER_SIZE, + (char **) &zipHandle->zipStream.next_in); + if (numBytes == -1) { + // errno is set + return -1; + } +#if 0 + if (numBytes == 0) { + if (zipHandle->uncompressedOffset != + zipHandle->file->extra->uncompressedSize) { + // premature eof + errno = EIO; + return -1; + } + break; + } +#endif + zipHandle->zipStream.avail_in = numBytes; + zipHandle->compressedOffset += numBytes; + } + inflateResult = inflate(&zipHandle->zipStream, Z_SYNC_FLUSH); + zipHandle->uncompressedOffset = zipHandle->zipStream.total_out; + if (inflateResult == Z_STREAM_END) { + // Everything is decompressed + break; + } + if (inflateResult != Z_OK) { + switch (inflateResult) { + case Z_VERSION_ERROR: + fprintf(stderr, "Error: Incompatible version problem for " + " decompression.\n"); + break; + case Z_NEED_DICT: + fprintf(stderr, "Error: Decompressing requires " + "preset dictionary.\n"); + break; + case Z_DATA_ERROR: + fprintf(stderr, "Error: Compressed file is corrupted.\n"); + break; + case Z_STREAM_ERROR: + // This means zipHandle->zipStream is bad, which is + // most likely an error in the code using zlib. + fprintf(stderr, "Fatal: internal error using zlib.\n"); + abort(); + break; + case Z_MEM_ERROR: + fprintf(stderr, "Error: Not enough memory available " + "while decompressing.\n"); + break; + case Z_BUF_ERROR: + // No progress possible. Probably caused by premature + // end of input file. + fprintf(stderr, "Error: When decompressing: premature " + "end of input file.\n"); + errno = EIO; + return -1; +#if 0 + // If this happens, either the input buffer is empty + // or the output buffer is full. This should not happen. + fprintf(stderr, "Fatal: internal error using zlib: " + " no progress is possible in decompression.\n"); + abort(); + break; +#endif + default: + fprintf(stderr, "Fatal: unknown error from inflate().\n"); + abort(); + } + if (zipHandle->zipStream.msg != NULL) + fprintf(stderr, "ZLib reports: %s\n", + zipHandle->zipStream.msg); + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + } + return count - zipHandle->zipStream.avail_out; +} + +off_t +zip_seek(uio_Handle *handle, off_t offset, int whence) { + zip_Handle *zipHandle; + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "zip_seek - handle=%p offset=%d whence=%s\n", + (void *) handle, (int) offset, + whence == SEEK_SET ? "SEEK_SET" : + whence == SEEK_CUR ? "SEEK_CUR" : + whence == SEEK_END ? "SEEK_END" : "INVALID"); +#endif + zipHandle = handle->native; + + assert(whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END); + switch(whence) { + case SEEK_SET: + break; + case SEEK_CUR: + offset += zipHandle->uncompressedOffset; + break; + case SEEK_END: + offset += zipHandle->file->extra->uncompressedSize; + break; + } + if (offset < 0) { + offset = 0; + } else if (offset > zipHandle->file->extra->uncompressedSize) { + offset = zipHandle->file->extra->uncompressedSize; + } + return zip_seekMethods[handle->native->file->extra->compressionMethod] + (handle, offset); +} + +static off_t +zip_seekStored(uio_Handle *handle, off_t offset) { + zip_Handle *zipHandle; + + zipHandle = handle->native; + if (offset > zipHandle->file->extra->uncompressedSize) + offset = zipHandle->file->extra->uncompressedSize; + + zipHandle->compressedOffset = offset; + zipHandle->uncompressedOffset = offset; + return offset; +} + +static off_t +zip_seekDeflated(uio_Handle *handle, off_t offset) { + zip_Handle *zipHandle; + + zipHandle = handle->native; + + if (offset < zipHandle->uncompressedOffset) { + // The new offset is earlier than the current offset. We need to + // seek from the beginning. + if (zip_reInitZipStream(&zipHandle->zipStream) == -1) { + // Need to abort. Handle would get in an inconsistent state. + // Should not fail anyhow. + fprintf(stderr, "Fatal: Could not reinitialise zip stream: " + "%s.\n", strerror(errno)); + abort(); + } + zipHandle->compressedOffset = 0; + zipHandle->uncompressedOffset = 0; + } + + if (offset == zipHandle->uncompressedOffset) + return offset; + + // Seek from the current position. + { + char *buffer; + ssize_t numRead; + size_t toRead; + + buffer = uio_malloc(zip_SEEK_BUFFER_SIZE); + toRead = offset - zipHandle->uncompressedOffset; + while (toRead > 0) { + numRead = zip_read(handle, buffer, + toRead < zip_SEEK_BUFFER_SIZE ? + toRead : zip_SEEK_BUFFER_SIZE); + if (numRead == -1) { + fprintf(stderr, "Warning: Could not read zipped file: %s\n", + strerror(errno)); + break; + // The current location is returned. + } + toRead -= numRead; + } + uio_free(buffer); + } + return zipHandle->uncompressedOffset; +} + +uio_PRoot * +zip_mount(uio_Handle *handle, int flags) { + uio_PRoot *result; + uio_PDirHandle *rootDirHandle; + + if ((flags & uio_MOUNT_RDONLY) != uio_MOUNT_RDONLY) { + errno = EACCES; + return NULL; + } + + uio_Handle_ref(handle); + result = uio_GPRoot_makePRoot( + uio_getFileSystemHandler(uio_FSTYPE_ZIP), flags, + &zip_GPRootOperations, NULL, uio_GPRoot_PERSISTENT, + handle, NULL, uio_GPDir_COMPLETE); + + rootDirHandle = uio_PRoot_getRootDirHandle(result); + if (zip_fillDirStructure(rootDirHandle->extra, handle) == -1) { + int savedErrno = errno; +#ifdef DEBUG + fprintf(stderr, "Error: failed to read the zip directory " + "structure - %s.\n", strerror(errno)); +#endif + uio_GPRoot_umount(result); + errno = savedErrno; + return NULL; + } + + return result; +} + +static int +zip_fillDirStructure(uio_GPDir *top, uio_Handle *handle) { +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS + return zip_fillDirStructureCentral(top, handle); +#endif +#if zip_USE_HEADERS == zip_USE_LOCAL_HEADERS + return zip_fillDirStructureLocal(top, handle); +#endif +} + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +static int +zip_fillDirStructureCentral(uio_GPDir *top, uio_Handle *handle) { + uio_FileBlock *fileBlock; + off_t pos; + char *buf; + ssize_t numBytes; + uio_uint16 numEntries; + // TODO: use numEntries to initialise the hash table + // to a smart size + off_t eocdr; + off_t startCentralDir; + + fileBlock = uio_openFileBlock(handle); + if (fileBlock == NULL) { + // errno is set + goto err; + } + + // first find the 'End of Central Directory Record' + eocdr = zip_findEndOfCentralDirectoryRecord(handle, fileBlock); + if (eocdr == -1) { + // errno is set + goto err; + } + + numBytes = uio_accessFileBlock(fileBlock, eocdr, 22, &buf); + if (numBytes == -1) { + // errno is set + goto err; + } + if (numBytes != 22) { + errno = EIO; + goto err; + } + + numEntries = makeUInt16(buf[10], buf[11]); + if (numEntries == 0xffff) { + fprintf(stderr, "Error: Zip64 .zip files are not supported.\n"); + errno = ENOSYS; + goto err; + } + + startCentralDir = makeUInt32(buf[16], buf[17], buf[18], buf[19]); + + // Enable read-ahead buffering, for speed. + uio_setFileBlockUsageHint(fileBlock, uio_FB_USAGE_FORWARD, + DIR_STRUCTURE_READ_BUFSIZE); + + pos = startCentralDir; + while (numEntries--) { + if (zip_fillDirStructureCentralProcessEntry(top, fileBlock, &pos) + == -1) { + // errno is set + goto err; + } + } + + uio_closeFileBlock(fileBlock); + return 0; + +err: + { + int savedErrno = errno; + + if (fileBlock != NULL) + uio_closeFileBlock(fileBlock); + errno = savedErrno; + return -1; + } +} + +static int +zip_fillDirStructureCentralProcessEntry(uio_GPDir *topGPDir, + uio_FileBlock *fileBlock, off_t *pos) { + char *buf; + zip_GPFileData *gPFileData; + ssize_t numBytes; + + uio_uint32 signature; + uio_uint16 lastModTime; + uio_uint16 lastModDate; + uio_uint32 crc; + uio_uint16 fileNameLength; + uio_uint16 extraFieldLength; + uio_uint16 fileCommentLength; + char *fileName; + zip_OSType creatorOS; + + off_t nextEntryOffset; + + numBytes = uio_accessFileBlock(fileBlock, *pos, 46, &buf); + if (numBytes != 46) + return zip_badFile(NULL, NULL); + + signature = makeUInt32(buf[0], buf[1], buf[2], buf[3]); + if (signature != 0x02014b50) { + fprintf(stderr, "Error: Premature end of central directory.\n"); + errno = EIO; + return -1; + } + + gPFileData = zip_GPFileData_new(); + creatorOS = (zip_OSType) buf[5]; + gPFileData->compressionFlags = makeUInt16(buf[8], buf[9]); + gPFileData->compressionMethod = makeUInt16(buf[10], buf[11]); + lastModTime = makeUInt16(buf[12], buf[13]); + lastModDate = makeUInt16(buf[14], buf[15]); + gPFileData->atime = (time_t) 0; + gPFileData->mtime = dosToUnixTime(lastModDate, lastModTime); + gPFileData->ctime = (time_t) 0; + crc = makeUInt32(buf[16], buf[17], buf[18], buf[19]); + gPFileData->compressedSize = + makeUInt32(buf[20], buf[21], buf[22], buf[23]); + gPFileData->uncompressedSize = + makeUInt32(buf[24], buf[25], buf[26], buf[27]); + fileNameLength = makeUInt16(buf[28], buf[29]); + extraFieldLength = makeUInt16(buf[30], buf[31]); + fileCommentLength = makeUInt16(buf[32], buf[33]); + gPFileData->uid = 0; + gPFileData->gid = 0; + gPFileData->mode = zip_makeFileMode(creatorOS, + makeUInt32(buf[38], buf[39], buf[40], buf[41])); + + gPFileData->headerOffset = + (off_t) makeSInt32(buf[42], buf[43], buf[44], buf[45]); + gPFileData->fileOffset = (off_t) -1; + + *pos += 46; + nextEntryOffset = *pos + fileNameLength + extraFieldLength + + fileCommentLength; + + numBytes = uio_accessFileBlock(fileBlock, *pos, fileNameLength, &buf); + if (numBytes != fileNameLength) + return zip_badFile(gPFileData, NULL); + fileName = uio_malloc(fileNameLength + 1); + memcpy(fileName, buf, fileNameLength); + fileName[fileNameLength] = '\0'; + *pos += fileNameLength; + + if (gPFileData->compressionMethod >= NUM_COMPRESSION_METHODS || + !zip_compressionMethodSupported[gPFileData->compressionMethod]) { + fprintf(stderr, "Warning: File '%s' is compressed with " + "unsupported method %d - skipped.\n", fileName, + gPFileData->compressionMethod); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + if (gPFileData->compressedSize == (off_t) 0xffffffff || + gPFileData->uncompressedSize == (off_t) 0xffffffff || + gPFileData->headerOffset < 0) { + fprintf(stderr, "Warning: Skipping Zip64 file '%s'\n", fileName); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + if (isBitSet(gPFileData->compressionFlags, 0)) { + fprintf(stderr, "Warning: Skipping encrypted file '%s'\n", fileName); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + switch (zip_fillDirStructureProcessExtraFields(fileBlock, + extraFieldLength, gPFileData, fileName, *pos, true)) { + case 0: // file is ok + break; + case 1: // file is not acceptable - skip file + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + case -1: + return zip_badFile(gPFileData, fileName); + } + + *pos += extraFieldLength; + + // If ctime or atime is 0, they will be filled in when the local + // file header is read. + + if (S_ISREG(gPFileData->mode)) { + if (zip_foundFile(topGPDir, fileName, gPFileData) == -1) { + if (errno == EISDIR) { + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + return zip_badFile(gPFileData, fileName); + } + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "Debug: Found file '%s'.\n", fileName); +#endif + } else if (S_ISDIR(gPFileData->mode)) { + if (fileName[fileNameLength - 1] == '/') + fileName[fileNameLength - 1] = '\0'; + if (zip_foundDir(topGPDir, fileName, gPFileData) == -1) { + if (errno == EISDIR) { + fprintf(stderr, "Warning: file '%s' already exists as a dir - " + "skipped.\n", fileName); + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + return zip_badFile(gPFileData, fileName); + } +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "Debug: Found dir '%s'.\n", fileName); +#endif + } else { + fprintf(stderr, "Warning: '%s' is not a regular file, nor a " + "directory - skipped.\n", fileName); + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + + uio_free(fileName); + return 0; +} + +static off_t +zip_findEndOfCentralDirectoryRecord(uio_Handle *handle, + uio_FileBlock *fileBlock) { + off_t fileSize; + off_t endPos, startPos; + char *buf, *bufPtr; + ssize_t bufLen; + + struct stat statBuf; + if (uio_fstat(handle, &statBuf) == -1) { + // errno is set + return -1; + } + fileSize = statBuf.st_size; + startPos = fileSize - 0xffff - 22; // max comment and record size + if (startPos < 0) + startPos = 0; + endPos = fileSize - 22; // last position to be checked + bufLen = uio_accessFileBlock(fileBlock, startPos, endPos - startPos + 4, + &buf); + if (bufLen == -1) { + int savedErrno = errno; + fprintf(stderr, "Error: Read error while searching for " + "'end-of-central-directory record'.\n"); + errno = savedErrno; + return -1; + } + if (bufLen != endPos - startPos + 4) { + fprintf(stderr, "Error: Read error while searching for " + "'end-of-central-directory record'.\n"); + errno = EIO; + return -1; + } + bufPtr = buf + (endPos - startPos); + while (1) { + if (bufPtr < buf) { + fprintf(stderr, "Error: Zip file corrupt; could not find " + "'end-of-central-directory record'.\n"); + errno = EIO; + return -1; + } + if (bufPtr[0] == 0x50 && bufPtr[1] == 0x4b && bufPtr[2] == 0x05 && + bufPtr[3] == 0x06) + break; + bufPtr--; + } + return startPos + (bufPtr - buf); +} + +static mode_t +zip_makeFileMode(zip_OSType creatorOS, uio_uint32 modeBytes) { + switch (creatorOS) { + case zip_OSType_FAT: + case zip_OSType_NTFS: + case zip_OSType_VFAT: { + // Only the least signigicant byte is relevant. + mode_t mode; + + if (modeBytes == 0) { + // File came from standard input + return S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | + S_IROTH | S_IWOTH; + } + if (modeBytes & 0x10) { + // Directory + mode = S_IFDIR | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | + S_IROTH | S_IXOTH; + } else { + // Regular file + mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; + } + if (modeBytes & 0x01) { + // readonly + return mode; + } else { + // Write allowed + return mode | S_IWUSR | S_IWGRP | S_IWOTH; + } + } + case zip_OSType_UNIX: + return (mode_t) (modeBytes >> 16); + default: + fprintf(stderr, "Warning: file created by unknown OS.\n"); + return S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + } +} + +// If the data is read from the central directory, certain data +// will need to be updated from the local directory when it is needed. +// This function does that. +// returns 0 for success, -1 for error (errno is set) +// NB: Only the fields that may offer new information are checked. +// the fields that were already read from the central directory +// aren't verified. +static int +zip_updatePFileDataFromLocalFileHeader(zip_GPFileData *gPFileData, + uio_FileBlock *fileBlock, int pos) { + ssize_t numBytes; + char *buf; + uio_uint32 signature; + uio_uint16 fileNameLength; + uio_uint16 extraFieldLength; + + numBytes = uio_accessFileBlock(fileBlock, pos, 30, &buf); + if (numBytes != 30) { + errno = EIO; + return -1; + } + signature = makeUInt32(buf[0], buf[1], buf[2], buf[3]); + if (signature != 0x04034b50) { + errno = EIO; + return -1; + } + fileNameLength = makeUInt16(buf[26], buf[27]); + extraFieldLength = makeUInt16(buf[28], buf[29]); + pos += 30 + fileNameLength; + + switch (zip_fillDirStructureProcessExtraFields(fileBlock, + extraFieldLength, gPFileData, "", pos, false)) { + case 0: // file is ok + break; + case 1: + // File is not acceptable (but according to the central header + // it was) + fprintf(stderr, "Warning: according to the central directory " + "of a zip file, some file inside is acceptable, " + "but according to the local header it isn't.\n"); + errno = EIO; + return -1; + case -1: + errno = EIO; + return -1; + } + pos += extraFieldLength; + gPFileData->fileOffset = pos; + return 0; +} +#endif /* zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS */ + +#if zip_USE_HEADERS == zip_USE_LOCAL_HEADERS +static int +zip_fillDirStructureLocal(uio_GPDir *top, uio_Handle *handle) { + uio_FileBlock *fileBlock; + off_t pos; + char *buf; + ssize_t numBytes; + + pos = uio_lseek(handle, 0, SEEK_SET); + if (pos == -1) { + int savedErrno = errno; + errno = savedErrno; + return -1; + } + if (pos != 0) { + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + + // We read all the files from the beginning of the zip file to the end. + // (the directory record at the end of the file is ignored) + fileBlock = uio_openFileBlock(handle); + if (fileBlock == NULL) { + // errno is set + return -1; + } + + pos = 0; + while (1) { + uio_uint32 signature; + + numBytes = uio_accessFileBlock(fileBlock, pos, 4, &buf); + if (numBytes == -1) + goto err; + if (numBytes != 4) + break; + signature = makeUInt32(buf[0], buf[1], buf[2], buf[3]); + if (signature != 0x04034b50) { + // End of file data reached. + break; + } + pos += 4; + if (zip_fillDirStructureLocalProcessEntry(top, fileBlock, &pos) == -1) + goto err; + } + + uio_closeFileBlock(fileBlock); + return 0; + +err: + { + int savedErrno = errno; + + uio_closeFileBlock(fileBlock); + errno = savedErrno; + return -1; + } +} + +static int +zip_fillDirStructureLocalProcessEntry(uio_GPDir *topGPDir, + uio_FileBlock *fileBlock, off_t *pos) { + char *buf; + zip_GPFileData *gPFileData; + ssize_t numBytes; + + uio_uint16 lastModTime; + uio_uint16 lastModDate; + uio_uint32 crc; + uio_uint16 fileNameLength; + uio_uint16 extraFieldLength; + char *fileName; + + off_t nextEntryOffset; + + numBytes = uio_accessFileBlock(fileBlock, *pos, 26, &buf); + if (numBytes != 26) + return zip_badFile(NULL, NULL); + + gPFileData = zip_GPFileData_new(); + gPFileData->compressionFlags = makeUInt16(buf[2], buf[3]); + gPFileData->compressionMethod = makeUInt16(buf[4], buf[5]); + lastModTime = makeUInt16(buf[6], buf[7]); + lastModDate = makeUInt16(buf[8], buf[9]); + gPFileData->atime = (time_t) 0; + gPFileData->mtime = dosToUnixTime(lastModDate, lastModTime); + gPFileData->ctime = (time_t) 0; + gPFileData->uid = 0; + gPFileData->gid = 0; + gPFileData->mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + if (!isBitSet(gPFileData->compressionFlags, 3)) { + // If bit 3 is not set, this info will be in the data descriptor + // behind the file data. + crc = makeUInt32(buf[10], buf[11], buf[12], buf[13]); + gPFileData->compressedSize = + makeUInt32(buf[14], buf[15], buf[16], buf[17]); + gPFileData->uncompressedSize = + makeUInt32(buf[18], buf[19], buf[20], buf[21]); + } + fileNameLength = makeUInt16(buf[22], buf[23]); + extraFieldLength = makeUInt16(buf[24], buf[25]); + *pos += 26; + nextEntryOffset = *pos + fileNameLength + extraFieldLength + + gPFileData->compressedSize; + if (isBitSet(gPFileData->compressionFlags, 3)) { + // There's a data descriptor present behind the file data. + nextEntryOffset += 16; + } + + if (gPFileData->compressionMethod >= NUM_COMPRESSION_METHODS || + !zip_compressionMethodSupported[gPFileData->compressionMethod]) { + fprintf(stderr, "Warning: File '%s' is compressed with " + "unsupported method %d - skipped.\n", fileName, + gPFileData->compressionMethod); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + if (gPFileData->compressedSize == (off_t) 0xffffffff || + gPFileData->uncompressedSize == (off_t) 0xffffffff) { + fprintf(stderr, "Warning: Skipping Zip64 file '%s'\n", fileName); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + if (isBitSet(gPFileData->compressionFlags, 0)) { + fprintf(stderr, "Warning: Skipping encrypted file '%s'\n", fileName); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + numBytes = uio_accessFileBlock(fileBlock, *pos, fileNameLength, &buf); + if (numBytes != fileNameLength) + return zip_badFile(gPFileData, NULL); + *pos += fileNameLength; + if (buf[fileNameLength - 1] == '/') { + gPFileData->mode |= S_IFDIR; + fileNameLength--; + } else + gPFileData->mode |= S_IFREG; + fileName = uio_malloc(fileNameLength + 1); + memcpy(fileName, buf, fileNameLength); + fileName[fileNameLength] = '\0'; + + switch (zip_fillDirStructureProcessExtraFields(fileBlock, + extraFieldLength, gPFileData, fileName, *pos, false)) { + case 0: // file is ok + break; + case 1: // file is not acceptable - skip file + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + case -1: + return zip_badFile(gPFileData, fileName); + } + *pos += extraFieldLength; + + gPFileData->fileOffset = *pos; + + *pos += gPFileData->compressedSize; + if (isBitSet(gPFileData->compressionFlags, 3)) { + // Now comes a data descriptor. + // The PKWare version (which was never used) misses the signature. + // The InfoZip version is used below. + uio_uint32 signature; + + numBytes = uio_accessFileBlock(fileBlock, *pos, 16, &buf); + if (numBytes != 16) + return zip_badFile(gPFileData, fileName); + + signature = makeUInt32(buf[0], buf[1], buf[2], buf[3]); + if (signature != 0x08074b50) + return zip_badFile(gPFileData, fileName); + crc = makeUInt32(buf[4], buf[5], buf[6], buf[7]); + gPFileData->compressedSize = + makeUInt32(buf[8], buf[9], buf[10], buf[11]); + gPFileData->uncompressedSize = + makeUInt32(buf[12], buf[13], buf[14], buf[15]); + } + + if (gPFileData->ctime == (time_t) 0) + gPFileData->ctime = gPFileData->mtime; + + if (gPFileData->atime == (time_t) 0) + gPFileData->atime = gPFileData->mtime; + + if (S_ISREG(gPFileData->mode)) { + if (zip_foundFile(topGPDir, fileName, gPFileData) == -1) { + if (errno == EISDIR) { + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + return zip_badFile(gPFileData, fileName); + } + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "Debug: Found file '%s'.\n", fileName); +#endif + } else if (S_ISDIR(gPFileData->mode)) { + if (fileName[fileNameLength - 1] == '/') + fileName[fileNameLength - 1] = '\0'; + if (zip_foundDir(topGPDir, fileName, gPFileData) == -1) { + if (errno == EISDIR) { + fprintf(stderr, "Warning: file '%s' already exists as a dir - " + "skipped.\n", fileName); + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + return zip_badFile(gPFileData, fileName); + } +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "Debug: Found dir '%s'.\n", fileName); +#endif + } else { + fprintf(stderr, "Warning: '%s' is not a regular file, nor a " + "directory - skipped.\n", fileName); + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + + uio_free(fileName); + return 0; +} +#endif /* zip_USE_HEADERS == zip_USE_LOCAL_HEADERS */ + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +int +zip_updateFileDataFromLocalHeader(uio_Handle *handle, + zip_GPFileData *gPFileData) { + uio_FileBlock *fileBlock; + + fileBlock = uio_openFileBlock(handle); + if (fileBlock == NULL) { + // errno is set + return -1; + } + if (zip_updatePFileDataFromLocalFileHeader(gPFileData, + fileBlock, gPFileData->headerOffset) == -1) { + int savedErrno = errno; + uio_closeFileBlock(fileBlock); + errno = savedErrno; + return -1; + } + if (gPFileData->ctime == (time_t) 0) + gPFileData->ctime = gPFileData->mtime; + if (gPFileData->atime == (time_t) 0) + gPFileData->atime = gPFileData->mtime; + uio_closeFileBlock(fileBlock); + return 0; +} +#endif + +// If the zip file is bad, -1 is returned (no errno set!) +// If the file in the zip file should be skipped, 1 is returned. +// If the file in the zip file is ok, 0 is returned. +static int +zip_fillDirStructureProcessExtraFields(uio_FileBlock *fileBlock, + off_t extraFieldLength, zip_GPFileData *gPFileData, + const char *fileName, off_t pos, uio_bool central) { + off_t posEnd; + uio_uint16 headerID; + ssize_t dataSize; + ssize_t numBytes; + char *buf; + + posEnd = pos + extraFieldLength; + while (pos < posEnd) { + numBytes = uio_accessFileBlock(fileBlock, pos, 4, &buf); + if (numBytes != 4) + return -1; + headerID = makeUInt16(buf[0], buf[1]); + dataSize = (ssize_t) makeUInt16(buf[2], buf[3]); + pos += 4; + numBytes = uio_accessFileBlock(fileBlock, pos, dataSize, &buf); + if (numBytes != dataSize) + return -1; + switch(headerID) { + case 0x000d: // 'Unix0' + // fallthrough + case 0x5855: // 'Unix1' + gPFileData->atime = (time_t) makeUInt32( + buf[0], buf[1], buf[2], buf[3]); + gPFileData->mtime = (time_t) makeUInt32( + buf[4], buf[5], buf[6], buf[7]); + if (central) + break; + if (dataSize > 8) { + gPFileData->uid = (uid_t) makeUInt16(buf[8], buf[9]); + gPFileData->gid = (uid_t) makeUInt16(buf[10], buf[11]); + } + // Unix0 has an extra (ignored) field at the end. + break; + case 0x5455: { // 'time' + uio_uint8 flags; + const char *bufPtr; + flags = buf[0]; + bufPtr = buf + 1; + if (isBitSet(flags, 0)) { + // modification time is present + gPFileData->mtime = (time_t) makeUInt32( + bufPtr[0], bufPtr[1], bufPtr[2], bufPtr[3]); + bufPtr += 4; + } + // The flags field, even in the central header, specifies what + // is present in the local header. + // The central header only contains a field for the mtime + // when it is present in the local header too, and + // never contains fields for other times. + if (central) + break; + if (isBitSet(flags, 1)) { + // modification time is present + gPFileData->atime = (time_t) makeUInt32( + bufPtr[0], bufPtr[1], bufPtr[2], bufPtr[3]); + bufPtr += 4; + } + // Creation time and possible other times are skipped. + break; + } + case 0x7855: // 'Unix2' + if (central) + break; + gPFileData->uid = (uid_t) makeUInt16(buf[0], buf[1]); + gPFileData->gid = (uid_t) makeUInt16(buf[2], buf[3]); + break; + case 0x756e: { // 'Unix3' + mode_t mode; + mode = (mode_t) makeUInt16(buf[4], buf[5]); + if (!S_ISREG(mode) && !S_ISDIR(mode)) { + fprintf(stderr, "Warning: Skipping '%s'; not a regular " + "file, nor a directory.\n", fileName); + return 1; + } + gPFileData->uid = (uid_t) makeUInt16(buf[10], buf[11]); + gPFileData->gid = (uid_t) makeUInt16(buf[12], buf[13]); + break; + } + default: +#ifdef DEBUG + fprintf(stderr, "Debug: Extra field 0x%04x unsupported, " + "used for file '%s' - ignored.\n", headerID, + fileName); +#endif + break; + } // switch + pos += dataSize; + } // while + if (pos != posEnd) + return -1; + return 0; +} + +static int +zip_badFile(zip_GPFileData *gPFileData, char *fileName) { + fprintf(stderr, "Error: Bad file format for .zip file.\n"); + if (gPFileData != NULL) + zip_GPFileData_delete(gPFileData); + if (fileName != NULL) + uio_free(fileName); + errno = EINVAL; // Is this the best choice? + return -1; +} + +static inline int +zip_foundFile(uio_GPDir *gPDir, const char *path, zip_GPFileData *gPFileData) { + uio_GPFile *file; + size_t pathLen; + const char *rest; + const char *pathEnd; + const char *start, *end; + char *buf; + + if (path[0] == '/') + path++; + pathLen = strlen(path); + if (path[pathLen - 1] == '/') { + fprintf(stderr, "Warning: '%s' is not a valid file name - skipped.\n", + path); + errno = EISDIR; + return -1; + } + pathEnd = path + pathLen; + + switch (uio_walkGPPath(gPDir, path, pathLen, &gPDir, &rest)) { + case 0: + // The entire path was matched. The last part was not supposed + // to be a dir. + fprintf(stderr, "Warning: '%s' already exists as a dir - " + "skipped.\n", path); + errno = EISDIR; + return -1; + case ENOTDIR: + fprintf(stderr, "Warning: A component to '%s' is not a " + "directory - file skipped.\n", path); + errno = ENOTDIR; + return -1; + case ENOENT: + break; + } + + buf = uio_malloc(pathLen + 1); + getFirstPathComponent(rest, pathEnd, &start, &end); + while (1) { + uio_GPDir *newGPDir; + + if (end == start || (end - start == 1 && start[0] == '.') || + (end - start == 2 && start[0] == '.' && start[1] == '.')) { + fprintf(stderr, "Warning: file '%s' has an invalid path - " + "skipped.\n", path); + uio_free(buf); + errno = EINVAL; + return -1; + } + if (end == pathEnd) { + // This is the last component; it should be the name of the dir. + rest = start; + break; + } + memcpy(buf, start, end - start); + buf[end - start] = '\0'; + newGPDir = uio_GPDir_prepareSubDir(gPDir, buf); + newGPDir->flags |= uio_GPDir_COMPLETE; + // It will be complete when we're done adding + // all files, and it won't be used before that. + uio_GPDir_commitSubDir(gPDir, buf, newGPDir); + + gPDir = newGPDir; + getNextPathComponent(pathEnd, &start, &end); + } + + uio_free(buf); + + file = uio_GPFile_new(gPDir->pRoot, (uio_GPFileExtra) gPFileData, + uio_gPFileFlagsFromPRootFlags(gPDir->pRoot->flags)); + uio_GPDir_addFile(gPDir, rest, file); + return 0; +} + +static inline int +zip_foundDir(uio_GPDir *gPDir, const char *path, zip_GPDirData *gPDirData) { + size_t pathLen; + const char *pathEnd; + const char *rest; + const char *start, *end; + char *buf; + + if (path[0] == '/') + path++; + pathLen = strlen(path); + pathEnd = path + pathLen; + + switch (uio_walkGPPath(gPDir, path, pathLen, &gPDir, &rest)) { + case 0: + // The dir already exists. Only need to add gPDirData + if (gPDir->extra != NULL) { + fprintf(stderr, "Warning: '%s' is present more than once " + "in the zip file. The last entry will be used.\n", + path); + zip_GPDirData_delete(gPDir->extra); + } + gPDir->extra = gPDirData; + return 0; + case ENOTDIR: + fprintf(stderr, "Warning: A component of '%s' is not a " + "directory - file skipped.\n", path); + errno = ENOTDIR; + return -1; + case ENOENT: + break; + } + + buf = uio_malloc(pathLen + 1); + getFirstPathComponent(rest, pathEnd, &start, &end); + while (start < pathEnd) { + uio_GPDir *newGPDir; + + if (end == start || (end - start == 1 && start[0] == '.') || + (end - start == 2 && start[0] == '.' && start[1] == '.')) { + fprintf(stderr, "Warning: directory '%s' has an invalid path - " + "skipped.\n", path); + uio_free(buf); + errno = EINVAL; + return -1; + } + memcpy(buf, start, end - start); + buf[end - start] = '\0'; + newGPDir = uio_GPDir_prepareSubDir(gPDir, buf); + newGPDir->flags |= uio_GPDir_COMPLETE; + // It will be complete when we're done adding + // all files, and it won't be used before that. + uio_GPDir_commitSubDir(gPDir, buf, newGPDir); + + gPDir = newGPDir; + getNextPathComponent(pathEnd, &start, &end); + } + gPDir->extra = gPDirData; + + uio_free(buf); + return 0; +} + +static int +zip_initZipStream(z_stream *zipStream) { + int retVal; + + zipStream->next_in = Z_NULL; + zipStream->avail_in = 0; + zipStream->zalloc = zip_alloc; + zipStream->zfree = zip_free; + zipStream->opaque = NULL; + retVal = inflateInit2(zipStream, -MAX_WBITS); + // Negative window size means that no zlib header is present. + // This feature is undocumented in zlib, but it's used + // in the minizip program from the zlib contrib dir. + // The absolute value is used as real Window size. + if (retVal != Z_OK) { + switch (retVal) { + case Z_MEM_ERROR: + fprintf(stderr, "Error: Not enough memory available for " + " decompression.\n"); + break; + case Z_VERSION_ERROR: + fprintf(stderr, "Error: Incompatible version problem for " + " decompression.\n"); + break; + default: + fprintf(stderr, "Fatal: unknown error from inflateInit().\n"); + abort(); + } + if (zipStream->msg != NULL) + fprintf(stderr, "ZLib reports: %s\n", zipStream->msg); + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + return 0; +} + +static int +zip_unInitZipStream(z_stream *zipStream) { + int retVal; + + retVal = inflateEnd(zipStream); + if (retVal != Z_OK) { + switch (retVal) { + case Z_STREAM_ERROR: + // This means zipStream is bad, which is most likely an + // error in the code using zlib. + fprintf(stderr, "Fatal: internal error using zlib.\n"); + abort(); + break; + default: + fprintf(stderr, "Fatal: unknown error from inflateEnd().\n"); + abort(); + } + if (zipStream->msg != NULL) + fprintf(stderr, "ZLib reports: %s\n", zipStream->msg); + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + return 0; +} + +static int +zip_reInitZipStream(z_stream *zipStream) { + int retVal; + + zipStream->next_in = Z_NULL; + zipStream->avail_in = 0; + retVal = inflateReset(zipStream); + if (retVal != Z_OK) { + switch (retVal) { + case Z_STREAM_ERROR: + // This means zipStream is bad, which is most likely an + // error in the code using zlib. + fprintf(stderr, "Fatal: internal error using zlib.\n"); + abort(); + break; + default: + fprintf(stderr, "Fatal: unknown error from inflateInit().\n"); + abort(); + } + if (zipStream->msg != NULL) + fprintf(stderr, "ZLib reports: %s\n", zipStream->msg); + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + return 0; +} + + +// Used internally by zlib for allocating memory. +static voidpf +zip_alloc(voidpf opaque, uInt items, uInt size) { + (void) opaque; + return (voidpf) uio_calloc((size_t) items, (size_t) size); +} + +// Used internally by zlib for freeing memory. +static void +zip_free(voidpf opaque, voidpf address) { + (void) opaque; + uio_free((void *) address); +} + +static inline zip_GPFileData * +zip_GPFileData_new(void) { + return zip_GPFileData_alloc(); +} + +static inline void +zip_GPFileData_delete(zip_GPFileData *gPFileData) { + zip_GPFileData_free(gPFileData); +} + +static inline zip_GPFileData * +zip_GPFileData_alloc(void) { + zip_GPFileData *result = uio_malloc(sizeof (zip_GPFileData)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(zip_GPFileData, (void *) result); +#endif + return result; +} + +static inline void +zip_GPFileData_free(zip_GPFileData *gPFileData) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(zip_GPFileData, (void *) gPFileData); +#endif + uio_free(gPFileData); +} + +static inline void +zip_GPDirData_delete(zip_GPDirData *gPDirData) { + zip_GPDirData_free(gPDirData); +} + +static inline void +zip_GPDirData_free(zip_GPDirData *gPDirData) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(zip_GPFileData, (void *) gPDirData); +#endif + uio_free(gPDirData); +} + + + diff --git a/src/libs/uio/zip/zip.h b/src/libs/uio/zip/zip.h new file mode 100644 index 0000000..4b2762f --- /dev/null +++ b/src/libs/uio/zip/zip.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +typedef struct zip_Handle *uio_NativeHandle; +typedef void *uio_GPRootExtra; +typedef struct zip_GPFileData *uio_GPFileExtra; +typedef struct zip_GPFileData *uio_GPDirExtra; +typedef struct uio_GPDirEntries_Iterator *uio_NativeEntriesContext; + +#define uio_INTERNAL_PHYSICAL + +#include "../gphys.h" +#include "../iointrn.h" +#include "../uioport.h" +#include "../physical.h" +#include "../types.h" +#include "../fileblock.h" + +#include +#include +#include + +// zip_USE_HEADERS determinines what header for files within a .zip file +// is used when building the directory structure. +// Set to 'zip_USE_CENTRAL_HEADERS' to use the central directory header, +// set to 'zip_USE_LOCAL_HEADERS' to use the local file header. +// Central is highly adviced: it uses much less seeking, and hence is much +// faster. +#define zip_USE_HEADERS zip_USE_CENTRAL_HEADERS +#define zip_USE_CENTRAL_HEADERS 1 +#define zip_USE_LOCAL_HEADERS 2 + +#define zip_INCOMPLETE_STAT + // Ignored unless zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS. + // If defined, extra meta-data for files in the .zip archive + // isn't retrieved from the local file header when zip_stat() + // is called. The uid, gid, file mode, and file times may be + // inaccurate. The advantage is that a possibly costly seek and + // read can be avoided. + +typedef struct zip_GPFileData { + off_t compressedSize; + off_t uncompressedSize; + uio_uint16 compressionFlags; + uio_uint16 compressionMethod; +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS + off_t headerOffset; // start of the local header for this file +#endif + off_t fileOffset; // start of the compressed data in the .zip file + uid_t uid; + gid_t gid; + mode_t mode; + time_t atime; // access time + time_t mtime; // modification time + time_t ctime; // change time +} zip_GPFileData; + +typedef zip_GPFileData zip_GPDirData; +// TODO: some of the fields from zip_GPFileData are not needed for +// directories. A few bytes could be saved here by making a seperate +// structure. + +typedef struct zip_Handle { + uio_GPFile *file; + z_stream zipStream; + uio_FileBlock *fileBlock; + off_t uncompressedOffset; + // seek location in the uncompressed stream + off_t compressedOffset; + // seek location in the compressed stream, from the start + // of the compressed file +} zip_Handle; + + +uio_PRoot *zip_mount(uio_Handle *handle, int flags); +int zip_umount(struct uio_PRoot *); +uio_Handle *zip_open(uio_PDirHandle *pDirHandle, const char *file, int flags, + mode_t mode); +void zip_close(uio_Handle *handle); +int zip_access(uio_PDirHandle *pDirHandle, const char *name, int mode); +int zip_fstat(uio_Handle *handle, struct stat *statBuf); +int zip_stat(uio_PDirHandle *pDirHandle, const char *name, + struct stat *statBuf); +ssize_t zip_read(uio_Handle *handle, void *buf, size_t count); +off_t zip_seek(uio_Handle *handle, off_t offset, int whence); + + + + diff --git a/src/libs/uioutils.h b/src/libs/uioutils.h new file mode 100644 index 0000000..ec8a5a5 --- /dev/null +++ b/src/libs/uioutils.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_UIOUTILS_H_ +#define LIBS_UIOUTILS_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "uio/utils.h" + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_UIOUTILS_H_ */ diff --git a/src/libs/unicode.h b/src/libs/unicode.h new file mode 100644 index 0000000..922a3cc --- /dev/null +++ b/src/libs/unicode.h @@ -0,0 +1,72 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 UNICODE_H +#define UNICODE_H + +#include "port.h" +#include "types.h" + // for uint32 +#include + // For size_t + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef uint32 UniChar; + +#ifdef UNICODE_INTERNAL +# define UNICODE_CHAR unsigned char +#else +# define UNICODE_CHAR char +#endif + +UniChar getCharFromString(const UNICODE_CHAR **ptr); +UniChar getCharFromStringN(const UNICODE_CHAR **ptr, + const UNICODE_CHAR *end); +unsigned char *getLineFromString(const UNICODE_CHAR *start, + const UNICODE_CHAR **end, const UNICODE_CHAR **startNext); +size_t utf8StringCount(const UNICODE_CHAR *start); +size_t utf8StringCountN(const UNICODE_CHAR *start, + const UNICODE_CHAR *end); +int utf8StringPos (const UNICODE_CHAR *pStr, UniChar ch); +unsigned char *utf8StringCopy (UNICODE_CHAR *dst, size_t size, + const UNICODE_CHAR *src); +int utf8StringCompare (const UNICODE_CHAR *str1, const UNICODE_CHAR *str2); +UNICODE_CHAR *skipUTF8Chars(const UNICODE_CHAR *ptr, size_t num); +size_t getUniCharFromString(UniChar *wstr, size_t maxcount, + const UNICODE_CHAR *start); +size_t getUniCharFromStringN(UniChar *wstr, size_t maxcount, + const UNICODE_CHAR *start, const UNICODE_CHAR *end); +int getStringFromChar(UNICODE_CHAR *ptr, size_t size, UniChar ch); +size_t getStringFromWideN(UNICODE_CHAR *ptr, size_t size, + const UniChar *wstr, size_t count); +size_t getStringFromWide(UNICODE_CHAR *ptr, size_t size, + const UniChar *wstr); +int UniChar_isGraph(UniChar ch); +int UniChar_isPrint(UniChar ch); +UniChar UniChar_toUpper(UniChar ch); +UniChar UniChar_toLower(UniChar ch); + +#undef UNICODE_CHAR + +#if defined(__cplusplus) +} +#endif + +#endif /* UNICODE_H */ + diff --git a/src/libs/video/Makeinfo b/src/libs/video/Makeinfo new file mode 100644 index 0000000..1282e49 --- /dev/null +++ b/src/libs/video/Makeinfo @@ -0,0 +1,3 @@ +uqm_CFILES="vfileins.c vresins.c video.c videodec.c vidplayer.c dukvid.c \ + legacyplayer.c" +uqm_HFILES="dukvid.h videodec.h video.h vidintrn.h vidplayer.h" diff --git a/src/libs/video/dukvid.c b/src/libs/video/dukvid.c new file mode 100644 index 0000000..d9cb8fa --- /dev/null +++ b/src/libs/video/dukvid.c @@ -0,0 +1,748 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* DUCK video player + * + * Status: fully functional + */ + +#include "video.h" +#include "dukvid.h" +#include +#include +#include "libs/uio.h" +#include "libs/memlib.h" +#include "endian_uqm.h" + +#define THIS_PTR TFB_VideoDecoder* This + +static const char* dukv_GetName (void); +static bool dukv_InitModule (int flags); +static void dukv_TermModule (void); +static uint32 dukv_GetStructSize (void); +static int dukv_GetError (THIS_PTR); +static bool dukv_Init (THIS_PTR, TFB_PixelFormat* fmt); +static void dukv_Term (THIS_PTR); +static bool dukv_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void dukv_Close (THIS_PTR); +static int dukv_DecodeNext (THIS_PTR); +static uint32 dukv_SeekFrame (THIS_PTR, uint32 frame); +static float dukv_SeekTime (THIS_PTR, float time); +static uint32 dukv_GetFrame (THIS_PTR); +static float dukv_GetTime (THIS_PTR); + +TFB_VideoDecoderFuncs dukv_DecoderVtbl = +{ + dukv_GetName, + dukv_InitModule, + dukv_TermModule, + dukv_GetStructSize, + dukv_GetError, + dukv_Init, + dukv_Term, + dukv_Open, + dukv_Close, + dukv_DecodeNext, + dukv_SeekFrame, + dukv_SeekTime, + dukv_GetFrame, + dukv_GetTime, +}; + +typedef struct tfb_duckvideoheader +{ + uint32 version; + uint32 scrn_x_ofs; // horz screen offset in pixels + uint32 scrn_y_ofs; // vert screen offset in pixels + uint16 wb, hb; // width + height in blocks + sint16 lumas[8]; // future luminance deltas + sint16 chromas[8]; // future chrominance deltas + +} TFB_DuckVideoHeader; + +#define NUM_VEC_ITEMS 0x010 +#define NUM_VECTORS 0x100 + +typedef struct tfb_duckvideodeltas +{ + sint32 lumas[NUM_VECTORS][NUM_VEC_ITEMS]; + sint32 chromas[NUM_VECTORS][NUM_VEC_ITEMS]; + +} TFB_DuckVideoDeltas; + +// specific video decoder struct derived from TFB_VideoDecoder +// the only sane way in C one can :) +typedef struct tfb_duckvideodecoder +{ + // always the first member + TFB_VideoDecoder decoder; + + sint32 last_error; + uio_DirHandle* basedir; + char* basename; + uio_Stream *stream; + +// loaded from disk + uint32* frames; + uint32 cframes; + uint32 iframe; + uint32 version; + uint32 wb, hb; // width, height in blocks + +// generated + TFB_DuckVideoDeltas d; + + uint8* inbuf; + uint32* decbuf; + +} TFB_DuckVideoDecoder; + +#define DUCK_GENERAL_FPS 14.622f +#define DUCK_MAX_FRAME_SIZE 0x8000U +#define DUCK_END_OF_SEQUENCE 1 + +static void +dukv_DecodeFrame (uint8* src_p, uint32* dst_p, uint32 wb, uint32 hb, + TFB_DuckVideoDeltas* deltas) +{ + int iVec; + int iSeq; + uint32 x, y; + sint32 w; + uint32 *d_p0, *d_p1; + int i; + + w = wb * 4; + + iVec = *(src_p++); + iSeq = 0; + + for (y = 0; y < hb; ++y) + { + sint32 accum0, accum1, corr, corr0, corr1, delta; + sint32 pix[4]; + + d_p0 = dst_p + y * w * 2; + d_p1 = d_p0 + w; + + accum0 = 0; + accum1 = 0; + corr0 = 0; + corr1 = 0; + + for (x = 0; x < wb; ++x) + { + if (y == 0) + { + pix[0] = pix[1] = pix[2] = pix[3] = 0; + } + else + { + uint32* p_p = d_p0 - w; + pix[0] = p_p[0]; + pix[1] = p_p[1]; + pix[2] = p_p[2]; + pix[3] = p_p[3]; + } + + // start with chroma delta + delta = deltas->chromas[iVec][iSeq++]; + iSeq++; // correctors ignored + + accum0 += delta >> 1; + if (delta & 1) + { + iVec = *(src_p++); + iSeq = 0; + } + + // line 0 + for (i = 0; i < 4; ++i, ++d_p0) + { + delta = deltas->lumas[iVec][iSeq++]; + corr = deltas->lumas[iVec][iSeq++]; + + accum0 += delta >> 1; + corr0 ^= corr; + pix[i] += accum0; + pix[i] ^= corr0; + + if (delta & 1) + { + iVec = *(src_p++); + iSeq = 0; + } + + *d_p0 = pix[i]; + } + + // line 1 + for (i = 0; i < 4; ++i, ++d_p1) + { + delta = deltas->lumas[iVec][iSeq++]; + corr = deltas->lumas[iVec][iSeq++]; + + accum1 += delta >> 1; + corr1 ^= corr; + pix[i] += accum1; + pix[i] ^= corr1; + + if (delta & 1) + { + iVec = *(src_p++); + iSeq = 0; + } + + *d_p1 = pix[i]; + } + } + } +} + +static void +dukv_DecodeFrameV3 (uint8* src_p, uint32* dst_p, uint32 wb, uint32 hb, + TFB_DuckVideoDeltas* deltas) +{ + int iVec; + int iSeq; + uint32 x, y; + sint32 w; + uint32* d_p; + int i; + + iVec = *(src_p++); + iSeq = 0; + + hb *= 2; + w = wb * 4; + + for (y = 0; y < hb; ++y) + { + sint32 accum, delta, pix; + + d_p = dst_p + y * w; + + accum = 0; + + for (x = 0; x < wb; ++x) + { + // start with chroma delta + delta = deltas->chromas[iVec][iSeq]; + iSeq += 2; // correctors ignored + + accum += delta >> 1; + + if (delta & DUCK_END_OF_SEQUENCE) + { + iVec = *(src_p++); + iSeq = 0; + } + + for (i = 0; i < 4; ++i, ++d_p) + { + if (y == 0) + pix = 0; + else + pix = d_p[-w]; + + // get next luma delta + delta = deltas->lumas[iVec][iSeq]; + iSeq += 2; // correctors ignored + + accum += delta >> 1; + pix += accum; + + if (delta & DUCK_END_OF_SEQUENCE) + { + iVec = *(src_p++); + iSeq = 0; + } + + *d_p = pix; + } + } + } +} + +static bool +dukv_OpenStream (TFB_DuckVideoDecoder* dukv) +{ + char filename[280]; + + strcat (strcpy (filename, dukv->basename), ".duk"); + + return (dukv->stream = + uio_fopen (dukv->basedir, filename, "rb")) != NULL; +} + +static bool +dukv_ReadFrames (TFB_DuckVideoDecoder* dukv) +{ + char filename[280]; + uint32 i; + uio_Stream *fp; + + strcat (strcpy (filename, dukv->basename), ".frm"); + + if (!(fp = uio_fopen (dukv->basedir, filename, "rb"))) + return false; + + // get number of frames + uio_fseek (fp, 0, SEEK_END); + dukv->cframes = uio_ftell (fp) / sizeof (uint32); + uio_fseek (fp, 0, SEEK_SET); + dukv->frames = (uint32*) HMalloc (dukv->cframes * sizeof (uint32)); + + if (uio_fread (dukv->frames, sizeof (uint32), dukv->cframes, + fp) != dukv->cframes) + { + HFree (dukv->frames); + dukv->frames = 0; + return 0; + } + uio_fclose (fp); + + for (i = 0; i < dukv->cframes; ++i) + dukv->frames[i] = UQM_SwapBE32 (dukv->frames[i]); + + return true; +} + +static bool +dukv_ReadVectors (TFB_DuckVideoDecoder* dukv, uint8* vectors) +{ + uio_Stream *fp; + char filename[280]; + int ret; + + strcat (strcpy (filename, dukv->basename), ".tbl"); + + if (!(fp = uio_fopen (dukv->basedir, filename, "rb"))) + return false; + + ret = uio_fread (vectors, NUM_VEC_ITEMS, NUM_VECTORS, fp); + uio_fclose (fp); + + return ret == NUM_VECTORS; +} + +static bool +dukv_ReadHeader (TFB_DuckVideoDecoder* dukv, sint32* pl, sint32* pc) +{ + uio_Stream *fp; + char filename[280]; + int ret; + int i; + TFB_DuckVideoHeader hdr; + + strcat (strcpy (filename, dukv->basename), ".hdr"); + + if (!(fp = uio_fopen (dukv->basedir, filename, "rb"))) + return false; + + ret = uio_fread (&hdr, sizeof (hdr), 1, fp); + uio_fclose (fp); + if (!ret) + return false; + + dukv->version = UQM_SwapBE32 (hdr.version); + dukv->wb = UQM_SwapBE16 (hdr.wb); + dukv->hb = UQM_SwapBE16 (hdr.hb); + + for (i = 0; i < 8; ++i) + { + pl[i] = (sint16) UQM_SwapBE16 (hdr.lumas[i]); + pc[i] = (sint16) UQM_SwapBE16 (hdr.chromas[i]); + } + + dukv->decoder.w = dukv->wb * 4; + dukv->decoder.h = dukv->hb * 4; + + return true; +} + +static sint32 +dukv_make_delta (sint32* protos, bool is_chroma, int i1, int i2) +{ + sint32 d1, d2; + + if (!is_chroma) + { + // 0x421 is (r,g,b)=(1,1,1) in 15bit pixel coding + d1 = (protos[i1] >> 1) * 0x421; + d2 = (protos[i2] >> 1) * 0x421; + return ((d1 << 16) + d2) << 1; + } + else + { + d1 = (protos[i1] << 10) + protos[i2]; + return ((d1 << 16) + d1) << 1; + } +} + +static sint32 +dukv_make_corr (sint32* protos, bool is_chroma, int i1, int i2) +{ + sint32 d1, d2; + + if (!is_chroma) + { + d1 = (protos[i1] & 1) << 15; + d2 = (protos[i2] & 1) << 15; + return (d1 << 16) + d2; + } + else + { + return (i1 << 3) + i2; + } +} + +static void +dukv_DecodeVector (uint8* vec, sint32* p, bool is_chroma, sint32* deltas) +{ + int citems = vec[0]; + int i; + + for (i = 0; i < citems; i += 2, vec += 2, deltas += 2) + { + sint32 d = dukv_make_delta (p, is_chroma, vec[1], vec[2]); + + if (i == citems - 2) + d |= DUCK_END_OF_SEQUENCE; + + deltas[0] = d; + deltas[1] = dukv_make_corr (p, is_chroma, vec[1], vec[2]); + } + +} + +static void +dukv_InitDeltas (TFB_DuckVideoDecoder* dukv, uint8* vectors, + sint32* pl, sint32* pc) +{ + int i; + + for (i = 0; i < NUM_VECTORS; ++i) + { + uint8* vector = vectors + i * NUM_VEC_ITEMS; + dukv_DecodeVector (vector, pl, false, dukv->d.lumas[i]); + dukv_DecodeVector (vector, pc, true, dukv->d.chromas[i]); + } +} + +static inline uint32 +dukv_PixelConv (uint16 pix, const TFB_PixelFormat* fmt) +{ + uint32 r, g, b; + + r = (pix >> 7) & 0xf8; + g = (pix >> 2) & 0xf8; + b = (pix << 3) & 0xf8; + + return + ((r >> fmt->Rloss) << fmt->Rshift) | + ((g >> fmt->Gloss) << fmt->Gshift) | + ((b >> fmt->Bloss) << fmt->Bshift); +} + +static void +dukv_RenderFrame (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + const TFB_PixelFormat* fmt = This->format; + uint32 h, x, y; + uint32* dec = dukv->decbuf; + + h = dukv->decoder.h / 2; + + // separate bpp versions for speed + switch (fmt->BytesPerPixel) + { + case 2: + { + for (y = 0; y < h; ++y) + { + uint16 *dst0, *dst1; + + dst0 = (uint16*) This->callbacks.GetCanvasLine (This, y * 2); + dst1 = (uint16*) This->callbacks.GetCanvasLine (This, y * 2 + 1); + + for (x = 0; x < dukv->decoder.w; ++x, ++dec, ++dst0, ++dst1) + { + uint32 pair = *dec; + *dst0 = dukv_PixelConv ((uint16)(pair >> 16), fmt); + *dst1 = dukv_PixelConv ((uint16)(pair & 0xffff), fmt); + } + } + break; + } + case 3: + { + for (y = 0; y < h; ++y) + { + uint8 *dst0, *dst1; + + dst0 = (uint8*) This->callbacks.GetCanvasLine (This, y * 2); + dst1 = (uint8*) This->callbacks.GetCanvasLine (This, y * 2 + 1); + + for (x = 0; x < dukv->decoder.w; + ++x, ++dec, dst0 += 3, dst1 += 3) + { + uint32 pair = *dec; + *(uint32*)dst0 = + dukv_PixelConv ((uint16)(pair >> 16), fmt); + *(uint32*)dst1 = + dukv_PixelConv ((uint16)(pair & 0xffff), fmt); + } + } + break; + } + case 4: + { + for (y = 0; y < h; ++y) + { + uint32 *dst0, *dst1; + + dst0 = (uint32*) This->callbacks.GetCanvasLine (This, y * 2); + dst1 = (uint32*) This->callbacks.GetCanvasLine (This, y * 2 + 1); + + for (x = 0; x < dukv->decoder.w; ++x, ++dec, ++dst0, ++dst1) + { + uint32 pair = *dec; + *dst0 = dukv_PixelConv ((uint16)(pair >> 16), fmt); + *dst1 = dukv_PixelConv ((uint16)(pair & 0xffff), fmt); + } + } + break; + } + default: + ; + } +} + +static const char* +dukv_GetName (void) +{ + return "DukVid"; +} + +static bool +dukv_InitModule (int flags) +{ + // no flags are defined for now + return true; + + (void)flags; // dodge compiler warning +} + +static void +dukv_TermModule (void) +{ + // do an extensive search on the word 'nothing' +} + +static uint32 +dukv_GetStructSize (void) +{ + return sizeof (TFB_DuckVideoDecoder); +} + +static int +dukv_GetError (THIS_PTR) +{ + return This->error; +} + +static bool +dukv_Init (THIS_PTR, TFB_PixelFormat* fmt) +{ + This->format = fmt; + This->audio_synced = true; + return true; +} + +static void +dukv_Term (THIS_PTR) +{ + //TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + dukv_Close (This); +} + +static bool +dukv_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + char* pext; + sint32 lumas[8], chromas[8]; + uint8* vectors; + + dukv->basedir = dir; + dukv->basename = HMalloc (strlen (filename) + 1); + strcpy (dukv->basename, filename); + pext = strrchr (dukv->basename, '.'); + if (pext) // strip extension + *pext = 0; + + vectors = HMalloc (NUM_VEC_ITEMS * NUM_VECTORS); + + if (!dukv_OpenStream (dukv) + || !dukv_ReadFrames (dukv) + || !dukv_ReadHeader (dukv, lumas, chromas) + || !dukv_ReadVectors (dukv, vectors)) + { + HFree (vectors); + dukv_Close (This); + dukv->last_error = dukve_BadFile; + return false; + } + + dukv_InitDeltas (dukv, vectors, lumas, chromas); + HFree (vectors); + + This->length = (float) dukv->cframes / DUCK_GENERAL_FPS; + This->frame_count = dukv->cframes; + This->interframe_wait = (uint32) (1000.0 / DUCK_GENERAL_FPS); + + dukv->inbuf = HMalloc (DUCK_MAX_FRAME_SIZE); + dukv->decbuf = HMalloc ( + dukv->decoder.w * dukv->decoder.h * sizeof (uint16)); + + return true; +} + +static void +dukv_Close (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + if (dukv->basename) + { + HFree (dukv->basename); + dukv->basename = NULL; + } + if (dukv->frames) + { + HFree (dukv->frames); + dukv->frames = NULL; + } + if (dukv->stream) + { + uio_fclose (dukv->stream); + dukv->stream = NULL; + } + if (dukv->inbuf) + { + HFree (dukv->inbuf); + dukv->inbuf = NULL; + } + if (dukv->decbuf) + { + HFree (dukv->decbuf); + dukv->decbuf = NULL; + } +} + +static int +dukv_DecodeNext (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + uint32 fh[2]; + uint32 vofs; + uint32 vsize; + uint16 ver; + + if (!dukv->stream || dukv->iframe >= dukv->cframes) + return 0; + + uio_fseek (dukv->stream, dukv->frames[dukv->iframe], SEEK_SET); + if (uio_fread (&fh, sizeof (fh), 1, dukv->stream) != 1) + { + dukv->last_error = dukve_EOF; + return 0; + } + + vofs = UQM_SwapBE32 (fh[0]); + vsize = UQM_SwapBE32 (fh[1]); + if (vsize > DUCK_MAX_FRAME_SIZE) + { + dukv->last_error = dukve_OutOfBuf; + return -1; + } + + uio_fseek (dukv->stream, vofs, SEEK_CUR); + if (uio_fread (dukv->inbuf, 1, vsize, dukv->stream) != vsize) + { + dukv->last_error = dukve_EOF; + return 0; + } + + ver = UQM_SwapBE16 (*(uint16*)dukv->inbuf); + if (ver == 0x0300) + dukv_DecodeFrameV3 (dukv->inbuf + 0x10, dukv->decbuf, + dukv->wb, dukv->hb, &dukv->d); + else + dukv_DecodeFrame (dukv->inbuf + 0x10, dukv->decbuf, + dukv->wb, dukv->hb, &dukv->d); + + dukv->iframe++; + + This->callbacks.BeginFrame (This); + dukv_RenderFrame (This); + This->callbacks.EndFrame (This); + + if (!This->audio_synced) + This->callbacks.SetTimer (This, (uint32) (1000.0f / DUCK_GENERAL_FPS)); + + return 1; +} + +static uint32 +dukv_SeekFrame (THIS_PTR, uint32 frame) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + if (frame > dukv->cframes) + frame = dukv->cframes; // EOS + + return dukv->iframe = frame; +} + +static float +dukv_SeekTime (THIS_PTR, float time) +{ + //TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + uint32 frame = (uint32) (time * DUCK_GENERAL_FPS); + + // Note that DUCK_GENERAL_FPS is a float constant + return dukv_SeekFrame (This, frame) / DUCK_GENERAL_FPS; +} + +static uint32 +dukv_GetFrame (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + return dukv->iframe; +} + +static float +dukv_GetTime (THIS_PTR) +{ + TFB_DuckVideoDecoder* dukv = (TFB_DuckVideoDecoder*) This; + + return (float) dukv->iframe / DUCK_GENERAL_FPS; +} diff --git a/src/libs/video/dukvid.h b/src/libs/video/dukvid.h new file mode 100644 index 0000000..a6d70b0 --- /dev/null +++ b/src/libs/video/dukvid.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 LIBS_VIDEO_DUKVID_H_ +#define LIBS_VIDEO_DUKVID_H_ + +#include "libs/video/videodec.h" + +extern TFB_VideoDecoderFuncs dukv_DecoderVtbl; + +typedef enum +{ + // positive values are the same as in errno + dukve_None = 0, + dukve_Unknown = -1, + dukve_BadFile = -2, + dukve_BadArg = -3, + dukve_OutOfBuf = -4, + dukve_EOF = -5, + dukve_Other = -1000, +} DukVid_Error; + +#endif // LIBS_VIDEO_DUKVID_H_ diff --git a/src/libs/video/legacyplayer.c b/src/libs/video/legacyplayer.c new file mode 100644 index 0000000..5dfddad --- /dev/null +++ b/src/libs/video/legacyplayer.c @@ -0,0 +1,81 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "vidintrn.h" +#include "video.h" +#include "vidplayer.h" +#include "libs/memlib.h" + + +LEGACY_VIDEO_REF +PlayLegacyVideo (LEGACY_VIDEO vid) +{ + const char *name, *audname, *speechname; + uint32 loopframe; + LEGACY_VIDEO_REF ref; + VIDEO_TYPE type; + + if (!vid) + return NULL; + ref = HCalloc (sizeof (*ref)); + if (!ref) + return NULL; + name = vid->video; + audname = vid->audio; + speechname = vid->speech; + loopframe = vid->loop; + + ref->vidref = LoadVideoFile (name); + if (!ref->vidref) + return NULL; + if (audname) + ref->audref = LoadMusicFile (audname); + if (speechname) + ref->speechref = LoadMusicFile (speechname); + + type = VidPlayEx (ref->vidref, ref->audref, ref->speechref, loopframe); + if (type == NO_FMV) + { // Video failed to start + StopLegacyVideo (ref); + return NULL; + } + + return ref; +} + +void +StopLegacyVideo (LEGACY_VIDEO_REF ref) +{ + if (!ref) + return; + VidStop (); + + DestroyVideo (ref->vidref); + if (ref->speechref) + DestroyMusic (ref->speechref); + if (ref->audref) + DestroyMusic (ref->audref); + + HFree (ref); +} + +BOOLEAN +PlayingLegacyVideo (LEGACY_VIDEO_REF ref) +{ + if (!ref) + return FALSE; + return TFB_VideoPlaying (ref->vidref); +} diff --git a/src/libs/video/vfileins.c b/src/libs/video/vfileins.c new file mode 100644 index 0000000..26cb7c1 --- /dev/null +++ b/src/libs/video/vfileins.c @@ -0,0 +1,28 @@ +//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/vidlib.h" +#include "video.h" + +VIDEO_REF +LoadVideoFile (const char *pStr) +{ + return _init_video_file (pStr); +} + + diff --git a/src/libs/video/video.c b/src/libs/video/video.c new file mode 100644 index 0000000..dd4cd46 --- /dev/null +++ b/src/libs/video/video.c @@ -0,0 +1,190 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "video.h" + +#include "vidintrn.h" +#include "options.h" +#include "vidplayer.h" +#include "libs/memlib.h" +#include "libs/sndlib.h" + + +#define NULL_VIDEO_REF (0) +static VIDEO_REF _cur_video = NULL_VIDEO_REF; +static MUSIC_REF _cur_speech = 0; + +BOOLEAN +InitVideoPlayer (BOOLEAN useCDROM) + //useCDROM doesn't really apply to us +{ + TFB_PixelFormat fmt; + + TFB_DrawCanvas_GetScreenFormat (&fmt); + if (!VideoDecoder_Init (0, fmt.BitsPerPixel, fmt.Rmask, + fmt.Gmask, fmt.Bmask, 0)) + return FALSE; + + return TFB_InitVideoPlayer (); + + (void)useCDROM; /* dodge compiler warning */ +} + +void +UninitVideoPlayer (void) +{ + TFB_UninitVideoPlayer (); + VideoDecoder_Uninit (); +} + +void +VidStop (void) +{ + if (_cur_speech) + snd_StopSpeech (); + if (_cur_video) + TFB_StopVideo (_cur_video); + _cur_speech = 0; + _cur_video = NULL_VIDEO_REF; +} + +VIDEO_REF +VidPlaying (void) + // this should just probably return BOOLEAN +{ + if (!_cur_video) + return NULL_VIDEO_REF; + + if (TFB_VideoPlaying (_cur_video)) + return _cur_video; + + return NULL_VIDEO_REF; +} + +BOOLEAN +VidProcessFrame (void) +{ + if (!_cur_video) + return FALSE; + return TFB_ProcessVideoFrame (_cur_video); +} + +// return current video position in milliseconds +DWORD +VidGetPosition (void) +{ + if (!VidPlaying ()) + return 0; + return TFB_GetVideoPosition (_cur_video); +} + +BOOLEAN +VidSeek (DWORD pos) + // pos in milliseconds +{ + if (!VidPlaying ()) + return FALSE; + return TFB_SeekVideo (_cur_video, pos); +} + +VIDEO_TYPE +VidPlayEx (VIDEO_REF vid, MUSIC_REF AudRef, MUSIC_REF SpeechRef, + DWORD LoopFrame) +{ + VIDEO_TYPE ret; + + if (!vid) + return NO_FMV; + + if (AudRef) + { + if (vid->hAudio) + DestroyMusic (vid->hAudio); + vid->hAudio = AudRef; + vid->decoder->audio_synced = FALSE; + } + + vid->loop_frame = LoopFrame; + vid->loop_to = 0; + + if (_cur_speech) + snd_StopSpeech (); + if (_cur_video) + TFB_StopVideo (_cur_video); + _cur_speech = 0; + _cur_video = NULL_VIDEO_REF; + + // play video in the center of the screen + if (TFB_PlayVideo (vid, (ScreenWidth - vid->w) / 2, + (ScreenHeight - vid->h) / 2)) + { + _cur_video = vid; + ret = SOFTWARE_FMV; + if (SpeechRef) + { + snd_PlaySpeech (SpeechRef); + _cur_speech = SpeechRef; + } + } + else + { + ret = NO_FMV; + } + + return ret; +} + +VIDEO_TYPE +VidPlay (VIDEO_REF VidRef) +{ + return VidPlayEx (VidRef, 0, 0, VID_NO_LOOP); +} + +VIDEO_REF +_init_video_file (const char *pStr) +{ + TFB_VideoClip* vid; + TFB_VideoDecoder* dec; + + dec = VideoDecoder_Load (contentDir, pStr); + if (!dec) + return NULL_VIDEO_REF; + + vid = HCalloc (sizeof (*vid)); + vid->decoder = dec; + vid->length = dec->length; + vid->w = vid->decoder->w; + vid->h = vid->decoder->h; + vid->guard = CreateMutex ("video guard", SYNC_CLASS_VIDEO); + + return (VIDEO_REF) vid; +} + +BOOLEAN +DestroyVideo (VIDEO_REF vid) +{ + if (!vid) + return FALSE; + + // just some armouring; should already be stopped + TFB_StopVideo (vid); + + VideoDecoder_Free (vid->decoder); + DestroyMutex (vid->guard); + HFree (vid); + + return TRUE; +} diff --git a/src/libs/video/video.h b/src/libs/video/video.h new file mode 100644 index 0000000..5e76aa4 --- /dev/null +++ b/src/libs/video/video.h @@ -0,0 +1,56 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_VIDEO_VIDEO_H_ +#define LIBS_VIDEO_VIDEO_H_ + +#include "libs/vidlib.h" +#include "libs/sndlib.h" +#include "libs/graphics/tfb_draw.h" +#include "types.h" +#include "videodec.h" +#include "libs/sound/sound.h" + + +typedef struct tfb_videoclip +{ + TFB_VideoDecoder *decoder; // decoder to read from + float length; // total length of clip seconds + uint32 w, h; + + // video player data + RECT dst_rect; // destination screen rect + RECT src_rect; // source rect + MUSIC_REF hAudio; + uint32 frame_time; // time when next frame should be rendered + TFB_Image* frame; // frame preped and optimized for rendering + uint32 cur_frame; // index of frame currently displayed + bool playing; + bool own_audio; + uint32 loop_frame; // frame index to loop from + uint32 loop_to; // frame index to loop to + + Mutex guard; + uint32 want_frame; // audio-signaled desired frame index + int lag_cnt; // N of frames video is behind or ahead of audio + + void* data; // user-defined data + +} TFB_VideoClip; + +extern VIDEO_REF _init_video_file(const char *pStr); + +#endif // LIBS_VIDEO_VIDEO_H_ diff --git a/src/libs/video/videodec.c b/src/libs/video/videodec.c new file mode 100644 index 0000000..b99a442 --- /dev/null +++ b/src/libs/video/videodec.c @@ -0,0 +1,363 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include "video.h" +#include "videodec.h" +#include "dukvid.h" +#include "libs/log.h" +#include "libs/memlib.h" + +#define MAX_REG_DECODERS 31 + +static bool vd_inited = false; +static TFB_PixelFormat vd_vidfmt; +static int vd_flags = 0; + +struct TFB_RegVideoDecoder +{ + bool builtin; + bool used; // ever used indicator + const char* ext; + const TFB_VideoDecoderFuncs* funcs; +}; +static TFB_RegVideoDecoder vd_decoders[MAX_REG_DECODERS + 1] = +{ + {true, true, "duk", &dukv_DecoderVtbl}, + {false, false, NULL, NULL}, // null term +}; + +static void vd_computeMasks (uint32 mask, DWORD* shift, DWORD* loss); + +const char* +VideoDecoder_GetName (TFB_VideoDecoder *decoder) +{ + if (!decoder || !decoder->funcs) + return "(Null)"; + return decoder->funcs->GetName (); +} + +bool +VideoDecoder_Init (int flags, int depth, uint32 Rmask, uint32 Gmask, + uint32 Bmask, uint32 Amask) +{ + TFB_RegVideoDecoder* info; + + vd_inited = false; + + if (depth < 15 || depth > 32) + { + log_add (log_Error, "VideoDecoder_Init: " + "Unsupported video depth %d", depth); + return false; + } + + if ((Rmask & Gmask) || (Rmask & Bmask) || (Rmask & Amask) || + (Gmask & Bmask) || (Gmask & Amask) || (Bmask & Amask)) + { + log_add (log_Error, "VideoDecoder_Init: Invalid channel masks"); + return false; + } + + // BEGIN: adapted from SDL + vd_vidfmt.BitsPerPixel = depth; + vd_vidfmt.BytesPerPixel = (depth + 7) / 8; + vd_vidfmt.Rmask = Rmask; + vd_vidfmt.Gmask = Gmask; + vd_vidfmt.Bmask = Bmask; + vd_vidfmt.Amask = Amask; + vd_computeMasks (Rmask, &vd_vidfmt.Rshift, &vd_vidfmt.Rloss); + vd_computeMasks (Gmask, &vd_vidfmt.Gshift, &vd_vidfmt.Gloss); + vd_computeMasks (Bmask, &vd_vidfmt.Bshift, &vd_vidfmt.Bloss); + vd_computeMasks (Amask, &vd_vidfmt.Ashift, &vd_vidfmt.Aloss); + // END: adapted from SDL + + // init built-in decoders + for (info = vd_decoders; info->ext; info++) + { + if (!info->funcs->InitModule (flags)) + { + log_add (log_Error, "VideoDecoder_Init(): " + "%s video decoder init failed", + info->funcs->GetName ()); + } + } + + vd_flags = flags; + vd_inited = true; + + return true; +} + +void +VideoDecoder_Uninit (void) +{ + TFB_RegVideoDecoder* info; + + // uninit all decoders + // and unregister loaded decoders + for (info = vd_decoders; info->used; info++) + { + if (info->ext) // check if present + info->funcs->TermModule (); + + if (!info->builtin) + { + info->used = false; + info->ext = NULL; + } + } + + vd_inited = false; +} + +TFB_RegVideoDecoder* +VideoDecoder_Register (const char* fileext, TFB_VideoDecoderFuncs* decvtbl) +{ + TFB_RegVideoDecoder* info; + TFB_RegVideoDecoder* newslot = NULL; + + if (!decvtbl) + { + log_add (log_Warning, "VideoDecoder_Register(): Null decoder table"); + return NULL; + } + if (!fileext) + { + log_add (log_Warning, "VideoDecoder_Register(): Bad file type for %s", + decvtbl->GetName ()); + return NULL; + } + + // check if extension already registered + for (info = vd_decoders; info->used && + (!info->ext || strcmp (info->ext, fileext) != 0); + ++info) + { + // and pick up an empty slot (where available) + if (!newslot && !info->ext) + newslot = info; + } + + if (info >= vd_decoders + MAX_REG_DECODERS) + { + log_add (log_Warning, "VideoDecoder_Register(): Decoders limit reached"); + return NULL; + } + else if (info->ext) + { + log_add (log_Warning, "VideoDecoder_Register(): " + "'%s' decoder already registered (%s denied)", + fileext, decvtbl->GetName ()); + return NULL; + } + + if (!decvtbl->InitModule (vd_flags)) + { + log_add (log_Warning, "VideoDecoder_Register(): %s decoder init failed", + decvtbl->GetName ()); + return NULL; + } + + if (!newslot) + { + newslot = info; + newslot->used = true; + // make next one a term + info[1].builtin = false; + info[1].used = false; + info[1].ext = NULL; + } + + newslot->ext = fileext; + newslot->funcs = decvtbl; + + return newslot; +} + +void +VideoDecoder_Unregister (TFB_RegVideoDecoder* regdec) +{ + if (regdec < vd_decoders || regdec >= vd_decoders + MAX_REG_DECODERS || + !regdec->ext || !regdec->funcs) + { + log_add (log_Warning, "VideoDecoder_Unregister(): " + "Invalid or expired decoder passed"); + return; + } + + regdec->funcs->TermModule (); + regdec->ext = NULL; + regdec->funcs = NULL; +} + +const TFB_VideoDecoderFuncs* +VideoDecoder_Lookup (const char* fileext) +{ + TFB_RegVideoDecoder* info; + + for (info = vd_decoders; info->used && + (!info->ext || strcmp (info->ext, fileext) != 0); + ++info) + ; + return info->ext ? info->funcs : NULL; +} + +TFB_VideoDecoder* +VideoDecoder_Load (uio_DirHandle *dir, const char *filename) +{ + const char* pext; + TFB_RegVideoDecoder* info; + TFB_VideoDecoder* decoder; + + + if (!vd_inited) + return NULL; + + pext = strrchr (filename, '.'); + if (!pext) + { + log_add (log_Warning, "VideoDecoder_Load: Unknown file type"); + return NULL; + } + ++pext; + + for (info = vd_decoders; info->used && + (!info->ext || strcmp (info->ext, pext) != 0); + ++info) + ; + if (!info->ext) + { + log_add (log_Warning, "VideoDecoder_Load: Unsupported file type"); + return NULL; + } + + decoder = HCalloc (info->funcs->GetStructSize ()); + decoder->funcs = info->funcs; + if (!decoder->funcs->Init (decoder, &vd_vidfmt)) + { + log_add (log_Warning, "VideoDecoder_Load: " + "Cannot init '%s' decoder, code %d", + decoder->funcs->GetName (), + decoder->funcs->GetError (decoder)); + HFree (decoder); + return NULL; + } + + decoder->dir = dir; + decoder->filename = (char *) HMalloc (strlen (filename) + 1); + strcpy (decoder->filename, filename); + decoder->error = VIDEODECODER_OK; + + if (!decoder->funcs->Open (decoder, dir, filename)) + { + log_add (log_Warning, "VideoDecoder_Load: " + "'%s' decoder did not load %s, code %d", + decoder->funcs->GetName (), filename, + decoder->funcs->GetError (decoder)); + + VideoDecoder_Free (decoder); + return NULL; + } + + return decoder; +} + +// return: >0 = OK, 0 = EOF, <0 = Error +int +VideoDecoder_Decode (TFB_VideoDecoder *decoder) +{ + int ret; + + if (!decoder) + return 0; + + decoder->cur_frame = decoder->funcs->GetFrame (decoder); + decoder->pos = decoder->funcs->GetTime (decoder); + + ret = decoder->funcs->DecodeNext (decoder); + if (ret == 0) + decoder->error = VIDEODECODER_EOF; + else if (ret < 0) + decoder->error = VIDEODECODER_ERROR; + else + decoder->error = VIDEODECODER_OK; + + return ret; +} + +float +VideoDecoder_Seek (TFB_VideoDecoder *decoder, float pos) +{ + if (!decoder) + return 0.0; + + decoder->pos = decoder->funcs->SeekTime (decoder, pos); + decoder->cur_frame = decoder->funcs->GetFrame (decoder); + + return decoder->pos; +} + +uint32 +VideoDecoder_SeekFrame (TFB_VideoDecoder *decoder, uint32 frame) +{ + if (!decoder) + return 0; + + decoder->cur_frame = decoder->funcs->SeekFrame (decoder, frame); + decoder->pos = decoder->funcs->GetTime (decoder); + + return decoder->cur_frame; +} + +void +VideoDecoder_Rewind (TFB_VideoDecoder *decoder) +{ + if (!decoder) + return; + + VideoDecoder_Seek (decoder, 0); +} + +void +VideoDecoder_Free (TFB_VideoDecoder *decoder) +{ + if (!decoder) + return; + + decoder->funcs->Close (decoder); + decoder->funcs->Term (decoder); + + HFree (decoder->filename); + HFree (decoder); +} + +// BEGIN: adapted from SDL +static void +vd_computeMasks (uint32 mask, DWORD* shift, DWORD* loss) +{ + *shift = 0; + *loss = 8; + if (mask) + { + for (; !(mask & 1); mask >>= 1 ) + ++*shift; + + for (; (mask & 1); mask >>= 1 ) + --*loss; + } +} +// END: adapted from SDL diff --git a/src/libs/video/videodec.h b/src/libs/video/videodec.h new file mode 100644 index 0000000..2d75c98 --- /dev/null +++ b/src/libs/video/videodec.h @@ -0,0 +1,124 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBS_VIDEO_VIDEODEC_H_ +#define LIBS_VIDEO_VIDEODEC_H_ + +#include "libs/vidlib.h" +#include "libs/video/video.h" +#include "libs/reslib.h" + +// forward-declare +typedef struct tfb_videodecoder TFB_VideoDecoder; + +#define THIS_PTR TFB_VideoDecoder* + +typedef struct tfb_videodecoderfunc +{ + const char* (* GetName) (void); + bool (* InitModule) (int flags); + void (* TermModule) (void); + uint32 (* GetStructSize) (void); + int (* GetError) (THIS_PTR); + bool (* Init) (THIS_PTR, TFB_PixelFormat* fmt); + void (* Term) (THIS_PTR); + bool (* Open) (THIS_PTR, uio_DirHandle *dir, const char *filename); + void (* Close) (THIS_PTR); + int (* DecodeNext) (THIS_PTR); + uint32 (* SeekFrame) (THIS_PTR, uint32 frame); + float (* SeekTime) (THIS_PTR, float time); + uint32 (* GetFrame) (THIS_PTR); + float (* GetTime) (THIS_PTR); + +} TFB_VideoDecoderFuncs; + +// decoder will call these to get info +// from the player +typedef struct tfb_videocallbacks +{ + // any decoder calls these + void (* BeginFrame) (THIS_PTR); + void (* EndFrame) (THIS_PTR); + void* (* GetCanvasLine) (THIS_PTR, uint32 line); + // non-audio-driven decoders call this to figure out + // when the next frame should be drawn + uint32 (* GetTicks) (THIS_PTR); + // non-audio-driven decoders call this to inform + // the player when the next frame should be drawn + bool (* SetTimer) (THIS_PTR, uint32 msecs); + +} TFB_VideoCallbacks; + +#undef THIS_PTR + +struct tfb_videodecoder +{ + // decoder virtual funcs - R/O + const TFB_VideoDecoderFuncs *funcs; + // video formats - R/O + const TFB_PixelFormat *format; + // decoder-set data - R/O + uint32 w, h; + float length; // total length in seconds + uint32 frame_count; + uint32 interframe_wait; // nominal interframe delay in msecs + bool audio_synced; + // decoder callbacks + TFB_VideoCallbacks callbacks; + + // other - public + bool looping; + void* data; // user-defined data + // info - public R/O + sint32 error; + float pos; // position in seconds + uint32 cur_frame; + + // semi-private + uio_DirHandle *dir; + char *filename; + +}; + +// return values +enum +{ + VIDEODECODER_OK, + VIDEODECODER_ERROR, + VIDEODECODER_EOF, +}; + +typedef struct TFB_RegVideoDecoder TFB_RegVideoDecoder; + +TFB_RegVideoDecoder* VideoDecoder_Register (const char* fileext, + TFB_VideoDecoderFuncs* decvtbl); +void VideoDecoder_Unregister (TFB_RegVideoDecoder* regdec); +const TFB_VideoDecoderFuncs* VideoDecoder_Lookup (const char* fileext); + +bool VideoDecoder_Init (int flags, int depth, uint32 Rmask, uint32 Gmask, + uint32 Bmask, uint32 Amask); +void VideoDecoder_Uninit (void); +TFB_VideoDecoder* VideoDecoder_Load (uio_DirHandle *dir, + const char *filename); +int VideoDecoder_Decode (TFB_VideoDecoder *decoder); +float VideoDecoder_Seek (TFB_VideoDecoder *decoder, float time_pos); +uint32 VideoDecoder_SeekFrame (TFB_VideoDecoder *decoder, uint32 frame_pos); +void VideoDecoder_Rewind (TFB_VideoDecoder *decoder); +void VideoDecoder_Free (TFB_VideoDecoder *decoder); +const char* VideoDecoder_GetName (TFB_VideoDecoder *decoder); + + +#endif // LIBS_VIDEO_VIDEODEC_H_ diff --git a/src/libs/video/vidintrn.h b/src/libs/video/vidintrn.h new file mode 100644 index 0000000..40a19e4 --- /dev/null +++ b/src/libs/video/vidintrn.h @@ -0,0 +1,41 @@ +// Copyright 2008 Michael Martin + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 VIDINTERN_H_ +#define VIDINTERN_H_ + +#include "types.h" +#include "libs/vidlib.h" +#include "libs/threadlib.h" + +struct legacy_video_desc +{ + char *video, *audio, *speech; + uint32 loop; +}; + +typedef struct legacy_video_desc LEGACY_VIDEO_DESC; + +struct legacy_video_ref +{ + VIDEO_REF vidref; + MUSIC_REF audref; + MUSIC_REF speechref; +}; + +#endif diff --git a/src/libs/video/vidplayer.c b/src/libs/video/vidplayer.c new file mode 100644 index 0000000..09a506d --- /dev/null +++ b/src/libs/video/vidplayer.c @@ -0,0 +1,481 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "vidplayer.h" + +#include "vidintrn.h" +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/tfb_draw.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "libs/sndlib.h" + +// video callbacks +static void vp_BeginFrame (TFB_VideoDecoder*); +static void vp_EndFrame (TFB_VideoDecoder*); +static void* vp_GetCanvasLine (TFB_VideoDecoder*, uint32 line); +static uint32 vp_GetTicks (TFB_VideoDecoder*); +static bool vp_SetTimer (TFB_VideoDecoder*, uint32 msecs); + + +static const TFB_VideoCallbacks vp_DecoderCBs = +{ + vp_BeginFrame, + vp_EndFrame, + vp_GetCanvasLine, + vp_GetTicks, + vp_SetTimer +}; + +// audio stream callbacks +static bool vp_AudioStart (TFB_SoundSample* sample); +static void vp_AudioEnd (TFB_SoundSample* sample); +static void vp_BufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag); +static void vp_QueueBuffer (TFB_SoundSample* sample, audio_Object buffer); + +static const TFB_SoundCallbacks vp_AudioCBs = +{ + vp_AudioStart, + NULL, + vp_AudioEnd, + vp_BufferTag, + vp_QueueBuffer +}; + + +bool +TFB_InitVideoPlayer (void) +{ + // now just a stub + return true; +} + +void +TFB_UninitVideoPlayer (void) +{ + // now just a stub +} + +static inline sint32 +msecToTimeCount (sint32 msec) +{ + return msec * ONE_SECOND / 1000; +} + +// audio-synced video playback frame function +// the frame rate and timing is dictated by the audio +static bool +processAudioSyncedFrame (VIDEO_REF vid) +{ +#define MAX_FRAME_LAG 8 +#define LAG_FRACTION 6 +#define SYNC_BIAS 1 / 3 + int ret; + uint32 want_frame; + uint32 prev_want_frame; + sint32 wait_msec; + CONTEXT oldContext; + TimeCount Now = GetTimeCounter (); + + if (!vid->playing) + return false; + + if (Now < vid->frame_time) + return true; // not time yet + + LockMutex (vid->guard); + want_frame = vid->want_frame; + UnlockMutex (vid->guard); + + if (want_frame >= vid->decoder->frame_count) + { + vid->playing = false; + return false; + } + + // this works like so (audio-synced): + // 1. you call VideoDecoder_Seek() [when necessary] and + // VideoDecoder_Decode() + // 2. wait till it's time for this frame to be drawn + // the timeout is necessary because the audio signaling is not + // precise (see vp_AudioStart, vp_AudioEnd, vp_BufferTag) + // 3. output the frame; if the audio is behind, the lag counter + // goes up; if the video is behind, the lag counter goes down + // 4. set the next frame timeout; lag counter increases or + // decreases the timeout to allow audio or video to catch up + // 5. on a seek operation, the audio stream is moved to the + // correct position and then the audio signals the frame + // that should be rendered + // The system of timeouts and lag counts should make the video + // *relatively* smooth + // + prev_want_frame = vid->cur_frame - vid->lag_cnt; + if (want_frame > prev_want_frame - MAX_FRAME_LAG + && want_frame <= prev_want_frame + MAX_FRAME_LAG) + { + // we will draw the next frame right now, thus +1 + vid->lag_cnt = vid->cur_frame + 1 - want_frame; + } + else + { // out of sequence frame, let's get it + vid->lag_cnt = 0; + vid->cur_frame = VideoDecoder_SeekFrame (vid->decoder, want_frame); + ret = VideoDecoder_Decode (vid->decoder); + if (ret < 0) + { // decoder returned a failure + vid->playing = false; + return false; + } + } + vid->cur_frame = vid->decoder->cur_frame; + + // draw the frame + // We have the cliprect precalculated and don't need the rest + oldContext = SetContext (NULL); + TFB_DrawScreen_Image (vid->frame, + vid->dst_rect.corner.x, vid->dst_rect.corner.y, 0, 0, + NULL, DRAW_REPLACE_MODE, TFB_SCREEN_MAIN); + SetContext (oldContext); + FlushGraphics (); // needed to prevent half-frame updates + + // increase interframe with positive lag-count to allow audio to catch up + // decrease interframe with negative lag-count to allow video to catch up + wait_msec = vid->decoder->interframe_wait + - (int)vid->decoder->interframe_wait * SYNC_BIAS + + (int)vid->decoder->interframe_wait * vid->lag_cnt / LAG_FRACTION; + vid->frame_time = Now + msecToTimeCount (wait_msec); + + ret = VideoDecoder_Decode (vid->decoder); + if (ret < 0) + { + // TODO: decide what to do on error + } + + return vid->playing; +} + +// audio-independent video playback frame function +// the frame rate and timing is dictated by the video decoder +static bool +processMuteFrame (VIDEO_REF vid) +{ + int ret; + TimeCount Now = GetTimeCounter (); + + if (!vid->playing) + return false; + + // this works like so: + // 1. you call VideoDecoder_Seek() [when necessary] and + // VideoDecoder_Decode() + // 2. the decoder calls back vp_GetTicks() and vp_SetTimer() + // to figure out and tell you when to render the frame + // being decoded + // On a seek operation, the decoder should reset its internal + // clock and call vp_GetTicks() again + // + if (Now >= vid->frame_time) + { + CONTEXT oldContext; + + vid->cur_frame = vid->decoder->cur_frame; + + // We have the cliprect precalculated and don't need the rest + oldContext = SetContext (NULL); + TFB_DrawScreen_Image (vid->frame, + vid->dst_rect.corner.x, vid->dst_rect.corner.y, 0, 0, + NULL, DRAW_REPLACE_MODE, TFB_SCREEN_MAIN); + SetContext (oldContext); + FlushGraphics (); // needed to prevent half-frame updates + + if (vid->cur_frame == vid->loop_frame) + VideoDecoder_SeekFrame (vid->decoder, vid->loop_to); + + ret = VideoDecoder_Decode (vid->decoder); + if (ret <= 0) + vid->playing = false; + } + + return vid->playing; +} + +bool +TFB_PlayVideo (VIDEO_REF vid, uint32 x, uint32 y) +{ + RECT scrn_r; + RECT clip_r = {{0, 0}, {vid->w, vid->h}}; + RECT vid_r = {{0, 0}, {ScreenWidth, ScreenHeight}}; + RECT dr = {{x, y}, {vid->w, vid->h}}; + RECT sr; + bool loop_music = false; + int ret; + + if (!vid) + return false; + + // calculate the frame-source and screen-destination rects + GetContextClipRect (&scrn_r); + if (!BoxIntersect(&scrn_r, &vid_r, &scrn_r)) + return false; // drawing outside visible + + sr = dr; + sr.corner.x = -sr.corner.x; + sr.corner.y = -sr.corner.y; + if (!BoxIntersect (&clip_r, &sr, &sr)) + return false; // drawing outside visible + + dr.corner.x += scrn_r.corner.x; + dr.corner.y += scrn_r.corner.y; + if (!BoxIntersect (&scrn_r, &dr, &vid->dst_rect)) + return false; // drawing outside visible + + vid->src_rect = vid->dst_rect; + vid->src_rect.corner.x = sr.corner.x; + vid->src_rect.corner.y = sr.corner.y; + + vid->decoder->callbacks = vp_DecoderCBs; + vid->decoder->data = vid; + + vid->frame = TFB_DrawImage_CreateForScreen (vid->w, vid->h, FALSE); + vid->cur_frame = -1; + vid->want_frame = -1; + + if (!vid->hAudio) + { + vid->hAudio = LoadMusicFile (vid->decoder->filename); + vid->own_audio = true; + } + + if (vid->decoder->audio_synced) + { + if (!vid->hAudio) + { + log_add (log_Warning, "TFB_PlayVideo: " + "Cannot load sound-track for audio-synced video"); + return false; + } + + TFB_SetSoundSampleCallbacks (*vid->hAudio, &vp_AudioCBs); + TFB_SetSoundSampleData (*vid->hAudio, vid); + } + + // get the first frame + ret = VideoDecoder_Decode (vid->decoder); + if (ret < 0) + return false; + + vid->playing = true; + + loop_music = !vid->decoder->audio_synced && vid->loop_frame != VID_NO_LOOP; + if (vid->hAudio) + PLRPlaySong (vid->hAudio, loop_music, 1); + + if (vid->decoder->audio_synced) + { + // draw the first frame now + vid->frame_time = GetTimeCounter (); + } + + return true; +} + +void +TFB_StopVideo (VIDEO_REF vid) +{ + if (!vid) + return; + + vid->playing = false; + + if (vid->hAudio) + { + PLRStop (vid->hAudio); + if (vid->own_audio) + { + DestroyMusic (vid->hAudio); + vid->hAudio = 0; + vid->own_audio = false; + } + } + if (vid->frame) + { + TFB_DrawScreen_DeleteImage (vid->frame); + vid->frame = NULL; + } +} + +bool +TFB_VideoPlaying (VIDEO_REF vid) +{ + if (!vid) + return false; + + return vid->playing; +} + +bool +TFB_ProcessVideoFrame (VIDEO_REF vid) +{ + if (!vid) + return false; + + if (vid->decoder->audio_synced) + return processAudioSyncedFrame (vid); + else + return processMuteFrame (vid); +} + +uint32 +TFB_GetVideoPosition (VIDEO_REF vid) +{ + uint32 pos; + + if (!TFB_VideoPlaying (vid)) + return 0; + + LockMutex (vid->guard); + pos = (uint32) (vid->decoder->pos * 1000); + UnlockMutex (vid->guard); + + return pos; +} + +bool +TFB_SeekVideo (VIDEO_REF vid, uint32 pos) +{ + if (!TFB_VideoPlaying (vid)) + return false; + + if (vid->decoder->audio_synced) + { + PLRSeek (vid->hAudio, pos); + TaskSwitch (); + return true; + } + else + { // TODO: Non-a/s decoder seeking is not supported yet + // Decide what to do with these. Seeking this kind of + // video is trivial, but we may not want to do it. + // The only non-a/s videos right now are ship spins. + return false; + } +} + +static void +vp_BeginFrame (TFB_VideoDecoder* decoder) +{ + TFB_VideoClip* vid = decoder->data; + + if (vid) + TFB_DrawCanvas_Lock (vid->frame->NormalImg); +} + +static void +vp_EndFrame (TFB_VideoDecoder* decoder) +{ + TFB_VideoClip* vid = decoder->data; + + if (vid) + TFB_DrawCanvas_Unlock (vid->frame->NormalImg); +} + +static void* +vp_GetCanvasLine (TFB_VideoDecoder* decoder, uint32 line) +{ + TFB_VideoClip* vid = decoder->data; + + if (!vid) + return NULL; + + return TFB_DrawCanvas_GetLine (vid->frame->NormalImg, line); +} + +static uint32 +vp_GetTicks (TFB_VideoDecoder* decoder) +{ + uint32 ctr = GetTimeCounter (); + return (ctr / ONE_SECOND) * 1000 + ((ctr % ONE_SECOND) * 1000) / ONE_SECOND; + + (void)decoder; // gobble up compiler warning +} + +static bool +vp_SetTimer (TFB_VideoDecoder* decoder, uint32 msecs) +{ + TFB_VideoClip* vid = decoder->data; + + if (!vid) + return false; + + // time when next frame should be displayed + vid->frame_time = GetTimeCounter () + msecs * ONE_SECOND / 1000; + return true; +} + +static bool +vp_AudioStart (TFB_SoundSample* sample) +{ + TFB_VideoClip* vid = TFB_GetSoundSampleData (sample); + TFB_SoundDecoder *decoder; + + assert (sizeof (intptr_t) >= sizeof (vid)); + assert (vid != NULL); + + decoder = TFB_GetSoundSampleDecoder (sample); + + LockMutex (vid->guard); + vid->want_frame = SoundDecoder_GetFrame (decoder); + UnlockMutex (vid->guard); + + return true; +} + +static void +vp_AudioEnd (TFB_SoundSample* sample) +{ + TFB_VideoClip* vid = TFB_GetSoundSampleData (sample); + + assert (vid != NULL); + + LockMutex (vid->guard); + vid->want_frame = vid->decoder->frame_count; // end it + UnlockMutex (vid->guard); +} + +static void +vp_BufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag) +{ + TFB_VideoClip* vid = TFB_GetSoundSampleData (sample); + uint32 frame = (uint32) tag->data; + + assert (sizeof (tag->data) >= sizeof (frame)); + assert (vid != NULL); + + LockMutex (vid->guard); + vid->want_frame = frame; // let it go! + UnlockMutex (vid->guard); +} + +static void +vp_QueueBuffer (TFB_SoundSample* sample, audio_Object buffer) +{ + //TFB_VideoClip* vid = (TFB_VideoClip*) TFB_GetSoundSampleData (sample); + TFB_SoundDecoder *decoder = TFB_GetSoundSampleDecoder (sample); + + TFB_TagBuffer (sample, buffer, + (intptr_t) SoundDecoder_GetFrame (decoder)); +} + diff --git a/src/libs/video/vidplayer.h b/src/libs/video/vidplayer.h new file mode 100644 index 0000000..78e4edb --- /dev/null +++ b/src/libs/video/vidplayer.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 LIBS_VIDEO_VIDPLAYER_H_ +#define LIBS_VIDEO_VIDPLAYER_H_ + +#include "video.h" + +extern bool TFB_InitVideoPlayer (void); +extern void TFB_UninitVideoPlayer (void); +extern bool TFB_PlayVideo (VIDEO_REF VidRef, uint32 x, uint32 y); +extern void TFB_StopVideo (VIDEO_REF VidRef); +extern bool TFB_VideoPlaying (VIDEO_REF VidRef); +extern bool TFB_ProcessVideoFrame (VIDEO_REF vid); +extern uint32 TFB_GetVideoPosition (VIDEO_REF VidRef); +extern bool TFB_SeekVideo (VIDEO_REF VidRef, uint32 pos); + +#endif // LIBS_VIDEO_VIDPLAYER_H_ diff --git a/src/libs/video/vresins.c b/src/libs/video/vresins.c new file mode 100644 index 0000000..dbb18e0 --- /dev/null +++ b/src/libs/video/vresins.c @@ -0,0 +1,186 @@ +// Copyright 2008 Michael Martin + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#include "vidintrn.h" +#include "libs/log.h" +#include "libs/memlib.h" + + +static BOOLEAN +FreeLegacyVideoData (void *data) +{ + LEGACY_VIDEO pLV; + if (!data) + return FALSE; + + pLV = (LEGACY_VIDEO) data; + if (pLV->video) + HFree (pLV->video); + if (pLV->audio) + HFree (pLV->audio); + if (pLV->speech) + HFree (pLV->speech); + HFree (pLV); + + return TRUE; +} + +static void +GetLegacyVideoData (const char *path, RESOURCE_DATA *resdata) +{ + void *result = NULL; + char paths[1024], *audio_path, *speech_path, *loop_str; + uint32 LoopFrame = VID_NO_LOOP; + + /* Parse out the video components. */ + strncpy (paths, path, 1023); + paths[1023] = '\0'; + audio_path = strchr (paths, ':'); + if (audio_path == NULL) + { + speech_path = NULL; + loop_str = NULL; + } + else + { + *audio_path = '\0'; + audio_path++; + + speech_path = strchr (audio_path, ':'); + if (speech_path == NULL) + { + loop_str = NULL; + } + else + { + *speech_path = '\0'; + speech_path++; + + loop_str = strchr (speech_path, ':'); + if (loop_str != NULL) { + *loop_str = '\0'; + loop_str++; + } + } + } + + log_add (log_Info, "\t'%s' -- video", paths); + if (audio_path) + log_add (log_Info, "\t'%s' -- audio", audio_path); + else + log_add (log_Info, "\tNo associated audio"); + if (speech_path) + log_add (log_Info, "\t'%s' -- speech path", speech_path); + else + log_add (log_Info, "\tNo associated speech"); + if (loop_str) + { + char *end; + LoopFrame = strtol (loop_str, &end, 10); + // We allow whitespace at the end, but nothing printable. + if (*end > 32) { + log_add (log_Warning, "Warning: Unparsable loop frame '%s'. Disabling loop.", loop_str); + LoopFrame = VID_NO_LOOP; + } + log_add (log_Info, "\tLoop frame is %u", LoopFrame); + } + else + log_add (log_Info, "\tNo specified loop frame"); + + result = HMalloc (sizeof (LEGACY_VIDEO_DESC)); + if (result) + { + LEGACY_VIDEO pLV = (LEGACY_VIDEO) result; + int len; + pLV->video = NULL; + pLV->audio = NULL; + pLV->speech = NULL; + pLV->loop = LoopFrame; + + len = strlen(paths)+1; + pLV->video = (char *)HMalloc (len); + if (!pLV->video) + { + log_add (log_Warning, "Warning: Couldn't allocate space for '%s'", paths); + goto err; + } + strncpy(pLV->video, paths, len); + + if (audio_path) + { + len = strlen(audio_path)+1; + pLV->audio = (char *)HMalloc (len); + if (!pLV->audio) + { + log_add (log_Warning, "Warning: Couldn't allocate space for '%s'", audio_path); + goto err; + } + strncpy(pLV->audio, audio_path, len); + } + + if (speech_path) + { + len = strlen(speech_path)+1; + pLV->speech = (char *)HMalloc (len); + if (!pLV->speech) + { + log_add (log_Warning, "Warning: Couldn't allocate space for '%s'", speech_path); + goto err; + } + strncpy(pLV->speech, speech_path, len); + } + + resdata->ptr = result; + } + return; +err: + if (result) + FreeLegacyVideoData ((LEGACY_VIDEO)result); + + resdata->ptr = NULL; + return; +} + +BOOLEAN +InstallVideoResType (void) +{ + InstallResTypeVectors ("3DOVID", GetLegacyVideoData, FreeLegacyVideoData, NULL); + return TRUE; +} + +LEGACY_VIDEO +LoadLegacyVideoInstance (RESOURCE res) +{ + void *data; + + data = res_GetResource (res); + if (data) + { + res_DetachResource (res); + } + + return (LEGACY_VIDEO)data; +} + +BOOLEAN +DestroyLegacyVideo (LEGACY_VIDEO vid) +{ + return FreeLegacyVideoData (vid); +} diff --git a/src/libs/vidlib.h b/src/libs/vidlib.h new file mode 100644 index 0000000..9c32d7c --- /dev/null +++ b/src/libs/vidlib.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 LIBS_VIDLIB_H_ +#define LIBS_VIDLIB_H_ + +#include "libs/compiler.h" +#include "libs/sndlib.h" +#include "libs/reslib.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef enum +{ + NO_FMV = 0, + HARDWARE_FMV, + SOFTWARE_FMV +} VIDEO_TYPE; + +typedef struct tfb_videoclip *VIDEO_REF; +typedef struct legacy_video_desc *LEGACY_VIDEO; +typedef struct legacy_video_ref *LEGACY_VIDEO_REF; + +extern BOOLEAN InstallVideoResType (void); + +extern BOOLEAN InitVideoPlayer (BOOLEAN UseCDROM); +extern void UninitVideoPlayer (void); + +extern VIDEO_REF LoadVideoFile (const char *pStr); +extern BOOLEAN DestroyVideo (VIDEO_REF VidRef); +extern VIDEO_TYPE VidPlay (VIDEO_REF VidRef); +extern VIDEO_TYPE VidPlayEx (VIDEO_REF VidRef, MUSIC_REF AudRef, + MUSIC_REF SpeechRef, DWORD LoopFrame); +#define VID_NO_LOOP (0U-1) +extern void VidStop (void); +extern VIDEO_REF VidPlaying (void); +extern BOOLEAN VidProcessFrame (void); +extern DWORD VidGetPosition (void); // position in milliseconds +extern BOOLEAN VidSeek (DWORD pos); // position in milliseconds + +extern LEGACY_VIDEO LoadLegacyVideoInstance (RESOURCE res); +extern BOOLEAN DestroyLegacyVideo (LEGACY_VIDEO vid); +extern LEGACY_VIDEO_REF PlayLegacyVideo (LEGACY_VIDEO vid); +extern void StopLegacyVideo (LEGACY_VIDEO_REF ref); +extern BOOLEAN PlayingLegacyVideo (LEGACY_VIDEO_REF ref); + +#if defined(__cplusplus) +} +#endif + +#endif /* LIBS_VIDLIB_H_ */ diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..ee9a577 --- /dev/null +++ b/src/options.c @@ -0,0 +1,647 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * Eventually this should include all configuration stuff, + * for now there's few options which indicate 3do/pc flavors. + */ + +#include "options.h" + +#include "port.h" +#include "libs/graphics/gfx_common.h" +#include "libs/file.h" +#include "config.h" +#include "libs/compiler.h" +#include "libs/uio.h" +#include "libs/strlib.h" +#include "libs/log.h" +#include "libs/reslib.h" +#include "libs/memlib.h" + +#include +#include +#include +#include +#include +#ifdef __APPLE__ +# include + /* for dirname() */ +#endif + + +int optWhichCoarseScan; +int optWhichMenu; +int optWhichFonts; +int optWhichIntro; +int optWhichShield; +int optSmoothScroll; +int optMeleeScale; +const char **optAddons; + +BOOLEAN opt3doMusic; +BOOLEAN optRemixMusic; +BOOLEAN optSpeech; +BOOLEAN optSubtitles; +BOOLEAN optStereoSFX; +BOOLEAN optKeepAspectRatio; + +float optGamma; + +uio_DirHandle *contentDir; +uio_DirHandle *configDir; +uio_DirHandle *saveDir; +uio_DirHandle *meleeDir; +uio_MountHandle* contentMountHandle; + +char baseContentPath[PATH_MAX]; + +extern uio_Repository *repository; +extern uio_DirHandle *rootDir; + +INPUT_TEMPLATE input_templates[6]; + +static const char *findFileInDirs (const char *locs[], int numLocs, + const char *file); +static uio_MountHandle *mountContentDir (uio_Repository *repository, + const char *contentPath); +static void mountAddonDir (uio_Repository *repository, + uio_MountHandle *contentMountHandle, const char *addonDirName); + +static void mountDirZips (uio_DirHandle *dirHandle, const char *mountPoint, + int relativeFlags, uio_MountHandle *relativeHandle); + + +// Looks for a file 'file' in all 'numLocs' locations from 'locs'. +// returns the first element from locs where 'file' is found. +// If there is no matching location, NULL will be returned and +// errno will be set to 'ENOENT'. +// Entries from 'locs' that together with 'file' are longer than +// PATH_MAX will be ignored, except for a warning given to stderr. +static const char * +findFileInDirs (const char *locs[], int numLocs, const char *file) +{ + int locI; + char path[PATH_MAX]; + size_t fileLen; + + fileLen = strlen (file); + for (locI = 0; locI < numLocs; locI++) + { + size_t locLen; + const char *loc; + bool needSlash; + + loc = locs[locI]; + locLen = strlen (loc); + + needSlash = (locLen != 0 && loc[locLen - 1] != '/'); + if (locLen + (needSlash ? 1 : 0) + fileLen + 1 >= sizeof path) + { + // This dir plus the file name is too long. + log_add (log_Warning, "Warning: path '%s' is ignored" + " because it is too long.", loc); + continue; + } + + snprintf (path, sizeof path, "%s%s%s", loc, needSlash ? "/" : "", + file); + if (fileExists (path)) + return loc; + } + + // No matching location was found. + errno = ENOENT; + return NULL; +} + +// contentDirName is an explicitely specified location for the content, +// or NULL if none was explicitely specified. +// execFile is the path to the uqm executable, as acquired through +// main()'s argv[0]. +void +prepareContentDir (const char *contentDirName, const char* addonDirName, const char *execFile) +{ + const char *testFile = "version"; + const char *loc; + + if (contentDirName == NULL) + { + // Try the default content locations. + const char *locs[] = { + CONTENTDIR, /* defined in config.h */ + "", + "content", + "../../content" /* For running from MSVC */ + }; + loc = findFileInDirs (locs, sizeof locs / sizeof locs[0], testFile); + +#ifdef __APPLE__ + /* On OSX, if the content can't be found in any of the static + * locations, attempt to look inside the application bundle, + * by looking relative to the location of the uqm executable. */ + if (loc == NULL) + { + char *tempDir = (char *) HMalloc (PATH_MAX); + char *execFileDup; + + /* dirname can modify its argument, so we need a local + * mutable copy of it. */ + execFileDup = (char *) HMalloc (strlen (execFile) + 1); + strcpy (execFileDup, execFile); + snprintf (tempDir, PATH_MAX, "%s/../Resources/content", + dirname (execFileDup)); + loc = findFileInDirs ((const char **) &tempDir, 1, testFile); + HFree (execFileDup); + HFree (tempDir); + } +#endif + } + else + { + // Only use the explicitely supplied content dir. + loc = findFileInDirs (&contentDirName, 1, testFile); + } + if (loc == NULL) + { + log_add (log_Fatal, "Fatal error: Could not find content."); + exit (EXIT_FAILURE); + } + + if (expandPath (baseContentPath, sizeof baseContentPath, loc, + EP_ALL_SYSTEM) == -1) + { + log_add (log_Fatal, "Fatal error: Could not expand path to content " + "directory: %s", strerror (errno)); + exit (EXIT_FAILURE); + } + + log_add (log_Debug, "Using '%s' as base content dir.", baseContentPath); + contentMountHandle = mountContentDir (repository, baseContentPath); + + if (addonDirName) + log_add (log_Debug, "Using '%s' as addon dir.", addonDirName); + mountAddonDir (repository, contentMountHandle, addonDirName); + +#ifndef __APPLE__ + (void) execFile; +#endif +} + +void +prepareConfigDir (const char *configDirName) { + char buf[PATH_MAX]; + static uio_AutoMount *autoMount[] = { NULL }; + uio_MountHandle *contentHandle; + + if (configDirName == NULL) + { + configDirName = getenv("UQM_CONFIG_DIR"); + + if (configDirName == NULL) + configDirName = CONFIGDIR; + } + + if (expandPath (buf, PATH_MAX - 13, configDirName, EP_ALL_SYSTEM) + == -1) + { + // Doesn't have to be fatal, but might mess up things when saving + // config files. + log_add (log_Fatal, "Fatal error: Invalid path to config files."); + exit (EXIT_FAILURE); + } + configDirName = buf; + + log_add (log_Debug, "Using config dir '%s'", configDirName); + + // Set the environment variable UQM_CONFIG_DIR so UQM_MELEE_DIR + // and UQM_SAVE_DIR can refer to it. + setenv("UQM_CONFIG_DIR", configDirName, 1); + + // Create the path upto the config dir, if not already existing. + if (mkdirhier (configDirName) == -1) + exit (EXIT_FAILURE); + + contentHandle = uio_mountDir (repository, "/", + uio_FSTYPE_STDIO, NULL, NULL, configDirName, autoMount, + uio_MOUNT_TOP, NULL); + if (contentHandle == NULL) + { + log_add (log_Fatal, "Fatal error: Could not mount config dir: %s", + strerror (errno)); + exit (EXIT_FAILURE); + } + + configDir = uio_openDir (repository, "/", 0); + if (configDir == NULL) + { + log_add (log_Fatal, "Fatal error: Could not open config dir: %s", + strerror (errno)); + exit (EXIT_FAILURE); + } +} + +void +prepareSaveDir (void) { + char buf[PATH_MAX]; + const char *saveDirName; + + saveDirName = getenv("UQM_SAVE_DIR"); + if (saveDirName == NULL) + saveDirName = SAVEDIR; + + if (expandPath (buf, PATH_MAX - 13, saveDirName, EP_ALL_SYSTEM) == -1) + { + // Doesn't have to be fatal, but might mess up things when saving + // config files. + log_add (log_Fatal, "Fatal error: Invalid path to config files."); + exit (EXIT_FAILURE); + } + + saveDirName = buf; + setenv("UQM_SAVE_DIR", saveDirName, 1); + + // Create the path upto the save dir, if not already existing. + if (mkdirhier (saveDirName) == -1) + exit (EXIT_FAILURE); + + log_add (log_Debug, "Saved games are kept in %s.", saveDirName); + + saveDir = uio_openDirRelative (configDir, "save", 0); + // TODO: this doesn't work if the save dir is not + // "save" in the config dir. + if (saveDir == NULL) + { + log_add (log_Fatal, "Fatal error: Could not open save dir: %s", + strerror (errno)); + exit (EXIT_FAILURE); + } +} + +void +prepareMeleeDir (void) { + char buf[PATH_MAX]; + const char *meleeDirName; + + meleeDirName = getenv("UQM_MELEE_DIR"); + if (meleeDirName == NULL) + meleeDirName = MELEEDIR; + + if (expandPath (buf, PATH_MAX - 13, meleeDirName, EP_ALL_SYSTEM) == -1) + { + // Doesn't have to be fatal, but might mess up things when saving + // config files. + log_add (log_Fatal, "Fatal error: Invalid path to config files."); + exit (EXIT_FAILURE); + } + + meleeDirName = buf; + setenv("UQM_MELEE_DIR", meleeDirName, 1); + + // Create the path upto the save dir, if not already existing. + if (mkdirhier (meleeDirName) == -1) + exit (EXIT_FAILURE); + + meleeDir = uio_openDirRelative (configDir, "teams", 0); + // TODO: this doesn't work if the save dir is not + // "teams" in the config dir. + if (meleeDir == NULL) + { + log_add (log_Fatal, "Fatal error: Could not open melee teams dir: %s", + strerror (errno)); + exit (EXIT_FAILURE); + } +} + +static uio_MountHandle * +mountContentDir (uio_Repository *repository, const char *contentPath) +{ + uio_DirHandle *packagesDir; + static uio_AutoMount *autoMount[] = { NULL }; + uio_MountHandle *contentMountHandle; + + contentMountHandle = uio_mountDir (repository, "/", + uio_FSTYPE_STDIO, NULL, NULL, contentPath, autoMount, + uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); + if (contentMountHandle == NULL) + { + log_add (log_Fatal, "Fatal error: Could not mount content dir: %s", + strerror (errno)); + exit (EXIT_FAILURE); + } + + contentDir = uio_openDir (repository, "/", 0); + if (contentDir == NULL) + { + log_add (log_Fatal, "Fatal error: Could not open content dir: %s", + strerror (errno)); + exit (EXIT_FAILURE); + } + + packagesDir = uio_openDir (repository, "/packages", 0); + if (packagesDir != NULL) + { + mountDirZips (packagesDir, "/", uio_MOUNT_BELOW, contentMountHandle); + uio_closeDir (packagesDir); + } + + return contentMountHandle; +} + +static void +mountAddonDir (uio_Repository *repository, uio_MountHandle *contentMountHandle, + const char *addonDirName) +{ + uio_DirHandle *addonsDir; + static uio_AutoMount *autoMount[] = { NULL }; + uio_MountHandle *mountHandle; + uio_DirList *availableAddons; + + if (addonDirName != NULL) + { + mountHandle = uio_mountDir (repository, "addons", + uio_FSTYPE_STDIO, NULL, NULL, addonDirName, autoMount, + uio_MOUNT_TOP | uio_MOUNT_RDONLY, NULL); + if (mountHandle == NULL) + { + log_add (log_Warning, "Warning: Could not mount addon directory: %s" + ";\n\t'--addon' options are ignored.", strerror (errno)); + return; + } + } + else + { + mountHandle = contentMountHandle; + } + + // NB: note the difference between addonsDir and addonDir. + // the former is the dir 'addons', the latter a directory + // in that dir. + addonsDir = uio_openDirRelative (contentDir, "addons", 0); + if (addonsDir == NULL) + { // No addon dir found. + log_add (log_Warning, "Warning: There's no 'addons' " + "directory in the 'content' directory;\n\t'--addon' " + "options are ignored."); + return; + } + + mountDirZips (addonsDir, "addons", uio_MOUNT_BELOW, mountHandle); + + availableAddons = uio_getDirList (addonsDir, "", "", match_MATCH_PREFIX); + if (availableAddons != NULL) + { + int i, count; + + // count the actual addon dirs + count = 0; + for (i = 0; i < availableAddons->numNames; ++i) + { + struct stat sb; + + if (availableAddons->names[i][0] == '.' || + uio_stat (addonsDir, availableAddons->names[i], &sb) == -1 + || !S_ISDIR (sb.st_mode)) + { // this dir entry ignored + availableAddons->names[i] = NULL; + continue; + } + ++count; + } + log_add (log_Info, "%d available addon pack%s.", count, + count == 1 ? "" : "s"); + + count = 0; + for (i = 0; i < availableAddons->numNames; ++i) + { + char mountname[128]; + uio_DirHandle *addonDir; + const char *addon = availableAddons->names[i]; + + if (!addon) + continue; + + ++count; + log_add (log_Info, " %d. %s", count, addon); + + snprintf (mountname, sizeof mountname, "addons/%s", addon); + + addonDir = uio_openDirRelative (addonsDir, addon, 0); + if (addonDir == NULL) + { + log_add (log_Warning, "Warning: directory 'addons/%s' " + "not found; addon skipped.", addon); + continue; + } + mountDirZips (addonDir, mountname, uio_MOUNT_BELOW, mountHandle); + uio_closeDir (addonDir); + } + } + else + { + log_add (log_Info, "0 available addon packs."); + } + + uio_DirList_free (availableAddons); + uio_closeDir (addonsDir); +} + +static void +mountDirZips (uio_DirHandle *dirHandle, const char *mountPoint, + int relativeFlags, uio_MountHandle *relativeHandle) +{ + static uio_AutoMount *autoMount[] = { NULL }; + uio_DirList *dirList; + + dirList = uio_getDirList (dirHandle, "", "\\.([zZ][iI][pP]|[uU][qQ][mM])$", + match_MATCH_REGEX); + if (dirList != NULL) + { + int i; + + for (i = 0; i < dirList->numNames; i++) + { + if (uio_mountDir (repository, mountPoint, uio_FSTYPE_ZIP, + dirHandle, dirList->names[i], "/", autoMount, + relativeFlags | uio_MOUNT_RDONLY, + relativeHandle) == NULL) + { + log_add (log_Warning, "Warning: Could not mount '%s': %s.", + dirList->names[i], strerror (errno)); + } + } + } + uio_DirList_free (dirList); +} + +int +loadIndices (uio_DirHandle *dir) +{ + uio_DirList *indices; + int numLoaded = 0; + + indices = uio_getDirList (dir, "", "\\.[rR][mM][pP]$", + match_MATCH_REGEX); + + if (indices != NULL) + { + int i; + + for (i = 0; i < indices->numNames; i++) + { + log_add (log_Debug, "Loading resource index '%s'", + indices->names[i]); + LoadResourceIndex (dir, indices->names[i], NULL); + numLoaded++; + } + } + uio_DirList_free (indices); + + /* Return the number of index files loaded. */ + return numLoaded; +} + +BOOLEAN +loadAddon (const char *addon) +{ + uio_DirHandle *addonsDir, *addonDir; + int numLoaded; + + addonsDir = uio_openDirRelative (contentDir, "addons", 0); + if (addonsDir == NULL) + { + // No addon dir found. + log_add (log_Warning, "Warning: There's no 'addons' " + "directory in the 'content' directory;\n\t'--addon' " + "options are ignored."); + return FALSE; + } + addonDir = uio_openDirRelative (addonsDir, addon, 0); + if (addonDir == NULL) + { + log_add (log_Warning, "Warning: Addon '%s' not found", addon); + uio_closeDir (addonsDir); + return FALSE; + } + + numLoaded = loadIndices (addonDir); + if (!numLoaded) + { + log_add (log_Error, "No RMP index files were loaded for addon '%s'", + addon); + } + + uio_closeDir (addonDir); + uio_closeDir (addonsDir); + + return (numLoaded > 0); +} + +void +prepareShadowAddons (const char **addons) +{ + uio_DirHandle *addonsDir; + const char *shadowDirName = "shadow-content"; + + addonsDir = uio_openDirRelative (contentDir, "addons", 0); + // If anything fails here, it will fail again later, so + // we'll just keep quiet about it for now + if (addonsDir == NULL) + return; + + for (; *addons != NULL; addons++) + { + const char *addon = *addons; + uio_DirHandle *addonDir; + uio_DirHandle *shadowDir; + + addonDir = uio_openDirRelative (addonsDir, addon, 0); + if (addonDir == NULL) + continue; + + // Mount addon's "shadow-content" on top of "/" + shadowDir = uio_openDirRelative (addonDir, shadowDirName, 0); + if (shadowDir) + { + log_add (log_Debug, "Mounting shadow content of '%s' addon", addon); + mountDirZips (shadowDir, "/", uio_MOUNT_ABOVE, contentMountHandle); + // Mount non-zipped shadow content + if (uio_transplantDir ("/", shadowDir, uio_MOUNT_RDONLY | + uio_MOUNT_ABOVE, contentMountHandle) == NULL) + { + log_add (log_Warning, "Warning: Could not mount shadow content" + " of '%s': %s.", addon, strerror (errno)); + } + + uio_closeDir (shadowDir); + } + uio_closeDir (addonDir); + } + + uio_closeDir (addonsDir); +} + +void +prepareAddons (const char **addons) +{ + for (; *addons != NULL; addons++) + { + log_add (log_Info, "Loading addon '%s'", *addons); + if (!loadAddon (*addons)) + { + // TODO: Should we do something like inform the user? + // Why simply refuse to load other addons? + // Maybe exit() to inform the user of the failure? + break; + } + } +} + +void +unprepareAllDirs (void) +{ + if (saveDir) + { + uio_closeDir (saveDir); + saveDir = 0; + } + if (meleeDir) + { + uio_closeDir (meleeDir); + meleeDir = 0; + } + if (contentDir) + { + uio_closeDir (contentDir); + contentDir = 0; + } + if (configDir) + { + uio_closeDir (configDir); + configDir = 0; + } +} + +bool +setGammaCorrection (float gamma) +{ + bool set = TFB_SetGamma (gamma); + if (set) + log_add (log_Info, "Gamma correction set to %.4f.", gamma); + else + log_add (log_Warning, "Unable to set gamma correction."); + return set; +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 0000000..67480a4 --- /dev/null +++ b/src/options.h @@ -0,0 +1,94 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * Eventually this should include all configuration stuff, + * for now there's few options which indicate 3do/pc flavors. + */ + +#ifndef OPTIONS_H +#define OPTIONS_H + +#include "port.h" +#include "libs/compiler.h" +#include "libs/uio.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +#define OPT_3DO 0x01 +#define OPT_PC 0x02 +#define OPT_ALL 0xFF + +extern int optWhichCoarseScan; +extern int optWhichMenu; +extern int optWhichFonts; +extern int optWhichIntro; +extern int optWhichShield; +extern int optSmoothScroll; +extern int optMeleeScale; + +extern BOOLEAN opt3doMusic; +extern BOOLEAN optRemixMusic; +extern BOOLEAN optSpeech; +extern BOOLEAN optSubtitles; +extern BOOLEAN optStereoSFX; +extern BOOLEAN optKeepAspectRatio; + +#define GAMMA_SCALE 1000 +extern float optGamma; + +extern uio_DirHandle *contentDir; +extern uio_DirHandle *configDir; +extern uio_DirHandle *saveDir; +extern uio_DirHandle *meleeDir; +extern char baseContentPath[PATH_MAX]; + +extern const char **optAddons; + +/* These get edited by TEXTENTRY widgets, so they should have room to + * hold as much as one of them allows by default. */ +typedef struct _input_template { + char name[30]; + + /* This should eventually also hold things like Joystick Port + * and whether or not joysticks are enabled at all, and + * possibly the whole configuration scheme. If we do that, we + * can actually ditch much of VControl. */ +} INPUT_TEMPLATE; + +extern INPUT_TEMPLATE input_templates[6]; + +void prepareContentDir (const char *contentDirName, const char *addonDirName, const char *execFile); +void prepareConfigDir (const char *configDirName); +void prepareMeleeDir (void); +void prepareSaveDir (void); +void prepareAddons (const char **addons); +void prepareShadowAddons (const char **addons); +void unprepareAllDirs (void); + +BOOLEAN loadAddon (const char *addon); +int loadIndices (uio_DirHandle *baseDir); + +bool setGammaCorrection (float gamma); + +#if defined(__cplusplus) +} +#endif + +#endif + diff --git a/src/port.c b/src/port.c new file mode 100644 index 0000000..dc25f16 --- /dev/null +++ b/src/port.c @@ -0,0 +1,145 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 might be included in one system, but + * omited in another. + * + * Created by Serge van den Boom + */ + +#include "port.h" + +#include +#include +#ifdef _MSC_VER +# include +# include +#endif /* _MSC_VER */ +#include +#include +#if !defined (_MSC_VER) && !defined (HAVE_READDIR_R) +# include +#endif + +#ifndef HAVE_STRUPR +char * +strupr (char *str) +{ + char *ptr; + + ptr = str; + while (*ptr) + { + *ptr = (char) toupper (*ptr); + ptr++; + } + return str; +} +#endif + +#ifndef HAVE_SETENV +int +setenv (const char *name, const char *value, int overwrite) +{ + char *string, *ptr; + size_t nameLen, valueLen; + + if (!overwrite) + { + char *old; + + old = getenv (name); + if (old != NULL) + return 0; + } + + nameLen = strlen (name); + valueLen = strlen (value); + + string = malloc (nameLen + valueLen + 2); + // "NAME=VALUE\0" + // putenv() does NOT make a copy, but uses the string passed. + + ptr = string; + + strcpy (string, name); + ptr += nameLen; + + *ptr = '='; + ptr++; + + strcpy (ptr, value); + + return putenv (string); +} +#endif + +#if !defined (_MSC_VER) && !defined (HAVE_READDIR_R) +// NB. This function calls readdir() directly, and as such has the same +// reentrance issues as that function. For the purposes of UQM it will +// do though. +// Note the POSIX requires that "The pointer returned by readdir() +// points to data which may be overwritten by another call to +// readdir( ) on the same directory stream. This data is not +// overwritten by another call to readdir() on a different directory +// stream." +// NB. This function makes an extra copy of the dirent and will hence be +// slower than a direct call to readdir() or readdir_r(). +int +readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) { + struct dirent *readdir_entry; + + readdir_entry = readdir(dirp); + if (readdir_entry == NULL) { + *result = NULL; + return errno; + } + + *entry = *readdir_entry; + *result = entry; + return 0; +} +#endif + +#ifdef _MSC_VER +// MSVC does not have snprintf() and vsnprintf(). It does have a _snprintf() +// and _vsnprintf(), but these do not terminate a truncated string as +// the C standard prescribes. +int +snprintf(char *str, size_t size, const char *format, ...) +{ + int result; + va_list args; + + va_start (args, format); + result = _vsnprintf (str, size, format, args); + if (str != NULL && size != 0) + str[size - 1] = '\0'; + va_end (args); + + return result; +} + +int +vsnprintf(char *str, size_t size, const char *format, va_list args) +{ + int result = _vsnprintf (str, size, format, args); + if (str != NULL && size != 0) + str[size - 1] = '\0'; + return result; +} +#endif /* _MSC_VER */ + diff --git a/src/port.h b/src/port.h new file mode 100644 index 0000000..4dc56ee --- /dev/null +++ b/src/port.h @@ -0,0 +1,554 @@ +#ifndef PORT_H_ +#define PORT_H_ + +#include "config.h" + +#ifdef __MINGW32__ +// Microsoft Windows headers expect this to be set. The MSVC compiler sets +// it, but MinGW doesn't. +# if defined(_X86_) +# define _M_IX86 +# elif defined(_IA64_) +# define _M_IA64 +# elif defined(__amd64__) +# define _M_AMD64 +# define _M_X64 +# elif defined(__m68k__) +# define _68K_ +# elif defined(__ppc__) +# define _M_PPC +# endif +#endif + + +// Compilation related +#ifdef _MSC_VER +# define inline __inline +#elif defined(__SYMBIAN32__) +#else +# define inline __inline__ +# ifdef __MINGW32__ + // For when including Microsoft Windows header files. +# define _inline inline +# endif +#endif + + +// Compilation warnings: +#ifdef _MSC_VER + // UQM uses a lot of functions that can be used unsafely, but it uses them + // in a safe way. The warnings about these functions however may drown out + // serious warnings, so we turn them off. +# define _CRT_SECURE_NO_DEPRECATE + + // Escalate some warnings we consider important + // "'operator' : 'identifier1' indirection to slightly different base + // types from 'identifier2' +# pragma warning( 3 : 4057 ) + // "unreferenced formal parameter" +# pragma warning( 3 : 4100 ) + // "'function' : unreferenced local function has been removed" +# pragma warning( 3 : 4505 ) + // "local variable 'name' may be used without having been initialized" +# pragma warning( 3 : 4701 ) + + // Downgrade some warnings we consider unimportant + // "'operator' conversion from 'type1' to 'type2', possible loss of data" +# pragma warning( 4 : 4244) +#endif + + +#ifdef _MSC_VER +# include +#else +# include +#endif + +// Using "HAVE_STRCASECMP_UQM" instead of "HAVE_STRCASECMP" as the latter +// conflicts with SDL. +#if !defined(HAVE_STRICMP) && !defined(HAVE_STRCASECMP_UQM) +# error Neither stricmp() nor strcasecmp() is available. +#elif !defined(HAVE_STRICMP) +# define stricmp strcasecmp +#elif !defined(HAVE_STRCASECMP_UQM) +# define strcasecmp stricmp +#else + // We should take care not to define anything if both strcasecmp() and + // stricmp() are defined, as one might exist as a macro to the other. +#endif + + +#ifndef HAVE_STRUPR +#if defined(__cplusplus) +extern "C" { +#endif +char *strupr (char *str); +#if defined(__cplusplus) +} +#endif +#endif + +#if !defined (_MSC_VER) && !defined (HAVE_READDIR_R) +# include +#if defined(__cplusplus) +extern "C" { +#endif +int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result); +#if defined(__cplusplus) +} +#endif +#endif + +// Directories +#ifdef WIN32 +# include +# define PATH_MAX _MAX_PATH +# define NAME_MAX _MAX_FNAME + // _MAX_DIR and FILENAME_MAX could also be candidates. + // If anyone can tell me which one matches NAME_MAX, please + // let me know. - SvdB +#elif defined(_WIN32_WCE) +# include +#else +# include + /* PATH_MAX is per POSIX defined in , but: + * "A definition of one of the values from Table 2.6 shall bea + * omitted from on specific implementations where the + * corresponding value is equal to or greater than the + * stated minimum, but where the value can vary depending + * on the file to which it is applied. The actual value supported + * for a specific pathname shall be provided by the pathconf() + * function." + * _POSIX_NAME_MAX will provide a minimum (14). + * This is relevant (at least) for Solaris. + */ +# ifndef NAME_MAX +# define NAME_MAX _POSIX_NAME_MAX +# endif +#endif + +// Some types +#ifdef _MSC_VER +typedef int ssize_t; +typedef unsigned short mode_t; +#endif + +// Directories +#include +#ifdef _MSC_VER +# define MKDIR(name, mode) ((void) mode, _mkdir(name)) +#elif defined(__MINGW32__) +# define MKDIR(name, mode) ((void) mode, mkdir(name)) +#else +# define MKDIR mkdir +#endif +#ifdef _MSC_VER +# include +# define chdir _chdir +# define getcwd _getcwd +# define access _access +# define F_OK 0 +# define W_OK 2 +# define R_OK 4 +# define open _open +# define read _read +//# define fstat _fstat +# define write _write +//# define stat _stat +# define unlink _unlink +#endif + +// Memory +#ifdef WIN32 +# ifdef __MINGW32__ +# include +# elif defined (_MSC_VER) +# define alloca _alloca +# endif +#elif defined(__linux__) || defined(__svr4__) +# include +#endif + +// String formatting +#ifdef _MSC_VER +# include +// Defined in port.c +#if defined(__cplusplus) +extern "C" { +#endif +int snprintf(char *str, size_t size, const char *format, ...); +int vsnprintf(char *str, size_t size, const char *format, va_list args); +#if defined(__cplusplus) +} +#endif + +#endif /* _MSC_VER */ + +#if defined(__cplusplus) +extern "C" { +#endif + +// setenv() +#ifndef HAVE_SETENV +int setenv (const char *name, const char *value, int overwrite); +#endif + +#ifndef HAVE_WCHAR_T +typedef unsigned short wchar_t; +#endif + +#ifndef HAVE_WINT_T +typedef unsigned int wint_t; +#endif + +#if defined(__cplusplus) +} +#endif + +#if defined (_MSC_VER) || defined(__MINGW32__) +# define USE_WINSOCK +#endif + +// errno error numbers. The values used don't matter, as long as they +// don't conflict with existing errno error numbers. +#ifdef PORT_WANT_ERRNO +# ifdef USE_WINSOCK +# include +# ifndef E2BIG +# define E2BIG 0x01000001 +# endif +# ifndef EACCES +# define EACCES 0x01000002 +# endif +# ifndef EADDRINUSE +# define EADDRINUSE 0x01000003 +# endif +# ifndef EADDRNOTAVAIL +# define EADDRNOTAVAIL 0x01000004 +# endif +# ifndef EAFNOSUPPORT +# define EAFNOSUPPORT 0x01000005 +# endif +# ifndef EAGAIN +# ifdef EWOULDBLOCK +# define EAGAIN EWOULDBLOCK +# else +# define EAGAIN 0x01000006 +# endif +# endif +# ifndef EALREADY +# define EALREADY 0x01000007 +# endif +# ifndef EBADF +# define EBADF 0x01000008 +# endif +# ifndef EBADMSG +# define EBADMSG 0x01000009 +# endif +# ifndef EBUSY +# define EBUSY 0x0100000a +# endif +# ifndef ECANCELED +# define ECANCELED 0x0100000b +# endif +# ifndef ECHILD +# define ECHILD 0x0100000c +# endif +# ifndef ECONNABORTED +# define ECONNABORTED 0x0100000d +# endif +# ifndef ECONNREFUSED +# define ECONNREFUSED 0x0100000e +# endif +# ifndef ECONNRESET +# define ECONNRESET 0x0100000f +# endif +# ifndef EDEADLK +# define EDEADLK 0x01000010 +# endif +# ifndef EDESTADDRREQ +# define EDESTADDRREQ 0x01000011 +# endif +# ifndef EDOM +# define EDOM 0x01000012 +# endif +// Reserved in POSIX +//# ifndef //EDQUOT +//# define //EDQUOT 0x01000013 +//# endif +# ifndef EEXIST +# define EEXIST 0x01000014 +# endif +# ifndef EFAULT +# define EFAULT 0x01000015 +# endif +# ifndef EFBIG +# define EFBIG 0x01000016 +# endif +# ifndef EHOSTUNREACH +# define EHOSTUNREACH 0x01000017 +# endif +# ifndef EIDRM +# define EIDRM 0x01000018 +# endif +# ifndef EILSEQ +# define EILSEQ 0x01000019 +# endif +# ifndef EINPROGRESS +# define EINPROGRESS 0x0100001a +# endif +# ifndef EINTR +# define EINTR 0x0100001b +# endif +# ifndef EINVAL +# define EINVAL 0x0100001c +# endif +# ifndef EIO +# define EIO 0x0100001d +# endif +# ifndef EISCONN +# define EISCONN 0x0100001e +# endif +# ifndef EISDIR +# define EISDIR 0x0100001f +# endif +# ifndef ELOOP +# define ELOOP 0x01000020 +# endif +# ifndef EMFILE +# define EMFILE 0x01000021 +# endif +# ifndef EMLINK +# define EMLINK 0x01000022 +# endif +# ifndef EMSGSIZE +# define EMSGSIZE 0x01000023 +# endif +// Reserved in POSIX +//# ifndef //EMULTIHOP +//# define //EMULTIHOP 0x01000024 +//# endif +# ifndef ENAMETOOLONG +# define ENAMETOOLONG 0x01000025 +# endif +# ifndef ENETDOWN +# define ENETDOWN 0x01000026 +# endif +# ifndef ENETRESET +# define ENETRESET 0x01000027 +# endif +# ifndef ENETUNREACH +# define ENETUNREACH 0x01000028 +# endif +# ifndef ENFILE +# define ENFILE 0x01000029 +# endif +# ifndef ENOBUFS +# define ENOBUFS 0x0100002a +# endif +# ifndef ENODATA +# define ENODATA 0x0100002b +# endif +# ifndef ENODEV +# define ENODEV 0x0100002c +# endif +# ifndef ENOENT +# define ENOENT 0x0100002d +# endif +# ifndef ENOEXEC +# define ENOEXEC 0x0100002e +# endif +# ifndef ENOLCK +# define ENOLCK 0x0100002f +# endif +// Reserved in POSIX +//# ifndef ENOLINK +//# define ENOLINK 0x01000030 +//# endif +# ifndef ENOMEM +# define ENOMEM 0x01000031 +# endif +# ifndef ENOMSG +# define ENOMSG 0x01000032 +# endif +# ifndef ENOPROTOOPT +# define ENOPROTOOPT 0x01000033 +# endif +# ifndef ENOSPC +# define ENOSPC 0x01000034 +# endif +# ifndef ENOSR +# define ENOSR 0x01000035 +# endif +# ifndef ENOSTR +# define ENOSTR 0x01000036 +# endif +# ifndef ENOSYS +# define ENOSYS 0x01000037 +# endif +# ifndef ENOTCONN +# define ENOTCONN 0x01000038 +# endif +# ifndef ENOTDIR +# define ENOTDIR 0x01000039 +# endif +# ifndef ENOTEMPTY +# define ENOTEMPTY 0x0100003a +# endif +# ifndef ENOTSOCK +# define ENOTSOCK 0x0100003b +# endif +# ifndef ENOTSUP +# define ENOTSUP 0x0100003c +# endif +# ifndef ENOTTY +# define ENOTTY 0x0100003d +# endif +# ifndef ENXIO +# define ENXIO 0x0100003e +# endif +# ifndef EOPNOTSUPP +# define EOPNOTSUPP 0x0100003f +# endif +# ifndef EOVERFLOW +# define EOVERFLOW 0x01000040 +# endif +# ifndef EPERM +# define EPERM 0x01000041 +# endif +# ifndef EPIPE +# define EPIPE 0x01000042 +# endif +# ifndef EPROTO +# define EPROTO 0x01000043 +# endif +# ifndef EPROTONOSUPPORT +# define EPROTONOSUPPORT 0x01000044 +# endif +# ifndef EPROTOTYPE +# define EPROTOTYPE 0x01000045 +# endif +# ifndef ERANGE +# define ERANGE 0x01000046 +# endif +# ifndef EROFS +# define EROFS 0x01000047 +# endif +# ifndef ESPIPE +# define ESPIPE 0x01000048 +# endif +# ifndef ESRCH +# define ESRCH 0x01000049 +# endif +// Reserved in POSIX +//# ifndef //ESTALE +//# define //ESTALE 0x0100004a +//# endif +# ifndef ETIME +# define ETIME 0x0100004b +# endif +# ifndef ETIMEDOUT +# define ETIMEDOUT 0x0100004c +# endif +# ifndef ETXTBSY +# define ETXTBSY 0x0100004d +# endif +# ifndef EWOULDBLOCK +# ifdef EAGAIN +# define EWOULDBLOCK EAGAIN +# else +# define EWOULDBLOCK 0x0100004e +# endif +# endif +# ifndef EXDEV +# define EXDEV 0x0100004f +# endif + +// Non-POSIX: +# ifndef EHOSTDOWN +# define EHOSTDOWN 0x01100001 +# endif +# ifndef EPFNOSUPPORT +# define EPFNOSUPPORT 0x01100002 +# endif +# ifndef EPROCLIM +# define EPROCLIM 0x01100003 +# endif +# ifndef ESHUTDOWN +# define ESHUTDOWN 0x01100004 +# endif +# ifndef ESOCKTNOSUPPORT +# define ESOCKTNOSUPPORT 0x01100005 +# endif +# elif defined (__FreeBSD__) || defined (__OpenBSD__) +# ifndef EBADMSG +# define EBADMSG EIO +# endif +# endif /* defined (__FreeBSD__) || defined (__OpenBSD__) */ +#endif /* defined (PORT_WANT_ERRNO) */ + +// Use SDL_INCLUDE to portably include the SDL files from the right location. +// The SDL_DIR definition is provided by the build configuration. +#define SDL_INCLUDE(file) #file + +// Mark a function as using printf-style function arguments, so that +// extra consistency checks can be made by the compiler. +// The first argument to PRINTF_FUNCTION and VPRINTF_FUNCTION is the +// index of the format string argument, the second is the index of +// the first argument which is specified in the format string (in the +// case of PRINTF_FUNCTION) or of the va_list argument (in the case of +// VPRINTF_FUNCTION). +#ifdef __GNUC__ +# define PRINTF_FUNCTION(formatArg, firstArg) \ + __attribute__((format(printf, formatArg, firstArg))) +# define VPRINTF_FUNCTION(formatArg) \ + __attribute__((format(printf, formatArg, 0))) +#else +# define PRINTF_FUNCTION(formatArg, firstArg) +# define VPRINTF_FUNCTION(formatArg) +#endif + +#if defined(__GNUC__) +# define _NORETURN __attribute__((noreturn)) +#else +# define _NORETURN +#endif + +// Buffered vs. unbuffered logfile +// stderr is normally unbuffered when connected to a terminal, but it +// will be buffered when connected to a file, when a --logfile argument +// is passed to uqm. +// Unbuffered output is slower, which can be significant if much debug output +// is requested, but after a crash occurs the logfile will still be up to +// date. +// On platforms where there is no console, having up-to-date log files +// after a crash is valuable enough to make the logfile unbuffered by +// default there. +#if defined(_WIN32_WCE) +# define UNBUFFERED_LOGFILE +#endif + +// Variations in path handling +#if defined(WIN32) || defined(__SYMBIAN32__) + // HAVE_DRIVE_LETTERS is defined to signify that DOS/Windows style drive + // letters are to be recognised on this platform. +# define HAVE_DRIVE_LETTERS + // BACKSLASH_IS_PATH_SEPARATOR is defined to signify that the backslash + // character is to be recognised as a path separator on this platform. + // This does not affect the acceptance of forward slashes as path + // separators. +# define BACKSLASH_IS_PATH_SEPARATOR +#endif +#if defined(WIN32) + // HAVE_UNC_PATHS is defined to signify that Universal Naming Convention + // style paths are to be recognised on this platform. +# define HAVE_UNC_PATHS + // HAVE_CWD_PER_DRIVE is defined to signify that every drive has its own + // current working directory. +# define HAVE_CWD_PER_DRIVE +#endif +// REJECT_DRIVE_PATH_WITHOUT_SLASH can also be defined, if paths of the form +// "d:foo/bar" (without a slash after the drive letter) are to be rejected. + +#endif /* PORT_H_ */ + diff --git a/src/regex/Makeinfo b/src/regex/Makeinfo new file mode 100644 index 0000000..7102683 --- /dev/null +++ b/src/regex/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="regex.c" +uqm_HFILES="regex.h regex_internal.h" diff --git a/src/regex/regcomp.ci b/src/regex/regcomp.ci new file mode 100644 index 0000000..6803d73 --- /dev/null +++ b/src/regex/regcomp.ci @@ -0,0 +1,3931 @@ +/* Extended regular expression matching and search library. + Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Isamu Hasegawa . + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +static reg_errcode_t re_compile_internal (regex_t *preg, const char * pattern, + int length, reg_syntax_t syntax); +static void re_compile_fastmap_iter (regex_t *bufp, + const re_dfastate_t *init_state, + char *fastmap); +static reg_errcode_t init_dfa (re_dfa_t *dfa, int pat_len); +static void init_word_char (re_dfa_t *dfa); +#ifdef RE_ENABLE_I18N +static void free_charset (re_charset_t *cset); +#endif /* RE_ENABLE_I18N */ +static void free_workarea_compile (regex_t *preg); +static reg_errcode_t create_initial_state (re_dfa_t *dfa); +#ifdef RE_ENABLE_I18N +static void optimize_utf8 (re_dfa_t *dfa); +#endif +struct subexp_optimize +{ + re_dfa_t *dfa; + re_token_t *nodes; + int no_sub, re_nsub; +}; +static bin_tree_t *optimize_subexps (struct subexp_optimize *so, + bin_tree_t *node, int sidx, int depth); +static reg_errcode_t analyze (re_dfa_t *dfa); +static reg_errcode_t analyze_tree (re_dfa_t *dfa, bin_tree_t *node); +static void calc_first (re_dfa_t *dfa, bin_tree_t *node); +static void calc_next (re_dfa_t *dfa, bin_tree_t *node); +static void calc_epsdest (re_dfa_t *dfa, bin_tree_t *node); +static reg_errcode_t duplicate_node_closure (re_dfa_t *dfa, int top_org_node, + int top_clone_node, int root_node, + unsigned int constraint); +static reg_errcode_t duplicate_node (int *new_idx, re_dfa_t *dfa, int org_idx, + unsigned int constraint); +static int search_duplicated_node (re_dfa_t *dfa, int org_node, + unsigned int constraint); +static reg_errcode_t calc_eclosure (re_dfa_t *dfa); +static reg_errcode_t calc_eclosure_iter (re_node_set *new_set, re_dfa_t *dfa, + int node, int root); +static void calc_inveclosure (re_dfa_t *dfa); +static int fetch_number (re_string_t *input, re_token_t *token, + reg_syntax_t syntax); +static void fetch_token (re_token_t *result, re_string_t *input, + reg_syntax_t syntax); +static int peek_token (re_token_t *token, re_string_t *input, + reg_syntax_t syntax); +static int peek_token_bracket (re_token_t *token, re_string_t *input, + reg_syntax_t syntax); +static bin_tree_t *parse (re_string_t *regexp, regex_t *preg, + reg_syntax_t syntax, reg_errcode_t *err); +static bin_tree_t *parse_reg_exp (re_string_t *regexp, regex_t *preg, + re_token_t *token, reg_syntax_t syntax, + int nest, reg_errcode_t *err); +static bin_tree_t *parse_branch (re_string_t *regexp, regex_t *preg, + re_token_t *token, reg_syntax_t syntax, + int nest, reg_errcode_t *err); +static bin_tree_t *parse_expression (re_string_t *regexp, regex_t *preg, + re_token_t *token, reg_syntax_t syntax, + int nest, reg_errcode_t *err); +static bin_tree_t *parse_sub_exp (re_string_t *regexp, regex_t *preg, + re_token_t *token, reg_syntax_t syntax, + int nest, reg_errcode_t *err); +static bin_tree_t *parse_dup_op (bin_tree_t *dup_elem, re_string_t *regexp, + re_dfa_t *dfa, re_token_t *token, + reg_syntax_t syntax, reg_errcode_t *err); +static bin_tree_t *parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, + re_token_t *token, reg_syntax_t syntax, + reg_errcode_t *err); +static reg_errcode_t parse_bracket_element (bracket_elem_t *elem, + re_string_t *regexp, + re_token_t *token, int token_len, + re_dfa_t *dfa, + reg_syntax_t syntax, + int accept_hyphen); +static reg_errcode_t parse_bracket_symbol (bracket_elem_t *elem, + re_string_t *regexp, + re_token_t *token); +#ifndef _LIBC +# ifdef RE_ENABLE_I18N +static reg_errcode_t build_range_exp (re_bitset_ptr_t sbcset, + re_charset_t *mbcset, int *range_alloc, + bracket_elem_t *start_elem, + bracket_elem_t *end_elem); +static reg_errcode_t build_collating_symbol (re_bitset_ptr_t sbcset, + re_charset_t *mbcset, + int *coll_sym_alloc, + const unsigned char *name); +# else /* not RE_ENABLE_I18N */ +static reg_errcode_t build_range_exp (re_bitset_ptr_t sbcset, + bracket_elem_t *start_elem, + bracket_elem_t *end_elem); +static reg_errcode_t build_collating_symbol (re_bitset_ptr_t sbcset, + const unsigned char *name); +# endif /* not RE_ENABLE_I18N */ +#endif /* not _LIBC */ +#ifdef RE_ENABLE_I18N +static reg_errcode_t build_equiv_class (re_bitset_ptr_t sbcset, + re_charset_t *mbcset, + int *equiv_class_alloc, + const unsigned char *name); +static reg_errcode_t build_charclass (unsigned RE_TRANSLATE_TYPE trans, + re_bitset_ptr_t sbcset, + re_charset_t *mbcset, + int *char_class_alloc, + const unsigned char *class_name, + reg_syntax_t syntax); +#else /* not RE_ENABLE_I18N */ +static reg_errcode_t build_equiv_class (re_bitset_ptr_t sbcset, + const unsigned char *name); +static reg_errcode_t build_charclass (unsigned RE_TRANSLATE_TYPE trans, + re_bitset_ptr_t sbcset, + const unsigned char *class_name, + reg_syntax_t syntax); +#endif /* not RE_ENABLE_I18N */ +static bin_tree_t *build_charclass_op (re_dfa_t *dfa, + unsigned RE_TRANSLATE_TYPE trans, + const unsigned char *class_name, + const unsigned char *extra, + int non_match, reg_errcode_t *err); +static bin_tree_t *create_tree (re_dfa_t *dfa, + bin_tree_t *left, bin_tree_t *right, + re_token_type_t type, int index); +static bin_tree_t *re_dfa_add_tree_node (re_dfa_t *dfa, + bin_tree_t *left, bin_tree_t *right, + const re_token_t *token) + __attribute ((noinline)); +static bin_tree_t *duplicate_tree (const bin_tree_t *src, re_dfa_t *dfa); +static void mark_opt_subexp (const bin_tree_t *src, re_dfa_t *dfa); +static void mark_opt_subexp_iter (const bin_tree_t *src, re_dfa_t *dfa, int idx); + +/* This table gives an error message for each of the error codes listed + in regex.h. Obviously the order here has to be same as there. + POSIX doesn't require that we do anything for REG_NOERROR, + but why not be nice? */ + +const char __re_error_msgid[] attribute_hidden = + { +#define REG_NOERROR_IDX 0 + gettext_noop ("Success") /* REG_NOERROR */ + "\0" +#define REG_NOMATCH_IDX (REG_NOERROR_IDX + sizeof "Success") + gettext_noop ("No match") /* REG_NOMATCH */ + "\0" +#define REG_BADPAT_IDX (REG_NOMATCH_IDX + sizeof "No match") + gettext_noop ("Invalid regular expression") /* REG_BADPAT */ + "\0" +#define REG_ECOLLATE_IDX (REG_BADPAT_IDX + sizeof "Invalid regular expression") + gettext_noop ("Invalid collation character") /* REG_ECOLLATE */ + "\0" +#define REG_ECTYPE_IDX (REG_ECOLLATE_IDX + sizeof "Invalid collation character") + gettext_noop ("Invalid character class name") /* REG_ECTYPE */ + "\0" +#define REG_EESCAPE_IDX (REG_ECTYPE_IDX + sizeof "Invalid character class name") + gettext_noop ("Trailing backslash") /* REG_EESCAPE */ + "\0" +#define REG_ESUBREG_IDX (REG_EESCAPE_IDX + sizeof "Trailing backslash") + gettext_noop ("Invalid back reference") /* REG_ESUBREG */ + "\0" +#define REG_EBRACK_IDX (REG_ESUBREG_IDX + sizeof "Invalid back reference") + gettext_noop ("Unmatched [ or [^") /* REG_EBRACK */ + "\0" +#define REG_EPAREN_IDX (REG_EBRACK_IDX + sizeof "Unmatched [ or [^") + gettext_noop ("Unmatched ( or \\(") /* REG_EPAREN */ + "\0" +#define REG_EBRACE_IDX (REG_EPAREN_IDX + sizeof "Unmatched ( or \\(") + gettext_noop ("Unmatched \\{") /* REG_EBRACE */ + "\0" +#define REG_BADBR_IDX (REG_EBRACE_IDX + sizeof "Unmatched \\{") + gettext_noop ("Invalid content of \\{\\}") /* REG_BADBR */ + "\0" +#define REG_ERANGE_IDX (REG_BADBR_IDX + sizeof "Invalid content of \\{\\}") + gettext_noop ("Invalid range end") /* REG_ERANGE */ + "\0" +#define REG_ESPACE_IDX (REG_ERANGE_IDX + sizeof "Invalid range end") + gettext_noop ("Memory exhausted") /* REG_ESPACE */ + "\0" +#define REG_BADRPT_IDX (REG_ESPACE_IDX + sizeof "Memory exhausted") + gettext_noop ("Invalid preceding regular expression") /* REG_BADRPT */ + "\0" +#define REG_EEND_IDX (REG_BADRPT_IDX + sizeof "Invalid preceding regular expression") + gettext_noop ("Premature end of regular expression") /* REG_EEND */ + "\0" +#define REG_ESIZE_IDX (REG_EEND_IDX + sizeof "Premature end of regular expression") + gettext_noop ("Regular expression too big") /* REG_ESIZE */ + "\0" +#define REG_ERPAREN_IDX (REG_ESIZE_IDX + sizeof "Regular expression too big") + gettext_noop ("Unmatched ) or \\)") /* REG_ERPAREN */ + }; + +const size_t __re_error_msgid_idx[] attribute_hidden = + { + REG_NOERROR_IDX, + REG_NOMATCH_IDX, + REG_BADPAT_IDX, + REG_ECOLLATE_IDX, + REG_ECTYPE_IDX, + REG_EESCAPE_IDX, + REG_ESUBREG_IDX, + REG_EBRACK_IDX, + REG_EPAREN_IDX, + REG_EBRACE_IDX, + REG_BADBR_IDX, + REG_ERANGE_IDX, + REG_ESPACE_IDX, + REG_BADRPT_IDX, + REG_EEND_IDX, + REG_ESIZE_IDX, + REG_ERPAREN_IDX + }; + +/* Entry points for GNU code. */ + +/* re_compile_pattern is the GNU regular expression compiler: it + compiles PATTERN (of length LENGTH) and puts the result in BUFP. + Returns 0 if the pattern was valid, otherwise an error string. + + Assumes the `allocated' (and perhaps `buffer') and `translate' fields + are set in BUFP on entry. */ + +const char * +re_compile_pattern (pattern, length, bufp) + const char *pattern; + size_t length; + struct re_pattern_buffer *bufp; +{ + reg_errcode_t ret; + + /* And GNU code determines whether or not to get register information + by passing null for the REGS argument to re_match, etc., not by + setting no_sub, unless RE_NO_SUB is set. */ + bufp->no_sub = !!(re_syntax_options & RE_NO_SUB); + + /* Match anchors at newline. */ + bufp->newline_anchor = 1; + + ret = re_compile_internal (bufp, pattern, length, re_syntax_options); + + if (!ret) + return NULL; + return gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]); +} +#ifdef _LIBC +weak_alias (__re_compile_pattern, re_compile_pattern) +#endif + +/* Set by `re_set_syntax' to the current regexp syntax to recognize. Can + also be assigned to arbitrarily: each pattern buffer stores its own + syntax, so it can be changed between regex compilations. */ +/* This has no initializer because initialized variables in Emacs + become read-only after dumping. */ +reg_syntax_t re_syntax_options; + + +/* Specify the precise syntax of regexps for compilation. This provides + for compatibility for various utilities which historically have + different, incompatible syntaxes. + + The argument SYNTAX is a bit mask comprised of the various bits + defined in regex.h. We return the old syntax. */ + +reg_syntax_t +re_set_syntax (syntax) + reg_syntax_t syntax; +{ + reg_syntax_t ret = re_syntax_options; + + re_syntax_options = syntax; + return ret; +} +#ifdef _LIBC +weak_alias (__re_set_syntax, re_set_syntax) +#endif + +int +re_compile_fastmap (bufp) + struct re_pattern_buffer *bufp; +{ + re_dfa_t *dfa = (re_dfa_t *) bufp->buffer; + char *fastmap = bufp->fastmap; + + memset (fastmap, '\0', sizeof (char) * SBC_MAX); + re_compile_fastmap_iter (bufp, dfa->init_state, fastmap); + if (dfa->init_state != dfa->init_state_word) + re_compile_fastmap_iter (bufp, dfa->init_state_word, fastmap); + if (dfa->init_state != dfa->init_state_nl) + re_compile_fastmap_iter (bufp, dfa->init_state_nl, fastmap); + if (dfa->init_state != dfa->init_state_begbuf) + re_compile_fastmap_iter (bufp, dfa->init_state_begbuf, fastmap); + bufp->fastmap_accurate = 1; + return 0; +} +#ifdef _LIBC +weak_alias (__re_compile_fastmap, re_compile_fastmap) +#endif + +static inline void +__attribute ((always_inline)) +re_set_fastmap (char *fastmap, int icase, int ch) +{ + fastmap[ch] = 1; + if (icase) + fastmap[tolower (ch)] = 1; +} + +/* Helper function for re_compile_fastmap. + Compile fastmap for the initial_state INIT_STATE. */ + +static void +re_compile_fastmap_iter (bufp, init_state, fastmap) + regex_t *bufp; + const re_dfastate_t *init_state; + char *fastmap; +{ + re_dfa_t *dfa = (re_dfa_t *) bufp->buffer; + int node_cnt; + int icase = (dfa->mb_cur_max == 1 && (bufp->syntax & RE_ICASE)); + for (node_cnt = 0; node_cnt < init_state->nodes.nelem; ++node_cnt) + { + int node = init_state->nodes.elems[node_cnt]; + re_token_type_t type = dfa->nodes[node].type; + + if (type == CHARACTER) + { + re_set_fastmap (fastmap, icase, dfa->nodes[node].opr.c); +#ifdef RE_ENABLE_I18N + if ((bufp->syntax & RE_ICASE) && dfa->mb_cur_max > 1) + { + unsigned char *buf = alloca (dfa->mb_cur_max), *p; + wchar_t wc; + mbstate_t state; + + p = buf; + *p++ = dfa->nodes[node].opr.c; + while (++node < dfa->nodes_len + && dfa->nodes[node].type == CHARACTER + && dfa->nodes[node].mb_partial) + *p++ = dfa->nodes[node].opr.c; + memset (&state, 0, sizeof (state)); + if (mbrtowc (&wc, (const char *) buf, p - buf, + &state) == p - buf + && __wcrtomb ((char *) buf, towlower (wc), &state) > 0) + re_set_fastmap (fastmap, 0, buf[0]); + } +#endif + } + else if (type == SIMPLE_BRACKET) + { + int i, j, ch; + for (i = 0, ch = 0; i < BITSET_UINTS; ++i) + for (j = 0; j < UINT_BITS; ++j, ++ch) + if (dfa->nodes[node].opr.sbcset[i] & (1 << j)) + re_set_fastmap (fastmap, icase, ch); + } +#ifdef RE_ENABLE_I18N + else if (type == COMPLEX_BRACKET) + { + int i; + re_charset_t *cset = dfa->nodes[node].opr.mbcset; + if (cset->non_match || cset->ncoll_syms || cset->nequiv_classes + || cset->nranges || cset->nchar_classes) + { +# ifdef _LIBC + if (_NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES) != 0) + { + /* In this case we want to catch the bytes which are + the first byte of any collation elements. + e.g. In da_DK, we want to catch 'a' since "aa" + is a valid collation element, and don't catch + 'b' since 'b' is the only collation element + which starts from 'b'. */ + int j, ch; + const int32_t *table = (const int32_t *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); + for (i = 0, ch = 0; i < BITSET_UINTS; ++i) + for (j = 0; j < UINT_BITS; ++j, ++ch) + if (table[ch] < 0) + re_set_fastmap (fastmap, icase, ch); + } +# else + if (dfa->mb_cur_max > 1) + for (i = 0; i < SBC_MAX; ++i) + if (__btowc (i) == WEOF) + re_set_fastmap (fastmap, icase, i); +# endif /* not _LIBC */ + } + for (i = 0; i < cset->nmbchars; ++i) + { + char buf[256]; + mbstate_t state; + memset (&state, '\0', sizeof (state)); + __wcrtomb (buf, cset->mbchars[i], &state); + re_set_fastmap (fastmap, icase, *(unsigned char *) buf); + if ((bufp->syntax & RE_ICASE) && dfa->mb_cur_max > 1) + { + __wcrtomb (buf, towlower (cset->mbchars[i]), &state); + re_set_fastmap (fastmap, 0, *(unsigned char *) buf); + } + } + } +#endif /* RE_ENABLE_I18N */ + else if (type == OP_PERIOD +#ifdef RE_ENABLE_I18N + || type == OP_UTF8_PERIOD +#endif /* RE_ENABLE_I18N */ + || type == END_OF_RE) + { + memset (fastmap, '\1', sizeof (char) * SBC_MAX); + if (type == END_OF_RE) + bufp->can_be_null = 1; + return; + } + } +} + +/* Entry point for POSIX code. */ +/* regcomp takes a regular expression as a string and compiles it. + + PREG is a regex_t *. We do not expect any fields to be initialized, + since POSIX says we shouldn't. Thus, we set + + `buffer' to the compiled pattern; + `used' to the length of the compiled pattern; + `syntax' to RE_SYNTAX_POSIX_EXTENDED if the + REG_EXTENDED bit in CFLAGS is set; otherwise, to + RE_SYNTAX_POSIX_BASIC; + `newline_anchor' to REG_NEWLINE being set in CFLAGS; + `fastmap' to an allocated space for the fastmap; + `fastmap_accurate' to zero; + `re_nsub' to the number of subexpressions in PATTERN. + + PATTERN is the address of the pattern string. + + CFLAGS is a series of bits which affect compilation. + + If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we + use POSIX basic syntax. + + If REG_NEWLINE is set, then . and [^...] don't match newline. + Also, regexec will try a match beginning after every newline. + + If REG_ICASE is set, then we considers upper- and lowercase + versions of letters to be equivalent when matching. + + If REG_NOSUB is set, then when PREG is passed to regexec, that + routine will report only success or failure, and nothing about the + registers. + + It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for + the return codes and their meanings.) */ + +int +regcomp (preg, pattern, cflags) + regex_t *__restrict preg; + const char *__restrict pattern; + int cflags; +{ + reg_errcode_t ret; + reg_syntax_t syntax = ((cflags & REG_EXTENDED) ? RE_SYNTAX_POSIX_EXTENDED + : RE_SYNTAX_POSIX_BASIC); + + preg->buffer = NULL; + preg->allocated = 0; + preg->used = 0; + + /* Try to allocate space for the fastmap. */ + preg->fastmap = re_malloc (char, SBC_MAX); + if (BE (preg->fastmap == NULL, 0)) + return REG_ESPACE; + + syntax |= (cflags & REG_ICASE) ? RE_ICASE : 0; + + /* If REG_NEWLINE is set, newlines are treated differently. */ + if (cflags & REG_NEWLINE) + { /* REG_NEWLINE implies neither . nor [^...] match newline. */ + syntax &= ~RE_DOT_NEWLINE; + syntax |= RE_HAT_LISTS_NOT_NEWLINE; + /* It also changes the matching behavior. */ + preg->newline_anchor = 1; + } + else + preg->newline_anchor = 0; + preg->no_sub = !!(cflags & REG_NOSUB); + preg->translate = NULL; + + ret = re_compile_internal (preg, pattern, strlen (pattern), syntax); + + /* POSIX doesn't distinguish between an unmatched open-group and an + unmatched close-group: both are REG_EPAREN. */ + if (ret == REG_ERPAREN) + ret = REG_EPAREN; + + /* We have already checked preg->fastmap != NULL. */ + if (BE (ret == REG_NOERROR, 1)) + /* Compute the fastmap now, since regexec cannot modify the pattern + buffer. This function never fails in this implementation. */ + (void) re_compile_fastmap (preg); + else + { + /* Some error occurred while compiling the expression. */ + re_free (preg->fastmap); + preg->fastmap = NULL; + } + + return (int) ret; +} +#ifdef _LIBC +weak_alias (__regcomp, regcomp) +#endif + +/* Returns a message corresponding to an error code, ERRCODE, returned + from either regcomp or regexec. We don't use PREG here. */ + +size_t +regerror (err_code, preg, errbuf, errbuf_size) + int err_code; + const regex_t *preg; + char *errbuf; + size_t errbuf_size; +{ + const char *msg; + size_t msg_size; + + if (BE (err_code < 0 + || err_code >= (int) (sizeof (__re_error_msgid_idx) + / sizeof (__re_error_msgid_idx[0])), 0)) + /* Only error codes returned by the rest of the code should be passed + to this routine. If we are given anything else, or if other regex + code generates an invalid error code, then the program has a bug. + Dump core so we can fix it. */ + abort (); + + msg = gettext (__re_error_msgid + __re_error_msgid_idx[err_code]); + + msg_size = strlen (msg) + 1; /* Includes the null. */ + + if (BE (errbuf_size != 0, 1)) + { + if (BE (msg_size > errbuf_size, 0)) + { +#if defined HAVE_MEMPCPY || defined _LIBC + *((char *) __mempcpy (errbuf, msg, errbuf_size - 1)) = '\0'; +#else + memcpy (errbuf, msg, errbuf_size - 1); + errbuf[errbuf_size - 1] = 0; +#endif + } + else + memcpy (errbuf, msg, msg_size); + } + + return msg_size; +} +#ifdef _LIBC +weak_alias (__regerror, regerror) +#endif + + +#ifdef RE_ENABLE_I18N +/* This static array is used for the map to single-byte characters when + UTF-8 is used. Otherwise we would allocate memory just to initialize + it the same all the time. UTF-8 is the preferred encoding so this is + a worthwhile optimization. */ +static const bitset utf8_sb_map = +{ + /* Set the first 128 bits. */ +# if UINT_MAX == 0xffffffff + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff +# else +# error "Add case for new unsigned int size" +# endif +}; +#endif + + +static void +free_dfa_content (re_dfa_t *dfa) +{ + int i, j; + + if (dfa->nodes) + for (i = 0; i < dfa->nodes_len; ++i) + { + re_token_t *node = dfa->nodes + i; +#ifdef RE_ENABLE_I18N + if (node->type == COMPLEX_BRACKET && node->duplicated == 0) + free_charset (node->opr.mbcset); + else +#endif /* RE_ENABLE_I18N */ + if (node->type == SIMPLE_BRACKET && node->duplicated == 0) + re_free (node->opr.sbcset); + } + re_free (dfa->nexts); + for (i = 0; i < dfa->nodes_len; ++i) + { + if (dfa->eclosures != NULL) + re_node_set_free (dfa->eclosures + i); + if (dfa->inveclosures != NULL) + re_node_set_free (dfa->inveclosures + i); + if (dfa->edests != NULL) + re_node_set_free (dfa->edests + i); + } + re_free (dfa->edests); + re_free (dfa->eclosures); + re_free (dfa->inveclosures); + re_free (dfa->nodes); + + if (dfa->state_table) + for (i = 0; i <= dfa->state_hash_mask; ++i) + { + struct re_state_table_entry *entry = dfa->state_table + i; + for (j = 0; j < entry->num; ++j) + { + re_dfastate_t *state = entry->array[j]; + free_state (state); + } + re_free (entry->array); + } + re_free (dfa->state_table); +#ifdef RE_ENABLE_I18N + if (dfa->sb_char != utf8_sb_map) + re_free (dfa->sb_char); +#endif + re_free (dfa->subexp_map); +#ifdef DEBUG + re_free (dfa->re_str); +#endif + + re_free (dfa); +} + + +/* Free dynamically allocated space used by PREG. */ + +void +regfree (preg) + regex_t *preg; +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + if (BE (dfa != NULL, 1)) + free_dfa_content (dfa); + preg->buffer = NULL; + preg->allocated = 0; + + re_free (preg->fastmap); + preg->fastmap = NULL; + + re_free (preg->translate); + preg->translate = NULL; +} +#ifdef _LIBC +weak_alias (__regfree, regfree) +#endif + +/* Entry points compatible with 4.2 BSD regex library. We don't define + them unless specifically requested. */ + +#if defined _REGEX_RE_COMP || defined _LIBC + +/* BSD has one and only one pattern buffer. */ +static struct re_pattern_buffer re_comp_buf; + +char * +# ifdef _LIBC +/* Make these definitions weak in libc, so POSIX programs can redefine + these names if they don't use our functions, and still use + regcomp/regexec above without link errors. */ +weak_function +# endif +re_comp (s) + const char *s; +{ + reg_errcode_t ret; + char *fastmap; + + if (!s) + { + if (!re_comp_buf.buffer) + return gettext ("No previous regular expression"); + return 0; + } + + if (re_comp_buf.buffer) + { + fastmap = re_comp_buf.fastmap; + re_comp_buf.fastmap = NULL; + __regfree (&re_comp_buf); + memset (&re_comp_buf, '\0', sizeof (re_comp_buf)); + re_comp_buf.fastmap = fastmap; + } + + if (re_comp_buf.fastmap == NULL) + { + re_comp_buf.fastmap = (char *) malloc (SBC_MAX); + if (re_comp_buf.fastmap == NULL) + return (char *) gettext (__re_error_msgid + + __re_error_msgid_idx[(int) REG_ESPACE]); + } + + /* Since `re_exec' always passes NULL for the `regs' argument, we + don't need to initialize the pattern buffer fields which affect it. */ + + /* Match anchors at newlines. */ + re_comp_buf.newline_anchor = 1; + + ret = re_compile_internal (&re_comp_buf, s, strlen (s), re_syntax_options); + + if (!ret) + return NULL; + + /* Yes, we're discarding `const' here if !HAVE_LIBINTL. */ + return (char *) gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]); +} + +#ifdef _LIBC +libc_freeres_fn (free_mem) +{ + __regfree (&re_comp_buf); +} +#endif + +#endif /* _REGEX_RE_COMP */ + +/* Internal entry point. + Compile the regular expression PATTERN, whose length is LENGTH. + SYNTAX indicate regular expression's syntax. */ + +static reg_errcode_t +re_compile_internal (preg, pattern, length, syntax) + regex_t *preg; + const char * pattern; + int length; + reg_syntax_t syntax; +{ + reg_errcode_t err = REG_NOERROR; + re_dfa_t *dfa; + re_string_t regexp; + + /* Initialize the pattern buffer. */ + preg->fastmap_accurate = 0; + preg->syntax = syntax; + preg->not_bol = preg->not_eol = 0; + preg->used = 0; + preg->re_nsub = 0; + preg->can_be_null = 0; + preg->regs_allocated = REGS_UNALLOCATED; + + /* Initialize the dfa. */ + dfa = (re_dfa_t *) preg->buffer; + if (BE (preg->allocated < sizeof (re_dfa_t), 0)) + { + /* If zero allocated, but buffer is non-null, try to realloc + enough space. This loses if buffer's address is bogus, but + that is the user's responsibility. If ->buffer is NULL this + is a simple allocation. */ + dfa = re_realloc (preg->buffer, re_dfa_t, 1); + if (dfa == NULL) + return REG_ESPACE; + preg->allocated = sizeof (re_dfa_t); + preg->buffer = (unsigned char *) dfa; + } + preg->used = sizeof (re_dfa_t); + + err = init_dfa (dfa, length); + if (BE (err != REG_NOERROR, 0)) + { + free_dfa_content (dfa); + preg->buffer = NULL; + preg->allocated = 0; + return err; + } +#ifdef DEBUG + dfa->re_str = re_malloc (char, length + 1); + strncpy (dfa->re_str, pattern, length + 1); +#endif + + err = re_string_construct (®exp, pattern, length, preg->translate, + syntax & RE_ICASE, dfa); + if (BE (err != REG_NOERROR, 0)) + { + re_compile_internal_free_return: + free_workarea_compile (preg); + re_string_destruct (®exp); + free_dfa_content (dfa); + preg->buffer = NULL; + preg->allocated = 0; + return err; + } + + /* Parse the regular expression, and build a structure tree. */ + preg->re_nsub = 0; + dfa->str_tree = parse (®exp, preg, syntax, &err); + if (BE (dfa->str_tree == NULL, 0)) + goto re_compile_internal_free_return; + +#ifdef RE_ENABLE_I18N + /* If possible, do searching in single byte encoding to speed things up. */ + if (dfa->is_utf8 && !(syntax & RE_ICASE) && preg->translate == NULL) + optimize_utf8 (dfa); +#endif + + if (preg->re_nsub > 0) + { + struct subexp_optimize so; + + so.dfa = dfa; + so.nodes = dfa->nodes; + so.no_sub = preg->no_sub; + so.re_nsub = preg->re_nsub; + dfa->str_tree = optimize_subexps (&so, dfa->str_tree, -1, 0); + } + + /* Analyze the tree and collect information which is necessary to + create the dfa. */ + err = analyze (dfa); + if (BE (err != REG_NOERROR, 0)) + goto re_compile_internal_free_return; + + /* Then create the initial state of the dfa. */ + err = create_initial_state (dfa); + + /* Release work areas. */ + free_workarea_compile (preg); + re_string_destruct (®exp); + + if (BE (err != REG_NOERROR, 0)) + { + free_dfa_content (dfa); + preg->buffer = NULL; + preg->allocated = 0; + } + + return err; +} + +/* Initialize DFA. We use the length of the regular expression PAT_LEN + as the initial length of some arrays. */ + +static reg_errcode_t +init_dfa (dfa, pat_len) + re_dfa_t *dfa; + int pat_len; +{ + int table_size; +#ifndef _LIBC + char *codeset_name; +#endif + + memset (dfa, '\0', sizeof (re_dfa_t)); + + /* Force allocation of str_tree_storage the first time. */ + dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE; + + dfa->nodes_alloc = pat_len + 1; + dfa->nodes = re_malloc (re_token_t, dfa->nodes_alloc); + + dfa->states_alloc = pat_len + 1; + + /* table_size = 2 ^ ceil(log pat_len) */ + for (table_size = 1; table_size > 0; table_size <<= 1) + if (table_size > pat_len) + break; + + dfa->state_table = calloc (sizeof (struct re_state_table_entry), table_size); + dfa->state_hash_mask = table_size - 1; + + dfa->mb_cur_max = MB_CUR_MAX; +#ifdef _LIBC + if (dfa->mb_cur_max == 6 + && strcmp (_NL_CURRENT (LC_CTYPE, _NL_CTYPE_CODESET_NAME), "UTF-8") == 0) + dfa->is_utf8 = 1; + dfa->map_notascii = (_NL_CURRENT_WORD (LC_CTYPE, _NL_CTYPE_MAP_TO_NONASCII) + != 0); +#else +# ifdef HAVE_LANGINFO_CODESET + codeset_name = nl_langinfo (CODESET); +# else + codeset_name = getenv ("LC_ALL"); + if (codeset_name == NULL || codeset_name[0] == '\0') + codeset_name = getenv ("LC_CTYPE"); + if (codeset_name == NULL || codeset_name[0] == '\0') + codeset_name = getenv ("LANG"); + if (codeset_name == NULL) + codeset_name = ""; + else if (strchr (codeset_name, '.') != NULL) + codeset_name = strchr (codeset_name, '.') + 1; +# endif + + if (strcasecmp (codeset_name, "UTF-8") == 0 + || strcasecmp (codeset_name, "UTF8") == 0) + dfa->is_utf8 = 1; + + /* We check exhaustively in the loop below if this charset is a + superset of ASCII. */ + dfa->map_notascii = 0; +#endif + +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + { + if (dfa->is_utf8) + dfa->sb_char = (re_bitset_ptr_t) utf8_sb_map; + else + { + int i, j, ch; + + dfa->sb_char = (re_bitset_ptr_t) calloc (sizeof (bitset), 1); + if (BE (dfa->sb_char == NULL, 0)) + return REG_ESPACE; + + /* Clear all bits by, then set those corresponding to single + byte chars. */ + bitset_empty (dfa->sb_char); + + for (i = 0, ch = 0; i < BITSET_UINTS; ++i) + for (j = 0; j < UINT_BITS; ++j, ++ch) + { + wchar_t wch = __btowc (ch); + if (wch != WEOF) + dfa->sb_char[i] |= 1 << j; +# ifndef _LIBC + if (isascii (ch) && wch != (wchar_t) ch) + dfa->map_notascii = 1; +# endif + } + } + } +#endif + + if (BE (dfa->nodes == NULL || dfa->state_table == NULL, 0)) + return REG_ESPACE; + return REG_NOERROR; +} + +/* Initialize WORD_CHAR table, which indicate which character is + "word". In this case "word" means that it is the word construction + character used by some operators like "\<", "\>", etc. */ + +static void +init_word_char (dfa) + re_dfa_t *dfa; +{ + int i, j, ch; + dfa->word_ops_used = 1; + for (i = 0, ch = 0; i < BITSET_UINTS; ++i) + for (j = 0; j < UINT_BITS; ++j, ++ch) + if (isalnum (ch) || ch == '_') + dfa->word_char[i] |= 1 << j; +} + +/* Free the work area which are only used while compiling. */ + +static void +free_workarea_compile (preg) + regex_t *preg; +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + bin_tree_storage_t *storage, *next; + for (storage = dfa->str_tree_storage; storage; storage = next) + { + next = storage->next; + re_free (storage); + } + dfa->str_tree_storage = NULL; + dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE; + dfa->str_tree = NULL; + re_free (dfa->org_indices); + dfa->org_indices = NULL; +} + +/* Create initial states for all contexts. */ + +static reg_errcode_t +create_initial_state (dfa) + re_dfa_t *dfa; +{ + int first, i; + reg_errcode_t err; + re_node_set init_nodes; + + /* Initial states have the epsilon closure of the node which is + the first node of the regular expression. */ + first = dfa->str_tree->first; + dfa->init_node = first; + err = re_node_set_init_copy (&init_nodes, dfa->eclosures + first); + if (BE (err != REG_NOERROR, 0)) + return err; + + /* The back-references which are in initial states can epsilon transit, + since in this case all of the subexpressions can be null. + Then we add epsilon closures of the nodes which are the next nodes of + the back-references. */ + if (dfa->nbackref > 0) + for (i = 0; i < init_nodes.nelem; ++i) + { + int node_idx = init_nodes.elems[i]; + re_token_type_t type = dfa->nodes[node_idx].type; + + int clexp_idx; + if (type != OP_BACK_REF) + continue; + for (clexp_idx = 0; clexp_idx < init_nodes.nelem; ++clexp_idx) + { + re_token_t *clexp_node; + clexp_node = dfa->nodes + init_nodes.elems[clexp_idx]; + if (clexp_node->type == OP_CLOSE_SUBEXP + && clexp_node->opr.idx == dfa->nodes[node_idx].opr.idx) + break; + } + if (clexp_idx == init_nodes.nelem) + continue; + + if (type == OP_BACK_REF) + { + int dest_idx = dfa->edests[node_idx].elems[0]; + if (!re_node_set_contains (&init_nodes, dest_idx)) + { + re_node_set_merge (&init_nodes, dfa->eclosures + dest_idx); + i = 0; + } + } + } + + /* It must be the first time to invoke acquire_state. */ + dfa->init_state = re_acquire_state_context (&err, dfa, &init_nodes, 0); + /* We don't check ERR here, since the initial state must not be NULL. */ + if (BE (dfa->init_state == NULL, 0)) + return err; + if (dfa->init_state->has_constraint) + { + dfa->init_state_word = re_acquire_state_context (&err, dfa, &init_nodes, + CONTEXT_WORD); + dfa->init_state_nl = re_acquire_state_context (&err, dfa, &init_nodes, + CONTEXT_NEWLINE); + dfa->init_state_begbuf = re_acquire_state_context (&err, dfa, + &init_nodes, + CONTEXT_NEWLINE + | CONTEXT_BEGBUF); + if (BE (dfa->init_state_word == NULL || dfa->init_state_nl == NULL + || dfa->init_state_begbuf == NULL, 0)) + return err; + } + else + dfa->init_state_word = dfa->init_state_nl + = dfa->init_state_begbuf = dfa->init_state; + + re_node_set_free (&init_nodes); + return REG_NOERROR; +} + +#ifdef RE_ENABLE_I18N +/* If it is possible to do searching in single byte encoding instead of UTF-8 + to speed things up, set dfa->mb_cur_max to 1, clear is_utf8 and change + DFA nodes where needed. */ + +static void +optimize_utf8 (dfa) + re_dfa_t *dfa; +{ + int node, i, mb_chars = 0, has_period = 0; + + for (node = 0; node < dfa->nodes_len; ++node) + switch (dfa->nodes[node].type) + { + case CHARACTER: + if (dfa->nodes[node].opr.c >= 0x80) + mb_chars = 1; + break; + case ANCHOR: + switch (dfa->nodes[node].opr.idx) + { + case LINE_FIRST: + case LINE_LAST: + case BUF_FIRST: + case BUF_LAST: + break; + default: + /* Word anchors etc. cannot be handled. */ + return; + } + break; + case OP_PERIOD: + has_period = 1; + break; + case OP_BACK_REF: + case OP_ALT: + case END_OF_RE: + case OP_DUP_ASTERISK: + case OP_DUP_QUESTION: + case OP_OPEN_SUBEXP: + case OP_CLOSE_SUBEXP: + break; + case SIMPLE_BRACKET: + /* Just double check. */ + for (i = 0x80 / UINT_BITS; i < BITSET_UINTS; ++i) + if (dfa->nodes[node].opr.sbcset[i]) + return; + break; + default: + return; + } + + if (mb_chars || has_period) + for (node = 0; node < dfa->nodes_len; ++node) + { + if (dfa->nodes[node].type == CHARACTER + && dfa->nodes[node].opr.c >= 0x80) + dfa->nodes[node].mb_partial = 0; + else if (dfa->nodes[node].type == OP_PERIOD) + dfa->nodes[node].type = OP_UTF8_PERIOD; + } + + /* The search can be in single byte locale. */ + dfa->mb_cur_max = 1; + dfa->is_utf8 = 0; + dfa->has_mb_node = dfa->nbackref > 0 || has_period; +} +#endif + +static bin_tree_t * +optimize_subexps (so, node, sidx, depth) + struct subexp_optimize *so; + bin_tree_t *node; + int sidx, depth; +{ + int idx, new_depth, new_sidx; + bin_tree_t *ret; + if (node == NULL) + return NULL; + + new_depth = 0; + new_sidx = sidx; + if ((depth & 1) && node->type == CONCAT + && node->right && node->right->type == 0 + && so->nodes[idx = node->right->node_idx].type == OP_CLOSE_SUBEXP) + { + new_depth = depth + 1; + if (new_depth == 2 + || (so->nodes[idx].opr.idx < 8 * sizeof (so->dfa->used_bkref_map) + && so->dfa->used_bkref_map & (1 << so->nodes[idx].opr.idx))) + new_sidx = so->nodes[idx].opr.idx; + } + node->left = optimize_subexps (so, node->left, new_sidx, new_depth); + new_depth = (depth & 1) == 0 && node->type == CONCAT + && node->left && node->left->type == 0 + && so->nodes[node->left->node_idx].type == OP_OPEN_SUBEXP + ? depth + 1 : 0; + node->right = optimize_subexps (so, node->right, sidx, new_depth); + + if (node->type != CONCAT) + return node; + if ((depth & 1) == 0 + && node->left + && node->left->type == 0 + && so->nodes[idx = node->left->node_idx].type == OP_OPEN_SUBEXP) + ret = node->right; + else if ((depth & 1) + && node->right + && node->right->type == 0 + && so->nodes[idx = node->right->node_idx].type == OP_CLOSE_SUBEXP) + ret = node->left; + else + return node; + + if (so->nodes[idx].opr.idx < 8 * sizeof (so->dfa->used_bkref_map) + && so->dfa->used_bkref_map & (1 << so->nodes[idx].opr.idx)) + return node; + + if (!so->no_sub) + { + int i; + + if (depth < 2) + return node; + + if (so->dfa->subexp_map == NULL) + { + so->dfa->subexp_map = re_malloc (int, so->re_nsub); + if (so->dfa->subexp_map == NULL) + return node; + + for (i = 0; i < so->re_nsub; i++) + so->dfa->subexp_map[i] = i; + } + + i = so->nodes[idx].opr.idx; + assert (sidx < i); + so->dfa->subexp_map[i] = sidx; + } + + so->nodes[idx].type = OP_DELETED_SUBEXP; + ret->parent = node->parent; + return ret; +} + +/* Analyze the structure tree, and calculate "first", "next", "edest", + "eclosure", and "inveclosure". */ + +static reg_errcode_t +analyze (dfa) + re_dfa_t *dfa; +{ + int i; + reg_errcode_t ret; + + /* Allocate arrays. */ + dfa->nexts = re_malloc (int, dfa->nodes_alloc); + dfa->org_indices = re_malloc (int, dfa->nodes_alloc); + dfa->edests = re_malloc (re_node_set, dfa->nodes_alloc); + dfa->eclosures = re_malloc (re_node_set, dfa->nodes_alloc); + dfa->inveclosures = re_malloc (re_node_set, dfa->nodes_alloc); + if (BE (dfa->nexts == NULL || dfa->org_indices == NULL || dfa->edests == NULL + || dfa->eclosures == NULL || dfa->inveclosures == NULL, 0)) + return REG_ESPACE; + /* Initialize them. */ + for (i = 0; i < dfa->nodes_len; ++i) + { + dfa->nexts[i] = -1; + re_node_set_init_empty (dfa->edests + i); + re_node_set_init_empty (dfa->eclosures + i); + re_node_set_init_empty (dfa->inveclosures + i); + } + + ret = analyze_tree (dfa, dfa->str_tree); + if (BE (ret == REG_NOERROR, 1)) + { + ret = calc_eclosure (dfa); + if (ret == REG_NOERROR) + calc_inveclosure (dfa); + } + return ret; +} + +/* Helper functions for analyze. + This function calculate "first", "next", and "edest" for the subtree + whose root is NODE. */ + +static reg_errcode_t +analyze_tree (dfa, node) + re_dfa_t *dfa; + bin_tree_t *node; +{ + reg_errcode_t ret; + if (node->first == -1) + calc_first (dfa, node); + if (node->next == -1) + calc_next (dfa, node); + calc_epsdest (dfa, node); + + /* Calculate "first" etc. for the left child. */ + if (node->left != NULL) + { + ret = analyze_tree (dfa, node->left); + if (BE (ret != REG_NOERROR, 0)) + return ret; + } + /* Calculate "first" etc. for the right child. */ + if (node->right != NULL) + { + ret = analyze_tree (dfa, node->right); + if (BE (ret != REG_NOERROR, 0)) + return ret; + } + return REG_NOERROR; +} + +/* Calculate "first" for the node NODE. */ +static void +calc_first (dfa, node) + re_dfa_t *dfa; + bin_tree_t *node; +{ + int idx, type; + idx = node->node_idx; + type = (node->type == 0) ? dfa->nodes[idx].type : node->type; + + switch (type) + { +#ifdef DEBUG + case OP_OPEN_BRACKET: + case OP_CLOSE_BRACKET: + case OP_OPEN_DUP_NUM: + case OP_CLOSE_DUP_NUM: + case OP_DUP_PLUS: + case OP_NON_MATCH_LIST: + case OP_OPEN_COLL_ELEM: + case OP_CLOSE_COLL_ELEM: + case OP_OPEN_EQUIV_CLASS: + case OP_CLOSE_EQUIV_CLASS: + case OP_OPEN_CHAR_CLASS: + case OP_CLOSE_CHAR_CLASS: + /* These must not appear here. */ + assert (0); +#endif + case END_OF_RE: + case CHARACTER: + case OP_PERIOD: + case OP_DUP_ASTERISK: + case OP_DUP_QUESTION: +#ifdef RE_ENABLE_I18N + case OP_UTF8_PERIOD: + case COMPLEX_BRACKET: +#endif /* RE_ENABLE_I18N */ + case SIMPLE_BRACKET: + case OP_BACK_REF: + case ANCHOR: + case OP_OPEN_SUBEXP: + case OP_CLOSE_SUBEXP: + node->first = idx; + break; + case OP_ALT: + node->first = idx; + break; + /* else fall through */ + default: +#ifdef DEBUG + assert (node->left != NULL); +#endif + if (node->left->first == -1) + calc_first (dfa, node->left); + node->first = node->left->first; + break; + } +} + +/* Calculate "next" for the node NODE. */ + +static void +calc_next (dfa, node) + re_dfa_t *dfa; + bin_tree_t *node; +{ + int idx, type; + bin_tree_t *parent = node->parent; + if (parent == NULL) + { + node->next = -1; + idx = node->node_idx; + if (node->type == 0) + dfa->nexts[idx] = node->next; + return; + } + + idx = parent->node_idx; + type = (parent->type == 0) ? dfa->nodes[idx].type : parent->type; + + switch (type) + { + case OP_DUP_ASTERISK: + node->next = idx; + break; + case CONCAT: + if (parent->left == node) + { + if (parent->right->first == -1) + calc_first (dfa, parent->right); + node->next = parent->right->first; + break; + } + /* else fall through */ + default: + if (parent->next == -1) + calc_next (dfa, parent); + node->next = parent->next; + break; + } + idx = node->node_idx; + if (node->type == 0) + dfa->nexts[idx] = node->next; +} + +/* Calculate "edest" for the node NODE. */ + +static void +calc_epsdest (dfa, node) + re_dfa_t *dfa; + bin_tree_t *node; +{ + int idx; + idx = node->node_idx; + if (node->type == 0) + { + if (dfa->nodes[idx].type == OP_DUP_ASTERISK + || dfa->nodes[idx].type == OP_DUP_QUESTION) + { + if (node->left->first == -1) + calc_first (dfa, node->left); + if (node->next == -1) + calc_next (dfa, node); + re_node_set_init_2 (dfa->edests + idx, node->left->first, + node->next); + } + else if (dfa->nodes[idx].type == OP_ALT) + { + int left, right; + if (node->left != NULL) + { + if (node->left->first == -1) + calc_first (dfa, node->left); + left = node->left->first; + } + else + { + if (node->next == -1) + calc_next (dfa, node); + left = node->next; + } + if (node->right != NULL) + { + if (node->right->first == -1) + calc_first (dfa, node->right); + right = node->right->first; + } + else + { + if (node->next == -1) + calc_next (dfa, node); + right = node->next; + } + re_node_set_init_2 (dfa->edests + idx, left, right); + } + else if (dfa->nodes[idx].type == ANCHOR + || dfa->nodes[idx].type == OP_OPEN_SUBEXP + || dfa->nodes[idx].type == OP_CLOSE_SUBEXP + || dfa->nodes[idx].type == OP_BACK_REF) + re_node_set_init_1 (dfa->edests + idx, node->next); + else + assert (!IS_EPSILON_NODE (dfa->nodes[idx].type)); + } +} + +/* Duplicate the epsilon closure of the node ROOT_NODE. + Note that duplicated nodes have constraint INIT_CONSTRAINT in addition + to their own constraint. */ + +static reg_errcode_t +duplicate_node_closure (dfa, top_org_node, top_clone_node, root_node, + init_constraint) + re_dfa_t *dfa; + int top_org_node, top_clone_node, root_node; + unsigned int init_constraint; +{ + reg_errcode_t err; + int org_node, clone_node, ret; + unsigned int constraint = init_constraint; + for (org_node = top_org_node, clone_node = top_clone_node;;) + { + int org_dest, clone_dest; + if (dfa->nodes[org_node].type == OP_BACK_REF) + { + /* If the back reference epsilon-transit, its destination must + also have the constraint. Then duplicate the epsilon closure + of the destination of the back reference, and store it in + edests of the back reference. */ + org_dest = dfa->nexts[org_node]; + re_node_set_empty (dfa->edests + clone_node); + err = duplicate_node (&clone_dest, dfa, org_dest, constraint); + if (BE (err != REG_NOERROR, 0)) + return err; + dfa->nexts[clone_node] = dfa->nexts[org_node]; + ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); + if (BE (ret < 0, 0)) + return REG_ESPACE; + } + else if (dfa->edests[org_node].nelem == 0) + { + /* In case of the node can't epsilon-transit, don't duplicate the + destination and store the original destination as the + destination of the node. */ + dfa->nexts[clone_node] = dfa->nexts[org_node]; + break; + } + else if (dfa->edests[org_node].nelem == 1) + { + /* In case of the node can epsilon-transit, and it has only one + destination. */ + org_dest = dfa->edests[org_node].elems[0]; + re_node_set_empty (dfa->edests + clone_node); + if (dfa->nodes[org_node].type == ANCHOR) + { + /* In case of the node has another constraint, append it. */ + if (org_node == root_node && clone_node != org_node) + { + /* ...but if the node is root_node itself, it means the + epsilon closure have a loop, then tie it to the + destination of the root_node. */ + ret = re_node_set_insert (dfa->edests + clone_node, + org_dest); + if (BE (ret < 0, 0)) + return REG_ESPACE; + break; + } + constraint |= dfa->nodes[org_node].opr.ctx_type; + } + err = duplicate_node (&clone_dest, dfa, org_dest, constraint); + if (BE (err != REG_NOERROR, 0)) + return err; + ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); + if (BE (ret < 0, 0)) + return REG_ESPACE; + } + else /* dfa->edests[org_node].nelem == 2 */ + { + /* In case of the node can epsilon-transit, and it has two + destinations. E.g. '|', '*', '+', '?'. */ + org_dest = dfa->edests[org_node].elems[0]; + re_node_set_empty (dfa->edests + clone_node); + /* Search for a duplicated node which satisfies the constraint. */ + clone_dest = search_duplicated_node (dfa, org_dest, constraint); + if (clone_dest == -1) + { + /* There are no such a duplicated node, create a new one. */ + err = duplicate_node (&clone_dest, dfa, org_dest, constraint); + if (BE (err != REG_NOERROR, 0)) + return err; + ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); + if (BE (ret < 0, 0)) + return REG_ESPACE; + err = duplicate_node_closure (dfa, org_dest, clone_dest, + root_node, constraint); + if (BE (err != REG_NOERROR, 0)) + return err; + } + else + { + /* There are a duplicated node which satisfy the constraint, + use it to avoid infinite loop. */ + ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); + if (BE (ret < 0, 0)) + return REG_ESPACE; + } + + org_dest = dfa->edests[org_node].elems[1]; + err = duplicate_node (&clone_dest, dfa, org_dest, constraint); + if (BE (err != REG_NOERROR, 0)) + return err; + ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); + if (BE (ret < 0, 0)) + return REG_ESPACE; + } + org_node = org_dest; + clone_node = clone_dest; + } + return REG_NOERROR; +} + +/* Search for a node which is duplicated from the node ORG_NODE, and + satisfies the constraint CONSTRAINT. */ + +static int +search_duplicated_node (dfa, org_node, constraint) + re_dfa_t *dfa; + int org_node; + unsigned int constraint; +{ + int idx; + for (idx = dfa->nodes_len - 1; dfa->nodes[idx].duplicated && idx > 0; --idx) + { + if (org_node == dfa->org_indices[idx] + && constraint == dfa->nodes[idx].constraint) + return idx; /* Found. */ + } + return -1; /* Not found. */ +} + +/* Duplicate the node whose index is ORG_IDX and set the constraint CONSTRAINT. + The new index will be stored in NEW_IDX and return REG_NOERROR if succeeded, + otherwise return the error code. */ + +static reg_errcode_t +duplicate_node (new_idx, dfa, org_idx, constraint) + re_dfa_t *dfa; + int *new_idx, org_idx; + unsigned int constraint; +{ + int dup_idx = re_dfa_add_node (dfa, dfa->nodes[org_idx], 1); + if (BE (dup_idx == -1, 0)) + return REG_ESPACE; + dfa->nodes[dup_idx].constraint = constraint; + if (dfa->nodes[org_idx].type == ANCHOR) + dfa->nodes[dup_idx].constraint |= dfa->nodes[org_idx].opr.ctx_type; + dfa->nodes[dup_idx].duplicated = 1; + re_node_set_init_empty (dfa->edests + dup_idx); + re_node_set_init_empty (dfa->eclosures + dup_idx); + re_node_set_init_empty (dfa->inveclosures + dup_idx); + + /* Store the index of the original node. */ + dfa->org_indices[dup_idx] = org_idx; + *new_idx = dup_idx; + return REG_NOERROR; +} + +static void +calc_inveclosure (dfa) + re_dfa_t *dfa; +{ + int src, idx, dest; + for (src = 0; src < dfa->nodes_len; ++src) + { + if (dfa->nodes[src].type == OP_DELETED_SUBEXP) + continue; + for (idx = 0; idx < dfa->eclosures[src].nelem; ++idx) + { + dest = dfa->eclosures[src].elems[idx]; + re_node_set_insert_last (dfa->inveclosures + dest, src); + } + } +} + +/* Calculate "eclosure" for all the node in DFA. */ + +static reg_errcode_t +calc_eclosure (dfa) + re_dfa_t *dfa; +{ + int node_idx, incomplete; +#ifdef DEBUG + assert (dfa->nodes_len > 0); +#endif + incomplete = 0; + /* For each nodes, calculate epsilon closure. */ + for (node_idx = 0; ; ++node_idx) + { + reg_errcode_t err; + re_node_set eclosure_elem; + if (node_idx == dfa->nodes_len) + { + if (!incomplete) + break; + incomplete = 0; + node_idx = 0; + } + +#ifdef DEBUG + assert (dfa->eclosures[node_idx].nelem != -1); +#endif + if (dfa->nodes[node_idx].type == OP_DELETED_SUBEXP) + continue; + + /* If we have already calculated, skip it. */ + if (dfa->eclosures[node_idx].nelem != 0) + continue; + /* Calculate epsilon closure of `node_idx'. */ + err = calc_eclosure_iter (&eclosure_elem, dfa, node_idx, 1); + if (BE (err != REG_NOERROR, 0)) + return err; + + if (dfa->eclosures[node_idx].nelem == 0) + { + incomplete = 1; + re_node_set_free (&eclosure_elem); + } + } + return REG_NOERROR; +} + +/* Calculate epsilon closure of NODE. */ + +static reg_errcode_t +calc_eclosure_iter (new_set, dfa, node, root) + re_node_set *new_set; + re_dfa_t *dfa; + int node, root; +{ + reg_errcode_t err; + unsigned int constraint; + int i, incomplete; + re_node_set eclosure; + incomplete = 0; + err = re_node_set_alloc (&eclosure, dfa->edests[node].nelem + 1); + if (BE (err != REG_NOERROR, 0)) + return err; + + /* This indicates that we are calculating this node now. + We reference this value to avoid infinite loop. */ + dfa->eclosures[node].nelem = -1; + + constraint = ((dfa->nodes[node].type == ANCHOR) + ? dfa->nodes[node].opr.ctx_type : 0); + /* If the current node has constraints, duplicate all nodes. + Since they must inherit the constraints. */ + if (constraint + && dfa->edests[node].nelem + && !dfa->nodes[dfa->edests[node].elems[0]].duplicated) + { + int org_node, cur_node; + org_node = cur_node = node; + err = duplicate_node_closure (dfa, node, node, node, constraint); + if (BE (err != REG_NOERROR, 0)) + return err; + } + + /* Expand each epsilon destination nodes. */ + if (IS_EPSILON_NODE(dfa->nodes[node].type)) + for (i = 0; i < dfa->edests[node].nelem; ++i) + { + re_node_set eclosure_elem; + int edest = dfa->edests[node].elems[i]; + /* If calculating the epsilon closure of `edest' is in progress, + return intermediate result. */ + if (dfa->eclosures[edest].nelem == -1) + { + incomplete = 1; + continue; + } + /* If we haven't calculated the epsilon closure of `edest' yet, + calculate now. Otherwise use calculated epsilon closure. */ + if (dfa->eclosures[edest].nelem == 0) + { + err = calc_eclosure_iter (&eclosure_elem, dfa, edest, 0); + if (BE (err != REG_NOERROR, 0)) + return err; + } + else + eclosure_elem = dfa->eclosures[edest]; + /* Merge the epsilon closure of `edest'. */ + re_node_set_merge (&eclosure, &eclosure_elem); + /* If the epsilon closure of `edest' is incomplete, + the epsilon closure of this node is also incomplete. */ + if (dfa->eclosures[edest].nelem == 0) + { + incomplete = 1; + re_node_set_free (&eclosure_elem); + } + } + + /* Epsilon closures include itself. */ + re_node_set_insert (&eclosure, node); + if (incomplete && !root) + dfa->eclosures[node].nelem = 0; + else + dfa->eclosures[node] = eclosure; + *new_set = eclosure; + return REG_NOERROR; +} + +/* Functions for token which are used in the parser. */ + +/* Fetch a token from INPUT. + We must not use this function inside bracket expressions. */ + +static void +fetch_token (result, input, syntax) + re_token_t *result; + re_string_t *input; + reg_syntax_t syntax; +{ + re_string_skip_bytes (input, peek_token (result, input, syntax)); +} + +/* Peek a token from INPUT, and return the length of the token. + We must not use this function inside bracket expressions. */ + +static int +peek_token (token, input, syntax) + re_token_t *token; + re_string_t *input; + reg_syntax_t syntax; +{ + unsigned char c; + + if (re_string_eoi (input)) + { + token->type = END_OF_RE; + return 0; + } + + c = re_string_peek_byte (input, 0); + token->opr.c = c; + + token->word_char = 0; +#ifdef RE_ENABLE_I18N + token->mb_partial = 0; + if (input->mb_cur_max > 1 && + !re_string_first_byte (input, re_string_cur_idx (input))) + { + token->type = CHARACTER; + token->mb_partial = 1; + return 1; + } +#endif + if (c == '\\') + { + unsigned char c2; + if (re_string_cur_idx (input) + 1 >= re_string_length (input)) + { + token->type = BACK_SLASH; + return 1; + } + + c2 = re_string_peek_byte_case (input, 1); + token->opr.c = c2; + token->type = CHARACTER; +#ifdef RE_ENABLE_I18N + if (input->mb_cur_max > 1) + { + wint_t wc = re_string_wchar_at (input, + re_string_cur_idx (input) + 1); + token->word_char = IS_WIDE_WORD_CHAR (wc) != 0; + } + else +#endif + token->word_char = IS_WORD_CHAR (c2) != 0; + + switch (c2) + { + case '|': + if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_NO_BK_VBAR)) + token->type = OP_ALT; + break; + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + if (!(syntax & RE_NO_BK_REFS)) + { + token->type = OP_BACK_REF; + token->opr.idx = c2 - '1'; + } + break; + case '<': + if (!(syntax & RE_NO_GNU_OPS)) + { + token->type = ANCHOR; + token->opr.ctx_type = WORD_FIRST; + } + break; + case '>': + if (!(syntax & RE_NO_GNU_OPS)) + { + token->type = ANCHOR; + token->opr.ctx_type = WORD_LAST; + } + break; + case 'b': + if (!(syntax & RE_NO_GNU_OPS)) + { + token->type = ANCHOR; + token->opr.ctx_type = WORD_DELIM; + } + break; + case 'B': + if (!(syntax & RE_NO_GNU_OPS)) + { + token->type = ANCHOR; + token->opr.ctx_type = NOT_WORD_DELIM; + } + break; + case 'w': + if (!(syntax & RE_NO_GNU_OPS)) + token->type = OP_WORD; + break; + case 'W': + if (!(syntax & RE_NO_GNU_OPS)) + token->type = OP_NOTWORD; + break; + case 's': + if (!(syntax & RE_NO_GNU_OPS)) + token->type = OP_SPACE; + break; + case 'S': + if (!(syntax & RE_NO_GNU_OPS)) + token->type = OP_NOTSPACE; + break; + case '`': + if (!(syntax & RE_NO_GNU_OPS)) + { + token->type = ANCHOR; + token->opr.ctx_type = BUF_FIRST; + } + break; + case '\'': + if (!(syntax & RE_NO_GNU_OPS)) + { + token->type = ANCHOR; + token->opr.ctx_type = BUF_LAST; + } + break; + case '(': + if (!(syntax & RE_NO_BK_PARENS)) + token->type = OP_OPEN_SUBEXP; + break; + case ')': + if (!(syntax & RE_NO_BK_PARENS)) + token->type = OP_CLOSE_SUBEXP; + break; + case '+': + if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_BK_PLUS_QM)) + token->type = OP_DUP_PLUS; + break; + case '?': + if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_BK_PLUS_QM)) + token->type = OP_DUP_QUESTION; + break; + case '{': + if ((syntax & RE_INTERVALS) && (!(syntax & RE_NO_BK_BRACES))) + token->type = OP_OPEN_DUP_NUM; + break; + case '}': + if ((syntax & RE_INTERVALS) && (!(syntax & RE_NO_BK_BRACES))) + token->type = OP_CLOSE_DUP_NUM; + break; + default: + break; + } + return 2; + } + + token->type = CHARACTER; +#ifdef RE_ENABLE_I18N + if (input->mb_cur_max > 1) + { + wint_t wc = re_string_wchar_at (input, re_string_cur_idx (input)); + token->word_char = IS_WIDE_WORD_CHAR (wc) != 0; + } + else +#endif + token->word_char = IS_WORD_CHAR (token->opr.c); + + switch (c) + { + case '\n': + if (syntax & RE_NEWLINE_ALT) + token->type = OP_ALT; + break; + case '|': + if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_NO_BK_VBAR)) + token->type = OP_ALT; + break; + case '*': + token->type = OP_DUP_ASTERISK; + break; + case '+': + if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_BK_PLUS_QM)) + token->type = OP_DUP_PLUS; + break; + case '?': + if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_BK_PLUS_QM)) + token->type = OP_DUP_QUESTION; + break; + case '{': + if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES)) + token->type = OP_OPEN_DUP_NUM; + break; + case '}': + if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES)) + token->type = OP_CLOSE_DUP_NUM; + break; + case '(': + if (syntax & RE_NO_BK_PARENS) + token->type = OP_OPEN_SUBEXP; + break; + case ')': + if (syntax & RE_NO_BK_PARENS) + token->type = OP_CLOSE_SUBEXP; + break; + case '[': + token->type = OP_OPEN_BRACKET; + break; + case '.': + token->type = OP_PERIOD; + break; + case '^': + if (!(syntax & (RE_CONTEXT_INDEP_ANCHORS | RE_CARET_ANCHORS_HERE)) && + re_string_cur_idx (input) != 0) + { + char prev = re_string_peek_byte (input, -1); + if (!(syntax & RE_NEWLINE_ALT) || prev != '\n') + break; + } + token->type = ANCHOR; + token->opr.ctx_type = LINE_FIRST; + break; + case '$': + if (!(syntax & RE_CONTEXT_INDEP_ANCHORS) && + re_string_cur_idx (input) + 1 != re_string_length (input)) + { + re_token_t next; + re_string_skip_bytes (input, 1); + peek_token (&next, input, syntax); + re_string_skip_bytes (input, -1); + if (next.type != OP_ALT && next.type != OP_CLOSE_SUBEXP) + break; + } + token->type = ANCHOR; + token->opr.ctx_type = LINE_LAST; + break; + default: + break; + } + return 1; +} + +/* Peek a token from INPUT, and return the length of the token. + We must not use this function out of bracket expressions. */ + +static int +peek_token_bracket (token, input, syntax) + re_token_t *token; + re_string_t *input; + reg_syntax_t syntax; +{ + unsigned char c; + if (re_string_eoi (input)) + { + token->type = END_OF_RE; + return 0; + } + c = re_string_peek_byte (input, 0); + token->opr.c = c; + +#ifdef RE_ENABLE_I18N + if (input->mb_cur_max > 1 && + !re_string_first_byte (input, re_string_cur_idx (input))) + { + token->type = CHARACTER; + return 1; + } +#endif /* RE_ENABLE_I18N */ + + if (c == '\\' && (syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) + && re_string_cur_idx (input) + 1 < re_string_length (input)) + { + /* In this case, '\' escape a character. */ + unsigned char c2; + re_string_skip_bytes (input, 1); + c2 = re_string_peek_byte (input, 0); + token->opr.c = c2; + token->type = CHARACTER; + return 1; + } + if (c == '[') /* '[' is a special char in a bracket exps. */ + { + unsigned char c2; + int token_len; + if (re_string_cur_idx (input) + 1 < re_string_length (input)) + c2 = re_string_peek_byte (input, 1); + else + c2 = 0; + token->opr.c = c2; + token_len = 2; + switch (c2) + { + case '.': + token->type = OP_OPEN_COLL_ELEM; + break; + case '=': + token->type = OP_OPEN_EQUIV_CLASS; + break; + case ':': + if (syntax & RE_CHAR_CLASSES) + { + token->type = OP_OPEN_CHAR_CLASS; + break; + } + /* else fall through. */ + default: + token->type = CHARACTER; + token->opr.c = c; + token_len = 1; + break; + } + return token_len; + } + switch (c) + { + case '-': + token->type = OP_CHARSET_RANGE; + break; + case ']': + token->type = OP_CLOSE_BRACKET; + break; + case '^': + token->type = OP_NON_MATCH_LIST; + break; + default: + token->type = CHARACTER; + } + return 1; +} + +/* Functions for parser. */ + +/* Entry point of the parser. + Parse the regular expression REGEXP and return the structure tree. + If an error is occured, ERR is set by error code, and return NULL. + This function build the following tree, from regular expression : + CAT + / \ + / \ + EOR + + CAT means concatenation. + EOR means end of regular expression. */ + +static bin_tree_t * +parse (regexp, preg, syntax, err) + re_string_t *regexp; + regex_t *preg; + reg_syntax_t syntax; + reg_errcode_t *err; +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + bin_tree_t *tree, *eor, *root; + re_token_t current_token; + dfa->syntax = syntax; + fetch_token (¤t_token, regexp, syntax | RE_CARET_ANCHORS_HERE); + tree = parse_reg_exp (regexp, preg, ¤t_token, syntax, 0, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + eor = re_dfa_add_tree_node (dfa, NULL, NULL, ¤t_token); + if (tree != NULL) + root = create_tree (dfa, tree, eor, CONCAT, 0); + else + root = eor; + if (BE (eor == NULL || root == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + return root; +} + +/* This function build the following tree, from regular expression + |: + ALT + / \ + / \ + + + ALT means alternative, which represents the operator `|'. */ + +static bin_tree_t * +parse_reg_exp (regexp, preg, token, syntax, nest, err) + re_string_t *regexp; + regex_t *preg; + re_token_t *token; + reg_syntax_t syntax; + int nest; + reg_errcode_t *err; +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + bin_tree_t *tree, *branch = NULL; + tree = parse_branch (regexp, preg, token, syntax, nest, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + + while (token->type == OP_ALT) + { + re_token_t alt_token = *token; + fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE); + if (token->type != OP_ALT && token->type != END_OF_RE + && (nest == 0 || token->type != OP_CLOSE_SUBEXP)) + { + branch = parse_branch (regexp, preg, token, syntax, nest, err); + if (BE (*err != REG_NOERROR && branch == NULL, 0)) + return NULL; + } + else + branch = NULL; + tree = re_dfa_add_tree_node (dfa, tree, branch, &alt_token); + if (BE (tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + dfa->has_plural_match = 1; + } + return tree; +} + +/* This function build the following tree, from regular expression + : + CAT + / \ + / \ + + + CAT means concatenation. */ + +static bin_tree_t * +parse_branch (regexp, preg, token, syntax, nest, err) + re_string_t *regexp; + regex_t *preg; + re_token_t *token; + reg_syntax_t syntax; + int nest; + reg_errcode_t *err; +{ + bin_tree_t *tree, *exp; + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + tree = parse_expression (regexp, preg, token, syntax, nest, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + + while (token->type != OP_ALT && token->type != END_OF_RE + && (nest == 0 || token->type != OP_CLOSE_SUBEXP)) + { + exp = parse_expression (regexp, preg, token, syntax, nest, err); + if (BE (*err != REG_NOERROR && exp == NULL, 0)) + { + return NULL; + } + if (tree != NULL && exp != NULL) + { + tree = create_tree (dfa, tree, exp, CONCAT, 0); + if (tree == NULL) + { + *err = REG_ESPACE; + return NULL; + } + } + else if (tree == NULL) + tree = exp; + /* Otherwise exp == NULL, we don't need to create new tree. */ + } + return tree; +} + +/* This function build the following tree, from regular expression a*: + * + | + a +*/ + +static bin_tree_t * +parse_expression (regexp, preg, token, syntax, nest, err) + re_string_t *regexp; + regex_t *preg; + re_token_t *token; + reg_syntax_t syntax; + int nest; + reg_errcode_t *err; +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + bin_tree_t *tree; + switch (token->type) + { + case CHARACTER: + tree = re_dfa_add_tree_node (dfa, NULL, NULL, token); + if (BE (tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + { + while (!re_string_eoi (regexp) + && !re_string_first_byte (regexp, re_string_cur_idx (regexp))) + { + bin_tree_t *mbc_remain; + fetch_token (token, regexp, syntax); + mbc_remain = re_dfa_add_tree_node (dfa, NULL, NULL, token); + tree = create_tree (dfa, tree, mbc_remain, CONCAT, 0); + if (BE (mbc_remain == NULL || tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + } + } +#endif + break; + case OP_OPEN_SUBEXP: + tree = parse_sub_exp (regexp, preg, token, syntax, nest + 1, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + break; + case OP_OPEN_BRACKET: + tree = parse_bracket_exp (regexp, dfa, token, syntax, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + break; + case OP_BACK_REF: + if (!BE (dfa->completed_bkref_map & (1 << token->opr.idx), 1)) + { + *err = REG_ESUBREG; + return NULL; + } + dfa->used_bkref_map |= 1 << token->opr.idx; + tree = re_dfa_add_tree_node (dfa, NULL, NULL, token); + if (BE (tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + ++dfa->nbackref; + dfa->has_mb_node = 1; + break; + case OP_OPEN_DUP_NUM: + if (syntax & RE_CONTEXT_INVALID_DUP) + { + *err = REG_BADRPT; + return NULL; + } + /* FALLTHROUGH */ + case OP_DUP_ASTERISK: + case OP_DUP_PLUS: + case OP_DUP_QUESTION: + if (syntax & RE_CONTEXT_INVALID_OPS) + { + *err = REG_BADRPT; + return NULL; + } + else if (syntax & RE_CONTEXT_INDEP_OPS) + { + fetch_token (token, regexp, syntax); + return parse_expression (regexp, preg, token, syntax, nest, err); + } + /* else fall through */ + case OP_CLOSE_SUBEXP: + if ((token->type == OP_CLOSE_SUBEXP) && + !(syntax & RE_UNMATCHED_RIGHT_PAREN_ORD)) + { + *err = REG_ERPAREN; + return NULL; + } + /* else fall through */ + case OP_CLOSE_DUP_NUM: + /* We treat it as a normal character. */ + + /* Then we can these characters as normal characters. */ + token->type = CHARACTER; + /* mb_partial and word_char bits should be initialized already + by peek_token. */ + tree = re_dfa_add_tree_node (dfa, NULL, NULL, token); + if (BE (tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + break; + case ANCHOR: + if ((token->opr.ctx_type + & (WORD_DELIM | NOT_WORD_DELIM | WORD_FIRST | WORD_LAST)) + && dfa->word_ops_used == 0) + init_word_char (dfa); + if (token->opr.ctx_type == WORD_DELIM + || token->opr.ctx_type == NOT_WORD_DELIM) + { + bin_tree_t *tree_first, *tree_last; + if (token->opr.ctx_type == WORD_DELIM) + { + token->opr.ctx_type = WORD_FIRST; + tree_first = re_dfa_add_tree_node (dfa, NULL, NULL, token); + token->opr.ctx_type = WORD_LAST; + } + else + { + token->opr.ctx_type = INSIDE_WORD; + tree_first = re_dfa_add_tree_node (dfa, NULL, NULL, token); + token->opr.ctx_type = INSIDE_NOTWORD; + } + tree_last = re_dfa_add_tree_node (dfa, NULL, NULL, token); + token->type = OP_ALT; + tree = re_dfa_add_tree_node (dfa, tree_first, tree_last, token); + if (BE (tree_first == NULL || tree_last == NULL || tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + } + else + { + tree = re_dfa_add_tree_node (dfa, NULL, NULL, token); + if (BE (tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + } + /* We must return here, since ANCHORs can't be followed + by repetition operators. + eg. RE"^*" is invalid or "", + it must not be "". */ + fetch_token (token, regexp, syntax); + return tree; + case OP_PERIOD: + tree = re_dfa_add_tree_node (dfa, NULL, NULL, token); + if (BE (tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + if (dfa->mb_cur_max > 1) + dfa->has_mb_node = 1; + break; + case OP_WORD: + case OP_NOTWORD: + tree = build_charclass_op (dfa, regexp->trans, + (const unsigned char *) "alnum", + (const unsigned char *) "_", + token->type == OP_NOTWORD, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + break; + case OP_SPACE: + case OP_NOTSPACE: + tree = build_charclass_op (dfa, regexp->trans, + (const unsigned char *) "space", + (const unsigned char *) "", + token->type == OP_NOTSPACE, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + break; + case OP_ALT: + case END_OF_RE: + return NULL; + case BACK_SLASH: + *err = REG_EESCAPE; + return NULL; + default: + /* Must not happen? */ +#ifdef DEBUG + assert (0); +#endif + return NULL; + } + fetch_token (token, regexp, syntax); + + while (token->type == OP_DUP_ASTERISK || token->type == OP_DUP_PLUS + || token->type == OP_DUP_QUESTION || token->type == OP_OPEN_DUP_NUM) + { + tree = parse_dup_op (tree, regexp, dfa, token, syntax, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + /* In BRE consecutive duplications are not allowed. */ + if ((syntax & RE_CONTEXT_INVALID_DUP) + && (token->type == OP_DUP_ASTERISK + || token->type == OP_OPEN_DUP_NUM)) + { + *err = REG_BADRPT; + return NULL; + } + dfa->has_plural_match = 1; + } + + return tree; +} + +/* This function build the following tree, from regular expression + (): + SUBEXP + | + +*/ + +static bin_tree_t * +parse_sub_exp (regexp, preg, token, syntax, nest, err) + re_string_t *regexp; + regex_t *preg; + re_token_t *token; + reg_syntax_t syntax; + int nest; + reg_errcode_t *err; +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + bin_tree_t *tree, *left_par, *right_par; + size_t cur_nsub; + cur_nsub = preg->re_nsub++; + + left_par = re_dfa_add_tree_node (dfa, NULL, NULL, token); + if (BE (left_par == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + dfa->nodes[left_par->node_idx].opr.idx = cur_nsub; + fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE); + + /* The subexpression may be a null string. */ + if (token->type == OP_CLOSE_SUBEXP) + tree = NULL; + else + { + tree = parse_reg_exp (regexp, preg, token, syntax, nest, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + } + if (BE (token->type != OP_CLOSE_SUBEXP, 0)) + { + *err = REG_EPAREN; + return NULL; + } + right_par = re_dfa_add_tree_node (dfa, NULL, NULL, token); + dfa->completed_bkref_map |= 1 << cur_nsub; + tree = ((tree == NULL) ? right_par + : create_tree (dfa, tree, right_par, CONCAT, 0)); + tree = create_tree (dfa, left_par, tree, CONCAT, 0); + if (BE (right_par == NULL || tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + dfa->nodes[right_par->node_idx].opr.idx = cur_nsub; + + return tree; +} + +/* This function parse repetition operators like "*", "+", "{1,3}" etc. */ + +static bin_tree_t * +parse_dup_op (elem, regexp, dfa, token, syntax, err) + bin_tree_t *elem; + re_string_t *regexp; + re_dfa_t *dfa; + re_token_t *token; + reg_syntax_t syntax; + reg_errcode_t *err; +{ + re_token_t dup_token; + bin_tree_t *tree = NULL, *old_tree = NULL; + int i, start, end, start_idx = re_string_cur_idx (regexp); + re_token_t start_token = *token; + + if (token->type == OP_OPEN_DUP_NUM) + { + end = 0; + start = fetch_number (regexp, token, syntax); + if (start == -1) + { + if (token->type == CHARACTER && token->opr.c == ',') + start = 0; /* We treat "{,m}" as "{0,m}". */ + else + { + *err = REG_BADBR; /* {} is invalid. */ + return NULL; + } + } + if (BE (start != -2, 1)) + { + /* We treat "{n}" as "{n,n}". */ + end = ((token->type == OP_CLOSE_DUP_NUM) ? start + : ((token->type == CHARACTER && token->opr.c == ',') + ? fetch_number (regexp, token, syntax) : -2)); + } + if (BE (start == -2 || end == -2, 0)) + { + /* Invalid sequence. */ + if (BE (!(syntax & RE_INVALID_INTERVAL_ORD), 0)) + { + if (token->type == END_OF_RE) + *err = REG_EBRACE; + else + *err = REG_BADBR; + + return NULL; + } + + /* If the syntax bit is set, rollback. */ + re_string_set_index (regexp, start_idx); + *token = start_token; + token->type = CHARACTER; + /* mb_partial and word_char bits should be already initialized by + peek_token. */ + return elem; + } + + if (BE (end != -1 && start > end, 0)) + { + /* First number greater than second. */ + *err = REG_BADBR; + return NULL; + } + } + else + { + start = (token->type == OP_DUP_PLUS) ? 1 : 0; + end = (token->type == OP_DUP_QUESTION) ? 1 : -1; + } + + fetch_token (token, regexp, syntax); + + /* Treat "{0}*" etc. as "{0}". */ + if (BE (elem == NULL || (start == 0 && end == 0), 0)) + return NULL; + + /* Extract "{n,m}" to "...{0,}". */ + if (BE (start > 0, 0)) + { + tree = elem; + for (i = 2; i <= start; ++i) + { + elem = duplicate_tree (elem, dfa); + tree = create_tree (dfa, tree, elem, CONCAT, 0); + if (BE (elem == NULL || tree == NULL, 0)) + goto parse_dup_op_espace; + } + + if (start == end) + return tree; + + /* Duplicate ELEM before it is marked optional. */ + elem = duplicate_tree (elem, dfa); + old_tree = tree; + } + else + old_tree = NULL; + + mark_opt_subexp (elem, dfa); + dup_token.type = (end == -1 ? OP_DUP_ASTERISK : OP_DUP_QUESTION); + tree = re_dfa_add_tree_node (dfa, elem, NULL, &dup_token); + if (BE (tree == NULL, 0)) + goto parse_dup_op_espace; + + /* This loop is actually executed only when end != -1, + to rewrite {0,n} as ((...?)?)?... We have + already created the start+1-th copy. */ + for (i = start + 2; i <= end; ++i) + { + elem = duplicate_tree (elem, dfa); + tree = create_tree (dfa, tree, elem, CONCAT, 0); + if (BE (elem == NULL || tree == NULL, 0)) + goto parse_dup_op_espace; + + tree = re_dfa_add_tree_node (dfa, tree, NULL, &dup_token); + if (BE (tree == NULL, 0)) + goto parse_dup_op_espace; + } + + if (old_tree) + tree = create_tree (dfa, old_tree, tree, CONCAT, 0); + + return tree; + + parse_dup_op_espace: + *err = REG_ESPACE; + return NULL; +} + +/* Size of the names for collating symbol/equivalence_class/character_class. + I'm not sure, but maybe enough. */ +#define BRACKET_NAME_BUF_SIZE 32 + +#ifndef _LIBC + /* Local function for parse_bracket_exp only used in case of NOT _LIBC. + Build the range expression which starts from START_ELEM, and ends + at END_ELEM. The result are written to MBCSET and SBCSET. + RANGE_ALLOC is the allocated size of mbcset->range_starts, and + mbcset->range_ends, is a pointer argument sinse we may + update it. */ + +static reg_errcode_t +# ifdef RE_ENABLE_I18N +build_range_exp (sbcset, mbcset, range_alloc, start_elem, end_elem) + re_charset_t *mbcset; + int *range_alloc; +# else /* not RE_ENABLE_I18N */ +build_range_exp (sbcset, start_elem, end_elem) +# endif /* not RE_ENABLE_I18N */ + re_bitset_ptr_t sbcset; + bracket_elem_t *start_elem, *end_elem; +{ + unsigned int start_ch, end_ch; + /* Equivalence Classes and Character Classes can't be a range start/end. */ + if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS + || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS, + 0)) + return REG_ERANGE; + + /* We can handle no multi character collating elements without libc + support. */ + if (BE ((start_elem->type == COLL_SYM + && strlen ((char *) start_elem->opr.name) > 1) + || (end_elem->type == COLL_SYM + && strlen ((char *) end_elem->opr.name) > 1), 0)) + return REG_ECOLLATE; + +# ifdef RE_ENABLE_I18N + { + wchar_t wc, start_wc, end_wc; + wchar_t cmp_buf[6] = {L'\0', L'\0', L'\0', L'\0', L'\0', L'\0'}; + + start_ch = ((start_elem->type == SB_CHAR) ? start_elem->opr.ch + : ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0] + : 0)); + end_ch = ((end_elem->type == SB_CHAR) ? end_elem->opr.ch + : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0] + : 0)); + start_wc = ((start_elem->type == SB_CHAR || start_elem->type == COLL_SYM) + ? __btowc (start_ch) : start_elem->opr.wch); + end_wc = ((end_elem->type == SB_CHAR || end_elem->type == COLL_SYM) + ? __btowc (end_ch) : end_elem->opr.wch); + if (start_wc == WEOF || end_wc == WEOF) + return REG_ECOLLATE; + cmp_buf[0] = start_wc; + cmp_buf[4] = end_wc; + if (wcscoll (cmp_buf, cmp_buf + 4) > 0) + return REG_ERANGE; + + /* Got valid collation sequence values, add them as a new entry. + However, for !_LIBC we have no collation elements: if the + character set is single byte, the single byte character set + that we build below suffices. parse_bracket_exp passes + no MBCSET if dfa->mb_cur_max == 1. */ + if (mbcset) + { + /* Check the space of the arrays. */ + if (BE (*range_alloc == mbcset->nranges, 0)) + { + /* There is not enough space, need realloc. */ + wchar_t *new_array_start, *new_array_end; + int new_nranges; + + /* +1 in case of mbcset->nranges is 0. */ + new_nranges = 2 * mbcset->nranges + 1; + /* Use realloc since mbcset->range_starts and mbcset->range_ends + are NULL if *range_alloc == 0. */ + new_array_start = re_realloc (mbcset->range_starts, wchar_t, + new_nranges); + new_array_end = re_realloc (mbcset->range_ends, wchar_t, + new_nranges); + + if (BE (new_array_start == NULL || new_array_end == NULL, 0)) + return REG_ESPACE; + + mbcset->range_starts = new_array_start; + mbcset->range_ends = new_array_end; + *range_alloc = new_nranges; + } + + mbcset->range_starts[mbcset->nranges] = start_wc; + mbcset->range_ends[mbcset->nranges++] = end_wc; + } + + /* Build the table for single byte characters. */ + for (wc = 0; wc < SBC_MAX; ++wc) + { + cmp_buf[2] = wc; + if (wcscoll (cmp_buf, cmp_buf + 2) <= 0 + && wcscoll (cmp_buf + 2, cmp_buf + 4) <= 0) + bitset_set (sbcset, wc); + } + } +# else /* not RE_ENABLE_I18N */ + { + unsigned int ch; + start_ch = ((start_elem->type == SB_CHAR ) ? start_elem->opr.ch + : ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0] + : 0)); + end_ch = ((end_elem->type == SB_CHAR ) ? end_elem->opr.ch + : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0] + : 0)); + if (start_ch > end_ch) + return REG_ERANGE; + /* Build the table for single byte characters. */ + for (ch = 0; ch < SBC_MAX; ++ch) + if (start_ch <= ch && ch <= end_ch) + bitset_set (sbcset, ch); + } +# endif /* not RE_ENABLE_I18N */ + return REG_NOERROR; +} +#endif /* not _LIBC */ + +#ifndef _LIBC +/* Helper function for parse_bracket_exp only used in case of NOT _LIBC.. + Build the collating element which is represented by NAME. + The result are written to MBCSET and SBCSET. + COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a + pointer argument since we may update it. */ + +static reg_errcode_t +# ifdef RE_ENABLE_I18N +build_collating_symbol (sbcset, mbcset, coll_sym_alloc, name) + re_charset_t *mbcset; + int *coll_sym_alloc; +# else /* not RE_ENABLE_I18N */ +build_collating_symbol (sbcset, name) +# endif /* not RE_ENABLE_I18N */ + re_bitset_ptr_t sbcset; + const unsigned char *name; +{ + size_t name_len = strlen ((const char *) name); + if (BE (name_len != 1, 0)) + return REG_ECOLLATE; + else + { + bitset_set (sbcset, name[0]); + return REG_NOERROR; + } +} +#endif /* not _LIBC */ + +/* This function parse bracket expression like "[abc]", "[a-c]", + "[[.a-a.]]" etc. */ + +static bin_tree_t * +parse_bracket_exp (regexp, dfa, token, syntax, err) + re_string_t *regexp; + re_dfa_t *dfa; + re_token_t *token; + reg_syntax_t syntax; + reg_errcode_t *err; +{ +#ifdef _LIBC + const unsigned char *collseqmb; + const char *collseqwc; + uint32_t nrules; + int32_t table_size; + const int32_t *symb_table; + const unsigned char *extra; + + /* Local function for parse_bracket_exp used in _LIBC environement. + Seek the collating symbol entry correspondings to NAME. + Return the index of the symbol in the SYMB_TABLE. */ + + auto inline int32_t + __attribute ((always_inline)) + seek_collating_symbol_entry (name, name_len) + const unsigned char *name; + size_t name_len; + { + int32_t hash = elem_hash ((const char *) name, name_len); + int32_t elem = hash % table_size; + int32_t second = hash % (table_size - 2); + while (symb_table[2 * elem] != 0) + { + /* First compare the hashing value. */ + if (symb_table[2 * elem] == hash + /* Compare the length of the name. */ + && name_len == extra[symb_table[2 * elem + 1]] + /* Compare the name. */ + && memcmp (name, &extra[symb_table[2 * elem + 1] + 1], + name_len) == 0) + { + /* Yep, this is the entry. */ + break; + } + + /* Next entry. */ + elem += second; + } + return elem; + } + + /* Local function for parse_bracket_exp used in _LIBC environement. + Look up the collation sequence value of BR_ELEM. + Return the value if succeeded, UINT_MAX otherwise. */ + + auto inline unsigned int + __attribute ((always_inline)) + lookup_collation_sequence_value (br_elem) + bracket_elem_t *br_elem; + { + if (br_elem->type == SB_CHAR) + { + /* + if (MB_CUR_MAX == 1) + */ + if (nrules == 0) + return collseqmb[br_elem->opr.ch]; + else + { + wint_t wc = __btowc (br_elem->opr.ch); + return __collseq_table_lookup (collseqwc, wc); + } + } + else if (br_elem->type == MB_CHAR) + { + return __collseq_table_lookup (collseqwc, br_elem->opr.wch); + } + else if (br_elem->type == COLL_SYM) + { + size_t sym_name_len = strlen ((char *) br_elem->opr.name); + if (nrules != 0) + { + int32_t elem, idx; + elem = seek_collating_symbol_entry (br_elem->opr.name, + sym_name_len); + if (symb_table[2 * elem] != 0) + { + /* We found the entry. */ + idx = symb_table[2 * elem + 1]; + /* Skip the name of collating element name. */ + idx += 1 + extra[idx]; + /* Skip the byte sequence of the collating element. */ + idx += 1 + extra[idx]; + /* Adjust for the alignment. */ + idx = (idx + 3) & ~3; + /* Skip the multibyte collation sequence value. */ + idx += sizeof (unsigned int); + /* Skip the wide char sequence of the collating element. */ + idx += sizeof (unsigned int) * + (1 + *(unsigned int *) (extra + idx)); + /* Return the collation sequence value. */ + return *(unsigned int *) (extra + idx); + } + else if (symb_table[2 * elem] == 0 && sym_name_len == 1) + { + /* No valid character. Match it as a single byte + character. */ + return collseqmb[br_elem->opr.name[0]]; + } + } + else if (sym_name_len == 1) + return collseqmb[br_elem->opr.name[0]]; + } + return UINT_MAX; + } + + /* Local function for parse_bracket_exp used in _LIBC environement. + Build the range expression which starts from START_ELEM, and ends + at END_ELEM. The result are written to MBCSET and SBCSET. + RANGE_ALLOC is the allocated size of mbcset->range_starts, and + mbcset->range_ends, is a pointer argument sinse we may + update it. */ + + auto inline reg_errcode_t + __attribute ((always_inline)) + build_range_exp (sbcset, mbcset, range_alloc, start_elem, end_elem) + re_charset_t *mbcset; + int *range_alloc; + re_bitset_ptr_t sbcset; + bracket_elem_t *start_elem, *end_elem; + { + unsigned int ch; + uint32_t start_collseq; + uint32_t end_collseq; + + /* Equivalence Classes and Character Classes can't be a range + start/end. */ + if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS + || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS, + 0)) + return REG_ERANGE; + + start_collseq = lookup_collation_sequence_value (start_elem); + end_collseq = lookup_collation_sequence_value (end_elem); + /* Check start/end collation sequence values. */ + if (BE (start_collseq == UINT_MAX || end_collseq == UINT_MAX, 0)) + return REG_ECOLLATE; + if (BE ((syntax & RE_NO_EMPTY_RANGES) && start_collseq > end_collseq, 0)) + return REG_ERANGE; + + /* Got valid collation sequence values, add them as a new entry. + However, if we have no collation elements, and the character set + is single byte, the single byte character set that we + build below suffices. */ + if (nrules > 0 || dfa->mb_cur_max > 1) + { + /* Check the space of the arrays. */ + if (BE (*range_alloc == mbcset->nranges, 0)) + { + /* There is not enough space, need realloc. */ + uint32_t *new_array_start; + uint32_t *new_array_end; + int new_nranges; + + /* +1 in case of mbcset->nranges is 0. */ + new_nranges = 2 * mbcset->nranges + 1; + new_array_start = re_realloc (mbcset->range_starts, uint32_t, + new_nranges); + new_array_end = re_realloc (mbcset->range_ends, uint32_t, + new_nranges); + + if (BE (new_array_start == NULL || new_array_end == NULL, 0)) + return REG_ESPACE; + + mbcset->range_starts = new_array_start; + mbcset->range_ends = new_array_end; + *range_alloc = new_nranges; + } + + mbcset->range_starts[mbcset->nranges] = start_collseq; + mbcset->range_ends[mbcset->nranges++] = end_collseq; + } + + /* Build the table for single byte characters. */ + for (ch = 0; ch < SBC_MAX; ch++) + { + uint32_t ch_collseq; + /* + if (MB_CUR_MAX == 1) + */ + if (nrules == 0) + ch_collseq = collseqmb[ch]; + else + ch_collseq = __collseq_table_lookup (collseqwc, __btowc (ch)); + if (start_collseq <= ch_collseq && ch_collseq <= end_collseq) + bitset_set (sbcset, ch); + } + return REG_NOERROR; + } + + /* Local function for parse_bracket_exp used in _LIBC environement. + Build the collating element which is represented by NAME. + The result are written to MBCSET and SBCSET. + COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a + pointer argument sinse we may update it. */ + + auto inline reg_errcode_t + __attribute ((always_inline)) + build_collating_symbol (sbcset, mbcset, coll_sym_alloc, name) + re_charset_t *mbcset; + int *coll_sym_alloc; + re_bitset_ptr_t sbcset; + const unsigned char *name; + { + int32_t elem, idx; + size_t name_len = strlen ((const char *) name); + if (nrules != 0) + { + elem = seek_collating_symbol_entry (name, name_len); + if (symb_table[2 * elem] != 0) + { + /* We found the entry. */ + idx = symb_table[2 * elem + 1]; + /* Skip the name of collating element name. */ + idx += 1 + extra[idx]; + } + else if (symb_table[2 * elem] == 0 && name_len == 1) + { + /* No valid character, treat it as a normal + character. */ + bitset_set (sbcset, name[0]); + return REG_NOERROR; + } + else + return REG_ECOLLATE; + + /* Got valid collation sequence, add it as a new entry. */ + /* Check the space of the arrays. */ + if (BE (*coll_sym_alloc == mbcset->ncoll_syms, 0)) + { + /* Not enough, realloc it. */ + /* +1 in case of mbcset->ncoll_syms is 0. */ + int new_coll_sym_alloc = 2 * mbcset->ncoll_syms + 1; + /* Use realloc since mbcset->coll_syms is NULL + if *alloc == 0. */ + int32_t *new_coll_syms = re_realloc (mbcset->coll_syms, int32_t, + new_coll_sym_alloc); + if (BE (new_coll_syms == NULL, 0)) + return REG_ESPACE; + mbcset->coll_syms = new_coll_syms; + *coll_sym_alloc = new_coll_sym_alloc; + } + mbcset->coll_syms[mbcset->ncoll_syms++] = idx; + return REG_NOERROR; + } + else + { + if (BE (name_len != 1, 0)) + return REG_ECOLLATE; + else + { + bitset_set (sbcset, name[0]); + return REG_NOERROR; + } + } + } +#endif + + re_token_t br_token; + re_bitset_ptr_t sbcset; +#ifdef RE_ENABLE_I18N + re_charset_t *mbcset; + int coll_sym_alloc = 0, range_alloc = 0, mbchar_alloc = 0; + int equiv_class_alloc = 0, char_class_alloc = 0; +#endif /* not RE_ENABLE_I18N */ + int non_match = 0; + bin_tree_t *work_tree; + int token_len; + int first_round = 1; +#ifdef _LIBC + collseqmb = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB); + nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); + if (nrules) + { + /* + if (MB_CUR_MAX > 1) + */ + collseqwc = _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQWC); + table_size = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_SYMB_HASH_SIZEMB); + symb_table = (const int32_t *) _NL_CURRENT (LC_COLLATE, + _NL_COLLATE_SYMB_TABLEMB); + extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, + _NL_COLLATE_SYMB_EXTRAMB); + } +#endif + sbcset = (re_bitset_ptr_t) calloc (sizeof (unsigned int), BITSET_UINTS); +#ifdef RE_ENABLE_I18N + mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1); +#endif /* RE_ENABLE_I18N */ +#ifdef RE_ENABLE_I18N + if (BE (sbcset == NULL || mbcset == NULL, 0)) +#else + if (BE (sbcset == NULL, 0)) +#endif /* RE_ENABLE_I18N */ + { + *err = REG_ESPACE; + return NULL; + } + + token_len = peek_token_bracket (token, regexp, syntax); + if (BE (token->type == END_OF_RE, 0)) + { + *err = REG_BADPAT; + goto parse_bracket_exp_free_return; + } + if (token->type == OP_NON_MATCH_LIST) + { +#ifdef RE_ENABLE_I18N + mbcset->non_match = 1; +#endif /* not RE_ENABLE_I18N */ + non_match = 1; + if (syntax & RE_HAT_LISTS_NOT_NEWLINE) + bitset_set (sbcset, '\0'); + re_string_skip_bytes (regexp, token_len); /* Skip a token. */ + token_len = peek_token_bracket (token, regexp, syntax); + if (BE (token->type == END_OF_RE, 0)) + { + *err = REG_BADPAT; + goto parse_bracket_exp_free_return; + } + } + + /* We treat the first ']' as a normal character. */ + if (token->type == OP_CLOSE_BRACKET) + token->type = CHARACTER; + + while (1) + { + bracket_elem_t start_elem, end_elem; + unsigned char start_name_buf[BRACKET_NAME_BUF_SIZE]; + unsigned char end_name_buf[BRACKET_NAME_BUF_SIZE]; + reg_errcode_t ret; + int token_len2 = 0, is_range_exp = 0; + re_token_t token2; + + start_elem.opr.name = start_name_buf; + ret = parse_bracket_element (&start_elem, regexp, token, token_len, dfa, + syntax, first_round); + if (BE (ret != REG_NOERROR, 0)) + { + *err = ret; + goto parse_bracket_exp_free_return; + } + first_round = 0; + + /* Get information about the next token. We need it in any case. */ + token_len = peek_token_bracket (token, regexp, syntax); + + /* Do not check for ranges if we know they are not allowed. */ + if (start_elem.type != CHAR_CLASS && start_elem.type != EQUIV_CLASS) + { + if (BE (token->type == END_OF_RE, 0)) + { + *err = REG_EBRACK; + goto parse_bracket_exp_free_return; + } + if (token->type == OP_CHARSET_RANGE) + { + re_string_skip_bytes (regexp, token_len); /* Skip '-'. */ + token_len2 = peek_token_bracket (&token2, regexp, syntax); + if (BE (token2.type == END_OF_RE, 0)) + { + *err = REG_EBRACK; + goto parse_bracket_exp_free_return; + } + if (token2.type == OP_CLOSE_BRACKET) + { + /* We treat the last '-' as a normal character. */ + re_string_skip_bytes (regexp, -token_len); + token->type = CHARACTER; + } + else + is_range_exp = 1; + } + } + + if (is_range_exp == 1) + { + end_elem.opr.name = end_name_buf; + ret = parse_bracket_element (&end_elem, regexp, &token2, token_len2, + dfa, syntax, 1); + if (BE (ret != REG_NOERROR, 0)) + { + *err = ret; + goto parse_bracket_exp_free_return; + } + + token_len = peek_token_bracket (token, regexp, syntax); + +#ifdef _LIBC + *err = build_range_exp (sbcset, mbcset, &range_alloc, + &start_elem, &end_elem); +#else +# ifdef RE_ENABLE_I18N + *err = build_range_exp (sbcset, + dfa->mb_cur_max > 1 ? mbcset : NULL, + &range_alloc, &start_elem, &end_elem); +# else + *err = build_range_exp (sbcset, &start_elem, &end_elem); +# endif +#endif /* RE_ENABLE_I18N */ + if (BE (*err != REG_NOERROR, 0)) + goto parse_bracket_exp_free_return; + } + else + { + switch (start_elem.type) + { + case SB_CHAR: + bitset_set (sbcset, start_elem.opr.ch); + break; +#ifdef RE_ENABLE_I18N + case MB_CHAR: + /* Check whether the array has enough space. */ + if (BE (mbchar_alloc == mbcset->nmbchars, 0)) + { + wchar_t *new_mbchars; + /* Not enough, realloc it. */ + /* +1 in case of mbcset->nmbchars is 0. */ + mbchar_alloc = 2 * mbcset->nmbchars + 1; + /* Use realloc since array is NULL if *alloc == 0. */ + new_mbchars = re_realloc (mbcset->mbchars, wchar_t, + mbchar_alloc); + if (BE (new_mbchars == NULL, 0)) + goto parse_bracket_exp_espace; + mbcset->mbchars = new_mbchars; + } + mbcset->mbchars[mbcset->nmbchars++] = start_elem.opr.wch; + break; +#endif /* RE_ENABLE_I18N */ + case EQUIV_CLASS: + *err = build_equiv_class (sbcset, +#ifdef RE_ENABLE_I18N + mbcset, &equiv_class_alloc, +#endif /* RE_ENABLE_I18N */ + start_elem.opr.name); + if (BE (*err != REG_NOERROR, 0)) + goto parse_bracket_exp_free_return; + break; + case COLL_SYM: + *err = build_collating_symbol (sbcset, +#ifdef RE_ENABLE_I18N + mbcset, &coll_sym_alloc, +#endif /* RE_ENABLE_I18N */ + start_elem.opr.name); + if (BE (*err != REG_NOERROR, 0)) + goto parse_bracket_exp_free_return; + break; + case CHAR_CLASS: + *err = build_charclass (regexp->trans, sbcset, +#ifdef RE_ENABLE_I18N + mbcset, &char_class_alloc, +#endif /* RE_ENABLE_I18N */ + start_elem.opr.name, syntax); + if (BE (*err != REG_NOERROR, 0)) + goto parse_bracket_exp_free_return; + break; + default: + assert (0); + break; + } + } + if (BE (token->type == END_OF_RE, 0)) + { + *err = REG_EBRACK; + goto parse_bracket_exp_free_return; + } + if (token->type == OP_CLOSE_BRACKET) + break; + } + + re_string_skip_bytes (regexp, token_len); /* Skip a token. */ + + /* If it is non-matching list. */ + if (non_match) + bitset_not (sbcset); + +#ifdef RE_ENABLE_I18N + /* Ensure only single byte characters are set. */ + if (dfa->mb_cur_max > 1) + bitset_mask (sbcset, dfa->sb_char); +#endif /* RE_ENABLE_I18N */ + + /* Build a tree for simple bracket. */ + br_token.type = SIMPLE_BRACKET; + br_token.opr.sbcset = sbcset; + work_tree = re_dfa_add_tree_node (dfa, NULL, NULL, &br_token); + if (BE (work_tree == NULL, 0)) + goto parse_bracket_exp_espace; + +#ifdef RE_ENABLE_I18N + if (mbcset->nmbchars || mbcset->ncoll_syms || mbcset->nequiv_classes + || mbcset->nranges || (dfa->mb_cur_max > 1 && (mbcset->nchar_classes + || mbcset->non_match))) + { + re_token_t alt_token; + bin_tree_t *mbc_tree; + int sbc_idx; + /* Build a tree for complex bracket. */ + dfa->has_mb_node = 1; + for (sbc_idx = 0; sbc_idx < BITSET_UINTS; ++sbc_idx) + if (sbcset[sbc_idx]) + break; + /* If there are no bits set in sbcset, there is no point + of having both SIMPLE_BRACKET and COMPLEX_BRACKET. */ + if (sbc_idx == BITSET_UINTS) + { + re_free (sbcset); + dfa->nodes[work_tree->node_idx].type = COMPLEX_BRACKET; + dfa->nodes[work_tree->node_idx].opr.mbcset = mbcset; + return work_tree; + } + br_token.type = COMPLEX_BRACKET; + br_token.opr.mbcset = mbcset; + mbc_tree = re_dfa_add_tree_node (dfa, NULL, NULL, &br_token); + if (BE (mbc_tree == NULL, 0)) + goto parse_bracket_exp_espace; + /* Then join them by ALT node. */ + alt_token.type = OP_ALT; + dfa->has_plural_match = 1; + work_tree = re_dfa_add_tree_node (dfa, work_tree, mbc_tree, &alt_token); + if (BE (mbc_tree != NULL, 1)) + return work_tree; + } + else + { + free_charset (mbcset); + return work_tree; + } +#else /* not RE_ENABLE_I18N */ + return work_tree; +#endif /* not RE_ENABLE_I18N */ + + parse_bracket_exp_espace: + *err = REG_ESPACE; + parse_bracket_exp_free_return: + re_free (sbcset); +#ifdef RE_ENABLE_I18N + free_charset (mbcset); +#endif /* RE_ENABLE_I18N */ + return NULL; +} + +/* Parse an element in the bracket expression. */ + +static reg_errcode_t +parse_bracket_element (elem, regexp, token, token_len, dfa, syntax, + accept_hyphen) + bracket_elem_t *elem; + re_string_t *regexp; + re_token_t *token; + int token_len; + re_dfa_t *dfa; + reg_syntax_t syntax; + int accept_hyphen; +{ +#ifdef RE_ENABLE_I18N + int cur_char_size; + cur_char_size = re_string_char_size_at (regexp, re_string_cur_idx (regexp)); + if (cur_char_size > 1) + { + elem->type = MB_CHAR; + elem->opr.wch = re_string_wchar_at (regexp, re_string_cur_idx (regexp)); + re_string_skip_bytes (regexp, cur_char_size); + return REG_NOERROR; + } +#endif /* RE_ENABLE_I18N */ + re_string_skip_bytes (regexp, token_len); /* Skip a token. */ + if (token->type == OP_OPEN_COLL_ELEM || token->type == OP_OPEN_CHAR_CLASS + || token->type == OP_OPEN_EQUIV_CLASS) + return parse_bracket_symbol (elem, regexp, token); + if (BE (token->type == OP_CHARSET_RANGE, 0) && !accept_hyphen) + { + /* A '-' must only appear as anything but a range indicator before + the closing bracket. Everything else is an error. */ + re_token_t token2; + (void) peek_token_bracket (&token2, regexp, syntax); + if (token2.type != OP_CLOSE_BRACKET) + /* The actual error value is not standardized since this whole + case is undefined. But ERANGE makes good sense. */ + return REG_ERANGE; + } + elem->type = SB_CHAR; + elem->opr.ch = token->opr.c; + return REG_NOERROR; +} + +/* Parse a bracket symbol in the bracket expression. Bracket symbols are + such as [::], [..], and + [==]. */ + +static reg_errcode_t +parse_bracket_symbol (elem, regexp, token) + bracket_elem_t *elem; + re_string_t *regexp; + re_token_t *token; +{ + unsigned char ch, delim = token->opr.c; + int i = 0; + if (re_string_eoi(regexp)) + return REG_EBRACK; + for (;; ++i) + { + if (i >= BRACKET_NAME_BUF_SIZE) + return REG_EBRACK; + if (token->type == OP_OPEN_CHAR_CLASS) + ch = re_string_fetch_byte_case (regexp); + else + ch = re_string_fetch_byte (regexp); + if (re_string_eoi(regexp)) + return REG_EBRACK; + if (ch == delim && re_string_peek_byte (regexp, 0) == ']') + break; + elem->opr.name[i] = ch; + } + re_string_skip_bytes (regexp, 1); + elem->opr.name[i] = '\0'; + switch (token->type) + { + case OP_OPEN_COLL_ELEM: + elem->type = COLL_SYM; + break; + case OP_OPEN_EQUIV_CLASS: + elem->type = EQUIV_CLASS; + break; + case OP_OPEN_CHAR_CLASS: + elem->type = CHAR_CLASS; + break; + default: + break; + } + return REG_NOERROR; +} + + /* Helper function for parse_bracket_exp. + Build the equivalence class which is represented by NAME. + The result are written to MBCSET and SBCSET. + EQUIV_CLASS_ALLOC is the allocated size of mbcset->equiv_classes, + is a pointer argument sinse we may update it. */ + +static reg_errcode_t +#ifdef RE_ENABLE_I18N +build_equiv_class (sbcset, mbcset, equiv_class_alloc, name) + re_charset_t *mbcset; + int *equiv_class_alloc; +#else /* not RE_ENABLE_I18N */ +build_equiv_class (sbcset, name) +#endif /* not RE_ENABLE_I18N */ + re_bitset_ptr_t sbcset; + const unsigned char *name; +{ +#if defined _LIBC + uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); + if (nrules != 0) + { + const int32_t *table, *indirect; + const unsigned char *weights, *extra, *cp; + unsigned char char_buf[2]; + int32_t idx1, idx2; + unsigned int ch; + size_t len; + /* This #include defines a local function! */ +# include + /* Calculate the index for equivalence class. */ + cp = name; + table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); + weights = (const unsigned char *) _NL_CURRENT (LC_COLLATE, + _NL_COLLATE_WEIGHTMB); + extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, + _NL_COLLATE_EXTRAMB); + indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE, + _NL_COLLATE_INDIRECTMB); + idx1 = findidx (&cp); + if (BE (idx1 == 0 || cp < name + strlen ((const char *) name), 0)) + /* This isn't a valid character. */ + return REG_ECOLLATE; + + /* Build single byte matcing table for this equivalence class. */ + char_buf[1] = (unsigned char) '\0'; + len = weights[idx1]; + for (ch = 0; ch < SBC_MAX; ++ch) + { + char_buf[0] = ch; + cp = char_buf; + idx2 = findidx (&cp); +/* + idx2 = table[ch]; +*/ + if (idx2 == 0) + /* This isn't a valid character. */ + continue; + if (len == weights[idx2]) + { + int cnt = 0; + while (cnt <= len && + weights[idx1 + 1 + cnt] == weights[idx2 + 1 + cnt]) + ++cnt; + + if (cnt > len) + bitset_set (sbcset, ch); + } + } + /* Check whether the array has enough space. */ + if (BE (*equiv_class_alloc == mbcset->nequiv_classes, 0)) + { + /* Not enough, realloc it. */ + /* +1 in case of mbcset->nequiv_classes is 0. */ + int new_equiv_class_alloc = 2 * mbcset->nequiv_classes + 1; + /* Use realloc since the array is NULL if *alloc == 0. */ + int32_t *new_equiv_classes = re_realloc (mbcset->equiv_classes, + int32_t, + new_equiv_class_alloc); + if (BE (new_equiv_classes == NULL, 0)) + return REG_ESPACE; + mbcset->equiv_classes = new_equiv_classes; + *equiv_class_alloc = new_equiv_class_alloc; + } + mbcset->equiv_classes[mbcset->nequiv_classes++] = idx1; + } + else +#endif /* _LIBC */ + { + if (BE (strlen ((const char *) name) != 1, 0)) + return REG_ECOLLATE; + bitset_set (sbcset, *name); + } + return REG_NOERROR; +} + + /* Helper function for parse_bracket_exp. + Build the character class which is represented by NAME. + The result are written to MBCSET and SBCSET. + CHAR_CLASS_ALLOC is the allocated size of mbcset->char_classes, + is a pointer argument sinse we may update it. */ + +static reg_errcode_t +#ifdef RE_ENABLE_I18N +build_charclass (trans, sbcset, mbcset, char_class_alloc, class_name, syntax) + re_charset_t *mbcset; + int *char_class_alloc; +#else /* not RE_ENABLE_I18N */ +build_charclass (trans, sbcset, class_name, syntax) +#endif /* not RE_ENABLE_I18N */ + unsigned RE_TRANSLATE_TYPE trans; + re_bitset_ptr_t sbcset; + const unsigned char *class_name; + reg_syntax_t syntax; +{ + int i; + const char *name = (const char *) class_name; + + /* In case of REG_ICASE "upper" and "lower" match the both of + upper and lower cases. */ + if ((syntax & RE_ICASE) + && (strcmp (name, "upper") == 0 || strcmp (name, "lower") == 0)) + name = "alpha"; + +#ifdef RE_ENABLE_I18N + /* Check the space of the arrays. */ + if (BE (*char_class_alloc == mbcset->nchar_classes, 0)) + { + /* Not enough, realloc it. */ + /* +1 in case of mbcset->nchar_classes is 0. */ + int new_char_class_alloc = 2 * mbcset->nchar_classes + 1; + /* Use realloc since array is NULL if *alloc == 0. */ + wctype_t *new_char_classes = re_realloc (mbcset->char_classes, wctype_t, + new_char_class_alloc); + if (BE (new_char_classes == NULL, 0)) + return REG_ESPACE; + mbcset->char_classes = new_char_classes; + *char_class_alloc = new_char_class_alloc; + } + mbcset->char_classes[mbcset->nchar_classes++] = __wctype (name); +#endif /* RE_ENABLE_I18N */ + +#define BUILD_CHARCLASS_LOOP(ctype_func) \ + for (i = 0; i < SBC_MAX; ++i) \ + { \ + if (ctype_func (i)) \ + { \ + int ch = trans ? trans[i] : i; \ + bitset_set (sbcset, ch); \ + } \ + } + + if (strcmp (name, "alnum") == 0) + BUILD_CHARCLASS_LOOP (isalnum) + else if (strcmp (name, "cntrl") == 0) + BUILD_CHARCLASS_LOOP (iscntrl) + else if (strcmp (name, "lower") == 0) + BUILD_CHARCLASS_LOOP (islower) + else if (strcmp (name, "space") == 0) + BUILD_CHARCLASS_LOOP (isspace) + else if (strcmp (name, "alpha") == 0) + BUILD_CHARCLASS_LOOP (isalpha) + else if (strcmp (name, "digit") == 0) + BUILD_CHARCLASS_LOOP (isdigit) + else if (strcmp (name, "print") == 0) + BUILD_CHARCLASS_LOOP (isprint) + else if (strcmp (name, "upper") == 0) + BUILD_CHARCLASS_LOOP (isupper) + else if (strcmp (name, "blank") == 0) + BUILD_CHARCLASS_LOOP (isblank) + else if (strcmp (name, "graph") == 0) + BUILD_CHARCLASS_LOOP (isgraph) + else if (strcmp (name, "punct") == 0) + BUILD_CHARCLASS_LOOP (ispunct) + else if (strcmp (name, "xdigit") == 0) + BUILD_CHARCLASS_LOOP (isxdigit) + else + return REG_ECTYPE; + + return REG_NOERROR; +} + +static bin_tree_t * +build_charclass_op (dfa, trans, class_name, extra, non_match, err) + re_dfa_t *dfa; + unsigned RE_TRANSLATE_TYPE trans; + const unsigned char *class_name; + const unsigned char *extra; + int non_match; + reg_errcode_t *err; +{ + re_bitset_ptr_t sbcset; +#ifdef RE_ENABLE_I18N + re_charset_t *mbcset; + int alloc = 0; +#endif /* not RE_ENABLE_I18N */ + reg_errcode_t ret; + re_token_t br_token; + bin_tree_t *tree; + + sbcset = (re_bitset_ptr_t) calloc (sizeof (unsigned int), BITSET_UINTS); +#ifdef RE_ENABLE_I18N + mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1); +#endif /* RE_ENABLE_I18N */ + +#ifdef RE_ENABLE_I18N + if (BE (sbcset == NULL || mbcset == NULL, 0)) +#else /* not RE_ENABLE_I18N */ + if (BE (sbcset == NULL, 0)) +#endif /* not RE_ENABLE_I18N */ + { + *err = REG_ESPACE; + return NULL; + } + + if (non_match) + { +#ifdef RE_ENABLE_I18N + /* + if (syntax & RE_HAT_LISTS_NOT_NEWLINE) + bitset_set(cset->sbcset, '\0'); + */ + mbcset->non_match = 1; +#endif /* not RE_ENABLE_I18N */ + } + + /* We don't care the syntax in this case. */ + ret = build_charclass (trans, sbcset, +#ifdef RE_ENABLE_I18N + mbcset, &alloc, +#endif /* RE_ENABLE_I18N */ + class_name, 0); + + if (BE (ret != REG_NOERROR, 0)) + { + re_free (sbcset); +#ifdef RE_ENABLE_I18N + free_charset (mbcset); +#endif /* RE_ENABLE_I18N */ + *err = ret; + return NULL; + } + /* \w match '_' also. */ + for (; *extra; extra++) + bitset_set (sbcset, *extra); + + /* If it is non-matching list. */ + if (non_match) + bitset_not (sbcset); + +#ifdef RE_ENABLE_I18N + /* Ensure only single byte characters are set. */ + if (dfa->mb_cur_max > 1) + bitset_mask (sbcset, dfa->sb_char); +#endif + + /* Build a tree for simple bracket. */ + br_token.type = SIMPLE_BRACKET; + br_token.opr.sbcset = sbcset; + tree = re_dfa_add_tree_node (dfa, NULL, NULL, &br_token); + if (BE (tree == NULL, 0)) + goto build_word_op_espace; + +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + { + re_token_t alt_token; + bin_tree_t *mbc_tree; + /* Build a tree for complex bracket. */ + br_token.type = COMPLEX_BRACKET; + br_token.opr.mbcset = mbcset; + dfa->has_mb_node = 1; + mbc_tree = re_dfa_add_tree_node (dfa, NULL, NULL, &br_token); + if (BE (mbc_tree == NULL, 0)) + goto build_word_op_espace; + /* Then join them by ALT node. */ + alt_token.type = OP_ALT; + dfa->has_plural_match = 1; + tree = re_dfa_add_tree_node (dfa, tree, mbc_tree, &alt_token); + if (BE (mbc_tree != NULL, 1)) + return tree; + } + else + { + free_charset (mbcset); + return tree; + } +#else /* not RE_ENABLE_I18N */ + return tree; +#endif /* not RE_ENABLE_I18N */ + + build_word_op_espace: + re_free (sbcset); +#ifdef RE_ENABLE_I18N + free_charset (mbcset); +#endif /* RE_ENABLE_I18N */ + *err = REG_ESPACE; + return NULL; +} + +/* This is intended for the expressions like "a{1,3}". + Fetch a number from `input', and return the number. + Return -1, if the number field is empty like "{,1}". + Return -2, If an error is occured. */ + +static int +fetch_number (input, token, syntax) + re_string_t *input; + re_token_t *token; + reg_syntax_t syntax; +{ + int num = -1; + unsigned char c; + while (1) + { + fetch_token (token, input, syntax); + c = token->opr.c; + if (BE (token->type == END_OF_RE, 0)) + return -2; + if (token->type == OP_CLOSE_DUP_NUM || c == ',') + break; + num = ((token->type != CHARACTER || c < '0' || '9' < c || num == -2) + ? -2 : ((num == -1) ? c - '0' : num * 10 + c - '0')); + num = (num > RE_DUP_MAX) ? -2 : num; + } + return num; +} + +#ifdef RE_ENABLE_I18N +static void +free_charset (re_charset_t *cset) +{ + re_free (cset->mbchars); +# ifdef _LIBC + re_free (cset->coll_syms); + re_free (cset->equiv_classes); + re_free (cset->range_starts); + re_free (cset->range_ends); +# endif + re_free (cset->char_classes); + re_free (cset); +} +#endif /* RE_ENABLE_I18N */ + +/* Functions for binary tree operation. */ + +/* Create a tree node. */ + +static bin_tree_t * +create_tree (dfa, left, right, type, index) + re_dfa_t *dfa; + bin_tree_t *left; + bin_tree_t *right; + re_token_type_t type; + int index; +{ + bin_tree_t *tree; + if (BE (dfa->str_tree_storage_idx == BIN_TREE_STORAGE_SIZE, 0)) + { + bin_tree_storage_t *storage = re_malloc (bin_tree_storage_t, 1); + + if (storage == NULL) + return NULL; + storage->next = dfa->str_tree_storage; + dfa->str_tree_storage = storage; + dfa->str_tree_storage_idx = 0; + } + tree = &dfa->str_tree_storage->data[dfa->str_tree_storage_idx++]; + + tree->parent = NULL; + tree->left = left; + tree->right = right; + tree->type = type; + tree->node_idx = index; + tree->first = -1; + tree->next = -1; + re_node_set_init_empty (&tree->eclosure); + + if (left != NULL) + left->parent = tree; + if (right != NULL) + right->parent = tree; + return tree; +} + +/* Create both a DFA node and a tree for it. */ + +static bin_tree_t * +re_dfa_add_tree_node (dfa, left, right, token) + re_dfa_t *dfa; + bin_tree_t *left; + bin_tree_t *right; + const re_token_t *token; +{ + int new_idx = re_dfa_add_node (dfa, *token, 0); + + if (new_idx == -1) + return NULL; + + return create_tree (dfa, left, right, 0, new_idx); +} + +/* Mark the tree SRC as an optional subexpression. */ + +static void +mark_opt_subexp (src, dfa) + const bin_tree_t *src; + re_dfa_t *dfa; +{ + /* Pass an OPT_SUBEXP_IDX which is != 1 if the duplicated tree is + a subexpression. */ + if (src->type == CONCAT + && src->left->type == NON_TYPE + && dfa->nodes[src->left->node_idx].type == OP_OPEN_SUBEXP) + mark_opt_subexp_iter (src, dfa, dfa->nodes[src->left->node_idx].opr.idx); +} + + +/* Recursive tree walker for mark_opt_subexp. */ + +static void +mark_opt_subexp_iter (src, dfa, idx) + const bin_tree_t *src; + re_dfa_t *dfa; + int idx; +{ + int node_idx; + + if (src->type == NON_TYPE) + { + node_idx = src->node_idx; + if ((dfa->nodes[node_idx].type == OP_OPEN_SUBEXP + || dfa->nodes[node_idx].type == OP_CLOSE_SUBEXP) + && dfa->nodes[node_idx].opr.idx == idx) + dfa->nodes[node_idx].opt_subexp = 1; + } + + if (src->left != NULL) + mark_opt_subexp_iter (src->left, dfa, idx); + + if (src->right != NULL) + mark_opt_subexp_iter (src->right, dfa, idx); +} + + +/* Duplicate the node SRC, and return new node. */ + +static bin_tree_t * +duplicate_tree (src, dfa) + const bin_tree_t *src; + re_dfa_t *dfa; +{ + bin_tree_t *left = NULL, *right = NULL, *new_tree; + int new_node_idx; + /* Since node indies must be according to Post-order of the tree, + we must duplicate the left at first. */ + if (src->left != NULL) + { + left = duplicate_tree (src->left, dfa); + if (left == NULL) + return NULL; + } + + /* Secondaly, duplicate the right. */ + if (src->right != NULL) + { + right = duplicate_tree (src->right, dfa); + if (right == NULL) + return NULL; + } + + /* At last, duplicate itself. */ + if (src->type == NON_TYPE) + { + new_node_idx = re_dfa_add_node (dfa, dfa->nodes[src->node_idx], 0); + dfa->nodes[new_node_idx].duplicated = 1; + if (BE (new_node_idx == -1, 0)) + return NULL; + } + else + new_node_idx = src->type; + + new_tree = create_tree (dfa, left, right, src->type, new_node_idx); + return new_tree; +} diff --git a/src/regex/regex.c b/src/regex/regex.c new file mode 100644 index 0000000..c6094f1 --- /dev/null +++ b/src/regex/regex.c @@ -0,0 +1,99 @@ +/* Extended regular expression matching and search library. + Copyright (C) 2002, 2003 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Isamu Hasegawa . + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "port.h" + +#ifdef _AIX +#pragma alloca +#else +# ifndef allocax /* predefined by HP cc +Olibcalls */ +# ifdef __GNUC__ +# define alloca(size) __builtin_alloca (size) +# else +# if HAVE_ALLOCA_H +# include +# else +# ifdef __hpux + void *alloca (); +# else +# if !defined __OS2__ && !defined WIN32 + char *alloca (); +# else +# include /* OS/2 defines alloca in here */ +# endif +# endif +# endif +# endif +# endif +#endif + +#ifdef _LIBC +/* We have to keep the namespace clean. */ +# define regfree(preg) __regfree (preg) +# define regexec(pr, st, nm, pm, ef) __regexec (pr, st, nm, pm, ef) +# define regcomp(preg, pattern, cflags) __regcomp (preg, pattern, cflags) +# define regerror(errcode, preg, errbuf, errbuf_size) \ + __regerror(errcode, preg, errbuf, errbuf_size) +# define re_set_registers(bu, re, nu, st, en) \ + __re_set_registers (bu, re, nu, st, en) +# define re_match_2(bufp, string1, size1, string2, size2, pos, regs, stop) \ + __re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop) +# define re_match(bufp, string, size, pos, regs) \ + __re_match (bufp, string, size, pos, regs) +# define re_search(bufp, string, size, startpos, range, regs) \ + __re_search (bufp, string, size, startpos, range, regs) +# define re_compile_pattern(pattern, length, bufp) \ + __re_compile_pattern (pattern, length, bufp) +# define re_set_syntax(syntax) __re_set_syntax (syntax) +# define re_search_2(bufp, st1, s1, st2, s2, startpos, range, regs, stop) \ + __re_search_2 (bufp, st1, s1, st2, s2, startpos, range, regs, stop) +# define re_compile_fastmap(bufp) __re_compile_fastmap (bufp) + +# include "../locale/localeinfo.h" +#endif + +/* POSIX says that must be included (by the caller) before + . */ +#include + +/* On some systems, limits.h sets RE_DUP_MAX to a lower value than + GNU regex allows. Include it before , which correctly + #undefs RE_DUP_MAX and sets it to the right value. */ +#include + +#include +#include "regex_internal.h" + +#include "regex_internal.ci" +#include "regcomp.ci" +#include "regexec.ci" + +/* Binary backward compatibility. */ +#if _LIBC +# include +# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3) +link_warning (re_max_failures, "the 're_max_failures' variable is obsolete and will go away.") +int re_max_failures = 2000; +# endif +#endif diff --git a/src/regex/regex.h b/src/regex/regex.h new file mode 100644 index 0000000..b2d9a62 --- /dev/null +++ b/src/regex/regex.h @@ -0,0 +1,593 @@ +/* Definitions for data structures and routines for the regular + expression library. + Copyright (C) 1985,1989-93,1995-98,2000,2001,2002,2003 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#ifndef _REGEX_H +#define _REGEX_H 1 + +#include + +/* Allow the use in C++ code. */ +#ifdef __cplusplus +extern "C" { +#endif + +/* POSIX says that must be included (by the caller) before + . */ + +#if !defined _POSIX_C_SOURCE && !defined _POSIX_SOURCE && defined VMS +/* VMS doesn't have `size_t' in , even though POSIX says it + should be there. */ +# include +#endif + +/* The following two types have to be signed and unsigned integer type + wide enough to hold a value of a pointer. For most ANSI compilers + ptrdiff_t and size_t should be likely OK. Still size of these two + types is 2 for Microsoft C. Ugh... */ +typedef long int s_reg_t; +typedef unsigned long int active_reg_t; + +/* The following bits are used to determine the regexp syntax we + recognize. The set/not-set meanings are chosen so that Emacs syntax + remains the value 0. The bits are given in alphabetical order, and + the definitions shifted by one from the previous bit; thus, when we + add or remove a bit, only one other definition need change. */ +typedef unsigned long int reg_syntax_t; + +/* If this bit is not set, then \ inside a bracket expression is literal. + If set, then such a \ quotes the following character. */ +#define RE_BACKSLASH_ESCAPE_IN_LISTS ((unsigned long int) 1) + +/* If this bit is not set, then + and ? are operators, and \+ and \? are + literals. + If set, then \+ and \? are operators and + and ? are literals. */ +#define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1) + +/* If this bit is set, then character classes are supported. They are: + [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:], + [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:]. + If not set, then character classes are not supported. */ +#define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1) + +/* If this bit is set, then ^ and $ are always anchors (outside bracket + expressions, of course). + If this bit is not set, then it depends: + ^ is an anchor if it is at the beginning of a regular + expression or after an open-group or an alternation operator; + $ is an anchor if it is at the end of a regular expression, or + before a close-group or an alternation operator. + + This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because + POSIX draft 11.2 says that * etc. in leading positions is undefined. + We already implemented a previous draft which made those constructs + invalid, though, so we haven't changed the code back. */ +#define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1) + +/* If this bit is set, then special characters are always special + regardless of where they are in the pattern. + If this bit is not set, then special characters are special only in + some contexts; otherwise they are ordinary. Specifically, + * + ? and intervals are only special when not after the beginning, + open-group, or alternation operator. */ +#define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1) + +/* If this bit is set, then *, +, ?, and { cannot be first in an re or + immediately after an alternation or begin-group operator. */ +#define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1) + +/* If this bit is set, then . matches newline. + If not set, then it doesn't. */ +#define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1) + +/* If this bit is set, then . doesn't match NUL. + If not set, then it does. */ +#define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1) + +/* If this bit is set, nonmatching lists [^...] do not match newline. + If not set, they do. */ +#define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1) + +/* If this bit is set, either \{...\} or {...} defines an + interval, depending on RE_NO_BK_BRACES. + If not set, \{, \}, {, and } are literals. */ +#define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1) + +/* If this bit is set, +, ? and | aren't recognized as operators. + If not set, they are. */ +#define RE_LIMITED_OPS (RE_INTERVALS << 1) + +/* If this bit is set, newline is an alternation operator. + If not set, newline is literal. */ +#define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1) + +/* If this bit is set, then `{...}' defines an interval, and \{ and \} + are literals. + If not set, then `\{...\}' defines an interval. */ +#define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1) + +/* If this bit is set, (...) defines a group, and \( and \) are literals. + If not set, \(...\) defines a group, and ( and ) are literals. */ +#define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1) + +/* If this bit is set, then \ matches . + If not set, then \ is a back-reference. */ +#define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1) + +/* If this bit is set, then | is an alternation operator, and \| is literal. + If not set, then \| is an alternation operator, and | is literal. */ +#define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1) + +/* If this bit is set, then an ending range point collating higher + than the starting range point, as in [z-a], is invalid. + If not set, then when ending range point collates higher than the + starting range point, the range is ignored. */ +#define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1) + +/* If this bit is set, then an unmatched ) is ordinary. + If not set, then an unmatched ) is invalid. */ +#define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1) + +/* If this bit is set, succeed as soon as we match the whole pattern, + without further backtracking. */ +#define RE_NO_POSIX_BACKTRACKING (RE_UNMATCHED_RIGHT_PAREN_ORD << 1) + +/* If this bit is set, do not process the GNU regex operators. + If not set, then the GNU regex operators are recognized. */ +#define RE_NO_GNU_OPS (RE_NO_POSIX_BACKTRACKING << 1) + +/* If this bit is set, turn on internal regex debugging. + If not set, and debugging was on, turn it off. + This only works if regex.c is compiled -DDEBUG. + We define this bit always, so that all that's needed to turn on + debugging is to recompile regex.c; the calling code can always have + this bit set, and it won't affect anything in the normal case. */ +#define RE_DEBUG (RE_NO_GNU_OPS << 1) + +/* If this bit is set, a syntactically invalid interval is treated as + a string of ordinary characters. For example, the ERE 'a{1' is + treated as 'a\{1'. */ +#define RE_INVALID_INTERVAL_ORD (RE_DEBUG << 1) + +/* If this bit is set, then ignore case when matching. + If not set, then case is significant. */ +#define RE_ICASE (RE_INVALID_INTERVAL_ORD << 1) + +/* This bit is used internally like RE_CONTEXT_INDEP_ANCHORS but only + for ^, because it is difficult to scan the regex backwards to find + whether ^ should be special. */ +#define RE_CARET_ANCHORS_HERE (RE_ICASE << 1) + +/* If this bit is set, then \{ cannot be first in an bre or + immediately after an alternation or begin-group operator. */ +#define RE_CONTEXT_INVALID_DUP (RE_CARET_ANCHORS_HERE << 1) + +/* If this bit is set, then no_sub will be set to 1 during + re_compile_pattern. */ +#define RE_NO_SUB (RE_CONTEXT_INVALID_DUP << 1) + +/* This global variable defines the particular regexp syntax to use (for + some interfaces). When a regexp is compiled, the syntax used is + stored in the pattern buffer, so changing this does not affect + already-compiled regexps. */ +extern reg_syntax_t re_syntax_options; + +/* Define combinations of the above bits for the standard possibilities. + (The [[[ comments delimit what gets put into the Texinfo file, so + don't delete them!) */ +/* [[[begin syntaxes]]] */ +#define RE_SYNTAX_EMACS 0 + +#define RE_SYNTAX_AWK \ + (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \ + | RE_NO_BK_PARENS | RE_NO_BK_REFS \ + | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \ + | RE_DOT_NEWLINE | RE_CONTEXT_INDEP_ANCHORS \ + | RE_UNMATCHED_RIGHT_PAREN_ORD | RE_NO_GNU_OPS) + +#define RE_SYNTAX_GNU_AWK \ + ((RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DEBUG) \ + & ~(RE_DOT_NOT_NULL | RE_INTERVALS | RE_CONTEXT_INDEP_OPS \ + | RE_CONTEXT_INVALID_OPS )) + +#define RE_SYNTAX_POSIX_AWK \ + (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \ + | RE_INTERVALS | RE_NO_GNU_OPS) + +#define RE_SYNTAX_GREP \ + (RE_BK_PLUS_QM | RE_CHAR_CLASSES \ + | RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS \ + | RE_NEWLINE_ALT) + +#define RE_SYNTAX_EGREP \ + (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE \ + | RE_NEWLINE_ALT | RE_NO_BK_PARENS \ + | RE_NO_BK_VBAR) + +#define RE_SYNTAX_POSIX_EGREP \ + (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES \ + | RE_INVALID_INTERVAL_ORD) + +/* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */ +#define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC + +#define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC + +/* Syntax bits common to both basic and extended POSIX regex syntax. */ +#define _RE_SYNTAX_POSIX_COMMON \ + (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \ + | RE_INTERVALS | RE_NO_EMPTY_RANGES) + +#define RE_SYNTAX_POSIX_BASIC \ + (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM | RE_CONTEXT_INVALID_DUP) + +/* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes + RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this + isn't minimal, since other operators, such as \`, aren't disabled. */ +#define RE_SYNTAX_POSIX_MINIMAL_BASIC \ + (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS) + +#define RE_SYNTAX_POSIX_EXTENDED \ + (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \ + | RE_NO_BK_PARENS | RE_NO_BK_VBAR \ + | RE_CONTEXT_INVALID_OPS | RE_UNMATCHED_RIGHT_PAREN_ORD) + +/* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INDEP_OPS is + removed and RE_NO_BK_REFS is added. */ +#define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \ + (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \ + | RE_NO_BK_PARENS | RE_NO_BK_REFS \ + | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD) +/* [[[end syntaxes]]] */ + +/* Maximum number of duplicates an interval can allow. Some systems + (erroneously) define this in other header files, but we want our + value, so remove any previous define. */ +#ifdef RE_DUP_MAX +# undef RE_DUP_MAX +#endif +/* If sizeof(int) == 2, then ((1 << 15) - 1) overflows. */ +#define RE_DUP_MAX (0x7fff) + + +/* POSIX `cflags' bits (i.e., information for `regcomp'). */ + +/* If this bit is set, then use extended regular expression syntax. + If not set, then use basic regular expression syntax. */ +#define REG_EXTENDED 1 + +/* If this bit is set, then ignore case when matching. + If not set, then case is significant. */ +#define REG_ICASE (REG_EXTENDED << 1) + +/* If this bit is set, then anchors do not match at newline + characters in the string. + If not set, then anchors do match at newlines. */ +#define REG_NEWLINE (REG_ICASE << 1) + +/* If this bit is set, then report only success or fail in regexec. + If not set, then returns differ between not matching and errors. */ +#define REG_NOSUB (REG_NEWLINE << 1) + + +/* POSIX `eflags' bits (i.e., information for regexec). */ + +/* If this bit is set, then the beginning-of-line operator doesn't match + the beginning of the string (presumably because it's not the + beginning of a line). + If not set, then the beginning-of-line operator does match the + beginning of the string. */ +#define REG_NOTBOL 1 + +/* Like REG_NOTBOL, except for the end-of-line. */ +#define REG_NOTEOL (1 << 1) + +/* Use PMATCH[0] to delimit the start and end of the search in the + buffer. */ +#define REG_STARTEND (1 << 2) + + +/* If any error codes are removed, changed, or added, update the + `re_error_msg' table in regex.c. */ +typedef enum +{ +#ifdef _XOPEN_SOURCE + REG_ENOSYS = -1, /* This will never happen for this implementation. */ +#endif + + REG_NOERROR = 0, /* Success. */ + REG_NOMATCH, /* Didn't find a match (for regexec). */ + + /* POSIX regcomp return error codes. (In the order listed in the + standard.) */ + REG_BADPAT, /* Invalid pattern. */ + REG_ECOLLATE, /* Inalid collating element. */ + REG_ECTYPE, /* Invalid character class name. */ + REG_EESCAPE, /* Trailing backslash. */ + REG_ESUBREG, /* Invalid back reference. */ + REG_EBRACK, /* Unmatched left bracket. */ + REG_EPAREN, /* Parenthesis imbalance. */ + REG_EBRACE, /* Unmatched \{. */ + REG_BADBR, /* Invalid contents of \{\}. */ + REG_ERANGE, /* Invalid range end. */ + REG_ESPACE, /* Ran out of memory. */ + REG_BADRPT, /* No preceding re for repetition op. */ + + /* Error codes we've added. */ + REG_EEND, /* Premature end. */ + REG_ESIZE, /* Compiled pattern bigger than 2^16 bytes. */ + REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */ +} reg_errcode_t; + +/* This data structure represents a compiled pattern. Before calling + the pattern compiler, the fields `buffer', `allocated', `fastmap', + `translate', and `no_sub' can be set. After the pattern has been + compiled, the `re_nsub' field is available. All other fields are + private to the regex routines. */ + +#ifndef RE_TRANSLATE_TYPE +# define RE_TRANSLATE_TYPE char * +#endif + +struct re_pattern_buffer +{ +/* [[[begin pattern_buffer]]] */ + /* Space that holds the compiled pattern. It is declared as + `unsigned char *' because its elements are + sometimes used as array indexes. */ + unsigned char *buffer; + + /* Number of bytes to which `buffer' points. */ + unsigned long int allocated; + + /* Number of bytes actually used in `buffer'. */ + unsigned long int used; + + /* Syntax setting with which the pattern was compiled. */ + reg_syntax_t syntax; + + /* Pointer to a fastmap, if any, otherwise zero. re_search uses + the fastmap, if there is one, to skip over impossible + starting points for matches. */ + char *fastmap; + + /* Either a translate table to apply to all characters before + comparing them, or zero for no translation. The translation + is applied to a pattern when it is compiled and to a string + when it is matched. */ + RE_TRANSLATE_TYPE translate; + + /* Number of subexpressions found by the compiler. */ + size_t re_nsub; + + /* Zero if this pattern cannot match the empty string, one else. + Well, in truth it's used only in `re_search_2', to see + whether or not we should use the fastmap, so we don't set + this absolutely perfectly; see `re_compile_fastmap' (the + `duplicate' case). */ + unsigned can_be_null : 1; + + /* If REGS_UNALLOCATED, allocate space in the `regs' structure + for `max (RE_NREGS, re_nsub + 1)' groups. + If REGS_REALLOCATE, reallocate space if necessary. + If REGS_FIXED, use what's there. */ +#define REGS_UNALLOCATED 0 +#define REGS_REALLOCATE 1 +#define REGS_FIXED 2 + unsigned regs_allocated : 2; + + /* Set to zero when `regex_compile' compiles a pattern; set to one + by `re_compile_fastmap' if it updates the fastmap. */ + unsigned fastmap_accurate : 1; + + /* If set, `re_match_2' does not return information about + subexpressions. */ + unsigned no_sub : 1; + + /* If set, a beginning-of-line anchor doesn't match at the + beginning of the string. */ + unsigned not_bol : 1; + + /* Similarly for an end-of-line anchor. */ + unsigned not_eol : 1; + + /* If true, an anchor at a newline matches. */ + unsigned newline_anchor : 1; + +/* [[[end pattern_buffer]]] */ +}; + +typedef struct re_pattern_buffer regex_t; + +/* Type for byte offsets within the string. POSIX mandates this. */ +typedef int regoff_t; + + +/* This is the structure we store register match data in. See + regex.texinfo for a full description of what registers match. */ +struct re_registers +{ + unsigned num_regs; + regoff_t *start; + regoff_t *end; +}; + + +/* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer, + `re_match_2' returns information about at least this many registers + the first time a `regs' structure is passed. */ +#ifndef RE_NREGS +# define RE_NREGS 30 +#endif + + +/* POSIX specification for registers. Aside from the different names than + `re_registers', POSIX uses an array of structures, instead of a + structure of arrays. */ +typedef struct +{ + regoff_t rm_so; /* Byte offset from string's start to substring's start. */ + regoff_t rm_eo; /* Byte offset from string's start to substring's end. */ +} regmatch_t; + +/* Declarations for routines. */ + +/* To avoid duplicating every routine declaration -- once with a + prototype (if we are ANSI), and once without (if we aren't) -- we + use the following macro to declare argument types. This + unfortunately clutters up the declarations a bit, but I think it's + worth it. */ + +#if __STDC__ + +# define _RE_ARGS(args) args + +#else /* not __STDC__ */ + +# define _RE_ARGS(args) () + +#endif /* not __STDC__ */ + +/* Sets the current default syntax to SYNTAX, and return the old syntax. + You can also simply assign to the `re_syntax_options' variable. */ +extern reg_syntax_t re_set_syntax _RE_ARGS ((reg_syntax_t syntax)); + +/* Compile the regular expression PATTERN, with length LENGTH + and syntax given by the global `re_syntax_options', into the buffer + BUFFER. Return NULL if successful, and an error string if not. */ +extern const char *re_compile_pattern + _RE_ARGS ((const char *pattern, size_t length, + struct re_pattern_buffer *buffer)); + + +/* Compile a fastmap for the compiled pattern in BUFFER; used to + accelerate searches. Return 0 if successful and -2 if was an + internal error. */ +extern int re_compile_fastmap _RE_ARGS ((struct re_pattern_buffer *buffer)); + + +/* Search in the string STRING (with length LENGTH) for the pattern + compiled into BUFFER. Start searching at position START, for RANGE + characters. Return the starting position of the match, -1 for no + match, or -2 for an internal error. Also return register + information in REGS (if REGS and BUFFER->no_sub are nonzero). */ +extern int re_search + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, + int length, int start, int range, struct re_registers *regs)); + + +/* Like `re_search', but search in the concatenation of STRING1 and + STRING2. Also, stop searching at index START + STOP. */ +extern int re_search_2 + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, + int length1, const char *string2, int length2, + int start, int range, struct re_registers *regs, int stop)); + + +/* Like `re_search', but return how many characters in STRING the regexp + in BUFFER matched, starting at position START. */ +extern int re_match + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, + int length, int start, struct re_registers *regs)); + + +/* Relates to `re_match' as `re_search_2' relates to `re_search'. */ +extern int re_match_2 + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, + int length1, const char *string2, int length2, + int start, struct re_registers *regs, int stop)); + + +/* Set REGS to hold NUM_REGS registers, storing them in STARTS and + ENDS. Subsequent matches using BUFFER and REGS will use this memory + for recording register information. STARTS and ENDS must be + allocated with malloc, and must each be at least `NUM_REGS * sizeof + (regoff_t)' bytes long. + + If NUM_REGS == 0, then subsequent matches should allocate their own + register data. + + Unless this function is called, the first search or match using + PATTERN_BUFFER will allocate its own register data, without + freeing the old data. */ +extern void re_set_registers + _RE_ARGS ((struct re_pattern_buffer *buffer, struct re_registers *regs, + unsigned num_regs, regoff_t *starts, regoff_t *ends)); + +#if defined _REGEX_RE_COMP || defined _LIBC +# ifndef _CRAY +/* 4.2 bsd compatibility. */ +extern char *re_comp _RE_ARGS ((const char *)); +extern int re_exec _RE_ARGS ((const char *)); +# endif +#endif + +/* GCC 2.95 and later have "__restrict"; C99 compilers have + "restrict", and "configure" may have defined "restrict". */ +#ifndef __restrict +# if ! (2 < __GNUC__ || (2 == __GNUC__ && 95 <= __GNUC_MINOR__)) +# if defined restrict || 199901L <= __STDC_VERSION__ +# define __restrict restrict +# else +# define __restrict +# endif +# endif +#endif +/* gcc 3.1 and up support the [restrict] syntax. */ +#ifndef __restrict_arr +# if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) +# define __restrict_arr __restrict +# else +# define __restrict_arr +# endif +#endif + +/* POSIX compatibility. */ +extern int regcomp _RE_ARGS ((regex_t *__restrict __preg, + const char *__restrict __pattern, + int __cflags)); + +extern int regexec _RE_ARGS ((const regex_t *__restrict __preg, + const char *__restrict __string, size_t __nmatch, + regmatch_t __pmatch[__restrict_arr], + int __eflags)); + +extern size_t regerror _RE_ARGS ((int __errcode, const regex_t *__preg, + char *__errbuf, size_t __errbuf_size)); + +extern void regfree _RE_ARGS ((regex_t *__preg)); + + +#ifdef __cplusplus +} +#endif /* C++ */ + +#endif /* regex.h */ + +/* +Local variables: +make-backup-files: t +version-control: t +trim-versions-without-asking: nil +End: +*/ diff --git a/src/regex/regex_internal.ci b/src/regex/regex_internal.ci new file mode 100644 index 0000000..001b50b --- /dev/null +++ b/src/regex/regex_internal.ci @@ -0,0 +1,1673 @@ +/* Extended regular expression matching and search library. + Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Isamu Hasegawa . + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +static void re_string_construct_common (const char *str, int len, + re_string_t *pstr, + RE_TRANSLATE_TYPE trans, int icase, + const re_dfa_t *dfa) internal_function; +#ifdef RE_ENABLE_I18N +static int re_string_skip_chars (re_string_t *pstr, int new_raw_idx, + wint_t *last_wc) internal_function; +#endif /* RE_ENABLE_I18N */ +static reg_errcode_t register_state (re_dfa_t *dfa, re_dfastate_t *newstate, + unsigned int hash) internal_function; +static re_dfastate_t *create_ci_newstate (re_dfa_t *dfa, + const re_node_set *nodes, + unsigned int hash) internal_function; +static re_dfastate_t *create_cd_newstate (re_dfa_t *dfa, + const re_node_set *nodes, + unsigned int context, + unsigned int hash) internal_function; +static unsigned int inline calc_state_hash (const re_node_set *nodes, + unsigned int context) internal_function; + +/* Functions for string operation. */ + +/* This function allocate the buffers. It is necessary to call + re_string_reconstruct before using the object. */ + +static reg_errcode_t +re_string_allocate (pstr, str, len, init_len, trans, icase, dfa) + re_string_t *pstr; + const char *str; + int len, init_len, icase; + RE_TRANSLATE_TYPE trans; + const re_dfa_t *dfa; +{ + reg_errcode_t ret; + int init_buf_len; + + /* Ensure at least one character fits into the buffers. */ + if (init_len < dfa->mb_cur_max) + init_len = dfa->mb_cur_max; + init_buf_len = (len + 1 < init_len) ? len + 1: init_len; + re_string_construct_common (str, len, pstr, trans, icase, dfa); + + ret = re_string_realloc_buffers (pstr, init_buf_len); + if (BE (ret != REG_NOERROR, 0)) + return ret; + + pstr->word_char = dfa->word_char; + pstr->word_ops_used = dfa->word_ops_used; + pstr->mbs = pstr->mbs_allocated ? pstr->mbs : (unsigned char *) str; + pstr->valid_len = (pstr->mbs_allocated || dfa->mb_cur_max > 1) ? 0 : len; + pstr->valid_raw_len = pstr->valid_len; + return REG_NOERROR; +} + +/* This function allocate the buffers, and initialize them. */ + +static reg_errcode_t +re_string_construct (pstr, str, len, trans, icase, dfa) + re_string_t *pstr; + const char *str; + int len, icase; + RE_TRANSLATE_TYPE trans; + const re_dfa_t *dfa; +{ + reg_errcode_t ret; + memset (pstr, '\0', sizeof (re_string_t)); + re_string_construct_common (str, len, pstr, trans, icase, dfa); + + if (len > 0) + { + ret = re_string_realloc_buffers (pstr, len + 1); + if (BE (ret != REG_NOERROR, 0)) + return ret; + } + pstr->mbs = pstr->mbs_allocated ? pstr->mbs : (unsigned char *) str; + + if (icase) + { +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + { + while (1) + { + ret = build_wcs_upper_buffer (pstr); + if (BE (ret != REG_NOERROR, 0)) + return ret; + if (pstr->valid_raw_len >= len) + break; + if (pstr->bufs_len > pstr->valid_len + dfa->mb_cur_max) + break; + ret = re_string_realloc_buffers (pstr, pstr->bufs_len * 2); + if (BE (ret != REG_NOERROR, 0)) + return ret; + } + } + else +#endif /* RE_ENABLE_I18N */ + build_upper_buffer (pstr); + } + else + { +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + build_wcs_buffer (pstr); + else +#endif /* RE_ENABLE_I18N */ + { + if (trans != NULL) + re_string_translate_buffer (pstr); + else + { + pstr->valid_len = pstr->bufs_len; + pstr->valid_raw_len = pstr->bufs_len; + } + } + } + + return REG_NOERROR; +} + +/* Helper functions for re_string_allocate, and re_string_construct. */ + +static reg_errcode_t +re_string_realloc_buffers (pstr, new_buf_len) + re_string_t *pstr; + int new_buf_len; +{ +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1) + { + wint_t *new_array = re_realloc (pstr->wcs, wint_t, new_buf_len); + if (BE (new_array == NULL, 0)) + return REG_ESPACE; + pstr->wcs = new_array; + if (pstr->offsets != NULL) + { + int *new_array = re_realloc (pstr->offsets, int, new_buf_len); + if (BE (new_array == NULL, 0)) + return REG_ESPACE; + pstr->offsets = new_array; + } + } +#endif /* RE_ENABLE_I18N */ + if (pstr->mbs_allocated) + { + unsigned char *new_array = re_realloc (pstr->mbs, unsigned char, + new_buf_len); + if (BE (new_array == NULL, 0)) + return REG_ESPACE; + pstr->mbs = new_array; + } + pstr->bufs_len = new_buf_len; + return REG_NOERROR; +} + + +static void +re_string_construct_common (str, len, pstr, trans, icase, dfa) + const char *str; + int len; + re_string_t *pstr; + RE_TRANSLATE_TYPE trans; + int icase; + const re_dfa_t *dfa; +{ + pstr->raw_mbs = (const unsigned char *) str; + pstr->len = len; + pstr->raw_len = len; + pstr->trans = (unsigned RE_TRANSLATE_TYPE) trans; + pstr->icase = icase ? 1 : 0; + pstr->mbs_allocated = (trans != NULL || icase); + pstr->mb_cur_max = dfa->mb_cur_max; + pstr->is_utf8 = dfa->is_utf8; + pstr->map_notascii = dfa->map_notascii; + pstr->stop = pstr->len; + pstr->raw_stop = pstr->stop; +} + +#ifdef RE_ENABLE_I18N + +/* Build wide character buffer PSTR->WCS. + If the byte sequence of the string are: + (0), (1), (0), (1), + Then wide character buffer will be: + , WEOF , , WEOF , + We use WEOF for padding, they indicate that the position isn't + a first byte of a multibyte character. + + Note that this function assumes PSTR->VALID_LEN elements are already + built and starts from PSTR->VALID_LEN. */ + +static void +build_wcs_buffer (pstr) + re_string_t *pstr; +{ +#ifdef _LIBC + unsigned char buf[pstr->mb_cur_max]; +#else + unsigned char buf[64]; +#endif + mbstate_t prev_st; + int byte_idx, end_idx, mbclen, remain_len; + + /* Build the buffers from pstr->valid_len to either pstr->len or + pstr->bufs_len. */ + end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; + for (byte_idx = pstr->valid_len; byte_idx < end_idx;) + { + wchar_t wc; + const char *p; + + remain_len = end_idx - byte_idx; + prev_st = pstr->cur_state; + /* Apply the translation if we need. */ + if (BE (pstr->trans != NULL, 0)) + { + int i, ch; + + for (i = 0; i < pstr->mb_cur_max && i < remain_len; ++i) + { + ch = pstr->raw_mbs [pstr->raw_mbs_idx + byte_idx + i]; + buf[i] = pstr->mbs[byte_idx + i] = pstr->trans[ch]; + } + p = (const char *) buf; + } + else + p = (const char *) pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx; + mbclen = mbrtowc (&wc, p, remain_len, &pstr->cur_state); + if (BE (mbclen == (size_t) -2, 0)) + { + /* The buffer doesn't have enough space, finish to build. */ + pstr->cur_state = prev_st; + break; + } + else if (BE (mbclen == (size_t) -1 || mbclen == 0, 0)) + { + /* We treat these cases as a singlebyte character. */ + mbclen = 1; + wc = (wchar_t) pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]; + if (BE (pstr->trans != NULL, 0)) + wc = pstr->trans[wc]; + pstr->cur_state = prev_st; + } + + /* Write wide character and padding. */ + pstr->wcs[byte_idx++] = wc; + /* Write paddings. */ + for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;) + pstr->wcs[byte_idx++] = WEOF; + } + pstr->valid_len = byte_idx; + pstr->valid_raw_len = byte_idx; +} + +/* Build wide character buffer PSTR->WCS like build_wcs_buffer, + but for REG_ICASE. */ + +static int +build_wcs_upper_buffer (pstr) + re_string_t *pstr; +{ + mbstate_t prev_st; + int src_idx, byte_idx, end_idx, mbclen, remain_len; +#ifdef _LIBC + unsigned char buf[pstr->mb_cur_max]; +#else + unsigned char buf[64]; +#endif + + byte_idx = pstr->valid_len; + end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; + + /* The following optimization assumes that ASCII characters can be + mapped to wide characters with a simple cast. */ + if (! pstr->map_notascii && pstr->trans == NULL && !pstr->offsets_needed) + { + while (byte_idx < end_idx) + { + wchar_t wc; + + if (isascii (pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]) + && mbsinit (&pstr->cur_state)) + { + /* In case of a singlebyte character. */ + pstr->mbs[byte_idx] + = toupper (pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]); + /* The next step uses the assumption that wchar_t is encoded + ASCII-safe: all ASCII values can be converted like this. */ + pstr->wcs[byte_idx] = (wchar_t) pstr->mbs[byte_idx]; + ++byte_idx; + continue; + } + + remain_len = end_idx - byte_idx; + prev_st = pstr->cur_state; + mbclen = mbrtowc (&wc, + ((const char *) pstr->raw_mbs + pstr->raw_mbs_idx + + byte_idx), remain_len, &pstr->cur_state); + if (BE (mbclen > 0, 1)) + { + wchar_t wcu = wc; + if (iswlower (wc)) + { + int mbcdlen; + + wcu = towupper (wc); + mbcdlen = wcrtomb (buf, wcu, &prev_st); + if (BE (mbclen == mbcdlen, 1)) + memcpy (pstr->mbs + byte_idx, buf, mbclen); + else + { + src_idx = byte_idx; + goto offsets_needed; + } + } + else + memcpy (pstr->mbs + byte_idx, + pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx, mbclen); + pstr->wcs[byte_idx++] = wcu; + /* Write paddings. */ + for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;) + pstr->wcs[byte_idx++] = WEOF; + } + else if (mbclen == (size_t) -1 || mbclen == 0) + { + /* It is an invalid character or '\0'. Just use the byte. */ + int ch = pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]; + pstr->mbs[byte_idx] = ch; + /* And also cast it to wide char. */ + pstr->wcs[byte_idx++] = (wchar_t) ch; + if (BE (mbclen == (size_t) -1, 0)) + pstr->cur_state = prev_st; + } + else + { + /* The buffer doesn't have enough space, finish to build. */ + pstr->cur_state = prev_st; + break; + } + } + pstr->valid_len = byte_idx; + pstr->valid_raw_len = byte_idx; + return REG_NOERROR; + } + else + for (src_idx = pstr->valid_raw_len; byte_idx < end_idx;) + { + wchar_t wc; + const char *p; + offsets_needed: + remain_len = end_idx - byte_idx; + prev_st = pstr->cur_state; + if (BE (pstr->trans != NULL, 0)) + { + int i, ch; + + for (i = 0; i < pstr->mb_cur_max && i < remain_len; ++i) + { + ch = pstr->raw_mbs [pstr->raw_mbs_idx + src_idx + i]; + buf[i] = pstr->trans[ch]; + } + p = (const char *) buf; + } + else + p = (const char *) pstr->raw_mbs + pstr->raw_mbs_idx + src_idx; + mbclen = mbrtowc (&wc, p, remain_len, &pstr->cur_state); + if (BE (mbclen > 0, 1)) + { + wchar_t wcu = wc; + if (iswlower (wc)) + { + int mbcdlen; + + wcu = towupper (wc); + mbcdlen = wcrtomb ((char *) buf, wcu, &prev_st); + if (BE (mbclen == mbcdlen, 1)) + memcpy (pstr->mbs + byte_idx, buf, mbclen); + else + { + int i; + + if (byte_idx + mbcdlen > pstr->bufs_len) + { + pstr->cur_state = prev_st; + break; + } + + if (pstr->offsets == NULL) + { + pstr->offsets = re_malloc (int, pstr->bufs_len); + + if (pstr->offsets == NULL) + return REG_ESPACE; + } + if (!pstr->offsets_needed) + { + for (i = 0; i < byte_idx; ++i) + pstr->offsets[i] = i; + pstr->offsets_needed = 1; + } + + memcpy (pstr->mbs + byte_idx, buf, mbcdlen); + pstr->wcs[byte_idx] = wcu; + pstr->offsets[byte_idx] = src_idx; + for (i = 1; i < mbcdlen; ++i) + { + pstr->offsets[byte_idx + i] + = src_idx + (i < mbclen ? i : mbclen - 1); + pstr->wcs[byte_idx + i] = WEOF; + } + pstr->len += mbcdlen - mbclen; + if (pstr->raw_stop > src_idx) + pstr->stop += mbcdlen - mbclen; + end_idx = (pstr->bufs_len > pstr->len) + ? pstr->len : pstr->bufs_len; + byte_idx += mbcdlen; + src_idx += mbclen; + continue; + } + } + else + memcpy (pstr->mbs + byte_idx, p, mbclen); + + if (BE (pstr->offsets_needed != 0, 0)) + { + int i; + for (i = 0; i < mbclen; ++i) + pstr->offsets[byte_idx + i] = src_idx + i; + } + src_idx += mbclen; + + pstr->wcs[byte_idx++] = wcu; + /* Write paddings. */ + for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;) + pstr->wcs[byte_idx++] = WEOF; + } + else if (mbclen == (size_t) -1 || mbclen == 0) + { + /* It is an invalid character or '\0'. Just use the byte. */ + int ch = pstr->raw_mbs[pstr->raw_mbs_idx + src_idx]; + + if (BE (pstr->trans != NULL, 0)) + ch = pstr->trans [ch]; + pstr->mbs[byte_idx] = ch; + + if (BE (pstr->offsets_needed != 0, 0)) + pstr->offsets[byte_idx] = src_idx; + ++src_idx; + + /* And also cast it to wide char. */ + pstr->wcs[byte_idx++] = (wchar_t) ch; + if (BE (mbclen == (size_t) -1, 0)) + pstr->cur_state = prev_st; + } + else + { + /* The buffer doesn't have enough space, finish to build. */ + pstr->cur_state = prev_st; + break; + } + } + pstr->valid_len = byte_idx; + pstr->valid_raw_len = src_idx; + return REG_NOERROR; +} + +/* Skip characters until the index becomes greater than NEW_RAW_IDX. + Return the index. */ + +static int +re_string_skip_chars (pstr, new_raw_idx, last_wc) + re_string_t *pstr; + int new_raw_idx; + wint_t *last_wc; +{ + mbstate_t prev_st; + int rawbuf_idx, mbclen; + wchar_t wc = 0; + + /* Skip the characters which are not necessary to check. */ + for (rawbuf_idx = pstr->raw_mbs_idx + pstr->valid_raw_len; + rawbuf_idx < new_raw_idx;) + { + int remain_len; + remain_len = pstr->len - rawbuf_idx; + prev_st = pstr->cur_state; + mbclen = mbrtowc (&wc, (const char *) pstr->raw_mbs + rawbuf_idx, + remain_len, &pstr->cur_state); + if (BE (mbclen == (size_t) -2 || mbclen == (size_t) -1 || mbclen == 0, 0)) + { + /* We treat these cases as a singlebyte character. */ + mbclen = 1; + pstr->cur_state = prev_st; + } + /* Then proceed the next character. */ + rawbuf_idx += mbclen; + } + *last_wc = (wint_t) wc; + return rawbuf_idx; +} +#endif /* RE_ENABLE_I18N */ + +/* Build the buffer PSTR->MBS, and apply the translation if we need. + This function is used in case of REG_ICASE. */ + +static void +build_upper_buffer (pstr) + re_string_t *pstr; +{ + int char_idx, end_idx; + end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; + + for (char_idx = pstr->valid_len; char_idx < end_idx; ++char_idx) + { + int ch = pstr->raw_mbs[pstr->raw_mbs_idx + char_idx]; + if (BE (pstr->trans != NULL, 0)) + ch = pstr->trans[ch]; + if (islower (ch)) + pstr->mbs[char_idx] = toupper (ch); + else + pstr->mbs[char_idx] = ch; + } + pstr->valid_len = char_idx; + pstr->valid_raw_len = char_idx; +} + +/* Apply TRANS to the buffer in PSTR. */ + +static void +re_string_translate_buffer (pstr) + re_string_t *pstr; +{ + int buf_idx, end_idx; + end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; + + for (buf_idx = pstr->valid_len; buf_idx < end_idx; ++buf_idx) + { + int ch = pstr->raw_mbs[pstr->raw_mbs_idx + buf_idx]; + pstr->mbs[buf_idx] = pstr->trans[ch]; + } + + pstr->valid_len = buf_idx; + pstr->valid_raw_len = buf_idx; +} + +/* This function re-construct the buffers. + Concretely, convert to wide character in case of pstr->mb_cur_max > 1, + convert to upper case in case of REG_ICASE, apply translation. */ + +static reg_errcode_t +re_string_reconstruct (pstr, idx, eflags) + re_string_t *pstr; + int idx, eflags; +{ + int offset = idx - pstr->raw_mbs_idx; + if (BE (offset < 0, 0)) + { + /* Reset buffer. */ +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1) + memset (&pstr->cur_state, '\0', sizeof (mbstate_t)); +#endif /* RE_ENABLE_I18N */ + pstr->len = pstr->raw_len; + pstr->stop = pstr->raw_stop; + pstr->valid_len = 0; + pstr->raw_mbs_idx = 0; + pstr->valid_raw_len = 0; + pstr->offsets_needed = 0; + pstr->tip_context = ((eflags & REG_NOTBOL) ? CONTEXT_BEGBUF + : CONTEXT_NEWLINE | CONTEXT_BEGBUF); + if (!pstr->mbs_allocated) + pstr->mbs = (unsigned char *) pstr->raw_mbs; + offset = idx; + } + + if (BE (offset != 0, 1)) + { + /* Are the characters which are already checked remain? */ + if (BE (offset < pstr->valid_raw_len, 1) +#ifdef RE_ENABLE_I18N + /* Handling this would enlarge the code too much. + Accept a slowdown in that case. */ + && pstr->offsets_needed == 0 +#endif + ) + { + /* Yes, move them to the front of the buffer. */ + pstr->tip_context = re_string_context_at (pstr, offset - 1, eflags); +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1) + memmove (pstr->wcs, pstr->wcs + offset, + (pstr->valid_len - offset) * sizeof (wint_t)); +#endif /* RE_ENABLE_I18N */ + if (BE (pstr->mbs_allocated, 0)) + memmove (pstr->mbs, pstr->mbs + offset, + pstr->valid_len - offset); + pstr->valid_len -= offset; + pstr->valid_raw_len -= offset; +#if DEBUG + assert (pstr->valid_len > 0); +#endif + } + else + { + /* No, skip all characters until IDX. */ +#ifdef RE_ENABLE_I18N + if (BE (pstr->offsets_needed, 0)) + { + pstr->len = pstr->raw_len - idx + offset; + pstr->stop = pstr->raw_stop - idx + offset; + pstr->offsets_needed = 0; + } +#endif + pstr->valid_len = 0; + pstr->valid_raw_len = 0; +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1) + { + int wcs_idx; + wint_t wc = WEOF; + + if (pstr->is_utf8) + { + const unsigned char *raw, *p, *q, *end; + + /* Special case UTF-8. Multi-byte chars start with any + byte other than 0x80 - 0xbf. */ + raw = pstr->raw_mbs + pstr->raw_mbs_idx; + end = raw + (offset - pstr->mb_cur_max); + for (p = raw + offset - 1; p >= end; --p) + if ((*p & 0xc0) != 0x80) + { + mbstate_t cur_state; + wchar_t wc2; + int mlen = raw + pstr->len - p; + unsigned char buf[6]; + + q = p; + if (BE (pstr->trans != NULL, 0)) + { + int i = mlen < 6 ? mlen : 6; + while (--i >= 0) + buf[i] = pstr->trans[p[i]]; + q = buf; + } + /* XXX Don't use mbrtowc, we know which conversion + to use (UTF-8 -> UCS4). */ + memset (&cur_state, 0, sizeof (cur_state)); + mlen = mbrtowc (&wc2, p, mlen, &cur_state) + - (raw + offset - p); + if (mlen >= 0) + { + memset (&pstr->cur_state, '\0', + sizeof (mbstate_t)); + pstr->valid_len = mlen; + wc = wc2; + } + break; + } + } + + if (wc == WEOF) + pstr->valid_len = re_string_skip_chars (pstr, idx, &wc) - idx; + if (BE (pstr->valid_len, 0)) + { + for (wcs_idx = 0; wcs_idx < pstr->valid_len; ++wcs_idx) + pstr->wcs[wcs_idx] = WEOF; + if (pstr->mbs_allocated) + memset (pstr->mbs, 255, pstr->valid_len); + } + pstr->valid_raw_len = pstr->valid_len; + pstr->tip_context = ((BE (pstr->word_ops_used != 0, 0) + && IS_WIDE_WORD_CHAR (wc)) + ? CONTEXT_WORD + : ((IS_WIDE_NEWLINE (wc) + && pstr->newline_anchor) + ? CONTEXT_NEWLINE : 0)); + } + else +#endif /* RE_ENABLE_I18N */ + { + int c = pstr->raw_mbs[pstr->raw_mbs_idx + offset - 1]; + if (pstr->trans) + c = pstr->trans[c]; + pstr->tip_context = (bitset_contain (pstr->word_char, c) + ? CONTEXT_WORD + : ((IS_NEWLINE (c) && pstr->newline_anchor) + ? CONTEXT_NEWLINE : 0)); + } + } + if (!BE (pstr->mbs_allocated, 0)) + pstr->mbs += offset; + } + pstr->raw_mbs_idx = idx; + pstr->len -= offset; + pstr->stop -= offset; + + /* Then build the buffers. */ +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1) + { + if (pstr->icase) + { + int ret = build_wcs_upper_buffer (pstr); + if (BE (ret != REG_NOERROR, 0)) + return ret; + } + else + build_wcs_buffer (pstr); + } + else +#endif /* RE_ENABLE_I18N */ + if (BE (pstr->mbs_allocated, 0)) + { + if (pstr->icase) + build_upper_buffer (pstr); + else if (pstr->trans != NULL) + re_string_translate_buffer (pstr); + } + else + pstr->valid_len = pstr->len; + + pstr->cur_idx = 0; + return REG_NOERROR; +} + +static unsigned char +re_string_peek_byte_case (pstr, idx) + const re_string_t *pstr; + int idx; +{ + int ch, off; + + /* Handle the common (easiest) cases first. */ + if (BE (!pstr->mbs_allocated, 1)) + return re_string_peek_byte (pstr, idx); + +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1 + && ! re_string_is_single_byte_char (pstr, pstr->cur_idx + idx)) + return re_string_peek_byte (pstr, idx); +#endif + + off = pstr->cur_idx + idx; +#ifdef RE_ENABLE_I18N + if (pstr->offsets_needed) + off = pstr->offsets[off]; +#endif + + ch = pstr->raw_mbs[pstr->raw_mbs_idx + off]; + +#ifdef RE_ENABLE_I18N + /* Ensure that e.g. for tr_TR.UTF-8 BACKSLASH DOTLESS SMALL LETTER I + this function returns CAPITAL LETTER I instead of first byte of + DOTLESS SMALL LETTER I. The latter would confuse the parser, + since peek_byte_case doesn't advance cur_idx in any way. */ + if (pstr->offsets_needed && !isascii (ch)) + return re_string_peek_byte (pstr, idx); +#endif + + return ch; +} + +static unsigned char +re_string_fetch_byte_case (pstr) + re_string_t *pstr; +{ + if (BE (!pstr->mbs_allocated, 1)) + return re_string_fetch_byte (pstr); + +#ifdef RE_ENABLE_I18N + if (pstr->offsets_needed) + { + int off, ch; + + /* For tr_TR.UTF-8 [[:islower:]] there is + [[: CAPITAL LETTER I WITH DOT lower:]] in mbs. Skip + in that case the whole multi-byte character and return + the original letter. On the other side, with + [[: DOTLESS SMALL LETTER I return [[:I, as doing + anything else would complicate things too much. */ + + if (!re_string_first_byte (pstr, pstr->cur_idx)) + return re_string_fetch_byte (pstr); + + off = pstr->offsets[pstr->cur_idx]; + ch = pstr->raw_mbs[pstr->raw_mbs_idx + off]; + + if (! isascii (ch)) + return re_string_fetch_byte (pstr); + + re_string_skip_bytes (pstr, + re_string_char_size_at (pstr, pstr->cur_idx)); + return ch; + } +#endif + + return pstr->raw_mbs[pstr->raw_mbs_idx + pstr->cur_idx++]; +} + +static void +re_string_destruct (pstr) + re_string_t *pstr; +{ +#ifdef RE_ENABLE_I18N + re_free (pstr->wcs); + re_free (pstr->offsets); +#endif /* RE_ENABLE_I18N */ + if (pstr->mbs_allocated) + re_free (pstr->mbs); +} + +/* Return the context at IDX in INPUT. */ + +static unsigned int +re_string_context_at (input, idx, eflags) + const re_string_t *input; + int idx, eflags; +{ + int c; + if (BE (idx < 0, 0)) + /* In this case, we use the value stored in input->tip_context, + since we can't know the character in input->mbs[-1] here. */ + return input->tip_context; + if (BE (idx == input->len, 0)) + return ((eflags & REG_NOTEOL) ? CONTEXT_ENDBUF + : CONTEXT_NEWLINE | CONTEXT_ENDBUF); +#ifdef RE_ENABLE_I18N + if (input->mb_cur_max > 1) + { + wint_t wc; + int wc_idx = idx; + while(input->wcs[wc_idx] == WEOF) + { +#ifdef DEBUG + /* It must not happen. */ + assert (wc_idx >= 0); +#endif + --wc_idx; + if (wc_idx < 0) + return input->tip_context; + } + wc = input->wcs[wc_idx]; + if (BE (input->word_ops_used != 0, 0) && IS_WIDE_WORD_CHAR (wc)) + return CONTEXT_WORD; + return (IS_WIDE_NEWLINE (wc) && input->newline_anchor + ? CONTEXT_NEWLINE : 0); + } + else +#endif + { + c = re_string_byte_at (input, idx); + if (bitset_contain (input->word_char, c)) + return CONTEXT_WORD; + return IS_NEWLINE (c) && input->newline_anchor ? CONTEXT_NEWLINE : 0; + } +} + +/* Functions for set operation. */ + +static reg_errcode_t +re_node_set_alloc (set, size) + re_node_set *set; + int size; +{ + set->alloc = size; + set->nelem = 0; + set->elems = re_malloc (int, size); + if (BE (set->elems == NULL, 0)) + return REG_ESPACE; + return REG_NOERROR; +} + +static reg_errcode_t +re_node_set_init_1 (set, elem) + re_node_set *set; + int elem; +{ + set->alloc = 1; + set->nelem = 1; + set->elems = re_malloc (int, 1); + if (BE (set->elems == NULL, 0)) + { + set->alloc = set->nelem = 0; + return REG_ESPACE; + } + set->elems[0] = elem; + return REG_NOERROR; +} + +static reg_errcode_t +re_node_set_init_2 (set, elem1, elem2) + re_node_set *set; + int elem1, elem2; +{ + set->alloc = 2; + set->elems = re_malloc (int, 2); + if (BE (set->elems == NULL, 0)) + return REG_ESPACE; + if (elem1 == elem2) + { + set->nelem = 1; + set->elems[0] = elem1; + } + else + { + set->nelem = 2; + if (elem1 < elem2) + { + set->elems[0] = elem1; + set->elems[1] = elem2; + } + else + { + set->elems[0] = elem2; + set->elems[1] = elem1; + } + } + return REG_NOERROR; +} + +static reg_errcode_t +re_node_set_init_copy (dest, src) + re_node_set *dest; + const re_node_set *src; +{ + dest->nelem = src->nelem; + if (src->nelem > 0) + { + dest->alloc = dest->nelem; + dest->elems = re_malloc (int, dest->alloc); + if (BE (dest->elems == NULL, 0)) + { + dest->alloc = dest->nelem = 0; + return REG_ESPACE; + } + memcpy (dest->elems, src->elems, src->nelem * sizeof (int)); + } + else + re_node_set_init_empty (dest); + return REG_NOERROR; +} + +/* Calculate the intersection of the sets SRC1 and SRC2. And merge it to + DEST. Return value indicate the error code or REG_NOERROR if succeeded. + Note: We assume dest->elems is NULL, when dest->alloc is 0. */ + +static reg_errcode_t +re_node_set_add_intersect (dest, src1, src2) + re_node_set *dest; + const re_node_set *src1, *src2; +{ + int i1, i2, is, id, delta, sbase; + if (src1->nelem == 0 || src2->nelem == 0) + return REG_NOERROR; + + /* We need dest->nelem + 2 * elems_in_intersection; this is a + conservative estimate. */ + if (src1->nelem + src2->nelem + dest->nelem > dest->alloc) + { + int new_alloc = src1->nelem + src2->nelem + dest->alloc; + int *new_elems = re_realloc (dest->elems, int, new_alloc); + if (BE (new_elems == NULL, 0)) + return REG_ESPACE; + dest->elems = new_elems; + dest->alloc = new_alloc; + } + + /* Find the items in the intersection of SRC1 and SRC2, and copy + into the top of DEST those that are not already in DEST itself. */ + sbase = dest->nelem + src1->nelem + src2->nelem; + i1 = src1->nelem - 1; + i2 = src2->nelem - 1; + id = dest->nelem - 1; + for (;;) + { + if (src1->elems[i1] == src2->elems[i2]) + { + /* Try to find the item in DEST. Maybe we could binary search? */ + while (id >= 0 && dest->elems[id] > src1->elems[i1]) + --id; + + if (id < 0 || dest->elems[id] != src1->elems[i1]) + dest->elems[--sbase] = src1->elems[i1]; + + if (--i1 < 0 || --i2 < 0) + break; + } + + /* Lower the highest of the two items. */ + else if (src1->elems[i1] < src2->elems[i2]) + { + if (--i2 < 0) + break; + } + else + { + if (--i1 < 0) + break; + } + } + + id = dest->nelem - 1; + is = dest->nelem + src1->nelem + src2->nelem - 1; + delta = is - sbase + 1; + + /* Now copy. When DELTA becomes zero, the remaining + DEST elements are already in place; this is more or + less the same loop that is in re_node_set_merge. */ + dest->nelem += delta; + if (delta > 0 && id >= 0) + for (;;) + { + if (dest->elems[is] > dest->elems[id]) + { + /* Copy from the top. */ + dest->elems[id + delta--] = dest->elems[is--]; + if (delta == 0) + break; + } + else + { + /* Slide from the bottom. */ + dest->elems[id + delta] = dest->elems[id]; + if (--id < 0) + break; + } + } + + /* Copy remaining SRC elements. */ + memcpy (dest->elems, dest->elems + sbase, delta * sizeof (int)); + + return REG_NOERROR; +} + +/* Calculate the union set of the sets SRC1 and SRC2. And store it to + DEST. Return value indicate the error code or REG_NOERROR if succeeded. */ + +static reg_errcode_t +re_node_set_init_union (dest, src1, src2) + re_node_set *dest; + const re_node_set *src1, *src2; +{ + int i1, i2, id; + if (src1 != NULL && src1->nelem > 0 && src2 != NULL && src2->nelem > 0) + { + dest->alloc = src1->nelem + src2->nelem; + dest->elems = re_malloc (int, dest->alloc); + if (BE (dest->elems == NULL, 0)) + return REG_ESPACE; + } + else + { + if (src1 != NULL && src1->nelem > 0) + return re_node_set_init_copy (dest, src1); + else if (src2 != NULL && src2->nelem > 0) + return re_node_set_init_copy (dest, src2); + else + re_node_set_init_empty (dest); + return REG_NOERROR; + } + for (i1 = i2 = id = 0 ; i1 < src1->nelem && i2 < src2->nelem ;) + { + if (src1->elems[i1] > src2->elems[i2]) + { + dest->elems[id++] = src2->elems[i2++]; + continue; + } + if (src1->elems[i1] == src2->elems[i2]) + ++i2; + dest->elems[id++] = src1->elems[i1++]; + } + if (i1 < src1->nelem) + { + memcpy (dest->elems + id, src1->elems + i1, + (src1->nelem - i1) * sizeof (int)); + id += src1->nelem - i1; + } + else if (i2 < src2->nelem) + { + memcpy (dest->elems + id, src2->elems + i2, + (src2->nelem - i2) * sizeof (int)); + id += src2->nelem - i2; + } + dest->nelem = id; + return REG_NOERROR; +} + +/* Calculate the union set of the sets DEST and SRC. And store it to + DEST. Return value indicate the error code or REG_NOERROR if succeeded. */ + +static reg_errcode_t +re_node_set_merge (dest, src) + re_node_set *dest; + const re_node_set *src; +{ + int is, id, sbase, delta; + if (src == NULL || src->nelem == 0) + return REG_NOERROR; + if (dest->alloc < 2 * src->nelem + dest->nelem) + { + int new_alloc = 2 * (src->nelem + dest->alloc); + int *new_buffer = re_realloc (dest->elems, int, new_alloc); + if (BE (new_buffer == NULL, 0)) + return REG_ESPACE; + dest->elems = new_buffer; + dest->alloc = new_alloc; + } + + if (BE (dest->nelem == 0, 0)) + { + dest->nelem = src->nelem; + memcpy (dest->elems, src->elems, src->nelem * sizeof (int)); + return REG_NOERROR; + } + + /* Copy into the top of DEST the items of SRC that are not + found in DEST. Maybe we could binary search in DEST? */ + for (sbase = dest->nelem + 2 * src->nelem, + is = src->nelem - 1, id = dest->nelem - 1; is >= 0 && id >= 0; ) + { + if (dest->elems[id] == src->elems[is]) + is--, id--; + else if (dest->elems[id] < src->elems[is]) + dest->elems[--sbase] = src->elems[is--]; + else /* if (dest->elems[id] > src->elems[is]) */ + --id; + } + + if (is >= 0) + { + /* If DEST is exhausted, the remaining items of SRC must be unique. */ + sbase -= is + 1; + memcpy (dest->elems + sbase, src->elems, (is + 1) * sizeof (int)); + } + + id = dest->nelem - 1; + is = dest->nelem + 2 * src->nelem - 1; + delta = is - sbase + 1; + if (delta == 0) + return REG_NOERROR; + + /* Now copy. When DELTA becomes zero, the remaining + DEST elements are already in place. */ + dest->nelem += delta; + for (;;) + { + if (dest->elems[is] > dest->elems[id]) + { + /* Copy from the top. */ + dest->elems[id + delta--] = dest->elems[is--]; + if (delta == 0) + break; + } + else + { + /* Slide from the bottom. */ + dest->elems[id + delta] = dest->elems[id]; + if (--id < 0) + { + /* Copy remaining SRC elements. */ + memcpy (dest->elems, dest->elems + sbase, + delta * sizeof (int)); + break; + } + } + } + + return REG_NOERROR; +} + +/* Insert the new element ELEM to the re_node_set* SET. + SET should not already have ELEM. + return -1 if an error is occured, return 1 otherwise. */ + +static int +re_node_set_insert (set, elem) + re_node_set *set; + int elem; +{ + int idx; + /* In case the set is empty. */ + if (set->alloc == 0) + { + if (BE (re_node_set_init_1 (set, elem) == REG_NOERROR, 1)) + return 1; + else + return -1; + } + + if (BE (set->nelem, 0) == 0) + { + /* We already guaranteed above that set->alloc != 0. */ + set->elems[0] = elem; + ++set->nelem; + return 1; + } + + /* Realloc if we need. */ + if (set->alloc == set->nelem) + { + int *new_array; + set->alloc = set->alloc * 2; + new_array = re_realloc (set->elems, int, set->alloc); + if (BE (new_array == NULL, 0)) + return -1; + set->elems = new_array; + } + + /* Move the elements which follows the new element. Test the + first element separately to skip a check in the inner loop. */ + if (elem < set->elems[0]) + { + idx = 0; + for (idx = set->nelem; idx > 0; idx--) + set->elems[idx] = set->elems[idx - 1]; + } + else + { + for (idx = set->nelem; set->elems[idx - 1] > elem; idx--) + set->elems[idx] = set->elems[idx - 1]; + } + + /* Insert the new element. */ + set->elems[idx] = elem; + ++set->nelem; + return 1; +} + +/* Insert the new element ELEM to the re_node_set* SET. + SET should not already have any element greater than or equal to ELEM. + Return -1 if an error is occured, return 1 otherwise. */ + +static int +re_node_set_insert_last (set, elem) + re_node_set *set; + int elem; +{ + /* Realloc if we need. */ + if (set->alloc == set->nelem) + { + int *new_array; + set->alloc = (set->alloc + 1) * 2; + new_array = re_realloc (set->elems, int, set->alloc); + if (BE (new_array == NULL, 0)) + return -1; + set->elems = new_array; + } + + /* Insert the new element. */ + set->elems[set->nelem++] = elem; + return 1; +} + +/* Compare two node sets SET1 and SET2. + return 1 if SET1 and SET2 are equivalent, return 0 otherwise. */ + +static int +re_node_set_compare (set1, set2) + const re_node_set *set1, *set2; +{ + int i; + if (set1 == NULL || set2 == NULL || set1->nelem != set2->nelem) + return 0; + for (i = set1->nelem ; --i >= 0 ; ) + if (set1->elems[i] != set2->elems[i]) + return 0; + return 1; +} + +/* Return (idx + 1) if SET contains the element ELEM, return 0 otherwise. */ + +static int +re_node_set_contains (set, elem) + const re_node_set *set; + int elem; +{ + unsigned int idx, right, mid; + if (set->nelem <= 0) + return 0; + + /* Binary search the element. */ + idx = 0; + right = set->nelem - 1; + while (idx < right) + { + mid = (idx + right) / 2; + if (set->elems[mid] < elem) + idx = mid + 1; + else + right = mid; + } + return set->elems[idx] == elem ? idx + 1 : 0; +} + +static void +re_node_set_remove_at (set, idx) + re_node_set *set; + int idx; +{ + if (idx < 0 || idx >= set->nelem) + return; + --set->nelem; + for (; idx < set->nelem; idx++) + set->elems[idx] = set->elems[idx + 1]; +} + + +/* Add the token TOKEN to dfa->nodes, and return the index of the token. + Or return -1, if an error will be occured. */ + +static int +re_dfa_add_node (dfa, token, mode) + re_dfa_t *dfa; + re_token_t token; + int mode; +{ + if (BE (dfa->nodes_len >= dfa->nodes_alloc, 0)) + { + int new_nodes_alloc = dfa->nodes_alloc * 2; + re_token_t *new_array = re_realloc (dfa->nodes, re_token_t, + new_nodes_alloc); + if (BE (new_array == NULL, 0)) + return -1; + dfa->nodes = new_array; + if (mode) + { + int *new_nexts, *new_indices; + re_node_set *new_edests, *new_eclosures, *new_inveclosures; + + new_nexts = re_realloc (dfa->nexts, int, new_nodes_alloc); + new_indices = re_realloc (dfa->org_indices, int, new_nodes_alloc); + new_edests = re_realloc (dfa->edests, re_node_set, new_nodes_alloc); + new_eclosures = re_realloc (dfa->eclosures, re_node_set, + new_nodes_alloc); + new_inveclosures = re_realloc (dfa->inveclosures, re_node_set, + new_nodes_alloc); + if (BE (new_nexts == NULL || new_indices == NULL + || new_edests == NULL || new_eclosures == NULL + || new_inveclosures == NULL, 0)) + return -1; + dfa->nexts = new_nexts; + dfa->org_indices = new_indices; + dfa->edests = new_edests; + dfa->eclosures = new_eclosures; + dfa->inveclosures = new_inveclosures; + } + dfa->nodes_alloc = new_nodes_alloc; + } + dfa->nodes[dfa->nodes_len] = token; + dfa->nodes[dfa->nodes_len].opt_subexp = 0; + dfa->nodes[dfa->nodes_len].duplicated = 0; + dfa->nodes[dfa->nodes_len].constraint = 0; + return dfa->nodes_len++; +} + +static unsigned int inline +calc_state_hash (nodes, context) + const re_node_set *nodes; + unsigned int context; +{ + unsigned int hash = nodes->nelem + context; + int i; + for (i = 0 ; i < nodes->nelem ; i++) + hash += nodes->elems[i]; + return hash; +} + +/* Search for the state whose node_set is equivalent to NODES. + Return the pointer to the state, if we found it in the DFA. + Otherwise create the new one and return it. In case of an error + return NULL and set the error code in ERR. + Note: - We assume NULL as the invalid state, then it is possible that + return value is NULL and ERR is REG_NOERROR. + - We never return non-NULL value in case of any errors, it is for + optimization. */ + +static re_dfastate_t* +re_acquire_state (err, dfa, nodes) + reg_errcode_t *err; + re_dfa_t *dfa; + const re_node_set *nodes; +{ + unsigned int hash; + re_dfastate_t *new_state; + struct re_state_table_entry *spot; + int i; + if (BE (nodes->nelem == 0, 0)) + { + *err = REG_NOERROR; + return NULL; + } + hash = calc_state_hash (nodes, 0); + spot = dfa->state_table + (hash & dfa->state_hash_mask); + + for (i = 0 ; i < spot->num ; i++) + { + re_dfastate_t *state = spot->array[i]; + if (hash != state->hash) + continue; + if (re_node_set_compare (&state->nodes, nodes)) + return state; + } + + /* There are no appropriate state in the dfa, create the new one. */ + new_state = create_ci_newstate (dfa, nodes, hash); + if (BE (new_state != NULL, 1)) + return new_state; + else + { + *err = REG_ESPACE; + return NULL; + } +} + +/* Search for the state whose node_set is equivalent to NODES and + whose context is equivalent to CONTEXT. + Return the pointer to the state, if we found it in the DFA. + Otherwise create the new one and return it. In case of an error + return NULL and set the error code in ERR. + Note: - We assume NULL as the invalid state, then it is possible that + return value is NULL and ERR is REG_NOERROR. + - We never return non-NULL value in case of any errors, it is for + optimization. */ + +static re_dfastate_t* +re_acquire_state_context (err, dfa, nodes, context) + reg_errcode_t *err; + re_dfa_t *dfa; + const re_node_set *nodes; + unsigned int context; +{ + unsigned int hash; + re_dfastate_t *new_state; + struct re_state_table_entry *spot; + int i; + if (nodes->nelem == 0) + { + *err = REG_NOERROR; + return NULL; + } + hash = calc_state_hash (nodes, context); + spot = dfa->state_table + (hash & dfa->state_hash_mask); + + for (i = 0 ; i < spot->num ; i++) + { + re_dfastate_t *state = spot->array[i]; + if (state->hash == hash + && state->context == context + && re_node_set_compare (state->entrance_nodes, nodes)) + return state; + } + /* There are no appropriate state in `dfa', create the new one. */ + new_state = create_cd_newstate (dfa, nodes, context, hash); + if (BE (new_state != NULL, 1)) + return new_state; + else + { + *err = REG_ESPACE; + return NULL; + } +} + +/* Finish initialization of the new state NEWSTATE, and using its hash value + HASH put in the appropriate bucket of DFA's state table. Return value + indicates the error code if failed. */ + +static reg_errcode_t +register_state (dfa, newstate, hash) + re_dfa_t *dfa; + re_dfastate_t *newstate; + unsigned int hash; +{ + struct re_state_table_entry *spot; + reg_errcode_t err; + int i; + + newstate->hash = hash; + err = re_node_set_alloc (&newstate->non_eps_nodes, newstate->nodes.nelem); + if (BE (err != REG_NOERROR, 0)) + return REG_ESPACE; + for (i = 0; i < newstate->nodes.nelem; i++) + { + int elem = newstate->nodes.elems[i]; + if (!IS_EPSILON_NODE (dfa->nodes[elem].type)) + re_node_set_insert_last (&newstate->non_eps_nodes, elem); + } + + spot = dfa->state_table + (hash & dfa->state_hash_mask); + if (BE (spot->alloc <= spot->num, 0)) + { + int new_alloc = 2 * spot->num + 2; + re_dfastate_t **new_array = re_realloc (spot->array, re_dfastate_t *, + new_alloc); + if (BE (new_array == NULL, 0)) + return REG_ESPACE; + spot->array = new_array; + spot->alloc = new_alloc; + } + spot->array[spot->num++] = newstate; + return REG_NOERROR; +} + +/* Create the new state which is independ of contexts. + Return the new state if succeeded, otherwise return NULL. */ + +static re_dfastate_t * +create_ci_newstate (dfa, nodes, hash) + re_dfa_t *dfa; + const re_node_set *nodes; + unsigned int hash; +{ + int i; + reg_errcode_t err; + re_dfastate_t *newstate; + + newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1); + if (BE (newstate == NULL, 0)) + return NULL; + err = re_node_set_init_copy (&newstate->nodes, nodes); + if (BE (err != REG_NOERROR, 0)) + { + re_free (newstate); + return NULL; + } + + newstate->entrance_nodes = &newstate->nodes; + for (i = 0 ; i < nodes->nelem ; i++) + { + re_token_t *node = dfa->nodes + nodes->elems[i]; + re_token_type_t type = node->type; + if (type == CHARACTER && !node->constraint) + continue; + + /* If the state has the halt node, the state is a halt state. */ + else if (type == END_OF_RE) + newstate->halt = 1; +#ifdef RE_ENABLE_I18N + else if (type == COMPLEX_BRACKET + || type == OP_UTF8_PERIOD + || (type == OP_PERIOD && dfa->mb_cur_max > 1)) + newstate->accept_mb = 1; +#endif /* RE_ENABLE_I18N */ + else if (type == OP_BACK_REF) + newstate->has_backref = 1; + else if (type == ANCHOR || node->constraint) + newstate->has_constraint = 1; + } + err = register_state (dfa, newstate, hash); + if (BE (err != REG_NOERROR, 0)) + { + free_state (newstate); + newstate = NULL; + } + return newstate; +} + +/* Create the new state which is depend on the context CONTEXT. + Return the new state if succeeded, otherwise return NULL. */ + +static re_dfastate_t * +create_cd_newstate (dfa, nodes, context, hash) + re_dfa_t *dfa; + const re_node_set *nodes; + unsigned int context, hash; +{ + int i, nctx_nodes = 0; + reg_errcode_t err; + re_dfastate_t *newstate; + + newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1); + if (BE (newstate == NULL, 0)) + return NULL; + err = re_node_set_init_copy (&newstate->nodes, nodes); + if (BE (err != REG_NOERROR, 0)) + { + re_free (newstate); + return NULL; + } + + newstate->context = context; + newstate->entrance_nodes = &newstate->nodes; + + for (i = 0 ; i < nodes->nelem ; i++) + { + unsigned int constraint = 0; + re_token_t *node = dfa->nodes + nodes->elems[i]; + re_token_type_t type = node->type; + if (node->constraint) + constraint = node->constraint; + + if (type == CHARACTER && !constraint) + continue; + /* If the state has the halt node, the state is a halt state. */ + else if (type == END_OF_RE) + newstate->halt = 1; +#ifdef RE_ENABLE_I18N + else if (type == COMPLEX_BRACKET + || type == OP_UTF8_PERIOD + || (type == OP_PERIOD && dfa->mb_cur_max > 1)) + newstate->accept_mb = 1; +#endif /* RE_ENABLE_I18N */ + else if (type == OP_BACK_REF) + newstate->has_backref = 1; + else if (type == ANCHOR) + constraint = node->opr.ctx_type; + + if (constraint) + { + if (newstate->entrance_nodes == &newstate->nodes) + { + newstate->entrance_nodes = re_malloc (re_node_set, 1); + if (BE (newstate->entrance_nodes == NULL, 0)) + { + free_state (newstate); + return NULL; + } + re_node_set_init_copy (newstate->entrance_nodes, nodes); + nctx_nodes = 0; + newstate->has_constraint = 1; + } + + if (NOT_SATISFY_PREV_CONSTRAINT (constraint,context)) + { + re_node_set_remove_at (&newstate->nodes, i - nctx_nodes); + ++nctx_nodes; + } + } + } + err = register_state (dfa, newstate, hash); + if (BE (err != REG_NOERROR, 0)) + { + free_state (newstate); + newstate = NULL; + } + return newstate; +} + +static void +free_state (state) + re_dfastate_t *state; +{ + re_node_set_free (&state->non_eps_nodes); + re_node_set_free (&state->inveclosure); + if (state->entrance_nodes != &state->nodes) + { + re_node_set_free (state->entrance_nodes); + re_free (state->entrance_nodes); + } + re_node_set_free (&state->nodes); + re_free (state->trtable); + re_free (state); +} diff --git a/src/regex/regex_internal.h b/src/regex/regex_internal.h new file mode 100644 index 0000000..18865a7 --- /dev/null +++ b/src/regex/regex_internal.h @@ -0,0 +1,801 @@ +/* Extended regular expression matching and search library. + Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Isamu Hasegawa . + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#ifndef _REGEX_INTERNAL_H +#define _REGEX_INTERNAL_H 1 + +#include +#include +#include +#include +#include + +#if defined HAVE_LANGINFO_H || defined HAVE_LANGINFO_CODESET || defined _LIBC +# include +#endif +#if defined HAVE_LOCALE_H || defined _LIBC +# include +#endif +#if defined HAVE_WCHAR_H || defined _LIBC +# include +#endif /* HAVE_WCHAR_H || _LIBC */ +#if defined HAVE_WCTYPE_H || defined _LIBC +# include +#endif /* HAVE_WCTYPE_H || _LIBC */ + +/* In case that the system doesn't have isblank(). */ +#if !defined _LIBC && !defined HAVE_ISBLANK && !defined isblank +# define isblank(ch) ((ch) == ' ' || (ch) == '\t') +#endif + +#ifdef _LIBC +# ifndef _RE_DEFINE_LOCALE_FUNCTIONS +# define _RE_DEFINE_LOCALE_FUNCTIONS 1 +# include +# include +# include +# endif +#endif + +/* This is for other GNU distributions with internationalized messages. */ +#if (HAVE_LIBINTL_H && ENABLE_NLS) || defined _LIBC +# include +# ifdef _LIBC +# undef gettext +# define gettext(msgid) \ + INTUSE(__dcgettext) (INTUSE(_libc_intl_domainname), msgid, LC_MESSAGES) +# endif +#else +# define gettext(msgid) (msgid) +#endif + +#ifndef gettext_noop +/* This define is so xgettext can find the internationalizable + strings. */ +# define gettext_noop(String) String +#endif + +#if (defined MB_CUR_MAX && HAVE_LOCALE_H && HAVE_WCTYPE_H && HAVE_WCHAR_H && HAVE_WCRTOMB && HAVE_MBRTOWC && HAVE_WCSCOLL) || _LIBC +# define RE_ENABLE_I18N +#endif + +#if __GNUC__ >= 3 +# define BE(expr, val) __builtin_expect (expr, val) +#else +# define BE(expr, val) (expr) +# define inline +#endif + +/* Number of bits in a byte. */ +#define BYTE_BITS 8 +/* Number of single byte character. */ +#define SBC_MAX 256 + +#define COLL_ELEM_LEN_MAX 8 + +/* The character which represents newline. */ +#define NEWLINE_CHAR '\n' +#define WIDE_NEWLINE_CHAR L'\n' + +/* Rename to standard API for using out of glibc. */ +#ifndef _LIBC +# define __wctype wctype +# define __iswctype iswctype +# define __btowc btowc +# define __mempcpy mempcpy +# define __wcrtomb wcrtomb +# define __regfree regfree +# define attribute_hidden +#endif /* not _LIBC */ + +#ifdef __GNUC__ +# define __attribute(arg) __attribute__ (arg) +#else +# define __attribute(arg) +#endif + +extern const char __re_error_msgid[] attribute_hidden; +extern const size_t __re_error_msgid_idx[] attribute_hidden; + +/* Number of bits in an unsinged int. */ +#define UINT_BITS (sizeof (unsigned int) * BYTE_BITS) +/* Number of unsigned int in an bit_set. */ +#define BITSET_UINTS ((SBC_MAX + UINT_BITS - 1) / UINT_BITS) +typedef unsigned int bitset[BITSET_UINTS]; +typedef unsigned int *re_bitset_ptr_t; +typedef const unsigned int *re_const_bitset_ptr_t; + +#define bitset_set(set,i) (set[i / UINT_BITS] |= 1 << i % UINT_BITS) +#define bitset_clear(set,i) (set[i / UINT_BITS] &= ~(1 << i % UINT_BITS)) +#define bitset_contain(set,i) (set[i / UINT_BITS] & (1 << i % UINT_BITS)) +#define bitset_empty(set) memset (set, 0, sizeof (unsigned int) * BITSET_UINTS) +#define bitset_set_all(set) \ + memset (set, 255, sizeof (unsigned int) * BITSET_UINTS) +#define bitset_copy(dest,src) \ + memcpy (dest, src, sizeof (unsigned int) * BITSET_UINTS) +static inline void bitset_not (bitset set); +static inline void bitset_merge (bitset dest, const bitset src); +static inline void bitset_not_merge (bitset dest, const bitset src); +static inline void bitset_mask (bitset dest, const bitset src); + +#define PREV_WORD_CONSTRAINT 0x0001 +#define PREV_NOTWORD_CONSTRAINT 0x0002 +#define NEXT_WORD_CONSTRAINT 0x0004 +#define NEXT_NOTWORD_CONSTRAINT 0x0008 +#define PREV_NEWLINE_CONSTRAINT 0x0010 +#define NEXT_NEWLINE_CONSTRAINT 0x0020 +#define PREV_BEGBUF_CONSTRAINT 0x0040 +#define NEXT_ENDBUF_CONSTRAINT 0x0080 +#define WORD_DELIM_CONSTRAINT 0x0100 +#define NOT_WORD_DELIM_CONSTRAINT 0x0200 + +typedef enum +{ + INSIDE_WORD = PREV_WORD_CONSTRAINT | NEXT_WORD_CONSTRAINT, + WORD_FIRST = PREV_NOTWORD_CONSTRAINT | NEXT_WORD_CONSTRAINT, + WORD_LAST = PREV_WORD_CONSTRAINT | NEXT_NOTWORD_CONSTRAINT, + INSIDE_NOTWORD = PREV_NOTWORD_CONSTRAINT | NEXT_NOTWORD_CONSTRAINT, + LINE_FIRST = PREV_NEWLINE_CONSTRAINT, + LINE_LAST = NEXT_NEWLINE_CONSTRAINT, + BUF_FIRST = PREV_BEGBUF_CONSTRAINT, + BUF_LAST = NEXT_ENDBUF_CONSTRAINT, + WORD_DELIM = WORD_DELIM_CONSTRAINT, + NOT_WORD_DELIM = NOT_WORD_DELIM_CONSTRAINT +} re_context_type; + +typedef struct +{ + int alloc; + int nelem; + int *elems; +} re_node_set; + +typedef enum +{ + NON_TYPE = 0, + + /* Node type, These are used by token, node, tree. */ + CHARACTER = 1, + END_OF_RE = 2, + SIMPLE_BRACKET = 3, + OP_BACK_REF = 4, + OP_PERIOD = 5, +#ifdef RE_ENABLE_I18N + COMPLEX_BRACKET = 6, + OP_UTF8_PERIOD = 7, +#endif /* RE_ENABLE_I18N */ + + /* We define EPSILON_BIT as a macro so that OP_OPEN_SUBEXP is used + when the debugger shows values of this enum type. */ +#define EPSILON_BIT 8 + OP_OPEN_SUBEXP = EPSILON_BIT | 0, + OP_CLOSE_SUBEXP = EPSILON_BIT | 1, + OP_ALT = EPSILON_BIT | 2, + OP_DUP_ASTERISK = EPSILON_BIT | 3, + OP_DUP_PLUS = EPSILON_BIT | 4, + OP_DUP_QUESTION = EPSILON_BIT | 5, + ANCHOR = EPSILON_BIT | 6, + OP_DELETED_SUBEXP = EPSILON_BIT | 7, + + /* Tree type, these are used only by tree. */ + CONCAT = 16, + + /* Token type, these are used only by token. */ + OP_OPEN_BRACKET = 17, + OP_CLOSE_BRACKET, + OP_CHARSET_RANGE, + OP_OPEN_DUP_NUM, + OP_CLOSE_DUP_NUM, + OP_NON_MATCH_LIST, + OP_OPEN_COLL_ELEM, + OP_CLOSE_COLL_ELEM, + OP_OPEN_EQUIV_CLASS, + OP_CLOSE_EQUIV_CLASS, + OP_OPEN_CHAR_CLASS, + OP_CLOSE_CHAR_CLASS, + OP_WORD, + OP_NOTWORD, + OP_SPACE, + OP_NOTSPACE, + BACK_SLASH + +} re_token_type_t; + +#ifdef RE_ENABLE_I18N +typedef struct +{ + /* Multibyte characters. */ + wchar_t *mbchars; + + /* Collating symbols. */ +# ifdef _LIBC + int32_t *coll_syms; +# endif + + /* Equivalence classes. */ +# ifdef _LIBC + int32_t *equiv_classes; +# endif + + /* Range expressions. */ +# ifdef _LIBC + uint32_t *range_starts; + uint32_t *range_ends; +# else /* not _LIBC */ + wchar_t *range_starts; + wchar_t *range_ends; +# endif /* not _LIBC */ + + /* Character classes. */ + wctype_t *char_classes; + + /* If this character set is the non-matching list. */ + unsigned int non_match : 1; + + /* # of multibyte characters. */ + int nmbchars; + + /* # of collating symbols. */ + int ncoll_syms; + + /* # of equivalence classes. */ + int nequiv_classes; + + /* # of range expressions. */ + int nranges; + + /* # of character classes. */ + int nchar_classes; +} re_charset_t; +#endif /* RE_ENABLE_I18N */ + +typedef struct +{ + union + { + unsigned char c; /* for CHARACTER */ + re_bitset_ptr_t sbcset; /* for SIMPLE_BRACKET */ +#ifdef RE_ENABLE_I18N + re_charset_t *mbcset; /* for COMPLEX_BRACKET */ +#endif /* RE_ENABLE_I18N */ + int idx; /* for BACK_REF */ + re_context_type ctx_type; /* for ANCHOR */ + } opr; +#if __GNUC__ >= 2 + re_token_type_t type : 8; +#else + re_token_type_t type; +#endif + unsigned int constraint : 10; /* context constraint */ + unsigned int duplicated : 1; + unsigned int opt_subexp : 1; +#ifdef RE_ENABLE_I18N + /* These 2 bits can be moved into the union if needed (e.g. if running out + of bits; move opr.c to opr.c.c and move the flags to opr.c.flags). */ + unsigned int mb_partial : 1; +#endif + unsigned int word_char : 1; +} re_token_t; + +#define IS_EPSILON_NODE(type) ((type) & EPSILON_BIT) +#define ACCEPT_MB_NODE(type) \ + ((type) >= OP_PERIOD && (type) <= OP_UTF8_PERIOD) + +struct re_string_t +{ + /* Indicate the raw buffer which is the original string passed as an + argument of regexec(), re_search(), etc.. */ + const unsigned char *raw_mbs; + /* Store the multibyte string. In case of "case insensitive mode" like + REG_ICASE, upper cases of the string are stored, otherwise MBS points + the same address that RAW_MBS points. */ + unsigned char *mbs; +#ifdef RE_ENABLE_I18N + /* Store the wide character string which is corresponding to MBS. */ + wint_t *wcs; + int *offsets; + mbstate_t cur_state; +#endif + /* Index in RAW_MBS. Each character mbs[i] corresponds to + raw_mbs[raw_mbs_idx + i]. */ + int raw_mbs_idx; + /* The length of the valid characters in the buffers. */ + int valid_len; + /* The corresponding number of bytes in raw_mbs array. */ + int valid_raw_len; + /* The length of the buffers MBS and WCS. */ + int bufs_len; + /* The index in MBS, which is updated by re_string_fetch_byte. */ + int cur_idx; + /* length of RAW_MBS array. */ + int raw_len; + /* This is RAW_LEN - RAW_MBS_IDX + VALID_LEN - VALID_RAW_LEN. */ + int len; + /* End of the buffer may be shorter than its length in the cases such + as re_match_2, re_search_2. Then, we use STOP for end of the buffer + instead of LEN. */ + int raw_stop; + /* This is RAW_STOP - RAW_MBS_IDX adjusted through OFFSETS. */ + int stop; + + /* The context of mbs[0]. We store the context independently, since + the context of mbs[0] may be different from raw_mbs[0], which is + the beginning of the input string. */ + unsigned int tip_context; + /* The translation passed as a part of an argument of re_compile_pattern. */ + unsigned RE_TRANSLATE_TYPE trans; + /* Copy of re_dfa_t's word_char. */ + re_const_bitset_ptr_t word_char; + /* 1 if REG_ICASE. */ + unsigned char icase; + unsigned char is_utf8; + unsigned char map_notascii; + unsigned char mbs_allocated; + unsigned char offsets_needed; + unsigned char newline_anchor; + unsigned char word_ops_used; + int mb_cur_max; +}; +typedef struct re_string_t re_string_t; + + +struct re_dfa_t; +typedef struct re_dfa_t re_dfa_t; + +#ifndef _LIBC +# ifdef __i386__ +# define internal_function __attribute ((regparm (3), stdcall)) +# else +# define internal_function +# endif +#endif + +#ifndef RE_NO_INTERNAL_PROTOTYPES +static reg_errcode_t re_string_allocate (re_string_t *pstr, const char *str, + int len, int init_len, + RE_TRANSLATE_TYPE trans, int icase, + const re_dfa_t *dfa) + internal_function; +static reg_errcode_t re_string_construct (re_string_t *pstr, const char *str, + int len, RE_TRANSLATE_TYPE trans, + int icase, const re_dfa_t *dfa) + internal_function; +static reg_errcode_t re_string_reconstruct (re_string_t *pstr, int idx, + int eflags) internal_function; +static reg_errcode_t re_string_realloc_buffers (re_string_t *pstr, + int new_buf_len) + internal_function; +# ifdef RE_ENABLE_I18N +static void build_wcs_buffer (re_string_t *pstr) internal_function; +static int build_wcs_upper_buffer (re_string_t *pstr) internal_function; +# endif /* RE_ENABLE_I18N */ +static void build_upper_buffer (re_string_t *pstr) internal_function; +static void re_string_translate_buffer (re_string_t *pstr) internal_function; +static void re_string_destruct (re_string_t *pstr) internal_function; +# ifdef RE_ENABLE_I18N +static int re_string_elem_size_at (const re_string_t *pstr, int idx) + internal_function __attribute ((pure)); +static inline int re_string_char_size_at (const re_string_t *pstr, int idx) + internal_function __attribute ((pure)); +static inline wint_t re_string_wchar_at (const re_string_t *pstr, int idx) + internal_function __attribute ((pure)); +# endif /* RE_ENABLE_I18N */ +static unsigned int re_string_context_at (const re_string_t *input, int idx, + int eflags) + internal_function __attribute ((pure)); +static unsigned char re_string_peek_byte_case (const re_string_t *pstr, + int idx) + internal_function __attribute ((pure)); +static unsigned char re_string_fetch_byte_case (re_string_t *pstr) + internal_function __attribute ((pure)); +#endif +#define re_string_peek_byte(pstr, offset) \ + ((pstr)->mbs[(pstr)->cur_idx + offset]) +#define re_string_fetch_byte(pstr) \ + ((pstr)->mbs[(pstr)->cur_idx++]) +#define re_string_first_byte(pstr, idx) \ + ((idx) == (pstr)->valid_len || (pstr)->wcs[idx] != WEOF) +#define re_string_is_single_byte_char(pstr, idx) \ + ((pstr)->wcs[idx] != WEOF && ((pstr)->valid_len == (idx) + 1 \ + || (pstr)->wcs[(idx) + 1] != WEOF)) +#define re_string_eoi(pstr) ((pstr)->stop <= (pstr)->cur_idx) +#define re_string_cur_idx(pstr) ((pstr)->cur_idx) +#define re_string_get_buffer(pstr) ((pstr)->mbs) +#define re_string_length(pstr) ((pstr)->len) +#define re_string_byte_at(pstr,idx) ((pstr)->mbs[idx]) +#define re_string_skip_bytes(pstr,idx) ((pstr)->cur_idx += (idx)) +#define re_string_set_index(pstr,idx) ((pstr)->cur_idx = (idx)) + +#define re_malloc(t,n) ((t *) malloc ((n) * sizeof (t))) +#define re_realloc(p,t,n) ((t *) realloc (p, (n) * sizeof (t))) +#define re_free(p) free (p) + +struct bin_tree_t +{ + struct bin_tree_t *parent; + struct bin_tree_t *left; + struct bin_tree_t *right; + + /* `node_idx' is the index in dfa->nodes, if `type' == 0. + Otherwise `type' indicate the type of this node. */ + re_token_type_t type; + int node_idx; + + int first; + int next; + re_node_set eclosure; +}; +typedef struct bin_tree_t bin_tree_t; + +#define BIN_TREE_STORAGE_SIZE \ + ((1024 - sizeof (void *)) / sizeof (bin_tree_t)) + +struct bin_tree_storage_t +{ + struct bin_tree_storage_t *next; + bin_tree_t data[BIN_TREE_STORAGE_SIZE]; +}; +typedef struct bin_tree_storage_t bin_tree_storage_t; + +#define CONTEXT_WORD 1 +#define CONTEXT_NEWLINE (CONTEXT_WORD << 1) +#define CONTEXT_BEGBUF (CONTEXT_NEWLINE << 1) +#define CONTEXT_ENDBUF (CONTEXT_BEGBUF << 1) + +#define IS_WORD_CONTEXT(c) ((c) & CONTEXT_WORD) +#define IS_NEWLINE_CONTEXT(c) ((c) & CONTEXT_NEWLINE) +#define IS_BEGBUF_CONTEXT(c) ((c) & CONTEXT_BEGBUF) +#define IS_ENDBUF_CONTEXT(c) ((c) & CONTEXT_ENDBUF) +#define IS_ORDINARY_CONTEXT(c) ((c) == 0) + +#define IS_WORD_CHAR(ch) (isalnum (ch) || (ch) == '_') +#define IS_NEWLINE(ch) ((ch) == NEWLINE_CHAR) +#define IS_WIDE_WORD_CHAR(ch) (iswalnum (ch) || (ch) == L'_') +#define IS_WIDE_NEWLINE(ch) ((ch) == WIDE_NEWLINE_CHAR) + +#define NOT_SATISFY_PREV_CONSTRAINT(constraint,context) \ + ((((constraint) & PREV_WORD_CONSTRAINT) && !IS_WORD_CONTEXT (context)) \ + || ((constraint & PREV_NOTWORD_CONSTRAINT) && IS_WORD_CONTEXT (context)) \ + || ((constraint & PREV_NEWLINE_CONSTRAINT) && !IS_NEWLINE_CONTEXT (context))\ + || ((constraint & PREV_BEGBUF_CONSTRAINT) && !IS_BEGBUF_CONTEXT (context))) + +#define NOT_SATISFY_NEXT_CONSTRAINT(constraint,context) \ + ((((constraint) & NEXT_WORD_CONSTRAINT) && !IS_WORD_CONTEXT (context)) \ + || (((constraint) & NEXT_NOTWORD_CONSTRAINT) && IS_WORD_CONTEXT (context)) \ + || (((constraint) & NEXT_NEWLINE_CONSTRAINT) && !IS_NEWLINE_CONTEXT (context)) \ + || (((constraint) & NEXT_ENDBUF_CONSTRAINT) && !IS_ENDBUF_CONTEXT (context))) + +struct re_dfastate_t +{ + unsigned int hash; + re_node_set nodes; + re_node_set non_eps_nodes; + re_node_set inveclosure; + re_node_set *entrance_nodes; + struct re_dfastate_t **trtable; + unsigned int context : 4; + unsigned int halt : 1; + /* If this state can accept `multi byte'. + Note that we refer to multibyte characters, and multi character + collating elements as `multi byte'. */ + unsigned int accept_mb : 1; + /* If this state has backreference node(s). */ + unsigned int has_backref : 1; + unsigned int has_constraint : 1; + unsigned int word_trtable : 1; +}; +typedef struct re_dfastate_t re_dfastate_t; + +struct re_state_table_entry +{ + int num; + int alloc; + re_dfastate_t **array; +}; + +/* Array type used in re_sub_match_last_t and re_sub_match_top_t. */ + +typedef struct +{ + int next_idx; + int alloc; + re_dfastate_t **array; +} state_array_t; + +/* Store information about the node NODE whose type is OP_CLOSE_SUBEXP. */ + +typedef struct +{ + int node; + int str_idx; /* The position NODE match at. */ + state_array_t path; +} re_sub_match_last_t; + +/* Store information about the node NODE whose type is OP_OPEN_SUBEXP. + And information about the node, whose type is OP_CLOSE_SUBEXP, + corresponding to NODE is stored in LASTS. */ + +typedef struct +{ + int str_idx; + int node; + int next_last_offset; + state_array_t *path; + int alasts; /* Allocation size of LASTS. */ + int nlasts; /* The number of LASTS. */ + re_sub_match_last_t **lasts; +} re_sub_match_top_t; + +struct re_backref_cache_entry +{ + int node; + int str_idx; + int subexp_from; + int subexp_to; + char more; + char unused; + unsigned short int eps_reachable_subexps_map; +}; + +typedef struct +{ + /* The string object corresponding to the input string. */ + re_string_t input; +#if defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) + re_dfa_t *const dfa; +#else + re_dfa_t *dfa; +#endif + /* EFLAGS of the argument of regexec. */ + int eflags; + /* Where the matching ends. */ + int match_last; + int last_node; + /* The state log used by the matcher. */ + re_dfastate_t **state_log; + int state_log_top; + /* Back reference cache. */ + int nbkref_ents; + int abkref_ents; + struct re_backref_cache_entry *bkref_ents; + int max_mb_elem_len; + int nsub_tops; + int asub_tops; + re_sub_match_top_t **sub_tops; +} re_match_context_t; + +typedef struct +{ + re_dfastate_t **sifted_states; + re_dfastate_t **limited_states; + int last_node; + int last_str_idx; + re_node_set limits; +} re_sift_context_t; + +struct re_fail_stack_ent_t +{ + int idx; + int node; + regmatch_t *regs; + re_node_set eps_via_nodes; +}; + +struct re_fail_stack_t +{ + int num; + int alloc; + struct re_fail_stack_ent_t *stack; +}; + +struct re_dfa_t +{ + re_token_t *nodes; + int nodes_alloc; + int nodes_len; + int *nexts; + int *org_indices; + re_node_set *edests; + re_node_set *eclosures; + re_node_set *inveclosures; + struct re_state_table_entry *state_table; + re_dfastate_t *init_state; + re_dfastate_t *init_state_word; + re_dfastate_t *init_state_nl; + re_dfastate_t *init_state_begbuf; + bin_tree_t *str_tree; + bin_tree_storage_t *str_tree_storage; + re_bitset_ptr_t sb_char; + int str_tree_storage_idx; + + /* number of subexpressions `re_nsub' is in regex_t. */ + unsigned int state_hash_mask; + int states_alloc; + int init_node; + int nbackref; /* The number of backreference in this dfa. */ + + /* Bitmap expressing which backreference is used. */ + unsigned int used_bkref_map; + unsigned int completed_bkref_map; + + unsigned int has_plural_match : 1; + /* If this dfa has "multibyte node", which is a backreference or + a node which can accept multibyte character or multi character + collating element. */ + unsigned int has_mb_node : 1; + unsigned int is_utf8 : 1; + unsigned int map_notascii : 1; + unsigned int word_ops_used : 1; + int mb_cur_max; + bitset word_char; + reg_syntax_t syntax; + int *subexp_map; +#ifdef DEBUG + char* re_str; +#endif +}; + +#ifndef RE_NO_INTERNAL_PROTOTYPES +static reg_errcode_t re_node_set_alloc (re_node_set *set, int size) internal_function; +static reg_errcode_t re_node_set_init_1 (re_node_set *set, int elem) internal_function; +static reg_errcode_t re_node_set_init_2 (re_node_set *set, int elem1, + int elem2) internal_function; +#define re_node_set_init_empty(set) memset (set, '\0', sizeof (re_node_set)) +static reg_errcode_t re_node_set_init_copy (re_node_set *dest, + const re_node_set *src) internal_function; +static reg_errcode_t re_node_set_add_intersect (re_node_set *dest, + const re_node_set *src1, + const re_node_set *src2) internal_function; +static reg_errcode_t re_node_set_init_union (re_node_set *dest, + const re_node_set *src1, + const re_node_set *src2) internal_function; +static reg_errcode_t re_node_set_merge (re_node_set *dest, + const re_node_set *src) internal_function; +static int re_node_set_insert (re_node_set *set, int elem) internal_function; +static int re_node_set_insert_last (re_node_set *set, + int elem) internal_function; +static int re_node_set_compare (const re_node_set *set1, + const re_node_set *set2) + internal_function __attribute ((pure)); +static int re_node_set_contains (const re_node_set *set, int elem) + internal_function __attribute ((pure)); +static void re_node_set_remove_at (re_node_set *set, int idx) internal_function; +#define re_node_set_remove(set,id) \ + (re_node_set_remove_at (set, re_node_set_contains (set, id) - 1)) +#define re_node_set_empty(p) ((p)->nelem = 0) +#define re_node_set_free(set) re_free ((set)->elems) +static int re_dfa_add_node (re_dfa_t *dfa, re_token_t token, int mode) internal_function; +static re_dfastate_t *re_acquire_state (reg_errcode_t *err, re_dfa_t *dfa, + const re_node_set *nodes) internal_function; +static re_dfastate_t *re_acquire_state_context (reg_errcode_t *err, + re_dfa_t *dfa, + const re_node_set *nodes, + unsigned int context) internal_function; +static void free_state (re_dfastate_t *state) internal_function; +#endif + + +typedef enum +{ + SB_CHAR, + MB_CHAR, + EQUIV_CLASS, + COLL_SYM, + CHAR_CLASS +} bracket_elem_type; + +typedef struct +{ + bracket_elem_type type; + union + { + unsigned char ch; + unsigned char *name; + wchar_t wch; + } opr; +} bracket_elem_t; + + +/* Inline functions for bitset operation. */ +static inline void +bitset_not (bitset set) +{ + int bitset_i; + for (bitset_i = 0; bitset_i < BITSET_UINTS; ++bitset_i) + set[bitset_i] = ~set[bitset_i]; +} + +static inline void +bitset_merge (bitset dest, const bitset src) +{ + int bitset_i; + for (bitset_i = 0; bitset_i < BITSET_UINTS; ++bitset_i) + dest[bitset_i] |= src[bitset_i]; +} + +static inline void +bitset_not_merge (bitset dest, const bitset src) +{ + int i; + for (i = 0; i < BITSET_UINTS; ++i) + dest[i] |= ~src[i]; +} + +static inline void +bitset_mask (bitset dest, const bitset src) +{ + int bitset_i; + for (bitset_i = 0; bitset_i < BITSET_UINTS; ++bitset_i) + dest[bitset_i] &= src[bitset_i]; +} + +#if defined RE_ENABLE_I18N && !defined RE_NO_INTERNAL_PROTOTYPES +/* Inline functions for re_string. */ +static inline int +internal_function +re_string_char_size_at (const re_string_t *pstr, int idx) +{ + int byte_idx; + if (pstr->mb_cur_max == 1) + return 1; + for (byte_idx = 1; idx + byte_idx < pstr->valid_len; ++byte_idx) + if (pstr->wcs[idx + byte_idx] != WEOF) + break; + return byte_idx; +} + +static inline wint_t +internal_function +re_string_wchar_at (const re_string_t *pstr, int idx) +{ + if (pstr->mb_cur_max == 1) + return (wint_t) pstr->mbs[idx]; + return (wint_t) pstr->wcs[idx]; +} + +static int +internal_function +re_string_elem_size_at (const re_string_t *pstr, int idx) +{ +#ifdef _LIBC + const unsigned char *p, *extra; + const int32_t *table, *indirect; + int32_t tmp; +# include + uint_fast32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); + + if (nrules != 0) + { + table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); + extra = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB); + indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE, + _NL_COLLATE_INDIRECTMB); + p = pstr->mbs + idx; + tmp = findidx (&p); + return p - pstr->mbs - idx; + } + else +#endif /* _LIBC */ + return 1; +} +#endif /* RE_ENABLE_I18N */ + +#endif /* _REGEX_INTERNAL_H */ diff --git a/src/regex/regexec.ci b/src/regex/regexec.ci new file mode 100644 index 0000000..e7461f4 --- /dev/null +++ b/src/regex/regexec.ci @@ -0,0 +1,4325 @@ +/* Extended regular expression matching and search library. + Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Isamu Hasegawa . + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +static reg_errcode_t match_ctx_init (re_match_context_t *cache, int eflags, + int n) internal_function; +static void match_ctx_clean (re_match_context_t *mctx) internal_function; +static void match_ctx_free (re_match_context_t *cache) internal_function; +static reg_errcode_t match_ctx_add_entry (re_match_context_t *cache, int node, + int str_idx, int from, int to) + internal_function; +static int search_cur_bkref_entry (re_match_context_t *mctx, int str_idx) + internal_function; +static reg_errcode_t match_ctx_add_subtop (re_match_context_t *mctx, int node, + int str_idx) internal_function; +static re_sub_match_last_t * match_ctx_add_sublast (re_sub_match_top_t *subtop, + int node, int str_idx) + internal_function; +static void sift_ctx_init (re_sift_context_t *sctx, re_dfastate_t **sifted_sts, + re_dfastate_t **limited_sts, int last_node, + int last_str_idx) + internal_function; +static reg_errcode_t re_search_internal (const regex_t *preg, + const char *string, int length, + int start, int range, int stop, + size_t nmatch, regmatch_t pmatch[], + int eflags) internal_function; +static int re_search_2_stub (struct re_pattern_buffer *bufp, + const char *string1, int length1, + const char *string2, int length2, + int start, int range, struct re_registers *regs, + int stop, int ret_len) internal_function; +static int re_search_stub (struct re_pattern_buffer *bufp, + const char *string, int length, int start, + int range, int stop, struct re_registers *regs, + int ret_len) internal_function; +static unsigned re_copy_regs (struct re_registers *regs, regmatch_t *pmatch, + int nregs, int regs_allocated) internal_function; +static inline re_dfastate_t *acquire_init_state_context + (reg_errcode_t *err, const re_match_context_t *mctx, int idx) + __attribute ((always_inline)) internal_function; +static reg_errcode_t prune_impossible_nodes (re_match_context_t *mctx) + internal_function; +static int check_matching (re_match_context_t *mctx, int fl_longest_match, + int *p_match_first) + internal_function; +static int check_halt_node_context (const re_dfa_t *dfa, int node, + unsigned int context) internal_function; +static int check_halt_state_context (const re_match_context_t *mctx, + const re_dfastate_t *state, int idx) + internal_function; +static void update_regs (re_dfa_t *dfa, regmatch_t *pmatch, + regmatch_t *prev_idx_match, int cur_node, + int cur_idx, int nmatch) internal_function; +static int proceed_next_node (const re_match_context_t *mctx, + int nregs, regmatch_t *regs, + int *pidx, int node, re_node_set *eps_via_nodes, + struct re_fail_stack_t *fs) internal_function; +static reg_errcode_t push_fail_stack (struct re_fail_stack_t *fs, + int str_idx, int dest_node, int nregs, + regmatch_t *regs, + re_node_set *eps_via_nodes) internal_function; +static int pop_fail_stack (struct re_fail_stack_t *fs, int *pidx, int nregs, + regmatch_t *regs, re_node_set *eps_via_nodes) internal_function; +static reg_errcode_t set_regs (const regex_t *preg, + const re_match_context_t *mctx, + size_t nmatch, regmatch_t *pmatch, + int fl_backtrack) internal_function; +static reg_errcode_t free_fail_stack_return (struct re_fail_stack_t *fs) internal_function; + +#ifdef RE_ENABLE_I18N +static int sift_states_iter_mb (const re_match_context_t *mctx, + re_sift_context_t *sctx, + int node_idx, int str_idx, int max_str_idx) internal_function; +#endif /* RE_ENABLE_I18N */ +static reg_errcode_t sift_states_backward (re_match_context_t *mctx, + re_sift_context_t *sctx) internal_function; +static reg_errcode_t build_sifted_states (re_match_context_t *mctx, + re_sift_context_t *sctx, int str_idx, + re_node_set *cur_dest) internal_function; +static reg_errcode_t update_cur_sifted_state (re_match_context_t *mctx, + re_sift_context_t *sctx, + int str_idx, + re_node_set *dest_nodes) internal_function; +static reg_errcode_t add_epsilon_src_nodes (re_dfa_t *dfa, + re_node_set *dest_nodes, + const re_node_set *candidates) internal_function; +static reg_errcode_t sub_epsilon_src_nodes (re_dfa_t *dfa, int node, + re_node_set *dest_nodes, + const re_node_set *and_nodes) internal_function; +static int check_dst_limits (re_match_context_t *mctx, re_node_set *limits, + int dst_node, int dst_idx, int src_node, + int src_idx) internal_function; +static int check_dst_limits_calc_pos_1 (re_match_context_t *mctx, + int boundaries, int subexp_idx, + int from_node, int bkref_idx) internal_function; +static int check_dst_limits_calc_pos (re_match_context_t *mctx, + int limit, int subexp_idx, + int node, int str_idx, + int bkref_idx) internal_function; +static reg_errcode_t check_subexp_limits (re_dfa_t *dfa, + re_node_set *dest_nodes, + const re_node_set *candidates, + re_node_set *limits, + struct re_backref_cache_entry *bkref_ents, + int str_idx) internal_function; +static reg_errcode_t sift_states_bkref (re_match_context_t *mctx, + re_sift_context_t *sctx, + int str_idx, const re_node_set *candidates) internal_function; +static reg_errcode_t clean_state_log_if_needed (re_match_context_t *mctx, + int next_state_log_idx) internal_function; +static reg_errcode_t merge_state_array (re_dfa_t *dfa, re_dfastate_t **dst, + re_dfastate_t **src, int num) internal_function; +static re_dfastate_t *find_recover_state (reg_errcode_t *err, + re_match_context_t *mctx) internal_function; +static re_dfastate_t *transit_state (reg_errcode_t *err, + re_match_context_t *mctx, + re_dfastate_t *state) internal_function; +static re_dfastate_t *merge_state_with_log (reg_errcode_t *err, + re_match_context_t *mctx, + re_dfastate_t *next_state) internal_function; +static reg_errcode_t check_subexp_matching_top (re_match_context_t *mctx, + re_node_set *cur_nodes, + int str_idx) internal_function; +#if 0 +static re_dfastate_t *transit_state_sb (reg_errcode_t *err, + re_match_context_t *mctx, + re_dfastate_t *pstate) internal_function; +#endif +#ifdef RE_ENABLE_I18N +static reg_errcode_t transit_state_mb (re_match_context_t *mctx, + re_dfastate_t *pstate) internal_function; +#endif /* RE_ENABLE_I18N */ +static reg_errcode_t transit_state_bkref (re_match_context_t *mctx, + const re_node_set *nodes) internal_function; +static reg_errcode_t get_subexp (re_match_context_t *mctx, + int bkref_node, int bkref_str_idx) internal_function; +static reg_errcode_t get_subexp_sub (re_match_context_t *mctx, + const re_sub_match_top_t *sub_top, + re_sub_match_last_t *sub_last, + int bkref_node, int bkref_str) internal_function; +static int find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes, + int subexp_idx, int type) internal_function; +static reg_errcode_t check_arrival (re_match_context_t *mctx, + state_array_t *path, int top_node, + int top_str, int last_node, int last_str, + int type) internal_function; +static reg_errcode_t check_arrival_add_next_nodes (re_match_context_t *mctx, + int str_idx, + re_node_set *cur_nodes, + re_node_set *next_nodes) internal_function; +static reg_errcode_t check_arrival_expand_ecl (re_dfa_t *dfa, + re_node_set *cur_nodes, + int ex_subexp, int type) internal_function; +static reg_errcode_t check_arrival_expand_ecl_sub (re_dfa_t *dfa, + re_node_set *dst_nodes, + int target, int ex_subexp, + int type) internal_function; +static reg_errcode_t expand_bkref_cache (re_match_context_t *mctx, + re_node_set *cur_nodes, int cur_str, + int subexp_num, int type) internal_function; +static re_dfastate_t **build_trtable (re_dfa_t *dfa, + re_dfastate_t *state) internal_function; +#ifdef RE_ENABLE_I18N +static int check_node_accept_bytes (re_dfa_t *dfa, int node_idx, + const re_string_t *input, int idx) internal_function; +# ifdef _LIBC +static unsigned int find_collation_sequence_value (const unsigned char *mbs, + size_t name_len) internal_function; +# endif /* _LIBC */ +#endif /* RE_ENABLE_I18N */ +static int group_nodes_into_DFAstates (re_dfa_t *dfa, + const re_dfastate_t *state, + re_node_set *states_node, + bitset *states_ch) internal_function; +static int check_node_accept (const re_match_context_t *mctx, + const re_token_t *node, int idx) internal_function; +static reg_errcode_t extend_buffers (re_match_context_t *mctx) internal_function; + +/* Entry point for POSIX code. */ + +/* regexec searches for a given pattern, specified by PREG, in the + string STRING. + + If NMATCH is zero or REG_NOSUB was set in the cflags argument to + `regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at + least NMATCH elements, and we set them to the offsets of the + corresponding matched substrings. + + EFLAGS specifies `execution flags' which affect matching: if + REG_NOTBOL is set, then ^ does not match at the beginning of the + string; if REG_NOTEOL is set, then $ does not match at the end. + + We return 0 if we find a match and REG_NOMATCH if not. */ + +int +regexec (preg, string, nmatch, pmatch, eflags) + const regex_t *__restrict preg; + const char *__restrict string; + size_t nmatch; + regmatch_t pmatch[]; + int eflags; +{ + reg_errcode_t err; + int start, length; + + if (eflags & ~(REG_NOTBOL | REG_NOTEOL | REG_STARTEND)) + return REG_BADPAT; + + if (eflags & REG_STARTEND) + { + start = pmatch[0].rm_so; + length = pmatch[0].rm_eo; + } + else + { + start = 0; + length = strlen (string); + } + if (preg->no_sub) + err = re_search_internal (preg, string, length, start, length - start, + length, 0, NULL, eflags); + else + err = re_search_internal (preg, string, length, start, length - start, + length, nmatch, pmatch, eflags); + return err != REG_NOERROR; +} + +#ifdef _LIBC +# include +versioned_symbol (libc, __regexec, regexec, GLIBC_2_3_4); + +# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3_4) +__typeof__ (__regexec) __compat_regexec; + +int +attribute_compat_text_section +__compat_regexec (const regex_t *__restrict preg, + const char *__restrict string, size_t nmatch, + regmatch_t pmatch[], int eflags) +{ + return regexec (preg, string, nmatch, pmatch, + eflags & (REG_NOTBOL | REG_NOTEOL)); +} +compat_symbol (libc, __compat_regexec, regexec, GLIBC_2_0); +# endif +#endif + +/* Entry points for GNU code. */ + +/* re_match, re_search, re_match_2, re_search_2 + + The former two functions operate on STRING with length LENGTH, + while the later two operate on concatenation of STRING1 and STRING2 + with lengths LENGTH1 and LENGTH2, respectively. + + re_match() matches the compiled pattern in BUFP against the string, + starting at index START. + + re_search() first tries matching at index START, then it tries to match + starting from index START + 1, and so on. The last start position tried + is START + RANGE. (Thus RANGE = 0 forces re_search to operate the same + way as re_match().) + + The parameter STOP of re_{match,search}_2 specifies that no match exceeding + the first STOP characters of the concatenation of the strings should be + concerned. + + If REGS is not NULL, and BUFP->no_sub is not set, the offsets of the match + and all groups is stroed in REGS. (For the "_2" variants, the offsets are + computed relative to the concatenation, not relative to the individual + strings.) + + On success, re_match* functions return the length of the match, re_search* + return the position of the start of the match. Return value -1 means no + match was found and -2 indicates an internal error. */ + +int +re_match (bufp, string, length, start, regs) + struct re_pattern_buffer *bufp; + const char *string; + int length, start; + struct re_registers *regs; +{ + return re_search_stub (bufp, string, length, start, 0, length, regs, 1); +} +#ifdef _LIBC +weak_alias (__re_match, re_match) +#endif + +int +re_search (bufp, string, length, start, range, regs) + struct re_pattern_buffer *bufp; + const char *string; + int length, start, range; + struct re_registers *regs; +{ + return re_search_stub (bufp, string, length, start, range, length, regs, 0); +} +#ifdef _LIBC +weak_alias (__re_search, re_search) +#endif + +int +re_match_2 (bufp, string1, length1, string2, length2, start, regs, stop) + struct re_pattern_buffer *bufp; + const char *string1, *string2; + int length1, length2, start, stop; + struct re_registers *regs; +{ + return re_search_2_stub (bufp, string1, length1, string2, length2, + start, 0, regs, stop, 1); +} +#ifdef _LIBC +weak_alias (__re_match_2, re_match_2) +#endif + +int +re_search_2 (bufp, string1, length1, string2, length2, start, range, regs, stop) + struct re_pattern_buffer *bufp; + const char *string1, *string2; + int length1, length2, start, range, stop; + struct re_registers *regs; +{ + return re_search_2_stub (bufp, string1, length1, string2, length2, + start, range, regs, stop, 0); +} +#ifdef _LIBC +weak_alias (__re_search_2, re_search_2) +#endif + +static int +re_search_2_stub (bufp, string1, length1, string2, length2, start, range, regs, + stop, ret_len) + struct re_pattern_buffer *bufp; + const char *string1, *string2; + int length1, length2, start, range, stop, ret_len; + struct re_registers *regs; +{ + const char *str; + int rval; + int len = length1 + length2; + int free_str = 0; + + if (BE (length1 < 0 || length2 < 0 || stop < 0, 0)) + return -2; + + /* Concatenate the strings. */ + if (length2 > 0) + if (length1 > 0) + { + char *s = re_malloc (char, len); + + if (BE (s == NULL, 0)) + return -2; + memcpy (s, string1, length1); + memcpy (s + length1, string2, length2); + str = s; + free_str = 1; + } + else + str = string2; + else + str = string1; + + rval = re_search_stub (bufp, str, len, start, range, stop, regs, + ret_len); + if (free_str) + re_free ((char *) str); + return rval; +} + +/* The parameters have the same meaning as those of re_search. + Additional parameters: + If RET_LEN is nonzero the length of the match is returned (re_match style); + otherwise the position of the match is returned. */ + +static int +re_search_stub (bufp, string, length, start, range, stop, regs, ret_len) + struct re_pattern_buffer *bufp; + const char *string; + int length, start, range, stop, ret_len; + struct re_registers *regs; +{ + reg_errcode_t result; + regmatch_t *pmatch; + int nregs, rval; + int eflags = 0; + + /* Check for out-of-range. */ + if (BE (start < 0 || start > length, 0)) + return -1; + if (BE (start + range > length, 0)) + range = length - start; + else if (BE (start + range < 0, 0)) + range = -start; + + eflags |= (bufp->not_bol) ? REG_NOTBOL : 0; + eflags |= (bufp->not_eol) ? REG_NOTEOL : 0; + + /* Compile fastmap if we haven't yet. */ + if (range > 0 && bufp->fastmap != NULL && !bufp->fastmap_accurate) + re_compile_fastmap (bufp); + + if (BE (bufp->no_sub, 0)) + regs = NULL; + + /* We need at least 1 register. */ + if (regs == NULL) + nregs = 1; + else if (BE (bufp->regs_allocated == REGS_FIXED && + regs->num_regs < bufp->re_nsub + 1, 0)) + { + nregs = regs->num_regs; + if (BE (nregs < 1, 0)) + { + /* Nothing can be copied to regs. */ + regs = NULL; + nregs = 1; + } + } + else + nregs = bufp->re_nsub + 1; + pmatch = re_malloc (regmatch_t, nregs); + if (BE (pmatch == NULL, 0)) + return -2; + + result = re_search_internal (bufp, string, length, start, range, stop, + nregs, pmatch, eflags); + + rval = 0; + + /* I hope we needn't fill ther regs with -1's when no match was found. */ + if (result != REG_NOERROR) + rval = -1; + else if (regs != NULL) + { + /* If caller wants register contents data back, copy them. */ + bufp->regs_allocated = re_copy_regs (regs, pmatch, nregs, + bufp->regs_allocated); + if (BE (bufp->regs_allocated == REGS_UNALLOCATED, 0)) + rval = -2; + } + + if (BE (rval == 0, 1)) + { + if (ret_len) + { + assert (pmatch[0].rm_so == start); + rval = pmatch[0].rm_eo - start; + } + else + rval = pmatch[0].rm_so; + } + re_free (pmatch); + return rval; +} + +static unsigned +re_copy_regs (regs, pmatch, nregs, regs_allocated) + struct re_registers *regs; + regmatch_t *pmatch; + int nregs, regs_allocated; +{ + int rval = REGS_REALLOCATE; + int i; + int need_regs = nregs + 1; + /* We need one extra element beyond `num_regs' for the `-1' marker GNU code + uses. */ + + /* Have the register data arrays been allocated? */ + if (regs_allocated == REGS_UNALLOCATED) + { /* No. So allocate them with malloc. */ + regs->start = re_malloc (regoff_t, need_regs); + regs->end = re_malloc (regoff_t, need_regs); + if (BE (regs->start == NULL, 0) || BE (regs->end == NULL, 0)) + return REGS_UNALLOCATED; + regs->num_regs = need_regs; + } + else if (regs_allocated == REGS_REALLOCATE) + { /* Yes. If we need more elements than were already + allocated, reallocate them. If we need fewer, just + leave it alone. */ + if (BE (need_regs > regs->num_regs, 0)) + { + regoff_t *new_start = re_realloc (regs->start, regoff_t, need_regs); + regoff_t *new_end = re_realloc (regs->end, regoff_t, need_regs); + if (BE (new_start == NULL, 0) || BE (new_end == NULL, 0)) + return REGS_UNALLOCATED; + regs->start = new_start; + regs->end = new_end; + regs->num_regs = need_regs; + } + } + else + { + assert (regs_allocated == REGS_FIXED); + /* This function may not be called with REGS_FIXED and nregs too big. */ + assert (regs->num_regs >= nregs); + rval = REGS_FIXED; + } + + /* Copy the regs. */ + for (i = 0; i < nregs; ++i) + { + regs->start[i] = pmatch[i].rm_so; + regs->end[i] = pmatch[i].rm_eo; + } + for ( ; i < regs->num_regs; ++i) + regs->start[i] = regs->end[i] = -1; + + return rval; +} + +/* Set REGS to hold NUM_REGS registers, storing them in STARTS and + ENDS. Subsequent matches using PATTERN_BUFFER and REGS will use + this memory for recording register information. STARTS and ENDS + must be allocated using the malloc library routine, and must each + be at least NUM_REGS * sizeof (regoff_t) bytes long. + + If NUM_REGS == 0, then subsequent matches should allocate their own + register data. + + Unless this function is called, the first search or match using + PATTERN_BUFFER will allocate its own register data, without + freeing the old data. */ + +void +re_set_registers (bufp, regs, num_regs, starts, ends) + struct re_pattern_buffer *bufp; + struct re_registers *regs; + unsigned num_regs; + regoff_t *starts, *ends; +{ + if (num_regs) + { + bufp->regs_allocated = REGS_REALLOCATE; + regs->num_regs = num_regs; + regs->start = starts; + regs->end = ends; + } + else + { + bufp->regs_allocated = REGS_UNALLOCATED; + regs->num_regs = 0; + regs->start = regs->end = (regoff_t *) 0; + } +} +#ifdef _LIBC +weak_alias (__re_set_registers, re_set_registers) +#endif + +/* Entry points compatible with 4.2 BSD regex library. We don't define + them unless specifically requested. */ + +#if defined _REGEX_RE_COMP || defined _LIBC +int +# ifdef _LIBC +weak_function +# endif +re_exec (s) + const char *s; +{ + return 0 == regexec (&re_comp_buf, s, 0, NULL, 0); +} +#endif /* _REGEX_RE_COMP */ + +/* Internal entry point. */ + +/* Searches for a compiled pattern PREG in the string STRING, whose + length is LENGTH. NMATCH, PMATCH, and EFLAGS have the same + mingings with regexec. START, and RANGE have the same meanings + with re_search. + Return REG_NOERROR if we find a match, and REG_NOMATCH if not, + otherwise return the error code. + Note: We assume front end functions already check ranges. + (START + RANGE >= 0 && START + RANGE <= LENGTH) */ + +static reg_errcode_t +re_search_internal (preg, string, length, start, range, stop, nmatch, pmatch, + eflags) + const regex_t *preg; + const char *string; + int length, start, range, stop, eflags; + size_t nmatch; + regmatch_t pmatch[]; +{ + reg_errcode_t err; + re_dfa_t *dfa = (re_dfa_t *)preg->buffer; + int left_lim, right_lim, incr; + int fl_longest_match, match_first, match_kind, match_last = -1; + int sb, ch; +#if defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) + re_match_context_t mctx = { .dfa = dfa }; +#else + re_match_context_t mctx; +#endif + char *fastmap = (preg->fastmap != NULL && preg->fastmap_accurate + && range && !preg->can_be_null) ? preg->fastmap : NULL; + unsigned RE_TRANSLATE_TYPE t = (unsigned RE_TRANSLATE_TYPE) preg->translate; + +#if !(defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L)) + memset (&mctx, '\0', sizeof (re_match_context_t)); + mctx.dfa = dfa; +#endif + + /* Check if the DFA haven't been compiled. */ + if (BE (preg->used == 0 || dfa->init_state == NULL + || dfa->init_state_word == NULL || dfa->init_state_nl == NULL + || dfa->init_state_begbuf == NULL, 0)) + return REG_NOMATCH; + +#ifdef DEBUG + /* We assume front-end functions already check them. */ + assert (start + range >= 0 && start + range <= length); +#endif + + /* If initial states with non-begbuf contexts have no elements, + the regex must be anchored. If preg->newline_anchor is set, + we'll never use init_state_nl, so do not check it. */ + if (dfa->init_state->nodes.nelem == 0 + && dfa->init_state_word->nodes.nelem == 0 + && (dfa->init_state_nl->nodes.nelem == 0 + || !preg->newline_anchor)) + { + if (start != 0 && start + range != 0) + return REG_NOMATCH; + start = range = 0; + } + + /* We must check the longest matching, if nmatch > 0. */ + fl_longest_match = (nmatch != 0 || dfa->nbackref); + + err = re_string_allocate (&mctx.input, string, length, dfa->nodes_len + 1, + preg->translate, preg->syntax & RE_ICASE, dfa); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + mctx.input.stop = stop; + mctx.input.raw_stop = stop; + mctx.input.newline_anchor = preg->newline_anchor; + + err = match_ctx_init (&mctx, eflags, dfa->nbackref * 2); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + + /* We will log all the DFA states through which the dfa pass, + if nmatch > 1, or this dfa has "multibyte node", which is a + back-reference or a node which can accept multibyte character or + multi character collating element. */ + if (nmatch > 1 || dfa->has_mb_node) + { + mctx.state_log = re_malloc (re_dfastate_t *, mctx.input.bufs_len + 1); + if (BE (mctx.state_log == NULL, 0)) + { + err = REG_ESPACE; + goto free_return; + } + } + else + mctx.state_log = NULL; + + match_first = start; + mctx.input.tip_context = (eflags & REG_NOTBOL) ? CONTEXT_BEGBUF + : CONTEXT_NEWLINE | CONTEXT_BEGBUF; + + /* Check incrementally whether of not the input string match. */ + incr = (range < 0) ? -1 : 1; + left_lim = (range < 0) ? start + range : start; + right_lim = (range < 0) ? start : start + range; + sb = dfa->mb_cur_max == 1; + match_kind = + (fastmap + ? ((sb || !(preg->syntax & RE_ICASE || t) ? 4 : 0) + | (range >= 0 ? 2 : 0) + | (t != NULL ? 1 : 0)) + : 8); + + for (;; match_first += incr) + { + err = REG_NOMATCH; + if (match_first < left_lim || right_lim < match_first) + goto free_return; + + /* Advance as rapidly as possible through the string, until we + find a plausible place to start matching. This may be done + with varying efficiency, so there are various possibilities: + only the most common of them are specialized, in order to + save on code size. We use a switch statement for speed. */ + switch (match_kind) + { + case 8: + /* No fastmap. */ + break; + + case 7: + /* Fastmap with single-byte translation, match forward. */ + while (BE (match_first < right_lim, 1) + && !fastmap[t[(unsigned char) string[match_first]]]) + ++match_first; + goto forward_match_found_start_or_reached_end; + + case 6: + /* Fastmap without translation, match forward. */ + while (BE (match_first < right_lim, 1) + && !fastmap[(unsigned char) string[match_first]]) + ++match_first; + + forward_match_found_start_or_reached_end: + if (BE (match_first == right_lim, 0)) + { + ch = match_first >= length + ? 0 : (unsigned char) string[match_first]; + if (!fastmap[t ? t[ch] : ch]) + goto free_return; + } + break; + + case 4: + case 5: + /* Fastmap without multi-byte translation, match backwards. */ + while (match_first >= left_lim) + { + ch = match_first >= length + ? 0 : (unsigned char) string[match_first]; + if (fastmap[t ? t[ch] : ch]) + break; + --match_first; + } + if (match_first < left_lim) + goto free_return; + break; + + default: + /* In this case, we can't determine easily the current byte, + since it might be a component byte of a multibyte + character. Then we use the constructed buffer instead. */ + for (;;) + { + /* If MATCH_FIRST is out of the valid range, reconstruct the + buffers. */ + unsigned int offset = match_first - mctx.input.raw_mbs_idx; + if (BE (offset >= (unsigned int) mctx.input.valid_raw_len, 0)) + { + err = re_string_reconstruct (&mctx.input, match_first, + eflags); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + + offset = match_first - mctx.input.raw_mbs_idx; + } + /* If MATCH_FIRST is out of the buffer, leave it as '\0'. + Note that MATCH_FIRST must not be smaller than 0. */ + ch = (match_first >= length + ? 0 : re_string_byte_at (&mctx.input, offset)); + if (fastmap[ch]) + break; + match_first += incr; + if (match_first < left_lim || match_first > right_lim) + { + err = REG_NOMATCH; + goto free_return; + } + } + break; + } + + /* Reconstruct the buffers so that the matcher can assume that + the matching starts from the beginning of the buffer. */ + err = re_string_reconstruct (&mctx.input, match_first, eflags); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + +#ifdef RE_ENABLE_I18N + /* Don't consider this char as a possible match start if it part, + yet isn't the head, of a multibyte character. */ + if (!sb && !re_string_first_byte (&mctx.input, 0)) + continue; +#endif + + /* It seems to be appropriate one, then use the matcher. */ + /* We assume that the matching starts from 0. */ + mctx.state_log_top = mctx.nbkref_ents = mctx.max_mb_elem_len = 0; + match_last = check_matching (&mctx, fl_longest_match, + range >= 0 ? &match_first : NULL); + if (match_last != -1) + { + if (BE (match_last == -2, 0)) + { + err = REG_ESPACE; + goto free_return; + } + else + { + mctx.match_last = match_last; + if ((!preg->no_sub && nmatch > 1) || dfa->nbackref) + { + re_dfastate_t *pstate = mctx.state_log[match_last]; + mctx.last_node = check_halt_state_context (&mctx, pstate, + match_last); + } + if ((!preg->no_sub && nmatch > 1 && dfa->has_plural_match) + || dfa->nbackref) + { + err = prune_impossible_nodes (&mctx); + if (err == REG_NOERROR) + break; + if (BE (err != REG_NOMATCH, 0)) + goto free_return; + match_last = -1; + } + else + break; /* We found a match. */ + } + } + + match_ctx_clean (&mctx); + } + +#ifdef DEBUG + assert (match_last != -1); + assert (err == REG_NOERROR); +#endif + + /* Set pmatch[] if we need. */ + if (nmatch > 0) + { + int reg_idx; + + /* Initialize registers. */ + for (reg_idx = 1; reg_idx < nmatch; ++reg_idx) + pmatch[reg_idx].rm_so = pmatch[reg_idx].rm_eo = -1; + + /* Set the points where matching start/end. */ + pmatch[0].rm_so = 0; + pmatch[0].rm_eo = mctx.match_last; + + if (!preg->no_sub && nmatch > 1) + { + err = set_regs (preg, &mctx, nmatch, pmatch, + dfa->has_plural_match && dfa->nbackref > 0); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + } + + /* At last, add the offset to the each registers, since we slided + the buffers so that we could assume that the matching starts + from 0. */ + for (reg_idx = 0; reg_idx < nmatch; ++reg_idx) + if (pmatch[reg_idx].rm_so != -1) + { +#ifdef RE_ENABLE_I18N + if (BE (mctx.input.offsets_needed != 0, 0)) + { + if (pmatch[reg_idx].rm_so == mctx.input.valid_len) + pmatch[reg_idx].rm_so += mctx.input.valid_raw_len - mctx.input.valid_len; + else + pmatch[reg_idx].rm_so = mctx.input.offsets[pmatch[reg_idx].rm_so]; + if (pmatch[reg_idx].rm_eo == mctx.input.valid_len) + pmatch[reg_idx].rm_eo += mctx.input.valid_raw_len - mctx.input.valid_len; + else + pmatch[reg_idx].rm_eo = mctx.input.offsets[pmatch[reg_idx].rm_eo]; + } +#else + assert (mctx.input.offsets_needed == 0); +#endif + pmatch[reg_idx].rm_so += match_first; + pmatch[reg_idx].rm_eo += match_first; + } + + if (dfa->subexp_map) + for (reg_idx = 0; + reg_idx + 1 < nmatch && reg_idx < preg->re_nsub; + reg_idx++) + if (dfa->subexp_map[reg_idx] != reg_idx) + { + pmatch[reg_idx + 1].rm_so + = pmatch[dfa->subexp_map[reg_idx] + 1].rm_so; + pmatch[reg_idx + 1].rm_eo + = pmatch[dfa->subexp_map[reg_idx] + 1].rm_eo; + } + } + + free_return: + re_free (mctx.state_log); + if (dfa->nbackref) + match_ctx_free (&mctx); + re_string_destruct (&mctx.input); + return err; +} + +static reg_errcode_t +prune_impossible_nodes (mctx) + re_match_context_t *mctx; +{ + re_dfa_t *const dfa = mctx->dfa; + int halt_node, match_last; + reg_errcode_t ret; + re_dfastate_t **sifted_states; + re_dfastate_t **lim_states = NULL; + re_sift_context_t sctx; +#ifdef DEBUG + assert (mctx->state_log != NULL); +#endif + match_last = mctx->match_last; + halt_node = mctx->last_node; + sifted_states = re_malloc (re_dfastate_t *, match_last + 1); + if (BE (sifted_states == NULL, 0)) + { + ret = REG_ESPACE; + goto free_return; + } + if (dfa->nbackref) + { + lim_states = re_malloc (re_dfastate_t *, match_last + 1); + if (BE (lim_states == NULL, 0)) + { + ret = REG_ESPACE; + goto free_return; + } + while (1) + { + memset (lim_states, '\0', + sizeof (re_dfastate_t *) * (match_last + 1)); + sift_ctx_init (&sctx, sifted_states, lim_states, halt_node, + match_last); + ret = sift_states_backward (mctx, &sctx); + re_node_set_free (&sctx.limits); + if (BE (ret != REG_NOERROR, 0)) + goto free_return; + if (sifted_states[0] != NULL || lim_states[0] != NULL) + break; + do + { + --match_last; + if (match_last < 0) + { + ret = REG_NOMATCH; + goto free_return; + } + } while (mctx->state_log[match_last] == NULL + || !mctx->state_log[match_last]->halt); + halt_node = check_halt_state_context (mctx, + mctx->state_log[match_last], + match_last); + } + ret = merge_state_array (dfa, sifted_states, lim_states, + match_last + 1); + re_free (lim_states); + lim_states = NULL; + if (BE (ret != REG_NOERROR, 0)) + goto free_return; + } + else + { + sift_ctx_init (&sctx, sifted_states, lim_states, halt_node, match_last); + ret = sift_states_backward (mctx, &sctx); + re_node_set_free (&sctx.limits); + if (BE (ret != REG_NOERROR, 0)) + goto free_return; + } + re_free (mctx->state_log); + mctx->state_log = sifted_states; + sifted_states = NULL; + mctx->last_node = halt_node; + mctx->match_last = match_last; + ret = REG_NOERROR; + free_return: + re_free (sifted_states); + re_free (lim_states); + return ret; +} + +/* Acquire an initial state and return it. + We must select appropriate initial state depending on the context, + since initial states may have constraints like "\<", "^", etc.. */ + +static inline re_dfastate_t * +acquire_init_state_context (err, mctx, idx) + reg_errcode_t *err; + const re_match_context_t *mctx; + int idx; +{ + re_dfa_t *const dfa = mctx->dfa; + if (dfa->init_state->has_constraint) + { + unsigned int context; + context = re_string_context_at (&mctx->input, idx - 1, mctx->eflags); + if (IS_WORD_CONTEXT (context)) + return dfa->init_state_word; + else if (IS_ORDINARY_CONTEXT (context)) + return dfa->init_state; + else if (IS_BEGBUF_CONTEXT (context) && IS_NEWLINE_CONTEXT (context)) + return dfa->init_state_begbuf; + else if (IS_NEWLINE_CONTEXT (context)) + return dfa->init_state_nl; + else if (IS_BEGBUF_CONTEXT (context)) + { + /* It is relatively rare case, then calculate on demand. */ + return re_acquire_state_context (err, dfa, + dfa->init_state->entrance_nodes, + context); + } + else + /* Must not happen? */ + return dfa->init_state; + } + else + return dfa->init_state; +} + +/* Check whether the regular expression match input string INPUT or not, + and return the index where the matching end, return -1 if not match, + or return -2 in case of an error. + FL_LONGEST_MATCH means we want the POSIX longest matching. + If P_MATCH_FIRST is not NULL, and the match fails, it is set to the + next place where we may want to try matching. + Note that the matcher assume that the maching starts from the current + index of the buffer. */ + +static int +check_matching (mctx, fl_longest_match, p_match_first) + re_match_context_t *mctx; + int fl_longest_match; + int *p_match_first; +{ + re_dfa_t *const dfa = mctx->dfa; + reg_errcode_t err; + int match = 0; + int match_last = -1; + int cur_str_idx = re_string_cur_idx (&mctx->input); + re_dfastate_t *cur_state; + int at_init_state = p_match_first != NULL; + int next_start_idx = cur_str_idx; + + err = REG_NOERROR; + cur_state = acquire_init_state_context (&err, mctx, cur_str_idx); + /* An initial state must not be NULL (invalid). */ + if (BE (cur_state == NULL, 0)) + { + assert (err == REG_ESPACE); + return -2; + } + + if (mctx->state_log != NULL) + { + mctx->state_log[cur_str_idx] = cur_state; + + /* Check OP_OPEN_SUBEXP in the initial state in case that we use them + later. E.g. Processing back references. */ + if (BE (dfa->nbackref, 0)) + { + at_init_state = 0; + err = check_subexp_matching_top (mctx, &cur_state->nodes, 0); + if (BE (err != REG_NOERROR, 0)) + return err; + + if (cur_state->has_backref) + { + err = transit_state_bkref (mctx, &cur_state->nodes); + if (BE (err != REG_NOERROR, 0)) + return err; + } + } + } + + /* If the RE accepts NULL string. */ + if (BE (cur_state->halt, 0)) + { + if (!cur_state->has_constraint + || check_halt_state_context (mctx, cur_state, cur_str_idx)) + { + if (!fl_longest_match) + return cur_str_idx; + else + { + match_last = cur_str_idx; + match = 1; + } + } + } + + while (!re_string_eoi (&mctx->input)) + { + re_dfastate_t *old_state = cur_state; + int next_char_idx = re_string_cur_idx (&mctx->input) + 1; + + if (BE (next_char_idx >= mctx->input.bufs_len, 0) + || (BE (next_char_idx >= mctx->input.valid_len, 0) + && mctx->input.valid_len < mctx->input.len)) + { + err = extend_buffers (mctx); + if (BE (err != REG_NOERROR, 0)) + { + assert (err == REG_ESPACE); + return -2; + } + } + + cur_state = transit_state (&err, mctx, cur_state); + if (mctx->state_log != NULL) + cur_state = merge_state_with_log (&err, mctx, cur_state); + + if (cur_state == NULL) + { + /* Reached the invalid state or an error. Try to recover a valid + state using the state log, if available and if we have not + already found a valid (even if not the longest) match. */ + if (BE (err != REG_NOERROR, 0)) + return -2; + + if (mctx->state_log == NULL + || (match && !fl_longest_match) + || (cur_state = find_recover_state (&err, mctx)) == NULL) + break; + } + + if (BE (at_init_state, 0)) + { + if (old_state == cur_state) + next_start_idx = next_char_idx; + else + at_init_state = 0; + } + + if (cur_state->halt) + { + /* Reached a halt state. + Check the halt state can satisfy the current context. */ + if (!cur_state->has_constraint + || check_halt_state_context (mctx, cur_state, + re_string_cur_idx (&mctx->input))) + { + /* We found an appropriate halt state. */ + match_last = re_string_cur_idx (&mctx->input); + match = 1; + + /* We found a match, do not modify match_first below. */ + p_match_first = NULL; + if (!fl_longest_match) + break; + } + } + } + + if (p_match_first) + *p_match_first += next_start_idx; + + return match_last; +} + +/* Check NODE match the current context. */ + +static int check_halt_node_context (dfa, node, context) + const re_dfa_t *dfa; + int node; + unsigned int context; +{ + re_token_type_t type = dfa->nodes[node].type; + unsigned int constraint = dfa->nodes[node].constraint; + if (type != END_OF_RE) + return 0; + if (!constraint) + return 1; + if (NOT_SATISFY_NEXT_CONSTRAINT (constraint, context)) + return 0; + return 1; +} + +/* Check the halt state STATE match the current context. + Return 0 if not match, if the node, STATE has, is a halt node and + match the context, return the node. */ + +static int +check_halt_state_context (mctx, state, idx) + const re_match_context_t *mctx; + const re_dfastate_t *state; + int idx; +{ + int i; + unsigned int context; +#ifdef DEBUG + assert (state->halt); +#endif + context = re_string_context_at (&mctx->input, idx, mctx->eflags); + for (i = 0; i < state->nodes.nelem; ++i) + if (check_halt_node_context (mctx->dfa, state->nodes.elems[i], context)) + return state->nodes.elems[i]; + return 0; +} + +/* Compute the next node to which "NFA" transit from NODE("NFA" is a NFA + corresponding to the DFA). + Return the destination node, and update EPS_VIA_NODES, return -1 in case + of errors. */ + +static int +proceed_next_node (mctx, nregs, regs, pidx, node, eps_via_nodes, fs) + const re_match_context_t *mctx; + regmatch_t *regs; + int nregs, *pidx, node; + re_node_set *eps_via_nodes; + struct re_fail_stack_t *fs; +{ + re_dfa_t *const dfa = mctx->dfa; + int i, err, dest_node; + dest_node = -1; + if (IS_EPSILON_NODE (dfa->nodes[node].type)) + { + re_node_set *cur_nodes = &mctx->state_log[*pidx]->nodes; + re_node_set *edests = &dfa->edests[node]; + int dest_node; + err = re_node_set_insert (eps_via_nodes, node); + if (BE (err < 0, 0)) + return -2; + /* Pick up a valid destination, or return -1 if none is found. */ + for (dest_node = -1, i = 0; i < edests->nelem; ++i) + { + int candidate = edests->elems[i]; + if (!re_node_set_contains (cur_nodes, candidate)) + continue; + if (dest_node == -1) + dest_node = candidate; + + else + { + /* In order to avoid infinite loop like "(a*)*", return the second + epsilon-transition if the first was already considered. */ + if (re_node_set_contains (eps_via_nodes, dest_node)) + return candidate; + + /* Otherwise, push the second epsilon-transition on the fail stack. */ + else if (fs != NULL + && push_fail_stack (fs, *pidx, candidate, nregs, regs, + eps_via_nodes)) + return -2; + + /* We know we are going to exit. */ + break; + } + } + return dest_node; + } + else + { + int naccepted = 0; + re_token_type_t type = dfa->nodes[node].type; + +#ifdef RE_ENABLE_I18N + if (ACCEPT_MB_NODE (type)) + naccepted = check_node_accept_bytes (dfa, node, &mctx->input, *pidx); + else +#endif /* RE_ENABLE_I18N */ + if (type == OP_BACK_REF) + { + int subexp_idx = dfa->nodes[node].opr.idx + 1; + naccepted = regs[subexp_idx].rm_eo - regs[subexp_idx].rm_so; + if (fs != NULL) + { + if (regs[subexp_idx].rm_so == -1 || regs[subexp_idx].rm_eo == -1) + return -1; + else if (naccepted) + { + char *buf = (char *) re_string_get_buffer (&mctx->input); + if (memcmp (buf + regs[subexp_idx].rm_so, buf + *pidx, + naccepted) != 0) + return -1; + } + } + + if (naccepted == 0) + { + err = re_node_set_insert (eps_via_nodes, node); + if (BE (err < 0, 0)) + return -2; + dest_node = dfa->edests[node].elems[0]; + if (re_node_set_contains (&mctx->state_log[*pidx]->nodes, + dest_node)) + return dest_node; + } + } + + if (naccepted != 0 + || check_node_accept (mctx, dfa->nodes + node, *pidx)) + { + dest_node = dfa->nexts[node]; + *pidx = (naccepted == 0) ? *pidx + 1 : *pidx + naccepted; + if (fs && (*pidx > mctx->match_last || mctx->state_log[*pidx] == NULL + || !re_node_set_contains (&mctx->state_log[*pidx]->nodes, + dest_node))) + return -1; + re_node_set_empty (eps_via_nodes); + return dest_node; + } + } + return -1; +} + +static reg_errcode_t +push_fail_stack (fs, str_idx, dest_node, nregs, regs, eps_via_nodes) + struct re_fail_stack_t *fs; + int str_idx, dest_node, nregs; + regmatch_t *regs; + re_node_set *eps_via_nodes; +{ + reg_errcode_t err; + int num = fs->num++; + if (fs->num == fs->alloc) + { + struct re_fail_stack_ent_t *new_array; + new_array = realloc (fs->stack, (sizeof (struct re_fail_stack_ent_t) + * fs->alloc * 2)); + if (new_array == NULL) + return REG_ESPACE; + fs->alloc *= 2; + fs->stack = new_array; + } + fs->stack[num].idx = str_idx; + fs->stack[num].node = dest_node; + fs->stack[num].regs = re_malloc (regmatch_t, nregs); + if (fs->stack[num].regs == NULL) + return REG_ESPACE; + memcpy (fs->stack[num].regs, regs, sizeof (regmatch_t) * nregs); + err = re_node_set_init_copy (&fs->stack[num].eps_via_nodes, eps_via_nodes); + return err; +} + +static int +pop_fail_stack (fs, pidx, nregs, regs, eps_via_nodes) + struct re_fail_stack_t *fs; + int *pidx, nregs; + regmatch_t *regs; + re_node_set *eps_via_nodes; +{ + int num = --fs->num; + assert (num >= 0); + *pidx = fs->stack[num].idx; + memcpy (regs, fs->stack[num].regs, sizeof (regmatch_t) * nregs); + re_node_set_free (eps_via_nodes); + re_free (fs->stack[num].regs); + *eps_via_nodes = fs->stack[num].eps_via_nodes; + return fs->stack[num].node; +} + +/* Set the positions where the subexpressions are starts/ends to registers + PMATCH. + Note: We assume that pmatch[0] is already set, and + pmatch[i].rm_so == pmatch[i].rm_eo == -1 for 0 < i < nmatch. */ + +static reg_errcode_t +set_regs (preg, mctx, nmatch, pmatch, fl_backtrack) + const regex_t *preg; + const re_match_context_t *mctx; + size_t nmatch; + regmatch_t *pmatch; + int fl_backtrack; +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + int idx, cur_node, real_nmatch; + re_node_set eps_via_nodes; + struct re_fail_stack_t *fs; + struct re_fail_stack_t fs_body = { 0, 2, NULL }; + regmatch_t *prev_idx_match; + +#ifdef DEBUG + assert (nmatch > 1); + assert (mctx->state_log != NULL); +#endif + if (fl_backtrack) + { + fs = &fs_body; + fs->stack = re_malloc (struct re_fail_stack_ent_t, fs->alloc); + if (fs->stack == NULL) + return REG_ESPACE; + } + else + fs = NULL; + + cur_node = dfa->init_node; + real_nmatch = (nmatch <= preg->re_nsub) ? nmatch : preg->re_nsub + 1; + re_node_set_init_empty (&eps_via_nodes); + + prev_idx_match = (regmatch_t *) alloca (sizeof (regmatch_t) * real_nmatch); + memcpy (prev_idx_match, pmatch, sizeof (regmatch_t) * real_nmatch); + + for (idx = pmatch[0].rm_so; idx <= pmatch[0].rm_eo ;) + { + update_regs (dfa, pmatch, prev_idx_match, cur_node, idx, real_nmatch); + + if (idx == pmatch[0].rm_eo && cur_node == mctx->last_node) + { + int reg_idx; + if (fs) + { + for (reg_idx = 0; reg_idx < nmatch; ++reg_idx) + if (pmatch[reg_idx].rm_so > -1 && pmatch[reg_idx].rm_eo == -1) + break; + if (reg_idx == nmatch) + { + re_node_set_free (&eps_via_nodes); + return free_fail_stack_return (fs); + } + cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch, + &eps_via_nodes); + } + else + { + re_node_set_free (&eps_via_nodes); + return REG_NOERROR; + } + } + + /* Proceed to next node. */ + cur_node = proceed_next_node (mctx, nmatch, pmatch, &idx, cur_node, + &eps_via_nodes, fs); + + if (BE (cur_node < 0, 0)) + { + if (BE (cur_node == -2, 0)) + { + re_node_set_free (&eps_via_nodes); + free_fail_stack_return (fs); + return REG_ESPACE; + } + if (fs) + cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch, + &eps_via_nodes); + else + { + re_node_set_free (&eps_via_nodes); + return REG_NOMATCH; + } + } + } + re_node_set_free (&eps_via_nodes); + return free_fail_stack_return (fs); +} + +static reg_errcode_t +free_fail_stack_return (fs) + struct re_fail_stack_t *fs; +{ + if (fs) + { + int fs_idx; + for (fs_idx = 0; fs_idx < fs->num; ++fs_idx) + { + re_node_set_free (&fs->stack[fs_idx].eps_via_nodes); + re_free (fs->stack[fs_idx].regs); + } + re_free (fs->stack); + } + return REG_NOERROR; +} + +static void +update_regs (dfa, pmatch, prev_idx_match, cur_node, cur_idx, nmatch) + re_dfa_t *dfa; + regmatch_t *pmatch, *prev_idx_match; + int cur_node, cur_idx, nmatch; +{ + int type = dfa->nodes[cur_node].type; + if (type == OP_OPEN_SUBEXP) + { + int reg_num = dfa->nodes[cur_node].opr.idx + 1; + + /* We are at the first node of this sub expression. */ + if (reg_num < nmatch) + { + pmatch[reg_num].rm_so = cur_idx; + pmatch[reg_num].rm_eo = -1; + } + } + else if (type == OP_CLOSE_SUBEXP) + { + int reg_num = dfa->nodes[cur_node].opr.idx + 1; + if (reg_num < nmatch) + { + /* We are at the last node of this sub expression. */ + if (pmatch[reg_num].rm_so < cur_idx) + { + pmatch[reg_num].rm_eo = cur_idx; + /* This is a non-empty match or we are not inside an optional + subexpression. Accept this right away. */ + memcpy (prev_idx_match, pmatch, sizeof (regmatch_t) * nmatch); + } + else + { + if (dfa->nodes[cur_node].opt_subexp + && prev_idx_match[reg_num].rm_so != -1) + /* We transited through an empty match for an optional + subexpression, like (a?)*, and this is not the subexp's + first match. Copy back the old content of the registers + so that matches of an inner subexpression are undone as + well, like in ((a?))*. */ + memcpy (pmatch, prev_idx_match, sizeof (regmatch_t) * nmatch); + else + /* We completed a subexpression, but it may be part of + an optional one, so do not update PREV_IDX_MATCH. */ + pmatch[reg_num].rm_eo = cur_idx; + } + } + } +} + +/* This function checks the STATE_LOG from the SCTX->last_str_idx to 0 + and sift the nodes in each states according to the following rules. + Updated state_log will be wrote to STATE_LOG. + + Rules: We throw away the Node `a' in the STATE_LOG[STR_IDX] if... + 1. When STR_IDX == MATCH_LAST(the last index in the state_log): + If `a' isn't the LAST_NODE and `a' can't epsilon transit to + the LAST_NODE, we throw away the node `a'. + 2. When 0 <= STR_IDX < MATCH_LAST and `a' accepts + string `s' and transit to `b': + i. If 'b' isn't in the STATE_LOG[STR_IDX+strlen('s')], we throw + away the node `a'. + ii. If 'b' is in the STATE_LOG[STR_IDX+strlen('s')] but 'b' is + thrown away, we throw away the node `a'. + 3. When 0 <= STR_IDX < MATCH_LAST and 'a' epsilon transit to 'b': + i. If 'b' isn't in the STATE_LOG[STR_IDX], we throw away the + node `a'. + ii. If 'b' is in the STATE_LOG[STR_IDX] but 'b' is thrown away, + we throw away the node `a'. */ + +#define STATE_NODE_CONTAINS(state,node) \ + ((state) != NULL && re_node_set_contains (&(state)->nodes, node)) + +static reg_errcode_t +sift_states_backward (mctx, sctx) + re_match_context_t *mctx; + re_sift_context_t *sctx; +{ + reg_errcode_t err; + int null_cnt = 0; + int str_idx = sctx->last_str_idx; + re_node_set cur_dest; + +#ifdef DEBUG + assert (mctx->state_log != NULL && mctx->state_log[str_idx] != NULL); +#endif + + /* Build sifted state_log[str_idx]. It has the nodes which can epsilon + transit to the last_node and the last_node itself. */ + err = re_node_set_init_1 (&cur_dest, sctx->last_node); + if (BE (err != REG_NOERROR, 0)) + return err; + err = update_cur_sifted_state (mctx, sctx, str_idx, &cur_dest); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + + /* Then check each states in the state_log. */ + while (str_idx > 0) + { + /* Update counters. */ + null_cnt = (sctx->sifted_states[str_idx] == NULL) ? null_cnt + 1 : 0; + if (null_cnt > mctx->max_mb_elem_len) + { + memset (sctx->sifted_states, '\0', + sizeof (re_dfastate_t *) * str_idx); + re_node_set_free (&cur_dest); + return REG_NOERROR; + } + re_node_set_empty (&cur_dest); + --str_idx; + + if (mctx->state_log[str_idx]) + { + err = build_sifted_states (mctx, sctx, str_idx, &cur_dest); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + } + + /* Add all the nodes which satisfy the following conditions: + - It can epsilon transit to a node in CUR_DEST. + - It is in CUR_SRC. + And update state_log. */ + err = update_cur_sifted_state (mctx, sctx, str_idx, &cur_dest); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + } + err = REG_NOERROR; + free_return: + re_node_set_free (&cur_dest); + return err; +} + +static reg_errcode_t +build_sifted_states (mctx, sctx, str_idx, cur_dest) + re_match_context_t *mctx; + re_sift_context_t *sctx; + int str_idx; + re_node_set *cur_dest; +{ + re_dfa_t *const dfa = mctx->dfa; + re_node_set *cur_src = &mctx->state_log[str_idx]->non_eps_nodes; + int i; + + /* Then build the next sifted state. + We build the next sifted state on `cur_dest', and update + `sifted_states[str_idx]' with `cur_dest'. + Note: + `cur_dest' is the sifted state from `state_log[str_idx + 1]'. + `cur_src' points the node_set of the old `state_log[str_idx]' + (with the epsilon nodes pre-filtered out). */ + for (i = 0; i < cur_src->nelem; i++) + { + int prev_node = cur_src->elems[i]; + int naccepted = 0; + int ret; + +#if defined DEBUG || defined RE_ENABLE_I18N + re_token_type_t type = dfa->nodes[prev_node].type; +#endif +#ifdef DEBUG + assert (!IS_EPSILON_NODE (type)); +#endif +#ifdef RE_ENABLE_I18N + /* If the node may accept `multi byte'. */ + if (ACCEPT_MB_NODE (type)) + naccepted = sift_states_iter_mb (mctx, sctx, prev_node, + str_idx, sctx->last_str_idx); +#endif /* RE_ENABLE_I18N */ + + /* We don't check backreferences here. + See update_cur_sifted_state(). */ + if (!naccepted + && check_node_accept (mctx, dfa->nodes + prev_node, str_idx) + && STATE_NODE_CONTAINS (sctx->sifted_states[str_idx + 1], + dfa->nexts[prev_node])) + naccepted = 1; + + if (naccepted == 0) + continue; + + if (sctx->limits.nelem) + { + int to_idx = str_idx + naccepted; + if (check_dst_limits (mctx, &sctx->limits, + dfa->nexts[prev_node], to_idx, + prev_node, str_idx)) + continue; + } + ret = re_node_set_insert (cur_dest, prev_node); + if (BE (ret == -1, 0)) + return REG_ESPACE; + } + + return REG_NOERROR; +} + +/* Helper functions. */ + +static reg_errcode_t +clean_state_log_if_needed (mctx, next_state_log_idx) + re_match_context_t *mctx; + int next_state_log_idx; +{ + int top = mctx->state_log_top; + + if (next_state_log_idx >= mctx->input.bufs_len + || (next_state_log_idx >= mctx->input.valid_len + && mctx->input.valid_len < mctx->input.len)) + { + reg_errcode_t err; + err = extend_buffers (mctx); + if (BE (err != REG_NOERROR, 0)) + return err; + } + + if (top < next_state_log_idx) + { + memset (mctx->state_log + top + 1, '\0', + sizeof (re_dfastate_t *) * (next_state_log_idx - top)); + mctx->state_log_top = next_state_log_idx; + } + return REG_NOERROR; +} + +static reg_errcode_t +merge_state_array (dfa, dst, src, num) + re_dfa_t *dfa; + re_dfastate_t **dst; + re_dfastate_t **src; + int num; +{ + int st_idx; + reg_errcode_t err; + for (st_idx = 0; st_idx < num; ++st_idx) + { + if (dst[st_idx] == NULL) + dst[st_idx] = src[st_idx]; + else if (src[st_idx] != NULL) + { + re_node_set merged_set; + err = re_node_set_init_union (&merged_set, &dst[st_idx]->nodes, + &src[st_idx]->nodes); + if (BE (err != REG_NOERROR, 0)) + return err; + dst[st_idx] = re_acquire_state (&err, dfa, &merged_set); + re_node_set_free (&merged_set); + if (BE (err != REG_NOERROR, 0)) + return err; + } + } + return REG_NOERROR; +} + +static reg_errcode_t +update_cur_sifted_state (mctx, sctx, str_idx, dest_nodes) + re_match_context_t *mctx; + re_sift_context_t *sctx; + int str_idx; + re_node_set *dest_nodes; +{ + re_dfa_t *const dfa = mctx->dfa; + reg_errcode_t err; + const re_node_set *candidates; + candidates = ((mctx->state_log[str_idx] == NULL) ? NULL + : &mctx->state_log[str_idx]->nodes); + + if (dest_nodes->nelem == 0) + sctx->sifted_states[str_idx] = NULL; + else + { + if (candidates) + { + /* At first, add the nodes which can epsilon transit to a node in + DEST_NODE. */ + err = add_epsilon_src_nodes (dfa, dest_nodes, candidates); + if (BE (err != REG_NOERROR, 0)) + return err; + + /* Then, check the limitations in the current sift_context. */ + if (sctx->limits.nelem) + { + err = check_subexp_limits (dfa, dest_nodes, candidates, &sctx->limits, + mctx->bkref_ents, str_idx); + if (BE (err != REG_NOERROR, 0)) + return err; + } + } + + sctx->sifted_states[str_idx] = re_acquire_state (&err, dfa, dest_nodes); + if (BE (err != REG_NOERROR, 0)) + return err; + } + + if (candidates && mctx->state_log[str_idx]->has_backref) + { + err = sift_states_bkref (mctx, sctx, str_idx, candidates); + if (BE (err != REG_NOERROR, 0)) + return err; + } + return REG_NOERROR; +} + +static reg_errcode_t +add_epsilon_src_nodes (dfa, dest_nodes, candidates) + re_dfa_t *dfa; + re_node_set *dest_nodes; + const re_node_set *candidates; +{ + reg_errcode_t err = REG_NOERROR; + int i; + + re_dfastate_t *state = re_acquire_state (&err, dfa, dest_nodes); + if (BE (err != REG_NOERROR, 0)) + return err; + + if (!state->inveclosure.alloc) + { + err = re_node_set_alloc (&state->inveclosure, dest_nodes->nelem); + if (BE (err != REG_NOERROR, 0)) + return REG_ESPACE; + for (i = 0; i < dest_nodes->nelem; i++) + re_node_set_merge (&state->inveclosure, + dfa->inveclosures + dest_nodes->elems[i]); + } + return re_node_set_add_intersect (dest_nodes, candidates, + &state->inveclosure); +} + +static reg_errcode_t +sub_epsilon_src_nodes (dfa, node, dest_nodes, candidates) + re_dfa_t *dfa; + int node; + re_node_set *dest_nodes; + const re_node_set *candidates; +{ + int ecl_idx; + reg_errcode_t err; + re_node_set *inv_eclosure = dfa->inveclosures + node; + re_node_set except_nodes; + re_node_set_init_empty (&except_nodes); + for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx) + { + int cur_node = inv_eclosure->elems[ecl_idx]; + if (cur_node == node) + continue; + if (IS_EPSILON_NODE (dfa->nodes[cur_node].type)) + { + int edst1 = dfa->edests[cur_node].elems[0]; + int edst2 = ((dfa->edests[cur_node].nelem > 1) + ? dfa->edests[cur_node].elems[1] : -1); + if ((!re_node_set_contains (inv_eclosure, edst1) + && re_node_set_contains (dest_nodes, edst1)) + || (edst2 > 0 + && !re_node_set_contains (inv_eclosure, edst2) + && re_node_set_contains (dest_nodes, edst2))) + { + err = re_node_set_add_intersect (&except_nodes, candidates, + dfa->inveclosures + cur_node); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&except_nodes); + return err; + } + } + } + } + for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx) + { + int cur_node = inv_eclosure->elems[ecl_idx]; + if (!re_node_set_contains (&except_nodes, cur_node)) + { + int idx = re_node_set_contains (dest_nodes, cur_node) - 1; + re_node_set_remove_at (dest_nodes, idx); + } + } + re_node_set_free (&except_nodes); + return REG_NOERROR; +} + +static int +check_dst_limits (mctx, limits, dst_node, dst_idx, src_node, src_idx) + re_match_context_t *mctx; + re_node_set *limits; + int dst_node, dst_idx, src_node, src_idx; +{ + re_dfa_t *const dfa = mctx->dfa; + int lim_idx, src_pos, dst_pos; + + int dst_bkref_idx = search_cur_bkref_entry (mctx, dst_idx); + int src_bkref_idx = search_cur_bkref_entry (mctx, src_idx); + for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx) + { + int subexp_idx; + struct re_backref_cache_entry *ent; + ent = mctx->bkref_ents + limits->elems[lim_idx]; + subexp_idx = dfa->nodes[ent->node].opr.idx; + + dst_pos = check_dst_limits_calc_pos (mctx, limits->elems[lim_idx], + subexp_idx, dst_node, dst_idx, + dst_bkref_idx); + src_pos = check_dst_limits_calc_pos (mctx, limits->elems[lim_idx], + subexp_idx, src_node, src_idx, + src_bkref_idx); + + /* In case of: + ( ) + ( ) + ( ) */ + if (src_pos == dst_pos) + continue; /* This is unrelated limitation. */ + else + return 1; + } + return 0; +} + +static int +check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx, from_node, bkref_idx) + re_match_context_t *mctx; + int boundaries, subexp_idx, from_node, bkref_idx; +{ + re_dfa_t *const dfa = mctx->dfa; + re_node_set *eclosures = dfa->eclosures + from_node; + int node_idx; + + /* Else, we are on the boundary: examine the nodes on the epsilon + closure. */ + for (node_idx = 0; node_idx < eclosures->nelem; ++node_idx) + { + int node = eclosures->elems[node_idx]; + switch (dfa->nodes[node].type) + { + case OP_BACK_REF: + if (bkref_idx != -1) + { + struct re_backref_cache_entry *ent = mctx->bkref_ents + bkref_idx; + do + { + int dst, cpos; + + if (ent->node != node) + continue; + + if (subexp_idx <= 8 * sizeof (ent->eps_reachable_subexps_map) + && !(ent->eps_reachable_subexps_map & (1 << subexp_idx))) + continue; + + /* Recurse trying to reach the OP_OPEN_SUBEXP and + OP_CLOSE_SUBEXP cases below. But, if the + destination node is the same node as the source + node, don't recurse because it would cause an + infinite loop: a regex that exhibits this behavior + is ()\1*\1* */ + dst = dfa->edests[node].elems[0]; + if (dst == from_node) + { + if (boundaries & 1) + return -1; + else /* if (boundaries & 2) */ + return 0; + } + + cpos = + check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx, + dst, bkref_idx); + if (cpos == -1 /* && (boundaries & 1) */) + return -1; + if (cpos == 0 && (boundaries & 2)) + return 0; + + ent->eps_reachable_subexps_map &= ~(1 << subexp_idx); + } + while (ent++->more); + } + break; + + case OP_OPEN_SUBEXP: + if ((boundaries & 1) && subexp_idx == dfa->nodes[node].opr.idx) + return -1; + break; + + case OP_CLOSE_SUBEXP: + if ((boundaries & 2) && subexp_idx == dfa->nodes[node].opr.idx) + return 0; + break; + + default: + break; + } + } + + return (boundaries & 2) ? 1 : 0; +} + +static int +check_dst_limits_calc_pos (mctx, limit, subexp_idx, from_node, str_idx, bkref_idx) + re_match_context_t *mctx; + int limit, subexp_idx, from_node, str_idx, bkref_idx; +{ + struct re_backref_cache_entry *lim = mctx->bkref_ents + limit; + int boundaries; + + /* If we are outside the range of the subexpression, return -1 or 1. */ + if (str_idx < lim->subexp_from) + return -1; + + if (lim->subexp_to < str_idx) + return 1; + + /* If we are within the subexpression, return 0. */ + boundaries = (str_idx == lim->subexp_from); + boundaries |= (str_idx == lim->subexp_to) << 1; + if (boundaries == 0) + return 0; + + /* Else, examine epsilon closure. */ + return check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx, + from_node, bkref_idx); +} + +/* Check the limitations of sub expressions LIMITS, and remove the nodes + which are against limitations from DEST_NODES. */ + +static reg_errcode_t +check_subexp_limits (dfa, dest_nodes, candidates, limits, bkref_ents, str_idx) + re_dfa_t *dfa; + re_node_set *dest_nodes; + const re_node_set *candidates; + re_node_set *limits; + struct re_backref_cache_entry *bkref_ents; + int str_idx; +{ + reg_errcode_t err; + int node_idx, lim_idx; + + for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx) + { + int subexp_idx; + struct re_backref_cache_entry *ent; + ent = bkref_ents + limits->elems[lim_idx]; + + if (str_idx <= ent->subexp_from || ent->str_idx < str_idx) + continue; /* This is unrelated limitation. */ + + subexp_idx = dfa->nodes[ent->node].opr.idx; + if (ent->subexp_to == str_idx) + { + int ops_node = -1; + int cls_node = -1; + for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx) + { + int node = dest_nodes->elems[node_idx]; + re_token_type_t type = dfa->nodes[node].type; + if (type == OP_OPEN_SUBEXP + && subexp_idx == dfa->nodes[node].opr.idx) + ops_node = node; + else if (type == OP_CLOSE_SUBEXP + && subexp_idx == dfa->nodes[node].opr.idx) + cls_node = node; + } + + /* Check the limitation of the open subexpression. */ + /* Note that (ent->subexp_to = str_idx != ent->subexp_from). */ + if (ops_node >= 0) + { + err = sub_epsilon_src_nodes (dfa, ops_node, dest_nodes, + candidates); + if (BE (err != REG_NOERROR, 0)) + return err; + } + + /* Check the limitation of the close subexpression. */ + if (cls_node >= 0) + for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx) + { + int node = dest_nodes->elems[node_idx]; + if (!re_node_set_contains (dfa->inveclosures + node, + cls_node) + && !re_node_set_contains (dfa->eclosures + node, + cls_node)) + { + /* It is against this limitation. + Remove it form the current sifted state. */ + err = sub_epsilon_src_nodes (dfa, node, dest_nodes, + candidates); + if (BE (err != REG_NOERROR, 0)) + return err; + --node_idx; + } + } + } + else /* (ent->subexp_to != str_idx) */ + { + for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx) + { + int node = dest_nodes->elems[node_idx]; + re_token_type_t type = dfa->nodes[node].type; + if (type == OP_CLOSE_SUBEXP || type == OP_OPEN_SUBEXP) + { + if (subexp_idx != dfa->nodes[node].opr.idx) + continue; + /* It is against this limitation. + Remove it form the current sifted state. */ + err = sub_epsilon_src_nodes (dfa, node, dest_nodes, + candidates); + if (BE (err != REG_NOERROR, 0)) + return err; + } + } + } + } + return REG_NOERROR; +} + +static reg_errcode_t +sift_states_bkref (mctx, sctx, str_idx, candidates) + re_match_context_t *mctx; + re_sift_context_t *sctx; + int str_idx; + const re_node_set *candidates; +{ + re_dfa_t *const dfa = mctx->dfa; + reg_errcode_t err; + int node_idx, node; + re_sift_context_t local_sctx; + int first_idx = search_cur_bkref_entry (mctx, str_idx); + + if (first_idx == -1) + return REG_NOERROR; + + local_sctx.sifted_states = NULL; /* Mark that it hasn't been initialized. */ + + for (node_idx = 0; node_idx < candidates->nelem; ++node_idx) + { + int enabled_idx; + re_token_type_t type; + struct re_backref_cache_entry *entry; + node = candidates->elems[node_idx]; + type = dfa->nodes[node].type; + /* Avoid infinite loop for the REs like "()\1+". */ + if (node == sctx->last_node && str_idx == sctx->last_str_idx) + continue; + if (type != OP_BACK_REF) + continue; + + entry = mctx->bkref_ents + first_idx; + enabled_idx = first_idx; + do + { + int subexp_len, to_idx, dst_node; + re_dfastate_t *cur_state; + + if (entry->node != node) + continue; + subexp_len = entry->subexp_to - entry->subexp_from; + to_idx = str_idx + subexp_len; + dst_node = (subexp_len ? dfa->nexts[node] + : dfa->edests[node].elems[0]); + + if (to_idx > sctx->last_str_idx + || sctx->sifted_states[to_idx] == NULL + || !STATE_NODE_CONTAINS (sctx->sifted_states[to_idx], dst_node) + || check_dst_limits (mctx, &sctx->limits, node, + str_idx, dst_node, to_idx)) + continue; + + if (local_sctx.sifted_states == NULL) + { + local_sctx = *sctx; + err = re_node_set_init_copy (&local_sctx.limits, &sctx->limits); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + } + local_sctx.last_node = node; + local_sctx.last_str_idx = str_idx; + err = re_node_set_insert (&local_sctx.limits, enabled_idx); + if (BE (err < 0, 0)) + { + err = REG_ESPACE; + goto free_return; + } + cur_state = local_sctx.sifted_states[str_idx]; + err = sift_states_backward (mctx, &local_sctx); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + if (sctx->limited_states != NULL) + { + err = merge_state_array (dfa, sctx->limited_states, + local_sctx.sifted_states, + str_idx + 1); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + } + local_sctx.sifted_states[str_idx] = cur_state; + re_node_set_remove (&local_sctx.limits, enabled_idx); + + /* mctx->bkref_ents may have changed, reload the pointer. */ + entry = mctx->bkref_ents + enabled_idx; + } + while (enabled_idx++, entry++->more); + } + err = REG_NOERROR; + free_return: + if (local_sctx.sifted_states != NULL) + { + re_node_set_free (&local_sctx.limits); + } + + return err; +} + + +#ifdef RE_ENABLE_I18N +static int +sift_states_iter_mb (mctx, sctx, node_idx, str_idx, max_str_idx) + const re_match_context_t *mctx; + re_sift_context_t *sctx; + int node_idx, str_idx, max_str_idx; +{ + re_dfa_t *const dfa = mctx->dfa; + int naccepted; + /* Check the node can accept `multi byte'. */ + naccepted = check_node_accept_bytes (dfa, node_idx, &mctx->input, str_idx); + if (naccepted > 0 && str_idx + naccepted <= max_str_idx && + !STATE_NODE_CONTAINS (sctx->sifted_states[str_idx + naccepted], + dfa->nexts[node_idx])) + /* The node can't accept the `multi byte', or the + destination was already thrown away, then the node + could't accept the current input `multi byte'. */ + naccepted = 0; + /* Otherwise, it is sure that the node could accept + `naccepted' bytes input. */ + return naccepted; +} +#endif /* RE_ENABLE_I18N */ + + +/* Functions for state transition. */ + +/* Return the next state to which the current state STATE will transit by + accepting the current input byte, and update STATE_LOG if necessary. + If STATE can accept a multibyte char/collating element/back reference + update the destination of STATE_LOG. */ + +static re_dfastate_t * +transit_state (err, mctx, state) + reg_errcode_t *err; + re_match_context_t *mctx; + re_dfastate_t *state; +{ + re_dfa_t *const dfa = mctx->dfa; + re_dfastate_t **trtable; + unsigned char ch; + +#ifdef RE_ENABLE_I18N + /* If the current state can accept multibyte. */ + if (BE (state->accept_mb, 0)) + { + *err = transit_state_mb (mctx, state); + if (BE (*err != REG_NOERROR, 0)) + return NULL; + } +#endif /* RE_ENABLE_I18N */ + + /* Then decide the next state with the single byte. */ + if (1) + { + /* Use transition table */ + ch = re_string_fetch_byte (&mctx->input); + trtable = state->trtable; + if (trtable == NULL) + { + trtable = build_trtable (dfa, state); + if (trtable == NULL) + { + *err = REG_ESPACE; + return NULL; + } + } + if (BE (state->word_trtable, 0)) + { + unsigned int context; + context + = re_string_context_at (&mctx->input, + re_string_cur_idx (&mctx->input) - 1, + mctx->eflags); + if (IS_WORD_CONTEXT (context)) + return trtable[ch + SBC_MAX]; + else + return trtable[ch]; + } + else + return trtable[ch]; + } +#if 0 + else + /* don't use transition table */ + return transit_state_sb (err, mctx, state); +#endif +} + +/* Update the state_log if we need */ +re_dfastate_t * +merge_state_with_log (err, mctx, next_state) + reg_errcode_t *err; + re_match_context_t *mctx; + re_dfastate_t *next_state; +{ + re_dfa_t *const dfa = mctx->dfa; + int cur_idx = re_string_cur_idx (&mctx->input); + + if (cur_idx > mctx->state_log_top) + { + mctx->state_log[cur_idx] = next_state; + mctx->state_log_top = cur_idx; + } + else if (mctx->state_log[cur_idx] == 0) + { + mctx->state_log[cur_idx] = next_state; + } + else + { + re_dfastate_t *pstate; + unsigned int context; + re_node_set next_nodes, *log_nodes, *table_nodes = NULL; + /* If (state_log[cur_idx] != 0), it implies that cur_idx is + the destination of a multibyte char/collating element/ + back reference. Then the next state is the union set of + these destinations and the results of the transition table. */ + pstate = mctx->state_log[cur_idx]; + log_nodes = pstate->entrance_nodes; + if (next_state != NULL) + { + table_nodes = next_state->entrance_nodes; + *err = re_node_set_init_union (&next_nodes, table_nodes, + log_nodes); + if (BE (*err != REG_NOERROR, 0)) + return NULL; + } + else + next_nodes = *log_nodes; + /* Note: We already add the nodes of the initial state, + then we don't need to add them here. */ + + context = re_string_context_at (&mctx->input, + re_string_cur_idx (&mctx->input) - 1, + mctx->eflags); + next_state = mctx->state_log[cur_idx] + = re_acquire_state_context (err, dfa, &next_nodes, context); + /* We don't need to check errors here, since the return value of + this function is next_state and ERR is already set. */ + + if (table_nodes != NULL) + re_node_set_free (&next_nodes); + } + + if (BE (dfa->nbackref, 0) && next_state != NULL) + { + /* Check OP_OPEN_SUBEXP in the current state in case that we use them + later. We must check them here, since the back references in the + next state might use them. */ + *err = check_subexp_matching_top (mctx, &next_state->nodes, + cur_idx); + if (BE (*err != REG_NOERROR, 0)) + return NULL; + + /* If the next state has back references. */ + if (next_state->has_backref) + { + *err = transit_state_bkref (mctx, &next_state->nodes); + if (BE (*err != REG_NOERROR, 0)) + return NULL; + next_state = mctx->state_log[cur_idx]; + } + } + + return next_state; +} + +/* Skip bytes in the input that correspond to part of a + multi-byte match, then look in the log for a state + from which to restart matching. */ +re_dfastate_t * +find_recover_state (err, mctx) + reg_errcode_t *err; + re_match_context_t *mctx; +{ + re_dfastate_t *cur_state = NULL; + do + { + int max = mctx->state_log_top; + int cur_str_idx = re_string_cur_idx (&mctx->input); + + do + { + if (++cur_str_idx > max) + return NULL; + re_string_skip_bytes (&mctx->input, 1); + } + while (mctx->state_log[cur_str_idx] == NULL); + + cur_state = merge_state_with_log (err, mctx, NULL); + } + while (*err == REG_NOERROR && cur_state == NULL); + return cur_state; +} + +/* Helper functions for transit_state. */ + +/* From the node set CUR_NODES, pick up the nodes whose types are + OP_OPEN_SUBEXP and which have corresponding back references in the regular + expression. And register them to use them later for evaluating the + correspoding back references. */ + +static reg_errcode_t +check_subexp_matching_top (mctx, cur_nodes, str_idx) + re_match_context_t *mctx; + re_node_set *cur_nodes; + int str_idx; +{ + re_dfa_t *const dfa = mctx->dfa; + int node_idx; + reg_errcode_t err; + + /* TODO: This isn't efficient. + Because there might be more than one nodes whose types are + OP_OPEN_SUBEXP and whose index is SUBEXP_IDX, we must check all + nodes. + E.g. RE: (a){2} */ + for (node_idx = 0; node_idx < cur_nodes->nelem; ++node_idx) + { + int node = cur_nodes->elems[node_idx]; + if (dfa->nodes[node].type == OP_OPEN_SUBEXP + && dfa->nodes[node].opr.idx < (8 * sizeof (dfa->used_bkref_map)) + && dfa->used_bkref_map & (1 << dfa->nodes[node].opr.idx)) + { + err = match_ctx_add_subtop (mctx, node, str_idx); + if (BE (err != REG_NOERROR, 0)) + return err; + } + } + return REG_NOERROR; +} + +#if 0 +/* Return the next state to which the current state STATE will transit by + accepting the current input byte. */ + +static re_dfastate_t * +transit_state_sb (err, mctx, state) + reg_errcode_t *err; + re_match_context_t *mctx; + re_dfastate_t *state; +{ + re_dfa_t *const dfa = mctx->dfa; + re_node_set next_nodes; + re_dfastate_t *next_state; + int node_cnt, cur_str_idx = re_string_cur_idx (&mctx->input); + unsigned int context; + + *err = re_node_set_alloc (&next_nodes, state->nodes.nelem + 1); + if (BE (*err != REG_NOERROR, 0)) + return NULL; + for (node_cnt = 0; node_cnt < state->nodes.nelem; ++node_cnt) + { + int cur_node = state->nodes.elems[node_cnt]; + if (check_node_accept (mctx, dfa->nodes + cur_node, cur_str_idx)) + { + *err = re_node_set_merge (&next_nodes, + dfa->eclosures + dfa->nexts[cur_node]); + if (BE (*err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return NULL; + } + } + } + context = re_string_context_at (&mctx->input, cur_str_idx, mctx->eflags); + next_state = re_acquire_state_context (err, dfa, &next_nodes, context); + /* We don't need to check errors here, since the return value of + this function is next_state and ERR is already set. */ + + re_node_set_free (&next_nodes); + re_string_skip_bytes (&mctx->input, 1); + return next_state; +} +#endif + +#ifdef RE_ENABLE_I18N +static reg_errcode_t +transit_state_mb (mctx, pstate) + re_match_context_t *mctx; + re_dfastate_t *pstate; +{ + re_dfa_t *const dfa = mctx->dfa; + reg_errcode_t err; + int i; + + for (i = 0; i < pstate->nodes.nelem; ++i) + { + re_node_set dest_nodes, *new_nodes; + int cur_node_idx = pstate->nodes.elems[i]; + int naccepted = 0, dest_idx; + unsigned int context; + re_dfastate_t *dest_state; + + if (dfa->nodes[cur_node_idx].constraint) + { + context = re_string_context_at (&mctx->input, + re_string_cur_idx (&mctx->input), + mctx->eflags); + if (NOT_SATISFY_NEXT_CONSTRAINT (dfa->nodes[cur_node_idx].constraint, + context)) + continue; + } + + /* How many bytes the node can accept? */ + if (ACCEPT_MB_NODE (dfa->nodes[cur_node_idx].type)) + naccepted = check_node_accept_bytes (dfa, cur_node_idx, &mctx->input, + re_string_cur_idx (&mctx->input)); + if (naccepted == 0) + continue; + + /* The node can accepts `naccepted' bytes. */ + dest_idx = re_string_cur_idx (&mctx->input) + naccepted; + mctx->max_mb_elem_len = ((mctx->max_mb_elem_len < naccepted) ? naccepted + : mctx->max_mb_elem_len); + err = clean_state_log_if_needed (mctx, dest_idx); + if (BE (err != REG_NOERROR, 0)) + return err; +#ifdef DEBUG + assert (dfa->nexts[cur_node_idx] != -1); +#endif + /* `cur_node_idx' may point the entity of the OP_CONTEXT_NODE, + then we use pstate->nodes.elems[i] instead. */ + new_nodes = dfa->eclosures + dfa->nexts[pstate->nodes.elems[i]]; + + dest_state = mctx->state_log[dest_idx]; + if (dest_state == NULL) + dest_nodes = *new_nodes; + else + { + err = re_node_set_init_union (&dest_nodes, + dest_state->entrance_nodes, new_nodes); + if (BE (err != REG_NOERROR, 0)) + return err; + } + context = re_string_context_at (&mctx->input, dest_idx - 1, mctx->eflags); + mctx->state_log[dest_idx] + = re_acquire_state_context (&err, dfa, &dest_nodes, context); + if (dest_state != NULL) + re_node_set_free (&dest_nodes); + if (BE (mctx->state_log[dest_idx] == NULL && err != REG_NOERROR, 0)) + return err; + } + return REG_NOERROR; +} +#endif /* RE_ENABLE_I18N */ + +static reg_errcode_t +transit_state_bkref (mctx, nodes) + re_match_context_t *mctx; + const re_node_set *nodes; +{ + re_dfa_t *const dfa = mctx->dfa; + reg_errcode_t err; + int i; + int cur_str_idx = re_string_cur_idx (&mctx->input); + + for (i = 0; i < nodes->nelem; ++i) + { + int dest_str_idx, prev_nelem, bkc_idx; + int node_idx = nodes->elems[i]; + unsigned int context; + const re_token_t *node = dfa->nodes + node_idx; + re_node_set *new_dest_nodes; + + /* Check whether `node' is a backreference or not. */ + if (node->type != OP_BACK_REF) + continue; + + if (node->constraint) + { + context = re_string_context_at (&mctx->input, cur_str_idx, + mctx->eflags); + if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context)) + continue; + } + + /* `node' is a backreference. + Check the substring which the substring matched. */ + bkc_idx = mctx->nbkref_ents; + err = get_subexp (mctx, node_idx, cur_str_idx); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + + /* And add the epsilon closures (which is `new_dest_nodes') of + the backreference to appropriate state_log. */ +#ifdef DEBUG + assert (dfa->nexts[node_idx] != -1); +#endif + for (; bkc_idx < mctx->nbkref_ents; ++bkc_idx) + { + int subexp_len; + re_dfastate_t *dest_state; + struct re_backref_cache_entry *bkref_ent; + bkref_ent = mctx->bkref_ents + bkc_idx; + if (bkref_ent->node != node_idx || bkref_ent->str_idx != cur_str_idx) + continue; + subexp_len = bkref_ent->subexp_to - bkref_ent->subexp_from; + new_dest_nodes = (subexp_len == 0 + ? dfa->eclosures + dfa->edests[node_idx].elems[0] + : dfa->eclosures + dfa->nexts[node_idx]); + dest_str_idx = (cur_str_idx + bkref_ent->subexp_to + - bkref_ent->subexp_from); + context = re_string_context_at (&mctx->input, dest_str_idx - 1, + mctx->eflags); + dest_state = mctx->state_log[dest_str_idx]; + prev_nelem = ((mctx->state_log[cur_str_idx] == NULL) ? 0 + : mctx->state_log[cur_str_idx]->nodes.nelem); + /* Add `new_dest_node' to state_log. */ + if (dest_state == NULL) + { + mctx->state_log[dest_str_idx] + = re_acquire_state_context (&err, dfa, new_dest_nodes, + context); + if (BE (mctx->state_log[dest_str_idx] == NULL + && err != REG_NOERROR, 0)) + goto free_return; + } + else + { + re_node_set dest_nodes; + err = re_node_set_init_union (&dest_nodes, + dest_state->entrance_nodes, + new_dest_nodes); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&dest_nodes); + goto free_return; + } + mctx->state_log[dest_str_idx] + = re_acquire_state_context (&err, dfa, &dest_nodes, context); + re_node_set_free (&dest_nodes); + if (BE (mctx->state_log[dest_str_idx] == NULL + && err != REG_NOERROR, 0)) + goto free_return; + } + /* We need to check recursively if the backreference can epsilon + transit. */ + if (subexp_len == 0 + && mctx->state_log[cur_str_idx]->nodes.nelem > prev_nelem) + { + err = check_subexp_matching_top (mctx, new_dest_nodes, + cur_str_idx); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + err = transit_state_bkref (mctx, new_dest_nodes); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + } + } + } + err = REG_NOERROR; + free_return: + return err; +} + +/* Enumerate all the candidates which the backreference BKREF_NODE can match + at BKREF_STR_IDX, and register them by match_ctx_add_entry(). + Note that we might collect inappropriate candidates here. + However, the cost of checking them strictly here is too high, then we + delay these checking for prune_impossible_nodes(). */ + +static reg_errcode_t +get_subexp (mctx, bkref_node, bkref_str_idx) + re_match_context_t *mctx; + int bkref_node, bkref_str_idx; +{ + re_dfa_t *const dfa = mctx->dfa; + int subexp_num, sub_top_idx; + const char *buf = (const char *) re_string_get_buffer (&mctx->input); + /* Return if we have already checked BKREF_NODE at BKREF_STR_IDX. */ + int cache_idx = search_cur_bkref_entry (mctx, bkref_str_idx); + if (cache_idx != -1) + { + const struct re_backref_cache_entry *entry = mctx->bkref_ents + cache_idx; + do + if (entry->node == bkref_node) + return REG_NOERROR; /* We already checked it. */ + while (entry++->more); + } + + subexp_num = dfa->nodes[bkref_node].opr.idx; + + /* For each sub expression */ + for (sub_top_idx = 0; sub_top_idx < mctx->nsub_tops; ++sub_top_idx) + { + reg_errcode_t err; + re_sub_match_top_t *sub_top = mctx->sub_tops[sub_top_idx]; + re_sub_match_last_t *sub_last; + int sub_last_idx, sl_str, bkref_str_off; + + if (dfa->nodes[sub_top->node].opr.idx != subexp_num) + continue; /* It isn't related. */ + + sl_str = sub_top->str_idx; + bkref_str_off = bkref_str_idx; + /* At first, check the last node of sub expressions we already + evaluated. */ + for (sub_last_idx = 0; sub_last_idx < sub_top->nlasts; ++sub_last_idx) + { + int sl_str_diff; + sub_last = sub_top->lasts[sub_last_idx]; + sl_str_diff = sub_last->str_idx - sl_str; + /* The matched string by the sub expression match with the substring + at the back reference? */ + if (sl_str_diff > 0) + { + if (BE (bkref_str_off + sl_str_diff > mctx->input.valid_len, 0)) + { + /* Not enough chars for a successful match. */ + if (bkref_str_off + sl_str_diff > mctx->input.len) + break; + + err = clean_state_log_if_needed (mctx, + bkref_str_off + + sl_str_diff); + if (BE (err != REG_NOERROR, 0)) + return err; + buf = (const char *) re_string_get_buffer (&mctx->input); + } + if (memcmp (buf + bkref_str_off, buf + sl_str, sl_str_diff) != 0) + break; /* We don't need to search this sub expression any more. */ + } + bkref_str_off += sl_str_diff; + sl_str += sl_str_diff; + err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node, + bkref_str_idx); + + /* Reload buf, since the preceding call might have reallocated + the buffer. */ + buf = (const char *) re_string_get_buffer (&mctx->input); + + if (err == REG_NOMATCH) + continue; + if (BE (err != REG_NOERROR, 0)) + return err; + } + + if (sub_last_idx < sub_top->nlasts) + continue; + if (sub_last_idx > 0) + ++sl_str; + /* Then, search for the other last nodes of the sub expression. */ + for (; sl_str <= bkref_str_idx; ++sl_str) + { + int cls_node, sl_str_off; + const re_node_set *nodes; + sl_str_off = sl_str - sub_top->str_idx; + /* The matched string by the sub expression match with the substring + at the back reference? */ + if (sl_str_off > 0) + { + if (BE (bkref_str_off >= mctx->input.valid_len, 0)) + { + /* If we are at the end of the input, we cannot match. */ + if (bkref_str_off >= mctx->input.len) + break; + + err = extend_buffers (mctx); + if (BE (err != REG_NOERROR, 0)) + return err; + + buf = (const char *) re_string_get_buffer (&mctx->input); + } + if (buf [bkref_str_off++] != buf[sl_str - 1]) + break; /* We don't need to search this sub expression + any more. */ + } + if (mctx->state_log[sl_str] == NULL) + continue; + /* Does this state have a ')' of the sub expression? */ + nodes = &mctx->state_log[sl_str]->nodes; + cls_node = find_subexp_node (dfa, nodes, subexp_num, OP_CLOSE_SUBEXP); + if (cls_node == -1) + continue; /* No. */ + if (sub_top->path == NULL) + { + sub_top->path = calloc (sizeof (state_array_t), + sl_str - sub_top->str_idx + 1); + if (sub_top->path == NULL) + return REG_ESPACE; + } + /* Can the OP_OPEN_SUBEXP node arrive the OP_CLOSE_SUBEXP node + in the current context? */ + err = check_arrival (mctx, sub_top->path, sub_top->node, + sub_top->str_idx, cls_node, sl_str, OP_CLOSE_SUBEXP); + if (err == REG_NOMATCH) + continue; + if (BE (err != REG_NOERROR, 0)) + return err; + sub_last = match_ctx_add_sublast (sub_top, cls_node, sl_str); + if (BE (sub_last == NULL, 0)) + return REG_ESPACE; + err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node, + bkref_str_idx); + if (err == REG_NOMATCH) + continue; + } + } + return REG_NOERROR; +} + +/* Helper functions for get_subexp(). */ + +/* Check SUB_LAST can arrive to the back reference BKREF_NODE at BKREF_STR. + If it can arrive, register the sub expression expressed with SUB_TOP + and SUB_LAST. */ + +static reg_errcode_t +get_subexp_sub (mctx, sub_top, sub_last, bkref_node, bkref_str) + re_match_context_t *mctx; + const re_sub_match_top_t *sub_top; + re_sub_match_last_t *sub_last; + int bkref_node, bkref_str; +{ + reg_errcode_t err; + int to_idx; + /* Can the subexpression arrive the back reference? */ + err = check_arrival (mctx, &sub_last->path, sub_last->node, + sub_last->str_idx, bkref_node, bkref_str, OP_OPEN_SUBEXP); + if (err != REG_NOERROR) + return err; + err = match_ctx_add_entry (mctx, bkref_node, bkref_str, sub_top->str_idx, + sub_last->str_idx); + if (BE (err != REG_NOERROR, 0)) + return err; + to_idx = bkref_str + sub_last->str_idx - sub_top->str_idx; + return clean_state_log_if_needed (mctx, to_idx); +} + +/* Find the first node which is '(' or ')' and whose index is SUBEXP_IDX. + Search '(' if FL_OPEN, or search ')' otherwise. + TODO: This function isn't efficient... + Because there might be more than one nodes whose types are + OP_OPEN_SUBEXP and whose index is SUBEXP_IDX, we must check all + nodes. + E.g. RE: (a){2} */ + +static int +find_subexp_node (dfa, nodes, subexp_idx, type) + const re_dfa_t *dfa; + const re_node_set *nodes; + int subexp_idx, type; +{ + int cls_idx; + for (cls_idx = 0; cls_idx < nodes->nelem; ++cls_idx) + { + int cls_node = nodes->elems[cls_idx]; + const re_token_t *node = dfa->nodes + cls_node; + if (node->type == type + && node->opr.idx == subexp_idx) + return cls_node; + } + return -1; +} + +/* Check whether the node TOP_NODE at TOP_STR can arrive to the node + LAST_NODE at LAST_STR. We record the path onto PATH since it will be + heavily reused. + Return REG_NOERROR if it can arrive, or REG_NOMATCH otherwise. */ + +static reg_errcode_t +check_arrival (mctx, path, top_node, top_str, last_node, last_str, + type) + re_match_context_t *mctx; + state_array_t *path; + int top_node, top_str, last_node, last_str, type; +{ + re_dfa_t *const dfa = mctx->dfa; + reg_errcode_t err; + int subexp_num, backup_cur_idx, str_idx, null_cnt; + re_dfastate_t *cur_state = NULL; + re_node_set *cur_nodes, next_nodes; + re_dfastate_t **backup_state_log; + unsigned int context; + + subexp_num = dfa->nodes[top_node].opr.idx; + /* Extend the buffer if we need. */ + if (BE (path->alloc < last_str + mctx->max_mb_elem_len + 1, 0)) + { + re_dfastate_t **new_array; + int old_alloc = path->alloc; + path->alloc += last_str + mctx->max_mb_elem_len + 1; + new_array = re_realloc (path->array, re_dfastate_t *, path->alloc); + if (new_array == NULL) + { + path->alloc = old_alloc; + return REG_ESPACE; + } + path->array = new_array; + memset (new_array + old_alloc, '\0', + sizeof (re_dfastate_t *) * (path->alloc - old_alloc)); + } + + str_idx = path->next_idx == 0 ? top_str : path->next_idx; + + /* Temporary modify MCTX. */ + backup_state_log = mctx->state_log; + backup_cur_idx = mctx->input.cur_idx; + mctx->state_log = path->array; + mctx->input.cur_idx = str_idx; + + /* Setup initial node set. */ + context = re_string_context_at (&mctx->input, str_idx - 1, mctx->eflags); + if (str_idx == top_str) + { + err = re_node_set_init_1 (&next_nodes, top_node); + if (BE (err != REG_NOERROR, 0)) + return err; + err = check_arrival_expand_ecl (dfa, &next_nodes, subexp_num, type); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + } + else + { + cur_state = mctx->state_log[str_idx]; + if (cur_state && cur_state->has_backref) + { + err = re_node_set_init_copy (&next_nodes, &cur_state->nodes); + if (BE ( err != REG_NOERROR, 0)) + return err; + } + else + re_node_set_init_empty (&next_nodes); + } + if (str_idx == top_str || (cur_state && cur_state->has_backref)) + { + if (next_nodes.nelem) + { + err = expand_bkref_cache (mctx, &next_nodes, str_idx, + subexp_num, type); + if (BE ( err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + } + cur_state = re_acquire_state_context (&err, dfa, &next_nodes, context); + if (BE (cur_state == NULL && err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + mctx->state_log[str_idx] = cur_state; + } + + for (null_cnt = 0; str_idx < last_str && null_cnt <= mctx->max_mb_elem_len;) + { + re_node_set_empty (&next_nodes); + if (mctx->state_log[str_idx + 1]) + { + err = re_node_set_merge (&next_nodes, + &mctx->state_log[str_idx + 1]->nodes); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + } + if (cur_state) + { + err = check_arrival_add_next_nodes (mctx, str_idx, + &cur_state->non_eps_nodes, &next_nodes); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + } + ++str_idx; + if (next_nodes.nelem) + { + err = check_arrival_expand_ecl (dfa, &next_nodes, subexp_num, type); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + err = expand_bkref_cache (mctx, &next_nodes, str_idx, + subexp_num, type); + if (BE ( err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + } + context = re_string_context_at (&mctx->input, str_idx - 1, mctx->eflags); + cur_state = re_acquire_state_context (&err, dfa, &next_nodes, context); + if (BE (cur_state == NULL && err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + mctx->state_log[str_idx] = cur_state; + null_cnt = cur_state == NULL ? null_cnt + 1 : 0; + } + re_node_set_free (&next_nodes); + cur_nodes = (mctx->state_log[last_str] == NULL ? NULL + : &mctx->state_log[last_str]->nodes); + path->next_idx = str_idx; + + /* Fix MCTX. */ + mctx->state_log = backup_state_log; + mctx->input.cur_idx = backup_cur_idx; + + /* Then check the current node set has the node LAST_NODE. */ + if (cur_nodes != NULL && re_node_set_contains (cur_nodes, last_node)) + return REG_NOERROR; + + return REG_NOMATCH; +} + +/* Helper functions for check_arrival. */ + +/* Calculate the destination nodes of CUR_NODES at STR_IDX, and append them + to NEXT_NODES. + TODO: This function is similar to the functions transit_state*(), + however this function has many additional works. + Can't we unify them? */ + +static reg_errcode_t +check_arrival_add_next_nodes (mctx, str_idx, cur_nodes, next_nodes) + re_match_context_t *mctx; + int str_idx; + re_node_set *cur_nodes, *next_nodes; +{ + re_dfa_t *const dfa = mctx->dfa; + int result; + int cur_idx; + reg_errcode_t err; + re_node_set union_set; + re_node_set_init_empty (&union_set); + for (cur_idx = 0; cur_idx < cur_nodes->nelem; ++cur_idx) + { + int naccepted = 0; + int cur_node = cur_nodes->elems[cur_idx]; +#if defined DEBUG || defined RE_ENABLE_I18N + re_token_type_t type = dfa->nodes[cur_node].type; +#endif +#ifdef DEBUG + assert (!IS_EPSILON_NODE (type)); +#endif +#ifdef RE_ENABLE_I18N + /* If the node may accept `multi byte'. */ + if (ACCEPT_MB_NODE (type)) + { + naccepted = check_node_accept_bytes (dfa, cur_node, &mctx->input, + str_idx); + if (naccepted > 1) + { + re_dfastate_t *dest_state; + int next_node = dfa->nexts[cur_node]; + int next_idx = str_idx + naccepted; + dest_state = mctx->state_log[next_idx]; + re_node_set_empty (&union_set); + if (dest_state) + { + err = re_node_set_merge (&union_set, &dest_state->nodes); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&union_set); + return err; + } + } + result = re_node_set_insert (&union_set, next_node); + if (BE (result < 0, 0)) + { + re_node_set_free (&union_set); + return REG_ESPACE; + } + mctx->state_log[next_idx] = re_acquire_state (&err, dfa, + &union_set); + if (BE (mctx->state_log[next_idx] == NULL + && err != REG_NOERROR, 0)) + { + re_node_set_free (&union_set); + return err; + } + } + } +#endif /* RE_ENABLE_I18N */ + if (naccepted + || check_node_accept (mctx, dfa->nodes + cur_node, str_idx)) + { + result = re_node_set_insert (next_nodes, dfa->nexts[cur_node]); + if (BE (result < 0, 0)) + { + re_node_set_free (&union_set); + return REG_ESPACE; + } + } + } + re_node_set_free (&union_set); + return REG_NOERROR; +} + +/* For all the nodes in CUR_NODES, add the epsilon closures of them to + CUR_NODES, however exclude the nodes which are: + - inside the sub expression whose number is EX_SUBEXP, if FL_OPEN. + - out of the sub expression whose number is EX_SUBEXP, if !FL_OPEN. +*/ + +static reg_errcode_t +check_arrival_expand_ecl (dfa, cur_nodes, ex_subexp, type) + re_dfa_t *dfa; + re_node_set *cur_nodes; + int ex_subexp, type; +{ + reg_errcode_t err; + int idx, outside_node; + re_node_set new_nodes; +#ifdef DEBUG + assert (cur_nodes->nelem); +#endif + err = re_node_set_alloc (&new_nodes, cur_nodes->nelem); + if (BE (err != REG_NOERROR, 0)) + return err; + /* Create a new node set NEW_NODES with the nodes which are epsilon + closures of the node in CUR_NODES. */ + + for (idx = 0; idx < cur_nodes->nelem; ++idx) + { + int cur_node = cur_nodes->elems[idx]; + re_node_set *eclosure = dfa->eclosures + cur_node; + outside_node = find_subexp_node (dfa, eclosure, ex_subexp, type); + if (outside_node == -1) + { + /* There are no problematic nodes, just merge them. */ + err = re_node_set_merge (&new_nodes, eclosure); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&new_nodes); + return err; + } + } + else + { + /* There are problematic nodes, re-calculate incrementally. */ + err = check_arrival_expand_ecl_sub (dfa, &new_nodes, cur_node, + ex_subexp, type); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&new_nodes); + return err; + } + } + } + re_node_set_free (cur_nodes); + *cur_nodes = new_nodes; + return REG_NOERROR; +} + +/* Helper function for check_arrival_expand_ecl. + Check incrementally the epsilon closure of TARGET, and if it isn't + problematic append it to DST_NODES. */ + +static reg_errcode_t +check_arrival_expand_ecl_sub (dfa, dst_nodes, target, ex_subexp, type) + re_dfa_t *dfa; + int target, ex_subexp, type; + re_node_set *dst_nodes; +{ + int cur_node; + for (cur_node = target; !re_node_set_contains (dst_nodes, cur_node);) + { + int err; + + if (dfa->nodes[cur_node].type == type + && dfa->nodes[cur_node].opr.idx == ex_subexp) + { + if (type == OP_CLOSE_SUBEXP) + { + err = re_node_set_insert (dst_nodes, cur_node); + if (BE (err == -1, 0)) + return REG_ESPACE; + } + break; + } + err = re_node_set_insert (dst_nodes, cur_node); + if (BE (err == -1, 0)) + return REG_ESPACE; + if (dfa->edests[cur_node].nelem == 0) + break; + if (dfa->edests[cur_node].nelem == 2) + { + err = check_arrival_expand_ecl_sub (dfa, dst_nodes, + dfa->edests[cur_node].elems[1], + ex_subexp, type); + if (BE (err != REG_NOERROR, 0)) + return err; + } + cur_node = dfa->edests[cur_node].elems[0]; + } + return REG_NOERROR; +} + + +/* For all the back references in the current state, calculate the + destination of the back references by the appropriate entry + in MCTX->BKREF_ENTS. */ + +static reg_errcode_t +expand_bkref_cache (mctx, cur_nodes, cur_str, subexp_num, + type) + re_match_context_t *mctx; + int cur_str, subexp_num, type; + re_node_set *cur_nodes; +{ + re_dfa_t *const dfa = mctx->dfa; + reg_errcode_t err; + int cache_idx_start = search_cur_bkref_entry (mctx, cur_str); + struct re_backref_cache_entry *ent; + + if (cache_idx_start == -1) + return REG_NOERROR; + + restart: + ent = mctx->bkref_ents + cache_idx_start; + do + { + int to_idx, next_node; + + /* Is this entry ENT is appropriate? */ + if (!re_node_set_contains (cur_nodes, ent->node)) + continue; /* No. */ + + to_idx = cur_str + ent->subexp_to - ent->subexp_from; + /* Calculate the destination of the back reference, and append it + to MCTX->STATE_LOG. */ + if (to_idx == cur_str) + { + /* The backreference did epsilon transit, we must re-check all the + node in the current state. */ + re_node_set new_dests; + reg_errcode_t err2, err3; + next_node = dfa->edests[ent->node].elems[0]; + if (re_node_set_contains (cur_nodes, next_node)) + continue; + err = re_node_set_init_1 (&new_dests, next_node); + err2 = check_arrival_expand_ecl (dfa, &new_dests, subexp_num, type); + err3 = re_node_set_merge (cur_nodes, &new_dests); + re_node_set_free (&new_dests); + if (BE (err != REG_NOERROR || err2 != REG_NOERROR + || err3 != REG_NOERROR, 0)) + { + err = (err != REG_NOERROR ? err + : (err2 != REG_NOERROR ? err2 : err3)); + return err; + } + /* TODO: It is still inefficient... */ + goto restart; + } + else + { + re_node_set union_set; + next_node = dfa->nexts[ent->node]; + if (mctx->state_log[to_idx]) + { + int ret; + if (re_node_set_contains (&mctx->state_log[to_idx]->nodes, + next_node)) + continue; + err = re_node_set_init_copy (&union_set, + &mctx->state_log[to_idx]->nodes); + ret = re_node_set_insert (&union_set, next_node); + if (BE (err != REG_NOERROR || ret < 0, 0)) + { + re_node_set_free (&union_set); + err = err != REG_NOERROR ? err : REG_ESPACE; + return err; + } + } + else + { + err = re_node_set_init_1 (&union_set, next_node); + if (BE (err != REG_NOERROR, 0)) + return err; + } + mctx->state_log[to_idx] = re_acquire_state (&err, dfa, &union_set); + re_node_set_free (&union_set); + if (BE (mctx->state_log[to_idx] == NULL + && err != REG_NOERROR, 0)) + return err; + } + } + while (ent++->more); + return REG_NOERROR; +} + +/* Build transition table for the state. + Return the new table if succeeded, otherwise return NULL. */ + +static re_dfastate_t ** +build_trtable (dfa, state) + re_dfa_t *dfa; + re_dfastate_t *state; +{ + reg_errcode_t err; + int i, j, ch; + unsigned int elem, mask; + int dests_node_malloced = 0, dest_states_malloced = 0; + int ndests; /* Number of the destination states from `state'. */ + re_dfastate_t **trtable; + re_dfastate_t **dest_states = NULL, **dest_states_word, **dest_states_nl; + re_node_set follows, *dests_node; + bitset *dests_ch; + bitset acceptable; + + /* We build DFA states which corresponds to the destination nodes + from `state'. `dests_node[i]' represents the nodes which i-th + destination state contains, and `dests_ch[i]' represents the + characters which i-th destination state accepts. */ +#ifdef _LIBC + if (__libc_use_alloca ((sizeof (re_node_set) + sizeof (bitset)) * SBC_MAX)) + dests_node = (re_node_set *) + alloca ((sizeof (re_node_set) + sizeof (bitset)) * SBC_MAX); + else +#endif + { + dests_node = (re_node_set *) + malloc ((sizeof (re_node_set) + sizeof (bitset)) * SBC_MAX); + if (BE (dests_node == NULL, 0)) + return NULL; + dests_node_malloced = 1; + } + dests_ch = (bitset *) (dests_node + SBC_MAX); + + /* Initialize transiton table. */ + state->word_trtable = 0; + + /* At first, group all nodes belonging to `state' into several + destinations. */ + ndests = group_nodes_into_DFAstates (dfa, state, dests_node, dests_ch); + if (BE (ndests <= 0, 0)) + { + if (dests_node_malloced) + free (dests_node); + /* Return NULL in case of an error, trtable otherwise. */ + if (ndests == 0) + { + state->trtable = (re_dfastate_t **) + calloc (sizeof (re_dfastate_t *), SBC_MAX);; + return state->trtable; + } + return NULL; + } + + err = re_node_set_alloc (&follows, ndests + 1); + if (BE (err != REG_NOERROR, 0)) + goto out_free; + +#ifdef _LIBC + if (__libc_use_alloca ((sizeof (re_node_set) + sizeof (bitset)) * SBC_MAX + + ndests * 3 * sizeof (re_dfastate_t *))) + dest_states = (re_dfastate_t **) + alloca (ndests * 3 * sizeof (re_dfastate_t *)); + else +#endif + { + dest_states = (re_dfastate_t **) + malloc (ndests * 3 * sizeof (re_dfastate_t *)); + if (BE (dest_states == NULL, 0)) + { +out_free: + if (dest_states_malloced) + free (dest_states); + re_node_set_free (&follows); + for (i = 0; i < ndests; ++i) + re_node_set_free (dests_node + i); + if (dests_node_malloced) + free (dests_node); + return NULL; + } + dest_states_malloced = 1; + } + dest_states_word = dest_states + ndests; + dest_states_nl = dest_states_word + ndests; + bitset_empty (acceptable); + + /* Then build the states for all destinations. */ + for (i = 0; i < ndests; ++i) + { + int next_node; + re_node_set_empty (&follows); + /* Merge the follows of this destination states. */ + for (j = 0; j < dests_node[i].nelem; ++j) + { + next_node = dfa->nexts[dests_node[i].elems[j]]; + if (next_node != -1) + { + err = re_node_set_merge (&follows, dfa->eclosures + next_node); + if (BE (err != REG_NOERROR, 0)) + goto out_free; + } + } + dest_states[i] = re_acquire_state_context (&err, dfa, &follows, 0); + if (BE (dest_states[i] == NULL && err != REG_NOERROR, 0)) + goto out_free; + /* If the new state has context constraint, + build appropriate states for these contexts. */ + if (dest_states[i]->has_constraint) + { + dest_states_word[i] = re_acquire_state_context (&err, dfa, &follows, + CONTEXT_WORD); + if (BE (dest_states_word[i] == NULL && err != REG_NOERROR, 0)) + goto out_free; + + if (dest_states[i] != dest_states_word[i] + && dfa->mb_cur_max > 1) + state->word_trtable = 1; + + dest_states_nl[i] = re_acquire_state_context (&err, dfa, &follows, + CONTEXT_NEWLINE); + if (BE (dest_states_nl[i] == NULL && err != REG_NOERROR, 0)) + goto out_free; + } + else + { + dest_states_word[i] = dest_states[i]; + dest_states_nl[i] = dest_states[i]; + } + bitset_merge (acceptable, dests_ch[i]); + } + + if (!BE (state->word_trtable, 0)) + { + /* We don't care about whether the following character is a word + character, or we are in a single-byte character set so we can + discern by looking at the character code: allocate a + 256-entry transition table. */ + trtable = (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), SBC_MAX); + if (BE (trtable == NULL, 0)) + goto out_free; + + /* For all characters ch...: */ + for (i = 0; i < BITSET_UINTS; ++i) + for (ch = i * UINT_BITS, elem = acceptable[i], mask = 1; + elem; + mask <<= 1, elem >>= 1, ++ch) + if (BE (elem & 1, 0)) + { + /* There must be exactly one destination which accepts + character ch. See group_nodes_into_DFAstates. */ + for (j = 0; (dests_ch[j][i] & mask) == 0; ++j) + ; + + /* j-th destination accepts the word character ch. */ + if (dfa->word_char[i] & mask) + trtable[ch] = dest_states_word[j]; + else + trtable[ch] = dest_states[j]; + } + } + else + { + /* We care about whether the following character is a word + character, and we are in a multi-byte character set: discern + by looking at the character code: build two 256-entry + transition tables, one starting at trtable[0] and one + starting at trtable[SBC_MAX]. */ + trtable = (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), + 2 * SBC_MAX); + if (BE (trtable == NULL, 0)) + goto out_free; + + /* For all characters ch...: */ + for (i = 0; i < BITSET_UINTS; ++i) + for (ch = i * UINT_BITS, elem = acceptable[i], mask = 1; + elem; + mask <<= 1, elem >>= 1, ++ch) + if (BE (elem & 1, 0)) + { + /* There must be exactly one destination which accepts + character ch. See group_nodes_into_DFAstates. */ + for (j = 0; (dests_ch[j][i] & mask) == 0; ++j) + ; + + /* j-th destination accepts the word character ch. */ + trtable[ch] = dest_states[j]; + trtable[ch + SBC_MAX] = dest_states_word[j]; + } + } + + /* new line */ + if (bitset_contain (acceptable, NEWLINE_CHAR)) + { + /* The current state accepts newline character. */ + for (j = 0; j < ndests; ++j) + if (bitset_contain (dests_ch[j], NEWLINE_CHAR)) + { + /* k-th destination accepts newline character. */ + trtable[NEWLINE_CHAR] = dest_states_nl[j]; + if (state->word_trtable) + trtable[NEWLINE_CHAR + SBC_MAX] = dest_states_nl[j]; + /* There must be only one destination which accepts + newline. See group_nodes_into_DFAstates. */ + break; + } + } + + if (dest_states_malloced) + free (dest_states); + + re_node_set_free (&follows); + for (i = 0; i < ndests; ++i) + re_node_set_free (dests_node + i); + + if (dests_node_malloced) + free (dests_node); + + state->trtable = trtable; + return trtable; +} + +/* Group all nodes belonging to STATE into several destinations. + Then for all destinations, set the nodes belonging to the destination + to DESTS_NODE[i] and set the characters accepted by the destination + to DEST_CH[i]. This function return the number of destinations. */ + +static int +group_nodes_into_DFAstates (dfa, state, dests_node, dests_ch) + re_dfa_t *dfa; + const re_dfastate_t *state; + re_node_set *dests_node; + bitset *dests_ch; +{ + reg_errcode_t err; + int result; + int i, j, k; + int ndests; /* Number of the destinations from `state'. */ + bitset accepts; /* Characters a node can accept. */ + const re_node_set *cur_nodes = &state->nodes; + bitset_empty (accepts); + ndests = 0; + + /* For all the nodes belonging to `state', */ + for (i = 0; i < cur_nodes->nelem; ++i) + { + re_token_t *node = &dfa->nodes[cur_nodes->elems[i]]; + re_token_type_t type = node->type; + unsigned int constraint = node->constraint; + + /* Enumerate all single byte character this node can accept. */ + if (type == CHARACTER) + bitset_set (accepts, node->opr.c); + else if (type == SIMPLE_BRACKET) + { + bitset_merge (accepts, node->opr.sbcset); + } + else if (type == OP_PERIOD) + { +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + bitset_merge (accepts, dfa->sb_char); + else +#endif + bitset_set_all (accepts); + if (!(dfa->syntax & RE_DOT_NEWLINE)) + bitset_clear (accepts, '\n'); + if (dfa->syntax & RE_DOT_NOT_NULL) + bitset_clear (accepts, '\0'); + } +#ifdef RE_ENABLE_I18N + else if (type == OP_UTF8_PERIOD) + { + memset (accepts, 255, sizeof (unsigned int) * BITSET_UINTS / 2); + if (!(dfa->syntax & RE_DOT_NEWLINE)) + bitset_clear (accepts, '\n'); + if (dfa->syntax & RE_DOT_NOT_NULL) + bitset_clear (accepts, '\0'); + } +#endif + else + continue; + + /* Check the `accepts' and sift the characters which are not + match it the context. */ + if (constraint) + { + if (constraint & NEXT_NEWLINE_CONSTRAINT) + { + int accepts_newline = bitset_contain (accepts, NEWLINE_CHAR); + bitset_empty (accepts); + if (accepts_newline) + bitset_set (accepts, NEWLINE_CHAR); + else + continue; + } + if (constraint & NEXT_ENDBUF_CONSTRAINT) + { + bitset_empty (accepts); + continue; + } + + if (constraint & NEXT_WORD_CONSTRAINT) + { + unsigned int any_set = 0; + if (type == CHARACTER && !node->word_char) + { + bitset_empty (accepts); + continue; + } +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + for (j = 0; j < BITSET_UINTS; ++j) + any_set |= (accepts[j] &= (dfa->word_char[j] | ~dfa->sb_char[j])); + else +#endif + for (j = 0; j < BITSET_UINTS; ++j) + any_set |= (accepts[j] &= dfa->word_char[j]); + if (!any_set) + continue; + } + if (constraint & NEXT_NOTWORD_CONSTRAINT) + { + unsigned int any_set = 0; + if (type == CHARACTER && node->word_char) + { + bitset_empty (accepts); + continue; + } +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + for (j = 0; j < BITSET_UINTS; ++j) + any_set |= (accepts[j] &= ~(dfa->word_char[j] & dfa->sb_char[j])); + else +#endif + for (j = 0; j < BITSET_UINTS; ++j) + any_set |= (accepts[j] &= ~dfa->word_char[j]); + if (!any_set) + continue; + } + } + + /* Then divide `accepts' into DFA states, or create a new + state. Above, we make sure that accepts is not empty. */ + for (j = 0; j < ndests; ++j) + { + bitset intersec; /* Intersection sets, see below. */ + bitset remains; + /* Flags, see below. */ + int has_intersec, not_subset, not_consumed; + + /* Optimization, skip if this state doesn't accept the character. */ + if (type == CHARACTER && !bitset_contain (dests_ch[j], node->opr.c)) + continue; + + /* Enumerate the intersection set of this state and `accepts'. */ + has_intersec = 0; + for (k = 0; k < BITSET_UINTS; ++k) + has_intersec |= intersec[k] = accepts[k] & dests_ch[j][k]; + /* And skip if the intersection set is empty. */ + if (!has_intersec) + continue; + + /* Then check if this state is a subset of `accepts'. */ + not_subset = not_consumed = 0; + for (k = 0; k < BITSET_UINTS; ++k) + { + not_subset |= remains[k] = ~accepts[k] & dests_ch[j][k]; + not_consumed |= accepts[k] = accepts[k] & ~dests_ch[j][k]; + } + + /* If this state isn't a subset of `accepts', create a + new group state, which has the `remains'. */ + if (not_subset) + { + bitset_copy (dests_ch[ndests], remains); + bitset_copy (dests_ch[j], intersec); + err = re_node_set_init_copy (dests_node + ndests, &dests_node[j]); + if (BE (err != REG_NOERROR, 0)) + goto error_return; + ++ndests; + } + + /* Put the position in the current group. */ + result = re_node_set_insert (&dests_node[j], cur_nodes->elems[i]); + if (BE (result < 0, 0)) + goto error_return; + + /* If all characters are consumed, go to next node. */ + if (!not_consumed) + break; + } + /* Some characters remain, create a new group. */ + if (j == ndests) + { + bitset_copy (dests_ch[ndests], accepts); + err = re_node_set_init_1 (dests_node + ndests, cur_nodes->elems[i]); + if (BE (err != REG_NOERROR, 0)) + goto error_return; + ++ndests; + bitset_empty (accepts); + } + } + return ndests; + error_return: + for (j = 0; j < ndests; ++j) + re_node_set_free (dests_node + j); + return -1; +} + +#ifdef RE_ENABLE_I18N +/* Check how many bytes the node `dfa->nodes[node_idx]' accepts. + Return the number of the bytes the node accepts. + STR_IDX is the current index of the input string. + + This function handles the nodes which can accept one character, or + one collating element like '.', '[a-z]', opposite to the other nodes + can only accept one byte. */ + +static int +check_node_accept_bytes (dfa, node_idx, input, str_idx) + re_dfa_t *dfa; + int node_idx, str_idx; + const re_string_t *input; +{ + const re_token_t *node = dfa->nodes + node_idx; + int char_len, elem_len; + int i; + + if (BE (node->type == OP_UTF8_PERIOD, 0)) + { + unsigned char c = re_string_byte_at (input, str_idx), d; + if (BE (c < 0xc2, 1)) + return 0; + + if (str_idx + 2 > input->len) + return 0; + + d = re_string_byte_at (input, str_idx + 1); + if (c < 0xe0) + return (d < 0x80 || d > 0xbf) ? 0 : 2; + else if (c < 0xf0) + { + char_len = 3; + if (c == 0xe0 && d < 0xa0) + return 0; + } + else if (c < 0xf8) + { + char_len = 4; + if (c == 0xf0 && d < 0x90) + return 0; + } + else if (c < 0xfc) + { + char_len = 5; + if (c == 0xf8 && d < 0x88) + return 0; + } + else if (c < 0xfe) + { + char_len = 6; + if (c == 0xfc && d < 0x84) + return 0; + } + else + return 0; + + if (str_idx + char_len > input->len) + return 0; + + for (i = 1; i < char_len; ++i) + { + d = re_string_byte_at (input, str_idx + i); + if (d < 0x80 || d > 0xbf) + return 0; + } + return char_len; + } + + char_len = re_string_char_size_at (input, str_idx); + if (node->type == OP_PERIOD) + { + if (char_len <= 1) + return 0; + /* FIXME: I don't think this if is needed, as both '\n' + and '\0' are char_len == 1. */ + /* '.' accepts any one character except the following two cases. */ + if ((!(dfa->syntax & RE_DOT_NEWLINE) && + re_string_byte_at (input, str_idx) == '\n') || + ((dfa->syntax & RE_DOT_NOT_NULL) && + re_string_byte_at (input, str_idx) == '\0')) + return 0; + return char_len; + } + + elem_len = re_string_elem_size_at (input, str_idx); + if ((elem_len <= 1 && char_len <= 1) || char_len == 0) + return 0; + + if (node->type == COMPLEX_BRACKET) + { + const re_charset_t *cset = node->opr.mbcset; +# ifdef _LIBC + const unsigned char *pin = ((char *) re_string_get_buffer (input) + + str_idx); + int j; + uint32_t nrules; +# endif /* _LIBC */ + int match_len = 0; + wchar_t wc = ((cset->nranges || cset->nchar_classes || cset->nmbchars) + ? re_string_wchar_at (input, str_idx) : 0); + + /* match with multibyte character? */ + for (i = 0; i < cset->nmbchars; ++i) + if (wc == cset->mbchars[i]) + { + match_len = char_len; + goto check_node_accept_bytes_match; + } + /* match with character_class? */ + for (i = 0; i < cset->nchar_classes; ++i) + { + wctype_t wt = cset->char_classes[i]; + if (__iswctype (wc, wt)) + { + match_len = char_len; + goto check_node_accept_bytes_match; + } + } + +# ifdef _LIBC + nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); + if (nrules != 0) + { + unsigned int in_collseq = 0; + const int32_t *table, *indirect; + const unsigned char *weights, *extra; + const char *collseqwc; + int32_t idx; + /* This #include defines a local function! */ +# include + + /* match with collating_symbol? */ + if (cset->ncoll_syms) + extra = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB); + for (i = 0; i < cset->ncoll_syms; ++i) + { + const unsigned char *coll_sym = extra + cset->coll_syms[i]; + /* Compare the length of input collating element and + the length of current collating element. */ + if (*coll_sym != elem_len) + continue; + /* Compare each bytes. */ + for (j = 0; j < *coll_sym; j++) + if (pin[j] != coll_sym[1 + j]) + break; + if (j == *coll_sym) + { + /* Match if every bytes is equal. */ + match_len = j; + goto check_node_accept_bytes_match; + } + } + + if (cset->nranges) + { + if (elem_len <= char_len) + { + collseqwc = _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQWC); + in_collseq = __collseq_table_lookup (collseqwc, wc); + } + else + in_collseq = find_collation_sequence_value (pin, elem_len); + } + /* match with range expression? */ + for (i = 0; i < cset->nranges; ++i) + if (cset->range_starts[i] <= in_collseq + && in_collseq <= cset->range_ends[i]) + { + match_len = elem_len; + goto check_node_accept_bytes_match; + } + + /* match with equivalence_class? */ + if (cset->nequiv_classes) + { + const unsigned char *cp = pin; + table = (const int32_t *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); + weights = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_WEIGHTMB); + extra = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB); + indirect = (const int32_t *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_INDIRECTMB); + idx = findidx (&cp); + if (idx > 0) + for (i = 0; i < cset->nequiv_classes; ++i) + { + int32_t equiv_class_idx = cset->equiv_classes[i]; + size_t weight_len = weights[idx]; + if (weight_len == weights[equiv_class_idx]) + { + int cnt = 0; + while (cnt <= weight_len + && (weights[equiv_class_idx + 1 + cnt] + == weights[idx + 1 + cnt])) + ++cnt; + if (cnt > weight_len) + { + match_len = elem_len; + goto check_node_accept_bytes_match; + } + } + } + } + } + else +# endif /* _LIBC */ + { + /* match with range expression? */ +#if __GNUC__ >= 2 + wchar_t cmp_buf[] = {L'\0', L'\0', wc, L'\0', L'\0', L'\0'}; +#else + wchar_t cmp_buf[] = {L'\0', L'\0', L'\0', L'\0', L'\0', L'\0'}; + cmp_buf[2] = wc; +#endif + for (i = 0; i < cset->nranges; ++i) + { + cmp_buf[0] = cset->range_starts[i]; + cmp_buf[4] = cset->range_ends[i]; + if (wcscoll (cmp_buf, cmp_buf + 2) <= 0 + && wcscoll (cmp_buf + 2, cmp_buf + 4) <= 0) + { + match_len = char_len; + goto check_node_accept_bytes_match; + } + } + } + check_node_accept_bytes_match: + if (!cset->non_match) + return match_len; + else + { + if (match_len > 0) + return 0; + else + return (elem_len > char_len) ? elem_len : char_len; + } + } + return 0; +} + +# ifdef _LIBC +static unsigned int +find_collation_sequence_value (mbs, mbs_len) + const unsigned char *mbs; + size_t mbs_len; +{ + uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); + if (nrules == 0) + { + if (mbs_len == 1) + { + /* No valid character. Match it as a single byte character. */ + const unsigned char *collseq = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB); + return collseq[mbs[0]]; + } + return UINT_MAX; + } + else + { + int32_t idx; + const unsigned char *extra = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB); + int32_t extrasize = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB + 1) - extra; + + for (idx = 0; idx < extrasize;) + { + int mbs_cnt, found = 0; + int32_t elem_mbs_len; + /* Skip the name of collating element name. */ + idx = idx + extra[idx] + 1; + elem_mbs_len = extra[idx++]; + if (mbs_len == elem_mbs_len) + { + for (mbs_cnt = 0; mbs_cnt < elem_mbs_len; ++mbs_cnt) + if (extra[idx + mbs_cnt] != mbs[mbs_cnt]) + break; + if (mbs_cnt == elem_mbs_len) + /* Found the entry. */ + found = 1; + } + /* Skip the byte sequence of the collating element. */ + idx += elem_mbs_len; + /* Adjust for the alignment. */ + idx = (idx + 3) & ~3; + /* Skip the collation sequence value. */ + idx += sizeof (uint32_t); + /* Skip the wide char sequence of the collating element. */ + idx = idx + sizeof (uint32_t) * (extra[idx] + 1); + /* If we found the entry, return the sequence value. */ + if (found) + return *(uint32_t *) (extra + idx); + /* Skip the collation sequence value. */ + idx += sizeof (uint32_t); + } + return UINT_MAX; + } +} +# endif /* _LIBC */ +#endif /* RE_ENABLE_I18N */ + +/* Check whether the node accepts the byte which is IDX-th + byte of the INPUT. */ + +static int +check_node_accept (mctx, node, idx) + const re_match_context_t *mctx; + const re_token_t *node; + int idx; +{ + unsigned char ch; + ch = re_string_byte_at (&mctx->input, idx); + switch (node->type) + { + case CHARACTER: + if (node->opr.c != ch) + return 0; + break; + + case SIMPLE_BRACKET: + if (!bitset_contain (node->opr.sbcset, ch)) + return 0; + break; + +#ifdef RE_ENABLE_I18N + case OP_UTF8_PERIOD: + if (ch >= 0x80) + return 0; + /* FALLTHROUGH */ +#endif + case OP_PERIOD: + if ((ch == '\n' && !(mctx->dfa->syntax & RE_DOT_NEWLINE)) + || (ch == '\0' && (mctx->dfa->syntax & RE_DOT_NOT_NULL))) + return 0; + break; + + default: + return 0; + } + + if (node->constraint) + { + /* The node has constraints. Check whether the current context + satisfies the constraints. */ + unsigned int context = re_string_context_at (&mctx->input, idx, + mctx->eflags); + if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context)) + return 0; + } + + return 1; +} + +/* Extend the buffers, if the buffers have run out. */ + +static reg_errcode_t +extend_buffers (mctx) + re_match_context_t *mctx; +{ + reg_errcode_t ret; + re_string_t *pstr = &mctx->input; + + /* Double the lengthes of the buffers. */ + ret = re_string_realloc_buffers (pstr, pstr->bufs_len * 2); + if (BE (ret != REG_NOERROR, 0)) + return ret; + + if (mctx->state_log != NULL) + { + /* And double the length of state_log. */ + /* XXX We have no indication of the size of this buffer. If this + allocation fail we have no indication that the state_log array + does not have the right size. */ + re_dfastate_t **new_array = re_realloc (mctx->state_log, re_dfastate_t *, + pstr->bufs_len + 1); + if (BE (new_array == NULL, 0)) + return REG_ESPACE; + mctx->state_log = new_array; + } + + /* Then reconstruct the buffers. */ + if (pstr->icase) + { +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1) + { + ret = build_wcs_upper_buffer (pstr); + if (BE (ret != REG_NOERROR, 0)) + return ret; + } + else +#endif /* RE_ENABLE_I18N */ + build_upper_buffer (pstr); + } + else + { +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1) + build_wcs_buffer (pstr); + else +#endif /* RE_ENABLE_I18N */ + { + if (pstr->trans != NULL) + re_string_translate_buffer (pstr); + } + } + return REG_NOERROR; +} + + +/* Functions for matching context. */ + +/* Initialize MCTX. */ + +static reg_errcode_t +match_ctx_init (mctx, eflags, n) + re_match_context_t *mctx; + int eflags, n; +{ + mctx->eflags = eflags; + mctx->match_last = -1; + if (n > 0) + { + mctx->bkref_ents = re_malloc (struct re_backref_cache_entry, n); + mctx->sub_tops = re_malloc (re_sub_match_top_t *, n); + if (BE (mctx->bkref_ents == NULL || mctx->sub_tops == NULL, 0)) + return REG_ESPACE; + } + /* Already zero-ed by the caller. + else + mctx->bkref_ents = NULL; + mctx->nbkref_ents = 0; + mctx->nsub_tops = 0; */ + mctx->abkref_ents = n; + mctx->max_mb_elem_len = 1; + mctx->asub_tops = n; + return REG_NOERROR; +} + +/* Clean the entries which depend on the current input in MCTX. + This function must be invoked when the matcher changes the start index + of the input, or changes the input string. */ + +static void +match_ctx_clean (mctx) + re_match_context_t *mctx; +{ + int st_idx; + for (st_idx = 0; st_idx < mctx->nsub_tops; ++st_idx) + { + int sl_idx; + re_sub_match_top_t *top = mctx->sub_tops[st_idx]; + for (sl_idx = 0; sl_idx < top->nlasts; ++sl_idx) + { + re_sub_match_last_t *last = top->lasts[sl_idx]; + re_free (last->path.array); + re_free (last); + } + re_free (top->lasts); + if (top->path) + { + re_free (top->path->array); + re_free (top->path); + } + free (top); + } + + mctx->nsub_tops = 0; + mctx->nbkref_ents = 0; +} + +/* Free all the memory associated with MCTX. */ + +static void +match_ctx_free (mctx) + re_match_context_t *mctx; +{ + /* First, free all the memory associated with MCTX->SUB_TOPS. */ + match_ctx_clean (mctx); + re_free (mctx->sub_tops); + re_free (mctx->bkref_ents); +} + +/* Add a new backreference entry to MCTX. + Note that we assume that caller never call this function with duplicate + entry, and call with STR_IDX which isn't smaller than any existing entry. +*/ + +static reg_errcode_t +match_ctx_add_entry (mctx, node, str_idx, from, to) + re_match_context_t *mctx; + int node, str_idx, from, to; +{ + if (mctx->nbkref_ents >= mctx->abkref_ents) + { + struct re_backref_cache_entry* new_entry; + new_entry = re_realloc (mctx->bkref_ents, struct re_backref_cache_entry, + mctx->abkref_ents * 2); + if (BE (new_entry == NULL, 0)) + { + re_free (mctx->bkref_ents); + return REG_ESPACE; + } + mctx->bkref_ents = new_entry; + memset (mctx->bkref_ents + mctx->nbkref_ents, '\0', + sizeof (struct re_backref_cache_entry) * mctx->abkref_ents); + mctx->abkref_ents *= 2; + } + if (mctx->nbkref_ents > 0 + && mctx->bkref_ents[mctx->nbkref_ents - 1].str_idx == str_idx) + mctx->bkref_ents[mctx->nbkref_ents - 1].more = 1; + + mctx->bkref_ents[mctx->nbkref_ents].node = node; + mctx->bkref_ents[mctx->nbkref_ents].str_idx = str_idx; + mctx->bkref_ents[mctx->nbkref_ents].subexp_from = from; + mctx->bkref_ents[mctx->nbkref_ents].subexp_to = to; + + /* This is a cache that saves negative results of check_dst_limits_calc_pos. + If bit N is clear, means that this entry won't epsilon-transition to + an OP_OPEN_SUBEXP or OP_CLOSE_SUBEXP for the N+1-th subexpression. If + it is set, check_dst_limits_calc_pos_1 will recurse and try to find one + such node. + + A backreference does not epsilon-transition unless it is empty, so set + to all zeros if FROM != TO. */ + mctx->bkref_ents[mctx->nbkref_ents].eps_reachable_subexps_map + = (from == to ? ~0 : 0); + + mctx->bkref_ents[mctx->nbkref_ents++].more = 0; + if (mctx->max_mb_elem_len < to - from) + mctx->max_mb_elem_len = to - from; + return REG_NOERROR; +} + +/* Search for the first entry which has the same str_idx, or -1 if none is + found. Note that MCTX->BKREF_ENTS is already sorted by MCTX->STR_IDX. */ + +static int +search_cur_bkref_entry (mctx, str_idx) + re_match_context_t *mctx; + int str_idx; +{ + int left, right, mid, last; + last = right = mctx->nbkref_ents; + for (left = 0; left < right;) + { + mid = (left + right) / 2; + if (mctx->bkref_ents[mid].str_idx < str_idx) + left = mid + 1; + else + right = mid; + } + if (left < last && mctx->bkref_ents[left].str_idx == str_idx) + return left; + else + return -1; +} + +/* Register the node NODE, whose type is OP_OPEN_SUBEXP, and which matches + at STR_IDX. */ + +static reg_errcode_t +match_ctx_add_subtop (mctx, node, str_idx) + re_match_context_t *mctx; + int node, str_idx; +{ +#ifdef DEBUG + assert (mctx->sub_tops != NULL); + assert (mctx->asub_tops > 0); +#endif + if (BE (mctx->nsub_tops == mctx->asub_tops, 0)) + { + int new_asub_tops = mctx->asub_tops * 2; + re_sub_match_top_t **new_array = re_realloc (mctx->sub_tops, + re_sub_match_top_t *, + new_asub_tops); + if (BE (new_array == NULL, 0)) + return REG_ESPACE; + mctx->sub_tops = new_array; + mctx->asub_tops = new_asub_tops; + } + mctx->sub_tops[mctx->nsub_tops] = calloc (1, sizeof (re_sub_match_top_t)); + if (BE (mctx->sub_tops[mctx->nsub_tops] == NULL, 0)) + return REG_ESPACE; + mctx->sub_tops[mctx->nsub_tops]->node = node; + mctx->sub_tops[mctx->nsub_tops++]->str_idx = str_idx; + return REG_NOERROR; +} + +/* Register the node NODE, whose type is OP_CLOSE_SUBEXP, and which matches + at STR_IDX, whose corresponding OP_OPEN_SUBEXP is SUB_TOP. */ + +static re_sub_match_last_t * +match_ctx_add_sublast (subtop, node, str_idx) + re_sub_match_top_t *subtop; + int node, str_idx; +{ + re_sub_match_last_t *new_entry; + if (BE (subtop->nlasts == subtop->alasts, 0)) + { + int new_alasts = 2 * subtop->alasts + 1; + re_sub_match_last_t **new_array = re_realloc (subtop->lasts, + re_sub_match_last_t *, + new_alasts); + if (BE (new_array == NULL, 0)) + return NULL; + subtop->lasts = new_array; + subtop->alasts = new_alasts; + } + new_entry = calloc (1, sizeof (re_sub_match_last_t)); + if (BE (new_entry != NULL, 1)) + { + subtop->lasts[subtop->nlasts] = new_entry; + new_entry->node = node; + new_entry->str_idx = str_idx; + ++subtop->nlasts; + } + return new_entry; +} + +static void +sift_ctx_init (sctx, sifted_sts, limited_sts, last_node, last_str_idx) + re_sift_context_t *sctx; + re_dfastate_t **sifted_sts, **limited_sts; + int last_node, last_str_idx; +{ + sctx->sifted_states = sifted_sts; + sctx->limited_states = limited_sts; + sctx->last_node = last_node; + sctx->last_str_idx = last_str_idx; + re_node_set_init_empty (&sctx->limits); +} diff --git a/src/res/Makeinfo b/src/res/Makeinfo new file mode 100644 index 0000000..61215fc --- /dev/null +++ b/src/res/Makeinfo @@ -0,0 +1,6 @@ +case "$HOST_SYSTEM" in + MINGW32*) + uqm_RCFILES="UrQuanMasters.rc" + ;; +esac + diff --git a/src/res/UrQuanMasters.rc b/src/res/UrQuanMasters.rc new file mode 100644 index 0000000..aaf61a1 --- /dev/null +++ b/src/res/UrQuanMasters.rc @@ -0,0 +1,76 @@ +///////////////////////////////////////////////////////////////////////////// +// UrQuanMasters.rc +// +// Resource script for Win32 builds +// + +#include "../uqmversion.h" + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#ifdef _WIN32 +LANGUAGE 0x09, 0x01 // LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +1 VERSIONINFO + FILEVERSION UQM_MAJOR_VERSION,UQM_MINOR_VERSION,UQM_PATCH_VERSION,0 + PRODUCTVERSION UQM_MAJOR_VERSION,UQM_MINOR_VERSION,UQM_PATCH_VERSION,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "See http://sc2.sourceforge.net\0" + VALUE "CompanyName", "sc2.sourceforge.net\0" + VALUE "FileDescription", "The Ur-Quan Masters main executable\0" + VALUE "FileVersion", UQM_STRING_VERSION "\0" + VALUE "InternalName", "uqm\0" + VALUE "LegalCopyright", "(C) 1992-1993, 2002-2011 by respective authors\0" +#ifdef _DEBUG + VALUE "OriginalFilename", "uqmdebug.exe\0" +#else + VALUE "OriginalFilename", "uqm.exe\0" +#endif + VALUE "ProductName", "The Ur-Quan Masters\0" + VALUE "ProductVersion", UQM_STRING_VERSION "\0" + VALUE "SpecialBuild", "Alpha\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +SDL_app ICON PRELOAD DISCARDABLE "ur-quan-icon-alpha.ico" +102 ICON DISCARDABLE "ur-quan-icon-std.ico" +103 ICON DISCARDABLE "ur-quan1.ico" +104 ICON DISCARDABLE "sis1.ico" +105 ICON DISCARDABLE "ur-quan2.ico" +106 ICON DISCARDABLE "kohr-ah1.ico" +107 ICON DISCARDABLE "starcon2.ico" diff --git a/src/res/darwin/Info.plist b/src/res/darwin/Info.plist new file mode 100644 index 0000000..775285e --- /dev/null +++ b/src/res/darwin/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + The Ur-Quan Masters + CFBundleIconFile + The Ur-Quan Masters.icns + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleShortVersionString + @@VERSION@@ + CFBundleSignature + ???? + CFBundleVersion + @@VERSION@@ + + diff --git a/src/res/darwin/PkgInfo b/src/res/darwin/PkgInfo new file mode 100644 index 0000000..bd04210 --- /dev/null +++ b/src/res/darwin/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/src/res/darwin/The Ur-Quan Masters.icns b/src/res/darwin/The Ur-Quan Masters.icns new file mode 100644 index 0000000..5948823 Binary files /dev/null and b/src/res/darwin/The Ur-Quan Masters.icns differ diff --git a/src/res/darwin/uqm.r b/src/res/darwin/uqm.r new file mode 100644 index 0000000..35bb001 --- /dev/null +++ b/src/res/darwin/uqm.r @@ -0,0 +1,3 @@ +data 'MBAR' (128) { + $"0001 0080" /* ...€ */ +}; diff --git a/src/res/kohr-ah1.ico b/src/res/kohr-ah1.ico new file mode 100644 index 0000000..d4aa7e7 Binary files /dev/null and b/src/res/kohr-ah1.ico differ diff --git a/src/res/sis1.ico b/src/res/sis1.ico new file mode 100644 index 0000000..84f9224 Binary files /dev/null and b/src/res/sis1.ico differ diff --git a/src/res/starcon2.ico b/src/res/starcon2.ico new file mode 100644 index 0000000..8b28c2c Binary files /dev/null and b/src/res/starcon2.ico differ diff --git a/src/res/ur-quan-icon-24-hover-alpha.ico b/src/res/ur-quan-icon-24-hover-alpha.ico new file mode 100644 index 0000000..fb8b472 Binary files /dev/null and b/src/res/ur-quan-icon-24-hover-alpha.ico differ diff --git a/src/res/ur-quan-icon-24-hover.ico b/src/res/ur-quan-icon-24-hover.ico new file mode 100644 index 0000000..5c46054 Binary files /dev/null and b/src/res/ur-quan-icon-24-hover.ico differ diff --git a/src/res/ur-quan-icon-alpha.ico b/src/res/ur-quan-icon-alpha.ico new file mode 100644 index 0000000..4242535 Binary files /dev/null and b/src/res/ur-quan-icon-alpha.ico differ diff --git a/src/res/ur-quan-icon-std.ico b/src/res/ur-quan-icon-std.ico new file mode 100644 index 0000000..d084908 Binary files /dev/null and b/src/res/ur-quan-icon-std.ico differ diff --git a/src/res/ur-quan1.ico b/src/res/ur-quan1.ico new file mode 100644 index 0000000..1c40bbb Binary files /dev/null and b/src/res/ur-quan1.ico differ diff --git a/src/res/ur-quan2.ico b/src/res/ur-quan2.ico new file mode 100644 index 0000000..d1ae7ec Binary files /dev/null and b/src/res/ur-quan2.ico differ diff --git a/src/symbian/bld.inf b/src/symbian/bld.inf new file mode 100644 index 0000000..f9f1ad4 --- /dev/null +++ b/src/symbian/bld.inf @@ -0,0 +1,9 @@ +PRJ_PLATFORMS + + +PRJ_EXPORTS + + +PRJ_MMPFILES +gnumakefile icons_scalable_dc.mk +uqm.mmp diff --git a/src/symbian/config.h b/src/symbian/config.h new file mode 100644 index 0000000..c97b49e --- /dev/null +++ b/src/symbian/config.h @@ -0,0 +1,57 @@ +/* This file contains some compile-time configuration options for Symbian + */ + +#ifndef SYMBIAN_CONFIG_H_ +#define SYMBIAN_CONFIG_H_ + +/* Directory where the UQM game data is located */ +#define CONTENTDIR "content" + +/* Directory where game data will be stored */ +#define USERDIR "userdata" + +/* Directory where config files will be stored */ +#define CONFIGDIR USERDIR + +/* Directory where supermelee teams will be stored */ +#define MELEEDIR "userdata\\teams\\" + +/* Directory where save games will be stored */ +#define SAVEDIR "userdata\\save\\" + +/* Define if words are stored with the most significant byte first */ +#undef WORDS_BIGENDIAN + +/* Defined if your system has readdir_r of its own */ +#undef HAVE_READDIR_R + +/* Defined if your system has setenv of its own */ +#define HAVE_SETENV + +/* Defined if your system has strupr of its own */ +#undef HAVE_STRUPR + +/* Defined if your system has strcasecmp of its own */ +#define HAVE_STRCASECMP_UQM + // Not using "HAVE_STRCASECMP" as that conflicts with SDL. + +/* Defined if your system has stricmp of its own */ +#undef HAVE_STRICMP + +/* Defined if your system has getopt_long */ +#undef HAVE_GETOPT_LONG + +/* Defined if your system has iswgraph of its own*/ +#define HAVE_ISWGRAPH + +/* Defined if your system has wchar_t of its own */ +#define HAVE_WCHAR_T + +/* Defined if your system has wint_t of its own */ +#define HAVE_WINT_T + +#define HAVE__BOOL + +#define PATH_MAX _POSIX_PATH_MAX + +#endif /* SYMBIAN_CONFIG_H_ */ diff --git a/src/symbian/icons_scalable_dc.mk b/src/symbian/icons_scalable_dc.mk new file mode 100644 index 0000000..99ac591 --- /dev/null +++ b/src/symbian/icons_scalable_dc.mk @@ -0,0 +1,37 @@ +ifeq (WINS,$(findstring WINS, $(PLATFORM))) +ZDIR=$(EPOCROOT)epoc32\release\$(PLATFORM)\$(CFG)\Z +else +ZDIR=$(EPOCROOT)epoc32\data\z +endif + +TARGETDIR=$(ZDIR)\resource\apps +ICONTARGETFILENAME=$(TARGETDIR)\uqm_icon.mif + +ICONDIR= + +do_nothing : + @rem do_nothing + +MAKMAKE : do_nothing + +BLD : do_nothing + +CLEAN : do_nothing + +LIB : do_nothing + +CLEANLIB : do_nothing + +RESOURCE : + mifconv $(ICONTARGETFILENAME) \ + /c32 uqm.svg + +FREEZE : do_nothing + +SAVESPACE : do_nothing + +RELEASABLES : + @echo $(ICONTARGETFILENAME) + +FINAL : do_nothing + diff --git a/src/symbian/uqm-armv5.pkg b/src/symbian/uqm-armv5.pkg new file mode 100644 index 0000000..3549fd7 --- /dev/null +++ b/src/symbian/uqm-armv5.pkg @@ -0,0 +1,26 @@ +;Language - standard language definitions +&EN + +; standard SIS file header +#{"Ur-Quan Masters"},(0xA000A0C3),1,0,0 + +;Localised Vendor name +%{"Interstellar Frungy League"} + +;Unique Vendor name +:"Interstellar Frungy League" + +;Supports Series 60 v 3.0 +[0x101F7961], 0, 0, 0, {"Series60ProductID"} + +*"uqm.key", "uqm.cer" + +;Files to install +; +"\Epoc32\release\Armv5\urel\uqm.exe" - "!:\sys\bin\uqm.exe" +"\Epoc32\data\z\resource\apps\uqm.rsc" - "!:\resource\apps\uqm.rsc" +"\Epoc32\data\z\resource\apps\uqm_icon.mif" - "!:\resource\apps\uqm_icon.mif" +"\Epoc32\data\z\private\10003a3f\import\apps\uqm_reg.rsc" - "!:\private\10003a3f\import\apps\uqm_reg.rsc" +"uqm.cfg" - "!:\private\A000A0C3\userdata\uqm.cfg" +"..\..\content\version" - "!:\private\A000A0C3\content\version" +"..\..\content.uqm" - "!:\private\A000A0C3\content\packages\content.uqm" diff --git a/src/symbian/uqm-gcce.pkg b/src/symbian/uqm-gcce.pkg new file mode 100644 index 0000000..afc6e57 --- /dev/null +++ b/src/symbian/uqm-gcce.pkg @@ -0,0 +1,26 @@ +;Language - standard language definitions +&EN + +; standard SIS file header +#{"Ur-Quan Masters"},(0xA000A0C3),1,0,0 + +;Localised Vendor name +%{"Interstellar Frungy League"} + +;Unique Vendor name +:"Interstellar Frungy League" + +;Supports Series 60 v 3.0 +[0x101F7961], 0, 0, 0, {"Series60ProductID"} + +*"uqm.key", "uqm.cer" + +;Files to install +; +"\Epoc32\release\gcce\urel\uqm.exe" - "!:\sys\bin\uqm.exe" +"\Epoc32\data\z\resource\apps\uqm.rsc" - "!:\resource\apps\uqm.rsc" +"\Epoc32\data\z\resource\apps\uqm_icon.mif" - "!:\resource\apps\uqm_icon.mif" +"\Epoc32\data\z\private\10003a3f\import\apps\uqm_reg.rsc" - "!:\private\10003a3f\import\apps\uqm_reg.rsc" +"uqm.cfg" - "!:\private\A000A0C3\userdata\uqm.cfg" +"..\..\content\version" - "!:\private\A000A0C3\content\version" +"..\..\content.uqm" - "!:\private\A000A0C3\content\packages\content.uqm" diff --git a/src/symbian/uqm.cfg b/src/symbian/uqm.cfg new file mode 100644 index 0000000..4dbde82 --- /dev/null +++ b/src/symbian/uqm.cfg @@ -0,0 +1,26 @@ +alwaysgl = BOOLEAN:false +sfxvol = INT32:20 +reswidth = INT32:320 +usegl = BOOLEAN:false +3domusic = BOOLEAN:true +textmenu = BOOLEAN:true +musicvol = INT32:20 +textgradients = BOOLEAN:true +subtitles = BOOLEAN:true +iconicscan = BOOLEAN:false +resheight = INT32:240 +scaler = STRING:no +3domovies = BOOLEAN:false +speechvol = INT32:20 +audioquality = STRING:low +positionalsfx = BOOLEAN:false +player1control = INT32:0 +showfps = BOOLEAN:false +pulseshield = BOOLEAN:false +smoothmelee = BOOLEAN:false +fullscreen = BOOLEAN:false +smoothscroll = BOOLEAN:false +audiodriver = STRING:mixsdl +player2control = INT32:3 +scanlines = BOOLEAN:false +remixmusic = BOOLEAN:false diff --git a/src/symbian/uqm.mmp b/src/symbian/uqm.mmp new file mode 100644 index 0000000..5c7a85c --- /dev/null +++ b/src/symbian/uqm.mmp @@ -0,0 +1,45 @@ +TARGET uqm.exe +TARGETTYPE exe +UID 0 0xA000A0C3 +EPOCHEAPSIZE 1000000 50000000 +EPOCSTACKSIZE 81920 + +SYSTEMINCLUDE \epoc32\include \epoc32\include\stdapis + +SOURCEPATH . +SOURCE uqmapp.cpp + +START RESOURCE uqm.rss +HEADER +TARGETPATH resource\apps +LANG SC +END // RESOURCE + +START RESOURCE uqm_reg.rss +#ifdef WINSCW +TARGETPATH \private\10003a3f\apps +#else +TARGETPATH \private\10003a3f\import\apps +#endif +END + +OPTION_REPLACE ARMCC --cpu ARM926EJ-S -O3 -Otime +ALWAYS_BUILD_AS_ARM + +LIBRARY avkon.lib +LIBRARY apparc.lib +LIBRARY cone.lib +LIBRARY eikcore.lib +LIBRARY ws32.lib +LIBRARY bafl.lib +LIBRARY euser.lib +LIBRARY efsrv.lib +LIBRARY sdl.lib +LIBRARY libc.lib +LIBRARY libm.lib +LIBRARY libz.lib +LIBRARY libpthread.lib + +STATICLIBRARY uqm.lib +STATICLIBRARY SDL_image.lib +STATICLIBRARY tremor.lib diff --git a/src/symbian/uqm.rss b/src/symbian/uqm.rss new file mode 100644 index 0000000..8f9ea8c --- /dev/null +++ b/src/symbian/uqm.rss @@ -0,0 +1,26 @@ +NAME UQM + +#include +#include +#include +#include +#include + +RESOURCE RSS_SIGNATURE { } + +RESOURCE TBUF { buf="UQM"; } + +RESOURCE EIK_APP_INFO + { + } + +RESOURCE LOCALISABLE_APP_INFO r_uqm_localisable_app_info + { + short_caption = "UQM"; + caption_and_icon = CAPTION_AND_ICON_INFO + { + caption = "UrQuanMasters"; + number_of_icons = 1; + icon_file = "\\resource\\apps\\uqm_icon.mif"; + }; + } diff --git a/src/symbian/uqm.svg b/src/symbian/uqm.svg new file mode 100644 index 0000000..b95264b --- /dev/null +++ b/src/symbian/uqm.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/symbian/uqm_reg.rss b/src/symbian/uqm_reg.rss new file mode 100644 index 0000000..a57e670 --- /dev/null +++ b/src/symbian/uqm_reg.rss @@ -0,0 +1,12 @@ +#include +#include + +UID2 KUidAppRegistrationResourceFile +UID3 0xA000A0C3 + +RESOURCE APP_REGISTRATION_INFO + { + app_file = "uqm"; + localisable_resource_file = "\\resource\\apps\\uqm"; + localisable_resource_id = R_UQM_LOCALISABLE_APP_INFO; + } diff --git a/src/symbian/uqmapp.cpp b/src/symbian/uqmapp.cpp new file mode 100644 index 0000000..86ac5dd --- /dev/null +++ b/src/symbian/uqmapp.cpp @@ -0,0 +1,308 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const TUid KUidSdlApp={ 0xA000A0C3 }; + +class CSDL; + +class CSDLObserver : public CBase, public MSDLObserver +{ +public: + CSDLObserver(CSDL* aSdl); + + TInt SdlEvent(TInt aEvent, TInt aParam); + TInt SdlThreadEvent(TInt aEvent, TInt aParam); + +private: + CSDL* iSdl; +}; + +class MExitWait + { + public: + virtual void DoExit(TInt aErr) = 0; + }; + +class CExitWait : public CActive + { + public: + CExitWait(MExitWait& aWait); + void Start(); + ~CExitWait(); + private: + void RunL(); + void DoCancel(); + private: + MExitWait& iWait; + TRequestStatus* iStatusPtr; + }; + +class CSDLWin : public CCoeControl + { + public: + void ConstructL(const TRect& aRect); + RWindow& GetWindow() const; + void SetNoDraw(); + private: + void Draw(const TRect& aRect) const; + }; + +class CSdlApplication : public CAknApplication + { +private: + // from CApaApplication + CApaDocument* CreateDocumentL(); + TUid AppDllUid() const; + }; + + +class CSdlAppDocument : public CAknDocument + { +public: + CSdlAppDocument(CEikApplication& aApp): CAknDocument(aApp) { } +private: + CEikAppUi* CreateAppUiL(); + }; + + +class CSdlAppUi : public CAknAppUi, public MExitWait + { +public: + void ConstructL(); + ~CSdlAppUi(); +private: + void HandleCommandL(TInt aCommand); + void DoExit(TInt aErr); + void HandleWsEventL(const TWsEvent& aEvent, CCoeControl* aDestination); + void HandleResourceChangeL(TInt aType); + void AddCmdLineParamsL(CDesC8Array& aArgs); +private: + CExitWait* iWait; + CSDLWin* iSDLWin; + CSDL* iSdl; + CSDLObserver* iSdlObserver; + TBool iExit; + }; + + +CExitWait::CExitWait(MExitWait& aWait) : CActive(CActive::EPriorityStandard), iWait(aWait) + { + CActiveScheduler::Add(this); + } + +CExitWait::~CExitWait() + { + Cancel(); + } + +void CExitWait::RunL() + { + if(iStatusPtr != NULL ) + iWait.DoExit(iStatus.Int()); + } + +void CExitWait::DoCancel() + { + if(iStatusPtr != NULL ) + User::RequestComplete(iStatusPtr , KErrCancel); + } + +void CExitWait::Start() + { + SetActive(); + iStatusPtr = &iStatus; + } + +void CSDLWin:: ConstructL(const TRect& aRect) + { + CreateWindowL(); + SetRect(aRect); + ActivateL(); + } + + +RWindow& CSDLWin::GetWindow() const + { + return Window(); + } + + +void CSDLWin::Draw(const TRect& /*aRect*/) const + { + CWindowGc& gc = SystemGc(); + gc.SetPenStyle(CGraphicsContext::ESolidPen); + gc.SetPenColor(KRgbBlack); + gc.SetBrushStyle(CGraphicsContext::ESolidBrush); + gc.SetBrushColor(0x000000); + gc.DrawRect(Rect()); + } + +void CSdlAppUi::ConstructL() + { + BaseConstructL(ENoScreenFurniture | EAppOrientationLandscape); + + iSDLWin = new (ELeave) CSDLWin; + iSDLWin->ConstructL(ApplicationRect()); + + iSdl = CSDL::NewL(CSDL::EEnableFocusStop); + iSdlObserver = new (ELeave) CSDLObserver(iSdl); + + iSdl->SetContainerWindowL( + iSDLWin->GetWindow(), + iEikonEnv->WsSession(), + *iEikonEnv->ScreenDevice()); + iSdl->SetObserver(iSdlObserver); + iSdl->DisableKeyBlocking(*this); + + iWait = new (ELeave) CExitWait(*this); + CDesC8ArrayFlat* args = new (ELeave)CDesC8ArrayFlat(10); + AddCmdLineParamsL(*args); + + iSdl->CallMainL(iWait->iStatus, *args, CSDL::ENoFlags, 81920); + delete args; + + iWait->Start(); + } + +void CSdlAppUi::HandleCommandL(TInt aCommand) + { + switch(aCommand) + { + case EAknCmdExit: + case EAknSoftkeyExit: + case EEikCmdExit: + exit(0); + break; + + default: + break; + } + } + +void CSdlAppUi::DoExit(TInt aErr) + { + delete iSdl; + iSdl = NULL; + + if(iExit) + Exit(); + } + +void CSdlAppUi::HandleWsEventL(const TWsEvent& aEvent, CCoeControl* aDestination) + { + if(iSdl != NULL) + iSdl->AppendWsEvent(aEvent); + CAknAppUi::HandleWsEventL(aEvent, aDestination); + } + +void CSdlAppUi::HandleResourceChangeL(TInt aType) + { + CAknAppUi::HandleResourceChangeL(aType); + + if(aType == KEikDynamicLayoutVariantSwitch) + { + iSDLWin->SetRect(ApplicationRect()); + if (iSdl) + { + iSdl->SetContainerWindowL( + iSDLWin->GetWindow(), + iEikonEnv->WsSession(), + *iEikonEnv->ScreenDevice()); + } + } + } + +void CSdlAppUi::AddCmdLineParamsL(CDesC8Array& aArgs) + { + _LIT8(KAddonParam, "--addondir=?:\\uqm-addons"); + _LIT16(KTestFolder, "?:\\uqm-addons"); + RFs fs; + + fs.Connect(); + for (TInt8 c = 'e'; c < 'z'; ++c) + { + TBuf16<32> buf(KTestFolder); + buf[0] = c; + if (BaflUtils::FolderExists(fs, buf)) + { + TBuf8<32> arg(KAddonParam); + arg[11] = c; + aArgs.AppendL(arg); + break; + } + } + fs.Close(); + } + +CSdlAppUi::~CSdlAppUi() + { + if(iWait != NULL) + iWait->Cancel(); + delete iSdl; + delete iWait; + delete iSDLWin; + delete iSdlObserver; + } + +CEikAppUi* CSdlAppDocument::CreateAppUiL() + { + return new(ELeave) CSdlAppUi(); + } + +TUid CSdlApplication::AppDllUid() const + { + return KUidSdlApp; + } + + +CApaDocument* CSdlApplication::CreateDocumentL() + { + CSdlAppDocument* document = new (ELeave) CSdlAppDocument(*this); + return document; + } + +LOCAL_C CApaApplication* NewApplication() + { + return new CSdlApplication; + } + +GLDEF_C TInt E32Main() + { + return EikStart::RunApplication(NewApplication); + } + +CSDLObserver::CSDLObserver(CSDL* aSdl) : iSdl(aSdl) +{ +} + +TInt CSDLObserver::SdlEvent(TInt aEvent, TInt aParam) +{ + if (aEvent == EEventKeyMapInit) + { + // starmap zoom + iSdl->SetSDLCode('3', SDLK_KP_PLUS); + iSdl->SetSDLCode('2', SDLK_KP_MINUS); + + iSdl->SetSDLCode('A', SDLK_KP_PLUS); + iSdl->SetSDLCode('Z', SDLK_KP_MINUS); + + iSdl->SetSDLCode('a', SDLK_KP_PLUS); + iSdl->SetSDLCode('z', SDLK_KP_MINUS); + } + return 0; +} + +TInt CSDLObserver::SdlThreadEvent(TInt aEvent, TInt aParam) +{ + return 0; +} diff --git a/src/types.h b/src/types.h new file mode 100644 index 0000000..db58630 --- /dev/null +++ b/src/types.h @@ -0,0 +1,188 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Data types used in the UQM port + * Draft 3 + * Compiled from various sources + */ + +#ifndef TYPES_H_ +#define TYPES_H_ + +#include "config.h" + +#ifdef _MSC_VER +# if (_MSC_VER >= 1300) +# include +# else + typedef int intptr_t; + typedef unsigned int uintptr_t; +# endif +# ifdef _WIN64 +# define PRIxPTR "lx" +# else +# define PRIxPTR "x" +# endif +#else +# include +# ifndef PRIxPTR + /* SunOS (at least version 5.9) does not have PRIxPTR in inttypes.h */ +# if defined(__arch64__) || defined(__alpha) || defined(__x86_64) \ + || defined(_M_IA64) || defined(_M_AMD64) + /* 64-bit platforms */ +# define PRIxPTR "lx" +# else +# define PRIxPTR "x" +# endif +# endif /* defined(PRIxPTR) */ +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(__arch64__) || defined(__alpha) || defined(__x86_64) \ + || defined(_M_IA64) || defined(_M_AMD64) + /* 64-bit platforms */ +# define UQM_INT16 short +# define UQM_INT32 int +# define UQM_INT64 long + +#elif defined(__MACOS__) + +# define UQM_INT16 short +# define UQM_INT32 long + +/* Add your OS support here */ + +#else +/* all other sane 32bit: WIN32, __MACOSX__, __BEOS__, __EMX__ */ + +# define UQM_INT16 short +# define UQM_INT32 int + +#endif + +/* Figure out how to support 64-bit datatypes */ +#if !defined(UQM_INT64) && !defined(__STRICT_ANSI__) +# if defined(__GNUC__) || defined(__MWERKS__) || defined(__SUNPRO_C) || defined(__ARMCC__) +# define UQM_INT64 long long +# elif defined(_MSC_VER) || defined(__BORLANDC__) +# define UQM_INT64 __int64 +# endif +#endif /* !__STRICT_ANSI__ */ + +// ISO C99 compatible boolean types. The ISO C99 standard defines: +// - An object declared as type _Bool, large enough to store the values 0 +// and 1, the rank of which is less than the rank of all other standard +// integer types. +// - A macro "bool", which expands to "_Bool". +// - A macro "true", which expands to the integer constant 1, suitable for +// use in #if preprocessing directives. +// - A macro "false", which expands to the integer constant 0, suitable for +// use in #if preprocessing directives. +// - A macro "__bool_true_false_are_defined", which expands to the integer +// constant 1, suitable for use in #if preprocessing directives. +#ifndef __bool_true_false_are_defined +#undef bool +#undef false +#undef true +#ifndef HAVE__BOOL +typedef unsigned char _Bool; +#endif /* HAVE_BOOL */ +#define bool _Bool +#define true 1 +#define false 0 +#define __bool_true_false_are_defined +#endif /* __bool_true_false_are_defined */ + +/* If it isn't char, what is it ?!*/ +typedef unsigned char uint8; +typedef signed char sint8; + +typedef unsigned UQM_INT16 uint16; +typedef signed UQM_INT16 sint16; + +typedef unsigned UQM_INT32 uint32; +typedef signed UQM_INT32 sint32; + +#if defined(UQM_INT64) + +typedef unsigned UQM_INT64 uint64; +typedef signed UQM_INT64 sint64; + +#undef UQM_INT64 + +#else /* dont have UQM_INT64 */ + +typedef struct +{ + uint32 hi; + uint32 lo; +} uint64; +typedef struct +{ + sint32 hi; + uint32 lo; +} sint64; + +#endif /* UQM_INT64 */ + +#undef UQM_INT16 +#undef UQM_INT32 + +/* Make sure the types really have the right sizes + * Adapted from SDL + * This will generate "negative subscript or subscript is too large" + * error during compile, if the actual size of a type is wrong + */ +#define UQM_COMPILE_TIME_ASSERT(name, x) \ + typedef int UQM_dummy_##name [(x) * 2 - 1] + +UQM_COMPILE_TIME_ASSERT(uint8, sizeof(uint8) == 1); +UQM_COMPILE_TIME_ASSERT(uint16, sizeof(uint16) == 2); +UQM_COMPILE_TIME_ASSERT(uint32, sizeof(uint32) == 4); +UQM_COMPILE_TIME_ASSERT(uint64, sizeof(uint64) == 8); + +#undef UQM_COMPILE_TIME_ASSERT + +#undef SINT8_MIN +#undef SINT8_MAX +#undef SINT16_MIN +#undef SINT16_MAX +#undef SINT32_MIN +#undef SINT32_MAX + +#undef UINT8_MAX +#undef UINT16_MAX +#undef UINT32_MAX + +#define SINT8_MIN (-128) +#define SINT8_MAX 127 +#define SINT16_MIN (-32767-1) +#define SINT16_MAX 32767 +#define SINT32_MIN (-2147483647-1) +#define SINT32_MAX 2147483647 + +#define UINT8_MAX 0xff /* 255U */ +#define UINT16_MAX 0xffff /* 65535U */ +#define UINT32_MAX 0xffffffff /* 4294967295U */ + +#if defined(__cplusplus) +} +#endif + +#endif /* TYPES_H_ */ diff --git a/src/uqm.c b/src/uqm.c new file mode 100644 index 0000000..43c25d8 --- /dev/null +++ b/src/uqm.c @@ -0,0 +1,1285 @@ +//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. + */ + +#ifdef HAVE_UNISTD_H +# include +#endif + +#ifdef HAVE_GETOPT_LONG +# include +#else +# include "getopt/getopt.h" +#endif + +#include +#include +#include "libs/graphics/gfx_common.h" +#include "libs/graphics/cmap.h" +#include "libs/sound/sound.h" +#include "libs/input/input_common.h" +#include "libs/inplib.h" +#include "libs/tasklib.h" +#include "uqm/controls.h" +#include "uqm/battle.h" + // For BATTLE_FRAME_RATE +#include "libs/file.h" +#include "types.h" +#include "port.h" +#include "libs/memlib.h" +#include "libs/platform.h" +#include "libs/log.h" +#include "options.h" +#include "uqmversion.h" +#include "uqm/comm.h" +#ifdef NETPLAY +# include "libs/callback.h" +# include "libs/alarm.h" +# include "libs/net.h" +# include "uqm/supermelee/netplay/netoptions.h" +# include "uqm/supermelee/netplay/netplay.h" +#endif +#include "uqm/setup.h" +#include "uqm/starcon.h" + + +#if defined (GFXMODULE_SDL) +# include SDL_INCLUDE(SDL.h) + // Including this is actually necessary on OSX. +#endif + +struct bool_option +{ + bool value; + bool set; +}; + +struct int_option +{ + int value; + bool set; +}; + +struct float_option +{ + float value; + bool set; +}; + +struct options_struct +{ +#define DECL_CONFIG_OPTION(type, name) \ + struct type##_option name + +#define DECL_CONFIG_OPTION2(type, name, val1, val2) \ + struct { type val1; type val2; bool set; } name + + // Commandline-only options + const char *logFile; + enum { + runMode_normal, + runMode_usage, + runMode_version, + } runMode; + + const char *configDir; + const char *contentDir; + const char *addonDir; + const char **addons; + int numAddons; + + const char *graphicsBackend; + + // Commandline and user config options + DECL_CONFIG_OPTION(bool, opengl); + DECL_CONFIG_OPTION2(int, resolution, width, height); + DECL_CONFIG_OPTION(bool, fullscreen); + DECL_CONFIG_OPTION(bool, scanlines); + DECL_CONFIG_OPTION(int, scaler); + DECL_CONFIG_OPTION(bool, showFps); + DECL_CONFIG_OPTION(bool, keepAspectRatio); + DECL_CONFIG_OPTION(float, gamma); + DECL_CONFIG_OPTION(int, soundDriver); + DECL_CONFIG_OPTION(int, soundQuality); + DECL_CONFIG_OPTION(bool, use3doMusic); + DECL_CONFIG_OPTION(bool, useRemixMusic); + DECL_CONFIG_OPTION(bool, useSpeech); + DECL_CONFIG_OPTION(int, whichCoarseScan); + DECL_CONFIG_OPTION(int, whichMenu); + DECL_CONFIG_OPTION(int, whichFonts); + DECL_CONFIG_OPTION(int, whichIntro); + DECL_CONFIG_OPTION(int, whichShield); + DECL_CONFIG_OPTION(int, smoothScroll); + DECL_CONFIG_OPTION(int, meleeScale); + DECL_CONFIG_OPTION(bool, subtitles); + DECL_CONFIG_OPTION(bool, stereoSFX); + DECL_CONFIG_OPTION(float, musicVolumeScale); + DECL_CONFIG_OPTION(float, sfxVolumeScale); + DECL_CONFIG_OPTION(float, speechVolumeScale); + DECL_CONFIG_OPTION(bool, safeMode); + +#define INIT_CONFIG_OPTION(name, val) \ + { val, false } + +#define INIT_CONFIG_OPTION2(name, val1, val2) \ + { val1, val2, false } +}; + +struct option_list_value +{ + const char *str; + int value; +}; + +static const struct option_list_value scalerList[] = +{ + {"bilinear", TFB_GFXFLAGS_SCALE_BILINEAR}, + {"biadapt", TFB_GFXFLAGS_SCALE_BIADAPT}, + {"biadv", TFB_GFXFLAGS_SCALE_BIADAPTADV}, + {"triscan", TFB_GFXFLAGS_SCALE_TRISCAN}, + {"hq", TFB_GFXFLAGS_SCALE_HQXX}, + {"none", 0}, + {"no", 0}, /* uqm.cfg value */ + {NULL, 0} +}; + +static const struct option_list_value meleeScaleList[] = +{ + {"smooth", TFB_SCALE_TRILINEAR}, + {"3do", TFB_SCALE_TRILINEAR}, + {"step", TFB_SCALE_STEP}, + {"pc", TFB_SCALE_STEP}, + {"bilinear", TFB_SCALE_BILINEAR}, + {NULL, 0} +}; + +static const struct option_list_value audioDriverList[] = +{ + {"openal", audio_DRIVER_OPENAL}, + {"mixsdl", audio_DRIVER_MIXSDL}, + {"none", audio_DRIVER_NOSOUND}, + {"nosound", audio_DRIVER_NOSOUND}, + {NULL, 0} +}; + +static const struct option_list_value audioQualityList[] = +{ + {"low", audio_QUALITY_LOW}, + {"medium", audio_QUALITY_MEDIUM}, + {"high", audio_QUALITY_HIGH}, + {NULL, 0} +}; + +static const struct option_list_value choiceList[] = +{ + {"pc", OPT_PC}, + {"3do", OPT_3DO}, + {NULL, 0} +}; + +static const struct option_list_value accelList[] = +{ + {"mmx", PLATFORM_MMX}, + {"sse", PLATFORM_SSE}, + {"3dnow", PLATFORM_3DNOW}, + {"none", PLATFORM_C}, + {"detect", PLATFORM_NULL}, + {NULL, 0} +}; + +// Looks up the given string value in the given list and passes +// the associated int value back. returns true if value was found. +// The list is terminated by a NULL 'str' value. +static bool lookupOptionValue (const struct option_list_value *list, + const char *strval, int *ret); + +// Error message buffer used for when we cannot use logging facility yet +static char errBuffer[512]; + +static void saveError (const char *fmt, ...) + PRINTF_FUNCTION(1, 2); + +static int parseOptions (int argc, char *argv[], + struct options_struct *options); +static void getUserConfigOptions (struct options_struct *options); +static void usage (FILE *out, const struct options_struct *defaultOptions); +static int parseIntOption (const char *str, int *result, + const char *optName); +static int parseFloatOption (const char *str, float *f, + const char *optName); +static void parseIntVolume (int intVol, float *vol); +static int InvalidArgument (const char *supplied, const char *opt_name); +static const char *choiceOptString (const struct int_option *option); +static const char *boolOptString (const struct bool_option *option); +static const char *boolNotOptString (const struct bool_option *option); + +int +main (int argc, char *argv[]) +{ + struct options_struct options = { + /* .logFile = */ NULL, + /* .runMode = */ runMode_normal, + /* .configDir = */ NULL, + /* .contentDir = */ NULL, + /* .addonDir = */ NULL, + /* .addons = */ NULL, + /* .numAddons = */ 0, + /* .graphicsBackend = */ NULL, + + INIT_CONFIG_OPTION( opengl, false ), + INIT_CONFIG_OPTION2( resolution, 640, 480 ), + INIT_CONFIG_OPTION( fullscreen, false ), + INIT_CONFIG_OPTION( scanlines, false ), + INIT_CONFIG_OPTION( scaler, 0 ), + INIT_CONFIG_OPTION( showFps, false ), + INIT_CONFIG_OPTION( keepAspectRatio, false ), + INIT_CONFIG_OPTION( gamma, 1.0f ), + INIT_CONFIG_OPTION( soundDriver, audio_DRIVER_MIXSDL ), + INIT_CONFIG_OPTION( soundQuality, audio_QUALITY_MEDIUM ), + INIT_CONFIG_OPTION( use3doMusic, true ), + INIT_CONFIG_OPTION( useRemixMusic, false ), + INIT_CONFIG_OPTION( useSpeech, true ), + INIT_CONFIG_OPTION( whichCoarseScan, OPT_PC ), + INIT_CONFIG_OPTION( whichMenu, OPT_PC ), + INIT_CONFIG_OPTION( whichFonts, OPT_PC ), + INIT_CONFIG_OPTION( whichIntro, OPT_PC ), + INIT_CONFIG_OPTION( whichShield, OPT_PC ), + INIT_CONFIG_OPTION( smoothScroll, OPT_PC ), + INIT_CONFIG_OPTION( meleeScale, TFB_SCALE_TRILINEAR ), + INIT_CONFIG_OPTION( subtitles, true ), + INIT_CONFIG_OPTION( stereoSFX, false ), + INIT_CONFIG_OPTION( musicVolumeScale, 1.0f ), + INIT_CONFIG_OPTION( sfxVolumeScale, 1.0f ), + INIT_CONFIG_OPTION( speechVolumeScale, 1.0f ), + INIT_CONFIG_OPTION( safeMode, false ), + }; + struct options_struct defaults = options; + int optionsResult; + int gfxDriver; + int gfxFlags; + int i; + + // NOTE: we cannot use the logging facility yet because we may have to + // log to a file, and we'll only get the log file name after parsing + // the options. + optionsResult = parseOptions (argc, argv, &options); + + log_init (15); + + if (options.logFile != NULL) + { + int i; + if (!freopen (options.logFile, "w", stderr)) + { + printf ("Error %d calling freopen() on stderr\n", errno); + return EXIT_FAILURE; + } +#ifdef UNBUFFERED_LOGFILE + setbuf (stderr, NULL); +#endif + for (i = 0; i < argc; ++i) + log_add (log_User, "argv[%d] = [%s]", i, argv[i]); + } + + if (options.runMode == runMode_version) + { + printf ("%d.%d.%d%s\n", UQM_MAJOR_VERSION, UQM_MINOR_VERSION, + UQM_PATCH_VERSION, UQM_EXTRA_VERSION); + log_showBox (false, false); + return EXIT_SUCCESS; + } + + log_add (log_User, "The Ur-Quan Masters v%d.%d.%d%s (compiled %s %s)\n" + "This software comes with ABSOLUTELY NO WARRANTY;\n" + "for details see the included 'COPYING' file.\n", + UQM_MAJOR_VERSION, UQM_MINOR_VERSION, + UQM_PATCH_VERSION, UQM_EXTRA_VERSION, + __DATE__, __TIME__); +#ifdef NETPLAY + log_add (log_User, "Netplay protocol version %d.%d. Netplay opponent " + "must have UQM %d.%d.%d or later.", + NETPLAY_PROTOCOL_VERSION_MAJOR, NETPLAY_PROTOCOL_VERSION_MINOR, + NETPLAY_MIN_UQM_VERSION_MAJOR, NETPLAY_MIN_UQM_VERSION_MINOR, + NETPLAY_MIN_UQM_VERSION_PATCH); +#endif + + if (errBuffer[0] != '\0') + { // Have some saved error to log + log_add (log_Error, "%s", errBuffer); + errBuffer[0] = '\0'; + } + + if (options.runMode == runMode_usage) + { + usage (stdout, &defaults); + log_showBox (true, false); + return EXIT_SUCCESS; + } + + if (optionsResult != EXIT_SUCCESS) + { // Options parsing failed. Oh, well. + log_add (log_Fatal, "Run with -h to see the allowed arguments."); + return optionsResult; + } + + TFB_PreInit (); + mem_init (); + InitThreadSystem (); + log_initThreads (); + initIO (); + prepareConfigDir (options.configDir); + + PlayerControls[0] = CONTROL_TEMPLATE_KB_1; + PlayerControls[1] = CONTROL_TEMPLATE_JOY_1; + + // Fill in the options struct based on uqm.cfg + if (!options.safeMode.value) + { + LoadResourceIndex (configDir, "uqm.cfg", "config."); + getUserConfigOptions (&options); + } + + { /* remove old control template names */ + int i; + + for (i = 0; i < 6; ++i) + { + char cfgkey[64]; + + snprintf(cfgkey, sizeof(cfgkey), "config.keys.%d.name", i + 1); + cfgkey[sizeof(cfgkey) - 1] = '\0'; + + res_Remove (cfgkey); + } + } + + /* TODO: Once threading is gone, these become local variables + again. In the meantime, they must be global so that + initAudio (in StarCon2Main) can see them. initAudio needed + to be moved there because calling AssignTask in the main + thread doesn't work */ + snddriver = options.soundDriver.value; + soundflags = options.soundQuality.value; + + // Fill in global variables: + opt3doMusic = options.use3doMusic.value; + optRemixMusic = options.useRemixMusic.value; + optSpeech = options.useSpeech.value; + optWhichCoarseScan = options.whichCoarseScan.value; + optWhichMenu = options.whichMenu.value; + optWhichFonts = options.whichFonts.value; + optWhichIntro = options.whichIntro.value; + optWhichShield = options.whichShield.value; + optSmoothScroll = options.smoothScroll.value; + optMeleeScale = options.meleeScale.value; + optKeepAspectRatio = options.keepAspectRatio.value; + optSubtitles = options.subtitles.value; + optStereoSFX = options.stereoSFX.value; + musicVolumeScale = options.musicVolumeScale.value; + sfxVolumeScale = options.sfxVolumeScale.value; + speechVolumeScale = options.speechVolumeScale.value; + optAddons = options.addons; + + prepareContentDir (options.contentDir, options.addonDir, argv[0]); + prepareMeleeDir (); + prepareSaveDir (); + prepareShadowAddons (options.addons); +#if 0 + initTempDir (); +#endif + + InitTimeSystem (); + InitTaskSystem (); + + Alarm_init (); + Callback_init (); + +#ifdef NETPLAY + Network_init (); + NetManager_init (); +#endif + + gfxDriver = options.opengl.value ? + TFB_GFXDRIVER_SDL_OPENGL : TFB_GFXDRIVER_SDL_PURE; + gfxFlags = options.scaler.value; + if (options.fullscreen.value) + gfxFlags |= TFB_GFXFLAGS_FULLSCREEN; + if (options.scanlines.value) + gfxFlags |= TFB_GFXFLAGS_SCANLINES; + if (options.showFps.value) + gfxFlags |= TFB_GFXFLAGS_SHOWFPS; + TFB_InitGraphics (gfxDriver, gfxFlags, options.graphicsBackend, + options.resolution.width, options.resolution.height); + if (options.gamma.set && setGammaCorrection (options.gamma.value)) + optGamma = options.gamma.value; + else + optGamma = 1.0f; // failed or default + + InitColorMaps (); + init_communication (); + /* TODO: Once threading is gone, restore initAudio here. + initAudio calls AssignTask, which currently blocks on + ProcessThreadLifecycles... */ + // initAudio (snddriver, soundflags); + // Make sure that the compiler treats multidim arrays the way we expect + assert (sizeof (int [NUM_TEMPLATES * NUM_KEYS]) == + sizeof (int [NUM_TEMPLATES][NUM_KEYS])); + TFB_SetInputVectors (ImmediateInputState.menu, NUM_MENU_KEYS, + (volatile int *)ImmediateInputState.key, NUM_TEMPLATES, NUM_KEYS); + TFB_InitInput (TFB_INPUTDRIVER_SDL, 0); + + StartThread (Starcon2Main, NULL, 1024, "Starcon2Main"); + + for (i = 0; i < 2000 && !MainExited; ) + { + if (QuitPosted) + { /* Try to stop the main thread, but limited number of times */ + SignalStopMainThread (); + ++i; + } + else if (!GameActive) + { // Throttle down the main loop when game is inactive + HibernateThread (ONE_SECOND / 4); + } + + TFB_ProcessEvents (); + ProcessUtilityKeys (); + ProcessThreadLifecycles (); + TFB_FlushGraphics (); + } + + /* Currently, we use atexit() callbacks everywhere, so we + * cannot simply call unInitAudio() and the like, because other + * tasks might still be using it */ + if (MainExited) + { + TFB_UninitInput (); + unInitAudio (); + uninit_communication (); + + TFB_PurgeDanglingGraphics (); + // Purge above refers to colormaps which have to be still up + UninitColorMaps (); + TFB_UninitGraphics (); + +#ifdef NETPLAY + NetManager_uninit (); + Network_uninit (); +#endif + + Callback_uninit (); + Alarm_uninit (); + + CleanupTaskSystem (); + UnInitTimeSystem (); +#if 0 + unInitTempDir (); +#endif + unprepareAllDirs (); + uninitIO (); + UnInitThreadSystem (); + mem_uninit (); + } + + HFree (options.addons); + + return EXIT_SUCCESS; +} + +static void +saveErrorV (const char *fmt, va_list list) +{ + int len = strlen (errBuffer); + int left = sizeof (errBuffer) - len; + if (len > 0 && left > 0) + { // Already something there + errBuffer[len] = '\n'; + ++len; + --left; + } + vsnprintf (errBuffer + len, left, fmt, list); + errBuffer[sizeof (errBuffer) - 1] = '\0'; +} + +static void +saveError (const char *fmt, ...) +{ + va_list list; + + va_start (list, fmt); + saveErrorV (fmt, list); + va_end (list); +} + + +static bool +lookupOptionValue (const struct option_list_value *list, + const char *strval, int *ret) +{ + if (!list) + return false; + + // The list is terminated by a NULL 'str' value. + while (list->str && strcmp (strval, list->str) != 0) + ++list; + if (!list->str) + return false; + + *ret = list->value; + return true; +} + +static void +getBoolConfigValue (struct bool_option *option, const char *config_val) +{ + if (option->set || !res_IsBoolean (config_val)) + return; + + option->value = res_GetBoolean (config_val); + option->set = true; +} + +static void +getBoolConfigValueXlat (struct int_option *option, const char *config_val, + int true_val, int false_val) +{ + if (option->set || !res_IsBoolean (config_val)) + return; + + option->value = res_GetBoolean (config_val) ? true_val : false_val; + option->set = true; +} + +static void +getVolumeConfigValue (struct float_option *option, const char *config_val) +{ + if (option->set || !res_IsInteger (config_val)) + return; + + parseIntVolume (res_GetInteger (config_val), &option->value); + option->set = true; +} + +static void +getGammaConfigValue (struct float_option *option, const char *config_val) +{ + int val; + + if (option->set || !res_IsInteger (config_val)) + return; + + val = res_GetInteger (config_val); + // gamma config option is a fixed-point number + // ignore ridiculously out-of-range values + if (val < (int)(0.03 * GAMMA_SCALE) || val > (int)(9.9 * GAMMA_SCALE)) + return; + option->value = val / (float)GAMMA_SCALE; + // avoid setting gamma when not necessary + if (option->value != 1.0f) + option->set = true; +} + +static bool +getListConfigValue (struct int_option *option, const char *config_val, + const struct option_list_value *list) +{ + const char *strval; + bool found; + + if (option->set || !res_IsString (config_val) || !list) + return false; + + strval = res_GetString (config_val); + found = lookupOptionValue (list, strval, &option->value); + option->set = found; + + return found; +} + +static void +getUserConfigOptions (struct options_struct *options) +{ + // Most of the user config options are only applied if they + // have not already been set (i.e. on the commandline) + + if (res_IsInteger ("config.reswidth") && res_IsInteger ("config.resheight") + && !options->resolution.set) + { + options->resolution.width = res_GetInteger ("config.reswidth"); + options->resolution.height = res_GetInteger ("config.resheight"); + options->resolution.set = true; + } + + if (res_IsBoolean ("config.alwaysgl") && !options->opengl.set) + { // config.alwaysgl is processed differently than others + // Only set when it's 'true' + if (res_GetBoolean ("config.alwaysgl")) + { + options->opengl.value = true; + options->opengl.set = true; + } + } + getBoolConfigValue (&options->opengl, "config.usegl"); + + getListConfigValue (&options->scaler, "config.scaler", scalerList); + + getBoolConfigValue (&options->fullscreen, "config.fullscreen"); + getBoolConfigValue (&options->scanlines, "config.scanlines"); + getBoolConfigValue (&options->showFps, "config.showfps"); + getBoolConfigValue (&options->keepAspectRatio, "config.keepaspectratio"); + getGammaConfigValue (&options->gamma, "config.gamma"); + + getBoolConfigValue (&options->subtitles, "config.subtitles"); + + getBoolConfigValueXlat (&options->whichMenu, "config.textmenu", + OPT_PC, OPT_3DO); + getBoolConfigValueXlat (&options->whichFonts, "config.textgradients", + OPT_PC, OPT_3DO); + getBoolConfigValueXlat (&options->whichCoarseScan, "config.iconicscan", + OPT_3DO, OPT_PC); + getBoolConfigValueXlat (&options->smoothScroll, "config.smoothscroll", + OPT_3DO, OPT_PC); + getBoolConfigValueXlat (&options->whichShield, "config.pulseshield", + OPT_3DO, OPT_PC); + getBoolConfigValueXlat (&options->whichIntro, "config.3domovies", + OPT_3DO, OPT_PC); + + getBoolConfigValue (&options->use3doMusic, "config.3domusic"); + getBoolConfigValue (&options->useRemixMusic, "config.remixmusic"); + getBoolConfigValue (&options->useSpeech, "config.speech"); + + getBoolConfigValueXlat (&options->meleeScale, "config.smoothmelee", + TFB_SCALE_TRILINEAR, TFB_SCALE_STEP); + + getListConfigValue (&options->soundDriver, "config.audiodriver", + audioDriverList); + getListConfigValue (&options->soundQuality, "config.audioquality", + audioQualityList); + getBoolConfigValue (&options->stereoSFX, "config.positionalsfx"); + getVolumeConfigValue (&options->musicVolumeScale, "config.musicvol"); + getVolumeConfigValue (&options->sfxVolumeScale, "config.sfxvol"); + getVolumeConfigValue (&options->speechVolumeScale, "config.speechvol"); + + if (res_IsInteger ("config.player1control")) + { + PlayerControls[0] = res_GetInteger ("config.player1control"); + /* This is an unsigned, so no < 0 check is necessary */ + if (PlayerControls[0] >= NUM_TEMPLATES) + { + log_add (log_Error, "Illegal control template '%d' for Player " + "One.", PlayerControls[0]); + PlayerControls[0] = CONTROL_TEMPLATE_KB_1; + } + } + + if (res_IsInteger ("config.player2control")) + { + /* This is an unsigned, so no < 0 check is necessary */ + PlayerControls[1] = res_GetInteger ("config.player2control"); + if (PlayerControls[1] >= NUM_TEMPLATES) + { + log_add (log_Error, "Illegal control template '%d' for Player " + "Two.", PlayerControls[1]); + PlayerControls[1] = CONTROL_TEMPLATE_JOY_1; + } + } +} + +enum +{ + CSCAN_OPT = 1000, + MENU_OPT, + FONT_OPT, + SHIELD_OPT, + SCROLL_OPT, + SOUND_OPT, + STEREOSFX_OPT, + ADDON_OPT, + ADDONDIR_OPT, + ACCEL_OPT, + SAFEMODE_OPT, + RENDERER_OPT, +#ifdef NETPLAY + NETHOST1_OPT, + NETPORT1_OPT, + NETHOST2_OPT, + NETPORT2_OPT, + NETDELAY_OPT, +#endif +}; + +static const char *optString = "+r:foc:b:spC:n:?hM:S:T:q:ug:l:i:vwxk"; +static struct option longOptions[] = +{ + {"res", 1, NULL, 'r'}, + {"fullscreen", 0, NULL, 'f'}, + {"opengl", 0, NULL, 'o'}, + {"scale", 1, NULL, 'c'}, + {"meleezoom", 1, NULL, 'b'}, + {"scanlines", 0, NULL, 's'}, + {"fps", 0, NULL, 'p'}, + {"configdir", 1, NULL, 'C'}, + {"contentdir", 1, NULL, 'n'}, + {"help", 0, NULL, 'h'}, + {"musicvol", 1, NULL, 'M'}, + {"sfxvol", 1, NULL, 'S'}, + {"speechvol", 1, NULL, 'T'}, + {"audioquality", 1, NULL, 'q'}, + {"nosubtitles", 0, NULL, 'u'}, + {"gamma", 1, NULL, 'g'}, + {"logfile", 1, NULL, 'l'}, + {"intro", 1, NULL, 'i'}, + {"version", 0, NULL, 'v'}, + {"windowed", 0, NULL, 'w'}, + {"nogl", 0, NULL, 'x'}, + {"keepaspectratio", 0, NULL, 'k'}, + + // options with no short equivalent: + {"cscan", 1, NULL, CSCAN_OPT}, + {"menu", 1, NULL, MENU_OPT}, + {"font", 1, NULL, FONT_OPT}, + {"shield", 1, NULL, SHIELD_OPT}, + {"scroll", 1, NULL, SCROLL_OPT}, + {"sound", 1, NULL, SOUND_OPT}, + {"stereosfx", 0, NULL, STEREOSFX_OPT}, + {"addon", 1, NULL, ADDON_OPT}, + {"addondir", 1, NULL, ADDONDIR_OPT}, + {"accel", 1, NULL, ACCEL_OPT}, + {"safe", 0, NULL, SAFEMODE_OPT}, + {"renderer", 1, NULL, RENDERER_OPT}, +#ifdef NETPLAY + {"nethost1", 1, NULL, NETHOST1_OPT}, + {"netport1", 1, NULL, NETPORT1_OPT}, + {"nethost2", 1, NULL, NETHOST2_OPT}, + {"netport2", 1, NULL, NETPORT2_OPT}, + {"netdelay", 1, NULL, NETDELAY_OPT}, +#endif + {0, 0, 0, 0} +}; + +static inline void +setBoolOption (struct bool_option *option, bool value) +{ + option->value = value; + option->set = true; +} + +static bool +setFloatOption (struct float_option *option, const char *strval, + const char *optName) +{ + if (parseFloatOption (strval, &option->value, optName) != 0) + return false; + option->set = true; + return true; +} + +// returns true is value was found and set successfully +static bool +setListOption (struct int_option *option, const char *strval, + const struct option_list_value *list) +{ + bool found; + + if (!list) + return false; // not found + + found = lookupOptionValue (list, strval, &option->value); + option->set = found; + + return found; +} + +static inline bool +setChoiceOption (struct int_option *option, const char *strval) +{ + return setListOption (option, strval, choiceList); +} + +static bool +setVolumeOption (struct float_option *option, const char *strval, + const char *optName) +{ + int intVol; + + if (parseIntOption (strval, &intVol, optName) != 0) + return false; + parseIntVolume (intVol, &option->value); + option->set = true; + return true; +} + +static int +parseOptions (int argc, char *argv[], struct options_struct *options) +{ + int optionIndex; + bool badArg = false; + + opterr = 0; + + options->addons = HMalloc (1 * sizeof (const char *)); + options->addons[0] = NULL; + options->numAddons = 0; + + if (argc == 0) + { + saveError ("Error: Bad command line."); + return EXIT_FAILURE; + } + +#ifdef __APPLE__ + // If we are launched by double-clicking an application bundle, Finder + // sticks a "-psn_" argument into the list, which makes + // getopt extremely unhappy. Check for this case and wipe out the + // entire command line if it looks like it happened. + if ((argc >= 2) && (strncmp (argv[1], "-psn_", 5) == 0)) + { + return EXIT_SUCCESS; + } +#endif + + while (!badArg) + { + int c; + optionIndex = -1; + c = getopt_long (argc, argv, optString, longOptions, &optionIndex); + if (c == -1) + break; + + switch (c) + { + case '?': + if (optopt != '?') + { + saveError ("\nInvalid option or its argument"); + badArg = true; + break; + } + // fall through + case 'h': + options->runMode = runMode_usage; + return EXIT_SUCCESS; + case 'v': + options->runMode = runMode_version; + return EXIT_SUCCESS; + case 'r': + { + int width, height; + if (sscanf (optarg, "%dx%d", &width, &height) != 2) + { + saveError ("Error: invalid argument specified " + "as resolution."); + badArg = true; + break; + } + options->resolution.width = width; + options->resolution.height = height; + options->resolution.set = true; + break; + } + case 'f': + setBoolOption (&options->fullscreen, true); + break; + case 'w': + setBoolOption (&options->fullscreen, false); + break; + case 'o': + setBoolOption (&options->opengl, true); + break; + case 'x': + setBoolOption (&options->opengl, false); + break; + case 'k': + setBoolOption (&options->keepAspectRatio, true); + break; + case 'c': + if (!setListOption (&options->scaler, optarg, scalerList)) + { + InvalidArgument (optarg, "--scale or -c"); + badArg = true; + } + break; + case 'b': + if (!setListOption (&options->meleeScale, optarg, + meleeScaleList)) + { + InvalidArgument (optarg, "--meleezoom or -b"); + badArg = true; + } + break; + case 's': + setBoolOption (&options->scanlines, true); + break; + case 'p': + setBoolOption (&options->showFps, true); + break; + case 'n': + options->contentDir = optarg; + break; + case 'M': + if (!setVolumeOption (&options->musicVolumeScale, optarg, + "music volume")) + { + badArg = true; + } + break; + case 'S': + if (!setVolumeOption (&options->sfxVolumeScale, optarg, + "sfx volume")) + { + badArg = true; + } + break; + case 'T': + if (!setVolumeOption (&options->speechVolumeScale, optarg, + "speech volume")) + { + badArg = true; + } + break; + case 'q': + if (!setListOption (&options->soundQuality, optarg, + audioQualityList)) + { + InvalidArgument (optarg, "--audioquality or -q"); + badArg = true; + } + break; + case 'u': + setBoolOption (&options->subtitles, false); + break; + case 'g': + if (!setFloatOption (&options->gamma, optarg, + "gamma correction")) + { + badArg = true; + } + break; + case 'l': + options->logFile = optarg; + break; + case 'C': + options->configDir = optarg; + break; + case 'i': + if (!setChoiceOption (&options->whichIntro, optarg)) + { + InvalidArgument (optarg, "--intro or -i"); + badArg = true; + } + break; + case CSCAN_OPT: + if (!setChoiceOption (&options->whichCoarseScan, optarg)) + { + InvalidArgument (optarg, "--cscan"); + badArg = true; + } + break; + case MENU_OPT: + if (!setChoiceOption (&options->whichMenu, optarg)) + { + InvalidArgument (optarg, "--menu"); + badArg = true; + } + break; + case FONT_OPT: + if (!setChoiceOption (&options->whichFonts, optarg)) + { + InvalidArgument (optarg, "--font"); + badArg = true; + } + break; + case SHIELD_OPT: + if (!setChoiceOption (&options->whichShield, optarg)) + { + InvalidArgument (optarg, "--shield"); + badArg = true; + } + break; + case SCROLL_OPT: + if (!setChoiceOption (&options->smoothScroll, optarg)) + { + InvalidArgument (optarg, "--scroll"); + badArg = true; + } + break; + case SOUND_OPT: + if (!setListOption (&options->soundDriver, optarg, + audioDriverList)) + { + InvalidArgument (optarg, "--sound"); + badArg = true; + } + break; + case STEREOSFX_OPT: + setBoolOption (&options->stereoSFX, true); + break; + case ADDON_OPT: + options->numAddons++; + options->addons = HRealloc ((void *) options->addons, + (options->numAddons + 1) * sizeof (const char *)); + options->addons[options->numAddons - 1] = optarg; + options->addons[options->numAddons] = NULL; + break; + case ADDONDIR_OPT: + options->addonDir = optarg; + break; + case ACCEL_OPT: + { + int value; + if (lookupOptionValue (accelList, optarg, &value)) + { + force_platform = value; + } + else + { + InvalidArgument (optarg, "--accel"); + badArg = true; + } + break; + } + case SAFEMODE_OPT: + setBoolOption (&options->safeMode, true); + break; + case RENDERER_OPT: + options->graphicsBackend = optarg; + break; +#ifdef NETPLAY + case NETHOST1_OPT: + netplayOptions.peer[0].isServer = false; + netplayOptions.peer[0].host = optarg; + break; + case NETPORT1_OPT: + netplayOptions.peer[0].port = optarg; + break; + case NETHOST2_OPT: + netplayOptions.peer[1].isServer = false; + netplayOptions.peer[1].host = optarg; + break; + case NETPORT2_OPT: + netplayOptions.peer[1].port = optarg; + break; + case NETDELAY_OPT: + { + int temp; + if (parseIntOption (optarg, &temp, "network input delay") + == -1) + { + badArg = true; + break; + } + netplayOptions.inputDelay = temp; + + if (netplayOptions.inputDelay > BATTLE_FRAME_RATE) + { + saveError ("Network input delay is absurdly large."); + badArg = true; + } + break; + } +#endif + default: + saveError ("Error: Unknown option '%s'", + optionIndex < 0 ? "" : + longOptions[optionIndex].name); + badArg = true; + break; + } + } + + if (!badArg && optind != argc) + { + saveError ("\nError: Extra arguments found on the command line."); + badArg = true; + } + + return badArg ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static void +parseIntVolume (int intVol, float *vol) +{ + if (intVol < 0) + { + *vol = 0.0f; + return; + } + + if (intVol > 100) + { + *vol = 1.0f; + return; + } + + *vol = intVol / 100.0f; + return; +} + +static int +parseIntOption (const char *str, int *result, const char *optName) +{ + char *endPtr; + int temp; + + if (str[0] == '\0') + { + saveError ("Error: Invalid value for '%s'.", optName); + return -1; + } + temp = (int) strtol (str, &endPtr, 10); + if (*endPtr != '\0') + { + saveError ("Error: Junk characters in argument '%s'.", optName); + return -1; + } + + *result = temp; + return 0; +} + +static int +parseFloatOption (const char *str, float *f, const char *optName) +{ + char *endPtr; + float temp; + + if (str[0] == '\0') + { + saveError ("Error: Invalid value for '%s'.", optName); + return -1; + } + temp = (float) strtod (str, &endPtr); + if (*endPtr != '\0') + { + saveError ("Error: Junk characters in argument '%s'.", optName); + return -1; + } + + *f = temp; + return 0; +} + +static void +usage (FILE *out, const struct options_struct *defaults) +{ + FILE *old = log_setOutput (out); + log_captureLines (LOG_CAPTURE_ALL); + + log_add (log_User, "Options:"); + log_add (log_User, " -r, --res=WIDTHxHEIGHT (default 640x480, bigger " + "works only with --opengl)"); + log_add (log_User, " -f, --fullscreen (default %s)", + boolOptString (&defaults->fullscreen)); + log_add (log_User, " -w, --windowed (default %s)", + boolNotOptString (&defaults->fullscreen)); + log_add (log_User, " -o, --opengl (default %s)", + boolOptString (&defaults->opengl)); + log_add (log_User, " -x, --nogl (default %s)", + boolNotOptString (&defaults->opengl)); + log_add (log_User, " -k, --keepaspectratio (default %s)", + boolOptString (&defaults->keepAspectRatio)); + log_add (log_User, " -c, --scale=MODE (bilinear, biadapt, biadv, " + "triscan, hq or none (default) )"); + log_add (log_User, " -b, --meleezoom=MODE (step, aka pc, or smooth, " + "aka 3do; default is 3do)"); + log_add (log_User, " -s, --scanlines (default %s)", + boolOptString (&defaults->scanlines)); + log_add (log_User, " -p, --fps (default %s)", + boolOptString (&defaults->showFps)); + log_add (log_User, " -g, --gamma=CORRECTIONVALUE (default 1.0, which " + "causes no change)"); + log_add (log_User, " -C, --configdir=CONFIGDIR"); + log_add (log_User, " -n, --contentdir=CONTENTDIR"); + log_add (log_User, " -M, --musicvol=VOLUME (0-100, default 100)"); + log_add (log_User, " -S, --sfxvol=VOLUME (0-100, default 100)"); + log_add (log_User, " -T, --speechvol=VOLUME (0-100, default 100)"); + log_add (log_User, " -q, --audioquality=QUALITY (high, medium or low, " + "default medium)"); + log_add (log_User, " -u, --nosubtitles"); + log_add (log_User, " -l, --logfile=FILE (sends console output to " + "logfile FILE)"); + log_add (log_User, " --addon ADDON (using a specific addon; " + "may be specified multiple times)"); + log_add (log_User, " --addondir=ADDONDIR (directory where addons " + "reside)"); + log_add (log_User, " --renderer=name (Select named rendering engine " + "if possible)"); + log_add (log_User, " --sound=DRIVER (openal, mixsdl, none; default " + "mixsdl)"); + log_add (log_User, " --stereosfx (enables positional sound effects, " + "currently only for openal)"); + log_add (log_User, " --safe (start in safe mode)"); +#ifdef NETPLAY + log_add (log_User, " --nethostN=HOSTNAME (server to connect to for " + "player N (1=bottom, 2=top)"); + log_add (log_User, " --netportN=PORT (port to connect to/listen on for " + "player N (1=bottom, 2=top)"); + log_add (log_User, " --netdelay=FRAMES (number of frames to " + "buffer/delay network input for"); +#endif + log_add (log_User, "The following options can take either '3do' or 'pc' " + "as an option:"); + log_add (log_User, " -i, --intro : Intro/ending version (default %s)", + choiceOptString (&defaults->whichIntro)); + log_add (log_User, " --cscan : coarse-scan display, pc=text, " + "3do=hieroglyphs (default %s)", + choiceOptString (&defaults->whichCoarseScan)); + log_add (log_User, " --menu : menu type, pc=text, 3do=graphical " + "(default %s)", choiceOptString (&defaults->whichMenu)); + log_add (log_User, " --font : font types and colors (default %s)", + choiceOptString (&defaults->whichFonts)); + log_add (log_User, " --shield : slave shield type; pc=static, " + "3do=throbbing (default %s)", + choiceOptString (&defaults->whichShield)); + log_add (log_User, " --scroll : ff/frev during comm. pc=per-page, " + "3do=smooth (default %s)", + choiceOptString (&defaults->smoothScroll)); + log_setOutput (old); +} + +static int +InvalidArgument (const char *supplied, const char *opt_name) +{ + saveError ("Invalid argument '%s' to option %s.", supplied, opt_name); + return EXIT_FAILURE; +} + +static const char * +choiceOptString (const struct int_option *option) +{ + switch (option->value) + { + case OPT_3DO: + return "3do"; + case OPT_PC: + return "pc"; + default: /* 0 */ + return "none"; + } +} + +static const char * +boolOptString (const struct bool_option *option) +{ + return option->value ? "on" : "off"; +} + +static const char * +boolNotOptString (const struct bool_option *option) +{ + return option->value ? "off" : "on"; +} 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 +#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 + +#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 +#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 +#include +#include +#include +#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 +#include + + +#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 + +#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 + +// 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 +#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 +#include + + +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 + +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 +#include +#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 +#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 + +#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 + + +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 + +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 + +#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, ¤t); + 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 + +#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 + + +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 + + +// 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 +#include + + +#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 + + +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, " "); + 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 +#include + + +#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 + +// 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 + +#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 +#include +#include + + +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 + + +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 + +// 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 + +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 + +#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 +#endif +#include + +// 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 + +#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 +#include + + +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 +#include + + +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, ""); +#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, ""); + } +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include + + + +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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE) +# include +# include +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include + + +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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include + + +//////////////////////////////////////////////////////////////////////////// + + +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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + + +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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include + + +// 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#include + + +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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include + + +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#include + + +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + + +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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include +#include + +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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 +#include + +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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + +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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + +// 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 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 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 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 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 +#include + + +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 + { + // " 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 + + +// 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 /* 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 WORLD_TO_VELOCITY(l) ((l)< + +// 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_ */ diff --git a/src/uqmversion.h b/src/uqmversion.h new file mode 100644 index 0000000..11b7156 --- /dev/null +++ b/src/uqmversion.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 UQMVERSION_H +#define UQMVERSION_H + +#define UQM_MAJOR_VERSION 0 +#define UQM_MAJOR_VERSION_S "0" +#define UQM_MINOR_VERSION 8 +#define UQM_MINOR_VERSION_S "8" +#define UQM_PATCH_VERSION 0 +#define UQM_PATCH_VERSION_S "0" +#define UQM_EXTRA_VERSION "" +/* The final version is interpreted as: + * printf ("%d.%d.%d%s", UQM_MAJOR_VERSION, UQM_MINOR_VERSION, + * UQM_PATCH_VERSION, UQM_EXTRA_VERSION); + */ + +#define UQM_STRING_VERSION \ + UQM_MAJOR_VERSION_S "." UQM_MINOR_VERSION_S "." UQM_PATCH_VERSION_S \ + UQM_EXTRA_VERSION + +#endif -- cgit v1.2.3