From 41065b89ab663b70a9175f3ae625fec492bbf872 Mon Sep 17 00:00:00 2001 From: LFeenanEA <170682497+LFeenanEA@users.noreply.github.com> Date: Thu, 27 Feb 2025 14:50:48 +0000 Subject: [PATCH] Initial commit of Command & Conquer source code. --- AADATA.CPP | 798 +++++ ABSTRACT.CPP | 109 + ABSTRACT.H | 109 + ADATA.CPP | 2429 ++++++++++++++ AIRCRAFT.CPP | 3495 +++++++++++++++++++ AIRCRAFT.H | 254 ++ ALLOC.CPP | 590 ++++ ANIM.CPP | 1175 +++++++ ANIM.H | 174 + AUDIO.CPP | 544 +++ AUDIO.H | 99 + BASE.CPP | 524 +++ BASE.H | 127 + BBDATA.CPP | 608 ++++ BDATA.CPP | 4938 +++++++++++++++++++++++++++ BFILE.MAK | 3406 +++++++++++++++++++ BUILDING.CPP | 4960 +++++++++++++++++++++++++++ BUILDING.H | 304 ++ BULLET.CPP | 786 +++++ BULLET.H | 153 + CARGO.CPP | 183 + CARGO.H | 91 + CCDDE.CPP | 422 +++ CCDDE.H | 88 + CCFILE.CPP | 626 ++++ CCFILE.H | 114 + CC_ICON.RC | 16 + CC_ICON.RH | 1 + CDATA.CPP | 2773 +++++++++++++++ CDFILE.CPP | 508 +++ CDFILE.H | 115 + CELL.CPP | 2316 +++++++++++++ CELL.H | 253 ++ CHECKBOX.CPP | 72 + CHECKBOX.H | 55 + CHEKLIST.CPP | 168 + CHEKLIST.H | 86 + CO-WC32.LNT | 67 + COLRLIST.CPP | 283 ++ COLRLIST.H | 87 + COMBAT.CPP | 235 ++ COMBUF.CPP | 1039 ++++++ COMBUF.H | 181 + COMPAT.H | 170 + COMQUEUE.CPP | 1006 ++++++ COMQUEUE.H | 193 ++ CONFDLG.CPP | 250 ++ CONFDLG.H | 56 + CONNECT.CPP | 247 ++ CONNECT.H | 343 ++ CONNMGR.H | 151 + CONQUER.CPP | 4013 ++++++++++++++++++++++ CONQUER.DEF | 11 + CONQUER.H | 773 +++++ CONQUER.ICO | Bin 0 -> 10134 bytes CONQUER.IDE | Bin 0 -> 42942 bytes CONQUER.LNT | 79 + CONQUER.PRO | 864 +++++ CONQUER.REP | 880 +++++ CONQUER.TXT | 1417 ++++++++ CONST.CPP | 425 +++ CONTROL.CPP | 202 ++ CONTROL.H | 89 + COORD.CPP | 545 +++ COORDA.ASM | 134 + CREDITS.CPP | 176 + CREDITS.H | 72 + CREW.CPP | 38 + CREW.H | 65 + CWSTUB.C | 71 + DDE.CPP | 450 +++ DDE.H | 175 + DEBUG.CPP | 690 ++++ DEBUG.H | 42 + DEFINES.H | 2771 +++++++++++++++ DESCDLG.CPP | 184 + DESCDLG.H | 69 + DIAL8.CPP | 317 ++ DIAL8.H | 76 + DIALOG.CPP | 790 +++++ DISPLAY.CPP | 3753 +++++++++++++++++++++ DISPLAY.H | 320 ++ DOOR.CPP | 202 ++ DOOR.H | 94 + DPMI.CPP | 169 + DPMI.H | 175 + DRIVE.CPP | 2213 ++++++++++++ DRIVE.H | 215 ++ EDIT.CPP | 471 +++ EDIT.H | 106 + ENDING.CPP | 261 ++ ENDING.H | 42 + EVENT.CPP | 760 +++++ EVENT.H | 226 ++ EXPAND.CPP | 422 +++ EXTERNS.H | 401 +++ FACING.CPP | 183 + FACING.H | 79 + FACTORY.CPP | 750 +++++ FACTORY.H | 144 + FIELD.CPP | 214 ++ FIELD.H | 85 + FINDPATH.CPP | 1546 +++++++++ FLASHER.CPP | 97 + FLASHER.H | 71 + FLY.CPP | 133 + FLY.H | 83 + FOOT.CPP | 2015 +++++++++++ FOOT.H | 311 ++ FTIMER.H | 91 + FUNCTION.H | 926 +++++ FUNCTION.I | 19 + FUSE.CPP | 196 ++ FUSE.H | 95 + GADGET.CPP | 804 +++++ GADGET.H | 239 ++ GAMEDLG.CPP | 422 +++ GAMEDLG.H | 52 + GAUGE.CPP | 539 +++ GAUGE.H | 111 + GDI.BAT | 5 + GLOBALS.CPP | 1035 ++++++ GOPTIONS.CPP | 587 ++++ GOPTIONS.H | 100 + GSCREEN.CPP | 529 +++ GSCREEN.H | 141 + HDATA.CPP | 318 ++ HEADER.MAC | 20 + HEAP.CPP | 553 +++ HEAP.H | 191 ++ HELP.CPP | 405 +++ HELP.H | 144 + HOTLIST.TXT | 114 + HOUSE.CPP | 4349 ++++++++++++++++++++++++ HOUSE.H | 570 ++++ IDATA.CPP | 2154 ++++++++++++ INFANTRY.CPP | 3221 ++++++++++++++++++ INFANTRY.H | 244 ++ INI.CPP | 1664 +++++++++ INIT.CPP | 2958 ++++++++++++++++ INTERNET.CPP | 876 +++++ INTERPAL.CPP | 458 +++ INTRO.CPP | 313 ++ INTRO.H | 42 + IOMAP.CPP | 1051 ++++++ IOOBJ.CPP | 2637 +++++++++++++++ IPX.CPP | 1111 ++++++ IPX.H | 188 ++ IPX95.CPP | 85 + IPX95.H | 57 + IPXADDR.CPP | 494 +++ IPXADDR.H | 106 + IPXCONN.CPP | 680 ++++ IPXCONN.H | 211 ++ IPXGCONN.CPP | 473 +++ IPXGCONN.H | 161 + IPXMGR.CPP | 1972 +++++++++++ IPXMGR.H | 395 +++ IPXPROT.ASM | 113 + IPXREAL.ASM | 319 ++ JAP.BAT | 5 + JSHELL.CPP | 429 +++ JSHELL.H | 229 ++ KEYFBUFF.ASM | 4854 +++++++++++++++++++++++++++ KEYFBUFF.INC | 40 + KEYFRAME.CPP | 593 ++++ LAYER.CPP | 163 + LAYER.H | 67 + LED.H | 46 + LINK.CPP | 416 +++ LINK.H | 81 + LIST.CPP | 897 +++++ LIST.H | 149 + LOADDLG.CPP | 734 ++++ LOADDLG.H | 92 + LOGIC.CPP | 275 ++ LOGIC.H | 55 + MAKEFILE | 950 ++++++ MAKEFILE.BAK | 861 +++++ MAP.CPP | 1183 +++++++ MAP.H | 154 + MAPEDDLG.CPP | 3850 +++++++++++++++++++++ MAPEDIT.CPP | 1924 +++++++++++ MAPEDIT.H | 355 ++ MAPEDPLC.CPP | 1999 +++++++++++ MAPEDSEL.CPP | 607 ++++ MAPEDTM.CPP | 2077 ++++++++++++ MAPSEL.CPP | 1276 +++++++ MEMCHECK.H | 2605 +++++++++++++++ MENUS.CPP | 932 ++++++ MESSAGE.H | 47 + MESSAGE.TXT | 57 + MISSION.CPP | 477 +++ MISSION.H | 136 + MIXFILE.CPP | 516 +++ MIXFILE.H | 87 + MMX.ASM | 325 ++ MONOC.CPP | 807 +++++ MONOC.H | 191 ++ MOUSE.CPP | 360 ++ MOUSE.H | 130 + MPLAYER.CPP | 1408 ++++++++ MSGBOX.CPP | 482 +++ MSGBOX.H | 60 + MSGLIST.CPP | 808 +++++ MSGLIST.H | 109 + NETDLG.CPP | 5431 ++++++++++++++++++++++++++++++ NETDLG.CPP.BAK | 5295 +++++++++++++++++++++++++++++ NOD.BAT | 5 + NOSEQCON.CPP | 690 ++++ NOSEQCON.H | 129 + NULLCONN.CPP | 265 ++ NULLCONN.H | 140 + NULLDLG.CPP | 8178 +++++++++++++++++++++++++++++++++++++++++++++ NULLDLG.CPP.BAK | 7580 +++++++++++++++++++++++++++++++++++++++++ NULLMGR.CPP | 2350 +++++++++++++ NULLMGR.H | 217 ++ OBJECT.CPP | 1507 +++++++++ OBJECT.H | 245 ++ ODATA.CPP | 925 +++++ OPTIONS.CPP | 797 +++++ OPTIONS.H | 104 + OPTIONS.LNT | 26 + OVERLAY.CPP | 429 +++ OVERLAY.H | 107 + PACKET.CPP | 463 +++ PACKET.H | 101 + PAGFAULT.ASM | 225 ++ PHONE.H | 69 + POWER.CPP | 462 +++ POWER.H | 126 + PROFILE.CPP | 656 ++++ QUEUE.CPP | 4204 +++++++++++++++++++++++ QUEUE.H | 275 ++ RADAR.CPP | 1960 +++++++++++ RADAR.H | 213 ++ RADIO.CPP | 252 ++ RADIO.H | 108 + RAND.CPP | 173 + RAWFILE.CPP | 1089 ++++++ RAWFILE.H | 243 ++ REAL.H | 934 ++++++ REGION.H | 59 + REG_ICON.ICO | Bin 0 -> 766 bytes REG_ICON.RC | 16 + REG_ICON.RH | 1 + REINF.CPP | 663 ++++ RULES.MAK | 423 +++ SAVEDLG.H | 71 + SAVELOAD.CPP | 1392 ++++++++ SCENARIO.CPP | 727 ++++ SCORE.CPP | 2316 +++++++++++++ SCORE.H | 159 + SCREEN.H | 68 + SCROLL.CPP | 256 ++ SCROLL.H | 86 + SDATA.CPP | 473 +++ SEQCONN.CPP | 561 ++++ SEQCONN.H | 111 + SESSION.H | 553 +++ SHAPEBTN.CPP | 166 + SHAPEBTN.H | 67 + SIDEBAR.CPP | 2535 ++++++++++++++ SIDEBAR.H | 397 +++ SLIDER.CPP | 408 +++ SLIDER.H | 110 + SMUDGE.CPP | 406 +++ SMUDGE.H | 104 + SOUNDDLG.CPP | 434 +++ SOUNDDLG.H | 159 + SPECIAL.CPP | 274 ++ SPECIAL.H | 241 ++ STAGE.H | 100 + STARTUP.CPP | 793 +++++ STATS.CPP | 676 ++++ STD.LNT | 102 + SUPER.CPP | 386 +++ SUPER.H | 84 + SUPPORT.ASM | 459 +++ TAB.CPP | 253 ++ TAB.H | 82 + TARCOM.CPP | 183 + TARCOM.H | 78 + TARGET.CPP | 502 +++ TARGET.H | 157 + TCPIP.CPP | 1004 ++++++ TCPIP.H | 225 ++ TDATA.CPP | 1029 ++++++ TEAM.CPP | 1505 +++++++++ TEAM.H | 249 ++ TEAMTYPE.CPP | 978 ++++++ TEAMTYPE.H | 259 ++ TECHNO.CPP | 3996 ++++++++++++++++++++++ TECHNO.H | 319 ++ TEMP.CPP | 41 + TEMPLATE.CPP | 411 +++ TEMPLATE.H | 120 + TERRAIN.CPP | 900 +++++ TERRAIN.H | 177 + TEXTBLIT.H | 53 + TEXTBTN.CPP | 377 +++ TEXTBTN.H | 74 + THEME.CPP | 562 ++++ THEME.H | 87 + TOGGLE.CPP | 199 ++ TOGGLE.H | 82 + TOOLS/MIXFILE.EXE | Bin 0 -> 78224 bytes TRIGGER.CPP | 1486 ++++++++ TRIGGER.H | 259 ++ TURRET.CPP | 504 +++ TURRET.H | 85 + TXTLABEL.CPP | 98 + TXTLABEL.H | 72 + TXTPRNT.ASM | 514 +++ TYPE.H | 1970 +++++++++++ UALL.BAT | 42 + UDATA.CPP | 1901 +++++++++++ UNIT.CPP | 4051 ++++++++++++++++++++++ UNIT.H | 200 ++ UTRACKER.CPP | 226 ++ UTRACKER.H | 86 + VECTOR.CPP | 898 +++++ VECTOR.H | 296 ++ VISUDLG.CPP | 424 +++ VISUDLG.H | 92 + WATCOM.H | 77 + WIDEFUNC.MAC | 1 + WIDEHDR.MAC | 1 + WINASM.ASM | 889 +++++ WINSTUB.CPP | 921 +++++ WWALLOC.H | 68 + WWFILE.H | 71 + 332 files changed, 228115 insertions(+) create mode 100644 AADATA.CPP create mode 100644 ABSTRACT.CPP create mode 100644 ABSTRACT.H create mode 100644 ADATA.CPP create mode 100644 AIRCRAFT.CPP create mode 100644 AIRCRAFT.H create mode 100644 ALLOC.CPP create mode 100644 ANIM.CPP create mode 100644 ANIM.H create mode 100644 AUDIO.CPP create mode 100644 AUDIO.H create mode 100644 BASE.CPP create mode 100644 BASE.H create mode 100644 BBDATA.CPP create mode 100644 BDATA.CPP create mode 100644 BFILE.MAK create mode 100644 BUILDING.CPP create mode 100644 BUILDING.H create mode 100644 BULLET.CPP create mode 100644 BULLET.H create mode 100644 CARGO.CPP create mode 100644 CARGO.H create mode 100644 CCDDE.CPP create mode 100644 CCDDE.H create mode 100644 CCFILE.CPP create mode 100644 CCFILE.H create mode 100644 CC_ICON.RC create mode 100644 CC_ICON.RH create mode 100644 CDATA.CPP create mode 100644 CDFILE.CPP create mode 100644 CDFILE.H create mode 100644 CELL.CPP create mode 100644 CELL.H create mode 100644 CHECKBOX.CPP create mode 100644 CHECKBOX.H create mode 100644 CHEKLIST.CPP create mode 100644 CHEKLIST.H create mode 100644 CO-WC32.LNT create mode 100644 COLRLIST.CPP create mode 100644 COLRLIST.H create mode 100644 COMBAT.CPP create mode 100644 COMBUF.CPP create mode 100644 COMBUF.H create mode 100644 COMPAT.H create mode 100644 COMQUEUE.CPP create mode 100644 COMQUEUE.H create mode 100644 CONFDLG.CPP create mode 100644 CONFDLG.H create mode 100644 CONNECT.CPP create mode 100644 CONNECT.H create mode 100644 CONNMGR.H create mode 100644 CONQUER.CPP create mode 100644 CONQUER.DEF create mode 100644 CONQUER.H create mode 100644 CONQUER.ICO create mode 100644 CONQUER.IDE create mode 100644 CONQUER.LNT create mode 100644 CONQUER.PRO create mode 100644 CONQUER.REP create mode 100644 CONQUER.TXT create mode 100644 CONST.CPP create mode 100644 CONTROL.CPP create mode 100644 CONTROL.H create mode 100644 COORD.CPP create mode 100644 COORDA.ASM create mode 100644 CREDITS.CPP create mode 100644 CREDITS.H create mode 100644 CREW.CPP create mode 100644 CREW.H create mode 100644 CWSTUB.C create mode 100644 DDE.CPP create mode 100644 DDE.H create mode 100644 DEBUG.CPP create mode 100644 DEBUG.H create mode 100644 DEFINES.H create mode 100644 DESCDLG.CPP create mode 100644 DESCDLG.H create mode 100644 DIAL8.CPP create mode 100644 DIAL8.H create mode 100644 DIALOG.CPP create mode 100644 DISPLAY.CPP create mode 100644 DISPLAY.H create mode 100644 DOOR.CPP create mode 100644 DOOR.H create mode 100644 DPMI.CPP create mode 100644 DPMI.H create mode 100644 DRIVE.CPP create mode 100644 DRIVE.H create mode 100644 EDIT.CPP create mode 100644 EDIT.H create mode 100644 ENDING.CPP create mode 100644 ENDING.H create mode 100644 EVENT.CPP create mode 100644 EVENT.H create mode 100644 EXPAND.CPP create mode 100644 EXTERNS.H create mode 100644 FACING.CPP create mode 100644 FACING.H create mode 100644 FACTORY.CPP create mode 100644 FACTORY.H create mode 100644 FIELD.CPP create mode 100644 FIELD.H create mode 100644 FINDPATH.CPP create mode 100644 FLASHER.CPP create mode 100644 FLASHER.H create mode 100644 FLY.CPP create mode 100644 FLY.H create mode 100644 FOOT.CPP create mode 100644 FOOT.H create mode 100644 FTIMER.H create mode 100644 FUNCTION.H create mode 100644 FUNCTION.I create mode 100644 FUSE.CPP create mode 100644 FUSE.H create mode 100644 GADGET.CPP create mode 100644 GADGET.H create mode 100644 GAMEDLG.CPP create mode 100644 GAMEDLG.H create mode 100644 GAUGE.CPP create mode 100644 GAUGE.H create mode 100644 GDI.BAT create mode 100644 GLOBALS.CPP create mode 100644 GOPTIONS.CPP create mode 100644 GOPTIONS.H create mode 100644 GSCREEN.CPP create mode 100644 GSCREEN.H create mode 100644 HDATA.CPP create mode 100644 HEADER.MAC create mode 100644 HEAP.CPP create mode 100644 HEAP.H create mode 100644 HELP.CPP create mode 100644 HELP.H create mode 100644 HOTLIST.TXT create mode 100644 HOUSE.CPP create mode 100644 HOUSE.H create mode 100644 IDATA.CPP create mode 100644 INFANTRY.CPP create mode 100644 INFANTRY.H create mode 100644 INI.CPP create mode 100644 INIT.CPP create mode 100644 INTERNET.CPP create mode 100644 INTERPAL.CPP create mode 100644 INTRO.CPP create mode 100644 INTRO.H create mode 100644 IOMAP.CPP create mode 100644 IOOBJ.CPP create mode 100644 IPX.CPP create mode 100644 IPX.H create mode 100644 IPX95.CPP create mode 100644 IPX95.H create mode 100644 IPXADDR.CPP create mode 100644 IPXADDR.H create mode 100644 IPXCONN.CPP create mode 100644 IPXCONN.H create mode 100644 IPXGCONN.CPP create mode 100644 IPXGCONN.H create mode 100644 IPXMGR.CPP create mode 100644 IPXMGR.H create mode 100644 IPXPROT.ASM create mode 100644 IPXREAL.ASM create mode 100644 JAP.BAT create mode 100644 JSHELL.CPP create mode 100644 JSHELL.H create mode 100644 KEYFBUFF.ASM create mode 100644 KEYFBUFF.INC create mode 100644 KEYFRAME.CPP create mode 100644 LAYER.CPP create mode 100644 LAYER.H create mode 100644 LED.H create mode 100644 LINK.CPP create mode 100644 LINK.H create mode 100644 LIST.CPP create mode 100644 LIST.H create mode 100644 LOADDLG.CPP create mode 100644 LOADDLG.H create mode 100644 LOGIC.CPP create mode 100644 LOGIC.H create mode 100644 MAKEFILE create mode 100644 MAKEFILE.BAK create mode 100644 MAP.CPP create mode 100644 MAP.H create mode 100644 MAPEDDLG.CPP create mode 100644 MAPEDIT.CPP create mode 100644 MAPEDIT.H create mode 100644 MAPEDPLC.CPP create mode 100644 MAPEDSEL.CPP create mode 100644 MAPEDTM.CPP create mode 100644 MAPSEL.CPP create mode 100644 MEMCHECK.H create mode 100644 MENUS.CPP create mode 100644 MESSAGE.H create mode 100644 MESSAGE.TXT create mode 100644 MISSION.CPP create mode 100644 MISSION.H create mode 100644 MIXFILE.CPP create mode 100644 MIXFILE.H create mode 100644 MMX.ASM create mode 100644 MONOC.CPP create mode 100644 MONOC.H create mode 100644 MOUSE.CPP create mode 100644 MOUSE.H create mode 100644 MPLAYER.CPP create mode 100644 MSGBOX.CPP create mode 100644 MSGBOX.H create mode 100644 MSGLIST.CPP create mode 100644 MSGLIST.H create mode 100644 NETDLG.CPP create mode 100644 NETDLG.CPP.BAK create mode 100644 NOD.BAT create mode 100644 NOSEQCON.CPP create mode 100644 NOSEQCON.H create mode 100644 NULLCONN.CPP create mode 100644 NULLCONN.H create mode 100644 NULLDLG.CPP create mode 100644 NULLDLG.CPP.BAK create mode 100644 NULLMGR.CPP create mode 100644 NULLMGR.H create mode 100644 OBJECT.CPP create mode 100644 OBJECT.H create mode 100644 ODATA.CPP create mode 100644 OPTIONS.CPP create mode 100644 OPTIONS.H create mode 100644 OPTIONS.LNT create mode 100644 OVERLAY.CPP create mode 100644 OVERLAY.H create mode 100644 PACKET.CPP create mode 100644 PACKET.H create mode 100644 PAGFAULT.ASM create mode 100644 PHONE.H create mode 100644 POWER.CPP create mode 100644 POWER.H create mode 100644 PROFILE.CPP create mode 100644 QUEUE.CPP create mode 100644 QUEUE.H create mode 100644 RADAR.CPP create mode 100644 RADAR.H create mode 100644 RADIO.CPP create mode 100644 RADIO.H create mode 100644 RAND.CPP create mode 100644 RAWFILE.CPP create mode 100644 RAWFILE.H create mode 100644 REAL.H create mode 100644 REGION.H create mode 100644 REG_ICON.ICO create mode 100644 REG_ICON.RC create mode 100644 REG_ICON.RH create mode 100644 REINF.CPP create mode 100644 RULES.MAK create mode 100644 SAVEDLG.H create mode 100644 SAVELOAD.CPP create mode 100644 SCENARIO.CPP create mode 100644 SCORE.CPP create mode 100644 SCORE.H create mode 100644 SCREEN.H create mode 100644 SCROLL.CPP create mode 100644 SCROLL.H create mode 100644 SDATA.CPP create mode 100644 SEQCONN.CPP create mode 100644 SEQCONN.H create mode 100644 SESSION.H create mode 100644 SHAPEBTN.CPP create mode 100644 SHAPEBTN.H create mode 100644 SIDEBAR.CPP create mode 100644 SIDEBAR.H create mode 100644 SLIDER.CPP create mode 100644 SLIDER.H create mode 100644 SMUDGE.CPP create mode 100644 SMUDGE.H create mode 100644 SOUNDDLG.CPP create mode 100644 SOUNDDLG.H create mode 100644 SPECIAL.CPP create mode 100644 SPECIAL.H create mode 100644 STAGE.H create mode 100644 STARTUP.CPP create mode 100644 STATS.CPP create mode 100644 STD.LNT create mode 100644 SUPER.CPP create mode 100644 SUPER.H create mode 100644 SUPPORT.ASM create mode 100644 TAB.CPP create mode 100644 TAB.H create mode 100644 TARCOM.CPP create mode 100644 TARCOM.H create mode 100644 TARGET.CPP create mode 100644 TARGET.H create mode 100644 TCPIP.CPP create mode 100644 TCPIP.H create mode 100644 TDATA.CPP create mode 100644 TEAM.CPP create mode 100644 TEAM.H create mode 100644 TEAMTYPE.CPP create mode 100644 TEAMTYPE.H create mode 100644 TECHNO.CPP create mode 100644 TECHNO.H create mode 100644 TEMP.CPP create mode 100644 TEMPLATE.CPP create mode 100644 TEMPLATE.H create mode 100644 TERRAIN.CPP create mode 100644 TERRAIN.H create mode 100644 TEXTBLIT.H create mode 100644 TEXTBTN.CPP create mode 100644 TEXTBTN.H create mode 100644 THEME.CPP create mode 100644 THEME.H create mode 100644 TOGGLE.CPP create mode 100644 TOGGLE.H create mode 100644 TOOLS/MIXFILE.EXE create mode 100644 TRIGGER.CPP create mode 100644 TRIGGER.H create mode 100644 TURRET.CPP create mode 100644 TURRET.H create mode 100644 TXTLABEL.CPP create mode 100644 TXTLABEL.H create mode 100644 TXTPRNT.ASM create mode 100644 TYPE.H create mode 100644 UALL.BAT create mode 100644 UDATA.CPP create mode 100644 UNIT.CPP create mode 100644 UNIT.H create mode 100644 UTRACKER.CPP create mode 100644 UTRACKER.H create mode 100644 VECTOR.CPP create mode 100644 VECTOR.H create mode 100644 VISUDLG.CPP create mode 100644 VISUDLG.H create mode 100644 WATCOM.H create mode 100644 WIDEFUNC.MAC create mode 100644 WIDEHDR.MAC create mode 100644 WINASM.ASM create mode 100644 WINSTUB.CPP create mode 100644 WWALLOC.H create mode 100644 WWFILE.H diff --git a/AADATA.CPP b/AADATA.CPP new file mode 100644 index 0000000..c7893d2 --- /dev/null +++ b/AADATA.CPP @@ -0,0 +1,798 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\aadata.cpv 2.18 16 Oct 1995 16:49:50 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * name in * + * File Name : AADATA.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : July 22, 1994 * + * * + * Last Update : August 7, 1995 [JLB] * + * Determines * + *---------------------------------------------------------------------------------------------* + * Functions: * + * AircraftTypeClass::AircraftTypeClass -- Constructor for aircraft objects. * + * AircraftTypeClass::Create_And_Place -- Creates and places aircraft using normal game syste* + * AircraftTypeClass::Create_One_Of -- Creates an aircraft object of the appropriate type. * + * AircraftTypeClass::Dimensions -- Fetches the graphic dimensions of the aircraft type. * + * AircraftTypeClass::Display -- Displays a generic version of the aircraft type. * + * AircraftTypeClass::From_Name -- Converts an ASCIIto an aircraft type number. * + * AircraftTypeClass::Max_Pips -- Fetches the maximum number of pips allowed. * + * AircraftTypeClass::Occupy_List -- Returns with occupation list for landed aircraft. * + * AircraftTypeClass::One_Time -- Performs one time initialization of the aircraft type class.* + * AircraftTypeClass::Overlap_List -- the overlap list for a landed aircraft. * + * AircraftTypeClass::Prep_For_Add -- Prepares the scenario editor for adding an aircraft objec* + * AircraftTypeClass::Repair_Cost -- Fetchs the cost per repair step. * + * AircraftTypeClass::Repair_Step -- Fetches the number of health points per repair. * + * AircraftTypeClass::Who_Can_Build_Me -- Determines which object can build the aircraft obje* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +void const * AircraftTypeClass::LRotorData = NULL; +void const * AircraftTypeClass::RRotorData = NULL; + +// A-10 attack plane +static AircraftTypeClass const AttackPlane( + AIRCRAFT_A10, // What kind of aircraft is this. + TXT_A10, // Translated text number for aircraft. + "A10", // INI name of aircraft. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is a leader type? + false, // Does it fire a pair of shots in quick succession? + false, // Is this a typical transport vehicle? + true, // Fixed wing aircraft? + false, // Equipped with a rotor? + false, // Custom rotor sets for each facing? + false, // Can this aircraft land on clear terrain? + false, // Can the aircraft be crushed by a tracked vehicle? + true, // Is it invisible on radar? + false, // Can the player select it so as to give it orders? + true, // Can it be assigned as a target for attack. + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Can it be repaired in a repair facility? + false, // Can the player construct or order this unit? + true, // Is there a crew inside? + 3, // Number of shots it has (default). + 60, // The strength of this unit. + 0, // The range that it reveals terrain around itself. + 800, // Credit cost to construct. + 0, // The scenario this becomes available. + 10,1, // Risk, reward when calculating AI. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // Who can own this aircraft type. + WEAPON_NAPALM,WEAPON_NONE, + ARMOR_ALUMINUM, // Armor type of this aircraft. + MPH_FAST, // Maximum speed of aircraft. + 5, // Rate of turn. + MISSION_HUNT // Default mission for aircraft. +); + +// Transport helicopter. +static AircraftTypeClass const TransportHeli( + AIRCRAFT_TRANSPORT, // What kind of aircraft is this. + TXT_TRANS, // Translated text number for aircraft. + "TRAN", // INI name of aircraft. + 6, // Build level. + STRUCTF_HELIPAD, // Building prerequisite. + false, // Is a leader type? + false, // Does it fire a pair of shots in quick succession? + true, // Is this a typical transport vehicle? + false, // Fixed wing aircraft? + true, // Equipped with a rotor? + true, // Custom rotor sets for each facing? + true, // Can this aircraft land on clear terrain? + false, // Can the aircraft be crushed by a tracked vehicle? + true, // Is it invisible on radar? + true, // Can the player select it so as to give it orders? + true, // Can it be assigned as a target for attack. + false, // Is it insignificant (won't be announced)? + false, // Theater specific graphic image? + false, // Is it equipped with a combat turret? + false, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + 0, // Number of shots it has (default). + 90, // The strength of this unit. + 0, // The range that it reveals terrain around itself. + 1500, // Credit cost to construct. + 98, // The scenario this becomes available. + 10,80, // Risk, reward when calculating AI. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD| + HOUSEF_GOOD, // Who can own this aircraft type. + WEAPON_NONE,WEAPON_NONE, + ARMOR_ALUMINUM, // Armor type of this aircraft. + MPH_MEDIUM_FAST, // Maximum speed of aircraft. + 5, // Rate of turn. + MISSION_HUNT // Default mission for aircraft. +); + +// Apache attach helicopter. +static AircraftTypeClass const AttackHeli( + AIRCRAFT_HELICOPTER, // What kind of aircraft is this. + TXT_HELI, // Translated text number for aircraft. + "HELI", // INI name of aircraft. + 6, // Build level. + STRUCTF_HELIPAD, // Building prerequisite. + true, // Is a leader type? + true, // Does it fire a pair of shots in quick succession? + false, // Is this a typical transport vehicle? + false, // Fixed wing aircraft? + true, // Equipped with a rotor? + false, // Custom rotor sets for each facing? + false, // Can this aircraft land on clear terrain? + false, // Can the aircraft be crushed by a tracked vehicle? + true, // Is it invisible on radar? + true, // Can the player select it so as to give it orders? + true, // Can it be assigned as a target for attack. + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + 15, // Number of shots it has (default). + 125, // The strength of this unit. + 0, // The range that it reveals terrain around itself. + 1200, // Credit cost to construct. + 10, // The scenario this becomes available. + 10,80, // Risk, reward when calculating AI. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // Who can own this aircraft type. + WEAPON_CHAIN_GUN,WEAPON_NONE, + ARMOR_STEEL, // Armor type of this aircraft. + MPH_FAST, // Maximum speed of aircraft. + 4, // Rate of turn. + MISSION_HUNT // Default mission for aircraft. +); + + +// Orca attack helicopter. +static AircraftTypeClass const OrcaHeli( + AIRCRAFT_ORCA, // What kind of aircraft is this. + TXT_ORCA, // Translated text number for aircraft. + "ORCA", // INI name of aircraft. + 6, // Build level. + STRUCTF_HELIPAD, // Building prerequisite. + true, // Is a leader type? + true, // Does it fire a pair of shots in quick succession? + false, // Is this a typical transport vehicle? + false, // Fixed wing aircraft? + false, // Equipped with a rotor? + false, // Custom rotor sets for each facing? + false, // Can this aircraft land on clear terrain? + false, // Can the aircraft be crushed by a tracked vehicle? + true, // Is it invisible on radar? + true, // Can the player select it so as to give it orders? + true, // Can it be assigned as a target for attack. + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + 6, // Number of shots it has (default). + 125, // The strength of this unit. + 0, // The range that it reveals terrain around itself. + 1200, // Credit cost to construct. + 10, // The scenario this becomes available. + 10,80, // Risk, reward when calculating AI. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD, // Who can own this aircraft type. + WEAPON_DRAGON,WEAPON_NONE, + ARMOR_STEEL, // Armor type of this aircraft. + MPH_FAST, // Maximum speed of aircraft. + 4, // Rate of turn. + MISSION_HUNT // Default mission for aircraft. +); + + +// C-17 transport plane. +static AircraftTypeClass const CargoPlane( + AIRCRAFT_CARGO, // What kind of aircraft is this. + TXT_C17, // Translated text number for aircraft. + "C17", // INI name of aircraft. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is a leader type? + false, // Does it fire a pair of shots in quick succession? + true, // Is this a typical transport vehicle? + true, // Fixed wing aircraft? + false, // Equipped with a rotor? + false, // Custom rotor sets for each facing? + false, // Can this aircraft land on clear terrain? + false, // Can the aircraft be crushed by a tracked vehicle? + true, // Is it invisible on radar? + false, // Can the player select it so as to give it orders? + false, // Can it be assigned as a target for attack. + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Can it be repaired in a repair facility? + false, // Can the player construct or order this unit? + true, // Is there a crew inside? + 0, // Number of shots it has (default). + 25, // The strength of this unit. + 0, // The range that it reveals terrain around itself. + 800, // Credit cost to construct. + 0, // The scenario this becomes available. + 10,1, // Risk, reward when calculating AI. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // Who can own this aircraft type. + WEAPON_NONE,WEAPON_NONE, + ARMOR_ALUMINUM, // Armor type of this aircraft. + MPH_FAST, // Maximum speed of aircraft. + 5, // Rate of turn. + MISSION_HUNT // Default mission for aircraft. +); + + +AircraftTypeClass const * const AircraftTypeClass::Pointers[AIRCRAFT_COUNT] = { + &TransportHeli, + &AttackPlane, + &AttackHeli, + &CargoPlane, + &OrcaHeli, +}; + + +/*********************************************************************************************** + * AircraftTypeClass::AircraftTypeClass -- Constructor for aircraft objects. * + * * + * This is the constructor for the aircraft object. * + * * + * INPUT: see below... * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +AircraftTypeClass::AircraftTypeClass( + AircraftType airtype, + int name, + char const *ininame, + unsigned char level, + long pre, + bool is_leader, + bool is_twoshooter, + bool is_transporter, + bool is_fixedwing, + bool is_rotorequipped, + bool is_rotorcustom, + bool is_landable, + bool is_crushable, + bool is_stealthy, + bool is_selectable, + bool is_legal_target, + bool is_insignificant, + bool is_immune, + bool is_theater, + bool is_repairable, + bool is_buildable, + bool is_crew, + int ammo, + unsigned short strength, + int sightrange, + int cost, + int scenario, + int risk, + int reward, + int ownable, + WeaponType primary, + WeaponType secondary, + ArmorType armor, + MPHType maxspeed, + int rot, + MissionType deforder) : + TechnoTypeClass(name, + ininame, + level, + pre, + is_leader, + false, + false, + is_transporter, + false, + is_crushable, + is_stealthy, + is_selectable, + is_legal_target, + is_insignificant, + is_immune, + is_theater, + is_twoshooter, + false, + is_repairable, + is_buildable, + is_crew, + ammo, + strength, + maxspeed, + sightrange, + cost, + scenario, + risk, + reward, + ownable, + primary, + secondary, + armor) +{ + IsRotorEquipped = is_rotorequipped; + IsRotorCustom = is_rotorcustom; + IsLandable = is_landable; + IsFixedWing = is_fixedwing; + Type = airtype; + ROT = rot; + Mission = deforder; +} + + +/*********************************************************************************************** + * AircraftTypeClass::From_Name -- Converts an ASCII name into an aircraft type number. * + * * + * This routine is used to convert an ASCII representation of an aircraft into the * + * matching aircraft type number. This is used by the scenario INI reader code. * + * * + * INPUT: name -- Pointer to ASCII name to translate. * + * * + * OUTPUT: Returns the aircraft type number that matches the ASCII name provided. If no * + * match could be found, then AIRCRAFT_NONE is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +AircraftType AircraftTypeClass::From_Name(char const *name) +{ + if (name) { + for (AircraftType classid = AIRCRAFT_FIRST; classid < AIRCRAFT_COUNT; classid++) { + if (stricmp(Pointers[classid]->IniName, name) == 0) { + return(classid); + } + } + } + return(AIRCRAFT_NONE); +} + + +/*********************************************************************************************** + * AircraftTypeClass::One_Time -- Performs one time initialization of the aircraft type class. * + * * + * This routine is used to perform the onetime initialization of the aircraft type. This * + * includes primarily the shape and other graphic data loading. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This goes to disk and also must only be called ONCE. * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +void AircraftTypeClass::One_Time(void) +{ + AircraftType index; + + for (index = AIRCRAFT_FIRST; index < AIRCRAFT_COUNT; index++) { + char fullname[_MAX_FNAME+_MAX_EXT]; + AircraftTypeClass const & uclass = As_Reference(index); + + /* + ** Fetch the supporting data files for the unit. + */ + char buffer[_MAX_FNAME]; + if ( Get_Resolution_Factor() ) { + sprintf(buffer, "%sICNH", uclass.IniName); + } else { + sprintf(buffer, "%sICON", uclass.IniName); + } + _makepath(fullname, NULL, NULL, buffer, ".SHP"); + ((void const *&)uclass.CameoData) = MixFileClass::Retrieve(fullname); + + /* + ** Generic shape for all houses load method. + */ + _makepath(fullname, NULL, NULL, uclass.IniName, ".SHP"); + ((void const *&)uclass.ImageData) = MixFileClass::Retrieve(fullname); + } + + LRotorData = MixFileClass::Retrieve("LROTOR.SHP"); + RRotorData = MixFileClass::Retrieve("RROTOR.SHP"); +} + + +/*********************************************************************************************** + * AircraftTypeClass::Create_One_Of -- Creates an aircraft object of the appropriate type. * + * * + * This routine is used to create an aircraft object that matches the aircraft type. It * + * serves as a shortcut to creating an object using the "new" operator and "if" checks. * + * * + * INPUT: house -- The house owner of the aircraft that is to be created. * + * * + * OUTPUT: Returns with a pointer to the aircraft created. If the aircraft could not be * + * created, then a NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +ObjectClass * AircraftTypeClass::Create_One_Of(HouseClass * house) const +{ + return(new AircraftClass(Type, house->Class->House)); +} + + +#ifdef SCENARIO_EDITOR +/*********************************************************************************************** + * AircraftTypeClass::Prep_For_Add -- Prepares the scenario editor for adding an aircraft objec* + * * + * This routine is used by the scenario editor to prepare for the adding operation. It * + * builds a list of pointers to object types that can be added. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +void AircraftTypeClass::Prep_For_Add(void) +{ + for (AircraftType index = AIRCRAFT_FIRST; index < AIRCRAFT_COUNT; index++) { + if (As_Reference(index).Get_Image_Data()) { + Map.Add_To_List(&As_Reference(index)); + } + } +} + + +/*********************************************************************************************** + * AircraftTypeClass::Display -- Displays a generic version of the aircraft type. * + * * + * This routine is used by the scenario editor to display a generic version of the object * + * type. This is displayed in the object selection dialog box. * + * * + * INPUT: x,y -- The coordinates to draw the aircraft at (centered). * + * * + * window -- The window to base the coordinates upon. * + * * + * house -- The owner of this generic aircraft. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +void AircraftTypeClass::Display(int x, int y, WindowNumberType window, HousesType house) const +{ + int shape = 0; + void const * ptr = Get_Cameo_Data(); + if (!ptr) { + ptr = Get_Image_Data(); + shape = 5; + } + CC_Draw_Shape(ptr, shape, x, y, window, SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_FADING, HouseClass::As_Pointer(house)->Remap_Table(false, true)); +} +#endif + + +/*********************************************************************************************** + * AircraftTypeClass::Occupy_List -- Returns with occupation list for landed aircraft. * + * * + * This determines the occupation list for the aircraft (if it was landed). * + * * + * INPUT: placement -- Is this for placement legality checking only? The normal condition * + * is for marking occupation flags. * + * * + * OUTPUT: Returns with a pointer to a cell offset occupation list for the aircraft. * + * * + * WARNINGS: This occupation list is only valid if the aircraft is landed. * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +short const * AircraftTypeClass::Occupy_List(bool) const +{ + static short const _list[] = {0, REFRESH_EOL}; + return(_list); +} + + +/*********************************************************************************************** + * AircraftTypeClass::Overlap_List -- Determines the overlap list for a landed aircraft. * + * * + * This routine figures out the overlap list for the aircraft as if it were landed. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the cell offset overlap list for the aircraft. * + * * + * WARNINGS: This overlap list is only valid when the aircraft is landed. * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +short const * AircraftTypeClass::Overlap_List(void) const +{ + static short const _list[] = {-(MAP_CELL_W-1), -MAP_CELL_W, -(MAP_CELL_W+1), -1, 1, (MAP_CELL_W-1), MAP_CELL_W, (MAP_CELL_W+1), REFRESH_EOL}; + return(_list); +} + + +/*********************************************************************************************** + * AircraftTypeClass::Who_Can_Build_Me -- Determines which object can build the aircraft objec * + * * + * Use this routine to determine which object (factory) can build the aircraft. It * + * determines this by scanning through the available factories, looking for one that is * + * of the proper ownership and is available. * + * * + * INPUT: intheory -- When true, it doesn't consider if the factory is currently busy. It * + * only considers that it is the right type. * + * * + * legal -- Should building prerequisite legality checks be performed as well? * + * For building placements, this is usually false. For sidebar button * + * adding, this is usually true. * + * * + * house -- The house of the desired aircraft to be built. * + * * + * OUTPUT: Returns with a pointer to the object that can build the aircraft. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/30/1994 JLB : Created. * + *=============================================================================================*/ +BuildingClass * AircraftTypeClass::Who_Can_Build_Me(bool , bool legal, HousesType house) const +{ + BuildingClass * anybuilding = NULL; + for (int index = 0; index < Buildings.Count(); index++) { + BuildingClass * building = Buildings.Ptr(index); + + if (building && + !building->IsInLimbo && + building->House->Class->House == house && + building->Mission != MISSION_DECONSTRUCTION && + ((1L << building->ActLike) & Ownable) && + (!legal || building->House->Can_Build(Type, building->ActLike)) && + building->Class->ToBuild == RTTI_AIRCRAFTTYPE) { + + if (building->IsLeader) return(building); + anybuilding = building; + } + } + return(anybuilding); +} + + +/*********************************************************************************************** + * AircraftTypeClass::Repair_Cost -- Fetchs the cost per repair step. * + * * + * This routine will return the cost for every repair step. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the credit expense for every repair step. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/26/1995 JLB : Created. * + *=============================================================================================*/ +int AircraftTypeClass::Repair_Cost(void) const +{ + return(Fixed_To_Cardinal(Cost/(MaxStrength/REPAIR_STEP), REPAIR_PERCENT)); +} + + +/*********************************************************************************************** + * AircraftTypeClass::Repair_Step -- Fetches the number of health points per repair. * + * * + * For every repair event, the returned number of health points is acquired. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of health points to recover each repair step. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/26/1995 JLB : Created. * + *=============================================================================================*/ +int AircraftTypeClass::Repair_Step(void) const +{ + return(REPAIR_STEP); +} + + +/*********************************************************************************************** + * AircraftTypeClass::Max_Pips -- Fetches the maximum number of pips allowed. * + * * + * Use this routine to retrieve the maximum pip count allowed for this aircraft. This is * + * the maximum number of passengers. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the maximum number of pips for this aircraft. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/26/1995 JLB : Created. * + *=============================================================================================*/ +int AircraftTypeClass::Max_Pips(void) const +{ + if (IsTransporter) { + return(Max_Passengers()); + } else { + if (Primary != WEAPON_NONE) { + return(5); + } + } + return(0); +} + + +/*********************************************************************************************** + * AircraftTypeClass::Create_And_Place -- Creates and places aircraft using normal game system * + * * + * This routine is used to create and place an aircraft through the normal game system. * + * Since creation of aircraft in this fashion is prohibited, this routine does nothing. * + * * + * INPUT: na * + * * + * OUTPUT: Always returns a failure code (false). * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/07/1995 JLB : Created. * + *=============================================================================================*/ +bool AircraftTypeClass::Create_And_Place(CELL, HousesType) const +{ + return(false); +} + + + + + +/*********************************************************************************************** + * ATC::Init -- load up terrain set dependant sidebar icons * + * * + * * + * * + * INPUT: theater type * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 4/25/96 0:33AM ST : Created * + *=============================================================================================*/ + +void AircraftTypeClass::Init(TheaterType theater) +{ + if (theater != LastTheater){ + if ( Get_Resolution_Factor() ) { + + AircraftType index; + char buffer[_MAX_FNAME]; + char fullname[_MAX_FNAME+_MAX_EXT]; + void const * cameo_ptr; + + for (index = AIRCRAFT_FIRST; index < AIRCRAFT_COUNT; index++) { + AircraftTypeClass const & uclass = As_Reference(index); + + ((void const *&)uclass.CameoData) = NULL; + + sprintf(buffer, "%.4sICNH", uclass.IniName); + _makepath (fullname, NULL, NULL, buffer, Theaters[theater].Suffix); + cameo_ptr = MixFileClass::Retrieve(fullname); + if (cameo_ptr){ + ((void const *&)uclass.CameoData) = cameo_ptr; + } + } + } + } +} + + + + + +/*********************************************************************************************** + * AircraftTypeClass::Dimensions -- Fetches the graphic dimensions of the aircraft type. * + * * + * This routine will fetch the pixel dimensions of this aircraft type. These dimensions * + * are used to control map refresh and select box rendering. * + * * + * INPUT: width -- Reference to variable that will be filled in with aircraft width. * + * * + * height -- Reference to variable that will be filled in with aircraft height. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/07/1995 JLB : Created. * + *=============================================================================================*/ +void AircraftTypeClass::Dimensions(int &width, int &height) const +{ + width = 21; + height = 20; +} + + +RTTIType AircraftTypeClass::What_Am_I(void) const {return RTTI_AIRCRAFTTYPE;}; diff --git a/ABSTRACT.CPP b/ABSTRACT.CPP new file mode 100644 index 0000000..f470692 --- /dev/null +++ b/ABSTRACT.CPP @@ -0,0 +1,109 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\abstract.cpv 2.20 16 Oct 1995 16:49:04 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : ABSTRACT.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/26/95 * + * * + * Last Update : May 22, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * AbstractClass::Distance -- Determines distance to target. * + * AbstractTypeClass::AbstractTypeClass -- Constructor for abstract type objects. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * AbstractClass::Distance -- Determines distance to target. * + * * + * This will determine the distance (direct line) to the target. The distance is in * + * 'leptons'. This routine is typically used for weapon range checks. * + * * + * INPUT: target -- The target to determine range to. * + * * + * OUTPUT: Returns with the range to the specified target (in leptons). * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/17/1994 JLB : Created. * + *=============================================================================================*/ +int AbstractClass::Distance(TARGET target) const +{ + /* + ** Should subtract a fudge-factor distance for building targets. + */ + BuildingClass *obj = As_Building(target); + int dist = Distance(As_Coord(target)); + + /* + ** If the object is a building the adjust it by the average radius + ** of the object. + */ + if (obj) { + dist -= ((obj->Class->Width() + obj->Class->Height()) * (0x100 / 4)); + if (dist < 0) dist = 0; + } + + /* + ** Return the distance to the target + */ + return(dist); +} + + +/*********************************************************************************************** + * AbstractTypeClass::AbstractTypeClass -- Constructor for abstract type objects. * + * * + * This is the constructor for AbstractTypeClass objects. It initializes the INI name and * + * the text name for this object type. * + * * + * INPUT: name -- Text number for the full name of the object. * + * * + * ini -- The ini name for this object type. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1995 JLB : Created. * + *=============================================================================================*/ +AbstractTypeClass::AbstractTypeClass(int name, char const * ini) +{ + Name = name; + strncpy((char *)IniName, ini, sizeof(IniName)); + ((char &)IniName[sizeof(IniName)-1]) = '\0'; +} + +RTTIType AbstractTypeClass::What_Am_I(void) const {return RTTI_ABSTRACTTYPE;}; +COORDINATE AbstractTypeClass::Coord_Fixup(COORDINATE coord) const {return coord;} +int AbstractTypeClass::Full_Name(void) const {return Name;}; +unsigned short AbstractTypeClass::Get_Ownable(void) const {return 0xffff;}; diff --git a/ABSTRACT.H b/ABSTRACT.H new file mode 100644 index 0000000..944781a --- /dev/null +++ b/ABSTRACT.H @@ -0,0 +1,109 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\abstract.h_v 2.20 16 Oct 1995 16:46:30 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : ABSTRACT.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/26/95 * + * * + * Last Update : January 26, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef ABSTRACT_H +#define ABSTRACT_H + +DirType Direction(CELL cell1, CELL cell2); +DirType Direction(COORDINATE coord1, COORDINATE coord2); +int Distance(COORDINATE coord1, COORDINATE coord2); +int Distance(CELL coord1, CELL coord2); +COORDINATE As_Coord(TARGET target); + +class AbstractTypeClass; + +class AbstractClass +{ + public: + + /* + ** The coordinate location of the unit. For vehicles, this is the center + ** point. For buildings, it is the upper left corner. + */ + COORDINATE Coord; + + /* + ** The actual object ram-space is located in arrays in the data segment. This flag + ** is used to indicate which objects are free to be reused and which are currently + ** in use by the game. + */ + unsigned IsActive:1; + + /*----------------------------------------------------------------------------------- + ** Constructor & destructors. + */ + AbstractClass(void) {Coord = 0L;}; + virtual ~AbstractClass(void) {}; + + /* + ** Query functions. + */ + virtual HousesType Owner(void) const {return HOUSE_NONE;}; + + /* + ** Coordinate query support functions. + */ + virtual COORDINATE Center_Coord(void) const {return Coord;}; + virtual COORDINATE Target_Coord(void) const {return Coord;}; + + /* + ** Coordinate inquiry functions. These are used for both display and + ** combat purposes. + */ + DirType Direction(AbstractClass const * object) const {return ::Direction(Center_Coord(), object->Target_Coord());}; + DirType Direction(COORDINATE coord) const {return ::Direction(Center_Coord(), coord);}; + DirType Direction(TARGET target) const {return ::Direction(Center_Coord(), As_Coord(target));}; + DirType Direction(CELL cell) const {return ::Direction(Coord_Cell(Center_Coord()), cell);}; + int Distance(TARGET target) const; + int Distance(COORDINATE coord) const {return ::Distance(Center_Coord(), coord);}; + int Distance(CELL cell) const {return ::Distance(Coord_Cell(Center_Coord()), cell);}; + int Distance(AbstractClass const * object) const {return ::Distance(Center_Coord(), object->Target_Coord());}; + + /* + ** Object entry and exit from the game system. + */ + virtual MoveType Can_Enter_Cell(CELL , FacingType = FACING_NONE) const {return MOVE_OK;}; + + /* + ** AI. + */ + virtual void AI(void) {}; + +}; + + +#endif diff --git a/ADATA.CPP b/ADATA.CPP new file mode 100644 index 0000000..b2aace5 --- /dev/null +++ b/ADATA.CPP @@ -0,0 +1,2429 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\adata.cpv 2.18 16 Oct 1995 16:49:32 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : ADATA.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 30, 1994 * + * * + * Last Update : August 23, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * AnimTypeClass::One_Time -- Performs one time action for animation types. * + * AnimTypeClass::AnimTypeClass -- Constructor for animation types. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +// Dinosaur death animations +static AnimTypeClass const TricDie( + ANIM_TRIC_DIE, // Animation number. + "TRIC", // Data name of animation. + 32, // Maximum dimension of animation. + 4, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + true, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 2, // Delay between frames. + 176, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + 20, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const TRexDie( + ANIM_TREX_DIE, // Animation number. + "TREX", // Data name of animation. + 48, // Maximum dimension of animation. + 4, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + true, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 2, // Delay between frames. + 144, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + 40, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const StegDie( + ANIM_STEG_DIE, // Animation number. + "STEG", // Data name of animation. + 33, // Maximum dimension of animation. + 4, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + true, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 2, // Delay between frames. + 176, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + 22, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const RaptDie( + ANIM_RAPT_DIE, // Animation number. + "RAPT", // Data name of animation. + 24, // Maximum dimension of animation. + 4, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + true, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 2, // Delay between frames. + 144, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + 40, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const SAMN( + ANIM_SAM_N, // Animation number. + "SAMFIRE", // Data name of animation. + 55, // Maximum dimension of animation. + 4, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 18*0, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + 18, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const SAMNW( + ANIM_SAM_NW, // Animation number. + "SAMFIRE", // Data name of animation. + 55, // Maximum dimension of animation. + 22, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 18*1, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + 18, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const SAMW( + ANIM_SAM_W, // Animation number. + "SAMFIRE", // Data name of animation. + 55, // Maximum dimension of animation. + 40, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 18*2, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + 18, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const SAMSW( + ANIM_SAM_SW, // Animation number. + "SAMFIRE", // Data name of animation. + 55, // Maximum dimension of animation. + 58, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 18*3, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + 18, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const SAMS( + ANIM_SAM_S, // Animation number. + "SAMFIRE", // Data name of animation. + 55, // Maximum dimension of animation. + 76, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 18*4, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + 18, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const SAMSE( + ANIM_SAM_SE, // Animation number. + "SAMFIRE", // Data name of animation. + 55, // Maximum dimension of animation. + 94, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 18*5, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + 18, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const SAME( + ANIM_SAM_E, // Animation number. + "SAMFIRE", // Data name of animation. + 55, // Maximum dimension of animation. + 112, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 18*6, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + 18, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const SAMNE( + ANIM_SAM_NE, // Animation number. + "SAMFIRE", // Data name of animation. + 55, // Maximum dimension of animation. + 130, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 18*7, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + 18, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const LZSmoke( + ANIM_LZ_SMOKE, // Animation number. + "SMOKLAND", // Data name of animation. + 32, // Maximum dimension of animation. + 72, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 72, // Loop start frame number. + 91, // Ending frame of loop back. + -1, // Number of animation stages. + 255, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +/* +** Flammable object burning animations. Primarily used on trees and buildings. +*/ +static AnimTypeClass const BurnSmall( + ANIM_BURN_SMALL, // Animation number. + "BURN-S", // Data name of animation. + 11, // Maximum dimension of animation. + 13, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0008, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 30, // Loop start frame number. + 62, // Ending frame of loop back. + -1, // Number of animation stages. + 4, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const BurnMed( + ANIM_BURN_MED, // Animation number. + "BURN-M", // Data name of animation. + 14, // Maximum dimension of animation. + 13, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0010, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 30, // Loop start frame number. + 62, // Ending frame of loop back. + -1, // Number of animation stages. + 4, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const BurnBig( + ANIM_BURN_BIG, // Animation number. + "BURN-L", // Data name of animation. + 23, // Maximum dimension of animation. + 13, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + true, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0018, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 30, // Loop start frame number. + 62, // Ending frame of loop back. + -1, // Number of animation stages. + 4, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +/* +** Flammable object burning animations that trail into smoke. Used for +** buildings and the gunboat. +*/ +static AnimTypeClass const OnFireSmall( + ANIM_ON_FIRE_SMALL, // Animation number. + "BURN-S", // Data name of animation. + 11, // Maximum dimension of animation. + 13, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0008, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 30, // Loop start frame number. + 62, // Ending frame of loop back. + -1, // Number of animation stages. + 4, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_SMOKE_M +); +static AnimTypeClass const OnFireMed( + ANIM_ON_FIRE_MED, // Animation number. + "BURN-M", // Data name of animation. + 14, // Maximum dimension of animation. + 13, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0010, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 30, // Loop start frame number. + 62, // Ending frame of loop back. + -1, // Number of animation stages. + 4, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_ON_FIRE_SMALL +); +static AnimTypeClass const OnFireBig( + ANIM_ON_FIRE_BIG, // Animation number. + "BURN-L", // Data name of animation. + 23, // Maximum dimension of animation. + 13, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + true, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0018, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 30, // Loop start frame number. + 62, // Ending frame of loop back. + -1, // Number of animation stages. + 4, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_ON_FIRE_MED +); + +/* +** Flame thrower animations. These are direction specific. +*/ +static AnimTypeClass const FlameN( + ANIM_FLAME_N, // Animation number. + "FLAME-N", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + true, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const FlameNW( + ANIM_FLAME_NW, // Animation number. + "FLAME-NW", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + true, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const FlameW( + ANIM_FLAME_W, // Animation number. + "FLAME-W", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + true, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const FlameSW( + ANIM_FLAME_SW, // Animation number. + "FLAME-SW", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + true, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const FlameS( + ANIM_FLAME_S, // Animation number. + "FLAME-S", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + true, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const FlameSE( + ANIM_FLAME_SE, // Animation number. + "FLAME-SE", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + true, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const FlameE( + ANIM_FLAME_E, // Animation number. + "FLAME-E", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + true, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const FlameNE( + ANIM_FLAME_NE, // Animation number. + "FLAME-NE", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + true, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +/* +** Chem sprayer animations. These are direction specific. +*/ +static AnimTypeClass const ChemN( + ANIM_CHEM_N, // Animation number. + "CHEM-N", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const ChemNW( + ANIM_CHEM_NW, // Animation number. + "CHEM-NW", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const ChemW( + ANIM_CHEM_W, // Animation number. + "CHEM-W", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const ChemSW( + ANIM_CHEM_SW, // Animation number. + "CHEM-SW", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const ChemS( + ANIM_CHEM_S, // Animation number. + "CHEM-S", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const ChemSE( + ANIM_CHEM_SE, // Animation number. + "CHEM-SE", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const ChemE( + ANIM_CHEM_E, // Animation number. + "CHEM-E", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const ChemNE( + ANIM_CHEM_NE, // Animation number. + "CHEM-NE", // Data name of animation. + 0, // Maximum dimension of animation. + 9, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const Grenade( + ANIM_GRENADE, // Animation number. + "VEH-HIT2", // Data name of animation. + 21, // Maximum dimension of animation. + 1, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + true, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_GUN20, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const FBall1( + ANIM_FBALL1, // Animation number. + "FBALL1", // Data name of animation. + 67, // Maximum dimension of animation. + 6, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + true, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_XPLOS, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const Frag1( + ANIM_FRAG1, // Animation number. + "FRAG1", // Data name of animation. + 45, // Maximum dimension of animation. + 3, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + true, // Forms a crater? + true, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_XPLOBIG4, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const Frag3( + ANIM_FRAG2, // Animation number. + "FRAG3", // Data name of animation. + 41, // Maximum dimension of animation. + 3, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + true, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_XPLOBIG6, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const VehHit1( + ANIM_VEH_HIT1, // Animation number. + "VEH-HIT1", // Data name of animation. + 30, // Maximum dimension of animation. + 4, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + true, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_XPLOS, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const VehHit2( + ANIM_VEH_HIT2, // Animation number. + "VEH-HIT2", // Data name of animation. + 21, // Maximum dimension of animation. + 1, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + true, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_XPLOS, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const VehHit3( + ANIM_VEH_HIT3, // Animation number. + "VEH-HIT3", // Data name of animation. + 19, // Maximum dimension of animation. + 3, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + true, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_XPLOS, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const ArtExp1( + ANIM_ART_EXP1, // Animation number. + "ART-EXP1", // Data name of animation. + 41, // Maximum dimension of animation. + 1, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + true, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_XPLOSML2, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const Napalm1( + ANIM_NAPALM1, // Animation number. + "NAPALM1", // Data name of animation. + 21, // Maximum dimension of animation. + 5, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + true, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_FLAMER1, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const Napalm2( + ANIM_NAPALM2, // Animation number. + "NAPALM2", // Data name of animation. + 41, // Maximum dimension of animation. + 5, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + true, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_FLAMER1, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const Napalm3( + ANIM_NAPALM3, // Animation number. + "NAPALM3", // Data name of animation. + 78, // Maximum dimension of animation. + 5, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + true, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_FLAMER1, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const SmokePuff( + ANIM_SMOKE_PUFF, // Animation number. + "SMOKEY", // Data name of animation. + 24, // Maximum dimension of animation. + 2, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + true, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const Piff( + ANIM_PIFF, // Animation number. + "PIFF", // Data name of animation. + 13, // Maximum dimension of animation. + 1, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const PiffPiff( + ANIM_PIFFPIFF, // Animation number. + "PIFFPIFF", // Data name of animation. + 20, // Maximum dimension of animation. + 2, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const Fire3( + ANIM_FIRE_SMALL, // Animation number. + "FIRE3", // Data name of animation. + 23, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0008, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 2, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const Fire1( + ANIM_FIRE_MED2, // Animation number. + "FIRE1", // Data name of animation. + 23, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + true, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0010, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 3, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const Fire4( + ANIM_FIRE_TINY, // Animation number. + "FIRE4", // Data name of animation. + 7, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0008, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 3, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const Fire2( + ANIM_FIRE_MED, // Animation number. + "FIRE2", // Data name of animation. + 23, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + true, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0010, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 3, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const OilFieldBurn( + ANIM_OILFIELD_BURN, // Animation number. + "FLMSPT", // Data name of animation. + 42, // Maximum dimension of animation. + 58, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 33, // Loop start frame number. + 99, // Ending frame of loop back. + 66, // Number of animation stages. + 65535, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const Gunfire( + ANIM_MUZZLE_FLASH, // Animation number. + "GUNFIRE", // Data name of animation. + 16, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + true, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Number of times the animation loops. +// 2, // Number of times the animation loops. + 1, // Number of animation stages. +// 2, // Number of animation stages. + 1, // Ending frame of loop back. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +#ifdef NEVER +static AnimTypeClass const E1RotFire( + ANIM_E1_ROT_FIRE, // Animation number. + "E1ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 28, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const E1RotGrenade( + ANIM_E1_ROT_GRENADE, // Animation number. + "E1ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 24, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const E1RotGun( + ANIM_E1_ROT_GUN, // Animation number. + "E1ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 16, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const E1RotExp( + ANIM_E1_ROT_EXP, // Animation number. + "E1ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 20, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const E2RotFire( + ANIM_E2_ROT_FIRE, // Animation number. + "E2ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 28, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const E2RotGrenade( + ANIM_E2_ROT_GRENADE, // Animation number. + "E2ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 24, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const E2RotGun( + ANIM_E2_ROT_GUN, // Animation number. + "E2ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 16, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const E2RotExp( + ANIM_E2_ROT_EXP, // Animation number. + "E2ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 20, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const E3RotFire( + ANIM_E3_ROT_FIRE, // Animation number. + "E3ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 28, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const E3RotGrenade( + ANIM_E3_ROT_GRENADE, // Animation number. + "E3ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 24, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const E3RotGun( + ANIM_E3_ROT_GUN, // Animation number. + "E3ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 16, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const E3RotExp( + ANIM_E3_ROT_EXP, // Animation number. + "E3ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 20, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const E4RotFire( + ANIM_E4_ROT_FIRE, // Animation number. + "E4ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 28, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const E4RotGrenade( + ANIM_E4_ROT_GRENADE, // Animation number. + "E4ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 24, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const E4RotGun( + ANIM_E4_ROT_GUN, // Animation number. + "E4ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 16, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const E4RotExp( + ANIM_E4_ROT_EXP, // Animation number. + "E4ROT", // Data name of animation. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Is a flat on the ground animation? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 30, // Delay between frames. + 20, // Starting frame number. + 0, // Loop start frame number. + 0, // Loopback frame number. + 4, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +#endif + +static AnimTypeClass const SmokeM( + ANIM_SMOKE_M, // Animation number. + "SMOKE_M", // Data name of animation. + 28, // Maximum dimension of animation. + 30, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 67, // Loop start frame number. + -1, // Loopback frame number. + -1, // Number of animation stages. + 6, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +/* +** Mini-gun fire effect -- used by guard towers. +*/ +static AnimTypeClass const GUNN( + ANIM_GUN_N, // Animation number. + "MINIGUN", // Data name of animation. + 18, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Number of times the animation loops. + 6, // Number of animation stages. + 0, // Ending frame of loop back. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const GUNNW( + ANIM_GUN_NW, // Animation number. + "MINIGUN", // Data name of animation. + 18, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 6, // Starting frame number. + 0, // Loop start frame number. + 0, // Number of times the animation loops. + 6, // Number of animation stages. + 0, // Ending frame of loop back. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const GUNW( + ANIM_GUN_W, // Animation number. + "MINIGUN", // Data name of animation. + 18, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 12, // Starting frame number. + 0, // Loop start frame number. + 0, // Number of times the animation loops. + 6, // Number of animation stages. + 0, // Ending frame of loop back. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const GUNSW( + ANIM_GUN_SW, // Animation number. + "MINIGUN", // Data name of animation. + 18, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 18, // Starting frame number. + 0, // Loop start frame number. + 0, // Number of times the animation loops. + 6, // Number of animation stages. + 0, // Ending frame of loop back. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const GUNS( + ANIM_GUN_S, // Animation number. + "MINIGUN", // Data name of animation. + 18, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 24, // Starting frame number. + 0, // Loop start frame number. + 0, // Number of times the animation loops. + 6, // Number of animation stages. + 0, // Ending frame of loop back. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const GUNSE( + ANIM_GUN_SE, // Animation number. + "MINIGUN", // Data name of animation. + 18, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 30, // Starting frame number. + 0, // Loop start frame number. + 0, // Number of times the animation loops. + 6, // Number of animation stages. + 0, // Ending frame of loop back. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const GUNE( + ANIM_GUN_E, // Animation number. + "MINIGUN", // Data name of animation. + 18, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 36, // Starting frame number. + 0, // Loop start frame number. + 0, // Number of times the animation loops. + 6, // Number of animation stages. + 0, // Ending frame of loop back. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const GUNNE( + ANIM_GUN_NE, // Animation number. + "MINIGUN", // Data name of animation. + 18, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 42, // Starting frame number. + 0, // Loop start frame number. + 0, // Number of times the animation loops. + 6, // Number of animation stages. + 0, // Ending frame of loop back. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const IonCannon( + ANIM_ION_CANNON, // Animation number. + "IONSFX", // Data name of animation. + 48, // Maximum dimension of animation. + 11, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + true, // Scorches the ground? + true, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + 15, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_ION_CANNON, // Sound effect to play. + ANIM_ART_EXP1 +); + +static AnimTypeClass const AtomBomb( + ANIM_ATOM_BLAST, // Animation number. + "ATOMSFX", // Data name of animation. + 72, // Maximum dimension of animation. + 19, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + true, // Scorches the ground? + true, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + -1, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NUKE_EXPLODE, // Sound effect to play. + ANIM_NONE +); +static AnimTypeClass const AtomDoor( + ANIM_ATOM_DOOR, // Animation number. + "ATOMDOOR", // Data name of animation. + 48, // Maximum dimension of animation. + 19, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + true, // Scorches the ground? + true, // Forms a crater? + true, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + -1, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const CDeviator( + ANIM_CRATE_DEVIATOR, // Animation number. + "DEVIATOR", // Data name of animation. + 48, // Maximum dimension of animation. + 0, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 2, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + -1, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE // Follow up animation. +); +static AnimTypeClass const CDollar( + ANIM_CRATE_DOLLAR, // Animation number. + "DOLLAR", // Data name of animation. + 48, // Maximum dimension of animation. + 0, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 2, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + -1, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE // Follow up animation. +); +static AnimTypeClass const CEarth( + ANIM_CRATE_EARTH, // Animation number. + "EARTH", // Data name of animation. + 48, // Maximum dimension of animation. + 0, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 2, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + -1, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE // Follow up animation. +); +static AnimTypeClass const CEmpulse( + ANIM_CRATE_EMPULSE, // Animation number. + "EMPULSE", // Data name of animation. + 48, // Maximum dimension of animation. + 0, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 2, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + -1, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE // Follow up animation. +); +static AnimTypeClass const CInvun( + ANIM_CRATE_INVUN, // Animation number. + "INVUN", // Data name of animation. + 48, // Maximum dimension of animation. + 0, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 2, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + -1, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE // Follow up animation. +); +static AnimTypeClass const CMine( + ANIM_CRATE_MINE, // Animation number. + "MINE", // Data name of animation. + 48, // Maximum dimension of animation. + 0, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 2, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + -1, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE // Follow up animation. +); +static AnimTypeClass const CRapid( + ANIM_CRATE_RAPID, // Animation number. + "RAPID", // Data name of animation. + 48, // Maximum dimension of animation. + 0, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 2, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + -1, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE // Follow up animation. +); +static AnimTypeClass const CStealth( + ANIM_CRATE_STEALTH, // Animation number. + "STEALTH2", // Data name of animation. + 48, // Maximum dimension of animation. + 0, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 2, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + -1, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE // Follow up animation. +); +static AnimTypeClass const CMissile( + ANIM_CRATE_MISSILE, // Animation number. + "MISSILE2", // Data name of animation. + 48, // Maximum dimension of animation. + 0, // Biggest animation stage. + true, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 2, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + -1, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE // Follow up animation. +); + +static AnimTypeClass const MoveFlash( + ANIM_MOVE_FLASH, // Animation number. + "MOVEFLSH", // Data name of animation. + 24, // Maximum dimension of animation. + 0, // Biggest animation stage. + true, // Normalized animation rate? + true, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + true, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + 0, // Ending frame of loop back. + -1, // Number of animation stages. + 0, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE // Follow up animation. +); + +static AnimTypeClass const ChemBall( + ANIM_CHEM_BALL, // Animation number. + "CHEMBALL", // Data name of animation. + 21, // Maximum dimension of animation. + 5, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + 1, // Number of times the animation loops. + VOC_FLAMER1, // Sound effect to play. + ANIM_NONE +); + + + +AnimTypeClass const * const AnimTypeClass::Pointers[ANIM_COUNT] = { + &FBall1, + &Grenade, + &Frag1, + &Frag3, + &VehHit1, + &VehHit2, + &VehHit3, + &ArtExp1, + &Napalm1, + &Napalm2, + &Napalm3, + &SmokePuff, + &Piff, + &PiffPiff, + &FlameN, + &FlameNE, + &FlameE, + &FlameSE, + &FlameS, + &FlameSW, + &FlameW, + &FlameNW, + &ChemN, + &ChemNE, + &ChemE, + &ChemSE, + &ChemS, + &ChemSW, + &ChemW, + &ChemNW, + &Fire3, + &Fire2, + &Fire1, + &Fire4, + &Gunfire, +#ifdef NEVER + &E1RotFire, + &E1RotGrenade, + &E1RotGun, + &E1RotExp, + &E2RotFire, + &E2RotGrenade, + &E2RotGun, + &E2RotExp, + &E3RotFire, + &E3RotGrenade, + &E3RotGun, + &E3RotExp, + &E4RotFire, + &E4RotGrenade, + &E4RotGun, + &E4RotExp, +#endif + &SmokeM, + &BurnSmall, + &BurnMed, + &BurnBig, + &OnFireSmall, + &OnFireMed, + &OnFireBig, + &SAMN, + &SAMNE, + &SAME, + &SAMSE, + &SAMS, + &SAMSW, + &SAMW, + &SAMNW, + &GUNN, + &GUNNE, + &GUNE, + &GUNSE, + &GUNS, + &GUNSW, + &GUNW, + &GUNNW, + &LZSmoke, + &IonCannon, + &AtomBomb, + &CDeviator, + &CDollar, + &CEarth, + &CEmpulse, + &CInvun, + &CMine, + &CRapid, + &CStealth, + &CMissile, + &AtomDoor, + &MoveFlash, + &OilFieldBurn, + &TricDie, + &TRexDie, + &StegDie, + &RaptDie, + &ChemBall +}; + + +/*********************************************************************************************** + * AnimTypeClass::AnimTypeClass -- Constructor for animation types. * + * * + * This is the constructor for static objects that elaborate the various animation types * + * allowed in the game. Each animation in the game is of one of these types. * + * * + * INPUT: see below... * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/23/1994 JLB : Created. * + *=============================================================================================*/ +AnimTypeClass::AnimTypeClass(AnimType anim, char const *name, int size, int biggest, + bool isnormal, bool iswhitetrans, bool isscorcher, bool iscrater, bool issticky, bool ground, + bool istrans, bool isflame, unsigned int damage, + int delaytime, int start, int loopstart, int loopend, int stages, int loops, + VocType sound, AnimType chainto) : + ObjectTypeClass(true, false, false, true, false, false, true, true, TXT_NONE, name, ARMOR_NONE, 0) +{ + Biggest = biggest; + ChainTo = chainto; + Damage = damage; + Delay = (unsigned char)delaytime; + IsCraterForming = iscrater; + IsFlameThrower = isflame; + IsGroundLayer = ground; + IsNormalized = isnormal; + IsScorcher = isscorcher; + IsSticky = issticky; + IsTranslucent = istrans; + IsWhiteTrans = iswhitetrans; + LoopEnd = loopend; + LoopStart = loopstart; + Loops = (unsigned char)loops; + Size = size; + Sound = sound; + Stages = stages; + Start = start; + Type = anim; +} + + +/*********************************************************************************************** + * AnimTypeClass::One_Time -- Performs one time action for animation types. * + * * + * This will load the animation shape data. It is called by the game initialization * + * process. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine should be called ONLY once. * + * * + * HISTORY: * + * 06/02/1994 JLB : Created. * + *=============================================================================================*/ +void AnimTypeClass::One_Time(void) +{ + AnimType index; + + for (index = ANIM_FIRST; index < ANIM_COUNT; index++) { + char fullname[_MAX_FNAME+_MAX_EXT]; + + _makepath(fullname, NULL, NULL, As_Reference(index).IniName, ".SHP"); + + RawFileClass file(fullname); + if (file.Is_Available()) { + ((void const *&)As_Reference(index).ImageData) = Load_Alloc_Data(file); + } else { + ((void const *&)As_Reference(index).ImageData) = MixFileClass::Retrieve(fullname); + } + } +} + + diff --git a/AIRCRAFT.CPP b/AIRCRAFT.CPP new file mode 100644 index 0000000..d5310b6 --- /dev/null +++ b/AIRCRAFT.CPP @@ -0,0 +1,3495 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\aircraft.cpv 2.12 19 Jun 1995 09:27:22 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : AIRCRAFT.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : July 22, 1994 * + * * + * Last Update : August 13, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * AircraftClass::AI -- Processes the normal non-graphic AI for the aircraft. * + * AircraftClass::Active_Click_With -- Handles clicking over specified cell. * + * AircraftClass::Active_Click_With -- Handles clicking over specified object. * + * AircraftClass::AircraftClass -- The constructor for aircraft objects. * + * AircraftClass::As_Target -- Returns aircraft as a target number. * + * AircraftClass::Can_Enter_Cell -- Determines if the aircraft can land at this location. * + * AircraftClass::Cell_Seems_Ok -- Checks to see if a cell is good to enter. * + * AircraftClass::Desired_Load_Dir -- Determines where passengers should line up. * + * AircraftClass::Draw_It -- Renders an aircraft object at the location specified. * + * AircraftClass::Enter_Idle_Mode -- Gives the aircraft an appropriate mission. * + * AircraftClass::Exit_Object -- Unloads passenger from aircraft. * + * AircraftClass::Fire_At -- Handles firing a projectile from an aircraft. * + * AircraftClass::Fire_Coord -- Calculates the point of origin for a bullet. * + * AircraftClass::Fire_Direction -- Determines the direction of fire. * + * AircraftClass::Good_Fire_Location -- Searches for and finds a good spot to fire from. * + * AircraftClass::Good_LZ -- Locates a good spot ot land. * + * AircraftClass::In_Which_Layer -- Determine which render layer the aircraft lies. * + * AircraftClass::Init -- Initialize the aircraft system to an empty state. * + * AircraftClass::Is_LZ_Clear -- Determines if landing zone is free for landing. * + * AircraftClass::Mark -- Flags cells under the aircraft so that they will be redrawn. * + * AircraftClass::Mission_Attack -- Handles the attack mission for aircraft. * + * AircraftClass::Mission_Enter -- Control aircraft to fly to the helipad or repair center. * + * AircraftClass::Mission_Guard -- Handles aircraft in guard mode. * + * AircraftClass::Mission_Guard_Area -- Handles the aircraft guard area logic. * + * AircraftClass::Mission_Hunt -- Maintains hunt AI for the aircraft. * + * AircraftClass::Mission_Move -- Handles movement mission. * + * AircraftClass::Mission_Retreat -- Handles the aircraft logic for leaving the battlefield. * + * AircraftClass::Mission_Unload -- Handles unloading cargo. * + * AircraftClass::New_LZ -- Find a good landing zone. * + * AircraftClass::Overlap_List -- Returns with list of cells the aircraft overlaps. * + * AircraftClass::Pip_Count -- Returns the number of "objects" in aircraft. * + * AircraftClass::Player_Assign_Mission -- Handles player input to assign a mission. * + * AircraftClass::Pose_Dir -- Fetches the natural landing facing. * + * AircraftClass::Process_Fly_To -- Handles state machine for flying to destination. * + * AircraftClass::Process_Landing -- Landing process state machine handler. * + * AircraftClass::Process_Take_Off -- State machine support for taking off. * + * AircraftClass::Read_INI -- Reads aircraft object data from an INI file. * + * AircraftClass::Rearm_Delay -- Returns the delay between shots for this aircraft. * + * AircraftClass::Receive_Message -- Handles receipt of radio messages. * + * AircraftClass::Response_Attack -- Gives audio response to attack order. * + * AircraftClass::Response_Move -- Gives audio response to move request. * + * AircraftClass::Response_Select -- Gives audio response when selected. * + * AircraftClass::Scatter -- Causes the aircraft to move away a bit. * + * AircraftClass::Set_Speed -- Sets the speed for the aircraft. * + * AircraftClass::Sort_Y -- Figures the sorting coordinate. * + * AircraftClass::Take_Damage -- Applies damage to the aircraft. * + * AircraftClass::Threat_Range -- Returns with a range to scan for targets. * + * AircraftClass::Unlimbo -- Removes an aircraft from the limbo state. * + * AircraftClass::What_Action -- Determines what action to perform. * + * AircraftClass::What_Action -- Determines what action to perform. * + * AircraftClass::Write_INI -- Writes the current aircraft objects to an INI file. * + * AircraftClass::operator delete -- Deletes the aircraft object. * + * AircraftClass::operator new -- Allocates a new aircraft object from the pool * + * AircraftClass::~AircraftClass -- Destructor for aircraft object. * + * AircraftClass::Validate -- validates aircraft pointer * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/* +** This contains the value of the Virtual Function Table Pointer +*/ +void * AircraftClass::VTable; + + +/*********************************************************************************************** + * AircraftClass::Validate -- validates aircraft pointer * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int AircraftClass::Validate(void) const +{ + int num; + + num = Aircraft.ID(this); + if (num < 0 || num >= AIRCRAFT_MAX) { + Validate_Error("AIRCRAFT"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*********************************************************************************************** + * AircraftClass::As_Target -- Returns aircraft as a target number. * + * * + * This routine will convert the aircraft into a target number. This target number can * + * then be assigned to a targeting or navigation computer. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the aircraft as a target number. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +TARGET AircraftClass::As_Target(void) const +{ + Validate(); + return(Build_Target(KIND_AIRCRAFT, Aircraft.ID(this))); +} + + +/*********************************************************************************************** + * AircraftClass::operator new -- Allocates a new aircraft object from the pool * + * * + * This routine will allocate an aircraft object from the free aircraft object pool. If * + * there are no free object available, then this routine will fail (return NULL). * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the allocate aircraft object or NULL if none were * + * available. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +void * AircraftClass::operator new(size_t) +{ + void * ptr = Aircraft.Allocate(); + if (ptr) { + ((AircraftClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +/*********************************************************************************************** + * AircraftClass::operator delete -- Deletes the aircraft object. * + * * + * This routine will return the aircraft object back to the free aircraft object pool. * + * * + * INPUT: ptr -- Pointer to the aircraft object to delete. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::operator delete(void *ptr) +{ + if (ptr) { + ((AircraftClass *)ptr)->IsActive = false; + } + Aircraft.Free((AircraftClass *)ptr); +} + + +/*********************************************************************************************** + * AircraftClass::AircraftClass -- The constructor for aircraft objects. * + * * + * This routine is the constructor for aircraft objects. An aircraft object can be * + * created and possibly placed into the game system by this routine. * + * * + * INPUT: classid -- The type of aircraft to create. * + * * + * house -- The owner of this aircraft. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +AircraftClass::AircraftClass(AircraftType classid, HousesType house) : + Class(&AircraftTypeClass::As_Reference(classid)), + FootClass(house) +{ + /* + ** For two shooters, clear out the second shot flag -- it will be set the first time + ** the object fires. For non two shooters, set the flag since it will never be cleared + ** and the second shot flag tells the system that normal rearm times apply -- this is + ** what is desired for non two shooters. + */ + if (Class->IsTwoShooter) { + IsSecondShot = false; + } else { + IsSecondShot = true; + } + Ammo = Class->MaxAmmo; + AttacksRemaining = 3; + Altitude = FLIGHT_LEVEL; + IsLanding = false; + IsTakingOff = false; + IsHovering = false; + IsHoming = false; + Strength = Class->MaxStrength; + NavCom = TARGET_NONE; + SecondaryFacing = PrimaryFacing; + Jitter = 0; + + /* + ** Keep count of the number of units created. Dont track cargo planes as they are created + ** automatically, not bought. + */ + if (classid != AIRCRAFT_CARGO && GameToPlay == GAME_INTERNET){ + House->AircraftTotals->Increment_Unit_Total((int)classid); + } +} + + +/*********************************************************************************************** + * AircraftClass::Unlimbo -- Removes an aircraft from the limbo state. * + * * + * This routine is used to transition the aircraft from the limbo to the non limbo state. * + * It occurs when the aircraft is placed on the map for whatever reason. When it is * + * unlimboed, only then will normal game processing recognize it. * + * * + * INPUT: coord -- The coordinate that the aircraft should appear at. * + * * + * dir -- The direction it should start facing. * + * * + * strength (optional) -- sets initial strength * + * * + * mission (optional) -- sets initial mission * + * * + * OUTPUT: bool; Was the aircraft unlimboed successfully? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +bool AircraftClass::Unlimbo(COORDINATE coord, DirType dir) +{ + Validate(); + if (FootClass::Unlimbo(coord, dir)) { + + /* + ** Ensure that the owning house knows about the + ** new object. + */ + House->AScan |= (1L << Class->Type); + House->ActiveAScan |= (1L << Class->Type); + + /* + ** Forces the body of the helicopter to face the correct direction. + */ + SecondaryFacing = dir; + + /* + ** Start rotor animation. + */ + Set_Rate(1); + Set_Stage(0); + + /* + ** Presume it starts in flight? + */ + if (Altitude == FLIGHT_LEVEL) { + Set_Speed(0xFF); + } else { + Set_Speed(0); + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * AircraftClass::Draw_It -- Renders an aircraft object at the location specified. * + * * + * This routine is used to display the aircraft object at the coordinates specified. * + * The tactical map display uses this routine for all aircraft rendering. * + * * + * INPUT: x,y -- The coordinates to render the aircraft at. * + * * + * window -- The window that the coordinates are based upon. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::Draw_It(int x, int y, WindowNumberType window) +{ + Validate(); + void const * shapefile; // Working shape file pointer. + int shapenum = 0; + int facing = Facing_To_32(SecondaryFacing); + + + /* + ** Verify the legality of the unit class. + */ + shapefile = Class->Get_Image_Data(); + if (!shapefile) return; + shapenum = UnitClass::BodyShape[facing]; + + /* + ** The orca attack helicopter uses a special shape set when it is travelling + ** forward above a certain speed. + */ + if (*this == AIRCRAFT_ORCA && Get_Speed() >= MPH_MEDIUM_FAST) { + shapenum += 32; + } + + /* + ** If there is a door on this aircraft (Chinook), then adjust the + ** shape number to match the door open state. + */ + if (!Is_Door_Closed()) { + shapenum = 32 + Door_Stage(); + } + + /* + ** Helicopters that are flying have a "bobbing" effect. + */ + int jitter = 0; + if (Altitude == FLIGHT_LEVEL && !Class->IsFixedWing) { + Jitter++; + + static int _jitter[] = {0,0,0,0,1,1,1,0,0,0,0,0,-1,-1,-1,0}; + jitter = _jitter[Jitter % 16]; + } + + /* + ** Special manual shadow draw code. + */ + if (Visual_Character() <= VISUAL_DARKEN) { + CC_Draw_Shape(shapefile, shapenum, x+1, y+2, window, SHAPE_PREDATOR|SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_FADING, Map.FadingShade, NULL); + } + + /* + ** Actually draw the root body of the unit. + */ + Techno_Draw_Object(shapefile, shapenum, x, (y-Altitude)+jitter, window); +// CC_Draw_Shape(shapefile, shapenum, x, (y-Altitude)+jitter, window, SHAPE_FADING|SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_GHOST, House->Remap_Table(IsBlushing, true), Map.UnitShadow); + + /* + ** Draw rotor effects. The rotor art can be either generic or custom. Custom rotor + ** art has a different rotor set for each facing. Rotor shapes occur after the first + ** 32 shapes of the helicopter body. + */ + if (Class->IsRotorEquipped) { + ShapeFlags_Type flags = SHAPE_CENTER|SHAPE_WIN_REL; + + /* + ** The rotor shape number depends on whether the helicopter is idling + ** or not. A landed helicopter uses slow moving "idling" blades. + */ + if (Altitude == 0) { + shapenum = (Fetch_Stage()%8)+4; + flags = flags | SHAPE_GHOST; + } else { + shapenum = Fetch_Stage()%4; + flags = flags | SHAPE_FADING|SHAPE_PREDATOR; + } + + if (*this == AIRCRAFT_TRANSPORT) { + int _stretch[FACING_COUNT] = {8, 9, 10, 9, 8, 9, 10, 9}; + + /* + ** Dual rotors offset along flight axis. + */ + short xx = x; + short yy = y-Altitude; + FacingType face = Dir_Facing(SecondaryFacing); + Move_Point(xx, yy, SecondaryFacing.Current(), _stretch[face]); + CC_Draw_Shape(Class->RRotorData, shapenum, xx, yy-2, window, flags, NULL, Map.UnitShadow); + + Move_Point(xx, yy, SecondaryFacing.Current()+DIR_S, _stretch[face]*2); + CC_Draw_Shape(Class->LRotorData, shapenum, xx, yy-2, window, flags, NULL, Map.UnitShadow); + + } else { + + /* + ** Single rotor centered about shape. + */ + CC_Draw_Shape(Class->RRotorData, shapenum, x, (y-Altitude)-2, window, flags, NULL, Map.UnitShadow); + } + } + + FootClass::Draw_It(x, y-Altitude, window); +} + + +/*********************************************************************************************** + * AircraftClass::Read_INI -- Reads aircraft object data from an INI file. * + * * + * This routine is used to read the aircraft object data from the INI file buffer * + * specified. This is used by the scenario loader code to interpret the INI file and * + * create the specified objects therein. * + * * + * INPUT: buffer -- Pointer to the INI buffer. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::Read_INI(char *buffer) +{ + AircraftClass *air; // Working unit pointer. + char *tbuffer; // Accumulation buffer of unit IDs. + HousesType inhouse; // Unit house. + AircraftType classid; // Unit class. + int len; // Length of data in buffer. + char buf[128]; + + len = strlen(buffer) + 2; + tbuffer = buffer + len; + + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-len, buffer); + while (*tbuffer != '\0') { + + WWGetPrivateProfileString(INI_Name(), tbuffer, NULL, buf, sizeof(buf)-1, buffer); + inhouse = HouseTypeClass::From_Name(strtok(buf, ",")); + if (inhouse != HOUSE_NONE) { + classid = AircraftTypeClass::From_Name(strtok(NULL, ",")); + + if (classid != AIRCRAFT_NONE) { + + air = new AircraftClass(classid, inhouse); + if (air) { + COORDINATE coord; + int strength; + DirType dir; + + /* + ** Read the raw data. + */ + strength = atoi(strtok(NULL, ",")); + coord = Cell_Coord((CELL)atoi(strtok(NULL, ","))); + dir = (DirType)atoi(strtok(NULL, ",")); + + if (!Map.In_Radar(Coord_Cell(coord))) { + delete air; + } else { + + air->Strength = Fixed_To_Cardinal(air->Class->MaxStrength, strength); + if (air->Unlimbo(coord, dir)) { + air->Assign_Mission(AircraftClass::Mission_From_Name(strtok(NULL, ",\n\r"))); + } else { + delete air; + } + } + } + } + } + tbuffer += strlen(tbuffer)+1; + } +} + + +/*********************************************************************************************** + * AircraftClass::Write_INI -- Writes the current aircraft objects to an INI file. * + * * + * This routine is used to output the current list of aircraft objects to the INI file * + * buffer specified. It is typically used by the scenario editor. * + * * + * INPUT: buffer -- Pointer to the buffer that holds the INI file data. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::Write_INI(char *buffer) +{ + int index; + char uname[10]; + char buf[128]; + char *tbuffer; // Accumulation buffer of unit IDs. + + /* + ** First, clear out all existing unit data from the ini file. + */ + tbuffer = buffer + strlen(buffer) + 2; + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-strlen(buffer), buffer); + while (*tbuffer != '\0') { + WWWritePrivateProfileString(INI_Name(), tbuffer, NULL, buffer); + tbuffer += strlen(tbuffer)+1; + } + + /* + ** Write the unit data out. + */ + for (index = 0; index < Aircraft.Count(); index++) { + AircraftClass * unit; + + unit = Aircraft.Ptr(index); + if (!unit->IsInLimbo) { + + sprintf(uname, "%03d", index); + sprintf(buf, "%s,%s,%d,%u,%d,%s", + unit->House->Class->IniName, + unit->Class->IniName, + unit->Health_Ratio(), + Coord_Cell(unit->Coord), + unit->PrimaryFacing.Current(), + MissionClass::Mission_Name(unit->Mission) + ); + WWWritePrivateProfileString(INI_Name(), uname, buf, buffer); + } + } +} + + +/*********************************************************************************************** + * AircraftClass::Mission_Hunt -- Maintains hunt AI for the aircraft. * + * * + * Hunt AI consists of finding a target and attacking it. If there is no target assigned * + * and this unit doesn't automatically hunt for more targets, then it will change * + * mission to a more passive (land and await further orders) type. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of ticks before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +int AircraftClass::Mission_Hunt(void) +{ + Validate(); + if (Class->IsFixedWing) { + enum { + LOOK_FOR_TARGET, + FLY_TO_TARGET, + DROP_BOMBS + }; + switch (Status) { + + /* + ** Acquiring target stage. + */ + case LOOK_FOR_TARGET: + if (Target_Legal(TarCom)) { + Status = FLY_TO_TARGET; + return(1); + } else { + Assign_Target(Greatest_Threat(THREAT_NORMAL)); + + /* + ** If there is no target, then this aircraft should just do its normal thing. + */ + if (!Target_Legal(TarCom)) { + Enter_Idle_Mode(); + } + } + break; + + /* + ** Homing in on target stage. + */ + case FLY_TO_TARGET: + if (Target_Legal(TarCom)) { + IsHoming = true; + if (!PrimaryFacing.Is_Rotating()) { + PrimaryFacing.Set_Desired(Direction(TarCom)); + } + if (Distance(TarCom) < 0x0380) { + IsHoming = false; + Status = DROP_BOMBS; + return(1); + } + } else { + Status = LOOK_FOR_TARGET; + } + break; + + /* + ** Dropping a stream of bombs phase. + */ + case DROP_BOMBS: + if (!Ammo) { + AttacksRemaining--; + if (!AttacksRemaining) { + Assign_Mission(MISSION_RETREAT); + Commence(); + } else { + Ammo = Class->MaxAmmo; + Status = LOOK_FOR_TARGET; + } + } + if (!Target_Legal(TarCom)) { + Status = LOOK_FOR_TARGET; + } else { + Fire_At(TarCom, 0); + Map[::As_Cell(TarCom)].Incoming(Coord, true); + return(5); + } + break; + } + } else { + if (!Ammo) { + Enter_Idle_Mode(); + } else { + Assign_Mission(MISSION_ATTACK); + return(1); +// return(FootClass::Mission_Hunt()); + } + } + return(TICKS_PER_SECOND); +} + + +/*********************************************************************************************** + * AircraftClass::AI -- Processes the normal non-graphic AI for the aircraft. * + * * + * This handles the non-graphic AI processing for the aircraft. This usually entails * + * maintenance and other AI functions. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::AI(void) +{ + Validate(); + /* + ** A Mission change can always occur if the aircraft is landed or flying. + */ + if (!IsLanding && !IsTakingOff) { + Commence(); + } + + FootClass::AI(); + + /* + ** A Mission change can always occur if the aircraft is landed or flying. + */ + if (!IsLanding && !IsTakingOff) { + Commence(); + } + + /* + ** Handle any body rotation at this time. Body rotation can occur even if the + ** flying object is not actually moving. + */ + if (PrimaryFacing.Is_Rotating()) { + if (PrimaryFacing.Rotation_Adjust(Class->ROT)) { + Mark(); + } + } + if (Class->IsFixedWing) { + SecondaryFacing = PrimaryFacing; + } + if (SecondaryFacing.Is_Rotating()) { + if (SecondaryFacing.Rotation_Adjust(Class->ROT)) { + Mark(); + } + } + if (Physics(Coord, PrimaryFacing) != RESULT_NONE) { + Mark(); + } + + /* + ** Perform sighting every so often as controlled by the sight timer. + */ + if (IsOwnedByPlayer && Class->SightRange && SightTimer.Expired()) { + Map.Sight_From(Coord_Cell(Coord), Class->SightRange, false); + SightTimer = TICKS_PER_SECOND; + } + + /* + ** Handle landing and taking off logic. Helicopters are prime users of this technique. The + ** aircraft will either gain or lose altitude as appropriate. As the aircraft transitions + ** between flying level and ground level, it will be moved into the appropriate render + ** layer. + */ + if (Is_Door_Closed() && (IsLanding || IsTakingOff)) { + Mark(); + LayerType layer = In_Which_Layer(); + + if (IsLanding) { + if (Altitude) Altitude--; + if (!Altitude) { + IsLanding = false; + Set_Speed(0); + if (Target_Legal(NavCom) && As_Techno(NavCom) == Contact_With_Whom()) { + if (In_Radio_Contact() && Transmit_Message(RADIO_IM_IN) != RADIO_ROGER) { + Scatter(0, true); + } + } + } + } + if (IsTakingOff) { + Altitude++; + if (Altitude >= FLIGHT_LEVEL) { + Altitude = FLIGHT_LEVEL; + IsTakingOff = false; + } + } + + /* + ** Make adjustments for altitude by moving from one layer to another as + ** necessary. + */ + if (layer != In_Which_Layer()) { + + /* + ** When the aircraft is about to enter the ground layer, perform on last + ** check to see if it is legal to enter that location. If not, then + ** start the take off process. Let the normal logic handle this + ** change of plans. + */ + bool ok = true; + if (In_Which_Layer() == LAYER_GROUND) { + if (!Is_LZ_Clear(::As_Target(Coord_Cell(Coord)))) { + IsTakingOff = true; + Altitude++; + ok = false; + } + } + + if (ok) { + /* + ** If landing in a cell that already contains an object, then + ** the landing attempt must be aborted. + */ + Map.Remove(this, layer); + Map.Submit(this, In_Which_Layer()); + + /* + ** When the aircraft is close to the ground, it should exist as a ground object. + ** This aspect is controlled by the Place_Down and Pick_Up functions. + */ + if (In_Which_Layer() == LAYER_GROUND) { + Assign_Destination(TARGET_NONE); // Clear the navcom. + Transmit_Message(RADIO_TETHER); + Map.Place_Down(Coord_Cell(Coord), this); + if (IsOwnedByPlayer) { + Map.Sight_From(Coord_Cell(Coord), 1, false); + } + } else { + Transmit_Message(RADIO_UNTETHER); + Map.Pick_Up(Coord_Cell(Coord), this); + + /* + ** If the navigation computer is not attached to the object this + ** aircraft is in radio contact with, then assume that radio + ** contact is now superfluous. Break radio contact. + */ + if (In_Radio_Contact() && Target_Legal(NavCom) && NavCom != Contact_With_Whom()->As_Target()) { + Transmit_Message(RADIO_OVER_OUT); + } + } + } + } + } + + /* + ** Always flag the map draw process to occur if there is an aircraft in the view. + ** This ensures that it will be rendered even if there is nothing else that flagged + ** the map to be redrawn. + */ + if (Map.In_View(Coord_Cell(Coord))) { + Map.Flag_To_Redraw(false); + Map.DisplayClass::IsToRedraw = true; + } + + /* + ** When aircraft leave the edge of the map, they might get destroyed. This occurs if the + ** aircraft is a non-player produced unit and it has completed its mission. A transport + ** helicopter that has already delivered reinforcements is a good example of this. + */ + if (!Map.In_Radar(Coord_Cell(Coord))) { + if (Mission == MISSION_RETREAT /*|| (*this == AIRCRAFT_CARGO && !Is_Something_Attached())*/) { + + /* + ** Check to see if there are any civilians aboard. If so, then flag the house + ** that the civilian evacuation trigger event has been fulfilled. + */ + while (Is_Something_Attached()) { + FootClass * obj = Detach_Object(); + if (obj->What_Am_I() == RTTI_INFANTRY && ((InfantryClass *)obj)->Class->IsCivilian && !((InfantryClass *)obj)->IsTechnician) { + House->IsCivEvacuated = true; + } + + /* + ** Transport planes that leave can only be because they carry purchased + ** equipment and must be have their cost refunded. + */ + if (*this == AIRCRAFT_CARGO) { + House->Refund_Money(obj->Class_Of().Cost_Of()); + } + delete obj; + } + Stun(); + delete this; + return; + } + } else { + IsLocked = true; +// House->NewAScan |= (1L << Class->Type); + +#ifdef NEVER + /* + ** Transport helicopters must ensure that their passengers are properly + ** considered "alive" by setting the appropriate scan bits. + */ + FootClass const * foot = Attached_Object(); + while (foot) { + switch (foot->What_Am_I()) { + case RTTI_UNIT: + House->NewUScan |= (1L << ((UnitTypeClass const &)Class_Of()).Type); + break; + + case RTTI_INFANTRY: + House->NewIScan |= (1L << ((InfantryTypeClass const &)Class_Of()).Type); + break; + } + + foot = (FootClass const *)foot->Next; + } +#endif + } +} + + +/*********************************************************************************************** + * AircraftClass::Mark -- Flags cells under the aircraft so that they will be redrawn. * + * * + * This routine is used to flag the cells under the aircraft so that those cells will * + * be redrawn during the next map drawing process. This is a necessary step whenever the * + * aircraft moves or changes shape. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +bool AircraftClass::Mark(MarkType mark) +{ + Validate(); + if (FootClass::Mark(mark)) { + Map.Refresh_Cells(Coord_Cell(Coord), Occupy_List()); + Map.Refresh_Cells(Coord_Cell(Coord), Overlap_List()); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * AircraftClass::Overlap_List -- Returns with list of cells the aircraft overlaps. * + * * + * When aircraft are flying, they can overlap quite a number of cells. These cells can * + * be determined from the coordinate where the aircraft is centered and the size of the * + * aircraft's shape. Landed aircraft are a special case and are usually much smaller * + * than when flying. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to a cell offset list that specifies all cells that * + * the aircraft overlaps given the aircraft's current state. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + *=============================================================================================*/ +short const *AircraftClass::Overlap_List(void) const +{ + Validate(); + static short const _list[] = { + -(MAP_CELL_W-1), -MAP_CELL_W, -(MAP_CELL_W+1), + -1, 0, 1, + (MAP_CELL_W-1), MAP_CELL_W, (MAP_CELL_W+1), + -((MAP_CELL_W*2)-1), -(MAP_CELL_W*2), -((MAP_CELL_W*2)+1), + -((MAP_CELL_W*3)-1), -(MAP_CELL_W*3), -((MAP_CELL_W*3)+1), + REFRESH_EOL + }; + + if (Altitude) { + return(_list); + //return Coord_Spillage_List(Coord, 25); + } + return(Class->Overlap_List()); +} + + +/*********************************************************************************************** + * AircraftClass::Init -- Initialize the aircraft system to an empty state. * + * * + * This routine is used to clear out the aircraft allocation system. It is called in * + * preparation for a scenario load or save game load. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::Init(void) +{ + AircraftClass *ptr; + + Aircraft.Free_All(); + + ptr = new AircraftClass(); + VTable = ((void **)(((char *)ptr) + sizeof(AbstractClass) - 4))[0]; + delete ptr; +} + + +/*********************************************************************************************** + * AircraftClass::Mission_Unload -- Handles unloading cargo. * + * * + * This function is used to handle finding, heading toward, landing, and unloading the * + * cargo from the aircraft. Once unloading of cargo has occurred, then the aircraft follows * + * a different mission. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the number of game ticks to delay before calling this function again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/31/94 JLB : Created. * + *=============================================================================================*/ +int AircraftClass::Mission_Unload(void) +{ + Validate(); + if (Class->IsFixedWing) { + enum { + PICK_AIRSTRIP, + FLY_TO_AIRSTRIP, + BUG_OUT + }; + + switch (Status) { + + /* + ** Find a suitable airfield to land at. + */ + case PICK_AIRSTRIP: + if (!Target_Legal(NavCom) || !In_Radio_Contact()) { + + BuildingClass * building = Find_Docking_Bay(STRUCT_AIRSTRIP, false); + if (building) { + if (Transmit_Message(RADIO_HELLO, building) == RADIO_ROGER) { + Set_Speed(0xFF); + Assign_Destination(building->As_Target()); + if (Team) { + Team->Target = NavCom; + } + Status = FLY_TO_AIRSTRIP; + } + } + + /* + ** If a suitable airfield could not be found, then just randomly change + ** direction and then try again later. + */ + if (Status == PICK_AIRSTRIP) { + + /* + ** If there are no more airstrips, regardless of busy state, then + ** abort this transport plane completely. + */ + if (!(House->ActiveBScan & STRUCTF_AIRSTRIP)) { + Assign_Mission(MISSION_RETREAT); + } + + /* + ** Pick a new direction and fly off. + */ + PrimaryFacing.Set_Desired(Random_Pick(DIR_N, DIR_MAX)); + SecondaryFacing.Set_Desired(PrimaryFacing.Desired()); + return(TICKS_PER_SECOND*3); + } + } else { + Status = FLY_TO_AIRSTRIP; + } + break; + + /* + ** Home in on target. When close enough, drop the cargo. + */ + case FLY_TO_AIRSTRIP: + if (!Target_Legal(NavCom) || !In_Radio_Contact()) { + Status = PICK_AIRSTRIP; + } else { + + /* + ** If, for some reason, there is no cargo, then don't stick around. + */ + if (!Is_Something_Attached()) { + Status = BUG_OUT; + return(1); + } + + if (!PrimaryFacing.Is_Rotating()) { + PrimaryFacing.Set_Desired(Direction(As_Movement_Coord(NavCom))); + } + SecondaryFacing.Set_Desired(PrimaryFacing.Desired()); + + int navdist = Distance(As_Movement_Coord(NavCom)); + Altitude = FLIGHT_LEVEL; + if (navdist < 0x0600) { + Altitude = Fixed_To_Cardinal(FLIGHT_LEVEL, Cardinal_To_Fixed(0x0600, navdist)); + } + + if (navdist < 0x0080) { + FootClass * unit = (FootClass *)Detach_Object(); + + if (unit) { + CELL cell = Contact_With_Whom()->Find_Exit_Cell(unit); + if (cell) { + ScenarioInit++; + if (!unit->Unlimbo(Cell_Coord(cell))) { + Attach(unit); + } else { + + /* + ** Cargo planes announce reinforcements when they unload + ** their cargo. + */ + if (*this == AIRCRAFT_CARGO && House == PlayerPtr) { + Speak(VOX_REINFORCEMENTS); + } + unit->IsALoaner = false; + unit->IsLocked = true; + unit->Scatter(0, true); + } + ScenarioInit--; + + Transmit_Message(RADIO_OVER_OUT); + Assign_Target(TARGET_NONE); + } else { + Attach(unit); + } + +// if (Is_Something_Attached()) { +// Status = PICK_AIRSTRIP; +// } else { + Status = BUG_OUT; +// } + } else { + Status = BUG_OUT; + } + } + return(1); + } + break; + + /* + ** All cargo unloaded, head off the map. + */ + case BUG_OUT: + Assign_Mission(MISSION_RETREAT); + return(1); + } + + } else { + enum { + SEARCH_FOR_LZ, + FLY_TO_LZ, + LAND_ON_LZ, + UNLOAD_PASSENGERS, + TAKE_OFF, + }; + + switch (Status) { + + /* + ** Search for an appropriate destination spot if one isn't already assigned. + */ + case SEARCH_FOR_LZ: + if (Altitude == 0 && (Target_Legal(NavCom) || Coord == As_Coord(NavCom))) { + Status = UNLOAD_PASSENGERS; + } else { + if (!Is_LZ_Clear(NavCom)) { + Assign_Destination(New_LZ(::As_Target(Waypoint[WAYPT_REINF]))); + } else { + if (Altitude == FLIGHT_LEVEL) { + Status = FLY_TO_LZ; + } else { + Status = TAKE_OFF; + } + } + } + break; + + /* + ** Fly to destination. + */ + case FLY_TO_LZ: + if (Is_LZ_Clear(NavCom)) { + int distance = Process_Fly_To(true); + + if (distance < 0x0100) { + SecondaryFacing.Set_Desired(Pose_Dir()); + + if (distance < 0x0010) { + Status = LAND_ON_LZ; + } + return(1); + } else { + SecondaryFacing.Set_Desired(PrimaryFacing.Desired()); + return(5); + } + } else { + Status = SEARCH_FOR_LZ; + } + break; + + /* + ** Landing phase. Just delay until landing is complete. At that time, + ** transition to the unloading phase. + */ + case LAND_ON_LZ: + if (IsTakingOff) { + Status = TAKE_OFF; + } else { + if (Process_Landing()) { + Status = UNLOAD_PASSENGERS; + } + } + return(1); + + /* + ** Hold while unloading passengers. When passengers are unloaded the order for this + ** transport gets changed to MISSION_RETREAT. + */ + case UNLOAD_PASSENGERS: + if (!IsTethered) { + if (Is_Something_Attached()) { + FootClass * unit = (FootClass *)Detach_Object(); + + /* + ** First thing is to lift the transport off of the map so that the unlimbo + ** process for the passengers is more likely to succeed. + */ + Map.Pick_Up(Coord_Cell(Coord), this); + + if (!Exit_Object(unit)) { + delete unit; + } + + /* + ** Restore the transport back down on the map. + */ + Map.Place_Down(Coord_Cell(Coord), this); + + if (!Is_Something_Attached()) { + Enter_Idle_Mode(); + } + + } else { + + Enter_Idle_Mode(); + } + } + break; + + /* + ** Aircraft is now taking off. Once the aircraft reaches flying altitude then it + ** will either take off or look for another landing spot to try again. + */ + case TAKE_OFF: { + if (Process_Take_Off()) { + if (Is_Something_Attached()) { + Status = SEARCH_FOR_LZ; + + /* + ** Break off radio contact with the helipad it is taking off from. + */ + if (In_Radio_Contact() && Map[Coord_Cell(Coord)].Cell_Building() == Contact_With_Whom()) { + Transmit_Message(RADIO_OVER_OUT); + } + } else { + Enter_Idle_Mode(); + } + } + return(1); + } + } + } + return(10); +} + + +/*********************************************************************************************** + * AircraftClass::Is_LZ_Clear -- Determines if landing zone is free for landing. * + * * + * This routine examines the landing zone (as specified by the target parameter) in order * + * to determine if it is free to be landed upon. Call this routine when it is necessary * + * to double check this. Typically this occurs right before a helicopter lands and also * + * when determining the landing zone in the first place. * + * * + * INPUT: target -- The target that is the "landing zone". * + * * + * OUTPUT: bool; Is the landing zone clear for landing? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/31/94 JLB : Created. * + *=============================================================================================*/ +bool AircraftClass::Is_LZ_Clear(TARGET target) const +{ + Validate(); + if (!Target_Legal(target)) return(false); + CELL cell = ::As_Cell(target); + if (!Map.In_Radar(cell)) return(false); + + ObjectClass * object = Map[cell].Cell_Object(); + if (object) { + if (object == this) return(true); + + if (In_Radio_Contact() && Contact_With_Whom() == object) { + return(true); + } + return(false); + } + + if (!Map[cell].Is_Generally_Clear()) return(false); + + return(true); +} + + +/*********************************************************************************************** + * AircraftClass::In_Which_Layer -- Determine which render layer the aircraft lies. * + * * + * This routine is used to figure out which rendering layer the aircraft is located in. * + * It can be determined from the aircraft's height. The layer value is used to handle the * + * display sequence. Objects in lower layers appear beneath objects in higher layers. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the layer that the aircraft is currently located in. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/01/1994 JLB : Created. * + *=============================================================================================*/ +LayerType AircraftClass::In_Which_Layer(void) const +{ + Validate(); + if (Class->IsFixedWing) return(LAYER_TOP); + + if (Altitude < FLIGHT_LEVEL - (FLIGHT_LEVEL/3)) { + return(LAYER_GROUND); + } + return(LAYER_TOP); +} + + +/*********************************************************************************************** + * AircraftClass::Sort_Y -- Figures the sorting coordinate. * + * * + * This routine is used to determine the coordinate to use for sorting the aircraft. This * + * sorting value is used when the aircraft is on the ground. At that time the aircraft * + * must be rendered in proper relationship to the other ground objects. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the coordinate to use when sorting the aircraft with other ground * + * objects. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/02/1994 JLB : Created. * + *=============================================================================================*/ +COORDINATE AircraftClass::Sort_Y(void) const +{ + Validate(); + return(Coord_Add(Coord, 0x00800000L)); +} + + +/*********************************************************************************************** + * AircraftClass::Mission_Retreat -- Handles the aircraft logic for leaving the battlefield. * + * * + * This mission will be followed when the aircraft decides that it is time to leave the * + * battle. Typically, this occurs when a loaner transport has dropped off its load or when * + * an attack air vehicle has expended its ordinance. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of game ticks to delay before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/19/1995 JLB : Created. * + * 08/13/1995 JLB : Handles aircraft altitude gain after takeoff logic. * + *=============================================================================================*/ +int AircraftClass::Mission_Retreat(void) +{ + Validate(); + if (Class->IsFixedWing) { + if (Class->IsFixedWing && Altitude < FLIGHT_LEVEL) { + Altitude++; + return(3); + } + return(TICKS_PER_SECOND*10); + } + + enum { + TAKE_OFF, + FACE_MAP_EDGE, + KEEP_FLYING + }; + switch (Status) { + + /* + ** Take off if landed. + */ + case TAKE_OFF: + if (Process_Take_Off()) { + Status = FACE_MAP_EDGE; + } + return(1); + + /* + ** Set facing and speed toward the friendly map edge. + */ + case FACE_MAP_EDGE: + Set_Speed(0xFF); + + /* + ** Take advantage of the fact that the source map edge enumerations happen to + ** occur in a clockwise order and are the first four enumerations of the map + ** edge default for the house. If this value is masked and then shifted, a + ** normalized direction value results. Use this value to head the aircraft + ** toward the "friendly" map edge. + */ + PrimaryFacing.Set_Desired((DirType)((House->Edge & 0x03) << 6)); + SecondaryFacing.Set_Desired(PrimaryFacing.Desired()); + Status = KEEP_FLYING; + break; + + /* + ** Just do nothing since we are headed toward the map edge. When the edge is + ** reached, the aircraft should be automatically eliminated. + */ + case KEEP_FLYING: + break; + + default: + break; + } + return(TICKS_PER_SECOND); +} + + +/*********************************************************************************************** + * AircraftClass::Exit_Object -- Unloads passenger from aircraft. * + * * + * This routine is called when the aircraft is to unload a passenger. The passenger must * + * be able to move under its own power. Typical situation is when a transport helicopter * + * is to unload an infantry unit. * + * * + * INPUT: unit -- Pointer to the unit that is to be unloaded from this aircraft. * + * * + * OUTPUT: bool; Was the unit unloaded successfully? * + * * + * WARNINGS: The unload process is merely started by this routine. Radio contact is * + * established with the unloading unit and when the unit is clear of the aircraft * + * the radio contact will be broken and then the aircraft is free to pursue * + * other. * + * * + * HISTORY: * + * 01/10/1995 JLB : Created. * + *=============================================================================================*/ +bool AircraftClass::Exit_Object(TechnoClass * unit) +{ + Validate(); + static FacingType _toface[FACING_COUNT] = {FACING_S, FACING_SW, FACING_SE, FACING_NW, FACING_NE, FACING_N, FACING_W, FACING_E}; + CELL cell; + + /* + ** Find a free cell to drop the unit off at. + */ + for (FacingType face = FACING_N; face < FACING_COUNT; face++) { + cell = Adjacent_Cell(Coord_Cell(Coord), _toface[face]); + if (unit->Can_Enter_Cell(cell) == MOVE_OK) break; + } + + // Should perform a check here to see if no cell could be found. + + /* + ** If the passenger can be placed on the map, then start it moving toward the + ** destination cell and establish radio contact with the transport. This is used + ** to make sure that the transport waits until the passenger is clear before + ** unloading the next passenger or taking off. + */ + if (unit->Unlimbo(Coord, Facing_Dir(_toface[face]))) { + unit->Assign_Mission(MISSION_MOVE); + unit->Assign_Destination(::As_Target(cell)); + if (Transmit_Message(RADIO_HELLO, unit) == RADIO_ROGER) { + Transmit_Message(RADIO_UNLOAD); + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * AircraftClass::Fire_At -- Handles firing a projectile from an aircraft. * + * * + * Sometimes, aircraft firing needs special handling. Example: for napalm bombs, the * + * bomb travels forward at nearly the speed of the delivery aircraft, not necessarily the * + * default speed defined in the BulletTypeClass structure. * + * * + * INPUT: target -- The target that the projectile is heading for. * + * * + * which -- Which weapon to use in the attack. 0=primary, 1=secondary. * + * * + * OUTPUT: Returns with a pointer to the bullet that was created as a result of this attack. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/19/1995 JLB : Created. * + *=============================================================================================*/ +BulletClass * AircraftClass::Fire_At(TARGET target, int which) +{ + Validate(); + BulletClass * bullet = FootClass::Fire_At(target, which); + + if (bullet) { + + /* + ** Play the sound effect associated with this weapon. + */ + WeaponTypeClass const * weapon = (which == 0) ? &Weapons[Class->Primary] : &Weapons[Class->Secondary]; + Sound_Effect(weapon->Sound, Coord); + + + /* + ** Falling bullets move at a speed proportionate to the delivery craft. + */ + if (bullet->Class->IsDropping) { + bullet->Fly_Speed(40, bullet->Class->MaxSpeed); + } + } + return(bullet); +} + + +/*********************************************************************************************** + * AircraftClass::Take_Damage -- Applies damage to the aircraft. * + * * + * This routine is used to apply damage to the specified aircraft. This is where any * + * special crash animation will be initiated. * + * * + * INPUT: damage -- Reference to the damage that will be applied to the aircraft. * + * This value will be filled in with the actual damage that was * + * applied. * + * * + * distance -- Distance from the source of the explosion to this aircraft. * + * * + * warhead -- The warhead type that the damage occurs from. * + * * + * source -- Pointer to the originator of the damage. This can be used so that * + * proper "thank you" can be delivered. * + * * + * OUTPUT: Returns with the result of the damage as it affects this aircraft. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/26/1995 JLB : Created. * + *=============================================================================================*/ +ResultType AircraftClass::Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source) +{ + Validate(); + ResultType res = RESULT_NONE; + + /* + ** Flying aircraft take half damage. + */ + if (Altitude) { + damage /= 2; + } + + /* + ** In order for a this to be damaged, it must either be a unit + ** with a crew or a sandworm. + */ + res = FootClass::Take_Damage(damage, distance, warhead, source); + + switch (res) { + case RESULT_DESTROYED: + Kill_Cargo(source); + Death_Announcement(); + new AnimClass(ANIM_FBALL1, Target_Coord()); + delete this; + break; + + default: + case RESULT_HALF: + break; + } + + return(res); +} + + +/*********************************************************************************************** + * AircraftClass::Mission_Move -- Handles movement mission. * + * * + * This state machine routine is used when an aircraft (usually helicopter) is to move * + * from one location to another. It will handle any necessary take off and landing this * + * may require. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of game frames that should elapse before this routine * + * is called again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/19/1995 JLB : Created. * + *=============================================================================================*/ +int AircraftClass::Mission_Move(void) +{ + Validate(); + if (Class->IsFixedWing) { + + /* + ** Force aircraft in movement mission into a retreat + ** mission so that it leaves the map. + */ + if (*this == AIRCRAFT_A10) { + Assign_Mission(MISSION_RETREAT); + Commence(); + return(1); + } + + enum { + FLY_TO_AIRSTRIP, + BUG_OUT + }; + switch (Status) { + /* + ** Home in on target. When close enough, drop the cargo. + */ + case FLY_TO_AIRSTRIP: + if (!Target_Legal(NavCom) || !In_Radio_Contact()) { + return(TICKS_PER_SECOND); + } else { + + /* + ** If, for some reason, there is no cargo, then don't stick around. + */ + if (!Is_Something_Attached()) { + Status = BUG_OUT; + return(1); + } + + + if (!PrimaryFacing.Is_Rotating()) { + PrimaryFacing.Set_Desired(Direction(NavCom)); + } + SecondaryFacing.Set_Desired(PrimaryFacing.Desired()); + if (Distance(NavCom) < 0x0080) { + FootClass * unit = (FootClass *)Detach_Object(); + + if (unit) { + ScenarioInit++; + if (!unit->Unlimbo(Coord_Snap(Contact_With_Whom()->Coord))) { + Attach(unit); + } + ScenarioInit--; + + Transmit_Message(RADIO_OVER_OUT); + Assign_Target(TARGET_NONE); + } + Status = BUG_OUT; + } + } + return(1); + + case BUG_OUT: + return(TICKS_PER_SECOND); + } + return(5); + } + + enum { + VALIDATE_LZ, + TAKE_OFF, + FLY_TO_LZ, + LAND + }; + switch (Status) { + + /* + ** Double check and change LZ if necessary. + */ + case VALIDATE_LZ: + if (!Target_Legal(NavCom)) { + Enter_Idle_Mode(); + } else { + if (!Is_LZ_Clear(NavCom) || !Cell_Seems_Ok(As_Cell(NavCom))) { + Assign_Destination(New_LZ(NavCom)); + } else { + Status = TAKE_OFF; + } + } + break; + + /* + ** Take off if necessary. + */ + case TAKE_OFF: + if (!Target_Legal(NavCom)) { + Status = VALIDATE_LZ; + } else { + if (Process_Take_Off()) { + /* + ** After takeoff is complete, break radio contact with any helipad that this + ** helicopter is taking off from. + */ + if (In_Radio_Contact() && Map[Coord_Cell(Coord)].Cell_Building() == Contact_With_Whom()) { + Transmit_Message(RADIO_OVER_OUT); + } + + Status = FLY_TO_LZ; + } + return(1); + } + break; + + /* + ** Fly toward target. + */ + case FLY_TO_LZ: + if (Is_LZ_Clear(NavCom)) { + int distance = Process_Fly_To(true); + + if (distance < 0x0080) { + if (Target_Legal(TarCom)) { + SecondaryFacing.Set_Desired(Direction(TarCom)); + } else { + SecondaryFacing.Set_Desired(Pose_Dir()); + } + + if (distance < 0x0010) { + Status = LAND; + } + return(1); + } + + SecondaryFacing.Set_Desired(::Direction(Fire_Coord(0), As_Coord(NavCom))); + } else { + Assign_Destination(New_LZ(NavCom)); + if (!Target_Legal(NavCom)) { + Status = LAND; + } + } + return(1); + + /* + ** Land on target. + */ + case LAND: + if (IsTakingOff) { + Assign_Destination(New_LZ(NavCom)); + Status = TAKE_OFF; + } + if (Process_Landing()) { + if (MissionQueue == MISSION_NONE) { + Enter_Idle_Mode(); + } + } + return(1); + } + + return(TICKS_PER_SECOND); +} + + +/*********************************************************************************************** + * AircraftClass::Enter_Idle_Mode -- Gives the aircraft an appropriate mission. * + * * + * Use this routine when the mission for the aircraft is in doubt. This routine will find * + * an appropriate mission for the aircraft and dispatch it. * + * * + * INPUT: initial -- Is this called when the unit just leaves a factory or is initially * + * or is initially placed on the map? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/05/1995 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::Enter_Idle_Mode(bool ) +{ + Validate(); + MissionType mission = MISSION_GUARD; + if (In_Which_Layer() == LAYER_GROUND) { + if (IsALoaner) { + if (Is_Something_Attached()) { + mission = MISSION_UNLOAD; + } else { + if (Team) { + Team->Remove(this); + } + mission = MISSION_RETREAT; + } + } else { +#ifdef NEVER + if (In_Radio_Contact() && Contact_With_Whom() == Map[Coord_Cell(Coord)].Cell_Techno()) { + Transmit_Message(RADIO_IM_IN); + } +#endif + Assign_Destination(TARGET_NONE); + Assign_Target(TARGET_NONE); + mission = MISSION_GUARD; + } + } else { + if (Is_Something_Attached()) { + if (IsALoaner) { + if (Team) { + mission = MISSION_GUARD; + } else { + mission = MISSION_UNLOAD; + Assign_Destination(Good_LZ()); + } + } else { + Assign_Destination(Good_LZ()); + mission = MISSION_MOVE; + } + } else { + + /* + ** If this transport is a loaner and part of a team, then remove it from + ** the team it is attached to. + */ + if (IsALoaner) { + if (Team) { + Team->Remove(this); + } + } + + if (Class->Primary != WEAPON_NONE) { + + /* + ** Weapon equipped helicopters that run out of ammo and were + ** brought in as reinforcements will leave the map. + */ + if (Ammo == 0 && !House->IsHuman && IsALoaner) { + mission = MISSION_RETREAT; + } else { + + /* + ** Normal aircraft try to find a good landing spot to rest. + */ + BuildingClass * building = Find_Docking_Bay(STRUCT_HELIPAD, false); + Assign_Destination(TARGET_NONE); + if (building && Transmit_Message(RADIO_HELLO, building) == RADIO_ROGER) { + mission = MISSION_ENTER; + } else { + Assign_Destination(Good_LZ()); + mission = MISSION_MOVE; + } + } + } else { + if (Team) return; + + Assign_Destination(Good_LZ()); + mission = MISSION_MOVE; + } + } + } + Assign_Mission(mission); + Commence(); +} + + +/*********************************************************************************************** + * AircraftClass::Process_Fly_To -- Handles state machine for flying to destination. * + * * + * This support routine is used when the helicopter is to fly to the destination. It can * + * optionally slow the helicopter down as it approaches the destination. * + * * + * INPUT: slowdown -- Should the aircraft be slowed down when it approaches the dest? * + * * + * OUTPUT: Returns with the distance remaining between the aircraft and the destination. * + * * + * WARNINGS: Because the aircraft can be move at a fast speed, the distance to target value * + * will probably never be zero. The likely case will be that the aircraft * + * overshoots the target. * + * * + * HISTORY: * + * 06/14/1995 JLB : Created. * + *=============================================================================================*/ +int AircraftClass::Process_Fly_To(bool slowdown) +{ + Validate(); + COORDINATE coord; + if (Is_Target_Building(NavCom)) { + coord = As_Building(NavCom)->Docking_Coord(); + } else { + coord = As_Coord(NavCom); + } + int distance = Distance(coord); + + PrimaryFacing.Set_Desired(Direction(coord)); + + if (slowdown) { + int speed = MIN(distance, 0x0300); + speed = Bound(speed/3, 0x0020, 0x00FF); + if (Speed != speed) { + Set_Speed(speed); + } + } + + if (distance < 0x0010) { + if (slowdown) { + Set_Speed(0); + } + distance = 0; + } + return(distance); +} + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * AircraftClass::Debug_Dump -- Displays the status of the aircraft to the mono monitor. * + * * + * This displays the current status of the aircraft class to the mono monitor. By this * + * display bugs may be tracked down or prevented. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/02/1994 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::Debug_Dump(MonoClass *mono) const +{ + Validate(); + mono->Set_Cursor(0, 0); + mono->Print( + "ÚName:ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂMission:ÄÄÄÂTarCom:ÂNavCom:ÂRadio:ÂCoord:ÄÄÂAltitudeÂSt:Ä¿\n" + "³ ³ ³ ³ ³ ³ ³ ³ ³\n" + "ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂNÂYÂHealth:ÄÂFdir:ÂÄBdir:ÄÂSpeed:ÂÄÄÄÄÄÁÄÄÄÄÄÄÂCargo:ÄÄÄÄÁÄÄÄÄ´\n" + "³Active........³ ³ ³ ³ ³ ³ ³ ³ ³\n" + "³Limbo.........³ ³ ÃÄÄÄÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´\n" + "³Owned.........³ ³ ³Last Message: ³\n" + "³Discovered....³ ³ ÃTimer:ÂArm:ÂÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÂFlash:ÂStage:ÂTeam:ÄÄÄÄÂArch:´\n" + "³Selected......³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³\n" + "³Teathered.....³ ³ ÃÄÄÄÄÄÄÁÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÙ\n" + "³Locked on Map.³ ³ ³ \n" + "³ ³ ³ ³ \n" + "³Is A Loaner...³ ³ ³ \n" + "³Is Landing....³ ³ ³ \n" + "³Is Taking Off.³ ³ ³ \n" + "³ ³ ³ ³ \n" + "³ ³ ³ ³ \n" + "³ ³ ³ ³ \n" + "³Recoiling.....³ ³ ³ \n" + "³To Display....³ ³ ³ \n" + "ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÁÄÙ \n"); + mono->Set_Cursor(1, 1);mono->Printf("%s:%s", House->Class->IniName, Class->IniName); + mono->Set_Cursor(36, 3);mono->Printf("%02X:%02X", SecondaryFacing.Current(), SecondaryFacing.Desired()); + mono->Set_Cursor(42, 1);mono->Printf("%04X", NavCom); + mono->Set_Cursor(66, 1);mono->Printf("%d", Altitude); + mono->Set_Cursor(44, 3);mono->Printf("%d", Get_Speed()); + mono->Text_Print("X", 16 + (IsLanding?2:0), 12); + mono->Text_Print("X", 16 + (IsTakingOff?2:0), 13); + FootClass::Debug_Dump(mono); +} +#endif + + +/*********************************************************************************************** + * AircraftClass::Active_Click_With -- Handles clicking over specified object. * + * * + * This routine is used when the player clicks over the speicifed object. It will assign * + * the appropriate mission to the aircraft. * + * * + * INPUT: action -- The action that was nominally determined by the What_Action function. * + * * + * object -- The object over which the mouse was clicked. * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine will alter the game sequence and causes an event packet to be * + * propogated to all connected machines. * + * * + * HISTORY: * + * 06/19/1995 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::Active_Click_With(ActionType action, ObjectClass * object) +{ + Validate(); + switch (action) { + case ACTION_ENTER: + Player_Assign_Mission(MISSION_ENTER, TARGET_NONE, object->As_Target()); + break; + + case ACTION_SELF: + Player_Assign_Mission(MISSION_UNLOAD, TARGET_NONE, TARGET_NONE); + break; + + default: + break; + } + FootClass::Active_Click_With(action, object); +} + + +/*********************************************************************************************** + * AircraftClass::Active_Click_With -- Handles clicking over specified cell. * + * * + * This routine is used when the player clicks the mouse of the specified cell. It will * + * assign the appropriate mission to the aircraft. * + * * + * INPUT: action -- The action nominally determined by What_Action(). * + * * + * cell -- The cell over which the mouse was clicked. * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine will affect the game sequence and causes an event object to be * + * propogated to all connected machines. * + * * + * HISTORY: * + * 06/19/1995 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::Active_Click_With(ActionType action, CELL cell) +{ + Validate(); +#ifdef NEVER + switch (action) { + case ACTION_MOVE: + if (Map[cell].IsVisible) { + Player_Assign_Mission(MISSION_MOVE, TARGET_NONE, ::As_Target(cell)); + } + break; + + case ACTION_NOMOVE: + break; + + case ACTION_ATTACK: + Player_Assign_Mission(MISSION_ATTACK, ::As_Target(cell)); + break; + } +#endif + FootClass::Active_Click_With(action, cell); +} + + +/*********************************************************************************************** + * AircraftClass::Player_Assign_Mission -- Handles player input to assign a mission. * + * * + * This routine is called as a result of player input with the intent to change the * + * mission of the aircraft. * + * * + * INPUT: mission -- The mission requested of the aircraft. * + * * + * target -- The value to assign to the aircraft's targeting computer. * + * * + * dest. -- The value to assign to the aircraft's navigation computer. * + * * + * OUTPUT: none * + * * + * WARNINGS: The mission specified will be executed at an indeterminate future game frame. * + * This is controlled by net/modem propogation delay. * + * * + * HISTORY: * + * 06/19/1995 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::Player_Assign_Mission(MissionType mission, TARGET target, TARGET destination) +{ + Validate(); + if (AllowVoice) { + if (mission == MISSION_ATTACK) { + Response_Attack(); + } else { + Response_Move(); + } + } + Queue_Mission(As_Target(), mission, target, destination); +} + + +/*********************************************************************************************** + * AircraftClass::What_Action -- Determines what action to perform. * + * * + * This routine is used to determine what action will likely be performed if the mouse * + * were clicked over the object specified. The display system calls this routine to * + * control the mouse shape. * + * * + * INPUT: target -- Pointer to the object that the mouse is currently over. * + * * + * OUTPUT: Returns with the action that will occur if the mouse were clicked over the * + * object specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/19/1995 JLB : Created. * + *=============================================================================================*/ +ActionType AircraftClass::What_Action(ObjectClass * target) const +{ + Validate(); + ActionType action = FootClass::What_Action(target); + + if (action == ACTION_SELF && !How_Many()) { + action = ACTION_NONE; + } + + if (action == ACTION_ATTACK && Class->Primary == WEAPON_NONE) { + action = ACTION_NONE; + } + + if (IsOwnedByPlayer && House->Is_Ally(target) && target->What_Am_I() == RTTI_BUILDING && ((AircraftClass *)this)->Transmit_Message(RADIO_CAN_LOAD, (TechnoClass*)target) == RADIO_ROGER) { + action = ACTION_ENTER; + } + return(action); +} + + +/*********************************************************************************************** + * AircraftClass::What_Action -- Determines what action to perform. * + * * + * This routine will determine what action would occur if the mouse were clicked over the * + * cell specified. The display system calls this routine to determine what mouse shape * + * to use. * + * * + * INPUT: cell -- The cell over which the mouse is currently positioned. * + * * + * OUTPUT: Returns with the action that will be performed if the mouse were clicked at the * + * specified cell location. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/19/1995 JLB : Created. * + *=============================================================================================*/ +ActionType AircraftClass::What_Action(CELL cell) const +{ + Validate(); + ActionType action = FootClass::What_Action(cell); + + if (action == ACTION_MOVE && GameToPlay == GAME_NORMAL && !Map[cell].IsVisible) { + action = ACTION_NOMOVE; + } + + if (action == ACTION_ATTACK && Class->Primary == WEAPON_NONE) { + action = ACTION_NONE; + } + return(action); +} + + +/*********************************************************************************************** + * AircraftClass::Pose_Dir -- Fetches the natural landing facing. * + * * + * Use this routine to get the desired facing the aircraft should assume when landing. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the normal default facing the aircraft should have when landed. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/19/1995 JLB : Created. * + *=============================================================================================*/ +DirType AircraftClass::Pose_Dir(void) const +{ + Validate(); + if (*this == AIRCRAFT_TRANSPORT) { + return(DIR_N); + } + return(DIR_NE); +} + + +/*********************************************************************************************** + * AircraftClass::Mission_Attack -- Handles the attack mission for aircraft. * + * * + * This routine is the state machine that handles the attack mission for aircraft. It will * + * handling homing in on and firing on the target in the aircraft's targeting computer. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of game ticks to pass before this routine must be called * + * again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/19/1995 JLB : Created. * + *=============================================================================================*/ +int AircraftClass::Mission_Attack(void) +{ + Validate(); + if (Class->IsFixedWing) { + Assign_Mission(MISSION_HUNT); + return(1); + } + + enum { + VALIDATE_AZ, + PICK_ATTACK_LOCATION, + TAKE_OFF, + FLY_TO_POSITION, + FIRE_AT_TARGET, + FIRE_AT_TARGET2, + RETURN_TO_BASE + }; + switch (Status) { + + /* + ** Double check target and validate the attack zone. + */ + case VALIDATE_AZ: + if (!Target_Legal(TarCom)) { + Status = RETURN_TO_BASE; + } else { + Status = PICK_ATTACK_LOCATION; + } + break; + + /* + ** Pick a good location to attack from. + */ + case PICK_ATTACK_LOCATION: + if (!Target_Legal(TarCom)) { + Status = RETURN_TO_BASE; + } else { + Assign_Destination(Good_Fire_Location(TarCom)); + if (Target_Legal(NavCom)) { + Status = TAKE_OFF; + } else { + Status = RETURN_TO_BASE; + } + } + break; + + /* + ** Take off (if necessary). + */ + case TAKE_OFF: + if (!Target_Legal(TarCom)) { + Status = RETURN_TO_BASE; + } else { + if (Process_Take_Off()) { + Status = FLY_TO_POSITION; + + /* + ** Break off radio contact with the helipad it is taking off from. + */ + if (In_Radio_Contact() && Map[Coord_Cell(Coord)].Cell_Building() == Contact_With_Whom()) { + Transmit_Message(RADIO_OVER_OUT); + } + + /* + ** Start flying toward the destination by skewing at first. + ** As the flight progresses, the body will rotate to face + ** the direction of travel. + */ + int diff = SecondaryFacing.Difference(Direction(NavCom)); + diff = Bound(diff, -128, 128); + PrimaryFacing = SecondaryFacing.Current()+diff; + } + return(1); + } + break; + + /* + ** Fly to attack location. + */ + case FLY_TO_POSITION: + if (Target_Legal(TarCom)) { + + /* + ** If the navcom was cleared mysteriously, then try to pick + ** a new attack location. This is a likely event if the player + ** clicks on a new target while in flight to an existing target. + */ + if (!Target_Legal(NavCom)) { + Status = PICK_ATTACK_LOCATION; + return(1); + } + + int distance = Process_Fly_To(true); + + if (distance < 0x0200) { + SecondaryFacing.Set_Desired(Direction(TarCom)); + + if (distance < 0x0010) { + Status = FIRE_AT_TARGET; + Assign_Destination(TARGET_NONE); + } + } else { + SecondaryFacing.Set_Desired(::Direction(Fire_Coord(0), As_Coord(NavCom))); + return(1); + } + } else { + Status = RETURN_TO_BASE; + } + return(1); + + /* + ** Fire at the target. + */ + case FIRE_AT_TARGET: + if (!Target_Legal(TarCom)) { + Status = RETURN_TO_BASE; + return(1); + } + + PrimaryFacing.Set_Desired(Direction(TarCom)); + SecondaryFacing.Set_Desired(Direction(TarCom)); + switch (Can_Fire(TarCom, 0)) { + case FIRE_CLOAKED: + Do_Uncloak(); + break; + + case FIRE_OK: + Fire_At(TarCom, 0); + Map[::As_Cell(TarCom)].Incoming(Coord, true); + Status = FIRE_AT_TARGET2; + break; + + default: + if (!Ammo) { + Status = RETURN_TO_BASE; + } else { + Status = FIRE_AT_TARGET2; + } + break; + } + return(1); + + /* + ** Fire at the target. + */ + case FIRE_AT_TARGET2: + if (!Target_Legal(TarCom)) { + Status = RETURN_TO_BASE; + return(1); + } + + PrimaryFacing.Set_Desired(Direction(TarCom)); + SecondaryFacing.Set_Desired(Direction(TarCom)); + switch (Can_Fire(TarCom, 0)) { + case FIRE_CLOAKED: + Do_Uncloak(); + break; + + case FIRE_REARM: + break; + + case FIRE_OK: + Fire_At(TarCom, 0); + Map[::As_Cell(TarCom)].Incoming(Coord, true); + + if (Ammo) { + Status = PICK_ATTACK_LOCATION; + } else { + Status = RETURN_TO_BASE; + } + break; + + default: + if (!Ammo) { + Status = RETURN_TO_BASE; + } else { + Status = PICK_ATTACK_LOCATION; + } + break; + } + break; + + /* + ** Fly back to landing spot. + */ + case RETURN_TO_BASE: + Assign_Destination(TARGET_NONE); + Enter_Idle_Mode(); + break; + } + + return(TICKS_PER_SECOND/2); +} + + +/*********************************************************************************************** + * AircraftClass::New_LZ -- Find a good landing zone. * + * * + * Use this routine to locate a good landing zone that is nearby the location specified. * + * By using this routine it is possible to assign the same landing zone to several * + * aircraft and they will land nearby without conflict. * + * * + * INPUT: oldlz -- Target value of desired landing zone (usually a cell target value). * + * * + * OUTPUT: Returns with the new good landing zone. It might be the same value passed in. * + * * + * WARNINGS: The landing zone might be a goodly distance away from the ideal if there is * + * extensive blocking terrain in the vicinity. * + * * + * HISTORY: * + * 06/19/1995 JLB : Created. * + *=============================================================================================*/ +TARGET AircraftClass::New_LZ(TARGET oldlz) const +{ + Validate(); + if (Target_Legal(oldlz) && (!Is_LZ_Clear(oldlz) || !Cell_Seems_Ok(As_Cell(oldlz)))) { + COORDINATE coord = As_Coord(oldlz); + + /* + ** Scan outward in a series of concentric rings up to certain distance + ** in cells. + */ + for (int radius = 0; radius < 16; radius++) { + FacingType modifier = Random_Pick(FACING_N, FACING_NW); + CELL lastcell = -1; + + /* + ** Perform a radius scan out from the original center location. Try to + ** find a cell that is allowed to be a legal LZ. + */ + for (FacingType facing = FACING_N; facing < FACING_COUNT; facing++) { + CELL newcell = Coord_Cell(Coord_Move(coord, Facing_Dir(facing+modifier), radius * ICON_LEPTON_W)); + if (Map.In_Radar(newcell)) { + TARGET newtarget = ::As_Target(newcell); + + if (newcell != lastcell && Is_LZ_Clear(newtarget) && Cell_Seems_Ok(newcell)) { + return(newtarget); + } + lastcell = newcell; + } + } + } + } + return(oldlz); +} + + +/*********************************************************************************************** + * AircraftClass::Fire_Coord -- Calculates the point of origin for a bullet. * + * * + * This routine is used to find the exact coordinate where the bullet should appear if * + * fired from this object. * + * * + * INPUT: which -- Which weapon to consider. * + * * + * OUTPUT: Returns with the coordinate of where the projectile will appear if fired. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/15/1995 JLB : Created. * + *=============================================================================================*/ +COORDINATE AircraftClass::Fire_Coord(int ) const +{ + Validate(); + return(Coord_Move(Coord_Add(XYP_Coord(0, -Altitude), Coord), SecondaryFacing, 0x040)); +} + + +COORDINATE AircraftClass::Target_Coord(void) const +{ + Validate(); + return(Coord_Add(XYP_Coord(0, -Altitude), Coord)); +} + + +/*********************************************************************************************** + * AircraftClass::Receive_Message -- Handles receipt of radio messages. * + * * + * This routine receives all radio messages directed at this aircraft. It is used to handle * + * all inter-object coordination. Typically, this would be for transport helicopters and * + * other complex landing operations required of helicopters. * + * * + * INPUT: from -- The source of this radio message. * + * * + * message -- The message itself. * + * * + * param -- An optional parameter that may be used to transfer additional * + * data. * + * * + * OUTPUT: Returns with the radio response from the aircraft. * + * * + * WARNINGS: Some radio messages are handled by the base classes. * + * * + * HISTORY: * + * 06/19/1995 JLB : Created. * + *=============================================================================================*/ +RadioMessageType AircraftClass::Receive_Message(RadioClass * from, RadioMessageType message, long & param) +{ + Validate(); + switch (message) { + + case RADIO_PREPARED: + if (Target_Legal(TarCom)) return(RADIO_NEGATIVE); + if ((Altitude == 0 && Ammo == Class->MaxAmmo) || (Altitude > 0 && Ammo > 0)) return(RADIO_ROGER); + return(RADIO_NEGATIVE); + + /* + ** Something disasterous has happened to the object in contact with. Fall back + ** and regroup. This means that any landing process is immediately aborted. + */ + case RADIO_RUN_AWAY: + if (IsLanding) { + IsLanding = false; + IsTakingOff = true; + } + Scatter(0, true); + break; + + /* + ** The ground control requests that this specified landing spot be used. + */ + case RADIO_MOVE_HERE: + FootClass::Receive_Message(from, message, param); + if (Is_Target_Building(param)) { + if (Transmit_Message(RADIO_CAN_LOAD, As_Techno(param)) != RADIO_ROGER) { + return(RADIO_NEGATIVE); + } + Assign_Mission(MISSION_ENTER); + Assign_Destination((TARGET)param); + } else { + Assign_Mission(MISSION_MOVE); + Assign_Destination((TARGET)param); + } + Commence(); + return(RADIO_ROGER); + + /* + ** Ground control is requesting if the aircraft requires navigation direction. + */ + case RADIO_NEED_TO_MOVE: + FootClass::Receive_Message(from, message, param); + if (!Target_Legal(NavCom) && !IsTakingOff && !IsLanding) { + return(RADIO_ROGER); + } + return(RADIO_NEGATIVE); + + /* + ** This message is sent by the passenger when it determines that it has + ** entered the transport. + */ + case RADIO_IM_IN: + if (How_Many() == Class->Max_Passengers()) { + Close_Door(5, 4); + } + + /* + ** If a civilian has entered the transport, then the transport will immediately + ** fly off the map. + */ + if (from->What_Am_I() == RTTI_INFANTRY && ((InfantryClass *)from)->Class->IsCivilian && !((InfantryClass *)from)->IsTechnician) { + Assign_Mission(MISSION_RETREAT); + } + return(RADIO_ATTACH); + + /* + ** Docking maintenance message received. Check to see if new orders should be given + ** to the impatient unit. + */ + case RADIO_DOCKING: + if (Class->IsTransporter && How_Many() < Class->Max_Passengers()) { + FootClass::Receive_Message(from, message, param); + + if (!IsTethered && !IsLanding && !IsTakingOff && Altitude == 0) { + + Open_Door(5, 4); + + /* + ** If the potential passenger needs someplace to go, then figure out a good + ** spot and tell it to go. + */ + if (Transmit_Message(RADIO_NEED_TO_MOVE, from) == RADIO_ROGER) { + CELL cell; + DirType dir = Desired_Load_Dir(from, cell); + + /* + ** If no adjacent free cells are detected, then passenger loading + ** cannot occur. Break radio contact. + */ + if (cell == 0) { + Transmit_Message(RADIO_OVER_OUT, from); + } else { + param = (long)::As_Target(cell); + + /* + ** Tell the potential passenger where it should go. If the passenger is + ** already at the staging location, then tell it to move onto the transport + ** directly. + */ + if (Transmit_Message(RADIO_MOVE_HERE, param, from) == RADIO_YEA_NOW_WHAT) { + param = (long)As_Target(); + Transmit_Message(RADIO_TETHER); + if (Transmit_Message(RADIO_MOVE_HERE, param, from) != RADIO_ROGER) { + Transmit_Message(RADIO_OVER_OUT, from); + } else { + Contact_With_Whom()->Unselect(); + } + } + } + } + } + return(RADIO_ROGER); + } + break; + + /* + ** Asks if the passenger can load on this transport. + */ + case RADIO_CAN_LOAD: + if (!In_Radio_Contact() && Class->IsTransporter && How_Many() < Class->Max_Passengers() && from && House->Class->House == from->Owner()) { + return(RADIO_ROGER); + } + return(RADIO_NEGATIVE); + } + + /* + ** Let the base class take over processing this message. + */ + return(FootClass::Receive_Message(from, message, param)); +} + + +/*********************************************************************************************** + * AircraftClass::Desired_Load_Dir -- Determines where passengers should line up. * + * * + * This routine is used by the transport helicopter to determine the location where the * + * infantry passengers should line up before loading. * + * * + * INPUT: object -- The object that is trying to load up on this transport. * + * * + * -- Reference to the cell that the passengers should move to before the * + * actual load process may begin. * + * * + * OUTPUT: Returns with the direction that the helicopter should face for the load operation. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/12/1995 JLB : Created. * + * 07/30/1995 JLB : Revamped to scan all adjacent cells. * + *=============================================================================================*/ +DirType AircraftClass::Desired_Load_Dir(ObjectClass * object, CELL & moveto) const +{ + Validate(); + CELL center = Coord_Cell(Center_Coord()); + for (int sweep = FACING_N; sweep < FACING_S; sweep++) { + moveto = Adjacent_Cell(center, FACING_S+sweep); + if (Map.In_Radar(moveto) && (Coord_Cell(object->Center_Coord()) == moveto || Map[moveto].Is_Generally_Clear())) return(DIR_N); + + moveto = Adjacent_Cell(center, FACING_S-sweep); + if (Map.In_Radar(moveto) && (Coord_Cell(object->Center_Coord()) == moveto || Map[moveto].Is_Generally_Clear())) return(DIR_N); + } + return(DIR_N); +} + + +/*********************************************************************************************** + * AircraftClass::Process_Take_Off -- State machine support for taking off. * + * * + * This routine is used by the main game state machine processor. This utility routine * + * handles a helicopter as it transitions from landed to flying state. * + * * + * INPUT: none * + * * + * OUTPUT: Has the helicopter reached flight level now? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/12/1995 JLB : Created. * + *=============================================================================================*/ +bool AircraftClass::Process_Take_Off(void) +{ + Validate(); + IsLanding = false; + IsTakingOff = true; + switch (Altitude) { + case 0: + Close_Door(5, 4); + PrimaryFacing = SecondaryFacing; + break; + + case FLIGHT_LEVEL/2: + PrimaryFacing.Set_Desired(Direction(NavCom)); + break; + + case FLIGHT_LEVEL-(FLIGHT_LEVEL/3): + SecondaryFacing.Set_Desired(PrimaryFacing.Desired()); + Set_Speed(0x20); + break; + + case FLIGHT_LEVEL-(FLIGHT_LEVEL/5): + Set_Speed(0x40); + break; + + case FLIGHT_LEVEL: + Set_Speed(0xFF); + IsTakingOff = false; + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * AircraftClass::Process_Landing -- Landing process state machine handler. * + * * + * This is a support routine that is called by the main state machine routines. This * + * routine is responsible for handling the helicopter as it transitions from flight to * + * landing. * + * * + * INPUT: none * + * * + * OUTPUT: Has the helicopter completely landed now? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/12/1995 JLB : Created. * + *=============================================================================================*/ +bool AircraftClass::Process_Landing(void) +{ + Validate(); + IsTakingOff = false; + IsLanding = true; + switch (Altitude) { + case 0: + IsLanding = false; + return(true); + + case FLIGHT_LEVEL/2: + Set_Speed(0); + break; + + case FLIGHT_LEVEL: + break; + } + return(false); +} + + +/*********************************************************************************************** + * AircraftClass::Can_Enter_Cell -- Determines if the aircraft can land at this location. * + * * + * This routine is used when the passability of a cell needs to be determined. This is * + * necessary when scanning for a location that the aircraft can land. * + * * + * INPUT: cell -- The cell location to check for landing. * + * * + * OUTPUT: Returns a value indicating if the cell is a legal landing spot or not. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/12/1995 JLB : Created. * + *=============================================================================================*/ +MoveType AircraftClass::Can_Enter_Cell(CELL cell, FacingType ) const +{ + Validate(); + if (!Map.In_Radar(cell)) return(MOVE_NO); + + CellClass * cellptr = &Map[cell]; + + if (!cellptr->Cell_Occupier() || + !cellptr->Cell_Occupier()->Is_Techno() || + ((TechnoClass *)cellptr->Cell_Occupier())->House->Is_Ally(House) || + ((TechnoClass *)cellptr->Cell_Occupier())->Cloak != CLOAKED) { + + if (!cellptr->Is_Generally_Clear()) return(MOVE_NO); + } + + if (GameToPlay == GAME_NORMAL && IsOwnedByPlayer && !cellptr->IsVisible) return(MOVE_NO); + + return(MOVE_OK); +} + + +/*********************************************************************************************** + * AircraftClass::Good_Fire_Location -- Searches for and finds a good spot to fire from. * + * * + * Given the specified target, this routine will locate a good spot for the aircraft to * + * fire at the target. * + * * + * INPUT: target -- The target that is desired to be attacked. * + * * + * OUTPUT: Returns with the target location of the place that firing should be made from. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/12/1995 JLB : Created. * + * 06/14/1995 JLB : Finer resolution on ring scan. * + *=============================================================================================*/ +TARGET AircraftClass::Good_Fire_Location(TARGET target) const +{ + Validate(); + if (Target_Legal(target)) { + int range = Weapon_Range(0); + COORDINATE tcoord = As_Coord(target); + CELL bestcell = 0; + CELL best2cell = 0; + int bestval = -1; + int best2val = -1; + + for (int r = range-0x0180; r > 0x0180; r -= 0x0100) { + for (int face = 0; face < 255; face += 16) { + COORDINATE newcoord = Coord_Move(tcoord, (DirType)face, r); + CELL newcell = Coord_Cell(newcoord); + + if (Map.In_Radar(newcell) && (GameToPlay != GAME_NORMAL || Map[newcell].IsVisible) && Cell_Seems_Ok(newcell, true)) { + int dist = Distance(newcoord); + if (bestval == -1 || dist < bestval) { + best2val = bestval; + best2cell = bestcell; + bestval = dist; + bestcell = newcell; + } + } + } + if (bestval != -1) break; + } + + if (best2val == -1) { + best2cell = bestcell; + } + + /* + ** If it found a good firing location, then return this location as + ** a target value. + */ + if (bestval != -1) { + if (Random_Pick(0, 1) == 0) { + return(::As_Target(bestcell)); + } else { + return(::As_Target(best2cell)); + } + } + } + return(TARGET_NONE); +} + + +/*********************************************************************************************** + * AircraftClass::Cell_Seems_Ok -- Checks to see if a cell is good to enter. * + * * + * This routine examines the navigation computers of other aircraft in order to see if the * + * specified cell is safe to fly to. The intent of this routine is to avoid unneccessary * + * mid-air collisions. * + * * + * INPUT: cell -- The cell to examine for clear airspace. * + * * + * strict -- Should the scan consider the aircraft, that is making this check, a * + * blocking aircraft. Typically, the aircraft itself is not considered * + * a blockage -- an aircraft can always exist where it is currently * + * located. A strict check is useful for helicopters that need to move * + * around at the slightest provocation. * + * * + * OUTPUT: Is the specified cell free from airspace conflicts? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/19/1995 JLB : Created. * + *=============================================================================================*/ +bool AircraftClass::Cell_Seems_Ok(CELL cell, bool strict) const +{ + Validate(); + /* + ** Make sure that no other aircraft are heading to the selected location. If they + ** are, then don't consider the location as valid. + */ + TARGET astarget = ::As_Target(cell); + bool ok = true; + for (int index = 0; index < Aircraft.Count(); index++) { + AircraftClass * air = Aircraft.Ptr(index); + if (air && (strict || air != this) && !air->IsInLimbo) { + if (Coord_Cell(air->Coord) == cell || air->NavCom == astarget) { + return(false); + } + } + } + return(true); +} + + +/*********************************************************************************************** + * AircraftClass::Pip_Count -- Returns the number of "objects" in aircraft. * + * * + * This routine is used by the render logic to draw the little container "pips". This * + * corresponds to the number of passengers for a transport helicopter or the number of * + * shots remaining for an attack helicopter. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of "pips" to render on the aircraft. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/11/1995 JLB : Created. * + *=============================================================================================*/ +int AircraftClass::Pip_Count(void) const +{ + Validate(); + int retval = 0; + + if (Class->IsTransporter) { + retval = How_Many(); + } else { + if (Ammo) { + retval = Cardinal_To_Fixed(Class->MaxAmmo, Ammo); + retval = Fixed_To_Cardinal(Class->Max_Pips(), retval); + if (!retval) retval = 1; + } + } + return(retval); +} + + +/*********************************************************************************************** + * AircraftClass::Mission_Enter -- Control aircraft to fly to the helipad or repair center. * + * * + * This routine is used when the aircraft needs to fly for either rearming or repairing. * + * It tries to establish contact with the support building. Once contact is established * + * the ground controller takes care of commanding the aircraft. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the delay before this routine should be called again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/12/1995 JLB : Created. * + * 07/04/1995 JLB : Ground controller gives orders. * + *=============================================================================================*/ +int AircraftClass::Mission_Enter(void) +{ + Validate(); + enum { + INITIAL, + TAKEOFF, + ALTITUDE, + TRAVEL, + LANDING + }; + switch (Status) { + case INITIAL: + if (Altitude < FLIGHT_LEVEL || IsLanding) { + Status = TAKEOFF; + } else { + Status = ALTITUDE; + } + break; + + case TAKEOFF: + if (Process_Take_Off()) { + /* + ** After takeoff is complete, break radio contact with any helipad that this + ** helicopter is taking off from. + */ + if (In_Radio_Contact() && Map[Coord_Cell(Coord)].Cell_Building() == Contact_With_Whom()) { + Transmit_Message(RADIO_OVER_OUT); + } + Status = ALTITUDE; + } + break; + + case ALTITUDE: + /* + ** Establish radio contact with the building this helicopter is trying + ** to land at. + */ + if (In_Radio_Contact()) { + Status = TRAVEL; + } else { + TechnoClass * tech = As_Techno(NavCom); + if (tech && Transmit_Message(RADIO_CAN_LOAD, tech) == RADIO_ROGER) { + Transmit_Message(RADIO_HELLO, tech); + Transmit_Message(RADIO_DOCKING); + Status = TRAVEL; + } else { + Assign_Destination(TARGET_NONE); + Enter_Idle_Mode(); + } + } + break; + + case TRAVEL: + Transmit_Message(RADIO_DOCKING); + if (!In_Radio_Contact()) { + Assign_Destination(TARGET_NONE); + Enter_Idle_Mode(); + } else { + int distance = Process_Fly_To(true); + + if (distance < 0x0080) { + if (Target_Legal(TarCom)) { + SecondaryFacing.Set_Desired(Direction(TarCom)); + } else { + SecondaryFacing.Set_Desired(Pose_Dir()); + } + + if (distance < 0x0010) { + Status = LANDING; + } + break; + } else { + SecondaryFacing.Set_Desired(::Direction(Fire_Coord(0), As_Coord(NavCom))); + } + return(3); + } + break; + + case LANDING: + if (IsTakingOff) { + Assign_Destination(TARGET_NONE); + Enter_Idle_Mode(); + } + if (Process_Landing()) { + switch (Transmit_Message(RADIO_IM_IN)) { + case RADIO_ROGER: + Assign_Mission(MISSION_GUARD); + break; + + case RADIO_ATTACH: + Limbo(); + Contact_With_Whom()->Attach(this); + break; + + default: + Enter_Idle_Mode(); + } + } + break; + } + return(1); +} + + +/*********************************************************************************************** + * AircraftClass::Good_LZ -- Locates a good spot ot land. * + * * + * This routine is used when helicopters need a place to land, but there are no obvious * + * spots (i.e., helipad) available. It will try to land near a friendly helipad or friendly * + * building if there are no helipads anywhere. In the event that there are no friendly * + * buildings anywhere on the map, then just land right where it is flying. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the target location where this aircraft should land. This value may * + * not be a clear cell, but the normal landing logic will resolve that problem. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/12/1995 JLB : Created. * + *=============================================================================================*/ +TARGET AircraftClass::Good_LZ(void) const +{ + Validate(); + /* + ** Scan through all of the buildings and try to land near + ** the helipad (if there is one) or the nearest friendly building. + */ + CELL bestcell; + int bestdist = -1; + for (int index = 0; index < Buildings.Count(); index++) { + BuildingClass * building = Buildings.Ptr(index); + + if (building && !building->IsInLimbo && building->House == House) { + int dist = Distance(building); + if (*building == STRUCT_HELIPAD) { + dist /= 4; + } + if (bestdist == -1 || dist < bestdist) { + bestdist = dist; + bestcell = Coord_Cell(building->Center_Coord()); + } + } + } + + /* + ** Return with the suitable location if one was found. + */ + if (bestdist != -1) { + return(::As_Target(bestcell)); + } + + /* + ** No good location was found. Just try to land here. + */ + return(::As_Target(Coord_Cell(Coord))); +} + + +/*********************************************************************************************** + * AircraftClass::Set_Speed -- Sets the speed for the aircraft. * + * * + * This routine will set the speed for the aircraft. The speed is specified as a fraction * + * of full speed. * + * * + * INPUT: speed -- The fixed point fractional speed setting. 0x00 is stopped, 0xFF is full * + * speed. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/19/1995 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::Set_Speed(int speed) +{ + Validate(); + Fly_Speed(speed, Class->MaxSpeed); +} + + +/*********************************************************************************************** + * AircraftClass::Fire_Direction -- Determines the direction of fire. * + * * + * This routine will determine what direction a projectile would take if it were fired * + * from the aircraft. This is the direction that the aircraft's body is facing. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the direction of projectile fire. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/19/1995 JLB : Created. * + *=============================================================================================*/ +DirType AircraftClass::Fire_Direction(void) const +{ + Validate(); + return(SecondaryFacing.Current()); +} + + +/*********************************************************************************************** + * AircraftClass::~AircraftClass -- Destructor for aircraft object. * + * * + * This is the destructor for aircraft. It will limbo the aircraft if it isn't already * + * and also removes the aircraft from any team it may be attached to. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/24/1995 JLB : Created. * + *=============================================================================================*/ +AircraftClass::~AircraftClass(void) +{ + if (GameActive && Class) { + /* + ** If there are any cargo members, delete them. + */ + while (Is_Something_Attached()) { + delete Detach_Object(); + } + + Limbo(); + } + + if (GameActive && Class && Team) Team->Remove(this); +} + + +/*********************************************************************************************** + * AircraftClass::Scatter -- Causes the aircraft to move away a bit. * + * * + * This routine will cause the aircraft to move away from its current location and then * + * enter some idle mode. Typically this is called when the aircraft is attacked while on * + * the ground. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::Scatter(COORDINATE , bool ) +{ + Validate(); + if (IsLanding || Altitude == 0) { + IsLanding = false; + IsTakingOff = true; + } + Enter_Idle_Mode(); +} + + +/*********************************************************************************************** + * AircraftClass::Rearm_Delay -- Returns the delay between shots for this aircraft. * + * * + * Aircraft have a faster rearm delay than their weapon would otherwise indicate. This is * + * necessary to give helicopters a combat edge while still allowing them to share the * + * weapon types used by ground units. * + * * + * INPUT: second -- Is this for the second shot? The second shot uses the full rearm * + * delay. The first shot, if part of a two shot weapon, is given an * + * abbreviated rearm time. * + * * + * OUTPUT: Returns with the game frames to delay before the next shot can fire. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/10/1995 JLB : Created. * + *=============================================================================================*/ +int AircraftClass::Rearm_Delay(bool second) const +{ + Validate(); + return(FootClass::Rearm_Delay(second)/2); +} + + +/*********************************************************************************************** + * AircraftClass::Threat_Range -- Returns with a range to scan for targets. * + * * + * This routine returns with the distance to scan for targets according to the type of * + * search requested. The search type is typically the weapon (or short) range and vicinity * + * distances. This is used by Guard and Guard Area missions. Aircraft never consider their * + * weapon range in this determination since they can fly so fast, weapon range is * + * meaningless. * + * * + * INPUT: control -- The range control parameter; * + * -1 = range doesn't matter -- return -1 for compatability reasons. * + * 0 = short range scan * + * 1 = long range scan * + * * + * OUTPUT: Returns with the lepton distance to use for the scan operation. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1995 JLB : Created. * + *=============================================================================================*/ +int AircraftClass::Threat_Range(int control) const +{ + Validate(); + if (control == -1) return(-1); + + int range = 20 * ICON_LEPTON_W; + if (control == 1) { + range *= 2; + } + return(range); +} + + +/*********************************************************************************************** + * AircraftClass::Mission_Guard -- Handles aircraft in guard mode. * + * * + * Aircraft don't like to be in guard mode if in flight. If this situation is detected, * + * then figure out what the aircraft should be doing and go do it. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of game frames to delay before calling this routine again. * + * * + * WARNINGS: This routine typically calls the normal guard logic for ground units. * + * * + * HISTORY: * + * 07/18/1995 JLB : Created. * + *=============================================================================================*/ +int AircraftClass::Mission_Guard(void) +{ + Validate(); + if (Altitude == FLIGHT_LEVEL) { + + /* + ** If part of a team, then do nothing, since the team + ** handler will take care of giving this aircraft a + ** mission. + */ + if (Team) { + if (Target_Legal(NavCom)) { + Assign_Mission(MISSION_MOVE); + } + return(TICKS_PER_SECOND); + } + + if (Class->Primary == WEAPON_NONE) { + Assign_Destination(::As_Target(Coord_Cell(Coord))); + Assign_Mission(MISSION_MOVE); + } else { + Enter_Idle_Mode(); + } + return(1); + } + if (House->IsHuman) return(TICKS_PER_SECOND); + + /* + ** Special case to force the GDI helicopter to be brain dead in the Nod + ** mission where it is supposed to be captured. + */ + if (GameToPlay == GAME_NORMAL && Scenario == 7 && House->Class->House == HOUSE_GOOD) { + return(TICKS_PER_SECOND*20); + } + + /* + ** If the aircraft is very badly damaged, then it will search for a + ** repair bay first. + */ + if (House->Available_Money() >= 100 && Health_Ratio() <= 0x0080) { + if (!In_Radio_Contact() || + (Altitude == 0 && + (Contact_With_Whom()->What_Am_I() != RTTI_BUILDING || *((BuildingClass *)Contact_With_Whom()) != STRUCT_REPAIR))) { + + + BuildingClass * building = Find_Docking_Bay(STRUCT_REPAIR, true); + if (building) { + Assign_Destination(building->As_Target()); + Assign_Target(TARGET_NONE); + Assign_Mission(MISSION_ENTER); + return(1); + } + } + } + + /* + ** If the aircraft cannot attack anything because of lack of ammo, + ** abort any normal guard logic in order to look for a helipad + ** to rearm. + */ + if (Ammo == 0 && Class->Primary != WEAPON_NONE) { + if (!In_Radio_Contact()) { + BuildingClass * building = Find_Docking_Bay(STRUCT_HELIPAD, false); + if (building) { + Assign_Destination(building->As_Target()); + Assign_Target(TARGET_NONE); + Assign_Mission(MISSION_ENTER); + return(1); + } + } +// return(TICKS_PER_SECOND*3); + } + + /* + ** If the aircraft already has a target, then attack it if possible. + */ + if (Target_Legal(TarCom)) { + Assign_Mission(MISSION_ATTACK); + return(1); + } + + /* + ** Transport helicopters don't really do anything but just sit there. + */ + if (Class->Primary == WEAPON_NONE) { + return(TICKS_PER_SECOND*3); + } + + /* + ** Computer controlled helicopters will defend themselves by bouncing around + ** and looking for a free helipad. + */ + if (Altitude == 0 && !In_Radio_Contact()) { + Scatter(0, true); + return(TICKS_PER_SECOND*3); + } + + return(FootClass::Mission_Guard()); +} + + +/*********************************************************************************************** + * AircraftClass::Mission_Guard_Area -- Handles the aircraft guard area logic. * + * * + * This routine handles area guard logic for aircraft. Aircraft require special handling * + * for this mode since they are to guard area only if they are in a position to do so. * + * Otherwise they just defend themselves. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of game frames to delay before calling this routine * + * again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/10/1995 JLB : Created. * + *=============================================================================================*/ +int AircraftClass::Mission_Guard_Area(void) +{ + Validate(); + if (Altitude == FLIGHT_LEVEL) { + Enter_Idle_Mode(); + return(1); + } + if (House->IsHuman) return(TICKS_PER_SECOND); + + if (Altitude == 0 && !In_Radio_Contact()) { + Scatter(0, true); + return(TICKS_PER_SECOND*3); + } + + if (Target_Legal(TarCom)) { + Assign_Mission(MISSION_ATTACK); + return(1); + } + return(FootClass::Mission_Guard_Area()); +} + + +/*********************************************************************************************** + * AircraftClass::Response_Attack -- Gives audio response to attack order. * + * * + * This routine is used to give an audio response to an attack order. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/10/1995 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::Response_Attack(void) +{ + Validate(); + static VocType _response[] = { + VOC_AFFIRM, + VOC_ACKNOWL, + VOC_YESSIR, + VOC_YESSIR, + VOC_YESSIR + }; + VocType response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; + if (AllowVoice) { + Sound_Effect(response, 0, -(Aircraft.ID(this)+1)); + } +} + + +/*********************************************************************************************** + * AircraftClass::Response_Move -- Gives audio response to move request. * + * * + * This routine is used to give an audio response to movement orders. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/10/1995 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::Response_Move(void) +{ + Validate(); + static VocType _response[] = { + VOC_MOVEOUT, + VOC_MOVEOUT, + VOC_MOVEOUT, + VOC_ACKNOWL, + VOC_AFFIRM, + VOC_AFFIRM + }; + VocType response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; + if (AllowVoice) { + Sound_Effect(response, 0, -(Aircraft.ID(this)+1)); + } +} + + +/*********************************************************************************************** + * AircraftClass::Response_Select -- Gives audio response when selected. * + * * + * This routine is called when an audio response for selection is desired. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/10/1995 JLB : Created. * + *=============================================================================================*/ +void AircraftClass::Response_Select(void) +{ + Validate(); + static VocType _response[] = { + VOC_VEHIC, + VOC_UNIT, + VOC_YESSIR, + VOC_YESSIR, + VOC_YESSIR, + VOC_AWAIT + }; + VocType response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; + if (AllowVoice) { + Sound_Effect(response, 0, -(Aircraft.ID(this)+1)); + } +} diff --git a/AIRCRAFT.H b/AIRCRAFT.H new file mode 100644 index 0000000..9bbd655 --- /dev/null +++ b/AIRCRAFT.H @@ -0,0 +1,254 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\aircraft.h_v 2.17 16 Oct 1995 16:47:48 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : AIRCRAFT.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : July 22, 1994 * + * * + * Last Update : November 28, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef AIRCRAFT_H +#define AIRCRAFT_H + +#include "radio.h" +#include "fly.h" +#include "target.h" + + +class AircraftClass : public FootClass, public FlyClass +{ + public: + /* + ** This is a pointer to the class control structure for the aircraft. + */ + AircraftTypeClass const * const Class; + + //----------------------------------------------------------------------------- + void * operator new(size_t); + void operator delete(void *); + operator AircraftType(void) const {return Class->Type;}; + AircraftClass(void) : Class(0) {}; + AircraftClass(AircraftType classid, HousesType house); + virtual ~AircraftClass(void); + virtual RTTIType What_Am_I(void) const {return RTTI_AIRCRAFT;}; + + static void Init(void); + enum {FLIGHT_LEVEL=24}; + + virtual int Mission_Attack(void); + virtual int Mission_Unload(void); + virtual int Mission_Hunt(void); + virtual int Mission_Retreat(void); + virtual int Mission_Move(void); + virtual int Mission_Enter(void); + virtual int Mission_Guard(void); + virtual int Mission_Guard_Area(void); + + /* + ** State machine support routines. + */ + bool Process_Take_Off(void); + bool Process_Landing(void); + int Process_Fly_To(bool slowdown); + + /* + ** Query functions. + */ + virtual int Threat_Range(int control) const; + virtual int Rearm_Delay(bool second) const; + virtual MoveType Can_Enter_Cell(CELL cell, FacingType facing=FACING_NONE) const; + virtual LayerType In_Which_Layer(void) const; + virtual ObjectTypeClass const & Class_Of(void) const {return *Class;}; + virtual ActionType What_Action(ObjectClass * target) const; + virtual ActionType What_Action(CELL cell) const; + virtual DirType Desired_Load_Dir(ObjectClass * passenger, CELL & moveto) const; + virtual int Pip_Count(void) const; + TARGET Good_Fire_Location(TARGET target) const; + bool Cell_Seems_Ok(CELL cell, bool landing=false) const; + DirType Pose_Dir(void) const; + TARGET Good_LZ(void) const; + virtual DirType Fire_Direction(void) const; + + /* + ** Landing zone support functionality. + */ + bool Is_LZ_Clear(TARGET target) const; + TARGET New_LZ(TARGET oldlz) const; + + /* + ** Coordinate inquiry functions. These are used for both display and + ** combat purposes. + */ + virtual COORDINATE Sort_Y(void) const; + virtual COORDINATE Fire_Coord(int which) const; + virtual COORDINATE Target_Coord(void) const; + + /* + ** Object entry and exit from the game system. + */ + virtual bool Unlimbo(COORDINATE , DirType facing = DIR_N); + + /* + ** Display and rendering support functionality. Supports imagery and how + ** object interacts with the map and thus indirectly controls rendering. + */ + virtual bool Exit_Object(TechnoClass *); + virtual bool Mark(MarkType mark=MARK_CHANGE); + virtual short const * Overlap_List(void) const; + virtual void Draw_It(int x, int y, WindowNumberType window); + virtual void Set_Speed(int speed); + + /* + ** User I/O. + */ + virtual void Active_Click_With(ActionType action, ObjectClass * object); + virtual void Active_Click_With(ActionType action, CELL cell); + virtual void Player_Assign_Mission(MissionType mission, TARGET target=TARGET_NONE, TARGET destination=TARGET_NONE); + virtual void Response_Select(void); + virtual void Response_Move(void); + virtual void Response_Attack(void); + + /* + ** Combat related. + */ +// virtual bool Target_Something_Nearby(ThreatType threat=THREAT_NORMAL); + virtual ResultType Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source); + virtual BulletClass * Fire_At(TARGET target, int which); + virtual TARGET As_Target(void) const; + + /* + ** AI. + */ + virtual void AI(void); + virtual void Enter_Idle_Mode(bool initial = false); + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, long & param); + virtual void Scatter(COORDINATE threat, bool forced=false); + + /* + ** Scenario and debug support. + */ + #ifdef CHEAT_KEYS + virtual void Debug_Dump(MonoClass *mono) const; + #endif + + /* + ** File I/O. + */ + static void Read_INI(char *buffer); + static void Write_INI(char *buffer); + static char * INI_Name(void) {return "AIRCRAFT";}; + bool Load(FileClass & file); + bool Save(FileClass & file); + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + public: + + /* + ** This is the facing used for the body of the aircraft. Typically, this is the same + ** as the PrimaryFacing, but in the case of helicopters, it can be different. + */ + FacingClass SecondaryFacing; + + private: + + /* + ** Aircraft can be in either state of landing, taking off, or in steady altitude. + ** These flags are used to control transition between flying and landing. It is + ** necessary to handle the transition in this manner so that it occurs smoothly + ** during the graphic processing section. + */ + unsigned IsLanding:1; + unsigned IsTakingOff:1; + + /* + ** It is very common for aircraft to be homing in on a target. When this flag is + ** true, the aircraft will constantly adjust its facing toward the TarCom. When the + ** target is very close (one cell away or less), then this flag is automatically cleared. + ** This is because the homing algorithm is designed to get the aircraft to the destination + ** but no more. Checking when this flag is cleared is a way of flagging transition into + ** a new mode. Example: Transport helicopters go into a hovering into correct position + ** mode when the target is reached. + */ + unsigned IsHoming:1; + + /* + ** Helicopters that are about to land must hover into a position exactly above the landing + ** zone. When this flag is true, the aircraft will be adjusted so that it is exactly over + ** the TarCom. The facing of the aircraft is not altered by this movement. The affect + ** like the helicopter is hovering and shifting sideways to position over the landing + ** zone. When the position is over the landing zone, then this flag is set to false. + */ + unsigned IsHovering:1; + + /* + ** This is the jitter tracker to be used when the aircraft is a helicopter and + ** is flying. It is most noticable when the helicopter is hovering. + */ + unsigned char Jitter; + + public: + /* + ** This is the altitude of the aircraft. It is expressed in pixels that + ** the shadow is offset to the south. If the altitude reaches zero, then + ** the aircraft has landed. The altitude for normal aircraft is at + ** Flight_Level(). + */ + int Altitude; + + private: + + /* + ** This timer controls when the aircraft will reveal the terrain around itself. + ** When this timer expires and this aircraft has a sight range, then the + ** look around process will occur. + */ + TCountDownTimerClass SightTimer; + + /* + ** Most attack aircraft can make several attack runs. This value contains the + ** number of attack runs the aircraft has left. When this value reaches + ** zero then the aircraft is technically out of ammo. + */ + char AttacksRemaining; + + /* + ** This contains the value of the Virtual Function Table Pointer + */ + static void * VTable; +}; + +#endif diff --git a/ALLOC.CPP b/ALLOC.CPP new file mode 100644 index 0000000..05bfff9 --- /dev/null +++ b/ALLOC.CPP @@ -0,0 +1,590 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S ** + *************************************************************************** + * * + * Project Name : Westwood Library * + * * + * File Name : ALLOC.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : February 1, 1992 * + * * + * Last Update : March 9, 1995 [JLB] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * Alloc -- Allocates system RAM. * + * Ram_Free -- Determines the largest free chunk of RAM. * + * Free -- Free an Alloc'ed block of RAM. * + * Resize_Alloc -- Change the size of an allocated block. * + * Heap_Size -- Size of the heap we have. * + * Total_Ram_Free -- Total amount of free RAM. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include +#include +#include +#include +#include +#include + + +#ifndef WWMEM_H +#include "wwmem.h" +#endif + + +extern "C" unsigned long Largest_Mem_Block ( void ) ; + +// +// use double-word alignment for allocs +// +#define LONG_ALIGNMENT 1 + +/* +** Define the equates necessary to call a DPMI interrupt. +*/ +#define DPMI_INT 0x0031 +#define DPMI_LOCK_MEM 0x0600 +#define DPMI_UNLOCK_MEM 0x0601 +#define LOGGING FALSE +/*=========================================================================*/ +/* The following PRIVATE functions are in this file: */ +/*=========================================================================*/ + + +/*= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =*/ + +unsigned long MinRam=0L; // Record of least memory at worst case. +unsigned long MaxRam=0L; // Record of total allocated at worst case. +static unsigned long TotalRam = 0L; +static unsigned long Memory_Calls = 0L; +static unsigned long RequestedSystemRam = 8*1024*1024; +static unsigned long LargestRamBlock = 0L; + +void (*Memory_Error)(void) = NULL; +void (*Memory_Error_Exit)(char *string) = NULL; + +/*************************************************************************** + * DPMI_LOCK -- handles locking a block of DPMI memory * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 06/23/1995 PWG : Created. * + *=========================================================================*/ +#include"mono.h" +void DPMI_Lock(VOID const *ptr, long const size) +{ + union REGS regs; + struct SREGS sregs; + + /* + ** Lock memory + ** AX = 0x600 + ** BX:CX = starting linear address of memory to lock + ** SI:DI = size of region to lock (in bytes) + ** - If Failure, carry flag is set. + */ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_LOCK_MEM; + regs.x.ebx = ((long)ptr & 0xffff0000) >> 16; + regs.x.ecx = ((long)ptr & 0x0000ffff); + regs.x.esi = ((long)size & 0xffff0000) >> 16; + regs.x.edi = ((long)size & 0x0000ffff); + int386x (DPMI_INT, ®s, ®s, &sregs); // call DPMI +// if (regs.x.cflag) { +// } +#if(0) + char *temp = (char *)ptr; + char hold; + for (int lp = 0; lp < size; lp += 2048) { + hold = *temp; + temp += 2048; + } +#endif + +} + +/*************************************************************************** + * DPMI_UNLOCK -- Handles unlocking a locked block of DPMI * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 06/23/1995 PWG : Created. * + *=========================================================================*/ +void DPMI_Unlock(void const *ptr, long const size) +{ + union REGS regs; + struct SREGS sregs; + + /* + ** Unlock the memory + */ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_UNLOCK_MEM; // DPMI function to call + regs.x.ebx = ((long)ptr & 0xffff0000) >> 16; + regs.x.ecx = ((long)ptr & 0x0000ffff); + regs.x.esi = ((long)size & 0xffff0000) >> 16; + regs.x.edi = ((long)size & 0x0000ffff); + int386x (DPMI_INT, ®s, ®s, &sregs); // call DPMI +// if (regs.x.cflag) { +// } + +} + +/*************************************************************************** + * Alloc -- Allocates system RAM. * + * * + * This is the basic RAM allocation function. It is used for all * + * memory allocations needed by the system or the main program. * + * * + * INPUT: bytes_to_alloc -- LONG value of the number of bytes to alloc. * + * * + * flags -- Memory allocation control flags. * + * MEM_NORMAL: No special flags. * + * MEM_CLEAR: Zero out memory block. * + * MEM_NEW: Called by a new. * + * * + * OUTPUT: Returns with pointer to allocated block. If NULL was returned * + * it indicates a failure to allocate. Note: NULL will never be * + * returned if the standard library allocation error routine is * + * used. * + * * + * WARNINGS: If you replace the standard memory allocation error routine * + * and make it so that Alloc CAN return with a NULL, be sure * + * and check for this in your code. * + * * + * HISTORY: * + * 09/03/1991 JLB : Documented. * + * 08/09/1993 JLB : Updated with EMS memory support. * + * 04/28/1994 JAW : Updated to 32bit Protected mode. * + * 03/09/1995 JLB : Fixed * + *=========================================================================*/ +void *Alloc(unsigned long bytes_to_alloc, MemoryFlagType flags) +{ + union REGS regs ; + struct SREGS sregs ; + unsigned char *retval=NULL; // Pointer to allocated block. + unsigned long original_size; // Original allocation size. + unsigned long bytesfree; // Number of free bytes. + long *longptr=NULL; // Pointer used to store selector + static unsigned char _allocinit=0; + + + // + // Init memory system by finding largest block to alloc + // then allocate it to get one large heap and free it. + // There may be more memory available from DPMI but we only are + // for now allocating and freeing the first largest block. + // + if ( !_allocinit ) { + unsigned long largestblock = Largest_Mem_Block(); + largestblock -= 1024; // subtract for heap header and misc + largestblock &= 0xffff0000; // forcing to 64K boundary + + if ( largestblock ) { + LargestRamBlock = MIN( largestblock, RequestedSystemRam ); + unsigned char *lptr = (unsigned char *)malloc( LargestRamBlock ); + if ( lptr ) { + free( (void *)lptr ); + } + } + + /* + ** Initialize the total ram available value. + */ + TotalRam = Total_Ram_Free(MEM_NORMAL); + + _allocinit = 1; + } + + /* + ** Save the original allocated space size so that we can clear the + ** exact amount of RAM if they specified MEM_CLEAR. + */ + original_size = bytes_to_alloc; + + /* + ** Reserve one byte for the header of the memory we allocated. + ** We will store the flags variable there for later use. + */ +#if (LONG_ALIGNMENT) + bytes_to_alloc += (flags & MEM_LOCK) ? 8 : 4; +#else + bytes_to_alloc += (flags & MEM_LOCK) ? 5 : 1; +#endif + + + // Try to allocate the memory out of the protected mode memory + // chain if we did not require a real mode allocation. If this + // fails we will have to try to allocate it out of real mode memory. + // Real mode memory is a last resort because some types of applications + // require real mode memory. + if (!(flags & MEM_REAL)) { + retval = (unsigned char*)malloc(bytes_to_alloc); + } + + // Try to allocate the memory out of the real mode memory using DPMI + // service 0x100. Note that retval will be null if we are requesting + // real mode memory so that we do not have to explicitly check for the + // real mode flag. Remember we need to reserve room for the dos + // selector value at the beginning of our allocated block so rather than + // adding fifteen and rounding, we need to add 19 and round. + if (!retval) { + flags = (MemoryFlagType)(flags | MEM_REAL); + regs.x.eax = 0x100; + regs.x.ebx = (bytes_to_alloc + 19) >> 4; + if (regs.x.ebx & 0xFFFF0000) { + retval = NULL; + } else { + segread ( & sregs ) ; + int386x ( 0x31 , & regs, & regs , & sregs ) ; + if (regs.x.cflag) + retval = NULL; + else { +#if (LONG_ALIGNMENT) + longptr = (long *)(((regs.x.eax & 0xFFFF) << 4)+ 4); +#else + longptr = (long *)(((regs.x.eax & 0xFFFF) << 4)+ 1); +#endif + *longptr++ = regs.x.edx & 0xFFFF; + retval = (unsigned char *)longptr; + } + } + } + + // If the alloc failed then we need to signify a memory error. + if (retval == NULL) { + if (Memory_Error != NULL) + Memory_Error(); + return NULL; + } + + // If the memory needs to be DPMI locked then we should store the + // original size in the header before we store the flags. + if (flags & MEM_LOCK) { + longptr = (long *)retval; + *longptr++ = original_size; + retval = (unsigned char *)longptr; + } + + + // Now that we know the alloc was sucessful (and for an extra byte + // more than the user wanted) we need to stick in the memory flags. +#if (LONG_ALIGNMENT) + if ( !(flags & (MEM_LOCK|MEM_REAL)) ) { + // + // WARNING!!!!!!!!!! + // USE this only with the WATCOM malloc ALLOCATION!!!!!!!!! + // it reads the actual block size before the ptr returned. + // then eors and uses the upper word for a validation later on free. + // + longptr = (long *)retval; + *longptr = ((*(longptr - 1)) ^ 0xffffffff) & 0xffff0000; + *retval++ = flags; + *retval++ = (unsigned char)(flags ^ 0xff); + retval += 2; + } + else { + *retval++ = flags; + *retval++ = (unsigned char)(flags ^ 0xff); + *retval++ = 0; + *retval++ = 0; + } +#else + *retval++ = (unsigned char)(flags | (((flags ^ 0x07) & 0x07) << 5)); +#endif + + // If the memory needed to be DPMI locked then set it up so it + // is locked. + if (flags & MEM_LOCK) { + DPMI_Lock(retval, original_size); + } + + /* Clear the space if they wanted it clear */ + + if (flags & MEM_CLEAR) { + unsigned char *ptr; // Working memory block pointer. + + ptr = retval; + memset(ptr, '\0', original_size); + } + + bytesfree = Total_Ram_Free(MEM_NORMAL); + if (bytesfree < MinRam) { + MinRam = bytesfree; + } + if (TotalRam-bytesfree > MaxRam) { + MaxRam = TotalRam-bytesfree; + } + + Memory_Calls++; + +#if(LOGGING) + int val = _heapchk(); + + FILE *file = fopen("mem.txt","at"); + fprintf(file, "%P Alloc size = %d, Actual Size = %d, flags = %d, heap = %d\n", + retval, + original_size, + bytes_to_alloc, + flags, + val); + fclose(file); +#endif + + return(retval); +} + + +/*************************************************************************** + * Free -- Free an Alloc'ed block of RAM. * + * * + * FUNCTION: * + * * + * INPUT: A pointer to a block of RAM from Alloc. * + * * + * OUTPUT: None. * + * * + * WARNINGS: Don't use this for an Alloc_Block'ed RAM block. * + * * + * HISTORY: * + * 05/25/1990 : Created. * + ***************************************************************************/ +void Free(void const *pointer) +{ + union REGS regs ; + struct SREGS sregs ; + + void const *original = pointer; + char string[80]; + + if (pointer) { + /* + ** Get a pointer to the flags that we stored off. + */ +#if (LONG_ALIGNMENT) + unsigned char *byteptr = ((unsigned char *)pointer) - 4; + + // + // validate the flags with and eor of the flags + // + if ( *byteptr != ((*(byteptr + 1)) ^ 0xff) ) { + if (Memory_Error_Exit != NULL) { + sprintf( string, "Error freeing pointer %p. Header invalid!!!\n", pointer ); + Memory_Error_Exit( string ); + } + } + else { + if ( !(*byteptr & (MEM_LOCK|MEM_REAL)) ) { + unsigned short *wordptr = (unsigned short *)(byteptr - 2); + + // + // WARNING!!!!!!!!!! + // USE this only with the WATCOM malloc ALLOCATION!!!!!!!!! + // it reads the actual block size before the ptr to be freed. + // then compares with the EOR to the value stored during allocation. + // + if ( *wordptr != ((*(wordptr + 2)) ^ 0xffff) ) { + if (Memory_Error_Exit != NULL) { + sprintf( string, "Error freeing pointer %p. Header invalid!!!\n", pointer ); + Memory_Error_Exit( string ); + } + } + } + else if ( *(byteptr + 2) || *(byteptr + 3) ) { + if (Memory_Error_Exit != NULL) { + sprintf( string, "Error freeing pointer %p. Header invalid!!!\n", pointer ); + Memory_Error_Exit( string ); + } + } + } +// if ( *byteptr != (*(byteptr + 1) ^ 0xff) || +// *(byteptr + 2) || *(byteptr + 3) ) { +// if (Memory_Error_Exit != NULL) { +// sprintf( string, "Error freeing pointer %p. Header invalid!!!\n", pointer ); +// Memory_Error_Exit( string ); +// } +// } +#else + unsigned char *byteptr = ((unsigned char *)pointer) - 1; + + if ( (*byteptr & 0xe0) != (((*byteptr ^ 0x07) & 0x07) << 5) ) { + if (Memory_Error_Exit != NULL) { + sprintf( string, "Error freeing pointer %p. Header invalid!!!\n", pointer ); + Memory_Error_Exit( string ); + } + } +#endif + + /* + ** Check to see if this was locked me and if it was unlock it. + */ + if (*byteptr & MEM_LOCK) { + long *longptr = ((long *)byteptr) - 1; + DPMI_Unlock(pointer, *longptr); + pointer = (void *)longptr; + } else + pointer = (void *)byteptr; + +#if(LOGGING) + int val = _heapchk(); + FILE *file = fopen("mem.txt","at"); + fprintf(file, "%P Free flags = %d, Heap = %d\n", + original, + *byteptr, + val); + fclose(file); +#endif + + // If the pointer is a real mode pointer than it will point to the + // first megabyte of system memory. If it does than we need to + // use DPMI to free it. + if (*byteptr & MEM_REAL) { + regs.x.eax = 0x101; + regs.x.edx = *(((long *)pointer) - 1); + segread ( & sregs ) ; + int386x(0x31, ®s, ®s, &sregs); + } else { + free((void *)pointer); + } + Memory_Calls--; + } +} + + +/*************************************************************************** + * Resize_Alloc -- Change the size of an allocated block. * + * * + * This routine will take a previously allocated block and change its * + * size without unnecessarily altering its contents. * + * * + * INPUT: pointer -- Pointer to the original memory allocation. * + * * + * new_size -- Size in bytes that it will be converted to. * + * * + * OUTPUT: Returns with a pointer to the new allocation. * + * * + * WARNINGS: ??? * + * * + * HISTORY: * + * 02/01/1992 JLB : Commented. * + *=========================================================================*/ +void *Resize_Alloc(void *original_ptr, unsigned long new_size_in_bytes) +{ + + unsigned long *temp; +// unsigned long diff, flags; + + temp = (unsigned long*)original_ptr; + + /* ReAlloc the space */ + temp = (unsigned long *)realloc(temp, new_size_in_bytes); + if (temp == NULL) { + if (Memory_Error != NULL) + Memory_Error(); + return NULL; + } + + return(temp); +} + + +/*************************************************************************** + * Ram_Free -- Determines the largest free chunk of RAM. * + * * + * Use this routine to determine the largest free chunk of available * + * RAM for allocation. It also performs a check of the memory chain. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the size of the largest free chunk of RAM. * + * * + * WARNINGS: This does not return the TOTAL memory free, only the * + * largest free chunk. * + * * + * HISTORY: * + * 09/03/1991 JLB : Commented. * + *=========================================================================*/ +long Ram_Free(MemoryFlagType) +{ + return(_memmax()); +// return Largest_Mem_Block(); +} + + +/*************************************************************************** + * Heap_Size -- Size of the heap we have. * + * * + * * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 06/21/1994 SKB : Created. * + *=========================================================================*/ +long Heap_Size(MemoryFlagType ) +{ + if (!TotalRam) { + TotalRam = Total_Ram_Free(MEM_NORMAL); + } + return(TotalRam); +} + + +/*************************************************************************** + * Total_Ram_Free -- Total amount of free RAM. * + * * + * * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 06/21/1994 SKB : Created. * + * 03/09/1995 JLB : Uses prerecorded heap size maximum. * + *=========================================================================*/ +long Total_Ram_Free(MemoryFlagType ) +{ + return(_memavl()); +// return Largest_Mem_Block () ; +} + diff --git a/ANIM.CPP b/ANIM.CPP new file mode 100644 index 0000000..43b070a --- /dev/null +++ b/ANIM.CPP @@ -0,0 +1,1175 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\anim.cpv 2.18 16 Oct 1995 16:48:48 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Dune * + * * + * File Name : ANIM.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : June 3, 1991 * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * AnimClass::AI -- This is the low level anim processor. * + * AnimClass::Adjust_Coord -- Adjusts anim coordinates * + * AnimClass::AnimClass -- The constructor for animation objects. * + * AnimClass::As_Target -- Converts the animation into a target value. * + * AnimClass::Attach_To -- Attaches animation to object specified. * + * AnimClass::Center_Coord -- Determine center of animation. * + * AnimClass::Detach -- Remove animation if attached to target. * + * AnimClass::Draw_It -- Draws the animation at the location specified. * + * AnimClass::In_Which_Layer -- Determines what render layer the anim should be in. * + * AnimClass::Init -- Performs pre-scenario initialization. * + * AnimClass::Mark -- Signals to map that redrawing is necessary. * + * AnimClass::Middle -- Processes any middle events. * + * AnimClass::Occupy_List -- Determines the occupy list for the animation. * + * AnimClass::Overlap_List -- Determines the overlap list for the animation. * + * AnimClass::Render -- Draws an animation object. * + * AnimClass::Sort_Y -- Returns with the sorting coordinate for the animation. * + * AnimClass::Start -- Processes initial animation side effects. * + * AnimClass::delete -- Returns an anim object back to the free pool. * + * AnimClass::new -- Allocates an anim object from the pool. * + * AnimClass::~AnimClass -- Destructor for anim objects. * + * AnimClass::Validate -- validates anim pointer * + * Shorten_Attached_Anims -- Reduces attached animation durations. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/* +** This contains the value of the Virtual Function Table Pointer +*/ +void * AnimClass::VTable; + + +/*********************************************************************************************** + * AnimClass::Validate -- validates anim pointer * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int AnimClass::Validate(void) const +{ + int num; + + num = Anims.ID(this); + if (num < 0 || num >= ANIM_MAX) { + Validate_Error("ANIM"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*********************************************************************************************** + * Shorten_Attached_Anims -- Reduces attached animation durations. * + * * + * This routine is used to reduce the amount of time any attached animations will process. * + * Typical use of this is when an object is on fire and the object should now be destroyed * + * but the attached animations are to run until completion before destruction can follow. * + * This routine will make the animation appear to run its course, but in as short of time * + * as possible. The shortening effect is achieved by reducing the number of times the * + * animation will loop. * + * * + * INPUT: obj -- Pointer to the object that all attached animations will be processed. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/11/1994 JLB : Created. * + *=============================================================================================*/ +void Shorten_Attached_Anims(ObjectClass * obj) +{ + if (obj) { + for (int index = 0; index < Anims.Count(); index++) { + AnimClass & anim = *Anims.Ptr(index); + + if (anim.Object == obj) { + anim.Loops = 0; + } + } + } +} + + +/*********************************************************************************************** + * AnimClass::Sort_Y -- Returns with the sorting coordinate for the animation. * + * * + * This routine is used by the sorting system. Animations that are located in the ground * + * layer will be sorted by this the value returned from this function. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the sort coordinate to use for this animation. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + * 12/15/1994 JLB : Handles flat anims (infantry decay anims). * + *=============================================================================================*/ +COORDINATE AnimClass::Sort_Y(void) const +{ + Validate(); + if (Object) { + return(Coord_Add(Object->Sort_Y(), 0x00010000L)); + } + if (*this == ANIM_MOVE_FLASH) { + return(Coord_Add(Center_Coord(), XYP_COORD(0, -24))); + } + if (*this == ANIM_LZ_SMOKE) { + return(Coord_Add(Center_Coord(), XYP_COORD(0, 14))); + } + return(Coord); +} + + +/*********************************************************************************************** + * AnimClass::Center_Coord -- Determine center of animation. * + * * + * This support function will return the "center" of the animation. The actual coordinate * + * of the animation may be dependant on if the the animation is attached to an object. * + * In such a case, it must factor in the object's location. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the coordinate of the center of the animation. The coordinate is in real * + * game coordinates -- taking into consideration if the animation is attached. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +COORDINATE AnimClass::Center_Coord(void) const +{ + Validate(); + if (Object) { + return(Coord_Add(Coord, Object->Center_Coord())); + } + return(Coord); +} + + +/*********************************************************************************************** + * AnimClass::Render -- Draws an animation object. * + * * + * This is the working routine that renders the animation shape. It gets called once * + * per animation per frame. It needs to be fast. * + * * + * INPUT: bool; Should the animation be rendered in spite of render flag? * + * * + * OUTPUT: bool; Was the animation rendered? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + *=============================================================================================*/ +bool AnimClass::Render(bool forced) +{ + Validate(); + if (Delay) return(false); + IsToDisplay = true; + return(ObjectClass::Render(forced)); +} + + +/*********************************************************************************************** + * AnimClass::Draw_It -- Draws the animation at the location specified. * + * * + * This routine is used to render the animation object at the location specified. This is * + * how the map imagery gets updated. * + * * + * INPUT: x,y -- The pixel coordinates to draw the animation at. * + * * + * window -- The to base the draw coordinates upon. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + * 05/19/1995 JLB : Added white translucent effect. * + *=============================================================================================*/ +#pragma off (unreferenced) +void AnimClass::Draw_It(int x, int y, WindowNumberType window) +{ + Validate(); + if (!IsInvisible) { + void const * shapefile = Class->Get_Image_Data(); + if (shapefile) { + void const * transtable = NULL; + int shapenum = Class->Start + Fetch_Stage(); + void const * remap = NULL; + + /* + ** Some animations require special fixups. + */ + switch (Class->Type) { + case ANIM_ION_CANNON: + y -= Get_Build_Frame_Height(shapefile) >> 1; + y += 12; + break; + + case ANIM_RAPT_DIE: + case ANIM_STEG_DIE: + case ANIM_TREX_DIE: + case ANIM_TRIC_DIE: + case ANIM_ATOM_BLAST: + transtable = Map.UnitShadow; + break; + } + + /* + ** If the translucent table hasn't been determined yet, then check to see if it + ** should use the white or normal translucent tables. + */ + if (!transtable && Class->IsWhiteTrans) transtable = Map.WhiteTranslucentTable; + if (!transtable && Class->IsTranslucent) transtable = Map.TranslucentTable; + + /* + ** Set the shape flags to properly take into account any fading or ghosting + ** table necessary. + */ + ShapeFlags_Type flags = SHAPE_CENTER|SHAPE_WIN_REL; + if (IsAlternate) { + flags = flags | SHAPE_FADING; + remap = Map.RemapTables[HOUSE_GOOD][0]; + } + if (transtable) flags = flags | SHAPE_GHOST; + + /* + ** Draw the animation shape. + */ + CC_Draw_Shape(shapefile, shapenum, x, y, window, flags, remap, transtable); + } + } +} + + +/*********************************************************************************************** + * AnimClass::Mark -- Signals to map that redrawing is necessary. * + * * + * This routine is used by the animation logic system to inform the map that the cells * + * under the animation must be rerendered. * + * * + * INPUT: * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + *=============================================================================================*/ +bool AnimClass::Mark(MarkType mark) +{ + Validate(); + if (ObjectClass::Mark(mark)) { + Map.Refresh_Cells(Coord_Cell(Center_Coord()), Overlap_List()); + ObjectClass::Mark(mark); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * AnimClass::Overlap_List -- Determines the overlap list for the animation. * + * * + * Use this routine to fetch the overlap list for the animation. This overlap list is the * + * cells that this animation spills over. * + * * + * INPUT: none * + * * + * OUTPUT: Returns a pointer to the overlap list for this particular instance of the * + * animation. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/19/1995 JLB : Created. * + *=============================================================================================*/ +short const * AnimClass::Overlap_List(void) const +{ + Validate(); + static short const OverlapN[] = {0, -MAP_CELL_W, -(MAP_CELL_W+1), -(MAP_CELL_W-1), -(2*MAP_CELL_W), -(2*MAP_CELL_W-1), -(2*MAP_CELL_W+1), REFRESH_EOL}; + static short const OverlapNW[] = {0, -1, -MAP_CELL_W, -(MAP_CELL_W+1), -(MAP_CELL_W+2), -(MAP_CELL_W*2+2), -(MAP_CELL_W*2+1), REFRESH_EOL}; + static short const OverlapW[] = {0, -1, -2, -(MAP_CELL_W+1), -(MAP_CELL_W+2), REFRESH_EOL}; + static short const OverlapSW[] = {0, -1, MAP_CELL_W, (MAP_CELL_W-1), (MAP_CELL_W-2), (MAP_CELL_W*2-2), (MAP_CELL_W*2-1), REFRESH_EOL}; + static short const OverlapS[] = {0, MAP_CELL_W-1, MAP_CELL_W, MAP_CELL_W+1, 2*MAP_CELL_W+1, 2*MAP_CELL_W, 2*MAP_CELL_W-1, REFRESH_EOL}; + static short const OverlapSE[] = {0, 1, MAP_CELL_W, (MAP_CELL_W+1), (MAP_CELL_W+2), (MAP_CELL_W*2+2), (MAP_CELL_W*2+1), REFRESH_EOL}; + static short const OverlapE[] = {0, 1, 2, -(MAP_CELL_W-1), -(MAP_CELL_W-2), REFRESH_EOL}; + static short const OverlapNE[] = {0, 1, -MAP_CELL_W, -(MAP_CELL_W-1), -(MAP_CELL_W-2), -(MAP_CELL_W*2-2), -(MAP_CELL_W*2-1), REFRESH_EOL}; + static short const OverlapIon[] = { + (-MAP_CELL_W * 7) - 1, (-MAP_CELL_W * 7), (-MAP_CELL_W * 7) + 1, + (-MAP_CELL_W * 6) - 1, (-MAP_CELL_W * 6), (-MAP_CELL_W * 6) + 1, + (-MAP_CELL_W * 5) - 1, (-MAP_CELL_W * 5), (-MAP_CELL_W * 5) + 1, + (-MAP_CELL_W * 4) - 1, (-MAP_CELL_W * 4), (-MAP_CELL_W * 4) + 1, + (-MAP_CELL_W * 3) - 1, (-MAP_CELL_W * 3), (-MAP_CELL_W * 3) + 1, + (-MAP_CELL_W * 2) - 1, (-MAP_CELL_W * 2), (-MAP_CELL_W * 2) + 1, + (-MAP_CELL_W * 1) - 1, (-MAP_CELL_W * 1), (-MAP_CELL_W * 1) + 1, + (-MAP_CELL_W * 0) - 1, (-MAP_CELL_W * 0), (-MAP_CELL_W * 0) + 1, + REFRESH_EOL + }; + + static short const OverlapAtom[] = { + (-MAP_CELL_W * 2) - 1, (-MAP_CELL_W * 2), (-MAP_CELL_W * 2) + 1, + (-MAP_CELL_W * 1) - 1, (-MAP_CELL_W * 1), (-MAP_CELL_W * 1) + 1, + (-MAP_CELL_W * 0) - 1, (-MAP_CELL_W * 0), (-MAP_CELL_W * 0) + 1, + ( MAP_CELL_W * 1) - 1, ( MAP_CELL_W * 1), ( MAP_CELL_W * 1) + 1, + ( MAP_CELL_W * 2) - 1, ( MAP_CELL_W * 2), ( MAP_CELL_W * 2) + 1, + REFRESH_EOL + }; + + switch (Class->Type) { + case ANIM_CHEM_N: + case ANIM_FLAME_N: + return(OverlapN); + + case ANIM_CHEM_NW: + case ANIM_FLAME_NW: + return(OverlapNW); + + case ANIM_CHEM_W: + case ANIM_FLAME_W: + return(OverlapW); + + case ANIM_CHEM_SW: + case ANIM_FLAME_SW: + return(OverlapSW); + + case ANIM_CHEM_S: + case ANIM_FLAME_S: + return(OverlapS); + + case ANIM_CHEM_SE: + case ANIM_FLAME_SE: + return(OverlapSE); + + case ANIM_CHEM_E: + case ANIM_FLAME_E: + return(OverlapE); + + case ANIM_CHEM_NE: + case ANIM_FLAME_NE: + return(OverlapNE); + + case ANIM_ION_CANNON: + return(OverlapIon); + + case ANIM_ATOM_BLAST: + return(OverlapAtom); + + default: + break; + } + return(Coord_Spillage_List(Center_Coord(), Class->Size)); +} + + +/*********************************************************************************************** + * AnimClass::Occupy_List -- Determines the occupy list for the animation. * + * * + * Animations always occupy only the cell that their center is located over. As such, this * + * routine always returns a simple (center cell) occupation list. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the occupation list for the animation. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/19/1995 JLB : Created. * + *=============================================================================================*/ +short const * AnimClass::Occupy_List(void) const +{ + Validate(); + static short _simple[] = {REFRESH_EOL}; + + return(_simple); +} + + +/*********************************************************************************************** + * AnimClass::Init -- Performs pre-scenario initialization. * + * * + * This routine is used to initialize the animation system prior to a scenario being loaded * + * or reloaded. It effectively removes all animations from the system. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + *=============================================================================================*/ +void AnimClass::Init(void) +{ + AnimClass *ptr; + + Anims.Free_All(); + + ptr = new AnimClass(); + VTable = ((void **)(((char *)ptr) + sizeof(AbstractClass) - 4))[0]; + delete ptr; +} + + +/*********************************************************************************************** + * AnimClass::new -- Allocates an anim object from the pool. * + * * + * This routine is used to allocate a free anim class object from the preallocated pool * + * in the near heap. If there are no free animation objects, then null is returned. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to a free anim object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + *=============================================================================================*/ +void *AnimClass::operator new(size_t) +{ + void * ptr = Anims.Allocate(); + if (ptr) { + ((AnimClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +/*********************************************************************************************** + * AnimClass::delete -- Returns an anim object back to the free pool. * + * * + * This routine is used to return an anim object back to the pool of free anim objects. * + * Anim objects so returned are available to be reallocated for the next animation. * + * * + * INPUT: ptr -- Pointer to the anim object to return to the pool. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + *=============================================================================================*/ +void AnimClass::operator delete(void *ptr) +{ + if (ptr) { + ((AnimClass *)ptr)->IsActive = false; + } + Anims.Free((AnimClass *)ptr); +} + + +/*********************************************************************************************** + * AnimClass::AnimClass -- The constructor for animation objects. * + * * + * This routine is used as the constructor of animation objects. It initializes and adds * + * the animation object to the display and logic systems. * + * * + * INPUT: animnum -- The animation number to start. * + * * + * coord -- The location of the animation. * + * * + * timedelay-- The delay before the animation starts. * + * * + * loop -- The number of times to loop this animation. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + * 08/03/1994 JLB : Added a delayed affect parameter. * + *=============================================================================================*/ +AnimClass::AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay, unsigned char loop, bool alt) : + Class(&AnimTypeClass::As_Reference(animnum)) +{ + Object = 0; + Owner = HOUSE_NONE; + + if (Class->Stages == -1) { + ((int&)Class->Stages) = Get_Build_Frame_Count(Class->Get_Image_Data()); + } + if (Class->LoopEnd == -1) { + ((int&)Class->LoopEnd) = Class->Stages; + } + if (Class->IsNormalized) { + Set_Rate(Options.Normalize_Delay(Class->Delay)); + } else { + Set_Rate(Class->Delay); + } + Set_Stage(0); + + Accum = 0; + coord = Adjust_Coord(coord); + Unlimbo(coord); + + /* + ** Drop zone smoke always reveals the map around itself. + */ + if (*this == ANIM_LZ_SMOKE) { + Map.Sight_From(Coord_Cell(coord), 4, false); + } + + /* + ** Determine the time before the first animation process. For time delayed + ** animations, this is the value passed as a parameter. + */ + Delay = timedelay; + + Loops = (unsigned char)(MAX(loop, 1) * Class->Loops); + Loops = (unsigned char)MAX(Loops, 1); + + IsToDelete = false; + IsBrandNew = true; + IsAlternate = alt; + IsInvisible = false; + + /* + ** If the animation starts immediately, then play the associated sound effect now. + */ + if (!Delay) { + Start(); + } +} + + +/*********************************************************************************************** + * AnimClass::~AnimClass -- Destructor for anim objects. * + * * + * This destructor handles removing the animation object from the system. It might require * + * informing any object this animation is attached to that it is no longer attached. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/29/1994 JLB : Created. * + *=============================================================================================*/ +AnimClass::~AnimClass(void) +{ + Validate(); + if (GameActive) { + + /* + ** If this anim is attached to another object + ** then check to see if this is the last anim attached to it. If this + ** is the case, then inform the object that it is no longer attached to + ** an animation. + */ + if (Object) { + ObjectClass * to = Object; + + Object = 0; + + /* + ** Scan for any other animations that are attached to the object that + ** this animation is attached to. If there are no others, then inform the + ** attached object of this fact. + */ + for (int index = 0; index < Anims.Count(); index++) { + if (Anims.Ptr(index)->Object == to) break; + } + + /* + ** Tell the object that it is no longer being damaged. + */ + if (index != Anims.Count()) { + to->Fire_Out(); + } + to->Mark(MARK_OVERLAP_UP); + to->IsAnimAttached = false; + to->Mark(MARK_OVERLAP_DOWN); + Object = to; + } + + Limbo(); + Object = 0; + } +} + + +/*********************************************************************************************** + * AnimClass::AI -- This is the low level anim processor. * + * * + * This routine is called once per frame per animation. It handles transition between * + * animation frames and marks the map for redraw as necessary. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Speed is of upmost importance. * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + *=============================================================================================*/ +void AnimClass::AI(void) +{ + Validate(); + /* + ** For ground level based animations (ones that can run slowly as well as + ** occur behind other ground objects) always cause the cell to be redrawn. + */ + if (!Delay && Class->IsGroundLayer) { + Map.Refresh_Cells(Coord_Cell(Center_Coord()), Overlap_List()); + } + + /* + ** Special case check to make sure that building on top of a smoke marker + ** causes the smoke marker to vanish. + */ + if (Class->Type == ANIM_LZ_SMOKE && Map[Coord_Cell(Center_Coord())].Cell_Building()) { + IsToDelete = true; + } + + /* + ** Delete this animation and bail early if the animation is flagged to be deleted + ** immediately. + */ + if (IsToDelete) { + delete this; + return; + } + + /* + ** If this is a brand new animation, then don't process it the first logic pass + ** since it might end up skipping the first animation frame before it has had a + ** chance to draw it. + */ + if (IsBrandNew) { + IsBrandNew = false; + return; + } + + if (Delay) { + Delay--; + if (!Delay) { + Start(); + } + } else { + + /* + ** This is necessary because there is no recording of animations on the map + ** and thus the animation cannot be intelligently flagged for redraw. Most + ** animations move fast enough that they would need to be redrawn every + ** game frame anyway so this isn't TOO bad. + */ + Mark(MARK_CHANGE); + + if (StageClass::Graphic_Logic()) { + int stage = Fetch_Stage(); + + /* + ** If this animation is attached to another object and it is a + ** damaging kind of animation, then do the damage to the other + ** object. + */ + if (Object && Class->Damage) { + unsigned int accum = Accum; + + accum += Class->Damage; + + if (accum > 255) { + + /* + ** Administer the damage. If the object was destroyed by this anim, + ** then the attached damaging anim is also destroyed. + */ + int damage = accum >> 8; + if (Object->Take_Damage(damage, 0, WARHEAD_FIRE) == RESULT_DESTROYED) { + //Object = 0; + delete this; + return; + } + } + Accum = (unsigned char)(accum & 0x00FF); + } + + /* + ** During the biggest stage (covers the most ground), perform any ground altering + ** action required. This masks craters and scorch marks, so that they appear + ** naturally rather than "popping" into existance while in plain sight. + */ + if (Class->Start+stage == Class->Biggest) { + Middle(); + } + + /* + ** Check to see if the last frame has been displayed. If so, then the + ** animation either ends or loops. + */ + if ((Loops <= 1 && stage >= Class->Stages) || (Loops > 1 && stage >= Class->LoopEnd-Class->Start)) { + + /* + ** Determine if this animation should loop another time. If so, then start the loop + ** but if not, then proceed into the animation termination handler. + */ + if (Loops) Loops--; + if (Loops) { + Set_Stage(Class->LoopStart); + } else { + + /* + ** The animation should end now, but first check to see if + ** it needs to chain into another animation. If so, then the + ** animation isn't technically over. It metamorphoses into the + ** new form. + */ + if (Class->ChainTo != ANIM_NONE) { +// AnimTypeClass const * aptr = &AnimTypeClass::As_Reference(Class->ChainTo); + + ((AnimTypeClass const *&)Class) = &AnimTypeClass::As_Reference(Class->ChainTo); + + if (Class->IsNormalized) { + Set_Rate(Options.Normalize_Delay(Class->Delay)); + } else { + Set_Rate(Class->Delay); + } + Set_Stage(Class->Start); + } else { + delete this; + } + } + } + } + } +} + + +/*********************************************************************************************** + * AnimClass::Attach_To -- Attaches animation to object specified. * + * * + * An animation can be "attached" to an object. In such cases, the animation is rendered * + * as an offset from the center of the object it is attached to. This allows affects such * + * as fire or smoke to be consistently placed on the vehicle it is associated with. * + * * + * INPUT: obj -- Pointer to the object to attach the animation to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +void AnimClass::Attach_To(ObjectClass * obj) +{ + Validate(); + if (!obj) return; + + obj->Mark(MARK_OVERLAP_UP); + obj->IsAnimAttached = true; + obj->Mark(MARK_OVERLAP_DOWN); + Map.Remove(this, In_Which_Layer()); + Object = obj; + Map.Submit(this, In_Which_Layer()); + Coord = Coord_Sub(Coord, obj->Center_Coord()); +} + + +/*********************************************************************************************** + * AnimClass::In_Which_Layer -- Determines what render layer the anim should be in. * + * * + * Use this routine to find out which display layer (ground or air) that the animation * + * should be in. This information is used to place the animation into the correct display * + * list. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the layer that the animation should exist in. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/25/1994 JLB : Created. * + *=============================================================================================*/ +LayerType AnimClass::In_Which_Layer(void) const +{ + Validate(); + if (Object || Class->IsGroundLayer) { + return(LAYER_GROUND); + } + return(LAYER_AIR); +} + + +/*********************************************************************************************** + * AnimClass::Start -- Processes initial animation side effects. * + * * + * This routine is called when the animation first starts. Sometimes there are side effects * + * associated with this animation that must occur immediately. Typically, this is the * + * sound effect assigned to this animation. If this animation is supposed to attach itself * + * to any object at its location, then do so at this time as well. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/30/1995 JLB : Created. * + *=============================================================================================*/ +void AnimClass::Start(void) +{ + Validate(); + CELL cell = Coord_Cell(Coord); + + Mark(); + + /* + ** Play the sound effect for this animation. + */ + Sound_Effect(Class->Sound, Coord); + + /* + ** If the stage where collateral effects occur is the first stage of the animation, then + ** perform this action now. Subsiquent checks against this stage value starts with the + ** second frame of the animation. + */ + if (!Class->Biggest) { + Middle(); + } + + /* + ** If this is the kind of animation that sticks to whatever object is in the same + ** location, then attach the animation to the object. If the animation is already + ** attached, then do nothing. + */ + if (!Object && Class->IsSticky && Map.In_Radar(cell)) { + UnitClass * unit = Map[cell].Cell_Unit(); + + if (unit && *unit == UNIT_GUNBOAT) { + Attach_To(unit); + } + } +} + + +/*********************************************************************************************** + * AnimClass::Middle -- Processes any middle events. * + * * + * This routine is called when the animation as reached its largest stage. Typically, this * + * routine is used to cause scorches or craters to appear at a cosmetically pleasing * + * moment. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/30/1995 JLB : Created. * + *=============================================================================================*/ +void AnimClass::Middle(void) +{ + Validate(); + CELL cell = Coord_Cell(Center_Coord()); + CellClass * cellptr = &Map[cell]; + + if (Class->Type == ANIM_ATOM_BLAST) { + + /* + ** Find someone to blame the explosion on. This is necessary in + ** order to properly enact retribution and record the kill for + ** score purposes. + */ + BuildingClass * building = NULL; + TechnoClass * backup = NULL; + if (Owner != HOUSE_NONE) { + for (int index = 0; index < Logic.Count(); index++) { + ObjectClass * obj = Logic[index]; + + if (obj && obj->Is_Techno() && obj->Owner() == Owner) { + backup = (TechnoClass *)obj; + if (obj->What_Am_I() == RTTI_BUILDING && *((BuildingClass *)obj) == STRUCT_TEMPLE) { + building = (BuildingClass *)obj; + break; + } + } + } + + if (!building) building = (BuildingClass *)backup; + } + + int radius = 3; + int rawdamage = 200; + if (GameToPlay == GAME_NORMAL) { + radius = 4; + rawdamage = 1000; + Fade_Palette_To(WhitePalette, 30, NULL); + } + for (int x = -radius; x <= radius; x++) { + for (int y = -radius; y <= radius; y++) { + int xpos = Cell_X(cell) + x; + int ypos = Cell_Y(cell) + y; + + /* + ** If the potential damage cell is outside of the map bounds, + ** then don't process it. This unusual check method ensures that + ** damage won't wrap from one side of the map to the other. + */ + if ((unsigned)xpos > MAP_CELL_W) { + continue; + } + if ((unsigned)ypos > MAP_CELL_H) { + continue; + } + CELL tcell = XY_Cell(xpos, ypos); + if (!Map.In_Radar(tcell)) continue; + + int damage = rawdamage / ((ABS(radius)/2)+1); + Explosion_Damage(Cell_Coord(tcell), damage, building, WARHEAD_FIRE); + new SmudgeClass(Random_Pick(SMUDGE_SCORCH1, SMUDGE_SCORCH6), Cell_Coord(tcell)); + } + } + Shake_Screen(3); + if (GameToPlay == GAME_NORMAL) { + Fade_Palette_To(GamePalette, 15, NULL); + } + } + + /* + ** If this animation leaves scorch marks (e.g., napalm), then do so at this time. + */ + if (Class->IsScorcher) { + new SmudgeClass(Random_Pick(SMUDGE_SCORCH1, SMUDGE_SCORCH6), Center_Coord()); + } + + /* + ** Some animations leave a crater when they occur. Artillery is a good example. + ** Craters always remove the Tiberium where they occur. + */ + if (Class->IsCraterForming) { + + /* + ** Craters reduce the level of Tiberium in the cell. + */ + cellptr->Reduce_Tiberium(6); + + /* + ** If there already is a crater in the cell, then just expand the + ** crater. + */ + new SmudgeClass(SMUDGE_CRATER1, Center_Coord()); + } + + /* + ** Flame throwers leave scorch marks in unusual positions. In addition, they leave fire + ** shards in unusual positions as well. + */ + if (Class->IsFlameThrower) { + COORDINATE c2 = Coord_Move(Center_Coord(), (DirType)((Class->Type - ANIM_FLAME_N)<<5), 0x00E0); + CELL cell = Coord_Cell(c2); + COORDINATE c3 = Map.Closest_Free_Spot(Coord_Move(Center_Coord(), (DirType)((Class->Type - ANIM_FLAME_N)<<5), 0x0140), true); + + c2 = Map.Closest_Free_Spot(c2, true); + if (c3 && (Random_Pick(0, 1) == 1)) { + if (!Map[Coord_Cell(c3)].Cell_Terrain()) { + new AnimClass(ANIM_FIRE_SMALL, c3, 0, 2); + } + } + if (c2 && (Random_Pick(0, 1) == 1)) { + if (!Map[Coord_Cell(c2)].Cell_Terrain()) { + new AnimClass(ANIM_FIRE_SMALL, c2, 0, 2); + } + } + new SmudgeClass(SMUDGE_SCORCH1, c2); + if (c3 && (Random_Pick(0, 1) == 1)) { + if (!Map[Coord_Cell(c3)].Cell_Terrain()) { + new AnimClass(ANIM_SMOKE_M, c3); + } + } + } + + AnimClass * newanim; + + /* + ** If this animation spawns side effects during its lifetime, then + ** do so now. + */ + switch (Class->Type) { + case ANIM_ION_CANNON: { + BuildingClass * building = NULL; + TechnoClass * backup = NULL; + if (Owner != HOUSE_NONE) { + for (int index = 0; index < Logic.Count(); index++) { + ObjectClass * obj = Logic[index]; + + if (obj && obj->Is_Techno() && obj->Owner() == Owner && !obj->IsInLimbo) { + backup = (TechnoClass *)obj; + if (obj->What_Am_I() == RTTI_BUILDING && *((BuildingClass *)obj) == STRUCT_EYE) { + building = (BuildingClass *)obj; + break; + } + } + } + + if (!building) building = (BuildingClass *)backup; + } + Explosion_Damage(Center_Coord(), 600, building, WARHEAD_PB); + } + break; + + case ANIM_NAPALM1: + case ANIM_NAPALM2: + case ANIM_NAPALM3: + new AnimClass(ANIM_FIRE_SMALL, Map.Closest_Free_Spot(Coord_Scatter(Center_Coord(), 0x0040), true), 0, ((Random_Pick(0, 1) == 1) ? 1 : 2)); + if (Random_Pick(0, 1) == 1) { + new AnimClass(ANIM_FIRE_SMALL, Map.Closest_Free_Spot(Coord_Scatter(Center_Coord(), 0x00A0), true), 0, ((Random_Pick(0, 1) == 1) ? 1 : 2)); + } + if (Random_Pick(0, 1) == 1) { + new AnimClass(ANIM_FIRE_MED, Map.Closest_Free_Spot(Coord_Scatter(Center_Coord(), 0x0070), true), 0, ((Random_Pick(0, 1) == 1) ? 1 : 2)); + } + break; + + case ANIM_FIRE_MED: + case ANIM_FIRE_MED2: + newanim = new AnimClass(ANIM_FIRE_SMALL, Center_Coord(), 0, ((Random_Pick(0, 1) == 1) ? 1 : 2)); + if (newanim && Object) { + newanim->Attach_To(Object); + } + break; + + default: + break; + } +} + + +/*********************************************************************************************** + * AnimClass::As_Target -- Converts the animation into a target value. * + * * + * This support routine is used to convert the animation (as a pointer) into a target * + * value (which is a number). * + * * + * INPUT: none * + * * + * OUTPUT: Returns the animation as a target value. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/08/1994 JLB : Created. * + *=============================================================================================*/ +TARGET AnimClass::As_Target(void) const +{ + Validate(); + return(Build_Target(KIND_ANIMATION, Anims.ID(this))); +} + + +/*************************************************************************** + * AnimClass::Adjust_Coord -- Adjusts anim coordinates * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * HISTORY: * + * 05/17/1995 PWG : Created. * + *=========================================================================*/ +COORDINATE AnimClass::Adjust_Coord(COORDINATE coord) +{ + Validate(); + int x,y; + + switch (Class->Type) { + case ANIM_ATOM_DOOR: + x = -1; + y = -36; + break; + + default: + return(coord); + } + COORDINATE addval = XYPixel_Coord(x, y); + coord = Coord_Add(coord, addval); + return(coord); +} + + +/*********************************************************************************************** + * AnimClass::Detach -- Remove animation if attached to target. * + * * + * This routine is called when the specified target is being removed from the game. If this * + * animation happens to be attached to this object, then the animation must be remove as * + * well. * + * * + * INPUT: target -- The target that is about to be destroyed. * + * * + * all -- Is the target being destroyed RIGHT NOW? If not, then it will be * + * destroyed soon. In that case, the animation should continue to remain * + * attached for cosmetic reasons. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/30/1995 JLB : Created. * + * 07/02/1995 JLB : Detach is a precursor to animation destruction. * + *=============================================================================================*/ +void AnimClass::Detach(TARGET target, bool all) +{ + Validate(); + if (Object && Object->As_Target() == target && all) { + Map.Remove(this, In_Which_Layer()); + Object = NULL; + IsToDelete = true; + } +} diff --git a/ANIM.H b/ANIM.H new file mode 100644 index 0000000..0cfc6a2 --- /dev/null +++ b/ANIM.H @@ -0,0 +1,174 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\anim.h_v 2.20 16 Oct 1995 16:45:40 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : ANIM.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 30, 1994 * + * * + * Last Update : May 30, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef ANIM_H +#define ANIM_H + +#include "type.h" + +/********************************************************************************************** +** This is the class that controls the shape animation objects. Shape animation objects are +** displayed over the top of the game map. Typically, they are used for explosion and fire +** effects. +*/ +class AnimClass : public ObjectClass, private StageClass { + public: + + static void * AnimClass::operator new(size_t size); + static void AnimClass::operator delete(void *ptr); + AnimClass(void) : Class(0) {Owner=HOUSE_NONE;Object=0;}; // Default constructor does nothing. + AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay=0, unsigned char loop=1, bool alt=false); + virtual ~AnimClass(void); + operator AnimType(void) const {return Class->Type;}; + virtual RTTIType What_Am_I(void) const {return RTTI_ANIM;}; + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + static void Init(void); + + void Attach_To(ObjectClass *obj); + void Make_Invisible(void) {IsInvisible = true;}; + + virtual bool Can_Place_Here(COORDINATE ) const {return true;} + virtual bool Mark(MarkType mark=MARK_CHANGE); + virtual bool Render(bool forced); + virtual COORDINATE Center_Coord(void) const; + virtual COORDINATE Sort_Y(void) const; + virtual LayerType In_Which_Layer(void) const; + virtual ObjectTypeClass const & Class_Of(void) const {return *Class;}; + virtual short const * Occupy_List(void) const; + virtual short const * Overlap_List(void) const; + virtual void Draw_It(int x, int y, WindowNumberType window); + virtual void AI(void); + virtual TARGET As_Target(void) const; + virtual void Detach(TARGET target, bool all); + + /* + ** File I/O. + */ + bool Load(FileClass & file); + bool Save(FileClass & file); + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + /* + ** If this animation is attached to an object, then this points to that object. An + ** animation that is attached will follow that object as it moves. This is important + ** for animations such as flames and smoke. + */ + ObjectClass * Object; + + /* + ** If this animation has an owner, then it will be recorded here. An owner + ** is used when damage is caused by this animation during the middle of its + ** animation. + */ + HousesType Owner; + + /* + ** This counter tells how many more times the animation should loop before it + ** terminates. + */ + unsigned char Loops; + + protected: + void Middle(void); + void Start(void); + + private: + /* + ** Define a function to make adjustments for where special animations + ** are going to render. + */ + COORDINATE Adjust_Coord(COORDINATE coord); + + /* + ** Delete this animation at the next opportunity. This is flagged when the + ** animation is to be prematurely ended as a result of some outside event. + */ + unsigned IsToDelete:1; + + /* + ** If the animation has just been created, then don't do any animation + ** processing until it has been through the render loop at least once. + */ + unsigned IsBrandNew:1; + + // Use alternate color when drawing? + unsigned IsAlternate:1; + + /* + ** If this animation is invisible, then this flag will be true. An invisible + ** animation is one that is created for the sole purpose of keeping all + ** machines syncronised. It will not be displayed. + */ + unsigned IsInvisible:1; + + /* + ** This points to the type of animation object this is. + */ + AnimTypeClass const * const Class; + + /* + ** Is this animation in a temporary suspended state? If so, then it won't + ** be rendered until this flag is false. The flag will be set to false + ** after the first countdown timer reaches 0. + */ + unsigned char Delay; + + /* + ** If this is an animation that damages whatever it is attached to, then this + ** value holds the accumulation of fractional damage points. When the accumulated + ** fractions reach 256, then one damage point is applied to the attached object. + */ + unsigned char Accum; + + /* + ** This contains the value of the Virtual Function Table Pointer + */ + static void * VTable; +}; + + + +#endif diff --git a/AUDIO.CPP b/AUDIO.CPP new file mode 100644 index 0000000..6ec4f75 --- /dev/null +++ b/AUDIO.CPP @@ -0,0 +1,544 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\audio.cpv 2.17 16 Oct 1995 16:50:20 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : AUDIO.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : May 4, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Is_Speaking -- Checks to see if the eva voice is still playing. * + * Sound_Effect -- General purpose sound player. * + * Sound_Effect -- Plays a sound effect in the tactical map. * + * Speak -- Computer speaks to the player. * + * Speak_AI -- Handles starting the EVA voices. * + * Stop_Speaking -- Forces the EVA voice to stop talking. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*************************************************************************** +** Controls what special effects may occur on the sound effect. +*/ +typedef enum { + IN_NOVAR, // No variation or alterations allowed. + IN_JUV, // Juvenile sound effect alternate option. + IN_VAR, // Infantry variance response modification. +} ContextType; + +static struct { + char const *Name; // Digitized voice file name. + int Priority; // Playback priority of this sample. + ContextType Where; // In what game context does this sample exist. +} SoundEffectName[VOC_COUNT] = { + + /* + ** Special voices (typically associated with the commando). + */ + {"BOMBIT1", 20, IN_NOVAR}, // VOC_RAMBO_PRESENT "I've got a present for ya" + {"CMON1", 20, IN_NOVAR}, // VOC_RAMBO_CMON "c'mon" + {"GOTIT1", 20, IN_NOVAR}, // VOC_RAMBO_UGOTIT "you got it" * + {"KEEPEM1", 20, IN_NOVAR}, // VOC_RAMBO_COMIN "keep 'em commin'" + {"LAUGH1", 20, IN_NOVAR}, // VOC_RAMBO_LAUGH "hahaha" + {"LEFTY1", 20, IN_NOVAR}, // VOC_RAMBO_LEFTY "that was left handed" * + {"NOPRBLM1", 20, IN_NOVAR}, // VOC_RAMBO_NOPROB "no problem" +// {"OHSH1", 20, IN_NOVAR}, // VOC_RAMBO_OHSH "oh shiiiiii...." + {"ONIT1", 20, IN_NOVAR}, // VOC_RAMBO_ONIT "I'm on it" + {"RAMYELL1", 20, IN_NOVAR}, // VOC_RAMBO_YELL "ahhhhhhh" + {"ROKROLL1", 20, IN_NOVAR}, // VOC_RAMBO_ROCK "time to rock and roll" + {"TUFFGUY1", 20, IN_NOVAR}, // VOC_RAMBO_TUFF "real tuff guy" * + {"YEAH1", 20, IN_NOVAR}, // VOC_RAMBO_YEA "yea" * + {"YES1", 20, IN_NOVAR}, // VOC_RAMBO_YES "yes" * + {"YO1", 20, IN_NOVAR}, // VOC_RAMBO_YO "yo" + + /* + ** Civilian voices (technicians too). + */ + {"GIRLOKAY", 20, IN_NOVAR}, // VOC_GIRL_OKAY + {"GIRLYEAH", 20, IN_NOVAR}, // VOC_GIRL_YEAH + {"GUYOKAY1", 20, IN_NOVAR}, // VOC_GUY_OKAY + {"GUYYEAH1", 20, IN_NOVAR}, // VOC_GUY_YEAH + + /* + ** Infantry and vehicle responses. + */ + {"2DANGR1", 10, IN_VAR}, // VOC_2DANGER "negative, too dangerous" + {"ACKNO", 10, IN_VAR}, // VOC_ACKNOWL "acknowledged" + {"AFFIRM1", 10, IN_VAR}, // VOC_AFFIRM "affirmative" + {"AWAIT1", 10, IN_VAR}, // VOC_AWAIT1 "awaiting orders" +// {"BACKUP", 10, IN_VAR}, // VOC_BACKUP "send backup" +// {"HELP", 10, IN_VAR}, // VOC_HELP "send help" + {"MOVOUT1", 10, IN_VAR}, // VOC_MOVEOUT "movin' out" + {"NEGATV1", 10, IN_VAR}, // VOC_NEGATIVE "negative" + {"NOPROB", 10, IN_VAR}, // VOC_NO_PROB "not a problem" + {"READY", 10, IN_VAR}, // VOC_READY "ready and waiting" + {"REPORT1", 10, IN_VAR}, // VOC_REPORT "reporting" + {"RITWAWA", 10, IN_VAR}, // VOC_RIGHT_AWAY "right away sir" + {"ROGER", 10, IN_VAR}, // VOC_ROGER "roger" +// {"SIR1", 10, IN_VAR}, // VOC_SIR1 "sir?" +// {"SQUAD1", 10, IN_VAR}, // VOC_SQUAD1 "squad reporting" +// {"TARGET1", 10, IN_VAR}, // VOC_PRACTICE "target practice" + {"UGOTIT", 10, IN_VAR}, // VOC_UGOTIT "you got it" + {"UNIT1", 10, IN_VAR}, // VOC_UNIT1 "unit reporting" + {"VEHIC1", 10, IN_VAR}, // VOC_VEHIC1 "vehicle reporting" + {"YESSIR1", 10, IN_VAR}, // VOC_YESSIR "yes sir" + + /* + ** Sound effects that have a juvenile counterpart. + */ + {"BAZOOK1", 1, IN_JUV}, // VOC_BAZOOKA Gunfire + {"BLEEP2", 1, IN_JUV}, // VOC_BLEEP Clean metal bing + {"BOMB1", 1, IN_JUV}, // VOC_BOMB1 Crunchy parachute bomb type explosion + {"BUTTON", 1, IN_JUV}, // VOC_BUTTON Dungeon Master button click + {"COMCNTR1", 10, IN_JUV}, // VOC_RADAR_ON Elecronic static with beeps + {"CONSTRU2", 10, IN_JUV}, // VOC_CONSTRUCTION construction sounds + {"CRUMBLE", 1, IN_JUV}, // VOC_CRUMBLE muffled crumble sound + {"FLAMER2", 4, IN_JUV}, // VOC_FLAMER1 flame thrower + {"GUN18", 4, IN_JUV}, // VOC_RIFLE rifle shot + {"GUN19", 4, IN_JUV}, // VOC_M60 machine gun burst -- 6 rounds + {"GUN20", 4, IN_JUV}, // VOC_GUN20 bat hitting heavy metal door + {"GUN5", 4, IN_JUV}, // VOC_M60A medium machine gun burst + {"GUN8", 4, IN_JUV}, // VOC_MINI mini gun burst + {"GUNCLIP1", 1, IN_JUV}, // VOC_RELOAD gun clip reload + {"HVYDOOR1", 5, IN_JUV}, // VOC_SLAM metal plates slamming together + {"HVYGUN10", 1, IN_JUV}, // VOC_HVYGUN10 loud sharp cannon + {"ION1", 1, IN_JUV}, // VOC_ION_CANNON partical beam + {"MGUN11", 1, IN_JUV}, // VOC_MGUN11 alternate tripple burst + {"MGUN2", 1, IN_JUV}, // VOC_MGUN2 M-16 tripple burst + {"NUKEMISL", 1, IN_JUV}, // VOC_NUKE_FIRE long missile sound + {"NUKEXPLO", 1, IN_JUV}, // VOC_NUKE_EXPLODE long but not loud explosion + {"OBELRAY1", 1, IN_JUV}, // VOC_LASER humming star wars laser beam + {"OBELPOWR", 1, IN_JUV}, // VOC_LASER_POWER warming-up sound of star wars laser beam + {"POWRDN1", 1, IN_JUV}, // VOC_RADAR_OFF doom door slide + {"RAMGUN2", 1, IN_JUV}, // VOC_SNIPER silenced rifle fire + {"ROCKET1", 1, IN_JUV}, // VOC_ROCKET1 rocket launch variation #1 + {"ROCKET2", 1, IN_JUV}, // VOC_ROCKET2 rocket launch variation #2 + {"SAMMOTR2", 1, IN_JUV}, // VOC_MOTOR dentists drill + {"SCOLD2", 1, IN_JUV}, // VOC_SCOLD cannot perform action feedback tone + {"SIDBAR1C", 1, IN_JUV}, // VOC_SIDEBAR_OPEN xylophone clink + {"SIDBAR2C", 1, IN_JUV}, // VOC_SIDEBAR_CLOSE xylophone clink + {"SQUISH2", 1, IN_JUV}, // VOC_SQUISH2 crushing infantry + {"TNKFIRE2", 1, IN_JUV}, // VOC_TANK1 sharp tank fire with recoil + {"TNKFIRE3", 1, IN_JUV}, // VOC_TANK2 sharp tank fire + {"TNKFIRE4", 1, IN_JUV}, // VOC_TANK3 sharp tank fire + {"TNKFIRE6", 1, IN_JUV}, // VOC_TANK4 big gun tank fire + {"TONE15", 0, IN_JUV}, // VOC_UP credits counting up + {"TONE16", 0, IN_JUV}, // VOC_DOWN credits counting down + {"TONE2", 1, IN_JUV}, // VOC_TARGET target sound + {"TONE5", 10, IN_JUV}, // VOC_SONAR sonar echo + {"TOSS", 1, IN_JUV}, // VOC_TOSS air swish + {"TRANS1", 1, IN_JUV}, // VOC_CLOAK stealth tank + {"TREEBRN1", 1, IN_JUV}, // VOC_BURN burning crackle + {"TURRFIR5", 1, IN_JUV}, // VOC_TURRET muffled gunfire + {"XPLOBIG4", 5, IN_JUV}, // VOC_XPLOBIG4 very long muffled explosion + {"XPLOBIG6", 5, IN_JUV}, // VOC_XPLOBIG6 very long muffled explosion + {"XPLOBIG7", 5, IN_JUV}, // VOC_XPLOBIG7 very long muffled explosion + {"XPLODE", 1, IN_JUV}, // VOC_XPLODE long soft muffled explosion + {"XPLOS", 4, IN_JUV}, // VOC_XPLOS short crunchy explosion + {"XPLOSML2", 5, IN_JUV}, // VOC_XPLOSML2 muffled mechanical explosion + + /* + ** Generic sound effects (no variations). + */ + {"NUYELL1", 10, IN_NOVAR}, // VOC_SCREAM1 short infantry scream + {"NUYELL3", 10, IN_NOVAR}, // VOC_SCREAM3 short infantry scream + {"NUYELL4", 10, IN_NOVAR}, // VOC_SCREAM4 short infantry scream + {"NUYELL5", 10, IN_NOVAR}, // VOC_SCREAM5 short infantry scream + {"NUYELL6", 10, IN_NOVAR}, // VOC_SCREAM6 short infantry scream + {"NUYELL7", 10, IN_NOVAR}, // VOC_SCREAM7 short infantry scream + {"NUYELL10", 10, IN_NOVAR}, // VOC_SCREAM10 short infantry scream + {"NUYELL11", 10, IN_NOVAR}, // VOC_SCREAM11 short infantry scream + {"NUYELL12", 10, IN_NOVAR}, // VOC_SCREAM12 short infantry scream + {"YELL1", 1, IN_NOVAR}, // VOC_YELL1 long infantry scream + + {"MYES1", 10, IN_NOVAR}, // VOC_YES "Yes?" + {"MCOMND1", 10, IN_NOVAR}, // VOC_COMMANDER "Commander?" + {"MHELLO1", 10, IN_NOVAR}, // VOC_HELLO "Hello?" + {"MHMMM1", 10, IN_NOVAR}, // VOC_HMMM "Hmmm?" +// {"MHASTE1", 10, IN_NOVAR}, // VOC_PROCEED1 "I will proceed, post haste." +// {"MONCE1", 10, IN_NOVAR}, // VOC_PROCEED2 "I will proceed, at once." +// {"MIMMD1", 10, IN_NOVAR}, // VOC_PROCEED3 "I will proceed, immediately." +// {"MPLAN1", 10, IN_NOVAR}, // VOC_EXCELLENT1 "That is an excellent plan." +// {"MPLAN2", 10, IN_NOVAR}, // VOC_EXCELLENT2 "Yes, that is an excellent plan." + {"MPLAN3", 10, IN_NOVAR}, // VOC_EXCELLENT3 "A wonderful plan." +// {"MACTION1", 10, IN_NOVAR}, // VOC_EXCELLENT4 "Astounding plan of action commander." +// {"MREMARK1", 10, IN_NOVAR}, // VOC_EXCELLENT5 "Remarkable contrivance." + {"MCOURSE1", 10, IN_NOVAR}, // VOC_OF_COURSE "Of course." + {"MYESYES1", 10, IN_NOVAR}, // VOC_YESYES "Yes yes yes." + {"MTIBER1", 10, IN_NOVAR}, // VOC_QUIP1 "Mind the Tiberium." +// {"MMG1", 10, IN_NOVAR}, // VOC_QUIP2 "A most remarkable Metasequoia Glyptostroboides." + {"MTHANKS1", 10, IN_NOVAR}, // VOC_THANKS "Thank you." + + {"CASHTURN", 1, IN_NOVAR}, // VOC_CASHTURN Sound of money being piled up. + {"BLEEP2", 10, IN_NOVAR}, // VOC_BLEEPY3 Clean computer bleep sound. + {"DINOMOUT", 10, IN_NOVAR}, // VOC_DINOMOUT Movin' out in dino-speak. + {"DINOYES", 10, IN_NOVAR}, // VOC_DINOYES Yes Sir in dino-speak. + {"DINOATK1", 10, IN_NOVAR}, // VOC_DINOATK1 Dino attack sound. + {"DINODIE1", 10, IN_NOVAR}, // VOC_DINODIE1 Dino die sound. +}; + + +/*********************************************************************************************** + * Sound_Effect -- Plays a sound effect in the tactical map. * + * * + * This routine is used when a sound effect occurs in the game world. It handles fading * + * the sound according to distance. * + * * + * INPUT: voc -- The sound effect number to play. * + * * + * coord -- The world location that the sound originates from. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/12/1994 JLB : Created. * + * 01/05/1995 JLB : Reduces sound more dramatically when off screen. * + *=============================================================================================*/ +void Sound_Effect(VocType voc, COORDINATE coord, int variation) +{ + unsigned distance; + CELL cell_pos; + int pan_value; + + if (!Options.Volume || voc == VOC_NONE || !SoundOn || SampleType == SAMPLE_NONE) { + return; + } + if (coord) { + cell_pos = Coord_Cell(coord); + } + + distance = 0xFF; + pan_value = 0; + if (coord && !Map.In_View(cell_pos)) { + distance = Map.Cell_Distance(cell_pos, Coord_Cell(Map.TacticalCoord)); + distance = (unsigned int)MIN((int)distance, (int)MAP_CELL_W); + distance = Cardinal_To_Fixed(MAP_CELL_W, distance); + distance = MIN(distance, 0xFFu); + distance ^= 0xFF; + + distance /= 2; + distance = MAX(distance, 25); + + pan_value = Cell_X(cell_pos); + pan_value -= Coord_XCell(Map.TacticalCoord) + (Lepton_To_Cell(Map.TacLeptonWidth) >> 1); + if (ABS(pan_value) > Lepton_To_Cell(Map.TacLeptonWidth >> 1)) { + pan_value *= 0x8000; + pan_value /= (MAP_CELL_W >> 2); + pan_value = Bound(pan_value, -0x7FFF, 0x7FFF); +// pan_value = MAX((int)pan_value, (int)-0x7FFF); +// pan_value = MIN((int)pan_value, 0x7FFF); + } else { + pan_value = 0; + } + } + + Sound_Effect(voc, (VolType)Fixed_To_Cardinal(distance, Options.Volume), variation, pan_value); +} + + +/*********************************************************************************************** + * Sound_Effect -- General purpose sound player. * + * * + * This is used for general purpose sound effects. These are sounds that occur outside * + * of the game world. They do not have a corresponding game world location as their source. * + * * + * INPUT: voc -- The sound effect number to play. * + * * + * volume -- The volume to assign to this sound effect. * + * * + * OUTPUT: Returns with the sound handle (-1 if no sound was played). * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/12/1994 JLB : Created. * + * 11/12/1994 JLB : Handles cache logic. * + * 05/04/1995 JLB : Variation adjustments. * + *=============================================================================================*/ +int Sound_Effect(VocType voc, VolType volume, int variation, signed short pan_value) +{ + char name[_MAX_FNAME+_MAX_EXT]; // Working filename of sound effect. + + if (!Options.Volume || voc == VOC_NONE || !SoundOn || SampleType == SAMPLE_NONE) { + return(-1); + } + + /* + ** Fetch a pointer to the sound effect data. Modify the sound as appropriate and desired. + */ + char const * ext = ".AUD"; + if (Special.IsJuvenile && SoundEffectName[voc].Where == IN_JUV) { + ext = ".JUV"; + } else { + if (SoundEffectName[voc].Where == IN_VAR) { + + /* + ** For infantry, use a variation on the response. For vehicles, always + ** use the vehicle response table. + */ + if (variation < 0) { + if (ABS(variation) % 2) { + ext = ".V00"; + } else { + ext = ".V02"; + } + } else { + if (variation % 2) { + ext = ".V01"; + } else { + ext = ".V03"; + } + } + } + } + _makepath(name, NULL, NULL, SoundEffectName[voc].Name, ext); + void const * ptr = MixFileClass::Retrieve(name); + + /* + ** If the sound data pointer is not null, then presume that it is valid. + */ + if (ptr) { + return(Play_Sample(ptr, Fixed_To_Cardinal(SoundEffectName[voc].Priority, (int)volume), (int)volume, pan_value)); + } + return(-1); +} + + +/* +** This elaborates all the EVA speech voices. +*/ +char const * Speech[VOX_COUNT] = { + "ACCOM1", // mission accomplished + "FAIL1", // your mission has failed + "BLDG1", // unable to comply, building in progress + "CONSTRU1", // construction complete + "UNITREDY", // unit ready + "NEWOPT1", // new construction options + "DEPLOY1", // cannot deploy here + "GDIDEAD1", // GDI unit destroyed + "NODDEAD1", // Nod unit destroyed + "CIVDEAD1", // civilian killed +// "EVAYES1", // affirmative +// "EVANO1", // negative +// "UPUNIT1", // upgrade complete, new unit available +// "UPSTRUC1", // upgrade complete, new structure available + "NOCASH1", // insufficient funds + "BATLCON1", // battle control terminated + "REINFOR1", // reinforcements have arrived + "CANCEL1", // canceled + "BLDGING1", // building + "LOPOWER1", // low power + "NOPOWER1", // insufficient power + "MOCASH1", // need more funds + "BASEATK1", // our base is under attack + "INCOME1", // incoming missile + "ENEMYA", // enemy planes approaching + "NUKE1", // nuclear warhead approaching +// "RADOK1", // radiation levels are acceptable +// "RADFATL1", // radiation levels are fatal + "NOBUILD1", // unable to build more + "PRIBLDG1", // primary building selected +// "REPDONE1", // repairs completed + "NODCAPT1", // Nod building captured + "GDICAPT1", // GDI building captured +// "SOLD1", // structure sold + "IONCHRG1", // ion cannon charging + "IONREDY1", // ion cannon ready + "NUKAVAIL", // nuclear weapon available + "NUKLNCH1", // nuclear weapon launched + "UNITLOST", // unit lost + "STRCLOST", // structure lost + "NEEDHARV", // need harvester + "SELECT1", // select target + "AIRREDY1", // airstrike ready + "NOREDY1", // not ready + "TRANSSEE", // Nod transport sighted + "TRANLOAD", // Nod transport loaded + "ENMYAPP1", // enemy approaching + "SILOS1", // silos needed + "ONHOLD1", // on hold + "REPAIR1", // repairing + "ESTRUCX", // enemy structure destroyed + "GSTRUC1", // GDI structure destroyed + "NSTRUC1", // NOD structure destroyed + "ENMYUNIT", // Enemy unit destroyed +// "GUKILL1", // gold unit destroyed +// "GSTRUD1", // gold structure destroyed +// "GONLINE1", // gold player online +// "GLEFT1", // gold player has departed +// "GOLDKILT", // gold player destroyed +// "GOLDWIN", // gold player is victorious +// "RUKILL1", // red unit destroyed +// "RSTRUD1", // red structure destroyed +// "RONLINE1", // red player online +// "RLEFT1", // red player has departed +// "REDKILT", // red player destroyed +// "REDWIN", // red player is victorious +// "GYUKILL1", // grey unit destroyed +// "GYSTRUD1", // grey structure destroyed +// "GYONLINE", // grey player online +// "GYLEFT1", // grey player has departed +// "GREYKILT", // grey player destroyed +// "GREYWIN", // grey player is victorious +// "OUKILL1", // orange unit destroyed +// "OSTRUD1", // orange structure destroyed +// "OONLINE1", // orange player online +// "OLEFT1", // orange player has departed +// "ORANKILT", // orange player destroyed +// "ORANWIN", // orange player is victorious +// "GNUKILL1", // green unit destroyed +// "GNSTRUD1", // green structure destroyed +// "GNONLINE", // green player online +// "GNLEFT1", // green player has departed +// "GRENKILT", // green player destroyed +// "GRENWIN", // green player is victorious +// "BUKILL1", // blue unit destroyed +// "BSTRUD1", // blue structure destroyed +// "BONLINE1", // blue player online +// "BLEFT1", // blue player has departed +// "BLUEKILT", // blue player destroyed +// "BLUEWIN" // blue player is victorious +}; +static VoxType CurrentVoice = VOX_NONE; + + +/*********************************************************************************************** + * Speak -- Computer speaks to the player. * + * * + * This routine is used to have the game computer (EVA) speak to the player. * + * * + * INPUT: voice -- The voice number to speak (see defines.h). * + * * + * OUTPUT: Returns with the handle of the playing speech (-1 if no voice started). * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/12/1994 JLB : Created. * + *=============================================================================================*/ +void Speak(VoxType voice) +{ + if (Options.Volume && SampleType != 0 && voice != VOX_NONE && voice != SpeakQueue && voice != CurrentVoice && SpeakQueue == VOX_NONE) { + SpeakQueue = voice; + } +} + + +/*********************************************************************************************** + * Speak_AI -- Handles starting the EVA voices. * + * * + * This starts the EVA voice talking as well. If there is any speech request in the queue, * + * it will be started when the current voice is finished. Call this routine as often as * + * possible (once per game tick is sufficient). * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/27/1994 JLB : Created. * + *=============================================================================================*/ +void Speak_AI(void) +{ + static VoxType _last = VOX_NONE; + if (SampleType == 0) return; + + if (!Is_Sample_Playing(SpeechBuffer)) { + CurrentVoice = VOX_NONE; + if (SpeakQueue != VOX_NONE) { + if (SpeakQueue != _last) { + char name[_MAX_FNAME+_MAX_EXT]; + + _makepath(name, NULL, NULL, Speech[SpeakQueue], ".AUD"); + if (CCFileClass(name).Read(SpeechBuffer, SPEECH_BUFFER_SIZE)) { + Play_Sample(SpeechBuffer, 254, Options.Volume); + } + _last = SpeakQueue; + } else { + Play_Sample(SpeechBuffer, 254, Options.Volume); + } + SpeakQueue = VOX_NONE; + } + } +} + + +/*********************************************************************************************** + * Stop_Speaking -- Forces the EVA voice to stop talking. * + * * + * Use this routine to immediately stop the EVA voice from speaking. It also clears out * + * the pending voice queue. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/27/1994 JLB : Created. * + *=============================================================================================*/ +void Stop_Speaking(void) +{ + SpeakQueue = VOX_NONE; + if (SampleType != 0) { + Stop_Sample_Playing(SpeechBuffer); + } +} + + +/*********************************************************************************************** + * Is_Speaking -- Checks to see if the eva voice is still playing. * + * * + * Call this routine when the EVA voice being played needs to be checked. A typical use * + * of this would be when some action needs to be delayed until the voice has finished -- * + * say the end of the game. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Is the EVA voice still playing? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/12/1995 JLB : Created. * + *=============================================================================================*/ +bool Is_Speaking(void) +{ + Speak_AI(); + if (SampleType != 0 && (SpeakQueue != VOX_NONE || Is_Sample_Playing(SpeechBuffer))) { + return(true); + } + return(false); +} diff --git a/AUDIO.H b/AUDIO.H new file mode 100644 index 0000000..70db62d --- /dev/null +++ b/AUDIO.H @@ -0,0 +1,99 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\audio.h_v 2.18 16 Oct 1995 16:45:34 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : AUDIO.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : June 21, 1994 * + * * + * Last Update : June 21, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef AUDIO_H +#define AUDIO_H + +#include "memory.h" + +class AudioClass { + char const * Name; // Name of audio asset. + void const * Data; // Loaded audio data. + int Handle; // Handle of asset (as it is playing). + MemoryClass *Mem; // Pointer to memory handler class. + unsigned IsMIDI:1; // Is this a midi file? + + public: + AudioClass(void); + AudioClass(char const *name, MemoryClass &mem); + virtual ~AudioClass(void); + + bool Load(char const *name = 0); + bool Free(void); + bool Play(int volume = 0xFF); + bool Stop(void); + bool Pause(void); + bool Resume(void); + bool Set_Name(char const *name); + bool Is_Playing(void) const; + bool Is_Loaded(void) const; + bool Is_MIDI(void) const; +}; + +inline AudioClass::AudioClass(void) +{ + Name = 0; + Data = 0; + Mem = 0; + Handle = -1; +}; + +inline AudioClass::AudioClass(char const *name, MemoryClass &mem) +{ + if (mem) { + Mem = &mem; + } else { + Mem = &::Mem; // Uses global default memory handler. + } + Name = strdup(name); + Data = 0; + Handle = -1; +}; + +inline AudioClass::~AudioClass(void) +{ + if (GameActive) { + if (Name) free(Name); + if (Data) Mem->Free(Data); + Name = 0; + Data = 0; + Handle = -1; + } +}; + + +#endif diff --git a/BASE.CPP b/BASE.CPP new file mode 100644 index 0000000..6e84169 --- /dev/null +++ b/BASE.CPP @@ -0,0 +1,524 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\base.cpv 1.9 16 Oct 1995 16:48:56 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : BASE.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : 03/27/95 * + * * + * Last Update : March 27, 1995 * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * BaseClass::Get_Building -- Returns ptr to the built building for the given node * + * BaseClass::Get_Node -- Returns ptr to the node corresponding to given object * + * BaseClass::Is_Built -- Tells if given item in the list has been built yet * + * BaseClass::Is_Node -- Tells if the given building is part of our base list * + * BaseClass::Load -- loads from a saved game file * + * BaseClass::Next_Buildable -- returns ptr to the next node that needs to be built * + * BaseClass::Read_INI -- INI reading routine * + * BaseClass::Save -- saves to a saved game file * + * BaseClass::Write_INI -- INI writing routine * + * BaseNodeClass::operator != -- inequality operator * + * BaseNodeClass::operator == -- equality operator * + * BaseNodeClass::operator > -- greater-than operator * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * BaseNodeClass::operator == -- equality operator * + * * + * INPUT: * + * node node to test against * + * * + * OUTPUT: * + * true = equal, false = not equal * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/24/1995 BRR : Created. * + *=============================================================================================*/ +int BaseNodeClass::operator == (BaseNodeClass const & node) +{ + return(Type == node.Type && Coord == node.Coord); +} + + +/*********************************************************************************************** + * BaseNodeClass::operator != -- inequality operator * + * * + * INPUT: * + * node node to test against * + * * + * OUTPUT: * + * comparison result * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/24/1995 BRR : Created. * + *=============================================================================================*/ +int BaseNodeClass::operator !=(BaseNodeClass const & node) +{ + return(Type != node.Type || Coord != node.Coord); +} + + +/*********************************************************************************************** + * BaseNodeClass::operator > -- greater-than operator * + * * + * INPUT: * + * node node to test against * + * * + * OUTPUT: * + * comparison result * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/24/1995 BRR : Created. * + *=============================================================================================*/ +int BaseNodeClass::operator > (BaseNodeClass const & ) +{ + return(true); +} + + +/*********************************************************************************************** + * BaseClass::Read_INI -- INI reading routine * + * * + * INI entry format: * + * BLDG=COORD * + * BLDG=COORD * + * ... * + * * + * INPUT: * + * buffer pointer to loaded INI file * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * This routines assumes there is only one base defined for the scenario. * + * * + * HISTORY: * + * 03/24/1995 BRR : Created. * + *=============================================================================================*/ +void BaseClass::Read_INI(char *buffer) +{ + char buf[128]; + char uname[10]; + BaseNodeClass node; // node to add to list + + /* + ** First, determine the house of the human player, and set the Base's house + ** accordingly. + */ + WWGetPrivateProfileString("BASIC", "Player", "GoodGuy", buf, 20, buffer); + if (HouseTypeClass::From_Name(buf) == HOUSE_GOOD) { + House = HOUSE_BAD; + } else { + House = HOUSE_GOOD; + } + + /* + ** Read the number of buildings that will go into the base node list + */ + int count = WWGetPrivateProfileInt (INI_Name(),"Count",0,buffer); + + /* + ** Read each entry in turn, in the same order they were written out. + */ + for (int i = 0; i < count; i++) { + + /* + ** Get an INI entry + */ + sprintf(uname,"%03d",i); + WWGetPrivateProfileString(INI_Name(), uname, NULL, buf, sizeof(buf)-1, buffer); + + /* + ** Set the node's building type + */ + node.Type = BuildingTypeClass::From_Name(strtok(buf,",")); + + /* + ** Read & set the node's coordinate + */ + node.Coord = atol(strtok(NULL,",")); + + /* + ** Add this node to the Base's list + */ + Nodes.Add(node); + } +} + + +/*********************************************************************************************** + * BaseClass::Write_INI -- INI writing routine * + * * + * INI entry format: * + * BLDG=COORD * + * BLDG=COORD * + * ... * + * * + * INPUT: * + * buffer pointer to loaded INI file staging area * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * This routines assumes there is only one base defined for the scenario. * + * * + * HISTORY: * + * 03/24/1995 BRR : Created. * + *=============================================================================================*/ +void BaseClass::Write_INI(char *buffer) +{ + char buf[128]; + char uname[10]; + + /* + ** Clear out all existing teamtype data from the INI file. + */ + WWWritePrivateProfileString(INI_Name(), NULL, NULL, buffer); + + /* + ** Save the # of buildings in the Nodes list. This is essential because + ** they must be read in the same order they were created, so "000" must be + ** read first, etc. + */ + WWWritePrivateProfileInt (INI_Name(),"Count",Nodes.Count(),buffer); + + /* + ** Write each entry into the INI + */ + for (int i = 0; i < Nodes.Count(); i++) { + sprintf(uname,"%03d",i); + sprintf(buf,"%s,%d", + BuildingTypeClass::As_Reference(Nodes[i].Type).IniName, + Nodes[i].Coord); + + WWWritePrivateProfileString(INI_Name(), uname, buf, buffer); + } +} + + +/*********************************************************************************************** + * BaseClass::Load -- loads from a saved game file * + * * + * INPUT: * + * file open file * + * * + * OUTPUT: * + * true = success, false = failure * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/24/1995 BRR : Created. * + *=============================================================================================*/ +bool BaseClass::Load(FileClass &file) +{ + int num_struct; + int i; + BaseNodeClass node; + + /* + ** Read in & check the size of this class + */ + if (file.Read(&i, sizeof(i)) != sizeof(i)) { + return(false); + } + + if (i != sizeof(*this)) { + return(false); + } + + /* + ** Read in the House & the number of structures in the base + */ + if (file.Read(&House,sizeof(House)) != sizeof(House)) { + return(false); + } + + if (file.Read(&num_struct,sizeof(num_struct)) != sizeof(num_struct)) { + return(false); + } + + /* + ** Read each node entry & add it to the list + */ + for (i = 0; i < num_struct; i++) { + if (file.Read(&node,sizeof(node)) != sizeof(node)) { + return(false); + } + Nodes.Add(node); + } + + return(true); +} + + +/*********************************************************************************************** + * BaseClass::Save -- saves to a saved game file * + * * + * INPUT: * + * file open file * + * * + * OUTPUT: * + * true = success, false = failure * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/24/1995 BRR : Created. * + *=============================================================================================*/ +bool BaseClass::Save(FileClass &file) +{ + int num_struct; + int i; + BaseNodeClass node; + + /* + ** Write the size of this class + */ + i = sizeof(*this); + if (file.Write(&i,sizeof(i)) != sizeof(i)) { + return(false); + } + + /* + ** Write the House & the number of structures in the base + */ + if (file.Write(&House,sizeof(House)) != sizeof(House)) { + return(false); + } + + num_struct = Nodes.Count(); + if (file.Write(&num_struct,sizeof(num_struct)) != sizeof(num_struct)) { + return(false); + } + + /* + ** Write each node entry + */ + for (i = 0; i < num_struct; i++) { + node = Nodes[i]; + if (file.Write(&node,sizeof(node)) != sizeof(node)) { + return(false); + } + } + + return(true); +} + + +/*********************************************************************************************** + * BaseClass::Is_Built -- Tells if given item in the list has been built yet * + * * + * INPUT: * + * index index into base list * + * * + * OUTPUT: * + * true = yes, false = no * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/24/1995 BRR : Created. * + *=============================================================================================*/ +bool BaseClass::Is_Built(int index) +{ + if (Get_Building(index) != NULL) { + return(true); + } else { + return(false); + } +} + + +/*********************************************************************************************** + * BaseClass::Get_Building -- Returns ptr to the built building for the given node * + * * + * INPUT: * + * obj pointer to building to test * + * * + * OUTPUT: * + * ptr to already-built building, NULL if none * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/24/1995 BRR : Created. * + *=============================================================================================*/ +BuildingClass * BaseClass::Get_Building(int index) +{ + BuildingClass *bldg; + ObjectClass *obj[4]; + + /* + ** Check the location on the map where this building should be; if it's + ** there, return a pointer to it. + */ + CELL cell = Coord_Cell(Nodes[index].Coord); + + obj[0] = Map[cell].Cell_Building(); + obj[1] = Map[cell].Overlapper[0]; + obj[2] = Map[cell].Overlapper[1]; + obj[3] = Map[cell].Overlapper[2]; + + bldg = NULL; + for (int i = 0; i < 4; i++) { + if (obj[i] && + obj[i]->Coord == Nodes[index].Coord && + obj[i]->What_Am_I() == RTTI_BUILDING && + ((BuildingClass *)obj[i])->Class->Type == Nodes[index].Type) { + + bldg = (BuildingClass *)obj[i]; + break; + } + } + + return(bldg); +} + + +/*********************************************************************************************** + * BaseClass::Is_Node -- Tells if the given building is part of our base list * + * * + * INPUT: * + * obj pointer to building to test * + * * + * OUTPUT: * + * true = building is a node in the list, false = isn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/24/1995 BRR : Created. * + *=============================================================================================*/ +bool BaseClass::Is_Node(BuildingClass *obj) +{ + if (Get_Node(obj) != NULL) { + return(true); + } else { + return(false); + } +} + + +/*********************************************************************************************** + * BaseClass::Get_Node -- Returns ptr to the node corresponding to given object * + * * + * INPUT: * + * obj pointer to building to test * + * * + * OUTPUT: * + * ptr to node * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/24/1995 BRR : Created. * + *=============================================================================================*/ +BaseNodeClass * BaseClass::Get_Node(BuildingClass *obj) +{ + for (int i = 0; i < Nodes.Count(); i++) { + if (obj->Class->Type == Nodes[i].Type && obj->Coord == Nodes[i].Coord) { + return(&Nodes[i]); + } + } + return(NULL); +} + + +/*********************************************************************************************** + * BaseClass::Next_Buildable -- returns ptr to the next node that needs to be built * + * * + * If 'type' is not NONE, returns ptr to the next "hole" in the list of the given type. * + * Otherwise, returns ptr to the next hole in the list of any type. * + * * + * INPUT: * + * type type of building to check for * + * * + * OUTPUT: * + * ptr to a BaseNodeClass, NULL if none * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/24/1995 BRR : Created. * + *=============================================================================================*/ +BaseNodeClass * BaseClass::Next_Buildable(StructType type) +{ + /* + ** Loop through all node entries, returning a pointer to the first + ** un-built one that matches the requested type. + */ + for (int i = 0; i < Nodes.Count(); i++) { + + /* + ** For STRUCT_NONE, return the first hole found + */ + if (type == STRUCT_NONE) { + if (!Is_Built(i)) { + return(&Nodes[i]); + } + + } else { + + /* + ** For a "real" building type, return the first hold for that type + */ + if (Nodes[i].Type==type && !Is_Built(i)) { + return(&Nodes[i]); + } + } + } + + +// If no entry could be found, then create a fake one that will allow +// placement of the building. Make it static and reuse the next time this +// routine is called. + + return(NULL); +} diff --git a/BASE.H b/BASE.H new file mode 100644 index 0000000..83efb3b --- /dev/null +++ b/BASE.H @@ -0,0 +1,127 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\base.h_v 1.12 16 Oct 1995 16:46:38 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : BASE.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : 03/27/95 * + * * + * Last Update : March 27, 1995 * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef BASE_H +#define BASE_H + + +/**************************************************************************** +** This class defines one "node" in the pre-built base list. Each node +** contains a type of building to build, and the COORD to build it at. +*/ +class BaseNodeClass +{ + public: + BaseNodeClass(void) {}; + int operator == (BaseNodeClass const & node); + int operator != (BaseNodeClass const & node); + int operator > (BaseNodeClass const & node); + + StructType Type; + COORDINATE Coord; +}; + + +/**************************************************************************** +** This is the class that defines a pre-built base for the computer AI. +** (Despite its name, this is NOT the "base" class for C&C's class hierarchy!) +*/ +class BaseClass +{ + public: + + /********************************************************************** + ** Constructor/Destructor + */ + BaseClass(void) {}; + virtual ~BaseClass() {Nodes.Clear();} + + /********************************************************************** + ** Initialization + */ + void Init(void) {Nodes.Clear();} + + /********************************************************************** + ** The standard suite of load/save support routines + */ + void Read_INI(char *buffer); + void Write_INI(char *buffer); + static char *INI_Name(void) {return "Base";} + bool Load(FileClass & file); + bool Save(FileClass & file); + virtual void Code_Pointers(void) {}; + virtual void Decode_Pointers(void) {}; + + /********************************************************************** + ** Tells if the given node has been built or not + */ + bool Is_Built(int index); + + /********************************************************************** + ** Returns a pointer to the object for the given node + */ + BuildingClass * Get_Building(int index); + + /********************************************************************** + ** Tells if the given building ptr is a node in this base's list. + */ + bool Is_Node (BuildingClass *obj); + + /********************************************************************** + ** Returns a pointer to the requested node. + */ + BaseNodeClass * Get_Node(BuildingClass *obj); + BaseNodeClass * Get_Node(int index) { return (&Nodes[index]); } + + /********************************************************************** + ** Returns a pointer to the next "hole" in the Nodes list. + */ + BaseNodeClass * Next_Buildable(StructType type = STRUCT_NONE); + + /********************************************************************** + ** This is the list of "nodes" that define the base. Portions of this + ** list can be pre-built by simply saving those buildings in the INI + ** along with non-base buildings, so Is_Built will return true for them. + */ + DynamicVectorClass Nodes; + + /********************************************************************** + ** This is the house this base belongs to. + */ + HousesType House; +}; + + +#endif diff --git a/BBDATA.CPP b/BBDATA.CPP new file mode 100644 index 0000000..d4ca730 --- /dev/null +++ b/BBDATA.CPP @@ -0,0 +1,608 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\bbdata.cpv 2.17 16 Oct 1995 16:49:46 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : BBDATA.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 23, 1994 * + * * + * Last Update : October 17, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * BulletTypeClass::BulletTypeClass -- Constructor for bullet type objects. * + * BulletTypeClass::Load_Shapes -- Load shape data for bullet types. * + * BulletTypeClass::One_Time -- Performs the one time processing for bullets. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +/*************************************************************************** +** Detailed information about each class of bullet (projectile) in the game. +*/ +static BulletTypeClass const ClassSniper( + BULLET_BULLET, + "50cal", // NAME: Text name of this unit type. + false, // Flies over tall walls? + false, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + true, // Is this projectile invisible? + false, // Will it blow up even if it gets just NEAR to target? + false, // Does it have flickering flame animation? + false, // Can it run out of fuel? + true, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + false, // Translucent colors are used? + false, // Good against aircraft? + 0, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_LIGHT_SPEED, // SPEED: Miles per hour. + 0, // ROT: Rate of turn (degrees per tick). + WARHEAD_HOLLOW_POINT, // WARHEAD: If fires weapon, warhead type + ANIM_PIFF // Explosion to use upon impact. +); + +static BulletTypeClass const ClassBullet( + BULLET_BULLET, + "50cal", // NAME: Text name of this unit type. + false, // Flies over tall walls? + false, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + true, // Is this projectile invisible? + false, // Will it blow up even if it gets just NEAR to target? + false, // Does it have flickering flame animation? + false, // Can it run out of fuel? + true, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + false, // Translucent colors are used? + false, // Good against aircraft? + 0, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_LIGHT_SPEED, // SPEED: Miles per hour. + 0, // ROT: Rate of turn (degrees per tick). + WARHEAD_SA, // WARHEAD: If fires weapon, warhead type + ANIM_PIFF // Explosion to use upon impact. +); + +static BulletTypeClass const ClassSpreadfire( + BULLET_SPREADFIRE, + "50cal", // NAME: Text name of this unit type. + true, // Flies over tall walls? + false, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + true, // Is this projectile invisible? + false, // Will it blow up even if it gets just NEAR to target? + false, // Does it have flickering flame animation? + false, // Can it run out of fuel? + true, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + false, // Translucent colors are used? + false, // Good against aircraft? + 0, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_LIGHT_SPEED, // SPEED: Miles per hour. + 0, // ROT: Rate of turn (degrees per tick). + WARHEAD_HE, // WARHEAD: If fires weapon, warhead type + ANIM_PIFFPIFF // Explosion to use upon impact. +); + +static BulletTypeClass const ClassAPDS( + BULLET_APDS, + "120mm", // NAME: Text name of this unit type. + false, // Flies over tall walls? + false, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + false, // Is this projectile invisible? + false, // Will it blow up even if it gets just NEAR to target? + false, // Does it have flickering flame animation? + false, // Can it run out of fuel? + true, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + false, // Translucent colors are used? + false, // Good against aircraft? + 0, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_VERY_FAST, // SPEED: Miles per hour. + 0, // ROT: Rate of turn (degrees per tick). + WARHEAD_AP, // WARHEAD: If fires weapon, warhead type + ANIM_VEH_HIT3 // Explosion to use upon impact. +); + +static BulletTypeClass const Class120mm( + BULLET_HE, + "120mm", // NAME: Text name of this unit type. + true, // Flies over tall walls? + false, // Homes in on target? + true, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + false, // Is this projectile invisible? + false, // Will it blow up even if it gets just NEAR to target? + false, // Does it have flickering flame animation? + false, // Can it run out of fuel? + true, // Is there no visual difference between projectile facings? + true, // Is projectile inherently inaccurate? + false, // Translucent colors are used? + false, // Good against aircraft? + 0, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_MEDIUM_FAST, // SPEED: Miles per hour. + 0, // ROT: Rate of turn (degrees per tick). + WARHEAD_HE, // WARHEAD: If fires weapon, warhead type + ANIM_ART_EXP1 // Explosion to use upon impact. +); + +static BulletTypeClass const ClassMissile( + BULLET_SSM, + "DRAGON", // NAME: Text name of this unit type. + true, // Flies over tall walls? + true, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + false, // Is this projectile invisible? + true, // Will it blow up even if it gets just NEAR to target? + true, // Does it have flickering flame animation? + true, // Can it run out of fuel? + false, // Is there no visual difference between projectile facings? + true, // Is projectile inherently inaccurate? + true, // Translucent colors are used? + true, // Good against aircraft? + 7, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_ROCKET, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + WARHEAD_HE, // WARHEAD: If fires weapon, warhead type + ANIM_FRAG1 // Explosion to use upon impact. +); + +static BulletTypeClass const ClassMissile2( + BULLET_SSM2, + "DRAGON", // NAME: Text name of this unit type. + true, // Flies over tall walls? + true, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + false, // Is this projectile invisible? + true, // Will it blow up even if it gets just NEAR to target? + true, // Does it have flickering flame animation? + true, // Can it run out of fuel? + false, // Is there no visual difference between projectile facings? + true, // Is projectile inherently inaccurate? + true, // Translucent colors are used? + true, // Good against aircraft? + 9, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_ROCKET, // SPEED: Miles per hour. + 7, // ROT: Rate of turn (degrees per tick). + WARHEAD_HE, // WARHEAD: If fires weapon, warhead type + ANIM_FRAG1 // Explosion to use upon impact. +); + +static BulletTypeClass const ClassPatriot( + BULLET_SAM, + "MISSILE", // NAME: Text name of this unit type. + true, // Flies over tall walls? + true, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + false, // Is this projectile invisible? + true, // Will it blow up even if it gets just NEAR to target? + true, // Does it have flickering flame animation? + true, // Can it run out of fuel? + false, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + false, // Translucent colors are used? + true, // Good against aircraft? + 0, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_VERY_FAST, // SPEED: Miles per hour. + 10, // ROT: Rate of turn (degrees per tick). + WARHEAD_AP, // WARHEAD: If fires weapon, warhead type + ANIM_VEH_HIT1 // Explosion to use upon impact. +); + +static BulletTypeClass const ClassDragon( + BULLET_TOW, + "DRAGON", // NAME: Text name of this unit type. + true, // Flies over tall walls? + true, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + false, // Is this projectile invisible? + true, // Will it blow up even if it gets just NEAR to target? + true, // Does it have flickering flame animation? + true, // Can it run out of fuel? + false, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + true, // Translucent colors are used? + true, // Good against aircraft? + 3, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_ROCKET, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + WARHEAD_AP, // WARHEAD: If fires weapon, warhead type + ANIM_VEH_HIT2 // Explosion to use upon impact. +); + +static BulletTypeClass const ClassFlame( + BULLET_FLAME, + "FLAME", // NAME: Text name of this unit type. + false, // Flies over tall walls? + false, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + true, // Is this projectile invisible? + false, // Will it blow up even if it gets just NEAR to target? + false, // Does it have flickering flame animation? + true, // Can it run out of fuel? + false, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + false, // Translucent colors are used? + false, // Good against aircraft? + 12, // ARMING: Time to arm projectile after launch. + 12, // RANGE: Inherent override range factor. + MPH_FAST, // SPEED: Miles per hour. + 0, // ROT: Rate of turn (degrees per tick). + WARHEAD_FIRE, // WARHEAD: If fires weapon, warhead type + ANIM_NONE // Explosion to use upon impact. +); + +static BulletTypeClass const ClassChem( + BULLET_CHEMSPRAY, + "FLAME", // NAME: Text name of this unit type. + false, // Flies over tall walls? + false, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + true, // Is this projectile invisible? + false, // Will it blow up even if it gets just NEAR to target? + false, // Does it have flickering flame animation? + true, // Can it run out of fuel? + false, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + false, // Translucent colors are used? + false, // Good against aircraft? + 12, // ARMING: Time to arm projectile after launch. + 12, // RANGE: Inherent override range factor. + MPH_FAST, // SPEED: Miles per hour. + 0, // ROT: Rate of turn (degrees per tick). + WARHEAD_HE, // WARHEAD: If fires weapon, warhead type + ANIM_NONE // Explosion to use upon impact. +); + +static BulletTypeClass const ClassNapalm( + BULLET_NAPALM, + "BOMBLET", // NAME: Text name of this unit type. + true, // Flies over tall walls? + false, // Homes in on target? + false, // Projectile arcs to the target? + true, // Is this a dropping bomb-like object? + false, // Is this projectile invisible? + false, // Will it blow up even if it gets just NEAR to target? + false, // Does it have flickering flame animation? + false, // Can it run out of fuel? + true, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + true, // Translucent colors are used? + false, // Good against aircraft? + 24, // ARMING: Time to arm projectile after launch. + 24, // RANGE: Inherent override range factor. + MPH_MEDIUM_SLOW, // SPEED: Miles per hour. + 0, // ROT: Rate of turn (degrees per tick). + WARHEAD_FIRE, // WARHEAD: If fires weapon, warhead type + ANIM_NAPALM2 // Explosion to use upon impact. +); + +static BulletTypeClass const ClassGrenade( + BULLET_GRENADE, + "BOMB", // NAME: Text name of this unit type. + true, // Flies over tall walls? + false, // Homes in on target? + true, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + false, // Is this projectile invisible? + false, // Will it blow up even if it gets just NEAR to target? + false, // Does it have flickering flame animation? + false, // Can it run out of fuel? + true, // Is there no visual difference between projectile facings? + true, // Is projectile inherently inaccurate? + true, // Translucent colors are used? + false, // Good against aircraft? + 0, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_MEDIUM_SLOW, // SPEED: Miles per hour. + 0, // ROT: Rate of turn (degrees per tick). + WARHEAD_HE, // WARHEAD: If fires weapon, warhead type + ANIM_VEH_HIT2 // Explosion to use upon impact. +); + +static BulletTypeClass const ClassLaser( + BULLET_LASER, + "Laser", // NAME: Text name of this unit type. + true, // Flies over tall walls? + false, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + true, // Is this projectile invisible? + false, // Will it blow up even if it gets just NEAR to target? + false, // Does it have flickering flame animation? + false, // Can it run out of fuel? + true, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + false, // Translucent colors are used? + false, // Good against aircraft? + 0, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_LIGHT_SPEED, // SPEED: Miles per hour. + 0, // ROT: Rate of turn (degrees per tick). + WARHEAD_LASER, // WARHEAD: If fires weapon, warhead type + ANIM_NONE // Explosion to use upon impact. +); + +static BulletTypeClass const ClassNukeUp( + BULLET_NUKE_UP, + "ATOMICUP", // NAME: Text name of this unit type. + true, // Flies over tall walls? + false, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + false, // Is this projectile invisible? + true, // Will it blow up even if it gets just NEAR to target? + false, // Does it have flickering flame animation? + false, // Can it run out of fuel? + true, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + false, // Translucent colors are used? + false, // Good against aircraft? + 0, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_VERY_FAST, // SPEED: Miles per hour. + 0, // ROT: Rate of turn (degrees per tick). + WARHEAD_HE, // WARHEAD: If fires weapon, warhead type + ANIM_FRAG1 // Explosion to use upon impact. +); + +static BulletTypeClass const ClassNukeDown( + BULLET_NUKE_DOWN, + "ATOMICDN", // NAME: Text name of this unit type. + true, // Flies over tall walls? + false, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + false, // Is this projectile invisible? + true, // Will it blow up even if it gets just NEAR to target? + false, // Does it have flickering flame animation? + false, // Can it run out of fuel? + true, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + false, // Translucent colors are used? + false, // Good against aircraft? + 0, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_VERY_FAST, // SPEED: Miles per hour. + 0, // ROT: Rate of turn (degrees per tick). + WARHEAD_HE, // WARHEAD: If fires weapon, warhead type + ANIM_ATOM_BLAST // Explosion to use upon impact. +); + +static BulletTypeClass const ClassHonestJohn( + BULLET_HONEST_JOHN, + "MISSILE", // NAME: Text name of this unit type. + true, // Flies over tall walls? + false, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + false, // Is this projectile invisible? + true, // Will it blow up even if it gets just NEAR to target? + true, // Does it have flickering flame animation? + true, // Can it run out of fuel? + false, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + false, // Translucent colors are used? + false, // Good against aircraft? + 10, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_FAST, // SPEED: Miles per hour. + 10, // ROT: Rate of turn (degrees per tick). + WARHEAD_FIRE, // WARHEAD: If fires weapon, warhead type + ANIM_NAPALM3 // Explosion to use upon impact. +); + +static BulletTypeClass const ClassHeadButt( + BULLET_HEADBUTT, + "GORE", // NAME: Text name of this unit type. + false, // Flies over tall walls? + false, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + true, // Is this projectile invisible? + false, // Will it blow up even if it gets just NEAR to target? + false, // Does it have flickering flame animation? + false, // Can it run out of fuel? + true, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + false, // Translucent colors are used? + false, // Good against aircraft? + 0, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_LIGHT_SPEED, // SPEED: Miles per hour. + 0, // ROT: Rate of turn (degrees per tick). + WARHEAD_HEADBUTT, // WARHEAD: If fires weapon, warhead type + ANIM_NONE // Explosion to use upon impact. +); + +static BulletTypeClass const ClassTRexBite( + BULLET_TREXBITE, + "CHEW", // NAME: Text name of this unit type. + false, // Flies over tall walls? + false, // Homes in on target? + false, // Projectile arcs to the target? + false, // Is this a dropping bomb-like object? + true, // Is this projectile invisible? + false, // Will it blow up even if it gets just NEAR to target? + false, // Does it have flickering flame animation? + false, // Can it run out of fuel? + true, // Is there no visual difference between projectile facings? + false, // Is projectile inherently inaccurate? + false, // Translucent colors are used? + false, // Good against aircraft? + 0, // ARMING: Time to arm projectile after launch. + 0, // RANGE: Inherent override range factor. + MPH_LIGHT_SPEED, // SPEED: Miles per hour. + 0, // ROT: Rate of turn (degrees per tick). + WARHEAD_FEEDME, // WARHEAD: If fires weapon, warhead type + ANIM_NONE // Explosion to use upon impact. +); + + +/* +** This is the array of pointers to the static data associated with +** each bullet (projectile) type. +*/ +BulletTypeClass const * const BulletTypeClass::Pointers[BULLET_COUNT] = { + &ClassSniper, // BULLET_SNIPER + &ClassBullet, // BULLET_BULLET + &ClassAPDS, // BULLET_APDS + &Class120mm, // BULLET_HE + &ClassMissile, // BULLET_SSM + &ClassMissile2, // BULLET_SSM2 + &ClassPatriot, // BULLET_SAM + &ClassDragon, // BULLET_TOW + &ClassFlame, // BULLET_FLAME + &ClassChem, // BULLET_CHEMSPRAY + &ClassNapalm, // BULLET_NAPALM + &ClassGrenade, // BULLET_GRENADE + &ClassLaser, // BULLET_LASER + &ClassNukeUp, // BULLET_NUKE_UP + &ClassNukeDown, // BULLET_NUKE_DOWN + &ClassHonestJohn, // BULLET_HONEST_JOHN + &ClassSpreadfire, // BULLET_SPREADFIRE + &ClassHeadButt, // BULLET_HEADBUTT + &ClassTRexBite, // BULLET_TREXBITE +}; + + +/*********************************************************************************************** + * BulletTypeClass::BulletTypeClass -- Constructor for bullet type objects. * + * * + * This is basically a constructor for static type objects used by bullets. All bullets * + * are of a type constructed by this routine at game initialization time. * + * * + * INPUT: see below... * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +BulletTypeClass::BulletTypeClass( + BulletType type, + char const *ininame, + bool is_high, + bool is_homing, + bool is_arcing, + bool is_dropping, + bool is_invisible, + bool is_proximity_armed, + bool is_flame_equipped, + bool is_fueled, + bool is_faceless, + bool is_inaccurate, + bool is_translucent, + bool is_antiaircraft, + int arming, int range, MPHType maxspeed, unsigned rot, + WarheadType warhead, AnimType explosion) : + ObjectTypeClass(true, false, false, true, false, false, true, true, TXT_NONE, ininame, ARMOR_NONE, 0) +{ + Explosion = explosion; + IsHigh = is_high; + IsAntiAircraft = is_antiaircraft; + IsTranslucent = is_translucent; + IsArcing = is_arcing; + IsHoming = is_homing; + IsDropping = is_dropping; + IsInvisible = is_invisible; + IsProximityArmed = is_proximity_armed; + IsFlameEquipped = is_flame_equipped; + IsFueled = is_fueled; + IsFaceless = is_faceless; + IsInaccurate = is_inaccurate; + Type = type; + Warhead = warhead; + MaxSpeed = maxspeed; + ROT = rot; + Arming = arming; + Range = range; +} + + +/*********************************************************************************************** + * BulletTypeClass::One_Time -- Performs the one time processing for bullets. * + * * + * This routine is used to perform any one time processing for the bullet type class. It * + * handles loading of the shape files. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine must be called before any rendering of bullets occurs and should * + * only be called once. * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +void BulletTypeClass::One_Time(void) +{ + BulletType index; + + /* + ** Load the bullet shapes. + */ + for (index = BULLET_FIRST; index < BULLET_COUNT; index++) { + BulletTypeClass const & bullet = As_Reference(index); + char fullname[_MAX_FNAME+_MAX_EXT]; + + if (!bullet.IsInvisible) { + _makepath(fullname, NULL, NULL, bullet.IniName, ".SHP"); + + RawFileClass file(fullname); + + if (file.Is_Available()) { + ((void const *&)bullet.ImageData) = Load_Alloc_Data(file); + } else { + ((void const *&)bullet.ImageData) = MixFileClass::Retrieve(fullname); + } + } + } +} + + diff --git a/BDATA.CPP b/BDATA.CPP new file mode 100644 index 0000000..04e2b93 --- /dev/null +++ b/BDATA.CPP @@ -0,0 +1,4938 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\bdata.cpv 2.17 16 Oct 1995 16:50:08 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : BDATA.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : July 17, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * BuildingTypeClass::As_Reference -- Fetches reference to the building type specified. * + * BuildingTypeClass::Bib_And_Offset -- Determines the bib and appropriate cell offset. * + * BuildingTypeClass::BuildingTypeClass -- This is the constructor for the building types. * + * BuildingTypeClass::Create_And_Place -- Creates and places a building object onto the map. * + * BuildingTypeClass::Create_One_Of -- Creates a building of this type. * + * BuildingTypeClass::Dimensions -- Fetches the pixel dimensions of the building. * + * BuildingTypeClass::Display -- Renders a generic view of building. * + * BuildingTypeClass::Full_Name -- Fetches the full name text number. * + * BuildingTypeClass::Height -- Determins the height of the building in icons. * + * BuildingTypeClass::Init -- Performs theater specific initialization. * + * BuildingTypeClass::Init_Anim -- Initialize an animation control for a building. * + * BuildingTypeClass::Legal_Placement -- Determines if building can be legally placed at pos.* + * BuildingTypeClass::Max_Pips -- Determines the maximum pips to display. * + * BuildingTypeClass::Occupy_List -- Fetches the occupy list for the building. * + * BuildingTypeClass::One_Time -- Performs special one time action for buildings. * + * BuildingTypeClass::Overlap_List -- Fetches the overlap list for the building. * + * BuildingTypeClass::Prep_For_Add -- Prepares scenario editor for adding an object. * + * BuildingTypeClass::Repair_Cost -- Determines cost per "step" of repair. * + * BuildingTypeClass::Repair_Step -- Determines the repair step rate. * + * BuildingTypeClass::Who_Can_Build_Me -- Determines which factory can create the building. * + * BuildingTypeClass::Width -- Determines width of bulding in icons. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#define MCW MAP_CELL_W + +#define XYCELL(x,y) (y*MAP_CELL_W+x) +static short const ExitPyle[] = { + XYCELL(1,2), + XYCELL(2,2), + XYCELL(0,2), + XYCELL(-1,2), + XYCELL(-1,-1), + XYCELL(0,-1), + XYCELL(1,-1), + XYCELL(2,-1), + XYCELL(2,-1), + XYCELL(-1,0), + XYCELL(2,0), + XYCELL(2,1), + XYCELL(-1,1), + REFRESH_EOL +}; +static short const ExitHand[] = { + XYCELL(2,3), + XYCELL(1,3), + XYCELL(0,3), + XYCELL(2,2), + XYCELL(-1,3), + XYCELL(-1,2), + XYCELL(0,0), + XYCELL(1,0), + XYCELL(-1,0), + XYCELL(2,0), + XYCELL(2,1), + XYCELL(-1,1), + REFRESH_EOL +}; +static short const ExitWeap[] = { + XYCELL(-1,3), + XYCELL(0,3), + XYCELL(-1,2), + XYCELL(1,3), +// XYCELL(0,0), +// XYCELL(1,0), +// XYCELL(2,0), +// XYCELL(-1,0), +// XYCELL(3,0), + XYCELL(-1,1), + XYCELL(3,1), + XYCELL(3,2), + XYCELL(3,3), + XYCELL(2,3), + REFRESH_EOL +}; +static short const ExitAirstrip[] = { + XYCELL(-1,-1), + XYCELL(-1,0), + XYCELL(-1,1), + XYCELL(-1,2), + XYCELL(0,-1), + XYCELL(0,2), + XYCELL(1,-1), + XYCELL(1,2), + XYCELL(2,-1), + XYCELL(2,2), + XYCELL(3,-1), + XYCELL(3,2), + XYCELL(4,-1), + XYCELL(4,0), + XYCELL(4,1), + XYCELL(4,2), + REFRESH_EOL +}; + +static short const OListSAM[] = {-MCW, -(MCW-1), REFRESH_EOL}; +static short const List32[] = {0, 1, 2, MCW, MCW+1, MCW+2, REFRESH_EOL}; +static short const List22_0011[] = {MCW, MCW+1, REFRESH_EOL}; +static short const List22_1100[] = {0, 1, REFRESH_EOL}; +static short const ListFix[] = {1, MCW, MCW+1, MCW+2, MCW+MCW+1, REFRESH_EOL}; +static short const StoreList[] = {0, 1, REFRESH_EOL}; +static short const List2[] = {0, 1, MCW+1, MCW, REFRESH_EOL}; +static short const List42[] = {0, 1, 2, 3, MCW, MCW+1, MCW+2, MCW+3, REFRESH_EOL}; +static short const ListWestwood[] = {1, 2, 3, MCW+1, MCW+2, MCW+3, REFRESH_EOL}; +static short const OListWestwood[] = {0, MCW, REFRESH_EOL}; +static short const ComList[] = {0, MCW, MCW+1, REFRESH_EOL}; +static short const List21[] = {0, 1, REFRESH_EOL}; +static short const ListWeap[] = {(MCW*1), (MCW*1)+1, (MCW*1)+2, (MCW*2), (MCW*2)+1, (MCW*2)+2, REFRESH_EOL}; +static short const List12[] = {MCW, REFRESH_EOL}; +static short const ListHand[] = {MCW, MCW+1, MCW*2+1, REFRESH_EOL}; +static short const ListTmpl[] = {MCW, MCW+1, MCW+2, MCW*2, MCW*2+1, MCW*2+2, REFRESH_EOL}; +static short const List0011[] = {(MCW*1), (MCW*1)+1, REFRESH_EOL}; +static short const List1101[] = {0, 1, (MCW*1)+1, REFRESH_EOL}; +static short const List11[] = {0, 1, REFRESH_EOL}; +static short const List1[] = {0, REFRESH_EOL}; +static short const List1100[] = {0, 1, REFRESH_EOL}; +static short const List0010[] = {MCW, REFRESH_EOL}; +static short const List1000[] = {0, REFRESH_EOL}; +static short const List0100[] = {1, REFRESH_EOL}; +static short const List0111[] = {1, (MCW*1), (MCW*1)+1, REFRESH_EOL}; +//static short const List1111[] = {0, 1, (MCW*1), (MCW*1)+1, REFRESH_EOL}; +static short const List1011[] = {0, (MCW*1), (MCW*1)+1, REFRESH_EOL}; +static short const List010111000[] = {1, (MCW*1), (MCW*1)+1, (MCW*1)+2, REFRESH_EOL}; +static short const List101000111[] = {0, 2, (MCW*2), (MCW*2)+1, (MCW*2)+2, REFRESH_EOL}; + +static short const OListFix[] = {0, 2, MCW+MCW, MCW+MCW+2, REFRESH_EOL}; +static short const OListWeap[] = {0, 1, 2, REFRESH_EOL}; +static short const OComList[] = {1, REFRESH_EOL}; +static short const OList12[] = {0, REFRESH_EOL}; +static short const OListHand[] = {0, 1, MCW*2, MCW*1, REFRESH_EOL}; +static short const OListTmpl[] = {0, 1, 2, REFRESH_EOL}; + + +/*************************************************************************** +*/ +static BuildingTypeClass const ClassTemple( + STRUCT_TEMPLE, + TXT_TEMPLE, // NAME: Short name of the structure. + "TMPL", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 7, // Build level. + STRUCTF_RADAR, // Building prerequisite. + false, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + false, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 1000, // STRNTH: Full strength of building. + 4, // SIGHTRANGE: Range of sighting. + 3000, // COST: Cost to purchase. + 13, // SCENARIO: Starting availability scenario. + 0,20, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points generated. + 150, // DRAIN: Power points required. + BSIZE_33, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)ListTmpl, // OCCUPYLIST: List of active foundation squares. + (short const *)OListTmpl // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassEye( + STRUCT_EYE, + TXT_EYE, // NAME: Short name of the structure. + "EYE", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 7, // Build level. + STRUCTF_RADAR, // Building prerequisite. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + true, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + (DirType)160, // Starting idle frame to match construction. + 500, // STRNTH: Full strength of building. + 10, // SIGHTRANGE: Range of sighting. + 2800, // COST: Cost to purchase. + 13, // SCENARIO: Starting availability scenario. + 0,100, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 200, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)ComList, // OCCUPYLIST: List of active foundation squares. + (short const *)OComList // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassWeapon( + STRUCT_WEAP, + TXT_WEAPON_FACTORY, // NAME: Short name of the structure. + "WEAP", // NAME: Short name of the structure. + XYP_COORD(10+(CELL_PIXEL_W/2), ((CELL_PIXEL_H*3)-(CELL_PIXEL_H/2))-21), // Exit point for produced units. + 2, // Build level. + STRUCTF_REFINERY, // Building prerequisite. + false, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + true, // Is it a factory type building? + true, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_UNITTYPE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. +#ifdef ADVANCED + 500, // STRNTH: Full strength of building. +#else + 200, // STRNTH: Full strength of building. +#endif + 3, // SIGHTRANGE: Range of sighting. + 2000, // COST: Cost to purchase. + 5, // SCENARIO: Starting availability scenario. + 0,86, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 30, // DRAIN: Power points required. + BSIZE_33, // SIZE: Building size. + (short const *)ExitWeap, // Preferred exit cell list. + (short const *)ListWeap, // OCCUPYLIST: List of active foundation squares. + (short const *)OListWeap // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassGTower( + STRUCT_GTOWER, + TXT_GUARD_TOWER, // NAME: Short name of the structure. + "GTWR", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 2, // Build level. + STRUCTF_BARRACKS, // Building prerequisite. + true, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + false, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 3, // SIGHTRANGE: Range of sighting. + 500, // COST: Cost to purchase. + 7, // SCENARIO: Starting availability scenario. + 100,25, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_CHAIN_GUN,WEAPON_NONE, +// WEAPON_M60MG,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 00, // POWER: Power points required. + 10, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassATower( + STRUCT_ATOWER, + TXT_AGUARD_TOWER, // NAME: Short name of the structure. + "ATWR", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 4, // Build level. + STRUCTF_RADAR, // Building prerequisite. + true, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + false, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + true, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 300, // STRNTH: Full strength of building. + 4, // SIGHTRANGE: Range of sighting. + 1000, // COST: Cost to purchase. + 13, // SCENARIO: Starting availability scenario. + 100,30, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_TOW_TWO,WEAPON_NONE, +// WEAPON_TOMAHAWK,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 20, // DRAIN: Power points required. + BSIZE_12, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List12, // OCCUPYLIST: List of active foundation squares. + (short const *)OList12 // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassObelisk( + STRUCT_OBELISK, + TXT_OBELISK, // NAME: Short name of the structure. + "OBLI", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 4, // Build level. + STRUCTF_RADAR, // Building prerequisite. + true, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 5, // SIGHTRANGE: Range of sighting. + 1500, // COST: Cost to purchase. + 11, // SCENARIO: Starting availability scenario. + 100,35, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_OBELISK_LASER,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 150, // DRAIN: Power points required. + BSIZE_12, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List12, // OCCUPYLIST: List of active foundation squares. + (short const *)OList12 // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassTurret( + STRUCT_TURRET, + TXT_TURRET, // NAME: Short name of the structure. + "GUN", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 2, // Build level. + STRUCTF_BARRACKS, // Building prerequisite. + true, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + true, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + (DirType)208, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 5, // SIGHTRANGE: Range of sighting. +#ifdef ADVANCED + 600, // COST: Cost to purchase. +#else +#ifdef PATCH + 600, +#else + 250, // COST: Cost to purchase. +#endif +#endif + 8, // SCENARIO: Starting availability scenario. + 300,26, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_TURRET_GUN,WEAPON_NONE, + ARMOR_STEEL, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 20, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassConst( + STRUCT_CONST, + TXT_CONST_YARD, // NAME: Short name of the structure. + "FACT", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + true, // Is it a factory type building? + true, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_BUILDINGTYPE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 400, // STRNTH: Full strength of building. + 3, // SIGHTRANGE: Range of sighting. + 5000, // COST: Cost to purchase. + 1, // SCENARIO: Starting availability scenario. + 0,70, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 30, // POWER: Power points required. + 15, // DRAIN: Power points required. + BSIZE_32, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List32, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassRefinery( + STRUCT_REFINERY, + TXT_REFINERY, // NAME: Short name of the structure. + "PROC", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 1, // Build level. + STRUCTF_POWER, // Building prerequisite. + false, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + true, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 450, // STRNTH: Full strength of building. + 4, // SIGHTRANGE: Range of sighting. + 2000, // COST: Cost to purchase. + 2, // SCENARIO: Starting availability scenario. + 0,55, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 1000, // CAPACITY: Spice storage capacity. + 10, // POWER: Power points required. + 40, // DRAIN: Power points required. + BSIZE_33, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List010111000, // OCCUPYLIST: List of active foundation squares. + (short const *)List101000111 // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassStorage( + STRUCT_STORAGE, + TXT_STORAGE, // NAME: Short name of the structure. + "SILO", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 1, // Build level. + STRUCTF_REFINERY, // Building prerequisite. + false, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + true, // Can this building be captured? + false, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + false, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 150, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 150, // COST: Cost to purchase. +// 300, // COST: Cost to purchase. + 2, // SCENARIO: Starting availability scenario. + 0,16, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 1500, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 10, // DRAIN: Power points required. + BSIZE_21, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)StoreList, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassHelipad( + STRUCT_HELIPAD, + TXT_HELIPAD, // NAME: Short name of the structure. + "HPAD", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 6, // Build level. + STRUCTF_BARRACKS, // Building prerequisite. + false, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + true, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + false, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_AIRCRAFTTYPE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 400, // STRNTH: Full strength of building. + 3, // SIGHTRANGE: Range of sighting. + 1500, // COST: Cost to purchase. + 10, // SCENARIO: Starting availability scenario. + 0,65, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 10, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List2, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassCommand( + STRUCT_RADAR, + TXT_COMMAND, // NAME: Short name of the structure. + "HQ", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 2, // Build level. + STRUCTF_REFINERY, // Building prerequisite. + true, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + true, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + (DirType)160, // Starting idle frame to match construction. + 500, // STRNTH: Full strength of building. + 10, // SIGHTRANGE: Range of sighting. + 1000, // COST: Cost to purchase. + 3, // SCENARIO: Starting availability scenario. + 0,20, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 40, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)ComList, // OCCUPYLIST: List of active foundation squares. + (short const *)OComList // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassSAM( + STRUCT_SAM, + TXT_SAM, // NAME: Short name of the structure. + "SAM", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 6, // Build level. + STRUCTF_BARRACKS, // Building prerequisite. + false, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + true, // Does it have a rotating turret? + true, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + false, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 3, // SIGHTRANGE: Range of sighting. + 750, // COST: Cost to purchase. + 5, // SCENARIO: Starting availability scenario. + 300,40, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NIKE,WEAPON_NONE, + ARMOR_STEEL, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 20, // DRAIN: Power points required. + BSIZE_21, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List21, // OCCUPYLIST: List of active foundation squares. + (short const *)OListSAM // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassAirStrip( + STRUCT_AIRSTRIP, + TXT_AIRSTRIP, // NAME: Short name of the structure. + "AFLD", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 2, // Build level. + STRUCTF_REFINERY, // Building prerequisite. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + true, // Is it a factory type building? + true, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_UNITTYPE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 500, // STRNTH: Full strength of building. + 5, // SIGHTRANGE: Range of sighting. + 2000, // COST: Cost to purchase. + 5, // SCENARIO: Starting availability scenario. + 300,86, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_STEEL, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 30, // DRAIN: Power points required. + BSIZE_42, // SIZE: Building size. + ExitAirstrip, // Preferred exit cell list. + (short const *)List42, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassPower( + STRUCT_POWER, + TXT_POWER, // NAME: Short name of the structure. + "NUKE", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 0, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + true, // Can this building be captured? + false, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 300, // COST: Cost to purchase. + 1, // SCENARIO: Starting availability scenario. + 0,50, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 100, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1011, // OCCUPYLIST: List of active foundation squares. + (short const *)List0100 // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassAdvancedPower( + STRUCT_ADVANCED_POWER, + TXT_ADVANCED_POWER, // NAME: Short name of the structure. + "NUK2", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 5, // Build level. + STRUCTF_POWER, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + true, // Can this building be captured? + false, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 300, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 700, // COST: Cost to purchase. + 13, // SCENARIO: Starting availability scenario. + 0,75, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 200, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1011, // OCCUPYLIST: List of active foundation squares. + (short const *)List0100 // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassHospital( + STRUCT_HOSPITAL, + TXT_HOSPITAL, // NAME: Short name of the structure. + "HOSP", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_BARRACKS, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + true, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 500, // COST: Cost to purchase. + 99, // SCENARIO: Starting availability scenario. + 0,20, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 100, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 20, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List2, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassBioLab( + STRUCT_BIO_LAB, + TXT_BIO_LAB, // NAME: Short name of the structure. + "BIO", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_HOSPITAL, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 300, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 500, // COST: Cost to purchase. + 99, // SCENARIO: Starting availability scenario. + 0,1, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 100, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 40, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List2, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassBarracks( + STRUCT_BARRACKS, + TXT_BARRACKS, // NAME: Short name of the structure. + "PYLE", // NAME: Short name of the structure. + XYP_COORD(30,33), // Exit point for produced units. + 0, // Build level. + STRUCTF_POWER, // Building prerequisite. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + true, // Is it a factory type building? + true, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_INFANTRYTYPE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 400, // STRNTH: Full strength of building. + 3, // SIGHTRANGE: Range of sighting. + 300, // COST: Cost to purchase. + 1, // SCENARIO: Starting availability scenario. + 0,60, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 20, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + (short const *)ExitPyle, // Preferred exit cell list. + (short const *)List22_1100, // OCCUPYLIST: List of active foundation squares. + (short const *)List22_0011 // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassHand( + STRUCT_HAND, + TXT_HAND, // NAME: Short name of the structure. + "HAND", // NAME: Short name of the structure. + XYP_COORD(36,63), // Exit point for produced units. + 0, // Build level. + STRUCTF_POWER, // Building prerequisite. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + true, // Is it a factory type building? + true, // Can this building be captured? + false, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_INFANTRYTYPE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 400, // STRNTH: Full strength of building. + 3, // SIGHTRANGE: Range of sighting. + 300, // COST: Cost to purchase. + 2, // SCENARIO: Starting availability scenario. + 0,61, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 20, // DRAIN: Power points required. + BSIZE_23, // SIZE: Building size. + (short const *)ExitHand, // Preferred exit cell list. + (short const *)ListHand, // OCCUPYLIST: List of active foundation squares. + (short const *)OListHand // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassTanker( + STRUCT_TANKER, + TXT_TANKER, // NAME: Short name of the structure. + "ARCO", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_POWER, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + true, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 100, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,1, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_21, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List21, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassRepair( + STRUCT_REPAIR, + TXT_FIX_IT, // NAME: Short name of the structure. + "FIX", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 5, // Build level. + STRUCTF_POWER, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + false, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + true, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + true, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 400, // STRNTH: Full strength of building. + 3, // SIGHTRANGE: Range of sighting. + 1200, // COST: Cost to purchase. + 8, // SCENARIO: Starting availability scenario. + 0,46, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 30, // DRAIN: Power points required. + BSIZE_33, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)ListFix, // OCCUPYLIST: List of active foundation squares. + (short const *)OListFix // OVERLAPLIST:List of overlap cell offset. +); + +#ifdef OBSOLETE +static BuildingTypeClass const ClassRoad( + STRUCT_ROAD, + TXT_ROAD, // NAME: Short name of the structure. + "ROAD", // NAME: Short name of the structure. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + 0, // Building prerequisite. + false, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + false, // Can the player select this? + false, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + true, // Can it be manufactured by the player? + false, // Does it contain a crew? + true, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 1, // STRNTH: Full strength of building. + 0, // SIGHTRANGE: Range of sighting. + 50, // COST: Cost to purchase. + 99, // SCENARIO: Starting availability scenario. + 0,0, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_NONE, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); +#endif + +static BuildingTypeClass const ClassV01( + STRUCT_V01, + TXT_CIV1, // NAME: Short name of the structure. + "V01", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List0011, // OCCUPYLIST: List of active foundation squares. + (short const *)List1100 // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV02( + STRUCT_V02, + TXT_CIV2, // NAME: Short name of the structure. + "V02", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List0011, // OCCUPYLIST: List of active foundation squares. + (short const *)List1100 // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV03( + STRUCT_V03, + TXT_CIV3, // NAME: Short name of the structure. + "V03", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List0111, // OCCUPYLIST: List of active foundation squares. + (short const *)List1000 // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV04( + STRUCT_V04, + TXT_CIV4, // NAME: Short name of the structure. + "V04", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List0011, // OCCUPYLIST: List of active foundation squares. + (short const *)List1100 // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV05( + STRUCT_V05, + TXT_CIV5, // NAME: Short name of the structure. + "V05", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_21, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List11, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV06( + STRUCT_V06, + TXT_CIV6, // NAME: Short name of the structure. + "V06", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_21, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List11, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV07( + STRUCT_V07, + TXT_CIV7, // NAME: Short name of the structure. + "V07", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_21, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List11, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV08( + STRUCT_V08, + TXT_CIV8, // NAME: Short name of the structure. + "V08", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV09( + STRUCT_V09, + TXT_CIV9, // NAME: Short name of the structure. + "V09", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV10( + STRUCT_V10, + TXT_CIV10, // NAME: Short name of the structure. + "V10", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV11( + STRUCT_V11, + TXT_CIV11, // NAME: Short name of the structure. + "V11", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV12( + STRUCT_V12, + TXT_CIV12, // NAME: Short name of the structure. + "V12", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV13( + STRUCT_V13, + TXT_CIV13, // NAME: Short name of the structure. + "V13", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV14( + STRUCT_V14, + TXT_CIV14, // NAME: Short name of the structure. + "V14", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV15( + STRUCT_V15, + TXT_CIV15, // NAME: Short name of the structure. + "V15", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV16( + STRUCT_V16, + TXT_CIV16, // NAME: Short name of the structure. + "V16", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV17( + STRUCT_V17, + TXT_CIV17, // NAME: Short name of the structure. + "V17", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV18( + STRUCT_V18, + TXT_CIV18, // NAME: Short name of the structure. + "V18", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + true, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 1, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV19( + STRUCT_PUMP, + TXT_PUMP, // NAME: Short name of the structure. + "V19", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV20( + STRUCT_V20, + TXT_CIV20, // NAME: Short name of the structure. + "V20", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List0011, // OCCUPYLIST: List of active foundation squares. + (short const *)List1100 // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV21( + STRUCT_V21, + TXT_CIV21, // NAME: Short name of the structure. + "V21", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1101, // OCCUPYLIST: List of active foundation squares. + (short const *)List0010 // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV22( + STRUCT_V22, + TXT_CIV22, // NAME: Short name of the structure. + "V22", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_21, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List11, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV23( + STRUCT_V23, + TXT_CIV23, // NAME: Short name of the structure. + "V23", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV24( + STRUCT_V24, + TXT_CIV24, // NAME: Short name of the structure. + "V24", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List0011, // OCCUPYLIST: List of active foundation squares. + (short const *)List1100 // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV25( + STRUCT_V25, + TXT_CIV25, // NAME: Short name of the structure. + "V25", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_22, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List0111, // OCCUPYLIST: List of active foundation squares. + (short const *)List1000 // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV26( + STRUCT_V26, + TXT_CIV26, // NAME: Short name of the structure. + "V26", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_21, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List11, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV27( + STRUCT_V27, + TXT_CIV27, // NAME: Short name of the structure. + "V27", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV28( + STRUCT_V28, + TXT_CIV28, // NAME: Short name of the structure. + "V28", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV29( + STRUCT_V29, + TXT_CIV29, // NAME: Short name of the structure. + "V29", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV30( + STRUCT_V30, + TXT_CIV30, // NAME: Short name of the structure. + "V30", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_21, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List11, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV31( + STRUCT_V31, + TXT_CIV31, // NAME: Short name of the structure. + "V31", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_21, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List11, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV32( + STRUCT_V32, + TXT_CIV32, // NAME: Short name of the structure. + "V32", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_21, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List11, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV33( + STRUCT_V33, + TXT_CIV33, // NAME: Short name of the structure. + "V33", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_21, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List11, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV34( + STRUCT_V34, + TXT_CIV34, // NAME: Short name of the structure. + "V34", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV35( + STRUCT_V35, + TXT_CIV35, // NAME: Short name of the structure. + "V35", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +static BuildingTypeClass const ClassV36( + STRUCT_V36, + TXT_CIV36, // NAME: Short name of the structure. + "V36", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); +static BuildingTypeClass const ClassV37( + STRUCT_V37, + TXT_CIV37, // NAME: Short name of the structure. + "V37", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + true, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 300, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_42, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)ListWestwood, // OCCUPYLIST: List of active foundation squares. + (short const *)OListWestwood // OVERLAPLIST:List of overlap cell offset. +); +static BuildingTypeClass const ClassMission( + STRUCT_MISSION, + TXT_CIVMISS, // NAME: Short name of the structure. + "MISS", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + true, // Has ability to detect adjacent cloaked objects? + true, // Animation rate is regulated for constant speed? + true, // Requires a bib dirt patch? + true, // Always use the given name for the building? + false, // Is this a wall type structure? + false, // Is it a factory type building? + true, // Can this building be captured? + true, // Does it catch fire? + true, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + true, // Can the player select this? + true, // Is this a legal target for attack or move? + false, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired? + false, // Can it be manufactured by the player? + true, // Does it contain a crew? + false, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 200, // STRNTH: Full strength of building. + 2, // SIGHTRANGE: Range of sighting. + 1000, // COST: Cost to purchase. + 0, // SCENARIO: Starting availability scenario. + 0,2, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_32, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List32, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + +// Sandbag wall +static BuildingTypeClass const Sandbag( + STRUCT_SANDBAG_WALL, + TXT_SANDBAG_WALL, // NAME: Short name of the structure. + "SBAG", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 2, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + true, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + false, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + true, // Can it be manufactured by the player? + false, // Does it contain a crew? + true, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 1, // STRNTH: Full strength of building. + 0, // SIGHTRANGE: Range of sighting. + 50, // COST: Cost to purchase. + 5, // SCENARIO: Starting availability scenario. + 0,0, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); +// Cyclone fence +static BuildingTypeClass const Cyclone( + STRUCT_CYCLONE_WALL, + TXT_CYCLONE_WALL, // NAME: Short name of the structure. + "CYCL", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 5, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + true, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + false, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + true, // Can it be manufactured by the player? + false, // Does it contain a crew? + true, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 1, // STRNTH: Full strength of building. + 0, // SIGHTRANGE: Range of sighting. + 75, // COST: Cost to purchase. + 9, // SCENARIO: Starting availability scenario. + 0,0, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); +// Brick wall +static BuildingTypeClass const Brick( + STRUCT_BRICK_WALL, + TXT_BRICK_WALL, // NAME: Short name of the structure. + "BRIK", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 7, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + true, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + false, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + true, // Can it be manufactured by the player? + false, // Does it contain a crew? + true, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 1, // STRNTH: Full strength of building. + 0, // SIGHTRANGE: Range of sighting. + 100, // COST: Cost to purchase. + 13, // SCENARIO: Starting availability scenario. + 0,0, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); +// Barbwire wall +static BuildingTypeClass const Barbwire( + STRUCT_BARBWIRE_WALL, + TXT_BARBWIRE_WALL, // NAME: Short name of the structure. + "BARB", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 98, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + true, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + false, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + true, // Can it be manufactured by the player? + false, // Does it contain a crew? + true, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 1, // STRNTH: Full strength of building. + 0, // SIGHTRANGE: Range of sighting. + 25, // COST: Cost to purchase. + 98, // SCENARIO: Starting availability scenario. + 0,0, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_BAD| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); +// Wood wall +static BuildingTypeClass const Wood( + STRUCT_WOOD_WALL, + TXT_WOOD_WALL, // NAME: Short name of the structure. + "WOOD", // NAME: Short name of the structure. + XYP_COORD(0,0), // Exit point for produced units. + 99, // Build level. + STRUCTF_NONE, // PREREQ: Buildings that must exist first. + false, // Has ability to detect adjacent cloaked objects? + false, // Animation rate is regulated for constant speed? + false, // Requires a bib dirt patch? + true, // Always use the given name for the building? + true, // Is this a wall type structure? + false, // Is it a factory type building? + false, // Can this building be captured? + false, // Does it catch fire? + false, // Simple (one frame) damage imagery? + false, // Is it invisible to radar? + false, // Can the player select this? + true, // Is this a legal target for attack or move? + true, // Is this an insignificant building? + false, // Is it immune to normal combat damage? + false, // Theater specific graphic image? + false, // Does it have a rotating turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired? + true, // Can it be manufactured by the player? + false, // Does it contain a crew? + true, // Does building care less if placed on concrete? + RTTI_NONE, // The object type produced at this factory. + DIR_N, // Starting idle frame to match construction. + 1, // STRNTH: Full strength of building. + 0, // SIGHTRANGE: Range of sighting. + 25, // COST: Cost to purchase. + 98, // SCENARIO: Starting availability scenario. + 0,0, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_NEUTRAL| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + 0, // CANENTER: Units that can enter building. + 0, // CAPACITY: Spice storage capacity. + 0, // POWER: Power points required. + 0, // DRAIN: Power points required. + BSIZE_11, // SIZE: Building size. + NULL, // Preferred exit cell list. + (short const *)List1, // OCCUPYLIST: List of active foundation squares. + (short const *)NULL // OVERLAPLIST:List of overlap cell offset. +); + + +BuildingTypeClass const * const BuildingTypeClass::Pointers[STRUCT_COUNT] = { + &ClassWeapon, // STRUCT_WEAP + &ClassGTower, // STRUCT_GTOWER + &ClassATower, // STRUCT_ATOWER + &ClassObelisk, // STRUCT_OBLISK + &ClassCommand, // STRUCT_RADAR + &ClassTurret, // STRUCT_TURRET + &ClassConst, // STRUCT_CONST + &ClassRefinery, // STRUCT_REFINERY + &ClassStorage, // STRUCT_STORAGE + &ClassHelipad, // STRUCT_HELIPAD + &ClassSAM, // STRUCT_SAM + &ClassAirStrip, // STRUCT_AIRSTRIP + &ClassPower, // STRUCT_POWER + &ClassAdvancedPower, // STRUCT_POWER + &ClassHospital, // STRUCT_HOSPITAL + &ClassBarracks, // STRUCT_BARRACKS + &ClassTanker, // STRUCT_TANKER + &ClassRepair, // STRUCT_REPAIR + &ClassBioLab, // STRUCT_BIO_LAB + &ClassHand, // STRUCT_HAND + &ClassTemple, // STRUCT_TEMPLE + &ClassEye, // STRUCT_EYE + &ClassMission, // STRUCT_MISSION + + &ClassV01, // STRUCT_V1 + &ClassV02, // STRUCT_V2 + &ClassV03, // STRUCT_V3 + &ClassV04, // STRUCT_V4 + &ClassV05, // STRUCT_V5 + &ClassV06, // STRUCT_V6 + &ClassV07, // STRUCT_V7 + &ClassV08, // STRUCT_V8 + &ClassV09, // STRUCT_V9 + &ClassV10, // STRUCT_V10 + &ClassV11, // STRUCT_V11 + &ClassV12, // STRUCT_V12 + &ClassV13, // STRUCT_V13 + &ClassV14, // STRUCT_V14 + &ClassV15, // STRUCT_V15 + &ClassV16, // STRUCT_V16 + &ClassV17, // STRUCT_V17 + &ClassV18, // STRUCT_V18 + &ClassV19, // STRUCT_PUMP + &ClassV20, // STRUCT_V20 + &ClassV21, // STRUCT_V21 + &ClassV22, // STRUCT_V22 + &ClassV23, // STRUCT_V23 + &ClassV24, // STRUCT_V24 + &ClassV25, // STRUCT_V25 + &ClassV26, // STRUCT_V26 + &ClassV27, // STRUCT_V27 + &ClassV28, // STRUCT_V28 + &ClassV29, // STRUCT_V29 + &ClassV30, // STRUCT_V30 + &ClassV31, // STRUCT_V31 + &ClassV32, // STRUCT_V32 + &ClassV33, // STRUCT_V33 + &ClassV34, // STRUCT_V34 + &ClassV35, // STRUCT_V35 + &ClassV36, // STRUCT_V36 + &ClassV37, // STRUCT_V37 +#ifdef OBSOLETE + &ClassRoad, // STRUCT_ROAD +#endif + &Sandbag, // STRUCT_SANDBAG_WALL + &Cyclone, // STRUCT_CYCLONE_WALL + &Brick, // STRUCT_BRICK_WALL + &Barbwire, // STRUCT_BARBWIRE_WALL + &Wood, // STRUCT_WOOD_WALL +}; + +void const *WarFactoryOverlay; + + +/*********************************************************************************************** + * BuildingTypeClass::BuildingTypeClass -- This is the constructor for the building types. * + * * + * This is the constructor used to create the building types. * + * * + * INPUT: see below... * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1994 JLB : Created. * + *=============================================================================================*/ +BuildingTypeClass::BuildingTypeClass( + StructType type, + int name, + char const *ininame, + COORDINATE exitpoint, + unsigned char level, + long pre, + bool is_scanner, + bool is_regulated, + bool is_bibbed, + bool is_nominal, + bool is_wall, + bool is_factory, + bool is_captureable, + bool is_flammable, + bool is_simpledamage, + bool is_stealthy, + bool is_selectable, + bool is_legal_target, + bool is_insignificant, + bool is_immune, + bool is_theater, + bool is_turret_equipped, + bool is_twoshooter, + bool is_repairable, + bool is_buildable, + bool is_crew, + bool is_sturdy, + RTTIType tobuild, + DirType sframe, + unsigned short strength, + int sightrange, + int cost, + int scenario, + int risk, + int reward, + int ownable, + WeaponType primary, WeaponType secondary, + ArmorType armor, + unsigned long canenter, + unsigned capacity, + int power, + int drain, + BSizeType size, + short const *exitlist, + short const *sizelist, + short const *overlap) : + TechnoTypeClass(name, + ininame, + level, + pre, + false, + is_scanner, + is_nominal, + false, + is_flammable, + false, + is_stealthy, + is_selectable, + is_legal_target, + is_insignificant, + is_immune, + is_theater, + is_twoshooter, + is_turret_equipped, + is_repairable, + is_buildable, + is_crew, + -1, + strength*2, + MPH_IMMOBILE, + sightrange, + cost, + scenario, + risk, + reward, + ownable, + primary, + secondary, + armor) +{ + CanEnter = canenter; + Capacity = capacity; + Drain = drain; + ExitList = exitlist; + ExitPoint = exitpoint; + IsBibbed = is_bibbed; + IsCaptureable = is_captureable; + IsFactory = is_factory; + IsRegulated = is_regulated; + IsSimpleDamage = is_simpledamage; + IsSturdy = is_sturdy; + IsWall = is_wall; + OccupyList = sizelist; + OverlapList = overlap; + Power = power; + Size = size; + StartFace = sframe; + ToBuild = tobuild; + Type = type; + + Anims[BSTATE_CONSTRUCTION].Start = 0; + Anims[BSTATE_CONSTRUCTION].Count = 1; + Anims[BSTATE_CONSTRUCTION].Rate = 0; + + Anims[BSTATE_IDLE].Start = 0; + Anims[BSTATE_IDLE].Count = 1; + Anims[BSTATE_IDLE].Rate = 0; + + Anims[BSTATE_ACTIVE].Start = 0; + Anims[BSTATE_ACTIVE].Count = 1; + Anims[BSTATE_ACTIVE].Rate = 0; + + Anims[BSTATE_AUX1].Start = 0; + Anims[BSTATE_AUX1].Count = 1; + Anims[BSTATE_AUX1].Rate = 0; + + Anims[BSTATE_AUX2].Start = 0; + Anims[BSTATE_AUX2].Count = 1; + Anims[BSTATE_AUX2].Rate = 0; +} + + +/*********************************************************************************************** + * BuildingTypeClass::One_Time -- Performs special one time action for buildings. * + * * + * This routine is used to do the one time action necessary to handle building type class * + * objects. This entails loading of the building shapes and the brain file used by * + * buildings. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine should only be called ONCE. * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + * 06/11/1994 JLB : Updated construction time and frame count logic. * + *=============================================================================================*/ +void BuildingTypeClass::One_Time(void) +{ + static const struct { + StructType Class; // Building class number. + BStateType Stage; // Animation sequence to assign animation range to. + int Start; // Starting frame number. + int Length; // Number of frames (-1 means use all frames). + int Rate; // Rate of animation. + } _anims[] = { + {STRUCT_OBELISK, BSTATE_ACTIVE, 0, 4, OBELISK_ANIMATION_RATE}, + {STRUCT_ADVANCED_POWER, BSTATE_IDLE, 0, 4, 15}, + {STRUCT_AIRSTRIP, BSTATE_IDLE, 0, 16,3}, + {STRUCT_BARRACKS, BSTATE_ACTIVE, 0, 10,3}, + {STRUCT_BARRACKS, BSTATE_IDLE, 0, 10,3}, + {STRUCT_CONST, BSTATE_ACTIVE, 4, 20,3}, + {STRUCT_CONST, BSTATE_IDLE, 0, 4, 3}, + {STRUCT_EYE, BSTATE_IDLE, 0, 16,4}, + {STRUCT_HELIPAD, BSTATE_ACTIVE, 0, 7, 4}, + {STRUCT_HELIPAD, BSTATE_IDLE, 0, 0, 0}, + {STRUCT_HOSPITAL, BSTATE_IDLE, 0, 4, 3}, + {STRUCT_POWER, BSTATE_IDLE, 0, 4, 15}, + {STRUCT_PUMP, BSTATE_IDLE, 0, 14,4}, + {STRUCT_RADAR, BSTATE_IDLE, 0, 16,4}, + {STRUCT_REFINERY, BSTATE_ACTIVE, 12,7, 4}, // Docking phase. + {STRUCT_REFINERY, BSTATE_AUX1, 19,5, 4}, // Siphoning phase. + {STRUCT_REFINERY, BSTATE_AUX2, 24,6, 4}, // Undocking phase. + {STRUCT_REFINERY, BSTATE_IDLE, 0, 6, 4}, // Idle phase. + {STRUCT_REFINERY, BSTATE_FULL, 6, 6, 4}, // Flashing lights + {STRUCT_REPAIR, BSTATE_ACTIVE, 0, 7, 2}, + {STRUCT_REPAIR, BSTATE_IDLE, 0, 1, 0}, + {STRUCT_TEMPLE, BSTATE_IDLE, 0, 1, 0}, + {STRUCT_V20, BSTATE_IDLE, 0, 3, 3}, + {STRUCT_V21, BSTATE_IDLE, 0, 3, 3}, + {STRUCT_V22, BSTATE_IDLE, 0, 3, 3}, + {STRUCT_V23, BSTATE_IDLE, 0, 3, 3}, + {STRUCT_WEAP, BSTATE_ACTIVE, 0, 1, 0}, + {STRUCT_WEAP, BSTATE_IDLE, 0, 1, 0}, + {STRUCT_TEMPLE, BSTATE_ACTIVE, 0, 5, 1}, + }; + + for (StructType sindex = STRUCT_FIRST; sindex < STRUCT_COUNT; sindex++) { + char fullname[_MAX_FNAME+_MAX_EXT]; + char buffer[_MAX_FNAME]; + BuildingTypeClass const & building = As_Reference(sindex); + + /* + ** Fetch the sidebar cameo image for this building. + */ + if (building.IsBuildable) { + if ( Get_Resolution_Factor() ) { + sprintf(buffer, "%sICNH", building.IniName); + } else { + sprintf(buffer, "%sICON", building.IniName); + } + _makepath(fullname, NULL, NULL, buffer, ".SHP"); + ((void const *&)building.CameoData) = MixFileClass::Retrieve(fullname); + } + + /* + ** Fetch the construction animation for this building. + */ + sprintf(buffer, "%sMAKE", building.IniName); + _makepath(fullname, NULL, NULL, buffer, ".SHP"); + void const * dataptr = MixFileClass::Retrieve(fullname); + ((void const *&)building.BuildupData) = dataptr; + if (dataptr) { + int timedelay = 1; + int count = Get_Build_Frame_Count(dataptr); + if (count) { + timedelay = (5 * TICKS_PER_SECOND) / count; + } + building.Init_Anim(BSTATE_CONSTRUCTION, 0, count, timedelay); + } + + /* + ** Fetch the normal game shape for this building. + */ + _makepath(fullname, NULL, NULL, building.IniName, ".SHP"); + ((void const *&)building.ImageData) = MixFileClass::Retrieve(fullname); + } + + // Try to load weap2.shp + char fullname[_MAX_FNAME+_MAX_EXT]; + _makepath(fullname, NULL, NULL, (char const *)"WEAP2",".SHP"); + WarFactoryOverlay = MixFileClass::Retrieve(fullname); + + /* + ** Install all the special animation sequences for the different building types. + */ + for (unsigned index = 0; index < (sizeof(_anims) / sizeof(_anims[0])); index++) { + BuildingTypeClass const *b = &As_Reference(_anims[index].Class); + if (b) { + b->Init_Anim(_anims[index].Stage, _anims[index].Start, _anims[index].Length, _anims[index].Rate); + } + } +} + + + + +/*********************************************************************************************** + * Struct_From_Name -- Find BData structure from its name. * + * * + * This routine will convert an ASCII name for a building class into * + * the actual building class it represents. * + * * + * INPUT: name -- ASCII representation of a building class. * + * * + * OUTPUT: Returns with the actual building class number that the string * + * represents. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + * 05/02/1994 JLB : Converted to member function. * + *=============================================================================================*/ +StructType BuildingTypeClass::From_Name(char const *name) +{ + if (name) { + for (StructType classid = STRUCT_FIRST; classid < STRUCT_COUNT; classid++) { + if (stricmp(As_Reference(classid).IniName, name) == 0) { + return(classid); + } + } + } + return(STRUCT_NONE); +} + + +#ifdef SCENARIO_EDITOR +/*********************************************************************************************** + * BuildingTypeClass::Display -- Renders a generic view of building. * + * * + * This routine is used to display a generic representation of the * + * building. Typical use of this occurs with the scenario editor. * + * * + * INPUT: x,y -- Coordinate to display the building (centered). * + * * + * window -- The window the building should be rendered * + * relative to. * + * * + * house -- The house color to use for the building. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1994 JLB : Created. * + *=============================================================================================*/ +void BuildingTypeClass::Display(int x, int y, WindowNumberType window, HousesType house) const +{ + void const * ptr = Get_Cameo_Data(); + if (!ptr) { + ptr = Get_Image_Data(); + } + CC_Draw_Shape(ptr, 0, x, y, window, SHAPE_FADING|SHAPE_CENTER|SHAPE_WIN_REL, HouseTypeClass::As_Reference(house).RemapTable); +} + + +/*********************************************************************************************** + * BuildingTypeClass::Prep_For_Add -- Prepares scenario editor for adding a * + * * + * This routine is used to prepare the scenario editor for the addition * + * of a building object to the game. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1994 JLB : Created. * + * 06/04/1994 JLB : Uses map editing interface routines. * + *=============================================================================================*/ +void BuildingTypeClass::Prep_For_Add(void) +{ + for (StructType index = STRUCT_FIRST; index < STRUCT_COUNT; index++) { + if (As_Reference(index).Get_Image_Data()) { + Map.Add_To_List(&As_Reference(index)); + } + } +} +#endif + + +/*********************************************************************************************** + * BuildingTypeClass::Create_And_Place -- Creates and places a building object onto the map. * + * * + * This routine is used by the scenario editor to create and place buildings on the map. * + * * + * INPUT: cell -- The cell that the building is to be placed upon. * + * * + * house -- The owner of the building. * + * * + * OUTPUT: bool; Was the building successfully created and placed on the map? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +bool BuildingTypeClass::Create_And_Place(CELL cell, HousesType house) const +{ + BuildingClass * ptr; + + ptr = new BuildingClass(Type, house); + if (ptr) { + return(ptr->Unlimbo(Cell_Coord(cell), DIR_N)); + } + return(false); +} + + +/*********************************************************************************************** + * BuildingTypeClass::Create_One_Of -- Creates a building of this type. * + * * + * This routine will create a building object of this type. The building object is in a * + * limbo state. It is presumed that the building object will be unlimboed at the correct * + * place and time. Typical use is when the building is created in a factory situation * + * and will be placed on the map when construction completes. * + * * + * INPUT: house -- Pointer to the house that is to be the owner of the building. * + * * + * OUTPUT: Returns with a pointer to the building. If the building could not be created * + * then a NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/07/1994 JLB : Created. * + *=============================================================================================*/ +ObjectClass * BuildingTypeClass::Create_One_Of(HouseClass * house) const +{ + HousesType htype = HOUSE_NEUTRAL; + if (house) { + htype = house->Class->House; + } + return(new BuildingClass(Type, htype)); +} + + +/*********************************************************************************************** + * BuildingTypeClass::Init_Anim -- Initialize an animation control for a building. * + * * + * This routine will initialize one animation control element for a * + * specified building. This modifies a "const" class and thus must * + * perform some strategic casting to get away with this. * + * * + * INPUT: state -- The animation state to apply these data values to. * + * * + * start -- Starting frame for the building's animation. * + * * + * count -- The number of frames in this animation. * + * * + * rate -- The countdown timer between animation frames. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/18/1994 JLB : Created. * + *=============================================================================================*/ +void BuildingTypeClass::Init_Anim(BStateType state, int start, int count, int rate) const +{ + ((int &)Anims[state].Start) = start; + ((int &)Anims[state].Count) = count; + ((int &)Anims[state].Rate) = rate; +} + + +/*********************************************************************************************** + * BuildingTypeClass::Legal_Placement -- Determines if building can be legally placed at pos. * + * * + * This routine is used to determine if a building can be legally * + * placed at the specified position. Buildings can only be placed on * + * unoccupied rock terrain. * + * * + * INPUT: pos -- Position that the building would be placed (up-left) * + * * + * OUTPUT: 0=illegal, 1=on concrete, -1..-8=part on concrete. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/15/1991 JLB : Created. * + * 04/26/1992 JLB : Concrete and scenario init adjustment. * + * 05/06/1992 JLB : Good, Bad, and Adjacent checking added. * + * 08/09/1992 JLB : Determines full or partial concrete foundation. * + * 06/07/1994 JLB : Handles concrete special check. * + * 06/21/1994 JLB : Converted to building type class member function. * + *=============================================================================================*/ +int BuildingTypeClass::Legal_Placement(CELL pos) const +{ + short const *offset; // Pointer to cell offset list. + + if (pos == -1) return(false); + +#ifdef NEVER + /* + ** Concrete has special checking performed to determine legal placement. Concrete + ** can legally be placed if there is any cell that would be affected by the concrete + ** placement. Unlike other buildings, only one cell needs to be effective in order + ** to flag legal placement for the entire "structure". + */ + if (Type == STRUCT_CONCRETE_NOD || Type == STRUCT_CONCRETE_GDI) { + offset = Occupy_List(); + while (*offset != REFRESH_EOL) { + if (!Map.Cell_Template(pos + (CELL)*offset++)) { + return(true); + } + } + + /* + ** No squares would be affected by concrete placement so consider legal + ** placement query to be false. + */ + return(false); + } +#endif + + /* + ** Normal buildings must check to see that every foundation square is free of + ** obstacles. If this check passes for all foundation squares, only then does the + ** routine return that it is legal to place. + */ + offset = Occupy_List(true); + while (*offset != REFRESH_EOL) { + CELL cell = pos + *offset++; + if (!Map.In_Radar(cell)) return(false); + if (!Map[cell].Is_Generally_Clear()) { + return(false); + } + } + return(true); +} + + +/*********************************************************************************************** + * BuildingTypeClass::Who_Can_Build_Me -- Determines which factory can create the building. * + * * + * Use this routine to determine which building is available to build the building. If * + * there are no suitable buildings, then NULL is returned. Typical use of this function is * + * to maintain the construction list sidebar. * + * * + * INPUT: intheory -- If true, then it merely checks for a building of the proper ownership * + * when determining if construction is allowed. It doesn't consider the * + * possibility that the construction building is currently busy or not. * + * * + * legal -- Should building prerequisite legality checks be performed as well? * + * For building placements, this is usually false. For sidebar button * + * adding, this is usually true. * + * * + * house -- The owner of the building to be built. Only construction buildings of * + * the same ownership are allowed to build. * + * * + * OUTPUT: Returns with a pointer to the construction object (building) that can build * + * the building type. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/29/1994 JLB : Created. * + * 01/02/1995 JLB : Scans in reverse order so that later buildings are biased. * + *=============================================================================================*/ +BuildingClass * BuildingTypeClass::Who_Can_Build_Me(bool intheory, bool legal, HousesType house) const +{ + for (int index = Buildings.Count()-1; index >= 0; index--) { + BuildingClass * building = Buildings.Ptr(index); + + if (building && !building->IsInLimbo && + building->House->Class->House == house && + building->Class->ToBuild == RTTI_BUILDINGTYPE && + building->Mission != MISSION_DECONSTRUCTION && + ((1L << building->ActLike) & Ownable) && + (!legal || building->House->Can_Build(Type, building->ActLike)) && + (intheory || !building->In_Radio_Contact())) { + return(building); + } + } + return(0); +} + + +/*********************************************************************************************** + * BuildingTypeClass::Init -- Performs theater specific initialization. * + * * + * This routine is used to perform any initialization that is custom per theater. * + * Typically, this is fetching the building shape data for those building types that have * + * theater specific art. * + * * + * INPUT: theater -- The theater to base this initialization on. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/21/1995 JLB : Created. * + *=============================================================================================*/ +void BuildingTypeClass::Init(TheaterType theater) +{ + if (theater != LastTheater){ + char fullname[_MAX_FNAME+_MAX_EXT]; + + for (StructType sindex = STRUCT_FIRST; sindex < STRUCT_COUNT; sindex++) { + BuildingTypeClass const *classptr = &As_Reference(sindex); + + if (classptr->IsTheater) { + _makepath(fullname, NULL, NULL, classptr->IniName, Theaters[theater].Suffix); + ((void const *&)classptr->ImageData) = MixFileClass::Retrieve(fullname); + } + + if ( Get_Resolution_Factor() ) { + char buffer[_MAX_FNAME]; + char fullname[_MAX_FNAME+_MAX_EXT]; + void const * cameo_ptr; + + ((void const *&)classptr->CameoData) = NULL; + + sprintf(buffer, "%.4sICNH", classptr->IniName); + _makepath (fullname, NULL, NULL, buffer, Theaters[theater].Suffix); + cameo_ptr = MixFileClass::Retrieve(fullname); + if (cameo_ptr){ + ((void const *&)classptr->CameoData) = cameo_ptr; + } + } + } + } +} + + +/*********************************************************************************************** + * BuildingTypeClass::Dimensions -- Fetches the pixel dimensions of the building. * + * * + * This routine will fetch the dimensions of the building (in pixels). These dimensions are * + * used to render the selection rectangle and the health bar. * + * * + * INPUT: width -- Reference to the pixel width (to be filled in). * + * * + * height -- Reference to the pixel height (to be filled in). * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +void BuildingTypeClass::Dimensions(int &width, int &height) const +{ + static struct { + int Width; + int Height; + } _dimensions[BSIZE_COUNT] = { + {1,1}, + {2,1}, + {1,2}, + {2,2}, + {2,3}, + {3,2}, + {3,3}, + {4,2}, + {5,5} + }; + + width = _dimensions[Size].Width * ICON_PIXEL_W; + width -= (width/5); + height = _dimensions[Size].Height * ICON_PIXEL_H; + height -= (height/5); +} + + +/*********************************************************************************************** + * BuildingTypeClass::As_Reference -- Fetches reference to the building type specified. * + * * + * This routine will fetch a reference to the BuildingTypeClass as indicated by the * + * building type number specified. * + * * + * INPUT: type -- The building type number to convert into a BuildingTypeClass reference. * + * * + * OUTPUT: Returns with a reference to the building type class as indicated by the * + * parameter. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +BuildingTypeClass const & BuildingTypeClass::As_Reference(StructType type) +{ + return(* Pointers[type]); +} + + +/*********************************************************************************************** + * BuildingTypeClass::Occupy_List -- Fetches the occupy list for the building. * + * * + * Use this routine to fetch the occupy list pointer for the building. The occupy list is * + * used to determine what cells the building occupies and thus precludes other buildings * + * or objects from using. * + * * + * INPUT: placement -- Is this for placement legality checking only? The normal condition * + * is for marking occupation flags. * + * * + * OUTPUT: Returns with a pointer to a cell offset list to be used to determine what cells * + * this building occupies. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +short const * BuildingTypeClass::Occupy_List(bool placement) const +{ + SmudgeType bib = SMUDGE_NONE; + CELL cell=0; + + if ((placement && Bib_And_Offset(bib, cell)) || (Special.IsRoad && (*this == STRUCT_BARRACKS || (placement && *this == STRUCT_REFINERY)))) { + + /* + ** The barracks is always considered to have a bib under it for placement reasons even + ** if the bib logic is turned off. + */ + if (Special.IsRoad && *this == STRUCT_BARRACKS) { + bib = SMUDGE_BIB3; + cell = 0; + } + + /* + ** If bibs are disabled, then always ensure that the refinery bib is marked + ** as occupied. + */ + if (Special.IsRoad && *this == STRUCT_REFINERY) { + bib = SMUDGE_BIB2; + cell = MAP_CELL_W; + } + + SmudgeTypeClass const & smudge = SmudgeTypeClass::As_Reference(bib); + static short _list[25]; + short * dest = &_list[0]; + + /* + ** Copy the bib overlap list into the working buffer. + */ + short const * src = smudge.Occupy_List(); + while (*src != REFRESH_EOL) { + *dest++ = (*src++) + cell; + } + + /* + ** Append the building occupy list to this working buffer. + */ + src = OccupyList; + while (src && *src != REFRESH_EOL) { + *dest++ = *src++; + } + *dest = REFRESH_EOL; + + return(&_list[0]); + } + + if (OccupyList) { + return(OccupyList); + } + + static short const _templap[] = {REFRESH_EOL}; + return(&_templap[0]); +} + + +/*********************************************************************************************** + * BuildingTypeClass::Overlap_List -- Fetches the overlap list for the building. * + * * + * This routine will fetch the overlap list for the building. The overlap list is used * + * to determine what cells the building's graphics cover, but is not considered to occupy * + * for movement purposes. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the cell offset list that is used to determine the * + * cells that this building overlaps. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +short const * BuildingTypeClass::Overlap_List(void) const +{ + if (OverlapList) { + return(OverlapList); + } + + static short const _templap[] = {REFRESH_EOL}; + return(&_templap[0]); +} + + +/*********************************************************************************************** + * BuildingTypeClass::Width -- Determines width of bulding in icons. * + * * + * Use this routine to determine the width of the building type in icons. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the building width in icons. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/23/1995 JLB : Created. * + *=============================================================================================*/ +int BuildingTypeClass::Width(void) const +{ + static int width[BSIZE_COUNT] = { + 1, + 2, + 1, + 2, + 2, + 3, + 3, + 4, + 5 + }; + return(width[Size]); +} + + +/*********************************************************************************************** + * BuildingTypeClass::Height -- Determins the height of the building in icons. * + * * + * Use this routine to find the height of the building in icons. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the building height in icons. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/23/1995 JLB : Created. * + *=============================================================================================*/ +int BuildingTypeClass::Height(void) const +{ + static int height[BSIZE_COUNT] = { + 1, + 1, + 2, + 2, + 3, + 2, + 3, + 2, + 5 + }; + return(height[Size]); +} + + +/*********************************************************************************************** + * BuildingTypeClass::Repair_Cost -- Determines cost per "step" of repair. * + * * + * Use this routine to determine how much it will cost to repair the building one * + * step. A step is defined as the number of hit points returned from the Repair_Step() * + * function. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the credit cost to repair this building one step. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/23/1995 JLB : Created. * + *=============================================================================================*/ +int BuildingTypeClass::Repair_Cost(void) const +{ + int cost = (Raw_Cost()*REPAIR_STEP) / MaxStrength; + cost /= 2; + cost = MAX(cost, 1); + cost = Fixed_To_Cardinal(cost, REPAIR_PERCENT); + return(MAX(cost, 1)); +} + + +/*********************************************************************************************** + * BuildingTypeClass::Repair_Step -- Determines the repair step rate. * + * * + * This routine will determine how many strength points get healed for each "step". The * + * cost to repair one step is determine from the Repair_Cost() function. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of health points repaired for each "step". * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/23/1995 JLB : Created. * + *=============================================================================================*/ +int BuildingTypeClass::Repair_Step(void) const +{ + return(REPAIR_STEP); +} + + +/*********************************************************************************************** + * BuildingTypeClass::Bib_And_Offset -- Determines the bib and appropriate cell offset. * + * * + * This routine is used to determine what (if any) bib should be used for this building * + * and also the cell offset for the upper left corner of the bib smudge type. * + * * + * INPUT: bib -- Reference to the bib that should be used for this building. * + * * + * cell -- The cell offset for the upper left corner of the bib. This offset is * + * relative to the upper left corner of the building. * + * * + * OUTPUT: Is a bib required for this building? If the result is true, then the correct * + * bib and cell offset will be filled in. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1995 JLB : Created. * + *=============================================================================================*/ +bool BuildingTypeClass::Bib_And_Offset(SmudgeType & bib, CELL & cell) const +{ + bib = SMUDGE_NONE; + + if (IsBibbed && !Special.IsRoad) { + switch (Width()) { + case 2: + bib = SMUDGE_BIB3; + break; + + case 3: + bib = SMUDGE_BIB2; + break; + + case 4: + bib = SMUDGE_BIB1; + break; + } + + /* + ** Adjust the bib position for special buildings that have the bib as part + ** of the building art itself. + */ + if (bib != SMUDGE_NONE) { + cell += ((Height()-1)*MAP_CELL_W); + } + } + return(bib != SMUDGE_NONE); +} + + +/*********************************************************************************************** + * BuildingTypeClass::Max_Pips -- Determines the maximum pips to display. * + * * + * Use this routine to determine the maximum number of pips to display on this building * + * when it is rendered. Typically, this is the tiberium capacity divided by 100. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of pips to display on this building when selected. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/29/1995 JLB : Created. * + *=============================================================================================*/ +int BuildingTypeClass::Max_Pips(void) const +{ + return(Bound(Capacity/100, 0, 10)); +} + + +/*********************************************************************************************** + * BuildingTypeClass::Full_Name -- Fetches the full name text number. * + * * + * This routine will fetch the full name of this building (expressed as a text number). * + * If special civilian names are enabled, then the civilian buildings will show their true * + * name rather than "civilian building". * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the full name of this building. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/29/1995 JLB : Created. * + * 07/17/1995 JLB : Village wells will always have their name displayed. * + *=============================================================================================*/ +int BuildingTypeClass::Full_Name(void) const +{ + if (::Scenario == 3 && Type == STRUCT_MISSION) { + return(TXT_PRISON); + } + if (!IsNominal || Special.IsNamed || IsWall || Debug_Map || Type == STRUCT_V23 || Type == STRUCT_V30 || Type == STRUCT_MISSION || Type == STRUCT_BIO_LAB) { + return(TechnoTypeClass::Full_Name()); + } + return(TXT_CIVILIAN_BUILDING); +} + + +int BuildingTypeClass::Raw_Cost(void) const +{ +#ifdef PATCH + /* + ** Forces the turret cost down to original 250 for old + ** version games. + */ + if (IsV107 && Type == STRUCT_TURRET && GameToPlay != GAME_NORMAL) { + return(250); + } +#endif + + int cost = TechnoTypeClass::Raw_Cost(); + + if (Type == STRUCT_HELIPAD) { + cost -= AircraftTypeClass::As_Reference(AIRCRAFT_ORCA).Cost; + } + if (Type == STRUCT_REFINERY) { + cost -= UnitTypeClass::As_Reference(UNIT_HARVESTER).Cost; + } + return(cost); +} + + +int BuildingTypeClass::Cost_Of(void) const +{ + if (Special.IsSeparate && Type == STRUCT_HELIPAD) { + return(Raw_Cost()); + } + +#ifdef PATCH + /* + ** Forces the turret cost down to original 250 for old + ** version games. + */ + if (IsV107 && Type == STRUCT_TURRET && GameToPlay != GAME_NORMAL) { + return(250); + } +#endif + + return(TechnoTypeClass::Cost_Of()); +} diff --git a/BFILE.MAK b/BFILE.MAK new file mode 100644 index 0000000..72f1434 --- /dev/null +++ b/BFILE.MAK @@ -0,0 +1,3406 @@ +# +# Command & Conquer(tm) +# Copyright 2025 Electronic Arts Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# $Header: F:\projects\c&c\vcs\code\bfile.mav 1.14 02 Aug 1995 17:04:30 JOE_BOSTIC $ +#*************************************************************************** +#** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** +#*************************************************************************** +#* * +#* Project Name : Command & Conquer * +#* * +#* File Name : MAKEFILE * +#* * +#* Programmer : Joe L. Bostic * +#* * +#* Start Date : March 25, 1993 * +#* * +#* Last Update : March 25, 1993 [JLB] * +#* * +#*-------------------------------------------------------------------------* + + +# Comment out the following line to disable "include file autodependency". +.AUTODEPEND +#.SWAP + +!include "rules.mak" + +# +# Name of the executable (without extension). Presumes .EXE +# +EXE = conquer + + +########################################################################## + + +# Village of the Unfortunate +# Deja View +SC001 = \ + EXPAND.DAT \ + ..\maps\expand\SCG20EA.INI \ + ..\maps\expand\SCG20EA.BIN \ + ..\maps\expand\SCG21EA.INI \ + ..\maps\expand\SCG21EA.BIN \ + SCM75EA.INI \ + SCM75EA.BIN \ + SCM76EA.INI \ + SCM76EA.BIN \ + +# Desert Shores +# Crete +SC002 = \ + EXPAND.DAT \ + SCM20EA.INI \ + SCM20EA.BIN \ + SCM81EA.INI \ + SCM81EA.BIN \ + +# The Great Race +# A Long Way From Home +SC003 = \ + EXPAND.DAT \ + SCM82EA.INI \ + SCM82EA.BIN \ + SCM90EA.INI \ + SCM90EA.BIN \ + + + +MAPFILES = \ + MLTIPLYR.WSA \ + GREYERTH.WSA \ + AFRICA.WSA \ + E-BWTOCL.WSA \ +# EARTH_A.WSA \ +# S_AFRICA.WSA \ + CLICK_SA.CPS \ + CLICK_A.CPS \ +# BOSNIA.WSA \ + CLICK_E.CPS \ + CLICK_EB.CPS \ + DARK_E.PAL \ +# EARTH_E.WSA \ + EUROPE.WSA \ + S-GDIIN2.WSA \ + SCRSCN1.WSA \ + DARK_B.PAL \ + DARK_SA.PAL \ + SATSEL.PAL \ + SATSEL.CPS \ +# CHOSEGDI.WSA \ +# CHOSENOD.WSA \ + +CACHEMAP = \ + COUNTRYA.SHP \ + COUNTRYE.SHP \ + TIME.SHP \ + HISCORE1.SHP \ + HISCORE2.SHP \ + BAR3RED.SHP \ + BAR3YLW.SHP \ + LOGOS.SHP \ + CREDS.SHP \ + +LOCALFILES = \ + CONQUER.ENG \ + SCOREFNT.FNT \ + GRAD6FNT.FNT \ + 3POINT.FNT \ + 6POINT.FNT \ + 8POINT.FNT \ + 8FAT.FNT \ + FONT12.FNT \ + FONT6.FNT \ + LED.FNT \ + VCR.FNT \ + TEMPERAT.PAL \ + MOUSE.SHP \ + +UPDATECFILES = \ + HPIPS.SHP \ + HPIPS_G.SHP \ + HPIPS_F.SHP \ + HBTN-DN.SHP \ + HBTN-UP.SHP \ + HBTN-DN2.SHP \ + HBTN-UP2.SHP \ + HSIDE1.SHP \ + HSIDE2.SHP \ + HPWRBAR.SHP \ + HREPAIR.SHP \ + HSELL.SHP \ + HMAP.SHP \ + HREPAIRG.SHP \ + HSELLG.SHP \ + HMAPG.SHP \ + HREPAIRF.SHP \ + HSELLF.SHP \ + HMAPF.SHP \ + BTN-STH.SHP \ + BTN-PLH.SHP \ + HRADAR.GDI \ + HRADAR.NOD \ + HRADAR.JP \ + HCLOCK.SHP \ + HPOWER.SHP \ + HSTRIP.SHP \ + HSTRIPDN.SHP \ + HSTRIPUP.SHP \ + HTABS.SHP \ + BTEXTURE.SHP \ + GRAD12FN.FNT \ +# EYEICNH.SHP \ +# BOMBICNH.SHP \ +# ATOMICNH.SHP \ +# IONICNH.SHP \ +# A10ICNH.SHP \ +# AFLDICNH.SHP \ +# APCICNH.SHP \ +# ARCOICNH.SHP \ +# ARTYICNH.SHP \ +# ATWRICNH.SHP \ +# BARBICNH.SHP \ +# BGGYICNH.SHP \ +# BIKEICNH.SHP \ +# BIOICNH.SHP \ +# BOATICNH.SHP \ +# BRIKICNH.SHP \ +# C17ICNH.SHP \ +# CYCLICNH.SHP \ +# E1ICNH.SHP \ +# E2ICNH.SHP \ +# E3ICNH.SHP \ +# E4ICNH.SHP \ +# E5ICNH.SHP \ +# E6ICNH.SHP \ +# FACTICNH.SHP \ +# FIXICNH.SHP \ +# FTNKICNH.SHP \ +# GTWRICNH.SHP \ +# GUNICNH.SHP \ +# HANDICNH.SHP \ +# HARVICNH.SHP \ +# HELIICNH.SHP \ +# HOSPICNH.SHP \ +# HPADICNH.SHP \ +# HQICNH.SHP \ +# HTNKICNH.SHP \ +# JEEPICNH.SHP \ +# LSTICNH.SHP \ +# LTNKICNH.SHP \ +# MCVICNH.SHP \ +# MHQICNH.SHP \ +# MLRSICNH.SHP \ +# MTNKICNH.SHP \ +# NUKEICNH.SHP \ +# NUK2ICNH.SHP \ +# ORCAICNH.SHP \ +# OBLIICNH.SHP \ +# PROCICNH.SHP \ +# PUMPICNH.SHP \ +# PYLEICNH.SHP \ +# RMBOICNH.SHP \ +# ROADICNH.SHP \ +# SAMICNH.SHP \ +# SBAGICNH.SHP \ +# SILOICNH.SHP \ +# STNKICNH.SHP \ +# TMPLICNH.SHP \ +# TRANICNH.SHP \ +# WEAPICNH.SHP \ +# WOODICNH.SHP \ +# MSAMICNH.SHP \ + + +UPDATEFILES = \ +# DESERT.PAL \ +# LASTSCEN.PAL \ + ATTRACT2.CPS \ + LASTSCNG.PAL \ + LASTSCNB.PAL \ + MAP1.PAL \ + MAP_GRY2.PAL \ + MAP_LOC2.PAL \ + MAP_LOC3.PAL \ + MAP_LOCL.PAL \ + MAP_PROG.PAL \ + MULTSCOR.PAL \ + SATSELIN.PAL \ + SCORPAL1.PAL \ + SIDES.PAL \ + SNODPAL1.PAL \ + AIRSTRK.VQP \ + AKIRA.VQP \ + BANNER.VQP \ + BCANYON.VQP \ + BKGROUND.VQP \ + BOMBAWAY.VQP \ + BOMBFLEE.VQP \ + BURDET1.VQP \ + BURDET2.VQP \ + CC2TEASE.VQP \ + OLDCC2T.VQP \ + CCLOGO.VQP \ + CONSYARD.VQP \ + DESFLEES.VQP \ + DESKILL.VQP \ + DESOLAT.VQP \ + DESSWEEP.VQP \ + DINO.VQP \ + FLAG.VQP \ + FLYY.VQP \ + FORESTKL.VQP \ + GAMEOVER.VQP \ + GDI1.VQP \ + GDI10.VQP \ + GDI11.VQP \ + GDI12.VQP \ + GDI13.VQP \ + GDI14.VQP \ + GDI15.VQP \ + GDI2.VQP \ + GDI3.VQP \ + GDI3LOSE.VQP \ + GDI4A.VQP \ + GDI4B.VQP \ + GDI5.VQP \ + GDI6.VQP \ + GDI7.VQP \ + GDI8A.VQP \ + GDI8B.VQP \ + GDI9.VQP \ + GDIEND1.VQP \ + GDIEND2.VQP \ + GDIFINA.VQP \ + GDIFINB.VQP \ + GDILOSE.VQP \ + GENERIC.VQP \ + GUNBOAT.VQP \ + HELLVALY.VQP \ + INSITES.VQP \ + INTRO2.VQP \ + KANEPRE.VQP \ + LANDING.VQP \ + LOGO.VQP \ + NAPALM.VQP \ + NEWLOGO.VQP \ + NITEJUMP.VQP \ + NOD1.VQP \ + NOD10A.VQP \ + NOD10B.VQP \ + NOD11.VQP \ + NOD12.VQP \ + NOD13.VQP \ + NOD1PRE.VQP \ + NOD2.VQP \ + NOD3.VQP \ + NOD4A.VQP \ + NOD4B.VQP \ + NOD5.VQP \ + NOD6.VQP \ + NOD7A.VQP \ + NOD7B.VQP \ + NOD8.VQP \ + NOD9.VQP \ + NODEND1.VQP \ + NODEND2.VQP \ + NODEND3.VQP \ + NODEND4.VQP \ + NODFINAL.VQP \ + NODFLEES.VQP \ + NODLOSE.VQP \ + NODSWEEP.VQP \ + NUKE.VQP \ + OBEL.VQP \ + PARATROP.VQP \ + PINTLE.VQP \ + PLANECRA.VQP \ + PODIUM.VQP \ + REFINT.VQP \ + RETRO.VQP \ + RETROGER.VQP \ + SABOTAGE.VQP \ + SAMDIE.VQP \ + SAMSITE.VQP \ + SEIGE.VQP \ + SETHPRE.VQP \ + SPYCRASH.VQP \ + STEALTH.VQP \ + SUNDIAL.VQP \ + TANKGO.VQP \ + TANKKILL.VQP \ + TBRINFO1.VQP \ + TBRINFO2.VQP \ + TBRINFO3.VQP \ + TIBERFX.VQP \ + TRAILER.VQP \ + TRTKIL_D.VQP \ + TURTKILL.VQP \ + VISOR.VQP \ + WWLOGO.VQP \ + BANR_NOD.VQP \ + BLACKOUT.VQP \ + BODYBAGS.VQP \ + INFERNO.VQP \ + IONTEST.VQP \ + REFINERY.VQP \ + 12GREEN.FNT \ + 12GRNGRD.FNT \ + HTITLE.PCX \ + HEARTH_A.WSA \ + HEARTH_E.WSA \ + HBOSNIA.WSA \ + HSAFRICA.WSA \ + + + + + + + +CONQUERFILES = \ + 120MM.SHP \ + 50CAL.SHP \ + A10.SHP \ +# A10ICON.SHP \ + AFLD.SHP \ +# AFLDICON.SHP \ + AFLDMAKE.SHP \ + APC.SHP \ +# APCICON.SHP \ + ARCO.SHP \ +# ARCOICON.SHP \ + ART-EXP1.SHP \ + ARTY.SHP \ +# ARTYICON.SHP \ + ATOMDOOR.SHP \ + ATOMICDN.SHP \ +# ATOMICON.SHP \ + ATOMICUP.SHP \ + ATOMSFX.SHP \ + ATWR.SHP \ +# ATWRICON.SHP \ + ATWRMAKE.SHP \ + BARB.SHP \ +# BARBICON.SHP \ + BGGY.SHP \ +# BGGYICON.SHP \ + BIKE.SHP \ +# BIKEICON.SHP \ + BIO.SHP \ +# BIOICON.SHP \ + BIOMAKE.SHP \ + BOAT.SHP \ +# BOATICON.SHP \ + BOMB.SHP \ +# BOMBICON.SHP \ + BOMBLET.SHP \ + BRIK.SHP \ +# BRIKICON.SHP \ + BTN-DN.SHP \ + BTN-PL.SHP \ + BTN-ST.SHP \ + BTN-UP.SHP \ + BURN-L.SHP \ + BURN-M.SHP \ + BURN-S.SHP \ + C1.SHP \ + C10.SHP \ + C17.SHP \ +# C17ICON.SHP \ + C2.SHP \ + C3.SHP \ + C4.SHP \ + C5.SHP \ + C6.SHP \ + C7.SHP \ + C8.SHP \ + C9.SHP \ + CHAN.SHP \ + CHEM-E.SHP \ + CHEM-N.SHP \ + CHEM-NE.SHP \ + CHEM-NW.SHP \ + CHEM-S.SHP \ + CHEM-SE.SHP \ + CHEM-SW.SHP \ + CHEM-W.SHP \ + CHEMBALL.SHP \ +# CLOCK.SHP \ + CONC.SHP \ + CYCL.SHP \ +# CYCLICON.SHP \ + DELPHI.SHP \ + DEVIATOR.SHP \ + DOLLAR.SHP \ + DRAGON.SHP \ + E1.SHP \ +# E1ICON.SHP \ + E1ROT.SHP \ + E2.SHP \ +# E2ICON.SHP \ + E2ROT.SHP \ + E3.SHP \ +# E3ICON.SHP \ + E3ROT.SHP \ + E4.SHP \ +# E4ICON.SHP \ + E4ROT.SHP \ + E5.SHP \ +# E5ICON.SHP \ + E6.SHP \ +# E6ICON.SHP \ + EARTH.SHP \ + EMPULSE.SHP \ + EYE.SHP \ +# EYEICON.SHP \ + EYEMAKE.SHP \ + FACT.SHP \ +# FACTICON.SHP \ + FACTMAKE.SHP \ + FBALL1.SHP \ + FIRE1.SHP \ + FIRE2.SHP \ + FIRE3.SHP \ + FIRE4.SHP \ + FIX.SHP \ +# FIXICON.SHP \ + FIXMAKE.SHP \ + FLAGFLY.SHP \ + FLAME-E.SHP \ + FLAME-N.SHP \ + FLAME-NE.SHP \ + FLAME-NW.SHP \ + FLAME-S.SHP \ + FLAME-SE.SHP \ + FLAME-SW.SHP \ + FLAME-W.SHP \ + FLMSPT.SHP \ + FPLS.SHP \ + FRAG1.SHP \ + FRAG3.SHP \ + FTNK.SHP \ +# FTNKICON.SHP \ + GTWR.SHP \ +# GTWRICON.SHP \ + GTWRMAKE.SHP \ + GUN.SHP \ + GUNFIRE.SHP \ +# GUNICON.SHP \ + GUNMAKE.SHP \ + HAND.SHP \ +# HANDICON.SHP \ + HANDMAKE.SHP \ + HARV.SHP \ +# HARVICON.SHP \ + HELI.SHP \ +# HELIICON.SHP \ + HOSP.SHP \ +# HOSPICON.SHP \ + HOSPMAKE.SHP \ + HPAD.SHP \ +# HPADICON.SHP \ + HPADMAKE.SHP \ + HQ.SHP \ +# HQICON.SHP \ + HQMAKE.SHP \ + HTNK.SHP \ +# HTNKICON.SHP \ + INVUN.SHP \ +# IONICON.SHP \ + IONSFX.SHP \ + JEEP.SHP \ +# JEEPICON.SHP \ + LROTOR.SHP \ + LST.SHP \ +# LSTICON.SHP \ + LTNK.SHP \ +# LTNKICON.SHP \ + MCV.SHP \ +# MCVICON.SHP \ + MHQ.SHP \ +# MHQICON.SHP \ + MINE.SHP \ + MINIGUN.SHP \ + MISS.SHP \ + MISSILE.SHP \ + MISSILE2.SHP \ + MLRS.SHP \ +# MLRSICON.SHP \ + MOEBIUS.SHP \ + MOVEFLSH.SHP \ + MSAM.SHP \ +# MSAMICON.SHP \ + MTNK.SHP \ +# MTNKICON.SHP \ + NAPALM1.SHP \ + NAPALM2.SHP \ + NAPALM3.SHP \ + NUK2.SHP \ +# NUK2ICON.SHP \ + NUK2MAKE.SHP \ + NUKE.SHP \ +# NUKEICON.SHP \ + NUKEMAKE.SHP \ + OBLI.SHP \ +# OBLIICON.SHP \ + OBLIMAKE.SHP \ + OPTIONS.SHP \ + ORCA.SHP \ +# ORCAICON.SHP \ + PATRIOT.SHP \ + PIFF.SHP \ + PIFFPIFF.SHP \ +## PIPS.SHP \ + POWER.SHP \ + PROC.SHP \ +# PROCICON.SHP \ + PROCMAKE.SHP \ +# PUMPICON.SHP \ + PUMPMAKE.SHP \ + PYLE.SHP \ +# PYLEICON.SHP \ + PYLEMAKE.SHP \ +## RADAR.GDI \ +## RADAR.JP \ +## RADAR.NOD \ + RAPID.SHP \ + RAPT.SHP \ + RMBO.SHP \ +# RMBOICON.SHP \ + ROAD.SHP \ +# ROADICON.SHP \ + RROTOR.SHP \ + SAM.SHP \ + SAMFIRE.SHP \ +# SAMICON.SHP \ + SAMMAKE.SHP \ + SBAG.SHP \ +# SBAGICON.SHP \ + SCRATE.SHP \ + SELECT.SHP \ + SHADOW.SHP \ + SILO.SHP \ +# SILOICON.SHP \ + SILOMAKE.SHP \ + SMOKEY.SHP \ + SMOKE_M.SHP \ + SMOKLAND.SHP \ + SQUISH.SHP \ + STEALTH2.SHP \ + STEG.SHP \ + STNK.SHP \ +# STNKICON.SHP \ +## STRIP.SHP \ +## STRIPDN.SHP \ +## STRIPUP.SHP \ +## TABS.SHP \ + TMPL.SHP \ +# TMPLICON.SHP \ + TMPLMAKE.SHP \ + TRAN.SHP \ +# TRANICON.SHP \ + TRANS.ICN \ + TREX.SHP \ + TRIC.SHP \ + V19.SHP \ + VEH-HIT1.SHP \ + VEH-HIT2.SHP \ + VEH-HIT3.SHP \ + VICE.SHP \ + WAKE.SHP \ + WCRATE.SHP \ + WEAP.SHP \ + WEAP2.SHP \ +# WEAPICON.SHP \ + WEAPMAKE.SHP \ + WOOD.SHP \ +# WOODICON.SHP \ + +# MESSAGE.ENG \ +# FAME.CPS \ +# BTN-CL.SHP \ +# BTN-MINS.SHP \ +# BTN-OP.SHP \ +# BTN-PLUS.SHP \ + +GDIMAPFILES = \ + SCB01EA.BIN \ + SCB01EA.INI \ + SCG01EA.BIN \ + SCG01EA.INI \ + SCG02EA.BIN \ + SCG02EA.INI \ + SCG03EA.BIN \ + SCG03EA.INI \ + SCG04EA.BIN \ + SCG04EA.INI \ + SCG04WA.BIN \ + SCG04WA.INI \ + SCG04WB.BIN \ + SCG04WB.INI \ + SCG05EA.BIN \ + SCG05EA.INI \ + SCG05EB.BIN \ + SCG05EB.INI \ + SCG05WA.BIN \ + SCG05WA.INI \ + SCG05WB.BIN \ + SCG05WB.INI \ + SCG06EA.BIN \ + SCG06EA.INI \ + SCG07EA.BIN \ + SCG07EA.INI \ + SCG08EA.BIN \ + SCG08EA.INI \ + SCG08EB.BIN \ + SCG08EB.INI \ + SCG09EA.BIN \ + SCG09EA.INI \ + SCG10EA.BIN \ + SCG10EA.INI \ + SCG10EB.BIN \ + SCG10EB.INI \ + SCG11EA.BIN \ + SCG11EA.INI \ + SCG12EA.BIN \ + SCG12EA.INI \ + SCG12EB.BIN \ + SCG12EB.INI \ + SCG13EA.BIN \ + SCG13EA.INI \ + SCG13EB.BIN \ + SCG13EB.INI \ + SCG14EA.BIN \ + SCG14EA.INI \ + SCG15EA.BIN \ + SCG15EA.INI \ + SCG15EB.BIN \ + SCG15EB.INI \ + SCG15EC.BIN \ + SCG15EC.INI \ + SCG60EA.BIN \ + SCG60EA.INI \ + SCG61EA.BIN \ + SCG61EA.INI \ + SCG62EA.BIN \ + SCG62EA.INI \ + SCB60EA.BIN \ + SCB60EA.INI \ + SCB61EA.BIN \ + SCB61EA.INI \ + +# SCG03EB.BIN \ +# SCG03EB.INI \ +# SCG03EL.BIN \ +# SCG03EL.INI \ + +NODMAPFILES = \ + SCG01EA.BIN \ + SCG01EA.INI \ + SCB01EA.BIN \ + SCB01EA.INI \ + SCB02EA.BIN \ + SCB02EA.INI \ + SCB02EB.BIN \ + SCB02EB.INI \ + SCB03EA.BIN \ + SCB03EA.INI \ + SCB03EB.BIN \ + SCB03EB.INI \ + SCB04EA.BIN \ + SCB04EA.INI \ + SCB04EB.BIN \ + SCB04EB.INI \ + SCB05EA.BIN \ + SCB05EA.INI \ + SCB06EA.BIN \ + SCB06EA.INI \ + SCB06EB.BIN \ + SCB06EB.INI \ + SCB06EC.BIN \ + SCB06EC.INI \ + SCB07EA.BIN \ + SCB07EA.INI \ + SCB07EB.BIN \ + SCB07EB.INI \ + SCB07EC.BIN \ + SCB07EC.INI \ + SCB08EA.BIN \ + SCB08EA.INI \ + SCB08EB.BIN \ + SCB08EB.INI \ + SCB09EA.BIN \ + SCB09EA.INI \ + SCB10EA.BIN \ + SCB10EA.INI \ + SCB10EB.BIN \ + SCB10EB.INI \ + SCB11EA.BIN \ + SCB11EA.INI \ + SCB11EB.BIN \ + SCB11EB.INI \ + SCB12EA.BIN \ + SCB12EA.INI \ + SCB13EA.BIN \ + SCB13EA.INI \ + SCB13EB.BIN \ + SCB13EB.INI \ + SCB13EC.BIN \ + SCB13EC.INI \ + SCG60EA.BIN \ + SCG60EA.INI \ + SCG61EA.BIN \ + SCG61EA.INI \ + SCG62EA.BIN \ + SCG62EA.INI \ + SCB60EA.BIN \ + SCB60EA.INI \ + SCB61EA.BIN \ + SCB61EA.INI \ + +NETMAPFILES = \ + SCJ01EA.BIN \ + SCJ01EA.INI \ + SCJ02EA.BIN \ + SCJ02EA.INI \ + SCJ03EA.BIN \ + SCJ03EA.INI \ + SCJ04EA.BIN \ + SCJ04EA.INI \ + SCJ05EA.BIN \ + SCJ05EA.INI \ + SCM01EA.BIN \ + SCM01EA.INI \ + SCM02EA.BIN \ + SCM02EA.INI \ + SCM03EA.BIN \ + SCM03EA.INI \ + SCM04EA.BIN \ + SCM04EA.INI \ + SCM05EA.BIN \ + SCM05EA.INI \ + SCM06EA.BIN \ + SCM06EA.INI \ + SCM07EA.BIN \ + SCM07EA.INI \ + SCM08EA.BIN \ + SCM08EA.INI \ + SCM09EA.BIN \ + SCM09EA.INI \ + SCM70EA.BIN \ + SCM70EA.INI \ + SCM71EA.BIN \ + SCM71EA.INI \ + SCM72EA.BIN \ + SCM72EA.INI \ + SCM73EA.BIN \ + SCM73EA.INI \ + SCM74EA.BIN \ + SCM74EA.INI \ + SCM77EA.BIN \ + SCM77EA.INI \ + SCM96EA.BIN \ + SCM96EA.INI \ + + + +JAPANFILES = \ + SCB01EA.CPS \ + SCB01EB.CPS \ + SCB02EA.CPS \ + SCB02EB.CPS \ + SCB03EA.CPS \ + SCB03EB.CPS \ + SCB04EA.CPS \ + SCB04EB.CPS \ + SCB05EA.CPS \ + SCB06EA.CPS \ + SCB06EB.CPS \ + SCB06EC.CPS \ + SCB07EA.CPS \ + SCB07EB.CPS \ + SCB07EC.CPS \ + SCB08EA.CPS \ + SCB08EB.CPS \ + SCB09EA.CPS \ + SCB09EB.CPS \ + SCB10EA.CPS \ + SCB10EB.CPS \ + SCB11EA.CPS \ + SCB11EB.CPS \ + SCB12EA.CPS \ + SCB12EB.CPS \ + SCB13EA.CPS \ + SCB13EB.CPS \ + SCB13EC.CPS \ + SCG01EA.CPS \ + SCG02EA.CPS \ + SCG03EA.CPS \ + SCG03EB.CPS \ + SCG04EA.CPS \ + SCG04WA.CPS \ + SCG04WB.CPS \ + SCG05EA.CPS \ + SCG05EB.CPS \ + SCG05WA.CPS \ + SCG05WB.CPS \ + SCG06EA.CPS \ + SCG07EA.CPS \ + SCG08EA.CPS \ + SCG08EB.CPS \ + SCG09EA.CPS \ + SCG10EA.CPS \ + SCG10EB.CPS \ + SCG11EA.CPS \ + SCG12EA.CPS \ + SCG12EB.CPS \ + SCG13EA.CPS \ + SCG13EB.CPS \ + SCG14EA.CPS \ + SCG15EA.CPS \ + SCG15EB.CPS \ + SCG15EC.CPS \ + + + + + +# Files for hard drive to allow quick access, but never cached. +GENERALFILES = \ + MISSION.INI \ + TITLE.CPS \ + +TEMPERATEFILES = \ + SPLIT2.TEM \ + SPLIT3.TEM \ + BIB1.TEM \ + BIB2.TEM \ + BIB3.TEM \ + B1.TEM \ + B2.TEM \ + B3.TEM \ + BRIDGE1.TEM \ + BRIDGE1D.TEM \ + BRIDGE2.TEM \ + BRIDGE2D.TEM \ + CLEAR1.TEM \ + CR1.TEM \ + CR2.TEM \ + CR3.TEM \ + CR4.TEM \ + CR5.TEM \ + CR6.TEM \ + D01.TEM \ + D02.TEM \ + D03.TEM \ + D04.TEM \ + D05.TEM \ + D06.TEM \ + D07.TEM \ + D08.TEM \ + D09.TEM \ + D10.TEM \ + D11.TEM \ + D12.TEM \ + D13.TEM \ + D14.TEM \ + D15.TEM \ + D16.TEM \ + D17.TEM \ + D18.TEM \ + D19.TEM \ + D20.TEM \ + D21.TEM \ + D22.TEM \ + D23.TEM \ + D24.TEM \ + D25.TEM \ + D26.TEM \ + D27.TEM \ + D28.TEM \ + D29.TEM \ + D30.TEM \ + D31.TEM \ + D32.TEM \ + D33.TEM \ + D34.TEM \ + D35.TEM \ + D36.TEM \ + D37.TEM \ + D38.TEM \ + D39.TEM \ + D40.TEM \ + D41.TEM \ + D42.TEM \ + D43.TEM \ + FALLS1.TEM \ + FALLS2.TEM \ + FORD1.TEM \ + FORD2.TEM \ + P01.TEM \ + P02.TEM \ + P03.TEM \ + P04.TEM \ + P07.TEM \ + P08.TEM \ + P13.TEM \ + P14.TEM \ + RV01.TEM \ + RV02.TEM \ + RV03.TEM \ + RV04.TEM \ + RV05.TEM \ + RV06.TEM \ + RV07.TEM \ + RV08.TEM \ + RV09.TEM \ + RV10.TEM \ + RV11.TEM \ + RV12.TEM \ + RV13.TEM \ + S01.TEM \ + S02.TEM \ + S03.TEM \ + S04.TEM \ + S05.TEM \ + S06.TEM \ + S07.TEM \ + S08.TEM \ + S09.TEM \ + S10.TEM \ + S11.TEM \ + S12.TEM \ + S13.TEM \ + S14.TEM \ + S15.TEM \ + S16.TEM \ + S17.TEM \ + S18.TEM \ + S19.TEM \ + S20.TEM \ + S21.TEM \ + S22.TEM \ + S23.TEM \ + S24.TEM \ + S25.TEM \ + S26.TEM \ + S27.TEM \ + S28.TEM \ + S29.TEM \ + S30.TEM \ + S31.TEM \ + S32.TEM \ + S33.TEM \ + S34.TEM \ + S35.TEM \ + S36.TEM \ + S37.TEM \ + S38.TEM \ + SC1.TEM \ + SC2.TEM \ + SC3.TEM \ + SC4.TEM \ + SC5.TEM \ + SC6.TEM \ + SH1.TEM \ + SH10.TEM \ + SH11.TEM \ + SH12.TEM \ + SH13.TEM \ + SH14.TEM \ + SH15.TEM \ + SH16.TEM \ + SH17.TEM \ + SH18.TEM \ + SH32.TEM \ + SH33.TEM \ + SH34.TEM \ + SH35.TEM \ + SH2.TEM \ + SH3.TEM \ + SH4.TEM \ + SH5.TEM \ + SH6.TEM \ + SH7.TEM \ + SH8.TEM \ + SH9.TEM \ + SR1.TEM \ + SR2.TEM \ + T01.TEM \ + T02.TEM \ + T03.TEM \ + T05.TEM \ + T06.TEM \ + T07.TEM \ + T08.TEM \ + T10.TEM \ + T11.TEM \ + T12.TEM \ + T13.TEM \ + T14.TEM \ + T15.TEM \ + T16.TEM \ + T17.TEM \ + TC01.TEM \ + TC02.TEM \ + TC03.TEM \ + TC04.TEM \ + TC05.TEM \ + TCLOCK.MRF \ + TWHITE.MRF \ + TEMPERAT.PAL \ + TGREEN.MRF \ + TI1.TEM \ + TI10.TEM \ + TI11.TEM \ + TI12.TEM \ + TI2.TEM \ + TI3.TEM \ + TI4.TEM \ + TI5.TEM \ + TI6.TEM \ + TI7.TEM \ + TI8.TEM \ + TI9.TEM \ + TLIGHT.MRF \ + TMOUSE.MRF \ + TRED.MRF \ + TSHADE.MRF \ + TSHADOW.MRF \ + TTRANS.MRF \ + TUNITS.MRF \ + TYELLOW.MRF \ + V01.TEM \ + V02.TEM \ + V03.TEM \ + V04.TEM \ + V05.TEM \ + V06.TEM \ + V07.TEM \ + V08.TEM \ + V09.TEM \ + V10.TEM \ + V11.TEM \ + V12.TEM \ + V13.TEM \ + V14.TEM \ + V15.TEM \ + V16.TEM \ + V17.TEM \ + V18.TEM \ + W1.TEM \ + W2.TEM \ + + +TEMPERATEICONFILES = \ + A10ICNH.TEM \ + AFLDICNH.TEM \ + APCICNH.TEM \ + ARTYICNH.TEM \ + ATWRICNH.TEM \ + BARBICNH.TEM \ + BGGYICNH.TEM \ + BIKEICNH.TEM \ + BOATICNH.TEM \ + BRIKICNH.TEM \ + E1ICNH.TEM \ + E2ICNH.TEM \ + E3ICNH.TEM \ + E4ICNH.TEM \ + E5ICNH.TEM \ + E6ICNH.TEM \ + EYEICNH.TEM \ + FIXICNH.TEM \ + GTWRICNH.TEM \ + GUNICNH.TEM \ + HANDICNH.TEM \ + HARVICNH.TEM \ + HELIICNH.TEM \ + HPADICNH.TEM \ + HQICNH.TEM \ + HTNKICNH.TEM \ + JEEPICNH.TEM \ + LSTICNH.TEM \ + LTNKICNH.TEM \ + MCVICNH.TEM \ + MSAMICNH.TEM \ + MTNKICNH.TEM \ + NUK2ICNH.TEM \ + NUKEICNH.TEM \ + OBLIICNH.TEM \ + ORCAICNH.TEM \ + PROCICNH.TEM \ + PYLEICNH.TEM \ + SAMICNH.TEM \ + SBAGICNH.TEM \ + SILOICNH.TEM \ + STNKICNH.TEM \ + TMPLICNH.TEM \ + TRANICNH.TEM \ + WEAPICNH.TEM \ + CYCLICNH.TEM \ + MLRSICNH.TEM \ + FTNKICNH.TEM \ + IONICNH.TEM \ + ATOMICNH.TEM \ + BOMBICNH.TEM \ + RMBOICNH.TEM \ + WOODICNH.TEM \ + + + +WINTERFILES = \ + SPLIT2.WIN \ + SPLIT3.WIN \ + BIB1.WIN \ + BIB2.WIN \ + BIB3.WIN \ + B1.WIN \ + B2.WIN \ + B3.WIN \ + BRIDGE1.WIN \ + BRIDGE1D.WIN \ + BRIDGE2.WIN \ + BRIDGE2D.WIN \ + RV01.WIN \ + RV02.WIN \ + RV03.WIN \ + RV04.WIN \ + RV05.WIN \ + RV06.WIN \ + RV07.WIN \ + RV08.WIN \ + RV09.WIN \ + RV10.WIN \ + RV11.WIN \ + RV12.WIN \ + RV13.WIN \ + CLEAR1.WIN \ + CR1.WIN \ + CR2.WIN \ + CR3.WIN \ + CR4.WIN \ + CR5.WIN \ + CR6.WIN \ + D01.WIN \ + D02.WIN \ + D03.WIN \ + D04.WIN \ + D05.WIN \ + D06.WIN \ + D07.WIN \ + D08.WIN \ + D09.WIN \ + D10.WIN \ + D11.WIN \ + D12.WIN \ + D13.WIN \ + D14.WIN \ + D15.WIN \ + D16.WIN \ + D17.WIN \ + D18.WIN \ + D19.WIN \ + D20.WIN \ + D21.WIN \ + D22.WIN \ + D23.WIN \ + D24.WIN \ + D25.WIN \ + D26.WIN \ + D27.WIN \ + D28.WIN \ + D29.WIN \ + D30.WIN \ + D31.WIN \ + D32.WIN \ + D33.WIN \ + D34.WIN \ + D35.WIN \ + D36.WIN \ + D37.WIN \ + D38.WIN \ + D39.WIN \ + D40.WIN \ + D41.WIN \ + D42.WIN \ + D43.WIN \ + FALLS1.WIN \ + FALLS2.WIN \ + FORD1.WIN \ + FORD2.WIN \ + P07.WIN \ + P08.WIN \ + P13.WIN \ + P14.WIN \ + P15.WIN \ + P16.WIN \ + P17.WIN \ + P18.WIN \ + P19.WIN \ + P20.WIN \ + S01.WIN \ + S02.WIN \ + S03.WIN \ + S04.WIN \ + S05.WIN \ + S06.WIN \ + S07.WIN \ + S08.WIN \ + S09.WIN \ + S10.WIN \ + S11.WIN \ + S12.WIN \ + S13.WIN \ + S14.WIN \ + S15.WIN \ + S16.WIN \ + S17.WIN \ + S18.WIN \ + S19.WIN \ + S20.WIN \ + S21.WIN \ + S22.WIN \ + S23.WIN \ + S24.WIN \ + S25.WIN \ + S26.WIN \ + S27.WIN \ + S28.WIN \ + S29.WIN \ + S30.WIN \ + S31.WIN \ + S32.WIN \ + S33.WIN \ + S34.WIN \ + S35.WIN \ + S36.WIN \ + S37.WIN \ + S38.WIN \ + SC1.WIN \ + SC2.WIN \ + SC3.WIN \ + SC4.WIN \ + SC5.WIN \ + SC6.WIN \ + SH1.WIN \ + SH10.WIN \ + SH11.WIN \ + SH12.WIN \ + SH13.WIN \ + SH14.WIN \ + SH15.WIN \ + SH16.WIN \ + SH17.WIN \ + SH18.WIN \ + SH32.WIN \ + SH33.WIN \ + SH34.WIN \ + SH35.WIN \ + SH2.WIN \ + SH3.WIN \ + SH4.WIN \ + SH5.WIN \ + SH6.WIN \ + SH7.WIN \ + SH8.WIN \ + SH9.WIN \ + SR1.WIN \ + SR1.WIN \ + SR2.WIN \ + SR2.WIN \ + T01.WIN \ + T02.WIN \ + T03.WIN \ + T05.WIN \ + T06.WIN \ + T07.WIN \ + T08.WIN \ + T10.WIN \ + T11.WIN \ + T12.WIN \ + T13.WIN \ + T14.WIN \ + T15.WIN \ + T16.WIN \ + T17.WIN \ + TC01.WIN \ + TC02.WIN \ + TC03.WIN \ + TC04.WIN \ + TC05.WIN \ + TI1.WIN \ + TI10.WIN \ + TI11.WIN \ + TI12.WIN \ + TI2.WIN \ + TI3.WIN \ + TI4.WIN \ + TI5.WIN \ + TI6.WIN \ + TI7.WIN \ + TI8.WIN \ + TI9.WIN \ + W1.WIN \ + W2.WIN \ + WWHITE.MRF \ + WCLOCK.MRF \ + WGREEN.MRF \ + WINTER.PAL \ + WLIGHT.MRF \ + WMOUSE.MRF \ + WRED.MRF \ + WSHADE.MRF \ + WSHADOW.MRF \ + WTRANS.MRF \ + WUNITS.MRF \ + WYELLOW.MRF \ + V01.WIN \ + V02.WIN \ + V03.WIN \ + V04.WIN \ + V05.WIN \ + V06.WIN \ + V07.WIN \ + V08.WIN \ + V09.WIN \ + V10.WIN \ + V11.WIN \ + V12.WIN \ + V13.WIN \ + V14.WIN \ + V15.WIN \ + V16.WIN \ + V17.WIN \ + V18.WIN \ + + +WINTERICONFILES = \ + A10ICNH.WIN \ + AFLDICNH.WIN \ + APCICNH.WIN \ + ARTYICNH.WIN \ + ATWRICNH.WIN \ + BARBICNH.WIN \ + BGGYICNH.WIN \ + BIKEICNH.WIN \ + BOATICNH.WIN \ + BRIKICNH.WIN \ + E1ICNH.WIN \ + E2ICNH.WIN \ + E3ICNH.WIN \ + E4ICNH.WIN \ + E5ICNH.WIN \ + E6ICNH.WIN \ + EYEICNH.WIN \ + FIXICNH.WIN \ + GTWRICNH.WIN \ + GUNICNH.WIN \ + HANDICNH.WIN \ + HARVICNH.WIN \ + HELIICNH.WIN \ + HPADICNH.WIN \ + HQICNH.WIN \ + HTNKICNH.WIN \ + JEEPICNH.WIN \ + LSTICNH.WIN \ + LTNKICNH.WIN \ + MCVICNH.WIN \ + MSAMICNH.WIN \ + MTNKICNH.WIN \ + NUK2ICNH.WIN \ + NUKEICNH.WIN \ + OBLIICNH.WIN \ + ORCAICNH.WIN \ + PROCICNH.WIN \ + PYLEICNH.WIN \ + SAMICNH.WIN \ + SBAGICNH.WIN \ + SILOICNH.WIN \ + STNKICNH.WIN \ + TMPLICNH.WIN \ + TRANICNH.WIN \ + WEAPICNH.WIN \ + CYCLICNH.WIN \ + MLRSICNH.WIN \ + FTNKICNH.WIN \ + IONICNH.WIN \ + ATOMICNH.WIN \ + BOMBICNH.WIN \ + RMBOICNH.WIN \ + WOODICNH.WIN \ + + +# BB1.WIN \ +# BB2.WIN \ +# BB3.WIN \ + + +DESERTFILES = \ + SPLIT3.DES \ + BIB1.DES \ + BIB2.DES \ + BIB3.DES \ + DESERT.PAL \ + B1.DES \ + B2.DES \ + B4.DES \ + B5.DES \ + B6.DES \ + BR1.DES \ + BR10.DES \ + BR2.DES \ + BR3.DES \ + BR4.DES \ + BR5.DES \ + BR6.DES \ + BR7.DES \ + BR8.DES \ + BR9.DES \ + BRIDGE3.DES \ + BRIDGE3D.DES \ + BRIDGE4.DES \ + BRIDGE4D.DES \ + CLEAR1.DES \ + CR1.DES \ + CR2.DES \ + CR3.DES \ + CR4.DES \ + CR5.DES \ + CR6.DES \ + D01.DES \ + D02.DES \ + D03.DES \ + D04.DES \ + D05.DES \ + D06.DES \ + D07.DES \ + D08.DES \ + D09.DES \ + D10.DES \ + D11.DES \ + D12.DES \ + D13.DES \ + D14.DES \ + D15.DES \ + D16.DES \ + D17.DES \ + D18.DES \ + D19.DES \ + D20.DES \ + D21.DES \ + D22.DES \ + D23.DES \ + D24.DES \ + D25.DES \ + D26.DES \ + D27.DES \ + D28.DES \ + D29.DES \ + D30.DES \ + D31.DES \ + D32.DES \ + D33.DES \ + D34.DES \ + D35.DES \ + D36.DES \ + D37.DES \ + D38.DES \ + D39.DES \ + D40.DES \ + D41.DES \ + D42.DES \ + D43.DES \ + DWHITE.MRF \ + DCLOCK.MRF \ + DGREEN.MRF \ + DLIGHT.MRF \ + DMOUSE.MRF \ + DRED.MRF \ + DSHADE.MRF \ + DSHADOW.MRF \ + DTRANS.MRF \ + DUNITS.MRF \ + DYELLOW.MRF \ + FALLS1.DES \ + FALLS2.DES \ + FORD1.DES \ + FORD2.DES \ + P01.DES \ + P02.DES \ + P03.DES \ + P04.DES \ + P05.DES \ + P06.DES \ + P07.DES \ + ROCK1.DES \ + ROCK2.DES \ + ROCK3.DES \ + ROCK4.DES \ + ROCK5.DES \ + ROCK6.DES \ + ROCK7.DES \ + RV14.DES \ + RV15.DES \ + RV16.DES \ + RV17.DES \ + RV18.DES \ + RV19.DES \ + RV20.DES \ + RV21.DES \ + RV22.DES \ + RV23.DES \ + RV24.DES \ + RV25.DES \ + S01.DES \ + S02.DES \ + S03.DES \ + S04.DES \ + S05.DES \ + S06.DES \ + S07.DES \ + S08.DES \ + S09.DES \ + S10.DES \ + S11.DES \ + S12.DES \ + S13.DES \ + S14.DES \ + S15.DES \ + S16.DES \ + S17.DES \ + S18.DES \ + S19.DES \ + S20.DES \ + S21.DES \ + S22.DES \ + S23.DES \ + S24.DES \ + S25.DES \ + S26.DES \ + S27.DES \ + S28.DES \ + S29.DES \ + S30.DES \ + S31.DES \ + S32.DES \ + S33.DES \ + S34.DES \ + S35.DES \ + S36.DES \ + S37.DES \ + S38.DES \ + SC1.DES \ + SC2.DES \ + SC3.DES \ + SC4.DES \ + SC5.DES \ + SC6.DES \ + SH17.DES \ + SH18.DES \ + SH19.DES \ + SH20.DES \ + SH21.DES \ + SH22.DES \ + SH23.DES \ + SH24.DES \ + SH25.DES \ + SH26.DES \ + SH27.DES \ + SH28.DES \ + SH29.DES \ + SH30.DES \ + SH31.DES \ + SH36.DES \ + SH37.DES \ + SH38.DES \ + SH39.DES \ + SH40.DES \ + SH41.DES \ + SH42.DES \ + SH43.DES \ + SH44.DES \ + SH45.DES \ + SH46.DES \ + SH47.DES \ + SH48.DES \ + SH49.DES \ + SH50.DES \ + SH51.DES \ + SH52.DES \ + SH53.DES \ + SH54.DES \ + SH55.DES \ + SH56.DES \ + SH57.DES \ + SH58.DES \ + SH59.DES \ + SH60.DES \ + SH61.DES \ + SH62.DES \ + SH63.DES \ + T04.DES \ + T08.DES \ + T09.DES \ + T18.DES \ + TI1.DES \ + TI10.DES \ + TI11.DES \ + TI12.DES \ + TI2.DES \ + TI3.DES \ + TI4.DES \ + TI5.DES \ + TI6.DES \ + TI7.DES \ + TI8.DES \ + TI9.DES \ + W1.DES \ + V20.DES \ + V21.DES \ + V22.DES \ + V23.DES \ + V24.DES \ + V25.DES \ + V26.DES \ + V27.DES \ + V28.DES \ + V29.DES \ + V30.DES \ + V31.DES \ + V32.DES \ + V33.DES \ + V34.DES \ + V35.DES \ + V36.DES \ + V37.DES \ + + +DESERTICONFILES = \ + A10ICNH.DES \ + AFLDICNH.DES \ + APCICNH.DES \ + ARTYICNH.DES \ + ATWRICNH.DES \ + BARBICNH.DES \ + BGGYICNH.DES \ + BIKEICNH.DES \ + BOATICNH.DES \ + BRIKICNH.DES \ + E1ICNH.DES \ + E2ICNH.DES \ + E3ICNH.DES \ + E4ICNH.DES \ + E5ICNH.DES \ + E6ICNH.DES \ + EYEICNH.DES \ + FIXICNH.DES \ + GTWRICNH.DES \ + GUNICNH.DES \ + HANDICNH.DES \ + HARVICNH.DES \ + HELIICNH.DES \ + HPADICNH.DES \ + HQICNH.DES \ + HTNKICNH.DES \ + JEEPICNH.DES \ + LSTICNH.DES \ + LTNKICNH.DES \ + MCVICNH.DES \ + MSAMICNH.DES \ + MTNKICNH.DES \ + NUK2ICNH.DES \ + NUKEICNH.DES \ + OBLIICNH.DES \ + ORCAICNH.DES \ + PROCICNH.DES \ + PYLEICNH.DES \ + SAMICNH.DES \ + SBAGICNH.DES \ + SILOICNH.DES \ + STNKICNH.DES \ + TMPLICNH.DES \ + TRANICNH.DES \ + WEAPICNH.DES \ + CYCLICNH.DES \ + MLRSICNH.DES \ + FTNKICNH.DES \ + IONICNH.DES \ + ATOMICNH.DES \ + BOMBICNH.DES \ + RMBOICNH.DES \ + WOODICNH.DES \ + + + + + +# Sound effects (Juvenile or Adult) +SFX = \ + CRATE4.AUD \ + HELIDOWN.AUD \ + HELIUP1.AUD \ + BAZOOK1.AUD \ + BLEEP2.AUD \ + BOMB1.AUD \ + BUTTON.AUD \ + COMCNTR1.AUD \ + CONSTRU2.AUD \ + CRUMBLE.AUD \ + FLAMER2.AUD \ + GUN18.AUD \ + GUN19.AUD \ + GUN20.AUD \ + GUN5.AUD \ + GUN8.AUD \ + GUNCLIP1.AUD \ + HVYDOOR1.AUD \ + HVYGUN10.AUD \ + ION1.AUD \ + MGUN11.AUD \ + MGUN2.AUD \ + NUKEMISL.AUD \ + NUKEXPLO.AUD \ + OBELRAY1.AUD \ + POWRDN1.AUD \ + RAMGUN2.AUD \ + ROCKET1.AUD \ + ROCKET2.AUD \ + SAMMOTR2.AUD \ + SCOLD2.AUD \ + SIDBAR1C.AUD \ + SIDBAR2C.AUD \ + SQUISH2.AUD \ + TNKFIRE2.AUD \ + TNKFIRE3.AUD \ + TNKFIRE4.AUD \ + TNKFIRE6.AUD \ + TONE15.AUD \ + TONE16.AUD \ + TONE2.AUD \ + TONE5.AUD \ + TOSS1.AUD \ + TRANS1.AUD \ + TREEBRN1.AUD \ + TURRFIR5.AUD \ + XPLOBIG4.AUD \ + XPLOBIG6.AUD \ + XPLOBIG7.AUD \ + XPLODE.AUD \ + XPLOS.AUD \ + XPLOSML2.AUD \ + +# Generic wave files (never changes). +WAVFILES = \ + GUYOKAY1.AUD \ + GUYYEAH1.AUD \ + GIRLOKAY.AUD \ + GIRLYEAH.AUD \ + OBELPOWR.AUD \ + YELL1.AUD \ + NUYELL1.AUD \ + NUYELL3.AUD \ + NUYELL4.AUD \ + NUYELL5.AUD \ + NUYELL6.AUD \ + NUYELL7.AUD \ + NUYELL10.AUD \ + NUYELL11.AUD \ + NUYELL12.AUD \ + NEWTARG1.AUD \ + APPEAR1.AUD \ + BEEPY2.AUD \ + BEEPY3.AUD \ + BEEPY6.AUD \ + CASHTURN.AUD \ + CLOCK1.AUD \ + COUNTER1.AUD \ + COUNTRY1.AUD \ + COUNTRY4.AUD \ + KEYSTROK.AUD \ + SFX4.AUD \ + SCOLD1.AUD \ + TARGET1.AUD \ + TARGET2.AUD \ + TARGET3.AUD \ + TEXT2.AUD \ + WORLD2.AUD \ + MYES1.AUD \ + MCOMND1.AUD \ + MHELLO1.AUD \ + MHMMM1.AUD \ + MPLAN3.AUD \ + MCOURSE1.AUD \ + MYESYES1.AUD \ + MTIBER1.AUD \ + MTHANKS1.AUD \ +# MMG1.AUD \ +# MACTION1.AUD \ +# MREMARK1.AUD \ +# MPLAN1.AUD \ +# MPLAN2.AUD \ +# MHASTE1.AUD \ +# MONCE1.AUD \ +# MIMMD1.AUD \ + +RESPONSE1 = \ + ACKNO.AUD \ + AFFIRM1.AUD \ + AWAIT1.AUD \ + MOVOUT1.AUD \ + REPORT1.AUD \ + UNIT1.AUD \ + VEHIC1.AUD \ + YESSIR1.AUD \ +# 2DANGR1.AUD \ +# NEGATV1.AUD \ + +RESPONSE2 = \ + ACKNO.AUD \ + AFFIRM1.AUD \ + AWAIT1.AUD \ + MOVOUT1.AUD \ + NOPROB.AUD \ + OVEROUT.AUD \ + READY.AUD \ + REPORT1.AUD \ + RITAWAY.AUD \ + ROGER.AUD \ + UGOTIT.AUD \ + YESSIR1.AUD \ + +RAMBO = \ + BOMBIT1.AUD \ + CMON1.AUD \ + GOTIT1.AUD \ + KEEPEM1.AUD \ + LAUGH1.AUD \ + LEFTY1.AUD \ + NOPRBLM1.AUD \ + ONIT1.AUD \ + RAMYELL1.AUD \ + ROKROLL1.AUD \ + TUFFGUY1.AUD \ + YEAH1.AUD \ + YES1.AUD \ + YO1.AUD \ +# OHSH1.AUD \ + +TSCOREFILES = \ + cps\record.bin \ + CHOOSE.WSA \ + NOD1PRE.VQA \ + STRUGGLE.AUD \ + GDI_SLCT.AUD \ + NOD_SLCT.AUD \ + KANEFINL.AUD \ + LOOPIE6M.AUD \ + WIN1.AUD \ + MAP1.AUD \ + +VARFILES = \ + TROUBLE.AUD \ + AOI.AUD \ + WIN1.AUD \ + BEFEARED.AUD \ + HEART.AUD \ + ROUT.AUD \ + +SCOREFILES = \ + NOD_WIN1.AUD \ + NOD_MAP1.AUD \ + AIRSTRIK.AUD \ + AOI.AUD \ + BEFEARED.AUD \ + CCTHANG.AUD \ + DIE.AUD \ + FWP.AUD \ + HEAVYG.AUD \ + I_AM.AUD \ + IND.AUD \ + IND2.AUD \ + J1.AUD \ + JDI_V2.AUD \ + JUSTDOIT.AUD \ + LINEFIRE.AUD \ + MARCH.AUD \ + NOMERCY.AUD \ + OTP.AUD \ + PRP.AUD \ + RADIO.AUD \ + RAIN.AUD \ + STOPTHEM.AUD \ + TARGET.AUD \ + TROUBLE.AUD \ + VALKYRIE.AUD \ + WARFARE.AUD \ + +# BFEARED.AUD \ +# CCTHANG.AUD \ +# CHOOSE1.AUD \ +# DIE!!.AUD \ +# FWP.AUD \ +# IAM.AUD \ +# IND.AUD \ +# JUSTDOIT.AUD \ +# LINEFIRE.AUD \ +# MARCH.AUD \ +# MECHMAN.AUD \ +# NOMERCY.AUD \ +# OTP.AUD \ +# PRP.AUD \ +# ROUT.AUD \ +# STOPTHEM.AUD \ +# TROUBLE.AUD \ +# WARFARE.AUD \ +# VALK.AUD \ + +SPEECHFILES = \ + UNITLOST.AUD \ + NEEDHARV.AUD \ + STRCLOST.AUD \ + ENMYUNIT.AUD \ + ACCOM1.AUD \ + FAIL1.AUD \ + BLDG1.AUD \ + CONSTRU1.AUD \ + NEWOPT1.AUD \ + DEPLOY1.AUD \ + GDIDEAD1.AUD \ + NODDEAD1.AUD \ + CIVDEAD1.AUD \ + NOCASH1.AUD \ + BATLCON1.AUD \ + REINFOR1.AUD \ + CANCEL1.AUD \ + BLDGING1.AUD \ + LOPOWER1.AUD \ + NOPOWER1.AUD \ + MOCASH1.AUD \ + BASEATK1.AUD \ + INCOME1.AUD \ + ENEMYA.AUD \ + NUKE1.AUD \ + NOBUILD1.AUD \ + PRIBLDG1.AUD \ + NODCAPT1.AUD \ + GDICAPT1.AUD \ + IONCHRG1.AUD \ + IONREDY1.AUD \ + UNITREDY.AUD \ + NUKAVAIL.AUD \ + NUKLNCH1.AUD \ + SELECT1.AUD \ + AIRREDY1.AUD \ + NOREDY1.AUD \ + ENMYAPP1.AUD \ + SILOS1.AUD \ + ONHOLD1.AUD \ + REPAIR1.AUD \ + ESTRUCX.AUD \ + GSTRUC1.AUD \ + NSTRUC1.AUD \ + +# TRANSSEE.AUD \ +# TRANLOAD.AUD \ +# BLUEP.AUD \ +# BLUES.AUD \ +# BLUEU.AUD \ +# CIVS.AUD \ +# CIVU.AUD \ +# DEPART.AUD \ +# DESTROY.AUD \ +# GDIS.AUD \ +# GDIU.AUD \ +# GOLDP.AUD \ +# GOLDS.AUD \ +# GOLDU.AUD \ +# GREENP.AUD \ +# GREENS.AUD \ +# GREENU.AUD \ +# GREYP.AUD \ +# GREYS.AUD \ +# GREYU.AUD \ +# NODS.AUD \ +# NODU.AUD \ +# ORANGEP.AUD \ +# ORANGES.AUD \ +# ORANGEU.AUD \ +# REDP.AUD \ +# REDU.AUD \ +# VICTORY.AUD \ + +# GUKILL1.AUD \ +# GSTRUD1.AUD \ +# GONLINE1.AUD \ +# GLEFT1.AUD \ +# GOLDKILT.AUD \ +# GOLDWIN.AUD \ +# RUKILL1.AUD \ +# RSTRUD1.AUD \ +# RONLINE1.AUD \ +# RLEFT1.AUD \ +# REDKILT.AUD \ +# REDWIN.AUD \ +# GYUKILL1.AUD \ +# GYSTRUD1.AUD \ +# GYONLINE.AUD \ +# GYLEFT1.AUD \ +# GREYKILT.AUD \ +# GREYWIN.AUD \ +# OUKILL1.AUD \ +# OSTRUD1.AUD \ +# OONLINE1.AUD \ +# OLEFT1.AUD \ +# ORANKILT.AUD \ +# ORANWIN.AUD \ +# GNUKILL1.AUD \ +# GNSTRUD1.AUD \ +# GNONLINE.AUD \ +# GNLEFT1.AUD \ +# GRENKILT.AUD \ +# GRENWIN.AUD \ +# BUKILL1.AUD \ +# BSTRUD1.AUD \ +# BONLINE1.AUD \ +# BLEFT1.AUD \ +# BLUEKILT.AUD \ +# BLUEWIN.AUD \ +# REPDONE1.AUD \ +# RADOK1.AUD \ +# RADFATL1.AUD \ +# UPUNIT1.AUD \ +# UPSTRUC1.AUD \ +# EVAYES1.AUD \ +# EVANO1.AUD \ +# SOLD1.AUD \ +# ACHIEV1.AUD \ +# ADJUST1.AUD \ +# AIRRAID1.AUD \ +# BRIGHT1.AUD \ +# COLOR1.AUD \ +# CONQUER1.AUD \ +# CONTRST1.AUD \ +# DEFEAT1.AUD \ +# DISCOV1.AUD \ +# ENEMYE.AUD \ +# ENEMYN.AUD \ +# ENEMYS.AUD \ +# ENEMYW.AUD \ +# EVABOOT1.AUD \ +# EXIT1.AUD \ +# FACTORY1.AUD \ +# FACTUSE1.AUD \ +# FULFILL1.AUD \ +# LOADGAM1.AUD \ +# LOST1.AUD \ +# MUSIC1.AUD \ +# OPTION1.AUD \ +# PLANEA.AUD \ +# PLANEE.AUD \ +# PLANEN.AUD \ +# PLANES.AUD \ +# PLANEW.AUD \ +# PREPARE1.AUD \ +# PROGRES1.AUD \ +# RADHAZ1.AUD \ +# RESTAB1.AUD \ +# SAVED1.AUD \ +# SAVEGAM1.AUD \ +# SOUND1.AUD \ +# SPEED1.AUD \ +# SUCCED1.AUD \ +# THANKS1.AUD \ +# TINT1.AUD \ +# UNAVAIL1.AUD \ + +GDIMOVIES = \ + BANNER.VQA \ + BCANYON.VQA \ + BKGROUND.VQA \ + BOMBAWAY.VQA \ + BOMBFLEE.VQA \ + BURDET1.VQA \ + BURDET2.VQA \ + CC2TEASE.VQA \ + CONSYARD.VQA \ + DESOLAT.VQA \ + DINO.VQA \ + FLAG.VQA \ + FLYY.VQA \ + FORESTKL.VQA \ + GAMEOVER.VQA \ + GDI1.VQA \ + GDI10.VQA \ + GDI11.VQA \ + GDI12.VQA \ + GDI13.VQA \ + GDI14.VQA \ + GDI15.VQA \ + GDI2.VQA \ + GDI3.VQA \ + GDI3LOSE.VQA \ + GDI4A.VQA \ + GDI4B.VQA \ + GDI5.VQA \ + GDI6.VQA \ + GDI7.VQA \ + GDI8A.VQA \ + GDI8B.VQA \ + GDI9.VQA \ + GDIEND1.VQA \ + GDIEND2.VQA \ + GDIFINA.VQA \ + GDIFINB.VQA \ + GDILOSE.VQA \ + GENERIC.VQA \ + GUNBOAT.VQA \ + HELLVALY.VQA \ + INTRO2.VQA \ + LANDING.VQA \ + LOGO.VQA \ + NAPALM.VQA \ + NITEJUMP.VQA \ + NOD1.VQA \ + NODFLEES.VQA \ + NODLOSE.VQA \ + NODLOSE.VQA \ + NODSWEEP.VQA \ + PARATROP.VQA \ + PINTLE.VQA \ + PLANECRA.VQA \ + PODIUM.VQA \ + RETRO.VQA \ + SABOTAGE.VQA \ + SAMDIE.VQA \ + SAMSITE.VQA \ + SEIGE.VQA \ + TBRINFO1.VQA \ + TBRINFO2.VQA \ + TBRINFO3.VQA \ +# TRAILER.VQA \ + TURTKILL.VQA \ + VISOR.VQA \ + +NODMOVIES = \ + AIRSTRK.VQA \ + AKIRA.VQA \ + BANNER.VQA \ + BCANYON.VQA \ + BOMBAWAY.VQA \ + BOMBFLEE.VQA \ + CC2TEASE.VQA \ + CONSYARD.VQA \ + DESFLEES.VQA \ + DESKILL.VQA \ + DESSWEEP.VQA \ + DINO.VQA \ + FLAG.VQA \ + FORESTKL.VQA \ + GAMEOVER.VQA \ + GDI1.VQA \ + GENERIC.VQA \ + INSITES.VQA \ + INTRO2.VQA \ + KANEPRE.VQA \ + LANDING.VQA \ + LOGO.VQA \ + NOD1.VQA \ + NOD10A.VQA \ + NOD10B.VQA \ + NOD11.VQA \ + NOD12.VQA \ + NOD13.VQA \ + NOD1PRE.VQA \ + NOD2.VQA \ + NOD3.VQA \ + NOD4A.VQA \ + NOD4B.VQA \ + NOD5.VQA \ + NOD6.VQA \ + NOD7A.VQA \ + NOD7B.VQA \ + NOD8.VQA \ + NOD9.VQA \ + NODEND1.VQA \ + NODEND2.VQA \ + NODEND3.VQA \ + NODEND4.VQA \ + NODFINAL.VQA \ + NODLOSE.VQA \ + NUKE.VQA \ + OBEL.VQA \ + REFINT.VQA \ + RETRO.VQA \ + SAMSITE.VQA \ + SEIGE.VQA \ + SETHPRE.VQA \ + SPYCRASH.VQA \ + STEALTH.VQA \ + SUNDIAL.VQA \ + TANKGO.VQA \ + TANKKILL.VQA \ + TIBERFX.VQA \ +# TRAILER.VQA \ + TRTKIL_D.VQA \ + VISOR.VQA \ + + +DEMOMFILES= \ + GDI1.VQA \ + GDI10.VQA \ + GDI6.VQA \ + LOGO.VQA \ + n:\code\cps\AOI.A6 \ + n:\code\cps\HEAVYG.A6 \ + +DEMO2MFILES= \ + INTRO2.VQA \ + AOI.AUD \ + HEAVYG.AUD \ + RADIO.AUD \ + LINEFIRE.AUD \ + CCTHANG.AUD \ + MARCH.AUD \ + BCANYON.VQA \ + CONSYARD.VQA \ + DESFLEES.VQA \ + DESKILL.VQA \ + DESSWEEP.VQA \ + FLAG.VQA \ + GAMEOVER.VQA \ + GDI1.VQA \ + GDI10.VQA \ + GDI6.VQA \ + GDILOSE.VQA \ + INSITES.VQA \ + LANDING.VQA \ + LOGO.VQA \ + NITEJUMP.VQA \ + NOD1.VQA \ + NOD5.VQA \ + NOD8.VQA \ + NODFLEES.VQA \ + NODLOSE.VQA \ + SABOTAGE.VQA \ + SAMSITE.VQA \ + SETHPRE.VQA \ + STEALTH.VQA \ + TBRINFO2.VQA \ + TIBERFX.VQA \ + TRAILER.VQA \ + CC2TEASE.VQA \ + +DEMOLFILES= \ + CONQUER.ENG \ + SCOREFNT.FNT \ + GRAD6FNT.FNT \ + 3POINT.FNT \ + 6POINT.FNT \ + 8POINT.FNT \ + 8FAT.FNT \ + FONT12.FNT \ + FONT6.FNT \ + LED.FNT \ + VCR.FNT \ + TEMPERAT.PAL \ + MOUSE.SHP \ + NOD1PRE.VQA \ + NOD1PRE.VQP \ + STRUGGLE.AUD \ + GDI_SLCT.AUD \ + NOD_SLCT.AUD \ + CHOOSE.WSA \ + S-GDIIN2.WSA \ + SCRSCN1.WSA \ + DEMOPIC.PCX \ + PREPICK.PCX \ + MISSION.INI \ + ATTRACT2.CPS \ + 12GREEN.FNT \ + 12GRNGRD.FNT \ + HTITLE.PCX \ + GDI1.VQP \ + GDI10.VQP \ + GDI6.VQP \ + LOGO.VQP \ + HPIPS.SHP \ + HBTN-DN.SHP \ + HBTN-UP.SHP \ + HBTN-DN2.SHP \ + HBTN-UP2.SHP \ + HSIDE1.SHP \ + HSIDE2.SHP \ + HPWRBAR.SHP \ + HREPAIR.SHP \ + HSELL.SHP \ + HMAP.SHP \ + BTN-STH.SHP \ + BTN-PLH.SHP \ + HRADAR.GDI \ + HRADAR.NOD \ + HCLOCK.SHP \ + HPOWER.SHP \ + HSTRIP.SHP \ + HSTRIPDN.SHP \ + HSTRIPUP.SHP \ + HTABS.SHP \ + BTEXTURE.SHP \ + GRAD12FN.FNT \ + SCORPAL1.PAL \ + SIDES.PAL \ + SNODPAL1.PAL \ + ..\maps\demo\SCG10EA.BIN \ + ..\maps\demo\SCG10EA.INI \ + ..\maps\demo\SCG01EA.BIN \ + ..\maps\demo\SCG01EA.INI \ + ..\maps\demo\SCG06EA.BIN \ + ..\maps\demo\SCG06EA.INI \ + + +DEMOCFILES= \ + CONQUER.ENG \ + SCOREFNT.FNT \ + GRAD6FNT.FNT \ + 6POINT.FNT \ + 8POINT.FNT \ + LED.FNT \ + VCR.FNT \ + TEMPERAT.PAL \ + MOUSE.SHP \ + +DEMO2LFILES= \ + NOD1PRE.VQA \ + STRUGGLE.AUD \ + GDI_SLCT.AUD \ + NOD_SLCT.AUD \ + CHOOSE.WSA \ + S-GDIIN2.WSA \ + SCRSCN1.WSA \ + DEMOPIC.CPS \ + PREPICK.CPS \ + PREPICK2.CPS \ + MISSION.INI \ + ATTRACT2.CPS \ + TITLE.CPS \ + ..\maps\demo2\SCG10EA.BIN \ + ..\maps\demo2\SCG10EA.INI \ + ..\maps\demo2\SCG01EA.BIN \ + ..\maps\demo2\SCG01EA.INI \ + ..\maps\demo2\SCG06EA.BIN \ + ..\maps\demo2\SCG06EA.INI \ + ..\maps\demo2\SCB01EA.BIN \ + ..\maps\demo2\SCB01EA.INI \ + ..\maps\demo2\SCB05EA.BIN \ + ..\maps\demo2\SCB05EA.INI \ + ..\maps\demo2\SCB08EA.BIN \ + ..\maps\demo2\SCB08EA.INI \ + + +DEMOFILES= \ + TIME.SHP \ + HISCORE1.SHP \ + HISCORE2.SHP \ + BAR3RED.SHP \ + BAR3YLW.SHP \ + LOGOS.SHP \ + CREDS.SHP \ + VICE.SHP \ + TABS.SHP \ + FLMSPT.SHP \ + PIPS.SHP \ + RROTOR.SHP \ + LROTOR.SHP \ + MOVEFLSH.SHP \ + ATOMDOOR.SHP \ + ATOMICDN.SHP \ + ATOMICUP.SHP \ + BTN-DN.SHP \ + BTN-ST.SHP \ + BTN-UP.SHP \ + BTN-PL.SHP \ + OPTIONS.SHP \ + SCRATE.SHP \ + WCRATE.SHP \ + EYE.SHP \ + EYEICON.SHP \ + EYEMAKE.SHP \ + SMOKLAND.SHP \ + BOMBICON.SHP \ + IONICON.SHP \ + ATOMSFX.SHP \ + ATOMICON.SHP \ + 120MM.SHP \ + 50CAL.SHP \ + A10.SHP \ + A10ICON.SHP \ + AFLD.SHP \ + AFLDICON.SHP \ + AFLDMAKE.SHP \ + APC.SHP \ + APCICON.SHP \ + ARCO.SHP \ + ARCOICON.SHP \ + ART-EXP1.SHP \ + ARTY.SHP \ + ARTYICON.SHP \ + ATWR.SHP \ + ATWRICON.SHP \ + ATWRMAKE.SHP \ + BARB.SHP \ + BARBICON.SHP \ + BGGY.SHP \ + BGGYICON.SHP \ + BIKE.SHP \ + BIKEICON.SHP \ + BIO.SHP \ + BIOICON.SHP \ + BIOMAKE.SHP \ + BOAT.SHP \ + BOATICON.SHP \ + BOMB.SHP \ + BOMBLET.SHP \ + BRIK.SHP \ + BRIKICON.SHP \ + BURN-L.SHP \ + BURN-M.SHP \ + BURN-S.SHP \ + C1.SHP \ + CHAN.SHP \ + C17.SHP \ + C17ICON.SHP \ + C2.SHP \ + C3.SHP \ + C4.SHP \ + C5.SHP \ + C6.SHP \ + C7.SHP \ + DELPHI.SHP \ + C8.SHP \ + C9.SHP \ + C10.SHP \ + CLOCK.SHP \ + CONC.SHP \ + CYCL.SHP \ + CYCLICON.SHP \ + DRAGON.SHP \ + E1.SHP \ + E1ICON.SHP \ + E1ROT.SHP \ + E2.SHP \ + E2ICON.SHP \ + E2ROT.SHP \ + E3.SHP \ + E3ICON.SHP \ + E3ROT.SHP \ + E4.SHP \ + E4ICON.SHP \ + E4ROT.SHP \ + E5.SHP \ + E5ICON.SHP \ + E6.SHP \ + E6ICON.SHP \ + FACT.SHP \ + FACTICON.SHP \ + FACTMAKE.SHP \ + FBALL1.SHP \ + FIRE1.SHP \ + FIRE2.SHP \ + FIRE3.SHP \ + FIRE4.SHP \ + FIX.SHP \ + FIXICON.SHP \ + FIXMAKE.SHP \ + FLAME-N.SHP \ + FLAME-NW.SHP \ + FLAME-S.SHP \ + FLAME-SW.SHP \ + FLAME-W.SHP \ + FLAME-E.SHP \ + FLAME-NE.SHP \ + FLAME-SE.SHP \ + FRAG1.SHP \ + FRAG3.SHP \ + FTNK.SHP \ + FTNKICON.SHP \ + GTWR.SHP \ + GTWRICON.SHP \ + GTWRMAKE.SHP \ + GUN.SHP \ + GUNFIRE.SHP \ + GUNICON.SHP \ + GUNMAKE.SHP \ + HAND.SHP \ + HANDICON.SHP \ + HANDMAKE.SHP \ + HARV.SHP \ + HARVICON.SHP \ + HELI.SHP \ + HELIICON.SHP \ + HOSP.SHP \ + HOSPICON.SHP \ + HOSPMAKE.SHP \ + HPAD.SHP \ + HPADICON.SHP \ + HPADMAKE.SHP \ + HQ.SHP \ + HQICON.SHP \ + HQMAKE.SHP \ + HTNK.SHP \ + HTNKICON.SHP \ + IONSFX.SHP \ + JEEP.SHP \ + JEEPICON.SHP \ + LST.SHP \ + LSTICON.SHP \ + LTNK.SHP \ + LTNKICON.SHP \ + MCV.SHP \ + MCVICON.SHP \ + MHQ.SHP \ + MHQICON.SHP \ + MINIGUN.SHP \ + MISS.SHP \ + MISSILE.SHP \ + MLRS.SHP \ + MLRSICON.SHP \ + MOEBIUS.SHP \ + MTNK.SHP \ + MTNKICON.SHP \ + NAPALM1.SHP \ + NAPALM2.SHP \ + NAPALM3.SHP \ + NUKE.SHP \ + NUKEICON.SHP \ + NUKEMAKE.SHP \ + NUK2.SHP \ + NUK2ICON.SHP \ + NUK2MAKE.SHP \ + ORCA.SHP \ + ORCAICON.SHP \ + OBLI.SHP \ + OBLIICON.SHP \ + OBLIMAKE.SHP \ + PATRIOT.SHP \ + PIFF.SHP \ + PIFFPIFF.SHP \ + POWER.SHP \ + PROC.SHP \ + PROCICON.SHP \ + PROCMAKE.SHP \ + PUMPICON.SHP \ + PUMPMAKE.SHP \ + PYLE.SHP \ + PYLEICON.SHP \ + PYLEMAKE.SHP \ + RADAR.GDI \ + RADAR.NOD \ + RADAR.JP \ + RMBO.SHP \ + RMBOICON.SHP \ + ROAD.SHP \ + ROADICON.SHP \ + SAM.SHP \ + SAMFIRE.SHP \ + SAMICON.SHP \ + SAMMAKE.SHP \ + SBAG.SHP \ + SBAGICON.SHP \ + SELECT.SHP \ + SHADOW.SHP \ + SILO.SHP \ + SILOICON.SHP \ + SILOMAKE.SHP \ + SMOKEY.SHP \ + SMOKE_M.SHP \ + SQUISH.SHP \ + STNK.SHP \ + STNKICON.SHP \ + STRIP.SHP \ + STRIPDN.SHP \ + STRIPUP.SHP \ + TRAN.SHP \ + TRANICON.SHP \ + TRANS.ICN \ + VEH-HIT1.SHP \ + VEH-HIT2.SHP \ + VEH-HIT3.SHP \ + WAKE.SHP \ + WEAP.SHP \ + WEAP2.SHP \ + WEAPICON.SHP \ + WEAPMAKE.SHP \ + WOOD.SHP \ + WOODICON.SHP \ + V19.SHP \ + MSAM.SHP \ + MSAMICON.SHP \ +# COUNTRYA.SHP \ +# CHEMBALL.SHP \ +# COUNTRYE.SHP \ +# 3POINT.FNT \ +# DEVIATOR.SHP \ +# DOLLAR.SHP \ +# EARTH.SHP \ +# EMPULSE.SHP \ +# INVUN.SHP \ +# MINE.SHP \ +# RAPID.SHP \ +# STEALTH2.SHP \ +# MISSILE2.SHP \ +# FLAGFLY.SHP \ +# FPLS.SHP \ +# TRIC.SHP \ +# STEG.SHP \ +# RAPT.SHP \ +# TREX.SHP \ +# TMPL.SHP \ +# TMPLICON.SHP \ +# TMPLMAKE.SHP \ +# CHEM-N.SHP \ +# CHEM-NW.SHP \ +# CHEM-S.SHP \ +# CHEM-SW.SHP \ +# CHEM-W.SHP \ +# CHEM-E.SHP \ +# CHEM-NE.SHP \ +# CHEM-SE.SHP \ +# SPLIT2.TEM \ +# SPLIT3.TEM \ +# BIB1.TEM \ +# BIB2.TEM \ +# BIB3.TEM \ +# B1.TEM \ +# B2.TEM \ +# B3.TEM \ +# BRIDGE1.TEM \ +# BRIDGE1D.TEM \ +# BRIDGE2.TEM \ +# BRIDGE2D.TEM \ +# CLEAR1.TEM \ +# CR1.TEM \ +# CR2.TEM \ +# CR3.TEM \ +# CR4.TEM \ +# CR5.TEM \ +# CR6.TEM \ +# D01.TEM \ +# D02.TEM \ +# D03.TEM \ +# D04.TEM \ +# D05.TEM \ +# D06.TEM \ +# D07.TEM \ +# D08.TEM \ +# D09.TEM \ +# D10.TEM \ +# D11.TEM \ +# D12.TEM \ +# D13.TEM \ +# D14.TEM \ +# D15.TEM \ +# D16.TEM \ +# D17.TEM \ +# D18.TEM \ +# D19.TEM \ +# D20.TEM \ +# D21.TEM \ +# D22.TEM \ +# D23.TEM \ +# D24.TEM \ +# D25.TEM \ +# D26.TEM \ +# D27.TEM \ +# D28.TEM \ +# D29.TEM \ +# D30.TEM \ +# D31.TEM \ +# D32.TEM \ +# D33.TEM \ +# D34.TEM \ +# D35.TEM \ +# D36.TEM \ +# D37.TEM \ +# D38.TEM \ +# D39.TEM \ +# D40.TEM \ +# D41.TEM \ +# D42.TEM \ +# D43.TEM \ +# FALLS1.TEM \ +# FALLS2.TEM \ +# FORD1.TEM \ +# FORD2.TEM \ +# P01.TEM \ +# P02.TEM \ +# P03.TEM \ +# P04.TEM \ +# P07.TEM \ +# P08.TEM \ +# P13.TEM \ +# P14.TEM \ +# RV01.TEM \ +# RV02.TEM \ +# RV03.TEM \ +# RV04.TEM \ +# RV05.TEM \ +# RV06.TEM \ +# RV07.TEM \ +# RV08.TEM \ +# RV09.TEM \ +# RV10.TEM \ +# RV11.TEM \ +# RV12.TEM \ +# RV13.TEM \ +# S01.TEM \ +# S02.TEM \ +# S03.TEM \ +# S04.TEM \ +# S05.TEM \ +# S06.TEM \ +# S07.TEM \ +# S08.TEM \ +# S09.TEM \ +# S10.TEM \ +# S11.TEM \ +# S12.TEM \ +# S13.TEM \ +# S14.TEM \ +# S15.TEM \ +# S16.TEM \ +# S17.TEM \ +# S18.TEM \ +# S19.TEM \ +# S20.TEM \ +# S21.TEM \ +# S22.TEM \ +# S23.TEM \ +# S24.TEM \ +# S25.TEM \ +# S26.TEM \ +# S27.TEM \ +# S28.TEM \ +# S29.TEM \ +# S30.TEM \ +# S31.TEM \ +# S32.TEM \ +# S33.TEM \ +# S34.TEM \ +# S35.TEM \ +# S36.TEM \ +# S37.TEM \ +# S38.TEM \ +# SC1.TEM \ +# SC2.TEM \ +# SC3.TEM \ +# SC4.TEM \ +# SC5.TEM \ +# SC6.TEM \ +# SH1.TEM \ +# SH10.TEM \ +# SH11.TEM \ +# SH12.TEM \ +# SH13.TEM \ +# SH14.TEM \ +# SH15.TEM \ +# SH16.TEM \ +# SH17.TEM \ +# SH18.TEM \ +# SH32.TEM \ +# SH33.TEM \ +# SH34.TEM \ +# SH35.TEM \ +# SH2.TEM \ +# SH3.TEM \ +# SH4.TEM \ +# SH5.TEM \ +# SH6.TEM \ +# SH7.TEM \ +# SH8.TEM \ +# SH9.TEM \ +# SR1.TEM \ +# SR2.TEM \ +# T01.TEM \ +# T02.TEM \ +# T03.TEM \ +# T05.TEM \ +# T06.TEM \ +# T07.TEM \ +# T08.TEM \ +# T10.TEM \ +# T11.TEM \ +# T12.TEM \ +# T13.TEM \ +# T14.TEM \ +# T15.TEM \ +# T16.TEM \ +# T17.TEM \ +# TC01.TEM \ +# TC02.TEM \ +# TC03.TEM \ +# TC04.TEM \ +# TC05.TEM \ +# TCLOCK.MRF \ +# TWHITE.MRF \ +# TEMPERAT.PAL \ +# TGREEN.MRF \ +# TI1.TEM \ +# TI10.TEM \ +# TI11.TEM \ +# TI12.TEM \ +# TI2.TEM \ +# TI3.TEM \ +# TI4.TEM \ +# TI5.TEM \ +# TI6.TEM \ +# TI7.TEM \ +# TI8.TEM \ +# TI9.TEM \ +# TLIGHT.MRF \ +# TMOUSE.MRF \ +# TRED.MRF \ +# TSHADE.MRF \ +# TSHADOW.MRF \ +# TTRANS.MRF \ +# TUNITS.MRF \ +# TYELLOW.MRF \ +# V01.TEM \ +# V02.TEM \ +# V03.TEM \ +# V04.TEM \ +# V05.TEM \ +# V06.TEM \ +# V07.TEM \ +# V08.TEM \ +# V09.TEM \ +# V10.TEM \ +# V11.TEM \ +# V12.TEM \ +# V13.TEM \ +# V14.TEM \ +# V15.TEM \ +# V16.TEM \ +# V17.TEM \ +# V18.TEM \ +# W1.TEM \ +# W2.TEM \ + +DEMO2FILES= \ + TIME.SHP \ + HISCORE1.SHP \ + HISCORE2.SHP \ + BAR3RED.SHP \ + BAR3YLW.SHP \ + LOGOS.SHP \ + CREDS.SHP \ + VICE.SHP \ + TABS.SHP \ + FLMSPT.SHP \ + PIPS.SHP \ + RROTOR.SHP \ + LROTOR.SHP \ + MOVEFLSH.SHP \ + ATOMDOOR.SHP \ + ATOMICDN.SHP \ + ATOMICUP.SHP \ + BTN-DN.SHP \ + BTN-ST.SHP \ + BTN-UP.SHP \ + BTN-PL.SHP \ + OPTIONS.SHP \ + SCRATE.SHP \ + WCRATE.SHP \ + EYE.SHP \ + EYEICON.SHP \ + EYEMAKE.SHP \ + SMOKLAND.SHP \ + BOMBICON.SHP \ + IONICON.SHP \ + ATOMSFX.SHP \ + ATOMICON.SHP \ + 120MM.SHP \ + 50CAL.SHP \ + A10.SHP \ + A10ICON.SHP \ + AFLD.SHP \ + AFLDICON.SHP \ + AFLDMAKE.SHP \ + APC.SHP \ + APCICON.SHP \ + ARCO.SHP \ + ARCOICON.SHP \ + ART-EXP1.SHP \ + ARTY.SHP \ + ARTYICON.SHP \ + ATWR.SHP \ + ATWRICON.SHP \ + ATWRMAKE.SHP \ + BARB.SHP \ + BARBICON.SHP \ + BGGY.SHP \ + BGGYICON.SHP \ + BIKE.SHP \ + BIKEICON.SHP \ + BIO.SHP \ + BIOICON.SHP \ + BIOMAKE.SHP \ + BOAT.SHP \ + BOATICON.SHP \ + BOMB.SHP \ + BOMBLET.SHP \ + BRIK.SHP \ + BRIKICON.SHP \ + BURN-L.SHP \ + BURN-M.SHP \ + BURN-S.SHP \ + C1.SHP \ + CHAN.SHP \ + C17.SHP \ + C17ICON.SHP \ + C2.SHP \ + C3.SHP \ + C4.SHP \ + C5.SHP \ + C6.SHP \ + C7.SHP \ + DELPHI.SHP \ + C8.SHP \ + C9.SHP \ + C10.SHP \ + CLOCK.SHP \ + CONC.SHP \ + CYCL.SHP \ + CYCLICON.SHP \ + DRAGON.SHP \ + E1.SHP \ + E1ICON.SHP \ + E1ROT.SHP \ + E2.SHP \ + E2ICON.SHP \ + E2ROT.SHP \ + E3.SHP \ + E3ICON.SHP \ + E3ROT.SHP \ + E4.SHP \ + E4ICON.SHP \ + E4ROT.SHP \ + E5.SHP \ + E5ICON.SHP \ + E6.SHP \ + E6ICON.SHP \ + FACT.SHP \ + FACTICON.SHP \ + FACTMAKE.SHP \ + FBALL1.SHP \ + FIRE1.SHP \ + FIRE2.SHP \ + FIRE3.SHP \ + FIRE4.SHP \ + FIX.SHP \ + FIXICON.SHP \ + FIXMAKE.SHP \ + FLAME-N.SHP \ + FLAME-NW.SHP \ + FLAME-S.SHP \ + FLAME-SW.SHP \ + FLAME-W.SHP \ + FLAME-E.SHP \ + FLAME-NE.SHP \ + FLAME-SE.SHP \ + FRAG1.SHP \ + FRAG3.SHP \ + FTNK.SHP \ + FTNKICON.SHP \ + GTWR.SHP \ + GTWRICON.SHP \ + GTWRMAKE.SHP \ + GUN.SHP \ + GUNFIRE.SHP \ + GUNICON.SHP \ + GUNMAKE.SHP \ + HAND.SHP \ + HANDICON.SHP \ + HANDMAKE.SHP \ + HARV.SHP \ + HARVICON.SHP \ + HELI.SHP \ + HELIICON.SHP \ + HOSP.SHP \ + HOSPICON.SHP \ + HOSPMAKE.SHP \ + HPAD.SHP \ + HPADICON.SHP \ + HPADMAKE.SHP \ + HQ.SHP \ + HQICON.SHP \ + HQMAKE.SHP \ + HTNK.SHP \ + HTNKICON.SHP \ + IONSFX.SHP \ + JEEP.SHP \ + JEEPICON.SHP \ + LST.SHP \ + LSTICON.SHP \ + LTNK.SHP \ + LTNKICON.SHP \ + MCV.SHP \ + MCVICON.SHP \ + MHQ.SHP \ + MHQICON.SHP \ + MINIGUN.SHP \ + MISS.SHP \ + MISSILE.SHP \ + MLRS.SHP \ + MLRSICON.SHP \ + MOEBIUS.SHP \ + MTNK.SHP \ + MTNKICON.SHP \ + NAPALM1.SHP \ + NAPALM2.SHP \ + NAPALM3.SHP \ + NUKE.SHP \ + NUKEICON.SHP \ + NUKEMAKE.SHP \ + NUK2.SHP \ + NUK2ICON.SHP \ + NUK2MAKE.SHP \ + ORCA.SHP \ + ORCAICON.SHP \ + OBLI.SHP \ + OBLIICON.SHP \ + OBLIMAKE.SHP \ + PATRIOT.SHP \ + PIFF.SHP \ + PIFFPIFF.SHP \ + POWER.SHP \ + PROC.SHP \ + PROCICON.SHP \ + PROCMAKE.SHP \ + PUMPICON.SHP \ + PUMPMAKE.SHP \ + PYLE.SHP \ + PYLEICON.SHP \ + PYLEMAKE.SHP \ + RADAR.GDI \ + RADAR.NOD \ + RADAR.JP \ + RMBO.SHP \ + RMBOICON.SHP \ + ROAD.SHP \ + ROADICON.SHP \ + SAM.SHP \ + SAMFIRE.SHP \ + SAMICON.SHP \ + SAMMAKE.SHP \ + SBAG.SHP \ + SBAGICON.SHP \ + SELECT.SHP \ + SHADOW.SHP \ + SILO.SHP \ + SILOICON.SHP \ + SILOMAKE.SHP \ + SMOKEY.SHP \ + SMOKE_M.SHP \ + SQUISH.SHP \ + STNK.SHP \ + STNKICON.SHP \ + STRIP.SHP \ + STRIPDN.SHP \ + STRIPUP.SHP \ + TRAN.SHP \ + TRANICON.SHP \ + TRANS.ICN \ + VEH-HIT1.SHP \ + VEH-HIT2.SHP \ + VEH-HIT3.SHP \ + WAKE.SHP \ + WEAP.SHP \ + WEAP2.SHP \ + WEAPICON.SHP \ + WEAPMAKE.SHP \ + WOOD.SHP \ + WOODICON.SHP \ + V19.SHP \ + MSAM.SHP \ + MSAMICON.SHP \ + + +LINTOBJECTS1 = $(OBJECTS:,=) +LINTOBJECTS = $(LINTOBJECTS1:.OBJ=.LOB) + +PACKFILES= \ + CONQUER.MIX \ + DESERT.MIX \ + $(.path.cd1)GENERAL.MIX \ + $(.path.cd2)GENERAL.MIX \ + TEMPERAT.MIX \ + WINTER.MIX \ + $(.path.cd1)install\CCLOCAL.MIX \ + $(.path.cd1)install\UPDATE.MIX \ + $(.path.cd1)install\UPDATEC.MIX \ + $(.path.cd1)install\TRANSIT.MIX \ + $(.path.cd1)install\DESEICNH.MIX \ + $(.path.cd1)install\WINTICNH.MIX \ + $(.path.cd1)install\TEMPICNH.MIX \ +# $(.path.cd1)aud1\SPEECH.MIX \ +# SOUNDS.MIX \ +# SCORES.MIX \ +# $(.path.cd1)MOVIES.MIX \ +# $(.path.cd2)MOVIES.MIX \ + +# Limited mixfiles that can be built on home system. +HOMEFILES= \ + CONQUER.MIX \ + DESERT.MIX \ + TEMPERAT.MIX \ + GENERAL.MIX \ + + +############################################################# +# Rebuilds all pack files. +packfiles: $(PACKFILES) + + +############################################################# +# Debug text file creation. +#debug.eng: debug.txt +# utils\textmake -b1000 $(.path.txt)$&.txt $(.path.eng)$&.eng $&.h + +############################################################# +# Demo #2 (Point of Sale version) +demo2: $(.path.dm2)install\DEMO.MIX $(.path.dm2)install\DEMOL.MIX $(.path.dm2)DEMOM.MIX $(.path.dm2)install\DEMOC.MIX + +############################################################# +# Help screen for this complex makefile. +#help: +# type &&! +# This makefile controls many C&C operations. Pass the +# following parameter to MAKE for the desired function. +# --------------------------------------------------------- +# _______________Rebuilds executable. +# PACKFILES______Rebuilds all mixfiles. +#! + +#################################################################### +# Creates the data file that is loaded at the beginning and then not +# freed. +CONQUER.MIX: $(CONQUERFILES) $(CACHEMAP) + UTILS\MIXFILE -I$(.path.cps) &&! + $** +! $(.path.cd1)$&.mix + copy $(.path.cd1)$&.mix $(.path.cd2)$&.mix + +DESERT.MIX: $(DESERTFILES) + UTILS\MIXFILE -I$(.path.cps) &&! + $** +! $(.path.cd1)$&.mix + copy $(.path.cd1)$&.mix $(.path.cd2)$&.mix + +WINTER.MIX: $(WINTERFILES) + UTILS\MIXFILE -I$(.path.cps) &&! + $** +! $(.path.cd1)$&.mix + copy $(.path.cd1)$&.mix $(.path.cd2)$&.mix + +TEMPERAT.MIX: $(TEMPERATEFILES) + UTILS\MIXFILE -I$(.path.cps) &&! + $** +! $(.path.cd1)$&.mix + copy $(.path.cd1)$&.mix $(.path.cd2)$&.mix + +DESEICNH.MIX: $(DESERTICONFILES) + UTILS\MIXFILE -I$(.path.cps) &&! + $** +! $(.path.cd1)install\$&.mix + copy $(.path.cd1)install\$&.mix $(.path.cd2)install\$&.mix + +WINTICNH.MIX: $(WINTERICONFILES) + UTILS\MIXFILE -I$(.path.cps) &&! + $** +! $(.path.cd1)install\$&.mix + copy $(.path.cd1)install\$&.mix $(.path.cd2)install\$&.mix + +TEMPICNH.MIX: $(TEMPERATEICONFILES) + UTILS\MIXFILE -I$(.path.cps) &&! + $** +! $(.path.cd1)install\$&.mix + copy $(.path.cd1)install\$&.mix $(.path.cd2)install\$&.mix + +$(.path.cd1)GENERAL.MIX: $(GENERALFILES) $(GDIMAPFILES) $(NETMAPFILES) $(MAPFILES) + UTILS\MIXFILE -I$(.path.cps) -I$(.path.ini) &&! + $** +! $(.path.cd1)$&.mix + +$(.path.cd2)GENERAL.MIX: $(GENERALFILES) $(NODMAPFILES) $(NETMAPFILES) $(MAPFILES) + UTILS\MIXFILE -I$(.path.cps) -I$(.path.ini) &&! + $** +! $(.path.cd2)$&.mix + +#SCORES8.MIX: $(SCOREFILES:.AUD=.A8) +# UTILS\MIXFILE -E.A8=.AUD -E.V16=.VAR -I$(.path.cps) -I$(.path.ini) &&! +# $** +#! $(.path.mix)$&.mix + +SCORES.MIX: $(SCOREFILES:.AUD=.A6) $(VARFILES:.AUD=.V16) + UTILS\MIXFILE -E.A6=.AUD -E.V16=.VAR -I$(.path.cps) -I$(.path.ini) &&! + $** +! $(.path.cd1)$&.mix + copy $(.path.cd1)$&.mix $(.path.cd2)$&.mix + +#$(.path.mix)8_st\SOUNDS.MIX: $(WAVFILES:.AUD=.A8) $(SFX:.AUD=.A8A) $(SFX:.AUD=.A8J) +# UTILS\MIXFILE -e.A8=.AUD -e.A8A=.AUD -e.A8J=.JUV -I$(.path.cps) &&! +# $** +#! $(.path.mix)8_st\$&.mix + +#$(.path.mix)install\SOUNDS.MIX: $(WAVFILES:.AUD=.A6) $(SFX:.AUD=.A6A) $(SFX:.AUD=.A6J) +# UTILS\MIXFILE -e.A6=.AUD -e.A6A=.AUD -e.A6J=.JUV -I$(.path.cps) &&! +# $** +#! $(.path.mix)install\$&.mix + +# UTILS\MIXFILE -e.A6=.AUD -e.A6A=.AUD -e.A6J=.JUV -E.A60=.V00 -E.A61=.V01 -E.A62=.V02 -E.A63=.V03 -E.A64=.V04 -I$(.path.aud);$(.path.cps) &&! +# UTILS\MIXFILE -eA6=AUD -eA6A=AUD -eA6J=JUV -EA60=V00 -EA61=V01 -EA62=V02 -EA63=V03 -EA64=V04 -I$(.path.aud) &&! +#SOUNDS.MIX: $(WAVFILES:.AUD=.A6) $(SFX:.AUD=.A6A) $(SFX:.AUD=.A6J) $(RESPONSE1:.AUD=.A60) $(RESPONSE2:.AUD=.A61) $(RESPONSE1:.AUD=.A62) $(RESPONSE2:.AUD=.A63) $(RAMBO:.AUD=.A6) $(TSCOREFILES:.AUD=.A6) +SOUNDS.MIX: $(WAVFILES:.AUD=.A6) $(SFX:.AUD=.A6A) $(RESPONSE1:.AUD=.A60) $(RESPONSE2:.AUD=.A61) $(RESPONSE1:.AUD=.A62) $(RESPONSE2:.AUD=.A63) $(RAMBO:.AUD=.A6) + UTILS\MIXFILE -eA6=AUD -eA6A=AUD -EA60=V00 -EA61=V01 -EA62=V02 -EA63=V03 -EA64=V04 -I$(.path.aud) &&! + $** +! $(.path.cd1)$&.mix + copy $(.path.cd1)$&.mix $(.path.cd2)$&.mix + +ZOUNDS.MIX: $(SFX:.AUD=.JUV) + UTILS\MIXFILE -eA6J=JUV -I$(.path.aud) &&! + $** +! $(.path.mix)$&.mix + +#$(.path.mix)8_st\INFANTRY.MIX: $(RESPONSE:.AUD=.A80) $(RESPONSE:.AUD=.A81) $(RESPONSE:.AUD=.A82) $(RESPONSE:.AUD=.A83) $(RAMBO:.AUD=.A8) +# UTILS\MIXFILE -E.A80=.V00 -E.A81=.V01 -E.A82=.V02 -E.A83=.V03 -E.A84=.V04 -E.A8=.AUD -I$(.path.aud) &&! +# $** +#! $(.path.mix)8_st\$&.mix + +#$(.path.mix)install\INFANTRY.MIX: $(RESPONSE1:.AUD=.A60) $(RESPONSE2:.AUD=.A61) $(RESPONSE1:.AUD=.A62) $(RESPONSE2:.AUD=.A63) $(RAMBO:.AUD=.A6) $(TSCOREFILES:.AUD=.A6) +# UTILS\MIXFILE -E.A60=.V00 -E.A61=.V01 -E.A62=.V02 -E.A63=.V03 -E.A64=.V04 -E.A6=.AUD -I$(.path.aud) &&! +# $** +#! $(.path.mix)install\$&.mix + +$(.path.cd1)install\CCLOCAL.MIX: $(LOCALFILES) + UTILS\MIXFILE -E.A6=.AUD -I$(.path.cps) &&! + $** +! $(.path.cd1)install\$&.mix + copy $(.path.cd1)install\$&.mix $(.path.cd2)install\$&.mix + +UPDATE.MIX: $(UPDATEFILES) + UTILS\MIXFILE -E.A6=.AUD -I$(.path.cps) &&! + $** +! $(.path.cd1)install\$&.mix + copy $(.path.cd1)\$&.mix $(.path.cd2)\$&.mix + +UPDATEC.MIX: $(UPDATECFILES) + UTILS\MIXFILE -E.A6=.AUD -I$(.path.cps) &&! + $** +! $(.path.cd1)install\$&.mix + copy $(.path.cd1)install\$&.mix $(.path.cd2)install\$&.mix + +LANGUAGE.MIX: $(JAPANFILES) + UTILS\MIXFILE -E.A6=.AUD -I$(.path.cps) &&! + $** +! $(.path.cd1)install\$&.mix + copy $(.path.cd1)\$&.mix $(.path.cd2)\$&.mix + +$(.path.cd1)install\TRANSIT.MIX: $(TSCOREFILES:.AUD=.A6) + UTILS\MIXFILE -E.A6=.AUD -I$(.path.cps) &&! + $** +! $(.path.cd1)install\$&.mix + copy $(.path.cd1)install\$&.mix $(.path.cd2)install\$&.mix + + +#$(.path.mix)8_st\SPEECH.MIX: $(SPEECHFILES:.AUD=.A8) +# UTILS\MIXFILE -E.A8=.AUD -I$(.path.aud) &&! +# $** +#! $(.path.mix)8_st\$&.mix + +SPEECH.MIX: $(SPEECHFILES:.AUD=.A6) + UTILS\MIXFILE -E.A6=.AUD -I$(.path.aud) &&! + $** +! $(.path.cd1)install\$&.mix + copy $(.path.cd1)aud1\$&.mix $(.path.cd2)aud1\$&.mix + +#$(.path.mix)8_st\TRANSIT.MIX: $(TSCOREFILES:.AUD=.A8) +# UTILS\MIXFILE -E.A8=.AUD -I$(.path.aud) &&! +# $** +#! $(.path.mix)8_st\$&.mix + +#$(.path.mix)16_st\TRANSIT.MIX: $(TSCOREFILES:.AUD=.A6) +# UTILS\MIXFILE -E.A6=.AUD -I$(.path.aud) &&! +# $** +#! $(.path.mix)16_st\$&.mix + +$(.path.cd1)MOVIES.MIX: $(GDIMOVIES) + UTILS\MIXFILE -I$(.path.vqa) &&! + $** +! $(.path.cd1)$&.mix + +$(.path.cd2)MOVIES.MIX: $(NODMOVIES) + UTILS\MIXFILE -I$(.path.vqa) &&! + $** +! $(.path.cd2)$&.mix + + +$(.path.dmo)DEMO.MIX: $(DEMOFILES:.AUD=.A6) + UTILS\MIXFILE -I$(.path.cps) &&! + $** +! $(.path.dmo)$&.mix + +$(.path.dmo)DEMOL.MIX: $(DEMOLFILES:.AUD=.A6) + UTILS\MIXFILE -E.A6=.AUD -I$(.path.cps) &&! + $** +! $(.path.dmo)$&.mix + +$(.path.dmo)DEMOM.MIX: $(DEMOMFILES) + UTILS\MIXFILE -E.A6=.AUD -I$(.path.cps) &&! + $** +! $(.path.dmo)$&.mix + + +$(.path.dm2)install\DEMO.MIX: $(DEMO2FILES:.AUD=.A6) + UTILS\MIXFILE -I$(.path.cps) &&! + $** +! $(.path.dm2)install\$&.mix + +$(.path.dm2)install\DEMOL.MIX: $(DEMO2LFILES:.AUD=.A6) + UTILS\MIXFILE -E.A6=.AUD -I$(.path.cps) &&! + $** +! $(.path.dm2)install\$&.mix + +$(.path.dm2)install\DEMOC.MIX: $(DEMOCFILES:.AUD=.A6) + UTILS\MIXFILE -E.A6=.AUD -I$(.path.cps) &&! + $** +! $(.path.dm2)install\$&.mix + +$(.path.dm2)DEMOM.MIX: $(DEMO2MFILES) + UTILS\MIXFILE -E.A6=.AUD -I$(.path.cps) &&! + $** +! $(.path.dm2)$&.mix + +###################################################################### +# Special rule to create the mouse shape (which must be a shape file) +mouse.shp: $(.path.anm)mouse.anm + utils\makeshps $(.path.lbm)title.lbm &&! + &$(.path.anm)mouse.anm; + end; +! $(.path.shp)$&.shp $(SHAPEBUFFSIZE) diff --git a/BUILDING.CPP b/BUILDING.CPP new file mode 100644 index 0000000..a6f5108 --- /dev/null +++ b/BUILDING.CPP @@ -0,0 +1,4960 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\building.cpv 2.13 02 Aug 1995 17:00:14 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : BUILDING.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : August 20, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * BuildingClass::AI -- Handles non-graphic AI processing for buildings. * + * BuildingClass::Active_Click_With -- Handles cell selection for buildings. * + * BuildingClass::As_Target -- Convert the building into a target value. * + * BuildingClass::Assign_Target -- Assigns a target to the building. * + * BuildingClass::Begin_Mode -- Begins an animation mode for the building. * + * BuildingClass::BuildingClass -- Constructor for buildings. * + * BuildingClass::Can_Demolish -- Can the player demolish (sell back) the building? * + * BuildingClass::Can_Enter_Cell -- Determines if building can be placed down. * + * BuildingClass::Can_Fire -- Determines if this building can fire. * + * BuildingClass::Captured -- Captures the building. * + * BuildingClass::Center_Coord -- Fetches the center coordinate for the building. * + * BuildingClass::Click_With -- Handles clicking on the map while the building is selected. * + * BuildingClass::Crew_Type -- This determines the crew that this object generates. * + * BuildingClass::Death_Announcement -- Announce the death of this building. * + * BuildingClass::Debug_Dump -- Displays building status to the monochrome screen. * + * BuildingClass::Detach -- Handles target removal from the game system. * + * BuildingClass::Detach_All -- Possibly abandons production according to factory type. * + * BuildingClass::Draw_It -- Displays the building at the location specified. * + * BuildingClass::Drop_Debris -- Drops rubble when building is destroyed. * + * BuildingClass::Enter_Idle_Mode -- The building will enter its idle mode. * + * BuildingClass::Exit_Object -- Initiates an object to leave the building. * + * BuildingClass::Fire_At -- Fires weapon at specified target. * + * BuildingClass::Fire_Coord -- Calculates the coordinate that projectile would appear. * + * BuildingClass::Fire_Direction -- Fetches the direction of firing. * + * BuildingClass::Fire_Out -- Handles when attached animation expires. * + * BuildingClass::Flush_For_Placement -- Handles clearing a zone for object placement. * + * BuildingClass::Grand_Opening -- Handles construction completed special operations. * + * BuildingClass::Greatest_Threat -- Searches for target that building can fire upon. * + * BuildingClass::Init -- Initialize the building system to an empty null state. * + * BuildingClass::Limbo -- Handles power adjustment as building goes into limbo. * + * BuildingClass::Look -- Reveal map around building. * + * BuildingClass::Mark -- Building interface to map rendering system. * + * BuildingClass::Mission_Attack -- Handles attack mission for building. * + * BuildingClass::Mission_Construction -- Handles mission construction. * + * BuildingClass::Mission_Deconstruction -- Handles building deconstruction. * + * BuildingClass::Mission_Guard -- Handles guard mission for combat buildings. * + * BuildingClass::Mission_Harvest -- Handles refinery unloading harvesters. * + * BuildingClass::Mission_Missile -- State machine for nuclear missile launch. * + * BuildingClass::Mission_Repair -- Handles the repair (active) state for building. * + * BuildingClass::Mission_Unload -- Handles the unload mission for a building. * + * BuildingClass::Pip_Count -- Determines "full" pips to display for building. * + * BuildingClass::Power_Output -- Fetches the current power output from this building. * + * BuildingClass::Read_INI -- Reads buildings from INI file. * + * BuildingClass::Receive_Message -- Handle an incoming message to the building. * + * BuildingClass::Refund_Amount -- Fetches the refund amount if building is sold. * + * BuildingClass::Remap_Table -- Fetches the remap table to use for this building. * + * BuildingClass::Repair -- Initiates or terminates the repair process. * + * BuildingClass::Revealed -- Reveals the building to the specified house. * + * BuildingClass::Sell_Back -- Controls the sell back (demolish) operation. * + * BuildingClass::Sort_Y -- Returns the building coordinate used for sorting. * + * BuildingClass::Take_Damage -- Inflicts damage points upon a building. * + * BuildingClass::Toggle_Primary -- Toggles the primary factory state. * + * BuildingClass::Unlimbo -- Removes a building from limbo state. * + * BuildingClass::Update_Buildables -- Informs sidebar of additional construction options. * + * BuildingClass::Update_Specials -- removes computer specials for lost bld * + * BuildingClass::What_Action -- Determines action to perform if click on specified object. * + * BuildingClass::What_Action -- Determines what action will occur. * + * BuildingClass::Write_INI -- Writes all building data to an INI file. * + * BuildingClass::delete -- Deallocates building object. * + * BuildingClass::new -- Allocates a building object from building pool. * + * BuildingClass::~BuildingClass -- Destructor for building type objects. * + * BuildingClass::Validate -- validates building pointer * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +enum SAMState { + SAM_NONE=-1, // Used for non SAM site buildings. + SAM_UNDERGROUND, // Launcher is underground and awaiting orders. + SAM_RISING, // Doors open and launcher rises to normal locked down position. + SAM_READY, // Launcher can be facing any direction tracking targets. + SAM_FIRING, // Stationary while missile is being fired. + SAM_READY2, // Launcher can be facing any direction tracking targets. + SAM_FIRING2, // Stationary while missile is being fired. + SAM_LOCKING, // Rotating to locked position in preparation for lowering. + SAM_LOWERING, // Launcher is lowering into the ground. +}; + + +/*************************************************************************** +** Center of building offset table. +*/ +COORDINATE const BuildingClass::CenterOffset[BSIZE_COUNT] = { + 0x00800080L, + 0x008000FFL, + 0x00FF0080L, + 0x00FF00FFL, + 0x018000FFL, + 0x00FF0180L, + 0x01800180L, + + 0x00FF0200L, + + 0x02800280L, +}; + +/* +** This contains the value of the Virtual Function Table Pointer +*/ +void * BuildingClass::VTable; + + +/*********************************************************************************************** + * BuildingClass::Validate -- validates building pointer * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int BuildingClass::Validate(void) const +{ + int num; + + num = Buildings.ID(this); + if (num < 0 || num >= BUILDING_MAX) { + Validate_Error("BUILDING"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*********************************************************************************************** + * BuildingClass::Receive_Message -- Handle an incoming message to the building. * + * * + * This routine handles an incoming message to the building. Messages regulate the * + * various cooperative ventures between buildings and units. This might include such * + * actions as coordinating the construction yard animation with the actual building's * + * construction animation. * + * * + * INPUT: from -- The originator of the message received. * + * * + * message -- The radio message received. * + * * + * param -- Reference to an optional parameter that might be used to return * + * extra information to the message originator. * + * * + * OUTPUT: Returns with the response to the message (typically, this is just RADIO_OK). * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/09/1994 JLB : Created. * + * 06/26/1995 JLB : Forces refinery load anim to start immediately. * + * 08/13/1995 JLB : Uses ScenarioInit for special loose "CAN_LOAD" check. * + *=============================================================================================*/ +RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageType message, long & param) +{ + Validate(); + switch (message) { + + /* + ** This message is received as a request to attach/load/dock with this building. + ** Verify that this is allowed and return the appropriate response. + */ + case RADIO_CAN_LOAD: + TechnoClass::Receive_Message(from, message, param); + if (BState == BSTATE_CONSTRUCTION || (!ScenarioInit && In_Radio_Contact())) return(RADIO_NEGATIVE); + switch (Class->Type) { + case STRUCT_AIRSTRIP: + if (from->What_Am_I() == RTTI_AIRCRAFT && *((AircraftClass const *)from) == AIRCRAFT_CARGO) { + return(RADIO_ROGER); + } + break; + + case STRUCT_HELIPAD: + if (from->What_Am_I() == RTTI_AIRCRAFT && !((AircraftClass const *)from)->Class->IsFixedWing) { + return(RADIO_ROGER); + } + break; + + case STRUCT_REPAIR: + if (/*from->Health_Ratio() < 0x0100 &&*/ from->What_Am_I() == RTTI_UNIT || from->What_Am_I() == RTTI_AIRCRAFT) { + return(RADIO_ROGER); + } + return(RADIO_NEGATIVE); + + case STRUCT_REFINERY: + if (from->What_Am_I() == RTTI_UNIT && + *((UnitClass *)from) == UNIT_HARVESTER && + (ScenarioInit || !Is_Something_Attached())) { + + return(RADIO_ROGER); + } + break; + + default: + break; + } + return(RADIO_NEGATIVE); + + /* + ** This message is received when the object has attached itself to this + ** building. + */ + case RADIO_IM_IN: + if (Mission == MISSION_DECONSTRUCTION) { + return(RADIO_NEGATIVE); + } + switch (Class->Type) { + case STRUCT_REPAIR: + IsReadyToCommence = true; + Assign_Mission(MISSION_REPAIR); + from->Assign_Mission(MISSION_SLEEP); + return(RADIO_ROGER); + + case STRUCT_HELIPAD: + Assign_Mission(MISSION_REPAIR); + from->Assign_Mission(MISSION_SLEEP); + return(RADIO_ROGER); + + case STRUCT_REFINERY: + ScenarioInit++; + Begin_Mode(BSTATE_ACTIVE); + ScenarioInit--; + Mark(MARK_CHANGE); + Assign_Mission(MISSION_HARVEST); + return(RADIO_ATTACH); + } + break; + + /* + ** Docking maneuver maintenance message. See if new order should be given to the + ** unit trying to dock. + */ + case RADIO_DOCKING: + TechnoClass::Receive_Message(from, message, param); + + /* + ** When in radio contact for loading, the refinery starts + ** flashing the lights. + */ + if (*this == STRUCT_REFINERY && BState != BSTATE_FULL) { + Begin_Mode(BSTATE_FULL); + } + + if (Transmit_Message(RADIO_NEED_TO_MOVE, from) == RADIO_ROGER) { + if (*this == STRUCT_HELIPAD) { + param = As_Target(); + } else { + if (*this == STRUCT_REPAIR) { + Transmit_Message(RADIO_TETHER); + param = ::As_Target(Coord_Cell(Center_Coord())); + } else { + param = ::As_Target(Coord_Cell(Adjacent_Cell(Center_Coord(), DIR_SW))); + } + } + + /* + ** Tell the harvester to move to the docking pad of the refinery. + */ + if (Transmit_Message(RADIO_MOVE_HERE, param, from) == RADIO_YEA_NOW_WHAT) { + + /* + ** Since the harvester is already there, tell it to begin the backup + ** procedure now. If it can't, then tell it to get outta here. + */ + Transmit_Message(RADIO_TETHER); + if (*this == STRUCT_REFINERY && Transmit_Message(RADIO_BACKUP_NOW, from) != RADIO_ROGER) { + from->Scatter(NULL, true); + } + } + } + return(RADIO_ROGER); + + /* + ** If a transport or harvester is requesting permission to head toward, dock + ** and load/unload, check to make sure that this is allowed given the current + ** state of the building. + */ + case RADIO_ARE_REFINERY: + if (Is_Something_Attached() || In_Radio_Contact() || IsInLimbo || House->Class->House != from->Owner() || (*this != STRUCT_REFINERY/* && *this != STRUCT_REPAIR*/)) { + return(RADIO_NEGATIVE); + } + return(RADIO_ROGER); + + /* + ** Someone is telling us that it is starting construction. This should only + ** occur if this is a construction yard and a building was just placed on + ** the map. + */ + case RADIO_BUILDING: + Assign_Mission(MISSION_REPAIR); + TechnoClass::Receive_Message(from, message, param); + return(RADIO_ROGER); + + /* + ** Someone is telling us that they have finished construction. This should + ** only occur if this is a construction yard and the building that was being + ** constructed has finished. In this case, stop the construction yard + ** animation. + */ + case RADIO_COMPLETE: + if (Mission != MISSION_DECONSTRUCTION) { + Assign_Mission(MISSION_GUARD); + } + TechnoClass::Receive_Message(from, message, param); + return(RADIO_ROGER); + + /* + ** This message may occur unexpectedly if the unit in contact with this + ** building is suddenly destroyed. Handle any cleanup necessary. For example, + ** a construction yard should stop its construction animation in this case. + */ + case RADIO_OVER_OUT: + Begin_Mode(BSTATE_IDLE); + TechnoClass::Receive_Message(from, message, param); + return(RADIO_ROGER); + + /* + ** This message is received when an object has completely left + ** building. Sometimes special cleanup action is required when + ** this event occurs. + */ + case RADIO_UNLOADED: + if (*this == STRUCT_REPAIR) { + if (Distance(from) < 0x0180) { + return(RADIO_ROGER); + } + } + TechnoClass::Receive_Message(from, message, param); + if (*this == STRUCT_WEAP || *this == STRUCT_AIRSTRIP || *this == STRUCT_REPAIR) return(RADIO_RUN_AWAY); + return(RADIO_ROGER); + } + + /* + ** Pass along the message to the default message handler in the radio itself. + */ + return(TechnoClass::Receive_Message(from, message, param)); +} + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * BuildingClass::Debug_Dump -- Displays building status to the monochrome screen. * + * * + * This utility function will output the current status of the building class to the * + * monochrome screen. It is through this data that bugs may be fixed or detected. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + *=============================================================================================*/ +void BuildingClass::Debug_Dump(MonoClass *mono) const +{ + Validate(); + mono->Set_Cursor(0, 0); + mono->Print( + "ÚName:ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂMission:ÄÄÄÂTarCom:ÂÄÄÄÄÄÄÄÂRadio:ÂCoord:ÄÄÂÄÄÄÄÄÄÄÄÂSt:Ä¿\n" + "³ ³ ³ ³ ³ ³ ³ ³ ³\n" + "ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂNÂYÂHealth:ÄÂÄÄÄÁÄÂTurret:ÂÄÄÄÄÄÁÂÄBuilding:ÄÄÂCargo:ÄÄÄÄÁÄÄÄÄ´\n" + "³Active........³ ³ ³ ³ ³ ³ ³ ³ ³\n" + "³Limbo.........³ ³ ÃÄÄÄÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´\n" + "³Owned.........³ ³ ³Last Message: ³\n" + "³Discovered....³ ³ ÃTimer:ÂArm:ÂÄÄÄÄÄÄÂTiberium:ÂFlash:ÂStage:ÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\n" + "³Selected......³ ³ ³ ³ ³ ³ ³ ³ ³ \n" + "³Teathered.....³ ³ ÃÄÄÄÄÄÄÁÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÙ \n" + "³Locked on Map.³ ³ ³ \n" + "³Is A Loaner...³ ³ ³ \n" + "³ ³ ³ ³ \n" + "³ ³ ³ ³ \n" + "³ ³ ³ ³ \n" + "³Repairing.....³ ³ ³ \n" + "³ ³ ³ ³ \n" + "³ ³ ³ ³ \n" + "³Recoiling.....³ ³ ³ \n" + "³To Display....³ ³ ³ \n" + "ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÁÄÙ \n"); + mono->Set_Cursor(1, 1);mono->Printf("%s:%s", House->Class->IniName, Class->IniName); + mono->Set_Cursor(35, 3);mono->Printf("%02X:%02X", PrimaryFacing.Current(), PrimaryFacing.Desired()); + mono->Set_Cursor(50, 3); + if (Factory) { + mono->Printf(Factory->Get_Object()->Class_Of().IniName); + mono->Printf(" "); + mono->Printf("%d%%", Factory->Completion()); + } else { + mono->Printf("(empty)"); + } + + mono->Text_Print("X", 16 + (IsRepairing?2:0), 14); +// mono->Set_Cursor(44, 3);mono->Printf("%d", SAM); + mono->Set_Cursor(34, 1);mono->Printf("%04X", TarCom); + mono->Set_Cursor(28, 7);mono->Printf("%2d", Arm); + + TechnoClass::Debug_Dump(mono); +} +#endif + + +/*********************************************************************************************** + * BuildingClass::Draw_It -- Displays the building at the location specified. * + * * + * This is the low level graphic routine that displays the building at the location * + * specified. * + * * + * INPUT: x,y -- The coordinate to draw the building at. * + * * + * window -- The clipping window to use. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/20/1994 JLB : Created. * + * 06/27/1994 JLB : Takes a clipping window parameter. * + * 07/06/1995 JLB : Handles damaged silos correctly. * + *=============================================================================================*/ +void BuildingClass::Draw_It(int x, int y, WindowNumberType window) +{ + Validate(); + void const * shapefile; // Pointer to loaded shape file. + int shapenum; + + shapenum = Fetch_Stage(); + + /* + ** The shape file to use for rendering depends on whether the building + ** is undergoing construction or not. + */ + if (BState == BSTATE_CONSTRUCTION) { + shapefile = Class->Get_Buildup_Data(); + + /* + ** If the building is deconstructing, then the display frame progresses + ** from the end to the beginning. Reverse the shape number accordingly. + */ + if (Mission == MISSION_DECONSTRUCTION) { + shapenum = (Class->Anims[BState].Start+Class->Anims[BState].Count-1)-shapenum; + } + + } else { + + shapefile = Class->Get_Image_Data(); + + /* + ** The obelisk has a stage value than can be overridden by + ** its current state. + */ + if (*this == STRUCT_OBELISK) { + if (IsCharged) { + shapenum = 3; + } else { + if (IsCharging) { + shapenum = Fetch_Stage(); + } else { + shapenum = 0; + } + } + } + + /* + ** Buildings that contain a turret handle their shape determination + ** differently than normal buildings. They need to take into consideration + ** the direction the turret is facing. + */ + if (Class->IsTurretEquipped) { + shapenum = UnitClass::BodyShape[Facing_To_32(PrimaryFacing.Current())]; + + if (*this == STRUCT_SAM) { + + /* + ** SAM sites that are free to rotate fetch their animation frame + ** from the building's turret facing. All other animation stages + ** fetch their frame from the embedded animation sequencer. + */ + if (Status == SAM_READY || Status == SAM_FIRING || Status == SAM_LOCKING) { + shapenum += 16; + } else { + shapenum = Fetch_Stage(); + } + } else { + if (IsInRecoilState) { + shapenum += 32; + } + } + if (Health_Ratio() < 0x0080) { + shapenum += 64; + } + } else { + + /* + ** If it has only one point of strength left, it is shown in the + ** worst state possible. + */ + if (Strength <= 1) { + shapenum = Get_Build_Frame_Count(shapefile)-1; + } else { + + if (*this == STRUCT_WEAP) { + shapenum = 0; + if (Health_Ratio() < 0x0080) { + shapenum = 1; + } + + } else { + + /* + ** Special render stage for silos. The stage is dependant on the current + ** Tiberium collected as it relates to Tiberium capacity. + */ + if (*this == STRUCT_STORAGE) { + int level = 0; + if (House->Capacity) { + level = (House->Tiberium * 5) / House->Capacity; + } +// int level = Fixed_To_Cardinal(4, Cardinal_To_Fixed(House->Capacity, House->Tiberium)); + + shapenum += Bound(level, 0, 4); + if (Health_Ratio() < 0x0080) { + shapenum += 5; + } + + } else { + + if (Health_Ratio() < 0x0080) { + + /* + ** Special damage stage for pump. + */ + if (!Class->IsSimpleDamage) { + int last1 = Class->Anims[BSTATE_IDLE].Start + Class->Anims[BSTATE_IDLE].Count; + int last2 = Class->Anims[BSTATE_ACTIVE].Start + Class->Anims[BSTATE_ACTIVE].Count; + int largest = MAX(last1, last2); + last2 = Class->Anims[BSTATE_AUX1].Start + Class->Anims[BSTATE_AUX1].Count; + largest = MAX(largest, last2); + last2 = Class->Anims[BSTATE_AUX2].Start + Class->Anims[BSTATE_AUX2].Count; + largest = MAX(largest, last2); + + shapenum += largest; + } else { + + /* + ** Presume that the damage stage is the end frame. + */ + shapenum = Get_Build_Frame_Count(shapefile) - 2; + } + } + } + } + } + } + } + + /* + ** Actually draw the building shape. + */ + IsTheaterShape = Class->IsTheater; + Techno_Draw_Object(shapefile, shapenum, x, y, window); + IsTheaterShape = false; + + /* + ** Patch for adding overlay onto weapon factory. Only add the overlay if + ** the building has more than 1 hp. Also, if the building's in radio + ** contact, he must be unloading a constructed vehicle, so draw that + ** vehicle before drawing the overlay. + */ + if (BState != BSTATE_CONSTRUCTION) { + + /* + ** A Tethered object is always rendered AFTER the building. + */ + if (IsTethered && In_Radio_Contact() && !Contact_With_Whom()->IsInLimbo) { + Contact_With_Whom()->Render(true); + } + + /* + ** Draw the weapon factory custom overlay graphic. + */ + if (*this == STRUCT_WEAP && Strength > 1) { + shapenum = Door_Stage(); + if (Health_Ratio() < 0x0080) shapenum += 10; + Techno_Draw_Object(WarFactoryOverlay, shapenum, x, y, window); + } + + /* + ** Draw any repair feedback graphic required. + */ + if (IsRepairing && IsWrenchVisible) { + CC_Draw_Shape(ObjectTypeClass::SelectShapes, SELECT_WRENCH, x, y, window, SHAPE_CENTER|SHAPE_WIN_REL); + } + } + + TechnoClass::Draw_It(x, y, window); +} + + +/*********************************************************************************************** + * BuildingClass::Mark -- Building interface to map rendering system. * + * * + * This routine is used to mark the map cells so that when it renders * + * the underlying icons will also be updated as necessary. * + * * + * INPUT: mark -- Type of image change (MARK_UP, _DOWN, _CHANGE) * + * MARK_UP -- Building is removed. * + * MARK_CHANGE -- Building changes shape. * + * MARK_DOWN -- Building is added. * + * * + * OUTPUT: bool; Did the mark operation succeed? Failure could be the result of marking down * + * when the building is already marked down, or visa versa. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/31/1994 JLB : Created. * + * 04/15/1994 JLB : Converted to member function. * + * 04/16/1994 JLB : Added health bar tracking. * + * 12/23/1994 JLB : Calls low level check before proceeding. * + * 01/27/1995 JLB : Special road spacer template added. * + *=============================================================================================*/ +bool BuildingClass::Mark(MarkType mark) +{ + Validate(); + if (TechnoClass::Mark(mark)) { + short const *offset = Overlap_List(); + short const *occupy = Occupy_List(); + CELL cell = Coord_Cell(Coord); + SmudgeType bib; + + switch (mark) { + case MARK_UP: + Map.Pick_Up(cell, this); + if (Class->Bib_And_Offset(bib, cell)) { + SmudgeClass * smudge = new SmudgeClass(bib); + if (smudge) { + smudge->Disown(cell); + delete smudge; + } + } + break; + + case MARK_DOWN: + + /* + ** Special wall logic is handled here. A building that is really a wall + ** gets converted into an overlay wall type when it is placed down. The + ** actual building object itself is destroyed. + */ + if (Class->IsWall) { + switch (Class->Type) { + case STRUCT_BRICK_WALL: + new OverlayClass(OVERLAY_BRICK_WALL, cell, House->Class->House); + break; + + case STRUCT_BARBWIRE_WALL: + new OverlayClass(OVERLAY_BARBWIRE_WALL, cell, House->Class->House); + break; + + case STRUCT_SANDBAG_WALL: + new OverlayClass(OVERLAY_SANDBAG_WALL, cell, House->Class->House); + break; + + case STRUCT_WOOD_WALL: + new OverlayClass(OVERLAY_WOOD_WALL, cell, House->Class->House); + break; + + case STRUCT_CYCLONE_WALL: + new OverlayClass(OVERLAY_CYCLONE_WALL, cell, House->Class->House); + break; + } + Transmit_Message(RADIO_OVER_OUT); + delete this; + + } else { + + if (Can_Enter_Cell(cell) == MOVE_OK) { + + /* + ** Determine if a bib is required for this building. If one is, then + ** create and place it. + */ + CELL newcell = cell; + if (Class->Bib_And_Offset(bib, newcell)) { + new SmudgeClass(bib, Cell_Coord(newcell), House->Class->House); + } + + Map.Place_Down(cell, this); + } else { + return(false); + } + } + break; + + default: + Map.Refresh_Cells(cell, offset); + Map.Refresh_Cells(cell, occupy); + break; + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * BuildingClass::Fire_At -- Fires weapon at specified target. * + * * + * This routine does the actual firing of a projectile from the * + * building toward the specified target. Prior to calling this * + * routine, the building must have rotated into position and acquired * + * a suitable target. * + * * + * INPUT: target -- The target to fire upon. * + * * + * which -- Which weapon to use for firing. 0=primary, 1=secondary. * + * * + * OUTPUT: Returns with a pointer to the projectile just launched. This * + * may come in handy if additional adjustments to the projectile * + * are required. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/16/1994 JLB : Created. * + *=============================================================================================*/ +BulletClass * BuildingClass::Fire_At(TARGET target, int which) +{ + Validate(); + BulletClass * bullet; // Projectile. + WeaponTypeClass const * weapon = (which == 0) ? &Weapons[Class->Primary] : &Weapons[Class->Secondary]; + + bullet = TechnoClass::Fire_At(target, which); + if (bullet) { + if (*this == STRUCT_SAM) { + AnimClass *anim = new AnimClass((AnimType)(ANIM_SAM_N + Dir_Facing(PrimaryFacing.Current())), Center_Coord()); + if (anim) { + anim->Attach_To(this); + } + + } else { + + /* + ** Flash the muzzle, play sound, and perform any firing animation. + */ + Sound_Effect(weapon->Sound, Coord); + + if (weapon->Fires == BULLET_BULLET) { + new AnimClass((AnimType)(ANIM_GUN_N + Dir_Facing(PrimaryFacing.Current())), Fire_Coord(which)); + } else { + if (weapon->Fires == BULLET_LASER) { + int x,y,x1,y1; + COORDINATE source, dest; + source = Fire_Coord(which); + dest = As_Coord(target); + IsCharging = false; + IsCharged = false; + Set_Stage(0); + Set_Rate(0); + + if (Map.Push_Onto_TacMap(source, dest) && SpecialDialog == SDLG_NONE) { + + Map.Coord_To_Pixel(source, x, y); + Map.Coord_To_Pixel(dest, x1, y1); + x += Map.TacPixelX; + x1 += Map.TacPixelX; + y += Map.TacPixelY; + y1 += Map.TacPixelY; + Set_Logic_Page(SeenBuff); + LogicPage->Draw_Line(x+1, y, x1, y1, 0x7D); + LogicPage->Draw_Line(x-1, y, x1, y1, 0x7D); + LogicPage->Draw_Line(x, y, x1, y1, 0x7F); + Delay(1); // Make sure line is visible briefly + Map.Flag_To_Redraw(true); + } + new SmudgeClass(Random_Pick(SMUDGE_SCORCH1, SMUDGE_SCORCH6), As_Coord(target) ); + } else { + new AnimClass(ANIM_MUZZLE_FLASH, Fire_Coord(which)); + } + } + Mark(MARK_CHANGE); + } + } + + return(bullet); +} + + +/*********************************************************************************************** + * BuildingClass::AI -- Handles non-graphic AI processing for buildings. * + * * + * This function is to handle the AI logic for the building. The graphic logic (facing, * + * firing, and animation) is handled elsewhere. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + * 12/26/1994 JLB : Handles production. * + * 06/11/1995 JLB : Revamped. * + *=============================================================================================*/ +void BuildingClass::AI(void) +{ + Validate(); + + /* + ** Process building animation state changes. Transition to a following state + ** if there is one specified and the current animation sequence has expired. + ** This process must occur before mission AI since the mission AI relies on + ** the bstate change to occur immediately before the MissionClass::AI. + */ + bool stagechange = Graphic_Logic(); + bool toloop = false; + + /* + ** Always refresh the SAM site if it has an animation change. + */ + if (*this == STRUCT_SAM && stagechange) Mark(MARK_CHANGE); + + if ((!Class->IsTurretEquipped && *this != STRUCT_OBELISK) || Mission == MISSION_CONSTRUCTION || Mission == MISSION_DECONSTRUCTION) { + if (stagechange) { + + /* + ** Check for animation end or if special case of MCV deconstructing when it is allowed + ** to convert back into an MCV. + */ + BuildingTypeClass::AnimControlType const * ctrl = Fetch_Anim_Control(); + + /* + ** When the last frame of the current animation sequence is reached, flag that + ** a new mission may be started. This must occur before the animation actually + ** loops so that if a mission change does occur, it will have a chance to change + ** the building graphic before the last frame is replaced by the first frame of + ** the loop. + */ + if (Fetch_Stage() == ctrl->Start+ctrl->Count-1 || (Special.IsMCVDeploy && *this == STRUCT_CONST && Mission == MISSION_DECONSTRUCTION && Fetch_Stage() == (42-19))) { + IsReadyToCommence = true; + } + + /* + ** If the animation advances beyond the last frame, then start the animation + ** sequence over from the beginning. + */ + if (Fetch_Stage() >= ctrl->Start+ctrl->Count) { + toloop = true; + } + Mark(MARK_CHANGE); + } else { + if (BState == BSTATE_NONE || Fetch_Rate() == 0) { + IsReadyToCommence = true; + } + } + } + + /* + ** If there is a door that is animating, then it might cause this building + ** to be redrawn. Check for and flag to redraw as necessary. + */ + if (Time_To_Redraw()) { + Clear_Redraw_Flag(); + Mark(MARK_CHANGE); + } + + /* + ** The animation sequence has looped. Restart it and flag this loop condition. + ** This is used to tell the mission system that the animation has completed. It + ** also signals that now is a good time to act on any pending mission. + */ + if (toloop) { + BuildingTypeClass::AnimControlType const * ctrl = Fetch_Anim_Control(); + if (BState == BSTATE_CONSTRUCTION || BState == BSTATE_IDLE) { + Set_Rate(Options.Normalize_Delay(ctrl->Rate)); + } else { + Set_Rate(ctrl->Rate); + } + Set_Stage(ctrl->Start); + Mark(MARK_CHANGE); + } + + /* + ** If now is a good time to act on a new mission, then do so. This process occurs + ** here because some outside event may have requested a mission change for the building. + ** Such outside requests (player input) must be initiated BEFORE the normal AI process. + */ + if (IsReadyToCommence && BState != BSTATE_CONSTRUCTION) { + + /* + ** Clear the commencement flag ONLY if something actually occured. By acting + ** this way, a building can set the IsReadyToCommence flag before it goes + ** to "sleep" knowing that it will wake up as soon as a new mission comes + ** along. + */ + if (Commence()) { + IsReadyToCommence = false; + } + } + + /* + ** Proceed with normal logic processing. This is where the mission processing + ** occurs. This call must be located after the animation sequence makes the + ** transition to the next frame (see above) in order for the mission logic to + ** act at the exact moment of graphic transition BEFORE it has a chance to + ** be displayed. + */ + TechnoClass::AI(); + + /* + ** If now is a good time to act on a new mission, then do so. This occurs here because + ** some AI event may have requested a mission change (usually from another mission + ** state machine). This must occur here before it has a chance to render. + */ + if (IsReadyToCommence) { + + /* + ** Clear the commencement flag ONLY if something actually occured. By acting + ** this way, a building can set the IsReadyToCommence flag before it goes + ** to "sleep" knowing that it will wake up as soon as a new mission comes + ** along. + */ + if (Commence()) { + IsReadyToCommence = false; + } + } + + /* + ** If a change of animation was requested, then make the change + ** now. The building animation system acts independantly but subordinate + ** to the mission state machine system. By performing the animation change-up + ** here, the mission AI system is ensured of immediate visual affect when it + ** decides to change the animation state of the building. + */ + if (QueueBState != BSTATE_NONE) { + if (BState != QueueBState) { + BState = QueueBState; + BuildingTypeClass::AnimControlType const * ctrl = Fetch_Anim_Control(); + if (BState == BSTATE_CONSTRUCTION || BState == BSTATE_IDLE) { + Set_Rate(Options.Normalize_Delay(ctrl->Rate)); + } else { + Set_Rate(ctrl->Rate); + } + Set_Stage(ctrl->Start); + } + QueueBState = BSTATE_NONE; + } + + /* + ** If the building's strength has changed, then update the power + ** accordingly. + */ + if (Strength != LastStrength) { + int oldpower = Power_Output(); + LastStrength = Strength; + int newpower = Power_Output(); + House->Adjust_Power(newpower - oldpower); + } + + /* + ** Check to see if the destruction countdown timer is active. If so, then decrement it. + ** When this timer reaches zero, the building is removed from the map. All the explosions + ** are presumed to be in progress at this time. + */ + if (!Strength) { + if (CountDown.Expired()) { + Limbo(); + Drop_Debris(WhomToRepay); + delete this; + } + return; + } + + /* + ** Obelisk charging logic. + */ + if (*this == STRUCT_OBELISK && BState != BSTATE_CONSTRUCTION) { + if (Target_Legal(TarCom) && House->Power_Fraction() >= 0x0100) { + if (!IsCharged) { + if (IsCharging) { + if (stagechange) { + Mark(MARK_CHANGE); + if (Fetch_Stage() >= 4) { + IsCharged = true; + IsCharging = false; + Set_Rate(0); + } + } + } else { + IsCharged = false; + IsCharging = true; + Set_Stage(0); + Set_Rate(OBELISK_ANIMATION_RATE); + Sound_Effect(VOC_LASER_POWER, Coord); + } + } + } else { + if (IsCharging || IsCharged) { + Mark(MARK_CHANGE); + IsCharging = false; + IsCharged = false; + Set_Stage(0); + Set_Rate(0); + } + } + } + + /* + ** Handle any repair process that may be going on. + */ + if (IsRepairing) { + if ((Frame % 15) == 0) { + IsWrenchVisible = (IsWrenchVisible == false); + Mark(MARK_CHANGE); + int cost = Class->Repair_Cost(); + int step = Class->Repair_Step(); + + /* + ** Check for and expend any necessary monies to continue the repair. + */ + if (House->Available_Money() >= cost) { + House->Spend_Money(cost); + Strength += step; + + if (Strength >= Class->MaxStrength) { + Strength = Class->MaxStrength; + IsRepairing = false; + } + } else { + IsRepairing = false; + } + } + } + + /* + ** Handle any production tied to this building. Only computer controlled buildings have + ** production attached to the building itself. The player uses the sidebar interface for + ** all production control. + */ + if (Factory && Factory->Has_Completed() && PlacementDelay.Expired()) { + + switch (Exit_Object(Factory->Get_Object())) { + + /* + ** If the object could not leave the factory, then either request + ** a transport, place the (what must be a) building using another method, or + ** abort the production and refund money. + */ + case 0: + Factory->Abandon(); + delete Factory; + Factory = 0; + break; + + case 1: + PlacementDelay = TICKS_PER_SECOND*3; + break; + + case 2: + Factory->Completed(); + delete Factory; + Factory = 0; + break; + + } + } + + /* + ** For computer controlled buildings, determine what should be produced and start + ** production accordingly. + */ + if (!House->IsHuman && Mission != MISSION_CONSTRUCTION && Mission != MISSION_DECONSTRUCTION) { + + /* + ** Possibly start repair process if the building is below half strength. + */ + int ratio = 0x0040; + if (Scenario > 6) ratio = 0x0080; + if (Scenario > 10) ratio = 0x00C0; + if (Class->IsRepairable && Health_Ratio() <= ratio) { + if (House->Available_Money() >= REPAIR_THRESHHOLD) { + Repair(1); + } else { + if (IsTickedOff && Scenario > 2 && Random_Pick(0, 50) < Scenario && !Trigger) { + Sell_Back(1); + } + } + } + + /* + ** Buildings that produce other objects have special factory logic handled here. + */ + if (Class->ToBuild != RTTI_NONE) { + + if (Factory) { + + /* + ** If production has halted, then just abort production and make the + ** funds available for something else. + */ + if (PlacementDelay.Expired() && !Factory->Is_Building()) { + Factory->Abandon(); + delete Factory; + Factory = 0; + } + + } else { + + /* + ** Only look to start production if there is at least a small amount of + ** money available. In cases where there is no practical money left, then + ** production can never complete -- don't bother starting it. + */ + if (House->IsStarted && House->Available_Money() > 10) { + TechnoTypeClass const * techno = House->Suggest_New_Object(Class->ToBuild); + + /* + ** If a suitable object type was selected for production, then start + ** producing it now. + */ + if (techno) { + Factory = new FactoryClass; + if (Factory) { + if (!Factory->Set(*techno, *House)) { + delete Factory; + Factory = 0; + } else { + Factory->Start(); + } + } + } + } + } + } + } + + /* + ** Check for demolition timeout. When timeout has expired, the building explodes. + */ + if (IsGoingToBlow && CountDown.Expired()) { + SabotagedType = Class->Type; + int damage = 5000; + Take_Damage(damage, 0, WARHEAD_FIRE, As_Techno(WhomToRepay)); + Mark(MARK_CHANGE); + } + + /* + ** If the building was in a recoil state (as it would be just as it fires), then + ** restore the building. + */ + if (IsInRecoilState) { + IsInRecoilState = false; + Mark(MARK_CHANGE); + } + + /* + ** Turret equiped buildings must handle turret rotation logic here. This entails + ** rotating the turret to the desired facing as well as figuring out what that + ** desired facing should be. + */ + if (Class->IsTurretEquipped && Mission != MISSION_CONSTRUCTION && Mission != MISSION_DECONSTRUCTION) { + + /* + ** Rotate turret to match desired facing. + */ + if (PrimaryFacing.Is_Rotating()) { + if (*this == STRUCT_SAM) { + if (PrimaryFacing.Rotation_Adjust(15)) { + Mark(MARK_CHANGE); + } + } else { + if (PrimaryFacing.Rotation_Adjust(12)) { + Mark(MARK_CHANGE); + } + } + } + } +} + + +/*********************************************************************************************** + * BuildingClass::Unlimbo -- Removes a building from limbo state. * + * * + * Use this routine to transform a building that has been held in limbo * + * state, into one that really exists on the map. Once a building as * + * been unlimboed, then it becomes a normal object in the game world. * + * * + * INPUT: pos -- The position to place the building on the map. * + * * + * dir (optional) -- not used for this class * + * * + * OUTPUT: bool; Was the unlimbo successful? * + * * + * WARNINGS: The unlimbo operation might not be successful if the * + * building could not be placed at the location specified. * + * * + * HISTORY: * + * 04/16/1994 JLB : Created. * + * 06/07/1994 JLB : Matches virtual function format for base class. * + * 05/09/1995 JLB : Handles wall placement. * + * 06/18/1995 JLB : Checks for wall legality before placing down. * + *=============================================================================================*/ +bool BuildingClass::Unlimbo(COORDINATE coord, DirType dir) +{ + Validate(); +#ifdef OBSOLETE + if (*this == STRUCT_ROAD) { + if (Can_Enter_Cell(Coord_Cell(coord), FACING_NONE) == MOVE_OK) { + ObjectClass * o = OverlayTypeClass::As_Reference(OVERLAY_ROAD).Create_One_Of(House); + if (o && o->Unlimbo(coord)) { + Transmit_Message(RADIO_OVER_OUT); + delete this; + return(true); + } + } + return(false); + } +#endif + + /* + ** If this is a wall type building, then it never gets unlimboed. Instead, it gets + ** converted to an overlay type. + */ + if (Class->IsWall) { + if (Can_Enter_Cell(Coord_Cell(coord), FACING_NONE) == MOVE_OK) { + OverlayType otype = OVERLAY_NONE; + switch (Class->Type) { + case STRUCT_SANDBAG_WALL: + otype = OVERLAY_SANDBAG_WALL; + break; + + case STRUCT_CYCLONE_WALL: + otype = OVERLAY_CYCLONE_WALL; + break; + + case STRUCT_BRICK_WALL: + otype = OVERLAY_BRICK_WALL; + break; + + case STRUCT_BARBWIRE_WALL: + otype = OVERLAY_BARBWIRE_WALL; + break; + + case STRUCT_WOOD_WALL: + otype = OVERLAY_WOOD_WALL; + break; + } + if (otype != OVERLAY_NONE) { + ObjectClass * o = OverlayTypeClass::As_Reference(otype).Create_One_Of(House); + if (o && o->Unlimbo(coord)) { + Map[Coord_Cell(coord)].Owner = House->Class->House; + Transmit_Message(RADIO_OVER_OUT); + delete this; + return(true); + } + } + } + return(false); + } + + /* + ** Normal building unlimbo process. + */ + if (TechnoClass::Unlimbo(coord, dir)) { + + /* + ** Ensure that the owning house knows about the + ** new object. + */ + House->BScan |= (1L << Class->Type); + House->ActiveBScan |= (1L << Class->Type); + + /* + ** Update the total factory type, assuming this building has a factory. + */ + switch (Class->ToBuild) { + case RTTI_AIRCRAFTTYPE: + House->AircraftFactories++; + break; + + case RTTI_INFANTRYTYPE: + House->InfantryFactories++; + break; + + case RTTI_UNITTYPE: + House->UnitFactories++; + break; + + case RTTI_BUILDINGTYPE: + House->BuildingFactories++; + break; + + default: + break; + } + + /* + ** Possibly the sidebar will be affected by this addition. + */ + House->IsRecalcNeeded = true; + LastStrength = 0; + + if ((!IsDiscoveredByPlayer && Map[Coord_Cell(coord)].IsVisible) || GameToPlay != GAME_NORMAL) { + Revealed(PlayerPtr); + } + if (!House->IsHuman) { + Revealed(House); + } + + if (IsOwnedByPlayer) { + Map.PowerClass::IsToRedraw = true; + Map.Flag_To_Redraw(false); + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * BuildingClass::Take_Damage -- Inflicts damage points upon a building. * + * * + * This routine will inflict damage points upon the specified building. * + * It will handle the damage animation and building destruction. Use * + * this routine whenever a building is attacked. * + * * + * INPUT: damage -- Amount of damage to inflict. * + * * + * distance -- The distance from the damage center point to the object's center point.* + * * + * warhead -- The kind of damage to inflict. * + * * + * source -- The source of the damage. This is used to change targeting. * + * * + * OUTPUT: true/false; Was the building destroyed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/21/1991 : Created. * + * 04/15/1994 JLB : Converted to member function. * + * 04/16/1994 JLB : Added warhead modifier to damage. * + * 06/03/1994 JLB : Added source of damage as target value. * + * 06/20/1994 JLB : Source is a base class pointer. * + * 11/22/1994 JLB : Shares base damage handler for techno objects. * + * 07/15/1995 JLB : Power ratio gets adjusted. * + *=============================================================================================*/ +ResultType BuildingClass::Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source) +{ + Validate(); + ResultType res = RESULT_NONE; + int shakes; + + if (this != source) { + if (source) Base_Is_Attacked(source); + + short const *offset = Occupy_List(); + + /* + ** SPECIAL CASE: + ** SAM sites that are closed will take half damage, but never less than one point. + */ + if (*this == STRUCT_SAM && Status == SAM_UNDERGROUND) { + damage /= 2; + damage++; + } + + /* + ** Damage from an ion cannon against the Temple of Nod does more damage than + ** usual. + */ + if (GameToPlay == GAME_NORMAL && *this == STRUCT_TEMPLE && warhead == WARHEAD_PB) { + damage += damage/2; + } + + /* + ** Perform the low level damage assessment. + */ + res = TechnoClass::Take_Damage(damage, distance, warhead, source); + + switch (res) { + case RESULT_DESTROYED: + + /* + ** Destroy all attached objects. + */ + while (Attached_Object()) { + FootClass * obj = Detach_Object(); + + Detach_All(true); + delete obj; + } + + Sound_Effect(VOC_XPLOBIG4, Coord); + while (*offset != REFRESH_EOL) { + CELL cell = Coord_Cell(Coord) + *offset++; + + /* + ** If the building is destroyed, then lots of + ** explosions occur. + */ + new SmudgeClass(Random_Pick(SMUDGE_CRATER1, SMUDGE_CRATER6), Cell_Coord(cell)); + if (Random_Pick(0, 1) == 0) { + new AnimClass(ANIM_FIRE_SMALL, Coord_Scatter(Cell_Coord(cell), 0x0080), Random_Pick(0, 7), Random_Pick(1, 3)); + if (Random_Pick(0, 1) == 0) { + new AnimClass(ANIM_FIRE_MED, Coord_Scatter(Cell_Coord(cell), 0x0040), Random_Pick(0, 7), Random_Pick(1, 3)); + } + } + //Start_Profiler(); + new AnimClass(ANIM_FBALL1, Coord_Scatter(Cell_Coord(cell), 0x0040), Random_Pick(0,3)); + } + + shakes = Class->Cost_Of() / 400; + if (shakes) { + Shake_Screen(shakes); + } + Sound_Effect(VOC_CRUMBLE, Coord); + if (Mission == MISSION_DECONSTRUCTION) { + CountDown = 0; + Set_Rate(0); + } else { + CountDown = 8; + } + + /* + ** A destuction of the Temple by an ion cannon requires a global + ** remembering of this fact. The finale uses this information to + ** play the correct movie. + */ + if (*this == STRUCT_TEMPLE && warhead == WARHEAD_PB) { + TempleIoned = true; + } else { + TempleIoned = false; + } + break; + + case RESULT_HALF: + if (*this == STRUCT_PUMP) { + AnimClass *anim = new AnimClass(ANIM_OILFIELD_BURN, Coord_Add(Coord, 0x00400130L), 1); + if (anim) { + anim->Attach_To(this); + } + } + // Fall into next case. + + case RESULT_MAJOR: + Sound_Effect(VOC_XPLOBIG4, Coord); + while (*offset != REFRESH_EOL) { + CELL cell = Coord_Cell(Coord) + *offset++; + AnimClass * anim = NULL; + + /* + ** Show pieces of fire to indicate that a significant change in + ** damage level has occurred. + */ + if (warhead == WARHEAD_FIRE) { + switch (Random_Pick(0, 13)) { + case 0: + case 1: + case 2: + case 3: + case 4: + anim = new AnimClass(ANIM_ON_FIRE_SMALL, Coord_Scatter(Cell_Coord(cell), 0x0060), 0, Random_Pick(1, 3)); + break; + + case 5: + case 6: + case 7: + anim = new AnimClass(ANIM_ON_FIRE_MED, Coord_Scatter(Cell_Coord(cell), 0x0060), 0, Random_Pick(1, 3)); + break; + + case 8: + anim = new AnimClass(ANIM_ON_FIRE_BIG, Coord_Scatter(Cell_Coord(cell), 0x0060), 0, 1); + break; + + case 9: + case 10: + case 11: + case 12: + case 13: + break; + } + } else { + if (Random_Pick(0, 1) == 0) { + anim = new AnimClass(ANIM_FIRE_SMALL, Coord_Scatter(Cell_Coord(cell), 0x0060), Random_Pick(0, 7), Random_Pick(1, 3)); + } + } + + /* + ** If the animation was created, then attach it to the building. + */ + if (anim) { + anim->Attach_To(this); + } + } + break; + + case RESULT_NONE: + break; + } + + if (source && res != RESULT_NONE) { + + /* + ** If any damage occurred, then inform the house of this fact. If it is the player's + ** house, it might announce this fact. + */ + House->Attacked(); + + /* + ** Save the type of the house that's doing the damage, so if the building burns + ** to death credit can still be given for the kill + */ + WhoLastHurtMe = source->Owner(); + + /* + ** When certain buildings are hit, they "snap out of it" and + ** return fire if they are able and allowed. + */ + if (*this != STRUCT_SAM && + !House->Is_Ally(source) && + Class->Primary != WEAPON_NONE && + (!Target_Legal(TarCom) || !In_Range(TarCom))) { + + if (source->What_Am_I() != RTTI_AIRCRAFT && (!House->IsHuman || Special.IsSmartDefense)) { + Assign_Target(source->As_Target()); + } else { + + /* + ** Generate a random rotation effect since there is nothing else that this + ** building can do. + */ + if (!PrimaryFacing.Is_Rotating()) { + PrimaryFacing.Set_Desired(Random_Pick(DIR_N, DIR_MAX)); + } + } + } + } + } + + return(res); +} + + +/*********************************************************************************************** + * BuildingClass::Look -- Reveal map around building. * + * * + * Given a building, reveal the cells around the building in accordance * + * with the building's sighting range. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This is a very slow routine. Call only when necessary. * + * * + * HISTORY: * + * 08/05/1992 JLB : Created. * + * 04/15/1994 JLB : Converted to member function. * + *=============================================================================================*/ +void BuildingClass::Look(bool) +{ + Validate(); + if (IsOwnedByPlayer || IsDiscoveredByPlayer) { + Map.Sight_From(Coord_Cell(Center_Coord()), Class->SightRange, false); + } +} + + +/*********************************************************************************************** + * BuildingClass::new -- Allocates a building object from building pool. * + * * + * This routine will allocate a building slot from the building alloc * + * system. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the allocated building. If NULL is * + * returned, then this indicates a failure to allocate. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/11/1994 JLB : Created. * + * 04/21/1994 JLB : Converted to operator new. * + * 05/17/1994 JLB : Revamped allocation scheme * + * 07/29/1994 JLB : Simplified. * + *=============================================================================================*/ +void * BuildingClass::operator new(size_t ) +{ + void * ptr = Buildings.Allocate(); + if (ptr) { + ((BuildingClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +/*********************************************************************************************** + * BuildingClass::delete -- Deallocates building object. * + * * + * This is the memory deallocation operation for a building object. * + * Since buildings are allocated out of a fixed memory block, all that * + * is needed is to flag the unit as inactive. * + * * + * INPUT: ptr -- Pointer to building to deallocate. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/21/1994 JLB : Created. * + *=============================================================================================*/ +void BuildingClass::operator delete(void *ptr) +{ + if (ptr) { + ((BuildingClass *)ptr)->IsActive = false; + } + Buildings.Free((BuildingClass *)ptr); +} + + +/*********************************************************************************************** + * BuildingClass::BuildingClass -- Constructor for buildings. * + * * + * This routine inserts a building into the object tracking system. * + * It is placed into a limbo state unless a location is provided for * + * it to unlimbo at. * + * * + * INPUT: type -- The structure type to make this object. * + * * + * house -- The owner of this building. * + * * + * pos -- The position to unlimbo the building. If -1 is * + * specified, then the building remains in a limbo * + * state. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/21/1994 JLB : Created. * + * 08/07/1995 JLB : Fixed act like value to match expected value. * + *=============================================================================================*/ +BuildingClass::BuildingClass(StructType type, HousesType house) : + Class(&BuildingTypeClass::As_Reference(type)), + TechnoClass(house) +{ + PlacementDelay = 0; + LastStrength = 0; + ActLike = House->ActLike; + BState = BSTATE_NONE; + CountDown.Set(0); + Factory = 0; + House->CurBuildings++; + WhomToRepay = TARGET_NONE; + IsCaptured = false; + IsCharged = false; + IsCharging = false; + IsSurvivorless = false; + IsGoingToBlow = false; + IsReadyToCommence = false; + IsRepairing = false; + IsSecondShot = !Class->IsTwoShooter; + IsWrenchVisible = false; + QueueBState = BSTATE_NONE; + Strength = Class->MaxStrength; + WhoLastHurtMe = house; + Ammo = Class->MaxAmmo; + + /* + ** Make sure that newly built house specific building types will act like + ** the house they are supposed to act like, regardless of who the current + ** owner may happen to be. + */ + if ((type == STRUCT_AIRSTRIP || type == STRUCT_HAND) && house != HOUSE_BAD) { + ActLike = HOUSE_BAD; + IsCaptured = true; + } + if ((type == STRUCT_WEAP || type == STRUCT_BARRACKS) && house != HOUSE_GOOD) { + ActLike = HOUSE_GOOD; + IsCaptured = true; + } + + if (GameToPlay == GAME_INTERNET){ + House->BuildingTotals->Increment_Unit_Total( (int) type); + } + +} + + +/*********************************************************************************************** + * BuildingClass::~BuildingClass -- Destructor for building type objects. * + * * + * This destructor for building objects will put the building in limbo if possible. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/18/1995 JLB : Created. * + *=============================================================================================*/ +BuildingClass::~BuildingClass(void) +{ + if (GameActive && Class) { + if (House) { + House->CurBuildings--; + } + Limbo(); + } +} + + +/*********************************************************************************************** + * BuildingClass::Drop_Debris -- Drops rubble when building is destroyed. * + * * + * This routine is called when a building is destroyed. It handles * + * placing the rubble down. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/14/1994 JLB : Created. * + * 06/13/1995 JLB : Added smoke and normal infantry survivor possibility. * + * 07/16/1995 JLB : Survival rate depends on if captured or sabotaged. * + *=============================================================================================*/ +void BuildingClass::Drop_Debris(TARGET source) +{ + Validate(); + CELL const *offset; + CELL cell; + + /* + ** Special case for Moebius to run from destroyed technology + ** building. + */ + if (GameToPlay == GAME_NORMAL && *this == STRUCT_MISSION && PlayerPtr->ActLike == HOUSE_BAD && Scenario == 10) { + InfantryClass * i = new InfantryClass(INFANTRY_CHAN, House->Class->House); + + ScenarioInit++; + if (i->Unlimbo(Center_Coord(), DIR_N)) { + i->Trigger = TriggerClass::As_Pointer("CHAN"); + i->Strength = Random_Pick(5, (int)i->Class->MaxStrength); + ScenarioInit--; + i->Scatter(0, true); + ScenarioInit++; + i->Assign_Mission(MISSION_GUARD_AREA); + } else { + delete i; + } + ScenarioInit--; + } + + + /* + ** Generate random survivors from the destroyed building. + */ + cell = Coord_Cell(Coord); + offset = Occupy_List(); + int odds = 2; + if (Target_Legal(WhomToRepay)) odds -= 1; + if (IsCaptured) odds += 6; + while (*offset != REFRESH_EOL) { + CELL newcell; + + newcell = cell + *offset++; + + /* + ** Infantry could run out of a destroyed building. + */ + if (!House->IsToDie && !IsSurvivorless) { + InfantryClass * i = NULL; + + if (Random_Pick(0, odds) == 1) { + i = new InfantryClass(Crew_Type(), House->Class->House); + if (i) { + if (Class->Get_Buildup_Data() != NULL && i->Class->IsNominal) i->IsTechnician = true; + ScenarioInit++; + if (i->Unlimbo(Cell_Coord(newcell), DIR_N)) { + i->Strength = Random_Pick(5, (int)i->Class->MaxStrength); + i->Scatter(0, true); + if (source != TARGET_NONE && !House->Is_Ally(As_Object(source))) { + i->Assign_Mission(MISSION_ATTACK); + i->Assign_Target(source); + } else { + if (House->IsHuman) { + i->Assign_Mission(MISSION_GUARD); + } else { + i->Assign_Mission(MISSION_HUNT); + } + } + } else { + delete i; + } + ScenarioInit--; + } + } + } + + /* + ** Possibly add some smoke rising from the ashes of the building. + */ + switch (Random_Pick(0, 5)) { + case 0: + case 1: + case 2: + new AnimClass(ANIM_SMOKE_M, Coord_Scatter(Cell_Coord(newcell), 0x0050, false), Random_Pick(0, 5), Random_Pick(1, 2)); + break; + + default: + break; + } + + /* + ** The building always scars the ground in some fashion. + */ + if (Random_Pick(0, 3) == 0) { + new SmudgeClass(Random_Pick(SMUDGE_SCORCH1, SMUDGE_SCORCH6), Cell_Coord(newcell)); + } else { + new SmudgeClass(Random_Pick(SMUDGE_CRATER1, SMUDGE_CRATER6), Coord_Scatter(Cell_Coord(newcell), 0x0080, false)); + } + } +} + + +/*********************************************************************************************** + * BuildingClass::Active_Click_With -- Handles clicking on the map while the building is selected.* + * * + * This interface routine handles when the player clicks on the map while this building * + * is currently selected. This is used to assign an override target to a turret or * + * guard tower. * + * * + * INPUT: target -- The target that was clicked upon. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +void BuildingClass::Active_Click_With(ActionType action, ObjectClass * object) +{ + Validate(); + if (action == ACTION_ATTACK) { + Player_Assign_Mission(MISSION_ATTACK, object->As_Target()); + } + + if (action == ACTION_SELF && Class->IsFactory) { + OutList.Add(EventClass(EventClass::PRIMARY, As_Target())); + } +} + + +/*********************************************************************************************** + * BuildingClass::Active_Click_With -- Handles cell selection for buildings. * + * * + * This routine really only serves one purpose -- to allow targeting of the ground for * + * buildings that are euipped with weapons. * + * * + * INPUT: action -- The requested action to perform. * + * * + * cell -- The cell location to perform the action upon. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/04/1995 JLB : Created. * + *=============================================================================================*/ +void BuildingClass::Active_Click_With(ActionType action, CELL cell) +{ + Validate(); + if (action == ACTION_ATTACK) { + Player_Assign_Mission(MISSION_ATTACK, ::As_Target(cell)); + } +} + + +/*********************************************************************************************** + * BuildingClass::Assign_Target -- Assigns a target to the building. * + * * + * Assigning of a target to a building makes sense if the building is one that can attack. * + * This routine would be used to assign the attack target to a turret or guard tower. * + * * + * INPUT: target -- The target that was clicked on while this building was selected. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + * 11/02/1994 JLB : Checks for range before assigning target. * + *=============================================================================================*/ +void BuildingClass::Assign_Target(TARGET target) +{ + Validate(); + if (*this != STRUCT_SAM && !In_Range(target, 0)) { + target = TARGET_NONE; + } + + TechnoClass::Assign_Target(target); +} + + +/*********************************************************************************************** + * BuildingClass::Init -- Initialize the building system to an empty null state. * + * * + * This routine initializes the building system in preparation for a scenario load. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +void BuildingClass::Init(void) +{ + BuildingClass *ptr; + + Buildings.Free_All(); + + ptr = new BuildingClass(); + VTable = ((void **)(((char *)ptr) + sizeof(AbstractClass) - 4))[0]; + delete ptr; +} + + +/*********************************************************************************************** + * BuildingClass::Exit_Object -- Initiates an object to leave the building. * + * * + * This function is used to cause an object to exit the building. It is called when a * + * factory produces a vehicle or other mobile object and that object needs to exit the * + * building to join the ranks of a regular unit. Typically, the object is placed down on * + * the map such that it overlaps the building and then it is given a movement order so that * + * it will move to an adjacent free cell. * + * * + * INPUT: base -- Pointer to the object that is to exit the building. * + * * + * OUTPUT: Returns the success rating for the exit attempt; * + * 0 = complete failure (refund money please) * + * 1 = temporarily prevented (try again later please) * + * 2 = successful * + * * + * WARNINGS: The building is placed in radio contact with the object. The object is in a * + * teathered condition. This condition will be automatically broken when the * + * object reaches the adjacent square. * + * * + * HISTORY: * + * 11/28/1994 JLB : Created. * + * 04/10/1995 JLB : Handles building production by computer. * + * 06/17/1995 JLB : Handles refinery exit. * + *=============================================================================================*/ +int BuildingClass::Exit_Object(TechnoClass * base) +{ + Validate(); + if (!base) return(0); + + TechnoTypeClass const * ttype = (TechnoTypeClass const *)&base->Class_Of(); + + /* + ** A unit exiting a building is always considered to be "locked". That means, it + ** will be considered as to have legally entered the visible map domain. + */ + base->IsLocked = true; + + /* + ** Find a good cell to unload the object to. The object, probably a vehicle + ** will drive/walk to the adjacent free cell. + */ + switch (base->What_Am_I()) { + CELL cell; + + case RTTI_AIRCRAFT: + if (!In_Radio_Contact()) { + AircraftClass *air = (AircraftClass *)base; + + air->Altitude = 0; + ScenarioInit++; + if (air->Unlimbo(Docking_Coord(), air->Pose_Dir())) { + Transmit_Message(RADIO_HELLO, air); + Transmit_Message(RADIO_TETHER); + ScenarioInit--; + return(2); + } + ScenarioInit--; + } else { + AircraftClass *air = (AircraftClass *)base; + + if (Cell_X(Coord_Cell(Center_Coord())) - Map.MapCellX < Map.MapCellWidth/2) { + cell = XY_Cell(Map.MapCellX-1, Random_Pick(0, Map.MapCellHeight-1)+Map.MapCellY); + } else { + cell = XY_Cell(Map.MapCellX+Map.MapCellWidth, Random_Pick(0, Map.MapCellHeight-1)+Map.MapCellY); + } + ScenarioInit++; + if (air->Unlimbo(Cell_Coord(cell), DIR_N)) { + air->Assign_Destination(::As_Target(Nearby_Location(air))); + air->Assign_Mission(MISSION_MOVE); + ScenarioInit--; + return(2); + } + ScenarioInit--; + } + break; + + case RTTI_INFANTRY: + case RTTI_UNIT: + switch (Class->Type) { + case STRUCT_REFINERY: + if (base->What_Am_I() == RTTI_UNIT) { + CELL cell = Coord_Cell(Center_Coord()); + UnitClass * unit = (UnitClass *)base; + + cell = Adjacent_Cell(cell, FACING_SW); + ScenarioInit++; + if (unit->Unlimbo(Coord_Add(unit->Coord, 0x00550060L), DIR_SW_X2)) { + unit->PrimaryFacing = DIR_SW_X2; + Transmit_Message(RADIO_HELLO, unit); + Transmit_Message(RADIO_TETHER); + unit->Assign_Mission(MISSION_HARVEST); + unit->Force_Track(DriveClass::OUT_OF_REFINERY, Cell_Coord(cell)); + unit->Set_Speed(128); + } + ScenarioInit--; + } else { + base->Scatter(true); + } + break; + + case STRUCT_AIRSTRIP: + if (Create_Special_Reinforcement(House, &AircraftTypeClass::As_Reference(AIRCRAFT_CARGO), ttype, TMISSION_UNLOAD, As_Target())) { + delete base; + return(2); + } + return(0); + + case STRUCT_WEAP: + ScenarioInit++; + if (base->Unlimbo(Coord_Add(Coord, Class->ExitPoint), DIR_SW)) { +// base->Assign_Mission(MISSION_MOVE); +// base->Assign_Destination(::As_Target(As_Cell(Coord)+MAP_CELL_W*2)); + base->Mark(MARK_UP); + base->Coord = Coord_Add(Coord, Class->ExitPoint); + base->Mark(MARK_DOWN); + Transmit_Message(RADIO_HELLO, base); + Transmit_Message(RADIO_TETHER); + Assign_Mission(MISSION_UNLOAD); + ScenarioInit--; + return(2); + } + ScenarioInit--; + break; + + case STRUCT_BARRACKS: + case STRUCT_HAND: + CELL cell; + bool found = false; + + cell = Find_Exit_Cell(base); + if (cell) found = true; + +#ifdef OBSOLETE + CELL const *ptr; + bool found = false; + + ptr = Class->ExitList; + while (*ptr != REFRESH_EOL) { + cell = Coord_Cell(Coord) + *ptr++; + if (base->Can_Enter_Cell(cell) == MOVE_OK) { + found = true; + break; + } + } +#endif + + if (found) { + DirType dir = Direction(cell); + COORDINATE start = Coord_Add(Coord, Class->ExitPoint); + + ScenarioInit++; + if (base->Unlimbo(start, dir)) { + + base->Assign_Mission(MISSION_MOVE); + base->Assign_Destination(::As_Target(cell)); + + /* + ** Establish radio contact so unload coordination can occur. This + ** radio contact should always succeed. + */ + if (Transmit_Message(RADIO_HELLO, base) == RADIO_ROGER) { + Transmit_Message(RADIO_UNLOAD); + } + ScenarioInit--; + return(2); + } + ScenarioInit--; + } + break; + } + break; + + case RTTI_BUILDING: + + if (!House->IsHuman) { + /* + ** Find the next available spot to place this newly created building. If the + ** building could be placed at the desired location, fine. If not, then this + ** routine will return failure. The calling routine will probably abandon this + ** building in preference to building another. + */ + BaseNodeClass * node = Base.Next_Buildable(((BuildingClass *)base)->Class->Type); + if (node) { + if (Flush_For_Placement(base, Coord_Cell(node->Coord))) { + return(1); + } + if (base->Unlimbo(node->Coord)) { + return(2); + } + } + } + break; + } + + /* + ** Failure to exit the object results in a false return value. + */ + return(0); +} + + +/*********************************************************************************************** + * BuildingClass::Update_Buildables -- Informs sidebar of additional construction options. * + * * + * This routine will tell the sidebar of objects that can be built. The function is called * + * whenever a building matures. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/11/1994 JLB : Created. * + * 12/23/1994 JLB : Only updates for PLAYER buildings. * + *=============================================================================================*/ +void BuildingClass::Update_Buildables(void) +{ + Validate(); + if (House == PlayerPtr && !IsInLimbo && IsDiscoveredByPlayer) { + switch (Class->ToBuild) { + StructType i; + UnitType u; + InfantryType f; + AircraftType a; + + case RTTI_BUILDINGTYPE: + for (i = STRUCT_FIRST; i < STRUCT_COUNT; i++) { + if (PlayerPtr->Can_Build(i, ActLike)) { +// if (BuildingTypeClass::As_Reference(i).Who_Can_Build_Me(true, true, ActLike)) { + Map.Add(RTTI_BUILDINGTYPE, i); +// } + } + } + break; + + case RTTI_UNITTYPE: + for (u = UNIT_FIRST; u < UNIT_COUNT; u++) { + if (PlayerPtr->Can_Build(u, ActLike)) { +// if (UnitTypeClass::As_Reference(u).Who_Can_Build_Me(true, true, ActLike)) { + Map.Add(RTTI_UNITTYPE, u); +// } + } + } + break; + + case RTTI_INFANTRYTYPE: + for (f = INFANTRY_FIRST; f < INFANTRY_COUNT; f++) { + if (PlayerPtr->Can_Build(f, ActLike)) { +// if (InfantryTypeClass::As_Reference(f).Who_Can_Build_Me(true, true, ActLike)) { + Map.Add(RTTI_INFANTRYTYPE, f); +// } + } + } + break; + + case RTTI_AIRCRAFTTYPE: + for (a = AIRCRAFT_FIRST; a < AIRCRAFT_COUNT; a++) { + if (PlayerPtr->Can_Build(a, ActLike)) { +// if (AircraftTypeClass::As_Reference(a).Who_Can_Build_Me(true, true, ActLike)) { + Map.Add(RTTI_AIRCRAFTTYPE, a); +// } + } + } + break; + } + } +} + + +/*********************************************************************************************** + * BuildingClass::Fire_Out -- Handles when attached animation expires. * + * * + * This routine is used to perform any fixups necessary when the attached animation has * + * terminated. This occurs when the fire & smoke animation that a SAM site produces stops. * + * At that point, normal reload procedures can commence. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/30/1994 JLB : Created. * + *=============================================================================================*/ +void BuildingClass::Fire_Out(void) +{ + Validate(); +// SAM = SAM_READY; +// IsFiring = false; +} + + +/*********************************************************************************************** + * BuildingClass::Limbo -- Handles power adjustment as building goes into limbo. * + * * + * This routine will handle the power adjustments for the associated house when the * + * building goes into limbo. This means that its power drain or production is subtracted * + * from the house accumulated totals. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the building limboed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +bool BuildingClass::Limbo(void) +{ + Validate(); +// HouseClass *housep; +// RTTIType bld_type; + + if (!IsInLimbo) { + + /* + ** Update the total factory type, assuming this building has a factory. + */ + switch (Class->ToBuild) { + case RTTI_AIRCRAFTTYPE: + House->AircraftFactories--; + break; + + case RTTI_INFANTRYTYPE: + House->InfantryFactories--; + break; + + case RTTI_UNITTYPE: + House->UnitFactories--; + break; + + case RTTI_BUILDINGTYPE: + House->BuildingFactories--; + break; + + default: + break; + } + + House->IsRecalcNeeded = true; + + /* + ** Update the power status of the owner's house. + */ + House->Adjust_Power(-Power_Output()); + House->Adjust_Drain(-Class->Drain); + House->Adjust_Capacity(-Class->Capacity, true); + if (House == PlayerPtr) { + Map.PowerClass::IsToRedraw = true; + Map.Flag_To_Redraw(false); + } + +#ifdef OBSOLETE + /* + ** If this building is building something, the FactoryClass doing the + ** building must be deleted, as well as the object being built. + ** - If this building is controlled by Computer AI, this building's Factory + ** pointer will point to the factory doing the building. + ** - Otherwise, the owner House's Factory indices will indicate what's + ** being built. (The player's sidebar also contains indices to indicate + ** what's being built, but those just echo the house's indices.) + */ + if (Factory) { + Factory->Abandon(); + delete Factory; + Factory = 0; + } + + /* + ** If the owner HouseClass is building something, and this building can + ** build that thing, we may be the last building for that house that can + ** build that thing; if so, abandon production of it. + */ + housep = HouseClass::As_Pointer(Owner()); + if (housep) { + FactoryClass * factory = 0; + bld_type = RTTI_NONE; + if (housep->AircraftFactory != -1 && Class->ToBuild == RTTI_AIRCRAFTTYPE) { + bld_type = RTTI_AIRCRAFTTYPE; + factory = Factories.Raw_Ptr(housep->AircraftFactory); + } + if (housep->InfantryFactory != -1 && Class->ToBuild==RTTI_INFANTRYTYPE) { + bld_type = RTTI_INFANTRYTYPE; + factory = Factories.Raw_Ptr(housep->InfantryFactory); + } + if (housep->UnitFactory != -1 && Class->ToBuild==RTTI_UNITTYPE) { + bld_type = RTTI_UNITTYPE; + factory = Factories.Raw_Ptr(housep->UnitFactory); + } + if (housep->BuildingFactory != -1 && Class->ToBuild==RTTI_BUILDINGTYPE) { + bld_type = RTTI_BUILDINGTYPE; + factory = Factories.Raw_Ptr(housep->BuildingFactory); + } + if (housep->SpecialFactory != -1 && Class->ToBuild==RTTI_SPECIAL) { + bld_type = RTTI_SPECIAL; + } + + if (bld_type != RTTI_NONE) { + if (factory) { + TechnoClass * object = factory->Get_Object(); + IsInLimbo = true; + if (object && !object->Techno_Type_Class()->Who_Can_Build_Me(true, false, housep->Class->House)) { + housep->Abandon_Production(bld_type); + } + IsInLimbo = false; + } + } + } +#endif + + /* + ** This could be a building that builds. If so, then the sidebar may need adjustment. + ** Set IsInLimbo to true to "fool" the sidebar into knowing that this building + ** isn't available. Set it back to false so the rest of the Limbo code works. + ** Otherwise, the sidebar won't properly remove non-available buildables. + */ + if (IsOwnedByPlayer && !ScenarioInit) { + IsInLimbo = true; + Map.Recalc(); + IsInLimbo = false; + } +#ifdef NEVER + if (!House->IsHuman) { + Update_Specials(); + } +#endif + + } + return(TechnoClass::Limbo()); +} + + +/*********************************************************************************************** + * BuildingClass::Fire_Coord -- Calculates the coordinate that projectile would appear. * + * * + * This routine is used to determine where a projectile would appear if this building * + * were to fire. The location usually depends on the current rotation setting of the * + * turret or else on the direction of the target that will be fired upon. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with coordinate that the projectile should appear at if the building * + * were to fire. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +COORDINATE BuildingClass::Fire_Coord(int ) const +{ + Validate(); + COORDINATE coord = Center_Coord(); // Center of firing building. + + /* + ** Make adjustments to the firing coordinate to account for turret + ** position. This depends on building type and turret facing. + */ + switch (Class->Type) { + default: + case STRUCT_GTOWER: + case STRUCT_ATOWER: + coord = Coord_Move(coord, DIR_N, 0x0030); + if (Target_Legal(TarCom)) { + coord = Coord_Move(coord, ::Direction(coord, As_Coord(TarCom)), 0x0040); + } + break; + case STRUCT_OBELISK: + coord = Coord_Move(coord, DIR_N, 0x00A8); +// coord = Coord_Move(coord, DIR_N, 0x0058); + coord = Coord_Move(coord, DIR_W, 0x0018); + break; + + case STRUCT_SAM: + case STRUCT_TURRET: + coord = Coord_Move(coord, DIR_N, 0x0030); + coord = Coord_Move(coord, PrimaryFacing.Current(), 0x0080); + break; + } + + return(coord); +} + + +/*********************************************************************************************** + * BuildingClass::Greatest_Threat -- Searches for target that building can fire upon. * + * * + * This routine intercepts the Greatest_Threat function so that it can add the ability * + * to search for ground targets, if this isn't a SAM site. * + * * + * INPUT: threat -- The base threat control value. Typically, it might be THREAT_RANGE * + * or THREAT_NORMAL. * + * * + * OUTPUT: Returns with a suitable target. If none could be found, then TARGET_NONE is * + * returned instead. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/01/1995 JLB : Created. * + *=============================================================================================*/ +TARGET BuildingClass::Greatest_Threat(ThreatType threat) const +{ + Validate(); + if (*this != STRUCT_SAM) { + threat = threat | (THREAT_INFANTRY|THREAT_BOATS|THREAT_VEHICLES|THREAT_RANGE); + + if (!House->IsHuman) { + threat = threat | THREAT_BUILDINGS; + } +// threat = threat & ~THREAT_AIR; + } else { + threat = threat | THREAT_AREA; + } + + if (Class->Primary != WEAPON_NONE && BulletTypeClass::As_Reference(Weapons[Class->Primary].Fires).IsAntiAircraft) { + threat = threat | THREAT_AIR; + } + return(TechnoClass::Greatest_Threat(threat)); +} + + +/*********************************************************************************************** + * BuildingClass::Grand_Opening -- Handles construction completed special operations. * + * * + * This routine is called when construction has finished. Typically, this enables * + * new production options for factories. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/08/1995 JLB : Created. * + * 06/13/1995 JLB : Added helipad. * + *=============================================================================================*/ +void BuildingClass::Grand_Opening(bool captured) +{ + Validate(); + + /* + ** Adjust the owning house according to the power, drain, and Tiberium capacity that + ** this building has. + */ + House->Adjust_Drain(Class->Drain); + House->Adjust_Capacity(Class->Capacity); + House->IsRecalcNeeded = true; + + /* SPECIAL CASE: + ** Refineries get a free harvester. Add a harvester to the reinforcement list + ** at this time. + */ + if (*this == STRUCT_REFINERY && !ScenarioInit && !captured && !Debug_Map && (!House->IsHuman || PurchasePrice == 0 || PurchasePrice > Class->Raw_Cost())) { + CELL cell = Coord_Cell(Adjacent_Cell(Center_Coord(), DIR_SW)); +// if (!Map[cell].Cell_Unit()) { + UnitClass * unit = new UnitClass(UNIT_HARVESTER, House->Class->House); + if (unit) { + + /* + ** Try to place down the harvesters. If it could not be placed, then try + ** to place it in a nearby location. + */ + if (!unit->Unlimbo(Cell_Coord(cell), DIR_SW)) { + cell = Nearby_Location(unit); + + /* + ** If the harvester could still not be placed, then refund the money + ** to the owner and then bail. + */ + if (!unit->Unlimbo(Cell_Coord(cell), DIR_SW)) { + House->Refund_Money(unit->Class->Cost_Of()); + delete unit; + } + } + } else { + + /* + ** If the harvester could not be created in the first place, then give + ** the full refund price to the owning player. + */ + House->Refund_Money(UnitTypeClass::As_Reference(UNIT_HARVESTER).Cost_Of()); + } + } +// } + + /* + ** Helicopter pads get a free attack helicopter. + */ + if (*this == STRUCT_HELIPAD && !captured && (!House->IsHuman || PurchasePrice == 0 || PurchasePrice > Class->Raw_Cost())) { + ScenarioInit++; + AircraftClass * air = 0; + if (House->ActLike == HOUSE_GOOD) { + air = new AircraftClass(AIRCRAFT_ORCA, House->Class->House); + } else { + air = new AircraftClass(AIRCRAFT_HELICOPTER, House->Class->House); + } + if (air) { + air->Altitude = 0; + if (air->Unlimbo(Docking_Coord(), air->Pose_Dir())) { + air->Assign_Mission(MISSION_GUARD); + air->Transmit_Message(RADIO_HELLO, this); + Transmit_Message(RADIO_TETHER); + } + } + ScenarioInit--; + } +} + + +/*********************************************************************************************** + * BuildingClass::Repair -- Initiates or terminates the repair process. * + * * + * This routine will start, stop, or toggle the repair process. When a building repairs, it * + * occurs incrementally over time. * + * * + * INPUT: control -- Determines how to control the repair process. * + * 0: Turns repair process off (if it was on). * + * 1: Turns repair process on (if it was off). * + * -1:Toggles repair process to other state. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/08/1995 JLB : Created. * + *=============================================================================================*/ +void BuildingClass::Repair(int control) +{ + Validate(); + switch (control) { + case -1: + IsRepairing = (IsRepairing == false); + break; + + case 1: + if (IsRepairing) return; + IsRepairing = true; + break; + + case 0: + if (!IsRepairing) return; + IsRepairing = false; + break; + } + + /* + ** At this point, we know that the repair state has changed. Perform + ** appropriate action. + */ + VocType sound = VOC_NONE; + if (IsRepairing) { + if (Strength == Class->MaxStrength) { + sound = VOC_SCOLD; + } else { + sound = VOC_BUTTON; + Clicked_As_Target(); + IsWrenchVisible = true; + } + } else { + sound = VOC_BUTTON; + } + if (IsOwnedByPlayer) { + Sound_Effect(sound, Coord); + } +} + + +/*********************************************************************************************** + * BuildingClass::Sell_Back -- Controls the sell back (demolish) operation. * + * * + * This routine will initiate or stop the sell back process for a building. It is called * + * when the player clicks on a building when the sell mode is active. * + * * + * INPUT: control -- The action to perform. 0 = turn deconstruction off, 1 = deconstruct, * + * -1 = toggle deconstruction state. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +void BuildingClass::Sell_Back(int control) +{ + Validate(); + if (Class->Get_Buildup_Data()) { + bool decon = false; + switch (control) { + case -1: + decon = (Mission != MISSION_DECONSTRUCTION); + break; + + case 1: + if (Mission == MISSION_DECONSTRUCTION) return; + decon = true; + break; + + case 0: + if (Mission != MISSION_DECONSTRUCTION) return; + decon = false; + break; + } + + /* + ** At this point, we know that the repair state has changed. Perform + ** appropriate action. + */ + if (decon) { +// Transmit_Message(RADIO_RUN_AWAY); +// Transmit_Message(RADIO_OVER_OUT); + Assign_Mission(MISSION_DECONSTRUCTION); + if (IsOwnedByPlayer) { + Clicked_As_Target(); + } + } + if (IsOwnedByPlayer) { + Sound_Effect(VOC_BUTTON); + } + } +} + + +/*********************************************************************************************** + * BuildingClass::What_Action -- Determines action to perform if click on specified object. * + * * + * This routine will determine what action to perform if the mouse was clicked on the * + * object specified. This determination is used to control the mouse imagery and the * + * function process when the mouse button is pressed. * + * * + * INPUT: object -- Pointer to the object that, if clicked on, will control what action * + * is to be performed. * + * * + * OUTPUT: Returns with the ActionType that will occur if the mouse is clicked over the * + * object specified while the building is currently selected. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/18/1995 JLB : Created. * + *=============================================================================================*/ +ActionType BuildingClass::What_Action(ObjectClass * object) const +{ + Validate(); + ActionType action = TechnoClass::What_Action(object); + + if (action == ACTION_SELF) { + if (Class->IsFactory && PlayerPtr == House) { + switch (Class->ToBuild) { + case RTTI_AIRCRAFTTYPE: + if (House->AircraftFactories < 2) { + action = ACTION_NONE; + } + break; + + case RTTI_INFANTRYTYPE: + if (House->InfantryFactories < 2) { + action = ACTION_NONE; + } + break; + + case RTTI_UNITTYPE: + if (House->UnitFactories < 2) { + action = ACTION_NONE; + } + break; + + default: + action = ACTION_NONE; + break; + } + + } else { + action = ACTION_NONE; + } + } + + /* + ** Don't allow targeting of SAM sites, even if the CTRL key + ** is held down. + */ + if (action == ACTION_ATTACK && *this == STRUCT_SAM) { + action = ACTION_NONE; + } + + if (action == ACTION_MOVE) { + action = ACTION_NONE; + } + + return(action); +} + + +/*********************************************************************************************** + * BuildingClass::What_Action -- Determines what action will occur. * + * * + * This routine examines the cell specified and returns with the action that will be * + * performed if that cell were clicked upon while the building is selected. * + * * + * INPUT: cell -- The cell to examine. * + * * + * OUTPUT: Returns the ActionType that indicates what should occur if the mouse is clicked * + * on this cell. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/18/1995 JLB : Created. * + *=============================================================================================*/ +ActionType BuildingClass::What_Action(CELL cell) const +{ + Validate(); + ActionType action = TechnoClass::What_Action(cell); + + if (action == ACTION_MOVE) { + action = ACTION_NONE; + } + + /* + ** Don't allow targeting of SAM sites, even if the CTRL key + ** is held down. + */ + if (action == ACTION_ATTACK && *this == STRUCT_SAM) { + action = ACTION_NONE; + } + + return(action); +} + + +/*********************************************************************************************** + * BuildingClass::Begin_Mode -- Begins an animation mode for the building. * + * * + * This routine will start the building animating. This animation will loop indefinately * + * until explicitly stopped. * + * * + * INPUT: bstate -- The animation state to initiate. * + * * + * OUTPUT: none * + * * + * WARNINGS: The buliding graphic state will reflect the first stage of this animation the * + * very next time it is rendered. * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + * 07/02/1995 JLB : Uses normalize animation rate where applicable. * + *=============================================================================================*/ +void BuildingClass::Begin_Mode(BStateType bstate) +{ + Validate(); + QueueBState = bstate; + if (BState == BSTATE_NONE || bstate == BSTATE_CONSTRUCTION || ScenarioInit) { + BState = bstate; + QueueBState = BSTATE_NONE; + BuildingTypeClass::AnimControlType const * ctrl = Fetch_Anim_Control(); + + int rate = ctrl->Rate; + if (Class->IsRegulated && bstate != BSTATE_CONSTRUCTION) { + rate = Options.Normalize_Delay(rate); + } + Set_Rate(rate); + Set_Stage(ctrl->Start); + } +} + + +/*********************************************************************************************** + * BuildingClass::Read_INI -- Reads buildings from INI file. * + * * + * This is the basic scenario initialization of building function. It * + * is called when reading the scenario startup INI file and it handles * + * creation of all specified buildings. * + * * + * INI entry format: * + * Housename, Typename, Strength, Cell, Facing, Triggername * + * * + * INPUT: buffer -- Pointer to the loaded INI file data. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/24/1994 JLB : Created. * + *=============================================================================================*/ +void BuildingClass::Read_INI(char *buffer) +{ + BuildingClass *b; // Working unit pointer. + char *tbuffer; // Accumulation buffer of unit IDs. + HousesType bhouse; // Building house. + StructType classid; // Building type. + int len; // Size of data in buffer. + CELL cell; // Cell of building. + char buf[128]; + char *trigname; // building's trigger's name + + len = strlen(buffer) + 2; + tbuffer = buffer + len; + + /* + ** Read the entire building INI section into HIDBUF + */ + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-len, buffer); + + while (*tbuffer != '\0') { + + /* + ** Get a building entry. + */ + WWGetPrivateProfileString(INI_Name(), tbuffer, NULL, buf, sizeof(buf)-1, buffer); + + /* + ** 1st token: house name. + */ + bhouse = HouseTypeClass::From_Name(strtok(buf, ",")); + + /* + ** 2nd token: building name. + */ + classid = BuildingTypeClass::From_Name(strtok(NULL, ",")); + + if (bhouse != HOUSE_NONE && classid != STRUCT_NONE) { + int strength; + DirType facing; + + /* + ** 3rd token: strength. + */ + strength = atoi(strtok(NULL, ",")); + + /* + ** 4th token: cell #. + */ + cell = atoi(strtok(NULL, ",")); + + /* + ** 5th token: facing. + */ + facing = (DirType)atoi(strtok(NULL, ",")); + + /* + ** 6th token: triggername (can be NULL). + */ + trigname = strtok(NULL,","); + + b = new BuildingClass(classid, bhouse); + if (b) { + if (b->Unlimbo(Cell_Coord(cell), facing)) { + strength = MIN(strength, 0x100); + strength = Fixed_To_Cardinal(b->Class->MaxStrength, strength); + b->Strength = strength; + b->IsALemon = false; + b->Trigger = TriggerClass::As_Pointer(trigname); + if (b->Trigger) { + b->Trigger->AttachCount++; + } + } else { + + /* + ** If the building could not be unlimboed on the map, then this indicates + ** a serious error. Delete the building. + */ + delete b; + } + } + } + tbuffer += strlen(tbuffer)+1; + } +} + + +/*********************************************************************************************** + * BuildingClass::Write_INI -- Writes all building data to an INI file. * + * * + * This routine is used to write the buildings into an INI file. It is necessary for the * + * scenario editor save game option. * + * * + * INI entry format: * + * Housename, Typename, Strength, Cell, Facing, Triggername * + * * + * INPUT: buffer -- The buffer that holds the INI data. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +void BuildingClass::Write_INI(char *buffer) +{ + int index; + char uname[10]; + char buf[127]; + char *tbuffer; // Accumulation buffer of unit IDs. + + /* + ** First, clear out all existing building data from the ini file. + */ + tbuffer = buffer + strlen(buffer) + 2; + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-strlen(buffer), buffer); + while (*tbuffer != '\0') { + WWWritePrivateProfileString(INI_Name(), tbuffer, NULL, buffer); + tbuffer += strlen(tbuffer)+1; + } + + /* + ** Write the data out. + */ + for (index = 0; index < Buildings.Count(); index++) { + BuildingClass * building; + + building = Buildings.Ptr(index); + if (!building->IsInLimbo) { + + sprintf(uname, "%03d", index); + sprintf(buf, "%s,%s,%d,%u,%d,%s", + building->House->Class->IniName, + building->Class->IniName, + building->Health_Ratio(), + Coord_Cell(building->Coord), + building->PrimaryFacing.Current(), + building->Trigger ? building->Trigger->Get_Name() : "None" + ); + WWWritePrivateProfileString(INI_Name(), uname, buf, buffer); + } + } +} + + +/*********************************************************************************************** + * BuildingClass::As_Target -- Convert the building into a target value. * + * * + * Use this routine to take the building and convert it into a target number. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the target number for this building. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +TARGET BuildingClass::As_Target(void) const +{ + Validate(); + return(Build_Target(KIND_BUILDING, Buildings.ID(this))); +} + + +/*********************************************************************************************** + * BuildingClass::Center_Coord -- Fetches the center coordinate for the building. * + * * + * This routine is used to set the center coordinate for this building. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the coordinate for the center location for the building. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +COORDINATE BuildingClass::Center_Coord(void) const +{ + Validate(); + return(Coord_Add(Coord, CenterOffset[Class->Size])); +} + + +COORDINATE BuildingClass::Docking_Coord(void) const +{ + Validate(); + if (*this == STRUCT_HELIPAD) { + return(Coord_Add(Coord, XYP_COORD(24, 18))); + } + if (*this == STRUCT_AIRSTRIP) { + return(Coord_Add(Coord, XYP_COORD(18, 30))); + } + return(TechnoClass::Docking_Coord()); +} + + +/*********************************************************************************************** + * BuildingClass::Can_Fire -- Determines if this building can fire. * + * * + * Use this routine to see if the building can fire its weapon. * + * * + * * + * INPUT: target -- The target that firing upon is desired. * + * * + * which -- Which weapon to use when firing. 0=primary, 1=secondary. * + * * + * OUTPUT: Returns with the fire possibility code. If firing is allowed, then FIRE_OK is * + * returned. Other cases will result in appropriate fire code value that indicates * + * why firing is not allowed. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/03/1995 JLB : Created. * + *=============================================================================================*/ +FireErrorType BuildingClass::Can_Fire(TARGET target, int which) const +{ + Validate(); + FireErrorType canfire = TechnoClass::Can_Fire(target, which); + + if (canfire == FIRE_OK) { + + /* + ** Double check to make sure that the facing is roughly toward + ** the target. If the difference is too great, then firing is + ** temporarily postponed. + */ + if (Class->IsTurretEquipped) { + /* + ** If the turret is rotating then firing must be delayed. + */ + if (PrimaryFacing.Is_Rotating()) { + return(FIRE_ROTATING); + } + + int diff = PrimaryFacing.Difference(Direction(TarCom)); + if (ABS(diff) > 8) { + return(FIRE_FACING); + } + } + + /* + ** Advanced guard towers need power to fire. + */ + if (*this == STRUCT_ATOWER && House->Power_Fraction() < 0x0100) { + return(FIRE_BUSY); + } + + /* + ** If an obelisk can fire, check the state of charge. If it isn't charging + ** up, start it charging up and return FIRE_BUSY. If it is charging but + ** isn't done yet, return FIRE_BUSY. If it's done charging, stop the + ** charging process, clear the stage timer, and return FIRE_OK. + */ + if (Class->Primary == WEAPON_OBELISK_LASER && !IsCharged) { + return(FIRE_BUSY); + } + } + return(canfire); +} + + +/*********************************************************************************************** + * BuildingClass::Toggle_Primary -- Toggles the primary factory state. * + * * + * This routine will change the primary factory state of this building. The primary * + * factory is the one that units will be produced from (by default). * + * * + * INPUT: none * + * * + * OUTPUT: Is this building NOW the primary factory? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/03/1995 JLB : Created. * + *=============================================================================================*/ +bool BuildingClass::Toggle_Primary(void) +{ + Validate(); + if (IsLeader) { + IsLeader = false; + } else { + for (int index = 0; index < Buildings.Count(); index++) { + BuildingClass * building = Buildings.Ptr(index); + + if (!building->IsInLimbo && building->Owner() == Owner() && building->Class->ToBuild == Class->ToBuild) { + building->IsLeader = false; + } + } + IsLeader = true; + if (House == PlayerPtr) { + Speak(VOX_PRIMARY_SELECTED); + } + } + Mark(MARK_CHANGE); + return(IsLeader); +} + + +/*********************************************************************************************** + * BuildingClass::Captured -- Captures the building. * + * * + * This routine will change the owner of the building. It handles updating any related * + * game systems as a result. Factories are the most prone to have great game related * + * consequences when captured. This could also affect the sidebar and building ownership. * + * * + * INPUT: newowner -- Pointer to the house that is now the new owner. * + * * + * OUTPUT: Was the capture attempt successful? * + * * + * WARNINGS: Capturing could fail if the house is already owned by the one specified or * + * the building isn't allowed to be captured. * + * * + * HISTORY: * + * 05/03/1995 JLB : Created. * + * 07/05/1995 JLB : Fixed production problem with capturing enemy buildings. * + *=============================================================================================*/ +bool BuildingClass::Captured(HouseClass * newowner) +{ + Validate(); + if (Class->IsCaptureable && newowner != House) { + switch (Owner()) { + case HOUSE_GOOD: + Speak(VOX_GDI_CAPTURED); + break; + + case HOUSE_BAD: + Speak(VOX_NOD_CAPTURED); + break; + } + + if (House == PlayerPtr) { + Map.PowerClass::IsToRedraw = true; + Map.Flag_To_Redraw(false); + } + + /* + ** Add this building to the list of buildings captured this game. For internet stats purposes + */ + if (GameToPlay == GAME_INTERNET){ + newowner->CapturedBuildings->Increment_Unit_Total (Class->Type); + } + + House->Adjust_Power(-Power_Output()); + LastStrength = 0; + House->Adjust_Drain(-Class->Drain); + int booty = House->Adjust_Capacity(-Class->Capacity, true); + + /* + ** If there is something loaded, then it gets captured as well. + */ + TechnoClass * tech = Attached_Object(); + if (tech) tech->Captured(newowner); + + /* + ** If something isn't technically attached, but is sitting on this + ** building for another reason (e.g., helicopter on helipad), then it + ** gets captured as well. + */ + tech = Contact_With_Whom(); + if (tech) { + if (Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER && ::Distance(tech->Center_Coord(), Docking_Coord()) < 0x0040) { + tech->Captured(newowner); + } else { + Transmit_Message(RADIO_RUN_AWAY); + Transmit_Message(RADIO_OVER_OUT); + } + } + + /* + ** Decrement the factory counter for the original owner. + */ + switch (Class->ToBuild) { + case RTTI_UNITTYPE: + House->UnitFactories--; + break; + + case RTTI_INFANTRYTYPE: + House->InfantryFactories--; + break; + + case RTTI_BUILDINGTYPE: + House->BuildingFactories--; + break; + + case RTTI_AIRCRAFTTYPE: + House->AircraftFactories--; + break; + + default: + break; + } + +#ifdef NEVER + if (IsOwnedByPlayer && !ScenarioInit) { + Map.Recalc(); + } + if (!House->IsHuman) { + Update_Specials(); + } +#endif + + /* + ** Flag that both owners now need to update their buildable lists. + */ + House->IsRecalcNeeded = true; + newowner->IsRecalcNeeded = true; + + IsCaptured = true; + TechnoClass::Captured(newowner); + + SmudgeType bib; + CELL cell = Coord_Cell(Coord); + if (Class->Bib_And_Offset(bib, cell)) { + SmudgeClass * smudge = new SmudgeClass(bib); + if (smudge) { + smudge->Disown(cell); + delete smudge; + } + new SmudgeClass(bib, Cell_Coord(cell), House->Class->House); + } + + House->Harvested(booty); + + /* + ** Increment the factory count for the new owner. + */ + switch (Class->ToBuild) { + case RTTI_UNITTYPE: + House->UnitFactories++; + break; + + case RTTI_INFANTRYTYPE: + House->InfantryFactories++; + break; + + case RTTI_BUILDINGTYPE: + House->BuildingFactories++; + break; + + case RTTI_AIRCRAFTTYPE: + House->AircraftFactories++; + break; + + default: + break; + } + + IsRepairing = false; + Grand_Opening(true); + + Mark(MARK_CHANGE); + + /* + ** Perform a look operation when catpured if it was the player + ** that performed the capture. + */ + if (House == PlayerPtr) { + Look(false); + } + + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * BuildingClass::Sort_Y -- Returns the building coordinate used for sorting. * + * * + * The coordinate value returned from this function should be used for sorting purposes. * + * It has special offset adjustment applied so that vehicles don't overlap (as much). * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a coordinate value suitable to be used for sorting. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1995 JLB : Created. * + * 06/19/1995 JLB : Handles buildings that come with bibs built-in. * + *=============================================================================================*/ +COORDINATE BuildingClass::Sort_Y(void) const +{ + Validate(); + if (*this == STRUCT_REPAIR) { + return(Coord); + } + if (*this == STRUCT_BARRACKS /*|| *this == STRUCT_POWER*/) { + return(Center_Coord()); + } + if (*this == STRUCT_REFINERY) { + return(Center_Coord()); + } + return(Coord_Add(Center_Coord(), XY_Coord(0, (Class->Height()*256)/3))); +} + + +/*********************************************************************************************** + * BuildingClass::Can_Enter_Cell -- Determines if building can be placed down. * + * * + * This routine will determine if the building can be placed down at the location * + * specified. * + * * + * INPUT: cell -- The cell to examine. This is usually the cell of the upper left corner * + * of the building if it were to be placed down. * + * * + * OUTPUT: Returns with the move legality value for placement at the location specified. This * + * will either be MOVE_OK or MOVE_NO. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +MoveType BuildingClass::Can_Enter_Cell(CELL cell, FacingType) const +{ + Validate(); + return(Class->Legal_Placement(cell) ? MOVE_OK : MOVE_NO); +} + + +/*********************************************************************************************** + * BuildingClass::Can_Demolish -- Can the player demolish (sell back) the building? * + * * + * Determines if the player can sell this building. Selling is possible if the building * + * is not currently in construction or deconstruction animation. * + * * + * INPUT: none * + * * + * OUTPUT: Can the building be demolished at this time? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + * 07/01/1995 JLB : If there is no buildup data, then the building can't be sold. * + * 07/17/1995 JLB : Cannot sell a refinery that has a harvester attached. * + *=============================================================================================*/ +bool BuildingClass::Can_Demolish(void) const +{ + Validate(); + if (Class->Get_Buildup_Data() && BState != BSTATE_CONSTRUCTION && !Mission != MISSION_DECONSTRUCTION && Mission != MISSION_CONSTRUCTION) { + if (*this == STRUCT_REFINERY && Is_Something_Attached()) return(false); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * BuildingClass::Mission_Guard -- Handles guard mission for combat buildings. * + * * + * Buildings that can attack are given this mission. They will wait until a suitable target * + * comes within range and then launch into the attack mission. Buildings that have no * + * weaponry will just sit in this routine forever. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of game frames to delay before this routine will be called * + * again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +int BuildingClass::Mission_Guard(void) +{ + Validate(); + /* + ** If this building has a weapon, then search for a target to attack. When + ** a target is found, switch into attack mode to deal with the threat. + */ + if (Class->Primary != WEAPON_NONE) { + + /* + ** Weapon equipped buildings are ALWAYS ready to launch into another mission if + ** they are sitting around in guard mode. + */ + IsReadyToCommence = true; + + /* + ** If there is no target available, then search for one. + */ + if (!Target_Legal(TarCom)) { + ThreatType threat = THREAT_NORMAL; + Assign_Target(Greatest_Threat(threat)); + } + + /* + ** There is a valid target. Switch into attack mode right away. + */ + if (Target_Legal(TarCom)) { + Assign_Mission(MISSION_ATTACK); + return(1); + } + } else { + + /* + ** This is the very simple state machine that basically does + ** nothing. This is the mode that non weapon equipped buildings + ** are normally in. + */ + enum { + INITIAL_ENTRY, + IDLE + }; + switch (Status) { + case INITIAL_ENTRY: + Begin_Mode(BSTATE_IDLE); + Status = IDLE; + break; + + case IDLE: + /* + ** Special case to break out of guard mode if this is a repair + ** facility and there is a customer waiting at the grease pit. + */ + if (*this == STRUCT_REPAIR && + In_Radio_Contact() && + Contact_With_Whom()->Is_Techno() && + ((TechnoClass *)Contact_With_Whom())->Mission == MISSION_ENTER && + Distance(Contact_With_Whom()) < 0x0040 && + Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER) { + + Assign_Mission(MISSION_REPAIR); + return(1); + } + break; + } + return(TICKS_PER_SECOND*5); + } + return(TICKS_PER_SECOND/2); +} + + +/*********************************************************************************************** + * BuildingClass::Mission_Construction -- Handles mission construction. * + * * + * This routine will handle mission construction. When this mission is complete, the * + * building will begin normal operation. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of game frames to delay before calling this routine * + * again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +int BuildingClass::Mission_Construction(void) +{ + Validate(); + enum { + INITIAL, + DURING + }; + switch (Status) { + case INITIAL: + Begin_Mode(BSTATE_CONSTRUCTION); + Transmit_Message(RADIO_BUILDING); + if (IsOwnedByPlayer) { + Sound_Effect(VOC_CONSTRUCTION, Coord); + } + Status = DURING; + break; + + case DURING: + if (IsReadyToCommence) { + + /* + ** When construction is complete, then transmit this + ** to the construction yard so that it can stop its + ** construction animation. + */ + Transmit_Message(RADIO_COMPLETE); // "I'm finished." + Transmit_Message(RADIO_OVER_OUT); // "You're free." + Begin_Mode(BSTATE_IDLE); + Grand_Opening(); + Assign_Mission(MISSION_GUARD); + PrimaryFacing = Class->StartFace; + } + break; + } + return(1); +} + + +/*********************************************************************************************** + * BuildingClass::Mission_Deconstruction -- Handles building deconstruction. * + * * + * This state machine is only used when the building is deconstructing as a result of * + * selling. When this mission is finished, the building will no longer exist. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of game frames to delay before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + * 08/13/1995 JLB : Enable selling of units on a repair bay. * + * 08/20/1995 JLB : Scatters infantry from scattered starting points. * + *=============================================================================================*/ +int BuildingClass::Mission_Deconstruction(void) +{ + Validate(); + /* + ** Always force repair off. + */ + Repair(0); + + enum { + INITIAL, + HOLDING, + DURING + }; + switch (Status) { + case INITIAL: + + /* + ** Special check for the repair bay which has the ability to sell + ** whatever is on it. If there is something on the repair bay, then + ** it will be sold. If there is nothing on the repair bay, then + ** the repair bay itself will be sold. + */ + if (*this == STRUCT_REPAIR && Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER && Distance(Contact_With_Whom()) < 0x0080) { + TechnoClass * tech = Contact_With_Whom(); + Transmit_Message(RADIO_OVER_OUT); + tech->Sell_Back(1); +// House->Refund_Money(tech->Refund_Amount()); +// tech->Limbo(); + Assign_Mission(MISSION_GUARD); + return(1); + } + + IsReadyToCommence = false; + Transmit_Message(RADIO_RUN_AWAY); + Status = HOLDING; + break; + + case HOLDING: + if (!IsTethered) { + + /* + ** The crew will evacuate from the building. The number of crew + ** members leaving is equal to the unrecovered cost of the building + ** divided by 100 (the typical cost of a minigunner infantryman). + */ + if (!Special.IsMCVDeploy || *this != STRUCT_CONST) { + int divisor = 200; + if (IsCaptured) divisor *= 2; + int count = (Class->Raw_Cost()+(divisor-1)) / divisor; + bool engine = false; + count = Bound(count, 1, 5); + + while (count) { + + /* + ** Ensure that the player only gets ONE engineer and not from a captured + ** construction yard. + */ + InfantryType typ = Crew_Type(); + while (typ == INFANTRY_E7 && engine) { + typ = Crew_Type(); + } + if (typ == INFANTRY_E7) engine = true; + + InfantryClass * infantry = new InfantryClass(typ, House->Class->House); + if (infantry) { + ScenarioInit++; + COORDINATE coord = Coord_Add(Center_Coord(), XYP_COORD(0, -12)); + coord = Map[Coord_Cell(coord)].Closest_Free_Spot(coord, false); + + if (infantry->Unlimbo(coord, DIR_N)) { + if (infantry->Class->IsNominal) infantry->IsTechnician = true; + ScenarioInit--; + infantry->Scatter(0, true); + ScenarioInit++; + infantry->Assign_Mission(MISSION_GUARD_AREA); + } else { + delete infantry; + } + ScenarioInit--; + } + count--; + } + } + + if (IsOwnedByPlayer) { + Sound_Effect(VOC_CASHTURN, Coord); + } + Status = DURING; + Begin_Mode(BSTATE_CONSTRUCTION); + Detach_All(true); + Transmit_Message(RADIO_OVER_OUT); + IsReadyToCommence = false; + break; + } + Transmit_Message(RADIO_RUN_AWAY); + break; + + case DURING: + if (IsReadyToCommence) { + + /* + ** Construction yards that deconstruct, really just revert back + ** to an MCV. + */ + if (Special.IsMCVDeploy && *this == STRUCT_CONST && House->IsHuman) { + ScenarioInit++; + UnitClass * unit = new UnitClass(UNIT_MCV, House->Class->House); + ScenarioInit--; + if (unit) { + + /* + ** Unlimbo the MCV onto the map. The MCV should start in the same + ** health condition that the construction yard was in. + */ + int ratio = Health_Ratio(); + int money = Refund_Amount(); + + delete this; + + if (unit->Unlimbo(Coord_Snap(Adjacent_Cell(Coord, DIR_SE)), DIR_SW)) { + unit->Strength = Fixed_To_Cardinal(unit->Class_Of().MaxStrength, ratio); + } else { + + /* + ** If, for some strange reason, the MCV could not be placed on the + ** map, then give the player some money to compensate. + */ + House->Refund_Money(money); + } + } else { + House->Refund_Money(Refund_Amount()); + delete this; + } + + } else { + + /* + ** A sold building still counts as a kill, but it just isn't directly + ** attributed to the enemy. + */ + WhoLastHurtMe = HOUSE_NONE; + Record_The_Kill(NULL); + + /* + ** The player gets part of the money back for the sell. + */ + House->Refund_Money(Refund_Amount()); + Limbo(); + + /* + ** Finally, delete the building from the game. + */ + delete this; + } + House->IsRecalcNeeded = true; + } + break; + } + return(1); +} + + +/*********************************************************************************************** + * BuildingClass::Mission_Attack -- Handles attack mission for building. * + * * + * Buildings that can attack are processed by this attack mission state machine. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of game frames to delay before calling this routine * + * again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +int BuildingClass::Mission_Attack(void) +{ + Validate(); + if (*this == STRUCT_SAM) { + switch (Status) { + + /* + ** The launcher is underground and awaiting the acquisition of + ** a target. + */ + case SAM_UNDERGROUND: + IsReadyToCommence = true; + if (Target_Legal(TarCom)) { + Set_Rate(2); + Set_Stage(0); + Status = SAM_RISING; + return(1); + } else { + Assign_Mission(MISSION_GUARD); + } + break; + + /* + ** The launcher is rising into the ready position so that it + ** may rotate to face the target. + */ + case SAM_RISING: + if (Fetch_Stage() == 15) { + Set_Rate(0); + PrimaryFacing = DIR_N; + if (!Target_Legal(TarCom)) { + Status = SAM_LOWERING; + } else { + Status = SAM_READY; + } + } + return(1); + + /* + ** This is the target tracking state of the launcher. It will rotate + ** to face the current TarCom of the launcher. + */ + case SAM_READY: + if (!Target_Legal(TarCom) || !Is_Target_Aircraft(TarCom) || As_Aircraft(TarCom)->Altitude == 0) { + Assign_Target(TARGET_NONE); + Status = SAM_LOCKING; + return(TICKS_PER_SECOND); + } else { + if (!PrimaryFacing.Is_Rotating()) { + DirType facing = Direction(TarCom); + if (PrimaryFacing.Difference(facing)) { + PrimaryFacing.Set_Desired(facing); + } else { + Status = SAM_FIRING; + } + } + } + return(1); + + /* + ** The launcher is in the process of firing. + */ + case SAM_FIRING: + if (!Target_Legal(TarCom) || !Is_Target_Aircraft(TarCom) || As_Aircraft(TarCom)->Altitude == 0) { + Assign_Target(TARGET_NONE); + Status = SAM_LOCKING; + } else { + FireErrorType error = Can_Fire(TarCom, 0); + if (error == FIRE_ILLEGAL || error == FIRE_CANT || error == FIRE_RANGE) { + Assign_Target(TARGET_NONE); + Status = SAM_LOCKING; + } else { + if (error == FIRE_FACING) { + Status = SAM_READY; + } else { + if (error == FIRE_OK) { + Fire_At(TarCom, 0); + Status = SAM_READY2; + return(1); + } + } + } + } + return(1); + + case SAM_READY2: + if (!Target_Legal(TarCom) || !Is_Target_Aircraft(TarCom) || As_Aircraft(TarCom)->Altitude == 0) { + Assign_Target(TARGET_NONE); + Status = SAM_LOCKING; + return(TICKS_PER_SECOND); + } else { + if (!PrimaryFacing.Is_Rotating()) { + DirType facing = Direction(TarCom); + if (PrimaryFacing.Difference(facing)) { + PrimaryFacing.Set_Desired(facing); + } else { + Status = SAM_FIRING2; + } + } + } + return(1); + + case SAM_FIRING2: + if (!Target_Legal(TarCom) || !Is_Target_Aircraft(TarCom) || As_Aircraft(TarCom)->Altitude == 0) { + Assign_Target(TARGET_NONE); + Status = SAM_LOCKING; + } else { + FireErrorType error = Can_Fire(TarCom, 0); + if (error == FIRE_ILLEGAL || error == FIRE_CANT || error == FIRE_RANGE) { + Assign_Target(TARGET_NONE); + Status = SAM_LOCKING; + } else { + if (error == FIRE_FACING) { + Status = SAM_READY2; + } else { + if (error == FIRE_OK) { + Fire_At(TarCom, 0); + Status = SAM_LOCKING; + return(TICKS_PER_SECOND*3); + } + } + } + } + return(1); + + /* + ** Rotating to face north in preparation for lowering to reload. + */ + case SAM_LOCKING: + if (!PrimaryFacing.Is_Rotating()) { + if (PrimaryFacing == DIR_N) { + Set_Rate(2); + Set_Stage(48); + Status = SAM_LOWERING; + } else { + PrimaryFacing.Set_Desired(DIR_N); + } + } + return(1); + + /* + ** Lowering into the ground in order to reload. + */ + case SAM_LOWERING: + if (Fetch_Stage() >= 63) { + Set_Rate(0); + Set_Stage(0); + Status = SAM_UNDERGROUND; + return(TICKS_PER_SECOND); + } else { + if (Fetch_Rate() == 0) { + Set_Rate(2); + } + } + return(1); + + default: + break; + } + + } else { + IsReadyToCommence = true; + switch (Can_Fire(TarCom, 0)) { + case FIRE_ILLEGAL: + case FIRE_CANT: + case FIRE_RANGE: + case FIRE_AMMO: + Assign_Target(TARGET_NONE); + Assign_Mission(MISSION_GUARD); + Commence(); + break; + + case FIRE_FACING: + PrimaryFacing.Set_Desired(Direction(TarCom)); + return(2); + + case FIRE_REARM: + case FIRE_BUSY: + return(1); + + case FIRE_CLOAKED: + Do_Uncloak(); + break; + + case FIRE_OK: + Fire_At(TarCom, 0); + return(1); + } + } + return(TICKS_PER_SECOND); +} + + +/*********************************************************************************************** + * BuildingClass::Mission_Harvest -- Handles refinery unloading harvesters. * + * * + * This state machine handles the refinery when it unloads the harvester. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of game frames to delay before calling this routine * + * again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +int BuildingClass::Mission_Harvest(void) +{ + Validate(); + enum { + INITIAL, // Dock the Tiberium canister. + WAIT_FOR_DOCK, // Waiting for docking to complete. + MIDDLE, // Offload "bails" of tiberium. + WAIT_FOR_UNDOCK, // Waiting for undocking to complete. + EXITING // Cause the harvester to drive away. + }; + switch (Status) { + case INITIAL: + Begin_Mode(BSTATE_ACTIVE); + Status = WAIT_FOR_DOCK; + break; + + case WAIT_FOR_DOCK: + if (IsReadyToCommence) { + IsReadyToCommence = false; + Status = MIDDLE; + Begin_Mode(BSTATE_AUX1); + } + break; + + case MIDDLE: + if (IsReadyToCommence) { + IsReadyToCommence = false; + + /* + ** Force any bib squaters to scatter. + */ + bool old = Special.IsScatter; + Special.IsScatter = true; + Map[Adjacent_Cell(Coord_Cell(Center_Coord()), DIR_SW)].Incoming(0, true); + Special.IsScatter = old; + + FootClass * techno = Attached_Object(); + if (techno) { + int bail = techno->Offload_Tiberium_Bail(); + + if (bail) { + House->Harvested(bail); + if (techno->Tiberium_Load()) { + return(1); + } + } + } + Begin_Mode(BSTATE_AUX2); + Status = WAIT_FOR_UNDOCK; + } + break; + + case WAIT_FOR_UNDOCK: + if (IsReadyToCommence) { + + /* + ** Detach harvester and go back into idle state. + */ + Exit_Object(Detach_Object()); + Assign_Mission(MISSION_GUARD); + } + break; + } + return(1); +} + + +/*********************************************************************************************** + * BuildingClass::Mission_Repair -- Handles the repair (active) state for building. * + * * + * This state machine is used when the building is active in some sort of repair or * + * construction mode. The construction yard will animate. The repair facility will repair * + * anything that it docked on it. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of game frames to delay before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + * 06/25/1995 JLB : Handles repair facility * + * 07/29/1995 JLB : Repair rate is controlled by power rating. * + *=============================================================================================*/ +int BuildingClass::Mission_Repair(void) +{ + Validate(); + if (*this == STRUCT_CONST) { + enum { + INITIAL, + DURING + }; + switch (Status) { + case INITIAL: + Begin_Mode(BSTATE_ACTIVE); + Status = DURING; + break; + + case DURING: + if (!In_Radio_Contact()) { + Assign_Mission(MISSION_GUARD); + } + break; + } + return(1); + } + + if (*this == STRUCT_REPAIR) { + enum { + INITIAL, + IDLE, + DURING + }; + switch (Status) { + case INITIAL: + if (!In_Radio_Contact()) { + Begin_Mode(BSTATE_IDLE); + Assign_Mission(MISSION_GUARD); + return(1); + } + IsReadyToCommence = false; + if (Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER && Distance(Contact_With_Whom()) < 0x0010) { + Status = IDLE; + return(TICKS_PER_SECOND/4); + } + break; + + case IDLE: + if (!In_Radio_Contact()) { + Assign_Mission(MISSION_GUARD); + return(1); + } + + if (Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER) { + if (Contact_With_Whom()->Health_Ratio() < 0x0100 && Transmit_Message(RADIO_REPAIR) == RADIO_ROGER) { + Status = DURING; + Begin_Mode(BSTATE_ACTIVE); + IsReadyToCommence = false; + } else { + if (!House->IsHuman) { + Transmit_Message(RADIO_RUN_AWAY); + } + } +// } else { +// Assign_Mission(MISSION_GUARD); +// return(1); + } + break; + + case DURING: + if (!In_Radio_Contact()) { + Begin_Mode(BSTATE_IDLE); + Status = IDLE; + return(1); + } + if (IsReadyToCommence && Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER) { + IsReadyToCommence = false; + long param = Health_Ratio(); + if (Transmit_Message(RADIO_REPAIR, param) != RADIO_ROGER) { +#ifdef OBSOLETE + if (House->Available_Money() < 10) { + Transmit_Message(RADIO_RUN_AWAY); + } +#endif + Begin_Mode(BSTATE_IDLE); + Status = IDLE; +#ifdef OBSOLETE + } else { + int time = Bound(Fixed_To_Cardinal(TICKS_PER_SECOND, House->Power_Fraction()), 0, TICKS_PER_SECOND); + time = (TICKS_PER_SECOND + (TICKS_PER_SECOND/2)) - time; + return(time); +#endif + } + } + break; + } + return(TICKS_PER_SECOND/2); + } + + if (*this == STRUCT_HELIPAD) { + enum { + INITIAL, + DURING + }; + switch (Status) { + case INITIAL: + if (Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER && Transmit_Message(RADIO_PREPARED) == RADIO_NEGATIVE) { + Begin_Mode(BSTATE_ACTIVE); + Contact_With_Whom()->Assign_Mission(MISSION_SLEEP); + Status = DURING; + return(1); + } + Assign_Mission(MISSION_GUARD); + break; + + case DURING: + if (IsReadyToCommence) { + if (!In_Radio_Contact() || Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_NEGATIVE) { + Assign_Mission(MISSION_GUARD); + return(1); + } + + if (Transmit_Message(RADIO_PREPARED) == RADIO_ROGER) { + Contact_With_Whom()->Assign_Mission(MISSION_GUARD); + Assign_Mission(MISSION_GUARD); + return(1); + } + + if (Transmit_Message(RADIO_RELOAD) != RADIO_ROGER) { + Assign_Mission(MISSION_GUARD); + Contact_With_Whom()->Assign_Mission(MISSION_GUARD); + return(1); + } else { + int time = Bound(Fixed_To_Cardinal(TICKS_PER_SECOND, House->Power_Fraction()), 0, TICKS_PER_SECOND); + time = (TICKS_PER_SECOND*3) - time; + IsReadyToCommence = false; + return(time); + } + } + break; + } + return(3); + } + return(TICKS_PER_SECOND); +} + + +/*********************************************************************************************** + * BuildingClass::Mission_Missile -- State machine for nuclear missile launch. * + * * + * This handles the Temple of Nod launching its nuclear missile. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of frames to delay before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/04/1995 JLB : Commented. * + *=============================================================================================*/ +int BuildingClass::Mission_Missile(void) +{ + Validate(); + enum { + INITIAL, + DOOR_OPENING, + LAUNCH_UP, + LAUNCH_DOWN, + DONE_LAUNCH + }; + + if (*this == STRUCT_TEMPLE) { + switch (Status) { + + /* + ** The initial case is responsible for starting the door + ** opening on the building. + */ + case INITIAL: + IsReadyToCommence = false; + Begin_Mode(BSTATE_ACTIVE); + Status = DOOR_OPENING; + return(1); + + /* + ** This polls for the case when the door is actually open and + ** then kicks off the missile smoke. + */ + case DOOR_OPENING: + if (IsReadyToCommence) { + Begin_Mode(BSTATE_IDLE); + new AnimClass(ANIM_ATOM_DOOR, Center_Coord()); + Status = LAUNCH_UP; + return(14); + } + return(1); + + /* + ** Once the smoke has been going for a little while this + ** actually handles launching the missile into the air. + */ + case LAUNCH_UP: + { + BulletClass *bullet = new BulletClass(BULLET_NUKE_UP); + if (bullet) { + COORDINATE launch = Coord_Move(Center_Coord(), (DirType)1, 0x1A0); + bullet->Assign_Target(TARGET_NONE); + bullet->Payback = NULL; + bullet->Strength = 1; + if (!bullet->Unlimbo(launch, DIR_N)) { + delete bullet; + bullet = NULL; + } else { + bullet->PrimaryFacing.Set_Current(DIR_N); + Sound_Effect(VOC_NUKE_FIRE, launch); + if (House == PlayerPtr) { + Speak(VOX_NUKE_LAUNCHED); + } + } + } + + if (bullet) { + Status = LAUNCH_DOWN; + return(8 * TICKS_PER_SECOND); + } + } + return(1); + + /* + ** Once the missile is in the air, this handles waiting for + ** the missile to be off the screen and then launching one down + ** over the target. + */ + case LAUNCH_DOWN: + { + BulletClass *bullet = new BulletClass(BULLET_NUKE_DOWN); + if (bullet) { +// Theme.Queue_Song(THEME_NONE); + COORDINATE start = Cell_Coord(XY_Cell(Cell_X(House->NukeDest), 1)); + bullet->Assign_Target(::As_Target(House->NukeDest)); + bullet->Payback = NULL; + bullet->Strength = 1; + if (!bullet->Unlimbo(start, DIR_S)) { + delete bullet; + } else { + bullet->PrimaryFacing.Set_Current(DIR_S); + } + Speak(VOX_INCOMING_NUKE); + Sound_Effect(VOC_NUKE_FIRE, start); + } + if (bullet) { + Status = DONE_LAUNCH; + return(7 * TICKS_PER_SECOND); + } + } + return(1); + + /* + ** Once the missile is done launching this handles allowing + ** the building to sit there with its door open. + */ + case DONE_LAUNCH: + Assign_Mission(MISSION_GUARD); + return(60); + } + } + return(60); +} + + +/*********************************************************************************************** + * BuildingClass::Revealed -- Reveals the building to the specified house. * + * * + * This routine will reveal the building to the specified house. It will handle updating * + * the sidebar for player owned buildings. A player owned building that hasn't been * + * revealed, is in a state of pseudo-limbo. It cannot be used for any of its special * + * abilities even though it exists on the map for all other purposes. * + * * + * INPUT: house -- The house that this building is being revealed to. * + * * + * OUTPUT: Was this building revealed by this procedure? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +bool BuildingClass::Revealed(HouseClass * house) +{ + Validate(); + if (TechnoClass::Revealed(house)) { + + if (!ScenarioInit) { + House->JustBuilt = Class->Type; + } + House->IsRecalcNeeded = true; + + /* + ** Perform any grand opening here so that in the scenarios where a player + ** owned house is not yet revealed, it won't be reflected in the sidebar + ** selection icons. + */ + if (!In_Radio_Contact() && (house == House || GameToPlay != GAME_NORMAL) && Mission != MISSION_CONSTRUCTION) { + Grand_Opening(); + } + + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * BuildingClass::Enter_Idle_Mode -- The building will enter its idle mode. * + * * + * This routine is called when the exact mode of the building isn't known. By examining * + * the building's condition, this routine will assign an appropriate mission. * + * * + * INPUT: initial -- This this being called during scenario init? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +void BuildingClass::Enter_Idle_Mode(bool initial) +{ + Validate(); + /* + ** Assign an appropriate mission for the building. If the ScenarioInit flag is true, then + ** this must be an initial building. Start such buildings in idle state. For other buildings + ** it indicates that it is being placed during game play and thus it must start in + ** the "construction" mission. + */ + MissionType mission = MISSION_GUARD; + if (!initial || ScenarioInit || Debug_Map) { + Begin_Mode(BSTATE_IDLE); + mission = MISSION_GUARD; + } else { + Begin_Mode(BSTATE_CONSTRUCTION); + mission = MISSION_CONSTRUCTION; + } + Assign_Mission(mission); +} + + +/*************************************************************************** + * BuildingClass::Update_Specials -- removes computer specials * + * * + * * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 06/21/1995 PWG : Created. * + *=========================================================================*/ +void BuildingClass::Update_Specials(void) +{ + Validate(); +} + + +/*********************************************************************************************** + * BuildingClass::Pip_Count -- Determines "full" pips to display for building. * + * * + * This routine will determine the number of pips that should be filled in when rendering * + * the building. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the number of pips to display as filled in. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/28/1995 JLB : Created. * + *=============================================================================================*/ +int BuildingClass::Pip_Count(void) const +{ + Validate(); + return(Fixed_To_Cardinal(Class->Max_Pips(), House->Tiberium_Fraction())); +} + + +/*********************************************************************************************** + * BuildingClass::Death_Announcement -- Announce the death of this building. * + * * + * This routine is called when the building is destroyed by "unnatural" means. Typically * + * as a result of combat. If the building is known to the player, then it should be * + * announced. * + * * + * INPUT: source -- The object most directly responsible for the building's death. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/04/1995 JLB : Created. * + *=============================================================================================*/ +void BuildingClass::Death_Announcement(TechnoClass const * ) const +{ + Validate(); + if (IsDiscoveredByPlayer || IsOwnedByPlayer) { + if (House != PlayerPtr && GameToPlay != GAME_NORMAL) { + if (Options.IsDeathAnnounce) Speak(VOX_ENEMY_STRUCTURE); + } else { + if (House == PlayerPtr || Options.IsDeathAnnounce) { + if (!Options.IsDeathAnnounce) { + Speak(VOX_STRUCTURE_LOST); + } else { + switch (House->ActLike) { + case HOUSE_GOOD: + Speak(VOX_GDI_STRUCTURE); + break; + + case HOUSE_BAD: + Speak(VOX_NOD_STRUCTURE); + break; + + default: + break; + } + } + } + } + } +} + + +/*********************************************************************************************** + * BuildingClass::Fire_Direction -- Fetches the direction of firing. * + * * + * This routine will return with the default direction to use when firing from this * + * building. This is the facing of the turret except for the case of non-turret equipped * + * buildings that have a weapon (e.g., guard tower). * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the default firing direction for this building. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/04/1995 JLB : Created. * + *=============================================================================================*/ +DirType BuildingClass::Fire_Direction(void) const +{ + Validate(); + if (Class->IsTurretEquipped) { + return(PrimaryFacing.Current()); + } + return(Direction(TarCom)); +} + + +/*********************************************************************************************** + * BuildingClass::Remap_Table -- Fetches the remap table to use for this building. * + * * + * Use this routine to fetch the remap table to use. This override function is needed * + * because the default remap table for techno objects presumes the object is a unit. * + * Buildings aren't units. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the proper remap table to use for this building. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +void const * BuildingClass::Remap_Table(void) +{ + Validate(); + return(House->Remap_Table(IsBlushing, false)); +} + + +/*********************************************************************************************** + * BuildingClass::Mission_Unload -- Handles the unload mission for a building. * + * * + * This is the unload mission for a building. This really only applies to the weapon's * + * factory, since it needs the sophistication of an unload mission due to the door * + * animation. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of game frames to delay before calling this routine * + * again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +int BuildingClass::Mission_Unload(void) +{ + Validate(); + if (*this == STRUCT_WEAP) { + enum { + INITIAL, + OPEN, + LEAVE, + CLOSE + }; + UnitClass * unit; + switch (Status) { + case INITIAL: + unit = (UnitClass *)Contact_With_Whom(); + if (unit) { + unit->Assign_Mission(MISSION_GUARD); + unit->Commence(); + } + Open_Door(2, 11); + Status = OPEN; + break; + + case OPEN: + if (Is_Door_Open()) { + unit = (UnitClass *)Contact_With_Whom(); + if (unit) { + unit->Assign_Mission(MISSION_MOVE); + unit->Force_Track(DriveClass::OUT_OF_WEAPON_FACTORY, Adjacent_Cell(Center_Coord(), FACING_SW)); + unit->Set_Speed(128); + Status = LEAVE; + } else { + Close_Door(2, 11); + Status = CLOSE; + } + } + break; + + case LEAVE: + if (!IsTethered) { + Close_Door(2, 11); + Status = CLOSE; + } + break; + + case CLOSE: + if (Is_Door_Closed()) { + Enter_Idle_Mode(); + } + break; + } + return(TICKS_PER_SECOND/2); + } + Assign_Mission(MISSION_GUARD); + return(TICKS_PER_SECOND); +} + + +/*********************************************************************************************** + * BuildingClass::Power_Output -- Fetches the current power output from this building. * + * * + * This routine will return the current power output for this building. The power output * + * is adjusted according to the damage level of the building. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the current power output for this building. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +int BuildingClass::Power_Output(void) const +{ + Validate(); + if (Class->Power) { + return(Fixed_To_Cardinal(Class->Power, Cardinal_To_Fixed(Class->MaxStrength, LastStrength))); + } + return(0); +} + + +/*********************************************************************************************** + * BuildingClass::Detach -- Handles target removal from the game system. * + * * + * This routine is called when the specified target is about to be removed from the game * + * system. * + * * + * INPUT: target -- The target to be removed from this building's targeting computer. * + * * + * all -- Is the target about to be completely eliminated? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +void BuildingClass::Detach(TARGET target, bool all) +{ + Validate(); + TechnoClass::Detach(target, all); + if (target == WhomToRepay) { + WhomToRepay = TARGET_NONE; + } +} + + +/*********************************************************************************************** + * BuildingClass::Refund_Amount -- Fetches the refund amount if building is sold. * + * * + * This routine will return the amount of money to be refunded to the building's owner * + * if the building is sold. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the refund amount available for this building. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +int BuildingClass::Refund_Amount(void) const +{ + Validate(); + int cost = TechnoClass::Refund_Amount(); + + /* + ** Add in any Tiberium that was stored within the building. + */ + if (IsV107 && Class->Capacity > 0) { + cost += Fixed_To_Cardinal(Class->Capacity, Cardinal_To_Fixed(House->Capacity, House->Tiberium)); + } + return(cost); +} + + +/*********************************************************************************************** + * BuildingClass::Crew_Type -- This determines the crew that this object generates. * + * * + * When selling very cheap buildings (such as the silo), a technician will pop out since * + * generating minigunners would be overkill -- the player could use this loophole to * + * gain an advantage. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the infantry type that this building will generate as a survivor. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/05/1995 JLB : Created. * + *=============================================================================================*/ +InfantryType BuildingClass::Crew_Type(void) const +{ + Validate(); + switch (Class->Type) { + case STRUCT_STORAGE: + if (Random_Pick(0, 1) == 0) { + return(INFANTRY_C1); + } else { + return(INFANTRY_C7); + } + + case STRUCT_CONST: + if (!IsCaptured && House->IsHuman && Random_Pick(0, 3) == 0) { + return(INFANTRY_E7); + } + break; + + default: + break; + } + return(TechnoClass::Crew_Type()); +} + + +/*********************************************************************************************** + * BuildingClass::Detach_All -- Possibly abandons production according to factory type. * + * * + * When this routine is called, it indicates that the building is about to be destroyed * + * or captured. In such a case any production it may be doing, must be abandoned. * + * * + * INPUT: all -- Is the object about the be completely destroyed? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/05/1995 JLB : Created. * + *=============================================================================================*/ +void BuildingClass::Detach_All(bool all) +{ + Validate(); + /* + ** If it is producing something, then it must be abandoned. + */ + if (Factory) { + Factory->Abandon(); + delete Factory; + Factory = 0; + } + + /* + ** If the owner HouseClass is building something, and this building can + ** build that thing, we may be the last building for that house that can + ** build that thing; if so, abandon production of it. + */ + if (House) { + int fnum = -1; + + switch (Class->ToBuild) { + case RTTI_AIRCRAFTTYPE: + fnum = House->AircraftFactory; + break; + + case RTTI_INFANTRYTYPE: + fnum = House->InfantryFactory; + break; + + case RTTI_UNITTYPE: + fnum = House->UnitFactory; + break; + + case RTTI_BUILDINGTYPE: + fnum = House->BuildingFactory; + break; + + case RTTI_SPECIAL: + fnum = House->SpecialFactory; + break; + + } + + /* + ** Convert the factory number into a real factory pointer. + */ + FactoryClass * factory = 0; + if (fnum != -1) { + factory = Factories.Raw_Ptr(fnum); + } + + /* + ** If a factory was found, then temporarily disable this building and then + ** detmermine if any object that is being produced can still be produced. If + ** not, then the object being produced must be abandoned. + */ + if (factory) { + TechnoClass * object = factory->Get_Object(); + IsInLimbo = true; + if (object && !object->Techno_Type_Class()->Who_Can_Build_Me(true, false, House->Class->House)) { + House->Abandon_Production(Class->ToBuild); + } + IsInLimbo = false; + } + } + + TechnoClass::Detach_All(all); +} + + +/*********************************************************************************************** + * BuildingClass::Flush_For_Placement -- Handles clearing a zone for object placement. * + * * + * This routine is used to clear the way for placement of the specified object (usually * + * a building). If there are friendly units blocking the placement area, they are told * + * to scatter. Enemy blocking units are attacked. * + * * + * INPUT: techno -- Pointer to the object that is desired to be placed. * + * * + * cell -- The cell that placement wants to occur at. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/06/1995 JLB : Created. * + *=============================================================================================*/ +bool BuildingClass::Flush_For_Placement(TechnoClass * techno, CELL cell) +{ + Validate(); + bool again = false; + if (techno && cell > 0) { + short const * list = techno->Class_Of().Occupy_List(true); + + while (*list != REFRESH_EOL) { + CELL newcell = cell + *list++; + + if (Map.In_Radar(newcell)) { + TechnoClass * occupier = Map[newcell].Cell_Techno(); + if (occupier) { + again = true; + if (occupier->House->Is_Ally(this)) { + Map[newcell].Incoming(0, true); + } else { + Base_Is_Attacked(occupier); + } + } + } + } + } + return(again); +} + + +void BuildingClass::Hidden(void) +{ +// if (IsDiscoveredByPlayer && House->IsHuman) { +// House->Adjust_Drain(-Class->Drain); +// } + TechnoClass::Hidden(); +} + + +CELL BuildingClass::Find_Exit_Cell(TechnoClass const * techno) const +{ + CELL const *ptr; + CELL origin = Coord_Cell(Coord); + bool found = false; + + ptr = Class->ExitList; + if (ptr) { + while (*ptr != REFRESH_EOL) { + CELL cell = origin + *ptr++; + if (Map.In_Radar(cell) && techno->Can_Enter_Cell(cell) == MOVE_OK) { + return(cell); + } + } + } + return(0); +} + +/*********************************************************************************************** + * BuildingClass::Passes_Proximity_Check -- Determines if building placement is near friendly sq* + * * + * This routine is used by the building placement cursor logic to determine whether the * + * at the current cursor position if the building would be adjacent to another friendly * + * building. In cases where this is not true, then the building cannot be placed at all. * + * This determination is returned by the function. * + * * + * INPUT: homecell -- The cell that the building would like to be placed down at. * + * * + * OUTPUT: bool; Can the pending building object be placed at the present cursor location * + * checking only for proximity to friendly buildings? If this isn't for a * + * building type object, then this routine always returns true. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/06/1994 JLB : Created. * + * 06/07/1994 JLB : Handles concrete check. * + *=============================================================================================*/ +bool BuildingClass::Passes_Proximity_Check(CELL homecell) +{ + /* + ** In editor mode, the proximity check always passes. + */ + if (Debug_Map || !House->IsHuman) { + return(true); + } + + /* + ** Scan through all cells that the building foundation would cover. If any adjacent + ** cells to these are of friendly persuasion, then consider the proximity check to + ** have been a success. + */ + short const * ptr = Occupy_List(true); + while (*ptr != REFRESH_EOL) { + CELL cell = homecell + *ptr++; + + if (!Map.In_Radar(cell)) return(false); + + for (FacingType facing = FACING_N; facing < FACING_COUNT; facing++) { + CELL newcell = Adjacent_Cell(cell, facing); + + TechnoClass * base = Map[newcell].Cell_Techno(); + + /* + ** The special cell ownership flag allows building adjacent + ** to friendly walls and bibs even though there is no official + ** building located there. + */ + if (Map[newcell].Owner == House->Class->House) { + return(true); + } + + if (base && base->What_Am_I() == RTTI_BUILDING && base->House->Class->House == House->Class->House) { + return(true); + } + } + } + return(false); +} diff --git a/BUILDING.H b/BUILDING.H new file mode 100644 index 0000000..bed3b6c --- /dev/null +++ b/BUILDING.H @@ -0,0 +1,304 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\building.h_v 2.20 16 Oct 1995 16:47:54 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : BUILDING.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 14, 1994 * + * * + * Last Update : April 14, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef BUILDING_H +#define BUILDING_H + +#include "tarcom.h" +#include "radio.h" +#include "cargo.h" +#include "mission.h" +#include "bullet.h" +#include "target.h" +#include "factory.h" + +#define MAX_DOOR_STAGE 18 // # of frames of door opening on weapons factory +#define DOOR_OPEN_STAGE 9 // frame on which the door is entirely open +#define MAX_REPAIR_ANIM_STAGE 5 // # of stages of anim for repair center cycling + +/**************************************************************************** +** For each instance of a building in the game, there is one of +** these structures. This structure holds information that is specific +** and dynamic for a particular building. +*/ +class BuildingClass : public TechnoClass +{ + public: + BuildingTypeClass const * const Class; + operator StructType(void) const {return Class->Type;}; + + /* + ** If this building is in the process of producing something, then this + ** will point to the factory manager. + */ + FactoryClass * Factory; + + /* + ** This is the house that originally owned this factory. Objects buildable + ** by this house type will be produced from this factory regardless of who + ** the current owner is. + */ + HousesType ActLike; + + /* + ** If the building is at a good point to change orders, then this + ** flag will be set to true. + */ + unsigned IsReadyToCommence:1; + + /* + ** If this building is currently spending money to repair itself, then + ** this flag is true. It will automatically be set to false when the building + ** has reached full strength, when money is exhausted, or if the player + ** specifically stops the repair process. + */ + unsigned IsRepairing:1; + + /* + ** If repair is currently in progress and this flag is true, then a wrench graphic + ** will be overlaid on the building to give visual feedback for the repair process. + */ + unsigned IsWrenchVisible:1; + + /* + ** This flag is set when a commando has raided the building and planted + ** plastic explosives. When the CommandoCountDown timer expires, the + ** building takes massive damage. + */ + unsigned IsGoingToBlow:1; + + /* + ** If this building was destroyed by some method that would prevent + ** survivors, then this flag will be true. + */ + unsigned IsSurvivorless:1; + + /* + ** These state control variables are used by the oblisk for the charging + ** animation. + */ + unsigned IsCharging:1; + unsigned IsCharged:1; + + /* + ** A building that has been captured will not contain the full compliment + ** of crew. This is true even if it subsiquently gets captured back. + */ + unsigned IsCaptured:1; + + /* + ** Special countdown to destruction value. If the building is destroyed, + ** it won't actually be removed from the map until this value reaches + ** zero. This delay is for cosmetic reasons. + */ + TCountDownTimerClass CountDown; + + /* + ** This is the current animation processing state that the building is + ** in. + */ + BStateType BState; + BStateType QueueBState; + + /* + ** For multiplayer games, this keeps track of the last house to damage + ** this building, so if it burns to death or otherwise gradually dies, + ** proper credit can be given for the kill. + */ + HousesType WhoLastHurtMe; + + /* + ** This is the saboteur responsible for this building's destruction. + */ + TARGET WhomToRepay; + + /* + ** This is a record of the last strength of the building. Every so often, + ** it will compare this strength to the current strength. If there is a + ** discrepency, then the owner power is adjusted accordingly. + */ + int LastStrength; + + /* + ** This is the countdown timer that regulates placement retry logic + ** for factory type buildings. + */ + TCountDownTimerClass PlacementDelay; + + /*--------------------------------------------------------------------- + ** Constructors, Destructors, and overloaded operators. + */ + static void * BuildingClass::operator new(size_t size); + static void BuildingClass::operator delete(void *ptr); + BuildingClass(void) : Class(0) {}; + BuildingClass(StructType type, HousesType house); + virtual ~BuildingClass(void); + virtual RTTIType What_Am_I(void) const {return RTTI_BUILDING;}; + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + static void Init(void); + + TARGET Target_Scan(void); + BuildingTypeClass::AnimControlType const * Fetch_Anim_Control(void) {return (&Class->Anims[BState]);}; + + /* + ** Query functions. + */ + virtual CELL Find_Exit_Cell(TechnoClass const * techno) const; + virtual InfantryType Crew_Type(void) const; + virtual int Pip_Count(void) const; + virtual bool Can_Player_Move(void) const {return(false);}; + virtual ActionType What_Action(ObjectClass * target) const; + virtual ActionType What_Action(CELL cell) const; + virtual bool Can_Demolish(void) const; + virtual ObjectTypeClass const & Class_Of(void) const {return *Class;}; + virtual int Refund_Amount(void) const; + virtual DirType Fire_Direction(void) const; + int Power_Output(void) const; + + /* + ** Coordinate inquiry functions. These are used for both display and + ** combat purposes. + */ + virtual COORDINATE Docking_Coord(void) const; + virtual COORDINATE Fire_Coord(int which) const; + virtual COORDINATE Center_Coord(void) const; + virtual COORDINATE Sort_Y(void) const; + virtual COORDINATE Target_Coord(void) const {return Center_Coord();}; + + /* + ** Object entry and exit from the game system. + */ + virtual void Detach(TARGET target, bool all); + virtual void Detach_All(bool all=true); + virtual void Grand_Opening(bool captured = false); + virtual void Update_Buildables(void); + virtual MoveType Can_Enter_Cell(CELL cell, FacingType = FACING_NONE) const; + virtual bool Unlimbo(COORDINATE , DirType dir = DIR_N); + virtual bool Limbo(void); + bool Passes_Proximity_Check(CELL homecell); + + /* + ** Display and rendering support functionality. Supports imagery and how + ** object interacts with the map and thus indirectly controls rendering. + */ + virtual void const * Remap_Table(void); + virtual int Exit_Object(TechnoClass * base); + virtual void Draw_It(int x, int y, WindowNumberType window); + virtual bool Mark(MarkType mark); + virtual void Look(bool incremental=false); + virtual void Fire_Out(void); + void Begin_Mode(BStateType bstate); + + /* + ** User I/O. + */ + virtual void Active_Click_With(ActionType action, ObjectClass * object); + virtual void Active_Click_With(ActionType action, CELL cell); + + /* + ** Combat related. + */ + virtual void Death_Announcement(TechnoClass const * source=0) const; + virtual FireErrorType Can_Fire(TARGET, int which) const; + virtual TARGET Greatest_Threat(ThreatType threat) const; + virtual ResultType Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source=0); + virtual TARGET As_Target(void) const; + virtual bool Captured(HouseClass * newowner); + + /* + ** AI. + */ + virtual void Hidden(void); + virtual bool Revealed(HouseClass * house); + virtual void Repair(int control); + virtual void Sell_Back(int control); + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, long & param); + virtual void AI(void); + virtual void Assign_Target(TARGET target); + virtual bool Toggle_Primary(void); + bool Flush_For_Placement(TechnoClass * techno, CELL cell); + + virtual int Mission_Unload(void); + virtual int Mission_Repair(void); + virtual int Mission_Attack(void); + virtual int Mission_Harvest(void); + virtual int Mission_Guard(void); + virtual int Mission_Construction(void); + virtual int Mission_Deconstruction(void); + virtual int Mission_Missile(void); + virtual void Enter_Idle_Mode(bool initial=false); + + /* + ** Scenario and debug support. + */ + #ifdef CHEAT_KEYS + virtual void Debug_Dump(MonoClass *mono) const; + #endif + + /* + ** File I/O. + */ + static void Read_INI(char *buffer); + static void Write_INI(char *buffer); + static char *INI_Name(void) {return "STRUCTURES";}; + bool Load(FileClass & file); + bool Save(FileClass & file); + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + void Update_Specials(void); + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + private: + void Drop_Debris(TARGET source = TARGET_NONE); + virtual BulletClass * Fire_At(TARGET target, int which); + + static COORDINATE const CenterOffset[BSIZE_COUNT]; + + /* + ** This contains the value of the Virtual Function Table Pointer + */ + static void * VTable; +}; + +#endif diff --git a/BULLET.CPP b/BULLET.CPP new file mode 100644 index 0000000..0966cb8 --- /dev/null +++ b/BULLET.CPP @@ -0,0 +1,786 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\bullet.cpv 2.18 16 Oct 1995 16:50:00 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : BULLET.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 23, 1994 * + * * + * Last Update : March 18, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * BulletClass::AI -- Logic processing for bullet. * + * BulletClass::As_Target -- Converts the bullet into a target value. * + * BulletClass::BulletClass -- Bullet constructor. * + * BulletClass::BulletClass -- Default constructor for bullet objects. * + * BulletClass::Detach -- Removes specified target from this bullet's targeting system. * + * BulletClass::Draw_It -- Displays the bullet at location specified. * + * BulletClass::Init -- Clears the bullets array for scenario preparation. * + * BulletClass::Mark -- Performs related map refreshing under bullet. * + * BulletClass::Occupy_List -- Determines the bullet occupation list. * + * BulletClass::Unlimbo -- Transitions a bullet object into the game render/logic system. * + * BulletClass::delete -- Bullet memory delete. * + * BulletClass::new -- Allocates memory for bullet object. * + * BulletClass::Validate -- validates bullet pointer * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#define GRAVITY 3 + +/* +** This contains the value of the Virtual Function Table Pointer +*/ +void * BulletClass::VTable; + + +/*********************************************************************************************** + * BulletClass::Validate -- validates bullet pointer * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int BulletClass::Validate(void) const +{ + int num; + + num = Bullets.ID(this); + if (num < 0 || num >= BULLET_MAX) { + Validate_Error("BULLET"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*********************************************************************************************** + * BulletClass::BulletClass -- Default constructor for bullet objects. * + * * + * This is the default constructor for bullet objects. A bullet constructed by this routine * + * is not in a usable state for game purposes. It must be constructed by the full * + * (parameterized) constructor -- usually called as part of the overloaded "new" operator. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Do not use bullets that were constructed solely by this routine. * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +BulletClass::BulletClass(void) : + Class(0) +{ + Payback = NULL; + IsToAnimate = false; + Altitude = 0; + Riser = 0; + TarCom = TARGET_NONE; + Strength = 0; + IsLocked = true; + IsInaccurate = false; +} + + +/*********************************************************************************************** + * BulletClass::new -- Allocates memory for bullet object. * + * * + * This function will "allocate" a block of memory for a bullet object. * + * This memory block is merely lifted from a fixed pool of blocks. * + * * + * INPUT: size -- The size of the memory block needed. * + * * + * OUTPUT: Returns with a pointer to an available bullet object block. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/02/1994 JLB : Created. * + *=============================================================================================*/ +void * BulletClass::operator new(size_t ) +{ + void * ptr = Bullets.Allocate(); + if (ptr) { + ((BulletClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +/*********************************************************************************************** + * BulletClass::delete -- Bullet memory delete. * + * * + * Since bullets memory is merely "allocated" out of a pool, it never * + * actually gets deleted. * + * * + * INPUT: ptr -- Generic pointer to bullet object. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/02/1994 JLB : Created. * + *=============================================================================================*/ +void BulletClass::operator delete(void *ptr) +{ + if (ptr) { + ((BulletClass *)ptr)->IsActive = false; + } + Bullets.Free((BulletClass *)ptr); +} + + +/*********************************************************************************************** + * BulletClass::BulletClass -- Bullet constructor. * + * * + * This is the constructor for the bullet class. It handles all * + * initialization of the bullet and starting it in motion toward its * + * target. * + * * + * INPUT: id -- The type of bullet this is (could be missile). * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/02/1994 JLB : Created. * + * 06/20/1994 JLB : Firer is a base class pointer. * + * 12/10/1994 JLB : Auto calculate range optional. * + * 12/12/1994 JLB : Handles small arms as an instantaneous effect. * + * 12/23/1994 JLB : Fixed scatter algorithm for non-homing projectiles. * + * 12/31/1994 JLB : Removed range parameter (not needed). * + *=============================================================================================*/ +BulletClass::BulletClass(BulletType id) : + Class(&BulletTypeClass::As_Reference(id)) +{ + Altitude = 0; + IsInaccurate = false; + IsLocked = true; +// IsLocked = false; + IsToAnimate = false; + Payback = 0; + Riser = 0; + Strength = Class->MaxStrength; + TarCom = TARGET_NONE; +} + + +/*********************************************************************************************** + * BulletClass::Occupy_List -- Determines the bullet occupation list. * + * * + * This function will determine the cell occupation list and return a pointer to it. Most * + * bullets are small and the list is usually short, but on occasion, it can be a list that * + * rivals the size of regular vehicles. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the cell offset list that covers all the cells a bullet * + * is over. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/20/1994 JLB : Created. * + * 01/05/1995 JLB : Handles projectiles with altitude. * + *=============================================================================================*/ +short const *BulletClass::Occupy_List(void) const +{ + Validate(); + switch (*this) { + case BULLET_FLAME: + return(Coord_Spillage_List(Coord, 25)); + + case BULLET_NUKE_UP: + case BULLET_NUKE_DOWN: + return(Coord_Spillage_List(Coord, 48)); + + default: + if (Altitude) { + static CELL _list[10]; + const short * ptr = Coord_Spillage_List(Coord, 5); + int index = 0; + CELL cell1 = Coord_Cell(Coord); + + while (ptr[index] != REFRESH_EOL) { + _list[index] = ptr[index]; + index++; + } + + COORDINATE coord = XY_Coord(0, Altitude); + coord = Coord_Sub(Coord, coord); + CELL cell2 = Coord_Cell(coord); + ptr = Coord_Spillage_List(coord, 5); + while (*ptr != REFRESH_EOL) { + _list[index++] = (*ptr++) + (cell2 - cell1); + } + _list[index] = REFRESH_EOL; + return(_list); + } + } + return(Coord_Spillage_List(Coord, 10)); +} + + +/*********************************************************************************************** + * BulletClass::Mark -- Performs related map refreshing under bullet. * + * * + * This routine marks the objects under the bullet so that they will * + * be redrawn. This is necessary as the bullet moves -- objects under * + * its path need to be restored. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/02/1994 JLB : Created. * + *=============================================================================================*/ +bool BulletClass::Mark(MarkType mark) +{ + Validate(); + if (ObjectClass::Mark(mark)) { + if (!Class->IsInvisible) { + Map.Refresh_Cells(Coord_Cell(Coord), Occupy_List()); + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * BulletClass::AI -- Logic processing for bullet. * + * * + * This routine will perform all logic (flight) logic on the bullet. * + * Primarily this is motion, fuse tracking, and detonation logic. Call * + * this routine no more than once per bullet per game tick. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/02/1994 JLB : Created. * + *=============================================================================================*/ +void BulletClass::AI(void) +{ + Validate(); + COORDINATE coord; + + ObjectClass::AI(); + + /* + ** Balistic objects are handled here. + */ + bool forced = false; // Forced explosion. + if (Class->IsArcing) { + Altitude += Riser; + + if (Altitude <= 0) { + forced = true; + } + if (Riser > -100) { + Riser -= GRAVITY; + } + } + if (Class->IsDropping) { + Altitude += Riser; + + if (Altitude <= 0) { + forced = true; + } + if (Riser > -100) { + Riser -= 1; + } + } + + /* + ** Homing projectiles constantly change facing to face toward the target but + ** they only do so every other game frame (improves game speed and makes + ** missiles not so deadly). + */ + if ((Frame & 0x01) && Class->IsHoming && Target_Legal(TarCom)) { + PrimaryFacing.Set_Desired(Direction256(Coord, ::As_Coord(TarCom))); + } + + /* + ** Move the projectile forward according to its speed + ** and direction. + */ + coord = Coord; + if (Class->IsFlameEquipped) { + if (IsToAnimate) { + new AnimClass(ANIM_SMOKE_PUFF, coord, 1); + } + IsToAnimate = !IsToAnimate; + } + + /* + ** Handle any body rotation at this time. This process must + ** occur every game fame in order to achieve smooth rotation. + */ + if (PrimaryFacing.Is_Rotating()) { + PrimaryFacing.Rotation_Adjust(Class->ROT); + } + + switch (Physics(coord, PrimaryFacing)) { + + /* + ** When a projectile reaches the edge of the world, it + ** vanishes from existence -- presumed to explode off + ** map. + */ + case IMPACT_EDGE: +// if (IsLocked) { + Mark(); + delete this; +// } + break; + + default: + case IMPACT_NONE: + + /* + ** The projectile has moved. Check its fuse. If detonation + ** is signaled, then do so. Otherwise, just move. + */ + case IMPACT_NORMAL: + Mark(); +// IsLocked = true; + if (!Class->IsHigh) { + CellClass * cellptr = &Map[Coord_Cell(coord)]; + if (cellptr->Overlay != OVERLAY_NONE && OverlayTypeClass::As_Reference(cellptr->Overlay).IsHigh) { + forced = true; + Coord = coord = Cell_Coord(Coord_Cell(coord)); + } + } + + /* + ** Bullets are generaly more effective when they are fired at aircraft. + */ + if (Class->IsAntiAircraft && As_Aircraft(TarCom) && Distance(TarCom) < 0x0080) { + forced = true; + + if (*this == BULLET_TOW) { + Strength += Strength/3; + } else { + Strength += Strength/2; + } + } + + if (!forced && (Class->IsDropping || !Fuse_Checkup(coord))) { + Coord = coord; + Mark(); + + /* + ** Certain projectiles loose strength when they travel. + */ + if (*this == BULLET_BULLET) { + if (Strength > 5) Strength--; + } + + } else { + + /* + ** When the target is reached, explode and do the damage + ** required of it. For homing objects, don't force the explosion to + ** match the target position. Non-homing projectiles adjust position so + ** that they hit the target. This compensates for the error in line of + ** flight logic. + */ + Mark(); + if (!forced && !Class->IsArcing && !Class->IsHoming && Fuse_Target()) { + Coord = Fuse_Target(); + } + + /* + ** Non-aircraft targets apply damage to the ground. + */ + if (!Is_Target_Aircraft(TarCom) || As_Aircraft(TarCom)->In_Which_Layer() == LAYER_GROUND) { + Explosion_Damage(Coord, Strength, Payback, Class->Warhead); + } else { + + /* + ** Special damage apply for SAM missiles. This is the only way that missile + ** damage affects the aircraft target. + */ + if (Distance(TarCom) < 0x0080) { + AircraftClass * object = As_Aircraft(TarCom); + + int str = Strength; + if (object) object->Take_Damage(str, 0, Class->Warhead, Payback); + } + } + + /* + ** For projectiles that are invisible while travelling toward the target, + ** allow scatter effect for the impact animation. + */ + if (Class->IsInvisible) { + Coord = Coord_Scatter(Coord, 0x0020); + } + if (Class->Explosion != ANIM_NONE) { + new AnimClass(Class->Explosion, Coord); + } + delete this; + return; + } + break; + } +} + + +/*********************************************************************************************** + * BulletClass::Draw_It -- Displays the bullet at location specified. * + * * + * This routine displays the bullet visual at the location specified. * + * * + * INPUT: x,y -- The center coordinate to render the bullet at. * + * * + * window -- The window to clip to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/20/1994 JLB : Created. * + * 06/27/1994 JLB : Takes a window clipping parameter. * + * 01/08/1995 JLB : Handles translucent colors if necessary. * + *=============================================================================================*/ +void BulletClass::Draw_It(int x, int y, WindowNumberType window) +{ + Validate(); + int facing = Facing_To_32(PrimaryFacing); + + /* + ** Certain projectiles aren't visible. This includes small bullets (which are actually + ** invisible) and flame thrower flames (which are rendered as an animation instead of a projectile). + */ + if (Class->IsInvisible) return; + + /* + ** If there is no shape loaded for this object, then + ** it obviously can't be rendered -- just bail. + */ + void const * shapeptr = Class->Get_Image_Data(); + if (!shapeptr) return; + + /* + ** Get the basic shape number for this projectile. + */ + int shapenum = 0; + if (!Class->IsFaceless) { + shapenum = UnitClass::BodyShape[facing]; + } + + /* + ** For tumbling projectiles, fetch offset stage. + */ + if (*this == BULLET_NAPALM) { + shapenum += Frame % 6; + } + + if (*this == BULLET_GRENADE) { + shapenum += Frame % 8; +// Timer++; + } + + /* + ** For flying projectiles, draw the shadow and adjust the actual projectile body + ** render position. + */ + if (Altitude) { + CC_Draw_Shape(shapeptr, shapenum, x, y, window, SHAPE_PREDATOR|SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_FADING, NULL, Map.FadingShade); + y -= Lepton_To_Pixel(Altitude); + } + + /* + ** Draw the main body of the projectile. + */ + ShapeFlags_Type flags = SHAPE_NORMAL; + if (Class->IsTranslucent) { + flags = SHAPE_GHOST; + } + CC_Draw_Shape(shapeptr, shapenum, x, y, window, flags|SHAPE_CENTER|SHAPE_WIN_REL, NULL, Map.UnitShadow); +} + + +/*********************************************************************************************** + * BulletClass::Init -- Clears the bullets array for scenario preparation. * + * * + * This routine will zero out the bullet tracking list and object array in preparation for * + * the start of a new scenario. All bullets cease to exists after this function is * + * called. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/15/1994 JLB : Created. * + *=============================================================================================*/ +void BulletClass::Init(void) +{ + BulletClass *ptr; + + Bullets.Free_All(); + + ptr = new BulletClass(); + VTable = ((void **)(((char *)ptr) + sizeof(AbstractClass) - 4))[0]; + delete ptr; +} + + +/*********************************************************************************************** + * BulletClass::Detach -- Removes specified target from this bullet's targeting system. * + * * + * When an object is removed from the game system, it must be removed all targeting and * + * tracking systems as well. This routine is used to remove the specified object from the * + * bullet. If the object isn't part of this bullet's tracking system, then no action is * + * performed. * + * * + * INPUT: target -- The target to remove from this tracking system. * + * * + * all -- Is the target going away for good as opposed to just cloaking/hiding? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +void BulletClass::Detach(TARGET target, bool all) +{ + Validate(); + ObjectClass * obj = As_Object(target); + + if (obj == Payback) { + Payback = 0; + } + + if (all && target == TarCom) { + TarCom = TARGET_NONE; + } +} + + +/*********************************************************************************************** + * BulletClass::Unlimbo -- Transitions a bullet object into the game render/logic system. * + * * + * This routine is used to take a bullet object that is in limbo and transition it to the * + * game system. A bullet object so transitioned, will be drawn and logic processing * + * performed. In effect, it comes into existance. * + * * + * INPUT: coord -- The location where the bullet object is to appear. * + * * + * dir -- The initial facing for the bullet object. * + * * + * OUTPUT: bool; Was the unlimbo successful? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/10/1995 JLB : Created. * + *=============================================================================================*/ +bool BulletClass::Unlimbo(COORDINATE coord, DirType dir) +{ + Validate(); + /* + ** Try to unlimbo the bullet as far as the base class is concerned. Use the already + ** set direction and strength if the "punt" values were passed in. This allows a bullet + ** to be setup prior to being launched. + */ + if (ObjectClass::Unlimbo(coord)) { + COORDINATE tcoord = As_Coord(TarCom); + + /* + ** Homing projectiles (missiles) do NOT override facing. They just fire in the + ** direction specified and let the chips fall where they may. + */ + if (!Class->IsHoming && !Class->IsDropping) { + dir = Direction(tcoord); + } + + /* + ** Possibly adjust the target if this projectile is inaccurate. This occurs whenever + ** certain weapons are trained upon targets they were never designed to attack. Example: when + ** turrets or anti-tank missiles are fired at infantry. Indirect + ** fire is inherently inaccurate. + */ + if (IsInaccurate || Class->IsInaccurate || + ((Is_Target_Cell(TarCom) || Is_Target_Infantry(TarCom)) && (Class->Warhead == WARHEAD_AP || Class->IsFueled))) { + + /* + ** Inaccuracy for low velocity or homing projectiles manifests itself as a standard + ** Cicular Error of Probability (CEP) algorithm. High speed projectiles usually + ** just overshoot the target by extending the straight line flight. + */ + if (Class->IsHoming || Class->IsArcing) { + int scatterdist = ::Distance(coord, tcoord)/3; + scatterdist = MIN(scatterdist, 0x0200); + + if (*this == BULLET_GRENADE) { + scatterdist = ::Distance(coord, tcoord)/4; + scatterdist = MIN(scatterdist, 0x0080); + } + + dir = (DirType)((dir + (Random_Pick(0, 10)-5)) & 0x00FF); + tcoord = Coord_Scatter(tcoord, Random_Pick(0, scatterdist)); + } else { + tcoord = Coord_Move(tcoord, dir, Random_Pick(0, 0x0100)); + } + + /* + ** Limit scatter to the weapon range of the firer. + */ + if (Payback) { + if (!Payback->In_Range(tcoord, 0) && !Payback->In_Range(tcoord, 1)) { + tcoord = Coord_Move(tcoord, ::Direction(tcoord, Coord), Distance(tcoord) - MAX(Payback->Weapon_Range(0), Payback->Weapon_Range(1))); + } + } + } + + /* + ** For very fast and invisible projectiles, just make the projectile exist at the target + ** location and dispense with the actual flight. + */ + if (Class->MaxSpeed == MPH_LIGHT_SPEED && Class->IsInvisible) { + Coord = tcoord; + } + + /* + ** Set the range equal to either the class defined range or the calculated + ** number of game frames it would take for the projectile to reach the target. + */ + int range = 0xFF; + if (!Class->Range) { + if (!Class->IsDropping) { + range = (::Distance(tcoord, Coord) / Class->MaxSpeed) + 4; + } + } else { + range = Class->Range; + } + + /* + ** Projectile speed is usually the default value for that projectile, but + ** certian projectiles alter speed according to the distance to the + ** target. + */ + int speed = Class->MaxSpeed; + if (speed == MPH_LIGHT_SPEED) speed = MPH_IMMOBILE; + if (Class->IsArcing) { + speed = Class->MaxSpeed + (Distance(tcoord)>>5); + + /* + ** Set minimum speed (i.e., distance) for arcing projectiles. + */ + speed = MAX(speed, 25); + } + if (!Class->IsDropping) { + Fly_Speed(255, (MPHType)speed); + } + + /* + ** Arm the fuse. + */ + Arm_Fuse(Coord, tcoord, range, ((As_Aircraft(TarCom)!=0) ? 0 : Class->Arming)); + + /* + ** Projectiles that make a ballistic flight to impact point must determine a + ** vertical component for the projectile launch. This is crudely simulated + ** by biasing ground speed according to target distance and then giving + ** enough vertical velocity to keep the projectile airborne for the + ** desired amount of time. The mathematically correct solution would be to + ** calculate launch angle (given fixed projectile velocity) and then derive + ** the vertical and horizontal components. This solution would require a + ** of square root and an arcsine lookup table. OUCH! + */ + Altitude = 0; + Riser = 0; + if (Class->IsArcing) { + Altitude = 1; + Riser = ((Distance(tcoord)/2) / (speed+1))*GRAVITY; + Riser = MAX(Riser, 10); + } + if (Class->IsDropping) { + Altitude = Pixel_To_Lepton(24); + Riser = 0; + } + + PrimaryFacing = dir; + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * BulletClass::As_Target -- Converts the bullet into a target value. * + * * + * This support routine is used to convert the bullet (as a pointer) into a target * + * value (which is a number). * + * * + * INPUT: none * + * * + * OUTPUT: Returns the bullet as a target value. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/08/1994 JLB : Created. * + *=============================================================================================*/ +TARGET BulletClass::As_Target(void) const +{ + Validate(); + return(Build_Target(KIND_BULLET, Bullets.ID(this))); +} + diff --git a/BULLET.H b/BULLET.H new file mode 100644 index 0000000..db97db0 --- /dev/null +++ b/BULLET.H @@ -0,0 +1,153 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\bullet.h_v 2.18 16 Oct 1995 16:47:40 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : BULLET.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 23, 1994 * + * * + * Last Update : April 23, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef BULLET_H +#define BULLET_H + +#include "object.h" +#include "fly.h" +#include "fuse.h" + + +class BulletClass : public ObjectClass, + public FlyClass, + public FuseClass +{ + public: + + /* + ** This specifies exactly what kind of bullet this is. All of the static attributes + ** for this bullet is located in the BulletTypeClass pointed to by this variable. + */ + BulletTypeClass const * const Class; + operator BulletType(void) const {return Class->Type;}; + + /* + ** Records who sent this "present" so that an appropriate "thank you" can + ** be returned. + */ + TechnoClass * Payback; + + /* + ** This is the facing that the projectile is travelling. + */ + FacingClass PrimaryFacing; + + /*--------------------------------------------------------------------- + ** Constructors, Destructors, and overloaded operators. + */ + static void * BulletClass::operator new(size_t size); + static void BulletClass::operator delete(void *ptr); + BulletClass(void); + BulletClass(BulletType id); + virtual ~BulletClass(void) {if (GameActive) BulletClass::Limbo();}; + virtual RTTIType What_Am_I(void) const {return RTTI_BULLET;}; + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + static void Init(void); + + virtual void Assign_Target(TARGET target) {TarCom = target;}; + virtual bool Unlimbo(COORDINATE , DirType facing = DIR_N); + virtual LayerType In_Which_Layer(void) const {return LAYER_TOP;}; + virtual ObjectTypeClass const & Class_Of(void) const {return *Class;}; + virtual void Detach(TARGET target, bool all); + virtual void Draw_It(int x, int y, WindowNumberType window); + virtual bool Mark(MarkType mark=MARK_CHANGE); + virtual void AI(void); + virtual short const * Occupy_List(void) const; + virtual short const * Overlap_List(void) const {return Occupy_List();}; + virtual TARGET As_Target(void) const; + + /* + ** File I/O. + */ + bool Load(FileClass & file); + bool Save(FileClass & file); + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + /* + ** If this bullet is forced to be inaccurate because of some outside means. A tank + ** firing while moving is a good example. + */ + unsigned IsInaccurate:1; + + private: + + // Crude animation flag. + unsigned IsToAnimate:1; + + /* + ** This is the height of the projectile. It starts at a low height, rises to an + ** apogee and then drops to explode upon impact. The height is used to render + ** the bullet's vertical offset. + */ + int Altitude; + + /* + ** This is a modifier for the altitude that rises and falls in order to simulate + ** the arc of the projectile. This value is added to the height every game tick + ** while simultaneously being reduced itself. The net effect, is a rising + ** projectile that slows and then eventually drops. + */ + signed char Riser; + + /* + ** This is the target of the projectile. It is especially significant for those projectiles + ** that home in on a target. + */ + TARGET TarCom; + + /* + ** Is this missle allowed to come in from out of bounds? + */ + unsigned IsLocked:1; + + /* + ** This contains the value of the Virtual Function Table Pointer + */ + static void * VTable; +}; + +#endif diff --git a/CARGO.CPP b/CARGO.CPP new file mode 100644 index 0000000..a8e7367 --- /dev/null +++ b/CARGO.CPP @@ -0,0 +1,183 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\cargo.cpv 2.18 16 Oct 1995 16:49:50 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CARGO.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 23, 1994 * + * * + * Last Update : 10/31/94 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * CargoClass::Attach -- Add unit to cargo hold. * + * CargoClass::Attached_Object -- Determine attached unit pointer. * + * CargoClass::Detach_Object -- Removes a unit from the cargo hold. * + * CargoClass::Debug_Dump -- Displays the cargo value to the monochrome screen. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * CargoClass::Debug_Dump -- Displays the cargo value to the monochrome screen. * + * * + * This routine is used to dump the current cargo value to the monochrome monitor. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/02/1994 JLB : Created. * + *=============================================================================================*/ +void CargoClass::Debug_Dump(MonoClass * mono) const +{ + if (How_Many()) { + mono->Set_Cursor(63, 3); + mono->Printf("(%d)%04X", How_Many(), Attached_Object()); + } +} +#endif + + +/*********************************************************************************************** + * CargoClass::Attach -- Add unit to cargo hold. * + * * + * This routine will add the specified unit to the cargo hold. The * + * unit will chain to any existing units in the hold. The chaining is * + * in a LIFO order. * + * * + * INPUT: object-- Pointer to the object to attach to the cargo hold. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/23/1994 JLB : Created. * + * 10/31/94 JLB : Handles chained objects. * + *=============================================================================================*/ +void CargoClass::Attach(FootClass * object) +{ + /* + ** If there is no object, then no action is necessary. + */ + if (!object) return; + + object->Limbo(); + + /* + ** Attach any existing cargo hold object to the end of the list as indicated by the + ** object pointer passed into this routine. This is necessary because several objects may + ** be attached at one time or several objects may be attached as a result of several calls + ** to this routine. Either case must be handled properly. + */ + ObjectClass * o = object->Next; + while (o) { + if (!o->Next) break; + o = o->Next; + } + if (o) { + o->Next = CargoHold; + } else { + object->Next = CargoHold; + } + + /* + ** Finally, assign the object pointer as the first object attached to this cargo hold. + */ + CargoHold = object; + Quantity = 0; + object = CargoHold; + while (object) { + Quantity++; + object = (FootClass *)object->Next; + } +} + + +/*********************************************************************************************** + * CargoClass::Detach_Object -- Removes a unit from the cargo hold. * + * * + * This routine will take a unit from the cargo hold and extract it. * + * The unit extracted is the last unit added to the hold. If there * + * is no unit in the hold or the occupant is not a unit, then NULL is * + * returned. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the unit that has been extracted. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/23/1994 JLB : Created. * + * 06/07/1994 JLB : Handles generic object types. * + *=============================================================================================*/ +FootClass * CargoClass::Detach_Object(void) +{ + FootClass * unit = Attached_Object(); + + if (unit) { + CargoHold = (FootClass *)unit->Next; + unit->Next = 0; + Quantity--; + } + return(unit); +} + + +/*********************************************************************************************** + * CargoClass::Attached_Object -- Determine attached unit pointer. * + * * + * This routine will return with a pointer to the attached unit if one * + * is present. One would need to know this if this is a transport * + * unit and it needs to unload. * + * * + * INPUT: none * + * * + * OUTPUT: Returns a pointer to the attached unit. If there is no * + * attached unit, then return NULL. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/07/1992 JLB : Created. * + * 06/07/1994 JLB : Handles generic object types. * + *=============================================================================================*/ +FootClass * CargoClass::Attached_Object(void) const +{ + if (Is_Something_Attached()) { + return(CargoHold); + } + return(NULL); +} + + diff --git a/CARGO.H b/CARGO.H new file mode 100644 index 0000000..6adefc4 --- /dev/null +++ b/CARGO.H @@ -0,0 +1,91 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\cargo.h_v 2.20 16 Oct 1995 16:45:14 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CARGO.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 23, 1994 * + * * + * Last Update : April 23, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef CARGO_H +#define CARGO_H + +class FootClass; + +/**************************************************************************** +** This class handles the basic cargo logic. +*/ +class CargoClass { + public: + + /*--------------------------------------------------------------------- + ** Constructors, Destructors, and overloaded operators. + */ + CargoClass(void) {Quantity = 0;CargoHold = 0;}; + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + + #ifdef CHEAT_KEYS + void Debug_Dump(MonoClass *mono) const; + #endif + void AI(void) {}; + + int How_Many(void) const {return Quantity;}; + bool Is_Something_Attached(void) const {return (CargoHold != 0);}; + FootClass * Attached_Object(void) const; + FootClass * Detach_Object(void); + void Attach(FootClass * object); + + /* + ** File I/O. + */ + void Code_Pointers(void); + void Decode_Pointers(void); + + private: + + /* + ** This is the number of objects attached to this cargo hold. For transporter + ** objects, they might contain more than one object. + */ + unsigned char Quantity; + + /* + ** This is the target value of any attached object. A value of zero indicates + ** that no object is attached. + */ + FootClass * CargoHold; +}; + +#endif + diff --git a/CCDDE.CPP b/CCDDE.CPP new file mode 100644 index 0000000..4f1603e --- /dev/null +++ b/CCDDE.CPP @@ -0,0 +1,422 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer - Red Alert * + * * + * File Name : CCDDE.CPP * + * * + * Programmer : Steve Tall * + * * + * Start Date : 10/04/95 * + * * + * Last Update : August 5th, 1996 [ST] * + * * + *---------------------------------------------------------------------------------------------* + * Overview: * + * C&C's interface to the DDE class * + * * + *---------------------------------------------------------------------------------------------* + * * + * Functions: * + * DDE_Callback -- DDE server callback function * + * DDEServerClass::DDEServerClass -- class constructor * + * DDEServerClass::Enable -- Enables the DDE callback * + * DDEServerClass::Disable -- Disables the DDE callback * + * DDEServerClass::~DDEServerClass -- class destructor * + * DDESC::Callback -- callback function. Called from the DDE_Callback wrapper function * + * DDESC::Get_MPlayer_Game_Info -- returns a pointer to the multiplayer setup info from wchat * + * DDESC::Delete_MPlayer_Game_Info -- clears out multi player game setup info * + * DDESC::Time_Since_Heartbeat -- returns the time in ticks since the last heartbeat from wchat* + * * + * Send_Data_To_DDE_Server -- sends a packet to WChat * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + +#define WIN32 +#include +#include "ccdde.h" +#include +#include + +DDEServerClass DDEServer; //Instance of the DDE Server class + +Instance_Class *DDE_Class = NULL; // pointer for client callback + // this *must* be called DDE_Class + +BOOL CC95AlreadyRunning = FALSE; //Was there an instance of C&C 95 already running when we started? + +extern HWND MainWindow; +extern TimerClass GameTimer; +extern BOOL GameTimerInUse; +extern void CCDebugString (char *string); + + +/*********************************************************************************************** + * DDE_Callback -- DDE server callback function * + * * + * Just acts as a wrapper for the DDEServerClass callback function * + * * + * INPUT: ptr to data from client * + * length of data * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/8/96 3:19PM ST : Created * + *=============================================================================================*/ +BOOL CALLBACK DDE_Callback (unsigned char *data, long length) +{ + return (DDEServer.Callback(data, length)); +} + + + + +/*********************************************************************************************** + * DDEServerClass::DDEServerClass -- class constructor * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/8/96 3:20PM ST : Created * + *=============================================================================================*/ +DDEServerClass::DDEServerClass(void) +{ + MPlayerGameInfo = NULL; //Flag that we havnt received a start game info packet yet + + DDE_Class = new Instance_Class ("CONQUER", "WCHAT"); + + DDE_Class->Enable_Callback( TRUE ); + IsEnabled = TRUE; + + if (DDE_Class->Test_Server_Running(DDE_Class->local_name)){ + CC95AlreadyRunning = TRUE; + }else{ + DDE_Class->Register_Server( DDE_Callback ); + } +} + + + +void DDEServerClass::Enable(void) +{ + if (!IsEnabled){ + DDE_Class->Enable_Callback( TRUE ); + IsEnabled = TRUE; + } +} + + + +/*********************************************************************************************** + * DDEServerClass::Disable -- Disables the DDE callback * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/5/96 9:44PM ST : Created * + *=============================================================================================*/ +void DDEServerClass::Disable(void) +{ + if (IsEnabled){ + DDE_Class->Enable_Callback( FALSE ); + IsEnabled = FALSE; + } +} + + + + + + +/*********************************************************************************************** + * DDEServerClass::~DDEServerClass -- class destructor * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/8/96 3:20PM ST : Created * + *=============================================================================================*/ +DDEServerClass::~DDEServerClass(void) +{ + Delete_MPlayer_Game_Info(); + delete( DDE_Class ); +} + + + +/*********************************************************************************************** + * DDESC::Callback -- callback function. Called from the DDE_Callback wrapper function * + * * + * * + * * + * INPUT: data from DDE client * + * length of data * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: Data has length and type as first 2 ints * + * * + * HISTORY: * + * 6/8/96 3:21PM ST : Created * + *=============================================================================================*/ +BOOL DDEServerClass::Callback(unsigned char *data, long length) +{ + + /* + ** If the packet length < 0 then this is a special advisory packet + */ + if ( length<0 ) { + + switch( length ) { + + case DDE_ADVISE_CONNECT: + CCDebugString("C&C95 - DDE advisory: client connect detected."); + return TRUE; + + case DDE_ADVISE_DISCONNECT: + CCDebugString("C&C95 - DDE advisory: client disconnect detected."); + return TRUE; + + default: + CCDebugString("C&C95 - DDE advisory: Unknown DDE advise type."); + return FALSE; + } + + }else{ + + /* + ** Packet must be at least the length of the packet type & size fields to be valid + */ + if (length < 2*sizeof(int)) { + CCDebugString ("C&C95 - Received invalid packet."); + return (FALSE); + } + + /* + ** Find out what kind of packet this is and its length. + */ + int *packet_pointer = (int *)data; + int actual_length = ntohl(*packet_pointer++); + int packet_type = ntohl(*packet_pointer++); + + /* + ** Strip the ID int from the start of the packet + */ + data += 2*sizeof (int); + length -= 2*sizeof (int); + actual_length -= 2*sizeof (int); + + /* + ** Take the appropriate action for the packet type + */ + switch ( packet_type ){ + + /* + ** This is a packet with the info required for starting a new internet game. This is really + * just C&CSPAWN.INI sent from WChat instead of read from disk. + */ + case DDE_PACKET_START_MPLAYER_GAME: + CCDebugString("C&C95 - Received start game packet."); + Delete_MPlayer_Game_Info(); + MPlayerGameInfo = new char [actual_length + 1]; + memcpy (MPlayerGameInfo, data, actual_length); + *(MPlayerGameInfo + actual_length) = 0; //Terminator in case we treat it as a string + MPlayerGameInfoLength = actual_length; + LastHeartbeat = 0; + break; + + case DDE_TICKLE: + CCDebugString("C&C95 - Received 'tickle' packet."); + //SetForegroundWindow ( MainWindow ); + //ShowWindow ( MainWindow, SW_SHOWMAXIMIZED ); + break; + + case DDE_PACKET_HEART_BEAT: + CCDebugString("C&C95 - Received heart beat packet."); + if (GameTimerInUse){ + LastHeartbeat = GameTimer.Time(); + }else{ + LastHeartbeat = 0; + } + break; + + default: + CCDebugString("C&C95 - Received unrecognised packet."); + break; + + } + } + + return (TRUE); + +} + + + +/*********************************************************************************************** + * DDESC::Get_MPlayer_Game_Info -- returns a pointer to the multiplayer setup info from wchat * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: ptr to data in .INI file format * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/8/96 3:23PM ST : Created * + *=============================================================================================*/ +char *DDEServerClass::Get_MPlayer_Game_Info (void) +{ + return (MPlayerGameInfo); +} + + + +/*********************************************************************************************** + * DDESC::Delete_MPlayer_Game_Info -- clears out multi player game setup info * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/8/96 3:24PM ST : Created * + *=============================================================================================*/ +void DDEServerClass::Delete_MPlayer_Game_Info(void) +{ + if (MPlayerGameInfo){ + delete [] MPlayerGameInfo; + MPlayerGameInfo = NULL; + } +} + + + +/*********************************************************************************************** + * DDESC::Time_Since_Heartbeat -- returns the time in ticks since the last heartbeat from wchat* + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: time since heartbeat * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/9/96 11:05PM ST : Created * + *=============================================================================================*/ +int DDEServerClass::Time_Since_Heartbeat(void) +{ + return (GameTimer.Time() - LastHeartbeat); +} + + + + +/*********************************************************************************************** + * Send_Data_To_DDE_Server -- sends a packet to WChat * + * * + * * + * * + * INPUT: ptr to the data to send * + * length of data * + * packet type identifier * + * * + * OUTPUT: true if packet successfully sent * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/9/96 11:07PM ST : Created * + *=============================================================================================*/ +BOOL Send_Data_To_DDE_Server (char *data, int length, int packet_type) +{ +#if (0) + BOOL app_exists; + + app_exists = DDE_Class->Test_Server_Running(DDE_Class->remote_name); + + if (app_exists != TRUE) { + CCDebugString("Connection to server failed!"); + return(FALSE); + } +#endif //(0) + + if( DDE_Class->Open_Poke_Connection(DDE_Class->remote_name) == FALSE) { + CCDebugString("C&C95 - Failed to connect for POKE!"); + return (FALSE); + } + + char *poke_data = new char [length + 2*sizeof(int)]; + + int *poke_data_int = (int*)poke_data; + + *poke_data_int = htonl (length + 2*sizeof(int)); + *(poke_data_int+1)= htonl (packet_type); + + memcpy (poke_data + 8, data, length); + + + if(DDE_Class->Poke_Server( (LPBYTE) poke_data, ntohl(*poke_data_int) ) == FALSE) { + CCDebugString("C&C95 - POKE failed!\n"); + DDE_Class->Close_Poke_Connection(); // close down the link + delete poke_data; + return (FALSE); + } + + DDE_Class->Close_Poke_Connection(); // close down the link + + delete poke_data; + + return (TRUE); +} + + diff --git a/CCDDE.H b/CCDDE.H new file mode 100644 index 0000000..1057a92 --- /dev/null +++ b/CCDDE.H @@ -0,0 +1,88 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer - Red Alert * + * * + * File Name : CCDDE.H * + * * + * Programmer : Steve Tall * + * * + * Start Date : 10/04/95 * + * * + * Last Update : August 5th, 1996 [ST] * + * * + *---------------------------------------------------------------------------------------------* + * Overview: * + * C&C's interface to the DDE class * + * * + *---------------------------------------------------------------------------------------------* + * * + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifdef WIN32 + +#include "dde.h" + +class DDEServerClass { + + public: + + DDEServerClass (void); + ~DDEServerClass (void); + + + char *Get_MPlayer_Game_Info (void); //Returns pointer to game info + int Get_MPlayer_Game_Info_Length(){return(MPlayerGameInfoLength);}; //Len of game info + BOOL Callback(unsigned char *data, long length); //DDE callback function + void Delete_MPlayer_Game_Info(void); //release the game info memory + void Enable(void); //Enable the DDE callback + void Disable(void); //Disable the DDE callback + int Time_Since_Heartbeat(void); //Returns the time since the last hearbeat from WChat + + /* + ** Enumeration for DDE packet types from WChat + */ + enum { + DDE_PACKET_START_MPLAYER_GAME, //Start game packet. This includes game options + DDE_PACKET_GAME_RESULTS, //Game results packet. The game statistics. + DDE_PACKET_HEART_BEAT, //Heart beat packet so we know WChat is still there. + DDE_TICKLE, //Message to prompt other app to take focus. + DDE_CONNECTION_FAILED + }; + + + private: + + char *MPlayerGameInfo; //Pointer to game start packet + int MPlayerGameInfoLength; //Length of game start packet. + BOOL IsEnabled; //Flag for DDE callback enable + int LastHeartbeat; // Time since last heartbeat packet was received from WChat + +}; + + +extern DDEServerClass DDEServer; +extern BOOL Send_Data_To_DDE_Server (char *data, int length, int packet_type); + + +#endif //WIN32 diff --git a/CCFILE.CPP b/CCFILE.CPP new file mode 100644 index 0000000..1ee39de --- /dev/null +++ b/CCFILE.CPP @@ -0,0 +1,626 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CCFILE.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : August 8, 1994 * + * * + * Last Update : March 20, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * CCFileClass::CCFileClass -- Default constructor for file object. * + * CCFileClass::CCFileClass -- Filename based constructor for C&C file. * + * CCFileClass::Close -- Closes the file. * + * CCFileClass::Is_Available -- Checks for existence of file on disk or in mixfile. * + * CCFileClass::Is_Open -- Determines if the file is open. * + * CCFileClass::Open -- Opens a file from either the mixfile system or the rawfile system. * + * CCFileClass::Read -- Reads data from the file. * + * CCFileClass::Seek -- Moves the current file pointer in the file. * + * CCFileClass::Size -- Determines the size of the file. * + * CCFileClass::Write -- Writes data to the file (non mixfile files only). * + * CCFileClass::Error -- Handles displaying a file error message. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + +#include "function.h" +//#include +//#include +//#include +//#include +//#include +//#include +//#include "ccfile.h" + + +/*********************************************************************************************** + * CCFileClass::Error -- Handles displaying a file error message. * + * * + * Display an error message as indicated. If it is allowed to retry, then pressing a key * + * will return from this function. Otherwise, it will exit the program with "exit()". * + * * + * INPUT: error -- The error number (same as the DOSERR.H error numbers). * + * * + * canretry -- Can this routine exit normally so that retrying can occur? If this is * + * false, then the program WILL exit in this routine. * + * * + * filename -- Optional filename to report with this error. If no filename is * + * supplied, then no filename is listed in the error message. * + * * + * OUTPUT: none, but this routine might not return at all if the "canretry" parameter is * + * false or the player pressed ESC. * + * * + * WARNINGS: This routine may not return at all. It handles being in text mode as well as * + * if in a graphic mode. * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +void CCFileClass::Error(int , int , char const * ) +{ +#ifdef DEMO + if (strstr(File_Name(), "\\")) { + if (!Force_CD_Available(-1)) { + Prog_End(); + exit(EXIT_FAILURE); + } + } + +#else + + if (!Force_CD_Available(RequiredCD)) { + Prog_End(); + exit(EXIT_FAILURE); + } + +#endif +} + + +/*********************************************************************************************** + * CCFileClass::CCFileClass -- Filename based constructor for C&C file. * + * * + * Use this constructor for a file when the filename is known at construction time. * + * * + * INPUT: filename -- Pointer to the filename to use for this file object. * + * * + * OUTPUT: none * + * * + * WARNINGS: The filename pointer is presumed to be inviolate throughout the duration of * + * the file object. If this is not guaranteed, then use the default constructor * + * and then set the name manually. * + * * + * HISTORY: * + * 03/20/1995 JLB : Created. * + *=============================================================================================*/ +CCFileClass::CCFileClass(char const *filename) : + CDFileClass(), + FromDisk(false), + Pointer(0), + Position(0), + Length(0), + Start(0) +{ + Set_Name(filename); +} + + +/*********************************************************************************************** + * CCFileClass::CCFileClass -- Default constructor for file object. * + * * + * This is the default constructor for a C&C file object. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/20/1995 JLB : Created. * + *=============================================================================================*/ +CCFileClass::CCFileClass(void) +{ + FromDisk = false; + Pointer = 0; + Position = 0; + Length = 0; + Start = 0; +} + + +/*********************************************************************************************** + * CCFileClass::Write -- Writes data to the file (non mixfile files only). * + * * + * This routine will write data to the file, but NOT to a file that is part of a mixfile. * + * * + * INPUT: buffer -- Pointer to the buffer that holds the data to be written. * + * * + * size -- The number of bytes to write. * + * * + * OUTPUT: Returns the number of bytes actually written. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + *=============================================================================================*/ +long CCFileClass::Write(void const *buffer, long size) +{ + + /* + ** If this is part of a mixfile, then writing is not allowed. Error out with a fatal + ** message. + */ + if (Pointer || FromDisk) { + Error(EACCES, false, File_Name()); + } + + return(CDFileClass::Write(buffer, size)); +} + + +/*********************************************************************************************** + * CCFileClass::Read -- Reads data from the file. * + * * + * This routine determines if the file is part of the mixfile system. If it is, then * + * the file is copied from RAM if it is located there. Otherwise it is read from disk * + * according to the correct position of the file within the parent mixfile. * + * * + * INPUT: buffer -- Pointer to the buffer to place the read data. * + * * + * size -- The number of bytes to read. * + * * + * OUTPUT: Returns the actual number of bytes read (this could be less than requested). * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + *=============================================================================================*/ +long CCFileClass::Read(void *buffer, long size) +{ + int opened = false; + + if (!Is_Open()) { + if (Open()) { + opened = true; + } + } + + /* + ** If the file is part of a loaded mixfile, then a mere copy is + ** all that is required for the read. + */ + if (Pointer) { + long maximum = Length - Position; + + size = MIN(maximum, size); + if (size) { + Mem_Copy(Add_Long_To_Pointer(Pointer, Position), buffer, size); + Position += size; + } + if (opened) Close(); + return(size); + } + + /* + ** If the file is part of a mixfile, but the mixfile is located + ** on disk, then a special read operation is necessary. + */ + if (FromDisk) { + long maximum = Length - Position; + + size = MIN(maximum, size); + if (size > 0) { + CDFileClass::Seek(Start + Position, SEEK_SET); + size = CDFileClass::Read(buffer, size); + Position += size; + } + if (opened) Close(); + return(size); + } + + long s = CDFileClass::Read(buffer, size); + if (opened) Close(); + return(s); +} + + +/*********************************************************************************************** + * CCFileClass::Seek -- Moves the current file pointer in the file. * + * * + * This routine will change the current file pointer to the position specified. It follows * + * the same rules the a normal Seek() does, but if the file is part of the mixfile system, * + * then only the position value needs to be updated. * + * * + * INPUT: pos -- The position to move the file to relative to the position indicated * + * by the "dir" parameter. * + * * + * dir -- The direction to affect the position change against. This can be * + * either SEEK_CUR, SEEK_END, or SEEK_SET. * + * * + * OUTPUT: Returns with the position of the new location. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + *=============================================================================================*/ +long CCFileClass::Seek(long pos, int dir) +{ + if (Pointer || FromDisk) { + switch (dir) { + case SEEK_END: + Position = Length; + break; + + case SEEK_SET: + Position = 0; + break; + + case SEEK_CUR: + default: + break; + } + Position += pos; + if (Position < 0) Position = 0; + if (Position > Length) Position = Length; + return(Position); + } + return(CDFileClass::Seek(pos, dir)); +} + + +/*********************************************************************************************** + * CCFileClass::Size -- Determines the size of the file. * + * * + * If the file is part of the mixfile system, then the size of the file is already * + * determined and available. Otherwise, go to the low level system to find the file * + * size. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the size of the file in bytes. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + *=============================================================================================*/ +long CCFileClass::Size(void) +{ + if (Pointer || FromDisk) return(Length); + + return(CDFileClass::Size()); +} + + +/*********************************************************************************************** + * CCFileClass::Is_Available -- Checks for existence of file on disk or in mixfile. * + * * + * This routine will examine the mixfile system looking for the file. If the file could * + * not be found there, then the disk is examined directly. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Is the file available for opening? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + *=============================================================================================*/ +int CCFileClass::Is_Available(int ) +{ + if (MixFileClass::Offset(File_Name())) { + return(true); + } + return(CDFileClass::Is_Available()); +} + + +/*********************************************************************************************** + * CCFileClass::Is_Open -- Determines if the file is open. * + * * + * A mixfile is open if there is a pointer to the mixfile data. In absence of this, * + * the the file is open if the file handle is valid. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Is the file open? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + *=============================================================================================*/ +int CCFileClass::Is_Open(void) const +{ + + /* + ** If the file is part of a cached file, then return that it is opened. A closed file + ** doesn't have a valid pointer. + */ + if (Pointer) return(true); + return(CDFileClass::Is_Open()); +} + + +/*********************************************************************************************** + * CCFileClass::Close -- Closes the file. * + * * + * If this is a mixfile file, then only the pointers need to be adjusted. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + *=============================================================================================*/ +void CCFileClass::Close(void) +{ + FromDisk = false; + Pointer = 0; + Position = 0; // Starts at beginning offset. + Start = 0; + Length = 0; + CDFileClass::Close(); +} + + +/*********************************************************************************************** + * CCFileClass::Open -- Opens a file from either the mixfile system or the rawfile system. * + * * + * This routine will open the specified file. It examines the mixfile system to find a * + * match. If one is found then the file is "opened" in a special cached way. Otherwise * + * it is opened as a standard DOS file. * + * * + * INPUT: rights -- The access rights desired. * + * * + * OUTPUT: bool; Was the file opened successfully? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + *=============================================================================================*/ +int CCFileClass::Open(int rights) +{ + /* + ** Always close the file if it was open. + */ + Close(); + + /* + ** Perform a preliminary check to see if the specified file + ** exists on the disk. If it does, then open this file regardless + ** of whether it also exists in RAM. This is slower, but allows + ** upgrade files to work. + */ + if ((rights & WRITE) || CDFileClass::Is_Available()) { + return(CDFileClass::Open(rights)); + } + + /* + ** Check to see if file is part of a mixfile and that mixfile is currently loaded + ** into RAM. + */ + MixFileClass *mixfile = 0; + if (MixFileClass::Offset(File_Name(), &Pointer, &mixfile, &Start, &Length)) { + + /* + ** If the mixfile is located on disk, then fake out the file system to read from + ** the mixfile, but think it is reading from a solitary file. + */ + if (!Pointer) { + long start = Start; + long length = Length; + + /* + ** This is a legitimate open to the file. All access to the file through this + ** file object will be appropriately adjusted for mixfile support however. Also + ** note that the filename attached to this object is NOT the same as the file + ** attached to the file handle. + */ + char const * dupfile = strdup(File_Name()); + Open(mixfile->Filename, READ); + Searching(false); // Disable multi-drive search. + Set_Name(dupfile); + Searching(true); + if (dupfile) free((void *)dupfile); + Start = start; + Length = length; + FromDisk = true; + } + + } else { + + /* + ** The file cannot be found in any mixfile, so it must reside as + ** an individual file on the disk. Or else it is just plain missing. + */ + return(CDFileClass::Open(rights)); + } + return(true); +} + + +/*********************************************************************************** +** Backward compatibility section. +*/ +//extern "C" { + +static CCFileClass Handles[10]; + +#ifdef NEVER +bool __cdecl Set_Search_Drives(char const *) +{ + CCFileClass::Set_Search_Path(path); + return(true); +} +#endif + +int __cdecl Open_File(char const *file_name, int mode) +{ + for (int index = 0; index < sizeof(Handles)/sizeof(Handles[0]); index++) { + if (!Handles[index].Is_Open()) { + Handles[index].Set_Name(file_name); + if (Handles[index].Open(mode)) { +// if (Handles[index].Open(file_name, mode)) { + return(index); + } + break; + } + } + return(WW_ERROR); +} + +VOID __cdecl Close_File(int handle) +{ + if (handle != WW_ERROR && Handles[handle].Is_Open()) { + Handles[handle].Close(); + } +} + +LONG __cdecl Read_File(int handle, VOID *buf, ULONG bytes) +{ + if (handle != WW_ERROR && Handles[handle].Is_Open()) { + return(Handles[handle].Read(buf, bytes)); + } + return(0); +} + +LONG __cdecl Write_File(int handle, VOID const *buf, ULONG bytes) +{ + if (handle != WW_ERROR && Handles[handle].Is_Open()) { + return(Handles[handle].Write(buf, bytes)); + } + return(0); +} + +int __cdecl Find_File(char const *file_name) +{ + CCFileClass file(file_name); + return(file.Is_Available()); +} + +#ifdef NEVER +int __cdecl Delete_File(char const *file_name) +{ + return(CCFileClass(file_name).Delete()); +} + +int __cdecl Create_File(char const *file_name) +{ + return(CCFileClass(file_name).Create()); +} + +ULONG __cdecl Load_Data(char const *name, VOID *ptr, ULONG size) +{ + return(CCFileClass(name).Read(ptr, size)); +} +#endif + +VOID * __cdecl Load_Alloc_Data(char const *name, int ) +{ + CCFileClass file(name); + + return(Load_Alloc_Data(file)); +} + +ULONG __cdecl File_Size(int handle) +{ + if (handle != WW_ERROR && Handles[handle].Is_Open()) { + return(Handles[handle].Size()); + } + return(0); +} + +#ifdef NEVER +ULONG __cdecl Write_Data(char const *name, VOID const *ptr, ULONG size) +{ + return(CCFileClass(name).Write(ptr, size)); +} +#endif + +ULONG __cdecl Seek_File(int handle, LONG offset, int starting) +{ + if (handle != WW_ERROR && Handles[handle].Is_Open()) { + return(Handles[handle].Seek(offset, starting)); + } + return(0); +} + +void WWDOS_Shutdown(void) +{ + for (int index = 0; index < 10; index++) { + Handles[index].Set_Name(NULL); + } +} + +#ifdef NEVER +bool __cdecl Multi_Drive_Search(bool on) +{ +// return(CCFileClass::Multi_Drive_Search(on)); + return(on); +} + +VOID __cdecl WWDOS_Init(VOID) +{ +} + +VOID __cdecl WWDOS_Shutdown(VOID) +{ +} + +int __cdecl Find_Disk_Number(char const *) +{ + return(0); +} +#endif + +//ULONG cdecl Load_Uncompress(BYTE const *file, BuffType uncomp_buff, BuffType dest_buff, VOID *reserved_data) +//{ +// return(Load_Uncompress(CCFileClass(file), uncomp_buff, dest_buff, reserved_data)); +// return(CCFileClass(file).Load_Uncompress(uncomp_buff, dest_buff, reserved_data)); +//} + +//extern "C" { +//int MaxDevice; +//int DefaultDrive; +//char CallingDOSInt; + +//} + + +void Unfragment_File_Cache(void) +{ +} + diff --git a/CCFILE.H b/CCFILE.H new file mode 100644 index 0000000..8612ae4 --- /dev/null +++ b/CCFILE.H @@ -0,0 +1,114 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ccfile.h_v 2.18 16 Oct 1995 16:45:28 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CCFILE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : October 17, 1994 * + * * + * Last Update : October 17, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef CCFILE_H +#define CCFILE_H + +#include +#include +#include "mixfile.h" +#include "cdfile.h" + + +/* +** This derived class for file access knows about mixfiles (packed files). It can handle opening +** a file that is embedded within a mixfile. This is true if the mixfile is cached or resides on +** disk. It is functionally similar to pakfiles, except much faster and less RAM intensive. +*/ +class CCFileClass : public CDFileClass +{ + public: + CCFileClass(char const *filename); + CCFileClass(void); + virtual ~CCFileClass(void) {}; + + // Delete should be overloaded here as well. Don't allow deletes of mixfiles. + + virtual int Open(char const *filename, int rights=READ) {Set_Name(filename);return Open(rights);}; + virtual int Open(int rights=READ); + virtual int Is_Open(void) const; + virtual int Is_Available(int forced=false); + virtual long Read(void *buffer, long size); + virtual long Seek(long pos, int dir=SEEK_CUR); + virtual long Size(void); + virtual long Write(void const *buffer, long size); + virtual void Close(void); + virtual void Error(int error, int canretry = false, char const * filename=NULL); + + private: + + /* + ** This flag indicates that the file is part of a mixfile and the mixfile resides on + ** disk. The file handle for this file is a legitimate DOS handle, although special + ** handling is necessary that takes into account the embedded nature of the file. + */ + bool FromDisk; + + /* + ** This indicates the file is actually part of a resident image of the mixfile + ** itself. In this case, the embedded file handle is invalid. All file access actually + ** gets routed through the cached version of the file. This is a pointer to the start + ** of the RAM image of the file. + */ + void * Pointer; + + /* + ** This is the starting offset of the beginning of the file. This value is only valid + ** if the file is part of a mixfile that resides on disk. It serves as the counterpart + ** to the "Pointer" variable. + */ + long Start; + + /* + ** This is the current seek position of the file. It is duplicated here if the file is + ** part of a mixfile since the DOS seek position is not accurate. This value will + ** range from zero to the size of the file in bytes. + */ + long Position; + + /* + ** This is the size of the file if it was embedded in a mixfile. The size must be manually + ** kept track of because the DOS file size is invalid. + */ + long Length; + + // Force these to never be invoked. + CCFileClass const operator = (CCFileClass const & c); + CCFileClass (CCFileClass const & ) {}; +}; + +#endif diff --git a/CC_ICON.RC b/CC_ICON.RC new file mode 100644 index 0000000..a2324fa --- /dev/null +++ b/CC_ICON.RC @@ -0,0 +1,16 @@ +/**************************************************************************** + + +CC_ICON.RC + +produced by Borland Resource Workshop + + +*****************************************************************************/ + +#include "cc_icon.rh" + + + +ICON_1 ICON "conquer.ico" + diff --git a/CC_ICON.RH b/CC_ICON.RH new file mode 100644 index 0000000..206d6bc --- /dev/null +++ b/CC_ICON.RH @@ -0,0 +1 @@ +#define ICON_1 1 diff --git a/CDATA.CPP b/CDATA.CPP new file mode 100644 index 0000000..2e499c6 --- /dev/null +++ b/CDATA.CPP @@ -0,0 +1,2773 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\cdata.cpv 2.18 16 Oct 1995 16:50:22 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CDATA.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 16, 1994 * + * * + * Last Update : July 29, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * TemplateTypeClass::Create_And_Place -- Creates and places a template object on the map. * + * TemplateTypeClass::Create_One_Of -- Creates an object of this template type. * + * TemplateTypeClass::Display -- Displays a generic representation of template. * + * TemplateTypeClass::From_Name -- Determine template from ASCII name. * + * TemplateTypeClass::Init -- Loads graphic data for templates. * + * TemplateTypeClass::Occupy_List -- Determines occupation list. * + * TemplateTypeClass::One_Time -- Performs one-time initialization * + * TemplateTypeClass::Prep_For_Add -- Prepares to add template to scenario. * + * TemplateTypeClass::TemplateTypeClass -- Constructor for template type objects. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +static char const _slope00000001[] = {7,-1}; +static char const _slope000000101[] = {6,8,-1}; +static char const _slope00000011[] = {6,7,-1}; +static char const _slope0000001[] = {6,-1}; +static char const _slope000001001[] = {5,8,-1}; +static char const _slope000001[] = {5,-1}; +static char const _slope000101[] = {3,5,-1}; +static char const _slope00011010000100000001000011[] = {3,4,6,11,19,25,25,-1}; +static char const _slope00011010010100100001000011[] = {3,4,6,9,11,14,19,24,25,-1}; +static char const _slope0001[] = {3,-1}; +static char const _slope001001001[] = {2,5,8,-1}; +static char const _slope00110000000011[] = {2,3,12,13,-1}; +static char const _slope00110010010011[] = {2,3,6,9,12,13,-1}; +static char const _slope001111001[] = {2,3,4,5,8,-1}; +static char const _slope0011[] = {2,3,-1}; +static char const _slope001[] = {2,-1}; +static char const _slope01000000000000000000001[] = {1,22,-1}; +static char const _slope01000000100000010000001[] = {1,8,15,22,-1}; +static char const _slope0111[] = {1,2,3,-1}; +static char const _slope01[] = {1,-1}; +static char const _slope1001001[] = {0,3,6,-1}; +static char const _slope1001[] = {0,3,-1}; +static char const _slope1100000000000000001100011[] = {0,1,18,19,23,24,-1}; +static char const _slope1100001000001000001100011[] = {0,1,6,12,18,19,23,24,-1}; +static char const _slope1101101[] = {0,1,3,4,6,-1}; +static char const _slope1101[] = {0,1,3,-1}; +static char const _slope111[] = {0,1,2,-1}; +static char const _slope111010011[] = {0,1,2,4,7,8,-1}; +static char const _slope11101[] = {0,1,2,4,-1}; +static char const _slope111111011[] = {0,1,2,3,4,5,7,8,-1}; +static char const _slope11111111[] = {0,1,2,3,4,5,6,7,-1}; +static char const _slope111111[] = {0,1,2,3,4,5,-1}; +static char const _slope1[] = {0,-1}; + +static TemplateTypeClass const Empty( + TEMPLATE_CLEAR1, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE|THEATERF_JUNGLE, + "CLEAR1", + TXT_CLEAR, + LAND_CLEAR, + 1,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Clear( + TEMPLATE_CLEAR1, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE|THEATERF_JUNGLE, + "CLEAR1", + TXT_CLEAR, + LAND_CLEAR, + 1,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road1( + TEMPLATE_ROAD1, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D01", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road2( + TEMPLATE_ROAD2, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D02", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road3( + TEMPLATE_ROAD3, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D03", + TXT_ROAD, + LAND_CLEAR, + 1,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road4( + TEMPLATE_ROAD4, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D04", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road5( + TEMPLATE_ROAD5, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D05", + TXT_ROAD, + LAND_CLEAR, + 3,4, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road6( + TEMPLATE_ROAD6, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D06", + TXT_ROAD, + LAND_CLEAR, + 2,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road7( + TEMPLATE_ROAD7, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D07", + TXT_ROAD, + LAND_CLEAR, + 3,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road8( + TEMPLATE_ROAD8, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D08", + TXT_ROAD, + LAND_CLEAR, + 3,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road9( + TEMPLATE_ROAD9, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D09", + TXT_ROAD, + LAND_CLEAR, + 4,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road10( + TEMPLATE_ROAD10, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D10", + TXT_ROAD, + LAND_CLEAR, + 4,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road11( + TEMPLATE_ROAD11, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D11", + TXT_ROAD, + LAND_CLEAR, + 2,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road12( + TEMPLATE_ROAD12, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D12", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road13( + TEMPLATE_ROAD13, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D13", + TXT_ROAD, + LAND_CLEAR, + 4,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road14( + TEMPLATE_ROAD14, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D14", + TXT_ROAD, + LAND_CLEAR, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road15( + TEMPLATE_ROAD15, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D15", + TXT_ROAD, + LAND_CLEAR, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road16( + TEMPLATE_ROAD16, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D16", + TXT_ROAD, + LAND_CLEAR, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road17( + TEMPLATE_ROAD17, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D17", + TXT_ROAD, + LAND_CLEAR, + 3,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road18( + TEMPLATE_ROAD18, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D18", + TXT_ROAD, + LAND_CLEAR, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road19( + TEMPLATE_ROAD19, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D19", + TXT_ROAD, + LAND_CLEAR, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road20( + TEMPLATE_ROAD20, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D20", + TXT_ROAD, + LAND_CLEAR, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road21( + TEMPLATE_ROAD21, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D21", + TXT_ROAD, + LAND_CLEAR, + 3,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road22( + TEMPLATE_ROAD22, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D22", + TXT_ROAD, + LAND_CLEAR, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road23( + TEMPLATE_ROAD23, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D23", + TXT_ROAD, + LAND_CLEAR, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road24( + TEMPLATE_ROAD24, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D24", + TXT_ROAD, + LAND_CLEAR, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road25( + TEMPLATE_ROAD25, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D25", + TXT_ROAD, + LAND_CLEAR, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road26( + TEMPLATE_ROAD26, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D26", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road27( + TEMPLATE_ROAD27, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D27", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road28( + TEMPLATE_ROAD28, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D28", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road29( + TEMPLATE_ROAD29, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D29", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road30( + TEMPLATE_ROAD30, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D30", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road31( + TEMPLATE_ROAD31, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D31", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road32( + TEMPLATE_ROAD32, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D32", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road33( + TEMPLATE_ROAD33, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D33", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road34( + TEMPLATE_ROAD34, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D34", + TXT_ROAD, + LAND_CLEAR, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road35( + TEMPLATE_ROAD35, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D35", + TXT_ROAD, + LAND_CLEAR, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road36( + TEMPLATE_ROAD36, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D36", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road37( + TEMPLATE_ROAD37, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D37", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road38( + TEMPLATE_ROAD38, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D38", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road39( + TEMPLATE_ROAD39, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D39", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road40( + TEMPLATE_ROAD40, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D40", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road41( + TEMPLATE_ROAD41, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D41", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road42( + TEMPLATE_ROAD42, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D42", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Road43( + TEMPLATE_ROAD43, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "D43", + TXT_ROAD, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Water( + TEMPLATE_WATER, + THEATERF_WINTER|THEATERF_TEMPERATE|THEATERF_DESERT, + "W1", + TXT_WATER, + LAND_WATER, + 1,1, + LAND_WATER, + NULL +); +static TemplateTypeClass const Water2( + TEMPLATE_WATER2, + THEATERF_WINTER|THEATERF_TEMPERATE, + "W2", + TXT_WATER, + LAND_WATER, + 2,2, + LAND_WATER, + NULL +); +static TemplateTypeClass const Shore1( + TEMPLATE_SHORE1, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH1", + TXT_WATER, + LAND_WATER, + 3,3, + LAND_BEACH, + (char const *)_slope111111 +); +static TemplateTypeClass const Shore2( + TEMPLATE_SHORE2, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH2", + TXT_WATER, + LAND_ROCK, + 3,3, + LAND_BEACH, + (char const *)_slope111 +); +static TemplateTypeClass const Shore3( + TEMPLATE_SHORE3, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH3", + TXT_WATER, + LAND_ROCK, + 1,1, + LAND_WATER, + NULL +); +static TemplateTypeClass const Shore4( + TEMPLATE_SHORE4, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH4", + TXT_WATER, + LAND_ROCK, + 2,1, + LAND_WATER, + NULL +); +static TemplateTypeClass const Shore5( + TEMPLATE_SHORE5, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH5", + TXT_WATER, + LAND_WATER, + 3,3, + LAND_BEACH, + (char const *)_slope111111 +); +static TemplateTypeClass const Shore6( + TEMPLATE_SHORE6, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH6", + TXT_WATER, + LAND_WATER, + 3,3, + LAND_BEACH, + (char const *)_slope111111 +); +static TemplateTypeClass const Shore7( + TEMPLATE_SHORE7, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH7", + TXT_WATER, + LAND_WATER, + 2,2, + LAND_BEACH, + (char const *)_slope1 +); +static TemplateTypeClass const Shore8( + TEMPLATE_SHORE8, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH8", + TXT_WATER, + LAND_WATER, + 3,3, + LAND_BEACH, + (char const *)_slope11111111 +); +static TemplateTypeClass const Shore9( + TEMPLATE_SHORE9, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH9", + TXT_WATER, + LAND_WATER, + 3,3, + LAND_BEACH, + (char const *)_slope111111011 +); +static TemplateTypeClass const Shore10( + TEMPLATE_SHORE10, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH10", + TXT_WATER, + LAND_WATER, + 2,2, + LAND_BEACH, + (char const *)_slope01 +); +static TemplateTypeClass const Shore11( + TEMPLATE_SHORE11, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH11", + TXT_WATER, + LAND_WATER, + 3,3, + LAND_BEACH, + (char const *)_slope1001 +); +static TemplateTypeClass const Shore12( + TEMPLATE_SHORE12, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH12", + TXT_WATER, + LAND_WATER, + 3,3, + LAND_BEACH, + (char const *)_slope000001001 +); +static TemplateTypeClass const Shore13( + TEMPLATE_SHORE13, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH13", + TXT_WATER, + LAND_WATER, + 3,3, + LAND_BEACH, + (char const *)_slope0000001 +); +static TemplateTypeClass const Shore14( + TEMPLATE_SHORE14, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH14", + TXT_WATER, + LAND_ROCK, + 3,3, + LAND_BEACH, + (char const *)_slope00000011 +); +static TemplateTypeClass const Shore15( + TEMPLATE_SHORE15, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH15", + TXT_WATER, + LAND_ROCK, + 3,3, + LAND_BEACH, + (char const *)_slope000000101 +); +static TemplateTypeClass const Shore16( + TEMPLATE_SHORE16, + THEATERF_WINTER|THEATERF_TEMPERATE, + "SH16", + TXT_WATER, + LAND_ROCK, + 3,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Shore17( + TEMPLATE_SHORE17, + THEATERF_WINTER|THEATERF_TEMPERATE|THEATERF_DESERT, + "SH17", + TXT_WATER, + LAND_WATER, + 2,2, + LAND_WATER, + NULL +); +static TemplateTypeClass const Shore18( + TEMPLATE_SHORE18, + THEATERF_WINTER|THEATERF_TEMPERATE|THEATERF_DESERT, + "SH18", + TXT_WATER, + LAND_WATER, + 2,2, + LAND_WATER, + NULL +); +static TemplateTypeClass const Shore19( + TEMPLATE_SHORE19, + THEATERF_DESERT, + "SH19", + TXT_WATER, + LAND_ROCK, + 3,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Shore20( + TEMPLATE_SHORE20, + THEATERF_DESERT, + "SH20", + TXT_WATER, + LAND_ROCK, + 4,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Shore21( + TEMPLATE_SHORE21, + THEATERF_DESERT, + "SH21", + TXT_WATER, + LAND_ROCK, + 3,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Shore22( + TEMPLATE_SHORE22, + THEATERF_DESERT, + "SH22", + TXT_WATER, + LAND_ROCK, + 6,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Shore23( + TEMPLATE_SHORE23, + THEATERF_DESERT, + "SH23", + TXT_WATER, + LAND_ROCK, + 2,2, + LAND_CLEAR, + (char const *)_slope01 +); +static TemplateTypeClass const Shore24( + TEMPLATE_SHORE24, + THEATERF_DESERT, + "SH24", + TXT_WATER, + LAND_ROCK, + 3,3, + LAND_CLEAR, + (char const *)_slope000001 +); +static TemplateTypeClass const Shore25( + TEMPLATE_SHORE25, + THEATERF_DESERT, + "SH25", + TXT_WATER, + LAND_ROCK, + 3,2, + LAND_CLEAR, + (char const *)_slope0001 +); +static TemplateTypeClass const Shore26( + TEMPLATE_SHORE26, + THEATERF_DESERT, + "SH26", + TXT_WATER, + LAND_ROCK, + 3,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore27( + TEMPLATE_SHORE27, + THEATERF_DESERT, + "SH27", + TXT_WATER, + LAND_ROCK, + 4,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore28( + TEMPLATE_SHORE28, + THEATERF_DESERT, + "SH28", + TXT_WATER, + LAND_ROCK, + 3,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore29( + TEMPLATE_SHORE29, + THEATERF_DESERT, + "SH29", + TXT_WATER, + LAND_ROCK, + 6,2, + LAND_CLEAR, + (char const *)_slope00000001 +); +static TemplateTypeClass const Shore30( + TEMPLATE_SHORE30, + THEATERF_DESERT, + "SH30", + TXT_WATER, + LAND_ROCK, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore31( + TEMPLATE_SHORE31, + THEATERF_DESERT, + "SH31", + TXT_WATER, + LAND_ROCK, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore32( + TEMPLATE_SHORE32, + THEATERF_TEMPERATE|THEATERF_WINTER, + "SH32", + TXT_WATER, + LAND_CLEAR, + 3,3, + LAND_WATER, + (char const *)_slope1 +); +static TemplateTypeClass const Shore33( + TEMPLATE_SHORE33, + THEATERF_TEMPERATE|THEATERF_WINTER, + "SH33", + TXT_WATER, + LAND_CLEAR, + 3,3, + LAND_WATER, + (char const *)_slope001 +); +static TemplateTypeClass const Shore34( + TEMPLATE_SHORE34, + THEATERF_TEMPERATE|THEATERF_WINTER, + "SH34", + TXT_WATER, + LAND_CLEAR, + 3,3, + LAND_WATER, + (char const *)_slope001001001 +); +static TemplateTypeClass const Shore35( + TEMPLATE_SHORE35, + THEATERF_TEMPERATE|THEATERF_WINTER, + "SH35", + TXT_WATER, + LAND_CLEAR, + 3,3, + LAND_WATER, + (char const *)_slope1001001 +); +static TemplateTypeClass const Shore36( + TEMPLATE_SHORE36, + THEATERF_DESERT, + "SH36", + TXT_WATER, + LAND_CLEAR, + 1,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore37( + TEMPLATE_SHORE37, + THEATERF_DESERT, + "SH37", + TXT_WATER, + LAND_CLEAR, + 1,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore38( + TEMPLATE_SHORE38, + THEATERF_DESERT, + "SH38", + TXT_WATER, + LAND_CLEAR, + 1,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore39( + TEMPLATE_SHORE39, + THEATERF_DESERT, + "SH39", + TXT_WATER, + LAND_CLEAR, + 1,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore40( + TEMPLATE_SHORE40, + THEATERF_DESERT, + "SH40", + TXT_WATER, + LAND_WATER, + 3,3, + LAND_CLEAR, + (char const *)_slope1 +); +static TemplateTypeClass const Shore41( + TEMPLATE_SHORE41, + THEATERF_DESERT, + "SH41", + TXT_WATER, + LAND_CLEAR, + 3,3, + LAND_WATER, + (char const *)_slope1101101 +); +static TemplateTypeClass const Shore42( + TEMPLATE_SHORE42, + THEATERF_DESERT, + "SH42", + TXT_WATER, + LAND_WATER, + 1,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore43( + TEMPLATE_SHORE43, + THEATERF_DESERT, + "SH43", + TXT_WATER, + LAND_WATER, + 1,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore44( + TEMPLATE_SHORE44, + THEATERF_DESERT, + "SH44", + TXT_WATER, + LAND_WATER, + 1,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore45( + TEMPLATE_SHORE45, + THEATERF_DESERT, + "SH45", + TXT_WATER, + LAND_WATER, + 1,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore46( + TEMPLATE_SHORE46, + THEATERF_DESERT, + "SH46", + TXT_WATER, + LAND_WATER, + 3,3, + LAND_CLEAR, + (char const *)_slope1101 +); +static TemplateTypeClass const Shore47( + TEMPLATE_SHORE47, + THEATERF_DESERT, + "SH47", + TXT_WATER, + LAND_WATER, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore48( + TEMPLATE_SHORE48, + THEATERF_DESERT, + "SH48", + TXT_WATER, + LAND_WATER, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore49( + TEMPLATE_SHORE49, + THEATERF_DESERT, + "SH49", + TXT_WATER, + LAND_WATER, + 3,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore50( + TEMPLATE_SHORE50, + THEATERF_DESERT, + "SH50", + TXT_WATER, + LAND_WATER, + 4,3, + LAND_CLEAR, + (char const *)_slope00000001 +); +static TemplateTypeClass const Shore51( + TEMPLATE_SHORE51, + THEATERF_DESERT, + "SH51", + TXT_WATER, + LAND_WATER, + 4,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore52( + TEMPLATE_SHORE52, + THEATERF_DESERT, + "SH52", + TXT_WATER, + LAND_WATER, + 4,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore53( + TEMPLATE_SHORE53, + THEATERF_DESERT, + "SH53", + TXT_WATER, + LAND_WATER, + 4,3, + LAND_CLEAR, + (char const *)_slope11101 +); +static TemplateTypeClass const Shore54( + TEMPLATE_SHORE54, + THEATERF_DESERT, + "SH54", + TXT_WATER, + LAND_WATER, + 3,2, + LAND_CLEAR, + (char const *)_slope1 +); +static TemplateTypeClass const Shore55( + TEMPLATE_SHORE55, + THEATERF_DESERT, + "SH55", + TXT_WATER, + LAND_WATER, + 3,2, + LAND_CLEAR, + (char const *)_slope001 +); +static TemplateTypeClass const Shore56( + TEMPLATE_SHORE56, + THEATERF_DESERT, + "SH56", + TXT_WATER, + LAND_WATER, + 3,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore57( + TEMPLATE_SHORE57, + THEATERF_DESERT, + "SH57", + TXT_WATER, + LAND_WATER, + 3,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore58( + TEMPLATE_SHORE58, + THEATERF_DESERT, + "SH58", + TXT_WATER, + LAND_WATER, + 2,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore59( + TEMPLATE_SHORE59, + THEATERF_DESERT, + "SH59", + TXT_WATER, + LAND_WATER, + 2,3, + LAND_CLEAR, + (char const *)_slope1 +); +static TemplateTypeClass const Shore60( + TEMPLATE_SHORE60, + THEATERF_DESERT, + "SH60", + TXT_WATER, + LAND_WATER, + 2,3, + LAND_CLEAR, + (char const *)_slope000101 +); +static TemplateTypeClass const Shore61( + TEMPLATE_SHORE61, + THEATERF_DESERT, + "SH61", + TXT_WATER, + LAND_WATER, + 2,3, + LAND_CLEAR, + (char const *)_slope01 +); +static TemplateTypeClass const Shore62( + TEMPLATE_SHORE62, + THEATERF_DESERT, + "SH62", + TXT_WATER, + LAND_WATER, + 6,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Shore63( + TEMPLATE_SHORE63, + THEATERF_DESERT, + "SH63", + TXT_WATER, + LAND_WATER, + 4,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Boulder1( + TEMPLATE_BOULDER1, + THEATERF_WINTER|THEATERF_TEMPERATE|THEATERF_DESERT, + "B1", + TXT_SLOPE, + LAND_ROCK, + 1,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Boulder2( + TEMPLATE_BOULDER2, + THEATERF_WINTER|THEATERF_TEMPERATE|THEATERF_DESERT, + "B2", + TXT_SLOPE, + LAND_ROCK, + 2,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Boulder3( + TEMPLATE_BOULDER3, + THEATERF_WINTER|THEATERF_TEMPERATE, + "B3", + TXT_SLOPE, + LAND_ROCK, + 3,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Boulder4( + TEMPLATE_BOULDER4, + THEATERF_TEMPERATE, + "B4", + TXT_SLOPE, + LAND_ROCK, + 1,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Boulder5( + TEMPLATE_BOULDER5, + THEATERF_TEMPERATE, + "B5", + TXT_SLOPE, + LAND_ROCK, + 1,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Boulder6( + TEMPLATE_BOULDER6, + THEATERF_TEMPERATE, + "B6", + TXT_SLOPE, + LAND_ROCK, + 1,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope1( + TEMPLATE_SLOPE1, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S01", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_CLEAR, + (char const *)_slope001 +); +static TemplateTypeClass const Slope2( + TEMPLATE_SLOPE2, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S02", + TXT_SLOPE, + LAND_ROCK, + 2,3, + LAND_CLEAR, + (char const *)_slope01 +); +static TemplateTypeClass const Slope3( + TEMPLATE_SLOPE3, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S03", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope4( + TEMPLATE_SLOPE4, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S04", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope5( + TEMPLATE_SLOPE5, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S05", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope6( + TEMPLATE_SLOPE6, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S06", + TXT_SLOPE, + LAND_ROCK, + 2,3, + LAND_CLEAR, + (char const *)_slope1 +); +static TemplateTypeClass const Slope7( + TEMPLATE_SLOPE7, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S07", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope8( + TEMPLATE_SLOPE8, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S08", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_CLEAR, + (char const *)_slope01 +); +static TemplateTypeClass const Slope9( + TEMPLATE_SLOPE9, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S09", + TXT_SLOPE, + LAND_ROCK, + 3,2, + LAND_CLEAR, + (char const *)_slope0001 +); +static TemplateTypeClass const Slope10( + TEMPLATE_SLOPE10, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S10", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope11( + TEMPLATE_SLOPE11, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S11", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope12( + TEMPLATE_SLOPE12, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S12", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope13( + TEMPLATE_SLOPE13, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S13", + TXT_SLOPE, + LAND_ROCK, + 3,2, + LAND_CLEAR, + (char const *)_slope000001 +); +static TemplateTypeClass const Slope14( + TEMPLATE_SLOPE14, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S14", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_CLEAR, + (char const *)_slope0111 +); +static TemplateTypeClass const Slope15( + TEMPLATE_SLOPE15, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S15", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_CLEAR, + (char const *)_slope01 +); +static TemplateTypeClass const Slope16( + TEMPLATE_SLOPE16, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S16", + TXT_SLOPE, + LAND_ROCK, + 2,3, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope17( + TEMPLATE_SLOPE17, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S17", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope18( + TEMPLATE_SLOPE18, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S18", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope19( + TEMPLATE_SLOPE19, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S19", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope20( + TEMPLATE_SLOPE20, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S20", + TXT_SLOPE, + LAND_ROCK, + 2,3, + LAND_CLEAR, + (char const *)_slope000001 +); +static TemplateTypeClass const Slope21( + TEMPLATE_SLOPE21, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S21", + TXT_SLOPE, + LAND_ROCK, + 1,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope22( + TEMPLATE_SLOPE22, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S22", + TXT_SLOPE, + LAND_ROCK, + 2,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope23( + TEMPLATE_SLOPE23, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S23", + TXT_SLOPE, + LAND_ROCK, + 3,2, + LAND_CLEAR, + (char const *)_slope000001 +); +static TemplateTypeClass const Slope24( + TEMPLATE_SLOPE24, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S24", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope25( + TEMPLATE_SLOPE25, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S25", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope26( + TEMPLATE_SLOPE26, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S26", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope27( + TEMPLATE_SLOPE27, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S27", + TXT_SLOPE, + LAND_ROCK, + 3,2, + LAND_CLEAR, + (char const *)_slope0011 +); +static TemplateTypeClass const Slope28( + TEMPLATE_SLOPE28, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S28", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope29( + TEMPLATE_SLOPE29, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S29", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope30( + TEMPLATE_SLOPE30, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S30", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope31( + TEMPLATE_SLOPE31, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S31", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope32( + TEMPLATE_SLOPE32, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S32", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope33( + TEMPLATE_SLOPE33, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S33", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope34( + TEMPLATE_SLOPE34, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S34", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope35( + TEMPLATE_SLOPE35, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S35", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope36( + TEMPLATE_SLOPE36, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S36", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope37( + TEMPLATE_SLOPE37, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S37", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Slope38( + TEMPLATE_SLOPE38, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "S38", + TXT_SLOPE, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Brush1( + TEMPLATE_BRUSH1, + THEATERF_DESERT, + "BR1", + TXT_BRUSH, + LAND_ROCK, + 1,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Brush2( + TEMPLATE_BRUSH2, + THEATERF_DESERT, + "BR2", + TXT_BRUSH, + LAND_ROCK, + 1,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Brush3( + TEMPLATE_BRUSH3, + THEATERF_DESERT, + "BR3", + TXT_BRUSH, + LAND_ROCK, + 1,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Brush4( + TEMPLATE_BRUSH4, + THEATERF_DESERT, + "BR4", + TXT_BRUSH, + LAND_ROCK, + 1,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Brush5( + TEMPLATE_BRUSH5, + THEATERF_DESERT, + "BR5", + TXT_BRUSH, + LAND_ROCK, + 1,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Brush6( + TEMPLATE_BRUSH6, + THEATERF_DESERT, + "BR6", + TXT_BRUSH, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Brush7( + TEMPLATE_BRUSH7, + THEATERF_DESERT, + "BR7", + TXT_BRUSH, + LAND_ROCK, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Brush8( + TEMPLATE_BRUSH8, + THEATERF_DESERT, + "BR8", + TXT_BRUSH, + LAND_ROCK, + 3,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Brush9( + TEMPLATE_BRUSH9, + THEATERF_DESERT, + "BR9", + TXT_BRUSH, + LAND_ROCK, + 3,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Brush10( + TEMPLATE_BRUSH10, + THEATERF_DESERT, + "BR10", + TXT_BRUSH, + LAND_ROCK, + 2,1, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Patch1( + TEMPLATE_PATCH1, + THEATERF_TEMPERATE|THEATERF_DESERT, + "P01", + TXT_PATCH, + LAND_CLEAR, + 1,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch2( + TEMPLATE_PATCH2, + THEATERF_TEMPERATE|THEATERF_DESERT, + "P02", + TXT_PATCH, + LAND_ROCK, + 1,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch3( + TEMPLATE_PATCH3, + THEATERF_TEMPERATE|THEATERF_DESERT, + "P03", + TXT_PATCH, + LAND_CLEAR, + 1,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch4( + TEMPLATE_PATCH4, + THEATERF_TEMPERATE|THEATERF_DESERT, + "P04", + TXT_PATCH, + LAND_ROCK, + 1,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch5( + TEMPLATE_PATCH5, + THEATERF_DESERT, + "P05", + TXT_PATCH, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch6( + TEMPLATE_PATCH6, + THEATERF_DESERT, + "P06", + TXT_PATCH, + LAND_CLEAR, + 6,4, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch7( + TEMPLATE_PATCH7, + THEATERF_WINTER|THEATERF_TEMPERATE|THEATERF_DESERT, + "P07", + TXT_PATCH, + LAND_CLEAR, + 4,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch8( + TEMPLATE_PATCH8, + THEATERF_WINTER|THEATERF_TEMPERATE|THEATERF_DESERT, + "P08", + TXT_PATCH, + LAND_CLEAR, + 3,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch13( + TEMPLATE_PATCH13, + THEATERF_WINTER|THEATERF_TEMPERATE, + "P13", + TXT_PATCH, + LAND_CLEAR, + 3,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch14( + TEMPLATE_PATCH14, + THEATERF_WINTER|THEATERF_TEMPERATE, + "P14", + TXT_PATCH, + LAND_CLEAR, + 2,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch15( + TEMPLATE_PATCH15, + THEATERF_WINTER|THEATERF_TEMPERATE, + "P15", + TXT_PATCH, + LAND_CLEAR, + 1,1, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch16( + TEMPLATE_PATCH16, + THEATERF_WINTER, + "P16", + TXT_PATCH, + LAND_CLEAR, + 2,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch17( + TEMPLATE_PATCH17, + THEATERF_WINTER, + "P17", + TXT_PATCH, + LAND_CLEAR, + 4,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch18( + TEMPLATE_PATCH18, + THEATERF_WINTER, + "P18", + TXT_PATCH, + LAND_CLEAR, + 4,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch19( + TEMPLATE_PATCH19, + THEATERF_WINTER, + "P19", + TXT_PATCH, + LAND_CLEAR, + 4,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Patch20( + TEMPLATE_PATCH20, + THEATERF_WINTER, + "P20", + TXT_PATCH, + LAND_CLEAR, + 4,3, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const River1( + TEMPLATE_RIVER1, + THEATERF_WINTER|THEATERF_TEMPERATE, + "RV01", + TXT_RIVER, + LAND_WATER, + 5,4, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const River2( + TEMPLATE_RIVER2, + THEATERF_WINTER|THEATERF_TEMPERATE, + "RV02", + TXT_RIVER, + LAND_WATER, + 5,3, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River3( + TEMPLATE_RIVER3, + THEATERF_WINTER|THEATERF_TEMPERATE, + "RV03", + TXT_RIVER, + LAND_WATER, + 4,4, + LAND_CLEAR, + (char const *)_slope00000001 +); +static TemplateTypeClass const River4( + TEMPLATE_RIVER4, + THEATERF_WINTER|THEATERF_TEMPERATE, + "RV04", + TXT_RIVER, + LAND_WATER, + 4,4, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River5( + TEMPLATE_RIVER5, + THEATERF_WINTER|THEATERF_TEMPERATE, + "RV05", + TXT_RIVER, + LAND_WATER, + 3,3, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River6( + TEMPLATE_RIVER6, + THEATERF_WINTER|THEATERF_TEMPERATE, + "RV06", + TXT_RIVER, + LAND_WATER, + 3,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River7( + TEMPLATE_RIVER7, + THEATERF_WINTER|THEATERF_TEMPERATE, + "RV07", + TXT_RIVER, + LAND_WATER, + 3,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River8( + TEMPLATE_RIVER8, + THEATERF_WINTER|THEATERF_TEMPERATE, + "RV08", + TXT_RIVER, + LAND_WATER, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River9( + TEMPLATE_RIVER9, + THEATERF_WINTER|THEATERF_TEMPERATE, + "RV09", + TXT_RIVER, + LAND_WATER, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River10( + TEMPLATE_RIVER10, + THEATERF_WINTER|THEATERF_TEMPERATE, + "RV10", + TXT_RIVER, + LAND_WATER, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River11( + TEMPLATE_RIVER11, + THEATERF_WINTER|THEATERF_TEMPERATE, + "RV11", + TXT_RIVER, + LAND_WATER, + 2,2, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River12( + TEMPLATE_RIVER12, + THEATERF_WINTER|THEATERF_TEMPERATE, + "RV12", + TXT_RIVER, + LAND_WATER, + 3,4, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River13( + TEMPLATE_RIVER13, + THEATERF_WINTER|THEATERF_TEMPERATE, + "RV13", + TXT_RIVER, + LAND_WATER, + 4,4, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River14( + TEMPLATE_RIVER14, + THEATERF_DESERT, + "RV14", + TXT_RIVER, + LAND_WATER, + 4,3, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River15( + TEMPLATE_RIVER15, + THEATERF_DESERT, + "RV15", + TXT_RIVER, + LAND_WATER, + 4,3, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River16( + TEMPLATE_RIVER16, + THEATERF_DESERT, + "RV16", + TXT_RIVER, + LAND_WATER, + 6,4, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River17( + TEMPLATE_RIVER17, + THEATERF_DESERT, + "RV17", + TXT_RIVER, + LAND_WATER, + 6,5, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River18( + TEMPLATE_RIVER18, + THEATERF_DESERT, + "RV18", + TXT_RIVER, + LAND_WATER, + 4,4, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River19( + TEMPLATE_RIVER19, + THEATERF_DESERT, + "RV19", + TXT_RIVER, + LAND_WATER, + 4,4, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River20( + TEMPLATE_RIVER20, + THEATERF_DESERT, + "RV20", + TXT_RIVER, + LAND_WATER, + 6,8, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River21( + TEMPLATE_RIVER21, + THEATERF_DESERT, + "RV21", + TXT_RIVER, + LAND_WATER, + 5,8, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River22( + TEMPLATE_RIVER22, + THEATERF_DESERT, + "RV22", + TXT_RIVER, + LAND_WATER, + 3,3, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River23( + TEMPLATE_RIVER23, + THEATERF_DESERT, + "RV23", + TXT_RIVER, + LAND_WATER, + 3,3, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River24( + TEMPLATE_RIVER24, + THEATERF_DESERT, + "RV24", + TXT_RIVER, + LAND_WATER, + 3,3, + LAND_ROCK, + NULL +); +static TemplateTypeClass const River25( + TEMPLATE_RIVER25, + THEATERF_DESERT, + "RV25", + TXT_RIVER, + LAND_WATER, + 3,3, + LAND_ROCK, + NULL +); +static TemplateTypeClass const Ford1( + TEMPLATE_FORD1, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "FORD1", + TXT_RIVER, + LAND_WATER, + 3,3, + LAND_CLEAR, + (char const *)_slope001111001 +); +static TemplateTypeClass const Ford2( + TEMPLATE_FORD2, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "FORD2", + TXT_RIVER, + LAND_WATER, + 3,3, + LAND_CLEAR, + (char const *)_slope111010011 +); +static TemplateTypeClass const Falls1( + TEMPLATE_FALLS1, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "FALLS1", + TXT_RIVER, + LAND_WATER, + 3,3, + LAND_CLEAR, + (char const *)_slope1 +); +static TemplateTypeClass const Falls2( + TEMPLATE_FALLS2, + THEATERF_WINTER|THEATERF_DESERT|THEATERF_TEMPERATE, + "FALLS2", + TXT_RIVER, + LAND_WATER, + 3,2, + LAND_CLEAR, + NULL +); +static TemplateTypeClass const Bridge1( + TEMPLATE_BRIDGE1, + THEATERF_WINTER|THEATERF_TEMPERATE, + "BRIDGE1", + TXT_RIVER, + LAND_WATER, + 4,4, + LAND_CLEAR, + (char const *)_slope00110010010011 +); +static TemplateTypeClass const Bridge1d( + TEMPLATE_BRIDGE1D, + THEATERF_WINTER|THEATERF_TEMPERATE, + "BRIDGE1D", + TXT_RIVER, + LAND_WATER, + 4,4, + LAND_CLEAR, + (char const *)_slope00110000000011 +); +static TemplateTypeClass const Bridge2( + TEMPLATE_BRIDGE2, + THEATERF_WINTER|THEATERF_TEMPERATE, + "BRIDGE2", + TXT_RIVER, + LAND_WATER, + 5,5, + LAND_CLEAR, + (char const *)_slope1100001000001000001100011 +); +static TemplateTypeClass const Bridge2d( + TEMPLATE_BRIDGE2D, + THEATERF_WINTER|THEATERF_TEMPERATE, + "BRIDGE2D", + TXT_RIVER, + LAND_WATER, + 5,5, + LAND_CLEAR, + (char const *)_slope1100000000000000001100011 +); +static TemplateTypeClass const Bridge3( + TEMPLATE_BRIDGE3, + THEATERF_DESERT, + "BRIDGE3", + TXT_RIVER, + LAND_WATER, + 6,5, + LAND_CLEAR, + (char const *)_slope00011010010100100001000011 +); +static TemplateTypeClass const Bridge3d( + TEMPLATE_BRIDGE3D, + THEATERF_DESERT, + "BRIDGE3D", + TXT_RIVER, + LAND_WATER, + 6,5, + LAND_CLEAR, + (char const *)_slope00011010000100000001000011 +); +static TemplateTypeClass const Bridge4( + TEMPLATE_BRIDGE4, + THEATERF_DESERT, + "BRIDGE4", + TXT_RIVER, + LAND_WATER, + 6,4, + LAND_CLEAR, + (char const *)_slope01000000100000010000001 +); +static TemplateTypeClass const Bridge4d( + TEMPLATE_BRIDGE4D, + THEATERF_DESERT, + "BRIDGE4D", + TXT_RIVER, + LAND_WATER, + 6,4, + LAND_CLEAR, + (char const *)_slope01000000000000000000001 +); + +TemplateTypeClass const * const TemplateTypeClass::Pointers[TEMPLATE_COUNT] = { + &Clear, // TEMPLATE_CLEAR1 + &Water, // TEMPLATE_WATER + &Water2, // TEMPLATE_WATER2 + &Shore1, // TEMPLATE_SHORE1 + &Shore2, // TEMPLATE_SHORE2 + &Shore3, // TEMPLATE_SHORE3 + &Shore4, // TEMPLATE_SHORE4 + &Shore5, // TEMPLATE_SHORE5 + &Shore11, // TEMPLATE_SHORE11 + &Shore12, // TEMPLATE_SHORE12 + &Shore13, // TEMPLATE_SHORE13 + &Shore14, // TEMPLATE_SHORE14 + &Shore15, // TEMPLATE_SHORE15 + &Slope1, // TEMPLATE_SLOPE1 + &Slope2, // TEMPLATE_SLOPE2 + &Slope3, // TEMPLATE_SLOPE3 + &Slope4, // TEMPLATE_SLOPE4 + &Slope5, // TEMPLATE_SLOPE5 + &Slope6, // TEMPLATE_SLOPE6 + &Slope7, // TEMPLATE_SLOPE7 + &Slope8, // TEMPLATE_SLOPE8 + &Slope9, // TEMPLATE_SLOPE9 + &Slope10, // TEMPLATE_SLOPE10 + &Slope11, // TEMPLATE_SLOPE11 + &Slope12, // TEMPLATE_SLOPE12 + &Slope13, // TEMPLATE_SLOPE13 + &Slope14, // TEMPLATE_SLOPE14 + &Slope15, // TEMPLATE_SLOPE15 + &Slope16, // TEMPLATE_SLOPE16 + &Slope17, // TEMPLATE_SLOPE17 + &Slope18, // TEMPLATE_SLOPE18 + &Slope19, // TEMPLATE_SLOPE19 + &Slope20, // TEMPLATE_SLOPE20 + &Slope21, // TEMPLATE_SLOPE21 + &Slope22, // TEMPLATE_SLOPE22 + &Slope23, // TEMPLATE_SLOPE23 + &Slope24, // TEMPLATE_SLOPE24 + &Slope25, // TEMPLATE_SLOPE25 + &Slope26, // TEMPLATE_SLOPE26 + &Slope27, // TEMPLATE_SLOPE27 + &Slope28, // TEMPLATE_SLOPE28 + &Slope29, // TEMPLATE_SLOPE29 + &Slope30, // TEMPLATE_SLOPE30 + &Slope31, // TEMPLATE_SLOPE31 + &Slope32, // TEMPLATE_SLOPE32 + &Slope33, // TEMPLATE_SLOPE33 + &Slope34, // TEMPLATE_SLOPE34 + &Slope35, // TEMPLATE_SLOPE35 + &Slope36, // TEMPLATE_SLOPE36 + &Slope37, // TEMPLATE_SLOPE37 + &Slope38, // TEMPLATE_SLOPE38 + &Shore32, // TEMPLATE_SHORE32 + &Shore33, // TEMPLATE_SHORE33 + &Shore20, // TEMPLATE_SHORE20 + &Shore21, // TEMPLATE_SHORE21 + &Shore22, // TEMPLATE_SHORE22 + &Shore23, // TEMPLATE_SHORE23 + &Brush1, // TEMPLATE_BRUSH1 + &Brush2, // TEMPLATE_BRUSH2 + &Brush3, // TEMPLATE_BRUSH3 + &Brush4, // TEMPLATE_BRUSH4 + &Brush5, // TEMPLATE_BRUSH5 + &Brush6, // TEMPLATE_BRUSH6 + &Brush7, // TEMPLATE_BRUSH7 + &Brush8, // TEMPLATE_BRUSH8 + &Brush9, // TEMPLATE_BRUSH9 + &Brush10, // TEMPLATE_BRUSH10 + &Patch1, // TEMPLATE_PATCH1 + &Patch2, // TEMPLATE_PATCH2 + &Patch3, // TEMPLATE_PATCH3 + &Patch4, // TEMPLATE_PATCH4 + &Patch5, // TEMPLATE_PATCH5 + &Patch6, // TEMPLATE_PATCH6 + &Patch7, // TEMPLATE_PATCH7 + &Patch8, // TEMPLATE_PATCH8 + &Shore16, // TEMPLATE_SHORE16 + &Shore17, // TEMPLATE_SHORE17 + &Shore18, // TEMPLATE_SHORE18 + &Shore19, // TEMPLATE_SHORE19 + &Patch13, // TEMPLATE_PATCH13 + &Patch14, // TEMPLATE_PATCH14 + &Patch15, // TEMPLATE_PATCH15 + &Boulder1, // TEMPLATE_BOULDER1 + &Boulder2, // TEMPLATE_BOULDER2 + &Boulder3, // TEMPLATE_BOULDER3 + &Boulder4, // TEMPLATE_BOULDER4 + &Boulder5, // TEMPLATE_BOULDER5 + &Boulder6, // TEMPLATE_BOULDER6 + &Shore6, // TEMPLATE_SHORE6 + &Shore7, // TEMPLATE_SHORE7 + &Shore8, // TEMPLATE_SHORE8 + &Shore9, // TEMPLATE_SHORE9 + &Shore10, // TEMPLATE_SHORE10 + + &Road1, // TEMPLATE_ROAD1 + &Road2, // TEMPLATE_ROAD2 + &Road3, // TEMPLATE_ROAD3 + &Road4, // TEMPLATE_ROAD4 + &Road5, // TEMPLATE_ROAD5 + &Road6, // TEMPLATE_ROAD6 + &Road7, // TEMPLATE_ROAD7 + &Road8, // TEMPLATE_ROAD8 + &Road9, // TEMPLATE_ROAD9 + &Road10, // TEMPLATE_ROAD10 + &Road11, // TEMPLATE_ROAD11 + &Road12, // TEMPLATE_ROAD12 + &Road13, // TEMPLATE_ROAD13 + &Road14, // TEMPLATE_ROAD14 + &Road15, // TEMPLATE_ROAD15 + &Road16, // TEMPLATE_ROAD16 + &Road17, // TEMPLATE_ROAD17 + &Road18, // TEMPLATE_ROAD18 + &Road19, // TEMPLATE_ROAD19 + &Road20, // TEMPLATE_ROAD20 + &Road21, // TEMPLATE_ROAD21 + &Road22, // TEMPLATE_ROAD22 + &Road23, // TEMPLATE_ROAD23 + &Road24, // TEMPLATE_ROAD24 + &Road25, // TEMPLATE_ROAD25 + &Road26, // TEMPLATE_ROAD26 + &Road27, // TEMPLATE_ROAD27 + &Road28, // TEMPLATE_ROAD28 + &Road29, // TEMPLATE_ROAD29 + &Road30, // TEMPLATE_ROAD30 + &Road31, // TEMPLATE_ROAD31 + &Road32, // TEMPLATE_ROAD32 + &Road33, // TEMPLATE_ROAD33 + &Road34, // TEMPLATE_ROAD34 + &Road35, // TEMPLATE_ROAD35 + &Road36, // TEMPLATE_ROAD36 + &Road37, // TEMPLATE_ROAD37 + &Road38, // TEMPLATE_ROAD38 + &Road39, // TEMPLATE_ROAD39 + &Road40, // TEMPLATE_ROAD40 + &Road41, // TEMPLATE_ROAD41 + &Road42, // TEMPLATE_ROAD42 + &Road43, // TEMPLATE_ROAD43 + + &River1, // TEMPLATE_RIVER1 + &River2, // TEMPLATE_RIVER2 + &River3, // TEMPLATE_RIVER3 + &River4, // TEMPLATE_RIVER4 + &River5, // TEMPLATE_RIVER5 + &River6, // TEMPLATE_RIVER6 + &River7, // TEMPLATE_RIVER7 + &River8, // TEMPLATE_RIVER8 + &River9, // TEMPLATE_RIVER9 + &River10, // TEMPLATE_RIVER10 + &River11, // TEMPLATE_RIVER11 + &River12, // TEMPLATE_RIVER12 + &River13, // TEMPLATE_RIVER13 + &River14, // TEMPLATE_RIVER14 + &River15, // TEMPLATE_RIVER15 + &River16, // TEMPLATE_RIVER16 + &River17, // TEMPLATE_RIVER17 + &River18, // TEMPLATE_RIVER18 + &River19, // TEMPLATE_RIVER19 + &River20, // TEMPLATE_RIVER20 + &River21, // TEMPLATE_RIVER21 + &River22, // TEMPLATE_RIVER22 + &River23, // TEMPLATE_RIVER23 + &River24, // TEMPLATE_RIVER24 + &River25, // TEMPLATE_RIVER25 + &Ford1, // TEMPLATE_FORD1 + &Ford2, // TEMPLATE_FORD2 + &Falls1, // TEMPLATE_FALLS1 + &Falls2, // TEMPLATE_FALLS2 + &Bridge1, // TEMPLATE_BRIDGE1 + &Bridge1d, // TEMPLATE_BRIDGE1D + &Bridge2, // TEMPLATE_BRIDGE2 + &Bridge2d, // TEMPLATE_BRIDGE2D + &Bridge3, // TEMPLATE_BRIDGE3 + &Bridge3d, // TEMPLATE_BRIDGE3D + &Bridge4, // TEMPLATE_BRIDGE4 + &Bridge4d, // TEMPLATE_BRIDGE4D + + &Shore24, // TEMPLATE_SHORE24 + &Shore25, // TEMPLATE_SHORE25 + &Shore26, // TEMPLATE_SHORE26 + &Shore27, // TEMPLATE_SHORE27 + &Shore28, // TEMPLATE_SHORE28 + &Shore29, // TEMPLATE_SHORE29 + &Shore30, // TEMPLATE_SHORE30 + &Shore31, // TEMPLATE_SHORE31 + + &Patch16, // TEMPLATE_PATCH16 + &Patch17, // TEMPLATE_PATCH17 + &Patch18, // TEMPLATE_PATCH18 + &Patch19, // TEMPLATE_PATCH19 + &Patch20, // TEMPLATE_PATCH20 + + &Shore34, // TEMPLATE_SHORE34 + &Shore35, // TEMPLATE_SHORE35 + &Shore36, // TEMPLATE_SHORE36 + &Shore37, // TEMPLATE_SHORE37 + &Shore38, // TEMPLATE_SHORE38 + &Shore39, // TEMPLATE_SHORE39 + &Shore40, // TEMPLATE_SHORE40 + &Shore41, // TEMPLATE_SHORE41 + &Shore42, // TEMPLATE_SHORE42 + &Shore43, // TEMPLATE_SHORE43 + &Shore44, // TEMPLATE_SHORE44 + &Shore45, // TEMPLATE_SHORE45 + + &Shore46, // TEMPLATE_SHORE46 + &Shore47, // TEMPLATE_SHORE47 + &Shore48, // TEMPLATE_SHORE48 + &Shore49, // TEMPLATE_SHORE49 + &Shore50, // TEMPLATE_SHORE50 + &Shore51, // TEMPLATE_SHORE51 + &Shore52, // TEMPLATE_SHORE52 + &Shore53, // TEMPLATE_SHORE53 + &Shore54, // TEMPLATE_SHORE54 + &Shore55, // TEMPLATE_SHORE55 + &Shore56, // TEMPLATE_SHORE56 + &Shore57, // TEMPLATE_SHORE57 + &Shore58, // TEMPLATE_SHORE58 + &Shore59, // TEMPLATE_SHORE59 + &Shore60, // TEMPLATE_SHORE60 + &Shore61, // TEMPLATE_SHORE61 + + &Shore62, // TEMPLATE_SHORE62 + &Shore63, // TEMPLATE_SHORE63 +}; + + +/*********************************************************************************************** + * TemplateTypeClass::TemplateTypeClass -- Constructor for template type objects. * + * * + * This is the constructor for the template types. * + * * + * INPUT: see below... * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1994 JLB : Created. * + *=============================================================================================*/ +TemplateTypeClass::TemplateTypeClass(TemplateType iconset, int theater, + char const *ininame, int fullname, LandType land, + int width, int height, LandType altland, char const *alticons ) : + ObjectTypeClass(false, false, false, true, false, false, true, true, fullname, ininame, ARMOR_NONE, 0) +{ + Theater = theater; + AltIcons = alticons; + AltLand = altland; + Type = iconset; + Land = land; + Width = width; + Height = height; +} + + +/*********************************************************************************************** + * TemplateTypeClass::From_Name -- Determine template from ASCII name. * + * * + * This routine is used to determine the template number given only * + * an ASCII representation. The scenario loader uses this routine * + * to construct the map from the INI control file. * + * * + * INPUT: name -- Pointer to the ASCII name of the template. * + * * + * OUTPUT: Returns with the template number. If the name had no match, * + * then returns with TEMPLATE_NONE. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1994 JLB : Created. * + *=============================================================================================*/ +TemplateType TemplateTypeClass::From_Name(char const *name) +{ + if (name) { + for (TemplateType index = TEMPLATE_FIRST; index < TEMPLATE_COUNT; index++) { + if (stricmp(As_Reference(index).IniName, name) == 0) { + return(index); + } + } + } + return(TEMPLATE_NONE); +} + + +/*********************************************************************************************** + * TemplateTypeClass::Occupy_List -- Determines occupation list. * + * * + * This routine is used to examine the template map and build an * + * occupation list. This list is used to render a template cursor as * + * well as placement of icon numbers. * + * * + * INPUT: placement -- Is this for placement legality checking only? The normal condition * + * is for marking occupation flags. * + * * + * OUTPUT: Returns with a pointer to the template occupation list. * + * * + * WARNINGS: The return pointer is valid only until the next time that * + * this routine is called. * + * * + * HISTORY: * + * 05/23/1994 JLB : Created. * + *=============================================================================================*/ +short const * TemplateTypeClass::Occupy_List(bool) const +{ + static short _occupy[13*8+5]; + unsigned char map[13*8]; + short *ptr; + int index; + + Mem_Copy(Get_Icon_Set_Map(Get_Image_Data()), map, Width*Height); + + ptr = &_occupy[0]; + for (index = 0; index < Width*Height; index++) { + if (map[index] != 0xFF) { + *ptr++ = (index % Width) + ((index / Width)*MAP_CELL_W); + } + } + *ptr = REFRESH_EOL; + + return((short const *)&_occupy[0]); +} + + +/*********************************************************************************************** + * TemplateTypeClass::Init -- Loads graphic data for templates. * + * * + * This routine loads the template graphic data for all the template * + * type supported for the specified theater. This routine is called * + * whenever the theater for the scenario is first determined. * + * * + * INPUT: theater -- The theater that the template data is to be * + * loaded for. * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine goes to disk! * + * * + * HISTORY: * + * 05/23/1994 JLB : Created. * + * 06/02/1994 JLB : Only handles iconset loading now (as it should). * + *=============================================================================================*/ +void TemplateTypeClass::Init(TheaterType theater) +{ + //if (theater != LastTheater){ + char fullname[_MAX_FNAME+_MAX_EXT]; // Fully constructed iconset name. + void const * ptr; // Working loaded iconset pointer. + + for (TemplateType index = TEMPLATE_FIRST; index < TEMPLATE_COUNT; index++) { + TemplateTypeClass const & tplate = As_Reference(index); + + ((void const *&)tplate.ImageData) = NULL; + if (tplate.Theater & (1< 3 || h > 3); + if (scale) { + x -= (w/2) * (ICON_PIXEL_W/2); + y -= (h/2) * (ICON_PIXEL_H/2); + } else { + x -= (w/2) * ICON_PIXEL_W; + y -= (h/2) * ICON_PIXEL_H; + } + x += WindowList[window][WINDOWX]<<3; + y += WindowList[window][WINDOWY]; + + Mem_Copy(Get_Icon_Set_Map(Get_Image_Data()), map, Width*Height); + + for (index = 0; index < w*h; index++) { + if (map[index] != 0xFF) { + HidPage.Draw_Stamp(Get_Image_Data(), index, 0, 0, NULL, WINDOW_MAIN); + if (scale) { + + HidPage.Scale((*LogicPage), 0, 0, + x + ((index % w)*(ICON_PIXEL_W/2)), + y + ((index / w)*(ICON_PIXEL_H/2)), + ICON_PIXEL_W, ICON_PIXEL_H, + ICON_PIXEL_W/2, ICON_PIXEL_H/2, (char *)NULL); + + } else { + HidPage.Blit((*LogicPage), 0, 0, x + ((index % w)*(ICON_PIXEL_W)), + y + ((index / w)*(ICON_PIXEL_H)), ICON_PIXEL_W, ICON_PIXEL_H); + } + } + } +} + + +/*********************************************************************************************** + * TemplateTypeClass::Prep_For_Add -- Prepares to add template to scenario. * + * * + * This routine prepares a list of template objects so that the * + * scenario editor can use this list to display a dialog box. The * + * selection of a template object will allow its placement upon the * + * map. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1994 JLB : Created. * + * 05/28/1994 JLB : Only handles real templates now. * + * 06/04/1994 JLB : Uses map editing interface functions. * + *=============================================================================================*/ +void TemplateTypeClass::Prep_For_Add(void) +{ + for (TemplateType index = TEMPLATE_CLEAR1; index < TEMPLATE_COUNT; index++) { + if (As_Reference(index).Get_Image_Data()) { + Map.Add_To_List(&As_Reference(index)); + } + } +} +#endif + + +/*********************************************************************************************** + * TemplateTypeClass::Create_And_Place -- Creates and places a template object on the map. * + * * + * This support routine is used by the scenario editor to add a template object to the map * + * and to the game. * + * * + * INPUT: cell -- The cell to place the template object. * + * * + * OUTPUT: bool; Was the template object placed successfully? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +bool TemplateTypeClass::Create_And_Place(CELL cell, HousesType ) const +{ + if (new TemplateClass(Type, cell)) { + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TemplateTypeClass::Create_One_Of -- Creates an object of this template type. * + * * + * This routine will create an object of this type. For certain template objects, such * + * as walls, it is actually created as a building. The "building" wall is converted into * + * a template at the moment of placing down on the map. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the appropriate object for this template type. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/18/1994 JLB : Created. * + *=============================================================================================*/ +ObjectClass * TemplateTypeClass::Create_One_Of(HouseClass *) const +{ + return(new TemplateClass(Type, -1)); +} + + +/*********************************************************************************************** + * TemplateTypeClass::One_Time -- Performs one-time initialization * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/12/1994 JLB : Created. * + *=============================================================================================*/ +void TemplateTypeClass::One_Time(void) +{ +} + + + + diff --git a/CDFILE.CPP b/CDFILE.CPP new file mode 100644 index 0000000..7fdc3f6 --- /dev/null +++ b/CDFILE.CPP @@ -0,0 +1,508 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\cdfile.cpv 2.18 16 Oct 1995 16:48:10 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Westwood Library * + * * + * File Name : CDFILE.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : October 18, 1994 * + * * + * Last Update : October 18, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * CDFileClass::Clear_Search_Drives -- Removes all record of a search path. * + * CDFileClass::Open -- Opens the file object -- with path search. * + * CDFileClass::Open -- Opens the file wherever it can be found. * + * CDFileClass::Set_Name -- Performs a multiple directory scan to set the filename. * + * CDFileClass::Set_Search_Drives -- Sets a list of search paths for file access. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +//#include "cdfile.h" + +/* +** Pointer to the first search path record. +*/ +CDFileClass::SearchDriveType * CDFileClass::First = 0; + +int CDFileClass::CurrentCDDrive = 0; +int CDFileClass::LastCDDrive = 0; +char CDFileClass::RawPath[512]; + + + +int __cdecl Is_Disk_Inserted(int disk) +{ + struct find_t fb; + char scan[] = "?:\\*.*"; + + scan[0] = 'A' + disk; + return(_dos_findfirst(scan, _A_SUBDIR, &fb) == 0); +} + + +CDFileClass::CDFileClass(char const *filename) : + IsDisabled(false) +{ + Set_Name(filename); + memset (RawPath, 0, sizeof(RawPath)); +} + + +CDFileClass::CDFileClass(void) : + IsDisabled(false) +{ +} + + +/*********************************************************************************************** + * CDFileClass::Open -- Opens the file object -- with path search. * + * * + * This will open the file object, but since the file object could have been constructed * + * with a pathname, this routine will try to find the file first. For files opened for * + * writing, then use the existing filename without performing a path search. * + * * + * INPUT: rights -- The access rights to use when opening the file * + * * + * OUTPUT: bool; Was the open successful? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/18/1994 JLB : Created. * + *=============================================================================================*/ +int CDFileClass::Open(int rights) +{ + return(RawFileClass::Open(rights)); +} + + +/*********************************************************************************************** + * CDFC::Refresh_Search_Drives -- Updates the search path when a CD changes or is added * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 5/22/96 9:01AM ST : Created * + *=============================================================================================*/ +void CDFileClass::Refresh_Search_Drives (void) +{ + Clear_Search_Drives(); + Set_Search_Drives(RawPath); +} + + + + + +/*********************************************************************************************** + * CDFileClass::Set_Search_Drives -- Sets a list of search paths for file access. * + * * + * This routine sets up a list of search paths to use when accessing files. The path list * + * is scanned if the file could not be found in the current directory. This is the primary * + * method of supporting CD-ROM drives, but is also useful for scanning network and other * + * directories. The pathlist as passed to this routine is of the same format as the path * + * list used by DOS -- paths are separated by semicolons and need not end in an antivirgule.* + * * + * If a path entry begins with "?:" then the question mark will be replaced with the first * + * CD-ROM drive letter available. If there is no CD-ROM driver detected, then this path * + * entry will be ignored. By using this feature, you can always pass the CD-ROM path * + * specification to this routine and it will not break if the CD-ROM is not loaded (as in * + * the case during development). * + * * + * Here is an example path specification: * + * * + * Set_Search_Drives("DATA;?:\DATA;F:\PROJECT\DATA"); * + * * + * In this example, the current directory will be searched first, followed by a the * + * subdirectory "DATA" located off of the current directory. If not found, then the CD-ROM * + * will be searched in a directory called "\DATA". If not found or the CD-ROM is not * + * present, then it will look to the hard coded path of "F:\PROJECTS\DATA" (maybe a * + * network?). If all of these searches fail, the file system will default to the current * + * directory and let the normal file error system take over. * + * * + * INPUT: pathlist -- Pointer to string of path specifications (separated by semicolons) * + * that will be used to search for files. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/18/1994 JLB : Created. * + * 05/21/1996 ST : Modified to recognise multiple CD drives * + *=============================================================================================*/ +int CDFileClass::Set_Search_Drives(char * pathlist) +{ + int found = FALSE; + int empty = FALSE; + /* + ** If there is no pathlist to add, then just return. + */ + if (!pathlist) return(0); + + /* + ** Save the path as it was passed in so we can parse it again later. + ** Check for the case where RawPath was passed in. + */ + if (pathlist != RawPath){ + strcat (RawPath, ";"); + strcat (RawPath, pathlist); + } + + char const * ptr = strtok(pathlist, ";"); + while (ptr) { + if (strlen(ptr)){ + + char path[PATH_MAX]; // Working path buffer. + + /* + ** Fixup the path to be legal. Legal is defined as all that is necessary to + ** create a pathname is to append the actual filename submitted to the + ** file system. This means that it must have either a trailing ':' or '\' + ** character. + */ + strcpy(path, ptr); + switch (path[strlen(path)-1]) { + case ':': + case '\\': + break; + + default: + strcat(path, "\\"); + break; + } + + /* + ** If there is a drive letter specified, and this drive letter is '?', then it should + ** be substituted with the CD-ROM drive letter. In the case of no CD-ROM attached, then + ** merely ignore this path entry. + */ + if (strncmp(path, "?:", 2) == 0) { + if (CurrentCDDrive){ + found = TRUE; + /* + ** If the drive has a C&C CD in it then add it to the path + */ + if (Get_CD_Index(CurrentCDDrive, 2*60) >= 0){ + path[0] = CurrentCDDrive + 'A'; + Add_Search_Drive(path); + } + } + /* + ** Find the next path string and resubmit. + */ + ptr = strtok(NULL, ";"); + continue; + } + + found = TRUE; + Add_Search_Drive(path); + } + + /* + ** Find the next path string and resubmit. + */ + ptr = strtok(NULL, ";"); + } + if (!found) return(1); + if (empty) return(2); + return(0); +} + + + +/*********************************************************************************************** + * CDFC::Set_CD_Drive -- sets the current CD drive letter * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 5/22/96 9:39AM ST : Created * + *=============================================================================================*/ + +void CDFileClass::Set_CD_Drive (int drive) +{ + LastCDDrive = CurrentCDDrive; + CurrentCDDrive = drive; +} + + + +/*********************************************************************************************** + * CDFC::Add_Search_Drive -- Add a new path to the search path list * + * * + * * + * * + * INPUT: path * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 5/22/96 10:12AM ST : Created * + *=============================================================================================*/ + +void CDFileClass::Add_Search_Drive(char *path) +{ + SearchDriveType *srch; // Working pointer to path object. + /* + ** Allocate a record structure. + */ + srch = new SearchDriveType; + + /* + ** Attach the path to this structure. + */ + srch->Path = strdup(path); + srch->Next = NULL; + + /* + ** Attach this path record to the end of the path chain. + */ + if (!First) { + First = srch; + } else { + SearchDriveType * chain = First; + + while (chain->Next) { + chain = (SearchDriveType *)chain->Next; + } + chain->Next = srch; + } +} + + + + +/*********************************************************************************************** + * CDFileClass::Clear_Search_Drives -- Removes all record of a search path. * + * * + * Use this routine to clear out any previous path(s) set with Set_Search_Drives() * + * function. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/18/1994 JLB : Created. * + *=============================================================================================*/ +void CDFileClass::Clear_Search_Drives(void) +{ + SearchDriveType * chain; // Working pointer to path chain. + + chain = First; + while (chain) { + SearchDriveType *next; + + next = (SearchDriveType *)chain->Next; + if (chain->Path) { + free((char *)chain->Path); + } + delete chain; + + chain = next; + } + First = 0; +} + + +/*********************************************************************************************** + * CDFileClass::Set_Name -- Performs a multiple directory scan to set the filename. * + * * + * This routine will scan all the directories specified in the path list and if the file * + * was found in one of the directories, it will set the filename to a composite of the * + * correct directory and the filename. It is used to allow path searching when searching * + * for files. Typical use is to support CD-ROM drives. This routine examines the current * + * directory first before scanning through the path list. If after scanning the entire * + * path list, the file still could not be found, then the file object's name is set with * + * just the raw filename as passed to this routine. * + * * + * INPUT: filename -- Pointer to the filename to set as the name of this file object. * + * * + * OUTPUT: Returns a pointer to the final and complete filename of this file object. This * + * may have a path attached to the file. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/18/1994 JLB : Created. * + *=============================================================================================*/ +char const * CDFileClass::Set_Name(char const *filename) +{ + /* + ** Try to find the file in the current directory first. If it can be found, then + ** just return with the normal file name setting process. Do the same if there is + ** no multi-drive search path. + */ + RawFileClass::Set_Name(filename); + if (IsDisabled || !First || RawFileClass::Is_Available()) { + return(File_Name()); + } + + /* + ** Attempt to find the file first. Check the current directory. If not found there, then + ** search all the path specifications available. If it still can't be found, then just + ** fall into the normal raw file filename setting system. + */ + SearchDriveType * srch = First; + + while (srch) { + char path[_MAX_PATH]; + + /* + ** Build a pathname to search for. + */ + strcpy(path, srch->Path); + strcat(path, filename); + + /* + ** Check to see if the file could be found. The low level Is_Available logic will + ** prompt if necessary when the CD-ROM drive has been removed. In all other cases, + ** it will return false and the search process will continue. + */ + RawFileClass::Set_Name(path); + if (RawFileClass::Is_Available()) { + return(File_Name()); + } + + /* + ** It wasn't found, so try the next path entry. + */ + srch = (SearchDriveType *)srch->Next; + } + + /* + ** At this point, all path searching has failed. Just set the file name to the + ** plain text passed to this routine and be done with it. + */ + RawFileClass::Set_Name(filename); + return(File_Name()); +} + + +/*********************************************************************************************** + * CDFileClass::Open -- Opens the file wherever it can be found. * + * * + * This routine is similar to the RawFileClass open except that if the file is being * + * opened only for READ access, it will search all specified directories looking for the * + * file. If after a complete search the file still couldn't be found, then it is opened * + * using the normal RawFileClass system -- resulting in normal error procedures. * + * * + * INPUT: filename -- Pointer to the override filename to supply for this file object. It * + * would be the base filename (sans any directory specification). * + * * + * rights -- The access rights to use when opening the file. * + * * + * OUTPUT: bool; Was the file opened successfully? If so then the filename may be different * + * than requested. The location of the file can be determined by examining the * + * filename of this file object. The filename will contain the complete * + * pathname used to open the file. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/18/1994 JLB : Created. * + *=============================================================================================*/ +int CDFileClass::Open(char const *filename, int rights) +{ + Close(); + + /* + ** Verify that there is a filename associated with this file object. If not, then this is a + ** big error condition. + */ + if (!filename) { + Error(ENOENT, false); + } + + /* + ** If writing is requested, then multiple drive searching is not performed. + */ + if (IsDisabled || rights == WRITE) { + return(RawFileClass::Open(filename, rights)); + } + + /* + ** Perform normal multiple drive searching for the filename and open + ** using the normal procedure. + */ + Set_Name(filename); + return(RawFileClass::Open(rights)); +} + + +#ifdef NEVER +/* Get the drive letters if the CD's online */ +WORD __cdecl GetCDDrive(VOID) +{ + _ES = FP_SEG(&cdDrive[0]); + _BX = FP_OFF(&cdDrive[0]); + _AX = 0x150d; + geninterrupt(0x2F); + return((WORD)(*cdDrive)); +} +#endif + +int Get_CD_Drive(void) +{ +#ifdef NEVER + for (int index = 0; index < 26; index++) { + union REGS regs; + + regs.w.ax = 0x150B; + regs.w.bx = 0; + regs.w.cx = index; + int386(0x2F, ®s, ®s); + if (regs.w.bx == 0xADAD) { + return(index); + } + } + return(0); +#else + +// GetCDClass temp; +// return(temp.GetCDDrive()); + return (0); +#endif + +} diff --git a/CDFILE.H b/CDFILE.H new file mode 100644 index 0000000..8f17d2d --- /dev/null +++ b/CDFILE.H @@ -0,0 +1,115 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\cdfile.h_v 2.20 16 Oct 1995 16:45:58 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Westwood LIbrary * + * * + * File Name : CDFILE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : October 18, 1994 * + * * + * Last Update : October 18, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef CDFILE_H +#define CDFILE_H + +#include +#include "rawfile.h" + +/* +** This class is derived from the raw file class. This class adds the functionality of searching +** across multiple directories or drives. It is designed for the typical case of a CD-ROM game +** were some data exists in the current directory (hard drive) and the rest exists on the CD-ROM. +** Searching for the file occurs by first examining the current directory. If the file does not +** exist there, then all the paths available are examined in turn until the file can be found. +** For opening files to write, only the current directory is examined. The directory search order +** is controlled by the path list as submitted to Set_Search_Drives(). The format of the path +** string is the same as the DOS path string. +*/ +class CDFileClass : public RawFileClass +{ + public: + CDFileClass(char const *filename); + CDFileClass(void); + virtual ~CDFileClass(void) {}; + + virtual char const * Set_Name(char const *filename); + virtual int Open(char const *filename, int rights=READ); + virtual int Open(int rights=READ); + + void Searching(int on) {IsDisabled = !on;}; + + static bool Is_There_Search_Drives(void) {return(First != NULL);}; + static int Set_Search_Drives(char * pathlist); + static void Add_Search_Drive(char *path); + static void Clear_Search_Drives(void); + static void Refresh_Search_Drives(void); + static void Set_CD_Drive(int drive); + static int Get_CD_Drive(void) {return(CurrentCDDrive);}; + static int Get_Last_CD_Drive(void) {return(LastCDDrive);}; + + private: + + /* + ** Is multi-drive searching disabled for this file object? + */ + unsigned IsDisabled:1; + + /* + ** This is the control record for each of the drives specified in the search + ** path. There can be many such search paths available. + */ + typedef struct { + void * Next; // Pointer to next search record. + char const * Path; // Pointer to path string. + } SearchDriveType; + + /* + ** This points to the first path record. + */ + static SearchDriveType * First; + + /* + ** This is a copy of the unparsed search path list + */ + static char RawPath[512]; + + /* + ** The drive letter of the current cd drive + */ + static int CurrentCDDrive; + + /* + ** The drive letter of the last used CD drive + */ + static int LastCDDrive; +}; + + +#endif + diff --git a/CELL.CPP b/CELL.CPP new file mode 100644 index 0000000..60e9e1b --- /dev/null +++ b/CELL.CPP @@ -0,0 +1,2316 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\cell.cpv 2.18 16 Oct 1995 16:49:20 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CELL.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 29, 1994 * + * * + * Last Update : August 17, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * CellClass::Adjacent_Cell -- Determines the adjacent cell according to facing. * + * CellClass::Adjust_Threat -- Allows adjustment of threat at cell level * + * CellClass::CellClass -- Constructor for cell objects. * + * CellClass::Cell_Building -- Return with building at specified cell. * + * CellClass::Cell_Color -- Determine what radar color to use for this cell. * + * CellClass::Cell_Coord -- Returns the coordinate of this cell. * + * CellClass::Cell_Find_Object -- Returns ptr to RTTI type occupying cell * + * CellClass::Cell_Infantry -- Returns with pointer of first infantry unit. * + * CellClass::Cell_Object -- Returns with clickable object in cell. * + * CellClass::Cell_Techno -- Return with the unit/building at specified cell. * + * CellClass::Cell_Terrain -- Determines terrain object in cell. * + * CellClass::Cell_Unit -- Returns with pointer to unit occupying cell. * + * CellClass::Clear_Icon -- Calculates what the clear icon number should be. * + * CellClass::Closest_Free_Spot -- returns free spot closest to given coord * + * CellClass::Concrete_Calc -- Calculates the concrete icon to use for the cell. * + * CellClass::Draw_It -- Draws the cell imagery at the location specified. * + * CellClass::Flag_Place -- Places a house flag down on the cell. * + * CellClass::Flag_Remove -- Removes the house flag from the cell. * + * CellClass::Cell_Occupier -- Fetches the occupier for the cell. * + * CellClass::Get_Trigger -- retrieves reference to the cell's trigger * + * CellClass::Goodie_Check -- Performs crate discovery logic. * + * CellClass::Incoming -- Causes objects in cell to "run for cover". * + * CellClass::Is_Generally_Clear -- Determines if cell can be built upon. * + * CellClass::Occupy_Down -- Flag occupation of specified cell. * + * CellClass::Occupy_Unit -- Marks cell as unit occupied. * + * CellClass::Occupy_Up -- Removes occupation flag from the specified cell. * + * CellClass::Overlap_Down -- This routine is used to mark a cell as being spilled over (overla* + * CellClass::Overlap_Unit -- Marks cell as being overlapped by unit. * + * CellClass::Overlap_Up -- Removes overlap flag for the cell. * + * CellClass::Read -- Reads a particular cell value from a save game file. * + * CellClass::Recalc_Attributes -- Recalculates the ground type attributes for the cell. * + * CellClass::Redraw_Objects -- Redraws all objects overlapping this cell. * + * CellClass::Reduce_Tiberium -- Reduces the tiberium in the cell by the amount specified. * + * CellClass::Reduce_Wall -- Damages a wall, if damage is high enough. * + * CellClass::Reserve_Cell -- Marks a cell as being occupied by the specified unit ID. * + * CellClass::Shimmer -- Causes all objects in the cell to shimmer. * + * CellClass::Spot_Index -- returns cell sub-coord index for given COORD * + * CellClass::Tiberium_Adjust -- Adjust the look of the Tiberium for smooth. * + * CellClass::Validate -- validates cell's number * + * CellClass::Wall_Update -- Updates the imagery for wall objects in cell. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#define FIXUP 0 + + +/*********************************************************************************************** + * CellClass::Validate -- validates cell's number * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int CellClass::Validate(void) const +{ + int num; + + num = Cell_Number(); + if (num < 0 || num > 4095) { + Validate_Error("CELL"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*********************************************************************************************** + * CellClass::CellClass -- Constructor for cell objects. * + * * + * A cell object is constructed into an empty state. It contains no specific objects, * + * templates, or overlays. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/09/1994 JLB : Created. * + *=============================================================================================*/ +CellClass::CellClass(void) +{ + memset(this, 0, sizeof(CellClass)); + Smudge = SMUDGE_NONE; + Overlay = OVERLAY_NONE; + Smudge = SMUDGE_NONE; + TType = TEMPLATE_NONE; + Owner = HOUSE_NONE; + InfType = HOUSE_NONE; +} + + +/*********************************************************************************************** + * CellClass::Cell_Color -- Determine what radar color to use for this cell. * + * * + * Use this routine to determine what radar color to render a radar * + * pixel with. This routine is called many many times to render the * + * radar map, so it must be fast. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the color to display the radar pixel with. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/01/1994 JLB : Created. * + * 04/30/1994 JLB : Converted to member function. * + * 05/31/1994 JLB : Takes into account any stealth characteristics of object. * + *=============================================================================================*/ +int CellClass::Cell_Color(bool override) const +{ + Validate(); + BuildingClass * object = Cell_Building(); + if (object) { + return(object->House->Class->Color); + } + + if (override) { + return(TBLACK); + } + return(::Ground[Land_Type()].Color); +} + + +/*********************************************************************************************** + * CellClass::Cell_Techno -- Return with the unit/building at specified cell. * + * * + * Returns an object located in the cell. If there is a * + * building present, it returns a pointer to that, otherwise it returns * + * a pointer to one of the units there. If nothing is present in the * + * specified cell, then it returns NULL. * + * * + * INPUT: x,y -- Coordinate offset (from upper left corner) to use as an aid in selecting * + * the desired object within the cell. * + * * + * OUTPUT: Returns a pointer to a building or unit located in cell. If * + * nothing present, just returns NULL. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/05/1992 JLB : Created. * + * 04/30/1994 JLB : Converted to member function. * + *=============================================================================================*/ +TechnoClass * CellClass::Cell_Techno(int x, int y) const +{ + Validate(); + ObjectClass * object; + COORDINATE click; // Coordinate of click relative to cell corner. + TechnoClass * close = NULL; + long distance = 0; // Recorded closest distance. + + /* + ** Create a coordinate value that represent the pixel location within the cell. This is + ** actually the lower significant bits (leptons) of a regular coordinate value. + */ + click = XY_Coord(Pixel_To_Lepton(x), Pixel_To_Lepton(y)); + + if (Cell_Occupier()) { + object = Cell_Occupier(); + while (object) { + if (object->Is_Techno()) { + COORDINATE coord; // Coordinate relative to cell corner. + long dist; + + coord = object->Center_Coord() & 0x00FF00FFL; + dist = Distance(coord, click); + if (!close || dist < distance) { + close = (TechnoClass *)object; + distance = dist; + } + } + object = object->Next; + } + } + return(close); +} + + +/*************************************************************************** + * CellClass::Cell_Find_Object -- Returns ptr to RTTI type occupying cell * + * * + * INPUT: RTTIType the RTTI type we are searching for * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/17/1995 PWG : Created. * + * 06/12/1995 JLB : Returns object class pointer. * + *=========================================================================*/ +ObjectClass * CellClass::Cell_Find_Object(RTTIType rtti) const +{ + Validate(); + ObjectClass * object = Cell_Occupier(); + + while (object) { + if (object->What_Am_I() == rtti) { + return(object); + } + object = object->Next; + } + return(NULL); +} + + +/*********************************************************************************************** + * CellClass::Cell_Building -- Return with building at specified cell. * + * * + * Given a cell, determine if there is a building associated * + * and return with a pointer to this building. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the building associated with the * + * cell. If there is no building associated, then NULL is * + * returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/05/1992 JLB : Created. * + * 04/30/1994 JLB : Converted to member function. * + *=============================================================================================*/ +BuildingClass * CellClass::Cell_Building(void) const +{ + Validate(); + return((BuildingClass *)Cell_Find_Object(RTTI_BUILDING)); +} + + +/*********************************************************************************************** + * CellClass::Cell_Terrain -- Determines terrain object in cell. * + * * + * This routine is used to determine the terrain object (if any) that * + * overlaps this cell. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the terrain object that overlaps * + * this cell. If there is no terrain object present, then NULL * + * is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1994 JLB : Created. * + *=============================================================================================*/ +TerrainClass * CellClass::Cell_Terrain(void) const +{ + Validate(); + return((TerrainClass *)Cell_Find_Object(RTTI_TERRAIN)); +} + + +/*********************************************************************************************** + * CellClass::Cell_Object -- Returns with clickable object in cell. * + * * + * This routine is used to determine which object is to be selected * + * by a player click upon the cell. Not all objects that overlap the * + * cell are selectable by the player. This routine sorts out which * + * is which and returns with the appropriate object pointer. * + * * + * INPUT: x,y -- Coordinate (from upper left corner of cell) to use as a guide when * + * selecting the object within the cell. This plays a role in those cases * + * where several objects (such as infantry) exist within the same cell. * + * * + * OUTPUT: Returns with pointer to the object clickable within the * + * cell. NULL is returned if there is no clickable object * + * present. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/13/1994 JLB : Created. * + *=============================================================================================*/ +ObjectClass * CellClass::Cell_Object(int x, int y) const +{ + Validate(); + ObjectClass * ptr; + + /* + ** Hack so that aircraft landed on helipads can still be selected if directly + ** clicked on. + */ + ptr = (ObjectClass *)Cell_Find_Object(RTTI_AIRCRAFT); + if (ptr) { + return(ptr); + } + + ptr = Cell_Techno(x, y); + if (ptr) { + return(ptr); + } + ptr = Cell_Terrain(); + if (ptr) return(ptr); + return(ptr); +} + + +/*********************************************************************************************** + * CellClass::Redraw_Objects -- Redraws all objects overlapping this cell. * + * * + * This is a low level routine that marks all objects that overlap this * + * cell to be redrawn. It is necessary to call this routine whenever * + * the underlying icon has to be redrawn. * + * * + * INPUT: forced -- Should this redraw be forced even if flags * + * indicate that it would be redundant? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1994 JLB : Created. * + * 06/20/1994 JLB : Simplified to use object pointers. * + * 12/24/1994 JLB : Only checks if cell is in view and not flagged already. * + *=============================================================================================*/ +void CellClass::Redraw_Objects(bool forced) +{ + Validate(); + CELL cell = Cell_Number(); + + if (Map.In_View(cell) && (forced || !Map.Is_Cell_Flagged(cell))) { + + /* + ** Flag the icon to be redrawn. + */ + Map.Flag_Cell(cell); + + /* + ** Flag the main object in the cell to be redrawn. + */ + if (Cell_Occupier()) { + ObjectClass * optr = Cell_Occupier(); + while (optr) { + optr->Mark(MARK_CHANGE); + optr = optr->Next; + } + } + + /* + ** Flag any overlapping object in this cell to be redrawn. + */ + for (int index = 0; index < sizeof(Overlapper)/sizeof(Overlapper[0]); index++) { + if (Overlapper[index]) { + Overlapper[index]->Mark(MARK_CHANGE); + } + } + } +} + + +/*********************************************************************************************** + * CellClass::Is_Generally_Clear -- Determines if cell can be built upon. * + * * + * This determines if the cell can become a proper foundation for * + * building placement. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Can the cell be built upon? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1994 JLB : Created. * + *=============================================================================================*/ +bool CellClass::Is_Generally_Clear(void) const +{ + Validate(); + if (ScenarioInit) return(true); + if (Flag.Composite || IsFlagged || Cell_Occupier()) { + return(false); + } + if (Smudge != SMUDGE_NONE && SmudgeTypeClass::As_Reference(Smudge).IsBib && Owner != HOUSE_NONE) { + return(false); + } + if (Overlay != OVERLAY_NONE && OverlayTypeClass::As_Reference(Overlay).IsWall) { + return(false); + } + +#ifdef ADVANCED + /* + ** Building certain kinds of terrain is prohibited -- bridges in particular. + */ + switch (TType) { + case TEMPLATE_BRIDGE1: + case TEMPLATE_BRIDGE1D: + case TEMPLATE_BRIDGE2: + case TEMPLATE_BRIDGE2D: + case TEMPLATE_BRIDGE3: + case TEMPLATE_BRIDGE3D: + case TEMPLATE_BRIDGE4: + case TEMPLATE_BRIDGE4D: + case TEMPLATE_FORD1: + case TEMPLATE_FORD2: + return(false); + + default: + break; + } +#endif + + return(::Ground[Land_Type()].Build); +} + + +/*********************************************************************************************** + * CellClass::Recalc_Attributes -- Recalculates the ground type attributes for the cell. * + * * + * This routine recalculates the ground type in the cell. The speeds the find path * + * algorithm and other determinations of the cell type. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/29/1994 JLB : Created. * + * 06/20/1994 JLB : Knows about template pointer in cell object. * + *=============================================================================================*/ +void CellClass::Recalc_Attributes(void) +{ + Validate(); + /* + ** Check for wall effects. + */ + if (Overlay != OVERLAY_NONE) { + Land = OverlayTypeClass::As_Reference(Overlay).Land; + if (Land != LAND_CLEAR) return; + } + + /* + ** If there is a template associated with this cell, then scan + ** through the template exception list checking to see if this cell + ** is one of the exception types. If it is, then return that ground type, + ** otherwise return the template's default type. + */ + if (TType != TEMPLATE_NONE) { + TemplateTypeClass const *ttype = &TemplateTypeClass::As_Reference(TType); + + /* + ** If there is an exception type list for the icons of this template, then + ** find out if the current icon is one of them. If so, apply the exception + ** ground type to the cell. + */ + char const *ptr = ttype->AltIcons; + if (ptr) { + int icon = TIcon; + + while (*ptr != -1) { + if (icon == *ptr++) { + Land = ttype->AltLand; + return; + } + } + } + + /* + ** No exception found, so just return the default ground type for this template. + */ + Land = ttype->Land; + return; + } + + /* + ** No template is the same as clear terrain. + */ + Land = TemplateTypeClass::As_Reference(TEMPLATE_CLEAR1).Land; +} + + +/*********************************************************************************************** + * CellClass::Occupy_Down -- Flag occupation of specified cell. * + * * + * This routine is used to mark the cell as being occupied by the specified object. * + * * + * INPUT: object -- The object that is to occupy the cell * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + * 11/29/1994 JLB : Simplified. * + *=============================================================================================*/ +void CellClass::Occupy_Down(ObjectClass * object) +{ + Validate(); + ObjectClass * optr; + + /* + ** If the specified object is already part of the occupation list, then don't add + ** it again -- bail instead. + */ + if (Cell_Occupier()) { + optr = Cell_Occupier(); + while (optr) { + if (optr == object) { + return; + } + if (!optr->Next) break; + optr = optr->Next; + } + } + optr = Cell_Occupier(); + object->Next = optr; + + OccupierPtr = object; + Map.Radar_Pixel(Cell_Number()); + + /* + ** If being placed down on a visible square, then flag this + ** techno object as being revealed to the player. + */ + if (IsVisible || GameToPlay != GAME_NORMAL) { + object->Revealed(PlayerPtr); + } + + /* + ** Special occupy bit set. + */ + switch (object->What_Am_I()) { + case RTTI_BUILDING: + Flag.Occupy.Building = true; + break; + + case RTTI_AIRCRAFT: + case RTTI_UNIT: + Flag.Occupy.Vehicle = true; + break; + + case RTTI_TERRAIN: + Flag.Occupy.Monolith = true; + break; + + default: + break; + } +} + + +/*********************************************************************************************** + * CellClass::Occupy_Up -- Removes occupation flag from the specified cell. * + * * + * This routine will lift the object from the cell and free the cell to be occupied by * + * another object. Only if the cell was previously marked with the object specified, will * + * the object be lifted off. This routine is the counterpart to Occupy_Down(). * + * * + * INPUT: object -- The object that is being lifted off. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + * 11/29/1994 JLB : Fixed to handle next pointer in previous object. * + *=============================================================================================*/ +void CellClass::Occupy_Up(ObjectClass * object) +{ + Validate(); + ObjectClass * optr = NULL; // Working pointer to the objects in the chain. + + if (Cell_Occupier()) { + optr = Cell_Occupier(); + } + if (optr == object) { + OccupierPtr = object->Next; + object->Next = 0; + } else { + while (optr) { + if (optr->Next == object) { + optr->Next = object->Next; + object->Next = 0; + break; + } + if (!optr->Next) break; + optr = optr->Next; + } + } + Map.Radar_Pixel(Cell_Number()); + + /* + ** Special occupy bit clear. + */ + switch (object->What_Am_I()) { + case RTTI_BUILDING: + Flag.Occupy.Building = false; + break; + + case RTTI_AIRCRAFT: + case RTTI_UNIT: + Flag.Occupy.Vehicle = false; +#ifdef NEVER + int x,y; + if (Map.Coord_To_Pixel(Cell_Coord(), x, y)) { + SeenBuff.Put_Pixel(x, y, BLUE); + } +#endif + break; + + case RTTI_TERRAIN: + Flag.Occupy.Monolith = false; + break; + + default: + break; + } +} + + +/*********************************************************************************************** + * CellClass::Overlap_Down -- This routine is used to mark a cell as being spilled over (overla* + * * + * Most game objects can often have their graphic imagery spill into more than one cell * + * even though they are considered to "occupy" only one cell. All cells overlapped are * + * flagged by this routine. Using this information it is possible to keep the tactical map * + * display correct. * + * * + * INPUT: object -- The object to mark as overlapping this cell. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + * 07/04/1995 JLB : Ensures that buildings are always marked down. * + *=============================================================================================*/ +void CellClass::Overlap_Down(ObjectClass * object) +{ + Validate(); + ObjectClass **ptr = 0; + + if (!object) return; + for (int index = 0; index < sizeof(Overlapper)/sizeof(Overlapper[0]); index++) { + if (Overlapper[index] == object) return; + if (!Overlapper[index]) ptr = &Overlapper[index]; + } + + /* + ** Buildings must ALWAYS succeed in marking the cell as overlapped. Bump somebody + ** else out in this case. + */ + if (!ptr && object->What_Am_I() == RTTI_BUILDING) { + for (index = 0; index < sizeof(Overlapper)/sizeof(Overlapper[0]); index++) { + switch (Overlapper[index]->What_Am_I()) { + case RTTI_BUILDING: + case RTTI_TERRAIN: + break; + + default: + Overlapper[index] = object; + index = sizeof(Overlapper)/sizeof(Overlapper[0]); + break; + } + } + } + if (ptr) *ptr = object; + + /* + ** If being placed down on a visible square, then flag this + ** techno object as being revealed to the player. + */ + if (IsVisible) { + object->Revealed(PlayerPtr); + } +} + + +/*********************************************************************************************** + * CellClass::Overlap_Up -- Removes overlap flag for the cell. * + * * + * This is the counterpart to Overlap_Down and is used to remove the overlap flag for the * + * specified unit on the cell. * + * * + * INPUT: object -- The object to remove the overlap flag for. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + *=============================================================================================*/ +void CellClass::Overlap_Up(ObjectClass *object) +{ + Validate(); + for (int index = 0; index < sizeof(Overlapper)/sizeof(Overlapper[0]); index++) { + if (Overlapper[index] == object) { + Overlapper[index] = 0; + break; + } + } +} + + +/*********************************************************************************************** + * CellClass::Cell_Unit -- Returns with pointer to unit occupying cell. * + * * + * This routine will determine if a unit is occupying the cell and if so, return a pointer * + * to it. If there is no unit occupying the cell, then NULL is returned. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with pointer to unit occupying cell, else NULL. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + *=============================================================================================*/ +UnitClass * CellClass::Cell_Unit(void) const +{ + Validate(); + return((UnitClass*)Cell_Find_Object(RTTI_UNIT)); +} + + +/*********************************************************************************************** + * CellClass::Cell_Infantry -- Returns with pointer of first infantry unit occupying the cell. * + * * + * This routine examines the cell and returns a pointer to the first infantry unit * + * that occupies it. If there is no infantry unit in the cell, then NULL is returned. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with pointer to infantry unit occupying the cell or NULL if none are * + * present. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/21/1994 JLB : Created. * + *=============================================================================================*/ +InfantryClass * CellClass::Cell_Infantry(void) const +{ + Validate(); + return((InfantryClass*)Cell_Find_Object(RTTI_INFANTRY)); +} + + +/*********************************************************************************************** + * CellClass::Draw_It -- Draws the cell imagery at the location specified. * + * * + * This is the gruntwork cell rendering code. It draws the cell at the screen location * + * specified. This routine doesn't draw any overlapping or occupying units. It only * + * deals with the ground (cell) layer -- icon level. * + * * + * INPUT: x,y -- The screen coordinates to render the cell imagery at. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + * 08/21/1994 JLB : Revised for simple template objects. * + * 11/01/1994 BRR : Updated placement cursor; draws actual object * + * 11/14/1994 BRR : Added remapping code to show passable areas * + * 12/02/1994 BRR : Added trigger display * + * 12/11/1994 JLB : Mixes up clear terrain through pseudo-random table. * + * 04/25/1995 JLB : Smudges drawn BELOW overlays. * + *=============================================================================================*/ +void CellClass::Draw_It(int x, int y, int draw_type) const +{ + Validate(); + TemplateTypeClass const *ttype = 0; + int icon; // The icon number to use from the template set. + CELL cell = Cell_Number(); + void * remap = NULL; +#ifdef SCENARIO_EDITOR + TemplateTypeClass * tptr; + TriggerClass * trig; + int i; + char waypt[2]; +#endif + + /* + ** Fetch a pointer to the template type associated with this cell. + */ + if (TType != TEMPLATE_NONE) { + ttype = &TemplateTypeClass::As_Reference(TType); + icon = TIcon; + } else { + ttype = &TemplateTypeClass::As_Reference(TEMPLATE_CLEAR1); + icon = Clear_Icon(); + } + + + /* + ** Draw the stamp of the template. + */ + if (Debug_Icon) { + LogicPage->Fill_Rect(Map.TacPixelX+x, Map.TacPixelY+y, Map.TacPixelX+x+ICON_PIXEL_W-1, Map.TacPixelY+y+ICON_PIXEL_H-1, Sim_Random_Pick(1, 254)); + FontXSpacing -= 2; + Fancy_Text_Print("%d\r%2X%c\r%02X.%02X", Map.TacPixelX+x+(ICON_PIXEL_W>>1), Map.TacPixelY+y, WHITE, TBLACK, TPF_6POINT|TPF_NOSHADOW|TPF_CENTER, cell, Flag.Composite, (Cell_Occupier() ? '*' : ' '), Overlay, OverlayData); + FontXSpacing += 2; + } else { + + + if (!draw_type || draw_type == CELL_BLIT_ONLY){ + + +#ifdef SCENARIO_EDITOR + /* + ** Set up the remap table for this icon. + */ + if (Debug_Map && Debug_Passable) { + if (::Ground[Land].Cost[0] == 0 || (Cell_Occupier() != NULL && + Cell_Occupier()->What_Am_I() != RTTI_INFANTRY)) { // impassable + remap = Map.FadingRed; + } else { + if (::Ground[Land].Cost[0] > 0x70) { // pretty passable + remap = Map.FadingGreen; + } else { + remap = Map.FadingYellow; // moderately passable + } + } + } +#endif + + // ****** maybe this icon shouldn't be drawn if it is known that the cell will be covered + // with shadow. + /* + ** This is the underlying terrain icon. + */ + if (ttype->Get_Image_Data()) { + LogicPage->Draw_Stamp(ttype->Get_Image_Data(), icon, x, y, NULL, WINDOW_TACTICAL); + if (remap) { + LogicPage->Remap(x+Map.TacPixelX, y+Map.TacPixelY, ICON_PIXEL_W, ICON_PIXEL_H, remap); + } + } + + +#ifdef SCENARIO_EDITOR + /* + ** Draw the map editor's "current" cell. This is the cell that can be + ** assigned attributes such as tag labels. + ** This must be draw before the placement cursor, but after drawing the + ** objects in the cell. + */ + if (Debug_Map && CurrentCell == Cell_Number()) { + LogicPage->Draw_Rect(x+Map.TacPixelX, y+Map.TacPixelY, Map.TacPixelX + x + CELL_PIXEL_W - 1, Map.TacPixelY + y + CELL_PIXEL_H - 1, YELLOW); + } +#endif + +#ifdef NEVER + /* + ** Special concrete render. It always renders over the underlying + ** terrain unless this concrete piece will cover the entire terrain + ** piece. + */ + if (Concrete) { + LogicPage->Draw_Stamp(TemplateTypeClass::As_Pointer(TEMPLATE_CONCRETE_GDI)->Get_Image_Data(), Concrete-1, x, y, NULL, WINDOW_TACTICAL); + } +#endif + } + + /* + ** Redraw any smudge. + */ + if (Smudge != SMUDGE_NONE) { +#ifdef NEVER + switch (Smudge) { + case SMUDGE_BIB1: + CC_Draw_Shape(Bib1, SmudgeData, x, y, WINDOW_TACTICAL, SHAPE_WIN_REL); + break; + + case SMUDGE_BIB2: + CC_Draw_Shape(Bib2, SmudgeData, x, y, WINDOW_TACTICAL, SHAPE_WIN_REL); + break; + + case SMUDGE_BIB3: + CC_Draw_Shape(Bib3, SmudgeData, x, y, WINDOW_TACTICAL, SHAPE_WIN_REL); + break; + } +#endif + SmudgeTypeClass::As_Reference(Smudge).Draw_It(x, y, SmudgeData); + } + + + if (!draw_type || draw_type == CELL_DRAW_ONLY){ + + /* + ** Draw the overlay object. + */ + if (Overlay != OVERLAY_NONE) { + OverlayTypeClass const & otype = OverlayTypeClass::As_Reference(Overlay); + IsTheaterShape = (bool)otype.IsTheater; + CC_Draw_Shape(otype.Get_Image_Data(), OverlayData, (x+(CELL_PIXEL_W>>1)), (y+(CELL_PIXEL_H>>1)), WINDOW_TACTICAL, SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_GHOST, NULL, Map.UnitShadow); + IsTheaterShape = false; + } + + #ifdef SCENARIO_EDITOR + if (Debug_Map) { + /* + ** Draw the cell's Trigger mnemonic, if it has a trigger + */ + if (IsTrigger) { + trig = Get_Trigger(); + Fancy_Text_Print(trig->Get_Name(), x+Map.TacPixelX, y+Map.TacPixelY, PINK, TBLACK, TPF_NOSHADOW|TPF_6POINT); + } + + /* + ** Draw the cell's Waypoint designation if there is one. + */ + if (IsWaypoint) { + for (i = 0; i < 26; i++) { + if (Waypoint[i]==Cell_Number()) { + waypt[0] = 'A' + i; + waypt[1] = 0; + Fancy_Text_Print(waypt, Map.TacPixelX + x + CELL_PIXEL_W / 2, + Map.TacPixelY + y + (CELL_PIXEL_H / 2) - 3, YELLOW, TBLACK, + TPF_NOSHADOW | TPF_6POINT | TPF_CENTER); + break; + } + } + if (Waypoint[WAYPT_HOME] == Cell_Number()) { + Fancy_Text_Print("Home", Map.TacPixelX + x, Map.TacPixelY + y + (CELL_PIXEL_H) - 7, + WHITE, TBLACK, TPF_NOSHADOW | TPF_6POINT); + } + if (Waypoint[WAYPT_REINF] == Cell_Number()) { + Fancy_Text_Print("Reinf", Map.TacPixelX + x, Map.TacPixelY + y + (CELL_PIXEL_H) - 7, + WHITE, TBLACK, TPF_NOSHADOW | TPF_6POINT); + } + } + } + #endif + + /* + ** Draw the placement cursor: + ** - First, draw the hash-mark cursor, so it will appear underneath + ** any cursor being drawn + ** - If the PendingObject is a template, overlay, or smudge, draw it + ** - Otherwise, it's up to the Display.Refresh_Map() routine to draw it + */ + if (IsCursorHere) { + + /* + ** Draw the hash-mark cursor: + */ + if (Map.ProximityCheck && Is_Generally_Clear()) { + LogicPage->Draw_Stamp(Map.TransIconset, 0, x, y, NULL, WINDOW_TACTICAL); + } else { + LogicPage->Draw_Stamp(Map.TransIconset, 2, x, y, NULL, WINDOW_TACTICAL); + } + + #ifdef SCENARIO_EDITOR + if (Debug_Map && Map.PendingObject) { + + switch (Map.PendingObject->What_Am_I()) { + + /* + ** Draw a template: + ** - Compute the icon offset of this cell for this template, using + ** ZoneCell+ZoneOffset to get the upper-left corner of the placement + ** cursor + ** - Draw the icon + */ + case RTTI_TEMPLATETYPE: + tptr = (TemplateTypeClass *)Map.PendingObject; + if (tptr->Get_Image_Data()) { + icon = (Cell_X(cell) - Cell_X(Map.ZoneCell + Map.ZoneOffset)) + + (Cell_Y(cell) - Cell_Y(Map.ZoneCell + Map.ZoneOffset)) * + tptr->Width; + LogicPage->Draw_Stamp(tptr->Get_Image_Data(), icon, x, y, NULL, WINDOW_TACTICAL); + } + break; + + /* + ** Draw an overlay; just use the existing 'OverlayData' even though + ** it means nothing. + */ + case RTTI_OVERLAYTYPE: + OverlayTypeClass::As_Reference(((OverlayTypeClass *)Map.PendingObject)->Type).Draw_It(x, y, OverlayData); + break; + + /* + ** Draw a smudge + */ + case RTTI_SMUDGETYPE: + SmudgeTypeClass::As_Reference(((SmudgeTypeClass *)Map.PendingObject)->Type).Draw_It(x, y, 0); + break; + } + } + #endif + } + + /* + ** Draw the flag if there is one located at this cell. + */ + if (IsFlagged) { + void const * remap = HouseClass::As_Pointer(Owner)->Remap_Table(false, false); + CC_Draw_Shape(MixFileClass::Retrieve("FLAGFLY.SHP"), Frame % 14, x+(ICON_PIXEL_W/2), y+(ICON_PIXEL_H/2), WINDOW_TACTICAL, SHAPE_CENTER|SHAPE_GHOST|SHAPE_FADING, remap, Map.UnitShadow); + } + } + } +} + + +/*********************************************************************************************** + * CellClass::Concrete_Calc -- Calculates the concrete icon to use for the cell. * + * * + * This routine examines the cells around the current one and from this, determines what * + * concrete icon shape to use (if any). The cell data is adjusted and the cell is marked * + * for redraw if the icon changed. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/01/1994 JLB : Created. * + *=============================================================================================*/ +void CellClass::Concrete_Calc(void) +{ + Validate(); + static FacingType _even[5] = {FACING_N, FACING_S, FACING_SW, FACING_W, FACING_NW}; + static FacingType _odd[5] = {FACING_N, FACING_NE, FACING_E, FACING_SE, FACING_S}; + FacingType *ptr; // Working pointer into adjacent cell list. + int index; // Constructed bit index. + int icon; // Icon number. + bool isodd; // Is this for the odd column? + +#define OF_N 0x01 +#define OF_NE 0x02 +#define OF_E 0x04 +#define OF_SE 0x08 +#define OF_S 0x10 + +#define EF_N 0x01 +#define EF_NW 0x10 +#define EF_W 0x08 +#define EF_SW 0x04 +#define EF_S 0x02 + + /* + ** Determine if the even or odd row logic is necessary. + */ + isodd = ((Cell_Number() & 0x01) != 0); + + /* + ** Fetch correct pointer depending on whether this is for an + ** odd or even row. + */ + ptr = (isodd) ? _odd : _even; + + /* + ** Build an index according to the presence of concrete in the special + ** adjacent cells. This is a short list of adjacent cell flags since + ** only 5 adjacent cells need to be examined. The choice of which 5 + ** depends on whether this is for an even or odd column. + */ + index = 0; + for (int i = 0; i < (sizeof(_even)/sizeof(_even[0])); i++) { + CellClass & cellptr = Adjacent_Cell(*ptr++); + +// if ((cellptr->IsConcrete) || +// cellptr->Concrete == C_UPDOWN_RIGHT || +// cellptr->Concrete == C_UPDOWN_LEFT) { + + if (cellptr.Overlay == OVERLAY_CONCRETE) { + index |= (1< levels) { + OverlayData -= levels; + reducer = levels; + } else { + Overlay = OVERLAY_NONE; + reducer = OverlayData; + OverlayData = 0; + Recalc_Attributes(); + } + } + return(reducer); +} + + +/*********************************************************************************************** + * CellClass::Reduce_Wall -- Damages a wall, if damage is high enough. * + * * + * This routine will change the wall shape used for a wall if it's damaged. * + * * + * * + * INPUT: damage -- The number of damage points the wall was hit with. * + * * + * OUTPUT: bool; Was the wall destroyed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/15/1995 BWG : Created. * + * 03/19/1995 JLB : Updates cell information if wall was destroyed. * + *=============================================================================================*/ +int CellClass::Reduce_Wall(int damage) +{ + Validate(); + if (Overlay != OVERLAY_NONE) { + bool destroyed = false; + OverlayTypeClass const & wall = OverlayTypeClass::As_Reference(Overlay); + + if (wall.IsWall) { + + /* + ** If the damage was great enough to ensure wall destruction, reduce the wall by one + ** level (no more). Otherwise determine wall reduction based on a percentage chance + ** proportional to the damage received and the wall's strength. + */ + if (damage >= wall.DamagePoints) { + destroyed = true; + } else { + destroyed = Random_Pick(0, wall.DamagePoints) < damage; + } + + if (destroyed) { + OverlayData+=16; + if ((OverlayData>>4) >= wall.DamageLevels) { + ObjectClass::Detach_This_From_All(As_Target()); + Owner = HOUSE_NONE; + Overlay = OVERLAY_NONE; + OverlayData = 0; + Recalc_Attributes(); + Redraw_Objects(); + Adjacent_Cell(FACING_N).Wall_Update(); + Adjacent_Cell(FACING_W).Wall_Update(); + Adjacent_Cell(FACING_S).Wall_Update(); + Adjacent_Cell(FACING_E).Wall_Update(); + return(true); + } + } + } + } + return(false); +} + + +/*********************************************************************************************** + * CellClass::Get_Trigger -- retrieves reference to the cell's trigger * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * TriggerClass reference * + * * + * WARNINGS: * + * Never call this function unless the IsTrigger flag is set for the cell! * + * * + * HISTORY: * + * 12/01/1994 BR : Created. * + *=============================================================================================*/ +TriggerClass * CellClass::Get_Trigger(void) const +{ + Validate(); + if (IsTrigger) { + return(CellTriggers[Cell_Number()]); + } + return(NULL); +} + + +/*********************************************************************************************** + * CellClass::Spot_Index -- returns cell sub-coord index for given COORD * + * * + * INPUT: * + * coord COORD to compute index for * + * * + * OUTPUT: * + * index into StoppingCoord that's closest to this coord * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1994 BR : Created. * + * 12/10/1994 JLB : Uses alternate sub-position algorithm. * + *=============================================================================================*/ +int CellClass::Spot_Index(COORDINATE coord) +{ + COORDINATE rel = coord & 0x00FF00FFL; // Sub coordinate value within cell. + + /* + ** If the coordinate is close enough to the center of the cell, then return + ** the center position index. + */ + if (Distance(rel, 0x00800080L) < 60) { + return(0); + } + + /* + ** Since the center cell position has been eliminated, a simple comparison + ** as related to the center of the cell can be used to determine the sub + ** position. Take advantage of the fact that the sub positions are organized + ** from left to right, top to bottom. + */ + int index = 0; + if (Coord_X(rel) > 0x80) index |= 0x01; + if (Coord_Y(rel) > 0x80) index |= 0x02; + return(index+1); +} + + +/*********************************************************************************************** + * CellClass::Closest_Free_Spot -- returns free spot closest to given coord * + * * + * Similar to the CellClass::Free_Spot; this routine finds the spot in * + * the cell closest to the given coordinate, and returns the COORD of * + * that spot if it's available, NULL if it's not. * + * * + * INPUT: * + * coord coordinate to check (only sub cell position examined) * + * * + * any -- If only the closest spot is desired regardless of whether it is free or * + * not, then this parameter will be true. * + * * + * OUTPUT: * + * COORD of free spot, NULL if none. The coordinate return value does not alter the cell * + * coordinate data portions of the coordinate passed in. Only the lower sub-cell * + * data is altered. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/08/1994 BR : Created. * + * 12/10/1994 JLB : Picks best of closest stopping positions. * + * 12/21/1994 JLB : Adds a mix-up factor if center location is occupied. * + *=============================================================================================*/ +COORDINATE CellClass::Closest_Free_Spot(COORDINATE coord, bool any) const +{ + Validate(); + int spot_index = Spot_Index(coord); + + /* + ** This precalculated sequence table records the closest spots to any given spot. Sequential + ** examination of these spots for availability ensures that the closes available one is + ** discovered first. + */ + static unsigned char _sequence[5][4] = { + {1,2,3,4}, + {0,2,3,4}, + {0,1,4,3}, + {0,1,4,2}, + {0,2,3,1} + }; + + /* + ** In the case of the center coordinate being requested, but is occupied, then all other + ** sublocations are equidistant. Instead of picking a static sequence of examination, the + ** order is mixed up by way of this table. + */ + static unsigned char _alternate[4][4] = { + {1,2,3,4}, + {2,3,4,1}, + {3,4,1,2}, + {4,1,2,3}, + }; + coord &= 0xFF00FF00L; + + /* + ** Cells occupied by buildings or vehicles don't have any free spots. + */ + if (!any && (Flag.Occupy.Vehicle || Flag.Occupy.Monolith)) { + return(NULL); + } + + /* + ** If just the nearest position is desired regardless of whether occupied or not, + ** then just return with the stopping coordinate value. + */ + if (any || Is_Spot_Free(spot_index)) { + return(Coord_Add(coord, StoppingCoordAbs[spot_index])); + } + + /* + ** Scan through all available sub-locations in the cell in order to determine + ** the closest one to the coordinate requested. Use precalculated table so that + ** when the first free position is found, bail. + */ + unsigned char *sequence; + if (spot_index == 0) { + sequence = &_alternate[Random_Pick(0,3)][0]; + } else { + sequence = &_sequence[spot_index][0]; + } + for (int index = 0; index < 4; index++) { + int pos = *sequence++; + + if (Is_Spot_Free(pos)) { + return(Coord_Add(coord, StoppingCoordAbs[pos])); + } + } + + /* + ** No free spot could be found so return a NULL coordinate. + */ + return(0x00000000L); +} + + +/*********************************************************************************************** + * CellClass::Clear_Icon -- Calculates what the clear icon number should be. * + * * + * This support routine will determine what the clear icon number would be for the cell. * + * The icon number is determined by converting the cell number into an index into a * + * lookup table. This yields what appears to be a randomized map without the necessity of * + * generating and recording randomized map numbers. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the icon number for clear terrain if it were displayed at the * + * cell. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + * 06/09/1995 JLB : Uses 16 entry scramble algorithm. * + *=============================================================================================*/ +int CellClass::Clear_Icon(void) const +{ + Validate(); + CELL cell = Cell_Number(); + return((cell & 0x03) | ((cell>>4) & 0x0C)); +} + + +/*********************************************************************************************** + * CellClass::Incoming -- Causes objects in cell to "run for cover". * + * * + * This routine is called whenever a great, but slow moving, threat is presented to the * + * occupants of a cell. The occupants will, in most cases, stop what they are doing and * + * try to get out of the way. * + * * + * INPUT: threat -- The coordinate source of the threat. * + * * + * forced -- If this threat is so major that the occupants should stop what * + * they are doing, then this parameter should be set to true. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/10/1995 JLB : Created. * + *=============================================================================================*/ +void CellClass::Incoming(COORDINATE threat, bool forced) +{ + Validate(); + ObjectClass * object = NULL; + + object = Cell_Occupier(); + while (object) { + + /* + ** Special check to make sure that friendly units never scatter. + */ + if (Special.IsScatter || (object->Is_Techno() && !((TechnoClass *)object)->House->IsHuman)) { + if (object->What_Am_I() == RTTI_INFANTRY) { + object->Scatter(threat, forced); + } else { + object->Scatter(threat, forced); +// object->Scatter(threat, false); + } + } + object = object->Next; + } +} + + +/*********************************************************************************************** + * CellClass::Adjacent_Cell -- Determines the adjacent cell according to facing. * + * * + * Use this routine to return a reference to the adjacent cell in the direction specified. * + * * + * INPUT: face -- The direction to use when determining the adjacent cell. * + * * + * OUTPUT: Returns with a reference to the adjacent cell. * + * * + * WARNINGS: If the facing value is invalid, then a reference to the same cell is returned. * + * * + * HISTORY: * + * 03/19/1995 JLB : Created. * + *=============================================================================================*/ +CellClass const & CellClass::Adjacent_Cell(FacingType face) const +{ + Validate(); + if ((unsigned)face >= FACING_COUNT) { + return(*this); + } + + CellClass const * ptr = this + AdjacentCell[face]; + if (ptr->Cell_Number() & 0xF000) return(*this); + return(*ptr); +// return(*(this + AdjacentCell[face])); +} + + +/*************************************************************************** + * CellClass::Adjust_Threat -- Allows adjustment of threat at cell level * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/24/1995 PWG : Created. * + *=========================================================================*/ +void CellClass::Adjust_Threat(HousesType house, int threat_value) +{ + Validate(); + int region = Map.Cell_Region(Cell_Number()); + + for (HousesType lp = HOUSE_FIRST; lp < HOUSE_COUNT; lp ++) { + if (lp == house) continue; + + HouseClass *house_ptr = HouseClass::As_Pointer(lp); + if (house_ptr && (!house_ptr->IsHuman || !house_ptr->Is_Ally(house))) { + house_ptr->Adjust_Threat(region, threat_value); + } + } + if (Debug_Threat) { + Map.Flag_To_Redraw(true); + } +} + + +/*********************************************************************************************** + * CellClass::Tiberium_Adjust -- Adjust the look of the Tiberium for smoothing purposes. * + * * + * This routine will adjust the level of the Tiberium in the cell so that it will * + * smoothly blend with the adjacent Tiberium. This routine should only be called for * + * new Tiberium cells. Existing cells that contain Tiberium follow a different growth * + * pattern. * + * * + * INPUT: pregame -- Is this a pregame call? Such a call will mixup the Tiberium overlay * + * used. * + * * + * OUTPUT: Returns with the added Tiberium value that is now available for harvesting. * + * * + * WARNINGS: The return value is only valid for the initial placement. Tiberium growth will * + * increase the net worth of the existing Tiberium. * + * * + * HISTORY: * + * 05/16/1995 JLB : Created. * + *=============================================================================================*/ +long CellClass::Tiberium_Adjust(bool pregame) +{ + Validate(); + if (Overlay != OVERLAY_NONE) { + if (OverlayTypeClass::As_Reference(Overlay).Land == LAND_TIBERIUM) { + static int _adj[9] = {0,1,3,4,6,7,8,10,11}; + int count = 0; + + /* + ** Mixup the Tiberium overlays so that they don't look the same. + */ + if (pregame) { + Overlay = Random_Pick(OVERLAY_TIBERIUM1, OVERLAY_TIBERIUM12); + } + + /* + ** Add up all adjacent cells that contain tiberium. + ** (Skip those cells which aren't on the map) + */ + for (FacingType face = FACING_FIRST; face < FACING_COUNT; face++) { + CELL cell = Cell_Number() + AdjacentCell[face]; + + CellClass & adj = Adjacent_Cell(face); + + if (adj.Overlay != OVERLAY_NONE && + OverlayTypeClass::As_Reference(adj.Overlay).Land == LAND_TIBERIUM) { + count++; + } + } + + OverlayData = _adj[count]; + return((OverlayData+1) * UnitTypeClass::TIBERIUM_STEP); + } + } + return(0); +} + + +/*********************************************************************************************** + * CellClass::Goodie_Check -- Performs crate discovery logic. * + * * + * Call this routine whenever an object enters a cell. It will check for the existance * + * of a crate and generate any "goodie" it might contain. * + * * + * INPUT: object -- Pointer to the object that is triggering this crate. * + * * + * OUTPUT: Can the object continue to enter this cell? A false return value means that the * + * cell is now occupied and must not be entered. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1995 JLB : Created. * + * 07/08/1995 JLB : Added a bunch of goodies to the crates. * + *=============================================================================================*/ +bool CellClass::Goodie_Check(FootClass * object) +{ + Validate(); + enum { + MONEY, // Cash award. + UNIT, // A free unit. + NUKE, // A nuclear device that explodes. + ION, // Calls forth an ion blast on discoverer. + NUKE_MISSILE, // Gets a one time nuclear missile options. + ION_BLAST, // Gets a one time ion blast option. + AIR_STRIKE, // Gets a one time air strike option. + HEAL_BASE, // Heals the player's entire base. + CLOAK, // Units in region gain cloak ability. + EXPLOSION, // Conventional explosion. + NAPALM, // A napalm explosion. + SQUAD, // A mixed squad of friendly infantry appear. + VISCEROID, // A visceroid appears! + DARKNESS, // Shroud the entire map. + REVEAL, // Reveal the entire map. + TOTAL_CRATES, + }; + static int _what[] = { + DARKNESS,DARKNESS, + REVEAL,REVEAL, + NUKE, +// ION,ION, + NUKE_MISSILE, + ION_BLAST,ION_BLAST, + AIR_STRIKE,AIR_STRIKE,AIR_STRIKE,AIR_STRIKE, + HEAL_BASE,HEAL_BASE, + CLOAK,CLOAK, + EXPLOSION,EXPLOSION,EXPLOSION,EXPLOSION, + NAPALM,NAPALM,NAPALM, + SQUAD,SQUAD,SQUAD,SQUAD,SQUAD, + UNIT,UNIT,UNIT,UNIT,UNIT, + VISCEROID + }; + + + /* + ** Crate types are only defined here so it needs to match my new global crate total ST - 6/4/96 2:16PM + */ +#if (TOTAL_CRATES) == (TOTAL_CRATE_TYPES) + +#else + //Huge_Errrrror..... Oh NO! + +#endif + + + if (object && Overlay != OVERLAY_NONE && OverlayTypeClass::As_Reference(Overlay).IsCrate) { + bool steel = (Overlay == OVERLAY_STEEL_CRATE); + COORDINATE coord; // Temporary working coordinate value. + + /* + ** A triggered crate is automatically destroyed regardless of who or how + ** it was triggered. + */ + Redraw_Objects(); + Overlay = OVERLAY_NONE; + OverlayData = 0; + + if (steel) { + if (object->Owner() == HOUSE_BAD) { + object->House->Add_Nuke_Piece(); + new AnimClass(ANIM_CRATE_EMPULSE, Cell_Coord()); + } + + } else { + + int index; + UnitClass * unit = 0; + unsigned damage = 0; + int what = MONEY; + + if (GameToPlay != GAME_NORMAL && (Random_Pick(1, 2) == 1 || !object->House->BScan)) { + what = -1; + + /* + ** If the player has a construction yeard and practically no money and no legitmate means + ** to make any more money, then give money to build a refinery. + */ + if ((object->House->BScan & (STRUCTF_CONST|STRUCTF_REFINERY)) == STRUCTF_CONST && + object->House->Available_Money() < BuildingTypeClass::As_Reference(STRUCT_REFINERY).Cost) { + + what = MONEY; + } + + /* + ** If the player should get an MCV replacement, then give it now (probably). + */ + if (Random_Pick(0, 1) == 0 && + MPlayerBases && + !(object->House->UScan & UNITF_MCV) && + object->House->BScan == 0 && + object->House->Available_Money() > (BuildingTypeClass::As_Reference(STRUCT_REFINERY).Cost + BuildingTypeClass::As_Reference(STRUCT_POWER).Cost)) { + + what = UNIT; + } + + while (what == -1) { + what = _what[Random_Pick((unsigned)0, sizeof(_what)/sizeof(_what[0])-1)]; + + if (what == REVEAL && object->House->IsVisionary) what = -1; + if (what == AIR_STRIKE && object->House->AirStrike.Is_Present()) what = -1; + if (what == NUKE_MISSILE && object->House->NukeStrike.Is_Present()) what = -1; + if (what == ION_BLAST && object->House->IonCannon.Is_Present()) what = -1; + if (what == CLOAK && object->IsCloakable) what = -1; + } + } + + /* + ** Keep track of the number of each type of crate found + */ + if (GameToPlay == GAME_INTERNET){ + object->House->TotalCrates->Increment_Unit_Total(what); + } + + /* + ** Update the crate count and when all the crates have been discovered, flag + ** to generate a new one. + */ + CrateCount--; + if (!CrateMaker && CrateCount <= 0 && GameToPlay != GAME_NORMAL) { + CrateMaker = true; + CrateTimer = 1; + } + + /* + ** Create the effect requested. + */ + switch (what) { + default: + + /* + ** Give the player money. + */ + case MONEY: + new AnimClass(ANIM_CRATE_DOLLAR, Cell_Coord()); + if (GameToPlay == GAME_NORMAL) { + HouseClass::As_Pointer(object->Owner())->Refund_Money(2000); + } else { + HouseClass::As_Pointer(object->Owner())->Refund_Money(100 + (Random_Pick(0,19)*100)); + } + break; + + /* + ** Shroud the world in blackness. + */ + case DARKNESS: + new AnimClass(ANIM_CRATE_EMPULSE, Cell_Coord()); + if (object->House == PlayerPtr) { + for (CELL cell = 0; cell < MAP_CELL_TOTAL; cell++) { + CellClass * cellptr = &Map[cell]; + if (cellptr->IsMapped || cellptr->IsVisible) { + cellptr->Redraw_Objects(); + cellptr->IsMapped = false; + cellptr->IsVisible = false; + } + } + for (int index = 0; index < Map.Layer[LAYER_GROUND].Count(); index++) { + ObjectClass * object = Map.Layer[LAYER_GROUND][index]; + if (object && object->Is_Techno() && ((TechnoClass *)object)->House == PlayerPtr) { + object->Look(); + } + } + Map.Flag_To_Redraw(true); + } + break; + + /* + ** Reveal the entire map. + */ + case REVEAL: + new AnimClass(ANIM_CRATE_EMPULSE, Cell_Coord()); + object->House->IsVisionary = true; + if (object->House == PlayerPtr) { + for (CELL cell = 0; cell < MAP_CELL_TOTAL; cell++) { + Map.Map_Cell(cell, PlayerPtr); + } + } + break; + + /* + ** Try to create a unit where the crate was. + */ + case UNIT: { + UnitTypeClass const * utp = NULL; + + /* + ** Give the player an MCV if he has no base left but does have more than enough + ** money to rebuild a new base. Of course, if he already has an MCV, then don't + ** give him another one. + */ + if (MPlayerBases && + !(object->House->UScan & UNITF_MCV) && + object->House->BScan == 0 && + object->House->Available_Money() > (BuildingTypeClass::As_Reference(STRUCT_REFINERY).Cost + BuildingTypeClass::As_Reference(STRUCT_POWER).Cost)) { + + utp = &UnitTypeClass::As_Reference(UNIT_MCV); + } + + /* + ** If the player has a base and a refinery, but no harvester, then give him + ** a free one. + */ + if (!utp && (object->House->BScan & STRUCTF_REFINERY) && !(object->House->UScan & UNITF_HARVESTER)) { + utp = &UnitTypeClass::As_Reference(UNIT_HARVESTER); + } + + while (!utp) { + UnitType utype = Random_Pick(UNIT_FIRST, (UnitType)(UNIT_COUNT-1)); + if (utype != UNIT_MCV || MPlayerBases) { + utp = &UnitTypeClass::As_Reference(utype); + if (utp->IsCrateGoodie && (utp->Ownable & (1 << object->Owner())) && utp->Level <= BuildLevel+2) break; + utp = NULL; + } + } + + UnitClass * unit = (UnitClass *)utp->Create_One_Of(object->House); + if (unit) { + if (unit->Unlimbo(Cell_Coord())) { + return(false); + } + delete unit; + } + } + break; + + /* + ** Create a squad of miscellanous composition. + */ + case SQUAD: + for (index = 0; index < 5; index++) { + static InfantryType _inf[] = { + INFANTRY_E1,INFANTRY_E1,INFANTRY_E1,INFANTRY_E1,INFANTRY_E1,INFANTRY_E1, + INFANTRY_E2, + INFANTRY_E3, + INFANTRY_E4, + INFANTRY_E5, + INFANTRY_E7, + INFANTRY_RAMBO + }; + InfantryTypeClass::As_Reference(_inf[Random_Pick((unsigned)0, sizeof(_inf)/sizeof(_inf[0])-1)]).Create_And_Place(Cell_Number(), object->Owner()); + } + return(false); + + /* + ** Sometimes an explosion of great magnitude occurs. + */ + case NUKE: + new AnimClass(ANIM_ATOM_BLAST, Cell_Coord()); + break; + + /* + ** Sometimes an explosion of great magnitude occurs. + */ + case ION: + new AnimClass(ANIM_ION_CANNON, Cell_Coord()); + break; + + /* + ** A nuclear missile was discovered. Add it to the sidebar. + */ + case NUKE_MISSILE: + new AnimClass(ANIM_CRATE_MISSILE, Cell_Coord()); + if (object->House->NukeStrike.Enable(true)) { + if (object->IsOwnedByPlayer) { + Map.Add(RTTI_SPECIAL, SPC_NUCLEAR_BOMB); + Map.Column[1].Flag_To_Redraw(); + } + } + break; + + /* + ** A one time ion blast was discovered. Add it to the sidebar. + */ + case ION_BLAST: + new AnimClass(ANIM_CRATE_EARTH, Cell_Coord()); + if (object->House->IonCannon.Enable(true)) { + if (object->IsOwnedByPlayer) { + Map.Add(RTTI_SPECIAL, SPC_ION_CANNON); + Map.Column[1].Flag_To_Redraw(); + } + } + break; + + /* + ** A one time air strike can be called int. Add it to the sidebar. + */ + case AIR_STRIKE: + new AnimClass(ANIM_CRATE_DEVIATOR, Cell_Coord()); + if (object->House->AirStrike.Enable(true)) { + if (object->IsOwnedByPlayer) { + Map.Add(RTTI_SPECIAL, SPC_AIR_STRIKE); + Map.Column[1].Flag_To_Redraw(); + } + } + break; + + /* + ** A group of explosions are triggered around the crate. + */ + case EXPLOSION: + damage = 400; + object->Take_Damage((int&)damage, 0, WARHEAD_HE); + for (index = 0; index < 5; index++) { + COORDINATE coord = Coord_Scatter(Cell_Coord(), Random_Pick(0, 0x0200)); + new AnimClass(ANIM_FBALL1, coord); + damage = 400; + Explosion_Damage(coord, damage, NULL, WARHEAD_HE); + } + break; + + /* + ** A napalm blast is triggered. + */ + case NAPALM: + coord = Coord_Mid(Cell_Coord(), object->Center_Coord()); + new AnimClass(ANIM_NAPALM3, coord); + damage = 600; + Explosion_Damage(coord, damage, NULL, WARHEAD_FIRE); + break; + + /* + ** A visceroid appears and, boy, he's angry! + */ + case VISCEROID: + unit = new UnitClass(UNIT_VICE, HOUSE_JP); + if (unit) { + if (unit->Unlimbo(Cell_Coord())) { + return(false); + } + delete unit; + } + break; + + /* + ** All objects within a certain range will gain the ability to cloak. + */ + case CLOAK: + new AnimClass(ANIM_CRATE_STEALTH, Cell_Coord()); + for (index = 0; index < Map.Layer[LAYER_GROUND].Count(); index++) { + ObjectClass * obj = Map.Layer[LAYER_GROUND][index]; + + if (obj && obj->Is_Techno() && Distance(Cell_Coord(), obj->Center_Coord()) < 0x0300) { + ((TechnoClass *)obj)->IsCloakable = true; + } + } + break; + + /* + ** All of the player's objects heal up. + */ + case HEAL_BASE: + new AnimClass(ANIM_CRATE_INVUN, Cell_Coord()); + for (index = 0; index < Logic.Count(); index++) { + ObjectClass * obj = Logic[index]; + + if (obj && object->Is_Techno() && object->House->Class->House == obj->Owner()) { + obj->Strength = obj->Class_Of().MaxStrength; + } + } + break; + + } + } + } + return(true); +} + + +/*********************************************************************************************** + * CellClass::Flag_Place -- Places a house flag down on the cell. * + * * + * This routine will place the house flag at this cell location. * + * * + * INPUT: house -- The house that is having its flag placed here. * + * * + * OUTPUT: Was the flag successfully placed here? * + * * + * WARNINGS: Failure to place means that the cell is impassable for some reason. * + * * + * HISTORY: * + * 05/23/1995 JLB : Created. * + *=============================================================================================*/ +bool CellClass::Flag_Place(HousesType house) +{ + Validate(); + if (!IsFlagged && Is_Generally_Clear()) { + IsFlagged = true; + Owner = house; + Redraw_Objects(); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * CellClass::Flag_Remove -- Removes the house flag from the cell. * + * * + * This routine will free the cell of any house flag that may be located there. * + * * + * INPUT: none * + * * + * OUTPUT: Was there a flag here that was removed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1995 JLB : Created. * + *=============================================================================================*/ +bool CellClass::Flag_Remove(void) +{ + Validate(); + if (IsFlagged) { + IsFlagged = false; + Owner = HOUSE_NONE; + Redraw_Objects(); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * CellClass::Shimmer -- Causes all objects in the cell to shimmer. * + * * + * This routine is called when some event would cause a momentary disruption in the * + * cloaking device. All objects that are cloaked in the cell will have their cloaking * + * device shimmer. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +void CellClass::Shimmer(void) +{ + Validate(); + ObjectClass * object = Cell_Occupier(); + + while (object) { + object->Do_Shimmer(); + object = object->Next; + } +} + + +/*********************************************************************************************** + * CellClass::Cell_Occupier -- Fetches the occupier for the cell. * + * * + * This routine returns with the first recorded occupier of this cell. A special validity * + * check is performed to ensure that there are no dead objects still marked on the * + * map. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the first occupier object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/17/1995 JLB : Created. * + *=============================================================================================*/ +ObjectClass * CellClass::Cell_Occupier(void) const +{ + ObjectClass * ptr = OccupierPtr; + + while (ptr && !ptr->IsActive) { + ptr = ptr->Next; + ((ObjectClass *&)OccupierPtr) = 0; + } + + return(ptr); +} diff --git a/CELL.H b/CELL.H new file mode 100644 index 0000000..c621611 --- /dev/null +++ b/CELL.H @@ -0,0 +1,253 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\cell.h_v 2.20 16 Oct 1995 16:45:36 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CELL.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 29, 1994 * + * * + * Last Update : April 29, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef CELL_H +#define CELL_H + +#include "building.h" +#include "unit.h" +#include "template.h" + + +/**************************************************************************** +** Each cell on the map is controlled by the following structure. +*/ +class CellClass +{ + public: + /* + ** Does this cell need to be updated on the radar map? If something changes in the cell + ** that might change the radar map imagery, then this flag will be set. It gets cleared + ** when the cell graphic is updated to the radar map. + */ + unsigned IsPlot:1; + + /* + ** Does this cell contain the special placement cursor graphic? This graphic is + ** present when selecting a site for building placement. + */ + unsigned IsCursorHere:1; + + /* + ** Is this cell mapped by the player? A mapped cell is visible. An unmapped cell + ** is covered in a dark shroud. In addition to visibility, mapped cells are the only + ** legal place for transports to land. + */ + unsigned IsMapped:1; + + /* + ** If any part of this cell is visible (even just peeking out from under the shadow), + ** this this flag will be true. Mapped cells always have this flag set, but unmapped + ** cells might not -- it depends on where the shadow edge is located. + */ + unsigned IsVisible:1; + + /* + ** Every cell can be assigned a trigger. The same trigger can be assigned to + ** multiple cells. This bitflag indicates whether this cell has a trigger. + ** The trigger pointers for all cells must be stored elsewhere. + */ + unsigned IsTrigger:1; + + /* + ** Every cell can be assigned a waypoint. A waypoint can only be assigned + ** to one cell, and vice-versa. This bit simply indicates whether this + ** cell is assigned a waypoint or not. + */ + unsigned IsWaypoint:1; + + /* + ** Is this cell currently under the radar map cursor? If so then it + ** needs to be updated whenever the map is updated. + */ + unsigned IsRadarCursor:1; + + /* + ** If this cell contains a house flag, then this will be true. The actual house + ** flag it contains is specified by the Owner field. + */ + unsigned IsFlagged:1; + + /* + ** This contains the icon number and set to use for the base + ** of the terrain. All rendering on an icon occurs AFTER the icon + ** specified by this element is rendered. It is the lowest of the low. + */ + TemplateType TType; + unsigned char TIcon; + + /* + ** The second layer of 'terrain' icons is represented by a simple + ** type number and a value byte. This is sufficient for handling + ** concrete and walls. + */ + OverlayType Overlay; + unsigned char OverlayData; + + /* + ** This is used to specify any special 'stain' overlay icon. This + ** typically includes infantry bodies or other temporary marks. + */ + SmudgeType Smudge; + unsigned char SmudgeData; + + /* + ** Smudges and walls need to record ownership values. For walls, this + ** allows adjacent building placement logic to work. For smudges, it + ** allows building over smudges that are no longer attached to buildings + ** in addition to fixing the adjacent placement logic. + */ + HousesType Owner; + + /* + ** This flag tells you what type of infantry currently occupy the + ** cell or are moving into it. + */ + HousesType InfType; + + /* + ** These point to the object(s) that are located in this cell or overlap + ** this cell. + */ + ObjectClass *OccupierPtr; + ObjectClass *Overlapper[3]; + + /* + ** This array of bit flags is used to indicate which sub positions + ** within the cell are either occupied or are soon going to be + ** occupied. For vehicles, the cells that the vehicle is passing over + ** will be flagged with the vehicle bit. For infantry, the the sub + ** position the infantry is stopped at or headed toward will be marked. + ** The sub positions it passes over will NOT be marked. + */ + union { + struct { + unsigned Center:1; + unsigned NW:1; + unsigned NE:1; + unsigned SW:1; + unsigned SE:1; + unsigned Vehicle:1; // Reserved for vehicle occupation. + unsigned Monolith:1; // Some immovable blockage is in cell. + unsigned Building:1; // A building of some time (usually blocks movement). + } Occupy; + unsigned char Composite; + } Flag; + + //---------------------------------------------------------------- + CellClass(void); + ~CellClass(void) {}; + + int operator == (CellClass const & cell) const {return &cell == this;}; + + /* + ** Query functions. + */ + ObjectClass * Cell_Occupier(void) const; + static int Spot_Index(COORDINATE coord); + bool Is_Spot_Free(int spot_index) const {return (! (Flag.Composite & (1 << spot_index)) ); } + COORDINATE Closest_Free_Spot(COORDINATE coord, bool any=false) const; + COORDINATE Free_Spot(void) const {return Closest_Free_Spot(Cell_Coord());}; + bool Is_Generally_Clear(void) const; + TARGET As_Target(void) const {return ::As_Target(Cell_Number());}; + BuildingClass * Cell_Building(void) const; + CellClass const & Adjacent_Cell(FacingType face) const; + CellClass & Adjacent_Cell(FacingType face) {return (CellClass &)((*((CellClass const *)this)).Adjacent_Cell(face));}; + COORDINATE Cell_Coord(void) const; + int Cell_Color(bool override=false) const; + CELL Cell_Number(void) const; + LandType Land_Type(void) const {return Land;}; + ObjectClass * Cell_Find_Object(RTTIType rtti) const; + ObjectClass * Cell_Object(int x=0, int y=0) const; + TechnoClass * Cell_Techno(int x=0, int y=0) const; + TerrainClass * Cell_Terrain(void) const; + UnitClass * Cell_Unit(void) const; + InfantryClass * Cell_Infantry(void) const; + TriggerClass * Get_Trigger(void) const; + int Clear_Icon(void) const; + bool Goodie_Check(FootClass * object); + ObjectClass * Fetch_Occupier(void) const; + + /* + ** Object placement and removal flag operations. + */ + void Occupy_Down(ObjectClass * object); + void Occupy_Up(ObjectClass * object); + void Overlap_Down(ObjectClass * object); + void Overlap_Up(ObjectClass * object); + bool Flag_Place(HousesType house); + bool Flag_Remove(void); + + /* + ** File I/O. + */ + bool Should_Save(void) const; + bool Save(FileClass & file); + bool Load(FileClass & file); + void Code_Pointers(void); + void Decode_Pointers(void); + + /* + ** Display and rendering controls. + */ + void Draw_It(int x, int y, int draw_flags = 0) const; + void Redraw_Objects(bool forced=false); + void Shimmer(void); + + /* + ** Maintenance calculation support. + */ + long Tiberium_Adjust(bool pregame=false); + void Wall_Update(void); + void Concrete_Calc(void); + void Recalc_Attributes(void); + int Reduce_Tiberium(int levels); + int Reduce_Wall(int damage); + void Incoming(COORDINATE threat=0, bool forced = false); + void Adjust_Threat(HousesType house, int threat_value); + + int operator != (CellClass const &) const {return 0;}; + + int Validate(void) const; + + private: + CellClass (CellClass const &) {}; + + LandType Land; // The land type of this cell. +}; + +#endif diff --git a/CHECKBOX.CPP b/CHECKBOX.CPP new file mode 100644 index 0000000..48040db --- /dev/null +++ b/CHECKBOX.CPP @@ -0,0 +1,72 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\checkbox.cpv 1.6 16 Oct 1995 16:49:36 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CHECKBOX.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 05/26/95 * + * * + * Last Update : July 1, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * CheckBoxClass::Draw_Me -- Draws the checkbox imagery. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "checkbox.h" + + +/*********************************************************************************************** + * CheckBoxClass::Draw_Me -- Draws the checkbox imagery. * + * * + * This routine will draw the checkbox either filled or empty as necessary. * + * * + * INPUT: forced -- Should the check box be drawn even if it doesn't think it needs to? * + * * + * OUTPUT: Was the check box rendered? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/01/1995 JLB : Created. * + *=============================================================================================*/ +int CheckBoxClass::Draw_Me(int forced) +{ + if (ToggleClass::Draw_Me(forced)) { + + Hide_Mouse(); + Draw_Box(X, Y, Width, Height, BOXSTYLE_GREEN_DOWN, false); + LogicPage->Fill_Rect(X+1, Y+1, X+Width-2, Y+Height-2, DKGREY); + if (IsOn) { + LogicPage->Draw_Line(X+1, Y+1, X+Width-2, Y+Height-2, BLACK); + LogicPage->Draw_Line(X+Width-2, Y+1, X+1, Y+Height-2, BLACK); + } + Show_Mouse(); + return(true); + } + return(false); +} diff --git a/CHECKBOX.H b/CHECKBOX.H new file mode 100644 index 0000000..6b75235 --- /dev/null +++ b/CHECKBOX.H @@ -0,0 +1,55 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\checkbox.h_v 1.6 16 Oct 1995 16:46:00 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CHECKBOX.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 05/26/95 * + * * + * Last Update : May 26, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef CHECKBOX_H +#define CHECKBOX_H + +#include "toggle.h" + +class CheckBoxClass : public ToggleClass +{ + public: + CheckBoxClass(unsigned id, int x, int y) : + ToggleClass(id, x, y, 7, 7) + {}; + + virtual int Draw_Me(int forced=false); + + protected: +}; + +#endif diff --git a/CHEKLIST.CPP b/CHEKLIST.CPP new file mode 100644 index 0000000..7f50505 --- /dev/null +++ b/CHEKLIST.CPP @@ -0,0 +1,168 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\cheklist.cpv 2.18 16 Oct 1995 16:48:36 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CHEKLIST.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : February 16, 1995 * + * * + * Last Update : February 16, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * CheckListClass::Action -- action function for this class * + * CheckListClass::CheckListClass -- constructor * + * CheckListClass::Check_Item -- [un]checks an items * + * CheckListClass::~CheckListClass -- destructor * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*************************************************************************** + * CheckListClass::CheckListClass -- constructor * + * * + * INPUT: * + * id control ID for this list box * + * x x-coord * + * y y-coord * + * w width * + * h height * + * flags mouse event flags * + * up ptr to Up-arrow shape * + * down ptr to Down-arrow shape * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/16/1995 BR : Created. * + *=========================================================================*/ +CheckListClass::CheckListClass(int id, int x, int y, int w, int h, TextPrintType flags, + void const * up, void const * down) : + ListClass (id, x, y, w, h, flags, up, down) +{ + IsReadOnly = false; +} + + +/*************************************************************************** + * CheckListClass::Check_Item -- [un]checks an items * + * * + * INPUT: * + * index index of item to check or uncheck * + * checked 0 = uncheck, non-zero = check * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/16/1995 BR : Created. * + *=========================================================================*/ +void CheckListClass::Check_Item(int index, int checked) +{ + if (List[index]) { + ((char &)List[index][0]) = checked ? CHECK_CHAR : UNCHECK_CHAR; + } +} + + +/*************************************************************************** + * CheckListClass::Is_Checked -- returns checked state of an item * + * * + * INPUT: * + * index index of item to query * + * * + * OUTPUT: * + * 0 = item is unchecked, 1 = item is checked * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/16/1995 BR : Created. * + *=========================================================================*/ +int CheckListClass::Is_Checked(int index) const +{ + if (List[index]) { + return(List[index][0] == CHECK_CHAR); + } + return(false); +} + + +/*************************************************************************** + * CheckListClass::Action -- action function for this class * + * * + * INPUT: * + * flags the reason we're being called * + * key the KN_number that was pressed * + * * + * OUTPUT: * + * true = event was processed, false = event not processed * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/16/1995 BR : Created. * + *=========================================================================*/ +int CheckListClass::Action(unsigned flags, KeyNumType &key) +{ + int rc; + + /* + ** If this is a read-only list, it's a display-only device + */ + if (IsReadOnly) { + return(false); + } + + /* + ** Invoke parents Action first, so it can set the SelectedIndex if needed. + */ + rc = ListClass::Action(flags, key); + + /* + ** Now, if this event was a left-press, toggle the checked state of the + ** current item. + */ + if (flags & LEFTPRESS) { + if (Is_Checked(SelectedIndex)) { + Check_Item(SelectedIndex,0); + } else { + Check_Item(SelectedIndex,1); + } + } + + return(rc); +} diff --git a/CHEKLIST.H b/CHEKLIST.H new file mode 100644 index 0000000..e9d637d --- /dev/null +++ b/CHEKLIST.H @@ -0,0 +1,86 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\cheklist.h_v 2.19 16 Oct 1995 16:46:20 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CHEKLIST.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : February 16, 1995 * + * * + * Last Update : February 16, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * This class behaves just like the standard list box, except that if the * + * first character of a list entry is a space, clicking on it toggles the * + * space with a check-mark ('\3'). This makes each entry in the list box * + * "toggle-able". * + *-------------------------------------------------------------------------* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef CHEKLIST_H +#define CHEKLIST_H + +#include "list.h" + + +class CheckListClass : public ListClass +{ + public: + /*--------------------------------------------------------------------- + Constructor/Destructor + ---------------------------------------------------------------------*/ + CheckListClass(int id, int x, int y, int w, int h, TextPrintType flags, + void const * up, void const * down); + ~CheckListClass(void) {}; + + /*--------------------------------------------------------------------- + Checkmark utility functions + ---------------------------------------------------------------------*/ + void Check_Item(int index, int checked); // sets checked state of item + int Is_Checked(int index) const; // gets checked state of item + + /*--------------------------------------------------------------------- + This defines the ASCII value of the checkmark character & non-checkmark + character. + ---------------------------------------------------------------------*/ + enum CheckListClassEnum { + CHECK_CHAR = '\3', + UNCHECK_CHAR = ' ', + }; + + void Set_Read_Only(int rdonly) {IsReadOnly = rdonly;} + + protected: + virtual int Action(unsigned flags, KeyNumType &key); + + private: + bool IsReadOnly; + +}; + + + +#endif +/************************** end of cheklist.h ******************************/ diff --git a/CO-WC32.LNT b/CO-WC32.LNT new file mode 100644 index 0000000..e3f749b --- /dev/null +++ b/CO-WC32.LNT @@ -0,0 +1,67 @@ +// Compiler Options for Watcom C, C++ 32 bit + +-cwc + +// This file contains options to allow PC-lint to process source +// files for your compiler. It is used as follows: +// +// lint co-wc32.lnt source-file(s) +// +-d_M_IX86=200 // assume Intel 80286 architecture -- modify to suit +-d__declspec()= // ignore this construct + + // additional reserve words needed ++rw(_loadds,_export) ++rw(__interrupt,__near,__far,__huge,__fortran,__pascal,__cdecl) ++rw(__export,__loadds,__saveregs,__asm,__fastcall,__stdcall) ++rw(_export) + ++fcd // makes cdecl significant -- used for proto generation ++fcu // chars are by default unsigned ++fsu // so are strings +-d__386__ // pre-defined macro for 386 version, not set by -cwc +-d__FLAT__ // not set by -cwc +-si4 // sizeof(int) is 4 +-spN4 // sizeof(near pointer) is 4 +-spF6 // sizeof( far pointer) is 6 +-sld10 // sizeof(long double) is 10. +-function(exit,_exit) // _exit() is like exit() +-emacro(734,putc) // don't complain about items being too large. +-emacro(506,putc) // don't complain about constant Boolean +-emacro(???,va_arg) // the va_arg() macro can yield 415, 416, 661, 662 + // 796 and 797 (out-of-bounds errors). + + // While processing compiler (library) header files ... +-elib(46) // an unsigned short bit field is used as __FILLER__ +-elib(522) // function return value ignored +-elib(537) // repeated include file (ios.h) +-elib(641) // converting enum to int +-elib(652) // suppress message about #define of earlier declared symbols +-elib(655) // ORing enum's +-elib(726) // extraneous comma in enumeration +-elib(760) // suppress message about multiple identical macro defs +-elib(762) // suppress message about multiple identical declarations and +-elib(806) // small bit field is signed +-elib(1053) // prototypes cannot be distinguished +-elib(1511) // member (rdbuf) hides nonvirtual member +-elib(1704) // private copy constructor +-elib(1712) // default constructor missing +-elib(1717) // empty prototypes +-elib(1720) // strange argument to assignment operator +-elib(1721) // unusual operator =() declaration +-elib(1722) // assignment operator does not return ref to class +-elib(1724) // strange argument to copy constructor + +-esym(1702,operator<<,operator>>) + +// The following functions exhibit variable return modes. +// That is, they may equally-usefully be called for a value +// as called just for their effects. Accordingly we inhibit +// Warning 534 for these functions. +// Feel free to add to or subtract from this list. + +-esym(534,close,creat,fclose,fflush,fprintf,fputc) +-esym(534,fputs,fscanf,fseek,fwrite,lseek,memcpy,memmove,memset) +-esym(534,printf,puts,scanf,sprintf,sscanf,strcat,strcpy) +-esym(534,strncat,strncpy,unlink,write) + diff --git a/COLRLIST.CPP b/COLRLIST.CPP new file mode 100644 index 0000000..a48be5a --- /dev/null +++ b/COLRLIST.CPP @@ -0,0 +1,283 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\colrlist.cpv 1.9 16 Oct 1995 16:50:02 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : LIST.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : April 19, 1995 [BRR] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * ColorListClass::Add_Item -- Adds an item to the list * + * ColorListClass::ColorListClass -- Class constructor * + * ColorListClass::Draw_Entry -- Draws one text line * + * ColorListClass::Remove_Item -- Removes an item from the list * + * ColorListClass::Set_Selected_Style -- tells how to draw selected item * + * ColorListClass::~ColorListClass -- Class destructor * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*************************************************************************** + * ColorListClass::ColorListClass -- class constructor * + * * + * INPUT: * + * id button ID * + * x,y upper-left corner, in pixels * + * w,h width, height, in pixels * + * list ptr to array of char strings to list * + * flags flags for mouse, style of listbox * + * up,down pointers to shapes for up/down buttons * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: 01/05/1995 MML : Created. * + *=========================================================================*/ +ColorListClass::ColorListClass (int id, int x, int y, int w, int h, + TextPrintType flags, void const * up, void const * down) : + ListClass (id, x, y, w, h, flags, up, down) +{ + Style = SELECT_HIGHLIGHT; + SelectColor = -1; +} + + +/*************************************************************************** + * ColorListClass::~ColorListClass -- Class destructor * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/19/1995 BRR : Created. * + *=========================================================================*/ +ColorListClass::~ColorListClass(void) +{ + Colors.Clear(); +} + + +/*************************************************************************** + * ColorListClass::Add_Item -- Adds an item to the list * + * * + * INPUT: * + * text text to add to list * + * color color for item * + * * + * OUTPUT: * + * position of item in the list * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/19/1995 BRR : Created. * + *=========================================================================*/ +int ColorListClass::Add_Item(char const * text, char color) +{ + Colors.Add(color); + return(ListClass::Add_Item(text)); +} + + +/*************************************************************************** + * ColorListClass::Add_Item -- Adds an item to the list * + * * + * INPUT: * + * text text to add to list * + * color color for item * + * * + * OUTPUT: * + * position of item in the list * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/19/1995 BRR : Created. * + *=========================================================================*/ +int ColorListClass::Add_Item(int text, char color) +{ + Colors.Add(color); + return(ListClass::Add_Item(text)); +} + + +/*************************************************************************** + * ColorListClass::Remove_Item -- Removes an item from the list * + * * + * INPUT: * + * text ptr to item to remove * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/19/1995 BRR : Created. * + *=========================================================================*/ +void ColorListClass::Remove_Item(char const * text) +{ + int index = List.ID(text); + if (index != -1) { + Colors.Delete(index); + ListClass::Remove_Item(text); + } +} + + +/*************************************************************************** + * ColorListClass::Set_Selected_Style -- tells how to draw selected item * + * * + * INPUT: * + * style style to draw * + * color color to draw the special style in; -1 = use item's color* + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/19/1995 BRR : Created. * + *=========================================================================*/ +void ColorListClass::Set_Selected_Style(SelectStyleType style, int color) +{ + Style = style; + SelectColor = color; +} + + +/*************************************************************************** + * ColorListClass::Draw_Entry -- Draws one text line * + * * + * INPUT: * + * index index into List of item to draw * + * x,y x,y coords to draw at * + * width maximum width allowed for text * + * selected true = this item is selected * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/19/1995 BRR : Created. * + *=========================================================================*/ +void ColorListClass::Draw_Entry(int index, int x, int y, int width, int selected) +{ + int color; + + /* + ** Draw a non-selected item in its color + */ + if (!selected) { + Conquer_Clip_Text_Print(List[index], x, y, Colors[index], TBLACK, TextFlags, width, Tabs); + return; + } + + /* + ** For selected items, choose the right color & style: + */ + if (SelectColor==-1) { + color = Colors[index]; + } else { + color = SelectColor; + } + + switch (Style) { + /* + ** NONE: Just print the string in its native color + */ + case SELECT_NONE: + Conquer_Clip_Text_Print(List[index], x, y, Colors[index], TBLACK, TextFlags, width, Tabs); + break; + + /* + ** HIGHLIGHT: Draw the string in the highlight color (SelectColor must + ** be set) + */ + case SELECT_HIGHLIGHT: + if (TextFlags & TPF_6PT_GRAD) { + Conquer_Clip_Text_Print(List[index], x, y, color, TBLACK, TextFlags | TPF_BRIGHT_COLOR, width, Tabs); + } else { + Conquer_Clip_Text_Print(List[index], x, y, color, TBLACK, TextFlags, width, Tabs); + } + break; + + /* + ** BOX: Draw a box around the item in the current select color + */ + case SELECT_BOX: + LogicPage->Draw_Rect (x, y, x + width - 2, y + LineHeight - 2, color); + Conquer_Clip_Text_Print(List[index], x, y, Colors[index], TBLACK, TextFlags, width, Tabs); + break; + + /* + ** BAR: draw a color bar under the text + */ + case SELECT_BAR: + if (TextFlags & TPF_6PT_GRAD) { + LogicPage->Fill_Rect (x, y, x + width - 1, y + LineHeight - 1, SelectColor); + Conquer_Clip_Text_Print(List[index], x, y, Colors[index], TBLACK, TextFlags | TPF_BRIGHT_COLOR, width, Tabs); + } else { + LogicPage->Fill_Rect (x, y, x + width - 2, y + LineHeight - 2, SelectColor); + Conquer_Clip_Text_Print(List[index], x, y, Colors[index], TBLACK, TextFlags, width, Tabs); + } + break; + + /* + ** INVERT: Draw text as the background color on foreground color + */ + case SELECT_INVERT: + if (TextFlags & TPF_6PT_GRAD) { + LogicPage->Fill_Rect (x, y, x + width - 1, y + LineHeight - 1, Colors[index]); + Conquer_Clip_Text_Print(List[index], x, y, BLACK, TBLACK, TextFlags, width, Tabs); + } else { + LogicPage->Fill_Rect (x, y, x + width - 2, y + LineHeight - 2, Colors[index]); + Conquer_Clip_Text_Print(List[index], x, y, LTGREY, TBLACK, TextFlags, width, Tabs); + } + break; + + } +} diff --git a/COLRLIST.H b/COLRLIST.H new file mode 100644 index 0000000..5a2b105 --- /dev/null +++ b/COLRLIST.H @@ -0,0 +1,87 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\colrlist.h_v 1.10 16 Oct 1995 16:47:16 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : COLRLIST.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 15, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef COLORLIST_H +#define COLORLIST_H + +#include "list.h" + + +/*************************************************************************** +** This class adds the ability for every list item to have a different color. +*/ +class ColorListClass : public ListClass +{ + public: + /********************************************************************* + ** These enums are the ways a selected item can be drawn + */ + typedef enum SelectEnum { + SELECT_NONE, // selected items aren't drawn differently + SELECT_HIGHLIGHT, // item is highlighted + SELECT_BOX, // draw a box around the item + SELECT_BAR, // draw a bar behind the item + SELECT_INVERT, // draw the string inverted + } SelectStyleType; + + ColorListClass(int id, int x, int y, int w, int h, TextPrintType flags, + void const * up, void const * down); + virtual ~ColorListClass(void); + + virtual int Add_Item(char const * text, char color = WHITE); + virtual int Add_Item(int text, char color = WHITE); + virtual void Remove_Item(char const * text); + + virtual void Set_Selected_Style(SelectStyleType style, int color = -1); + + /* + ** This is the list of colors for each item. + */ + DynamicVectorClass Colors; + + protected: + virtual void Draw_Entry(int index, int x, int y, int width, int selected); + + /* + ** This tells how to draw the selected item. + */ + SelectStyleType Style; + int SelectColor; + +}; + +#endif diff --git a/COMBAT.CPP b/COMBAT.CPP new file mode 100644 index 0000000..ac34d41 --- /dev/null +++ b/COMBAT.CPP @@ -0,0 +1,235 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\combat.cpv 2.17 16 Oct 1995 16:48:32 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : COMBAT.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 19, 1994 * + * * + * Last Update : January 1, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Explosion_Damage -- Inflict an explosion damage affect. * + * Modify_Damage -- Adjusts damage to reflect the nature of the target. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +int Modify_Damage(int damage, WarheadType warhead, ArmorType armor); +void Explosion_Damage(COORDINATE coord, unsigned strength, TechnoClass *source, WarheadType warhead); + + +/*********************************************************************************************** + * Modify_Damage -- Adjusts damage to reflect the nature of the target. * + * * + * This routine is the core of combat tactics. It implements the * + * affect various armor types have against various weapon types. By * + * careful exploitation of this table, tactical advantage can be * + * obtained. * + * * + * INPUT: damage -- The damage points to process. * + * * + * warhead -- The source of the damage points. * + * * + * armor -- The type of armor defending against the damage. * + * * + * distance -- The distance (in leptons) from the source of the damage. * + * * + * OUTPUT: Returns with the adjusted damage points to inflict upon the * + * target. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/16/1994 JLB : Created. * + * 04/17/1994 JLB : Always does a minimum of damage. * + * 01/01/1995 JLB : Takes into account distance from damage source. * + *=============================================================================================*/ +int Modify_Damage(int damage, WarheadType warhead, ArmorType armor, int distance) +{ + /* + ** If there is no raw damage value to start with, then + ** there can be no modified damage either. + */ + if (Special.IsInert || !damage || warhead == WARHEAD_NONE) return(0); + + WarheadTypeClass const * whead = &Warheads[warhead]; + + damage = Fixed_To_Cardinal(damage, whead->Modifier[armor]); + + /* + ** Reduce damage according to the distance from the impact point. + */ + if (damage) { +// if (distance < 0x0010) damage *= 2; // Double damage for direct hits. + distance >>= whead->SpreadFactor; + distance = Bound(distance, 0, 16); + damage >>= distance; + } + + /* + ** If damage was indicated, then it should never drop below one damage point regardless + ** of modifiers. This allows a very weak attacker to eventually destroy anything it + ** fires upon, given enough time. + */ + return(damage); +} + + +/*********************************************************************************************** + * Explosion_Damage -- Inflict an explosion damage affect. * + * * + * Processes the collateral damage affects typically caused by an * + * explosion. * + * * + * INPUT: coord -- The coordinate of ground zero. * + * * + * strength -- Raw damage points at ground zero. * + * * + * source -- Source of the explosion (who is responsible). * + * * + * warhead -- The kind of explosion to process. * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine can consume some time and will affect the AI * + * of nearby enemy units (possibly). * + * * + * HISTORY: * + * 08/16/1991 JLB : Created. * + * 11/30/1991 JLB : Uses coordinate system. * + * 12/27/1991 JLB : Radius of explosion damage effect. * + * 04/13/1994 JLB : Streamlined. * + * 04/16/1994 JLB : Warhead damage type modifier. * + * 04/17/1994 JLB : Cleaned up. * + * 06/20/1994 JLB : Uses object pointers to distribute damage. * + * 06/20/1994 JLB : Source is a pointer. * + *=============================================================================================*/ +void Explosion_Damage(COORDINATE coord, unsigned strength, TechnoClass * source, WarheadType warhead) +{ + CELL cell; // Cell number under explosion. + ObjectClass * object; // Working object pointer. + ObjectClass * objects[32]; // Maximum number of objects that can be damaged. + int distance; // Distance to unit. + int range; // Damage effect radius. + int index; + int count; // Number of vehicle IDs in list. + + if (!strength || Special.IsInert || warhead == WARHEAD_NONE) return; + + WarheadTypeClass const * whead = &Warheads[warhead]; + range = ICON_LEPTON_W + (ICON_LEPTON_W >> 1); + cell = Coord_Cell(coord); + if ((unsigned)cell >= MAP_CELL_TOTAL) return; +// if (!Map.In_Radar(cell)) return; + + CellClass * cellptr = &Map[cell]; + ObjectClass * impacto = cellptr->Cell_Occupier(); + + /* + ** Fill the list of unit IDs that will have damage + ** assessed upon them. The units can be lifted from + ** the cell data directly. + */ + count = 0; + for (FacingType i = FACING_NONE; i < FACING_COUNT; i++) { + /* + ** Fetch a pointer to the cell to examine. This is either + ** an adjacent cell or the center cell. Damage never spills + ** further than one cell away. + */ + if (i != FACING_NONE) { + cellptr = &Map[cell].Adjacent_Cell(i); + } + + /* + ** Add all objects in this cell to the list of objects to possibly apply + ** damage to. The list stops building when the object pointer list becomes + ** full. Do not include overlapping objects; selection state can affect + ** the overlappers, and this causes multiplayer games to go out of sync. + */ + object = cellptr->Cell_Occupier(); + while (object) { + if (!object->IsToDamage && object != source) { + object->IsToDamage = true; + objects[count++] = object; + if (count >= (sizeof(objects)/sizeof(objects[0]))) break; + } + object = object->Next; + } + if (count >= (sizeof(objects)/sizeof(objects[0]))) break; + } + + /* + ** Sweep through the units to be damaged and damage them. When damaging + ** buildings, consider a hit on any cell the building occupies as if it + ** were a direct hit on the building's center. + */ + for (index = 0; index < count; index++) { + object = objects[index]; + + object->IsToDamage = false; + if (object->What_Am_I() == RTTI_BUILDING && impacto == object) { + distance = 0; + } else { + distance = Distance(coord, object->Center_Coord()); + } + if (object->IsDown && !object->IsInLimbo && distance < range) { + int damage = strength; + + /* + ** High explosive does double damage against aircraft. + */ + if (warhead == WARHEAD_HE && object->What_Am_I() == RTTI_AIRCRAFT) { + damage *= 2; + } + + /* + ** Apply the damage to the object. + */ + if (damage) { + object->Take_Damage(damage, distance, warhead, source); + } + } + } + + /* + ** If there is a wall present at this location, it may be destroyed. Check to + ** make sure that the warhead is of the kind that can destroy walls. + */ + cellptr = &Map[cell]; + cellptr->Reduce_Tiberium(strength / 10); + if (cellptr->Overlay != OVERLAY_NONE) { + OverlayTypeClass const * optr = &OverlayTypeClass::As_Reference(cellptr->Overlay); + + if (optr->IsWall) { + if (whead->IsWallDestroyer || (whead->IsWoodDestroyer && optr->IsWooden)) { + Map[cell].Reduce_Wall(strength); + } + } + } +} diff --git a/COMBUF.CPP b/COMBUF.CPP new file mode 100644 index 0000000..a381524 --- /dev/null +++ b/COMBUF.CPP @@ -0,0 +1,1039 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\combuf.cpv 1.4 16 Oct 1995 16:50:16 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : COMBUF.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : May 2, 1995 [BRR] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * CommBufferClass::CommBufferClass -- class constructor * + * CommBufferClass::~CommBufferClass -- class destructor * + * CommBufferClass::Init -- initializes this queue * + * CommBufferClass::Queue_Send -- queues a message for sending * + * CommBufferClass::UnQueue_Send -- removes next entry from send queue * + * CommBufferClass::Get_Send -- gets ptr to queue entry * + * CommBufferClass::Queue_Receive -- queues a received message * + * CommBufferClass::UnQueue_Receive -- removes next entry from send queue* + * CommBufferClass::Get_Receive -- gets ptr to queue entry * + * CommBufferClass::Add_Delay -- adds a new delay value for response time* + * CommBufferClass::Avg_Response_Time -- returns average response time * + * CommBufferClass::Max_Response_Time -- returns max response time * + * CommBufferClass::Reset_Response_Time -- resets computations * + * Mono_Debug_Print -- Debug output routine * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*************************************************************************** + * CommBufferClass::CommBufferClass -- class constructor * + * * + * INPUT: * + * numsend # queue entries for sending * + * numreceive # queue entries for receiving * + * maxlen maximum desired packet length, in bytes * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +CommBufferClass::CommBufferClass(int numsend, int numreceive, int maxlen) +{ + int i; + + /* + ----------------------------- Init variables ----------------------------- + */ + MaxSend = numsend; + MaxReceive = numreceive; + MaxPacketSize = maxlen; + + /* + ----------------------- Allocate the queue entries ----------------------- + */ + SendQueue = new SendQueueType[numsend]; + ReceiveQueue = new ReceiveQueueType[numreceive]; + + SendIndex = new int[numsend]; + ReceiveIndex = new int[numreceive]; + + /* + ---------------------- Allocate queue entry buffers ---------------------- + */ + for (i = 0; i < MaxSend; i++) { + SendQueue[i].Buffer = new char[maxlen]; + } + + for (i = 0; i < MaxReceive; i++) { + ReceiveQueue[i].Buffer = new char[maxlen]; + } + + Init(); + +} /* end of CommBufferClass */ + + +/*************************************************************************** + * CommBufferClass::~CommBufferClass -- class destructor * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +CommBufferClass::~CommBufferClass() +{ + int i; + + /* + ------------------------ Free queue entry buffers ------------------------ + */ + for (i = 0; i < MaxSend; i++) { + delete [] SendQueue[i].Buffer; + } + + for (i = 0; i < MaxReceive; i++) { + delete [] ReceiveQueue[i].Buffer; + } + + delete [] SendQueue; + delete [] ReceiveQueue; + + delete [] SendIndex; + delete [] ReceiveIndex; + +} /* end of ~CommBufferClass */ + + +/*************************************************************************** + * CommBufferClass::Init -- initializes this queue * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/20/1995 BR : Created. * + *=========================================================================*/ +void CommBufferClass::Init(void) +{ + int i; + + /*------------------------------------------------------------------------ + Init data members + ------------------------------------------------------------------------*/ + SendTotal = 0L; + ReceiveTotal = 0L; + + DelaySum = 0L; + NumDelay = 0L; + MeanDelay = 0L; + MaxDelay = 0L; + + SendCount = 0; + + ReceiveCount = 0; + + /*------------------------------------------------------------------------ + Init the queue entries + ------------------------------------------------------------------------*/ + for (i = 0; i < MaxSend; i++) { + SendQueue[i].IsActive = 0; + SendQueue[i].IsACK = 0; + SendQueue[i].FirstTime = 0L; + SendQueue[i].LastTime = 0L; + SendQueue[i].SendCount = 0L; + SendQueue[i].BufLen = 0; + + SendIndex[i] = 0; + } + + for (i = 0; i < MaxReceive; i++) { + ReceiveQueue[i].IsActive = 0; + ReceiveQueue[i].IsRead = 0; + ReceiveQueue[i].IsACK = 0; + ReceiveQueue[i].BufLen = 0; + + ReceiveIndex[i] = 0; + } + + /*------------------------------------------------------------------------ + Init debug values + ------------------------------------------------------------------------*/ + DebugOffset = 0; + DebugSize = 0; + DebugNames = NULL; + DebugMaxNames = 0; + +} /* end of Init */ + + +void CommBufferClass::Init_Send_Queue(void) +{ + int i; + + /*------------------------------------------------------------------------ + Init data members + ------------------------------------------------------------------------*/ + SendCount = 0; + + /*------------------------------------------------------------------------ + Init the queue entries + ------------------------------------------------------------------------*/ + for (i = 0; i < MaxSend; i++) { + SendQueue[i].IsActive = 0; + SendQueue[i].IsACK = 0; + SendQueue[i].FirstTime = 0L; + SendQueue[i].LastTime = 0L; + SendQueue[i].SendCount = 0L; + + SendIndex[i] = 0; + } + +} /* end of Init_Send_Queue */ + + +/*************************************************************************** + * CommBufferClass::Queue_Send -- queues a message for sending * + * * + * INPUT: * + * buf buffer containing the message * + * buflen length of 'buf' * + * * + * OUTPUT: * + * 1 = OK, 0 = no room in the queue * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int CommBufferClass::Queue_Send(void *buf, int buflen) +{ + int i; + int index; + + /* + --------------------- Error if no room in the queue ---------------------- + */ + if (SendCount==MaxSend) + return(0); + + /* + -------------------------- Find an empty slot ---------------------------- + */ + index = -1; + for (i = 0; i < MaxSend; i++) { + if (SendQueue[i].IsActive==0) { + index = i; + break; + } + } + + /* + ---------------------------- Set entry flags ----------------------------- + */ + SendQueue[index].IsActive = 1; // entry is now active + SendQueue[index].IsACK = 0; // entry hasn't been ACK'd + SendQueue[index].FirstTime = 0L; // filled in by Manager when sent + SendQueue[index].LastTime = 0L; // filled in by Manager when sent + SendQueue[index].SendCount = 0L; // filled in by Manager when sent + SendQueue[index].BufLen = buflen; // save buffer size + + /* + ------------------------- Copy the packet data --------------------------- + */ + memcpy(SendQueue[index].Buffer,buf,buflen); + + /* + ----------------------- Save this entry's index -------------------------- + */ + SendIndex[SendCount] = index; + + /* + -------------------- Increment counters & entry ptr ---------------------- + */ + SendCount++; + SendTotal++; + + return(1); + +} /* end of Queue_Send */ + + +/*************************************************************************** + * CommBufferClass::UnQueue_Send -- removes next entry from send queue * + * * + * Frees the given entry; the index given by the caller is the "active" * + * index value (ie the "nth" active entry), not the actual index in the * + * array. * + * * + * INPUT: * + * buf buffer to store entry's data in; if NULL, it's discarded * + * buflen filled in with length of entry retrieved * + * index "index" of entry to un-queue * + * * + * OUTPUT: * + * 1 = OK, 0 = no entry to retreive * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int CommBufferClass::UnQueue_Send(void *buf, int *buflen, int index) +{ + int i; + + /* + --------------------- Error if no entry to retrieve ---------------------- + */ + if (SendCount==0 || SendQueue[SendIndex[index]].IsActive==0) + return(0); + + /* + ---------------------- Copy the data from the entry ---------------------- + */ + if (buf!=NULL) { + memcpy(buf,SendQueue[SendIndex[index]].Buffer, + SendQueue[SendIndex[index]].BufLen); + (*buflen) = SendQueue[SendIndex[index]].BufLen; + } + + /* + ---------------------------- Set entry flags ----------------------------- + */ + SendQueue[SendIndex[index]].IsActive = 0; + SendQueue[SendIndex[index]].IsACK = 0; + SendQueue[SendIndex[index]].FirstTime = 0L; + SendQueue[SendIndex[index]].LastTime = 0L; + SendQueue[SendIndex[index]].SendCount = 0L; + SendQueue[SendIndex[index]].BufLen = 0; + + /* + ------------------------- Move Indices back one -------------------------- + */ + for (i = index; i < SendCount - 1; i++) { + SendIndex[i] = SendIndex[i + 1]; + } + SendIndex[SendCount - 1] = 0; + SendCount--; + + return(1); + +} /* end of UnQueue_Send */ + + +/*************************************************************************** + * CommBufferClass::Get_Send -- gets ptr to queue entry * + * * + * This routine gets a pointer to the indicated queue entry. The index * + * value is relative to the next-accessable queue entry; 0 = get the * + * next available queue entry, 1 = get the one behind that, etc. * + * * + * INPUT: * + * index index of entry to get (0 = 1st available) * + * * + * OUTPUT: * + * ptr to entry * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/21/1994 BR : Created. * + *=========================================================================*/ +SendQueueType * CommBufferClass::Get_Send(int index) +{ + if (SendQueue[SendIndex[index]].IsActive==0) { + return(NULL); + } else { + return(&SendQueue[SendIndex[index]]); + } + +} /* end of Get_Send */ + + +/*************************************************************************** + * CommBufferClass::Queue_Receive -- queues a received message * + * * + * INPUT: * + * buf buffer containing the message * + * buflen length of 'buf' * + * * + * OUTPUT: * + * 1 = OK, 0 = no room in the queue * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int CommBufferClass::Queue_Receive(void *buf, int buflen) +{ + int i; + int index; + +//CCDebugString ("C&C95 - Queueing a receive packet\n"); + + /* + --------------------- Error if no room in the queue ---------------------- + */ + if (ReceiveCount==MaxReceive) { +CCDebugString ("C&C95 - Error - Receive queue full!\n"); + return(0); + } + + /* + -------------------------- Find an empty slot ---------------------------- + */ + index = -1; + for (i = 0; i < MaxReceive; i++) { + if (ReceiveQueue[i].IsActive==0) { + index = i; + break; + } + } + +if (index == -1){ +CCDebugString ("C&C95 - Error - Receive queue full too!\n"); +} + + + /* + ---------------------------- Set entry flags ----------------------------- + */ + ReceiveQueue[index].IsActive = 1; + ReceiveQueue[index].IsRead = 0; + ReceiveQueue[index].IsACK = 0; + ReceiveQueue[index].BufLen = buflen; + + /* + ------------------------- Copy the packet data --------------------------- + */ + memcpy(ReceiveQueue[index].Buffer,buf,buflen); + + /* + ----------------------- Save this entry's index -------------------------- + */ + ReceiveIndex[ReceiveCount] = index; + + /* + -------------------- Increment counters & entry ptr ---------------------- + */ + ReceiveCount++; + ReceiveTotal++; + + return(1); + +} /* end of Queue_Receive */ + + +/*************************************************************************** + * CommBufferClass::UnQueue_Receive -- removes next entry from send queue * + * * + * Frees the given entry; the index given by the caller is the "active" * + * index value (ie the "nth" active entry), not the actual index in the * + * array. * + * * + * INPUT: * + * buf buffer to store entry's data in; if NULL, it's discarded * + * buflen filled in with length of entry retrieved * + * index index of entry to un-queue * + * * + * OUTPUT: * + * 1 = OK, 0 = no entry to retreive * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int CommBufferClass::UnQueue_Receive(void *buf, int *buflen, int index) +{ + int i; + + /* + --------------------- Error if no entry to retrieve ---------------------- + */ + if (ReceiveCount==0 || ReceiveQueue[ReceiveIndex[index]].IsActive==0) { + return(0); + } + + /* + ---------------------- Copy the data from the entry ---------------------- + */ + if (buf!=NULL) { + memcpy(buf,ReceiveQueue[ReceiveIndex[index]].Buffer, + ReceiveQueue[ReceiveIndex[index]].BufLen); + (*buflen) = ReceiveQueue[ReceiveIndex[index]].BufLen; + } + + /* + ---------------------------- Set entry flags ----------------------------- + */ + ReceiveQueue[ReceiveIndex[index]].IsActive = 0; + ReceiveQueue[ReceiveIndex[index]].IsRead = 0; + ReceiveQueue[ReceiveIndex[index]].IsACK = 0; + ReceiveQueue[ReceiveIndex[index]].BufLen = 0; + + /* + ------------------------- Move Indices back one -------------------------- + */ + for (i = index; i < ReceiveCount - 1; i++) { + ReceiveIndex[i] = ReceiveIndex[i + 1]; + } + ReceiveIndex[ReceiveCount - 1] = 0; + ReceiveCount--; + + return(1); + +} /* end of UnQueue_Receive */ + + +/*************************************************************************** + * CommBufferClass::Get_Receive -- gets ptr to queue entry * + * * + * This routine gets a pointer to the indicated queue entry. The index * + * value is relative to the next-accessable queue entry; 0 = get the * + * next available queue entry, 1 = get the one behind that, etc. * + * * + * INPUT: * + * index index of entry to get (0 = 1st available) * + * * + * OUTPUT: * + * ptr to entry * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/21/1994 BR : Created. * + *=========================================================================*/ +ReceiveQueueType * CommBufferClass::Get_Receive(int index) +{ + if (ReceiveQueue[ReceiveIndex[index]].IsActive==0) { + return(NULL); + } else { + return(&ReceiveQueue[ReceiveIndex[index]]); + } + +} /* end of Get_Receive */ + + +/*************************************************************************** + * CommBufferClass::Add_Delay -- adds a new delay value for response time * + * * + * This routine updates the average response time for this queue. The * + * computation is based on the average of the last 'n' delay values given, * + * It computes a running total of the last n delay values, then divides * + * that by n to compute the average. * + * * + * When the number of values given exceeds the max, the mean is subtracted * + * off the total, then the new value is added in. Thus, any single delay * + * value will have an effect on the total that approaches 0 over time, and * + * the new delay value contributes to 1/n of the mean. * + * * + * INPUT: * + * delay value to add into the response time computation * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/19/1995 BR : Created. * + *=========================================================================*/ +void CommBufferClass::Add_Delay(unsigned long delay) +{ + int roundoff = 0; + + if (NumDelay==256) { + DelaySum -= MeanDelay; + DelaySum += delay; + if ( (DelaySum & 0x00ff) > 127) + roundoff = 1; + MeanDelay = (DelaySum >> 8) + roundoff; + } else { + NumDelay++; + DelaySum += delay; + MeanDelay = DelaySum / NumDelay; + } + + if (delay > MaxDelay) + MaxDelay = delay; + +} /* end of Add_Delay */ + + +/*************************************************************************** + * CommBufferClass::Avg_Response_Time -- returns average response time * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * latest computed average response time * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/19/1995 BR : Created. * + *=========================================================================*/ +unsigned long CommBufferClass::Avg_Response_Time(void) +{ + return(MeanDelay); + +} /* end of Avg_Response_Time */ + + +/*************************************************************************** + * CommBufferClass::Max_Response_Time -- returns max response time * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * latest computed average response time * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/19/1995 BR : Created. * + *=========================================================================*/ +unsigned long CommBufferClass::Max_Response_Time(void) +{ + return(MaxDelay); + +} /* end of Max_Response_Time */ + + +/*************************************************************************** + * CommBufferClass::Reset_Response_Time -- resets computations * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/19/1995 BR : Created. * + *=========================================================================*/ +void CommBufferClass::Reset_Response_Time(void) +{ + DelaySum = 0L; + NumDelay = 0L; + MeanDelay = 0L; + MaxDelay = 0L; + +} /* end of Reset_Response_Time */ + + +/*************************************************************************** + * CommBufferClass::Configure_Debug -- sets up special debug values * + * * + * Mono_Debug_Print2() can look into a packet to pull out a particular * + * ID, and can print both that ID and a string corresponding to * + * that ID. This routine configures these values so it can find * + * and decode the ID. This ID is used in addition to the normal * + * CommHeaderType values. * + * * + * INPUT: * + * offset ID's byte offset into packet * + * size size of ID, in bytes; 0 if none * + * names ptr to array of names; use ID as an index into this * + * maxnames max # in the names array; 0 if none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * Names shouldn't be longer than 12 characters. * + * * + * HISTORY: * + * 05/31/1995 BRR : Created. * + *=========================================================================*/ +void CommBufferClass::Configure_Debug(int offset, int size, char **names, + int maxnames) +{ + DebugOffset = offset; + DebugSize = size; + DebugNames = names; + DebugMaxNames = maxnames; + +} /* end of Configure_Debug */ + + +/*************************************************************************** + * Mono_Debug_Print -- Debug output routine * + * * + * This routine leaves 5 lines at the top for the caller's use. * + * * + * INPUT: * + * refresh 1 = clear screen & completely refresh * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/02/1995 BRR : Created. * + *=========================================================================*/ +void CommBufferClass::Mono_Debug_Print(int refresh) +{ +#ifdef WWLIB32_H + int i; // loop counter + static int send_col[] = {1,14,28}; // coords of send queue columns + static int receive_col[] = {40,54,68}; // coords of recv queue columns + int row,col; // current row,col for printing + int num; // max # items to print + + struct CommHdr { // this mirrors the CommHeaderType + unsigned short MagicNumber; + unsigned char Code; + unsigned long PacketID; + } *hdr; + + /*------------------------------------------------------------------------ + If few enough entries, call the verbose debug version + ------------------------------------------------------------------------*/ + if (MaxSend <= 16) { + Mono_Debug_Print2(refresh); + return; + } + + /*------------------------------------------------------------------------ + Refresh the screen + ------------------------------------------------------------------------*/ + if (refresh) { + Mono_Clear_Screen (); + Mono_Printf("ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\n"); + Mono_Printf("³ ³\n"); + Mono_Printf("³ ³\n"); + Mono_Printf("³ ³\n"); + Mono_Printf("ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´\n"); + Mono_Printf("³ Send Queue ³ Receive Queue ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ID Ct ACK ID Ct ACK ID Ct ACK³ ID Rd ACK ID Rd ACK ID Rd ACK³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ"); + } + + /*------------------------------------------------------------------------ + Print Send Queue items + ------------------------------------------------------------------------*/ + if (MaxSend <= 48) { + num = MaxSend; + } else { + num = 48; + } + col = 0; + row = 0; + for (i = 0; i < MaxSend; i++) { + Mono_Set_Cursor (send_col[col],row + 8); + if (SendQueue[i].IsActive) { + hdr = (CommHdr *)SendQueue[i].Buffer; + hdr->MagicNumber = hdr->MagicNumber; + hdr->Code = hdr->Code; + Mono_Printf ("%4d %2d %d",hdr->PacketID, SendQueue[i].SendCount, + SendQueue[i].IsACK); + } else { + Mono_Printf ("____ __ _ "); + } + + row++; + if (row > 15) { + row = 0; + col++; + } + } + + /*------------------------------------------------------------------------ + Print Receive Queue items + ------------------------------------------------------------------------*/ + if (MaxReceive <= 48) { + num = MaxSend; + } else { + num = 48; + } + col = 0; + row = 0; + for (i = 0; i < MaxReceive; i++) { + Mono_Set_Cursor (receive_col[col],row + 8); + if (ReceiveQueue[i].IsActive) { + hdr = (CommHdr *)ReceiveQueue[i].Buffer; + Mono_Printf ("%4d %d %d",hdr->PacketID, ReceiveQueue[i].IsRead, + ReceiveQueue[i].IsACK); + } else { + Mono_Printf ("____ _ _ "); + } + + row++; + if (row > 15) { + row = 0; + col++; + } + } + +#else + refresh = refresh; +#endif +} /* end of Mono_Debug_Print */ + + +/*************************************************************************** + * CommBufferClass::Mono_Debug_Print2 -- Debug output; alternate format * + * * + * This routine prints more information than the other version; it's * + * called only if the number of queue entries is small enough to support * + * this format. * + * * + * INPUT: * + * refresh 1 = clear screen & completely refresh * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/31/1995 BRR : Created. * + *=========================================================================*/ +void CommBufferClass::Mono_Debug_Print2(int refresh) +{ +#ifdef WWLIB32_H + int i; // loop counter + char txt[80]; + int val; + + struct CommHdr { // this mirrors the CommHeaderType + unsigned short MagicNumber; + unsigned char Code; + unsigned long PacketID; + } *hdr; + + /*------------------------------------------------------------------------ + Refresh the screen + ------------------------------------------------------------------------*/ + if (refresh) { + Mono_Clear_Screen (); + Mono_Printf("ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\n"); + Mono_Printf("³ ³\n"); + Mono_Printf("³ ³\n"); + Mono_Printf("³ ³\n"); + Mono_Printf("ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´\n"); + Mono_Printf("³ Send Queue ³ Receive Queue ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ID Ct Type Data Name ACK ³ ID Rd Type Data Name ACK ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ"); + } + + /*------------------------------------------------------------------------ + Print Send Queue items + ------------------------------------------------------------------------*/ + for (i = 0; i < MaxSend; i++) { + Mono_Set_Cursor (1,8 + i); + /*..................................................................... + Print an active entry + .....................................................................*/ + if (SendQueue[i].IsActive) { + /*.................................................................. + Get header info + ..................................................................*/ + hdr = (CommHdr *)SendQueue[i].Buffer; + hdr->MagicNumber = hdr->MagicNumber; + hdr->Code = hdr->Code; + sprintf(txt,"%4d %2d %-5s ", + hdr->PacketID, + SendQueue[i].SendCount, + ConnectionClass::Command_Name(hdr->Code)); + + /*.................................................................. + Decode app's ID & its name + ..................................................................*/ + if (DebugSize && (DebugOffset + DebugSize) <= SendQueue[i].BufLen) { + if (DebugSize==1) { + val = *(SendQueue[i].Buffer + DebugOffset); + } else { + if (DebugSize==2) { + val = *((short *)(SendQueue[i].Buffer + DebugOffset)); + } else { + if (DebugSize==4) { + val = *((int *)(SendQueue[i].Buffer + DebugOffset)); + } + } + } + sprintf(txt + strlen(txt),"%4d ",val); + + if (DebugMaxNames > 0 && val >= 0 && val < DebugMaxNames) { + sprintf(txt + strlen(txt),"%-12s %x", DebugNames[val], SendQueue[i].IsACK); + } else { + sprintf(txt + strlen(txt)," %x",SendQueue[i].IsACK); + } + } else { + sprintf(txt + strlen(txt)," %x",SendQueue[i].IsACK); + } + + Mono_Printf("%s",txt); + } else { + + /*..................................................................... + Entry isn't active; print blanks + .....................................................................*/ + Mono_Printf("____ __ _"); + } + } + + /*------------------------------------------------------------------------ + Print Receive Queue items + ------------------------------------------------------------------------*/ + for (i = 0; i < MaxReceive; i++) { + Mono_Set_Cursor (40,8 + i); + /*..................................................................... + Print an active entry + .....................................................................*/ + if (ReceiveQueue[i].IsActive) { + /*.................................................................. + Get header info + ..................................................................*/ + hdr = (CommHdr *)ReceiveQueue[i].Buffer; + hdr->MagicNumber = hdr->MagicNumber; + hdr->Code = hdr->Code; + sprintf(txt,"%4d %2d %-5s ", + hdr->PacketID, + ReceiveQueue[i].IsRead, + ConnectionClass::Command_Name(hdr->Code)); + /*.................................................................. + Decode app's ID & its name + ..................................................................*/ + if (DebugSize && (DebugOffset + DebugSize) <= ReceiveQueue[i].BufLen) { + if (DebugSize==1) { + val = *(ReceiveQueue[i].Buffer + DebugOffset); + } else { + if (DebugSize==2) { + val = *((short *)(ReceiveQueue[i].Buffer + DebugOffset)); + } else { + if (DebugSize==4) { + val = *((int *)(ReceiveQueue[i].Buffer + DebugOffset)); + } + } + } + sprintf(txt + strlen(txt),"%4d ",val); + + if (DebugMaxNames > 0 && val >= 0 && val < DebugMaxNames) { + sprintf(txt + strlen(txt),"%-12s %x", + DebugNames[val], + ReceiveQueue[i].IsACK); + } else { + sprintf(txt + strlen(txt)," %x",ReceiveQueue[i].IsACK); + } + } else { + sprintf(txt + strlen(txt)," %x",ReceiveQueue[i].IsACK); + } + + Mono_Printf("%s",txt); + } else { + + /*..................................................................... + Entry isn't active; print blanks + .....................................................................*/ + Mono_Printf("____ __ _"); + } + } + +#else + refresh = refresh; +#endif +} /* end of Mono_Debug_Print2 */ + + diff --git a/COMBUF.H b/COMBUF.H new file mode 100644 index 0000000..15b710c --- /dev/null +++ b/COMBUF.H @@ -0,0 +1,181 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\combuf.h_v 1.6 16 Oct 1995 16:46:00 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : COMBUF.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : April 1, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * * + * This class's job is to store outgoing messages & incoming messages, * + * and serves as a storage area for various flags for ACK & Retry logic. * + * * + * This class stores buffers in a non-sequenced order; it allows freeing * + * any entry, so the buffers can be kept clear, even if packets come in * + * out of order. * + * * + * The class also contains routines to maintain a cumulative response time * + * for this queue. It's up to the caller to call Add_Delay() whenever * + * it detects that an outgoing message has been ACK'd; this class adds * + * that delay into a computed average delay over the last few message * + * delays. * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef COMBUF_H +#define COMBUF_H + + +/* +********************************** Defines ********************************** +*/ +/*--------------------------------------------------------------------------- +This is one output queue entry +---------------------------------------------------------------------------*/ +typedef struct { + unsigned int IsActive : 1; // 1 = this entry is ready to be processed + unsigned int IsACK : 1; // 1 = ACK received for this packet + unsigned long FirstTime; // time this packet was first sent + unsigned long LastTime; // time this packet was last sent + unsigned long SendCount; // # of times this packet has been sent + int BufLen; // size of the packet stored in this entry + char *Buffer; // the data packet +} SendQueueType; + +/*--------------------------------------------------------------------------- +This is one input queue entry +---------------------------------------------------------------------------*/ +typedef struct { + unsigned int IsActive : 1; // 1 = this entry is ready to be processed + unsigned int IsRead : 1; // 1 = caller has read this entry + unsigned int IsACK : 1; // 1 = ACK sent for this packet + int BufLen; // size of the packet stored in this entry + char *Buffer; // the data packet +} ReceiveQueueType; + +/* +***************************** Class Declaration ***************************** +*/ +class CommBufferClass +{ + /* + ---------------------------- Public Interface ---------------------------- + */ + public: + /* + ....................... Constructor/Destructor ........................ + */ + CommBufferClass(int numsend, int numrecieve, int maxlen); + virtual ~CommBufferClass(); + void Init(void); + void Init_Send_Queue(void); + + /* + ......................... Send Queue routines ......................... + */ + int Queue_Send(void *buf, int buflen); // add to Send queue + int UnQueue_Send(void *buf, int *buflen, int index); // remove from Send queue + int Num_Send(void) {return (SendCount);} // # entries in queue + int Max_Send(void) { return (MaxSend);} // max # send queue entries + SendQueueType * Get_Send(int index); // random access to queue + unsigned long Send_Total(void) {return (SendTotal);} + + /* + ....................... Receive Queue routines ........................ + */ + int Queue_Receive(void *buf, int buflen); // add to Receive queue + int UnQueue_Receive(void *buf, int *buflen, int index); // remove from Receive queue + int Num_Receive(void) {return (ReceiveCount);} // # entries in queue + int Max_Receive(void) { return (MaxReceive); } // max # recv queue entries + ReceiveQueueType * Get_Receive(int index); // random access to queue + unsigned long Receive_Total(void) {return (ReceiveTotal);} + + /* + ....................... Response time routines ........................ + */ + void Add_Delay(unsigned long delay); // accumulates response time + unsigned long Avg_Response_Time(void); // gets mean response time + unsigned long Max_Response_Time(void); // gets max response time + void Reset_Response_Time(void); // resets computations + + /* + ........................ Debug output routines ........................ + */ + void Configure_Debug(int offset, int size, char **names, int maxnames); + void Mono_Debug_Print(int refresh = 0); + void Mono_Debug_Print2(int refresh = 0); + + /* + --------------------------- Private Interface ---------------------------- + */ + private: + /* + .......................... Limiting variables ......................... + */ + int MaxSend; // max # send queue entries + int MaxReceive; // max # receive queue entries + int MaxPacketSize; // max size of a packet, in bytes + + /* + ....................... Response time variables ....................... + */ + unsigned long DelaySum; // sum of last 4 delay times + unsigned long NumDelay; // current # delay times summed + unsigned long MeanDelay; // current average delay time + unsigned long MaxDelay; // max delay ever for this queue + + /* + ........................ Send Queue variables ......................... + */ + SendQueueType * SendQueue; // incoming packets + int SendCount; // # packets in the queue + unsigned long SendTotal; // total # added to send queue + int *SendIndex; // array of Send entry indices + + /* + ....................... Receive Queue variables ....................... + */ + ReceiveQueueType * ReceiveQueue; // outgoing packets + int ReceiveCount; // # packets in the queue + unsigned long ReceiveTotal; // total # added to receive queue + int *ReceiveIndex; // array of Receive entry indices + + /* + ......................... Debugging Variables ......................... + */ + int DebugOffset; // offset into app's packet for ID + int DebugSize; // size of app's ID + char **DebugNames; // ptr to array of app-specific names + int DebugMaxNames; // max # of names in array +}; + +#endif + +/**************************** end of combuf.h ******************************/ + diff --git a/COMPAT.H b/COMPAT.H new file mode 100644 index 0000000..4057f91 --- /dev/null +++ b/COMPAT.H @@ -0,0 +1,170 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\compat.h_v 2.19 16 Oct 1995 16:46:02 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : COMPAT.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 03/02/95 * + * * + * Last Update : March 2, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef COMPAT_H +#define COMPAT_H + + +#include "i86.h" + +#define KeyNumType int +#define KeyASCIIType int + + + +#define BuffType BufferClass +#define movmem(a,b,c) memmove(b,a,c) +#define ShapeBufferSize _ShapeBufferSize +extern "C" { + extern long ShapeBufferSize; + extern char *ShapeBuffer; +} + +/*=========================================================================*/ +/* Define some equates for the different graphic routines we will install */ +/* later. */ +/*=========================================================================*/ +#define HIDBUFF ((void *)(0xA0000)) +//#define Size_Of_Region(a, b) a*b + +/*=========================================================================*/ +/* Define some Graphic Routines which will only be fixed by these defines */ +/*=========================================================================*/ +#define Set_Font_Palette(a) Set_Font_Palette_Range(a, 0, 15) + +/* +** These are the Open_File, Read_File, and Seek_File constants. +*/ +#define READ 1 // Read access. +#define WRITE 2 // Write access. + +#ifndef SEEK_SET +#define SEEK_SET 0 // Seek from start of file. +#define SEEK_CUR 1 // Seek relative from current location. +#define SEEK_END 2 // Seek from end of file. +#endif + +#define ERROR_WINDOW 1 +#define ErrorWindow 1 + + +extern unsigned char *Palette; +extern unsigned char MDisabled; // Is mouse disabled? +extern WORD Hard_Error_Occured; + +/* +** This is the menu control structures. +*/ +typedef enum MenuIndexType { + MENUX, + MENUY, + ITEMWIDTH, + ITEMSHIGH, + MSELECTED, + NORMCOL, + HILITE, + MENUPADDING=0x1000 +} MenuIndexType; + + +#define BITSPERBYTE 8 +#define MAXSHORT 0x7fff +#define HIBITS 0x8000 +//#define MAXLONG 0x7fffffffL +#define HIBITL 0x80000000 + +#define MAXINT MAXLONG +#define HIBITI HIBITL + +#define DMAXEXP 308 +#define FMAXEXP 38 +#define DMINEXP -307 +#define FMINEXP -37 + +#define MAXDOUBLE 1.797693E+308 +#define MAXFLOAT 3.37E+38F +#define MINDOUBLE 2.225074E-308 +#define MINFLOAT 8.43E-37F + +#define DSIGNIF 53 +#define FSIGNIF 24 + +#define DMAXPOWTWO 0x3FF +#define FMAXPOWTWO 0x7F +#define DEXPLEN 11 +#define FEXPLEN 8 +#define EXPBASE 2 +#define IEEE 1 +#define LENBASE 1 +#define HIDDENBIT 1 +#define LN_MAXDOUBLE 7.0978E+2 +#define LN_MINDOUBLE -7.0840E+2 + +/* These defines handle the various names given to the same color. */ +#define DKGREEN GREEN +#define DKBLUE BLUE +#define GRAY GREY +#define DKGREY GREY +#define DKGRAY GREY +#define LTGRAY LTGREY + +#if 0 +typedef struct { + short Width; // Width of icons (pixels). + short Height; // Height of icons (pixels). + short Count; // Number of (logical) icons in this set. + short Allocated; // Was this iconset allocated? + long Size; // Size of entire iconset memory block. + unsigned char * Icons; // Offset from buffer start to icon data. + long Palettes; // Offset from buffer start to palette data. + long Remaps; // Offset from buffer start to remap index data. + long TransFlag; // Offset for transparency flag table. + unsigned char * Map; // Icon map offset (if present). +} IControl_Type; +#endif + + + +void Stuff_Key_Num ( int ); + + +extern "C"{ +extern int MouseQX; +extern int MouseQY; +} + +#endif diff --git a/COMQUEUE.CPP b/COMQUEUE.CPP new file mode 100644 index 0000000..b828394 --- /dev/null +++ b/COMQUEUE.CPP @@ -0,0 +1,1006 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\comqueue.cpv 1.10 16 Oct 1995 16:49:14 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : COMQUEUE.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : May 31, 1995 [BRR] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * CommQueueClass::CommQueueClass -- class constructor * + * CommQueueClass::~CommQueueClass -- class destructor * + * CommQueueClass::Init -- initializes this queue * + * CommQueueClass::Queue_Send -- queues a message for sending * + * CommQueueClass::UnQueue_Send -- removes next entry from send queue * + * CommQueueClass::Next_Send -- gets ptr to next entry in send queue * + * CommQueueClass::Get_Send -- gets ptr to queue entry * + * CommQueueClass::Queue_Receive -- queues a received message * + * CommQueueClass::UnQueue_Receive -- removes next entry from send queue * + * CommQueueClass::Next_Receive -- gets ptr to next entry in send queue * + * CommQueueClass::Get_Receive -- gets ptr to queue entry * + * CommQueueClass::Add_Delay -- adds a new delay value for response time * + * CommQueueClass::Avg_Response_Time -- returns average response time * + * CommQueueClass::Max_Response_Time -- returns max response time * + * CommQueueClass::Reset_Response_Time -- resets computations * + * CommQueueClass::Configure_Debug -- sets up special debug values * + * CommQueueClass::Mono_Debug_Print -- Debug output routine * + * CommQueueClass::Mono_Debug_Print2 -- Debug output; alternate format * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#ifndef DEMO + +/*************************************************************************** + * CommQueueClass::CommQueueClass -- class constructor * + * * + * INPUT: * + * numsend # queue entries for sending * + * numreceive # queue entries for receiving * + * maxlen maximum desired packet length, in bytes * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +CommQueueClass::CommQueueClass(int numsend, int numreceive, int maxlen) +{ + int i; + + /* + ----------------------------- Init variables ----------------------------- + */ + MaxSend = numsend; + MaxReceive = numreceive; + MaxPacketSize = maxlen; + + /* + ----------------------- Allocate the queue entries ----------------------- + */ + SendQueue = new SendQueueType[numsend]; + ReceiveQueue = new ReceiveQueueType[numreceive]; + + /* + ---------------------- Allocate queue entry buffers ---------------------- + */ + for (i = 0; i < MaxSend; i++) { + SendQueue[i].Buffer = new char[maxlen]; + } + + for (i = 0; i < MaxReceive; i++) { + ReceiveQueue[i].Buffer = new char[maxlen]; + } + + Init(); + +} /* end of CommQueueClass */ + + +/*************************************************************************** + * CommQueueClass::~CommQueueClass -- class destructor * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +CommQueueClass::~CommQueueClass() +{ + int i; + + /* + ------------------------ Free queue entry buffers ------------------------ + */ + for (i = 0; i < MaxSend; i++) { + delete [] SendQueue[i].Buffer; + } + + for (i = 0; i < MaxReceive; i++) { + delete [] ReceiveQueue[i].Buffer; + } + + delete [] SendQueue; + delete [] ReceiveQueue; + +} /* end of ~CommQueueClass */ + + +/*************************************************************************** + * CommQueueClass::Init -- initializes this queue * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/20/1995 BR : Created. * + *=========================================================================*/ +void CommQueueClass::Init(void) +{ + int i; + + /*------------------------------------------------------------------------ + Init data members + ------------------------------------------------------------------------*/ + SendTotal = 0L; + ReceiveTotal = 0L; + + DelaySum = 0L; + NumDelay = 0L; + MeanDelay = 0L; + MaxDelay = 0L; + + SendCount = 0; + SendNext = 0; + SendEmpty = 0; + + ReceiveCount = 0; + ReceiveNext = 0; + ReceiveEmpty = 0; + + /*------------------------------------------------------------------------ + Init the queue entries + ------------------------------------------------------------------------*/ + for (i = 0; i < MaxSend; i++) { + SendQueue[i].IsActive = 0; + SendQueue[i].IsACK = 0; + SendQueue[i].FirstTime = 0L; + SendQueue[i].LastTime = 0L; + SendQueue[i].SendCount = 0L; + ReceiveQueue[i].BufLen = 0; + } + for (i = 0; i < MaxReceive; i++) { + ReceiveQueue[i].IsActive = 0; + ReceiveQueue[i].IsRead = 0; + ReceiveQueue[i].IsACK = 0; + ReceiveQueue[i].BufLen = 0; + } + + /*------------------------------------------------------------------------ + Init debug values + ------------------------------------------------------------------------*/ + DebugOffset = 0; + DebugSize = 0; + DebugNames = NULL; + DebugMaxNames = 0; + +} /* end of Init */ + + +/*************************************************************************** + * CommQueueClass::Queue_Send -- queues a message for sending * + * * + * INPUT: * + * buf buffer containing the message * + * buflen length of 'buf' * + * * + * OUTPUT: * + * 1 = OK, 0 = no room in the queue * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int CommQueueClass::Queue_Send(void *buf, int buflen) +{ + /* + --------------------- Error if no room in the queue ---------------------- + */ + if (SendCount==MaxSend || SendQueue[SendEmpty].IsActive!=0) + return(0); + + /* + ---------------------------- Set entry flags ----------------------------- + */ + SendQueue[SendEmpty].IsActive = 1; // entry is now active + SendQueue[SendEmpty].IsACK = 0; // entry hasn't been ACK'd + SendQueue[SendEmpty].FirstTime = 0L; // filled in by Manager when sent + SendQueue[SendEmpty].LastTime = 0L; // filled in by Manager when sent + SendQueue[SendEmpty].SendCount = 0L; // filled in by Manager when sent + SendQueue[SendEmpty].BufLen = buflen; // save buffer size + + /* + ------------------------- Copy the packet data --------------------------- + */ + memcpy(SendQueue[SendEmpty].Buffer,buf,buflen); + + /* + -------------------- Increment counters & entry ptr ---------------------- + */ + SendCount++; + SendEmpty++; + if (SendEmpty==MaxSend) + SendEmpty = 0; + + SendTotal++; + + return(1); + +} /* end of Queue_Send */ + + +/*************************************************************************** + * CommQueueClass::UnQueue_Send -- removes next entry from send queue * + * * + * INPUT: * + * buf buffer to store entry's data in; if NULL, it's discarded * + * buflen filled in with length of entry retrieved * + * * + * OUTPUT: * + * 1 = OK, 0 = no entry to retreive * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int CommQueueClass::UnQueue_Send(void *buf, int *buflen) +{ + /* + --------------------- Error if no entry to retrieve ---------------------- + */ + if (SendCount==0 || SendQueue[SendNext].IsActive==0) + return(0); + + /* + ---------------------- Copy the data from the entry ---------------------- + */ + if (buf!=NULL) { + memcpy(buf,SendQueue[SendNext].Buffer,SendQueue[SendNext].BufLen); + (*buflen) = SendQueue[SendNext].BufLen; + } + + /* + ---------------------------- Set entry flags ----------------------------- + */ + SendQueue[SendNext].IsActive = 0; + SendQueue[SendNext].IsACK = 0; + SendQueue[SendNext].FirstTime = 0L; + SendQueue[SendNext].LastTime = 0L; + SendQueue[SendNext].SendCount = 0L; + SendQueue[SendNext].BufLen = 0; + SendCount--; + SendNext++; + if (SendNext==MaxSend) + SendNext = 0; + + return(1); + +} /* end of UnQueue_Send */ + + +/*************************************************************************** + * CommQueueClass::Next_Send -- gets ptr to next entry in send queue * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * ptr to entry, NULL if there is none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +SendQueueType * CommQueueClass::Next_Send(void) +{ + if (SendCount==0) { + return(NULL); + } else { + return(&SendQueue[SendNext]); + } +} + + +/*************************************************************************** + * CommQueueClass::Get_Send -- gets ptr to queue entry * + * * + * This routine gets a pointer to the indicated queue entry. The index * + * value is relative to the next-accessable queue entry; 0 = get the * + * next available queue entry, 1 = get the one behind that, etc. * + * * + * INPUT: * + * index index of entry to get (0 = 1st available) * + * * + * OUTPUT: * + * ptr to entry * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/21/1994 BR : Created. * + *=========================================================================*/ +SendQueueType * CommQueueClass::Get_Send(int index) +{ + int i; + + i = SendNext + index; + if (i >= MaxSend) + i -= MaxSend; + + if (SendQueue[i].IsActive==0) { + return(NULL); + } else { + return(&SendQueue[i]); + } +} + + +/*************************************************************************** + * CommQueueClass::Queue_Receive -- queues a received message * + * * + * INPUT: * + * buf buffer containing the message * + * buflen length of 'buf' * + * * + * OUTPUT: * + * 1 = OK, 0 = no room in the queue * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int CommQueueClass::Queue_Receive(void *buf, int buflen) +{ + /* + --------------------- Error if no room in the queue ---------------------- + */ + if (ReceiveCount==MaxReceive || ReceiveQueue[ReceiveEmpty].IsActive!=0) { + return(0); + } + + /* + ---------------------------- Set entry flags ----------------------------- + */ + ReceiveQueue[ReceiveEmpty].IsActive = 1; + ReceiveQueue[ReceiveEmpty].IsRead = 0; + ReceiveQueue[ReceiveEmpty].IsACK = 0; + ReceiveQueue[ReceiveEmpty].BufLen = buflen; + + /* + ------------------------- Copy the packet data --------------------------- + */ + if (buflen > MaxPacketLen){ + CCDebugString ("C&C95 - Error. Incoming packet too large"); + } + memcpy(ReceiveQueue[ReceiveEmpty].Buffer,buf,buflen); + + /* + -------------------- Increment counters & entry ptr ---------------------- + */ + ReceiveCount++; + ReceiveEmpty++; + if (ReceiveEmpty==MaxReceive) + ReceiveEmpty = 0; + + ReceiveTotal++; + + return(1); + +} /* end of Queue_Receive */ + + +/*************************************************************************** + * CommQueueClass::UnQueue_Receive -- removes next entry from send queue * + * * + * INPUT: * + * buf buffer to store entry's data in; if NULL, it's discarded * + * buflen filled in with length of entry retrieved * + * * + * OUTPUT: * + * 1 = OK, 0 = no entry to retreive * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int CommQueueClass::UnQueue_Receive(void *buf, int *buflen) +{ + /* + --------------------- Error if no entry to retrieve ---------------------- + */ + if (ReceiveCount==0 || ReceiveQueue[ReceiveNext].IsActive==0) { + return(0); + } + + /* + ---------------------- Copy the data from the entry ---------------------- + */ + if (buf!=NULL) { + memcpy(buf,ReceiveQueue[ReceiveNext].Buffer, + ReceiveQueue[ReceiveNext].BufLen); + (*buflen) = ReceiveQueue[ReceiveNext].BufLen; + } + + /* + ---------------------------- Set entry flags ----------------------------- + */ + ReceiveQueue[ReceiveNext].IsActive = 0; + ReceiveQueue[ReceiveNext].IsRead = 0; + ReceiveQueue[ReceiveNext].IsACK = 0; + ReceiveQueue[ReceiveNext].BufLen = 0; + ReceiveCount--; + ReceiveNext++; + if (ReceiveNext==MaxReceive) + ReceiveNext = 0; + + return(1); + +} /* end of UnQueue_Receive */ + + +/*************************************************************************** + * CommQueueClass::Next_Receive -- gets ptr to next entry in send queue * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * ptr to entry, NULL if there is none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +ReceiveQueueType * CommQueueClass::Next_Receive(void) +{ + if (ReceiveCount==0) { + return(NULL); + } else { + return(&ReceiveQueue[ReceiveNext]); + } +} + + +/*************************************************************************** + * CommQueueClass::Get_Receive -- gets ptr to queue entry * + * * + * This routine gets a pointer to the indicated queue entry. The index * + * value is relative to the next-accessable queue entry; 0 = get the * + * next available queue entry, 1 = get the one behind that, etc. * + * * + * INPUT: * + * index index of entry to get (0 = 1st available) * + * * + * OUTPUT: * + * ptr to entry * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/21/1994 BR : Created. * + *=========================================================================*/ +ReceiveQueueType * CommQueueClass::Get_Receive(int index) +{ + int i; + + i = ReceiveNext + index; + if (i >= MaxReceive) + i -= MaxReceive; + + if (ReceiveQueue[i].IsActive==0) { + return(NULL); + } else { + return(&ReceiveQueue[i]); + } +} + + +/*************************************************************************** + * CommQueueClass::Add_Delay -- adds a new delay value for response time * + * * + * This routine updates the average response time for this queue. The * + * computation is based on the average of the last 'n' delay values given, * + * It computes a running total of the last n delay values, then divides * + * that by n to compute the average. * + * * + * When the number of values given exceeds the max, the mean is subtracted * + * off the total, then the new value is added in. Thus, any single delay * + * value will have an effect on the total that approaches 0 over time, and * + * the new delay value contributes to 1/n of the mean. * + * * + * INPUT: * + * delay value to add into the response time computation * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/19/1995 BR : Created. * + *=========================================================================*/ +void CommQueueClass::Add_Delay(unsigned long delay) +{ + int roundoff = 0; + + if (NumDelay==256) { + DelaySum -= MeanDelay; + DelaySum += delay; + if ( (DelaySum & 0x00ff) > 127) + roundoff = 1; + MeanDelay = (DelaySum >> 8) + roundoff; + } else { + NumDelay++; + DelaySum += delay; + MeanDelay = DelaySum / NumDelay; + } + + if (delay > MaxDelay) { + MaxDelay = delay; + } + +} /* end of Add_Delay */ + + +/*************************************************************************** + * CommQueueClass::Avg_Response_Time -- returns average response time * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * latest computed average response time * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/19/1995 BR : Created. * + *=========================================================================*/ +unsigned long CommQueueClass::Avg_Response_Time(void) +{ + return(MeanDelay); + +} /* end of Avg_Response_Time */ + + +/*************************************************************************** + * CommQueueClass::Max_Response_Time -- returns max response time * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * latest computed average response time * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/19/1995 BR : Created. * + *=========================================================================*/ +unsigned long CommQueueClass::Max_Response_Time(void) +{ + return(MaxDelay); + +} /* end of Max_Response_Time */ + + +/*************************************************************************** + * CommQueueClass::Reset_Response_Time -- resets computations * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/19/1995 BR : Created. * + *=========================================================================*/ +void CommQueueClass::Reset_Response_Time(void) +{ + DelaySum = 0L; + NumDelay = 0L; + MeanDelay = 0L; + MaxDelay = 0L; + +} /* end of Reset_Response_Time */ + + +/*************************************************************************** + * CommQueueClass::Configure_Debug -- sets up special debug values * + * * + * Mono_Debug_Print2() can look into a packet to pull out a particular * + * ID, and can print both that ID and a string corresponding to * + * that ID. This routine configures these values so it can find * + * and decode the ID. This ID is used in addition to the normal * + * CommHeaderType values. * + * * + * INPUT: * + * offset ID's byte offset into packet * + * size size of ID, in bytes; 0 if none * + * names ptr to array of names; use ID as an index into this * + * maxnames max # in the names array; 0 if none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * Names shouldn't be longer than 12 characters. * + * * + * HISTORY: * + * 05/31/1995 BRR : Created. * + *=========================================================================*/ +void CommQueueClass::Configure_Debug(int offset, int size, char **names, + int maxnames) +{ + DebugOffset = offset; + DebugSize = size; + DebugNames = names; + DebugMaxNames = maxnames; + +} /* end of Configure_Debug */ + + +/*************************************************************************** + * Mono_Debug_Print -- Debug output routine * + * * + * This routine leaves 5 lines at the top for the caller's use. * + * * + * INPUT: * + * refresh 1 = clear screen & completely refresh * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/02/1995 BRR : Created. * + *=========================================================================*/ +void CommQueueClass::Mono_Debug_Print(int refresh) +{ +#ifdef WWLIB32_H + int i; // loop counter + static int send_col[] = {1,14,28}; // coords of send queue columns + static int receive_col[] = {40,54,68}; // coords of recv queue columns + int row,col; // current row,col for printing + int num; // max # items to print + + struct CommHdr { // this mirrors the CommHeaderType + unsigned short MagicNumber; + unsigned char Code; + unsigned long PacketID; + } *hdr; + + /*------------------------------------------------------------------------ + If few enough entries, call the verbose debug version + ------------------------------------------------------------------------*/ + if (MaxSend <= 16) { + Mono_Debug_Print2(refresh); + return; + } + + /*------------------------------------------------------------------------ + Refresh the screen + ------------------------------------------------------------------------*/ + if (refresh) { + Mono_Clear_Screen (); + Mono_Printf("ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\n"); + Mono_Printf("³ ³\n"); + Mono_Printf("³ ³\n"); + Mono_Printf("³ ³\n"); + Mono_Printf("ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´\n"); + Mono_Printf("³ Send Queue ³ Receive Queue ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ID Ct ACK ID Ct ACK ID Ct ACK³ ID Rd ACK ID Rd ACK ID Rd ACK³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ"); + } + + /*------------------------------------------------------------------------ + Print Send Queue items + ------------------------------------------------------------------------*/ + if (MaxSend <= 48) { + num = MaxSend; + } else { + num = 48; + } + col = 0; + row = 0; + for (i = 0; i < MaxSend; i++) { + Mono_Set_Cursor (send_col[col],row + 8); + if (SendQueue[i].IsActive) { + hdr = (CommHdr *)SendQueue[i].Buffer; + hdr->MagicNumber = hdr->MagicNumber; + hdr->Code = hdr->Code; + Mono_Printf ("%4d %2d %d",hdr->PacketID, SendQueue[i].SendCount, + SendQueue[i].IsACK); + } else { + Mono_Printf ("____ __ _ "); + } + + row++; + if (row > 15) { + row = 0; + col++; + } + } + + /*------------------------------------------------------------------------ + Print Receive Queue items + ------------------------------------------------------------------------*/ + if (MaxReceive <= 48) { + num = MaxSend; + } else { + num = 48; + } + col = 0; + row = 0; + for (i = 0; i < MaxReceive; i++) { + Mono_Set_Cursor (receive_col[col],row + 8); + if (ReceiveQueue[i].IsActive) { + hdr = (CommHdr *)ReceiveQueue[i].Buffer; + Mono_Printf ("%4d %d %d",hdr->PacketID, ReceiveQueue[i].IsRead, + ReceiveQueue[i].IsACK); + } else { + Mono_Printf ("____ _ _ "); + } + + row++; + if (row > 15) { + row = 0; + col++; + } + } + +#else + refresh = refresh; +#endif +} /* end of Mono_Debug_Print */ + + +/*************************************************************************** + * CommQueueClass::Mono_Debug_Print2 -- Debug output; alternate format * + * * + * This routine prints more information than the other version; it's * + * called only if the number of queue entries is small enough to support * + * this format. * + * * + * INPUT: * + * refresh 1 = clear screen & completely refresh * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/31/1995 BRR : Created. * + *=========================================================================*/ +void CommQueueClass::Mono_Debug_Print2(int refresh) +{ +#ifdef WWLIB32_H + int i; // loop counter + char txt[80]; + int val; + + struct CommHdr { // this mirrors the CommHeaderType + unsigned short MagicNumber; + unsigned char Code; + unsigned long PacketID; + } *hdr; + + /*------------------------------------------------------------------------ + Refresh the screen + ------------------------------------------------------------------------*/ + if (refresh) { + Mono_Clear_Screen (); + Mono_Printf("ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\n"); + Mono_Printf("³ ³\n"); + Mono_Printf("³ ³\n"); + Mono_Printf("³ ³\n"); + Mono_Printf("ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´\n"); + Mono_Printf("³ Send Queue ³ Receive Queue ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ID Ct Type Data Name ACK ³ ID Rd Type Data Name ACK ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("³ ³ ³\n"); + Mono_Printf("ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ"); + } + + /*------------------------------------------------------------------------ + Print Send Queue items + ------------------------------------------------------------------------*/ + for (i = 0; i < MaxSend; i++) { + Mono_Set_Cursor (1,8 + i); + /*..................................................................... + Print an active entry + .....................................................................*/ + if (SendQueue[i].IsActive) { + /*.................................................................. + Get header info + ..................................................................*/ + hdr = (CommHdr *)SendQueue[i].Buffer; + hdr->MagicNumber = hdr->MagicNumber; + hdr->Code = hdr->Code; + sprintf(txt,"%4d %2d %-5s ", + hdr->PacketID, + SendQueue[i].SendCount, + ConnectionClass::Command_Name(hdr->Code)); + + /*.................................................................. + Decode app's ID & its name + ..................................................................*/ + if (DebugSize && (DebugOffset + DebugSize) <= SendQueue[i].BufLen) { + if (DebugSize==1) { + val = *(SendQueue[i].Buffer + DebugOffset); + } else { + if (DebugSize==2) { + val = *((short *)(SendQueue[i].Buffer + DebugOffset)); + } else { + if (DebugSize==4) { + val = *((int *)(SendQueue[i].Buffer + DebugOffset)); + } + } + } + sprintf(txt + strlen(txt),"%4d ",val); + + if (DebugMaxNames && val > 0 && val < DebugMaxNames) { + sprintf(txt + strlen(txt),"%-12s %x", + DebugNames[val], + SendQueue[i].IsACK); + } else { + sprintf(txt + strlen(txt)," %x",SendQueue[i].IsACK); + } + } + } else { + + /*..................................................................... + Entry isn't active; print blanks + .....................................................................*/ + Mono_Printf("____ __ _"); + } + } + + /*------------------------------------------------------------------------ + Print Receive Queue items + ------------------------------------------------------------------------*/ + for (i = 0; i < MaxReceive; i++) { + Mono_Set_Cursor (40,8 + i); + /*..................................................................... + Print an active entry + .....................................................................*/ + if (ReceiveQueue[i].IsActive) { + /*.................................................................. + Get header info + ..................................................................*/ + hdr = (CommHdr *)ReceiveQueue[i].Buffer; + hdr->MagicNumber = hdr->MagicNumber; + hdr->Code = hdr->Code; + sprintf(txt,"%4d %2d %-5s ", + hdr->PacketID, + ReceiveQueue[i].IsRead, + ConnectionClass::Command_Name(hdr->Code)); + /*.................................................................. + Decode app's ID & its name + ..................................................................*/ + if (DebugSize && (DebugOffset + DebugSize) <= SendQueue[i].BufLen) { + if (DebugSize==1) { + val = *(ReceiveQueue[i].Buffer + DebugOffset); + } else { + if (DebugSize==2) { + val = *((short *)(ReceiveQueue[i].Buffer + DebugOffset)); + } else { + if (DebugSize==4) { + val = *((int *)(ReceiveQueue[i].Buffer + DebugOffset)); + } + } + } + sprintf(txt + strlen(txt),"%4d ",val); + + if (DebugMaxNames && val > 0 && val < DebugMaxNames) { + sprintf(txt + strlen(txt),"%-12s %x", DebugNames[val], ReceiveQueue[i].IsACK); + } else { + sprintf(txt + strlen(txt)," %x",ReceiveQueue[i].IsACK); + } + } + } else { + + /*..................................................................... + Entry isn't active; print blanks + .....................................................................*/ + Mono_Printf("____ __ _"); + } + } + +#else + refresh = refresh; +#endif +} /* end of Mono_Debug_Print2 */ + + +#endif diff --git a/COMQUEUE.H b/COMQUEUE.H new file mode 100644 index 0000000..7d406b4 --- /dev/null +++ b/COMQUEUE.H @@ -0,0 +1,193 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\comqueue.h_v 1.12 16 Oct 1995 16:45:08 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : COMQUEUE.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : April 1, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * * + * This class's job is to queue up outgoing messages & incoming messages, * + * and serves as a storage area for various flags for ACK & Retry logic. * + * It allows the application to keep track of how many messages have * + * passed through this queue, in & out, so packets can use this as a * + * unique ID. (If packets have a unique ID, the application can use this * + * to detect re-sends.) * + * * + * The queues act as FIFO buffers (First-In, First-Out). The first entry * + * placed in a queue is the first one read from it, and so on. The * + * controlling application must ensure it places the entries on the queue * + * in the order it wants to access them. * + * * + * The queue is implemented as an array of Queue Entries. Index 0 is the * + * first element placed on the queue, and is the first retrieved. Index * + * 1 is the next, and so on. When Index 0 is retrieved, the next-available* + * entry becomes Index 1. The array is circular; when the end is reached, * + * the indices wrap around to the beginning. * + * * + * The class also contains routines to maintain a cumulative response time * + * for this queue. It's up to the caller to call Add_Delay() whenever * + * it detects that an outgoing message has been ACK'd; this class adds * + * that delay into a computed average delay over the last few message * + * delays. * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef COMQUEUE_H +#define COMQUEUE_H + + + +/*--------------------------------------------------------------------------- +This is one output queue entry +---------------------------------------------------------------------------*/ +typedef struct { + unsigned int IsActive : 1; // 1 = this entry is ready to be processed + unsigned int IsACK : 1; // 1 = ACK received for this packet + unsigned long FirstTime; // time this packet was first sent + unsigned long LastTime; // time this packet was last sent + unsigned long SendCount; // # of times this packet has been sent + int BufLen; // size of the packet stored in this entry + char *Buffer; // the data packet +} SendQueueType; + +/*--------------------------------------------------------------------------- +This is one input queue entry +---------------------------------------------------------------------------*/ +typedef struct { + unsigned int IsActive : 1; // 1 = this entry is ready to be processed + unsigned int IsRead : 1; // 1 = caller has read this entry + unsigned int IsACK : 1; // 1 = ACK sent for this packet + int BufLen; // size of the packet stored in this entry + char *Buffer; // the data packet +} ReceiveQueueType; + +/* +***************************** Class Declaration ***************************** +*/ +class CommQueueClass +{ + /* + ---------------------------- Public Interface ---------------------------- + */ + public: + /* + ....................... Constructor/Destructor ........................ + */ + CommQueueClass(int numsend, int numrecieve, int maxlen); + virtual ~CommQueueClass(); + void Init(void); + + /* + ......................... Send Queue routines ......................... + */ + int Queue_Send(void *buf, int buflen); // add to Send queue + int UnQueue_Send(void *buf, int *buflen); // remove from Send queue + SendQueueType * Next_Send(void); // ptr to next avail entry + int Num_Send(void) {return (SendCount);} // # entries in queue + int Max_Send(void) { return (MaxSend);} // max # send queue entries + SendQueueType * Get_Send(int index); // random access to queue + unsigned long Send_Total(void) {return (SendTotal);} + + /* + ....................... Receive Queue routines ........................ + */ + int Queue_Receive(void *buf, int buflen); // add to Receive queue + int UnQueue_Receive(void *buf, int *buflen); // remove from Receive queue + ReceiveQueueType * Next_Receive(void); // ptr to next avail entry + int Num_Receive(void) {return (ReceiveCount);} // # entries in queue + int Max_Receive(void) { return (MaxReceive); } // max # recv queue entries + ReceiveQueueType * Get_Receive(int index); // random access to queue + unsigned long Receive_Total(void) {return (ReceiveTotal);} + + /* + ....................... Response time routines ........................ + */ + void Add_Delay(unsigned long delay); // accumulates response time + unsigned long Avg_Response_Time(void); // gets mean response time + unsigned long Max_Response_Time(void); // gets max response time + void Reset_Response_Time(void); // resets computations + + /* + ........................ Debug output routines ........................ + */ + void Configure_Debug(int offset, int size, char **names, int maxnames); + void Mono_Debug_Print(int refresh = 0); + void Mono_Debug_Print2(int refresh = 0); + + /* + --------------------------- Private Interface ---------------------------- + */ + private: + /* + .......................... Limiting variables ......................... + */ + int MaxSend; // max # send queue entries + int MaxReceive; // max # receive queue entries + int MaxPacketSize; // max size of a packet, in bytes + + /* + ....................... Response time variables ....................... + */ + unsigned long DelaySum; // sum of last 4 delay times + unsigned long NumDelay; // current # delay times summed + unsigned long MeanDelay; // current average delay time + unsigned long MaxDelay; // max delay ever for this queue + + /* + ........................ Send Queue variables ......................... + */ + SendQueueType * SendQueue; // incoming packets + int SendCount; // # packets in the queue + int SendNext; // next entry read from queue + int SendEmpty; // next empty spot in queue + unsigned long SendTotal; // total # added to send queue + + /* + ....................... Receive Queue variables ....................... + */ + ReceiveQueueType * ReceiveQueue; // outgoing packets + int ReceiveCount; // # packets in the queue + int ReceiveNext; // next entry read from queue + int ReceiveEmpty; // next empty spot in queue + unsigned long ReceiveTotal; // total # added to receive queue + + /* + ......................... Debugging Variables ......................... + */ + int DebugOffset; // offset into app's packet for ID + int DebugSize; // size of app's ID + char **DebugNames; // ptr to array of app-specific names + int DebugMaxNames; // max # of names in array +}; + +#endif + +/*************************** end of comqueue.h *****************************/ + diff --git a/CONFDLG.CPP b/CONFDLG.CPP new file mode 100644 index 0000000..9bdb6c1 --- /dev/null +++ b/CONFDLG.CPP @@ -0,0 +1,250 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\confdlg.cpv 2.17 16 Oct 1995 16:49:52 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CONFDLG.CPP * + * * + * Programmer : Maria del Mar McCready Legg * + * Joe L. Bostic * + * * + * Start Date : Jan 30, 1995 * + * * + * Last Update : Jan 30, 1995 [MML] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * ConfirmationClass::Process -- Handles all the options graphic interface. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "confdlg.h" + + +bool ConfirmationClass::Process(int text) +{ + return(Process(Text_String(text))); +} + + +/*********************************************************************************************** + * ConfirmationClass::Process -- Handles all the options graphic interface. * + * * + * This dialog uses an edit box to confirm a deletion. * + * * + * INPUT: char *string - display in edit box. * + * OUTPUT: none * + * WARNINGS: none * + * HISTORY: 12/31/1994 MML : Created. * + *=============================================================================================*/ +bool ConfirmationClass::Process(char const * string) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + enum { + NUM_OF_BUTTONS = 2 + }; + + char buffer[80*3]; + int result = true; + int width; + int bwidth, bheight; // button width and height + int height; + int selection; + bool pressed; + int curbutton; + TextButtonClass *buttons[NUM_OF_BUTTONS]; + + /* + ** Set up the window. Window x-coords are in bytes not pixels. + */ + strcpy(buffer, string); + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + Format_Window_String(buffer, 200*factor, width, height); + width += 60*factor; + height += 60*factor; + int x = (320*factor - width) / 2; + int y = (200*factor - height) / 2; + + Set_Logic_Page(SeenBuff); + + /* + ** Create Buttons. Button coords are in pixels, but are window-relative. + */ + + bheight = FontHeight + FontYSpacing + 2; + bwidth = MAX( (String_Pixel_Width( Text_String( TXT_YES ) ) + 8), 30); + + TextButtonClass yesbtn(BUTTON_YES, TXT_YES, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + x + 10*factor, y + height - (bheight + 5*factor), bwidth ); + + TextButtonClass nobtn(BUTTON_NO, TXT_NO, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + x + width - (bwidth + 10*factor), + y + height - (bheight + 5*factor), bwidth ); + + nobtn.Add_Tail(yesbtn); + + curbutton = 1; + buttons[0] = &yesbtn; + buttons[1] = &nobtn; + buttons[curbutton]->Turn_On(); + + /* + ** This causes left mouse button clicking within the confines of the dialog to + ** be ignored if it wasn't recognized by any other button or slider. + */ + GadgetClass dialog(x, y, width, height, GadgetClass::LEFTPRESS); + dialog.Add_Tail(yesbtn); + + /* + ** This causes a right click anywhere or a left click outside the dialog region + ** to be equivalent to clicking on the return to options dialog. + */ + ControlClass background(BUTTON_NO, 0, 0, SeenBuff.Get_Width(), SeenBuff.Get_Height(), GadgetClass::LEFTPRESS|GadgetClass::RIGHTPRESS); + background.Add_Tail(yesbtn); + + /* + ** Main Processing Loop. + */ + bool display = true; + bool process = true; + pressed = false; + while (process) { + + /* + ** Invoke game callback. + */ + if (GameToPlay == GAME_NORMAL) { + Call_Back(); + } else { + if (Main_Loop()) { + process = false; + result = false; + } + } + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=TRUE; + } + + /* + ** Refresh display if needed. + */ + if (display) { + Hide_Mouse(); + + /* + ** Draw the background. + */ + Dialog_Box(x, y, width, height); + Draw_Caption(TXT_CONFIRMATION, x, y, width); + Fancy_Text_Print(buffer, x+20*factor, y+30*factor, CC_GREEN, TBLACK, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW); + + /* + ** Draw the titles. + */ + yesbtn.Draw_All(); + Show_Mouse(); + display = false; + } + + /* + ** Get user input. + */ + KeyNumType input = yesbtn.Input(); + + /* + ** Process Input. + */ + switch (input) { + case (BUTTON_YES | KN_BUTTON): + selection = BUTTON_YES; + pressed = true; + break; + + case (KN_ESC): + case (BUTTON_NO | KN_BUTTON): + selection = BUTTON_NO; + pressed = true; + break; + + case (KN_LEFT): + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + + curbutton--; + if (curbutton < 0) { + curbutton = NUM_OF_BUTTONS - 1; + } + + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + break; + + case (KN_RIGHT): + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + + curbutton++; + if (curbutton > (NUM_OF_BUTTONS - 1) ) { + curbutton = 0; + } + + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + break; + + case (KN_RETURN): + selection = curbutton + BUTTON_YES; + pressed = true; + break; + + default: + break; + } + + if (pressed) { + switch (selection) { + case (BUTTON_YES): + result = true; + process = false; + break; + + case (BUTTON_NO): + result = false; + process = false; + break; + } + + pressed = false; + } + } + return(result); +} diff --git a/CONFDLG.H b/CONFDLG.H new file mode 100644 index 0000000..e66c9ba --- /dev/null +++ b/CONFDLG.H @@ -0,0 +1,56 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\confdlg.h_v 2.18 16 Oct 1995 16:46:06 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CONFDLG.H * + * * + * Programmer : Maria del Mar McCready Legg * + * Joe L. Bostic * + * * + * Start Date : Jan 30, 1995 * + * * + * Last Update : Jan 30, 1995 [MML] * + * * + *---------------------------------------------------------------------------------------------*/ + +#ifndef CONFDLG_H +#define CONFDLG_H + +#include "gadget.h" + +class ConfirmationClass +{ + private: + enum ConfirmationClassEnum { + BUTTON_YES=1, // Button number for "Options menu" + BUTTON_NO, // Button number for "Options menu" + }; + + public: + ConfirmationClass(void) { }; + bool Process(char const * string); + bool Process(int text); +}; + +#endif diff --git a/CONNECT.CPP b/CONNECT.CPP new file mode 100644 index 0000000..fc70f20 --- /dev/null +++ b/CONNECT.CPP @@ -0,0 +1,247 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\connect.cpv 1.9 16 Oct 1995 16:48:56 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CONNECT.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 20, 1994 * + * * + * Last Update : May 31, 1995 [BRR] * + *-------------------------------------------------------------------------* + * Functions: * + * ConnectionClass::ConnectionClass -- class constructor * + * ConnectionClass::~ConnectionClass -- class destructor * + * ConnectionClass::Service -- main polling routine; services packets * + * ConnectionClass::Time -- gets current time * + * ConnectionClass::Command_Name -- returns name for a packet command * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#ifdef WWLIB32_H +#else +#include +#endif + +/* +********************************* Globals *********************************** +*/ +static char *ConnectionClass::Commands[PACKET_COUNT] = { + "ADATA", + "NDATA", + "ACK" +}; + + +/*************************************************************************** + * ConnectionClass::ConnectionClass -- class constructor * + * * + * If either max_retries or timeout is -1, that parameter is ignored in * + * timeout computations. If both are -1, the connection will just keep * + * retrying forever. * + * * + * INPUT: * + * numsend desired # of entries for the send queue * + * numreceive desired # of entries for the recieve queue * + * maxlen max length of an application packet * + * magicnum the packet "magic number" for this connection * + * retry_delta the time to wait between sends * + * max_retries the max # of retries allowed for a packet * + * (-1 means retry forever, based on this parameter) * + * timeout the max amount of time before we give up on a packet * + * (-1 means retry forever, based on this parameter) * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +ConnectionClass::ConnectionClass (int maxlen, unsigned short magicnum, + unsigned long retry_delta, unsigned long max_retries, unsigned long timeout) +{ + /*------------------------------------------------------------------------ + Compute our maximum packet length + ------------------------------------------------------------------------*/ + MaxPacketLen = maxlen + sizeof(CommHeaderType); + + /*------------------------------------------------------------------------ + Assign the magic number + ------------------------------------------------------------------------*/ + MagicNum = magicnum; + + /*------------------------------------------------------------------------ + Initialize the retry time. This is the time that t2 - t1 must be greater + than before a retry will occur. + ------------------------------------------------------------------------*/ + RetryDelta = retry_delta; + + /*------------------------------------------------------------------------ + Set the maximum allowable retries. + ------------------------------------------------------------------------*/ + MaxRetries = max_retries; + + /*------------------------------------------------------------------------ + Set the timeout for this connection. + ------------------------------------------------------------------------*/ + Timeout = timeout; + + /*------------------------------------------------------------------------ + Allocate the packet staging buffer. This will be used to + ------------------------------------------------------------------------*/ + PacketBuf = new char[ MaxPacketLen ]; + +} /* end of ConnectionClass */ + + +/*************************************************************************** + * ConnectionClass::~ConnectionClass -- class destructor * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +ConnectionClass::~ConnectionClass () +{ + /*------------------------------------------------------------------------ + Free memory. + ------------------------------------------------------------------------*/ + delete [] PacketBuf; + +} /* end of ~ConnectionClass */ + + +/*************************************************************************** + * ConnectionClass::Service -- main polling routine; services packets * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = OK, 0 = error (connection is broken!) * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int ConnectionClass::Service (void) +{ + /*------------------------------------------------------------------------ + Service the Send Queue. This [re]sends packets in the Send Queue which + haven't been ACK'd yet, and if their retry timeout has expired, and + updates the FirstTime, LastTime & SendCount values in the Queue entry. + Entries that have been ACK'd should be removed. + ------------------------------------------------------------------------*/ +// if (!Service_Send_Queue()) +// return(0); + + /*------------------------------------------------------------------------ + Service the Receive Queue. This sends ACKs for packets that haven't + been ACK'd yet. Entries that the app has read, and have been ACK'd, + should be removed. + ------------------------------------------------------------------------*/ +// if (!Service_Receive_Queue()) +// return(0); + +// return(1); + + if ( Service_Send_Queue() && Service_Receive_Queue() ) { + return(1); + } else { + return(0); + } + +} /* end of Service */ + + +/*************************************************************************** + * ConnectionClass::Time -- gets current time * + * * + * INPUT: * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +unsigned long ConnectionClass::Time (void) +{ +#ifdef WWLIB32_H + return(TickCount.Time()); // Westwood Library time +#else + static struct timeb mytime; // DOS time + unsigned long msec; + + ftime(&mytime); + msec = (unsigned long)mytime.time * 1000L + (unsigned long)mytime.millitm; + return((msec / 100) * 6); +#endif + +} /* end of Time */ + + +/*************************************************************************** + * ConnectionClass::Command_Name -- returns name for given packet command * + * * + * INPUT: * + * command packet Command value to get name for * + * * + * OUTPUT: * + * ptr to command name, NULL if invalid * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/31/1995 BRR : Created. * + *=========================================================================*/ +char *ConnectionClass::Command_Name(int command) +{ + if (command >= 0 && command < PACKET_COUNT) { + return(Commands[command]); + } else { + return(NULL); + } +} + + diff --git a/CONNECT.H b/CONNECT.H new file mode 100644 index 0000000..ae9e217 --- /dev/null +++ b/CONNECT.H @@ -0,0 +1,343 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\connect.h_v 1.12 16 Oct 1995 16:46:04 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CONNECT.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : April 1, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * * + * DESCRIPTION: * + * This class represents a single "connection" with another system. It's * + * a pure virtual base class that acts as a framework for other classes. * + * * + * This class contains a CommQueueClass member, which stores received * + * & transmitted packets. The ConnectionClass has virtual functions to * + * handle adding packets to the queue, reading them from the queue, * + * a Send routine for actually sending data, and a Receive_Packet function * + * which is used to tell the connection that a new packet has come in. * + * * + * The virtual Service routine will handle all ACK & Retry logic for * + * communicating between this system & another. Thus, any class derived * + * from this class must provide the basic ACK/Retry logic. * + * * + * THE HEADER: * + * The Connection Classes prefix every packet sent with a header that's * + * local to this class. The header contains a "Magic Number" which should * + * be unique for each product, and Packet "Code", which will tell the * + * receiving end if this is DATA, or an ACK packet, and a packet ID, which * + * is a unique numerical ID for this packet (useful for detecting resends).* + * The header is stored with each packet in the send & receive Queues; * + * it's removed before it's passed back to the application, via * + * Get_Packet() * + * * + * THE CONNECTION MANAGER: * + * It is assumed that there will be a "Connection Manager" class which * + * will handle parsing incoming packets; it will then tell the connection * + * that new packets have come in, and the connection will process them in * + * whatever way it needs to for its protocol (check for resends, handle * + * ACK packets, etc). The job of the connection manager is to parse * + * incoming packets & distribute them to the connections that need to * + * store them (for multi-connection protocols). * + * * + * NOTES ON ACK/RETRY: * + * The packet's ID is used to check for re-sends. The ID is set to the * + * Queue's total Send Count; if the receiving system checks this value, * + * and it's less than that system's Receive Count, this is a resend. * + * (ie packet 0 will be the 1st packet sent, since the Send Queue's count * + * is 0 when it's sent; as soon as it's received, the Receive Count goes * + * up to 1, and an ID of 0 then means a resend.) This scheme keeps the * + * application from seeing the same packet twice. All the Connection * + * Manager has to do is mark the resent packet as non-ACK'd. Of course, * + * the Manager doesn't have to use this value at all. * + * * + * Both DATA_ACK packets and DATA_NOACK packets must go through the Send * + * Queue when "sent", so that the SendTotal value for this system * + * will still match the ReceiveTotal value for the other system; this is * + * why a NOACK packet can't just be sent immediately; it must go through * + * the queue. * + * * + * If the protocol being used already guarantees delivery of packets, * + * no ACK is required for the packets. In this case, the connection * + * class for this protocol can overload the Service routine to avoid * + * sending ACK packets, or the Connection Manager can just mark the * + * packet as ACK'd when it adds it to the Receive Queue for the connection.* + * * + * Derived classes must provide: * + * - Init a version of Init that gives the connection * + * access to any hardware-specific values it needs * + * Must chain to the parent's Init routine. * + * - Send_Packet adds the CommHeaderType header, adds the packet * + * to the out-going queue * + * - Receive_Packet processes incoming ACK packets, detects resends,* + * adds new packets to the in-coming queue * + * - Get_Packet reads the next-available packet from the * + * receive queue * + * - Send the hardware-dependent data-sending routine * + * - Service_Send_Queue services the send queue; handles re-sends, * + * detects when outgoing packets have been ACK'd; * + * cleans out the queue of old packets * + * - Service_Receive_Queue services the receive queue; handles sending * + * ACK's for new or re-sent packets; cleans out * + * the queue of old packets * + * * + * Any other routines can be overloaded as the derived class needs. * + * * + * CLASS HIERARCHY: * + * ConnectionClass * + * | * + * | * + * -------------------------------------- * + * | | * + * | | * + * SequencedConnClass NonSequencedConnClass * + * | | * + * | | * + * IPXConnClass ------------------------ * + * | | | * + * | | | * + * IPXGlobalConnClass NullModemConnClass ModemConnClass * + * * + * * + * ConnectionClass: * + * Abstract base class. * + * Provides: Queue for sent/recv'd packets * + * PacketBuf for preparing packets * + * Timeout variables * + * Service() routine * + * * + * SequencedConnClass: * + * Abstract base class * + * Provides: * "Sequenced" ACK/Retry logic, in Service_Send_Queue() & * + * Service_Receive_Queue() routines * + * * Send_Packet(): adds header to packet, adds it to Queue * + * * Receive_Packet(): adds incoming packet to receive Queue, * + * handles incoming ACK's & resends * + * * Get_Packet(): gets packet from the receive queue * + * * + * NonSequencedConnClass: * + * Abstract base class * + * Provides: * "Non-Sequenced" ACK/Retry logic, in Service_Send_Queue() * + * & Service_Receive_Queue() routines * + * * Send_Packet(): adds header to packet, adds it to Queue * + * * Receive_Packet(): adds incoming packet to receive Queue, * + * handles incoming ACK's & resends * + * * Get_Packet(): gets packet from the receive queue * + * * + * IPXConnClass: * + * Provides: * Hardware-dependent IPX interface routines, which allow * + * Service_Send_Queue() & Service_Receive_Queue() to do * + * their job * + * * Ability to associate an IPX Address, a numerical ID, and * + * a character-string Name with a connection * + * Inherits: * Sequenced ACK/Retry logic, Service routines, Queue & * + * PacketBuf, timeout variables * + * * + * IPXGlobalConnClass: * + * Special type of IPX Connection; supports receiving packets from * + * multiple systems at once, and sending packets via Broadcast or * + * to a specific address. * + * Provides: * Specialized Receive_Packet() routine, which handles * + * receiving packets from multiple systems * + * * Specialized Send_Packet() & Get_Packet() routines, * + * which pass IPX address of destination through to * + * the application, giving the application control over * + * whether the packet will be Broadcast or sent to a * + * specific destination (embeds destination address within * + * the packet itself) * + * * Specialized Send routine, which extracts the destination * + * address from the packet * + * Inherits: * Sequenced ACK/Retry logic, Service routines, Queue & * + * PacketBuf, timeout variables, IPX-specific routines * + * * + * NullModemConnClass: * + * Provides: * Hardware-dependent Serial-communication routines, which * + * allow Service_Send_Queue() & Service_Receive_Queue() to * + * do their job * + * Inherits: * Non-Sequenced ACK/Retry logic, Service routines, Queue & * + * PacketBuf, timeout variables * + * * + * ModemConnClass: * + * Provides: * Hardware-dependent Modem-communication routines, which * + * allow Service_Send_Queue() & Service_Receive_Queue() to * + * do their job * + * Inherits: * Non-Sequenced ACK/Retry logic, Service routines, Queue & * + * PacketBuf, timeout variables * + * * + * So, do ya think this header is long enough, or what? * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef CONNECTION_H +#define CONNECTION_H + +#define CONN_DEBUG 0 + +/* +********************************** Defines ********************************** +*/ +/*--------------------------------------------------------------------------- +This structure is the header prefixed to any packet sent by the application. +MagicNumber: This is a number unique to the application; it's up to the + Receive_Packet routine to check this value, to be sure we're + not getting data from some other product. This value should + be unique for each application. +Code: This will be one of the below-defined codes. +PacketID: This is a unique numerical ID for this packet. The Connection + sets this ID on all packets sent out. +---------------------------------------------------------------------------*/ +typedef struct { + unsigned short MagicNumber; + unsigned char Code; + unsigned long PacketID; +} CommHeaderType; + + +/* +***************************** Class Declaration ***************************** +*/ +class ConnectionClass +{ + /* + ---------------------------- Public Interface ---------------------------- + */ + public: + /*..................................................................... + These are the possible values for the Code field of the CommHeaderType: + .....................................................................*/ + enum ConnectionEnum { + PACKET_DATA_ACK, // this is a data packet requiring an ACK + PACKET_DATA_NOACK, // this is a data packet not requiring an ACK + PACKET_ACK, // this is an ACK for a packet + PACKET_COUNT, // for computational purposes + }; + + /*..................................................................... + Constructor/destructor. + .....................................................................*/ + ConnectionClass (int maxlen, unsigned short magicnum, + unsigned long retry_delta, unsigned long max_retries, + unsigned long timeout); + virtual ~ConnectionClass (); + + /*..................................................................... + Initialization. + .....................................................................*/ + virtual void Init (void) {}; + + /*..................................................................... + Send/Receive routines. + .....................................................................*/ + virtual int Send_Packet (void * buf, int buflen, int ack_req) = 0; + virtual int Receive_Packet (void * buf, int buflen) = 0; + virtual int Get_Packet (void * buf, int * buflen) = 0; + + /*..................................................................... + The main polling routine for the connection. Should be called as often + as possible. + .....................................................................*/ + virtual int Service (void); + + /*..................................................................... + This routine is used by the retry logic; returns the current time in + 60ths of a second. + .....................................................................*/ + static unsigned long Time (void); + + /*..................................................................... + Utility routines. + .....................................................................*/ + unsigned short Magic_Num (void) { return (MagicNum); } + unsigned long Retry_Delta (void) { return (RetryDelta); } + void Set_Retry_Delta (unsigned long delta) { RetryDelta = delta;} + unsigned long Max_Retries (void) { return (MaxRetries); } + void Set_Max_Retries (unsigned long retries) { MaxRetries = retries;} + unsigned long Time_Out (void) { return (Timeout); } + void Set_TimeOut (unsigned long t) { Timeout = t;} + unsigned long Max_Packet_Len (void) { return (MaxPacketLen); } + static char * Command_Name(int command); + + /* + -------------------------- Protected Interface --------------------------- + */ + protected: + /*..................................................................... + Routines to service the Send & Receive queues. + .....................................................................*/ + virtual int Service_Send_Queue(void) = 0; + virtual int Service_Receive_Queue(void) = 0; + + /*..................................................................... + This routine actually performs a hardware-dependent data send. It's + pure virtual, so it >must< be defined by a derived class. + .....................................................................*/ + virtual int Send(char *buf, int buflen) = 0; + + /*..................................................................... + This is the maximum packet length, including our own internal header. + .....................................................................*/ + int MaxPacketLen; + + /*..................................................................... + Packet staging area; this is where the CommHeaderType gets tacked onto + the application's packet before it's sent. + .....................................................................*/ + char *PacketBuf; + + /*..................................................................... + This is the magic number assigned to this connection. It is the first + few bytes of any transmission. + .....................................................................*/ + unsigned short MagicNum; + + /*..................................................................... + This value determines the time delay before a packet is re-sent. + .....................................................................*/ + unsigned long RetryDelta; + + /*..................................................................... + This is the maximum number of retries allowed for a packet; if this + value is exceeded, the connection is probably broken. + .....................................................................*/ + unsigned long MaxRetries; + + /*..................................................................... + This is the total timeout for this connection; if this time is exceeded + on a packet, the connection is probably broken. + .....................................................................*/ + unsigned long Timeout; + + /*..................................................................... + Names of all packet commands + .....................................................................*/ + static char *ConnectionClass::Commands[PACKET_COUNT]; +}; + +#endif + diff --git a/CONNMGR.H b/CONNMGR.H new file mode 100644 index 0000000..a913916 --- /dev/null +++ b/CONNMGR.H @@ -0,0 +1,151 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c0\vcs\code\connmgr.h_v 1.25 02 Jan 1996 11:14:54 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CONNMGR.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : April 3, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * * + * This is the Connection Manager base class. This is an abstract base * + * class that's just a shell for more functional derived classes. * + * The main job of the Connection Manager classes is to parse a "pool" of * + * incoming packets, which may be from different computers, and distribute * + * those packets to Connection Classes via their Receive_Packet function. * + * * + * This class should be the only access to the network/modem for the * + * application, so if the app needs any functions to access the * + * connections or the queue's, the derived versions of this class should * + * provide them. * + * * + * It's up to the derived class to define: * + * - Service: polling routine; should Service each connection * + * - Init: initialization; should perform hardware-dependent * + * initialization, then Init each connection; this function * + * isn't defined in this class, since the parameters will * + * be highly protocol-dependent) * + * - Send_Message:sends a packet across the connection (this function * + * isn't defined in this class, since the parameters will * + * be highly protocol-dependent) * + * - Get_Message: gets a message from the connection (this function * + * isn't defined in this class, since the parameters will * + * be highly protocol-dependent) * + * * + * If the derived class supports multiple connections, it should provide * + * functions for creating the connections, associating them with a name * + * or ID or both, destroying them, and sending data through all or any * + * connection. * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef CONNMGR_H +#define CONNMGR_H + + +/* +***************************** Class Declaration ***************************** +*/ +class ConnManClass +{ + /* + ---------------------------- Public Interface ---------------------------- + */ + public: + /*..................................................................... + Various useful enums: + .....................................................................*/ + enum IPXConnTag { + CONNECTION_NONE = -1, // value of an invalid connection ID + }; + + /*..................................................................... + Constructor/Destructor. These currently do nothing. + .....................................................................*/ + ConnManClass (void) {}; + virtual ~ConnManClass () {}; + + /*..................................................................... + The Service routine: + - Parses incoming packets, and adds them to the Receive Queue for the + Connection Class(s) for this protocol + - Invokes each connection's Service routine; returns an error if the + connection's Service routine indicates an error. + .....................................................................*/ + virtual int Service (void) = 0; + + /*..................................................................... + Sending & receiving data + .....................................................................*/ + virtual int Send_Private_Message (void *buf, int buflen, + int ack_req = 1, int conn_id = CONNECTION_NONE) = 0; + virtual int Get_Private_Message (void *buf, int *buflen, + int *conn_id) = 0; + + /*..................................................................... + Connection management + .....................................................................*/ + virtual int Num_Connections(void) = 0; + virtual int Connection_ID(int index) = 0; + virtual int Connection_Index(int id) = 0; + + /*..................................................................... + Queue utility routines + .....................................................................*/ + virtual int Global_Num_Send(void) = 0; + virtual int Global_Num_Receive(void) = 0; + virtual int Private_Num_Send(int id = CONNECTION_NONE) = 0; + virtual int Private_Num_Receive(int id = CONNECTION_NONE) = 0; + + /*..................................................................... + Timing management + .....................................................................*/ + virtual void Reset_Response_Time(void) = 0; + virtual unsigned long Response_Time(void) = 0; + virtual void Set_Timing (unsigned long retrydelta, + unsigned long maxretries, unsigned long timeout) = 0; + + /*..................................................................... + Debugging + .....................................................................*/ + virtual void Configure_Debug(int index, int type_offset, int type_size, + char **names, int maxnames) = 0; + virtual void Mono_Debug_Print(int index, int refresh) = 0; + /* + --------------------------- Private Interface ---------------------------- + */ + private: + /*..................................................................... + This abstract class contains no data members; but a derived class + will contain: + - An instance of one or more derived Connection Classes + - A buffer to store incoming packets + .....................................................................*/ +}; + +#endif diff --git a/CONQUER.CPP b/CONQUER.CPP new file mode 100644 index 0000000..d259f77 --- /dev/null +++ b/CONQUER.CPP @@ -0,0 +1,4013 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\conquer.cpv 2.18 16 Oct 1995 16:50:24 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CONQUER.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 3, 1991 * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * CC_Draw_Shape -- Custom draw shape handler. * + * Call_Back -- Main game maintenance callback routine. * + * Color_Cycle -- Handle the general palette color cycling. * + * Disk_Space_Available -- returns bytes of free disk space * + * Do_Record_Playback -- handles saving/loading map pos & current object * + * Fading_Table_Name -- Builds a theater specific fading table name. * + * Fetch_Techno_Type -- Convert type and ID into TechnoTypeClass pointer. * + * Force_CD_Available -- Ensures that specified CD is available. * + * Get_Radar_Icon -- Builds and alloc a radar icon from a shape file * + * Handle_Team -- Processes team selection command. * + * Handle_View -- Either records or restores the tactical view. * + * KN_To_Facing -- Converts a keyboard input number into a facing value. * + * Keyboard_Process -- Processes the tactical map input codes. * + * Language_Name -- Build filename for current language. * + * Main_Game -- Main game startup routine. * + * Main_Loop -- This is the main game loop (as a single loop). * + * Map_Edit_Loop -- a mini-main loop for map edit mode only * + * Message_Input -- allows inter-player message input processing * + * MixFileHandler -- Handles VQ file access. * + * Name_From_Source -- retrieves the name for the given SourceType * + * Play_Movie -- Plays a VQ movie. * + * Source_From_Name -- Converts ASCII name into SourceType. * + * Sync_Delay -- Forces the game into a 15 FPS rate. * + * Theater_From_Name -- Converts ASCII name into a theater number. * + * Trap_Object -- gets a ptr to object of given type & coord * + * Unselect_All -- Causes all selected objects to become unselected. * + * VQ_Call_Back -- Maintenance callback used for VQ movies. * + * Validate_Error -- prints an error message when an object fails validation * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "tcpip.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ccdde.h" + +#define SHAPE_TRANS 0x40 + +void *Get_Shape_Header_Data(void *ptr); + +/**************************************** +** Function prototypes for this module ** +*****************************************/ +bool Main_Loop(); +void Keyboard_Process(KeyNumType & input); +#ifndef DEMO +static void Message_Input(KeyNumType &input); +#endif +static bool Color_Cycle(void); +bool Map_Edit_Loop(void); +void Trap_Object(void); + +#ifdef CHEAT_KEYS +void Heap_Dump_Check( char *string ); +void Dump_Heap_Pointers( void ); +void Error_In_Heap_Pointers( char *string ); +#endif +static void Do_Record_Playback(void); +extern void Register_Game_Start_Time(void); +extern void Register_Game_End_Time(void); +extern void Send_Statistics_Packet(void); +extern "C" { + extern char *__nheapbeg; +} +bool InMainLoop = false; + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) +#endif + +/*********************************************************************************************** + * Main_Game -- Main game startup routine. * + * * + * This is the first official routine of the game. It handles game initialization and * + * the main game loop control. * + * * + * Initialization: * + * - Init_Game handles one-time-only inits * + * - Select_Game is responsible for initializations required for each new game played * + * (these may be different depending on whether a multiplayer game is selected, and * + * other parameters) * + * - This routine performs any un-inits required, both for each game played, and one-time * + * * + * INPUT: argc -- Number of command line arguments (including program name itself). * + * * + * argv -- Array of command line argument pointers. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/01/1994 JLB : Created. * + *=============================================================================================*/ +extern int TotalLocks; +extern bool Spawn_WChat(bool can_launch); +extern bool SpawnedFromWChat; +void Main_Game(int argc, char *argv[]) +{ + bool fade = false; // don't fade title screen the first time through + + /* + ** Perform one-time-only initializations + */ + if (!Init_Game(argc, argv)) { + return; + } + + CCDebugString ("C&C95 - Game initialisation complete.\n"); + /* + ** Game processing loop: + ** 1) Select which game to play, or whether to exit (don't fade the palette + ** on the first game selection, but fade it in on subsequent calls) + ** 2) Invoke either the main-loop routine, or the editor-loop routine, + ** until they indicate that the user wants to exit the scenario. + */ + while (Select_Game(fade)) { + ScenarioInit = 0; // Kludge. +// Theme.Queue_Song(THEME_PICK_ANOTHER); + + fade = true; + + /* + ** Make the game screen visible, clear the keyboard buffer of spurious + ** values, and then show the mouse. This PRESUMES that Select_Game() has + ** told the map to draw itself. + */ + Fade_Palette_To(GamePalette, FADE_PALETTE_MEDIUM, NULL); + Keyboard::Clear(); + + /* + ** Only show the mouse if we're not playing back a recording. + */ + if (PlaybackGame) { + Hide_Mouse(); + } else { + Show_Mouse(); + } + + SpecialDialog = SDLG_NONE; + //Start_Profiler(); + if (GameToPlay == GAME_INTERNET){ + Register_Game_Start_Time(); + GameStatisticsPacketSent = false; + PacketLater = NULL; + ConnectionLost = false; + }else{ + DDEServer.Disable(); + } + + InMainLoop = true; + +#ifdef SCENARIO_EDITOR + /* + ** Scenario-editor version of main-loop processing + */ + for (;;) { + + /* + ** Non-scenario-editor-mode: call the game's main loop + */ + if (!Debug_Map) { + TotalLocks=0; + if (Main_Loop()) { + break; + } + + if (SpecialDialog != SDLG_NONE) { + //Stop_Profiler(); + switch (SpecialDialog) { + case SDLG_SPECIAL: + Map.Help_Text(TXT_NONE); + Map.Override_Mouse_Shape(MOUSE_NORMAL, false); + Special_Dialog(); + Map.Revert_Mouse_Shape(); + SpecialDialog = SDLG_NONE; + break; + + case SDLG_OPTIONS: + Map.Help_Text(TXT_NONE); + Map.Override_Mouse_Shape(MOUSE_NORMAL, false); + Options.Process(); + Map.Revert_Mouse_Shape(); + SpecialDialog = SDLG_NONE; + break; + + case SDLG_SURRENDER: + Map.Help_Text(TXT_NONE); + Map.Override_Mouse_Shape(MOUSE_NORMAL, false); + if (Surrender_Dialog()) { + OutList.Add(EventClass(EventClass::DESTRUCT)); + } + SpecialDialog = SDLG_NONE; + Map.Revert_Mouse_Shape(); + break; + + default: + break; + } + } + } else { + + /* + ** Scenario-editor-mode: call the editor's main loop + */ + if (Map_Edit_Loop()) { + break; + } + } + } +#else + /* + ** Non-editor version of main-loop processing + */ + for (;;) { + + /* + ** Call the game's main loop + */ + TotalLocks=0; + if (Main_Loop()) { + break; + } + + /* + ** If the SpecialDialog flag is set, invoke the given special dialog. + ** This must be done outside the main loop, since the dialog will call + ** Main_Loop(), allowing the game to run in the background. + */ + if (SpecialDialog != SDLG_NONE) { + //Stop_Profiler(); + switch (SpecialDialog) { + case SDLG_SPECIAL: + Map.Help_Text(TXT_NONE); + Map.Override_Mouse_Shape(MOUSE_NORMAL, false); + Special_Dialog(); + Map.Revert_Mouse_Shape(); + SpecialDialog = SDLG_NONE; + break; + + case SDLG_OPTIONS: + Map.Help_Text(TXT_NONE); + Map.Override_Mouse_Shape(MOUSE_NORMAL, false); + Options.Process(); + Map.Revert_Mouse_Shape(); + SpecialDialog = SDLG_NONE; + break; + + case SDLG_SURRENDER: + Map.Help_Text(TXT_NONE); + Map.Override_Mouse_Shape(MOUSE_NORMAL, false); + if (Surrender_Dialog()) { + OutList.Add(EventClass(EventClass::DESTRUCT)); + } + SpecialDialog = SDLG_NONE; + Map.Revert_Mouse_Shape(); + break; + + default: + break; + } + } + } +#endif + //Stop_Profiler(); + InMainLoop = false; + + if (!GameStatisticsPacketSent && PacketLater){ + Send_Statistics_Packet(); + } + + /* + ** Scenario is done; fade palette to black + */ + Fade_Palette_To(BlackPalette, FADE_PALETTE_SLOW, NULL); + VisiblePage.Clear(); + +#ifndef DEMO + /* + ** Un-initialize whatever needs it, for each game played. + ** + ** Shut down either the modem or network; they'll get re-initialized if + ** the user selections those options again in Select_Game(). This + ** "re-boots" the modem & network code, which I currently feel is safer + ** than just letting it hang around. + ** (Skip this step if we're in playback mode; the modem or net won't have + ** been initialized in that case.) + */ + if ( (RecordGame && !SuperRecord) || PlaybackGame) { + RecordFile.Close(); + } + + if (!PlaybackGame){ + + switch (GameToPlay){ + case GAME_NULL_MODEM: + case GAME_MODEM: + Modem_Signoff(); + break; + + case GAME_IPX: + Shutdown_Network(); + break; + + case GAME_INTERNET: + //Winsock.Close(); + break; + } + } + + + /* + ** If we're playing back, the mouse will be hidden; show it. + ** Also, set all variables back to normal, to return to the main menu. + */ + if (PlaybackGame) { + Show_Mouse(); + GameToPlay = GAME_NORMAL; + PlaybackGame = 0; + } + + + /* + ** If we were spawned from WChat then dont go back to the main menu - just quit + ** + ** New: If spawned from WChat then maximise WChat and go back to the main menu after all + */ +#ifdef FORCE_WINSOCK + if (Special.IsFromWChat){ + Shutdown_Network(); // Clear up the pseudo IPX stuff + Winsock.Close(); + Special.IsFromWChat = false; + SpawnedFromWChat = false; + DDEServer.Delete_MPlayer_Game_Info(); //Make sure we dont use the same start packet twice + GameToPlay = GAME_NORMAL; //Have to do this or we will got straight to the multiplayer menu + Spawn_WChat(false); //Will switch back to Wchat. It must be there because its been poking us + //break; + } +#endif //FORCE_WINSOCK + +#endif //DEMO + + } + +#ifdef DEMO + Hide_Mouse(); + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, NULL); + Load_Title_Screen("DEMOPIC.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Fade_Palette_To(Palette, FADE_PALETTE_MEDIUM, NULL); + Clear_KeyBuffer(); + Get_Key(); + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, NULL); +// Show_Mouse(); +#else + + /* + ** Free the scenario description buffers + */ + Free_Scenario_Descriptions(); +#endif + +#ifndef NOMEMCHECK + Uninit_Game(); +#endif + +} + + +/*********************************************************************************************** + * Keyboard_Process -- Processes the tactical map input codes. * + * * + * This routine is used to process the input codes while the player * + * has the tactical map displayed. It handles all the keys that * + * are appropriate to that mode. * + * * + * INPUT: input -- Input code as returned from Input_Num(). * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/21/1992 JLB : Created. * + * 07/04/1995 JLB : Handles team and map control hotkeys. * + *=============================================================================================*/ +extern int DebugColour; +void Keyboard_Process(KeyNumType &input) +{ + ObjectClass * obj; + int index; + + /* + ** Don't do anything if there is not keyboard event. + */ + if (input == KN_NONE) { + return; + } + +#ifndef DEMO + /* + ** For network & modem, process user input for inter-player messages. + */ + Message_Input(input); +#endif + /* + ** Use WWKEY values because KN values have WWKEY_VK_BIT or'd in with them + ** and we need WWKEY_VK_BIT to still be set if it is. + */ + KeyNumType plain = input & ~(WWKEY_SHIFT_BIT|WWKEY_ALT_BIT|WWKEY_CTRL_BIT); + +#ifdef CHEAT_KEYS + + if (Debug_Flag) { + switch (input) { + case (int)KN_M|(int)KN_SHIFT_BIT: + case (int)KN_M|(int)KN_ALT_BIT: + case (int)KN_M|(int)KN_CTRL_BIT: + PlayerPtr->Credits += 10000; + break; + + default: + break; + } + } +#endif + +#ifdef VIRGIN_CHEAT_KEYS + if (Debug_Playtest && input == (KN_W|KN_ALT_BIT)) { + PlayerPtr->Blockage = false; + PlayerPtr->Flag_To_Win(); + } +#endif + +//#ifdef CHEAT_KEYS + if (/*Debug_Playtest && */input == (KN_W|KN_ALT_BIT)) { + PlayerPtr->Blockage = false; + PlayerPtr->Flag_To_Win(); + } + + if (Debug_Flag && input == KN_SLASH) { + if (GameToPlay != GAME_NORMAL) { + SpecialDialog = SDLG_SPECIAL; + input = KN_NONE; + } else { + Special_Dialog(); + } + } +//#endif + + /* + ** If the options key(s) were pressed, then bring up the options screen. + */ + if (input == KN_SPACE || input == KN_ESC) { + Map.Help_Text(TXT_NONE); // Turns off help text. + Queue_Options(); + input = KN_NONE; + //DebugColour++; + //DebugColour &=7; + } + + /* + ** Process prerecorded team selection. This will be an addative select + ** if the SHIFT key is held down. It will create the team if the + ** CTRL or ALT key is held down. + */ + int action = 0; + if (input & WWKEY_SHIFT_BIT) action = 1; + if (input & WWKEY_ALT_BIT) action = 3; + if (input & WWKEY_CTRL_BIT) action = 2; + + switch (KN_To_VK(plain)) { + + /* + ** Center the map around the currently selected objects. If no + ** objects are selected, then fall into the home case. + */ + case VK_HOME: + if (CurrentObject.Count()) { + Map.Center_Map(); + Map.Flag_To_Redraw(true); + break; + } + // Fall into next case. + + /* + ** Center the map about the construction yard or construction vehicle + ** if one is present. + */ + case VK_H: + for (index = 0; index < Units.Count(); index++) { + UnitClass * unit = Units.Ptr(index); + + if (unit && !unit->IsInLimbo && unit->House == PlayerPtr && *unit == UNIT_MCV) { + Unselect_All(); + unit->Select(); + break; + } + } + for (index = 0; index < Buildings.Count(); index++) { + BuildingClass * building = Buildings.Ptr(index); + + if (building && !building->IsInLimbo && building->House == PlayerPtr && *building == STRUCT_CONST) { + Unselect_All(); + building->Select(); + break; + } + } + Map.Center_Map(); + Map.Flag_To_Redraw(true); + break; + +#ifdef CHEAT_KEYS + /* + ** Toggle free scrolling mode. + */ + case VK_F: + Options.IsFreeScroll = (Options.IsFreeScroll == false); + break; +#endif + + /* + ** If the "N" key is pressed, then select the next object. + */ + case VK_N: + if (action) { + obj = Map.Prev_Object(CurrentObject.Count() ? CurrentObject[0] : NULL); + } else { + obj = Map.Next_Object(CurrentObject.Count() ? CurrentObject[0] : NULL); + } + if (obj) { + Unselect_All(); + obj->Select(); + Map.Center_Map(); + Map.Flag_To_Redraw(true); + } + break; + + /* + ** For multiplayer, 'R' pops up the surrender dialog. + */ + case VK_R: + if (/*GameToPlay != GAME_NORMAL &&*/ !PlayerPtr->IsDefeated) { + SpecialDialog = SDLG_SURRENDER; + input = KN_NONE; + } + break; + + /* + ** Handle making and breaking alliances. + */ + case VK_A: + if (GameToPlay != GAME_NORMAL || Debug_Flag) { + if (CurrentObject.Count() && !PlayerPtr->IsDefeated) { + if (CurrentObject[0]->Owner() != PlayerPtr->Class->House) { + OutList.Add(EventClass(EventClass::ALLY, CurrentObject[0]->Owner())); + } + } + } + break; + + /* + ** Control the remembered tactical location. + */ + case VK_F7: + case VK_F8: + case VK_F9: + case VK_F10: + if (!Debug_Map) { + Handle_View(KN_To_VK(plain) - VK_F7, action); + } + break; +#if (0) + case VK_F11: + Winsock.Set_Protocol_UDP(FALSE); + break; + + case VK_F12: + Winsock.Set_Protocol_UDP(TRUE); + break; +#endif //(0) + + + /* + ** Control the custom team select state. + */ + case VK_1: + case VK_2: + case VK_3: + case VK_4: + case VK_5: + case VK_6: + case VK_7: + case VK_8: + case VK_9: + case VK_0: + Handle_Team(KN_To_VK(plain) - VK_1, action); + break; + + /* + ** All selected units will go into idle mode. + */ + case VK_S: + if (CurrentObject.Count()) { + for (int index = 0; index < CurrentObject.Count(); index++) { + ObjectClass const * tech = CurrentObject[index]; + + if (tech && (tech->Can_Player_Move() || (tech->Can_Player_Fire() && + tech->What_Am_I() != RTTI_BUILDING))) { + OutList.Add(EventClass(EventClass::IDLE, tech->As_Target())); + } + } + } + break; + + /* + ** All selected units will attempt to scatter. + */ + case VK_X: + if (CurrentObject.Count()) { + for (int index = 0; index < CurrentObject.Count(); index++) { + ObjectClass const * tech = CurrentObject[index]; + + if (tech && tech->Can_Player_Move()) { + OutList.Add(EventClass(EventClass::SCATTER, tech->As_Target())); + } + } + } + break; + + /* + ** All selected units will attempt to go into guard area mode. + */ + case VK_G: + if (CurrentObject.Count()) { + for (int index = 0; index < CurrentObject.Count(); index++) { + ObjectClass const * tech = CurrentObject[index]; + + if (tech && tech->Can_Player_Move() && tech->Can_Player_Fire()) { + OutList.Add(EventClass(tech->As_Target(), MISSION_GUARD_AREA)); + } + } + } + break; + + default: + break; + } + +#ifdef NEVER + FacingType facing = KN_To_Facing(input); + + /* + ** Scroll the map according to the cursor key pressed. + */ + if (facing != FACING_NONE) { + Map.Scroll_Map(facing); + input = 0; + facing = FACING_NONE; + } +#endif + +#ifdef NEVER + /* + ** If the key is pressed, then select the next object. + */ + if (input == KN_TAB) { + ObjectClass * obj = Map.Next_Object(CurrentObject); + if (obj) { + if (CurrentObject) { + CurrentObject->Unselect(); + } + obj->Select(); + } + } +#endif + +#ifdef CHEAT_KEYS + if (Debug_Flag && input && (input & KN_RLSE_BIT) == 0) { + Debug_Key(input); + } +#endif +} + + +#ifndef DEMO +/*********************************************************************************************** + * Message_Input -- allows inter-player message input processing * + * * + * INPUT: * + * input key value * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * MAX_MESSAGE_LENGTH has increased over the DOS version. COMPAT_MESSAGE_LENGTH reflects * * + * the length of the DOS message and also the length of the message in the packet header. * + * To allow transmission of longer messages I split the message into COMPAT_MESSAGE_LENGTH-4 * + * sized chunks and use the extra space after the zero terminator to specify which segment * + * of the whole message this is and also to supply a crc for the string. * + * This allows message segments to arrive out of order and still be displayed correctly. * + * * + * HISTORY: * + * 05/22/1995 BRR : Created. * + * 03/26/1995 ST : Modified to break up longer messages into multiple packets * + *=============================================================================================*/ +static void Message_Input(KeyNumType &input) +{ + int rc; + char txt[MAX_MESSAGE_LENGTH+12]; + int id; + SerialPacketType *serial_packet; + int i; + int message_length; + int sent_so_far; + unsigned short magic_number; + unsigned short crc; + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + + /* + ** Check keyboard input for a request to send a message. + ** The 'to' argument for Add_Edit is prefixed to the message buffer; the + ** message buffer is big enough for the 'to' field plus MAX_MESSAGE_LENGTH. + ** To send the message, calling Get_Edit_Buf retrieves the buffer minus the + ** 'to' portion. At the other end, the buffer allocated to display the + ** message must be MAX_MESSAGE_LENGTH plus the size of "From: xxx (house)". + */ + if (input >= KN_F1 && input < (KN_F1 + MPlayerMax) && + Messages.Get_Edit_Buf()==NULL) { + memset (txt, 0, 40); + + /* + ** For a serial game, send a message on F1 or F4; set 'txt' to the + ** "Message:" string & add an editable message to the list. + */ + if (GameToPlay==GAME_NULL_MODEM + || GameToPlay==GAME_MODEM){ + //|| GameToPlay == GAME_INTERNET) { + if (input==KN_F1 || input==(KN_F1 + MPlayerMax - 1)) { + + strcpy(txt,Text_String(TXT_MESSAGE)); // "Message:" + + Messages.Add_Edit (MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, txt, 180*factor); + + Map.Flag_To_Redraw(false); + } + } else { + + /* + ** For a network game: + ** F1-F3 = "To (house):" (only allowed if we're not in ObiWan mode) + ** F4 = "To All:" + */ + if (GameToPlay == GAME_IPX || GameToPlay == GAME_INTERNET) { + if (input==(KN_F1 + MPlayerMax - 1) && Messages.Get_Edit_Buf()==NULL) { + + MessageAddress = IPXAddressClass(); // set to broadcast + strcpy(txt,Text_String(TXT_TO_ALL)); // "To All:" + + Messages.Add_Edit(MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, txt, 180*factor); + + Map.Flag_To_Redraw(false); + } else { + if (Messages.Get_Edit_Buf()==NULL) { + if ((input - KN_F1) < Ipx.Num_Connections() && !MPlayerObiWan) { + + id = Ipx.Connection_ID(input - KN_F1); + MessageAddress = (*(Ipx.Connection_Address (id))); + sprintf(txt,Text_String(TXT_TO),Ipx.Connection_Name(id)); + + Messages.Add_Edit(MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, txt, 180*factor); + + Map.Flag_To_Redraw(false); + } + } + } + } + } + } + + /* + ** Function key input is meaningless beyond this point + */ + if (input >= KN_F1 && input <= KN_F10) return; + if (input >= KN_F11 && input <= KN_F12) return; + + /* + ** Process message-system input; send the message out if RETURN is hit. + */ + rc = Messages.Input(input); + + /* + ** If a single character has been added to an edit buffer, update the display. + */ + if (rc == 1) { + Map.Flag_To_Redraw(false); + } + + /* + ** If backspace was hit, redraw the map. This assumes the map is going to + ** completely refresh all cells covered by the messages. Set DisplayClass's + ** IsToRedraw to true to tell it to re-compute the cells that it needs to + ** redraw. + */ + if (rc==2) { + Map.Flag_To_Redraw(false); + Map.DisplayClass::IsToRedraw = true; + } + + + /* + ** Send a message + */ + if (rc==3) { + /*..................................................................... + Store this message in our LastMessage buffer; the computer may send + us a version of it later. + .....................................................................*/ + if (strlen(Messages.Get_Edit_Buf())) { + strcpy(LastMessage,Messages.Get_Edit_Buf()); + } + + message_length = strlen(Messages.Get_Edit_Buf()); + + long actual_message_size; + char *the_string; + + /* + ** Serial game: fill in a SerialPacketType & send it. + ** (Note: The size of the SerialPacketType.Command must be the same as + ** the EventClass.Type!) + */ + if (GameToPlay==GAME_NULL_MODEM + || GameToPlay==GAME_MODEM){ + //|| GameToPlay==GAME_INTERNET) { + + sent_so_far = 0; + magic_number = MESSAGE_HEAD_MAGIC_NUMBER; + crc = (unsigned short) (Calculate_CRC(Messages.Get_Edit_Buf(), message_length) & 0xffff); + + while (sent_so_far < message_length){ + + serial_packet = (SerialPacketType *)NullModem.BuildBuf; + + serial_packet->Command = SERIAL_MESSAGE; + strcpy (serial_packet->Name, MPlayerName); + memcpy (serial_packet->Message, Messages.Get_Edit_Buf()+sent_so_far, COMPAT_MESSAGE_LENGTH-5); + + /* + ** Steve I's stuff for splitting message on word boundries + */ + actual_message_size = COMPAT_MESSAGE_LENGTH - 5; + + /* Start at the end of the message and find a space with 10 chars. */ + the_string = serial_packet->Message; + while ( (COMPAT_MESSAGE_LENGTH -5) -actual_message_size < 10 && + the_string[actual_message_size] != ' '){ + --actual_message_size; + } + if ( the_string[actual_message_size] == ' ' ){ + + /* Now delete the extra characters after the space (they musnt print) */ + for ( int i=0 ; i< (COMPAT_MESSAGE_LENGTH-5) - actual_message_size; i++ ){ + the_string[i + actual_message_size] = 0xff; + } + }else{ + actual_message_size = COMPAT_MESSAGE_LENGTH - 5; + } + + + *(serial_packet->Message + COMPAT_MESSAGE_LENGTH-5) = 0; + /* + ** Flag this message segment as either a message head or a message tail. + */ + *((unsigned short*)(serial_packet->Message + COMPAT_MESSAGE_LENGTH-4)) = magic_number; + *((unsigned short*)(serial_packet->Message + COMPAT_MESSAGE_LENGTH-2)) = crc; + serial_packet->ID = MPlayerLocalID; + + NullModem.Send_Message(NullModem.BuildBuf, sizeof(SerialPacketType), 1); + + magic_number++; + sent_so_far += actual_message_size; //COMPAT_MESSAGE_LENGTH-5; + } + + } else { + + /* + ** Network game: fill in a GlobalPacketType & send it. + */ + if (GameToPlay == GAME_IPX || GameToPlay == GAME_INTERNET) { + + sent_so_far = 0; + magic_number = MESSAGE_HEAD_MAGIC_NUMBER; + crc = (unsigned short) (Calculate_CRC(Messages.Get_Edit_Buf(), message_length) & 0xffff); + + while (sent_so_far < message_length){ + + GPacket.Command = NET_MESSAGE; + strcpy (GPacket.Name, MPlayerName); + memcpy (GPacket.Message.Buf, Messages.Get_Edit_Buf()+sent_so_far, COMPAT_MESSAGE_LENGTH-5); + + /* + ** Steve I's stuff for splitting message on word boundries + */ + actual_message_size = COMPAT_MESSAGE_LENGTH - 5; + + /* Start at the end of the message and find a space with 10 chars. */ + the_string = GPacket.Message.Buf; + while ( (COMPAT_MESSAGE_LENGTH -5) -actual_message_size < 10 && + the_string[actual_message_size] != ' '){ + --actual_message_size; + } + if ( the_string[actual_message_size] == ' ' ){ + + /* Now delete the extra characters after the space (they musnt print) */ + for ( int i=0 ; i< (COMPAT_MESSAGE_LENGTH-5) - actual_message_size; i++ ){ + the_string[i + actual_message_size] = 0xff; + } + }else{ + actual_message_size = COMPAT_MESSAGE_LENGTH - 5; + } + + *(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-5) = 0; + /* + ** Flag this message segment as either a message head or a message tail. + */ + *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-4)) = magic_number; + *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-2)) = crc; + + GPacket.Message.ID = MPlayerLocalID; + GPacket.Message.NameCRC = Compute_Name_CRC(MPlayerGameName); + + /* + ** If 'F4' was hit, MessageAddress will be a broadcast address; send + ** the message to every player we have a connection with. + */ + if (MessageAddress.Is_Broadcast()) { + for (i = 0; i < Ipx.Num_Connections(); i++) { + Ipx.Send_Global_Message(&GPacket, sizeof(GlobalPacketType), 1, + Ipx.Connection_Address(Ipx.Connection_ID(i))); + Ipx.Service(); + } + } else { + + /* + ** Otherwise, MessageAddress contains the exact address to send to. + ** Send to that address only. + */ + Ipx.Send_Global_Message(&GPacket, sizeof(GlobalPacketType), 1, + &MessageAddress); + Ipx.Service(); + } + + magic_number++; + sent_so_far += actual_message_size; //COMPAT_MESSAGE_LENGTH-5; + } + } + + } + + /* + ** Tell the map to completely update itself, since a message is now missing. + */ + Map.Flag_To_Redraw(true); + } +} +#endif + + +/*********************************************************************************************** + * Color_Cycle -- Handle the general palette color cycling. * + * * + * This is a maintenance routine that handles the color cycling. It should be called as * + * often as necessary to achieve smooth color cycling effects -- at least 8 times a second. * + * * + * INPUT: none * + * * + * OUTPUT: true if palette changed * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + * 06/10/1994 JLB : Uses new cycle color values. * + * 12/21/1994 JLB : Handles text fade color. * + *=============================================================================================*/ +bool Color_Cycle(void) +{ + static CountDownTimerClass _timer(BT_SYSTEM,0L); + static CountDownTimerClass _ftimer(BT_SYSTEM,0L); + static bool _up = false; + bool changed = false; + + /* + ** Process the fading white color. It is used for the radar box and other glowing + ** game interface elements. + */ + if (!_ftimer.Time()) { + _ftimer.Set(TIMER_SECOND/8); + + /* + ** Pulse the pulsing text color. + */ + #define STEP_RATE 5 + if (_up) { + GamePalette[767] += STEP_RATE; + GamePalette[766] += STEP_RATE; + GamePalette[765] += STEP_RATE; + if (GamePalette[767] > MAX_CYCLE_COLOR) { + GamePalette[767] = MAX_CYCLE_COLOR; + GamePalette[766] = MAX_CYCLE_COLOR; + GamePalette[765] = MAX_CYCLE_COLOR; + _up = false; + } + } else { + GamePalette[767] -= STEP_RATE; + GamePalette[766] -= STEP_RATE; + GamePalette[765] -= STEP_RATE; + if ((unsigned)GamePalette[767] < MIN_CYCLE_COLOR) { + GamePalette[767] = MIN_CYCLE_COLOR; + GamePalette[766] = MIN_CYCLE_COLOR; + GamePalette[765] = MIN_CYCLE_COLOR; + _up = true; + } + } + changed = true; + } + + /* + ** Process the color cycling effects -- water. + */ + if (!_timer.Time()) { + unsigned char colors[3]; + + _timer.Set(TIMER_SECOND/4); + + memmove(colors, &GamePalette[(CYCLE_COLOR_START+CYCLE_COLOR_COUNT-1)*3], sizeof(colors)); + memmove(&GamePalette[(CYCLE_COLOR_START+1)*3], &GamePalette[CYCLE_COLOR_START*3], (CYCLE_COLOR_COUNT-1)*3); + memmove(&GamePalette[CYCLE_COLOR_START*3], colors, sizeof(colors)); + changed = true; + } + + /* + ** If any of the processing functions changed the palette, then this palette must be + ** passed to the system. + */ + if (changed) { + Wait_Vert_Blank(); + Set_Palette(GamePalette); + return (true); + } + return (false); +} + + +/*********************************************************************************************** + * Call_Back -- Main game maintenance callback routine. * + * * + * This routine handles all the "real time" processing that needs to * + * occur. This includes palette fading and sound updating. It needs * + * to be called as often as possible. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + *=============================================================================================*/ +void Call_Back(void) +{ +#ifndef DEMO + int i; + int id; + int color; + unsigned short magic_number; + unsigned short crc; +#endif + + /* + ** Score maintenance + */ + if (SampleType) { + Theme.AI(); + Speak_AI(); + } + +#ifndef DEMO + /* + ** Network maintenance + */ + if (GameToPlay == GAME_IPX || GameToPlay == GAME_INTERNET) { + + Ipx.Service(); + + /* + ** Read packets only if the game is "closed", so we don't steal global + ** messages from the connection dialogs. + */ + if (!NetOpen) { + if (Ipx.Get_Global_Message (&GPacket, &GPacketlen, &GAddress, &GProductID)) { + if (GProductID == IPXGlobalConnClass::COMMAND_AND_CONQUER) { + + /* + ** If this is another player signing off, remove the connection & + ** mark that player's house as non-human, so the computer will take + ** it over. + */ + if (GPacket.Command == NET_SIGN_OFF) { + for (i = 0; i < Ipx.Num_Connections(); i++) { + + id = Ipx.Connection_ID(i); + + if (!strcmp (GPacket.Name, Ipx.Connection_Name(id) ) && + GAddress == (*Ipx.Connection_Address(id))) { + + CCDebugString ("C&C95 = Destroying connection due to sign off\n"); + Destroy_Connection (id,0); + } + } + } else { + + /* + ** Process a message from another user. + */ + if (GPacket.Command == NET_MESSAGE) { + bool msg_ok = false; + char txt[80]; + + /* + ** If NetProtect is set, make sure this message came from within + ** this game. + */ + if (!NetProtect) { + msg_ok = true; + } else { + if (GPacket.Message.NameCRC == Compute_Name_CRC(MPlayerGameName)) { + msg_ok = true; + } else { + msg_ok = false; + } + } + + if (msg_ok) { + sprintf(txt,Text_String (TXT_FROM), GPacket.Name, GPacket.Message.Buf); + magic_number = *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-4)); + crc = *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-2)); + color = MPlayerID_To_ColorIndex(GPacket.Message.ID); + Messages.Add_Message(txt, MPlayerTColors[color], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, magic_number, crc); + + /* + ** Tell the map to do a partial update (just to force the messages + ** to redraw). + */ + Map.Flag_To_Redraw(false); + + /* + ** Save this message in our last-message buffer + */ + if (strlen(GPacket.Message.Buf)) { + strcpy(LastMessage, GPacket.Message.Buf); + } + } + } else { + Process_Global_Packet(&GPacket, &GAddress); + } + } + } + } + } + } + + /* + ** Modem and Null Modem maintenance + */ + if (GameToPlay == GAME_NULL_MODEM + || ((GameToPlay == GAME_MODEM) && ModemService)){ + //|| GameToPlay == GAME_INTERNET) { + NullModem.Service(); + } +#endif +} + + +/*********************************************************************************************** + * Language_Name -- Build filename for current language. * + * * + * This routine attaches a language specific suffix to the base * + * filename provided. Typical use of this is when loading language * + * specific files at game initialization time. * + * * + * INPUT: basename -- Base name to append language specific * + * extension to. * + * * + * OUTPUT: Returns with pointer to completed filename. * + * * + * WARNINGS: The return pointer value is valid only until the next time * + * this routine is called. * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + *=============================================================================================*/ +char const *Language_Name(char const *basename) +{ + static char _fullname[_MAX_FNAME+_MAX_EXT]; + + if (!basename) return(NULL); + + sprintf(_fullname, "%s.ENG", basename); + return(_fullname); +} + + +/*********************************************************************************************** + * Source_From_Name -- Converts ASCII name into SourceType. * + * * + * This routine is used to convert an ASCII name representing a * + * SourceType into the actual SourceType value. Typically, this is * + * used when processing the scenario INI file. * + * * + * INPUT: name -- The ASCII source name to process. * + * * + * OUTPUT: Returns with the SourceType represented by the name * + * specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/17/1994 JLB : Created. * + *=============================================================================================*/ +SourceType Source_From_Name(char const *name) +{ + if (name) { + for (SourceType source = SOURCE_FIRST; source < SOURCE_COUNT; source++) { + if (stricmp(SourceName[source], name) == 0) { + return(source); + } + } + } + return(SOURCE_NONE); +} + + +/*********************************************************************************************** + * Name_From_Source -- retrieves the name for the given SourceType * + * * + * INPUT: * + * source SourceType to get the name for * + * * + * OUTPUT: * + * name of SourceType * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/15/1994 BR : Created. * + *=============================================================================================*/ +char const *Name_From_Source(SourceType source) +{ + if ((unsigned)source < SOURCE_COUNT) { + return(SourceName[source]); + } + return("None"); +} + + +/*********************************************************************************************** + * Theater_From_Name -- Converts ASCII name into a theater number. * + * * + * This routine converts an ASCII representation of a theater and converts it into a * + * matching theater number. If no match was found, then THEATER_NONE is returned. * + * * + * INPUT: name -- Pointer to ASCII name to convert. * + * * + * OUTPUT: Returns with the name converted into a theater number. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/01/1994 JLB : Created. * + *=============================================================================================*/ +TheaterType Theater_From_Name(char const *name) +{ + TheaterType index; + + if (name) { + for (index = THEATER_FIRST; index < THEATER_COUNT; index++) { + if (stricmp(name, Theaters[index].Name) == 0) { + return(index); + } + } + } + return(THEATER_NONE); +} + + +/*********************************************************************************************** + * KN_To_Facing -- Converts a keyboard input number into a facing value. * + * * + * This routine determine which compass direction is represented by the keyboard value * + * provided. It is used for map scrolling and other directional control operations from * + * the keyboard. * + * * + * INPUT: input -- The KN number to convert. * + * * + * OUTPUT: Returns with the facing type that the keyboard number represents. If it could * + * not be translated, then FACING_NONE is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +FacingType KN_To_Facing(int input) +{ + input &= ~(KN_ALT_BIT|KN_SHIFT_BIT|KN_CTRL_BIT); + switch (input) { + case KN_LEFT: + return(FACING_W); + + case KN_RIGHT: + return(FACING_E); + + case KN_UP: + return(FACING_N); + + case KN_DOWN: + return(FACING_S); + + case KN_UPLEFT: + return(FACING_NW); + + case KN_UPRIGHT: + return(FACING_NE); + + case KN_DOWNLEFT: + return(FACING_SW); + + case KN_DOWNRIGHT: + return(FACING_SE); + } + return(FACING_NONE); +} + + +/*********************************************************************************************** + * Sync_Delay -- Forces the game into a 15 FPS rate. * + * * + * This routine will wait until the timer for the current frame has expired before * + * returning. It is called at the end of every game loop in order to force the game loop * + * to run at a fixed rate. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Will delay for up to 1/15 of a second. * + * * + * HISTORY: * + * 01/04/1995 JLB : Created. * + * 03/06/1995 JLB : Fixed. * + *=============================================================================================*/ +static void Sync_Delay(void) +{ + /* + ** Delay one tick and keep a record that one tick was "wasted" here. + ** This accumulates into a running histogram of performance. + */ + SpareTicks += FrameTimer.Time(); + while (FrameTimer.Time()) { + Color_Cycle(); + Call_Back(); + + if (SpecialDialog == SDLG_NONE) { + WWMouse->Erase_Mouse(&HidPage, TRUE); + KeyNumType input = KN_NONE; + int x, y; + WWMouse->Erase_Mouse(&HidPage, TRUE); + Map.Input(input, x, y); + if (input) { + Keyboard_Process(input); + } + Map.Render(); + } + } + Color_Cycle(); + Call_Back(); +} + + +/*********************************************************************************************** + * Main_Loop -- This is the main game loop (as a single loop). * + * * + * This function will perform one game loop. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Should the game end? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/01/1994 JLB : Created. * + *=============================================================================================*/ +extern void Check_For_Focus_Loss(void); +void Reallocate_Big_Shape_Buffer(void); + + +bool Main_Loop() +{ + KeyNumType input; // Player input. + int x; + int y; + int framedelay; + +// InMainLoop = true; + + /* + ** I think I'm gonna cry if this makes it work + */ + if (Get_Mouse_State())Show_Mouse(); + + /* + ** Call the focus loss handler + */ + Check_For_Focus_Loss(); + + /* + ** Allocate extra memory for uncompressed shapes as needed + */ + Reallocate_Big_Shape_Buffer(); + + /* + ** Sync-bug trapping code + */ + if (Frame >= TrapFrame) { + Trap_Object(); + } + + // + // Initialize our AI processing timer + // + ProcessTimer.Set(0, true); + + +#if 1 + if (TrapCheckHeap) { + Debug_Trap_Check_Heap = true; + } +#endif + +#ifdef CHEAT_KEYS + Heap_Dump_Check( "After Trap" ); + + /* + ** Update the running status debug display. + */ + Self_Regulate(); +#endif + + /* + ** If there is no theme playing, but it looks like one is required, then start one + ** playing. This is usually the symptom of there being no transition score. + */ + if (SampleType && Theme.What_Is_Playing() == THEME_NONE) { + Theme.Queue_Song(THEME_PICK_ANOTHER); + } + + /* + ** Setup the timer so that the Main_Loop function processes at the correct rate. + */ + if (GameToPlay != GAME_NORMAL && CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { + framedelay = 60 / DesiredFrameRate; + FrameTimer.Set(framedelay); + } else { + FrameTimer.Set(Options.GameSpeed); + } + + /* + ** Update the display, unless we're inside a dialog. + */ + if (!PlaybackGame) { + if (SpecialDialog == SDLG_NONE && GameInFocus) { + + WWMouse->Erase_Mouse(&HidPage, TRUE); + Map.Input(input, x, y); + if (input) { + Keyboard_Process(input); + } +// HidPage.Lock(); + Map.Render(); +// HidPage.Unlock(); + } + } + + /* + ** Save map's position & selected objects, if we're recording the game. + */ + if (RecordGame || PlaybackGame) { + Do_Record_Playback(); + } + + /* + ** Sort the map's ground layer by y-coordinate value. This is done + ** outside the IsToRedraw check, for the purposes of game sync'ing + ** between machines; this way, all machines will sort the Map's + ** layer in the same way, and any processing done that's based on + ** the order of this layer will sync on different machines. + */ + Map.Layer[LAYER_GROUND].Sort(); + +// Heap_Dump_Check( "Before Logic.AI" ); + + /* + ** AI logic operations are performed here. + */ + Logic.AI(); + +// Heap_Dump_Check( "After Logic.AI" ); + + /* + ** Manage the inter-player message list. If Manage() returns true, it means + ** a message has expired & been removed, and the entire map must be updated. + */ + if (Messages.Manage()) { + HiddenPage.Clear(); + Map.Flag_To_Redraw(true); + } + + // + // Measure how long it took to process the AI + // + ProcessTicks += ProcessTimer.Time(); + ProcessFrames++; + +// Heap_Dump_Check( "Before Queue_AI" ); + + /* + ** Process all commands that are ready to be processed. + */ + Queue_AI(); + +// Heap_Dump_Check( "After Queue_AI" ); + + /* + ** Keep track of elapsed time in the game. + */ + Score.ElapsedTime += TIMER_SECOND / TICKS_PER_SECOND; + + Call_Back(); + +// Heap_Dump_Check( "After Call_Back" ); + + /* + ** Perform any win/lose code as indicated by the global control flags. + */ + if (EndCountDown) EndCountDown--; + + /* + ** Check for player wins or loses according to global event flag. + */ + + + + if (PlayerWins) { + + if (GameToPlay == GAME_INTERNET && !GameStatisticsPacketSent){ + Register_Game_End_Time(); + Send_Statistics_Packet(); + } + + WWMouse->Erase_Mouse(&HidPage, TRUE); + PlayerLoses = false; + PlayerWins = false; + PlayerRestarts = false; + Map.Help_Text(TXT_NONE); + Do_Win(); + } + if (PlayerLoses) { + + if (GameToPlay == GAME_INTERNET && !GameStatisticsPacketSent){ + Register_Game_End_Time(); + Send_Statistics_Packet(); + } + + WWMouse->Erase_Mouse(&HidPage, TRUE); + PlayerWins = false; + PlayerLoses = false; + PlayerRestarts = false; + Map.Help_Text(TXT_NONE); + Do_Lose(); + } + if (PlayerRestarts) { + WWMouse->Erase_Mouse(&HidPage, TRUE); + PlayerWins = false; + PlayerLoses = false; + PlayerRestarts = false; + Map.Help_Text(TXT_NONE); + Do_Restart(); + } + + /* + ** The frame logic has been completed. Increment the frame + ** counter. + */ + Frame++; + + /* + ** Very rarely, the human players will get a message from the computer. + */ + if (GameToPlay != GAME_NORMAL && MPlayerGhosts && IRandom(0,10000) == 1) { + Computer_Message(); + } + + /* + ** Is there a memory trasher altering the map?? + */ + if (Debug_Check_Map) { + if (!Map.Validate()) { +#ifdef GERMAN + if (CCMessageBox().Process ("Kartenfehler!","Halt","Weiter")==0) +#else +#ifdef FRENCH + if (CCMessageBox().Process ("Erreur de carte!","Stop","Continuer")==0) +#else + if (CCMessageBox().Process ("Map Error!","Stop","Continue")==0) +#endif +#endif + GameActive = false; + Map.Validate(); // give debugger a chance to catch it + } + } + + Sync_Delay(); +// InMainLoop = false; + return(!GameActive); +} + + +#ifdef SCENARIO_EDITOR +/*************************************************************************** + * Map_Edit_Loop -- a mini-main loop for map edit mode only * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 10/19/1994 BR : Created. * + *=========================================================================*/ +bool Map_Edit_Loop(void) +{ + /* + ** Redraw the map. + */ + Map.Render(); + + /* + ** Get user input (keys, mouse clicks). + */ + KeyNumType input; + + int x; + int y; + Map.Input(input, x, y); + + /* + ** Process keypress. + */ + if (input) { + Keyboard_Process(input); + } + + Call_Back(); // maintains Theme.AI() for music + Color_Cycle(); + + return(!GameActive); +} + + +/*************************************************************************** + * Go_Editor -- Enables/disables the map editor * + * * + * INPUT: * + * flag true = go into editor mode; false = go into game mode * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 10/19/1994 BR : Created. * + *=========================================================================*/ +void Go_Editor(bool flag) +{ + /* + ** Go into Scenario Editor mode + */ + if (flag) { + Debug_Map = true; + Debug_Unshroud = true; + + /* + ** Un-select any selected objects + */ + Unselect_All(); + + /* + ** Turn off the sidebar if it's on + */ + Map.Activate(0); + + /* + ** Reset the map's Button list for the new mode + */ + Map.Init_IO(); + + /* + ** Force a complete redraw of the screen + */ + HiddenPage.Clear(); + Map.Flag_To_Redraw(true); + Map.Render(); + + } else { + + /* + ** Go into normal game mode + */ + Debug_Map = false; + Debug_Unshroud = false; + + /* + ** Un-select any selected objects + */ + Unselect_All(); + + /* + ** Reset the map's Button list for the new mode + */ + Map.Init_IO(); + + /* + ** Force a complete redraw of the screen + */ + HiddenPage.Clear(); + Map.Flag_To_Redraw(true); + Map.Render(); + } +} + +#endif + +#if (0) +#define VQ_THREAD_BUFFER_SIZE 1024*1024 +#define VQ_THREAD_BUFFER_CHUNK VQ_THREAD_BUFFER_SIZE/4 +unsigned char *VQThreadBuffer = NULL; +volatile bool ThreadReading = false; +unsigned long VQThreadBlockHead; +unsigned long VQThreadBlockTail; +unsigned long VQBytesLeft; +unsigned long VQBytesRead; + + +void Init_VQ_Threading(CCFileClass *file) +{ + if (!VQThreadBuffer){ + VQThreadBuffer = new unsigned char [VQ_THREAD_BUFFER_SIZE]; + } + Force_VM_Page_In(VQThreadBuffer, VQ_THREAD_BUFFER_SIZE); + VQThreadBlockHead = 0; + VQThreadBlockTail = 0; + VQBytesRead = 0; + VQBytesLeft = file->Size(); +} + + +void Cleanup_VQ_Threading(void) +{ + while (ThreadReading){} + if (VQThreadBuffer){ + delete VQThreadBuffer; + VQThreadBuffer = NULL; + } +} + +unsigned long __stdcall Thread_Read(void *file) +{ + int bytes_to_read; + int left_to_read; + int read_this_time; + unsigned long head; + int sleep_time; + + CCFileClass *ccfile = (CCFileClass*)file; + + bytes_to_read = MIN (VQBytesLeft, VQ_THREAD_BUFFER_CHUNK); + + if (!bytes_to_read){ + ThreadReading = false; + return(0); + } + + left_to_read = bytes_to_read; + + while (left_to_read){ + read_this_time = MIN(8*1024, left_to_read); + //if (read_this_time & 3){ + ccfile->Read(VQThreadBuffer+VQThreadBlockHead, read_this_time); + //}else{ + // ccfile->Read(VQThreadBuffer+VQThreadBlockHead, read_this_time/4); + // ccfile->Read(VQThreadBuffer+VQThreadBlockHead+read_this_time/4, read_this_time/4); + // ccfile->Read(VQThreadBuffer+VQThreadBlockHead+(read_this_time/4)*2, read_this_time/4); + // ccfile->Read(VQThreadBuffer+VQThreadBlockHead+(read_this_time/4)*3, read_this_time/4); + //} + VQThreadBlockHead += read_this_time; + left_to_read -= read_this_time; + + head = VQThreadBlockHead; + if (head VQThreadBlockTail){ + bytes_to_read = MIN(bytes, VQThreadBlockHead-VQThreadBlockTail); + + }else{ + bytes_to_read = MIN(bytes, VQThreadBlockHead+VQ_THREAD_BUFFER_SIZE - VQThreadBlockTail); + } + + }while(ThreadReading && bytes_to_read VQ_THREAD_BUFFER_SIZE){ + + int first_chunk = VQ_THREAD_BUFFER_SIZE - VQThreadBlockTail; + int second_chunk = bytes_to_read - first_chunk; + + memcpy (buffer, VQThreadBuffer + VQThreadBlockTail, first_chunk); + memcpy ((unsigned char*)buffer + first_chunk, VQThreadBuffer, second_chunk); + }else{ + memcpy (buffer, VQThreadBuffer + VQThreadBlockTail, bytes_to_read); + } + + VQThreadBlockTail += bytes_to_read; + VQThreadBlockTail &= VQ_THREAD_BUFFER_SIZE - 1; + + unsigned long head = VQThreadBlockHead; + if (headVQAio; + + /* + ** Perform the action specified by the stream command. + */ + switch (action) { + + /* + ** VQACMD_READ means read NBytes from the stream and place it in the + ** memory pointed to by Buffer. + ** + ** Any error code returned will be remapped by VQA library into + ** VQAERR_READ. + */ + case VQACMD_READ: + error = VQ_Thread_Read (file, buffer, nbytes); + //error = (file->Read(buffer, (unsigned short)nbytes) != (unsigned short)nbytes); + if (error == nbytes) error = 0; + break; + + /* + ** VQACMD_WRITE is analogous to VQACMD_READ. + ** + ** Writing is not allowed to the VQA file, VQA library will remap the + ** error into VQAERR_WRITE. + */ + case VQACMD_WRITE: + error = 1; + break; + + /* + ** VQACMD_SEEK asks that you perform a seek relative to the current + ** position. NBytes is a signed number, indicating seek direction + ** (positive for forward, negative for backward). Buffer has no meaning + ** here. + ** + ** Any error code returned will be remapped by VQA library into + ** VQAERR_SEEK. + */ + case VQACMD_SEEK: + //error = (file->Seek(nbytes, SEEK_CUR) == -1); + VQ_Thread_Seek(nbytes); + error = 0; + break; + + /* + ** VQACMD_OPEN asks that you open your stream for access. + */ + case VQACMD_OPEN: + file = new CCFileClass((char *)buffer); + + if (file != NULL && file->Is_Available()) { + error = file->Open((char *)buffer, READ); + + if (error != -1) { + vqa->VQAio = (unsigned long)file; + error = 0; + //file->Set_Buffer_Size(8*1024); + } else { + delete file; + file = 0; + error = 1; + } + } else { + error = 1; + } + + if (error != -1){ + Init_VQ_Threading(file); + Read_VQ_Thread_Block(file); + CountDownTimerClass timer; + timer.Set(60); + while (ThreadReading || timer.Time()){} + } + break; + + case VQACMD_CLOSE: + Cleanup_VQ_Threading(); + file->Close(); + delete file; + file = 0; + vqa->VQAio = 0; + error = 0; + break; + + /* + ** VQACMD_INIT means to prepare your stream for reading. This is used for + ** certain streams that can't be read immediately upon opening, and need + ** further preparation. This operation is allowed to fail; the error code + ** will be returned directly to the client. + */ + case VQACMD_INIT: + + /* + ** IFFCMD_CLEANUP means to terminate the transaction with the associated + ** stream. This is used for streams that can't simply be closed. This + ** operation is not allowed to fail; any error returned will be ignored. + */ + case VQACMD_CLEANUP: + error = 0; + break; + } + + return(error); +} +#endif //(0) +//#if (0) +/*********************************************************************************************** + * MixFileHandler -- Handles VQ file access. * + * * + * This routine is called from the VQ player when it needs to access the source file. By * + * using this routine it is possible to virtualize the file system. * + * * + * INPUT: vqa -- Pointer to the VQA handle for this animation. * + * * + * action-- The requested action to perform. * + * * + * buffer-- Optional buffer pointer as needed by the type of action. * + * * + * nbytes-- The number of bytes (if needed) for this operation. * + * * + * OUTPUT: Returns a value consistent with the action requested. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/04/1995 JLB : Created. * + *=============================================================================================*/ +long MixFileHandler(VQAHandle *vqa, long action, void *buffer, long nbytes) +{ + CCFileClass *file; + long error; + + file = (CCFileClass *)vqa->VQAio; + + /* + ** Perform the action specified by the stream command. + */ + switch (action) { + + /* + ** VQACMD_READ means read NBytes from the stream and place it in the + ** memory pointed to by Buffer. + ** + ** Any error code returned will be remapped by VQA library into + ** VQAERR_READ. + */ + case VQACMD_READ: + error = (file->Read(buffer, (unsigned short)nbytes) != (unsigned short)nbytes); + break; + + /* + ** VQACMD_WRITE is analogous to VQACMD_READ. + ** + ** Writing is not allowed to the VQA file, VQA library will remap the + ** error into VQAERR_WRITE. + */ + case VQACMD_WRITE: + error = 1; + break; + + /* + ** VQACMD_SEEK asks that you perform a seek relative to the current + ** position. NBytes is a signed number, indicating seek direction + ** (positive for forward, negative for backward). Buffer has no meaning + ** here. + ** + ** Any error code returned will be remapped by VQA library into + ** VQAERR_SEEK. + */ + case VQACMD_SEEK: + error = (file->Seek(nbytes, SEEK_CUR) == -1); + break; + + /* + ** VQACMD_OPEN asks that you open your stream for access. + */ + case VQACMD_OPEN: + file = new CCFileClass((char *)buffer); + + if (file != NULL && file->Is_Available()) { + error = file->Open((char *)buffer, READ); + + if (error != -1) { + vqa->VQAio = (unsigned long)file; + error = 0; + file->Set_Buffer_Size(8*1024); + } else { + delete file; + file = 0; + error = 1; + } + } else { + error = 1; + } + break; + + case VQACMD_CLOSE: + file->Close(); + delete file; + file = 0; + vqa->VQAio = 0; + error = 0; + break; + + /* + ** VQACMD_INIT means to prepare your stream for reading. This is used for + ** certain streams that can't be read immediately upon opening, and need + ** further preparation. This operation is allowed to fail; the error code + ** will be returned directly to the client. + */ + case VQACMD_INIT: + + /* + ** IFFCMD_CLEANUP means to terminate the transaction with the associated + ** stream. This is used for streams that can't simply be closed. This + ** operation is not allowed to fail; any error returned will be ignored. + */ + case VQACMD_CLEANUP: + error = 0; + break; + } + + return(error); +} + +//#endif //(0) + + +void Rebuild_Interpolated_Palette(unsigned char *interpal) +{ + for (int y=0 ; y<255 ; y++){ + for (int x=y+1 ; x<256 ; x++){ + *(interpal + (y*256+x)) = *(interpal + (x*256+y)); + } + } +} + + +unsigned char *InterpolatedPalettes[100]; +BOOL PalettesRead; +unsigned PaletteCounter; + + +int Load_Interpolated_Palettes(char const *filename, BOOL add) +{ + int num_palettes=0; + int i; + int start_palette; + + PalettesRead = FALSE; + CCFileClass file(filename); + +// RawFileClass *palette_file; + + if (!add){ + for (i=0 ; iUnselect(); + } +} + + +/*********************************************************************************************** + * Fading_Table_Name -- Builds a theater specific fading table name. * + * * + * This routine builds a standard fading table name. This name is dependant on the theater * + * being played, since each theater has its own palette. * + * * + * INPUT: base -- The base name of this fading table. The base name can be no longer than * + * seven characters. * + * * + * theater -- The theater that this fading table is specific to. * + * * + * OUTPUT: Returns with a pointer to the constructed fading table filename. This pointer is * + * valid until this function is called again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +char const * Fading_Table_Name(char const * base, TheaterType theater) +{ + static char _buffer[_MAX_FNAME+_MAX_EXT]; + char root[_MAX_FNAME]; + + sprintf(root, "%1.1s%s", Theaters[theater].Root, base); + _makepath(_buffer, NULL, NULL, root, ".MRF"); + return(_buffer); +} + + +/*********************************************************************************************** + * Get_Radar_Icon -- Builds and alloc a radar icon from a shape file * + * * + * INPUT: void const * shapefile - pointer to a key framed shapefile * + * int shapenum - shape to extract from shapefile * + * * + * OUTPUT: void const * - 3/3 icon set of shape from file * + * * + * HISTORY: * + * 04/12/1995 PWG : Created. * + * 05/10/1995 JLB : Handles a null shapefile pointer. * + *=============================================================================================*/ +void const * Get_Radar_Icon(void const * shapefile, int shapenum, int frames, int zoomfactor) +{ + static int _offx[]={ 0, 0, -1, 1, 0, -1, 1, -1, 1}; + static int _offy[]={ 0, 0, -1, 1, 0, -1, 1, -1, 1}; + int lp,framelp; + char pixel; + + char *retval = NULL; + char *buffer = NULL; + void *ptr; + + + /* + ** If there is no shape file, then there can be no radar icon imagery. + */ + if (!shapefile) return(NULL); + + /* + ** Get the pixel width and height of the frame we built. This will + ** be used to extract icons and build pixels. + */ + int pixel_width = Get_Build_Frame_Width( shapefile ); + int pixel_height = Get_Build_Frame_Height( shapefile ); + + /* + ** Find the width and height in icons, adjust these by half an + ** icon because the artists may be sloppy and miss the edge of an + ** icon one way or the other. + */ + int icon_width = (pixel_width + 12) / 24; + int icon_height = (pixel_height + 12) / 24; + + /* + ** If we have been told to build as many frames as possible, then + ** find out how many frames there are to build. + */ + if (frames == -1) frames = Get_Build_Frame_Count( shapefile ); + + /* + ** Allocate a position to store our icons. If the alloc fails then + ** we dont add these icons to the set. + **/ + buffer = new char[(icon_width * icon_height * 9 * frames)+2]; + if (!buffer) return(NULL); + + /* + ** Save off the return value so that we can return it to the calling + ** function. + */ + retval = (char *)buffer; + *buffer++ = (char)icon_width; + *buffer++ = (char)icon_height; + int val = 24/zoomfactor; + + for (framelp = 0; framelp < frames; framelp ++) { + /* + ** Build the current frame. If the frame can not be built then we + ** just need to skip past this set of icons and try to build the + ** next frame. + */ + if ((ptr = (void *)(Build_Frame(shapefile, shapenum + framelp, SysMemPage.Get_Buffer()))) != NULL) { + ptr = Get_Shape_Header_Data(ptr); + /* + ** Loop through the icon width and the icon height building icons + ** into the buffer pointer. When the getx or gety falls outside of + ** the width and height of the shape, just insert transparent pixels. + */ + for (int icony = 0; icony < icon_height; icony ++) { + for (int iconx = 0; iconx < icon_width; iconx ++) { + for (int y = 0; y < zoomfactor; y++) { + for (int x = 0; x < zoomfactor; x++) { + int getx = (iconx * 24) + (x * val) + (zoomfactor / 2); + int gety = (icony * 24) + (y * val) + (zoomfactor / 2); + if ((getx < pixel_width) && (gety < pixel_height)) { + for (lp = 0; lp < 9; lp ++) { + pixel = *((char *)(Add_Long_To_Pointer(ptr, ((gety - _offy[lp]) * pixel_width) + getx-_offx[lp]))); + if (pixel == LTGREEN) pixel = 0; + if (pixel) { + break; + } + } + *buffer++ = pixel; + } else { + *buffer++ = 0; + } + } + } + } + } + } else { + buffer += icon_width * icon_height * 9; + } + } + return(retval); +} + + + + +void CC_Texture_Fill (void const *shapefile, int shapenum, int xpos, int ypos, int width, int height) +{ + unsigned char *shape_pointer; + //unsigned char *shape_save; + unsigned long shape_size; + //int x,y; + + if (shapefile && shapenum != -1) { + + /* + ** Build frame returns a pointer now instead of the shapes length + */ + shape_size=Build_Frame(shapefile , shapenum , ShapeBuffer); + if (Get_Last_Frame_Length() > _ShapeBufferSize) { + Mono_Printf("Attempt to use shape buffer for size %d buffer is only size %d", shape_size, _ShapeBufferSize); + Get_Key(); + } + + if (shape_size) { + shape_pointer = (unsigned char *)Get_Shape_Header_Data ((void*)shape_size); + int source_width = Get_Build_Frame_Width (shapefile); + int source_height = Get_Build_Frame_Height (shapefile); + + + LogicPage->Texture_Fill_Rect (xpos, ypos, width, height, shape_pointer, source_width, source_height); +#if (0) + if (LogicPage->Lock()){ + + for (y = ypos ; y < ypos + MIN(source_height, height) ; y++ ){ + + shape_save = shape_pointer; + + for (x = xpos ; x < xpos + MIN(source_width, width) ; x++ ){ + LogicPage->Put_Pixel (x, y, *shape_pointer++); + } + + shape_pointer = shape_save + source_width; + } + + LogicPage->Unlock(); + } +#endif + } + } +} + + + + + +/*********************************************************************************************** + * CC_Draw_Shape -- Custom draw shape handler. * + * * + * All draw shape calls will route through this function. It handles all draws for * + * C&C. Such draws always occur to the logical page and assume certain things about * + * the parameters passed. * + * * + * INPUT: shapefile -- Pointer to the shape data file. This data file contains all the * + * embedded shapes. * + * * + * shapenum -- The shape number within the shapefile that will be drawn. * + * * + * x,y -- The pixel coordinates to draw the shape. * + * * + * window -- The clipping window to use. * + * * + * flags -- The custom draw shape flags. This controls how the parameters * + * are used (if any). * + * * + * fadingdata -- If SHAPE_FADING is desired, then this points to the fading * + * data table. * + * * + * ghostdata -- If SHAPE_GHOST is desired, then this points to the ghost remap * + * table. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/21/1995 JLB : Created. * + *=============================================================================================*/ +#pragma off(unreferenced) +void CC_Draw_Shape(void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata, void const * ghostdata) +{ +#if(TRUE) + int predoffset; + char *shape_pointer; + unsigned long shape_size; + + if (shapefile && shapenum != -1) { + + /* + ** Build frame returns a pointer now instead of the shapes length + */ + shape_size=Build_Frame(shapefile , shapenum , ShapeBuffer); + if (Get_Last_Frame_Length() > _ShapeBufferSize) { + Mono_Printf("Attempt to use shape buffer for size %d buffer is only size %d", shape_size, _ShapeBufferSize); + Get_Key(); + } + + if (shape_size) { + GraphicViewPortClass draw_window(LogicPage->Get_Graphic_Buffer(), + WindowList[window][WINDOWX] << 3 + LogicPage->Get_XPos(), + WindowList[window][WINDOWY] + LogicPage->Get_YPos(), + WindowList[window][WINDOWWIDTH] << 3, + WindowList[window][WINDOWHEIGHT]); + + shape_pointer = (char *)shape_size; + + /* + ** Special shadow drawing code (used for aircraft and bullets). + */ + if ((flags & (SHAPE_FADING|SHAPE_PREDATOR)) == (SHAPE_FADING|SHAPE_PREDATOR)) { + flags = flags & ~(SHAPE_FADING|SHAPE_PREDATOR); + flags = flags | SHAPE_GHOST; + ghostdata = Map.SpecialGhost; + } + + predoffset = Frame; + + if (x > ( WindowList[window][WINDOWWIDTH] << 2)) { + predoffset = -predoffset; + } + + if (draw_window.Lock()){ + if ((flags & (SHAPE_GHOST|SHAPE_FADING)) == (SHAPE_GHOST|SHAPE_FADING)) { + Buffer_Frame_To_Page(x, y, Get_Build_Frame_Width(shapefile), Get_Build_Frame_Height(shapefile), + shape_pointer, draw_window, flags | SHAPE_TRANS, ghostdata, fadingdata, 1, predoffset); + } else { + if (flags & SHAPE_FADING) { + Buffer_Frame_To_Page(x, y, Get_Build_Frame_Width(shapefile), Get_Build_Frame_Height(shapefile), + shape_pointer, draw_window, flags | SHAPE_TRANS, fadingdata, 1, predoffset); + } else { + if (flags & SHAPE_PREDATOR) { + Buffer_Frame_To_Page(x, y, Get_Build_Frame_Width(shapefile), Get_Build_Frame_Height(shapefile), + shape_pointer, draw_window, flags | SHAPE_TRANS, predoffset); + } else { + Buffer_Frame_To_Page(x, y, Get_Build_Frame_Width(shapefile), Get_Build_Frame_Height(shapefile), + shape_pointer, draw_window, flags | SHAPE_TRANS, ghostdata, predoffset); + } + } + } + } + draw_window.Unlock(); +// } else { +// Mono_Printf( "Overrun ShapeBuffer!!!!!!!!!\n" ); + } + } +#endif +} + + + +/*********************************************************************************************** + * Fetch_Techno_Type -- Convert type and ID into TechnoTypeClass pointer. * + * * + * This routine will convert the supplied RTTI type number and the ID value into a valid * + * TechnoTypeClass pointer. If there is an error in conversion, then NULL is returned. * + * * + * INPUT: type -- RTTI type of the techno class object. * + * * + * id -- Integer representation of the techno sub type number. * + * * + * OUTPUT: Returns with a pointer to the techno type class object specified or NULL if the * + * conversion could not occur. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +TechnoTypeClass const * Fetch_Techno_Type(RTTIType type, int id) +{ + switch (type) { + case RTTI_UNITTYPE: + case RTTI_UNIT: + return(&UnitTypeClass::As_Reference((UnitType)id)); + + case RTTI_BUILDINGTYPE: + case RTTI_BUILDING: + return(&BuildingTypeClass::As_Reference((StructType)id)); + + case RTTI_INFANTRYTYPE: + case RTTI_INFANTRY: + return(&InfantryTypeClass::As_Reference((InfantryType)id)); + + case RTTI_AIRCRAFTTYPE: + case RTTI_AIRCRAFT: + return(&AircraftTypeClass::As_Reference((AircraftType)id)); + } + return(NULL); +} + + +/*************************************************************************** + * Trap_Object -- gets a ptr to object of given type & coord * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 06/02/1995 BRR : Created. * + *=========================================================================*/ +void Trap_Object(void) +{ + int i; + + TrapObject.Ptr.All = NULL; + + switch (TrapObjType) { + case RTTI_AIRCRAFT: + for (i = 0; i < Aircraft.Count(); i++) { + if (Aircraft.Ptr(i)->Coord == TrapCoord || Aircraft.Ptr(i)==TrapThis) { + TrapObject.Ptr.Aircraft = Aircraft.Ptr(i); + break; + } + } + break; + + case RTTI_ANIM: + for (i = 0; i < Anims.Count(); i++) { + if (Anims.Ptr(i)->Coord == TrapCoord || Anims.Ptr(i)==TrapThis) { + TrapObject.Ptr.Anim = Anims.Ptr(i); + break; + } + } + break; + + case RTTI_BUILDING: + for (i = 0; i < Buildings.Count(); i++) { + if (Buildings.Ptr(i)->Coord == TrapCoord || Buildings.Ptr(i)==TrapThis) { + TrapObject.Ptr.Building = Buildings.Ptr(i); + break; + } + } + break; + + case RTTI_BULLET: + for (i = 0; i < Bullets.Count(); i++) { + if (Bullets.Ptr(i)->Coord == TrapCoord || Bullets.Ptr(i)==TrapThis) { + TrapObject.Ptr.Bullet = Bullets.Ptr(i); + break; + } + } + break; + + case RTTI_INFANTRY: + for (i = 0; i < Infantry.Count(); i++) { + if (Infantry.Ptr(i)->Coord == TrapCoord || Infantry.Ptr(i)==TrapThis) { + TrapObject.Ptr.Infantry = Infantry.Ptr(i); + break; + } + } + break; + + case RTTI_UNIT: + for (i = 0; i < Units.Count(); i++) { + if (Units.Ptr(i)->Coord == TrapCoord || Units.Ptr(i)==TrapThis) { + TrapObject.Ptr.Unit = Units.Ptr(i); + break; + } + } + break; + + /* + ** Last-ditch find-the-object-right-now-darnit loop + */ + case RTTI_NONE: + for (i = 0; i < Aircraft.Count(); i++) { + if (Aircraft.Raw_Ptr(i)->Coord == TrapCoord || Aircraft.Raw_Ptr(i)==TrapThis) { + TrapObject.Ptr.Aircraft = Aircraft.Raw_Ptr(i); + TrapObjType = RTTI_AIRCRAFT; + return; + } + } + for (i = 0; i < Anims.Count(); i++) { + if (Anims.Raw_Ptr(i)->Coord == TrapCoord || Anims.Raw_Ptr(i)==TrapThis) { + TrapObject.Ptr.Anim = Anims.Raw_Ptr(i); + TrapObjType = RTTI_ANIM; + return; + } + } + for (i = 0; i < Buildings.Count(); i++) { + if (Buildings.Raw_Ptr(i)->Coord == TrapCoord || Buildings.Raw_Ptr(i)==TrapThis) { + TrapObject.Ptr.Building = Buildings.Raw_Ptr(i); + TrapObjType = RTTI_BUILDING; + return; + } + } + for (i = 0; i < Bullets.Count(); i++) { + if (Bullets.Raw_Ptr(i)->Coord == TrapCoord || Bullets.Raw_Ptr(i)==TrapThis) { + TrapObject.Ptr.Bullet = Bullets.Raw_Ptr(i); + TrapObjType = RTTI_BULLET; + return; + } + } + for (i = 0; i < Infantry.Count(); i++) { + if (Infantry.Raw_Ptr(i)->Coord == TrapCoord || Infantry.Raw_Ptr(i)==TrapThis) { + TrapObject.Ptr.Infantry = Infantry.Raw_Ptr(i); + TrapObjType = RTTI_INFANTRY; + return; + } + } + for (i = 0; i < Units.Count(); i++) { + if (Units.Raw_Ptr(i)->Coord == TrapCoord || Units.Raw_Ptr(i)==TrapThis) { + TrapObject.Ptr.Unit = Units.Raw_Ptr(i); + TrapObjType = RTTI_UNIT; + return; + } + } + + default: + break; + } +} + + +/*********************************************************************************************** + * VQ_Call_Back -- Maintenance callback used for VQ movies. * + * * + * This routine is called every frame of the VQ movie as it is being played. If this * + * routine returns non-zero, then the movie will stop. * + * * + * INPUT: buffer -- Pointer to the image buffer for the current frame. * + * * + * frame -- The frame number about to be displayed. * + * * + * OUTPUT: Should the movie be stopped? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/24/1995 JLB : Created. * + *=============================================================================================*/ +void VQA_PauseAudio(void); +void Check_VQ_Palette_Set(void); + +long VQ_Call_Back(unsigned char *, long ) +{ + int key = 0; + if (Keyboard::Check()){ + key = Keyboard::Get(); + Keyboard::Clear(); + } + + Check_VQ_Palette_Set(); + + Interpolate_2X_Scale(&SysMemPage,&SeenBuff,NULL); + //Call_Back(); + if ((BreakoutAllowed || Debug_Flag) && key == KN_ESC) { + Keyboard::Clear(); + Brokeout = true; + return(true); + } + + if (!GameInFocus){ + VQA_PauseAudio(); + while (!GameInFocus){ + Keyboard::Check(); + Check_For_Focus_Loss(); + } + } + + return(false); +} + + +/*********************************************************************************************** + * Handle_Team -- Processes team selection command. * + * * + * This routine will handle creation and selection of pseudo teams that the player can * + * create or control. A team in this sense is an arbitrary grouping of units such that * + * rapid selection control is allowed. * + * * + * INPUT: team -- The logical team number to process. * + * * + * action-- The action to perform on this team: * + * 0 - Toggle the select state for all members of this team. * + * 1 - Select the members of this team. * + * 2 - Make all selected objects members of this team. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/27/1995 JLB : Created. * + *=============================================================================================*/ +void Handle_Team(int team, int action) +{ + int index; + + AllowVoice = true; + switch (action) { + + /* + ** Toggle the team selection. If the team is selected, then merely unselect it. If the + ** team is not selected, then unselect all others before selecting this team. + */ + case 3: + case 0: + + /* + ** If a non team member is currently selected, then deselect all objects + ** before selecting this team. + */ + if (CurrentObject.Count()) { + switch (CurrentObject[0]->What_Am_I()) { + case RTTI_UNIT: + case RTTI_INFANTRY: + case RTTI_AIRCRAFT: + if (((FootClass *)CurrentObject[0])->Group != team) { + Unselect_All(); + } + break; + } + } + for (index = 0; index < Units.Count(); index++) { + UnitClass * obj = Units.Ptr(index); + if (obj && !obj->IsInLimbo && obj->Group == team && obj->House == PlayerPtr) { + if (!obj->IsSelected) { + obj->Select(); + AllowVoice = false; + } + } + } + for (index = 0; index < Infantry.Count(); index++) { + InfantryClass * obj = Infantry.Ptr(index); + if (obj && !obj->IsInLimbo && obj->Group == team && obj->House == PlayerPtr) { + if (!obj->IsSelected) { + obj->Select(); + AllowVoice = false; + } + } + } + for (index = 0; index < Aircraft.Count(); index++) { + AircraftClass * obj = Aircraft.Ptr(index); + if (obj && !obj->IsInLimbo && obj->Group == team && obj->House == PlayerPtr) { + if (!obj->IsSelected) { + obj->Select(); + AllowVoice = false; + } + } + } + + /* + ** Center the map around the team if the ALT key was pressed too. + */ + if (action == 3) { + Map.Center_Map(); + Map.Flag_To_Redraw(true); + } + break; + + /* + ** Additive selection of team. + */ + case 1: + for (index = 0; index < Units.Count(); index++) { + UnitClass * obj = Units.Ptr(index); + if (obj && !obj->IsInLimbo && obj->Group == team && obj->House == PlayerPtr) { + if (!obj->IsSelected) { + obj->Select(); + AllowVoice = false; + } + } + } + for (index = 0; index < Infantry.Count(); index++) { + InfantryClass * obj = Infantry.Ptr(index); + if (obj && !obj->IsInLimbo && obj->Group == team && obj->House == PlayerPtr) { + if (!obj->IsSelected) { + obj->Select(); + AllowVoice = false; + } + } + } + for (index = 0; index < Aircraft.Count(); index++) { + AircraftClass * obj = Aircraft.Ptr(index); + if (obj && !obj->IsInLimbo && obj->Group == team && obj->House == PlayerPtr) { + if (!obj->IsSelected) { + obj->Select(); + AllowVoice = false; + } + } + } + break; + + /* + ** Create the team. + */ + case 2: + for (index = 0; index < Units.Count(); index++) { + UnitClass * obj = Units.Ptr(index); + if (obj && !obj->IsInLimbo && obj->House == PlayerPtr) { + if (obj->Group == team) obj->Group = -1; + if (obj->IsSelected) obj->Group = team; + } + } + for (index = 0; index < Infantry.Count(); index++) { + InfantryClass * obj = Infantry.Ptr(index); + if (obj && !obj->IsInLimbo && obj->House == PlayerPtr) { + if (obj->Group == team) obj->Group = -1; + if (obj->IsSelected) obj->Group = team; + } + } + for (index = 0; index < Aircraft.Count(); index++) { + AircraftClass * obj = Aircraft.Ptr(index); + if (obj && !obj->IsInLimbo && obj->House == PlayerPtr) { + if (obj->Group == team) obj->Group = -1; + if (obj->IsSelected) obj->Group = team; + } + } + break; + } + AllowVoice = true; +} + + +/*********************************************************************************************** + * Handle_View -- Either records or restores the tactical view. * + * * + * This routine is used to record or restore the current map tactical view. * + * * + * INPUT: view -- The view number to work with. * + * * + * action-- The action to perform with this view number. * + * 0 = Restore the view to this previously remembered location. * + * 1 = Record the current view location. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/04/1995 JLB : Created. * + *=============================================================================================*/ +void Handle_View(int view, int action) +{ + if ((unsigned)view < sizeof(Views)/sizeof(Views[0])) { + if (action == 0) { + Map.Set_Tactical_Position(Cell_Coord(Views[view])&0xFF00FF00L); + Map.Flag_To_Redraw(true); + } else { + Views[view] = Coord_Cell(Map.TacticalCoord); + } + } +} + +#ifdef CHEAT_KEYS +void Heap_Dump_Check( char *string ) +{ +#if 0 + struct _heapinfo h_info; + int heap_status; +#endif + + + if ( !Debug_Trap_Check_Heap ) { // check the heap? + return; + } + +// Debug_Heap_Dump = true; + + Smart_Printf( "%s\n", string ); + + Dump_Heap_Pointers(); + +#if 0 + heap_status = _heapset( 0xee ); + +#if (1) + if ( heap_status == _HEAPOK || + heap_status == _HEAPEMPTY ) { + Debug_Heap_Dump = false; + return; + } + + h_info._pentry = NULL; + + for(;;) { + heap_status = _heapwalk( &h_info ); + + if ( heap_status != _HEAPOK ) + break; + } + + if (heap_status != _HEAPEND && + heap_status != _HEAPEMPTY) { +#endif + h_info._pentry = NULL; + + for(;;) { + heap_status = _heapwalk( &h_info ); + + if ( heap_status != _HEAPOK ) + break; + + Smart_Printf( " %s block at %Fp of size %4.4X\n", + (h_info._useflag == _USEDENTRY ? "USED" : "FREE"), + h_info._pentry, h_info._size ); + } + + Smart_Printf( " %d block at %Fp of size %4.4X\n", + h_info._useflag, h_info._pentry, h_info._size ); + + switch ( heap_status ) { +// case _HEAPEND: +// Smart_Printf( "OK - end of heap\n" ); +// break; + +// case _HEAPEMPTY: +// Smart_Printf( "OK - heap is empty\n" ); +// break; + + case _HEAPBADBEGIN: + Smart_Printf( "ERROR - heap is damaged\n" ); + break; + + case _HEAPBADPTR: + Smart_Printf( "ERROR - bad pointer to heap\n" ); + break; + + case _HEAPBADNODE: + Smart_Printf( "ERROR - bad node in heap\n" ); + break; + } +#if (1) + } +#endif +#endif + +// Debug_Heap_Dump = false; +} + + +void Dump_Heap_Pointers( void ) +{ + char *ptr, *lptr, *nptr, *cptr, *dptr, *wlptr, *nlptr, *aptr, *clptr; + int numallocs, numfrees, sizefree; + static char _freeorused[2][5] = { "FREE", "USED" }; + + + ptr = (char *)__nheapbeg; + + while ( ptr ) { + + if ( Debug_Heap_Dump ) { + Smart_Printf( "%p pre header\n", (ptr - 8) ); + Hex_Dump_Data( (ptr - 8), 0x08); + + Smart_Printf( "%p header\n", ptr ); + Hex_Dump_Data( ptr, 0x30); + } + + dptr = (char *)*(int *)(ptr + 0x0c); + + sizefree = *(int *)(ptr + 0x14); + numallocs = *(int *)(ptr + 0x18); + numfrees = *(int *)(ptr + 0x1c); + + cptr = (char *)*(int *)(ptr + 0x24); + lptr = (char *)*(int *)(ptr + 0x28); + + if ( ((int)cptr & 0xff000000) || + ((int)dptr & 0xff000000) || + ((int)lptr & 0xff000000) ) { + Error_In_Heap_Pointers( "local free heap ptrs too large" ); + } + + if ( Debug_Heap_Dump ) { + if ( lptr != dptr || + lptr != cptr || + cptr != dptr ) { + Smart_Printf( "The pointers are different!!\n" ); + } + } + + nptr = (char *)*(int *)(ptr + 8); // next block + + if ( ((int)nptr & 0xFF000000) ) { + Error_In_Heap_Pointers( "next block ptr too large" ); + } + + if ( lptr != (ptr + 0x20) ) { + + if ( !(*((int *)(ptr + 0x20))) ) { // allocated + aptr = (ptr + 0x2c); + + while ( aptr < lptr ) { + + if ( (*(int *)(aptr)) == -1) { +// Smart_Printf( "end alloc chain %p.\n", aptr ); +// Hex_Dump_Data( aptr, 0x10); + break; + } + + if ( Debug_Heap_Dump ) { + Smart_Printf( "%p chain %s, size %X.\n", aptr, + _freeorused[ ((*aptr) & 1) ], + ((*(int *)(aptr)) & 0xfffffffe) ); + Hex_Dump_Data( aptr, 0x10); + } + + if ( ((*(int *)(aptr)) & 0xff000000) ) { + Error_In_Heap_Pointers( "alloc block size way too large" ); + } + + aptr += ((*(int *)(aptr)) & 0xfffffffe); + + if ( ((int)aptr & 0xff000000) ) { + Error_In_Heap_Pointers( "next alloc block ptr way too large" ); + } + + numallocs--; + + if ( aptr > lptr ) { + Error_In_Heap_Pointers( "next alloc block ptr too large" ); + } + } + } else { + if ( sizefree != -1 ) { + sizefree -= ((*(int *)(ptr + 0x20)) & 0xfffffffe); + } + numfrees--; + } + + wlptr = lptr; + + while ( wlptr != (ptr + 0x20) ) { + + if ( Debug_Heap_Dump ) { + Smart_Printf( "%p link %s, size %X.\n", wlptr, + _freeorused[ ((*wlptr) & 1) ], + ((*(int *)(wlptr)) & 0xfffffffe) ); + Hex_Dump_Data( wlptr, 0x10); + } + + nlptr = (char *)*(int *)(wlptr + 8); + + if ( !(*((int *)(wlptr))) ) { // allocated + aptr = (wlptr + 0x0c); + } else { + if ( sizefree != -1 ) { + sizefree -= ((*(int *)(wlptr)) & 0xfffffffe); + } + numfrees--; + + aptr = (wlptr + ((*(int *)(wlptr)) & 0xfffffffe)); + } + + if (nlptr == (ptr + 0x20) ) { + clptr = nptr; + } else { + clptr = nlptr; + } + + while ( aptr < clptr ) { + + if ( (*(int *)(aptr)) == -1) { +// Smart_Printf( "end alloc chain %p.\n", aptr ); +// Hex_Dump_Data( aptr, 0x10); + break; + } + + if ( Debug_Heap_Dump ) { + Smart_Printf( "%p chain %s, size %X.\n", aptr, + _freeorused[ ((*aptr) & 1) ], + ((*(int *)(aptr)) & 0xfffffffe) ); + Hex_Dump_Data( aptr, 0x10); + } + + if ( ((*(int *)(aptr)) & 0xff000000) ) { + Error_In_Heap_Pointers( "alloc block size way too large" ); + } + + aptr += ((*(int *)(aptr)) & 0xfffffffe); + + if ( ((int)aptr & 0xff000000) ) { + Error_In_Heap_Pointers( "next alloc block ptr way too large" ); + } + + numallocs--; + + if ( aptr > clptr ) { + Error_In_Heap_Pointers( "next alloc block ptr too large" ); + } + } + + wlptr = nlptr; + } + } else { +// Smart_Printf( "only link %s, size %X.\n", +// _freeorused[ ((*lptr) & 1) ], +// ((*(int *)(lptr)) & 0xfffffffe) ); + + if ( !(*((int *)(lptr))) ) { // allocated + aptr = (ptr + 0x2c); + + while ( aptr < nptr ) { + + if ( (*(int *)(aptr)) == -1) { +// Smart_Printf( "end alloc chain %p.\n", aptr ); +// Hex_Dump_Data( aptr, 0x10); + break; + } + + if ( Debug_Heap_Dump ) { + Smart_Printf( "%p chain %s, size %X.\n", aptr, + _freeorused[ ((*aptr) & 1) ], + ((*(int *)(aptr)) & 0xfffffffe) ); + Hex_Dump_Data( aptr, 0x10); + } + + if ( ((*(int *)(aptr)) & 0xff000000) ) { + Error_In_Heap_Pointers( "alloc block size way too large" ); + } + + aptr += ((*(int *)(aptr)) & 0xfffffffe); + + if ( ((int)aptr & 0xff000000) ) { + Error_In_Heap_Pointers( "next alloc block ptr way too large" ); + } + + numallocs--; + + if ( aptr > nptr ) { + Error_In_Heap_Pointers( "next alloc block ptr too large" ); + } + } + } else { + if ( sizefree != -1 ) { + sizefree -= ((*(int *)(ptr + 0x20)) & 0xfffffffe); + } + numfrees--; + } + } + + if ( sizefree != 0 && sizefree != -1 ) { + Smart_Printf( "sizefree left over %X.\n", sizefree ); + } + + if ( numallocs != 0 ) { + Smart_Printf( "numallocs unaccounted for %d.\n", numallocs ); + } + + if ( numfrees != 0 ) { + Smart_Printf( "numfrees unaccounted for %d.\n", numfrees ); + } + + ptr = nptr; + } + +} + + +void Error_In_Heap_Pointers( char *string ) +{ + Smart_Printf( "Error in Heap for %s\n", string ); +} +#endif + + +#ifndef ROR_NOT_READY +#define ROR_NOT_READY 21 +#endif + +/*********************************************************************************************** + * Get_CD_Index -- returns the volume type of the CD in the given drive * + * * + * * + * * + * INPUT: drive number * + * timeout * + * * + * OUTPUT: 0 = gdi * + * 1 = nod * + * 2 = covert * + * -1 = non C&C * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 5/21/96 5:27PM ST : Created * + *=============================================================================================*/ +int Get_CD_Index (int cd_drive, int timeout) +{ + char volume_name[128]; + unsigned filename_length; + unsigned misc_dword; + unsigned error_code; + int count = 0; + + CountDownTimerClass timer; + + static char * _volid[] = { + "GDI95", + "NOD95", + "COVERT" + }; + static int num_volumes = 3; + + char buffer[128]; + + timer.Set(timeout); + + + /* + ** Get the volume label. If we get a 'not ready' error then retry for the timeout + ** period. + */ + do{ + sprintf(buffer, "%c:\\", 'A' + cd_drive); + + if (GetVolumeInformation ((char const *)buffer, + &volume_name[0] , + (unsigned long)128 , + (unsigned long *)NULL , + (unsigned long *)&filename_length, + (unsigned long *)&misc_dword , + (char *)NULL , + (unsigned long)0)) { + + /* + ** Try opening 'movies.mix' to verify that the CD is really there and is what + ** it says it is. + */ + sprintf(buffer, "%c:\\movies.mix", 'A' + cd_drive); + + HANDLE handle = CreateFile(buffer, GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (handle != INVALID_HANDLE_VALUE) { + CloseHandle(handle); + + /* + ** Match the volume label to the list of known C&C volume labels. + */ + for (int i=0 ; i. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/11/1995 JLB : Created. * + * 05/22/1996 ST : Handles multiple CD drives / CD changers * + *=============================================================================================*/ +bool Force_CD_Available(int cd) +{ +#ifndef DEMO + static int _last = -1; + int open_failed; + int file; +#endif + static char _palette[768]; + static char _hold[256]; + static void *font; + static char * _volid[] = { + "GDI", + "NOD", + "COVERT" + }; + + int drive; + + char volume_name[100]; + unsigned filename_length; + unsigned misc_dword; + int new_cd_drive = 0; + int cd_index; + char buffer[128]; + int cd_drive; + int current_drive; + int drive_search_timeout; + bool old_in_main_loop; + + ThemeType theme_playing = THEME_NONE; + + /* + ** If the required CD is set to -2 then it means that the file is present + ** on the local hard drive and we shouldn't have to worry about it. + */ + if (cd == -2) return(true); + + + /* + ** Find out if the CD in the current drive is the one we are looking for + */ + current_drive = CCFileClass::Get_CD_Drive(); + cd_index = Get_CD_Index(current_drive, 1*60); + if (cd_index >= 0){ + if (cd == cd_index || cd == -1){ + /* + ** The required CD is still in the CD drive we used last time + */ + new_cd_drive = current_drive; + } + } + + + /* + ** Flag that we will have to restart the theme + */ + theme_playing = Theme.What_Is_Playing(); + Theme.Stop(); + + + if (!new_cd_drive){ + /* + ** Check the last CD drive we used if its different from the current one + */ + int last_drive = CCFileClass::Get_Last_CD_Drive(); + /* + ** Make sure the last drive is valid and it isnt the current drive + */ + if (last_drive && last_drive != CCFileClass::Get_CD_Drive()){ + /* + ** Find out if there is a C&C cd in the last drive and if so is it the one we are looking for + ** Give it a nice big timeout so the CD changer has time to swap the discs + */ + cd_index = Get_CD_Index(last_drive, 10*60); + if (cd_index >= 0){ + if (cd == cd_index || cd == -1){ + /* + ** The required CD is in the CD drive we used last time + */ + new_cd_drive = last_drive; + } + } + } + } + + /* + ** Lordy. No sign of that blimming CD anywhere. Search all the CD drives + ** then if we still cant find it prompt the user to insert it. + */ + if (!new_cd_drive){ + + /* + ** Small timeout for the first pass through the drives + */ + drive_search_timeout = 2*60; + + for (;;) { + /* + ** Search all present CD drives for the required disc. + */ + for (int i=0 ; i=0){ + /* + ** We found a C&C cd - lets see if it was the one we were looking for + */ + if (cd == cd_index || cd == -1){ + /* + ** Woohoo! The disk was in a different cd drive. Refresh the search path list + * and return. + */ + new_cd_drive = cd_drive; + break; + } + } + } + + /* + ** A new disc has become available so break + */ + if (new_cd_drive) break; + + /* + ** Increase the timeout for subsequent drive searches. + */ + drive_search_timeout = 5*60; + + /* + ** Prompt to insert the CD into the drive. + */ + if (cd == -1) { + sprintf(buffer, Text_String(TXT_CD_DIALOG_1), cd+1, _volid[cd]); + } else { + if (cd == 2) { + sprintf(buffer, Text_String(TXT_CD_DIALOG_3)); + } else { + sprintf(buffer, Text_String(TXT_CD_DIALOG_2), cd+1, _volid[cd]); + } + } + GraphicViewPortClass * oldpage = Set_Logic_Page(SeenBuff); + theme_playing = Theme.What_Is_Playing(); + Theme.Stop(); + int hidden = Get_Mouse_State(); + font = (void *)FontPtr; + Mem_Copy(CurrentPalette, _palette, 768); + Mem_Copy(Get_Font_Palette_Ptr(), _hold, 256); + + + /* + ** Only set the palette if necessary. + */ +// if (CurrentPalette[3] == 0) { + Set_Palette(GamePalette); +// } + + /* + ** Pretend we are in the game, even if we arent + */ + old_in_main_loop = InMainLoop; + InMainLoop = true; + + Keyboard::Clear(); + + while (Get_Mouse_State()) Show_Mouse(); + + if (CCMessageBox().Process(buffer, TXT_OK, TXT_CANCEL, TXT_NONE, TRUE) == 1) { + Set_Logic_Page(oldpage); + Hide_Mouse(); + InMainLoop = old_in_main_loop; + return(false); + } + while (hidden--) Hide_Mouse(); + Set_Palette(_palette); + Set_Font(font); + Mem_Copy(_hold, Get_Font_Palette_Ptr(), 256); + Set_Logic_Page(oldpage); + InMainLoop = old_in_main_loop; + } + } + + +#ifndef DEMO + + CCFileClass::Set_CD_Drive(new_cd_drive); + CCFileClass::Refresh_Search_Drives(); + + /* + ** If it broke out of the query for CD-ROM loop, then this means that the + ** CD-ROM has been inserted. + */ + if (cd > -1 && _last != cd) { + _last = cd; + + Theme.Stop(); + + if (MoviesMix) delete MoviesMix; + if (GeneralMix) delete GeneralMix; + if (ScoreMix) delete ScoreMix; + + MoviesMix = new MixFileClass("MOVIES.MIX"); + GeneralMix = new MixFileClass("GENERAL.MIX"); + ScoreMix = new MixFileClass("SCORES.MIX"); + ThemeClass::Scan(); + } +#endif + + if (theme_playing != THEME_NONE) { + Theme.Queue_Song(theme_playing); + } + + return(true); +} + + +/*************************************************************************** + * DISK_SPACE_AVAILABLE -- returns bytes of free disk space * + * * + * INPUT: none * + * * + * OUTPUT: returns amount of free disk space * + * * + * HISTORY: * + * 08/11/1995 PWG : Created. * + *=========================================================================*/ +unsigned long Disk_Space_Available(void) +{ + struct diskfree_t diskdata; + unsigned drive; + + _dos_getdrive(&drive); + _dos_getdiskfree(drive, &diskdata); + + return(diskdata.avail_clusters * diskdata.sectors_per_cluster * diskdata.bytes_per_sector); +} + + +/*********************************************************************************************** + * Validate_Error -- prints an error message when an object fails validation * + * * + * INPUT: * + * name name of object type that failed * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/15/1995 BRR : Created. * + *=============================================================================================*/ +void Validate_Error(char *name) +{ +#ifdef CHEAT_KEYS + Prog_End(); + printf("%s object error!\n",name); + exit(0); +#else + name = name; +#endif +} + + +/*********************************************************************************************** + * Do_Record_Playback -- handles saving/loading map pos & current object * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/15/1995 BRR : Created. * + *=============================================================================================*/ +static void Do_Record_Playback(void) +{ + int count; + TARGET tgt; + int i; + COORDINATE coord; + ObjectClass *obj; + unsigned long sum; + unsigned long sum2; + unsigned long ltgt; + + /*------------------------------------------------------------------------ + Record a game + ------------------------------------------------------------------------*/ + if (RecordGame) { + + /*..................................................................... + For 'SuperRecord', we'll open & close the file with every entry. + .....................................................................*/ + if (SuperRecord) { + RecordFile.Open(READ|WRITE); + RecordFile.Seek(0,SEEK_END); + } + + /*..................................................................... + Save the map's location + .....................................................................*/ + RecordFile.Write(&Map.DesiredTacticalCoord, sizeof (Map.DesiredTacticalCoord)); + + /*..................................................................... + Save the current object list count + .....................................................................*/ + count = CurrentObject.Count(); + RecordFile.Write(&count, sizeof(count)); + + /*..................................................................... + Save a CRC of the selected-object list. + .....................................................................*/ + sum = 0; + for (i = 0; i < count; i++) { + ltgt = (unsigned long)(CurrentObject[i]->As_Target()); + sum += ltgt; + } + RecordFile.Write (&sum, sizeof(sum)); + + /*..................................................................... + Save all selected objects. + .....................................................................*/ + for (i = 0; i < count; i++) { + tgt = CurrentObject[i]->As_Target(); + RecordFile.Write (&tgt, sizeof(tgt)); + } + + /*..................................................................... + If 'SuperRecord', close the file now. + .....................................................................*/ + if (SuperRecord) { + RecordFile.Close(); + } + } + + /*------------------------------------------------------------------------ + Play back a game ("attract" mode) + ------------------------------------------------------------------------*/ + if (PlaybackGame) { + + /*..................................................................... + Read & set the map's location. + .....................................................................*/ + if (RecordFile.Read(&coord, sizeof(coord))==sizeof(coord)) { + if (coord != Map.DesiredTacticalCoord) { + Map.Set_Tactical_Position(coord); + } + } + + if (RecordFile.Read(&count, sizeof(count))==sizeof(count)) { + /*.................................................................. + Compute a CRC of the current object-selection list. + ..................................................................*/ + sum = 0; + for (i = 0; i < CurrentObject.Count(); i++) { + ltgt = (unsigned long)(CurrentObject[i]->As_Target()); + sum += ltgt; + } + + /*.................................................................. + Load the CRC of the objects on disk; if it doesn't match, select + all objects as they're loaded. + ..................................................................*/ + RecordFile.Read (&sum2, sizeof(sum2)); + if (sum2 != sum) + Unselect_All(); + + AllowVoice = true; + + for (i = 0; i < count; i++) { + if (RecordFile.Read (&tgt, sizeof(tgt))==sizeof(tgt)) { + obj = As_Object(tgt); + if (obj && (sum2 != sum)) { + obj->Select(); + AllowVoice = false; + } + } + } + + AllowVoice = true; + + } + + /*..................................................................... + The map isn't drawn in playback mode, so draw it here. + .....................................................................*/ + Map.Render(); + } +} +/*************************************************************************** + * HIRES_RETRIEVE -- retrieves a resolution dependant file * + * * + * INPUT: char * file name of the file to retrieve * + * * + * OUTPUT: none * + * * + * HISTORY: * + * 01/25/1996 : Created. * + *=========================================================================*/ +void const * Hires_Retrieve(char *name) +{ + char filename[30]; + + if (SeenBuff.Get_Width() != 320) { + sprintf(filename,"H%s", name); + } else { + strcpy(filename, name); + } + return(MixFileClass::Retrieve(filename)); +} +int Get_Resolution_Factor(void) +{ + return ((SeenBuff.Get_Width() == 320) ? 0 : 1); +} diff --git a/CONQUER.DEF b/CONQUER.DEF new file mode 100644 index 0000000..6d74649 --- /dev/null +++ b/CONQUER.DEF @@ -0,0 +1,11 @@ +NAME CONQUER +DESCRIPTION 'Command and Conquer for Windows 95' +EXETYPE WINDOWS + +IMPORTS + DirectDrawCreate=DDRAW.DirectDrawCreate + IID_IDirectDraw2=DDRAW.IID_IDirectDraw2 + DirectSoundCreate=DSOUND.DirectSoundCreate + DirectPlayEnumerate=DPLAY.DirectPlayEnumerate + DirectPlayCreate=DPLAY.DirectPlayCreate + diff --git a/CONQUER.H b/CONQUER.H new file mode 100644 index 0000000..42a7df1 --- /dev/null +++ b/CONQUER.H @@ -0,0 +1,773 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#define TXT_NONE 0 // +#define TXT_CREDIT_FORMAT 1 // %3d.%02d +#define TXT_BUTTON_UPGRADE 2 // Upgrade +#define TXT_UPGRADE 3 // Upgrade Structure +#define TXT_UPGRADE_BUTTON 4 // Upgrade +#define TXT_BUTTON_SELL 5 // Sell +#define TXT_SELL 6 // Sell Structure +#define TXT_DEMOLISH 7 // Demolish Structure +#define TXT_BUTTON_REPAIR 8 // Repair +#define TXT_REPAIR 9 // Repair Structure +#define TXT_REPAIR_BUTTON 10 // Repair +#define TXT_YOU 11 // You: +#define TXT_ENEMY 12 // Enemy: +#define TXT_BUILD_DEST 13 // Buildings Destroyed By +#define TXT_UNIT_DEST 14 // Units Destroyed By +#define TXT_TIB_HARV 15 // Tiberium Harvested By +#define TXT_SCORE_1 16 // Score: %d +#define TXT_RANK_OF 17 // You have attained the rank +#define TXT_YES 18 // Yes +#define TXT_NO 19 // No +#define TXT_READY 20 // Ready +#define TXT_HOLDING 21 // Holding +#define TXT_SCENARIO_WON 22 // Accomplished +#define TXT_SCENARIO_LOST 23 // Failed +#define TXT_CHOOSE_SIDE 24 // Choose Your Side +#define TXT_START_NEW_GAME 25 // Start New Game +#define TXT_INTRO 26 // Intro & Sneak Peek +#define TXT_CANCEL 27 // Cancel +#define TXT_ROCK 28 // Rock +#define TXT_CHOAM_RESUME 29 // Resume Game +#define TXT_CHOAM_BUILD_THIS 30 // Build This +#define TXT_THANK_YOU 31 // Thank you for playing +#define TXT_FAME 32 // Hall of Fame +#define TXT_GDI 33 // Global Defense Initiative +#define TXT_NOD 34 // Brotherhood of Nod +#define TXT_CIVILIAN 35 // Civilian +#define TXT_JP 36 // Containment Team +#define TXT_OK 37 // OK +#define TXT_TREE 38 // Tree +#define TXT_LEFT 39 //  +#define TXT_RIGHT 40 //  +#define TXT_UP 41 //  +#define TXT_DOWN 42 //  +#define TXT_CLEAR_MAP 43 // Clear the map +#define TXT_INHERIT_MAP 44 // Inherit previous map +#define TXT_CLEAR 45 // Clear +#define TXT_WATER 46 // Water +#define TXT_ROAD 47 // Road +#define TXT_TILE 48 // Tile Object +#define TXT_SLOPE 49 // Slope +#define TXT_BRUSH 50 // Brush +#define TXT_PATCH 51 // Patch +#define TXT_RIVER 52 // River +#define TXT_LOAD_MISSION 53 // Load Mission +#define TXT_SAVE_MISSION 54 // Save Mission +#define TXT_DELETE_MISSION 55 // Delete Mission +#define TXT_LOAD_BUTTON 56 // Load +#define TXT_SAVE_BUTTON 57 // Save +#define TXT_DELETE_BUTTON 58 // Delete +#define TXT_GAME_CONTROLS 59 // Game Controls +#define TXT_SOUND_CONTROLS 60 // Sound Controls +#define TXT_RESUME_MISSION 61 // Resume Mission +#define TXT_VISUAL_CONTROLS 62 // Visual Controls +#define TXT_QUIT_MISSION 63 // Abort Mission +#define TXT_EXIT_GAME 64 // Exit Game +#define TXT_OPTIONS 65 // Options +#define TXT_TIBERIUM 66 // Tiberium +#define TXT_TIBERIUM_ON 67 // Tiberium On +#define TXT_TIBERIUM_OFF 68 // Tiberium Off +#define TXT_SQUISH 69 // Squish mark +#define TXT_CRATER 70 // Crater +#define TXT_SCORCH 71 // Scorch Mark +#define TXT_BRIGHTNESS 72 // BRIGHTNESS: +#define TXT_MUSIC 73 // MUSIC VOLUME +#define TXT_VOLUME 74 // SOUND VOLUME +#define TXT_TINT 75 // TINT: +#define TXT_CONTRAST 76 // CONTRAST: +#define TXT_SPEED 77 // GAME SPEED: +#define TXT_SCROLLRATE 78 // SCROLL RATE: +#define TXT_COLOR 79 // COLOR: +#define TXT_RETURN_TO_GAME 80 // Return to game +#define TXT_ENEMY_SOLDIER 81 // Enemy Soldier +#define TXT_ENEMY_VEHICLE 82 // Enemy Vehicle +#define TXT_ENEMY_STRUCTURE 83 // Enemy Structure +#define TXT_FTANK 84 // Flame Tank +#define TXT_STANK 85 // Stealth Tank +#define TXT_LTANK 86 // Light Tank +#define TXT_MTANK 87 // Med. Tank +#define TXT_HTANK 88 // Mammoth Tank +#define TXT_DUNE_BUGGY 89 // Nod Buggy +#define TXT_SAM 90 // SAM Site +#define TXT_EYE 91 // Advanced Com. Center +#define TXT_MLRS 92 // Rocket Launcher +#define TXT_MHQ 93 // Mobile HQ +#define TXT_JEEP 94 // Hum-vee +#define TXT_TRANS 95 // Transport Helicopter +#define TXT_A10 96 // A10 +#define TXT_C17 97 // C17 +#define TXT_HARVESTER 98 // Harvester +#define TXT_ARTY 99 // Artillery +#define TXT_MSAM 100 // S.S.M. Launcher +#define TXT_E1 101 // Minigunner +#define TXT_E2 102 // Grenadier +#define TXT_E3 103 // Bazooka +#define TXT_E4 104 // Flamethrower +#define TXT_E5 105 // Chem-warrior +#define TXT_RAMBO 106 // Commando +#define TXT_HOVER 107 // Hovercraft +#define TXT_HELI 108 // Attack Helicopter +#define TXT_ORCA 109 // Orca +#define TXT_APC 110 // APC +#define TXT_GUARD_TOWER 111 // Guard Tower +#define TXT_COMMAND 112 // Communications Center +#define TXT_HELIPAD 113 // Helicopter Pad +#define TXT_AIRSTRIP 114 // Airstrip +#define TXT_STORAGE 115 // Tiberium Silo +#define TXT_CONST_YARD 116 // Construction Yard +#define TXT_REFINERY 117 // Tiberium Refinery +#define TXT_CIV1 118 // Church +#define TXT_CIV2 119 // Han's and Gretel's +#define TXT_CIV3 120 // Hewitt's Manor +#define TXT_CIV4 121 // Ricktor's House +#define TXT_CIV5 122 // Gretchin's House +#define TXT_CIV6 123 // The Barn +#define TXT_CIV7 124 // Damon's pub +#define TXT_CIV8 125 // Fran's House +#define TXT_CIV9 126 // Music Factory +#define TXT_CIV10 127 // Toymaker's +#define TXT_CIV11 128 // Ludwig's House +#define TXT_CIV12 129 // Haystacks +#define TXT_CIV13 130 // Haystack +#define TXT_CIV14 131 // Wheat Field +#define TXT_CIV15 132 // Fallow Field +#define TXT_CIV16 133 // Corn Field +#define TXT_CIV17 134 // Celery Field +#define TXT_CIV18 135 // Potato Field +#define TXT_CIV20 136 // Sala's House +#define TXT_CIV21 137 // Abdul's House +#define TXT_CIV22 138 // Pablo's Wicked Pub +#define TXT_CIV23 139 // Village Well +#define TXT_CIV24 140 // Camel Trader +#define TXT_CIV25 141 // Church +#define TXT_CIV26 142 // Ali's House +#define TXT_CIV27 143 // Trader Ted's +#define TXT_CIV28 144 // Menelik's House +#define TXT_CIV29 145 // Prestor John's House +#define TXT_CIV30 146 // Village Well +#define TXT_CIV31 147 // Witch Doctor's Hut +#define TXT_CIV32 148 // Rikitikitembo's Hut +#define TXT_CIV33 149 // Roarke's Hut +#define TXT_CIV34 150 // Mubasa's Hut +#define TXT_CIV35 151 // Aksum's Hut +#define TXT_CIV36 152 // Mambo's Hut +#define TXT_CIV37 153 // The Studio +#define TXT_CIVMISS 154 // Technology Center +#define TXT_TURRET 155 // Gun Turret +#define TXT_GUNBOAT 156 // Gun Boat +#define TXT_MCV 157 // Mobile Construction Yard +#define TXT_BIKE 158 // Recon Bike +#define TXT_POWER 159 // Power Plant +#define TXT_ADVANCED_POWER 160 // Advanced Power Plant +#define TXT_HOSPITAL 161 // Hospital +#define TXT_BARRACKS 162 // Barracks +#define TXT_CONCRETE 163 // Concrete +#define TXT_PUMP 164 // Oil Pump +#define TXT_TANKER 165 // Oil Tanker +#define TXT_SANDBAG_WALL 166 // Sandbag Wall +#define TXT_CYCLONE_WALL 167 // Chain Link Fence +#define TXT_BRICK_WALL 168 // Concrete Wall +#define TXT_BARBWIRE_WALL 169 // Barbwire Fence +#define TXT_WOOD_WALL 170 // Wood Fence +#define TXT_WEAPON_FACTORY 171 // Weapons Factory +#define TXT_AGUARD_TOWER 172 // Advanced Guard Tower +#define TXT_OBELISK 173 // Obelisk Guard Tower +#define TXT_BIO_LAB 174 // Bio-Research Laboratory +#define TXT_HAND 175 // Hand of Nod +#define TXT_TEMPLE 176 // Temple of Nod +#define TXT_FIX_IT 177 // Repair Bay +#define TXT_TAB_SIDEBAR 178 // Sidebar +#define TXT_TAB_BUTTON_CONTROLS 179 // Options +#define TXT_TAB_BUTTON_DATABASE 180 // Database +#define TXT_SHADOW 181 // Unrevealed Terrain +#define TXT_OPTIONS_MENU 182 // Options Menu +#define TXT_STOP 183 // STOP +#define TXT_PLAY 184 // PLAY +#define TXT_SHUFFLE 185 // SHUFFLE +#define TXT_REPEAT 186 // REPEAT +#define TXT_MUSIC_VOLUME 187 // Music volume: +#define TXT_SOUND_VOLUME 188 // Sound volume: +#define TXT_ON 189 // On +#define TXT_OFF 190 // Off +#define TXT_THEME_AOI 191 // Act On Instinct +#define TXT_THEME_TROUBLE 192 // Looks Like Trouble +#define TXT_THEME_IND 193 // Industrial +#define TXT_THEME_ROUT 194 // Reaching Out +#define TXT_THEME_OTP 195 // On The Prowl +#define TXT_THEME_PRP 196 // Prepare For Battle +#define TXT_THEME_JUSTDOIT 197 // Just Do It! +#define TXT_THEME_LINEFIRE 198 // In The Line Of Fire +#define TXT_THEME_MARCH 199 // March To Doom +#define TXT_THEME_STOPTHEM 200 // Deception +#define TXT_THEME_CCTHANG 201 // C&C Thang +#define TXT_THEME_BEFEARED 202 // Enemies To Be Feared +#define TXT_THEME_WARFARE 203 // Warfare +#define TXT_THEME_FWP 204 // Fight, Win, Prevail +#define TXT_THEME_DIE 205 // Die!! +#define TXT_THEME_NOMERCY 206 // No Mercy +#define TXT_THEME_TARGET 207 // Mechanical Man +#define TXT_THEME_IAM 208 // I Am +#define TXT_THEME_WIN1 209 // Great Shot! +#define TXT_MULTIPLAYER_GAME 210 // Multiplayer Game +#define TXT_NO_FILES 211 // No files available +#define TXT_DELETE_SINGLE_FILE 212 // Do you want to delete this +#define TXT_DELETE_MULTIPLE_FILES 213 // Do you want to delete %d +#define TXT_RESET_MENU 214 // Reset Values +#define TXT_CONFIRMATION 215 // Confirmation +#define TXT_CONFIRM_EXIT 216 // Do you want to abort the +#define TXT_MISSION_DESCRIPTION 217 // Mission Description +#define TXT_C1 218 // Joe +#define TXT_C2 219 // Bill +#define TXT_C3 220 // Shelly +#define TXT_C4 221 // Maria +#define TXT_C5 222 // Eydie +#define TXT_C6 223 // Dave +#define TXT_C7 224 // Phil +#define TXT_C8 225 // Dwight +#define TXT_C9 226 // Erik +#define TXT_MOEBIUS 227 // Dr. Moebius +#define TXT_BIB 228 // Road Bib +#define TXT_FASTER 229 // Faster +#define TXT_SLOWER 230 // Slower +#define TXT_ION_CANNON 231 // Ion Cannon +#define TXT_NUKE_STRIKE 232 // Nuclear Strike +#define TXT_AIR_STRIKE 233 // Air Strike +#define TXT_TREX 234 // Tyrannosaurus Rex +#define TXT_TRIC 235 // Triceratops +#define TXT_RAPT 236 // Velociraptor +#define TXT_STEG 237 // Stegasaurus +#define TXT_STEEL_CRATE 238 // Steel Crate +#define TXT_WOOD_CRATE 239 // Wood Crate +#define TXT_FLAG_SPOT 240 // Flag Location +#define TXT_G_D_I 241 // GDI +#define TXT_N_O_D 242 // NOD +#define TXT_UNABLE_READ_SCENARIO 243 // Unable to read scenario! +#define TXT_ERROR_LOADING_GAME 244 // Error loading game! +#define TXT_OBSOLETE_SAVEGAME 245 // Obsolete saved game. +#define TXT_MUSTENTER_DESCRIPTION 246 // You must enter a +#define TXT_ERROR_SAVING_GAME 247 // Error saving game! +#define TXT_DELETE_FILE_QUERY 248 // Delete this file? +#define TXT_EMPTY_SLOT 249 // [EMPTY SLOT] +#define TXT_SELECT_MPLAYER_GAME 250 // Select Multiplayer Game +#define TXT_MODEM_SERIAL 251 // Modem/Serial +#define TXT_NETWORK 252 // Network +#define TXT_INIT_NET_ERROR 253 // Unable to initialize +#define TXT_JOIN_NETWORK_GAME 254 // Join Network Game +#define TXT_NEW 255 // New +#define TXT_JOIN 256 // Join +#define TXT_SEND_MESSAGE 257 // Send Message +#define TXT_YOUR_NAME 258 // Your Name: +#define TXT_SIDE_COLON 259 // Side: +#define TXT_COLOR_COLON 260 // Color: +#define TXT_GAMES 261 // Games +#define TXT_PLAYERS 262 // Players +#define TXT_SCENARIO_COLON 263 // Scenario: +#define TXT_NOT_FOUND 264 // >> NOT FOUND << +#define TXT_START_CREDITS_COLON 265 // Starting Credits: +#define TXT_BASES_COLON 266 // Bases: +#define TXT_TIBERIUM_COLON 267 // Tiberium: +#define TXT_CRATES_COLON 268 // Crates: +#define TXT_AI_PLAYERS_COLON 269 // AI Players: +#define TXT_REQUEST_DENIED 270 // Request denied. +#define TXT_UNABLE_PLAY_WAAUGH 271 // Unable to play; scenario +#define TXT_NOTHING_TO_JOIN 272 // Nothing to join! +#define TXT_NAME_ERROR 273 // You must enter a name! +#define TXT_DUPENAMES_NOTALLOWED 274 // Duplicate names are not +#define TXT_YOURGAME_OUTDATED 275 // Your game version is +#define TXT_DESTGAME_OUTDATED 276 // Destination game version is +#define TXT_THATGUYS_GAME 277 // %s's Game +#define TXT_THATGUYS_GAME_BRACKET 278 // [%s's Game] +#define TXT_NETGAME_SETUP 279 // Network Game Setup +#define TXT_REJECT 280 // Reject +#define TXT_CANT_REJECT_SELF 281 // You can't reject yourself! +#define TXT_SELECT_PLAYER_REJECT 282 // You must select a player to +#define TXT_BASES_ON 283 // Bases On +#define TXT_BASES_OFF 284 // Bases Off +#define TXT_CRATES_ON 285 // Crates On +#define TXT_CRATES_OFF 286 // Crates Off +#define TXT_AI_PLAYERS_ON 287 // AI Players On +#define TXT_AI_PLAYERS_OFF 288 // AI Players Off +#define TXT_SCENARIOS 289 // Scenarios +#define TXT_START_CREDITS 290 // Starting Credits +#define TXT_ONLY_ONE 291 // Only one player? +#define TXT_OOPS 292 // Oops! +#define TXT_TO 293 // To %s: +#define TXT_TO_ALL 294 // To All: +#define TXT_MESSAGE 295 // Message: +#define TXT_CONNECTION_LOST 296 // Connection to %s lost! +#define TXT_LEFT_GAME 297 // %s has left the game. +#define TXT_PLAYER_DEFEATED 298 // %s has been defeated! +#define TXT_WAITING_CONNECT 299 // Waiting to Connect... +#define TXT_NULL_CONNERR_CHECK_CABLES 300 // Connection error! Check +#define TXT_MODEM_CONNERR_REDIALING 301 // Connection +#define TXT_MODEM_CONNERR_WAITING 302 // Connection error! Waiting +#define TXT_SELECT_SERIAL_GAME 303 // Select Serial Game +#define TXT_DIAL_MODEM 304 // Dial Modem +#define TXT_ANSWER_MODEM 305 // Answer Modem +#define TXT_NULL_MODEM 306 // Null Modem +#define TXT_SETTINGS 307 // Settings +#define TXT_PORT_COLON 308 // Port: +#define TXT_IRQ_COLON 309 // IRQ: +#define TXT_BAUD_COLON 310 // Baud: +#define TXT_INIT_STRING 311 // Init String: +#define TXT_CWAIT_STRING 312 // Call Waiting String: +#define TXT_TONE_BUTTON 313 // Tone Dialing +#define TXT_PULSE_BUTTON 314 // Pulse Dialing +#define TXT_HOST_SERIAL_GAME 315 // Host Serial Game +#define TXT_OPPONENT_COLON 316 // Opponent: +#define TXT_USER_SIGNED_OFF 317 // User signed off! +#define TXT_JOIN_SERIAL_GAME 318 // Join Serial Game +#define TXT_PHONE_LIST 319 // Phone List +#define TXT_ADD 320 // Add +#define TXT_EDIT 321 // Edit +#define TXT_DIAL 322 // Dial +#define TXT_DEFAULT 323 // Default +#define TXT_DEFAULT_SETTINGS 324 // Default Settings +#define TXT_CUSTOM_SETTINGS 325 // Custom Settings +#define TXT_PHONE_LISTING 326 // Phone Listing +#define TXT_NAME_COLON 327 // Name: +#define TXT_NUMBER_COLON 328 // Number: +#define TXT_UNABLE_FIND_MODEM 329 // Unable to find modem. Check +#define TXT_NO_CARRIER 330 // No carrier. +#define TXT_LINE_BUSY 331 // Line busy. +#define TXT_NUMBER_INVALID 332 // Number invalid. +#define TXT_SYSTEM_NOT_RESPONDING 333 // Other system not +#define TXT_OUT_OF_SYNC 334 // Games are out of sync! +#define TXT_PACKET_TOO_LATE 335 // Packet received too late! +#define TXT_PLAYER_LEFT_GAME 336 // Other player has left the +#define TXT_FROM 337 // From %s:%s +#define TXT_MAP_P01 338 // 2,728,000 +#define TXT_MAP_P02 339 // 38,385,000 +#define TXT_MAP_P03 340 // 10,373,000 +#define TXT_MAP_P04 341 // 51,994,000 +#define TXT_MAP_P05 342 // 80,387,000 +#define TXT_MAP_P06 343 // 10,400,000 +#define TXT_MAP_P07 344 // 5,300,000 +#define TXT_MAP_P08 345 // 7,867,000 +#define TXT_MAP_P09 346 // 10,333,000 +#define TXT_MAP_P10 347 // 1,974,000 +#define TXT_MAP_P11 348 // 23,169,000 +#define TXT_MAP_P12 349 // 10,064,000 +#define TXT_MAP_P13 350 // 3,285,000 +#define TXT_MAP_P14 351 // 8,868,000 +#define TXT_MAP_P15 352 // 10,337,000 +#define TXT_MAP_P16 353 // 4,365,000 +#define TXT_MAP_P17 354 // 1,607,000 +#define TXT_MAP_P18 355 // 4,485,000 +#define TXT_MAP_P19 356 // 56,386,000 +#define TXT_MAP_P20 357 // 28,305,000 +#define TXT_MAP_P21 358 // 5,238,000 +#define TXT_MAP_P22 359 // 2,059,000 +#define TXT_MAP_P23 360 // 13,497,000 +#define TXT_MAP_P24 361 // 4,997,000 +#define TXT_MAP_P25 362 // 88,500,000 +#define TXT_MAP_P26 363 // 1,106,000 +#define TXT_MAP_P27 364 // 12,658,000 +#define TXT_MAP_P28 365 // 3,029,000 +#define TXT_MAP_P29 366 // 39,084,000 +#define TXT_MAP_P30 367 // 23,154,000 +#define TXT_MAP_P31 368 // 8,902,000 +#define TXT_MAP_P32 369 // 27,791,000 +#define TXT_MAP_P33 370 // 1,574,000 +#define TXT_MAP_P34 371 // 15,469,000 +#define TXT_MAP_P35 372 // 1,300,000 +#define TXT_MAP_P36 373 // 41,688,000 +#define TXT_MAP_A00 374 // 24,900 SQ. MI. +#define TXT_MAP_A01 375 // 120,727 SQ. MI. +#define TXT_MAP_A02 376 // 80,134 SQ. MI. +#define TXT_MAP_A03 377 // 233,100 SQ. MI. +#define TXT_MAP_A04 378 // 137,838 SQ. MI. +#define TXT_MAP_A05 379 // 30,449 SQ. MI. +#define TXT_MAP_A06 380 // 18,932 SQ. MI. +#define TXT_MAP_A07 381 // 32,377 SQ. MI. +#define TXT_MAP_A08 382 // 35,919 SQ. MI. +#define TXT_MAP_A09 383 // 7,819 SQ. MI. +#define TXT_MAP_A10 384 // 91,699 SQ. MI. +#define TXT_MAP_A11 385 // 51,146 SQ. MI. +#define TXT_MAP_A12 386 // 11,100 SQ. MI. +#define TXT_MAP_A13 387 // 44,365 SQ. MI. +#define TXT_MAP_A14 388 // 39,449 SQ. MI. +#define TXT_MAP_A15 389 // 19,741 SQ. MI. +#define TXT_MAP_A16 390 // 17,413 SQ. MI. +#define TXT_MAP_C00 391 // RIGA +#define TXT_MAP_C01 392 // WARSAW +#define TXT_MAP_C02 393 // MINSK +#define TXT_MAP_C03 394 // KIEV +#define TXT_MAP_C04 395 // BERLIN +#define TXT_MAP_C05 396 // PRAGUE +#define TXT_MAP_C06 397 // BRATISLAVA +#define TXT_MAP_C07 398 // VIENNA +#define TXT_MAP_C08 399 // BUDAPEST +#define TXT_MAP_C09 400 // LJUBLJANA +#define TXT_MAP_C10 401 // BUCHAREST +#define TXT_MAP_C11 402 // ATHENS +#define TXT_MAP_C12 403 // TIRANA +#define TXT_MAP_C13 404 // SOFIA +#define TXT_MAP_C14 405 // BELGRADE +#define TXT_MAP_C15 406 // SARAJEVO +#define TXT_MAP_C16 407 // TALLINN +#define TXT_MAP_C17 408 // TRIPOLI +#define TXT_MAP_C18 409 // CAIRO +#define TXT_MAP_C19 410 // KHARTOUM +#define TXT_MAP_C20 411 // N'DJAMENA +#define TXT_MAP_C21 412 // NOUAKCHOTT +#define TXT_MAP_C22 413 // YAMOUSSOUKRO +#define TXT_MAP_C23 414 // PORTO-NOVO +#define TXT_MAP_C24 415 // ABUJA +#define TXT_MAP_C25 416 // LIBREVILLE +#define TXT_MAP_C26 417 // YAOUNDE +#define TXT_MAP_C27 418 // BANGUI +#define TXT_MAP_C28 419 // KINSHASA +#define TXT_MAP_C29 420 // CAIRO +#define TXT_MAP_C30 421 // LUANDA +#define TXT_MAP_C31 422 // DAR-ES-SALAAM +#define TXT_MAP_C32 423 // WINDHOEK +#define TXT_MAP_C33 424 // MAPUTO +#define TXT_MAP_C34 425 // GABARONE +#define TXT_MAP_C35 426 // CAPE TOWN +#define TXT_MAP_GDP00 427 // NEGLIGIBLE +#define TXT_MAP_GDP01 428 // $162.7 BLN +#define TXT_MAP_GDP02 429 // $47.6 BLN +#define TXT_MAP_GDP03 430 // $1,131 BLN +#define TXT_MAP_GDP04 431 // $120 BLN +#define TXT_MAP_GDP05 432 // $164 BLN +#define TXT_MAP_GDP06 433 // $60.1 BLN +#define TXT_MAP_GDP07 434 // $21 BLN +#define TXT_MAP_GDP08 435 // $71.9 BLN +#define TXT_MAP_GDP09 436 // $77 BLN +#define TXT_MAP_GDP10 437 // $4.0 BLN +#define TXT_MAP_GDP11 438 // $47.3 BLN +#define TXT_MAP_GDP12 439 // $120.1 BLN +#define TXT_MAP_GDP13 440 // $14.0 BLN +#define TXT_MAP_GDP14 441 // $28.9 BLN +#define TXT_MAP_GDP15 442 // $39.2 BLN +#define TXT_MAP_GDP16 443 // $12.1 BLN +#define TXT_MAP_GDP17 444 // $1.0 BLN +#define TXT_MAP_GDP18 445 // $10.0 BLN +#define TXT_MAP_GDP19 446 // $1.7 BLN +#define TXT_MAP_GDP20 447 // $28.0 BLN +#define TXT_MAP_GDP21 448 // $5.3 BLN +#define TXT_MAP_GDP22 449 // $11.6 BLN +#define TXT_MAP_GDP23 450 // $1.3 BLN +#define TXT_MAP_GDP24 451 // $6.6 BLN +#define TXT_MAP_GDP25 452 // $8.3 BLN +#define TXT_MAP_GDP26 453 // $6.9 BLN +#define TXT_MAP_GDP27 454 // $2.0 BLN +#define TXT_MAP_GDP28 455 // $3.1 BLN +#define TXT_MAP_GDP29 456 // $104.0 BLN +#define TXT_MAP_PC00 457 // JELGAVA +#define TXT_MAP_PC01 458 // GDANSK +#define TXT_MAP_PC02 459 // BYELISTOK +#define TXT_MAP_PC03 460 // BOBYRUSK +#define TXT_MAP_PC04 461 // IVANO-FRANKOVSK +#define TXT_MAP_PC05 462 // HANOVER +#define TXT_MAP_PC06 463 // DRESDEN +#define TXT_MAP_PC07 464 // OSTRAVA +#define TXT_MAP_PC08 465 // BRATISLAVA +#define TXT_MAP_PC09 466 // SALZBURG +#define TXT_MAP_PC10 467 // BUDAPEST +#define TXT_MAP_PC11 468 // TRIESTE +#define TXT_MAP_PC12 469 // ARAD +#define TXT_MAP_PC13 470 // CORINTH +#define TXT_MAP_PC14 471 // SHKODER +#define TXT_MAP_PC15 472 // SOFIA +#define TXT_MAP_PC16 473 // NIS +#define TXT_MAP_PC17 474 // BELGRADE +#define TXT_MAP_PC18 475 // ? +#define TXT_MAP_PC19 476 // PARNU +#define TXT_MAP_PC20 477 // TMASSAH +#define TXT_MAP_PC21 478 // AL-ALAMYN +#define TXT_MAP_PC22 479 // AL-KHARIJAH +#define TXT_MAP_PC23 480 // AL-UBAYYID +#define TXT_MAP_PC24 481 // KAFIA-KINGI +#define TXT_MAP_PC25 482 // OUM HADJER +#define TXT_MAP_PC26 483 // MAO +#define TXT_MAP_PC27 484 // TIDJIKDJA +#define TXT_MAP_PC28 485 // ABIDJAN +#define TXT_MAP_PC29 486 // PORTO-NOVO +#define TXT_MAP_PC30 487 // ABUJA +#define TXT_MAP_PC31 488 // KOULA-MOUTOU +#define TXT_MAP_PC32 489 // BERTOUA +#define TXT_MAP_PC33 490 // BANGASSOU +#define TXT_MAP_PC34 491 // LODJA +#define TXT_MAP_PC35 492 // KINSHASA +#define TXT_MAP_PC36 493 // LUXOR +#define TXT_MAP_PC37 494 // CAIUNDO +#define TXT_MAP_PC38 495 // MZUZU +#define TXT_MAP_PC39 496 // KEETMANSHOOP +#define TXT_MAP_PC40 497 // XAI-XAI +#define TXT_MAP_PC41 498 // GHANZI +#define TXT_MAP_PC42 499 // CAPE TOWN +#define TXT_MAP_GDI 500 // GDI PROGRESSION +#define TXT_MAP_NOD 501 // NOD PROGRESSION +#define TXT_MAP_LOCATE 502 // LOCATING COORDINATES +#define TXT_MAP_NEXT_MISSION 503 // OF NEXT MISSION +#define TXT_MAP_SELECT 504 // SELECT TERRITORY +#define TXT_MAP_TO_ATTACK 505 // TO ATTACK +#define TXT_MAP_GDISTAT0 506 // POPULATION: +#define TXT_MAP_GDISTAT1 507 // GEOGRAPHIC AREA: +#define TXT_MAP_GDISTAT2 508 // CAPITAL: +#define TXT_MAP_GDISTAT3 509 // GOVERNMENT: +#define TXT_MAP_GDISTAT4 510 // GROSS DOMESTIC PRODUCT: +#define TXT_MAP_GDISTAT5 511 // POINT OF CONFLICT: +#define TXT_MAP_GDISTAT6 512 // MILITARY POWER: +#define TXT_MAP_NODSTAT0 513 // EXPENDABILITY: +#define TXT_MAP_NODSTAT1 514 // GOVT CORRUPTABILITY: +#define TXT_MAP_NODSTAT2 515 // NET WORTH: +#define TXT_MAP_NODSTAT3 516 // MILITARY STRENGTH: +#define TXT_MAP_NODSTAT4 517 // MILITARY RESISTANCE: +#define TXT_MAP_COUNTRYNAME0 518 // LATVIA +#define TXT_MAP_COUNTRYNAME1 519 // POLAND +#define TXT_MAP_COUNTRYNAME2 520 // BELARUS +#define TXT_MAP_COUNTRYNAME3 521 // UKRAINE +#define TXT_MAP_COUNTRYNAME4 522 // GERMANY +#define TXT_MAP_COUNTRYNAME5 523 // CZECH REPUBLIC +#define TXT_MAP_COUNTRYNAME6 524 // SLOVAKIA +#define TXT_MAP_COUNTRYNAME7 525 // AUSTRIA +#define TXT_MAP_COUNTRYNAME8 526 // HUNGARY +#define TXT_MAP_COUNTRYNAME9 527 // SLOVENIA +#define TXT_MAP_COUNTRYNAME10 528 // ROMANIA +#define TXT_MAP_COUNTRYNAME11 529 // GREECE +#define TXT_MAP_COUNTRYNAME12 530 // ALBANIA +#define TXT_MAP_COUNTRYNAME13 531 // BULGARIA +#define TXT_MAP_COUNTRYNAME14 532 // YUGOSLAVIA +#define TXT_MAP_COUNTRYNAME15 533 // BOSNIA/HERZOGOVINA +#define TXT_MAP_COUNTRYNAME16 534 // LIBYA +#define TXT_MAP_COUNTRYNAME17 535 // EGYPT +#define TXT_MAP_COUNTRYNAME18 536 // SUDAN +#define TXT_MAP_COUNTRYNAME19 537 // CHAD +#define TXT_MAP_COUNTRYNAME20 538 // MAURITANIA +#define TXT_MAP_COUNTRYNAME21 539 // IVORY COAST +#define TXT_MAP_COUNTRYNAME22 540 // BENIN +#define TXT_MAP_COUNTRYNAME23 541 // NIGERIA +#define TXT_MAP_COUNTRYNAME24 542 // GABON +#define TXT_MAP_COUNTRYNAME25 543 // CAMEROON +#define TXT_MAP_COUNTRYNAME26 544 // CENTRAL AFRICAN REPUBLIC +#define TXT_MAP_COUNTRYNAME27 545 // ZAIRE +#define TXT_MAP_COUNTRYNAME28 546 // ANGOLA +#define TXT_MAP_COUNTRYNAME29 547 // TANZANIA +#define TXT_MAP_COUNTRYNAME30 548 // NAMIBIA +#define TXT_MAP_COUNTRYNAME31 549 // MOZAMBIQUE +#define TXT_MAP_COUNTRYNAME32 550 // BOTSWANA +#define TXT_MAP_COUNTRYNAME33 551 // SOUTH AFRICA +#define TXT_MAP_COUNTRYNAME34 552 // ESTONIA +#define TXT_MAP_GOVT0 553 // REPUBLIC +#define TXT_MAP_GOVT1 554 // DEMOCRATIC STATE +#define TXT_MAP_GOVT2 555 // FEDERAL REPUBLIC +#define TXT_MAP_GOVT3 556 // CONST. REPUBLIC +#define TXT_MAP_GOVT4 557 // PARL. DEMOCRACY +#define TXT_MAP_GOVT5 558 // PRES. PARL. REPUBLIC +#define TXT_MAP_GOVT6 559 // DEMOCRACY +#define TXT_MAP_GOVT7 560 // IN TRANSITION +#define TXT_MAP_GOVT8 561 // ISLAMIC SOCIALIST +#define TXT_MAP_GOVT9 562 // MILITARY +#define TXT_MAP_GOVT10 563 // ISLAMIC REPUBLIC +#define TXT_MAP_GOVT11 564 // PARL. REPUBLIC +#define TXT_MAP_ARMY0 565 // LOCAL MILITIA +#define TXT_MAP_ARMY1 566 // STATE MILITIA +#define TXT_MAP_ARMY2 567 // NATIONAL GUARD +#define TXT_MAP_ARMY3 568 // FREE STANDING ARMY +#define TXT_MAP_ARMY4 569 // ? +#define TXT_MAP_ARMY5 570 // NATIONAL POWER +#define TXT_MAP_MILITARY0 571 // RESPECTABLE +#define TXT_MAP_MILITARY1 572 // FORMIDABLE +#define TXT_MAP_MILITARY2 573 // LAUGHABLE +#define TXT_MAP_MILITARY3 574 // REASONABLE +#define TXT_MAP_MILITARY4 575 // INSIGNIFICANT +#define TXT_MAP_CLICK2 576 // CLICK TO CONTINUE +#define TXT_MAP_LMH0 577 // LOW +#define TXT_MAP_LMH1 578 // MEDIUM +#define TXT_MAP_LMH2 579 // HIGH +#define TXT_SCORE_TIME 580 // TIME: +#define TXT_SCORE_LEAD 581 // LEADERSHIP: +#define TXT_SCORE_EFFI 582 // EFFICIENCY: +#define TXT_SCORE_TOTA 583 // TOTAL SCORE: +#define TXT_SCORE_CASU 584 // CASUALTIES: +#define TXT_SCORE_NEUT 585 // NEUTRAL: +#define TXT_SCORE_GDI 586 // GDI: +#define TXT_SCORE_BUIL 587 // BUILDINGS LOST +#define TXT_SCORE_BUIL1 588 // BUILDINGS +#define TXT_SCORE_BUIL2 589 // LOST: +#define TXT_SCORE_TOP 590 // TOP SCORES +#define TXT_SCORE_ENDCRED 591 // ENDING CREDITS: +#define TXT_SCORE_TIMEFORMAT1 592 // %dh %dm +#define TXT_SCORE_TIMEFORMAT2 593 // %dm +#define TXT_SCORE_NOD 594 // NOD: +#define TXT_DIALING 595 // Dialing... +#define TXT_DIALING_CANCELED 596 // Dialing Canceled +#define TXT_WAITING_FOR_CALL 597 // Waiting for Call... +#define TXT_ANSWERING_CANCELED 598 // Answering Canceled +#define TXT_E7 599 // Engineer +#define TXT_SPECIAL_OPTIONS 600 // Special Options +#define TXT_VISIBLE_TARGET 601 // Targeting flash visible to +#define TXT_TREE_TARGET 602 // Allow targeting of trees. +#define TXT_MCV_DEPLOY 603 // Allow undeploy of +#define TXT_SMART_DEFENCE 604 // Employ smarter self defense +#define TXT_SLOW_BUILD 605 // Moderate production speed. +#define TXT_THREE_POINT 606 // Use three point turn logic. +#define TXT_TIBERIUM_GROWTH 607 // Tiberium will grow. +#define TXT_TIBERIUM_SPREAD 608 // Tiberium will spread. +#define TXT_ROAD_PIECES 609 // Disable building "bib" +#define TXT_SCATTER 610 // Allow running from +#define TXT_MODEM_OR_LOOPBACK 611 // Not a Null Modem Cable +#define TXT_MAP 612 // Map +#define TXT_FROM_COMPUTER 613 // From Computer: +#define TXT_COMP_MSG1 614 // Prepare to die! +#define TXT_COMP_MSG2 615 // How about a bullet +#define TXT_COMP_MSG3 616 // Incoming! +#define TXT_COMP_MSG4 617 // I see you! +#define TXT_COMP_MSG5 618 // Hey, I'm over here! +#define TXT_COMP_MSG6 619 // Come get some! +#define TXT_COMP_MSG7 620 // I got you! +#define TXT_COMP_MSG8 621 // You humans are never a +#define TXT_COMP_MSG9 622 // Abort, Retry, Ignore? (Ha +#define TXT_COMP_MSG10 623 // Format another? (Just +#define TXT_COMP_MSG11 624 // Beat me and I'll reboot! +#define TXT_COMP_MSG12 625 // You're artificial +#define TXT_COMP_MSG13 626 // My AI is better than your +#define TXT_THEME_AIRSTRIKE 627 // Air Strike +#define TXT_THEME_HEAVYG 628 // Demolition +#define TXT_THEME_J1 629 // Untamed Land +#define TXT_THEME_JDI_V2 630 // Take 'em Out +#define TXT_THEME_RADIO 631 // Radio +#define TXT_THEME_RAIN 632 // Rain In The Night +#define TXT_THEME_IND2 633 // Canyon Chase +#define TXT_THEME_HEART 634 // Heartbreak +#define TXT_BLOSSOM_TREE 635 // Blossom Tree +#define TXT_RESTATE_MISSION 636 // Restate +#define TXT_COMPUTER 637 // Computer +#define TXT_COUNT 638 // Unit Count: +#define TXT_LEVEL 639 // Tech Level: +#define TXT_OPPONENT 640 // Opponent +#define TXT_KILLS_COLON 641 // Kills: +#define TXT_VIDEO 642 // Video +#define TXT_C10 643 // Nikoomba +#define TXT_CAPTURE_THE_FLAG 644 // Capture The Flag +#define TXT_THEME_VALK 645 // Ride of the Valkyries +#define TXT_OBJECTIVE 646 // Mission Objective +#define TXT_MISSION 647 // Mission +#define TXT_NO_SAVES 648 // No saved games available. +#define TXT_CIVILIAN_BUILDING 649 // Civilian Building +#define TXT_TECHNICIAN 650 // Technician +#define TXT_VISCEROID 651 // Visceroid +#define TXT_NO_SAVELOAD 652 // Save game options are not +#define TXT_DEFENDER_ADVANTAGE 653 // Defender has the advantage. +#define TXT_SHOW_NAMES 654 // Show true object names. +#define TXT_DELPHI 655 // Agent Delphi +#define TXT_TO_REPLAY 656 // Would you like to replay +#define TXT_RECONN_TO 657 // Reconnecting to %s. +#define TXT_PLEASE_WAIT 658 // Please wait %02d seconds. +#define TXT_SURRENDER 659 // Do you wish to surrender? +#define TXT_GDI_NAME 660 // GLOBAL DEFENSE INITIATIVE +#define TXT_NOD_NAME 661 // BROTHERHOOD OF NOD +#define TXT_SEL_TRANS 662 // SELECT TRANSMISSION +#define TXT_GAMENAME_MUSTBE_UNIQUE 663 // Your game name must be +#define TXT_GAME_IS_CLOSED 664 // Game is closed. +#define TXT_NAME_MUSTBE_UNIQUE 665 // Your name must be unique. +#define TXT_RECONNECTING_TO 666 // Reconnecting to %s +#define TXT_WAITING_FOR_CONNECTIONS 667 // Waiting for connections... +#define TXT_TIME_ALLOWED 668 // Time allowed: %02d seconds +#define TXT_PRESS_ESC 669 // Press ESC to cancel. +#define TXT_JUST_YOU_AND_ME 670 // From Computer: It's just +#define TXT_CAPTURE_THE_FLAG_COLON 671 // Capture the Flag: +#define TXT_CHAN 672 // Dr. Chan +#define TXT_HAS_ALLIED 673 // %s has allied with %s +#define TXT_AT_WAR 674 // %s declares war on %s +#define TXT_SEL_TARGET 675 // Select a target +#define TXT_SEPARATE_HELIPAD 676 // Allow separate helipad +#define TXT_RESIGN 677 // Resign Game +#define TXT_TIBERIUM_FAST 678 // Tiberium grows quickly. +#define TXT_ANSWERING 679 // Answering... +#define TXT_INITIALIZING_MODEM 680 // Initializing Modem... +#define TXT_SCENARIOS_DO_NOT_MATCH 681 // Scenarios don't match. +#define TXT_POWER_OUTPUT 682 // Power Output +#define TXT_POWER_OUTPUT_LOW 683 // Power Output (low) +#define TXT_CONTINUE 684 // Continue +#define TXT_QUEUE_FULL 685 // Data Queue Overflow +#define TXT_SPECIAL_WARNING 686 // %s changed game options! +#define TXT_CD_DIALOG_1 687 // Please insert a Command & +#define TXT_CD_DIALOG_2 688 // Please insert CD %d (%s) +#define TXT_CD_ERROR1 689 // Command & Conquer is unable +#define TXT_NO_SOUND_CARD 690 // No Sound Card Detected +#define TXT_UNKNOWN 691 // UNKNOWN +#define TXT_OLD_GAME 692 // (old) +#define TXT_NO_SPACE 693 // Insufficient Disk Space to +#define TXT_MUST_HAVE_SPACE 694 // You must have %d megabytes +#define TXT_RUN_SETUP 695 // Run SETUP program first. +#define TXT_WAITING_FOR_OPPONENT 696 // Waiting for Opponent +#define TXT_SELECT_SETTINGS 697 // Please select 'Settings' to +#define TXT_PRISON 698 // Prison +#define TXT_GAME_WAS_SAVED 699 // Game Saved +#define TXT_SPACE_CANT_SAVE 700 // Insufficient disk space to +#define TXT_INVALID_PORT_ADDRESS 701 // Invalid Port/Address. COM +#define TXT_INVALID_SETTINGS 702 // Invalid Port and/or IRQ +#define TXT_IRQ_ALREADY_IN_USE 703 // IRQ already in use +#define TXT_ABORT 704 // Abort +#define TXT_RESTART 705 // Restart +#define TXT_RESTARTING 706 // Mission is +#define TXT_LOADING 707 // Mission is loading. Please +#define TXT_ERROR_IN_INITSTRING 708 // Error in the InitString +#define TXT_ORDER_INFO 709 // Order Info +#define TXT_SCENES 710 // Scenes +#define TXT_NEW_MISSIONS 711 // New Missions +#define TXT_THEME_CHRG 712 // Depth Charge +#define TXT_THEME_DRON 713 // Drone +#define TXT_THEME_FIST 714 // Iron Fist +#define TXT_THEME_CREP 715 // Creeping Upon +#define TXT_THEME_80MX 716 // C&C 80's Mix +#define TXT_THEME_DRIL 717 // Drill +#define TXT_CD_DIALOG_3 718 // Please insert the Covert +#define TXT_THEME_RECON 719 // Recon +#define TXT_THEME_VOICE 720 // Voice Rhythm +#define TXT_ERROR_NO_INIT 721 // Error - modem did not +#define TXT_NO_FLOW_CONTROL_RESPONSE 722 // Error - Modem failed to +#define TXT_NO_COMPRESSION_RESPONSE 723 // Error - Modem failed to +#define TXT_NO_ERROR_CORRECTION_RESPONSE 724 // Error - Modem failed to +#define TXT_ERROR_NO_DISABLE 725 // Error - unable to disable +#define TXT_ERROR_TOO_MANY 726 // Error - Too many errors +#define TXT_IGNORE 727 // Ignore +#define TXT_CONNECTING 728 // Connecting... Please Wait. +#define TXT_EXPLAIN_REGISTRATION 729 // To play Command & Conquer +#define TXT_REGISTER 730 // Register +#define TXT_ERROR_UNABLE_TO_RUN_WCHAT 731 // Wchat not installed. Please +#define TXT_INTERNET 732 // Internet Game +#define TXT_UNABLE_TO_SET_VIDEO_MODE 733 // Error - Unable to set the +#define TXT_UNABLE_TO_ALLOCATE_PRIMARY_VIDEO_BUFFER 734 // Error - Unable to allocate +#define TXT_NO_DIAL_TONE 735 // No dial tone. Ensure your +#define TXT_MODEM_INITIALISATION 736 // Modem Initialization +#define TXT_DATA_COMPRESSION 737 // Data Compression +#define TXT_ERROR_CORRECTION 738 // Error Correction +#define TXT_HARDWARE_FLOW_CONTROL 739 // Hardware Flow Control +#define TXT_ADVANCED 740 // Advanced +#define TXT_JUST_INTRO 741 // Intro +#define TXT_READING_IMAGE_DATA 742 // READING IMAGE DATA +#define TXT_ANALYZING 743 // ANALYZING +#define TXT_ENHANCING_IMAGE_DATA 744 // ENHANCING IMAGE DATA +#define TXT_ISOLATING_OPERATIONAL_THEATER 745 // ISOLATING OPERATIONAL +#define TXT_ESTABLISHING_TRADITIONAL_BOUNDARIES 746 // ESTABLISHING TRADITIONAL +#define TXT_FOR_VISUAL_REFERENCE 747 // FOR VISUAL REFERENCE +#define TXT_ENHANCING_IMAGE 748 // ENHANCING IMAGE +#define TXT_BONUS_MISSIONS 749 // Bonus Missions +#define TXT_BONUS_MISSION_1 750 // Bonus Mission 1 +#define TXT_BONUS_MISSION_2 751 // Bonus Mission 2 +#define TXT_BONUS_MISSION_3 752 // Bonus Mission 3 +#define TXT_BONUS_MISSION_4 753 // Bonus Mission 4 +#define TXT_BONUS_MISSION_5 754 // Bonus Mission 5 diff --git a/CONQUER.ICO b/CONQUER.ICO new file mode 100644 index 0000000000000000000000000000000000000000..41cc42c92c823d64fc50efd0b05e30a8518f3fc0 GIT binary patch literal 10134 zcmeHNeNbChc0Ux?pfGET={k|daf!N<8EaE_tYq9+cCj5X5m_bP%qUqlRvOGyk!7Np zjg3;Uqlpo_u+GHlteDlV_YJ}s#!&{q;Vnetzl$;9Q4c84XMcC{GL6S!U( zL-hJPPwxqV$<}G}UvtdSxgY22-gn=*RUSdJlsiW>*~x~mvjjz zp+Qq985V>}A}DAEY~D#`LC~UY6*>hWTEbQ;83e(AiV)T6aKOrf9uGb6K)N}R8THPR zym^HrNrtFFFd9_z3hAT}D!B?zd2{LQmeX&O8lJsZoM_FooH({s%?NerbZbRL+1rM$ zu6*>N6D<`3EiEOLm6aU5V(9egip|-fFv?67CypJH%8m`RbZPaoWfjtpG*qZZ!$?Qk zT&Vww(;H~b>iWl^ElFicNV6Z3RE0ZIzD@(&FmU?BfL52zl%twGqt`1?{n^h>Xy#Co z=(KXD(2Wdv{JJ-G!u~a9_X_0>l_?o2Ri>y!8O55=nd{8l#|*t3QP~}$%^C0%Q596H z!bJO-iKrFbSWx$Jm^LfSqf`Z#`{Aw??i3|gswlk9d7d8u>+3v>vQFH$LSd|N)Uc*i zflm~_>N+=fnt#r2?NJhTLSiMFqmbrK!C<6{0fSjE5VZtLjK)r?Xn8U$pu6nt!LS(% zsw^}Vgf5kyk)+lEqVm0xbQ-d3aPR4sFgnicK0U|^Wj}f17)yMTuUg^tJLBD>v2E|-JcZWnpI z9<;eB5b#nk7@!2~lSz?MsYz-_oY=?+h2n9Fq3j6+s2ydG*GnVg<0PVQ0!ii0wswLHGzE zehjhW7`KSnNyN6nk3AScjxqAUhZnX%5pxAw59T125GjeACe(ZaWc38e<_(b@b~cU; zJG&=<9Fye57~2se!ec|6F^t3H@E|ATk{Cg*0dgT$5cya=>?1;P%)J*m+8r)xkHjc} zIT*t;vDqDXwqrDbu~NB%%8ec@Pd;z5U>S^dngmaP$H8OZ5pWVLf)n64I0kM9hrs;m z1;Ab~`*VX`U!8Ul0fSuke#vMT5Qn{OM;e zEj~!2$!p@k)qyn%zwU+kh5rA1@WwLz)BEC;%d0(nJki^GQS9rx_}PU?+C*0`i{dH= z4^w|%@5SV$zTV#c-%M>9ym09X74LWt(?tITDD+*rc=6NzhyLzT-;Gt-(ZeM6U%rv- z75n=y|Jx?hXT7V;3H=k7lOHF0FI>DVzCZHq&(@d|)O-K^+_fv0dO_!YAr_hYe05AT zk$mWyIC$mawN0A_hYHbsKA+*epQh8kJ9LGHZ;03a{zF1ZPu}NKWJ6m00Hc53hWnn{&5f*wEJ2Bjd;z_(yI}iUo7ETfXi5-g`flGZ`AZBwkHk75j8^pU*18 z{f57~{eb+|Tj%dkFP4zlyJBut6j^@wY1{4e?0odiG>yFfFGE8TV5v$zUmE=jcMjP$ z%={$!<{rZxjULwet+2km>_7CT{qeut^EWPo{J=AHnk|dEv^>ATv9G4>W!qmrzsF_B z$PXBb`yKE7*YZm>ZHM+gRby*wGn6T7`hWG{<&%|kI{iTA{CeH+ zu*=upe*Qdq*TgOibXQ#+I@jq{mI1)-sF;oM8?9l+b9qb$9SzTwW%77#^C+o!PQ`SH z1Wb1?mgBbn3D50>aRa!3Qvjz5RD@H?DFU2gQPGSM;KPc&%x1Nc1AAKlcp(Vv5Rb(Q z_(jJp{9Q@MDvVQ1CL{JjU>RT)n+^63djY3RPEKH7v-M6v2KQ$G$s;eb|c}0>Dry=!)2n8K=l7$8nzOSjCNV&x5^O$17f( z@d?H*IQtl<1aV$Ra8|biC&Ym*60(9bJw_4W5eLp(3(ihA&UY8iRWHupAkJQg7r2V? z3;Z%h363E*ocVG1VVq(DcCg^wHd<}u!Py+Zd2e&MRgA)zB!-wt*gJqN9KaPW#s-WF z#xW1TB<<{PG7CI4j$Bj76?mlxk1z%aCdXCW!C1l$-#VVK00)@tIM;wB7)Q9_KMuc) z6|7$13g7~JFh)kZ8~NGc*IU32CY*aV=rDG0K*s}o7mx@*X&p4&9d8{p92P67o%NA9x?Yy_%gYSBv?(S|?svp#Iub$Qd z^}~k`N1{DjsHa>@nCAQn>%*46KU`a6dK>!QNlEsJ+y z&?CgO2z0LnvSjb94}&kZEFmdaaEz#c(!GN~?yMY*L?c3WDJCF9F4xWaY;X_t2zlpG z7O;uJKmK^s>*;SHRUZMm^A?f3(-?QaOx5ca zv|&Vq-!En{Abz9oAq2p5b1(GOB%+a?FLLHsM9XZgMUD^|CS5(S;X*8iS-cDVwVL@l z0; zA4QnybUK&g7XUvNc6H&wu5oX*E-Wl$(sSt_RRQ!WH@9#9u^ND%Eia_Q@2qk^XWhQA zV_`u~yKGgA?QTw|pR&WCh*o89y>;u>_J#TMg6r`^tUovJ+G8o24ve~}R$Fg{wk&7l zGu3arIy<-hHCLV8vgg%Y4sW4OD|1Qp@+_X~?9H3ET-Aqmz4qEmKiqR@PnE?k@)frhQel&gU&v-*4FU>YhFF z?DVv9b6x$+!j8=RttMw}^}*_WkMFPA<9+o}8LJ4CUN4^=3)^lz)8uSye8t&VTfM*P zhr8cVQ~WahAKmI`YI~+5=&W`IgU{C1ANa1WzP|sCj^i);TaUN49j$J3`WqXa&I1SX z7SrqGL-of&!M4`crZzuxUpV?KQaf+}3u7Adqfg_CtgU~!5*{%3>FHtx1Oid8*V-?-wpP^x#nB+ts*jJ!K^A40w%jrL$ z&IZ~j)9IOMO#8yNKd87v_LtC}U@8@g$tgb5fqK2QsL3i-2i)9Ai!dOo6S1iydeq?4D#uq7;Y=1ve z*VpB!=0}%PdH)Ej#zC_h7lI|SQuE<%&I?(oc}^9}Ry8~P;;%`h73O|Ta=KiHxbLhr z^TKFhuZ~)mqw9~NFkAZUA@3@VtIsYH;3^VTClk|Mg-ZVS^qmOW0504+=s(b)%?Mth zFcJRvvOyL2MDZ)$JK&;Erv|;ivVpIfR5BK+&_=#41L4-~s+y%{(|~KXPO9t#brl&v zeVFgN%uJmt1`W8I6E-%GY9SOEm=^`}gDvRD8))8%-W?X;x`ADFb{0V!xDm|e%3mHK z17ty+xV;l5El8|LzroDSX2JYW0}1BJm6749PbgsnBLYMVem3$IBhG2F`9U(6jmS$U zv;#@Gol$|h?cClCqe)u!un*;-IqQgNl)Nb5jQ_V z=R*yZT1d1i%2I>bXfR^{8%cAI=FZAW^MD~*f|$jn3_6*JI5pD&SuiG z-#grLYHGN$;t1{`H#Tf+&~7VBrL!qn#+}@ehOK;mS)xr2tEZK%!!5r(##f<*$=Q3> z@0YV(!zupAGbp_#xfl7z{EwXY_dN()K)u6UuC4nl2 zPo2ssTlZc+iQCA0yZX8JL+qUMgSaA;q$Ph~Q19A%F30M7(X0&o_T(+SU3!<*SzlS& zC$ySTX3`AztT?oud~HR zOXt!p%`0KC_bo|-ezn=}?<$1l-AM9F(%F`l=H`<{Zd3#5>?tL+T3t0bdnyg>u2t$p zPb^7zssb;?)r1Adw6+ZzxmGow)Bb5WJ8+PR`jC4Zi9cvj;Bf+fV)Cd2nPs={flpEnp+E9aqJ=(5bHhxwZO zZSCH+gtOMDsQk^n?Kk(f-`v}p@M0Lj|1a-tMC!Z(9OP_zDR?{^#%bz4aAN*Ma!?fI=w)jU26A4^McmOAKv>bdG@TCU-H zd(@k&U;M%A2WvAmzsuKsyPm77O{ahE%=c5u_gwW{x<^xIi>9ZYHF~e=A_Y>7@v)4} gy!ijeaCr)Cqu_Ay|BtD{UaxU@Kl)ptrzpyQ1AizERsaA1 literal 0 HcmV?d00001 diff --git a/CONQUER.IDE b/CONQUER.IDE new file mode 100644 index 0000000000000000000000000000000000000000..66bf576c8a877a8ed8b59d1c7af8204a1838ff65 GIT binary patch literal 42942 zcmeHwdwf>aneIxyT)y0|ga9A#i4h}01VlhCO6~+h5|eOI5e-Qo!H~oxAS&JwDI(rc zE49{IODUz+T5BCD9;f3l4s|*`9cz{GIF840JRHYytm9Z}&-1)%zu(?jd`Zvz+2614 z_j~u+Yp?ft-nG}>d+oK?T6=#LeFHshy&ciYv(Jt;4fJj7Y#)l&boX?ooAD;mJY!yX z@r&19`Gzr{CmU0|F4dG86HYK)r|p{bcA|lNmiX&=8_nc-Tvy5Zn&3Ox-v1$sKP1bPN^D)dZfDfBGpH0asT>ChR_GUz$bnb29#+0b*LbD-xz z=R)U0&xbC6E`%Ah zhBiZ6psmmq(3Q|t(ACgOpqD}~gRX&I4qXeq0(vF14Z0564()(;Lf1n#K)ayb(2dY} z(5s+5&`r=@DCL7a#zf`p(CaG6q?-1=-m5ov4w#uh=_qWXd{#5==y{R87NqW6B1GVrjgBRWeaSlyw=i!^82)RuNp+K)Z7C zidRWQKFbEW%vKM_nm|Qx-Muj!YX}udYa8rVPSzYMVdk@M-9}AgjiQpyw4UyDwoD^Z z(`Z_f>|f`g;#dQzSbzOnZCa_+OnzFfX&D;m?l0dw)K}Hn-`U&YaZH!m%eN~q&7GV2 zwsc0P22Jj(NaMcX9e9Y@xXDl51z?fw0_ zFRVFL!cyk#rpGID)~G5{KAl@T6~~%ZC6$~VJw2Mn8dxQZ$G_c}gy^@sbCk$-N8?vVoDksTUM1fMN2BNSl&`wy0r4#S+lWnRvklckd4fVsRJ}VlV>*=$ss=ofL=-DOBEls5}>PqSw zDx)*1W-TnKs;-I7=qqVl(aH=VXADZc{GC-&y}C6P(PP8Ao(k%HQ`T+FDsMk{H&jVv z`IR@wW`4?QgJpel0hUbPN)Zq_dmxwHCda;|L9{P2#eB4szUb;zbg zeF(?93I}I(HA^AN%uC)&=nyujvn8Y= z$8l5dX2r3Eq#|j9Lj#(|mXnID4>rfr*rHMq-1@#=#j&NO!nC@|#s-@fl@^$vmSQUE zT9=nM8PhKN7?svpFukg-yuPv4n9IB}V9QN~+{>#QR#@&5Y0>#<8K$MRxvrtMwY;Lf z+SGgeZ1Jg}EoCZKG`BQ18&fIiCDI!7(=$wYWo31Jb#r+uj23!%U~5r@by-bgL+b+X zT4XCyh5Ytd8gsVipDj!kwBd|a5L!NLeX1lPeZxR!e_4B?B38d zdZI};eb@A~Z?Su6ZV`@BY1yj{>#WMY zUNSVUs-M-;w|StwQ@HA-_3zuAYi#9T0=vqUt*Gg>ko_B!?lW%#a;(fa@mViV5kA)( z^{KU4&$ZFP&K^9}b#?@s5O5Wp6&>n}a%Hxj|9lEj!LxuVZ>ecm;59gBdFLmeSX6i) z8S316-h30qCR@kw2}XtYp!OdB+T)Xr3a`D*z1^|<0iSqOc;0XB>D$nawndqDeE2k^ z!fR%8N83;vHq7|^#!@a0;V4$tL*Hn1i zeDYG^H8<3`slTUf$ZIb}<*Ce1hwskzuHHUwv1P(1HWlW5Nb+ud^GQxc@-25CV;=K- z^C?foOXt<>-6x~+1n8%ynnB#YhBo&b+rEs-)8Vl6zRkTI-F>k(JD(g?n76_9z5#ol zM&*gpPYWZhv$t&kV98rVo;LmTWHZp&-Mik~XFi3hkawVsUhO{1)2Wxp_FDfy-+FX8 zcx{!aJh}SVRMXc#)ZN!Rh)ovHr&<-337Y*l!@0$#^NCl5{F}N52SMPaN91YPPfs?R zI(s({dRRUstB`vW+U^6_atpa5@&xUthnViA;aP1M7Jl3MJ3DZSE0#~)DtuN$dMu4k z<0>pKd-_|Sd`ef5w6<%}va$Qhr*{?RcjI7JCtB-x>bL29vR7exH=EXQ`-OK5`P8q% z>!Le$O-7_Qz)uS?4Lub$PwXX75gh6XJuldMpu+ri^=%%s=Tt;`6^5k+t_AissPMRL z=;>S6)-!1C@O-i-LWMrplh#XP4~2@P_4T3PZC=@Pp~7-s-*YW`J8b!|M?-}^*Y~uc z1P5@OZ9029RCw*L@9ypBZyV~eexuS8;->?T6M693LDUJ>H+xA`SQZ@v-COkfV(*Cx zeRp&ZqF%W+cI~rgMTN(%qq_~Kg2>kb!OcdmcV4hJMuk|kZU_4YOsp)~Q=`K2 zY9HvlM)!$5I4aB+(r_9NnqF_8*t?^`eQNLP8|d)Ln!P?MHm!Hi-b16(8|0@UPkoy< z_2IHy?eVgwNJX$+eQldOUiKiVNPc;idpPzisR%B1PDP~0X;>QSS6dH7q$g^4T6>os z8}?ACu>96-9vWg^ZxWwT>Af1B-qVBo4SK%pdBdJ96`nWiHh1@QboXvBUOIcdR9MFA zY(Fe|-YlLyVk+X_yC0@XdPI85{B%5L#P&HVy=Z|CPXjKHANJs>a9`Tm+dBtQo$r?Xlt}NMpPnvl z=9ZSS$|khy?eklS^!}+$GTL7?D;g?W>lz!%mKt-t@XVCnLEmPqxoE+fipn`_@JL<1 zqN;k0ZO#KQ;Mub$Q6%~4QJ#}i&Hez{Id26|3ljS;7dL=bI*~IZ* zgYUq_Xvr zChj77(`}ivr&lFTnU}Y;R5!N*@TS;DrB~RuPcwM5ym&=5GievhBM+$zK`ZCuepe3zHb9&nXp zT$ft5S9gucTG*6qe zJpb(BSMl;!*%-SfqSE_6EPX|Db1?rL8Bk&QRyEhHWclWL{2VndK`JtP#9)jzp+1y|H3hb!DJ@IZB~I{+8DATITQXJv>J+ zR9OBs_2n%~1J?paH&n=9v7)}--~Wh=eE8|;$Eeq17nKnaKMnhCPd)T^+jEDbB`VBY zCC&}*+5s<}BPl8@yA=&}vS+q0t3*aw{Pc9wT8%#6@>cF%MtvrIl*lNIZ0+A?j?$<|K9)9?^Au?G^1%@t6}bk=o2siMD;Ceu9TleI3TQ5`<88^NbL2;b z`%&IdM|zZzc_!tfl=Rf8 zsb5WfF7=nGpQL7or-f_6>%)7(&xJn;PfA;m)|+-;+6!r~r+t!^ojxtSCVgG{jp>i2 zznK1J`j_dGGAc6GX55(ZSjLMP*_pF5S7yGD`FiGOnd7r6vi4-Xne}DXr0fORE3>y` z@6CQL`bKlPWGIxC5g1nV^ z19^M$4&}X;_hDX2{*?R+@-NN5E`M+SGx@LOf0UnHP*$+4U}M3qf~N{zDR{Rav2ar1 zg2GD+w-nx2c&PBz!uJbP#!MYkGiLpm9b=vt^YWOt$9yrSsAzUkbJ3QfuNJ*p^hMG5 zvCGC@H}=5T*T*Inmldxq-c$Ty@!Q2;7Ec|wY~03iyT%VreVN&m;ql-frxi#TL^Yo;jO*|n7mGdl)P6^|#n1JoZ94TkI%IgU> zEm5{zvxsvvieY1IZCXguQfo~(gt3St<+QB{*|a1{`|$!3PBu+QqwNxNWfw;6O)}Ow zM*&X*;Uw!Y95!1L=2b9m@hO6@Z8G6hbh#ZV7rw;k2gXb{sYtV9+Pu;~MQT{AHSacT z+>4nZNlbY(=_L9MF}EOr`}cMZ*OO)a57=%@NY;Ok^+Yon>zI)sTe4+6DoZ2F1X-Se zr7=^n{KHM(0h=*NvZYW`3uJwatVd*7EL(momj5QpAIkDMS!POVjx3+W7L<^z|5Vn; z%KA%UISK0_b4bP#smG$q-@}MlgC(9qn@^ryw$ubSg*=B~UpDW8US)L=yDOe_& zk8@C-zZK|La=BY`uz8v}PnL6KIZu}JWqH0V7szsRva2A0M=A{iQ zpRDubNH9D(Fw+Z`9fl_dX6nh3b)zvnEx3PnpR?fgRl1jL46{ebN0p?^9WfvFWVAV8 zo-ns8j=vnR|1I6b{N;||FN!CwM3e9Nv$?kWt$EKw`mteL`6F^7Y zZ_AbGz;n+n-x%1uHjJ-m1iq+YN`4@JoMjn2PGgb&cf+`fN8p-n zm@?6vn=sB?f!?l)6}5|JFTCQ4*5>lcYJ{uGn;S@E^EeK+zZk|l-sKJB`2hFvOk-2n zewc11z{<8C3eaA7BeV*wj2_dFG_Qhjl?~gI(YA@e){ZbH@Wg{m#1?WE%UUK%3_XYm zxpE={ zr@yfKeHymgkx^a~&!!zIpMou}kYl@H$`-@+3-*k=z`6`mHW|GcgP93a*b%g_YFNkQ z!r}dts29cutxz|p1*?gggfXUa!5WOp^I^YbP36?eHI*2WHwlw$d7O?sa8@8jaJ78H zeWnQI!ML?4felA$F!;xESlTc);OnAVt zITJPK5RHD9JJZ)*qjd=jtmEFZg*@Jz$WC_>=DVlR4M&oL zHoG00a@cT0IcRg2V^aYejw%Oj_Bb|`u;IvZ(B^K(rV2J$j?F!eO*L#d(j4Tu*RiRA z4M&`VHupI;wXorQ_MpxEj?Gfoa0EJN^MGSh2OEw;2W=j7Y?i@>qtQW|y^hUAu;Hk5 z(B>h>rXDsNoetVO?AR=a4M(VhHjg+q4Y1*S{h-ZP9h*kjaHKkD^QdFf1RIW62W|E_ zHW$N&qt-#2uQ@i&u;KdxL7T@Mn- zh9lZRn(Y@Ts! zHo%4>>%si|(6Q-)4c~bQ+C1ynbi-zrWAh`&W+QAk;vVFAE@orf*;m1)5;nGx%6nE1 zwy+KRQUceWO;}>)%DDXRLcfX3f#v5nz7?rL*nQXW$ax5tL|=_?{G8!@FXVVO;|siy zZ{{%0w{sYB+?e47UdXXt#yN(|upD6`{BR8RufF+agyW-|A9*XnanKV(+qfj!5VauMfP674u5X&iTZZ2M0+wpYiFE!XU^<#^pboLhfBwtq^;cHop_I~Y5* zT(ifPW35XrKK|!pi*oo=sITpHJ9Nsi-5fi%T(ifP{XBYaQzaQJBW({OHDXenbX*N|D0`%ca_X?<|K+L<Z^9Xf z&#N%#L!7gMY1WAOKVg13c%r0+8(2nbe38NDV*8htR0baDN^d7R|zhDc}|zKV0q?%bJ>WT7a^q{x^WcF839gvZsdY< zd5m*P0nd$doO2@&HjObGk3M#8G$G|;Xd4m|a2#h~$i@n}MZ#s?76$g*$VXc9h@34* zX@#zX{YacMh0~74*nKGgCn`(`ububvxi6d(608FYVY4b`BNSNZdA=Gcmq0IOf}E2r zo(Z`{!m}jEk@Ls;9fPzhVw?xW?;OW(5p3FGHXdor?>eNkQ<1>^JttO%{E}NFyjX(q0l$-xcC&E)9_RDhxh&@; z&UqDu&8;yTOJ&bze!t*0q-=-Y!vq{(J-@KW3u@?iZKtW5O&=J)V|YOK!9f=bLbyZboQ1OOf_qT+Y8m8KS$=JOmZa z02Z_TgcF;>7X`N4eK`wh563vC%Dya@uRw95^*aqVkHl=0Lci_sRir!$mGd8oyq{Vj zw@A3&+rq$do{hA9aXEjEeIe&#e}q#eV|culb2>P`9+#8n7&*W3M>rb;`LyLT1DsF9 zIDa{r=k^uy9SUx=e#>C<&6tf+=ohq}M9R0IO-#VK?F+d@!jk4CSk7~h_EcO>eiPx_ zNZAjSGLL#db2KBh)^c8hTwbZ05!!v33C{1tIQPoFtTP9&g&VEkS+MzT%tk3_!*4Ad zMEE_Z)^(bWD>t?;ZT?2j?`)(U0?HWHb(f0Y4(B>OcbJV*X#Ma5pr3)>%!FZONN$mE zyA5S&_jeA`ekguV!tW&U+bLfp<3{f9YI7cJo{iZkg?_c{M@V@NI=q~>XQkGXJHA-o zbCLez81K82c;2j+??Z5-E$ex(c|K;NZ119+DL+B*0(7`i zPuib!GN~zf2t6Dpl9!}(q&%N;JY{BTYwG^gW2w<_RrulX3G{1ppikp?nn`a>-=BUg zJ(@9;aWvyZ#*)kjGM~@P%$k|CJ!^lK$u7y>n!PXkIR$>sdc;EU)_NHS;RmxwR~O@eY}dI~fZ8iuAp)1evAOeoubY>Tno%jZ?@ zXFim(!3v>cphZwVSF!Ch4$8L71ZV_05jqJv85)J22Au*u9a;iC13DFYCbSfK7IYf) zY^Xh!XvM4zqlbvOmIGzWJ-7@PVa`HoE!z|w zO!hD$AH$;>{1#onESxvYk6&&)3xeOM3u5vEkziT#tOQ!=lAc1m)pt=p4X?FDzw4*9I`(twbs9WSStj-ix>1$GRzPA z0<0g_BEj$F1u>9w`4J_b%Ni~3u4X;M1mOBQvUb#Sfj-Z!*hrE;q$Zg!&>dd z_{TH`_E#*1wcwHXnDD}3e)!yKF|0L%-=*_8c?YuTTT;HPq!AKqUrhP5)k@)}3N zRl|-CEi8t$_$wn|s)zaUF|752-%$(r3tv2fAKoQ`-&YG_nn%F!t`hw2S`cGJpzVz-8V+STrvWNcj@4F;evUyEsj&mjdyi^0XU9?FB`!RTLHoE#|5zA zH6viyYB)ZEpUX$Uu$AF|V~#D7n5XdC5io3(@GHeu*z$>C;;ux@n|KGIB4JCyfrPgc z@)K(kuS-0X_-Y{vuqKwl1xy8!aZBN6BVA`b(31f|!PAZh|sN_KNpasw|P`(ercNaL1-%6>;?Z8|#c)|CQ64od9 zUS3c9>(ZU@!FL{W(e9y_3lb7wcM*Qv%C{N##sO#9a~Ay>P|kwqEO*Xg=PY&3Lgy@V z&LZb5an1ri2g+I8v!I-XeJ+%WR5NYd$+334j>ogq)HWqxt1-2WJPut4@%;oG&s0%-R~*kafa~>1ENi3F@k0^^I-(mPn!WMdYP^|FoR`^IckEt)Uczd?jB;9LdUQ{O9 z-OQ!sOU63Y<}Wj$wkZWM&08AQOYwdP&kt9UZc#oWlrI(QRDXZNh$$aE%}+m4&U4L` zgcu(Y%Euf~eZF+Wl#d*1z{m5}6`zj?<;%c2)$*Oh{$>XFnDd*#$7{e9e}9QkzAUU$ zEnj_NZBsU4nm>Aa5`4U#T=DscP`(_jQ!U><@Z}<=eDrh_e7rVY@%e~QzC5f`E#C?7 zp?qVoPPKfy!B>Qs z^3l^A@Ui}J#pfeJ`Nm?MYWa?XuNX1qqo>v2V_oKo&qsvvjl(+C@=Z^wZ5of5^3l^a z@UdQW#pfeJ`6ghUYWcQ-FM^oz(bMDLV;$>?&qsvvO~g9Y@*M-;B*c`Do_+>C*4M81 zd_*YUWUNyyUnIGcXD?T3)%6BH# zsg`dI_(~B|K6=^;KHe)`@%e~QzO%4SwR{J`Hw`i6qo;k~<9*i^pN|OTI~(g%%Xbod z(-BiXdU^?byobBu^AVwZGq6szeD$fdO=XBFA3ePXKDGy3@%e~QzH_inwS4=)Hxn`C zqo;7zk#e>zT=DscP`+7Mr&_)f;G2z@^3l_D@UflZiqA)c@|}xys^wc0u5FrwnDWt6 z6ZqKXamD8&Lix_aI@R*+2H#x7l#iZd~A!k z;`0%qeCK1GYWb$8)iy0aO!?^PCGfFb=Zeoqgz_!KI@R)R1K%RVl#ia?10UOnuK0XJ zDBohNQ!U>y@LhnI^3hW``$#$4o38kLL@3{dSf^UPNP2D462z2`o~DD3ZC6)(J|dK_ z9P3oe*8{!^#FUSon!v|)uq!?v5z1GIb*kk%48AJFl#iZc%12L!!N<0~D?T3)%2$VVs^vQfzGa9h zA3ePTK0Y_N;`0%qd>3JzYWYrruO2bwqo<^tBjtPsamD8&Liv_sooe~&Gi#d~5K}&S zDg_^(XI%05h)}*ptWz!DKJYamrhN2N4?aE{x#IH?p?nu(ooe|`fUg-b<)f#5@bNjz z6`zj?gTO!?^P0r2se%@v=I2<2OWb*kmt4Zf9#DIYyO4?aHsx#IH? zp?s^bPPKf;!M7SQ<)f$L;N!ETD?T3)%6AFYsg`egc5Tz8h$)|V$~{uf=TcXEJ|dLw zGOSZA-!|~A3GmTV3HbPo>x$1ugz{aEb*kk%2EMh3Y5wSGDfsxj?26Avgz{a1b*kly zr~5k7p?sZKr&_+u+}ft~h$$aEy#YSI@R(W1m8x)l#iaGc}L3G@8OEiM}+cSg>|asI|;rX#FUSos=&uS z6jyvcB9w0v)~S}SKCiZ^7cu3dr!MfZKgJcGj|k=K!#dUS?E_ywV#-HPyTQl49anrl zB9!lHtWz!D3GfXdrhN4DH2Bz0Zr&_+l;M=y`w&wf^!_n?us_@t-v<%&@dSJ<5ufwLXr+$6mzL2ffdS55GU( z_vHH^fHT*^!=MDdr|`HHR^7lbKMQuqL4^;b6k^2`3Z6iS>!A6Za+VPdt(M zdE%nb($MbE!V|Z z(+lPoY%AD_H>Tbwh!mC<_TY`F!-Yo+GshH_<%)v3wk2yIeTvT7wQ}lGvFNzAs z&KbLP?89S^kNpD<5NA4t@#g*Pz{uCj7?a{SR1wB4!iH~2CCCwD5l76^?ffPvKK_Ey zu(<&gQ;8Z6>om-Cuw!ClV`qzxOn3Zk3Hkog#a~(gb@;Iq@t0xtm|X#XkD0I6(qAw9 zW#HTQV-bch78AmFL?XM-BNn_*_VGlR3)X^It0YWez(VjOD^|TeaQJE6Xy=Oif@v zn9BW_hWuyYD`P3R8bTOJ51}75gps`v`UCN98q|uezSF~U;vDqR$|`@9vdWjTq6ar$1*0700y)Y=(Z7o< z9K~PZi2lZmf*La-s%Vs47RAk_9>KBZ0$}~oXzm4%;3Sm$SbUC>~2Nt{!SRBq$YSJW#@7#QqR!HDEo>p zB;&^`#YIhYP!}ZhRkD0}g-$#LrMaBspvbBy92xvLAJg?U*)*GmILC4}d`-qT2E%e4 zbAB1;n(-YKUNd}$g6}-&{7u$Wnvdux*Ejxq3O{!iE9d%g4w{>{)12#jeX^~)d3~QI z*Ef$huW$F-=6gYIJ;=Gnu7@e3?EMtkdwSrEJeSQqSM-RV?l9iy<(cJqdW>Oannby2fMLCq2r zXYMLWb2-~V{W8JUOst`CrQ^psLs6Q;a~%}ZwT3=dQ1-n`ZjTuCHOE2S7w|PlP{BK% z`Z~`+1$~_-DElt9^`&*qT+{;e5mJW~nd^z$werz8rQcLK3$KS01f9H!o){2VK65?}6@cD)5dNeR&4?oT$ zR+N^|LZ^076x+cIrFP+*V)t$_E8t&qy~y$ZY9QB(#6OwTzvg=JD5bR6yFP~BP1WB8 zj=%Jfe^f3If2`eIe;1D8??UmXSFhFKZ@b89=g2W#&s2O*89(||l$L*ugIXIn-niR4C@sO-QEGr%sR4MrwFF11 z#qr;6!>+AqN&dOERD81qp*dFHbxsMb^xC~VW9s4_ciq=z4)VjmxwcG@+G2HmUgRK| zuIJiCaeZoT>qlvq)l0i9c&_n0GyeDJjzkN zSdQ{Y?d9fCkjSlMXrH(*Lv$HsMZltD@MuXicxd9auk0nNA$O96x6B_ zQL9HmtsW6|$tb8xMnqjY3hL4kQJ0N^x@<($no&?|Mnqjc3hMF^QENv*tvv;$waXRG z`H}9`F05Ivh4r*h-*HwZF_TcC!M2~Z3AM!>acxLy=naxq6$$^mp zMQ(EZ;hmaTFWV;ZmmFvOqSryC1V$k+rhwmRaeVbTsJ+Jb)hDRbfG^Eqzk@muK=lhM zEe`5x2lZkAb+w=};-Cf`)PV#)hXaDjih~+-P#XhfI4G!`IH(~P74m%z2`VoRYO{ly zlH{W{3#uRvYKwzvNb*OGwg_rW9Mm-q>Xj59b&a6L#zAd$P|u|LsI7t;7YB8%gK9|g zQP&D;LLAg}4r*Eeb)BFl#z9^0po%hlU)Kw2avan)2UQk8Z4=aKaZoons9OWa>jpuc z9tU-!gUZkJb9kem&WMA$$w5s^nmkWyx0?iYW*pSb4(jay>SjTm6$f>TgPN4$`?^I? zXU9R^>Yz5J`lwq4H6sq{HV3sJ%}3oPsB_|=wmYb|(|y!-LCuPTy4^u7Gqv*N3ifH; zE~s6HSGJkQ&1PiLG5->A7=Zg-GVBQgSyK>H7E4V)pPzXK~=^< z?Qu|L#z*ZDRCOHG-41Ge0Cl&ZYU7~paZoQO`o8WFR9zg@y$rT@(j(pM&ZR zpzagY@;Ipb9n|_9zvS;1RAU^}0}g6|`IiPQ`3D4baU9fx4(ieX>On!Z#6j(KP&Em@ zuf2j=5eM~cQ)Wd?hBo68k2lZ~q_w|UNE{lWus)K4awXw7J ztAe^b4(d?{^;!V+sGzQhgWBhyCMEj5_6e#j4(e+T>a|oK^)*4Y$3Z>jpl%GH9urh& z9Msnx)cXO{*9Em94(f3S^<0*p!^Z{H9S8Ld2lZ68kNSq7u8Mc%sBa3YFAnNS2elyEenW|Oq9+A)bsW^U98^Q5kNTFN2IHWfa!}dnKI$n! zZH|Nbwu5>t)kl3>P@F@nPdM71-|wLIB>SlSg5peB7xf(n)e!Pg-w_n&!@8&g4k|mr zM;#CpXT7?p?>eYS#z%cuP@LQ9q7FK!ngHscpl*qS`ksS&Es(?S3F@{us6!5FL870- zLxQ?J4(j_3YD=IDzb~je;-H>(Q1>PIzMdA;SK^?4;Gh};NBIYWx-$;y8Rt3n%M|O2 z&#}+QbL?F}4aIr3{Go$A_^tJFhHSq9zs$g|C_EkFzsEco;#kwU`~(=p zF7ahWFRovZ;=ch=G|qno3dZ?wG8D!0--0M=&wmXH#`*6-!8rd#C>ZCz2?gW)SD|2> z|1K1a^IwL7asJy-FwTD+3dZ^GL%}%z^(YwUzY+!G{CA>Yoc~f3jPu`$f^q(9Q83Pb zFAB!_FGj&Q|IH{E=f4^SrpVye?JPw`7cPpIR6bP80Wtt z1>^j8q+p!?k`#>d-;#oH{%cY&&VNq|#`!Nw!8rd-DH!L!Dh1>GccoyQ|FRT}^WT<& zasKO4FwTEp3dZ>_Ou;z+jVT!CzcK~m{CB2cod41kjPu``f^q(9Q!vhdZwkiw4?PsS z#MhmjIR6PGn9hHin14G)bubIqn9q|xP4=(l`9(*{H{qJU=Ww$5RdNE-eU$q2Z2gx<&aeSKf<21~7$ET0h!{#%D8{zUwe4=O?J`uNTGrq@(?Jpr* zfba`1J`)qm_aOcvLT>*Y;WC63*I4_l2pNCb3p2OcFQWG%B;S(=iF@0N7hY?(FGI-f z+Yw$sytn;xFFyA=yL|{D?G7O%-+Nv-{(2j4LfDM$PatGIP9R)|uwk3s{KL@<<123F|lI|zv%f3ue#FWljUhrRF< zFD$*q+O0)cV^BU`{7r=9DZJIDFZ05i5pus?_2T9>YqtO)%cI|mAMnEWyl~QXTMjoP zB<~Ae_$fl-rrmDCD-kjuPkHg<2x(um!-h>>xCRM7)^3d#-sgo!z3@vfob#}?+vtUld*SO|nE8lJukgaHUUx z4}0M!URe65wOi|j4|w6LUTF5&^toQx>) + +// The following functions exhibit variable return modes. +// That is, they may equally-usefully be called for a value +// as called just for their effects. Accordingly we inhibit +// Warning 534 for these functions. +// Feel free to add to or subtract from this list. + +-esym(534,close,creat,fclose,fflush,fprintf,fputc) +-esym(534,fputs,fscanf,fseek,fwrite,lseek,memcpy,memmove,memset) +-esym(534,printf,puts,scanf,sprintf,sscanf,strcat,strcpy) +-esym(534,strncat,strncpy,unlink,write) + +//------------------------------------------------------------------ + +-width(0,0) // don't break up message lines + +// 32 bit integer and pointer size is four bytes. +-si4 -sp4 + +// Include directories +-ic:\projects\c&czero\code\watcom\h;..\vq\include;..\gcl510\h diff --git a/CONQUER.PRO b/CONQUER.PRO new file mode 100644 index 0000000..5c4b2e9 --- /dev/null +++ b/CONQUER.PRO @@ -0,0 +1,864 @@ + + Page 1 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 12/13/1993 +Phase: 1 Time: 22:19:33 +Total Ticks: 2696 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Main_Program 1 | 2618 2618 97.11 | 1020 1020 37.83 | +_Edit_Unit_Offsets | | | + 1 | 1021 1021 37.87 | 941 941 34.90 | +display.c_NONPUB_Refresh_M | | | +ap 1090 | 206 0 7.64 | 200 0 7.42 | +_Self_Regulate 41213 | 122 0 4.53 | 122 0 4.53 | +scenario.c_NONPUB_Do_Brief | | | +ing 1 | 91 91 3.38 | 91 91 3.38 | +_Fancy_Text_Print 938 | 80 0 2.97 | 80 0 2.97 | +_main 1 | 2694 2694 99.93 | 76 76 2.82 | +_Init_Game 1 | 241 241 8.94 | 54 54 2.00 | +_Check_Menu 40124 | 47 0 1.74 | 47 0 1.74 | +_Load_Map 1 | 16 16 0.59 | 16 16 0.59 | +map.c_NONPUB_Smooth_Shadow | | | + 21775 | 11 0 0.41 | 10 0 0.37 | +_Load_A_Icon_Set 3 | 9 3 0.33 | 9 3 0.33 | +_Call_Back 41213 | 126 0 4.67 | 4 0 0.15 | +_Coord_Cell 77858 | 4 0 0.15 | 4 0 0.15 | +_Draw_Map 1090 | 209 0 7.75 | 3 0 0.11 | +_Anim_Update 2179 | 2 0 0.07 | 2 0 0.07 | +_Game_Screen 1 | 3 3 0.11 | 2 2 0.07 | +_Load_System_Strings | | | + 2 | 2 1 0.07 | 2 1 0.07 | +_Map_Cell 4355 | 13 0 0.48 | 2 0 0.07 | +_Shape_Ptr 1685 | 2 0 0.07 | 2 0 0.07 | +_Load_Brains 3 | 1 0 0.04 | 1 0 0.04 | +_Process_Logic 1089 | 5 0 0.19 | 1 0 0.04 | +_Unit_From_ID 2603 | 1 0 0.04 | 1 0 0.04 | +_Unit_Next 8519 | 1 0 0.04 | 1 0 0.04 | +_Unit_Revealed 12676 | 1 0 0.04 | 1 0 0.04 | +__cleanup 1 | 1 1 0.04 | 1 1 0.04 | +scenario.c_NONPUB_Clear_Sc | | | +enario 1 | 1 1 0.04 | 1 1 0.04 | +DGROUP@ 0 | 0 0 0.00 | 0 0 0.00 | +_Adjacent_Cell 0 | 0 0 0.00 | 0 0 0.00 | +_Adjacent_Free_Cell | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Assign_Destination | | | + 22 | 0 0 0.00 | 0 0 0.00 | +_Assign_Order 47 | 0 0 0.00 | 0 0 0.00 | +_Assign_Target 0 | 0 0 0.00 | 0 0 0.00 | +_Attached_Building | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Attached_Unit 0 | 0 0 0.00 | 0 0 0.00 | +_Base_Under_Attack | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Bldg_Chunk_In 0 | 0 0 0.00 | 0 0 0.00 | +_Bldg_Chunk_Out 0 | 0 0 0.00 | 0 0 0.00 | +_Bound_Cursor 59 | 0 0 0.00 | 0 0 0.00 | +_Break_Contact_With | | | + 106 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 2 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 12/13/1993 +Phase: 1 Time: 22:19:33 +Total Ticks: 2696 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Break_Radio_Contact | | | + 53 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Building 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Cancel 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Custom_Render | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Doit_Render 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Enter 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Enter_Render | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Mode 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_OrderSelect 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Repair 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Return 0 | 0 0 0.00 | 0 0 0.00 | +_Buildables 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Alloc 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Create 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Damage 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Delete 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Destroy 0 | 0 0 0.00 | 0 0 0.00 | +_Building_First 6 | 0 0 0.00 | 0 0 0.00 | +_Building_Free 0 | 0 0 0.00 | 0 0 0.00 | +_Building_From_ID 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Logic 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Look 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Next 6 | 0 0 0.00 | 0 0 0.00 | +_Building_Render 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Scan 3 | 0 0 0.00 | 0 0 0.00 | +_Building_Spec 0 | 0 0 0.00 | 0 0 0.00 | +_Building_System_Calibrate | | | + 1 | 0 0 0.00 | 0 0 0.00 | +_Building_System_Shutdown | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Building_System_Startup | | | + 2 | 0 0 0.00 | 0 0 0.00 | +_Building_Threat 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Unlimbo 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Untarget | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Calculated_Cell 0 | 0 0 0.00 | 0 0 0.00 | +_Can_Unit_Enter_Building | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Can_Unit_Enter_Cell | | | + 49 | 0 0 0.00 | 0 0 0.00 | +_Can_Upgrade 0 | 0 0 0.00 | 0 0 0.00 | +_Cardinal_To_Fixed | | | + 20 | 0 0 0.00 | 0 0 0.00 | +_CellXY_Coord 463 | 0 0 0.00 | 0 0 0.00 | +_Cell_Building 4700 | 0 0 0.00 | 0 0 0.00 | +_Cell_Coord 199 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 3 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 12/13/1993 +Phase: 1 Time: 22:19:33 +Total Ticks: 2696 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Cell_Distance 28 | 0 0 0.00 | 0 0 0.00 | +_Cell_Facing 16 | 0 0 0.00 | 0 0 0.00 | +_Cell_Object 15 | 0 0 0.00 | 0 0 0.00 | +_Cell_Unit 5201 | 0 0 0.00 | 0 0 0.00 | +_Cell_X 0 | 0 0 0.00 | 0 0 0.00 | +_Cell_Y 0 | 0 0 0.00 | 0 0 0.00 | +_Center_Map 0 | 0 0 0.00 | 0 0 0.00 | +_Change_State 30 | 3 0 0.11 | 0 0 0.00 | +_Class_From_Name 2 | 0 0 0.00 | 0 0 0.00 | +_Common_Attached 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Available_Building | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Class_Count | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Dialog 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Display_Message | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Distance 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Is_Clear 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Is_Enemy 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Is_Friendly | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Mono_Print | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Nearest_Spice | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Pause 181 | 0 0 0.00 | 0 0 0.00 | +_Common_Pause_Random | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Play_Sound | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Random 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Scenario 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Target_Distance | | | + 283 | 0 0 0.00 | 0 0 0.00 | +_Common_Target_Facing | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Target_Kind | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Target_Type | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Tutor_Message | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_Add 208 | 0 0 0.00 | 0 0 0.00 | +_Coord_Cell_Distance | | | + 454 | 0 0 0.00 | 0 0 0.00 | +_Coord_Distance 1429 | 0 0 0.00 | 0 0 0.00 | +_Coord_Facing16 5 | 0 0 0.00 | 0 0 0.00 | +_Coord_Facing256 1373 | 0 0 0.00 | 0 0 0.00 | +_Coord_Facing8 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 4 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 12/13/1993 +Phase: 1 Time: 22:19:33 +Total Ticks: 2696 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Coord_Mid 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_Move 472 | 0 0 0.00 | 0 0 0.00 | +_Coord_Scatter 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_Snap 712 | 0 0 0.00 | 0 0 0.00 | +_Coord_Spillage_Number | | | + 1100 | 0 0 0.00 | 0 0 0.00 | +_Coord_Sub 454 | 0 0 0.00 | 0 0 0.00 | +_Coord_XCell 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_XPixel 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_YCell 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_YPixel 0 | 0 0 0.00 | 0 0 0.00 | +_Coordinates_In_Region | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Cursor 1175 | 0 0 0.00 | 0 0 0.00 | +_Cursor_Move 57 | 0 0 0.00 | 0 0 0.00 | +_Cursor_Size 0 | 0 0 0.00 | 0 0 0.00 | +_Debug_Callback 0 | 0 0 0.00 | 0 0 0.00 | +_Debug_Key 96 | 1021 11 37.87 | 0 0 0.00 | +_Desired_Facing256 | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Destroy_Wall 0 | 0 0 0.00 | 0 0 0.00 | +_Dialog_Message 0 | 0 0 0.00 | 0 0 0.00 | +_Display_Selected_Terrain | | | + 9 | 0 0 0.00 | 0 0 0.00 | +_Display_Status 1089 | 0 0 0.00 | 0 0 0.00 | +_Do_Button_Setup 0 | 0 0 0.00 | 0 0 0.00 | +_Do_Explosion 20 | 0 0 0.00 | 0 0 0.00 | +_Do_Gas 0 | 0 0 0.00 | 0 0 0.00 | +_Do_Lose 0 | 0 0 0.00 | 0 0 0.00 | +_Do_Win 0 | 0 0 0.00 | 0 0 0.00 | +_Draw_Box 0 | 0 0 0.00 | 0 0 0.00 | +_Draw_Radar 1 | 0 0 0.00 | 0 0 0.00 | +_End_Game 1089 | 0 0 0.00 | 0 0 0.00 | +_Establish_Contact_With | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Establish_Origin 20 | 0 0 0.00 | 0 0 0.00 | +_Establish_Radio_Contact | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Fetch_Text_String | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Fill_In_Data 1 | 13 13 0.48 | 0 0 0.00 | +_Find_Brain_Size 0 | 0 0 0.00 | 0 0 0.00 | +_Find_Path 5 | 0 0 0.00 | 0 0 0.00 | +_Find_Waypoint 0 | 0 0 0.00 | 0 0 0.00 | +_Fire_Bullet 0 | 0 0 0.00 | 0 0 0.00 | +_Fire_Weapon 107 | 1 0 0.04 | 0 0 0.00 | +_Fixed_To_Cardinal | | | + 48 | 0 0 0.00 | 0 0 0.00 | +_Format_Window_String | | | + 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 5 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 12/13/1993 +Phase: 1 Time: 22:19:33 +Total Ticks: 2696 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Free_Brains 0 | 0 0 0.00 | 0 0 0.00 | +_GetPrivateProfileInt | | | + 16 | 0 0 0.00 | 0 0 0.00 | +_GetPrivateProfileString | | | + 41 | 0 0 0.00 | 0 0 0.00 | +_Get_Connection_Info | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Get_Connection_Num | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Get_Internet_Address | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Get_Local_Connection_Numb | | | +er 0 | 0 0 0.00 | 0 0 0.00 | +_Greatest_Building_Threat | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Greatest_Threat 8 | 0 0 0.00 | 0 0 0.00 | +_Greatest_Unit_Noise | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Greatest_Unit_Threat | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Harvester_Check 0 | 0 0 0.00 | 0 0 0.00 | +_House_From_Name 10 | 0 0 0.00 | 0 0 0.00 | +_IPX_Cancel_Event 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Close_Socket 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Get_Local_Target | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Listen_For_Packet | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Open_Socket 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Relinquish_Control | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Schedule_IPX_Event | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Send_Packet 0 | 0 0 0.00 | 0 0 0.00 | +_Icon_Install 0 | 0 0 0.00 | 0 0 0.00 | +_Icon_Remove 0 | 0 0 0.00 | 0 0 0.00 | +_Icon_Update 1090 | 0 0 0.00 | 0 0 0.00 | +_In_Radar 75 | 0 0 0.00 | 0 0 0.00 | +_In_Radio_Contact 0 | 0 0 0.00 | 0 0 0.00 | +_In_View 3 | 0 0 0.00 | 0 0 0.00 | +_Info_Chunk_In 0 | 0 0 0.00 | 0 0 0.00 | +_Info_Chunk_Out 0 | 0 0 0.00 | 0 0 0.00 | +_Init_Shapes 1 | 0 0 0.00 | 0 0 0.00 | +_Init_Thread 109 | 0 0 0.00 | 0 0 0.00 | +_Interrupt_Thread 0 | 0 0 0.00 | 0 0 0.00 | +_Is_Allied 1224 | 0 0 0.00 | 0 0 0.00 | +_Is_Lined_Up 668 | 0 0 0.00 | 0 0 0.00 | +_Is_Mapped 3 | 0 0 0.00 | 0 0 0.00 | +_Language_Name 3 | 0 0 0.00 | 0 0 0.00 | +_Launch_GoldenBB 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 6 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 12/13/1993 +Phase: 1 Time: 22:19:33 +Total Ticks: 2696 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Launch_Special 0 | 0 0 0.00 | 0 0 0.00 | +_Legal_Placement 0 | 0 0 0.00 | 0 0 0.00 | +_Load_Samples 2 | 0 0 0.00 | 0 0 0.00 | +_MO_From_Name 0 | 0 0 0.00 | 0 0 0.00 | +_Make_Button 11 | 0 0 0.00 | 0 0 0.00 | +_Map_Chunk_In 0 | 0 0 0.00 | 0 0 0.00 | +_Map_Chunk_Out 0 | 0 0 0.00 | 0 0 0.00 | +_Map_Edit 0 | 0 0 0.00 | 0 0 0.00 | +_Mouse_Coord 0 | 0 0 0.00 | 0 0 0.00 | +_Nearest_Spice 0 | 0 0 0.00 | 0 0 0.00 | +_Novell_Detect 0 | 0 0 0.00 | 0 0 0.00 | +_Order_From_Name 2 | 0 0 0.00 | 0 0 0.00 | +_Phrase_CallBack 0 | 0 0 0.00 | 0 0 0.00 | +_Pixel_Coordinate 2348 | 0 0 0.00 | 0 0 0.00 | +_Play_Theme 0 | 0 0 0.00 | 0 0 0.00 | +_Player_Alloc 3 | 0 0 0.00 | 0 0 0.00 | +_Player_First 3 | 0 0 0.00 | 0 0 0.00 | +_Player_Free 0 | 0 0 0.00 | 0 0 0.00 | +_Player_From_ID 2428 | 0 0 0.00 | 0 0 0.00 | +_Player_Logic 0 | 0 0 0.00 | 0 0 0.00 | +_Player_Next 12 | 0 0 0.00 | 0 0 0.00 | +_Player_System_Shutdown | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Player_System_Startup | | | + 2 | 0 0 0.00 | 0 0 0.00 | +_Plot_Radar_Pixel 0 | 0 0 0.00 | 0 0 0.00 | +_Plyr_Chunk_In 0 | 0 0 0.00 | 0 0 0.00 | +_Plyr_Chunk_Out 0 | 0 0 0.00 | 0 0 0.00 | +_Power_Effects 0 | 0 0 0.00 | 0 0 0.00 | +_Production_Needs 0 | 0 0 0.00 | 0 0 0.00 | +_Production_Start 0 | 0 0 0.00 | 0 0 0.00 | +_Radar_Checkup 0 | 0 0 0.00 | 0 0 0.00 | +_Radar_Cursor 1090 | 0 0 0.00 | 0 0 0.00 | +_Radar_Pixel 4965 | 0 0 0.00 | 0 0 0.00 | +_Radio_Contact 97 | 0 0 0.00 | 0 0 0.00 | +_Read_Scenario 1 | 34 34 1.26 | 0 0 0.00 | +_Read_Scenario_Ini | | | + 1 | 20 20 0.74 | 0 0 0.00 | +_Read_Scenario_Raw | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Recalc_Storage 3 | 0 0 0.00 | 0 0 0.00 | +_Redistribute_Power | | | + 3 | 0 0 0.00 | 0 0 0.00 | +_Redraw_Objects 0 | 0 0 0.00 | 0 0 0.00 | +_Refresh_Cell 5007 | 0 0 0.00 | 0 0 0.00 | +_Repair_On 0 | 0 0 0.00 | 0 0 0.00 | +_Request_Transport | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Run_Thread 9080 | 0 0 0.00 | 0 0 0.00 | +_Sample_Effect 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 7 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 12/13/1993 +Phase: 1 Time: 22:19:33 +Total Ticks: 2696 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Say_Phrase 2 | 0 0 0.00 | 0 0 0.00 | +_Scroll_Tactical 48 | 0 0 0.00 | 0 0 0.00 | +_Select_House 0 | 0 0 0.00 | 0 0 0.00 | +_Select_Next 1 | 0 0 0.00 | 0 0 0.00 | +_Send_Message 0 | 0 0 0.00 | 0 0 0.00 | +_Set_Unit_Movement | | | + 42 | 0 0 0.00 | 0 0 0.00 | +_Set_Unit_Rotation | | | + 1187 | 0 0 0.00 | 0 0 0.00 | +_Setup_Menu 1 | 0 0 0.00 | 0 0 0.00 | +_Sight_From 14 | 1 0 0.04 | 0 0 0.00 | +_Sound_Effect 31 | 0 0 0.00 | 0 0 0.00 | +_Special_Discovery | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Speed_Adjust 35 | 0 0 0.00 | 0 0 0.00 | +_Speed_From_Name 0 | 0 0 0.00 | 0 0 0.00 | +_Spew_Spice 0 | 0 0 0.00 | 0 0 0.00 | +_Spice_Adjust 0 | 0 0 0.00 | 0 0 0.00 | +_Spice_Discovery 0 | 0 0 0.00 | 0 0 0.00 | +_Start_Scenario 1 | 125 125 4.64 | 0 0 0.00 | +_Start_Thread 38 | 0 0 0.00 | 0 0 0.00 | +_Struct_From_Name 1 | 0 0 0.00 | 0 0 0.00 | +_System_Error 0 | 0 0 0.00 | 0 0 0.00 | +_Target_Build 277 | 0 0 0.00 | 0 0 0.00 | +_Target_Building 22 | 0 0 0.00 | 0 0 0.00 | +_Target_Cell 27 | 0 0 0.00 | 0 0 0.00 | +_Target_Coord 1222 | 1 0 0.04 | 0 0 0.00 | +_Target_Distance 331 | 0 0 0.00 | 0 0 0.00 | +_Target_Kind 2070 | 0 0 0.00 | 0 0 0.00 | +_Target_Legal 437 | 0 0 0.00 | 0 0 0.00 | +_Target_Object 53 | 0 0 0.00 | 0 0 0.00 | +_Target_Unit 91 | 0 0 0.00 | 0 0 0.00 | +_Target_Value 22 | 0 0 0.00 | 0 0 0.00 | +_Team_Alloc 0 | 0 0 0.00 | 0 0 0.00 | +_Team_Chunk_In 0 | 0 0 0.00 | 0 0 0.00 | +_Team_Chunk_Out 0 | 0 0 0.00 | 0 0 0.00 | +_Team_Create 0 | 0 0 0.00 | 0 0 0.00 | +_Team_First 31 | 0 0 0.00 | 0 0 0.00 | +_Team_Free 0 | 0 0 0.00 | 0 0 0.00 | +_Team_From_ID 0 | 0 0 0.00 | 0 0 0.00 | +_Team_Logic 0 | 0 0 0.00 | 0 0 0.00 | +_Team_Next 31 | 0 0 0.00 | 0 0 0.00 | +_Team_System_Calibrate | | | + 1 | 0 0 0.00 | 0 0 0.00 | +_Team_System_Shutdown | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Team_System_Startup | | | + 2 | 0 0 0.00 | 0 0 0.00 | +_Terrain_Cost 32 | 0 0 0.00 | 0 0 0.00 | +_Terrain_Type 65 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 8 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 12/13/1993 +Phase: 1 Time: 22:19:33 +Total Ticks: 2696 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Text_Input 0 | 0 0 0.00 | 0 0 0.00 | +_Text_String 70 | 0 0 0.00 | 0 0 0.00 | +_Track_Func 1089 | 0 0 0.00 | 0 0 0.00 | +_Tutor_Message 2 | 0 0 0.00 | 0 0 0.00 | +_Unit_Alloc 22 | 0 0 0.00 | 0 0 0.00 | +_Unit_Available 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Brainwash 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Check 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Chunk_In 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Chunk_Out 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Create 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Damage 38 | 0 0 0.00 | 0 0 0.00 | +_Unit_Delete 20 | 0 0 0.00 | 0 0 0.00 | +_Unit_Enters_Building | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_First 3316 | 0 0 0.00 | 0 0 0.00 | +_Unit_Free 20 | 0 0 0.00 | 0 0 0.00 | +_Unit_Hidden 31 | 0 0 0.00 | 0 0 0.00 | +_Unit_House 1379 | 0 0 0.00 | 0 0 0.00 | +_Unit_Joins_Team 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Leaves_Team 32 | 0 0 0.00 | 0 0 0.00 | +_Unit_Limbo 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Loaner 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Look 29 | 1 0 0.04 | 0 0 0.00 | +_Unit_Mark 1144 | 0 0 0.00 | 0 0 0.00 | +_Unit_Physics 2295 | 0 0 0.00 | 0 0 0.00 | +_Unit_Rally 18 | 0 0 0.00 | 0 0 0.00 | +_Unit_Select 29 | 0 0 0.00 | 0 0 0.00 | +_Unit_Sort 1090 | 0 0 0.00 | 0 0 0.00 | +_Unit_Stun 1 | 0 0 0.00 | 0 0 0.00 | +_Unit_System_Calibrate | | | + 1 | 0 0 0.00 | 0 0 0.00 | +_Unit_System_Shutdown | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_System_Startup | | | + 2 | 0 0 0.00 | 0 0 0.00 | +_Unit_Threat 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_UnTarget 31 | 0 0 0.00 | 0 0 0.00 | +_Unit_Unlimbo 11 | 0 0 0.00 | 0 0 0.00 | +_Units_Team 8 | 0 0 0.00 | 0 0 0.00 | +_Upgrade_On 0 | 0 0 0.00 | 0 0 0.00 | +_Valid_Thread 1918 | 0 0 0.00 | 0 0 0.00 | +_Wall_Flags 0 | 0 0 0.00 | 0 0 0.00 | +_Window_Box 2 | 0 0 0.00 | 0 0 0.00 | +_WritePrivateProfileInt | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_WritePrivateProfileString | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Write_Scenario 0 | 0 0 0.00 | 0 0 0.00 | +_Write_Scenario_Ini | | | + 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 9 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 12/13/1993 +Phase: 1 Time: 22:19:33 +Total Ticks: 2696 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Write_Scenario_Raw | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_XYPixel_Coord 20 | 0 0 0.00 | 0 0 0.00 | +_XY_Cell 0 | 0 0 0.00 | 0 0 0.00 | +__MMODEL 0 | 0 0 0.00 | 0 0 0.00 | +__checknull 1 | 0 0 0.00 | 0 0 0.00 | +__restorezero 1 | 0 0 0.00 | 0 0 0.00 | +__terminate 1 | 0 0 0.00 | 0 0 0.00 | +_abort 0 | 0 0 0.00 | 0 0 0.00 | +_strtrim 33 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Anim_Remove | | | + 20 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Animation_In | | | +stall 20 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Crash | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Crater | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Illegal | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Normal | | | + 640 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_RTimer | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Repeat | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Reverse | | | + 1280 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Shake | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_ShiftX | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_ShiftY | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Sound | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Stop | | | + 640 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Timer | | | + 1920 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Trigger_ | | | +Bloom 0 | 0 0 0.00 | 0 0 0.00 | +audio.c_NONPUB_Dune_Load_S | | | +ample 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Abort_ | | | +Transport 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Busy_S | | | +tate 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Change | | | +_Facing_One 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 10 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 12/13/1993 +Phase: 1 Time: 22:19:33 +Total Ticks: 2696 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +blogic.c_NONPUB_Cmd_Check_ | | | +Up 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Direct | | | +ion 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Dummy | | | + 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Fire_A | | | +t 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Image | | | + 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Launch | | | +_Unit 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Look | | | + 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Neares | | | +t_Enemy 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Play_S | | | +ound 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Reques | | | +t_Transport 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Self_E | | | +xplode 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Termin | | | +ate_Self 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Unload | | | +_Harvester 0 | 0 0 0.00 | 0 0 0.00 | +conquer.c_NONPUB_Keyboard_ | | | +Process 97 | 1021 11 37.87 | 0 0 0.00 | +conquer.c_NONPUB_MT32_Init | | | + 1 | 0 0 0.00 | 0 0 0.00 | +cursor.c_NONPUB_Cursor_Mar | | | +k 324 | 0 0 0.00 | 0 0 0.00 | +findpath.c_NONPUB_Follow_E | | | +dge 0 | 0 0 0.00 | 0 0 0.00 | +findpath.c_NONPUB_Optimize | | | +_Moves 5 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_IconSet | | | + 0 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Illegal | | | + 0 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Loop | | | + 0 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Map | | | + 0 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Normal | | | + 0 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Repeat | | | + 0 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Sound | | | + 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 11 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 12/13/1993 +Phase: 1 Time: 22:19:33 +Total Ticks: 2696 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +icon.c_NONPUB_Cmd_Stop | | | + 0 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Term | | | + 0 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Timer | | | + 0 | 0 0 0.00 | 0 0 0.00 | +init.c_NONPUB_Init_Globals | | | + 1 | 0 0 0.00 | 0 0 0.00 | +init.c_NONPUB_Load_A_Brain | | | + 3 | 1 0 0.04 | 0 0 0.00 | +loadgame.c_NONPUB_BYTE_In | | | + 0 | 0 0 0.00 | 0 0 0.00 | +loadgame.c_NONPUB_LONG_In | | | + 0 | 0 0 0.00 | 0 0 0.00 | +loadgame.c_NONPUB_Struct_I | | | +n 0 | 0 0 0.00 | 0 0 0.00 | +loadgame.c_NONPUB_Thread_G | | | +ameize 0 | 0 0 0.00 | 0 0 0.00 | +loadgame.c_NONPUB_WORD_In | | | + 0 | 0 0 0.00 | 0 0 0.00 | +physics.c_NONPUB_End_Of_Mo | | | +ve 14 | 0 0 0.00 | 0 0 0.00 | +physics.c_NONPUB_Momentum_ | | | +Physics 938 | 0 0 0.00 | 0 0 0.00 | +physics.c_NONPUB_Rotation_ | | | +Physics 237 | 0 0 0.00 | 0 0 0.00 | +physics.c_NONPUB_Start_Of_ | | | +Move 64 | 0 0 0.00 | 0 0 0.00 | +physics.c_NONPUB_While_Mov | | | +ing 930 | 0 0 0.00 | 0 0 0.00 | +plogic.c_NONPUB_Cmd_Dummy | | | + 0 | 0 0 0.00 | 0 0 0.00 | +plogic.c_NONPUB_Cmd_Harves | | | +ter_Check 0 | 0 0 0.00 | 0 0 0.00 | +plogic.c_NONPUB_Cmd_Sound | | | + 0 | 0 0 0.00 | 0 0 0.00 | +plogic.c_NONPUB_Cmd_Unit_C | | | +reate 0 | 0 0 0.00 | 0 0 0.00 | +savegame.c_NONPUB_BYTE_Out | | | + 0 | 0 0 0.00 | 0 0 0.00 | +savegame.c_NONPUB_LONG_Out | | | + 0 | 0 0 0.00 | 0 0 0.00 | +savegame.c_NONPUB_Struct_O | | | +ut 0 | 0 0 0.00 | 0 0 0.00 | +savegame.c_NONPUB_Thread_N | | | +ormalize 0 | 0 0 0.00 | 0 0 0.00 | +savegame.c_NONPUB_WORD_Out | | | + 0 | 0 0 0.00 | 0 0 0.00 | +scenario.c_NONPUB_Typical_ | | | +Briefing 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 12 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 12/13/1993 +Phase: 1 Time: 22:19:33 +Total Ticks: 2696 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +strinput.c_NONPUB_Input_Cu | | | +rsor 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Attack | | | +_Team_Target 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Change | | | +_MO 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Displa | | | +y_Message 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Dummy | | | + 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Find_T | | | +eam_Target 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Recrui | | | +t 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Regrou | | | +p 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Restor | | | +e_MO 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Team_C | | | +ount 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Team_M | | | +in 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Team_S | | | +pread 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Team_T | | | +arget 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Abort_ | | | +Transport 14 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_All_St | | | +op 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Altern | | | +ate_Target 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Animat | | | +e 3 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Call_F | | | +or_Landing 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Cargo | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Change | | | +_Facing 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Change | | | +_Order 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Death_ | | | +Message 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Deploy | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Dock | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Dummy | | | + 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 13 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 12/13/1993 +Phase: 1 Time: 22:19:33 +Total Ticks: 2696 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +ulogic.c_NONPUB_Cmd_Eject | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Explos | | | +ion 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Face_M | | | +ove_Toward 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Face_T | | | +arCom 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Flash | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Greate | | | +st_Threat 8 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Hot_LZ | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Idle | | | + 6 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Inq_Un | | | +it 862 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Is_Clo | | | +se_To_NavCom 181 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Is_Clo | | | +se_To_TarCom 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Load | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Loaded | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Lock_O | | | +n_To_Target 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Look | | | + 27 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Mine_S | | | +pice 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_MultiE | | | +xplosion 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Noisie | | | +st_Target 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Reques | | | +t_Transport 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Rest | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Set_Hu | | | +lk 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Set_Na | | | +vCom 8 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Set_Sp | | | +eed 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Set_Ta | | | +rCom 16 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Spew_B | | | +odies 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 14 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 12/13/1993 +Phase: 1 Time: 22:19:33 +Total Ticks: 2696 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +ulogic.c_NONPUB_Cmd_Target | | | +_Dir 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Termin | | | +ate_Self 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Threat | | | +_Value 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Travel | | | +_To 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Unload | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Unload | | | +ed_Struct 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Verify | | | +_Contact 0 | 0 0 0.00 | 0 0 0.00 | + +Total Logical Percentage : 362.39 +Total Physical Percentage : 99.93 diff --git a/CONQUER.REP b/CONQUER.REP new file mode 100644 index 0000000..9361013 --- /dev/null +++ b/CONQUER.REP @@ -0,0 +1,880 @@ + + Page 1 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 01/20/1994 +Phase: 1 Time: 17:04:46 +Total Ticks: 2081 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +display.c_NONPUB_Refresh_M | | | +ap 790 | 630 1 30.27 | 620 1 29.79 | +_Call_Back 450547 | 626 0 30.08 | 541 0 26.00 | +_Main_Program 0 | 2071 0 99.52 | 521 0 25.04 | +_Self_Regulate 790 | 156 0 7.50 | 156 0 7.50 | +_Sound_Callback 450547 | 85 0 4.08 | 85 0 4.08 | +_Draw_Map 790 | 667 1 32.05 | 37 0 1.78 | +_Process_Logic 790 | 100 0 4.81 | 21 0 1.01 | +_Icon_Update 790 | 18 0 0.86 | 18 0 0.86 | +_Refresh_Cell 5464 | 9 0 0.43 | 9 0 0.43 | +_Anim_Update 790 | 14 0 0.67 | 7 0 0.34 | +_Unit_Mark 1319 | 15 0 0.72 | 7 0 0.34 | +physics.c_NONPUB_Momentum_ | | | +Physics 570 | 26 0 1.25 | 7 0 0.34 | +_Unit_Physics 1636 | 36 0 1.73 | 5 0 0.24 | +_Coord_Cell 55104 | 4 0 0.19 | 4 0 0.19 | +_Draw_Vehicle 493 | 4 0 0.19 | 4 0 0.19 | +physics.c_NONPUB_While_Mov | | | +ing 553 | 4 0 0.19 | 4 0 0.19 | +anim.c_NONPUB_Cmd_Normal | | | + 256 | 3 0 0.14 | 3 0 0.14 | +$$VMGETPAGE 1 | 2 2 0.10 | 2 2 0.10 | +_Map_Cell 168 | 2 0 0.10 | 2 0 0.10 | +_Unit_First 2054 | 2 0 0.10 | 2 0 0.10 | +_main 1 | 2073 2073 99.62 | 2 2 0.10 | +anim.c_NONPUB_Anim_Remove | | | + 8 | 2 0 0.10 | 2 0 0.10 | +_Can_Unit_Enter_Cell | | | + 540 | 1 0 0.05 | 1 0 0.05 | +_Coord_Distance 1376 | 1 0 0.05 | 1 0 0.05 | +_Coord_Facing256 822 | 1 0 0.05 | 1 0 0.05 | +_Coord_Spillage_Number | | | + 1291 | 1 0 0.05 | 1 0 0.05 | +_Greatest_Threat 8 | 1 0 0.05 | 1 0 0.05 | +_Radio_Contact 45 | 1 0 0.05 | 1 0 0.05 | +_Run_Thread 2430 | 4 0 0.19 | 1 0 0.05 | +_Shape_Ptr 689 | 1 0 0.05 | 1 0 0.05 | +_Target_Coord 773 | 1 0 0.05 | 1 0 0.05 | +_Target_Legal 298 | 1 0 0.05 | 1 0 0.05 | +_Unit_Damage 13 | 1 0 0.05 | 1 0 0.05 | +_Unit_From_ID 1424 | 1 0 0.05 | 1 0 0.05 | +__cleanup 1 | 1 1 0.05 | 1 1 0.05 | +anim.c_NONPUB_Cmd_Reverse | | | + 512 | 2 0 0.10 | 1 0 0.05 | +DGROUP@ 0 | 0 0 0.00 | 0 0 0.00 | +_Adjacent_Cell 0 | 0 0 0.00 | 0 0 0.00 | +_Adjacent_Free_Cell | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Assign_Destination | | | + 5 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 2 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 01/20/1994 +Phase: 1 Time: 17:04:46 +Total Ticks: 2081 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Assign_Order 15 | 0 0 0.00 | 0 0 0.00 | +_Assign_Target 2 | 0 0 0.00 | 0 0 0.00 | +_Attached_Building | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Attached_Unit 0 | 0 0 0.00 | 0 0 0.00 | +_Base_Under_Attack | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Bldg_Chunk_In 0 | 0 0 0.00 | 0 0 0.00 | +_Bldg_Chunk_Out 0 | 0 0 0.00 | 0 0 0.00 | +_Bound_Cursor 0 | 0 0 0.00 | 0 0 0.00 | +_Break_Contact_With | | | + 40 | 0 0 0.00 | 0 0 0.00 | +_Break_Radio_Contact | | | + 20 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Building 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Cancel 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Custom_Render | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Doit_Render 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Enter 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Enter_Render | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Mode 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_OrderSelect 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Repair 0 | 0 0 0.00 | 0 0 0.00 | +_Bttn_Return 0 | 0 0 0.00 | 0 0 0.00 | +_Buildables 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Alloc 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Create 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Damage 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Delete 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Destroy 0 | 0 0 0.00 | 0 0 0.00 | +_Building_First 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Free 0 | 0 0 0.00 | 0 0 0.00 | +_Building_From_ID 369 | 0 0 0.00 | 0 0 0.00 | +_Building_Logic 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Look 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Next 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Render 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Scan 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Spec 0 | 0 0 0.00 | 0 0 0.00 | +_Building_System_Calibrate | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Building_System_Shutdown | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Building_System_Startup | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Threat 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Unlimbo 0 | 0 0 0.00 | 0 0 0.00 | +_Building_Untarget | | | + 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 3 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 01/20/1994 +Phase: 1 Time: 17:04:46 +Total Ticks: 2081 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Calculated_Cell 0 | 0 0 0.00 | 0 0 0.00 | +_Can_Unit_Enter_Building | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Can_Upgrade 0 | 0 0 0.00 | 0 0 0.00 | +_Cardinal_To_Fixed | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_CellXY_Coord 281 | 0 0 0.00 | 0 0 0.00 | +_Cell_Building 28 | 0 0 0.00 | 0 0 0.00 | +_Cell_Coord 46 | 0 0 0.00 | 0 0 0.00 | +_Cell_Distance 3 | 0 0 0.00 | 0 0 0.00 | +_Cell_Facing 28 | 0 0 0.00 | 0 0 0.00 | +_Cell_Object 0 | 0 0 0.00 | 0 0 0.00 | +_Cell_Unit 1104 | 0 0 0.00 | 0 0 0.00 | +_Cell_X 0 | 0 0 0.00 | 0 0 0.00 | +_Cell_Y 0 | 0 0 0.00 | 0 0 0.00 | +_Center_Map 0 | 0 0 0.00 | 0 0 0.00 | +_Change_State 10 | 0 0 0.00 | 0 0 0.00 | +_Check_Menu 0 | 0 0 0.00 | 0 0 0.00 | +_Class_From_Name 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Attached 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Available_Building | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Class_Count | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Dialog 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Display_Message | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Distance 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Is_Clear 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Is_Enemy 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Is_Friendly | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Mono_Print | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Nearest_Spice | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Pause 128 | 0 0 0.00 | 0 0 0.00 | +_Common_Pause_Random | | | + 6 | 0 0 0.00 | 0 0 0.00 | +_Common_Play_Sound | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Random 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Scenario 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Target_Distance | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Target_Facing | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Target_Kind | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Common_Target_Type | | | + 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 4 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 01/20/1994 +Phase: 1 Time: 17:04:46 +Total Ticks: 2081 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Common_Tutor_Message | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_Add 18 | 0 0 0.00 | 0 0 0.00 | +_Coord_Cell_Distance | | | + 274 | 0 0 0.00 | 0 0 0.00 | +_Coord_Facing16 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_Facing8 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_Mid 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_Move 570 | 0 0 0.00 | 0 0 0.00 | +_Coord_Scatter 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_Snap 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_Sub 730 | 0 0 0.00 | 0 0 0.00 | +_Coord_XCell 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_XPixel 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_YCell 0 | 0 0 0.00 | 0 0 0.00 | +_Coord_YPixel 0 | 0 0 0.00 | 0 0 0.00 | +_Coordinates_In_Region | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Cursor 239 | 0 0 0.00 | 0 0 0.00 | +_Cursor_Move 0 | 0 0 0.00 | 0 0 0.00 | +_Cursor_Size 0 | 0 0 0.00 | 0 0 0.00 | +_Debug_Key 40 | 0 0 0.00 | 0 0 0.00 | +_Desired_Facing256 | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Destroy_Wall 0 | 0 0 0.00 | 0 0 0.00 | +_Dialog_Message 0 | 0 0 0.00 | 0 0 0.00 | +_Display_Selected_Terrain | | | + 10 | 0 0 0.00 | 0 0 0.00 | +_Display_Status 790 | 0 0 0.00 | 0 0 0.00 | +_Do_Button_Setup 0 | 0 0 0.00 | 0 0 0.00 | +_Do_Explosion 8 | 3 0 0.14 | 0 0 0.00 | +_Do_Gas 0 | 0 0 0.00 | 0 0 0.00 | +_Do_Lose 0 | 0 0 0.00 | 0 0 0.00 | +_Do_Win 0 | 0 0 0.00 | 0 0 0.00 | +_Draw_Box 0 | 0 0 0.00 | 0 0 0.00 | +_Edit_Unit_Offsets | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_End_Game 790 | 0 0 0.00 | 0 0 0.00 | +_Establish_Contact_With | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Establish_Origin 9 | 0 0 0.00 | 0 0 0.00 | +_Establish_Radio_Contact | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Fancy_Text_Print 0 | 0 0 0.00 | 0 0 0.00 | +_Fetch_Text_String | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_File_Stream_Sample | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Fill_In_Data 0 | 0 0 0.00 | 0 0 0.00 | +_Find_Brain_Size 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 5 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 01/20/1994 +Phase: 1 Time: 17:04:46 +Total Ticks: 2081 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Find_Path 5 | 1 0 0.05 | 0 0 0.00 | +_Find_Waypoint 0 | 0 0 0.00 | 0 0 0.00 | +_Fire_Bullet 0 | 0 0 0.00 | 0 0 0.00 | +_Fire_Weapon 267 | 3 0 0.14 | 0 0 0.00 | +_Fixed_To_Cardinal | | | + 20 | 0 0 0.00 | 0 0 0.00 | +_Format_Window_String | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Free_Brains 0 | 0 0 0.00 | 0 0 0.00 | +_Free_Sample 0 | 0 0 0.00 | 0 0 0.00 | +_Game_Screen 0 | 0 0 0.00 | 0 0 0.00 | +_GetPrivateProfileInt | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_GetPrivateProfileString | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Get_Connection_Info | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Get_Connection_Num | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Get_Internet_Address | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Get_Local_Connection_Numb | | | +er 0 | 0 0 0.00 | 0 0 0.00 | +_Greatest_Building_Threat | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Greatest_Unit_Noise | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Greatest_Unit_Threat | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Harvester_Check 0 | 0 0 0.00 | 0 0 0.00 | +_House_From_Name 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Cancel_Event 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Close_Socket 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Get_Local_Target | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Listen_For_Packet | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Open_Socket 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Relinquish_Control | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Schedule_IPX_Event | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_IPX_Send_Packet 0 | 0 0 0.00 | 0 0 0.00 | +_Icon_Install 0 | 0 0 0.00 | 0 0 0.00 | +_Icon_Remove 0 | 0 0 0.00 | 0 0 0.00 | +_In_Radar 547 | 0 0 0.00 | 0 0 0.00 | +_In_Radio_Contact 0 | 0 0 0.00 | 0 0 0.00 | +_In_View 0 | 0 0 0.00 | 0 0 0.00 | +_Info_Chunk_In 0 | 0 0 0.00 | 0 0 0.00 | +_Info_Chunk_Out 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 6 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 01/20/1994 +Phase: 1 Time: 17:04:46 +Total Ticks: 2081 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Init_Game 0 | 0 0 0.00 | 0 0 0.00 | +_Init_Shapes 0 | 0 0 0.00 | 0 0 0.00 | +_Init_Thread 46 | 0 0 0.00 | 0 0 0.00 | +_Interrupt_Thread 0 | 0 0 0.00 | 0 0 0.00 | +_Is_Allied 860 | 0 0 0.00 | 0 0 0.00 | +_Is_Lined_Up 273 | 0 0 0.00 | 0 0 0.00 | +_Is_Mapped 11 | 0 0 0.00 | 0 0 0.00 | +_Language_Name 0 | 0 0 0.00 | 0 0 0.00 | +_Launch_GoldenBB 0 | 0 0 0.00 | 0 0 0.00 | +_Launch_Special 0 | 0 0 0.00 | 0 0 0.00 | +_Legal_Placement 0 | 0 0 0.00 | 0 0 0.00 | +_Load_Brains 0 | 0 0 0.00 | 0 0 0.00 | +_Load_Map 0 | 0 0 0.00 | 0 0 0.00 | +_Load_Sample 9 | 0 0 0.00 | 0 0 0.00 | +_Load_Sample_Into_Buffer | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Load_Samples 0 | 0 0 0.00 | 0 0 0.00 | +_Load_System_Strings | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Load_Terrain_Icons | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_MO_From_Name 0 | 0 0 0.00 | 0 0 0.00 | +_Make_Button 0 | 0 0 0.00 | 0 0 0.00 | +_Map_Chunk_In 0 | 0 0 0.00 | 0 0 0.00 | +_Map_Chunk_Out 0 | 0 0 0.00 | 0 0 0.00 | +_Map_Edit 0 | 0 0 0.00 | 0 0 0.00 | +_Mouse_Coord 0 | 0 0 0.00 | 0 0 0.00 | +_Nearest_Spice 0 | 0 0 0.00 | 0 0 0.00 | +_Novell_Detect 0 | 0 0 0.00 | 0 0 0.00 | +_Order_From_Name 0 | 0 0 0.00 | 0 0 0.00 | +_Phrase_CallBack 0 | 0 0 0.00 | 0 0 0.00 | +_Pixel_Coordinate 1389 | 0 0 0.00 | 0 0 0.00 | +_Play_Sample 9 | 0 0 0.00 | 0 0 0.00 | +_Play_Sample_Vol 9 | 0 0 0.00 | 0 0 0.00 | +_Play_Theme 0 | 0 0 0.00 | 0 0 0.00 | +_Player_Alloc 0 | 0 0 0.00 | 0 0 0.00 | +_Player_First 0 | 0 0 0.00 | 0 0 0.00 | +_Player_Free 0 | 0 0 0.00 | 0 0 0.00 | +_Player_From_ID 1663 | 0 0 0.00 | 0 0 0.00 | +_Player_Logic 0 | 0 0 0.00 | 0 0 0.00 | +_Player_Next 0 | 0 0 0.00 | 0 0 0.00 | +_Player_System_Shutdown | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Player_System_Startup | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Plyr_Chunk_In 0 | 0 0 0.00 | 0 0 0.00 | +_Plyr_Chunk_Out 0 | 0 0 0.00 | 0 0 0.00 | +_Power_Effects 0 | 0 0 0.00 | 0 0 0.00 | +_Production_Needs 0 | 0 0 0.00 | 0 0 0.00 | +_Production_Start 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 7 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 01/20/1994 +Phase: 1 Time: 17:04:46 +Total Ticks: 2081 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Radar_Checkup 0 | 0 0 0.00 | 0 0 0.00 | +_Radar_Cursor 790 | 0 0 0.00 | 0 0 0.00 | +_Radar_Pixel 1053 | 0 0 0.00 | 0 0 0.00 | +_Read_Scenario 0 | 0 0 0.00 | 0 0 0.00 | +_Read_Scenario_Ini | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Read_Scenario_Raw | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Recalc_Storage 0 | 0 0 0.00 | 0 0 0.00 | +_Redistribute_Power | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Redraw_Objects 0 | 0 0 0.00 | 0 0 0.00 | +_Repair_On 0 | 0 0 0.00 | 0 0 0.00 | +_Request_Transport | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Sample_Effect 0 | 0 0 0.00 | 0 0 0.00 | +_Sample_Read 0 | 0 0 0.00 | 0 0 0.00 | +_Sample_Status 0 | 0 0 0.00 | 0 0 0.00 | +_Say_Phrase 0 | 0 0 0.00 | 0 0 0.00 | +_Scroll_Tactical 128 | 0 0 0.00 | 0 0 0.00 | +_Select_House 0 | 0 0 0.00 | 0 0 0.00 | +_Select_Next 0 | 0 0 0.00 | 0 0 0.00 | +_Send_Message 0 | 0 0 0.00 | 0 0 0.00 | +_Set_Unit_Movement | | | + 18 | 0 0 0.00 | 0 0 0.00 | +_Set_Unit_Rotation | | | + 532 | 0 0 0.00 | 0 0 0.00 | +_Setup_Menu 0 | 0 0 0.00 | 0 0 0.00 | +_Sight_From 7 | 2 0 0.10 | 0 0 0.00 | +_Sound_Effect 7 | 0 0 0.00 | 0 0 0.00 | +_Sound_End 1 | 0 0 0.00 | 0 0 0.00 | +_Sound_Init 0 | 0 0 0.00 | 0 0 0.00 | +_Special_Discovery | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Speed_Adjust 16 | 0 0 0.00 | 0 0 0.00 | +_Speed_From_Name 0 | 0 0 0.00 | 0 0 0.00 | +_Spew_Spice 0 | 0 0 0.00 | 0 0 0.00 | +_Spice_Adjust 0 | 0 0 0.00 | 0 0 0.00 | +_Spice_Discovery 0 | 0 0 0.00 | 0 0 0.00 | +_Start_Scenario 0 | 0 0 0.00 | 0 0 0.00 | +_Start_Thread 14 | 0 0 0.00 | 0 0 0.00 | +_Stop_Sample 0 | 0 0 0.00 | 0 0 0.00 | +_Stream_Sample 0 | 0 0 0.00 | 0 0 0.00 | +_Struct_From_Name 0 | 0 0 0.00 | 0 0 0.00 | +_Target_Build 92 | 0 0 0.00 | 0 0 0.00 | +_Target_Building 5 | 0 0 0.00 | 0 0 0.00 | +_Target_Cell 25 | 0 0 0.00 | 0 0 0.00 | +_Target_Distance 151 | 1 0 0.05 | 0 0 0.00 | +_Target_Kind 1265 | 0 0 0.00 | 0 0 0.00 | +_Target_Object 20 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 8 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 01/20/1994 +Phase: 1 Time: 17:04:46 +Total Ticks: 2081 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Target_Unit 24 | 0 0 0.00 | 0 0 0.00 | +_Target_Value 299 | 0 0 0.00 | 0 0 0.00 | +_Team_Alloc 0 | 0 0 0.00 | 0 0 0.00 | +_Team_Chunk_In 0 | 0 0 0.00 | 0 0 0.00 | +_Team_Chunk_Out 0 | 0 0 0.00 | 0 0 0.00 | +_Team_Create 0 | 0 0 0.00 | 0 0 0.00 | +_Team_First 15 | 0 0 0.00 | 0 0 0.00 | +_Team_Free 0 | 0 0 0.00 | 0 0 0.00 | +_Team_From_ID 0 | 0 0 0.00 | 0 0 0.00 | +_Team_Logic 0 | 0 0 0.00 | 0 0 0.00 | +_Team_Next 15 | 0 0 0.00 | 0 0 0.00 | +_Team_System_Calibrate | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Team_System_Shutdown | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Team_System_Startup | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Terrain_Cost 516 | 1 0 0.05 | 0 0 0.00 | +_Terrain_Type 356 | 0 0 0.00 | 0 0 0.00 | +_Text_Input 0 | 0 0 0.00 | 0 0 0.00 | +_Text_String 51 | 0 0 0.00 | 0 0 0.00 | +_Track_Func 790 | 0 0 0.00 | 0 0 0.00 | +_Tutor_Message 3 | 0 0 0.00 | 0 0 0.00 | +_Unit_Alloc 9 | 0 0 0.00 | 0 0 0.00 | +_Unit_Available 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Brainwash 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Check 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Chunk_In 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Chunk_Out 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Create 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Delete 8 | 0 0 0.00 | 0 0 0.00 | +_Unit_Enters_Building | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Free 8 | 0 0 0.00 | 0 0 0.00 | +_Unit_Hidden 15 | 0 0 0.00 | 0 0 0.00 | +_Unit_House 91 | 0 0 0.00 | 0 0 0.00 | +_Unit_Joins_Team 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Leaves_Team 15 | 0 0 0.00 | 0 0 0.00 | +_Unit_Limbo 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Loaner 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Look 7 | 2 0 0.10 | 0 0 0.00 | +_Unit_Next 5013 | 0 0 0.00 | 0 0 0.00 | +_Unit_Rally 5 | 0 0 0.00 | 0 0 0.00 | +_Unit_Revealed 168 | 0 0 0.00 | 0 0 0.00 | +_Unit_Select 10 | 0 0 0.00 | 0 0 0.00 | +_Unit_Sort 790 | 0 0 0.00 | 0 0 0.00 | +_Unit_Stun 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_System_Calibrate | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_System_Shutdown | | | + 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 9 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 01/20/1994 +Phase: 1 Time: 17:04:46 +Total Ticks: 2081 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +_Unit_System_Startup | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_Threat 0 | 0 0 0.00 | 0 0 0.00 | +_Unit_UnTarget 15 | 1 0 0.05 | 0 0 0.00 | +_Unit_Unlimbo 7 | 0 0 0.00 | 0 0 0.00 | +_Units_Team 2 | 0 0 0.00 | 0 0 0.00 | +_Upgrade_On 0 | 0 0 0.00 | 0 0 0.00 | +_Valid_Thread 882 | 0 0 0.00 | 0 0 0.00 | +_Vehicle_Edit 0 | 0 0 0.00 | 0 0 0.00 | +_Window_Box 0 | 0 0 0.00 | 0 0 0.00 | +_WritePrivateProfileInt | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_WritePrivateProfileString | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Write_Scenario 0 | 0 0 0.00 | 0 0 0.00 | +_Write_Scenario_Ini | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_Write_Scenario_Raw | | | + 0 | 0 0 0.00 | 0 0 0.00 | +_XYPixel_Coord 9 | 0 0 0.00 | 0 0 0.00 | +_XY_Cell 0 | 0 0 0.00 | 0 0 0.00 | +__MMODEL 0 | 0 0 0.00 | 0 0 0.00 | +__checknull 1 | 0 0 0.00 | 0 0 0.00 | +__restorezero 1 | 0 0 0.00 | 0 0 0.00 | +__terminate 1 | 0 0 0.00 | 0 0 0.00 | +_abort 0 | 0 0 0.00 | 0 0 0.00 | +_strtrim 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Animation_In | | | +stall 8 | 2 0 0.10 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Crash | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Crater | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Illegal | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_RTimer | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Repeat | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Shake | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_ShiftX | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_ShiftY | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Sound | | | + 0 | 0 0 0.00 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Stop | | | + 256 | 2 0 0.10 | 0 0 0.00 | +anim.c_NONPUB_Cmd_Timer | | | + 768 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 10 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 01/20/1994 +Phase: 1 Time: 17:04:46 +Total Ticks: 2081 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +anim.c_NONPUB_Cmd_Trigger_ | | | +Bloom 0 | 0 0 0.00 | 0 0 0.00 | +audio.c_NONPUB_Dune_Load_S | | | +ample 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Abort_ | | | +Transport 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Busy_S | | | +tate 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Change | | | +_Facing_One 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Check_ | | | +Up 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Direct | | | +ion 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Dummy | | | + 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Fire_A | | | +t 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Image | | | + 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Launch | | | +_Unit 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Look | | | + 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Neares | | | +t_Enemy 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Play_S | | | +ound 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Reques | | | +t_Transport 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Self_E | | | +xplode 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Termin | | | +ate_Self 0 | 0 0 0.00 | 0 0 0.00 | +blogic.c_NONPUB_Cmd_Unload | | | +_Harvester 0 | 0 0 0.00 | 0 0 0.00 | +conquer.c_NONPUB_Keyboard_ | | | +Process 40 | 0 0 0.00 | 0 0 0.00 | +cursor.c_NONPUB_Cursor_Mar | | | +k 0 | 0 0 0.00 | 0 0 0.00 | +findpath.c_NONPUB_Follow_E | | | +dge 10 | 1 0 0.05 | 0 0 0.00 | +findpath.c_NONPUB_Optimize | | | +_Moves 14 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_IconSet | | | + 0 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Illegal | | | + 0 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Loop | | | + 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 11 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 01/20/1994 +Phase: 1 Time: 17:04:46 +Total Ticks: 2081 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +icon.c_NONPUB_Cmd_Map | | | + 130 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Normal | | | + 0 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Repeat | | | + 16 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Sound | | | + 0 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Stop | | | + 0 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Term | | | + 1 | 0 0 0.00 | 0 0 0.00 | +icon.c_NONPUB_Cmd_Timer | | | + 129 | 0 0 0.00 | 0 0 0.00 | +init.c_NONPUB_Init_Globals | | | + 0 | 0 0 0.00 | 0 0 0.00 | +init.c_NONPUB_Load_A_Brain | | | + 0 | 0 0 0.00 | 0 0 0.00 | +loadgame.c_NONPUB_BYTE_In | | | + 0 | 0 0 0.00 | 0 0 0.00 | +loadgame.c_NONPUB_LONG_In | | | + 0 | 0 0 0.00 | 0 0 0.00 | +loadgame.c_NONPUB_Struct_I | | | +n 0 | 0 0 0.00 | 0 0 0.00 | +loadgame.c_NONPUB_Thread_G | | | +ameize 0 | 0 0 0.00 | 0 0 0.00 | +loadgame.c_NONPUB_WORD_In | | | + 0 | 0 0 0.00 | 0 0 0.00 | +map.c_NONPUB_Smooth_Shadow | | | + 840 | 0 0 0.00 | 0 0 0.00 | +physics.c_NONPUB_End_Of_Mo | | | +ve 17 | 0 0 0.00 | 0 0 0.00 | +physics.c_NONPUB_Rotation_ | | | +Physics 318 | 0 0 0.00 | 0 0 0.00 | +physics.c_NONPUB_Start_Of_ | | | +Move 78 | 1 0 0.05 | 0 0 0.00 | +plogic.c_NONPUB_Cmd_Dummy | | | + 0 | 0 0 0.00 | 0 0 0.00 | +plogic.c_NONPUB_Cmd_Harves | | | +ter_Check 0 | 0 0 0.00 | 0 0 0.00 | +plogic.c_NONPUB_Cmd_Sound | | | + 0 | 0 0 0.00 | 0 0 0.00 | +plogic.c_NONPUB_Cmd_Unit_C | | | +reate 0 | 0 0 0.00 | 0 0 0.00 | +savegame.c_NONPUB_BYTE_Out | | | + 0 | 0 0 0.00 | 0 0 0.00 | +savegame.c_NONPUB_LONG_Out | | | + 0 | 0 0 0.00 | 0 0 0.00 | +savegame.c_NONPUB_Struct_O | | | +ut 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 12 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 01/20/1994 +Phase: 1 Time: 17:04:46 +Total Ticks: 2081 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +savegame.c_NONPUB_Thread_N | | | +ormalize 0 | 0 0 0.00 | 0 0 0.00 | +savegame.c_NONPUB_WORD_Out | | | + 0 | 0 0 0.00 | 0 0 0.00 | +scenario.c_NONPUB_Clear_Sc | | | +enario 0 | 0 0 0.00 | 0 0 0.00 | +scenario.c_NONPUB_Do_Brief | | | +ing 0 | 0 0 0.00 | 0 0 0.00 | +scenario.c_NONPUB_Typical_ | | | +Briefing 0 | 0 0 0.00 | 0 0 0.00 | +soundio.c_NONPUB_DigiCallb | | | +ack 0 | 0 0 0.00 | 0 0 0.00 | +soundio.c_NONPUB_File_Call | | | +back 0 | 0 0 0.00 | 0 0 0.00 | +strinput.c_NONPUB_Input_Cu | | | +rsor 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Attack | | | +_Team_Target 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Change | | | +_MO 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Displa | | | +y_Message 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Dummy | | | + 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Find_T | | | +eam_Target 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Recrui | | | +t 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Regrou | | | +p 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Restor | | | +e_MO 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Team_C | | | +ount 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Team_M | | | +in 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Team_S | | | +pread 0 | 0 0 0.00 | 0 0 0.00 | +tlogic.c_NONPUB_Cmd_Team_T | | | +arget 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Abort_ | | | +Transport 3 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_All_St | | | +op 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Altern | | | +ate_Target 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Animat | | | +e 8 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Call_F | | | +or_Landing 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 13 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 01/20/1994 +Phase: 1 Time: 17:04:46 +Total Ticks: 2081 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +ulogic.c_NONPUB_Cmd_Cargo | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Change | | | +_Facing 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Change | | | +_Order 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Death_ | | | +Message 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Deploy | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Dock | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Dummy | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Eject | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Explos | | | +ion 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Face_M | | | +ove_Toward 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Face_T | | | +arCom 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Flash | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Greate | | | +st_Threat 8 | 1 0 0.05 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Hot_LZ | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Idle | | | + 2 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Inq_Un | | | +it 127 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Is_Clo | | | +se_To_NavCom 21 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Is_Clo | | | +se_To_TarCom 96 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Load | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Loaded | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Lock_O | | | +n_To_Target 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Look | | | + 7 | 2 0 0.10 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Mine_S | | | +pice 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_MultiE | | | +xplosion 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Noisie | | | +st_Target 0 | 0 0 0.00 | 0 0 0.00 | +------------------------------------------------------------------------------- + + Page 14 + .RTProfiler Performance Report + +Program: CONQUER.EXE Date: 01/20/1994 +Phase: 1 Time: 17:04:46 +Total Ticks: 2081 + + ---------- Logical --------------- Physical -------- + | Total Average Pct | Total Average Pct | + Total | Clock No. of Total | Clock No. of Total| +Symbol Name Calls | Ticks Ticks Time | Ticks Ticks Time | +------------------------------------------------------------------------------- +ulogic.c_NONPUB_Cmd_Reques | | | +t_Transport 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Rest | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Set_Hu | | | +lk 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Set_Na | | | +vCom 87 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Set_Sp | | | +eed 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Set_Ta | | | +rCom 10 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Spew_B | | | +odies 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Target | | | +_Dir 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Termin | | | +ate_Self 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Threat | | | +_Value 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Travel | | | +_To 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Unload | | | + 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Unload | | | +ed_Struct 0 | 0 0 0.00 | 0 0 0.00 | +ulogic.c_NONPUB_Cmd_Verify | | | +_Contact 0 | 0 0 0.00 | 0 0 0.00 | + +Total Logical Percentage : 316.67 +Total Physical Percentage : 99.62 diff --git a/CONQUER.TXT b/CONQUER.TXT new file mode 100644 index 0000000..5a6567d --- /dev/null +++ b/CONQUER.TXT @@ -0,0 +1,1417 @@ +# TXT_NONE +%3d.%02d +# TXT_CREDIT_FORMAT +Upgrade +# TXT_BUTTON_UPGRADE +Upgrade Structure +# TXT_UPGRADE +Upgrade +# TXT_UPGRADE_BUTTON +Sell +# TXT_BUTTON_SELL +Sell Structure +# TXT_SELL +Demolish Structure +# TXT_DEMOLISH +Repair +# TXT_BUTTON_REPAIR +Repair Structure +# TXT_REPAIR +Repair +# TXT_REPAIR_BUTTON +You: +# TXT_YOU +Enemy: +# TXT_ENEMY +Buildings Destroyed By +# TXT_BUILD_DEST +Units Destroyed By +# TXT_UNIT_DEST +Tiberium Harvested By +# TXT_TIB_HARV +Score: %d +# TXT_SCORE_1 +You have attained the rank of +# TXT_RANK_OF +Yes +# TXT_YES +No +# TXT_NO +Ready +# TXT_READY +Holding +# TXT_HOLDING +Accomplished +# TXT_SCENARIO_WON +Failed +# TXT_SCENARIO_LOST +Choose Your Side +# TXT_CHOOSE_SIDE +Start New Game +# TXT_START_NEW_GAME +Replay Introduction +# TXT_INTRO +Cancel +# TXT_CANCEL +Rock +# TXT_ROCK +Resume Game +# TXT_CHOAM_RESUME +Build This +# TXT_CHOAM_BUILD_THIS +Thank you for playing Command & Conquer.\r +# TXT_THANK_YOU +Hall of Fame +# TXT_FAME +Global Defense Initiative +# TXT_GDI +Brotherhood of Nod +# TXT_NOD +Civilian +# TXT_CIVILIAN +Containment Team +# TXT_JP +OK +# TXT_OK +Tree +# TXT_TREE +\x011 +# TXT_LEFT +\x010 +# TXT_RIGHT +\x01E +# TXT_UP +\x01F +# TXT_DOWN +Clear the map +# TXT_CLEAR_MAP +Inherit previous map +# TXT_INHERIT_MAP +Clear +# TXT_CLEAR +Water +# TXT_WATER +Road +# TXT_ROAD +Tile Object +# TXT_TILE +Slope +# TXT_SLOPE +Brush +# TXT_BRUSH +Patch +# TXT_PATCH +River +# TXT_RIVER +Load Mission +# TXT_LOAD_MISSION +Save Mission +# TXT_SAVE_MISSION +Delete Mission +# TXT_DELETE_MISSION +Load +# TXT_LOAD_BUTTON +Save +# TXT_SAVE_BUTTON +Delete +# TXT_DELETE_BUTTON +Game Controls +# TXT_GAME_CONTROLS +Sound Controls +# TXT_SOUND_CONTROLS +Resume Mission +# TXT_RESUME_MISSION +Visual Controls +# TXT_VISUAL_CONTROLS +Abort Mission +# TXT_QUIT_MISSION +Exit Game +# TXT_EXIT_GAME +Options +# TXT_OPTIONS +Tiberium +# TXT_TIBERIUM +Tiberium On +# TXT_TIBERIUM_ON +Tiberium Off +# TXT_TIBERIUM_OFF +Squish mark +# TXT_SQUISH +Crater +# TXT_CRATER +Scorch Mark +# TXT_SCORCH +BRIGHTNESS: +# TXT_BRIGHTNESS +MUSIC VOLUME +# TXT_MUSIC +SOUND VOLUME +# TXT_VOLUME +TINT: +# TXT_TINT +CONTRAST: +# TXT_CONTRAST +GAME SPEED: +# TXT_SPEED +SCROLL RATE: +# TXT_SCROLLRATE +COLOR: +# TXT_COLOR +Return to game +# TXT_RETURN_TO_GAME +Enemy Soldier +# TXT_ENEMY_SOLDIER +Enemy Vehicle +# TXT_ENEMY_VEHICLE +Enemy Structure +# TXT_ENEMY_STRUCTURE +Flame Tank +# TXT_FTANK +Stealth Tank +# TXT_STANK +Light Tank +# TXT_LTANK +Med. Tank +# TXT_MTANK +Mammoth Tank +# TXT_HTANK +Nod Buggy +# TXT_DUNE_BUGGY +SAM Site +# TXT_SAM +Advanced Com. Center +# TXT_EYE +Rocket Launcher +# TXT_MLRS +Mobile HQ +# TXT_MHQ +Hum-vee +# TXT_JEEP +Transport Helicopter +# TXT_TRANS +A10 +# TXT_A10 +C17 +# TXT_C17 +Harvester +# TXT_HARVESTER +Artillery +# TXT_ARTY +S.S.M. Launcher +# TXT_MSAM +Minigunner +# TXT_E1 +Grenadier +# TXT_E2 +Bazooka +# TXT_E3 +Flamethrower +# TXT_E4 +Chem-warrior +# TXT_E5 +Commando +#TXT_RAMBO +Hovercraft +# TXT_HOVER +Attack Helicopter +# TXT_HELI +Orca +# TXT_ORCA +APC +# TXT_APC +Guard Tower +# TXT_GUARD_TOWER +Communications Center +# TXT_COMMAND +Helicopter Pad +# TXT_HELIPAD +Airstrip +# TXT_AIRSTRIP +Tiberium Silo +# TXT_STORAGE +Construction Yard +# TXT_CONST_YARD +Tiberium Refinery +# TXT_REFINERY +Church +# TXT_CIV1 +Han's and Gretel's +# TXT_CIV2 +Hewitt's Manor +# TXT_CIV3 +Ricktor's House +# TXT_CIV4 +Gretchin's House +# TXT_CIV5 +The Barn +# TXT_CIV6 +Damon's pub +# TXT_CIV7 +Fran's House +# TXT_CIV8 +Music Factory +# TXT_CIV9 +Toymaker's +# TXT_CIV10 +Ludwig's House +# TXT_CIV11 +Haystacks +# TXT_CIV12 +Haystack +# TXT_CIV13 +Wheat Field +# TXT_CIV14 +Fallow Field +# TXT_CIV15 +Corn Field +# TXT_CIV16 +Celery Field +# TXT_CIV17 +Potato Field +# TXT_CIV18 +Sala's House +# TXT_CIV20 +Abdul's House +# TXT_CIV21 +Pablo's Wicked Pub +# TXT_CIV22 +Village Well +# TXT_CIV23 +Camel Trader +# TXT_CIV24 +Church +# TXT_CIV25 +Ali's House +# TXT_CIV26 +Trader Ted's +# TXT_CIV27 +Menelik's House +# TXT_CIV28 +Prestor John's House +# TXT_CIV29 +Village Well +# TXT_CIV30 +Witch Doctor's Hut +# TXT_CIV31 +Rikitikitembo's Hut +# TXT_CIV32 +Roarke's Hut +# TXT_CIV33 +Mubasa's Hut +# TXT_CIV34 +Aksum's Hut +# TXT_CIV35 +Mambo's Hut +# TXT_CIV36 +The Studio +# TXT_CIV37 +Technology Center +# TXT_CIVMISS +Gun Turret +# TXT_TURRET +Gun Boat +# TXT_GUNBOAT +Mobile Construction Yard +# TXT_MCV +Recon Bike +# TXT_BIKE +Power Plant +# TXT_POWER +Advanced Power Plant +# TXT_ADVANCED_POWER +Hospital +# TXT_HOSPITAL +Barracks +# TXT_BARRACKS +Concrete +# TXT_CONCRETE +Oil Pump +# TXT_PUMP +Oil Tanker +# TXT_TANKER +Sandbag Wall +# TXT_SANDBAG_WALL +Chain Link Fence +# TXT_CYCLONE_WALL +Concrete Wall +# TXT_BRICK_WALL +Barbwire Fence +# TXT_BARBWIRE_WALL +Wood Fence +# TXT_WOOD_WALL +Weapons Factory +# TXT_WEAPON_FACTORY +Advanced Guard Tower +# TXT_AGUARD_TOWER +Obelisk Guard Tower +# TXT_OBELISK +Bio-Research Laboritory +# TXT_BIO_LAB +Hand of Nod +# TXT_HAND +Temple of Nod +# TXT_TEMPLE +Repair Bay +# TXT_FIX_IT +Sidebar +# TXT_TAB_SIDEBAR +Options +# TXT_TAB_BUTTON_CONTROLS +Database +# TXT_TAB_BUTTON_DATABASE +Unrevealed Terrain +# TXT_SHADOW +Options Menu +# TXT_OPTIONS_MENU +STOP +# TXT_STOP +PLAY +# TXT_PLAY +SHUFFLE +# TXT_SHUFFLE +REPEAT +# TXT_REPEAT +Music volume: +# TXT_MUSIC_VOLUME +Sound volume: +# TXT_SOUND_VOLUME +On +# TXT_ON +Off +# TXT_OFF +Act On Instinct +# TXT_THEME_AOI +Looks Like Trouble +# TXT_THEME_TROUBLE +Industrial +# TXT_THEME_IND +Reaching Out +# TXT_THEME_ROUT +On The Prowl +# TXT_THEME_OTP +Prepare For Battle +# TXT_THEME_PRP +Just Do It! +# TXT_THEME_JUSTDOIT +In The Line Of Fire +# TXT_THEME_LINEFIRE +March To Doom +# TXT_THEME_MARCH +Deception +# TXT_THEME_STOPTHEM +C&C Thang +# TXT_THEME_CCTHANG +Enemies To Be Feared +# TXT_THEME_BEFEARED +Warfare +# TXT_THEME_WARFARE +Fight, Win, Prevail +# TXT_THEME_FWP +Die!! +# TXT_THEME_DIE +No Mercy +# TXT_THEME_NOMERCY +Mechanical Man +# TXT_THEME_TARGET +I Am +# TXT_THEME_IAM +Great Shot! +# TXT_THEME_WIN1 +Multiplayer Game +# TXT_MULTIPLAYER_GAME +No files available +# TXT_NO_FILES +Do you want to delete this file? +# TXT_DELETE_SINGLE_FILE +Do you want to delete %d files? +# TXT_DELETE_MULTIPLE_FILES +Reset Values +# TXT_RESET_MENU +Confirmation +# TXT_CONFIRMATION +Do you want to abort the mission? +# TXT_CONFIRM_EXIT +Mission Description +# TXT_MISSION_DESCRIPTION +Joe +# TXT_C1 +Bill +# TXT_C2 +Shelly +# TXT_C3 +Maria +# TXT_C4 +Eydie +# TXT_C5 +Dave +# TXT_C6 +Phil +# TXT_C7 +Dwight +# TXT_C8 +Erik +# TXT_C9 +Dr. Moebius +# TXT_MOEBIUS +Road Bib +# TXT_BIB +Faster +# TXT_FASTER +Slower +# TXT_SLOWER +Ion Cannon +# TXT_ION_CANNON +Nuclear Strike +# TXT_NUKE_STRIKE +Air Strike +# TXT_AIR_STRIKE +Tyrannosaurus Rex +# TXT_TREX +Triceratops +# TXT_TRIC +Velociraptor +# TXT_RAPT +Stegasaurus +# TXT_STEG +Steel Crate +# TXT_STEEL_CRATE +Wood Crate +# TXT_WOOD_CRATE +Flag Location +# TXT_FLAG_SPOT +GDI +# TXT_G_D_I +NOD +# TXT_N_O_D +Unable to read scenario! +# TXT_UNABLE_READ_SCENARIO +Error loading game! +# TXT_ERROR_LOADING_GAME +Obsolete saved game. +# TXT_OBSOLETE_SAVEGAME +You must enter a description! +# TXT_MUSTENTER_DESCRIPTION +Error saving game! +# TXT_ERROR_SAVING_GAME +Delete this file? +# TXT_DELETE_FILE_QUERY +[EMPTY SLOT] +# TXT_EMPTY_SLOT +Select Multiplayer Game +# TXT_SELECT_MPLAYER_GAME +Modem/Serial +# TXT_MODEM_SERIAL +Network +# TXT_NETWORK +Unable to initialize network! +# TXT_INIT_NET_ERROR +Join Network Game +# TXT_JOIN_NETWORK_GAME +New +# TXT_NEW +Join +# TXT_JOIN +Send Message +# TXT_SEND_MESSAGE +Your Name: +# TXT_YOUR_NAME +Side: +# TXT_SIDE_COLON +Color: +# TXT_COLOR_COLON +Games +# TXT_GAMES +Players +# TXT_PLAYERS +Scenario: +# TXT_SCENARIO_COLON +>> NOT FOUND << +# TXT_NOT_FOUND +Starting Credits: +# TXT_START_CREDITS_COLON +Bases: +# TXT_BASES_COLON +Tiberium: +# TXT_TIBERIUM_COLON +Crates: +# TXT_CRATES_COLON +AI Players: +# TXT_AI_PLAYERS_COLON +Request denied. +# TXT_REQUEST_DENIED +Unable to play; scenario not found. +# TXT_UNABLE_PLAY_WAAUGH +Nothing to join! +# TXT_NOTHING_TO_JOIN +You must enter a name! +# TXT_NAME_ERROR +Duplicate names are not allowed. +# TXT_DUPENAMES_NOTALLOWED +Your game version is outdated. +# TXT_YOURGAME_OUTDATED +Destination game version is outdated. +# TXT_DESTGAME_OUTDATED +%s's Game +# TXT_THATGUYS_GAME +[%s's Game] +# TXT_THATGUYS_GAME_BRACKET +Network Game Setup +# TXT_NETGAME_SETUP +Reject +# TXT_REJECT +You can't reject yourself! You might develop serious self-esteem problems. +# TXT_CANT_REJECT_SELF +You must select a player to reject. +# TXT_SELECT_PLAYER_REJECT +Bases On +# TXT_BASES_ON +Bases Off +# TXT_BASES_OFF +Crates On +# TXT_CRATES_ON +Crates Off +# TXT_CRATES_OFF +AI Players On +# TXT_AI_PLAYERS_ON +AI Players Off +# TXT_AI_PLAYERS_OFF +Scenarios +# TXT_SCENARIOS +Starting Credits +# TXT_START_CREDITS +Only one player? +# TXT_ONLY_ONE +Oops! +# TXT_OOPS +To %s: +# TXT_TO +To All: +# TXT_TO_ALL +Message: +# TXT_MESSAGE +Connection to %s lost! +# TXT_CONNECTION_LOST +%s has left the game. +# TXT_LEFT_GAME +%s has been defeated! +# TXT_PLAYER_DEFEATED +Waiting to Connect... +# TXT_WAITING_CONNECT +Connection error!\rCheck your cables.\rAttempting to Reconnect... +# TXT_NULL_CONNERR_CHECK_CABLES +Connection error!\rRedialing... +# TXT_MODEM_CONNERR_REDIALING +Connection error!\rWaiting for Call... +# TXT_MODEM_CONNERR_WAITING +Select Serial Game +# TXT_SELECT_SERIAL_GAME +Dial Modem +# TXT_DIAL_MODEM +Answer Modem +# TXT_ANSWER_MODEM +Null Modem +# TXT_NULL_MODEM +Settings +# TXT_SETTINGS +Port: +# TXT_PORT_COLON +IRQ: +# TXT_IRQ_COLON +Baud: +# TXT_BAUD_COLON +Init String: +# TXT_INIT_STRING +Call Waiting String: +# TXT_CWAIT_STRING +Tone Dialing +# TXT_TONE_BUTTON +Pulse Dialing +# TXT_PULSE_BUTTON +Host Serial Game +# TXT_HOST_SERIAL_GAME +Opponent: +# TXT_OPPONENT_COLON +User signed off! +# TXT_USER_SIGNED_OFF +Join Serial Game +# TXT_JOIN_SERIAL_GAME +Phone List +# TXT_PHONE_LIST +Add +# TXT_ADD +Edit +# TXT_EDIT +Dial +# TXT_DIAL +Default +# TXT_DEFAULT +Default Settings +# TXT_DEFAULT_SETTINGS +Custom Settings +# TXT_CUSTOM_SETTINGS +Phone Listing +# TXT_PHONE_LISTING +Name: +# TXT_NAME_COLON +Number: +# TXT_NUMBER_COLON +Unable to find modem.\rCheck power and cables. +# TXT_UNABLE_FIND_MODEM +No carrier. +# TXT_NO_CARRIER +Line busy. +# TXT_LINE_BUSY +Number invalid. +# TXT_NUMBER_INVALID +Other system not responding! +# TXT_SYSTEM_NOT_RESPONDING +Games are out of sync! +# TXT_OUT_OF_SYNC +Packet received too late! +# TXT_PACKET_TOO_LATE +Other player has left the game. +# TXT_PLAYER_LEFT_GAME +From %s:%s +# TXT_FROM +2,728,000 +# TXT_MAP_P01 +38,385,000 +# TXT_MAP_P02 +10,373,000 +# TXT_MAP_P03 +51,994,000 +# TXT_MAP_P04 +80,387,000 +# TXT_MAP_P05 +10,400,000 +# TXT_MAP_P06 +5,300,000 +# TXT_MAP_P07 +7,867,000 +# TXT_MAP_P08 +10,333,000 +# TXT_MAP_P09 +1,974,000 +# TXT_MAP_P10 +23,169,000 +# TXT_MAP_P11 +10,064,000 +# TXT_MAP_P12 +3,285,000 +# TXT_MAP_P13 +8,868,000 +# TXT_MAP_P14 +10,337,000 +# TXT_MAP_P15 +4,365,000 +# TXT_MAP_P16 +1,607,000 +# TXT_MAP_P17 +4,485,000 +# TXT_MAP_P18 +56,386,000 +# TXT_MAP_P19 +28,305,000 +# TXT_MAP_P20 +5,238,000 +# TXT_MAP_P21 +2,059,000 +# TXT_MAP_P22 +13,497,000 +# TXT_MAP_P23 +4,997,000 +# TXT_MAP_P24 +88,500,000 +# TXT_MAP_P25 +1,106,000 +# TXT_MAP_P26 +12,658,000 +# TXT_MAP_P27 +3,029,000 +# TXT_MAP_P28 +39,084,000 +# TXT_MAP_P29 +23,154,000 +# TXT_MAP_P30 +8,902,000 +# TXT_MAP_P31 +27,791,000 +# TXT_MAP_P32 +1,574,000 +# TXT_MAP_P33 +15,469,000 +# TXT_MAP_P34 +1,300,000 +# TXT_MAP_P35 +41,688,000 +# TXT_MAP_P36 +24,900 SQ. MI. +# TXT_MAP_A00 +120,727 SQ. MI. +# TXT_MAP_A01 +80,134 SQ. MI. +# TXT_MAP_A02 +233,100 SQ. MI. +# TXT_MAP_A03 +137,838 SQ. MI. +# TXT_MAP_A04 +30,449 SQ. MI. +# TXT_MAP_A05 +18,932 SQ. MI. +# TXT_MAP_A06 +32,377 SQ. MI. +# TXT_MAP_A07 +35,919 SQ. MI. +# TXT_MAP_A08 +7,819 SQ. MI. +# TXT_MAP_A09 +91,699 SQ. MI. +# TXT_MAP_A10 +51,146 SQ. MI. +# TXT_MAP_A11 +11,100 SQ. MI. +# TXT_MAP_A12 +44,365 SQ. MI. +# TXT_MAP_A13 +39,449 SQ. MI. +# TXT_MAP_A14 +19,741 SQ. MI. +# TXT_MAP_A15 +17,413 SQ. MI. +# TXT_MAP_A16 +RIGA +# TXT_MAP_C00 +WARSAW +# TXT_MAP_C01 +MINSK +# TXT_MAP_C02 +KIEV +# TXT_MAP_C03 +BERLIN +# TXT_MAP_C04 +PRAGUE +# TXT_MAP_C05 +BRATISLAVA +# TXT_MAP_C06 +VIENNA +# TXT_MAP_C07 +BUDAPEST +# TXT_MAP_C08 +LJUBLJANA +# TXT_MAP_C09 +BUCHAREST +# TXT_MAP_C10 +ATHENS +# TXT_MAP_C11 +TIRANA +# TXT_MAP_C12 +SOFIA +# TXT_MAP_C13 +BELGRADE +# TXT_MAP_C14 +SARAJEVO +# TXT_MAP_C15 +TALLINN +# TXT_MAP_C16 +TRIPOLI +# TXT_MAP_C17 +CAIRO +# TXT_MAP_C18 +KHARTOUM +# TXT_MAP_C19 +N'DJAMENA +# TXT_MAP_C20 +NOUAKCHOTT +# TXT_MAP_C21 +YAMOUSSOUKRO +# TXT_MAP_C22 +PORTO-NOVO +# TXT_MAP_C23 +ABUJA +# TXT_MAP_C24 +LIBREVILLE +# TXT_MAP_C25 +YAOUNDE +# TXT_MAP_C26 +BANGUI +# TXT_MAP_C27 +KINSHASA +# TXT_MAP_C28 +CAIRO +# TXT_MAP_C29 +LUANDA +# TXT_MAP_C30 +DAR-ES-SALAAM +# TXT_MAP_C31 +WINDHOEK +# TXT_MAP_C32 +MAPUTO +# TXT_MAP_C33 +GABARONE +# TXT_MAP_C34 +CAPE TOWN +# TXT_MAP_C35 +NEGLIGIBLE +# TXT_MAP_GDP00 +$162.7 BLN +# TXT_MAP_GDP01 +$47.6 BLN +# TXT_MAP_GDP02 +$1,131 BLN +# TXT_MAP_GDP03 +$120 BLN +# TXT_MAP_GDP04 +$164 BLN +# TXT_MAP_GDP05 +$60.1 BLN +# TXT_MAP_GDP06 +$21 BLN +# TXT_MAP_GDP07 +$71.9 BLN +# TXT_MAP_GDP08 +$77 BLN +# TXT_MAP_GDP09 +$4.0 BLN +# TXT_MAP_GDP10 +$47.3 BLN +# TXT_MAP_GDP11 +$120.1 BLN +# TXT_MAP_GDP12 +$14.0 BLN +# TXT_MAP_GDP13 +$28.9 BLN +# TXT_MAP_GDP14 +$39.2 BLN +# TXT_MAP_GDP15 +$12.1 BLN +# TXT_MAP_GDP16 +$1.0 BLN +# TXT_MAP_GDP17 +$10.0 BLN +# TXT_MAP_GDP18 +$1.7 BLN +# TXT_MAP_GDP19 +$28.0 BLN +# TXT_MAP_GDP20 +$5.3 BLN +# TXT_MAP_GDP21 +$11.6 BLN +# TXT_MAP_GDP22 +$1.3 BLN +# TXT_MAP_GDP23 +$6.6 BLN +# TXT_MAP_GDP24 +$8.3 BLN +# TXT_MAP_GDP25 +$6.9 BLN +# TXT_MAP_GDP26 +$2.0 BLN +# TXT_MAP_GDP27 +$3.1 BLN +# TXT_MAP_GDP28 +$104.0 BLN +# TXT_MAP_GDP29 +JELGAVA +# TXT_MAP_PC00 +GDANSK +# TXT_MAP_PC01 +BYELISTOK +# TXT_MAP_PC02 +BOBYRUSK +# TXT_MAP_PC03 +IVANO-FRANKOVSK +# TXT_MAP_PC04 +HANOVER +# TXT_MAP_PC05 +DRESDEN +# TXT_MAP_PC06 +OSTRAVA +# TXT_MAP_PC07 +BRATISLAVA +# TXT_MAP_PC08 +SALZBURG +# TXT_MAP_PC09 +BUDAPEST +# TXT_MAP_PC10 +TRIESTE +# TXT_MAP_PC11 +ARAD +# TXT_MAP_PC12 +CORINTH +# TXT_MAP_PC13 +SHKODER +# TXT_MAP_PC14 +SOFIA +# TXT_MAP_PC15 +NIS +# TXT_MAP_PC16 +BELGRADE +# TXT_MAP_PC17 +? +# TXT_MAP_PC18 +PARNU +# TXT_MAP_PC19 +TMASSAH +# TXT_MAP_PC20 +AL-ALAMYN +# TXT_MAP_PC21 +AL-KHARIJAH +# TXT_MAP_PC22 +AL-UBAYYID +# TXT_MAP_PC23 +KAFIA-KINGI +# TXT_MAP_PC24 +OUM HADJER +# TXT_MAP_PC25 +MAO +# TXT_MAP_PC26 +TIDJIKDJA +# TXT_MAP_PC27 +ABIDJAN +# TXT_MAP_PC28 +PORTO-NOVO +# TXT_MAP_PC29 +ABUJA +# TXT_MAP_PC30 +KOULA-MOUTOU +# TXT_MAP_PC31 +BERTOUA +# TXT_MAP_PC32 +BANGASSOU +# TXT_MAP_PC33 +LODJA +# TXT_MAP_PC34 +KINSHASA +# TXT_MAP_PC35 +LUXOR +# TXT_MAP_PC36 +CAIUNDO +# TXT_MAP_PC37 +MZUZU +# TXT_MAP_PC38 +KEETMANSHOOP +# TXT_MAP_PC39 +XAI-XAI +# TXT_MAP_PC40 +GHANZI +# TXT_MAP_PC41 +CAPE TOWN +# TXT_MAP_PC42 +GDI PROGRESSION +# TXT_MAP_GDI +NOD PROGRESSION +# TXT_MAP_NOD +LOCATING COORDINATES +# TXT_MAP_LOCATE +OF NEXT MISSION +# TXT_MAP_NEXT_MISSION +SELECT TERRITORY +# TXT_MAP_SELECT +TO ATTACK +# TXT_MAP_TO_ATTACK +POPULATION: +# TXT_MAP_GDISTAT0 +GEOGRAPHIC AREA: +# TXT_MAP_GDISTAT1 +CAPITAL: +# TXT_MAP_GDISTAT2 +GOVERNMENT: +# TXT_MAP_GDISTAT3 +GROSS DOMESTIC PRODUCT: +# TXT_MAP_GDISTAT4 +POINT OF CONFLICT: +# TXT_MAP_GDISTAT5 +MILITARY POWER: +# TXT_MAP_GDISTAT6 +EXPENDABILITY: +# TXT_MAP_NODSTAT0 +GOVT CORRUPTABILITY: +# TXT_MAP_NODSTAT1 +NET WORTH: +# TXT_MAP_NODSTAT2 +MILITARY STRENGTH: +# TXT_MAP_NODSTAT3 +MILITARY RESISTANCE: +# TXT_MAP_NODSTAT4 +LATVIA +# TXT_MAP_COUNTRYNAME0 +POLAND +# TXT_MAP_COUNTRYNAME1 +BELARUS +# TXT_MAP_COUNTRYNAME2 +UKRAINE +# TXT_MAP_COUNTRYNAME3 +GERMANY +# TXT_MAP_COUNTRYNAME4 +CZECH REPUBLIC +# TXT_MAP_COUNTRYNAME5 +SLOVAKIA +# TXT_MAP_COUNTRYNAME6 +AUSTRIA +# TXT_MAP_COUNTRYNAME7 +HUNGARY +# TXT_MAP_COUNTRYNAME8 +SLOVENIA +# TXT_MAP_COUNTRYNAME9 +ROMANIA +# TXT_MAP_COUNTRYNAME10 +GREECE +# TXT_MAP_COUNTRYNAME11 +ALBANIA +# TXT_MAP_COUNTRYNAME12 +BULGARIA +# TXT_MAP_COUNTRYNAME13 +YUGOSLAVIA +# TXT_MAP_COUNTRYNAME14 +BOSNIA/HERZOGOVINA +# TXT_MAP_COUNTRYNAME15 +LIBYA +# TXT_MAP_COUNTRYNAME16 +EGYPT +# TXT_MAP_COUNTRYNAME17 +SUDAN +# TXT_MAP_COUNTRYNAME18 +CHAD +# TXT_MAP_COUNTRYNAME19 +MAURITANIA +# TXT_MAP_COUNTRYNAME20 +IVORY COAST +# TXT_MAP_COUNTRYNAME21 +BENIN +# TXT_MAP_COUNTRYNAME22 +NIGERIA +# TXT_MAP_COUNTRYNAME23 +GABON +# TXT_MAP_COUNTRYNAME24 +CAMEROON +# TXT_MAP_COUNTRYNAME25 +CENTRAL AFRICAN REPUBLIC +# TXT_MAP_COUNTRYNAME26 +ZAIRE +# TXT_MAP_COUNTRYNAME27 +ANGOLA +# TXT_MAP_COUNTRYNAME28 +TANZANIA +# TXT_MAP_COUNTRYNAME29 +NAMIBIA +# TXT_MAP_COUNTRYNAME30 +MOZAMBIQUE +# TXT_MAP_COUNTRYNAME31 +BOTSWANA +# TXT_MAP_COUNTRYNAME32 +SOUTH AFRICA +# TXT_MAP_COUNTRYNAME33 +ESTONIA +# TXT_MAP_COUNTRYNAME34 +REPUBLIC +# TXT_MAP_GOVT0 +DEMOCRATIC STATE +# TXT_MAP_GOVT1 +FEDERAL REPUBLIC +# TXT_MAP_GOVT2 +CONST. REPUBLIC +# TXT_MAP_GOVT3 +PARL. DEMOCRACY +# TXT_MAP_GOVT4 +PRES. PARL. REPUBLIC +# TXT_MAP_GOVT5 +DEMOCRACY +# TXT_MAP_GOVT6 +IN TRANSITION +# TXT_MAP_GOVT7 +ISLAMIC SOCIALIST +# TXT_MAP_GOVT8 +MILITARY +# TXT_MAP_GOVT9 +ISLAMIC REPUBLIC +# TXT_MAP_GOVT10 +PARL. REPUBLIC +# TXT_MAP_GOVT11 +LOCAL MILITIA +# TXT_MAP_ARMY0 +STATE MILITIA +# TXT_MAP_ARMY1 +NATIONAL GUARD +# TXT_MAP_ARMY2 +FREE STANDING ARMY +# TXT_MAP_ARMY3 +? +# TXT_MAP_ARMY4 +NATIONAL POWER +# TXT_MAP_ARMY5 +RESPECTABLE +# TXT_MAP_MILITARY0 +FORMIDABLE +# TXT_MAP_MILITARY1 +LAUGHABLE +# TXT_MAP_MILITARY2 +REASONABLE +# TXT_MAP_MILITARY3 +INSIGNIFICANT +# TXT_MAP_MILITARY4 +CLICK TO CONTINUE +# TXT_MAP_CLICK2 +LOW +# TXT_MAP_LMH0 +MEDIUM +# TXT_MAP_LMH1 +HIGH +# TXT_MAP_LMH2 +TIME: +# TXT_SCORE_TIME +LEADERSHIP: +# TXT_SCORE_LEAD +EFFICIENCY: +# TXT_SCORE_EFFI +TOTAL SCORE: +# TXT_SCORE_TOTA +CASUALTIES: +# TXT_SCORE_CASU +NEUTRAL: +# TXT_SCORE_NEUT +GDI: +# TXT_SCORE_GDI +BUILDINGS LOST +# TXT_SCORE_BUIL +BUILDINGS +# TXT_SCORE_BUIL1 +LOST: +# TXT_SCORE_BUIL2 +TOP SCORES +# TXT_SCORE_TOP +ENDING CREDITS: +# TXT_SCORE_ENDCRED +%dh %dm +# TXT_SCORE_TIMEFORMAT1 +%dm +# TXT_SCORE_TIMEFORMAT2 +NOD: +# TXT_SCORE_NOD +Dialing... +# TXT_DIALING +Dialing Canceled +# TXT_DIALING_CANCELED +Waiting for Call... +# TXT_WAITING_FOR_CALL +Answering Canceled +# TXT_ANSWERING_CANCELED +Engineer +# TXT_E7 +Special Options +# TXT_SPECIAL_OPTIONS +Targeting flash visible to all. +# TXT_VISIBLE_TARGET +Allow targeting of trees. +# TXT_TREE_TARGET +Allow undeploy of construction yard. +# TXT_MCV_DEPLOY +Employ smarter self defense logic. +# TXT_SMART_DEFENCE +Moderate production speed. +# TXT_SLOW_BUILD +Use three point turn logic. +# TXT_THREE_POINT +Tiberium will grow. +# TXT_TIBERIUM_GROWTH +Tiberium will spread. +# TXT_TIBERIUM_SPREAD +Disable building "bib" pieces. +# TXT_ROAD_PIECES +Allow running from immediate threats. +# TXT_SCATTER +Not a Null Modem Cable Attached!\rIt is a modem or loopback cable. +# TXT_MODEM_OR_LOOPBACK +Map +# TXT_MAP +From Computer: +# TXT_FROM_COMPUTER +Prepare to die! +# TXT_COMP_MSG1 +How about a bullet sandwich?! +# TXT_COMP_MSG2 +Incoming! +# TXT_COMP_MSG3 +I see you! +# TXT_COMP_MSG4 +Hey, I'm over here! +# TXT_COMP_MSG5 +Come get some! +# TXT_COMP_MSG6 +I got you! +# TXT_COMP_MSG7 +You humans are never a challenge! +# TXT_COMP_MSG8 +Abort, Retry, Ignore? (Ha ha!) +# TXT_COMP_MSG9 +Format another? (Just kidding!) +# TXT_COMP_MSG10 +Beat me and I'll reboot! +# TXT_COMP_MSG11 +You're artificial intelligence! +# TXT_COMP_MSG12 +My AI is better than your AI. +# TXT_COMP_MSG13 +Air Strike +# TXT_THEME_AIRSTRIKE +Demolition +# TXT_THEME_HEAVYG +Untamed Land +# TXT_THEME_J1 +Take 'em Out +# TXT_THEME_JDI_V2 +Radio +# TXT_THEME_RADIO +Rain In The Night +# TXT_THEME_RAIN +Canyon Chase +# TXT_THEME_IND2 +Heartbreak +# TXT_THEME_HEART +Blossom Tree +# TXT_BLOSSOM_TREE +Restate +# TXT_RESTATE_MISSION +Computer +# TXT_COMPUTER +Unit Count: +# TXT_COUNT +Tech Level: +# TXT_LEVEL +Opponent +# TXT_OPPONENT +Kills: +# TXT_KILLS_COLON +Video +# TXT_VIDEO +Nikoomba +# TXT_C10 +Capture The Flag +# TXT_CAPTURE_THE_FLAG +Ride of the Valkyries +# TXT_THEME_VALK +Mission Objective +# TXT_OBJECTIVE +Mission +# TXT_MISSION +No saved games available. +# TXT_NO_SAVES +Civilian Building +# TXT_CIVILIAN_BUILDING +Technician +# TXT_TECHNICIAN +Visceroid +# TXT_VISCEROID +Save game options are not allowed during a multiplayer session. +# TXT_NO_SAVELOAD +Defender has the advantage. +# TXT_DEFENDER_ADVANTAGE +Show true object names. +# TXT_SHOW_NAMES +Agent Delphi +# TXT_DELPHI +Would you like to replay this mission? +# TXT_TO_REPLAY +Reconnecting to %s. +# TXT_RECONN_TO +Please wait %02d seconds. +# TXT_PLEASE_WAIT +Do you wish\rto surrender? +# TXT_SURRENDER +GLOBAL DEFENSE INITIATIVE +# TXT_GDI_NAME +BROTHERHOOD OF NOD +# TXT_NOD_NAME +SELECT TRANSMISSION +# TXT_SEL_TRANS +Your game name must be unique. +# TXT_GAMENAME_MUSTBE_UNIQUE +Game is closed. +# TXT_GAME_IS_CLOSED +Your name must be unique. +# TXT_NAME_MUSTBE_UNIQUE +Reconnecting to %s +# TXT_RECONNECTING_TO +Waiting for connections... +# TXT_WAITING_FOR_CONNECTIONS +Time allowed: %02d seconds +# TXT_TIME_ALLOWED +Press ESC to cancel. +# TXT_PRESS_ESC +From Computer: It's just you and me now! +# TXT_JUST_YOU_AND_ME +Capture the Flag: +# TXT_CAPTURE_THE_FLAG_COLON +Dr. Chan +# TXT_CHAN +%s has allied with %s +# TXT_HAS_ALLIED +%s declares war on %s +# TXT_AT_WAR +Select a target +# TXT_SEL_TARGET +Allow separate helipad purchase +# TXT_SEPARATE_HELIPAD +Resign Game +# TXT_RESIGN +Tiberium grows quickly. +# TXT_TIBERIUM_FAST +Answering... +# TXT_ANSWERING +Initializing Modem... +# TXT_INITIALIZING_MODEM +Scenarios don't match. +# TXT_SCENARIOS_DO_NOT_MATCH +Power Output +# TXT_POWER_OUTPUT +Power Output (low) +# TXT_POWER_OUTPUT_LOW +Continue +# TXT_CONTINUE +Data Queue Overflow +# TXT_QUEUE_FULL +%s changed game options! +# TXT_SPECIAL_WARNING +Please insert a Command & Conquer CD into the CD-ROM drive. +# TXT_CD_DIALOG_1 +Please insert CD %d (%s) into the CD-ROM drive. +# TXT_CD_DIALOG_2 +Command & Conquer is unable to detect your CD ROM drive. +# TXT_CD_ERROR1 +No Sound Card Detected +# TXT_NO_SOUND_CARD +UNKNOWN +# TXT_UNKNOWN +(old) +# TXT_OLD_GAME +Insufficient Disk Space to run Command & Conquer. +# TXT_NO_SPACE +You must have %d megabytes of free disk space. +# TXT_MUST_HAVE_SPACE +Run SETUP program first. +# TXT_RUN_SETUP +Waiting for Opponent +# TXT_WAITING_FOR_OPPONENT +Please select 'Settings' to setup default configuration +# TXT_SELECT_SETTINGS +Prison +# TXT_PRISON +Game Saved +# TXT_GAME_WAS_SAVED +Insufficient disk space to save a game. Please delete a previous save to free up some disk space and try again. +# TXT_SPACE_CANT_SAVE +Invalid Port/Address.\rCOM 1-4 OR ADDRESS +# TXT_INVALID_PORT_ADDRESS +Invalid Port and/or IRQ settings +# TXT_INVALID_SETTINGS +IRQ already in use +# TXT_IRQ_ALREADY_IN_USE +Abort +# TXT_ABORT +Restart +# TXT_RESTART +Mission is restarting.\rPlease wait... +# TXT_RESTARTING +Mission is loading.\rPlease wait... +# TXT_LOADING +Error in the InitString +# TXT_ERROR_IN_INITSTRING diff --git a/CONST.CPP b/CONST.CPP new file mode 100644 index 0000000..654e761 --- /dev/null +++ b/CONST.CPP @@ -0,0 +1,425 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\const.cpv 2.17 16 Oct 1995 16:52:24 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CONST.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 20, 1993 * + * * + * Last Update : September 20, 1993 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +char const * SourceName[SOURCE_COUNT] = +{ + "North", + "East", + "South", + "West", + "Shipping", + "Beach", + "Air", + "Visible", + "EnemyBase", + "HomeBase", + "Ocean", +}; + + +/*************************************************************************** +** Relative coordinate offsets from the center of a cell for each +** of the legal positions that an object in a cell may stop at. Only infantry +** are allowed to stop at other than the center of the cell. +*/ +COORDINATE const StoppingCoordAbs[5] = { + 0x00800080L, // center + 0x00400040L, // upper left + 0x004000C0L, // upper right + 0x00C00040L, // lower left + 0x00C000C0L // lower right +}; + + +/*************************************************************************** +** These are the various weapons and their characteristics. +** +** bullet type dmg, rof, range, sound +*/ +WeaponTypeClass const Weapons[WEAPON_COUNT] = { + {BULLET_SNIPER, 125, 40, 0x0580, VOC_SNIPER, ANIM_NONE}, // WEAPON_RIFLE + {BULLET_SPREADFIRE, 25, 50, 0x0400, VOC_MINI, ANIM_GUN_N}, // WEAPON_CHAIN_GUN + {BULLET_BULLET, 1, 7, 0x01C0, VOC_RIFLE, ANIM_NONE}, // WEAPON_PISTOL + {BULLET_BULLET, 15, 20, 0x0200, VOC_MGUN2, ANIM_NONE}, // WEAPON_M16 + {BULLET_TOW, 30, 60, 0x0400, VOC_BAZOOKA,ANIM_NONE}, // WEAPON_DRAGON + {BULLET_FLAME, 35, 50, 0x0200, VOC_FLAMER1,ANIM_FLAME_N}, // WEAPON_FLAMETHROWER + {BULLET_FLAME, 50, 50, 0x0200, VOC_FLAMER1,ANIM_FLAME_N}, // WEAPON_FLAME_TONGUE + {BULLET_CHEMSPRAY, 80, 70, 0x0200, VOC_FLAMER1,ANIM_CHEM_N}, // WEAPON_CHEMSPRAY + {BULLET_GRENADE, 50, 60, 0x0340, VOC_TOSS, ANIM_NONE}, // WEAPON_GRENADE + {BULLET_APDS, 25, 60, 0x0400, VOC_TANK2, ANIM_MUZZLE_FLASH}, // WEAPON_75MM + {BULLET_APDS, 30, 50, 0x04C0, VOC_TANK3, ANIM_MUZZLE_FLASH}, // WEAPON_105MM + {BULLET_APDS, 40, 80, 0x04C0, VOC_TANK4, ANIM_MUZZLE_FLASH}, // WEAPON_120MM + {BULLET_APDS, 40, 60, 0x0600, VOC_TANK4, ANIM_MUZZLE_FLASH}, // WEAPON_TURRET_GUN + {BULLET_SSM, 75, 80, 0x0500, VOC_ROCKET1,ANIM_NONE}, // WEAPON_MAMMOTH_TUSK + {BULLET_SSM2, 75, 80, 0x0600, VOC_ROCKET1,ANIM_NONE}, // WEAPON_MLRS + {BULLET_HE, 150, 65, 0x0600, VOC_TANK1, ANIM_MUZZLE_FLASH}, // WEAPON_155MM + {BULLET_BULLET, 15, 30, 0x0400, VOC_MGUN11, ANIM_GUN_N}, // WEAPON_M60MG + {BULLET_SSM, 60, 35, 0x0780, VOC_ROCKET2,ANIM_NONE}, // WEAPON_TOMAHAWK + {BULLET_SSM, 60, 40, 0x0680, VOC_ROCKET2,ANIM_NONE}, // WEAPON_TOW_TWO + {BULLET_NAPALM, 100, 20, 0x0480, VOC_NONE, ANIM_NONE}, // WEAPON_NAPALM + {BULLET_LASER, 200, 90, 0x0780, VOC_LASER, ANIM_NONE}, // WEAPON_OBELISK_LASER + {BULLET_SAM, 50, 50, 0x0780, VOC_ROCKET2,ANIM_NONE}, // WEAPON_NIKE + {BULLET_HONEST_JOHN, 100, 200, 0x0A00, VOC_ROCKET1,ANIM_NONE}, // WEAPON_HONEST_JOHN + {BULLET_HEADBUTT, 100, 30, 0x0180, VOC_DINOATK1,ANIM_NONE}, // WEAPON_STEG + {BULLET_TREXBITE, 155, 30, 0x0180, VOC_DINOATK1,ANIM_NONE}, // WEAPON_TREX +}; + + +/*************************************************************************** +** These are the various warheads. +** +** spread factor, destroys walls, destroys wood, destroys Tiberium, {armor defense table} +** -vs- {none, wood, aluminum, steel, concrete} +*/ +WarheadTypeClass const Warheads[WARHEAD_COUNT] = { + { 2,false,false,false,{0xFF, 0x80, 0x90, 0x40, 0x40}}, // WARHEAD_SA Small arms -- good against infantry. + { 6,true,true,true,{0xE0, 0xC0, 0x90, 0x40, 0xFF}}, // WARHEAD_HE High explosive -- good against buildings & infantry. + { 6,true,true,false,{0x40, 0xC0, 0xC0, 0xFF, 0x80}}, // WARHEAD_AP Armor piercing -- good against armor. + { 8,false,true,true,{0xE0, 0xFF, 0xB0, 0x40, 0x80}}, // WARHEAD_FIRE Incendiary -- Good against flammables. + { 4,false,false,false,{0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}, // WARHEAD_LASER Light Amplification of Stimulated Emission by Radiation. + { 7,true,true,true,{0xFF, 0xFF, 0xC0, 0xC0, 0xC0}}, // WARHEAD_PB Particle beam (neutron beam). + { 4,false,false,false,{0xFF, 0x20, 0x20, 0x10, 0x10}}, // WARHEAD_FIST Punching in hand-to-hand combat. + { 4,false,false,false,{0xFF, 0x20, 0x20, 0x10, 0x10}}, // WARHEAD_FOOT Kicking in hand-to-hand combat. + { 4,false,false,false,{0xFF, 0x08, 0x08, 0x08, 0x08}}, // WARHEAD_HOLLOW_POINT Sniper bullet type. + {255,false,false,false,{0xFF, 0x01, 0x01, 0x01, 0x01}}, // WARHEAD_SPORE + { 1, true,true,false,{0xFF, 0xC0, 0x80, 0x20, 0x08}}, // WARHEAD_HEADBUTT + { 1, true,true,false,{0xFF, 0xC0, 0x80, 0x20, 0x08}}, // WARHEAD_FEEDME +}; + + +/*************************************************************************** +** Converts pixel values (cell relative) into the appropriate lepton (sub cell) +** value. This is used to convert pixel (screen) coordinates into the underlying +** coordinate system. +*/ +unsigned char const Pixel2Lepton[24] = { + 0x00,0x0B,0x15,0x20,0x2B,0x35,0x40,0x4B, + 0x55,0x60,0x6B,0x75,0x80,0x8B,0x95,0xA0, + 0xAB,0xB5,0xC0,0xCB,0xD5,0xE0,0xEB,0xF5 +}; + + +/*************************************************************************** +** This array is used to index a facing in order to retrieve a cell +** offset that, when added to another cell, will achieve the adjacent cell +** in the indexed direction. +*/ +CELL const AdjacentCell[FACING_COUNT] = { + -(MAP_CELL_W), // North + -(MAP_CELL_W-1), // North East + 1, // East + MAP_CELL_W+1, // South East + MAP_CELL_W, // South + MAP_CELL_W-1, // South West + -1, // West + -(MAP_CELL_W+1) // North West +}; + +COORDINATE const AdjacentCoord[FACING_COUNT] = { + 0xFF000000L, + 0xFF000100L, + 0x00000100L, + 0x01000100L, + 0x01000000L, + 0x0100FF00L, + 0x0000FF00L, + 0xFF00FF00L +}; + + +/*************************************************************************** +** This converts 0..255 facing values into either 8, 16, or 32 facing values. +** Note: a simple shift won't suffice because 0..255 facing values should +** be converted to the CLOSEST appropriate facing, NOT rounded down to the +** nearest facing. +*/ +unsigned char const Facing8[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +#ifdef NEVER +unsigned char const Facing16[256] = { + 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4, + 4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0 +}; +#endif + +/* +** This table incorporates a compensating factor for the distortion caused +** by 3D-Studio when it tries to render 45% angles. +*/ +unsigned char const Facing32[256] = { + 0,0,0,0,0,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3, + 3,4,4,4,4,4,4,5,5,5,5,5,5,5,6,6,6,6,6,6,6,7,7,7,7,7,7,7,8,8,8,8, + 8,8,8,9,9,9,9,9,9,9,10,10,10,10,10,10,10,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12, + 13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16, + 16,16,16,16,16,17,17,17,17,17,17,17,17,17,18,18,18,18,18,18,18,18,18,19,19,19,19,19,19,19,19,19, + 19,20,20,20,20,20,20,21,21,21,21,21,21,21,22,22,22,22,22,22,22,23,23,23,23,23,23,23,24,24,24,24, + 24,24,24,25,25,25,25,25,25,25,26,26,26,26,26,26,26,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28, + 29,29,29,29,29,29,29,29,30,30,30,30,30,30,30,30,30,31,31,31,31,31,31,31,31,31,0,0,0,0,0,0 +}; + +#ifdef OBSOLETE +unsigned char const Facing32[256] = { + 0,0,0,0, + 1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2, + 3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8, + 9,9,9,9,9,9,9,9, + 10,10,10,10,10,10,10,10, + 11,11,11,11,11,11,11,11, + 12,12,12,12,12,12,12,12, + 13,13,13,13,13,13,13,13, + 14,14,14,14,14,14,14,14, + 15,15,15,15,15,15,15,15, + 16,16,16,16,16,16,16,16, + 17,17,17,17,17,17,17,17, + 18,18,18,18,18,18,18,18, + 19,19,19,19,19,19,19,19, + 20,20,20,20,20,20,20,20, + 21,21,21,21,21,21,21,21, + 22,22,22,22,22,22,22,22, + 23,23,23,23,23,23,23,23, + 24,24,24,24,24,24,24,24, + 25,25,25,25,25,25,25,25, + 26,26,26,26,26,26,26,26, + 27,27,27,27,27,27,27,27, + 28,28,28,28,28,28,28,28, + 29,29,29,29,29,29,29,29, + 30,30,30,30,30,30,30,30, + 31,31,31,31,31,31,31,31, + 0,0,0,0 +}; +#endif + + +/*************************************************************************** +** These are the movement costs (in ticks at fastest speed) to enter each +** of the given terrain cells. +*/ +#define S1 0x00 +#define S2 0x40 +#define S3 0x70 +#define S4 0xA0 +#define S5 0xC0 +#define S6 0xFF +GroundType const Ground[LAND_COUNT] = { +// Foot +// | Tracked +// | | Harvester +// | | | Wheeled +// | | | | Winged +// | | | | | Hover +// | | | | | | float build + {66, {S3, S3, S3, S4, S6, S5, S1 }, true}, // LAND_CLEAR + {68, {S5, S4, S4, S4, S6, S5, S1 }, true}, // LAND_ROAD + {BLUE, {S1, S1, S1, S1, S6, S5, S6 }, false}, // LAND_WATER + {DKGREY, {S1, S1, S1, S1, S6, S1, S1 }, false}, // LAND_ROCK + {DKGREY, {S1, S1, S1, S1, S6, S1, S1 }, false}, // LAND_WALL + {143, {S3, S3, S3, S4, S6, S5, S1 }, false}, // LAND_TIBERIUM + {66, {S3, S3, S3, S4, S6, S5, S1 }, false}, // LAND_BEACH +}; + + +/*************************************************************************** +** These are the names of the theaters. +*/ +TheaterDataType const Theaters[THEATER_COUNT] = { + {"DESERT","DESERT","DES"}, + {"JUNGLE","JUNGLE","JUN"}, + {"TEMPERATE","TEMPERAT","TEM"}, + {"WINTER","WINTER","WIN"} +}; + + +/*************************************************************************** +** These are the remap tables that are used to convert the units/buildings +** into the other color schemes. +*/ +unsigned char const RemapYellow[256] = { + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, // 0..15 + 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // 16..31 + 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, // 32..47 + 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, // 48..63 + 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, // 64..79 + 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95, // 80..95 + 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, // 96..111 + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, // 112..127 + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, // 128..143 + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, // 144..159 + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, // 160..175 + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, // 176..191 + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, // 192..207 + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, // 208..223 + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, // 224..239 + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 // 240..255 +}; + +unsigned char const RemapRed[256] = { + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, // 0..15 + 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // 16..31 + 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, // 32..47 + 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, // 48..63 + 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, // 64..79 + 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95, // 80..95 + 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, // 96..111 + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, // 112..127 + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, // 128..143 + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, // 144..159 + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, // 160..175 + 127,126,125,124,122,46,120,47,125,124,123,122,42,121,120,120, // 176..191 + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, // 192..207 + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, // 208..223 + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, // 224..239 + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 // 240..255 +}; + +unsigned char const RemapBlueGreen[256] = { + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, // 0..15 + 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // 16..31 + 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, // 32..47 + 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, // 48..63 + 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, // 64..79 + 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95, // 80..95 + 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, // 96..111 + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, // 112..127 + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, // 128..143 + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, // 144..159 + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, // 160..175 + 2,119,118,135,136,138,112,12,118,135,136,137,138,139,114,112, // 176..191 + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, // 192..207 + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, // 208..223 + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, // 224..239 + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 // 240..255 +}; + +unsigned char const RemapOrange[256] = { + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, // 0..15 + 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // 16..31 + 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, // 32..47 + 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, // 48..63 + 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, // 64..79 + 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95, // 80..95 + 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, // 96..111 + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, // 112..127 + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, // 128..143 + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, // 144..159 + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, // 160..175 + 24,25,26,27,29,31,46,47,26,27,28,29,30,31,43,47, // 176..191 + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, // 192..207 + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, // 208..223 + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, // 224..239 + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 // 240..255 +}; + +unsigned char const RemapGreen[256] = { + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, // 0..15 + 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // 16..31 + 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, // 32..47 + 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, // 48..63 + 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, // 64..79 + 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95, // 80..95 + 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, // 96..111 + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, // 112..127 + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, // 128..143 + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, // 144..159 + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, // 160..175 + 5,165,166,167,159,142,140,199,166,167,157,3,159,143,142,141, // 176..191 + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, // 192..207 + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, // 208..223 + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, // 224..239 + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 // 240..255 +}; + +unsigned char const RemapBlue[256] = { + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, // 0..15 + 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // 16..31 + 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, // 32..47 + 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, // 48..63 + 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, // 64..79 + 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95, // 80..95 + 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, // 96..111 + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, // 112..127 + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, // 128..143 + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, // 144..159 + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, // 160..175 + 161,200,201,202,204,205,206,12,201,202,203,204,205,115,198,114, // 176..191 + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, // 192..207 + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, // 208..223 + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, // 224..239 + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 // 240..255 +}; + +unsigned char const RemapNone[256] = { + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, // 0..15 + 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, // 16..31 + 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, // 32..47 + 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, // 48..63 + 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, // 64..79 + 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95, // 80..95 + 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, // 96..111 + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, // 112..127 + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, // 128..143 + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, // 144..159 + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, // 160..175 + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, // 176..191 + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, // 192..207 + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, // 208..223 + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, // 224..239 + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 // 240..255 +}; diff --git a/CONTROL.CPP b/CONTROL.CPP new file mode 100644 index 0000000..f5e8468 --- /dev/null +++ b/CONTROL.CPP @@ -0,0 +1,202 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\control.cpv 2.18 16 Oct 1995 16:51:38 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CONTROL.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 19, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * ControlClass::Action -- Normal action for control gaget objects. * + * ControlClass::ControlClass -- Constructor for control class objects. * + * ControlClass::Draw_Me -- Draw logic for the control class object. * + * ControlClass::Get_ID -- Gets the ID number for this gadget. * + * ControlClass::Make_Peer -- Assigns a peer gadget to this gadget. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * ControlClass::ControlClass -- Constructor for control class objects. * + * * + * This is the normal constructor for control class objects. At this level, it only needs * + * to record the ID number assigned to this button. * + * * + * INPUT: id -- The ID number for this gadget. If the ID number specified is 0, then * + * this tells the system that no special ID code should be returned. * + * * + * x,y -- Pixel coordinate of upper left corner of gadget's region. * + * * + * w,h -- Pixel dimensions of the gadget's region. * + * * + * flags -- The input event flags that this gadget recognizes. * + * * + * sticky-- This this a "sticky" gadget? A sticky gadget is one that takes over the * + * gadget list while the mouse button is held down, if the mouse button was * + * initially clicked over its region. This is the behavior of "normal" * + * buttons in Windows. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +ControlClass::ControlClass(unsigned id, int x, int y, int w, int h, unsigned flags, int sticky) + : GadgetClass(x, y, w, h, flags, sticky) +{ + ID = id; + Peer = 0; +} + + +/*********************************************************************************************** + * ControlClass::Action -- Normal action for control gaget objects. * + * * + * This function gets called when the input event that this control gadget is looking for * + * occurs. In such a case, the return key code value is changed to the gaget's ID number * + * with the special button bit flag attached. * + * * + * INPUT: flags -- The event that triggered this function call. If this value is NULL, then * + * this is a forced (probably due to the sticky flag) call and the key code * + * is not altered. * + * * + * key -- Reference to the key code that will be returned by the controlling * + * Input() function. * + * * + * OUTPUT: bool; Should futher list processing be aborted? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +int ControlClass::Action(unsigned flags, KeyNumType & key) +{ + /* + ** If there is a peer link established, inform that gadget of this + ** action call. + */ + if (Peer) { + Peer->Peer_To_Peer(flags, key, *this); + } + + /* + ** Only if the flags indicate that a recognized action has occured, do the + ** normal processing of this gadget and set return value to the gadget ID. + */ + if (flags) { + if (ID) { + key = (KeyNumType)(ID | KN_BUTTON); + } else { + key = KN_NONE; + } + } + + return(GadgetClass::Action(flags, key)); +} + + +/*********************************************************************************************** + * ControlClass::Make_Peer -- Assigns a peer gadget to this gadget. * + * * + * This function will assign another gadget to this one. That other gadget will receive * + * notification of any Action() call to this gadget. Presumably, this is how one gadget * + * can automatically adapt to changes in another. Say for example, a slider bar can affect * + * the list box it is attached to. * + * * + * INPUT: gadget -- The gadget to inform when any Action() function is called. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +void ControlClass::Make_Peer(GadgetClass & gadget) +{ + Peer = &gadget; +} + + +/*********************************************************************************************** + * ControlClass::Get_ID -- Gets the ID number for this gadget. * + * * + * This function will query and return with the ID number for this gadget. It is primarily * + * used by the Extract_Gadget() function. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the ID number for this gadget. If zero is returned, this means that * + * no ID was assigned to this gadget. This is a special case since a zero value will * + * never be returned as a pseudo-key as is done with non-zero values. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +unsigned ControlClass::Get_ID(void) const +{ + return(ID); +} + + +/*********************************************************************************************** + * ControlClass::Draw_Me -- Draw logic for the control class object. * + * * + * This is called when the control object might need to be redrawn or when redrawing is * + * necessary. Since at this level of the class heirarchy, no actual drawing occurs, this * + * routine doesn't perform any rendering. It does, however, inform any peer attached * + * object that a Draw_Me function has been called. Presumably, the attached peer gadget * + * might very well need to be redrawn as a result of some action by this gadget. Since this * + * gadget might, more than likely, be of the "sticky" variety, a normal call to Draw_Me * + * for the other gadget will not occur. It must rely on the call by this routine in order * + * to update correctly. A typical example of this would be a slider that is attached to * + * a list box. As the slider is being drug around, the attached list box must be redrawn. * + * * + * INPUT: forced -- Should the redraw be forced regardless of the redraw flag? * + * * + * OUTPUT: bool; Was the gadget redrawn? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +int ControlClass::Draw_Me(int forced) +{ + if (Peer) { + Peer->Draw_Me(); + } + return(GadgetClass::Draw_Me(forced)); +} diff --git a/CONTROL.H b/CONTROL.H new file mode 100644 index 0000000..174e734 --- /dev/null +++ b/CONTROL.H @@ -0,0 +1,89 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\control.h_v 2.18 16 Oct 1995 16:46:08 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CONTROL.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 15, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef CONTROL_H +#define CONTROL_H + +#include "gadget.h" + +/*************************************************************************** + * ControlClass -- Region tracking class * + * * + * INPUT: int x -- x position of gadget * + * int y -- y position of gadget * + * int w -- width of gadget * + * int h -- height of gadget * + * UWORD flags -- see enumeration choices * + * * + * OUTPUT: 0 = new scenario created, -1 = not * + * WARNINGS: This class is Abstract (cannot make an instance of it) * + * * + * HISTORY: * + * 01/03/1995 MML : Created. * + *=========================================================================*/ +class ControlClass : public GadgetClass +{ + public: + ControlClass(unsigned id, int x, int y, int w, int h, unsigned flags=LEFTPRESS|RIGHTPRESS, int sticky=false); +// static ControlClass * Create_One_Of(unsigned id, int x, int y, int w, int h, unsigned flags=LEFTPRESS|RIGHTPRESS, int sticky=false); + + virtual void Make_Peer(GadgetClass & gadget); + + /* + ** Render support function. + */ + virtual int Draw_Me(int forced=false); + + /* + ** This is the ID number for this control gadget. This number is used to generate + ** a special pseudo-key when the gadget detects valid input. + */ + unsigned ID; + + protected: + virtual unsigned Get_ID(void) const; + virtual int Action(unsigned flags, KeyNumType & key); + + /* + ** This points to the peer button to inform when something happens to this + ** gadget. + */ + GadgetClass * Peer; +}; + +#endif + diff --git a/COORD.CPP b/COORD.CPP new file mode 100644 index 0000000..369b276 --- /dev/null +++ b/COORD.CPP @@ -0,0 +1,545 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\coord.cpv 2.18 16 Oct 1995 16:51:24 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : COORD.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : January 7, 1995 [JLB] * + * * + * Support code to handle the coordinate system is located in this module. * + * Routines here will be called QUITE frequently during play and must be * + * as efficient as possible. * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Cardinal_To_Fixed -- Converts cardinal numbers into a fixed point number. * + * Coord_Move -- Moves a coordinate an arbitrary direction for an arbitrary distance * + * Coord_Scatter -- Determines a random coordinate from an anchor point. * + * Coord_Spillage_List -- Determines the offset list for cell spillage/occupation. * + * Fixed_To_Cardinal -- Converts a fixed point number into a cardinal number. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * Coord_Spillage_List -- Determines the offset list for cell spillage/occupation. * + * * + * This routine will take an arbitrary position and object size and return with a list of * + * cell offsets from the current cell for all cells that are overlapped by the object. The * + * first cell offset is always zero, so to just get the adjacent spill cell list, add one * + * to the return pointer. * + * * + * INPUT: coord -- The coordinate to examine. * + * * + * maxsize -- The maximum width/height of the object (pixels). * + * * + * OUTPUT: Returns with a pointer to a spillage list. * + * * + * WARNINGS: The algorithm is limited to working with a maxsize of 48 or less. Larger values * + * will generate an incomplete overlap list. * + * * + * HISTORY: * + * 11/06/1993 JLB : Created. * + * 03/25/1994 JLB : Added width optimization. * + * 04/29/1994 JLB : Converted to C. * + * 06/03/1994 JLB : Converted to general purpose spillage functionality. * + * 01/07/1995 JLB : Manually calculates spillage list for large objects. * + *=============================================================================================*/ +short const * Coord_Spillage_List(COORDINATE coord, int maxsize) +{ + static short const _MoveSpillage[(int)FACING_COUNT+1][5] = { + {0, -MAP_CELL_W, REFRESH_EOL, 0, 0}, // N + {0, -MAP_CELL_W, 1, -(MAP_CELL_W-1), REFRESH_EOL}, // NE + {0, 1, REFRESH_EOL, 0, 0}, // E + {0, 1, MAP_CELL_W, MAP_CELL_W+1, REFRESH_EOL}, // SE + {0, MAP_CELL_W, REFRESH_EOL, 0, 0}, // S + {0, -1, MAP_CELL_W, MAP_CELL_W-1, REFRESH_EOL}, // SW + {0, -1, REFRESH_EOL, 0, 0}, // W + {0, -1, -MAP_CELL_W, -(MAP_CELL_W+1), REFRESH_EOL}, // NW + {0, REFRESH_EOL, 0, 0, 0} // non-moving. +// {0, -MAP_CELL_W, -(MAP_CELL_W-1), 1, MAP_CELL_W+1, MAP_CELL_W, MAP_CELL_W-1, -1, -(MAP_CELL_W+1), REFRESH_EOL} + }; + static short _manual[10]; +//; 00 = on axis +//; 01 = below axis +//; 10 = above axis +//; 11 = undefined + static char const _SpillTable[16] = {8,6,2,-1,0,7,1,-1,4,5,3,-1,-1,-1,-1,-1}; + int index=0; + int x,y; + + /* + ** For mondo-enourmo-gigundo objects, use a prebuilt mammoth table + ** that covers a 5x5 square region. + */ + if (maxsize > ICON_PIXEL_W * 2) { + static short const _gigundo[] = { + -((2*MAP_CELL_W)-2),-((2*MAP_CELL_W)-1),-((2*MAP_CELL_W)),-((2*MAP_CELL_W)+1),-((2*MAP_CELL_W)+2), + -((1*MAP_CELL_W)-2),-((1*MAP_CELL_W)-1),-((1*MAP_CELL_W)),-((1*MAP_CELL_W)+1),-((1*MAP_CELL_W)+2), + -((0*MAP_CELL_W)-2),-((0*MAP_CELL_W)-1),-((0*MAP_CELL_W)),-((0*MAP_CELL_W)+1),-((0*MAP_CELL_W)+2), + ((1*MAP_CELL_W)-2),((1*MAP_CELL_W)-1),((1*MAP_CELL_W)),((1*MAP_CELL_W)+1),((1*MAP_CELL_W)+2), + +((2*MAP_CELL_W)-2),+((2*MAP_CELL_W)-1),+((2*MAP_CELL_W)),+((2*MAP_CELL_W)+1),+((2*MAP_CELL_W)+2), + REFRESH_EOL + }; + return(&_gigundo[0]); + } + + /* + ** For very large objects, build the overlap list by hand. This is time consuming, but + ** not nearly as time consuming as drawing even a single cell unnecessarily. + */ + if (maxsize > ICON_PIXEL_W) { + maxsize = MIN(maxsize, (ICON_PIXEL_W*2))/2; + + x = Fixed_To_Cardinal(ICON_PIXEL_W, Coord_XLepton(coord)); + y = Fixed_To_Cardinal(ICON_PIXEL_H, Coord_YLepton(coord)); + int left = x-maxsize; + int right = x+maxsize; + int top = y-maxsize; + int bottom = y+maxsize; + + _manual[index++] = 0; + if (left < 0) _manual[index++] = -1; + if (right >= ICON_PIXEL_W) _manual[index++] = 1; + if (top < 0) _manual[index++] = -MAP_CELL_W; + if (bottom >= ICON_PIXEL_H) _manual[index++] = MAP_CELL_W; + if (left < 0 && top < 0) _manual[index++] = -(MAP_CELL_W+1); + if (right >= ICON_PIXEL_W && bottom >= ICON_PIXEL_H) _manual[index++] = MAP_CELL_W+1; + if (left < 0 && bottom >= ICON_PIXEL_H) _manual[index++] = MAP_CELL_W-1; + if (right >= ICON_PIXEL_H && top < 0) _manual[index++] = -(MAP_CELL_W-1); + _manual[index] = REFRESH_EOL; + return(&_manual[0]); + } + + /* + ** Determine the number of leptons "leeway" allowed this unit. + */ + int posval = Pixel2Lepton[(ICON_PIXEL_W-maxsize)/2]; + + x = Coord_XLepton(coord) - 0x0080; + y = Coord_YLepton(coord) - 0x0080; + if (y > posval) index |= 0x08; // Spilling South. + if (y < -posval) index |= 0x04; // Spilling North. + if (x > posval) index |= 0x02; // Spilling East. + if (x < -posval) index |= 0x01; // Spilling West. + + return(&_MoveSpillage[_SpillTable[index]][0]); +} + + +/*********************************************************************************************** + * Coord_Move -- Moves a coordinate an arbitrary direction for an arbitrary distance * + * * + * This function will move a coordinate in a using SIN and COS arithmetic. * + * * + * INPUT: start -- The starting coordinate. * + * * + * dir -- The direction to move the coordinate. * + * * + * distance -- The distance to move the coordinate position (in leptons). * + * * + * OUTPUT: Returns the new coordinate position. * + * * + * WARNINGS: This routine uses multiplies -- use with caution. * + * * + * HISTORY: * + * 05/27/1994 JLB : Created. * + *=============================================================================================*/ +COORDINATE Coord_Move(COORDINATE start, register DirType dir, unsigned short distance) +{ + short x = Coord_X(start); + short y = Coord_Y(start); + + Move_Point(x, y, dir, distance); + return(XY_Coord(x,y)); +#ifdef NEVER + static char const CosTable[256] = { + 0x00,0x03,0x06,0x09,0x0c,0x0f,0x12,0x15, + 0x18,0x1b,0x1e,0x21,0x24,0x27,0x2a,0x2d, + 0x30,0x33,0x36,0x39,0x3b,0x3e,0x41,0x43, + 0x46,0x49,0x4b,0x4e,0x50,0x52,0x55,0x57, + 0x59,0x5b,0x5e,0x60,0x62,0x64,0x65,0x67, + 0x69,0x6b,0x6c,0x6e,0x6f,0x71,0x72,0x74, + 0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7b, + 0x7c,0x7d,0x7d,0x7e,0x7e,0x7e,0x7e,0x7e, + + 0x7f,0x7e,0x7e,0x7e,0x7e,0x7e,0x7d,0x7d, + 0x7c,0x7b,0x7b,0x7a,0x79,0x78,0x77,0x76, + 0x75,0x74,0x72,0x71,0x70,0x6e,0x6c,0x6b, + 0x69,0x67,0x66,0x64,0x62,0x60,0x5e,0x5b, + 0x59,0x57,0x55,0x52,0x50,0x4e,0x4b,0x49, + 0x46,0x43,0x41,0x3e,0x3b,0x39,0x36,0x33, + 0x30,0x2d,0x2a,0x27,0x24,0x21,0x1e,0x1b, + 0x18,0x15,0x12,0x0f,0x0c,0x09,0x06,0x03, + + 0x00,0xfd,0xfa,0xf7,0xf4,0xf1,0xee,0xeb, + 0xe8,0xe5,0xe2,0xdf,0xdc,0xd9,0xd6,0xd3, + 0xd0,0xcd,0xca,0xc7,0xc5,0xc2,0xbf,0xbd, + 0xba,0xb7,0xb5,0xb2,0xb0,0xae,0xab,0xa9, + 0xa7,0xa5,0xa2,0xa0,0x9e,0x9c,0x9a,0x99, + 0x97,0x95,0x94,0x92,0x91,0x8f,0x8e,0x8c, + 0x8b,0x8a,0x89,0x88,0x87,0x86,0x85,0x85, + 0x84,0x83,0x83,0x82,0x82,0x82,0x82,0x82, + + 0x82,0x82,0x82,0x82,0x82,0x82,0x83,0x83, + 0x84,0x85,0x85,0x86,0x87,0x88,0x89,0x8a, + 0x8b,0x8c,0x8e,0x8f,0x90,0x92,0x94,0x95, + 0x97,0x99,0x9a,0x9c,0x9e,0xa0,0xa2,0xa5, + 0xa7,0xa9,0xab,0xae,0xb0,0xb2,0xb5,0xb7, + 0xba,0xbd,0xbf,0xc2,0xc5,0xc7,0xca,0xcd, + 0xd0,0xd3,0xd6,0xd9,0xdc,0xdf,0xe2,0xe5, + 0xe8,0xeb,0xee,0xf1,0xf4,0xf7,0xfa,0xfd, + }; + + static char const SinTable[256] = { + 0x7f,0x7e,0x7e,0x7e,0x7e,0x7e,0x7d,0x7d, + 0x7c,0x7b,0x7b,0x7a,0x79,0x78,0x77,0x76, + 0x75,0x74,0x72,0x71,0x70,0x6e,0x6c,0x6b, + 0x69,0x67,0x66,0x64,0x62,0x60,0x5e,0x5b, + 0x59,0x57,0x55,0x52,0x50,0x4e,0x4b,0x49, + 0x46,0x43,0x41,0x3e,0x3b,0x39,0x36,0x33, + 0x30,0x2d,0x2a,0x27,0x24,0x21,0x1e,0x1b, + 0x18,0x15,0x12,0x0f,0x0c,0x09,0x06,0x03, + + 0x00,0xfd,0xfa,0xf7,0xf4,0xf1,0xee,0xeb, + 0xe8,0xe5,0xe2,0xdf,0xdc,0xd9,0xd6,0xd3, + 0xd0,0xcd,0xca,0xc7,0xc5,0xc2,0xbf,0xbd, + 0xba,0xb7,0xb5,0xb2,0xb0,0xae,0xab,0xa9, + 0xa7,0xa5,0xa2,0xa0,0x9e,0x9c,0x9a,0x99, + 0x97,0x95,0x94,0x92,0x91,0x8f,0x8e,0x8c, + 0x8b,0x8a,0x89,0x88,0x87,0x86,0x85,0x85, + 0x84,0x83,0x83,0x82,0x82,0x82,0x82,0x82, + + 0x82,0x82,0x82,0x82,0x82,0x82,0x83,0x83, + 0x84,0x85,0x85,0x86,0x87,0x88,0x89,0x8a, + 0x8b,0x8c,0x8e,0x8f,0x90,0x92,0x94,0x95, + 0x97,0x99,0x9a,0x9c,0x9e,0xa0,0xa2,0xa5, + 0xa7,0xa9,0xab,0xae,0xb0,0xb2,0xb5,0xb7, + 0xba,0xbd,0xbf,0xc2,0xc5,0xc7,0xca,0xcd, + 0xd0,0xd3,0xd6,0xd9,0xdc,0xdf,0xe2,0xe5, + 0xe8,0xeb,0xee,0xf1,0xf4,0xf7,0xfa,0xfd, + + 0x00,0x03,0x06,0x09,0x0c,0x0f,0x12,0x15, + 0x18,0x1b,0x1e,0x21,0x24,0x27,0x2a,0x2d, + 0x30,0x33,0x36,0x39,0x3b,0x3e,0x41,0x43, + 0x46,0x49,0x4b,0x4e,0x50,0x52,0x55,0x57, + 0x59,0x5b,0x5e,0x60,0x62,0x64,0x65,0x67, + 0x69,0x6b,0x6c,0x6e,0x6f,0x71,0x72,0x74, + 0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7b, + 0x7c,0x7d,0x7d,0x7e,0x7e,0x7e,0x7e,0x7e, + }; + distance = distance; // Keep LINT quiet. + + /* + ** Calculate and add in the X component of the move. + */ + _AX = CosTable[dir]; + asm imul word ptr distance + asm shl ax,1 + asm rcl dx,1 + asm mov al,ah + asm mov ah,dl + _DX = _AX; + *((int*)&start) += _DX; +// asm add [word ptr start],ax + + /* + ** Calculate and add in the Y component of the move. + */ + _AX = SinTable[dir]; + asm imul word ptr distance + asm shl ax,1 + asm rcl dx,1 + asm mov al,ah + asm mov ah,dl + asm neg ax // Subtraction needed because of inverted sine table. + _DX = _AX; + *(((int*)&start)+1) += _DX; +// asm add [word ptr start+2],ax + + return(start); +#endif +} + +#ifdef OBSOLETE +//BG: Note, this routine is replaced by assembly in COORDA.ASM +/*********************************************************************************************** + * Cardinal_To_Fixed -- Converts cardinal numbers into a fixed point number. * + * * + * This utility function will convert cardinal numbers into a fixed point fraction. The * + * use of fixed point numbers occurs throughout the product -- since it is a convenient * + * tool. The fixed point number is based on the formula: * + * * + * result = cardinal / base * + * * + * The accuracy of the fixed point number is limited to 1/256 as the lowest and up to * + * 256 as the largest. * + * * + * INPUT: base -- The key number to base the fraction about. * + * * + * cardinal -- The other number (hey -- what do you call it?) * + * * + * OUTPUT: Returns with the fixed point number of the "cardinal" parameter as it relates * + * to the "base" parameter. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/27/1994 JLB : Created. * + *=============================================================================================*/ +unsigned Cardinal_To_Fixed(unsigned base, unsigned cardinal) +{ + long temp = 0; + + if (base) { + *(unsigned*)(((char*)&temp)+1) = cardinal; // Shift up by 8 bits. + _DX = FP_SEG(temp); + _AX = FP_OFF(temp); + asm div word ptr base + return(_AX); + } + return(0xFFFF); +} +#endif + +#ifdef OBSOLETE +//BG: Note, this routine is replaced by assembly in COORDA.ASM +/*********************************************************************************************** + * Fixed_To_Cardinal -- Converts a fixed point number into a cardinal number. * + * * + * Use this routine to convert a fixed point number into a cardinal number. * + * * + * INPUT: base -- The base number that the original fixed point number was created from. * + * * + * fixed -- The fixed point number to convert. * + * * + * OUTPUT: Returns with the reconverted number. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/27/1994 JLB : Created. * + *=============================================================================================*/ +unsigned Fixed_To_Cardinal(unsigned base, unsigned fixed) +{ + unsigned long temp; + + _AX = base; + asm mul word ptr fixed + _CX = _AX; + temp = ((unsigned long)MK_FP(_DX, _CX)) + 0x80; + if ( *((char*)&temp+3) ) { + return(0xFFFF); + } + return(*(unsigned*)(((char*)&temp)+1)); +} +#endif + + +/*********************************************************************************************** + * Coord_Scatter -- Determines a random coordinate from an anchor point. * + * * + * This routine will perform a scatter algorithm on the specified * + * anchor point in order to return with another coordinate that is * + * randomly nearby the original. Typical use of this would be for * + * missile targeting. * + * * + * INPUT: coord -- This is the anchor coordinate. * + * * + * distance -- This is the distance in pixels that the scatter * + * should fall within. * + * * + * lock -- bool; Convert the new coordinate into a center * + * cell based coordinate? * + * * + * OUTPUT: Returns with a new coordinate that is nearby the original. * + * * + * WARNINGS: Maximum pixel scatter distance is 255. * + * * + * HISTORY: * + * 02/01/1992 JLB : Created. * + * 05/13/1992 JLB : Only uses Random(). * + *=============================================================================================*/ +COORDINATE Coord_Scatter(COORDINATE coord, unsigned distance, bool lock) +{ + COORDINATE newcoord; + + newcoord = Coord_Move(coord, Random_Pick(DIR_N, DIR_MAX), distance); + + if (newcoord & 0xC000C000L) newcoord = coord; + + if (lock) { + newcoord = Coord_Snap(newcoord); + } + + return(newcoord); +} + +extern int calcx(signed short, short distance); +#pragma aux calcx parm [ax] [bx] \ + modify [eax dx] \ + value [eax] = \ + "imul bx" \ + "shl ax,1" \ + "rcl dx,1" \ + "mov al,ah" \ + "mov ah,dl" \ + "cwd" \ +// "and eax,0FFFFh"; + +extern int calcy(signed short, short distance); +#pragma aux calcy parm [ax] [bx] \ + modify [eax dx] \ + value [eax] = \ + "imul bx" \ + "shl ax,1" \ + "rcl dx,1" \ + "mov al,ah" \ + "mov ah,dl" \ + "cwd" \ + "neg eax"; +// "and eax,0FFFFh" \ + +void Move_Point(short &x, short &y, register DirType dir, unsigned short distance) +{ +// static char const CosTable[256] = { + static char const CosTable[256] = { + 0x00,0x03,0x06,0x09,0x0c,0x0f,0x12,0x15, + 0x18,0x1b,0x1e,0x21,0x24,0x27,0x2a,0x2d, + 0x30,0x33,0x36,0x39,0x3b,0x3e,0x41,0x43, + 0x46,0x49,0x4b,0x4e,0x50,0x52,0x55,0x57, + 0x59,0x5b,0x5e,0x60,0x62,0x64,0x65,0x67, + 0x69,0x6b,0x6c,0x6e,0x6f,0x71,0x72,0x74, + 0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7b, + 0x7c,0x7d,0x7d,0x7e,0x7e,0x7e,0x7e,0x7e, + + 0x7f,0x7e,0x7e,0x7e,0x7e,0x7e,0x7d,0x7d, + 0x7c,0x7b,0x7b,0x7a,0x79,0x78,0x77,0x76, + 0x75,0x74,0x72,0x71,0x70,0x6e,0x6c,0x6b, + 0x69,0x67,0x66,0x64,0x62,0x60,0x5e,0x5b, + 0x59,0x57,0x55,0x52,0x50,0x4e,0x4b,0x49, + 0x46,0x43,0x41,0x3e,0x3b,0x39,0x36,0x33, + 0x30,0x2d,0x2a,0x27,0x24,0x21,0x1e,0x1b, + 0x18,0x15,0x12,0x0f,0x0c,0x09,0x06,0x03, + + 0x00,0xfd,0xfa,0xf7,0xf4,0xf1,0xee,0xeb, + 0xe8,0xe5,0xe2,0xdf,0xdc,0xd9,0xd6,0xd3, + 0xd0,0xcd,0xca,0xc7,0xc5,0xc2,0xbf,0xbd, + 0xba,0xb7,0xb5,0xb2,0xb0,0xae,0xab,0xa9, + 0xa7,0xa5,0xa2,0xa0,0x9e,0x9c,0x9a,0x99, + 0x97,0x95,0x94,0x92,0x91,0x8f,0x8e,0x8c, + 0x8b,0x8a,0x89,0x88,0x87,0x86,0x85,0x85, + 0x84,0x83,0x83,0x82,0x82,0x82,0x82,0x82, + + 0x82,0x82,0x82,0x82,0x82,0x82,0x83,0x83, + 0x84,0x85,0x85,0x86,0x87,0x88,0x89,0x8a, + 0x8b,0x8c,0x8e,0x8f,0x90,0x92,0x94,0x95, + 0x97,0x99,0x9a,0x9c,0x9e,0xa0,0xa2,0xa5, + 0xa7,0xa9,0xab,0xae,0xb0,0xb2,0xb5,0xb7, + 0xba,0xbd,0xbf,0xc2,0xc5,0xc7,0xca,0xcd, + 0xd0,0xd3,0xd6,0xd9,0xdc,0xdf,0xe2,0xe5, + 0xe8,0xeb,0xee,0xf1,0xf4,0xf7,0xfa,0xfd, + }; + +// static char const SinTable[256] = { + static char const SinTable[256] = { + 0x7f,0x7e,0x7e,0x7e,0x7e,0x7e,0x7d,0x7d, + 0x7c,0x7b,0x7b,0x7a,0x79,0x78,0x77,0x76, + 0x75,0x74,0x72,0x71,0x70,0x6e,0x6c,0x6b, + 0x69,0x67,0x66,0x64,0x62,0x60,0x5e,0x5b, + 0x59,0x57,0x55,0x52,0x50,0x4e,0x4b,0x49, + 0x46,0x43,0x41,0x3e,0x3b,0x39,0x36,0x33, + 0x30,0x2d,0x2a,0x27,0x24,0x21,0x1e,0x1b, + 0x18,0x15,0x12,0x0f,0x0c,0x09,0x06,0x03, + + 0x00,0xfd,0xfa,0xf7,0xf4,0xf1,0xee,0xeb, + 0xe8,0xe5,0xe2,0xdf,0xdc,0xd9,0xd6,0xd3, + 0xd0,0xcd,0xca,0xc7,0xc5,0xc2,0xbf,0xbd, + 0xba,0xb7,0xb5,0xb2,0xb0,0xae,0xab,0xa9, + 0xa7,0xa5,0xa2,0xa0,0x9e,0x9c,0x9a,0x99, + 0x97,0x95,0x94,0x92,0x91,0x8f,0x8e,0x8c, + 0x8b,0x8a,0x89,0x88,0x87,0x86,0x85,0x85, + 0x84,0x83,0x83,0x82,0x82,0x82,0x82,0x82, + + 0x82,0x82,0x82,0x82,0x82,0x82,0x83,0x83, + 0x84,0x85,0x85,0x86,0x87,0x88,0x89,0x8a, + 0x8b,0x8c,0x8e,0x8f,0x90,0x92,0x94,0x95, + 0x97,0x99,0x9a,0x9c,0x9e,0xa0,0xa2,0xa5, + 0xa7,0xa9,0xab,0xae,0xb0,0xb2,0xb5,0xb7, + 0xba,0xbd,0xbf,0xc2,0xc5,0xc7,0xca,0xcd, + 0xd0,0xd3,0xd6,0xd9,0xdc,0xdf,0xe2,0xe5, + 0xe8,0xeb,0xee,0xf1,0xf4,0xf7,0xfa,0xfd, + + 0x00,0x03,0x06,0x09,0x0c,0x0f,0x12,0x15, + 0x18,0x1b,0x1e,0x21,0x24,0x27,0x2a,0x2d, + 0x30,0x33,0x36,0x39,0x3b,0x3e,0x41,0x43, + 0x46,0x49,0x4b,0x4e,0x50,0x52,0x55,0x57, + 0x59,0x5b,0x5e,0x60,0x62,0x64,0x65,0x67, + 0x69,0x6b,0x6c,0x6e,0x6f,0x71,0x72,0x74, + 0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7b, + 0x7c,0x7d,0x7d,0x7e,0x7e,0x7e,0x7e,0x7e, + }; + distance = distance; // Keep LINT quiet. + +#ifdef OBSOLETE + /* + ** Calculate and add in the X component of the move. + */ + _AX = CosTable[dir]; + asm imul word ptr distance + asm shl ax,1 + asm rcl dx,1 + asm mov al,ah + asm mov ah,dl + _DX = _AX; + x += _DX; +#else + x += calcx(CosTable[dir], distance); +#endif +// asm add [word ptr start],ax + +#ifdef OBSOLETE + /* + ** Calculate and add in the Y component of the move. + */ + _AX = SinTable[dir]; + asm imul word ptr distance + asm shl ax,1 + asm rcl dx,1 + asm mov al,ah + asm mov ah,dl + asm neg ax // Subtraction needed because of inverted sine table. + _DX = _AX; + y += _DX; +#else + y += calcy(SinTable[dir], distance); +#endif +// asm add [word ptr start+2],ax + +} diff --git a/COORDA.ASM b/COORDA.ASM new file mode 100644 index 0000000..3d31d1e --- /dev/null +++ b/COORDA.ASM @@ -0,0 +1,134 @@ +; +; Command & Conquer(tm) +; Copyright 2025 Electronic Arts Inc. +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; + +;*************************************************************************** +;** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S I N C ** +;*************************************************************************** +;* * +;* Project Name : Command & Conquer * +;* * +;* File Name : COORDA.ASM * +;* * +;* Programmer : Barry W. Green * +;* * +;* Start Date : February 17, 1995 * +;* * +;* Last Update : February 17, 1995 [BWG] * +;* * +;*-------------------------------------------------------------------------* +;* Functions: * +;* Cardinal_To_Fixed -- Converts cardinal numbers into a fixed point number. * +;* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * + + +IDEAL +P386 +MODEL USE32 FLAT + +global C Cardinal_To_Fixed :NEAR +global C Fixed_To_Cardinal :NEAR + + CODESEG + +;*********************************************************************************************** +;* Cardinal_To_Fixed -- Converts cardinal numbers into a fixed point number. * +;* * +;* This utility function will convert cardinal numbers into a fixed point fraction. The * +;* use of fixed point numbers occurs throughout the product -- since it is a convenient * +;* tool. The fixed point number is based on the formula: * +;* * +;* result = cardinal / base * +;* * +;* The accuracy of the fixed point number is limited to 1/256 as the lowest and up to * +;* 256 as the largest. * +;* * +;* INPUT: base -- The key number to base the fraction about. * +;* * +;* cardinal -- The other number (hey -- what do you call it?) * +;* * +;* OUTPUT: Returns with the fixed point number of the "cardinal" parameter as it relates * +;* to the "base" parameter. * +;* * +;* WARNINGS: none * +;* * +;* HISTORY: * +;* 02/17/1995 BWG : Created. * +;*=============================================================================================*/ +;unsigned int Cardinal_To_Fixed(unsigned base, unsigned cardinal); + + PROC Cardinal_To_Fixed C near + USES ebx, edx + + ARG base:DWORD + ARG cardinal:DWORD + + mov eax,0FFFFh ; establish default return value + + mov ebx,[base] + or ebx,ebx + jz near ??retneg1 ; if base==0, return 65535 + + mov eax,[cardinal] ; otherwise, return (cardinal*256)/base + shl eax,8 + xor edx,edx + div ebx + +??retneg1: + ret + + ENDP Cardinal_To_Fixed + + +;*********************************************************************************************** +;* Fixed_To_Cardinal -- Converts a fixed point number into a cardinal number. * +;* * +;* Use this routine to convert a fixed point number into a cardinal number. * +;* * +;* INPUT: base -- The base number that the original fixed point number was created from. * +;* * +;* fixed -- The fixed point number to convert. * +;* * +;* OUTPUT: Returns with the reconverted number. * +;* * +;* WARNINGS: none * +;* * +;* HISTORY: * +;* 02/17/1995 BWG : Created. * +;*=============================================================================================*/ +;unsigned int Fixed_To_Cardinal(unsigned base, unsigned fixed); + PROC Fixed_To_Cardinal C near + USES edx + + ARG base:DWORD + ARG fixed:DWORD + + mov eax,[base] + mul [fixed] + add eax,080h ; eax = (base * fixed) + 0x80 + + test eax,0FF000000h ; if high byte set, return FFFF + jnz ??rneg1 + shr eax,8 ; else, return eax/256 + ret +??rneg1 : + mov eax,0FFFFh ; establish default return value + ret + + ENDP Fixed_To_Cardinal + + END diff --git a/CREDITS.CPP b/CREDITS.CPP new file mode 100644 index 0000000..774afc2 --- /dev/null +++ b/CREDITS.CPP @@ -0,0 +1,176 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\credits.cpv 2.17 16 Oct 1995 16:51:28 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CREDITS.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 17, 1994 * + * * + * Last Update : March 13, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * CreditClass::AI -- Handles updating the credit display. * + * CreditClass::Graphic_Logic -- Handles the credit redraw logic. * + * CreditClass::CreditClass -- Default constructor for the credit class object. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * CreditClass::CreditClass -- Default constructor for the credit class object. * + * * + * This is the constructor for the credit class object. It merely sets the credit display * + * state to null. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/13/1995 JLB : Created. * + *=============================================================================================*/ +CreditClass::CreditClass(void) +{ + IsToRedraw = false; + IsUp = false; + IsAudible = false; + Credits = 0; + Current = 0; + Countdown = 0; +} + + +/*********************************************************************************************** + * CreditClass::Graphic_Logic -- Handles the credit redraw logic. * + * * + * This routine should be called whenever the main game screen is to be updated. It will * + * check to see if the credit display should be redrawn. If so, it will redraw it. * + * * + * INPUT: forced -- Should the credit display be redrawn regardless of whether the redraw * + * flag is set? This is typically the case when the screen needs to be * + * redrawn from scratch. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/13/1995 JLB : Created. * + *=============================================================================================*/ +#define XX (320-120) +#define WW 50 +void CreditClass::Graphic_Logic(bool forced) +{ + int factor = Get_Resolution_Factor(); + int xx = SeenBuff.Get_Width() - (120 << factor); + if (forced || IsToRedraw) { + + /* + ** Play a sound effect when the money display changes, but only if a sound + ** effect was requested. + */ + if (IsAudible) { + if (IsUp) { + Sound_Effect(VOC_UP, VOL_1); + } else { + Sound_Effect(VOC_DOWN, VOL_1); + } + } + + /* + ** Display the new current value. + */ + //LogicPage->Fill_Rect(xx-(20 << factor), 1 << factor, xx+(20 << factor), 6 << factor, LTGREY); + TabClass::Draw_Credits_Tab(); + Fancy_Text_Print("%ld", xx, 0, 11, TBLACK, TPF_GREEN12_GRAD|TPF_CENTER | TPF_USE_GRAD_PAL, Current); + + IsToRedraw = false; + IsAudible = false; + } +} + + +/*********************************************************************************************** + * CreditClass::AI -- Handles updating the credit display. * + * * + * This routine handles the logic that controls the rate of credit change in the credit * + * display. It doesn't actually redraw the credit display, but will flag it to be redrawn * + * if it detects that a change is to occur. * + * * + * INPUT: forced -- Should the credit display immediately reflect the current credit * + * total for the player? This is usually desired when initially loading * + * a scenario or saved game. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/13/1995 JLB : Created. * + *=============================================================================================*/ +void CreditClass::AI(bool forced) +{ + Credits = PlayerPtr->Available_Money(); + + /* + ** Make sure that the credit counter doesn't drop below zero. + */ + Credits = MAX(Credits, 0L); + + if (Current == Credits) return; + + if (forced) { + IsAudible = false; + Current = Credits; + } else { + + if (Countdown) Countdown--; + if (Countdown) return; + + /* + ** Determine the amount to change the display toward the + ** desired value. + */ + long adder = Credits - Current; + adder = ABS(adder); + adder >>= 5; + adder = Bound(adder, 1L, 71+72); + if (Current > Credits) adder = -adder; + Current += adder; + Countdown = 1; + + if (Current-adder != Current) { + IsAudible = true; + IsUp = (adder > 0); + } + } + IsToRedraw = true; + Map.Flag_To_Redraw(false); +} diff --git a/CREDITS.H b/CREDITS.H new file mode 100644 index 0000000..b08c46c --- /dev/null +++ b/CREDITS.H @@ -0,0 +1,72 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\credits.h_v 2.18 16 Oct 1995 16:47:26 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CREDIT.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 19, 1994 * + * * + * Last Update : April 19, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef CREDITS_H +#define CREDITS_H + +/**************************************************************************** +** The animating credit counter display is controlled by this class. +*/ +class CreditClass { + public: + long Credits; // Value of credits trying to update display to. + + /*--------------------------------------------------------------------- + ** Constructors, Destructors, and overloaded operators. + */ + CreditClass(void); + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + void Update(bool forced=false, bool redraw=false); + + void Graphic_Logic(bool forced=false); + void AI(bool forced=false); + + long Current; // Credit value currently displayed. + + unsigned IsToRedraw:1; + unsigned IsUp:1; + unsigned IsAudible:1; + + private: + int Countdown; // Delay between ticks. +}; + +#endif + diff --git a/CREW.CPP b/CREW.CPP new file mode 100644 index 0000000..18bf6fc --- /dev/null +++ b/CREW.CPP @@ -0,0 +1,38 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\crew.cpv 2.18 16 Oct 1995 16:50:50 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CREW.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 23, 1994 * + * * + * Last Update : April 23, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" diff --git a/CREW.H b/CREW.H new file mode 100644 index 0000000..0b9bac8 --- /dev/null +++ b/CREW.H @@ -0,0 +1,65 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\crew.h_v 2.18 16 Oct 1995 16:47:56 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CREW.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 23, 1994 * + * * + * Last Update : April 23, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef CREW_H +#define CREW_H + + +/**************************************************************************** +** This class handles the basic crew logic. This includes hero tracking, +** crew bail-out, and attached object logic. +*/ +class CrewClass +{ + public: + /* + ** This keeps track of the number of "kills" the unit as accumulated. + ** When it reaches a certain point, the unit improves. + */ + unsigned short Kills; + + /* + ** Constructors, Destructors, and overloaded operators. + */ + CrewClass(void) {Kills = 0;}; + + int Made_A_Kill(void) {Kills++;return(Kills);}; + + private: +}; + +#endif diff --git a/CWSTUB.C b/CWSTUB.C new file mode 100644 index 0000000..0834d8d --- /dev/null +++ b/CWSTUB.C @@ -0,0 +1,71 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include + +char *dos4g_path() +{ + static char *paths_to_check[] = { + "DOS4GPATH", + "PATH" + }; + static char fullpath[80]; + char *dos4gpath; + int i; + + /* If DOS4GPATH points to an executable file name, don't bother + searching any paths for DOS4GW.EXE. + */ + if (dos4gpath = getenv("DOS4GPATH")) { + strlwr(strcpy(fullpath, dos4gpath)); + if (strstr(fullpath, ".exe")) { + return(fullpath); + } + } + for( i = 0; i < sizeof(paths_to_check) / sizeof(paths_to_check[0]); i++ ) { + _searchenv("dos4gw.exe", paths_to_check[i], fullpath); + if (fullpath[0]) { + return( &fullpath ); + } + } + return("dos4gw.exe"); +} + +main( int argc, char *argv[] ) +{ + char *av[4]; + auto char cmdline[128]; + + av[0] = dos4g_path(); /* Locate the DOS/4GW loader */ + av[1] = argv[0]; /* name of executable to run */ + av[2] = getcmd(cmdline); /* command line */ + av[3] = NULL; /* end of list */ +#ifdef VMM + putenv("DOS4GVM=MINMEM#2000 MAXMEM#16000 SWAPMIN#4096 SWAPINC#1024 VIRTUALSIZE#10000 SWAPFILE#CONQUER.SWP DELETESWAP @CONQUER.VMC"); +#endif +#ifdef QUIET + putenv("DOS4G=QUIET"); /* disables DOS/4GW Copyright banner */ +#endif + execvp(av[0], av); + perror(av[0]); + exit(1); /* indicate error */ +} diff --git a/DDE.CPP b/DDE.CPP new file mode 100644 index 0000000..5fbdebe --- /dev/null +++ b/DDE.CPP @@ -0,0 +1,450 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Dynamic Data Encapsulation * + * * + * File Name : DDE.CPP * + * * + * Programmer : Steve Wetherill * + * * + * Start Date : June 1, 1996 * + * * + * Last Update : June 8, 1996 [SW] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * Instance_Class::InstanceClass -- class constructor * + * Instance_Class::InstanceClass -- class destructor * + * Instance_Class::Enable_Callback -- enables local processing of pokes * + * Instance_Class::Register_Servers -- registers a local DDE DNS service * + * Instance_Class::Cleanup_App -- currently does nothing * + * Instance_Class::Test_Server_Running -- does a trial connect to remote * + * Instance_Class::Open_Poke_Connection -- pokes some data to server * + * Instance_Class::Close_Poke_Connectionp -- closes connection to remote * + * Instance_Class::Poke_Server -- sends a chunk of data to remote * + * Instance_Class::dde_callback -- processes DDE transactions * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#define WIN32 +#include +#include "dde.h" + +/*************************************************************************** + * These are static members of Instance_Class + *=========================================================================*/ + +static DWORD Instance_Class::id_inst; // instance identifier set by DdeInitialize +static BOOL Instance_Class::process_pokes; // controls response to pokes +static char Instance_Class::ascii_name[32]; // name of server + +static BOOL CALLBACK (*Instance_Class::callback) ( + LPBYTE pointer, // pointer to received data + long length // length of received data or advisory flag + ) = NULL; + +/*************************************************************************** + * Instance_Class::InstanceClass -- class constructor * + * * + * INPUT: * + * name1 null terminated ASCII client name * + * name1 null terminated ASCII server name * + * * + * OUTPUT: * + * dde_error = TRUE if error occurs when initializing DDE * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 6/1/1996 SW : Created. * + *=========================================================================*/ + +Instance_Class::Instance_Class( LPSTR name1, LPSTR name2 ) +{ + + dde_error = FALSE; // no errors + process_pokes = FALSE; // disable pokes in callback + + id_inst = 0; // set to 0 for first time through + conv_handle = 0; // conversation handle reset + + lstrcpy( ascii_name, name1 ); // keep a record of ASCII name + + if ( DdeInitialize( + (LPDWORD) &id_inst, // instance identifier + dde_callback, + APPCLASS_STANDARD | // filter server messages + CBF_FAIL_SELFCONNECTIONS, // prevent from connecting with self + 0) != DMLERR_NO_ERROR) { // reserved + dde_error = TRUE; // flag an error + } + + local_name = DdeCreateStringHandle( + id_inst, // instance identifier + name1, // string to register + CP_WINANSI); // Windows ANSI code page + + remote_name = DdeCreateStringHandle( + id_inst, // instance identifier + name2, // string to register + CP_WINANSI); // Windows ANSI code page + + poke_topic = DdeCreateStringHandle( + id_inst, // instance identifier + "POKE TOPIC", // System topic + CP_WINANSI); // Windows ANSI code page + + poke_item = DdeCreateStringHandle( + id_inst, // instance identifier + "POKE ITEM", // System topic + CP_WINANSI); // Windows ANSI code page + + system_topic = DdeCreateStringHandle( + id_inst, // instance identifier + SZDDESYS_TOPIC, // System topic + CP_WINANSI); // Windows ANSI code page + +} + +/*************************************************************************** + * Instance_Class::~Instance_Class -- class destructor * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 6/1/1996 SW : Created. * + *=========================================================================*/ + +Instance_Class::~Instance_Class() +{ + DdeUninitialize( id_inst ); +} + +/*************************************************************************** + * Instance_Class::Enable_Callback -- enables user callback * + * * + * INPUT: * + * TRUE = enable poke processing * + * FALSE = disable poke processing * + * * + * OUTPUT: * + * echos the input * + * * + * WARNINGS: * + * user callback must be explicitly enabled. Disbabled by default. * + * * + * HISTORY: * + * 6/1/1996 SW : Created. * + *=========================================================================*/ + +BOOL Instance_Class::Enable_Callback( BOOL flag ) // enable or disable callback +{ + return (process_pokes = flag); + +} + +/*************************************************************************** + * Instance_Class::Register_Server -- registers a local DDE DNS service * + * * + * INPUT: * + * BOOL CALLBACK ( *callback_fnc) ( LPBYTE, DWORD) = user poke callbacl * + * * + * OUTPUT: * + * TRUE == success * + * FALSE == failed * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 6/1/1996 SW : Created. * + *=========================================================================*/ + +BOOL Instance_Class::Register_Server( BOOL CALLBACK ( *callback_fnc) (LPBYTE, long) ) +{ + + if (DdeNameService( id_inst, local_name, 0L, DNS_REGISTER ) != 0L) { + callback = callback_fnc; + return ( TRUE ); + } else { + return ( FALSE ); + } +} + +/*************************************************************************** + * Instance_Class::Test_Server_Running -- does a trial connect to remote * + * * + * INPUT: * + * name = HSZ string handle of server name. * + * * + * OUTPUT: * + * TRUE == successfully connected to remote * + * FALSE == failed to connect * + * * + * WARNINGS: * + * - Can be called for local or remote server but of course will * + * fail if a called for local and local server is not "up". * + * - Disconects before exiting. * + * * + * HISTORY: * + * 6/1/1996 SW : Created. * + *=========================================================================*/ + +BOOL Instance_Class::Test_Server_Running( HSZ name ) +{ + + if( Open_Poke_Connection( name ) == TRUE) { + Close_Poke_Connection(); + return( TRUE ); + } else { + return( FALSE ); + } +} + +/*************************************************************************** + * Instance_Class::Open_Poke_Connection -- open a connection to server * + * * + * INPUT: * + * name = HSZ server name. * + * * + * OUTPUT: * + * TRUE == successfully opened connection * + * FALSE == failed to connect * + * * + * WARNINGS: * + * Can be called for local or remote server but of course will * + * fail if a called for local and local server is not "up". * + * * + * HISTORY: * + * 6/1/1996 SW : Created. * + *=========================================================================*/ + +BOOL Instance_Class::Open_Poke_Connection( HSZ name ) +{ + conv_handle = DdeConnect( + id_inst, // instance identifier + name, // service name string handle + poke_topic, // topic string handle + (PCONVCONTEXT) NULL);// use default context + + if (conv_handle == NULL) { + return FALSE; + } else { + return TRUE; + } +} + +/*************************************************************************** + * Instance_Class::Close_Poke_Connection -- closes poke connection * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * TRUE == successfully closed connection * + * FALSE == failed to close connection for some reason * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 6/1/1996 SW : Created. * + *=========================================================================*/ + +BOOL Instance_Class::Close_Poke_Connection( void ) +{ + if( conv_handle ) { + HCONV temp_handle = conv_handle; + conv_handle = NULL; + return( DdeDisconnect( temp_handle )); + } else { + return( TRUE ); + } +} + +/*************************************************************************** + * Instance_Class::Poke_Server -- pokes some data to server * + * * + * INPUT: * + * poke_data points to data to send to remote * + * poke_length length of buffer to send * + * * + * OUTPUT: * + * TRUE == successfully poked the data * + * FALSE == failed to connect * + * * + * WARNINGS: * + * has a 3 second timeout (change POKE_TIMEOUT, in milliseconds) * + * * + * HISTORY: * + * 6/1/1996 SW : Created. * + *=========================================================================*/ + +#define POKE_TIMEOUT 60*1000 // 60 sec timeout + +BOOL Instance_Class::Poke_Server( LPBYTE poke_data, DWORD poke_length ) +{ + + if( DdeClientTransaction( + + poke_data, // address of data to pass to server + poke_length, // length of data + conv_handle, // handle of conversation + poke_topic, // handle of item name string + CF_TEXT, // no special clipboard data format + XTYP_POKE, // transaction type + POKE_TIMEOUT, // time-out duration (millisecs) + (LPDWORD) NULL // address of transaction result (don't check) + ) == 0) { + + return( FALSE); + } else { + return( TRUE ); + } +} + +/*************************************************************************** + * Instance_Class::dde_callback -- callback dde event handler * + * * + * INPUT: * + * dde_event transaction type * + * uFmt clipboard data format * + * hconv handle of the conversation * + * hsz1 handle of a string * + * hsz2 handle of a string * + * hdata handle of a global memory object * + * dwData1 transaction-specific data * + * dwData2 transaction-specific data * + * * + * OUTPUT: * + * context specific HDDEDATA object * + * * + * WARNINGS: * + * NOTE: declared as HDDEDATA CALLBACK which means PASCAL parameters * + * * + * HISTORY: * + * 6/1/1996 SW : Created. * + *=========================================================================*/ + +HDDEDATA CALLBACK Instance_Class::dde_callback( + + UINT dde_event, // transaction type + UINT uFmt, // clipboard data format + HCONV , // handle of the conversation + HSZ hsz1, // handle of a string + HSZ hsz2, // handle of a string + HDDEDATA hdata, // handle of a global memory object + DWORD , // transaction-specific data + DWORD // transaction-specific data + ) +{ + + + if (!Instance_Class::callback){ + return (HDDEDATA) NULL; + } + + switch ( dde_event ) { + + case XTYP_REGISTER: + case XTYP_UNREGISTER: + + return (HDDEDATA) NULL; + + case XTYP_ADVDATA: + return (HDDEDATA) DDE_FACK; + + case XTYP_XACT_COMPLETE: + + return (HDDEDATA) NULL; + + case XTYP_DISCONNECT: + + Instance_Class::callback( NULL, DDE_ADVISE_DISCONNECT); + return (HDDEDATA) NULL; + + case XTYP_CONNECT: { + + char buffer[32]; + + DdeQueryString (Instance_Class::id_inst, hsz2, buffer, sizeof (buffer), 0) ; + + if (0 != strcmp (buffer, Instance_Class::ascii_name)) { + return (HDDEDATA) NULL; + } + + DdeQueryString (Instance_Class::id_inst, hsz1, buffer, sizeof (buffer), 0) ; + + if (0 != strcmp (buffer, "POKE TOPIC")) { + return (HDDEDATA) NULL; + } + + Instance_Class::callback( NULL, DDE_ADVISE_CONNECT); + return (HDDEDATA) TRUE; + } + + case XTYP_POKE: + + if (Instance_Class::process_pokes == FALSE ) { + return (HDDEDATA) DDE_FNOTPROCESSED; // processing disabled + } else { + + char buffer[32]; + + DdeQueryString (Instance_Class::id_inst, hsz1, buffer, sizeof (buffer), 0) ; + + if (0 != strcmp (buffer, "POKE TOPIC")) { + return (HDDEDATA) DDE_FNOTPROCESSED; + } else if (uFmt == CF_TEXT) { // make sure it's CF_TEXT + + BOOL processed; + BYTE FAR *pdata; + DWORD dw_length; + + if ( (pdata = DdeAccessData( hdata, &dw_length)) == NULL ) { + return (HDDEDATA) DDE_FNOTPROCESSED; + } + + processed = Instance_Class::callback((LPBYTE) pdata, dw_length); + + DdeUnaccessData( hdata ); + + if (processed == TRUE) { + return (HDDEDATA) DDE_FACK; + } else { + return (HDDEDATA) NULL; + } + + } + } + + default: + return (HDDEDATA) NULL; + } +} diff --git a/DDE.H b/DDE.H new file mode 100644 index 0000000..88d2e24 --- /dev/null +++ b/DDE.H @@ -0,0 +1,175 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Dynamic Data Encapsulation * + * * + * File Name : DDE.H * + * * + * Programmer : Steve Wetherill * + * * + * Start Date : June 1, 1996 * + * * + * Last Update : June 8, 1996 [SW] * + * * + *-------------------------------------------------------------------------* + * * + * This is the DDE (Instance_Class) which provides a simple CLIENT/SERVER * + * DDE model for data transactions between Windows applications. * + * This is a fairly naieve implementation allowing only one client/server * + * per Instance_Class object. * + * * + * Typical uses for this class are: * + * * + * i. Robust verification of whether an application is running * + * ii. Data transfer between applications * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* +***************************** Class defines ***************************** +*/ + +#ifndef __DDE_H +#define __DDE_H + +#define DDE_ADVISE_CONNECT -1 // advisory "client has connected" +#define DDE_ADVISE_DISCONNECT -2 // advisory "client has disconnected" + +/* +***************************** Class Declaration ***************************** +*/ + +class Instance_Class { + + /* + ---------------------------- Public Interface ---------------------------- + */ + public: + + /*..................................................................... + Constructor: + - takes null terminated ASCII strings names for client and server + .....................................................................*/ + + Instance_Class( // constructor + LPSTR, // null terminated local sever name string + LPSTR // null terminated remote server name string + ); + + /*..................................................................... + Destructor: + .....................................................................*/ + ~Instance_Class(void); // the destructor + + /*..................................................................... + Send data routine: + - sends an unsolicited packet of data to the remote server + .....................................................................*/ + BOOL Poke_Server( LPBYTE, DWORD); + + /*..................................................................... + Send data routine: + - sets up DNS for the server and registers a user callback to handle + incoming data + .....................................................................*/ + BOOL Register_Server( BOOL CALLBACK (*)(LPBYTE, long)); + + /*..................................................................... + Does a trial connect to the remote server. + - used to determine whether server is alive or not (and thus running) + .....................................................................*/ + BOOL Test_Server_Running( HSZ ); + + /*..................................................................... + Enables user callback (disabled by default) + .....................................................................*/ + BOOL Enable_Callback( BOOL ); // enable or disable callback + + /*..................................................................... + Open a connection for sending data to remote server + .....................................................................*/ + BOOL Open_Poke_Connection( HSZ ); + + /*..................................................................... + Close connection with remote server + .....................................................................*/ + BOOL Close_Poke_Connection( void ); + + // + // static members + // + + /*..................................................................... + User callback - called upon receipt of incoming data (static member!) + .....................................................................*/ + static BOOL CALLBACK (*callback) ( + + LPBYTE pointer, // pointer to received data + long length // if >0 length of received data + // if <0 + // -1 == client connect detected + // -2 == client disconnect detected + ); + + /*..................................................................... + DDE callback, called when DDEML has an event for us + .....................................................................*/ + static HDDEDATA CALLBACK dde_callback( + + UINT uType, // transaction type + UINT uFmt, // clipboard data format + HCONV hconv, // handle of the conversation + HSZ hsz1, // handle of a string + HSZ hsz2, // handle of a string + HDDEDATA hdata, // handle of a global memory object + DWORD dwData1, // transaction-specific data + DWORD dwData2 // transaction-specific data + ); + HANDLE instance; // this application's instance + HWND hwnd; // valid window handle + + /*..................................................................... + member variables + .....................................................................*/ + + static DWORD id_inst; // instance identifier set by DdeInitialize + static BOOL process_pokes; // controls response to pokes + static char ascii_name[32]; // name of server + + // + // non-static member variables + // + + HSZ remote_name; // string handle for remote server name + HSZ local_name; // string handle for local server name + HSZ system_topic; // string handle for the "system" topic + HSZ poke_topic; // string handle for poking data to server topic + HSZ poke_item; // string handle for poking data to server item + + HCONV conv_handle; // conversation handle + BOOL dde_error; // error flag + +}; + +#endif + + \ No newline at end of file diff --git a/DEBUG.CPP b/DEBUG.CPP new file mode 100644 index 0000000..6515925 --- /dev/null +++ b/DEBUG.CPP @@ -0,0 +1,690 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\debug.cpv 2.17 16 Oct 1995 16:49:18 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DEBUG.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : July 5, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Self_Regulate -- Regulates the logic timer to result in smooth animation. * + * Debug_Key -- Debug mode keyboard processing. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include +#include +#include +#ifdef CHEAT_KEYS + +extern bool ScreenRecording; + +/*********************************************************************************************** + * Debug_Key -- Debug mode keyboard processing. * + * * + * If debugging is enabled, then this routine will be called for every keystroke that the * + * game doesn't recognize. These extra keys usually perform some debugging function. * + * * + * INPUT: input -- The key code that was pressed. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + *=============================================================================================*/ +void Debug_Key(unsigned input) +{ + static int map_x = -1; + static int map_y = -1; + static int map_width = -1; + static int map_height = -1; + + if (!input || input & KN_BUTTON) return; + + /* + ** Processing of normal keystrokes. + */ + if (Debug_Flag) { + + switch (input) { + + case KN_L: + extern int NetMonoMode,NewMonoMode; + if (NetMonoMode) + NetMonoMode = 0; + else + NetMonoMode = 1; + NewMonoMode = 1; + break; + + /* + ** Start saving off screens + */ + case (int)KN_K|(int)KN_CTRL_BIT: + ScreenRecording = true; + break; + + case KN_K: + /* + ** time to create a screen shot using the PCX code (if it works) + */ + { + GraphicBufferClass temp_page( SeenBuff.Get_Width(), + SeenBuff.Get_Height(), + NULL, + SeenBuff.Get_Width() * SeenBuff.Get_Height()); + char filename[30]; + + SeenBuff.Blit(temp_page); + for (int lp = 0; lp < 99; lp ++) { + if (lp < 10) { + sprintf(filename, "scrsht0%d.pcx", lp); + } else { + sprintf(filename, "scrsht%d.pcx", lp); + } + if (access(filename, F_OK) == -1) + break; + } + + Write_PCX_File(filename, temp_page, (unsigned char *)CurrentPalette); + //Map.Place_Random_Crate(); + } + break; + + case KN_P: + Keyboard::Clear(); + while (!Keyboard::Check()) { + Self_Regulate(); + Sound_Callback(); + } + Keyboard::Clear(); + break; + + case KN_O: + { + AircraftClass * air = new AircraftClass(AIRCRAFT_ORCA, PlayerPtr->Class->House); + if (air) { + air->Altitude = 0; + air->Unlimbo(Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y()), DIR_N); + } + } + break; + + case (int)KN_B|(int)KN_ALT_BIT: + { + Debug_Instant_Build ^= 1; + } + break; + case KN_B: + { + AircraftClass * air = new AircraftClass(AIRCRAFT_HELICOPTER, PlayerPtr->Class->House); + if (air) { + air->Altitude = 0; + air->Unlimbo(Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y()), DIR_N); + } + } + break; + + case KN_T: + { + AircraftClass * air = new AircraftClass(AIRCRAFT_TRANSPORT, PlayerPtr->Class->House); + if (air) { + air->Altitude = 0; + air->Unlimbo(Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y()), DIR_N); + } + } + break; + + case KN_GRAVE: + new AnimClass(ANIM_ART_EXP1, Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y())); + Explosion_Damage(Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y()), 250, NULL, WARHEAD_HE); + break; + + case KN_Z: +// new AnimClass(ANIM_LZ_SMOKE, Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y())); + GDI_Ending(); + break; + + case KN_C: + Debug_Cheat = (Debug_Cheat == false); + PlayerPtr->IsRecalcNeeded = true; + PlayerPtr->Add_Nuke_Piece(); + PlayerPtr->Add_Nuke_Piece(); + PlayerPtr->Add_Nuke_Piece(); + + /* + ** This placement might affect any prerequisite requirements for construction + ** lists. Update the buildable options accordingly. + */ + if (!ScenarioInit) { + Map.Recalc(); + for (int index = 0; index < Buildings.Count(); index++) { + Buildings.Ptr(index)->Update_Buildables(); + } + } + break; + + case (int)KN_Z|(int)KN_ALT_BIT: + if (map_x == -1) { + map_x = Map.MapCellX; + map_y = Map.MapCellY; + map_width = Map.MapCellWidth; + map_height = Map.MapCellHeight; + Map.MapCellX = 1; + Map.MapCellY = 1; + Map.MapCellWidth = 62; + Map.MapCellHeight = 62; + } else { + Map.MapCellX = map_x; + Map.MapCellY = map_y; + Map.MapCellWidth = map_width; + Map.MapCellHeight = map_height; + map_x = -1; + map_y = -1; + map_width = -1; + map_height = -1; + } + break; + +#ifdef NEVER + case KN_G: + HouseClass::As_Pointer(HOUSE_GOOD)->Flag_Attach(Map.Click_Cell_Calc(Get_Mouse_X(), Get_Mouse_Y())); + break; + + case KN_N: + HouseClass::As_Pointer(HOUSE_BAD)->Flag_Attach(Map.Click_Cell_Calc(Get_Mouse_X(), Get_Mouse_Y())); + break; +#endif + + case KN_R: + if (CurrentObject.Count()) { + ((TechnoClass *)CurrentObject[0])->IsCloakable = true; + } + break; + + case KN_M: + if (Debug_Flag) { + if (MonoClass::Is_Enabled()) { + MonoClass::Disable(); + } else { + MonoClass::Enable(); + } + } + break; + + case (int)KN_W|(int)KN_ALT_BIT: + PlayerPtr->Flag_To_Win(); + break; + + case (int)KN_L|(int)KN_ALT_BIT: + PlayerPtr->Flag_To_Lose(); + break; + + case KN_F: + Debug_Find_Path ^= 1; + break; + + case KN_DELETE: + if (CurrentObject.Count()) { + Map.Recalc(); + //CurrentObject[0]->Detach_All(); + delete CurrentObject[0]; + } + break; + + case KN_D: + if (Teams.Ptr(0)) { + delete Teams.Ptr(0); + } + break; + + case (int)KN_DELETE|(int)KN_SHIFT_BIT: + if (CurrentObject.Count()) { + Map.Recalc(); + int damage = 50; + CurrentObject[0]->Take_Damage(damage, 0, WARHEAD_SA); + } + break; + + case KN_INSERT: + if (CurrentObject.Count()) { + Map.PendingObject = &CurrentObject[0]->Class_Of(); + if (Map.PendingObject) { + Map.PendingHouse = CurrentObject[0]->Owner(); + Map.PendingObjectPtr = Map.PendingObject->Create_One_Of(HouseClass::As_Pointer(Map.PendingHouse)); + if (Map.PendingObjectPtr) { + Map.Set_Cursor_Pos(); + Map.Set_Cursor_Shape(Map.PendingObject->Occupy_List()); + } + } + } + break; + +#ifdef NEVER + case KN_1: + case KN_2: + case KN_3: + case KN_4: + case KN_5: + case KN_6: + case KN_7: + case KN_8: + case KN_9: + case KN_0: + MonoPage = (input & 0xFF) - KN_1; + MonoPage %= sizeof(MonoArray)/sizeof(MonoArray[0]); + MonoArray[MonoPage].View(); + input = 0; + break; +#endif + +#ifdef NEVER + case ((int)KN_F1 | (int)KN_SHIFT_BIT): + Special.IsBarOn = (Special.IsBarOn == false); + Map.Flag_To_Redraw(true); + break; + + case ((int)KN_F1 | (int)KN_SHIFT_BIT): // quick load/save for debugging + if (!Save_Game(0,"Command & Conquer Save Game File")) { + CCMessageBox().Process("Error saving game!"); + Prog_End(); + exit(EXIT_SUCCESS); + } + break; + + case ((int)KN_F2 | (int)KN_SHIFT_BIT): // quick load/save for debugging + if (!Load_Game(0)) { + CCMessageBox().Process("Error loading game!"); + Prog_End(); + exit(EXIT_SUCCESS); + } + break; + +//#ifdef SCENARIO_EDITOR + case KN_F2: // enable/disable the map editor + Go_Editor(!Debug_Map); + break; +//#endif +#endif + +#ifdef NEVER + case KN_F2: { + Debug_Map++; + Scenario_Editor(); + Debug_Map--; +#ifdef NEVER + COORDINATE coord; + int index; + static COORDINATE _coords[] = { + 0x00010001L, + 0x00800080L, + 0x00810081L, + 0x00010081L, + 0x00810001L, + 0x00800081L, + 0x00800001L, + 0x00010080L, + 0x00810080L, + 0L + }; + index = 0; + while (_coords[index]) { + coord = _coords[index++]; + Mono_Printf("Spillage for %08lX = %d.\r", coord, Coord_Spillage_Number(coord)); + } + Keyboard::Clear(); + Keyboard::Get(); + +#endif + +#ifdef NEVER +#define MAX_RADIUS 10 + COORDINATE coord; + int x,y; + COORDINATE const *ptr; + int input; + int f1,f2; + TurnTrackType const *track; + + #define XCENTER 160 + #define YCENTER 100 + for (;;) { + VisiblePage.Clear(); + + // Draw grid. + { + static int _gridx[] = {0,64,128,192,0,64,128,192,0,64,128,192}; + static int _gridy[] = {0,0,0,0,64,64,64,64,128,128,128,128}; + int index; + + for (index = 0; index < 12; index++) { + LogicPage->Put_Pixel((_gridx[index]+XCENTER)-(32+64),(_gridy[index]+YCENTER)-(32+64), DKGRAY); + } + } + + // Get facing #1. + LogicPage->Print("Facing #1 (0-7)?", 0, 0, WHITE, BLACK); + input = Keyboard::Get(); + if (input == KA_ESC) break; + input -= KA_0; + input = Bound(input, 0, 7); +// input = MAX(input, 0); +// input = MIN(input, 7); + f1 = input; + Int_Print(f1, 100, 0, WHITE, BLACK); + + // Get facing #2. + LogicPage->Print("Facing #2 (0-7)?", 0, 10, WHITE, BLACK); + input = Keyboard::Get(); + if (input == KA_ESC) break; + input -= KA_0; + input = Bound(input, 0, 7); +// input = MAX(input, 0); +// input = MIN(input, 7); + f2 = input; + Int_Print(f2, 100, 10, WHITE, BLACK); + + track = &TrackControl[f1][f2]; + if (track->Track == 0) { + LogicPage->Print("Undefined track.", 0, 30, WHITE, BLACK); + } else { + int index; // Track index counter. + + ptr = TrackPointers[track->Track-1]; + index = 0; + while (ptr[index]) { + coord = Smooth_Turn(NULL, ptr[index], track->Flag); + + x = (int)(coord & 0xFFFF); + y = (int)((coord >> 16) & 0xFFFF); + LogicPage->Put_Pixel(XCENTER + (x>>2), YCENTER + (y>>2), WHITE); + Delay(1); + index++; + } + + } + input = Keyboard::Get(); + if (input == KA_ESC) break; + } + + Map.Flag_To_Redraw(true); +#endif +#ifdef NEVER + FILE *fh; + int index; + COORDINATE coord; + + fh = fopen("diagonal.txt", "wt"); + if (fh) { + + fprintf(fh, "track 2\n"); + coord = 0x0100FF00L; + for (index = 0; index <= 48; index++) { + fprintf(fh, "0x%08lXL\n", coord); + coord = Coord_Move(coord, 32, 11); + } + fprintf(fh, "\n\n"); + + fprintf(fh, "track 1\n"); + coord = 0x01000000L; + for (index = 0; index <= 40; index++) { + fprintf(fh, "0x%08lXL\n", coord); + coord = Coord_Move(coord, 0, 11); + } + fprintf(fh, "\n\n"); + + fclose(fh); + } +#endif +#ifdef NEVER + FILE *fh; + int x,y,radius; + int radsize[MAX_RADIUS+2]; + int count; + + memset(radsize, 0, sizeof(radsize)); + fh = fopen("Range.txt", "wt"); + if (fh) { + fprintf(fh, "int const RadiusOffset[] = {\n"); + + for (radius = 0; radius <= MAX_RADIUS; radius++) { + + fprintf(fh, "\t/* %-2d */\t", radius); + for (y = -MAX_RADIUS; y <= MAX_RADIUS; y++) { + for (x = -MAX_RADIUS; x <= MAX_RADIUS; x++) { + int xd,yd,dist; + + xd = ABS(x); + yd = ABS(y); + if (xd > yd) { + dist = yd/2 + xd; + } else { + dist = xd/2 + yd; + } + if (dist == radius) { + dist = y*MAP_CELL_W + x; + + if (y) { + if (y < 0) { + fprintf(fh, "(-MCW*%d)", ABS(y)); + } else { + fprintf(fh, "(MCW*%d)", ABS(y)); + } + fprintf(fh, "%c%d,", (x<0) ? '-' : '+', ABS(x)); + } else { + fprintf(fh, "%d,", x); + } + radsize[radius]++; + } + } + } + fprintf(fh, "\n"); + } + fprintf(fh, "};\n\n"); + + count = 0; + fprintf(fh, "int const RadiusCount[%d] = {", MAX_RADIUS+1); + for (radius = 0; radius <= MAX_RADIUS; radius++) { + count += radsize[radius]; + fprintf(fh, "%d", count); + if (radius != MAX_RADIUS) { + fprintf(fh, ","); + } + } + fprintf(fh, "};\n"); + fclose(fh); + } +#endif + } + break; +#endif + +#ifdef NEVER + case ((int)KN_F3 | (int)KN_ALT_BIT): // quick load/save for debugging + Debug_Threat = (Debug_Threat == false); + Map.Flag_To_Redraw(true); + break; + +#endif + + case KN_F3: + Debug_Icon = (Debug_Icon == false); + Map.Flag_To_Redraw(true); + break; + + + /* + ** Reveal entire map to player. + */ + case KN_F4: + if (GameToPlay == GAME_NORMAL) { + Debug_Unshroud = (Debug_Unshroud == false); + Map.Flag_To_Redraw(true); + } + break; + + /* + ** Shows sight and fire range in the form of circles emanating from the currently + ** selected unit. The white circle is for sight range, the red circle is for + ** fire range. + */ + case KN_F7: + if (CurrentObject.Count() && CurrentObject[0]->Is_Techno()) { + TechnoTypeClass const & ttype = (TechnoTypeClass const &)CurrentObject[0]->Class_Of(); + int sight = ((int)ttype.SightRange)<<8; + int weapon = 0; + if (ttype.Primary != WEAPON_NONE) weapon = Weapons[ttype.Primary].Range; + Set_Logic_Page(SeenBuff); + COORDINATE center = CurrentObject[0]->Center_Coord(); + COORDINATE center2 = CurrentObject[0]->Fire_Coord(0); + + for (int r = 0; r < 255; r += 10) { + int x,y,x1,y1; + DirType r1 = (DirType)r; + DirType r2 = (DirType)((r+10) & 0xFF); + + if (Map.Coord_To_Pixel(Coord_Move(center, r1, sight), x, y)) { + Map.Coord_To_Pixel(Coord_Move(center, r2, sight), x1, y1); + LogicPage->Draw_Line(x, y+8, x1, y1+8, WHITE); + } + if (Map.Coord_To_Pixel(Coord_Move(center2, r1, weapon), x, y)) { + Map.Coord_To_Pixel(Coord_Move(center2, r2, weapon), x1, y1); + LogicPage->Draw_Line(x, y+8, x1, y1+8, RED); + } + } + } + break; + + case ((int)KN_F4 | (int)KN_CTRL_BIT): + Debug_Unshroud = (Debug_Unshroud == false); + Map.Flag_To_Redraw(true); + break; + +#ifdef NEVER + case KN_F5: + Special.IsShowPath = (Special.IsShowPath == false); + //PlayerPtr->Credits += 1000; + break; + + case KN_F6: + if (Map.In_Radar(XY_Cell(Map.MapCellX+5, Map.MapCellY - 1))) { + Mono_Printf("Arrrggggghhhhh!"); + } else { + Mono_Printf("No Arrrggggghhhhh!"); + } + break; + + case ((int)KN_F9 | (int)KN_CTRL_BIT): + if (HouseClass::As_Pointer(HOUSE_GOOD)) + (HouseClass::As_Pointer(HOUSE_GOOD))->Blowup_All(); + break; + + case ((int)KN_F10 | (int)KN_CTRL_BIT): + if (HouseClass::As_Pointer(HOUSE_BAD)) + (HouseClass::As_Pointer(HOUSE_BAD))->Blowup_All(); + break; +#endif + } + + } +} + + +/*********************************************************************************************** + * Self_Regulate -- Regulates the logic timer to result in smooth animation * + * * + * The self regulation process checks the number of frames displayed * + * per second and from this determines the amount of time to devote * + * to internal logic processing. By adjusting the time allotted to * + * internal processing, smooth animation can be maintained. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: In order for this routine to work properly it MUST be * + * called every display loop. * + * * + * HISTORY: * + * 07/31/1991 JLB : Created. * + * 07/05/1994 JLB : Handles new monochrome system. * + *=============================================================================================*/ +#define UPDATE_INTERVAL TIMER_SECOND +void Self_Regulate(void) +{ + static CountDownTimerClass DebugTimer(BT_SYSTEM); + static ObjectClass * _lastobject = 0; + + if (!DebugTimer.Time()) { + DebugTimer.Set(UPDATE_INTERVAL); + + if (MonoClass::Is_Enabled()) { + MonoClass *mono = MonoClass::Get_Current(); + mono->Set_Default_Attribute(2); + + switch (MonoPage) { + case 0: + mono = &MonoArray[0]; + mono->Clear(); + + /* + ** Display the status of the currently selected object. + */ + if (CurrentObject.Count()) { + _lastobject = CurrentObject[0]; + } + if (_lastobject && !_lastobject->IsActive) { + _lastobject = 0; + } + if (_lastobject) { + _lastobject->Debug_Dump(mono); + } + Logic.Debug_Dump(mono); + mono->Set_Cursor(0, 20); + mono->Printf( + "Heap size:%10ld \r" + "Largest: %10ld \r" + "Ttl Free: %10ld \r" + "Frag: %10ld \r", + Heap_Size(MEM_NORMAL), + Ram_Free(MEM_NORMAL), + Total_Ram_Free(MEM_NORMAL), + Total_Ram_Free(MEM_NORMAL)-Ram_Free(MEM_NORMAL) + ); + *MonoClass::Get_Current() = *mono; + break; + } + + MonoArray[MonoPage] = *mono; + } + } +} +#endif diff --git a/DEBUG.H b/DEBUG.H new file mode 100644 index 0000000..3d379b7 --- /dev/null +++ b/DEBUG.H @@ -0,0 +1,42 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#define TXT_NONE_DEBUG 0x3e8 // +#define TXTED_BLANK 0x3e9 // ____ +#define TXTED_UNABLETOREAD 0x3ea // Unable to read scenario! +#define TXTED_FILEEXISTS 0x3eb // File exists. Replace? +#define TXTED_LOWMEM 0x3ec // Insufficient memory! +#define TXTED_EXIT 0x3ed // Exit Scenario Editor? +#define TXT_GENERIC_EXCEPTION 0x3ee // ERROR: Exception was +#define TXT_RADIO_1 0x3ef // hisssss +#define TXT_RADIO_2 0x3f0 // Roger. +#define TXT_RADIO_3 0x3f1 // Come in. +#define TXT_RADIO_4 0x3f2 // Over and out. +#define TXT_RADIO_5 0x3f3 // Requesting transport. +#define TXT_RADIO_6 0x3f4 // I've got a delivery for +#define TXT_RADIO_7 0x3f5 // I'm performing load/unload +#define TXT_RADIO_8 0x3f6 // I'm clear. +#define TXT_RADIO_9 0x3f7 // You are clear to unload. +#define TXT_RADIO_10 0x3f8 // Am unable to comply. +#define TXT_RADIO_11 0x3f9 // I'm starting construction +#define TXT_RADIO_12 0x3fa // I've finished construction. +#define TXT_RADIO_13 0x3fb // Oops, sorry. I might have +#define TXT_RADIO_14 0x3fc // I'm full. May I unload at +#define TXT_RADIO_15 0x3fd // Are you a refinery and are +#define TXT_RADIO_16 0x3fe // Take this kick! You... +#define TXT_RADIO_17 0x3ff // Take this punch! You... diff --git a/DEFINES.H b/DEFINES.H new file mode 100644 index 0000000..6c725be --- /dev/null +++ b/DEFINES.H @@ -0,0 +1,2771 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\defines.h_v 2.19 16 Oct 1995 16:44:54 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DEFINES.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : September 10, 1993 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef DEFINES_H +#define DEFINES_H + + +/********************************************************************** +** If defined, then the advanced balancing features will be enabled +** for this version. +*/ +//#define ADVANCED +#define PATCH // Super patch (1.17?) + + +/********************************************************************** +** The demo version of C&C will be built if the following define +** is active. +*/ +//#define DEMO + + +/********************************************************************** +** Define this to allow play of the bonus missions for the Gateway +** bundle deal. +*/ +#define BONUS_MISSIONS + + +/********************************************************************** +** Handle expansion scnearios as a set of single missions with all +** necessary information self contained within the mission file. +*/ +#ifndef DEMO +#define NEWMENU +#endif + + +/********************************************************************** +** If the scenario editor to to be active in this build then uncomment +** the following #define line. +*/ +//#define SCENARIO_EDITOR + + +/********************************************************************** +** This define enables the full set of cheat keys and special +** command line options. +*/ +#define CHEAT_KEYS + + +/********************************************************************** +** If this is defined, the special Virgin limited cheat keys +** are enabled. This allows the "cheat" parameter and then only +** allows the ALT-W to win the mission. +*/ +//#define VIRGIN_CHEAT_KEYS + + +/********************************************************************** +** Optional parameter control for special options. +*/ +//#define PARM_6PLAYER 0x5D9F6F24 // "6" +#define PARM_6PLAYER 0x9CAFC93B // Alternate 6 player keyphrase. + +/* +** Enable the set of limited cheat key options. +*/ +#ifdef VIRGIN_CHEAT_KEYS +#define PARM_PLAYTEST 0xF7DDC227 // "PLAYTEST" +#endif + +/* +** Enable the full set of cheat key options. +*/ +#ifdef CHEAT_KEYS +#ifndef PARM_PLAYTEST +#define PARM_PLAYTEST 0xF7DDC227 // "PLAYTEST" +#endif +#define PARM_CHEATDAVID 0xBE79088C // Cheat keys for David Dettmer +#define PARM_CHEATERIK 0x9F38A19D // Cheat keys for Erik Yeo +#define PARM_EDITORERIK 0xC2AA509B // Map editor for Erik Yeo +#define PARM_CHEATPHIL 0x39D01821 // Cheat keys for Phil Gorrow +#define PARM_CHEATJOE 0xABDD0362 // Cheat keys for Joe Bostic +#define PARM_CHEATBILL 0xB5B63531 // Cheat keys for Bill Randolph +#define PARM_CHEAT_STEVET 0x2E7FE493 // Cheat keys for Steve Tall +#define PARM_EDITORBILL 0x7E7C4CCA // "-EDITOR" +#define PARM_CHEATMIKE 0x00532693 // Mike Lightner +#define PARM_CHEATADAM 0xDFABC23A // Adam Isgreen +#endif + +//#define PARM_CHEAT 0x6F4BE7CA // "CHEAT" +//#define PARM_EDITOR 0x7E7C4CCA // "-EDITOR" + +#define PARM_EASY 0x59E975CE // "EASY" Enables easy mode. +#define PARM_HARD 0xACFE9D13 // "HARD" Enables hard mode. + +#define PARM_INSTALL 0xD95C68A2 // "FROMINSTALL" +#define PARM_TRUENAME 0xB1A34435 // Enables true object names. +#define PARM_3POINT 0x03552894 // Enable three point turns. +#define PARM_SCORE 0x7FDE2C33 // Enables alternate themes. +#define PARM_COMBAT 0xDC57C4B2 // Gives combat advantage to attacker. +#define PARM_TREETARGET 0x00AB6BEF // Allows targeting of trees without key. +#define PARM_BIB 0xF7867BF0 // Disables building bibs. +#define PARM_MCV 0x104DF10F // MCV undeploys rather than sells. +#define PARM_HELIPAD 0x53EBECBC // Helipad can be purchased separately from helicopter. +#define PARM_IQ 0x9E3881B8 // Smart self defense logic enable. +#define PARM_SQUISH 0x4EA2FBDF // Squish images for infantry bodies. +#define PARM_HUMAN 0xACB58F61 // Human generated sound effects. +#define PARM_SCROLLING 0xC084AE82 // Restricts scrolling over the tabs. +//#define PARM_SPECIAL 0xD18129F6 // Enables special mode. +//#define PARM_SPECIAL 0x2E84E394 // #1 +//#define PARM_SPECIAL 0x63CE7584 // #2 +//#define PARM_SPECIAL 0x85F110A5 // #3 +///#define PARM_SPECIAL 0x7F65F13C // #4 +//#define PARM_SPECIAL 0x431F5F61 // #5 +#define PARM_SPECIAL 0x11CA05BB // #6 funpark +//#define PARM_SPECIAL 0xE0F651B9 // #7 +//#define PARM_SPECIAL 0x10B9683D // #8 +//#define PARM_SPECIAL 0xEE1CD37D // #9 + + +/********************************************************************** +** Defines for verifying free disk space +*/ +#define INIT_FREE_DISK_SPACE 1024*4096 //8388608 +#define SAVE_GAME_DISK_SPACE INIT_FREE_DISK_SPACE // (INIT_FREE_DISK_SPACE - (1024*4096)) +//#define SAVE_GAME_DISK_SPACE 100000 + + +/********************************************************************** +** This is the credit threshold that the computer's money must exceed +** in order for structure repair to commence. +*/ +#define REPAIR_THRESHHOLD 1000 + + +//#define GERMAN 1 +//#define FRENCH 1 +//#define JAPANESE 1 + +#define FOREIGN_VERSION_NUMBER 6 + + +/********************************************************************** +** These enumerations are used to implement RTTI. +*/ +typedef enum RTTIType { + RTTI_NONE=0, + RTTI_INFANTRY, + RTTI_INFANTRYTYPE, + RTTI_UNIT, + RTTI_UNITTYPE, + RTTI_AIRCRAFT, + RTTI_AIRCRAFTTYPE, + RTTI_BUILDING, + RTTI_BUILDINGTYPE, + + RTTI_TERRAIN, + RTTI_ABSTRACTTYPE, + RTTI_ANIM, + RTTI_ANIMTYPE, + RTTI_BULLET, + RTTI_BULLETTYPE, + RTTI_OVERLAY, + RTTI_OVERLAYTYPE, + RTTI_SMUDGE, + RTTI_SMUDGETYPE, + RTTI_TEAM, + RTTI_TEMPLATE, + RTTI_TEMPLATETYPE, + RTTI_TERRAINTYPE, + RTTI_OBJECT, + RTTI_SPECIAL +} RTTIType; + + +/********************************************************************** +** This is the size of the speech buffer. This value should be as large +** as the largest speech sample, plus a few bytes for overhead +** (16 bytes is sufficient). +*/ +#define SPEECH_BUFFER_SIZE 50000L + + +/********************************************************************** +** This is the size of the shape buffer. This buffer is used as a staging +** buffer for the shape drawing technology. It MUST be as big as the +** largest shape (uncompressed) that will be drawn. If this value is +** changed, be sure to update the makefile and rebuild all of the shape +** data files. +*/ +#define SHAPE_BUFFER_SIZE 40000L + +// Use this to allow keep track of versions as they affect saved games. +#define VERSION_NUMBER 1 +#define RELEASE_NUMBER 01 + +#define FAME_FILE_NAME "HALLFAME.DAT" + + +/********************************************************************** +** Map controls. The map is composed of square elements called 'cells'. +** All larger elements are build upon these. +*/ + +// Size of the map in cells. The width of the map must be a power +// of two. This is accomplished by setting the width by the number of +// bits it occupies. The number of meta-cells will be a subset of the +// cell width. +#define MAP_CELL_MAX_X_BITS 6 +#define MAP_CELL_MAX_Y_BITS 6 +#define MAP_CELL_X_MASK (~(~0 << MAP_CELL_MAX_X_BITS)) +//#define MAP_CELL_Y_MASK ((~(~0 << MAP_CELL_MAX_Y_BITS)) << MAP_CELL_MAX_Y_BITS) + +// Size of the map in cells. +#define MAP_CELL_W (1<APC or vehicle->Repair facility. + ACTION_SELF, // Self select special case. + ACTION_ATTACK, // Can attack or fire upon it in some fashion. + ACTION_HARVEST, // Special harvest mode. + ACTION_SELECT, // Would change selection to specified object. + ACTION_TOGGLE_SELECT,// Toggles select state of the object. + ACTION_CAPTURE, // The unit will try to capture the object. + ACTION_REPAIR, // The target object should be repaired. + ACTION_SELL, // The target building should be sold back. + ACTION_SELL_UNIT, // The target unit should be sold back. + ACTION_NO_SELL, // No sell or no repair. + ACTION_NO_REPAIR, // No sell or no repair. + ACTION_SABOTAGE, // The unit will try to sabotage/destroy the object. + ACTION_ION, // That target object should be blasted. + ACTION_NUKE_BOMB, // That target object should be blasted. + ACTION_AIR_STRIKE, // That target object should be blasted. + ACTION_GUARD_AREA, // Guard the area/object clicked on. + + ACTION_COUNT +} ActionType; + + +/********************************************************************** +** When a unit gets damaged, the result of the damage is returned as +** this type. It can range from no damage taken to complete destruction. +*/ +typedef enum ResultType { + RESULT_NONE, // No damage was taken by the target. + RESULT_LIGHT, // Some damage was taken, but no state change occurred. + RESULT_HALF, // Damaged to below half strength (only returned on transition). + RESULT_MAJOR, // Damaged down to 1 hit point. + RESULT_DESTROYED, // Damaged to complete destruction. +} ResultType; + + +/********************************************************************** +** These are the special concrete control defines. They enumerate the +** sequence order of the concrete icons in the concrete art file. +*/ +// DEBUG === convert this to be zero based so that a nulled cell is the +// default cell. +enum ConcreteEnum { + C_NONE=-1, + C_LEFT=0, + C_RIGHT=1, + C_RIGHT_UPDOWN=2, + C_LEFT_UPDOWN=3, + C_UP_RIGHT=4, + C_UP_LEFT=5, + C_DOWN_RIGHT=6, + C_DOWN_LEFT=7, + C_RIGHT_DOWN=8, + C_LEFT_DOWN=9, + C_RIGHT_UP=10, + C_LEFT_UP=11, + C_UPDOWN_RIGHT=12, + C_UPDOWN_LEFT=13 +}; + + +/********************************************************************** +** Units that move can move at different speeds. These enumerate the +** different speeds that a unit can move. +*/ +typedef enum MPHType{ + MPH_IMMOBILE=0, + MPH_VERY_SLOW=5, + MPH_KINDA_SLOW=6, + MPH_SLOW=8, + MPH_SLOW_ISH=10, + MPH_MEDIUM_SLOW=12, + MPH_MEDIUM=18, + MPH_MEDIUM_FAST=30, + MPH_MEDIUM_FASTER=35, + MPH_FAST=40, + MPH_ROCKET=60, + MPH_VERY_FAST=100, + MPH_LIGHT_SPEED=255 +} MPHType; + + +/********************************************************************** +** General audio volume is enumerated by these identifiers. Since small +** volume variations are usually unnoticable when specifying the volume +** to play a sample, this enumeration list creates more readable code. +*/ +typedef enum VolType +{ + VOL_OFF=0, + VOL_0=VOL_OFF, + VOL_1=0x19, + VOL_2=0x32, + VOL_3=0x4C, + VOL_4=0x66, + VOL_5=0x80, + VOL_6=0x9A, + VOL_7=0xB4, + VOL_8=0xCC, + VOL_9=0xE6, + VOL_10=0xFF, + VOL_FULL=VOL_10 +} VolType; + + +/********************************************************************** +** The houses that can be played are listed here. Each has their own +** personality and strengths. +*/ +typedef enum HousesType { + HOUSE_NONE=-1, + HOUSE_GOOD, // Global Defense Initiative + HOUSE_BAD, // Brotherhood of Nod + HOUSE_NEUTRAL, // Civilians + HOUSE_JP, // Disaster Containment Team + HOUSE_MULTI1, // Multi-Player house #1 + HOUSE_MULTI2, // Multi-Player house #2 + HOUSE_MULTI3, // Multi-Player house #3 + HOUSE_MULTI4, // Multi-Player house #4 + HOUSE_MULTI5, // Multi-Player house #5 + HOUSE_MULTI6, // Multi-Player house #6 + + HOUSE_COUNT, + HOUSE_FIRST=HOUSE_GOOD +} HousesType; + +inline HousesType operator++(HousesType &, int); + +#define HOUSEF_GOOD (1< 2 players. +*/ +typedef enum ScenarioPlayerEnum +{ + SCEN_PLAYER_NONE = -1, + SCEN_PLAYER_GDI, + SCEN_PLAYER_NOD, + SCEN_PLAYER_JP, + SCEN_PLAYER_2PLAYER, + SCEN_PLAYER_MPLAYER, + SCEN_PLAYER_COUNT, + SCEN_PLAYER_FIRST = 0, +} ScenarioPlayerType; + +inline ScenarioPlayerType operator++(ScenarioPlayerType &, int); + + +/********************************************************************** +** These are the directional parameters for a scenario. +*/ +typedef enum ScenarioDirEnum +{ + SCEN_DIR_NONE = -1, + SCEN_DIR_EAST, + SCEN_DIR_WEST, + SCEN_DIR_COUNT, + SCEN_DIR_FIRST = 0, +} ScenarioDirType; + +inline ScenarioDirType operator++(ScenarioDirType &, int); + + +/********************************************************************** +** These are the random variations of a scenario. +*/ +typedef enum ScenarioVarEnum +{ + SCEN_VAR_NONE = -1, + SCEN_VAR_A, + SCEN_VAR_B, + SCEN_VAR_C, + SCEN_VAR_D, + SCEN_VAR_COUNT, // comes before the Lose value! + SCEN_VAR_LOSE, + SCEN_VAR_FIRST = 0, +} ScenarioVarType; + +inline ScenarioVarType operator++(ScenarioVarType &, int); + + +/********************************************************************** +** The objects to be drawn on the map are grouped into layers. These +** enumerated values specify those layers. The ground layer is sorted +** from back to front. +*/ +typedef enum LayerType { + LAYER_NONE=-1, + LAYER_GROUND, // Touching the ground type object (units & buildings). + LAYER_AIR, // Flying above the ground (explosions & flames). + LAYER_TOP, // Topmost layer (aircraft & bullets). + + LAYER_COUNT, + LAYER_FIRST=0 +} LayerType; + +inline LayerType operator++(LayerType &, int); + + +/********************************************************************** +** This enumerates the various bullet types. These types specify bullet's +** visual and explosive characteristics. +*/ +typedef enum BulletType { + BULLET_NONE=-1, + BULLET_SNIPER, // Sniper bullet. + BULLET_BULLET, // Small arms + BULLET_APDS, // Armor piercing projectile. + BULLET_HE, // High explosive shell. + BULLET_SSM, // Surface to surface small missile type. + BULLET_SSM2, // MLRS missile. + BULLET_SAM, // Fast homing anti-aircraft missile. + BULLET_TOW, // TOW anti-vehicle short range missile. + BULLET_FLAME, // Flame thrower flame. + BULLET_CHEMSPRAY, // Chemical weapon spray. + BULLET_NAPALM, // Napalm bomblet. + BULLET_GRENADE, // Hand tossed grenade. + BULLET_LASER, // Laser beam from obelisk + BULLET_NUKE_UP, // Nuclear Missile on its way down + BULLET_NUKE_DOWN, // Nuclear Missile on its way up + BULLET_HONEST_JOHN, // SSM with napalm warhead. + BULLET_SPREADFIRE, // Chain gun bullets. + BULLET_HEADBUTT, // Stegosaurus, Triceratops head butt + BULLET_TREXBITE, // Tyrannosaurus Rex's bite - especially bad for infantry + + BULLET_COUNT, + BULLET_FIRST=0 +} BulletType; + +inline BulletType operator++(BulletType &, int); + + +/********************************************************************** +** All game buildings (structures) are enumerated here. This includes +** civilian structures as well. +*/ +typedef enum StructType { + STRUCT_NONE=-1, + STRUCT_WEAP, + STRUCT_GTOWER, + STRUCT_ATOWER, + STRUCT_OBELISK, + STRUCT_RADAR, + STRUCT_TURRET, + STRUCT_CONST, + STRUCT_REFINERY, + STRUCT_STORAGE, + STRUCT_HELIPAD, + STRUCT_SAM, + STRUCT_AIRSTRIP, + STRUCT_POWER, + STRUCT_ADVANCED_POWER, + STRUCT_HOSPITAL, + STRUCT_BARRACKS, + STRUCT_TANKER, + STRUCT_REPAIR, + STRUCT_BIO_LAB, + STRUCT_HAND, + STRUCT_TEMPLE, + STRUCT_EYE, + STRUCT_MISSION, + + /* + ** All buildings that are never used as a prerequisite + ** for construction, follow this point. Typically, this is + ** limited to civilian structures. + */ + STRUCT_V01, + STRUCT_V02, + STRUCT_V03, + STRUCT_V04, + STRUCT_V05, + STRUCT_V06, + STRUCT_V07, + STRUCT_V08, + STRUCT_V09, + STRUCT_V10, + STRUCT_V11, + STRUCT_V12, + STRUCT_V13, + STRUCT_V14, + STRUCT_V15, + STRUCT_V16, + STRUCT_V17, + STRUCT_V18, + STRUCT_PUMP, + STRUCT_V20, + STRUCT_V21, + STRUCT_V22, + STRUCT_V23, + STRUCT_V24, + STRUCT_V25, + STRUCT_V26, + STRUCT_V27, + STRUCT_V28, + STRUCT_V29, + STRUCT_V30, + STRUCT_V31, + STRUCT_V32, + STRUCT_V33, + STRUCT_V34, + STRUCT_V35, + STRUCT_V36, + STRUCT_V37, +#ifdef OBSOLETE + STRUCT_ROAD, +#endif + STRUCT_SANDBAG_WALL, + STRUCT_CYCLONE_WALL, + STRUCT_BRICK_WALL, + STRUCT_BARBWIRE_WALL, + STRUCT_WOOD_WALL, + + STRUCT_COUNT, + STRUCT_FIRST=0 +} StructType; + +inline StructType operator++(StructType &, int); + +#define STRUCTF_NONE 0L +#define STRUCTF_ADVANCED_POWER (1L << STRUCT_ADVANCED_POWER) +#define STRUCTF_REPAIR (1L << STRUCT_REPAIR) +#define STRUCTF_EYE (1L << STRUCT_EYE) +#define STRUCTF_TEMPLE (1L << STRUCT_TEMPLE) +#define STRUCTF_HAND (1L << STRUCT_HAND) +#define STRUCTF_BIO_LAB (1L << STRUCT_BIO_LAB) +#define STRUCTF_OBELISK (1L << STRUCT_OBELISK) +#define STRUCTF_ATOWER (1L << STRUCT_ATOWER) +#define STRUCTF_WEAP (1L << STRUCT_WEAP) +#define STRUCTF_GTOWER (1L << STRUCT_GTOWER) +#define STRUCTF_RADAR (1L << STRUCT_RADAR) +#define STRUCTF_TURRET (1L << STRUCT_TURRET) +#define STRUCTF_CIV1 (1L << STRUCT_CIV1) +#define STRUCTF_CIV2 (1L << STRUCT_CIV2) +#define STRUCTF_CIV3 (1L << STRUCT_CIV3) +#define STRUCTF_CONST (1L << STRUCT_CONST) +#define STRUCTF_REFINERY (1L << STRUCT_REFINERY) +#define STRUCTF_STORAGE (1L << STRUCT_STORAGE) +#define STRUCTF_HELIPAD (1L << STRUCT_HELIPAD) +#define STRUCTF_SAM (1L << STRUCT_SAM) +#define STRUCTF_AIRSTRIP (1L << STRUCT_AIRSTRIP) +#define STRUCTF_POWER (1L << STRUCT_POWER) +#define STRUCTF_HOSPITAL (1L << STRUCT_HOSPITAL) +#define STRUCTF_BARRACKS (1L << STRUCT_BARRACKS) +#define STRUCTF_TANKER (1L << STRUCT_TANKER) +#define STRUCTF_MISSION (1L << STRUCT_MISSION) + + +/********************************************************************** +** The overlays are enumerated here. An overlay functions similarly to +** a transparent icon. It is placed over the terrain but usually falls +** "under" buildings, trees, and units. +*/ +typedef enum OverlayType { + OVERLAY_NONE=-1, + OVERLAY_CONCRETE, // Concrete. + OVERLAY_SANDBAG_WALL, // Piled sandbags. + OVERLAY_CYCLONE_WALL, // Chain-link fence. + OVERLAY_BRICK_WALL, // Solid concrete wall. + OVERLAY_BARBWIRE_WALL, // Barbed-wire wall. + OVERLAY_WOOD_WALL, // Wooden fence. + OVERLAY_TIBERIUM1, // Tiberium patch. + OVERLAY_TIBERIUM2, // Tiberium patch. + OVERLAY_TIBERIUM3, // Tiberium patch. + OVERLAY_TIBERIUM4, // Tiberium patch. + OVERLAY_TIBERIUM5, // Tiberium patch. + OVERLAY_TIBERIUM6, // Tiberium patch. + OVERLAY_TIBERIUM7, // Tiberium patch. + OVERLAY_TIBERIUM8, // Tiberium patch. + OVERLAY_TIBERIUM9, // Tiberium patch. + OVERLAY_TIBERIUM10, // Tiberium patch. + OVERLAY_TIBERIUM11, // Tiberium patch. + OVERLAY_TIBERIUM12, // Tiberium patch. + OVERLAY_ROAD, // Road/concrete piece. + OVERLAY_SQUISH, // Squish mark for overran infantry. + OVERLAY_V12, // Haystacks + OVERLAY_V13, // Haystack + OVERLAY_V14, // Wheat field + OVERLAY_V15, // Fallow field + OVERLAY_V16, // Corn field + OVERLAY_V17, // Celery field + OVERLAY_V18, // Potato field + OVERLAY_FLAG_SPOT, // Flag start location. + OVERLAY_WOOD_CRATE, // Wooden goodie crate. + OVERLAY_STEEL_CRATE, // Steel goodie crate. + + OVERLAY_COUNT, + OVERLAY_FIRST=0 +} OverlayType; + +inline OverlayType operator++(OverlayType &, int); + + +/********************************************************************** +** This specifies the infantry in the game. The "E" designation is +** similar to the army classification of enlisted soldiers. +*/ +typedef enum InfantryType{ + INFANTRY_NONE=-1, + INFANTRY_E1, // Mini-gun armed. + INFANTRY_E2, // Grenade thrower. + INFANTRY_E3, // Rocket launcher. + INFANTRY_E4, // Flame thrower equipped. + INFANTRY_E5, // Chemical thrower equipped. + INFANTRY_E7, // Engineer. + INFANTRY_RAMBO, // Commando. + + INFANTRY_C1, // Civilian + INFANTRY_C2, // Civilian + INFANTRY_C3, // Civilian + INFANTRY_C4, // Civilian + INFANTRY_C5, // Civilian + INFANTRY_C6, // Civilian + INFANTRY_C7, // Civilian + INFANTRY_C8, // Civilian + INFANTRY_C9, // Civilian + INFANTRY_C10, // Nikumba + INFANTRY_MOEBIUS, // Dr. Moebius + INFANTRY_DELPHI, // Agent "Delphi" + INFANTRY_CHAN, // Dr. Chan + + INFANTRY_COUNT, + INFANTRY_FIRST=0 +} InfantryType; + +inline InfantryType operator++(InfantryType &, int); + + +/********************************************************************** +** The game units are enumerated here. These include not only traditional +** vehicles, but also hovercraft and gunboats. +*/ +typedef enum UnitType{ + UNIT_NONE=-1, + UNIT_HTANK, // Heavy tank (Mammoth). + UNIT_MTANK, // Medium tank (M1). + UNIT_LTANK, // Light tank ('Bradly'). + UNIT_STANK, // Stealth tank (Romulan). + UNIT_FTANK, // Flame thrower tank. + UNIT_VICE, // Visceroid + UNIT_APC, // APC. + UNIT_MLRS, // MLRS rocket launcher. + UNIT_JEEP, // 4x4 jeep replacement. + UNIT_BUGGY, // Rat patrol dune buggy type. + UNIT_HARVESTER, // Resource gathering vehicle. + UNIT_ARTY, // Artillery unit. + UNIT_MSAM, // Anti-Aircraft vehicle. + UNIT_HOVER, // Hovercraft. + UNIT_MHQ, // Mobile Head Quarters. + UNIT_GUNBOAT, // Gunboat + UNIT_MCV, // Mobile construction vehicle. + UNIT_BIKE, // Nod recon motor-bike. + UNIT_TRIC, // Triceratops + UNIT_TREX, // Tyranosaurus Rex + UNIT_RAPT, // Velociraptor + UNIT_STEG, // Stegasaurus + + UNIT_COUNT, + UNIT_FIRST=0 +} UnitType; + +inline UnitType operator++(UnitType &, int); + +#define UNITF_HTANK (1L<id) + + +#endif diff --git a/DESCDLG.CPP b/DESCDLG.CPP new file mode 100644 index 0000000..7a08ef5 --- /dev/null +++ b/DESCDLG.CPP @@ -0,0 +1,184 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\descdlg.cpv 2.17 16 Oct 1995 16:49:44 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DESCDLG.CPP * + * * + * Programmer : Maria del Mar McCready Legg * + * Joe L. Bostic * + * * + * Start Date : Jan 26, 1995 * + * * + * Last Update : Jan 26, 1995 [MML] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * DescriptionClass::Process -- Handles all the options graphic interface. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "descdlg.h" + + +/*********************************************************************************************** + * DescriptionClass::Process -- Handles all the options graphic interface. * + * * + * This dialog uses an edit box to "fill-out" a description. * + * * + * INPUT: char *string - return answer here. * + * OUTPUT: none * + * WARNINGS: none * + * HISTORY: 12/31/1994 MML : Created. * + *=============================================================================================*/ +void DescriptionClass::Process(char *string) +{ + /* + ----------------------------------------------------------------- + Set up the window. Window x-coords are in bytes not pixels. + ----------------------------------------------------------------- + */ + Set_Window(WINDOW_EDITOR, OPTION_X, OPTION_Y, OPTION_WIDTH, OPTION_HEIGHT); + Set_Logic_Page(SeenBuff); + + /* + ----------------------------------------------------------------------- + Create Buttons. Button coords are in pixels, but are window-relative. + ----------------------------------------------------------------------- + */ + TextButtonClass optionsbtn( + BUTTON_OPTIONS, + TXT_OK, + TPF_6PT_GRAD, + 0, + BUTTON_Y); + + TextButtonClass cancelbtn( + BUTTON_CANCEL, + TXT_CANCEL, + TPF_6PT_GRAD, + 0, + BUTTON_Y); + + cancelbtn.X = OPTION_X + ((OPTION_WIDTH - optionsbtn.Width)/3)*2; + optionsbtn.X = OPTION_X + ((OPTION_WIDTH - optionsbtn.Width)/3); + optionsbtn.Add_Tail(cancelbtn); + + EditClass edit( + BUTTON_EDIT, + string, + 31, + TPF_6PT_GRAD, + 0, + EDIT_Y + ,EDIT_W); + + edit.Set_Focus(); + edit.X = OPTION_X + (OPTION_WIDTH - edit.Width)/2, + optionsbtn.Add_Tail(edit); + + /* + ** This causes left mouse button clicking within the confines of the dialog to + ** be ignored if it wasn't recognized by any other button or slider. + */ + GadgetClass dialog(OPTION_X, OPTION_Y, OPTION_WIDTH, OPTION_HEIGHT, GadgetClass::LEFTPRESS); + optionsbtn.Add_Tail(dialog); + + /* + ** This causes a right click anywhere or a left click outside the dialog region + ** to be equivalent to clicking on the return to options dialog. + */ + ControlClass background(BUTTON_OPTIONS, 0, 0, SeenBuff.Get_Width(), SeenBuff.Get_Height(), GadgetClass::LEFTPRESS|GadgetClass::RIGHTPRESS); + optionsbtn.Add_Tail(background); + + /* + ------------------- Main Processing Loop -------------------- + */ + bool display = true; + bool process = true; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=TRUE; + } + + /* + -------------- Invoke game callback ------------- + */ + Call_Back(); + + /* + -------------- Refresh display if needed -------------- + */ + if (display) { + + Window_Hide_Mouse(WINDOW_EDITOR); + + /* + --------- Draw the background ----------- + */ + Window_Box (WINDOW_EDITOR, BOXSTYLE_GREEN_BORDER); // has border, raised up + Draw_Caption(TXT_MISSION_DESCRIPTION, OPTION_X, OPTION_Y, OPTION_WIDTH); + + /* + --------- Draw the titles ----------- + */ + optionsbtn.Draw_All(); + Window_Show_Mouse(); + display = false; + } + + /* + -------------- Get user input --------------- + */ + KeyNumType input = optionsbtn.Input(); + + /* + -------------- Process Input ---------------- + */ + switch (input) { + + case KN_RETURN: + case BUTTON_OPTIONS|KN_BUTTON: + strtrim(string); + process = false; + break; + + case KN_ESC: + case BUTTON_CANCEL|KN_BUTTON: + string[0]= NULL; + strtrim(string); + process = false; + break; + + case BUTTON_EDIT|KN_BUTTON: + break; + } + } +} + diff --git a/DESCDLG.H b/DESCDLG.H new file mode 100644 index 0000000..985505b --- /dev/null +++ b/DESCDLG.H @@ -0,0 +1,69 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\descdlg.h_v 2.18 16 Oct 1995 16:47:26 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DESCDLG.H * + * * + * Programmer : Maria del Mar McCready Legg * + * Joe L. Bostic * + * * + * Start Date : Jan 26, 1995 * + * * + * Last Update : Jan 26, 1995 [MML] * + * * + *---------------------------------------------------------------------------------------------*/ + +#ifndef DESCDLG_H +#define DESCDLG_H + +#include "gadget.h" + +class DescriptionClass +{ + private: + + enum DescriptionClassEnum { + OPTION_WIDTH=216, // Width of dialog box. + OPTION_HEIGHT=122, // Height of dialog box. + OPTION_X=(((320 - OPTION_WIDTH) / 2) & ~7), + OPTION_Y=((200 - OPTION_HEIGHT) / 2), + TEXT_X=OPTION_X+32, // Title's x pos + TEXT_Y=OPTION_Y+32, // Add 11 for each following line + BUTTON_OPTIONS=1, // Button number for "Ok" + BUTTON_CANCEL, + BUTTON_EDIT, + BUTTON_X=OPTION_X+63, // Options button x pos + BUTTON_Y=OPTION_Y+102, // Options button y pos + EDIT_Y =OPTION_Y+50, + EDIT_W =180 //204, + }; + + public: + DescriptionClass(void) {}; + void Process(char *string); +}; + +#endif + + diff --git a/DIAL8.CPP b/DIAL8.CPP new file mode 100644 index 0000000..ea068ad --- /dev/null +++ b/DIAL8.CPP @@ -0,0 +1,317 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\dial8.cpv 2.18 16 Oct 1995 16:51:32 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DIAL8.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : February 6, 1995 * + * * + * Last Update : February 6, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * Dial8Class::Action -- action routine for Dial8Class * + * Dial8Class::Dial8Class -- constructor for the facing dial * + * Dial8Class::Draw_Me -- render routine for Dial8Class * + * Dial8Class::Get_Direction -- retrieves direction (0-255) of dial * + * Dial8Class::Set_Direction -- sets current direction (0-255) of dial * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*************************************************************************** + * Dial8Class::Dial8Class -- constructor for the facing dial * + * * + * INPUT: * + * id button ID * + * x,y,w,h dimensions in window-relative pixels * + * dir numerical initial facing value (0-255); this is the * + * value returned by WWLIB Desired_Facing8() * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/16/1994 BR : Created. * + *=========================================================================*/ +Dial8Class::Dial8Class(int id, int x, int y, int w, int h, DirType dir) : + ControlClass(id, x, y, w, h, LEFTPRESS | LEFTHELD | LEFTRELEASE, true) +{ + /* + ** Center coordinates. + */ + FaceX = X + (Width / 2); + FaceY = Y + (Height / 2); + + /* + ** Init directions. + */ + Direction = dir; // 0 - 255 + Facing = Dir_Facing(Direction); // 0 - 7 + OldFacing = Facing; // 0 - 7 + + /* + ** Compute the drawing dimensions: a 45-degree angle intersects a unity- + ** radius circle at (.707,.707). Make the decorations 8/10 of the radius, + ** and the line extend to 6/10 of the radius. Use Width/2 for x-radius, + ** Height/2 for y-radius. + */ + FacePoint[0][0] = FaceX; + FacePoint[0][1] = FaceY - (h * 8 / 2) / 10; + + FacePoint[1][0] = FaceX + (w * 7 * 8 / 2) / 100; + FacePoint[1][1] = FaceY - (h * 7 * 8 / 2) / 100; + + FacePoint[2][0] = FaceX + (w * 8 / 2) / 10; + FacePoint[2][1] = FaceY; + + FacePoint[3][0] = FaceX + (w * 7 * 8 / 2) / 100; + FacePoint[3][1] = FaceY + (h * 7 * 8 / 2) / 100; + + FacePoint[4][0] = FaceX; + FacePoint[4][1] = FaceY + (h * 8 / 2) / 10; + + FacePoint[5][0] = FaceX - (w * 7 * 8 / 2) / 100; + FacePoint[5][1] = FaceY + (h * 7 * 8 / 2) / 100; + + FacePoint[6][0] = FaceX - (w * 8 / 2) / 10; + FacePoint[6][1] = FaceY; + + FacePoint[7][0] = FaceX - (w * 7 * 8 / 2) / 100; + FacePoint[7][1] = FaceY - (h * 7 * 8 / 2) / 100; + + FaceLine[0][0] = FaceX; + FaceLine[0][1] = FaceY - (h * 6 / 2) / 10; + + FaceLine[1][0] = FaceX + (w * 7 * 6 / 2) / 100; + FaceLine[1][1] = FaceY - (h * 7 * 6 / 2) / 100; + + FaceLine[2][0] = FaceX + (w * 6 / 2) / 10; + FaceLine[2][1] = FaceY; + + FaceLine[3][0] = FaceX + (w * 7 * 6 / 2) / 100; + FaceLine[3][1] = FaceY + (h * 7 * 6 / 2) / 100; + + FaceLine[4][0] = FaceX; + FaceLine[4][1] = FaceY + (h * 6 / 2) / 10; + + FaceLine[5][0] = FaceX - (w * 7 * 6 / 2) / 100; + FaceLine[5][1] = FaceY + (h * 7 * 6 / 2) / 100; + + FaceLine[6][0] = FaceX - (w * 6 / 2) / 10; + FaceLine[6][1] = FaceY; + + FaceLine[7][0] = FaceX - (w * 7 * 6 / 2) / 100; + FaceLine[7][1] = FaceY - (h * 7 * 6 / 2) / 100; +} + + +/*************************************************************************** + * Dial8Class::Action -- activation function for Dial8Class * + * * + * INPUT: * + * flags the reason we're being called * + * key the KN_number that was pressed * + * * + * OUTPUT: * + * true = event was processed, false = event not processed * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/06/1995 BR : Created. * + *=========================================================================*/ +int Dial8Class::Action(unsigned flags, KeyNumType &key) +{ + static int is_sel = 0; + + /* + ** We might end up clearing the event bits. Make sure that the sticky + ** process is properly updated anyway. + */ + Sticky_Process(flags); + + if (flags & LEFTPRESS) { + is_sel = 1; + } + + /* + ** If left mouse is clicked or held, and the dial has changed its direction, + ** invoke the parent Action routine: + ** GadgetClass::Action handles Sticky processing, & sets IsToRepaint if any + ** flag bits are set. + ** ControlClass::Action handles Peer_To_Peer notification, and substitues + ** 'key' with the button ID if any flags are set, or 0 if no flags are set + */ + if (flags & LEFTPRESS || ((flags & LEFTHELD) && is_sel)) { + /* + ** Get new dial position (0-255) + */ + Direction = (DirType)Desired_Facing8(FaceX, FaceY, Get_Mouse_X(), Get_Mouse_Y()); + + /* + ** Convert to Facing value (0-7). + */ + Facing = Dir_Facing(Direction); + + /* + ** If it's moved, redraw. + */ + if (Facing!=OldFacing) { + OldFacing = Facing; + ControlClass::Action(flags,key); + return(true); + + } else { + + /* + ** Dial hasn't moved; kill the event & return + */ + key = KN_NONE; + ControlClass::Action(0,key); + return(true); + } + + } else { + + /* + ** Otherwise, no events have occurred; kill the event if it's a LEFTRELEASE, + ** and return + */ + if (flags & LEFTRELEASE) { + key = KN_NONE; + is_sel = 0; + } + return(ControlClass::Action(0,key)); + } +} + + +/*************************************************************************** + * Dial8Class::Draw_Me -- custom render routine for Dial8Class * + * * + * INPUT: * + * forced true = draw regardless of the current redraw flag state* + * * + * OUTPUT: * + * true = gadget was redrawn, false = wasn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/06/1995 BR : Created. * + *=========================================================================*/ +int Dial8Class::Draw_Me(int forced) +{ + /* + ** Redraw if parent indicates a redraw is needed + */ + if (ControlClass::Draw_Me(forced)) { + /* + ** Hide the mouse. + */ + + if (LogicPage == &SeenBuff) { + Hide_Mouse(); + } + + /* + ** Draw background & decorations. + */ + Draw_Box(X, Y, Width, Height, BOXSTYLE_GREEN_DOWN, true); + for (int i=0; i<8; i++) { + Draw_Box(FacePoint[i][0] - 1, FacePoint[i][1] -1, 3, 3, BOXSTYLE_GREEN_RAISED, false); + } + + /* + ** Draw the hand & its shadow. + */ + LogicPage->Draw_Line(FaceX+1, FaceY+1, FaceLine[Facing][0]+1, FaceLine[Facing][1]+1,CC_GREEN_SHADOW); + LogicPage->Draw_Line(FaceX, FaceY, FaceLine[Facing][0], FaceLine[Facing][1],CC_LIGHT_GREEN); + + /* + ** Restore the mouse. + */ + if (LogicPage == &SeenBuff) { + Show_Mouse(); + } + + return(true); + } + + return(false); +} + + +/*************************************************************************** + * Dial8Class::Get_Direction -- retrieves direction (0-255) of dial * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * DirType dial is pointing to * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/17/1994 BR : Created. * + *=========================================================================*/ +DirType Dial8Class::Get_Direction(void) const +{ + return(Direction); +} + + +/*************************************************************************** + * Dial8Class::Set_Direction -- sets current direction (0-255) of dial * + * * + * INPUT: * + * DirType to set dial to * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/17/1994 BR : Created. * + *=========================================================================*/ +void Dial8Class::Set_Direction(DirType dir) +{ + Direction = dir; + Facing = Dir_Facing(Direction); + OldFacing = Facing; + Flag_To_Redraw(); +} diff --git a/DIAL8.H b/DIAL8.H new file mode 100644 index 0000000..b9b5d43 --- /dev/null +++ b/DIAL8.H @@ -0,0 +1,76 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\dial8.h_v 2.18 16 Oct 1995 16:47:28 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DIAL8.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : 02/06/95 * + * * + * Last Update : February 6, 1995 [BR] * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef DIAL8_H +#define DIAL8_H + +class Dial8Class : public ControlClass +{ + public: + /* + ** Constructor/Destructor + */ + Dial8Class(int id, int x, int y, int w, int h, DirType dir); + + /* + ** Get/Set the direction the dial is currently pointing + */ + DirType Get_Direction(void) const; + void Set_Direction(DirType dir); + + /* + ** Overloaded draw routine + */ + virtual int Draw_Me(int forced = false); + + protected: + /* + ** Overloaded event processing routine + */ + virtual int Action(unsigned flags, KeyNumType &key); + + private: + int FaceX; // x-coord of center of face + int FaceY; // y-coord of center of face + int FacePoint[8][2]; // coords of the little dial decorations + int FaceLine[8][2]; // coords for drawing the dial hand + DirType Direction; // 0-255 numerical direction of dial + FacingType Facing; // numerical facing direction of dial (0 - 7) + FacingType OldFacing; // previous Facing value + +}; + +#endif + diff --git a/DIALOG.CPP b/DIALOG.CPP new file mode 100644 index 0000000..da9e371 --- /dev/null +++ b/DIALOG.CPP @@ -0,0 +1,790 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\dialog.cpv 2.17 16 Oct 1995 16:51:50 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DIALOG.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : May 18, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Clip_Text_Print -- Prints text with clipping and support. * + * Dialog_Box -- draws a dialog background box * + * Display_Place_Building -- Displays the "place building" dialog box. * + * Display_Select_Target -- Displays the "choose target" prompt. * + * Display_Status -- Display the player scenario status box. * + * Draw_Box -- Displays a highlighted box. * + * Fancy_Text_Print -- Prints text with a drop shadow. * + * Redraw_Needed -- Determine if sidebar needs to be redrawn. * + * Render_Bar_Graph -- Renders a specified bargraph. * + * Simple_Text_Print -- Prints text with a drop shadow. * + * Window_Box -- Draws a fancy box over the specified window. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * Dialog_Box -- draws a dialog background box * + * * + * INPUT: * + * x,y,w,h the usual * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/26/1995 BR : Created. * + *=============================================================================================*/ +void Dialog_Box(int x, int y, int w, int h) +{ + Draw_Box( x, y, w, h, BOXSTYLE_GREEN_BORDER, true); +} + + +/*********************************************************************************************** + * Draw_Box -- Displays a highlighted box. * + * * + * This will draw a highlighted box to the logicpage. It can * + * optionally fill the box with a color as well. This is a low level * + * function and thus, it doesn't do any graphic mode color adjustments. * + * * + * INPUT: x,y -- Upper left corner of the box to be drawn (pixels). * + * * + * w,h -- Width and height of box (in pixels). * + * * + * up -- Is the box rendered in the "up" stated? * + * * + * filled-- Is the box to be filled. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1991 JLB : Created. * + * 05/30/1992 JLB : Embedded color codes. * + * 07/31/1992 JLB : Depressed option added. * + *=============================================================================================*/ +extern void CC_Texture_Fill (void const *shapefile, int shapenum, int xpos, int ypos, int width, int height); + +void Draw_Box(int x, int y, int w, int h, BoxStyleEnum up, bool filled) +{ + static BoxStyleType const ButtonColors[BOXSTYLE_COUNT] = { + + //Filler, Shadow, Hilite, Corner colors + + { LTGREY, WHITE, DKGREY, LTGREY}, // 0 Button is down. + { LTGREY, DKGREY, WHITE, LTGREY}, // 1 Button is up w/border. + { LTBLUE, BLUE, LTCYAN, LTBLUE}, // 2 Raised blue. + { DKGREY, WHITE, BLACK, DKGREY}, // 3 Button is disabled down. + { DKGREY, BLACK, WHITE, LTGREY}, // 4 Button is disabled up. + { LTGREY, DKGREY, WHITE, LTGREY}, // 5 Button is up w/arrows. + //{ CC_GREEN_BKGD, CC_LIGHT_GREEN, CC_GREEN_SHADOW, CC_GREEN_CORNERS }, // 6 Button is down. + //{ CC_GREEN_BKGD, CC_GREEN_SHADOW, CC_LIGHT_GREEN, CC_GREEN_CORNERS }, // 7 Button is up w/border. + { CC_GREEN_BKGD, 14, 12, 13 }, // 6 Button is down. + { CC_GREEN_BKGD, 12, 14, 13 }, // 7 Button is up w/border. + { DKGREY, WHITE, BLACK, DKGREY}, // 8 Button is disabled down. + { DKGREY, BLACK, LTGREY, DKGREY}, // 9 Button is disabled up. + //{ BLACK, CC_GREEN_BOX, CC_GREEN_BOX, BLACK}, // 10 List box. + //{ BLACK, CC_GREEN_BOX, CC_GREEN_BOX, BLACK}, // 11 Menu box. + { BLACK, 14, 14, BLACK}, // 10 List box. + { BLACK, 14, 14, BLACK}, // 11 Menu box. + }; + + w--; + h--; + BoxStyleType const &style = ButtonColors[up]; + + if (filled) { + if (style.Filler == CC_GREEN_BKGD){ + CC_Texture_Fill (MixFileClass::Retrieve("BTEXTURE.SHP"), InMainLoop, x, y, w, h); + }else{ + LogicPage->Fill_Rect( x, y, x+w, y+h, style.Filler); + } + } + + switch ( up ) { + case ( BOXSTYLE_GREEN_BOX ): + LogicPage->Draw_Rect(x, y, x+w, y+h, style.Highlight); + break; + + case ( BOXSTYLE_GREEN_BORDER ): + LogicPage->Draw_Rect(x+1, y+1, x+w-1, y+h-1, style.Highlight); + break; + + default: + LogicPage->Draw_Line(x, y+h, x+w, y+h, style.Shadow); + LogicPage->Draw_Line(x+w, y, x+w, y+h, style.Shadow); + + LogicPage->Draw_Line(x, y, x+w, y, style.Highlight); + LogicPage->Draw_Line(x, y, x, y+h, style.Highlight); + + LogicPage->Put_Pixel(x, y+h, style.Corner); + LogicPage->Put_Pixel(x+w, y, style.Corner); + break; + } +} + + +/*********************************************************************************************** + * Format_Window_String -- Separates a String into Lines. * + * This function will take a long string and break it up into lines * + * which are not longer then the window width. Any character < ' ' is * + * considered a new line marker and will be replaced by a NULL. * + * * + * INPUT: char *String - string to be formated. * + * int maxlinelen - Max length of any line in pixels. * + * * + * OUTPUT: int - number of lines string is. * + * * + * WARNINGS: The string passed in will be modified - NULLs will be put * + * into each position that will be a new line. * + * * + * HISTORY: * + * 03/27/1992 SB : Created. * + * 05/18/1995 JLB : Greatly revised for new font system. * + *=============================================================================================*/ +int Format_Window_String(char * string, int maxlinelen, int & width, int & height) +{ + int linelen; + int lines = 0; + width = 0; + height = 0; + + // In no string was passed in, then there are no lines. + if (!string) return(0); + + // While there are more letters left divide the line up. + while (*string) { + linelen = 0; + height += FontHeight + FontYSpacing; + lines++; + + // While the current line is less then the max length... + while (linelen < maxlinelen && *string != '\r' && *string != '\0') { + linelen += Char_Pixel_Width(*string++); + } + + // if the line is to long... + if (linelen >= maxlinelen) { + + /* + ** Back up to an appropriate location to break. + */ + while (*string != ' ' && *string != '\r' && *string != '\0') { + linelen -= Char_Pixel_Width(*string--); + } + + } + + /* + ** Record the largest width of the worst case string. + */ + if (linelen > width) { + width = linelen; + } + + /* + ** Force a break at the end of the line. + */ + if (*string) { + *string++ = '\r'; + } + } + return(lines); +} + + +/*********************************************************************************************** + * Window_Box -- Draws a fancy box over the specified window. * + * * + * This routine will draw a fancy (shaded) box over the specified * + * window. This is the effect used to give the polished look to * + * screen rectangles without having to use art. * + * * + * INPUT: window -- Specified window to fill and border. * + * * + * style -- The style to render the window. * + * * + * OUTPUT: none * + * * + * WARNINGS: The rendering is done to the LogicPage. * + * * + * HISTORY: * + * 03/03/1992 JLB : Created. * + * 07/31/1992 JLB : Cool raised border effect. * + * 06/08/1994 JLB : Takes appropriate enumeration parameters. * + *=============================================================================================*/ +void Window_Box(WindowNumberType window, BoxStyleEnum style) +{ + int x,y,w,h; // Window dimensions. + int border; // Width of border. + + static int _border[BOXSTYLE_COUNT][2] = { + {0,0}, // 0 Simple beveled edge. + {2,4}, // 1 Wide raised border. + {1,1}, // 2 Thick beveled edge. + {2,1}, // 3 Thin raised border. + {0,0}, // 4 Simple beveled edge. + {20,0}, // 5 Simple beveled edge. + {0,0}, // 6 Simple beveled edge. + {2,4}, // 7 Wide raised border. + {0,0}, // 8 Simple beveled edge. + {20,0}, // 9 Simple beveled edge. + {0,1} // 10 Simple 1 pixel box. + }; + + x = WindowList[window][WINDOWX]<<3; + y = WindowList[window][WINDOWY]; + w = WindowList[window][WINDOWWIDTH]<<3; + h = WindowList[window][WINDOWHEIGHT]; + + /* + ** If it is to be rendered to the seenpage, then + ** hide the mouse. + */ + if (LogicPage == (&SeenBuff)) Conditional_Hide_Mouse(x,y,x+w,y+h); + + Draw_Box(x, y, w, h, style, true); + border = _border[style][1]; + + /* + ** Draw the second border if requested. + */ + if (border) { + Draw_Box(x+border, y+border, w-(border<<1), h-(border<<1), style, false); + } + + /* + ** Restore the mouse if it has been hidden and return. + */ + if (LogicPage == &SeenBuff) Conditional_Show_Mouse(); +} + + +/*********************************************************************************************** + * Simple_Text_Print -- Prints text with a drop shadow. * + * * + * This routine functions like Text_Print, but will render a drop * + * shadow (in black). * + * * + * INPUT: text -- Pointer to text to render. * + * * + * x,y -- Pixel coordinate for to print text. * + * * + * fore -- Foreground color. * + * * + * back -- Background color. * + * * + * flag -- Text print control flags. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1991 JLB : Created. * + * 10/26/94 JLB : Handles font X spacing in a more friendly manner. * + *=============================================================================================*/ +void Simple_Text_Print(char const *text, unsigned x, unsigned y, unsigned fore, unsigned back, TextPrintType flag) +{ + static int yspace=0; // Y spacing adjustment for font. + static int xspace=0; // Spacing adjustment for font. + void const * font=0; // Font to use. + +////////////////#if (0) + static unsigned char _textfontpal[16][16] = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 27, 26, 25, 24 }, + { 0,135, 0, 0, 0, 0, 0, 0, 0, 0, 0,136, 136,135,119, 2 }, + { 0,159, 0, 0, 0, 0, 0, 0, 0, 0, 0,142, 143,159,41 ,167 }, + + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0,157, 0, 0, 0, 0, 0, 0, 0, 0, 0,180, 180,157,158, 5 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0,179, 0, 0, 0, 0, 0, 0, 0, 0, 0,180, 180,179,178,176 }, + + { 0,123, 0, 0, 0, 0, 0, 0, 0, 0, 0,122, 122,123,125,127 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0,203, 0, 0, 0, 0, 0, 0, 0, 0, 0,204, 204,203,202,201 }, + { 0, 1, 4,166, 41, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + + { 0,203, 0, 0, 0, 0, 0, 0, 0, 0, 0,204, 204,203,202,201 }, + { 0,203, 0, 0, 0, 0, 0, 0, 0, 0, 0,204, 204,203,202,201 }, + + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + }; + static unsigned char _textpalmedium[16] = { + 0, 25, 119,41, 0, 158,0, 178, 125,0, 202,0, 0, 0, 0, 0 + }; + + static unsigned char _textpalbright[16] = { + 0, 24, 2, 4, 0, 5, 0, 176, 127,0, 201,0, 0, 0, 0, 0 + }; +///////////////////////#endif //(0) + + int point; // Requested font size. + int shadow; // Requested shadow value. + unsigned char fontpalette[16]; // Working font palette array. + memset(&fontpalette[0], back, 16); + + if ((flag & 0xf) == TPF_VCR) { + fontpalette[3] = 12; + fontpalette[9] = 15; + fontpalette[10] = 200; + fontpalette[11] = 201; + fontpalette[12] = 202; + fontpalette[13] = 203; + fontpalette[14] = 204; + fontpalette[15] = 205; + } + + char *tempstr = NULL; + + if (text){ + /* + ** remove any 0xff characters from the string + */ + tempstr = new char [strlen (text)+1]; + char *tempptr = tempstr; + + for ( int i=0 ; i>1; + break; + + case TPF_RIGHT: + x -= String_Pixel_Width(tempstr); + break; + + default: + break; + } + + if ((unsigned)x < SeenBuff.Get_Width() && (unsigned)y < SeenBuff.Get_Height()) { + LogicPage->Print(tempstr, x, y, fore, back); + } + } + if (tempstr){ + delete [] tempstr; + } + +} + + +/*********************************************************************************************** + * Fancy_Text_Print -- Prints text with a drop shadow. * + * * + * This routine functions like Text_Print, but will render a drop * + * shadow (in black). * + * * + * INPUT: text -- Text number to print. * + * * + * x,y -- Pixel coordinate for to print text. * + * * + * fore -- Foreground color. * + * * + * back -- Background color. * + * * + * flag -- Text print control flags. * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine is much slower than normal text print and * + * if rendered to the SEENPAGE, the intermediate rendering * + * steps could be visible. * + * * + * HISTORY: * + * 11/29/1994 JLB : Created * + *=============================================================================================*/ +void Fancy_Text_Print(int text, unsigned x, unsigned y, unsigned fore, unsigned back, TextPrintType flag, ...) +{ + char buffer[512]; // Working staging buffer. + va_list arg; // Argument list var. + + /* + ** If the text number is valid, then process it. + */ + if (text != TXT_NONE) { + va_start(arg, flag); + + /* + ** The text string must be locked since the vsprintf function doesn't know + ** how to handle EMS pointers. + */ + char const * tptr = Text_String(text); + vsprintf(buffer, tptr, arg); + va_end(arg); + + Simple_Text_Print(buffer, x, y, fore, back, flag); + } else { + + /* + ** Just the flags are to be changed, since the text number is TXT_NONE. + */ + Simple_Text_Print((char const *)0, x, y, fore, back, flag); + } +} + + +/*********************************************************************************************** + * Fancy_Text_Print -- Prints text with a drop shadow. * + * * + * This routine functions like Text_Print, but will render a drop * + * shadow (in black). * + * * + * INPUT: text -- Pointer to text to render. * + * * + * x,y -- Pixel coordinate for to print text. * + * * + * fore -- Foreground color. * + * * + * back -- Background color. * + * * + * flag -- Text print control flags. * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine is much slower than normal text print and * + * if rendered to the SEENPAGE, the intermediate rendering * + * steps could be visible. * + * * + * HISTORY: * + * 12/24/1991 JLB : Created. * + * 10/26/94 JLB : Handles font X spacing in a more friendly manner. * + * 11/29/1994 JLB : Separated actual draw action. * + *=============================================================================================*/ +void Fancy_Text_Print(char const *text, unsigned x, unsigned y, unsigned fore, unsigned back, TextPrintType flag, ...) +{ + char buffer[512]; // Working staging buffer. + va_list arg; // Argument list var. + + /* + ** If there is a valid text string pointer then build the final string into the + ** working buffer before sending it to the simple string printing routine. + */ + if (text) { + + /* + ** Since vsprintf doesn't know about EMS pointers, be sure to surround this + ** call with locking code. + */ + va_start(arg, flag); + vsprintf(buffer, text, arg); + va_end(arg); + + Simple_Text_Print(buffer, x, y, fore, back, flag); + } else { + + /* + ** Just the flags are desired to be changed, so call the simple print routine with + ** a NULL text pointer. + */ + Simple_Text_Print((char const *)0, x, y, fore, back, flag); + } +} + + +/*********************************************************************************************** + * Clip_Text_Print -- Prints text with clipping and support. * + * * + * Use this routine to print text that that should be clipped at an arbitrary right margin * + * as well as possibly recognizing characters. Typical users of this routine would * + * be list boxes. * + * * + * INPUT: text -- Reference to the text to print. * + * * + * x,y -- Pixel coordinate of the upper left corner of the text position. * + * * + * fore -- The foreground color to use. * + * * + * back -- The background color to use. * + * * + * flag -- The text print flags to use. * + * * + * width -- The maximum pixel width to draw the text. Extra characters beyond this * + * point will not be printed. * + * * + * tabs -- Optional pointer to a series of pixel tabstop positions. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/21/1995 JLB : Created. * + *=============================================================================================*/ +void Conquer_Clip_Text_Print(char const *text, unsigned x, unsigned y, unsigned fore, unsigned back, TextPrintType flag, unsigned width, int const * tabs) +{ + char buffer[512]; + + if (text) { + strcpy(buffer, text); + + /* + ** Set the font and spacing characteristics according to the flag + ** value passed in. + */ + Simple_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, flag); + + char * source = &buffer[0]; + unsigned offset = 0; + int processing = true; + while (processing && offset < width) { + char * ptr = strchr(source, '\t'); + + /* + ** Zap the tab character. It will be processed later. + */ + if (ptr) { + *ptr = '\0'; + } + + if (*source) { + + /* + ** Scan forward until the end of the string is reached or the + ** maximum width, whichever comes first. + */ + int w = 0; + char * bptr = source; + do { + w += Char_Pixel_Width(*bptr++); + } while(*bptr && offset+w < width); + + /* + ** If the maximum width has been exceeded, then remove the last + ** character and signal that further processing is not necessary. + */ + if (offset+w >= width) { + bptr--; + w -= Char_Pixel_Width(*bptr); + *bptr = '\0'; + processing = 0; + } + + /* + ** Print this text block and advance the offset accordingly. + */ + Simple_Text_Print(source, x+offset, y, fore, back, flag); + offset += w; + } + + /* + ** If a was the terminator for this text block, then advance + ** to the next tabstop. + */ + if (ptr) { + if (tabs) { + while (offset > *tabs) { + tabs++; + } + offset = *tabs; + } else { + offset = ((offset+1 / 50) + 1) * 50; + } + source = ptr+1; + } else { + break; + } + } + } +} diff --git a/DISPLAY.CPP b/DISPLAY.CPP new file mode 100644 index 0000000..a531ef4 --- /dev/null +++ b/DISPLAY.CPP @@ -0,0 +1,3753 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\display.cpv 2.16 16 Oct 1995 16:48:24 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DISPLAY.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : August 24, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * DisplayClass::AI -- Handles the maintenance tasks for the map display. * + * DisplayClass::Calculated_Cell -- Fetch a map cell based on specified method. * + * DisplayClass::Cell_Object -- Determines what has been clicked on. * + * DisplayClass::Cell_Shadow -- Determine what shadow icon to use for the cell. * + * DisplayClass::Click_Cell_Calc -- Determines cell from screen X & Y. * + * DisplayClass::Coord_To_Pixel -- Determines X and Y pixel coordinates. * + * DisplayClass::Cursor_Mark -- Set or resets the cursor display flag bits. * + * DisplayClass::DisplayClass -- Default constructor for display class. * + * DisplayClass::Draw_It -- Draws the tactical map. * + * DisplayClass::Flag_To_Redraw -- Flags the display so that it will be redrawn as soon as poss* + * DisplayClass::Get_Occupy_Dimensions -- computes width & height of the given occupy list * + * DisplayClass::Init_Clear -- Clears the display to a known state. * + * DisplayClass::Init_IO -- Creates the map's button list * + * DisplayClass::Init_Theater -- Theater-specific initialization * + * DisplayClass::Map_Cell -- Mark specified cell as having been mapped. * + * DisplayClass::Mouse_Left_Held -- Handles the left button held down. * + * DisplayClass::Mouse_Left_Press -- Handles the left mouse button press. * + * DisplayClass::Mouse_Left_Release -- Handles the left mouse button release. * + * DisplayClass::Mouse_Left_Up -- Handles the left mouse "cruising" over the map. * + * DisplayClass::Mouse_Right_Press -- Handles the right mouse button press. * + * DisplayClass::Next_Object -- Searches for next object on display. * + * DisplayClass::One_Time -- Performs any special one time initializations. * + * DisplayClass::Passes_Proximity_Check -- Determines if building placement is near friendly sq* + * DisplayClass::Pixel_To_Coord -- converts screen coord to COORDINATE * + * DisplayClass::Read_INI -- Reads map control data from INI file. * + * DisplayClass::Redraw_Icons -- Draws all terrain icons necessary. * + * DisplayClass::Redraw_Shadow -- Draw the shadow overlay. * + * DisplayClass::Refresh_Band -- Causes all cells under the rubber band to be redrawn. * + * DisplayClass::Refresh_Cells -- Redraws all cells in list. * + * DisplayClass::Remove -- Removes a game object from the rendering system. * + * DisplayClass::Repair_Mode_Control -- Controls the repair mode. * + * DisplayClass::Scroll_Map -- Scroll the tactical map in desired direction. * + * DisplayClass::Select_These -- All selectable objects in region are selected. * + * DisplayClass::Sell_Mode_Control -- Controls the sell mode. * + * DisplayClass::Set_Cursor_Pos -- Controls the display and animation of the tac cursor. * + * DisplayClass::Set_Cursor_Shape -- Changes the shape of the terrain square cursor. * + * DisplayClass::Set_View_Dimensions -- Sets the tactical display screen coordinates. * + * DisplayClass::Submit -- Adds a game object to the map rendering system. * + * DisplayClass::TacticalClass::Action -- Processes input for the tactical map. * + * DisplayClass::Text_Overlap_List -- Creates cell overlap list for specified text string. * + * DisplayClass::Write_INI -- Writes map data into INI file. * + * DisplayClass::Set_Tactical_Position -- Sets the tactical view position. * + * DisplayClass::Center_Map -- Centers the map about the currently selected objects * + * DisplayClass::Prev_Object -- Searches for the previous object on the map. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#include "function.h" + +/* +** These layer control elements are used to group the displayable objects +** so that proper overlap can be obtained. +*/ +LayerClass DisplayClass::Layer[LAYER_COUNT]; + +/* +** Fading tables +*/ +unsigned char DisplayClass::FadingBrighten[256]; +unsigned char DisplayClass::FadingShade[256]; +unsigned char DisplayClass::FadingLight[256]; +unsigned char DisplayClass::RemapTables[HOUSE_COUNT][3][256]; +unsigned char DisplayClass::FadingGreen[256]; +unsigned char DisplayClass::FadingYellow[256]; +unsigned char DisplayClass::FadingRed[256]; +unsigned char DisplayClass::TranslucentTable[(MAGIC_COL_COUNT+1)*256]; +unsigned char DisplayClass::WhiteTranslucentTable[(1+1)*256]; +unsigned char DisplayClass::MouseTranslucentTable[(4+1)*256]; +void const * DisplayClass::TransIconset; +unsigned char DisplayClass::UnitShadow[(USHADOW_COL_COUNT+1)*256]; +unsigned char DisplayClass::SpecialGhost[2*256]; + +void const * DisplayClass::ShadowShapes; +unsigned char DisplayClass::ShadowTrans[(SHADOW_COL_COUNT+1)*256]; + +/* +** Bit array of cell redraw flags +*/ +BooleanVectorClass DisplayClass::CellRedraw; + +/* +** The main button that intercepts user input to the map +*/ +DisplayClass::TacticalClass DisplayClass::TacButton; + +/* +** Define "_RETRIEVE" if the palette morphing tables are part of the loaded data. If this +** is undefined, then the files will be created. +*/ +#define _RETRIEVE + + +static int const TEX_X = 0; +static int const TEX_Y = 6; +static int const TEX_W = 14; + +extern MixFileClass *TheaterIcons; + +/*********************************************************************************************** + * DisplayClass::DisplayClass -- Default constructor for display class. * + * * + * This constructor for the display class just initializes some of the display settings. * + * Most settings are initialized with the correct values at the time that the Init function * + * is called. There are some cases where default values are wise and this routine fills * + * those particular ones in. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/06/1994 JLB : Created. * + *=============================================================================================*/ +DisplayClass::DisplayClass(void) +{ + TacticalCoord = 0; + ShadowShapes = 0; + TransIconset = 0; + ZoneCell = 0; + ZoneOffset = 0; + CursorSize = 0; + ProximityCheck = false; + PendingObjectPtr = 0; + PendingObject = 0; + PendingHouse = HOUSE_NONE; + IsRepairMode = false; + IsTargettingMode = false; + IsToRedraw = true; + IsRubberBand = false; + IsTentative = false; + IsSellMode = false; +} + + +/*********************************************************************************************** + * DisplayClass::One_Time -- Performs any special one time initializations. * + * * + * This routine is called from the game initialization process. It is to perform any one * + * time initializations necessary for the map display system. It allocates the staging * + * buffer needed for the radar map. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine must be called ONCE and only once. * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + * 05/31/1994 JLB : Handles layer system now. * + * 06/02/1994 JLB : Takes care of misc display tables and data allocation. * + *=============================================================================================*/ +void DisplayClass::One_Time(void) +{ + Set_View_Dimensions(0, Map.Get_Tab_Height()); + + MapClass::One_Time(); + + /* + ** Init the CellRedraw bit array. Do not do this in the constructor, since the + ** BooleanVector may not have been constructed yet. + */ + CellRedraw.Resize(MAP_CELL_TOTAL); + + for (LayerType layer = LAYER_FIRST; layer < LAYER_COUNT; layer++) { + Layer[layer].One_Time(); + } + + /* + ** Load the generic transparent icon set. + */ + TransIconset = MixFileClass::Retrieve("TRANS.ICN"); + + ShadowShapes = MixFileClass::Retrieve("SHADOW.SHP"); + + Set_View_Dimensions(0, Map.Get_Tab_Height()); + + /* + ** Allocate and initialize the remap tables needed for each "house". + */ + HousesType hindex; + int fade; + + for (fade = 0; fade < 3; fade++) { + for (hindex = HOUSE_FIRST; hindex < HOUSE_COUNT; hindex++) { + int color; + + switch (fade) { + case 0: + for (color = 0; color < 256; color++) { + RemapTables[hindex][fade][color] = color; + } + break; + + case 1: + Mem_Copy(FadingLight, RemapTables[hindex][fade], 256); + break; + + case 2: + Mem_Copy(FadingShade, RemapTables[hindex][fade], 256); + break; + } + Mem_Copy(&RemapTables[hindex][fade][((int)hindex+11)*16], &RemapTables[hindex][fade][(0+11)*16], 16); + } + } +} + + +/*********************************************************************************************** + * DisplayClass::Init_Clear -- clears the display to a known state * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/17/1995 BRR : Created. * + *=============================================================================================*/ +void DisplayClass::Init_Clear(void) +{ + MapClass::Init_Clear(); + + /* + ** Clear any object being placed + */ + PendingObjectPtr = 0; + PendingObject = 0; + PendingHouse = HOUSE_NONE; + CursorSize = 0; + IsTargettingMode = false; + IsRepairMode = false; + IsRubberBand = false; + IsTentative = false; + IsSellMode = false; + + /* + ** Empty all the display's layers + */ + for (LayerType layer = LAYER_FIRST; layer < LAYER_COUNT; layer++) { + Layer[layer].Init(); + } +} + + +/*********************************************************************************************** + * DisplayClass::Init_IO -- clears & re-builds the map's button list * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/17/1995 BRR : Created. * + *=============================================================================================*/ +void DisplayClass::Init_IO(void) +{ + MapClass::Init_IO(); + + /* + ** Re-attach our buttons to the main map button list, only in non-edit mode. + */ + if (!Debug_Map) { + TacButton.Zap(); + Add_A_Button(TacButton); + } +} + + +/*********************************************************************************************** + * DisplayClass::Init_Theater -- Performs theater-specific initialization (mixfiles, etc) * + * * + * INPUT: * + * theater new theater * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/17/1995 BRR : Created. * + *=============================================================================================*/ +void DisplayClass::Init_Theater(TheaterType theater) +{ + char fullname[16]; + char iconname[16]; +#ifndef _RETRIEVE + static TLucentType const MouseCols[4] = { + {BLACK, BLACK, 110, 0}, + {WHITE, WHITE, 110, 0}, + {LTGREY, LTGREY, 110, 0}, + {DKGREY, DKGREY, 110, 0} + }; + static TLucentType const MagicCols[MAGIC_COL_COUNT] = { + {32,32,110,0}, + {33,33,110,0}, + {34,34,110,0}, + {35,35,110,0}, + {36,36,110,0}, + {37,37,110,0}, + {38,38,110,0}, + {39,39,110,0}, + {BLACK, BLACK, 200, 0}, + {WHITE, BLACK, 40, 0}, + {LTGREY, BLACK, 80, 0}, + {DKGREY, BLACK, 140, 0} + }; + static TLucentType const WhiteCols[1] = { + {1, WHITE, 80, 0} + }; + static TLucentType const ShadowCols[SHADOW_COL_COUNT] = { + {WHITE+1, BLACK,130,0}, + {WHITE, BLACK,170,0}, + {LTGRAY, BLACK,250,0}, + {DKGRAY, BLACK,250,0} + }; + static TLucentType const UShadowCols[USHADOW_COL_COUNT] = { + {LTGREEN, BLACK,130,0} + }; +#endif + + /* + ---------------------- Invoke parent's init routine ---------------------- + */ + MapClass::Init_Theater(theater); + + /* + ** Save the new theater value + */ + Theater = theater; + +#ifndef DEMO + /* + ** Unload old mixfiles, and cache the new ones + */ + sprintf(fullname, "%s.MIX", Theaters[Theater].Root); + if (Theater != LastTheater){ + if (TheaterData) { + delete TheaterData; + } + TheaterData = new MixFileClass(fullname); + TheaterData->Cache(); + } + +#endif + /* + ** Register the hi-res icons mix file now since it is theater specific + */ + sprintf(fullname, "%s.MIX", Theaters[Theater].Root); + strcpy (iconname, fullname); + strcpy (&iconname[4], "ICNH.MIX"); + if (Theater != LastTheater){ + if (TheaterIcons) { + delete TheaterIcons; + } + TheaterIcons = new MixFileClass(iconname); + TheaterIcons->Cache(); + } + + + + /* + ** Load the custom palette associated with this theater. + ** The fading palettes will have to be generated as well. + */ + sprintf(fullname, "%s.PAL", Theaters[theater].Root); + void const * ptr = MixFileClass::Retrieve(fullname); + Mem_Copy((void *)ptr, GamePalette, 768); + + + Mem_Copy(GamePalette, OriginalPalette, 768); + +#ifndef _RETRIEVE + /* + ** Make sure that remapping doesn't occur on the colors that cycle. + */ + memset(&GamePalette[CYCLE_COLOR_START*3], 0x3F, CYCLE_COLOR_COUNT*3); +#endif + + +#ifdef _RETRIEVE + CCFileClass(Fading_Table_Name("GREEN", theater)).Read(FadingGreen, sizeof(FadingGreen)); +#else + Build_Fading_Table(GamePalette, FadingGreen, GREEN, 110); + CCFileClass(Fading_Table_Name("GREEN", theater)).Write(FadingGreen, sizeof(FadingGreen)); +#endif + if (theater == THEATER_DESERT) { + FadingGreen[196] = 160; + } + +#ifdef _RETRIEVE + CCFileClass(Fading_Table_Name("YELLOW", theater)).Read(FadingYellow, sizeof(FadingYellow)); +#else + Build_Fading_Table(GamePalette, FadingYellow, YELLOW, 140); + CCFileClass(Fading_Table_Name("YELLOW", theater)).Write(FadingYellow, sizeof(FadingYellow)); +#endif + +#ifdef _RETRIEVE + CCFileClass(Fading_Table_Name("RED", theater)).Read(FadingRed, sizeof(FadingRed)); +#else + Build_Fading_Table(GamePalette, FadingRed, RED, 140); + CCFileClass(Fading_Table_Name("RED", theater)).Write(FadingRed, sizeof(FadingRed)); +#endif + +#ifdef _RETRIEVE + CCFileClass(Fading_Table_Name("MOUSE", theater)).Read(MouseTranslucentTable, sizeof(MouseTranslucentTable)); +#else + Build_Translucent_Table(GamePalette, &MouseCols[0], 4, MouseTranslucentTable); + CCFileClass(Fading_Table_Name("MOUSE", theater)).Write(MouseTranslucentTable, sizeof(MouseTranslucentTable)); +#endif + +// MouseDrawPtr = MouseTranslucentTable; +// MouseDrawPtr2 = Add_Long_To_Pointer(MouseTranslucentTable, 256L); +// MouseDrawVal = 1; +// MouseDrawFlags = (int)SHAPE_GHOST; + +#ifdef _RETRIEVE + CCFileClass(Fading_Table_Name("TRANS", theater)).Read(TranslucentTable, sizeof(TranslucentTable)); +#else + Build_Translucent_Table(GamePalette, &MagicCols[0], MAGIC_COL_COUNT, TranslucentTable); + CCFileClass(Fading_Table_Name("TRANS", theater)).Write(TranslucentTable, sizeof(TranslucentTable)); +#endif + +#ifdef _RETRIEVE + CCFileClass(Fading_Table_Name("WHITE", theater)).Read(WhiteTranslucentTable, sizeof(WhiteTranslucentTable)); +#else + Build_Translucent_Table(GamePalette, &WhiteCols[0], 1, WhiteTranslucentTable); + CCFileClass(Fading_Table_Name("WHITE", theater)).Write(WhiteTranslucentTable, sizeof(WhiteTranslucentTable)); +#endif + +#ifdef _RETRIEVE + CCFileClass(Fading_Table_Name("SHADOW", theater)).Read(ShadowTrans, sizeof(ShadowTrans)); +#else + Build_Translucent_Table(GamePalette, &ShadowCols[0], SHADOW_COL_COUNT, ShadowTrans); + CCFileClass(Fading_Table_Name("SHADOW", theater)).Write(ShadowTrans, sizeof(ShadowTrans)); +#endif + +#ifdef _RETRIEVE + CCFileClass(Fading_Table_Name("UNITS", theater)).Read(UnitShadow, sizeof(UnitShadow)); +#else + Conquer_Build_Translucent_Table(GamePalette, &UShadowCols[0], USHADOW_COL_COUNT, UnitShadow); + CCFileClass(Fading_Table_Name("UNITS", theater)).Write(UnitShadow, sizeof(UnitShadow)); +#endif + +#ifdef _RETRIEVE + CCFileClass(Fading_Table_Name("SHADE", theater)).Read(FadingShade, sizeof(FadingShade)); +#else + Conquer_Build_Fading_Table(GamePalette, FadingShade, BLACK, 150); + CCFileClass(Fading_Table_Name("SHADE", theater)).Write(FadingShade, sizeof(FadingShade)); +#endif + +#ifdef _RETRIEVE + CCFileClass(Fading_Table_Name("LIGHT", theater)).Read(FadingLight, sizeof(FadingLight)); +#else + Conquer_Build_Fading_Table(GamePalette, FadingLight, WHITE, 85); + CCFileClass(Fading_Table_Name("LIGHT", theater)).Write(FadingLight, sizeof(FadingLight)); +#endif + + /* + ** Create the shadow color used by aircraft. + */ + Conquer_Build_Fading_Table(GamePalette, &SpecialGhost[256], BLACK, 100); + for (int index = 0; index < 256; index++) { + SpecialGhost[index] = 0; + } + + Build_Fading_Table(GamePalette, FadingBrighten, WHITE, 25); + + +#ifndef _RETRIEVE + /* + ** Restore the palette since it was mangled while building the fading tables. + */ + sprintf(fullname, "%s.PAL", Theaters[theater].Root); + ptr = MixFileClass::Retrieve(fullname); + Mem_Copy((void *)ptr, GamePalette, 768); + Mem_Copy(GamePalette, OriginalPalette, 768); +#endif + + /* + ** Adjust the palette according to the visual control option settings. + */ + Options.Fixup_Palette(); +} + + +/*********************************************************************************************** + * DisplayClass::Text_Overlap_List -- Creates cell overlap list for specified text string. * + * * + * This routine is used to create an overlap list that specifies all the cells that are * + * covered by the specified text string. This overlap list is used to handle map refresh * + * logic. * + * * + * INPUT: text -- Pointer to the text that would appear on the map and must have an * + * overlap list generated. * + * * + * x,y -- The coordinates that the text would appear (upper left corner). * + * * + * OUTPUT: Returns with a pointer to an overlap list that covers all cells "under" the text * + * if were displayed at the coordinates specified. The list is actually a series of * + * offsets from the display's upper left corner cell number. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/06/1994 JLB : Created. * + * 12/07/1994 JLB : Sidebar fixup. * + * 08/13/1995 JLB : Optimized for variable sized help text. * + *=============================================================================================*/ +short const * DisplayClass::Text_Overlap_List(char const * text, int x, int y, int lines) +{ + static short _list[30]; + + if (text) { + short * ptr = &_list[0]; + int len = String_Pixel_Width(text)+CELL_PIXEL_W; + int right = TacPixelX + Lepton_To_Pixel(TacLeptonWidth); + + /* + ** If the help text would spill into the sidebar, then flag this fact, but + ** shorten the apparent length so that the icon list calculation will + ** function correctly. + */ + if (x+len >= TacPixelX+Lepton_To_Pixel(TacLeptonWidth)) { + len = right-x; + *ptr++ = REFRESH_SIDEBAR; + } + + /* + ** Build the list of overlap cell offset values according to the text + ** coordinate and the length. + */ + int height = (((FontHeight * lines) + 23) / 24) * 24; + + if (x <= right) { + CELL ul = Click_Cell_Calc(x, y-1); + CELL lr = Click_Cell_Calc(x+len-1, Bound(y+height, TacPixelY, SeenBuff.Get_Height() - 1)); + + if (ul == -1) ul = Click_Cell_Calc(x, y); +// if (lr == -1) lr = Click_Cell_Calc(x+len, y); + + if (ul != -1 && lr != -1) { + for (int yy = Cell_Y(ul); yy <= Cell_Y(lr); yy++) { + for (int xx = Cell_X(ul); xx <= Cell_X(lr); xx++) { + *ptr++ = XY_Cell(xx, yy) - Coord_Cell(TacticalCoord); + } + } + } + } + + *ptr = REFRESH_EOL; + } + return(_list); +} + + +/*********************************************************************************************** + * DisplayClass::Set_View_Dimensions -- Sets the tactical display screen coordinates. * + * * + * Use this routine to set the tactical map screen coordinates and dimensions. This routine * + * is typically used when the screen size or position changes as a result of the sidebar * + * changing position or appearance. * + * * + * INPUT: x,y -- The X and Y pixel position on the screen for the tactical map upper left * + * corner. * + * * + * width -- The width of the tactical display (in pixels). If this parameter is * + * omitted, then the width will be as wide as the screen will allow. * + * * + * height-- The height of the tactial display (in pixels). If this parameter is * + * omitted, then the width wil be as wide as the screen will allow. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/06/1994 JLB : Created. * + * 06/27/1995 JLB : Adjusts tactical map position if necessary. * + *=============================================================================================*/ +void DisplayClass::Set_View_Dimensions(int x, int y, int width, int height) +{ + if (width == -1) { + width = SeenBuff.Get_Width() - x; + } + TacLeptonWidth = Pixel_To_Lepton(width); + + if (height == -1) { + height = SeenBuff.Get_Height() - y; + } + TacLeptonHeight = Pixel_To_Lepton(height); + + /* + ** Adjust the tactical cell if it is now in an invalid position + ** because of the changed dimensions. + */ + int xx = Coord_X(TacticalCoord) - (MapCellX * CELL_LEPTON_W); + int yy = Coord_Y(TacticalCoord) - (MapCellY * CELL_LEPTON_H); + + Confine_Rect(&xx, &yy, TacLeptonWidth, TacLeptonHeight, MapCellWidth * CELL_LEPTON_W, MapCellHeight * CELL_LEPTON_H); + + Set_Tactical_Position(XY_Coord(xx + (MapCellX * CELL_LEPTON_W), yy + (MapCellY * CELL_LEPTON_H))); + + TacPixelX = x; + TacPixelY = y; + WindowList[WINDOW_TACTICAL][WINDOWX] = x >> 3; + WindowList[WINDOW_TACTICAL][WINDOWY] = y; + WindowList[WINDOW_TACTICAL][WINDOWWIDTH] = width >> 3; + WindowList[WINDOW_TACTICAL][WINDOWHEIGHT] = height; + if (Window == WINDOW_TACTICAL) { + Change_Window(0); + Change_Window(Window); + } + IsToRedraw = true; + Flag_To_Redraw(false); + + TacButton.X = TacPixelX; + TacButton.Y = TacPixelY; + TacButton.Width = Lepton_To_Pixel(TacLeptonWidth); + TacButton.Height = Lepton_To_Pixel(TacLeptonHeight); +} + + +/*********************************************************************************************** + * DisplayClass::Set_Cursor_Shape -- Changes the shape of the terrain square cursor. * + * * + * This routine is used to set up the terrain cursor according to the size of the object * + * that is to be placed down. The terrain cursor looks like an arbitrary collection of * + * hatched square overlays. Typical use is when placing buildings. * + * * + * INPUT: list -- A pointer to the list that contains offsets to the cells that are to * + * be marked. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/03/1994 JLB : Created. * + * 06/26/1995 JLB : Puts placement cursor into static buffer. * + *=============================================================================================*/ +void DisplayClass::Set_Cursor_Shape(short const * list) +{ + if (CursorSize) { + Cursor_Mark(ZoneCell+ZoneOffset, false); + } + + ZoneOffset = 0; + + if (list) { + int w,h; + static short _list[50]; + + memcpy(_list, list, sizeof(_list)); + CursorSize = _list; + Get_Occupy_Dimensions (w, h, CursorSize); + ZoneOffset = -(((h/2)*MAP_CELL_W)+(w/2)); + Cursor_Mark(ZoneCell+ZoneOffset, true); + } else { + CursorSize = 0; + } +} + + +/*********************************************************************************************** + * DisplayClass::Passes_Proximity_Check -- Determines if building placement is near friendly sq* + * * + * This routine is used by the building placement cursor logic to determine whether the * + * at the current cursor position if the building would be adjacent to another friendly * + * building. In cases where this is not true, then the building cannot be placed at all. * + * This determination is returned by the function. * + * * + * INPUT: object -- The building object that the current placement system is examining. * + * * + * OUTPUT: bool; Can the pending building object be placed at the present cursor location * + * checking only for proximity to friendly buildings? If this isn't for a * + * building type object, then this routine always returns true. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/06/1994 JLB : Created. * + * 06/07/1994 JLB : Handles concrete check. * + *=============================================================================================*/ +bool DisplayClass::Passes_Proximity_Check(ObjectTypeClass const *object) +{ + short const *ptr; + + /* + ** In editor mode, the proximity check always passes. + */ + if (Debug_Map) { + return(true); + } + + if (!object || !CursorSize || object->What_Am_I() != RTTI_BUILDINGTYPE) { + return(true); + } + + /* + ** Scan through all cells that the building foundation would cover. If any adjacent + ** cells to these are of friendly persuasion, then consider the proximity check to + ** have been a success. + */ + ptr = CursorSize; + while (*ptr != REFRESH_EOL) { + CELL cell = ZoneCell + ZoneOffset + *ptr++; + + for (FacingType facing = FACING_N; facing < FACING_COUNT; facing++) { + CELL newcell = Adjacent_Cell(cell, facing); + + if (!In_Radar(cell)) return(false); + + TechnoClass * base = (*this)[newcell].Cell_Techno(); + + /* + ** The special cell ownership flag allows building adjacent + ** to friendly walls and bibs even though there is no official + ** building located there. + */ + if ((*this)[newcell].Owner == PendingHouse) { + return(true); + } + + if (base && base->What_Am_I() == RTTI_BUILDING && base->House->Class->House == PendingHouse) { + return(true); + } + } + } + return(false); +} + + +/*********************************************************************************************** + * DisplayClass::Set_Cursor_Pos -- Controls the display and animation of the tac cursor. * + * * + * This routine controls the location, display, and animation of the * + * tactical map cursor. * + * * + * INPUT: pos -- Position to move the cursor do. If -1 is passed then * + * the cursor will just be hidden. If the position * + * passed is the same as the last position passed in, * + * then animation could occur (based on timers). * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1991 JLB : Created. * + * 06/02/1994 JLB : Converted to member function. * + * 06/08/1994 JLB : If position is -1, then follow mouse. * + * 02/28/1995 JLB : Forces placement cursor to fit on map. * + *=============================================================================================*/ +CELL DisplayClass::Set_Cursor_Pos(CELL pos) +{ + CELL prevpos; // Last position of cursor (for jump-back reasons). + + /* + ** Follow the mouse position if no cell number is provided. + */ + if (pos == -1) { + pos = Click_Cell_Calc(Get_Mouse_X(), Get_Mouse_Y()); + } + + if (!CursorSize) { + prevpos = ZoneCell; + ZoneCell = pos; + return(prevpos); + } + + /* + ** Adjusts the position so that the placement cursor is never partway off the + ** tactical map. + */ + int w,h; + Get_Occupy_Dimensions (w, h, CursorSize); + + int x = Cell_X(pos + ZoneOffset); + int y = Cell_Y(pos + ZoneOffset); + + if (x < Coord_XCell(TacticalCoord)) x = Coord_XCell(TacticalCoord); +// if (x < TacMapX) x = TacMapX; + if (y < Coord_YCell(TacticalCoord)) y = Coord_YCell(TacticalCoord); +// if (y < TacMapY) y = TacMapY; + if (x+w >= Coord_XCell(TacticalCoord) + Lepton_To_Cell(TacLeptonWidth)) x = Coord_XCell(TacticalCoord)+Lepton_To_Cell(TacLeptonWidth)-w; +// if (x+w >= TacMapX+TacWidth) x = TacMapX+TacWidth-w; + if (y+h >= Coord_YCell(TacticalCoord) + Lepton_To_Cell(TacLeptonHeight)) x = Coord_YCell(TacticalCoord)+Lepton_To_Cell(TacLeptonHeight)-h; +// if (y+h >= TacMapY+TacHeight) y = TacMapY+TacHeight-h; + pos = XY_Cell(x, y) - ZoneOffset; + + /* + ** This checks to see if NO animation or drawing is to occur and, if so, + ** exits. + */ + if (pos == ZoneCell) return(pos); + + prevpos = ZoneCell; + + /* + ** If the cursor is visible, then handle the graphic update. + ** Otherwise, just update the global position of the cursor. + */ + if (CursorSize) { + + /* + ** Erase the old cursor (if it exists) AND the cursor is moving. + */ + if (pos != ZoneCell && ZoneCell != -1) { + Cursor_Mark(ZoneCell+ZoneOffset, false); + } + + /* + ** Render the cursor (could just be animation). + */ + if (pos != -1) { + Cursor_Mark(pos+ZoneOffset, true); + } + } + ZoneCell = pos; + ProximityCheck = Passes_Proximity_Check(PendingObject); + + return(prevpos); +} + + +/*********************************************************************************************** + * DisplayClass::Get_Occupy_Dimensions -- computes width & height of the given occupy list * + * * + * INPUT: * + * w ptr to fill in with height * + * h ptr to fill in with width * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/31/1995 BRR : Created. * + *=============================================================================================*/ +void DisplayClass::Get_Occupy_Dimensions(int & w, int & h, short const *list) +{ + int min_x = MAP_CELL_W; + int max_x = -MAP_CELL_W; + int min_y = MAP_CELL_H; + int max_y = -MAP_CELL_H; + int x,y; + + w = 0; + h = 0; + + if (!list) { + /* + ** Loop through all cell offsets, accumulating max & min x- & y-coords + */ + while (*list != REFRESH_EOL) { + /* + ** Compute x & y coords of the current cell offset. We can't use Cell_X() + ** & Cell_Y(), because they use shifts to compute the values, and if the + ** offset is negative we'll get a bogus coordinate! + */ + x = (*list) % MAP_CELL_W; + y = (*list) / MAP_CELL_H; + + max_x = MAX(max_x, x); + min_x = MIN(min_x, x); + max_y = MAX(max_y, y); + min_y = MIN(min_y, y); + + list++; + } + + w = MAX(1, max_x - min_x + 1); + h = MAX(1, max_y - min_y + 1); + } +} + + +/*********************************************************************************************** + * DisplayClass::Cursor_Mark -- Set or resets the cursor display flag bits. * + * * + * This routine will clear or set the cursor display bits on the map. * + * If the bit is set, then the cursor will be rendered on that map * + * icon. * + * * + * INPUT: pos -- Position of the upper left corner of the cursor. * + * * + * on -- Should the bit be turned on? * + * * + * OUTPUT: none * + * * + * WARNINGS: Be sure that every call to set the bits is matched by a * + * corresponding call to clear the bits. * + * * + * HISTORY: * + * 09/04/1991 JLB : Created. * + * 06/02/1994 JLB : Converted to member function. * + *=============================================================================================*/ +void DisplayClass::Cursor_Mark(CELL pos, bool on) +{ + CELL const *ptr; + CellClass *cellptr; + + if (pos == -1) return; + + /* + ** For every cell in the CursorSize list, invoke its Redraw_Objects and + ** toggle its IsCursorHere flag + */ + ptr = CursorSize; + while (*ptr != REFRESH_EOL) { + CELL cell = pos + *ptr++; + if (In_Radar(cell)) { + cellptr = &(*this)[cell]; + cellptr->Redraw_Objects(); + if (on) { + cellptr->IsCursorHere = true; + } else { + cellptr->IsCursorHere = false; + } + } + } + + /* + ** For every cell in the PendingObjectPtr's Overlap_List, invoke its + ** Redraw_Objects routine. + */ + if (PendingObjectPtr) { + ptr = PendingObjectPtr->Overlap_List(); + while (*ptr != REFRESH_EOL) { + CELL cell = pos + *ptr++; + if (In_Radar(cell)) { + cellptr = &(*this)[cell]; + cellptr->Redraw_Objects(); + } + } + } +} + + +/*********************************************************************************************** + * DisplayClass::AI -- Handles the maintenance tasks for the map display. * + * * + * This routine is called once per game display frame (15 times per second). It handles * + * the mouse shape tracking and map scrolling as necessary. * + * * + * INPUT: input -- The next key just fetched from the input queue. * + * * + * x,y -- Mouse coordinates. * + * * + * OUTPUT: Modifies the input code if necessary. When the input code is consumed, it gets * + * set to 0. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/01/1994 JLB : Created. * + * 06/02/1994 JLB : Filters mouse click input. * + * 06/07/1994 JLB : Fixed so template click will behave right. * + * 10/14/1994 JLB : Changing cursor shape over target. * + * 12/31/1994 JLB : Takes mouse coordinates as parameters. * + * 06/27/1995 JLB : Breaks out of rubber band mode if mouse leaves map. * + *=============================================================================================*/ +void DisplayClass::AI(KeyNumType & input, int x, int y) +{ + if ( + IsRubberBand && + (Get_Mouse_X() < TacPixelX || + Get_Mouse_Y() < TacPixelY || + Get_Mouse_X() >= (TacPixelX + Lepton_To_Pixel(TacLeptonWidth)) || + Get_Mouse_Y() >= (TacPixelY + Lepton_To_Pixel(TacLeptonHeight)))) { + Mouse_Left_Release(-1, Get_Mouse_X(), Get_Mouse_Y(), NULL, ACTION_NONE); + } + + MapClass::AI(input, x, y); +} + + +/*********************************************************************************************** + * DisplayClass::Submit -- Adds a game object to the map rendering system. * + * * + * This routine is used to add an arbitrary (but tangible) game object to the map. It will * + * be rendered (made visible) once it is submitted to this function. This function builds * + * the list of game objects that get rendered each frame as necessary. It is possible to * + * submit the game object to different rendering layers. All objects in a layer get drawn * + * at the same time. Using this layer method it becomes possible to have objects "below" * + * other objects. * + * * + * INPUT: object -- Pointer to the object to add. * + * * + * layer -- The layer to add the object to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + * 05/31/1994 JLB : Improved layer system. * + * 05/31/1994 JLB : Sorts object position if this is for the ground layer. * + *=============================================================================================*/ +void DisplayClass::Submit(ObjectClass const * object, LayerType layer) +{ + if (object) { + Layer[layer].Submit(object, (layer == LAYER_GROUND)); + } +} + + +/*********************************************************************************************** + * DisplayClass::Remove -- Removes a game object from the rendering system. * + * * + * Every object that is to disappear from the map must be removed from the rendering * + * system. * + * * + * INPUT: object -- The object to remove. * + * * + * layer -- The layer to remove it from. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + * 05/31/1994 JLB : Improved layer system. * + *=============================================================================================*/ +void DisplayClass::Remove(ObjectClass const * object, LayerType layer) +{ + if (object) { + Layer[layer].Delete((ObjectClass *)object); + } +} + + +/*********************************************************************************************** + * DisplayClass::Click_Cell_Calc -- Determines cell from screen X & Y. * + * * + * This routine is used to determine the cell that is located at the * + * screen pixel coordinates given. Typical use is when the player * + * clicks with the mouse on the tactical map. * + * * + * INPUT: x,y -- Screen pixel coordinates. * + * * + * OUTPUT: Returns with cell that is under the coordinates specified. * + * If the coordinate specified is outside of the tactical * + * map, then -1 is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/27/1994 JLB : Created. * + *=============================================================================================*/ +CELL DisplayClass::Click_Cell_Calc(int x, int y) +{ + x -= TacPixelX; + x = Pixel_To_Lepton(x); + y -= TacPixelY; + y = Pixel_To_Lepton(y); + + if ((unsigned)x < TacLeptonWidth && + (unsigned)y < TacLeptonHeight) { + + COORDINATE tcoord = XY_Coord(Pixel_To_Lepton(Lepton_To_Pixel(Coord_X(TacticalCoord))), Pixel_To_Lepton(Lepton_To_Pixel(Coord_Y(TacticalCoord)))); + + return(Coord_Cell(Coord_Add(tcoord, XY_Coord(x, y)))); + } + return(-1); +} + + +/*********************************************************************************************** + * DisplayClass::Read_INI -- Reads map control data from INI file. * + * * + * This routine is used to read the map control data from the INI * + * file. * + * * + * INPUT: buffer -- Pointer to the loaded INI file data. * + * * + * OUTPUT: none * + * * + * WARNINGS: The TriggerClass INI data must have been read before calling this function. * + * * + * HISTORY: * + * 05/27/1994 JLB : Created. * + *=============================================================================================*/ +void DisplayClass::Read_INI(char *buffer) +{ + char name[16]; + int len; // Length of data in buffer. + char *tbuffer; // Accumulation buffer of Trigger names. + char *trigsection = "CellTriggers"; + char buf[20]; // trigger name for a cell + int cell; + int i; + + /* + ** Read the map dimensions. + */ + Set_Map_Dimensions(WWGetPrivateProfileInt("MAP", "X", 1, buffer), + WWGetPrivateProfileInt("MAP", "Y", 1, buffer), + WWGetPrivateProfileInt("MAP", "Width", MAP_CELL_W-2, buffer), + WWGetPrivateProfileInt("MAP", "Height", MAP_CELL_H-2, buffer)); + + /* + ** The theater is determined at this point. There is specific data that + ** is custom to this data. Load the custom data (as it related to terrain) + ** at this point. + */ + WWGetPrivateProfileString("MAP", "Theater", Theaters[THEATER_DESERT].Name, name, 13, buffer); + Theater = Theater_From_Name(name); + + /* + ** Remove any old theater specific uncompressed shapes + */ + if (Theater != LastTheater){ + Reset_Theater_Shapes(); + } + + /* + ** Now that the theater is known, init the entire map hierarchy + */ + Init(Theater); + + /* + ** Special initializations occur when the theater is known. + */ + TerrainTypeClass::Init(Theater); + TemplateTypeClass::Init(Theater); + OverlayTypeClass::Init(Theater); + UnitTypeClass::Init(Theater); + InfantryTypeClass::Init(Theater); + BuildingTypeClass::Init(Theater); + BulletTypeClass::Init(Theater); + AnimTypeClass::Init(Theater); + AircraftTypeClass::Init(Theater); + SmudgeTypeClass::Init(Theater); + + LastTheater = Theater; + + /* + ** Read the Waypoint entries. + */ + for (i = 0; i < WAYPT_COUNT; i++) { + sprintf(buf,"%d",i); + Waypoint[i] = WWGetPrivateProfileInt ("Waypoints",buf,-1,buffer); + if (Waypoint[i] != -1) { + (*this)[Waypoint[i]].IsWaypoint = 1; + } + } + + /* + ** Set the starting position (do this after Init(), which clears the cells' + ** IsWaypoint flags). + */ + if (Waypoint[WAYPT_HOME] == -1) { + Waypoint[WAYPT_HOME] = XY_Cell(MapCellX, MapCellY); + } + Set_Tactical_Position(Cell_Coord(Waypoint[WAYPT_HOME])&0xFF00FF00L); + Views[0] = Views[1] = Views[2] = Views[3] = Waypoint[WAYPT_HOME]; + + /* + ** Read the cell trigger names, and assign TriggerClass pointers + */ + len = strlen(buffer) + 2; // len is the length of the INI data + tbuffer = buffer + len; // tbuffer is after the INI data + + /* + ** Read all entry names into 'tbuffer'. + */ + WWGetPrivateProfileString(trigsection, NULL, NULL, tbuffer, ShapeBufferSize-len, buffer); + + /* + ** Loop through all CellTrigger entries. + */ + while (*tbuffer != '\0') { + + /* + ** Get a cell trigger assignment. + */ + WWGetPrivateProfileString(trigsection, tbuffer, NULL, buf, sizeof(buf) - 1, buffer); + + /* + ** Get cell # from entry name. + */ + cell = atoi(tbuffer); + if (cell > 0 && cell < MAP_CELL_TOTAL && !(*this)[cell].IsTrigger) { + + /* + ** Assign trigger pointer using trigger name. + */ + CellTriggers[cell] = TriggerClass::As_Pointer(buf); + if (CellTriggers[cell]) { + (*this)[cell].IsTrigger = 1; + if (CellTriggers[cell]) { + CellTriggers[cell]->AttachCount++; + } + } + } + + /* + ** Step to next entry name. + */ + tbuffer += strlen(tbuffer) + 1; + } +} + + +/*********************************************************************************************** + * DisplayClass::Write_INI -- Writes map data into INI file. * + * * + * This routine is used to write the map control data into the INI * + * file. The scenario editor uses this when creating the scenario * + * startup file. * + * * + * INPUT: buffer -- Pointer to INI file data. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/27/1994 JLB : Created. * + *=============================================================================================*/ +void DisplayClass::Write_INI(char *buffer) +{ + char entry[20]; + + /* + ** Save the map parameters. + */ + WWWritePrivateProfileString("MAP", "Theater", Theaters[Theater].Name, buffer); + WWWritePrivateProfileInt("MAP", "X", MapCellX, buffer); + WWWritePrivateProfileInt("MAP", "Y", MapCellY, buffer); + WWWritePrivateProfileInt("MAP", "Width", MapCellWidth, buffer); + WWWritePrivateProfileInt("MAP", "Height", MapCellHeight, buffer); + + /* + ** Save the Waypoint entries. + */ + for (int i = 0; i < WAYPT_COUNT; i++) { + sprintf(entry,"%d",i); + WWWritePrivateProfileInt ("Waypoints",entry,Waypoint[i],buffer); + } + + /* + ** Erase the CellTriggers section. + */ + WWWritePrivateProfileString("CellTriggers",NULL,NULL,buffer); + + /* + ** Save the cell's triggers. + */ + for (CELL cell = 0; cell < MAP_CELL_TOTAL; cell++) { + if ((*this)[cell].IsTrigger) { + + /* + ** Get cell trigger pointer. + */ + TriggerClass const * trig = CellTriggers[cell]; + + /* + ** Generate entry name. + */ + sprintf(entry,"%d",cell); + + /* + ** Save entry. + */ + WWWritePrivateProfileString("CellTriggers", entry, trig->Get_Name(), buffer); + } + } +} + + +/*********************************************************************************************** + * DisplayClass::Scroll_Map -- Scroll the tactical map in desired direction. * + * * + * This routine is used to scroll the tactical map view in the desired * + * direction. It can also be used to determine if scrolling would be * + * legal without actually performing any scrolling action. * + * * + * INPUT: facing -- The direction to scroll the tactical map. * + * * + * distance -- The distance in leptons to scroll the map. * + * * + * really -- Should the map actually be scrolled? If false, * + * then only the legality of a scroll is checked. * + * * + * OUTPUT: bool; Would scrolling in the desired direction be possible? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + * 05/20/1994 JLB : Converted to member function. * + * 08/09/1995 JLB : Added distance parameter. * + * 08/10/1995 JLB : Any direction scrolling. * + *=============================================================================================*/ +bool DisplayClass::Scroll_Map(DirType facing, int & distance, bool really) +{ + /* + ** If the distance is invalid then no further checking is required. Bail + ** with a no-can-do flag. + */ + if (distance == 0) return(false); + FacingType crude = Dir_Facing(facing); + + if (Coord_X(TacticalCoord) == Cell_To_Lepton(MapCellX) && crude != FACING_W) { + if (crude == FACING_SW) facing = DIR_S; + if (crude == FACING_NW) facing = DIR_N; + } + if (Coord_Y(TacticalCoord) == Cell_To_Lepton(MapCellY) && crude != FACING_N) { + if (crude == FACING_NW) facing = DIR_W; + if (crude == FACING_NE) facing = DIR_E; + } + if (Coord_X(TacticalCoord) + TacLeptonWidth == Cell_To_Lepton(MapCellX+MapCellWidth) && crude != FACING_E) { + if (crude == FACING_NE) facing = DIR_N; + if (crude == FACING_SE) facing = DIR_S; + } + if (Coord_Y(TacticalCoord) + TacLeptonHeight == Cell_To_Lepton(MapCellY+MapCellHeight) && crude != FACING_S) { + if (crude == FACING_SE) facing = DIR_E; + if (crude == FACING_SW) facing = DIR_W; + } + + /* + ** Determine the coordinate that it wants to scroll to. + */ + COORDINATE coord = Coord_Move(TacticalCoord, facing, distance); + + /* + ** Clip the new coordinate to the edges of the game world. + */ + int xx = Coord_X(coord) - Cell_To_Lepton(MapCellX); + int yy = Coord_Y(coord) - Cell_To_Lepton(MapCellY); + bool shifted = Confine_Rect(&xx, &yy, TacLeptonWidth, TacLeptonHeight, Cell_To_Lepton(MapCellWidth), Cell_To_Lepton(MapCellHeight)); + if (xx < 0) { + xx = 0; + shifted = true; + } + if (yy < 0) { + yy = 0; + shifted = true; + } + coord = XY_Coord(xx + Cell_To_Lepton(MapCellX), yy + Cell_To_Lepton(MapCellY)); + + /* + ** If the desired scroll was bound by the edge of the map, then adjust the distance to more accurately + ** reflect the actual distance moved. + */ + if (shifted) { + distance = Distance(TacticalCoord, coord); + } + + /* + ** If the new coordinate is the same as the old, then no scrolling would occur. + */ + if (!distance || coord == TacticalCoord) return(false); + + /* + ** Since the new coordinate is different than the old one, possibly adjust the real + ** tactical map accordingly. + */ + if (really) { + Set_Tactical_Position(coord); + IsToRedraw = true; + Flag_To_Redraw(false); + } + return(true); +} + + +/*********************************************************************************************** + * DisplayClass::Refresh_Cells -- Redraws all cells in list. * + * * + * This routine is used to flag all cells in the specified list for * + * redrawing. * + * * + * INPUT: cell -- The origin cell that the list is offset from. * + * * + * list -- Pointer to a list of offsets from the origin cell. * + * Each cell so specified is flagged for redraw. * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine is rather slow (by definition). * + * * + * HISTORY: * + * 05/14/1994 JLB : Created. * + * 08/01/1994 JLB : Simplified. * + *=============================================================================================*/ +void DisplayClass::Refresh_Cells(CELL cell, short const *list) +{ + if (*list == REFRESH_SIDEBAR) { + list++; + } + while (*list != REFRESH_EOL) { + CELL newcell = cell + *list++; + if (In_Radar(newcell)) { + (*this)[newcell].Redraw_Objects(); + } + } +} + + +/*********************************************************************************************** + * DisplayClass::Cell_Shadow -- Determine what shadow icon to use for the cell. * + * * + * This routine will examine the specified cell and adjacent cells to * + * determine what shadow icon to use. * + * * + * INPUT: cell -- The cell to examine. * + * * + * OUTPUT: Returns with the shadow icon to use. -2= all black. * + * -1= map cell. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/01/1994 JLB : Created. * + * 04/04/1994 JLB : Revamped for new shadow icon method. * + * 04/30/1994 JLB : Converted to member function. * + *=============================================================================================*/ +int DisplayClass::Cell_Shadow(CELL cell) +{ + int index; + int value = -1; + CellClass *cellptr; + static char const CardShadow[16] = {-2,0,1,2,3,-1,4,-1,5,6,-1,-1,7,-1,-1,-1}; + static char const DiagShadow[16] = {-2,8,9,-1,10,-1,-1,-1,11,-1,-1,-1,-1,-1,-1,-1}; + + /* + ** Don't map cells that are at the top or bottom edge. This solves + ** problem of accessing cells off the top or bottom of the map and into + ** who-knows-what memory. + */ + if ((unsigned)(Cell_Y(cell)-1) > MAP_CELL_H-2) return(-2); + + cellptr = &(*this)[cell]; + if (!cellptr->IsMapped) { + + /* + ** Check the cardinal directions first. This will either result + ** in a solution or the flag to check the diagonals. + */ + index = 0; + cellptr--; + if (cellptr->IsMapped) index |= 0x08; + cellptr += MAP_CELL_W+1; + if (cellptr->IsMapped) index |= 0x04; + cellptr -= MAP_CELL_W-1; + if (cellptr->IsMapped) index |= 0x02; + cellptr -= MAP_CELL_W+1; + if (cellptr->IsMapped) index |= 0x01; + value = CardShadow[index]; + + /* + ** The diagonals must be checked, since the cardinal directions + ** did not yield a valid result. + */ + if (value == -2) { + index = 0; + cellptr--; + if (cellptr->IsMapped) index |= 0x08; + cellptr += MAP_CELL_W*2; + if (cellptr->IsMapped) index |= 0x04; + cellptr += 2; + if (cellptr->IsMapped) index |= 0x02; + cellptr -= MAP_CELL_W*2; + if (cellptr->IsMapped) index |= 0x01; + value = DiagShadow[index]; + } + + /* + ** Randomizer should go here. Add sets in multiples of 12. + */ + + } + return(value); +} + + +/*********************************************************************************************** + * DisplayClass::Map_Cell -- Mark specified cell as having been mapped. * + * * + * This routine maps the specified cell. The cell must not already * + * have been mapped and the mapping player must be the human. * + * This routine will update any adjacent cell map icon as appropriate. * + * * + * INPUT: cell -- The cell to be mapped. * + * * + * house -- The player that is doing the mapping. * + * * + * OUTPUT: bool; Was action taken to map this cell? * + * * + * WARNINGS: none. * + * * + * HISTORY: * + * 08/05/1992 JLB : Created. * + * 04/30/1994 JLB : Converted to member function. * + * 05/24/1994 JLB : Takes pointer to HouseClass. * + *=============================================================================================*/ +bool DisplayClass::Map_Cell(CELL cell, HouseClass * house) +{ + if (house != PlayerPtr || cell >= (CELL)Size) return(false); + + /* + ** Don't bother remapping this cell if it is already mapped. + */ + if ((*this)[cell].IsMapped) { + return(false); + } + + /* + ** Mark the cell as being mapped. + */ + (*this)[cell].IsMapped = true; + (*this)[cell].IsVisible = true; + (*this)[cell].Redraw_Objects(); + + /* + ** Check out all adjacent cells to see if they need + ** to be mapped as well. This is necessary because of the + ** "unique" method of showing shadowed cells. Many combinations + ** are not allowed, and to fix this, just map the cells until + ** all is ok. + */ + for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir++) { + int shadow; + CELL c; + + c = Adjacent_Cell(cell, dir); + + if (c != cell && !(*this)[c].IsMapped) { + shadow = Cell_Shadow(c); + + /* + ** Either map the cell or mark it to be refreshed. It + ** will probably change form if it isn't actually mapped. + */ + if (shadow == -1) { + Map_Cell(c, house); + } else { + if (shadow != -2) { + (*this)[c].IsVisible = true; + (*this)[c].Redraw_Objects(); + } + } + } + } + + TechnoClass * tech = (*this)[cell].Cell_Techno(); + if (tech) { + tech->Revealed(house); + } + return(true); +} + + +/*********************************************************************************************** + * DisplayClass::Coord_To_Pixel -- Determines X and Y pixel coordinates. * + * * + * This is the routine that figures out the location on the screen for * + * a specified coordinate. It is one of the fundamental routines * + * necessary for rendering the game objects. It performs some quick * + * tests to see if the coordinate is in a visible region and returns * + * this check as a boolean value. * + * * + * INPUT: coord -- The coordinate to check. * + * * + * x,y -- Reference to the pixel coordinates that this * + * coordinate would be when rendered. * + * * + * OUTPUT: bool; Is this coordinate in a visible portion of the map? * + * * + * WARNINGS: If the coordinate is not in a visible portion of the * + * map, then this X and Y parameters are not set. * + * * + * HISTORY: * + * 05/14/1994 JLB : Created. * + * 12/15/1994 JLB : Converted to member function. * + * 01/07/1995 JLB : Uses inline functions to extract coord components. * + * 08/09/1995 JLB : Uses new coordinate system. * + *=============================================================================================*/ +#define EDGE_ZONE (CELL_LEPTON_W*2) +bool DisplayClass::Coord_To_Pixel(COORDINATE coord, int &x, int &y) +{ + if (coord) { + int xtac = Pixel_To_Lepton(Lepton_To_Pixel(Coord_X(TacticalCoord))); + int xoff = Pixel_To_Lepton(Lepton_To_Pixel(Coord_X(coord))); + + xoff = (xoff+EDGE_ZONE) - xtac; + if ((unsigned)xoff <= TacLeptonWidth + EDGE_ZONE*2) { + int ytac = Pixel_To_Lepton(Lepton_To_Pixel(Coord_Y(TacticalCoord))); + int yoff = Pixel_To_Lepton(Lepton_To_Pixel(Coord_Y(coord))); + + yoff = (yoff+EDGE_ZONE) - ytac; + if ((unsigned)yoff <= TacLeptonHeight + EDGE_ZONE*2) { + x = Lepton_To_Pixel(xoff)-CELL_PIXEL_W*2; + y = Lepton_To_Pixel(yoff)-CELL_PIXEL_H*2; + return(true); + } + } + } + return(false); +} + + +/*********************************************************************************************** + * DisplayClass::Push_Onto_TacMap -- Moves x & y coords to being on tactical map * + * * + * This routine expects a line to be drawn between SOURCE & DEST, so it pushes the coords to * + * be within the region bounded by TacMapX,Y - + TacMapW,H. * + * * + * INPUT: source, dest -- References to the coordinates to check. * + * * + * * + * OUTPUT: bool; Are these coordinates in a visible portion of the map? * + * Returns true if the pushed source & dest are visible, but if neither are * + * within the map, then it returns false. * + * * + * * + * HISTORY: * + * 03/27/1995 BWG : Created. * + *=============================================================================================*/ +bool DisplayClass::Push_Onto_TacMap(COORDINATE &source, COORDINATE &dest) +{ + if (!source || !dest) return(false); + + int x1 = Coord_X(source); + int y1 = Coord_Y(source); + int x2 = Coord_X(dest); + int y2 = Coord_Y(dest); + int left = Coord_X(TacticalCoord); + int right = Coord_X(TacticalCoord) + TacLeptonWidth; + int top = Coord_Y(TacticalCoord); + int bottom = Coord_Y(TacticalCoord) + TacLeptonHeight; + + if (x1 < left && x2 < left) return(false); + if (x1 > right && x2 > right) return(false); + if (y1 < top && y2 < top) return(false); + if (y1 > bottom && y2 > bottom) return(false); + + x1 = Bound(x1, left, right); + x2 = Bound(x2, left, right); + y1 = Bound(y1, top, bottom); + y2 = Bound(y2, top, bottom); + + source = XY_Coord(x1, y1); + dest = XY_Coord(x2, y2); + return(true); +} + + +/*********************************************************************************************** + * DisplayClass::Cell_Object -- Determines what has been clicked on. * + * * + * This routine is used to determine what the player has clicked on. * + * It is passed the cell that the click was on and it then examines * + * the cell and returns with a pointer to the object that is there. * + * * + * INPUT: cell -- The cell that has been clicked upon. * + * * + * x,y -- Optional offsets from the upper left corner of the cell to be used in * + * determining exactly which object in the cell is desired. * + * * + * OUTPUT: Returns with a pointer to the object that is "clickable" in * + * the specified cell. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/14/1994 JLB : Created. * + *=============================================================================================*/ +ObjectClass * DisplayClass::Cell_Object(CELL cell, int x, int y) +{ + return(*this)[cell].Cell_Object(x, y); +} + + +/*********************************************************************************************** + * DisplayClass::Draw_It -- Draws the tactical map. * + * * + * This will draw the tactical map at the recorded position. This * + * routine is used whenever the tactical map moves or needs to be * + * completely redrawn. It will handle making the necessary adjustments * + * to accomodate a moving cursor. * + * * + * INPUT: forced -- bool; force redraw of the entire display? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/15/1991 JLB : Created. (benchmark = 292) * + * 04/15/1991 JLB : Added _cell2meta[] reference array (206) * + * 04/15/1991 JLB : Added actual map reference for terrain (207) * + * 04/16/1991 JLB : _cell2meta converted to int (194) * + * 04/16/1991 JLB : References actual CellIcon[] array (204) * + * 04/16/1991 JLB : Cell size increased to 16 x 16 (167) * + * 04/17/1991 JLB : Cell based tactical map rendering (165) * + * 04/22/1991 JLB : Uses Draw_Stamp() for icon rendering (426) * + * 04/22/1991 JLB : Draw_Stamp uses LogicPage now (276) * + * 04/23/1991 JLB : Map active location cursor (334) * + * 05/02/1991 JLB : Added smoothing and 3 icons sets (431) * + * 05/22/1991 JLB : Broken into Draw_Map() and Refresh_Map(). * + * 09/14/1991 JLB : Uses Refresh_Cell when new cells scroll onto display. * + * 05/12/1992 JLB : Destination page support. * + * 02/14/1994 JLB : Revamped. * + * 05/01/1994 JLB : Converted to member function. * + * 12/15/1994 JLB : Updated to work with display heirarchy. * + * 12/24/1994 JLB : Examines redraw bit intelligently. * + * 12/24/1994 JLB : Combined with old Refresh_Map() function. * + * 01/10/1995 JLB : Rubber band drawing. * + *=============================================================================================*/ + void DisplayClass::Draw_It(bool forced) +{ + int x,y; // Working cell index values. + + MapClass::Draw_It(forced); + + if (IsToRedraw || forced) { + IsToRedraw = false; + + /* + ** In rubber band mode, mark all cells under the "rubber band" to be + ** redrawn. + */ + Refresh_Band(); + + /* + ** If the multiplayer message system is displaying one or more messages, + ** flag all cells covered by the messages to redraw. This will prevent + ** messages from smearing the map if it scrolls. + */ + int num = Messages.Num_Messages(); + if (num) { + for (CELL cell = Coord_Cell(TacticalCoord); cell < Coord_Cell(TacticalCoord) + Lepton_To_Cell(TacLeptonWidth)+1; cell++) { + (*this)[cell].Redraw_Objects(); + } + for (cell = Coord_Cell(TacticalCoord) + MAP_CELL_W; + cell < Coord_Cell(TacticalCoord) + MAP_CELL_W + Lepton_To_Cell(TacLeptonWidth)+1; cell++) { + (*this)[cell].Redraw_Objects(); + } + if (num > 1) { + for (cell = Coord_Cell(TacticalCoord) + MAP_CELL_W*2; + cell < Coord_Cell(TacticalCoord) + MAP_CELL_W*2 + Lepton_To_Cell(TacLeptonWidth)+1; cell++) { + (*this)[cell].Redraw_Objects(); + } + } + if (num > 3) { + for (cell = Coord_Cell(TacticalCoord) + MAP_CELL_W*3; + cell < Coord_Cell(TacticalCoord) + MAP_CELL_W*3 + Lepton_To_Cell(TacLeptonWidth)+1; cell++) { + (*this)[cell].Redraw_Objects(); + } + } + if (num > 4) { + for (cell = Coord_Cell(TacticalCoord) + MAP_CELL_W*4; + cell < Coord_Cell(TacticalCoord) + MAP_CELL_W*4 + Lepton_To_Cell(TacLeptonWidth)+1; cell++) { + (*this)[cell].Redraw_Objects(); + } + } + } + + /* + ** Check for a movement of the tactical map. If there has been some + ** movement, then part (or all) of the icons must be redrawn. + */ + if (Lepton_To_Pixel(Coord_X(DesiredTacticalCoord)) != Lepton_To_Pixel(Coord_X(TacticalCoord)) || + Lepton_To_Pixel(Coord_Y(DesiredTacticalCoord)) != Lepton_To_Pixel(Coord_Y(TacticalCoord))) { + + int xmod = Lepton_To_Pixel(Coord_X(DesiredTacticalCoord)); + int ymod = Lepton_To_Pixel(Coord_Y(DesiredTacticalCoord)); + + int oldx = Lepton_To_Pixel(Coord_X(TacticalCoord))-xmod; // Old relative offset. + int oldy = Lepton_To_Pixel(Coord_Y(TacticalCoord))-ymod; + + int oldw = Lepton_To_Pixel(TacLeptonWidth)-ABS(oldx); // Replicable width. + int oldh = Lepton_To_Pixel(TacLeptonHeight)-ABS(oldy); // Replicable height. + + if (oldw < 1) forced = true; + if (oldh < 1) forced = true; + + /* + ** Work out which map edges need to be redrawn + */ + BOOL redraw_right = (oldx < 0) ? TRUE : FALSE; //Right hand edge + BOOL redraw_left = (oldx > 0) ? TRUE : FALSE; //Left hand edge + BOOL redraw_bottom= (oldy < 0) ? TRUE : FALSE; //Bottom edge + BOOL redraw_top = (oldy > 0) ? TRUE : FALSE; //Top edge + + +//Colour_Debug(2); + /* + ** Blit any replicable block to avoid having to drawstamp. + */ + CachedIconsDrawn=0; + UnCachedIconsDrawn=0; + if (!forced && (oldw != Lepton_To_Pixel(TacLeptonWidth) || oldh != Lepton_To_Pixel(TacLeptonHeight))) { + Set_Cursor_Pos(-1); + /* + ** If hid page is in video memory then we may nned to blit from the seen page to + ** avoid blitting an overlapped region. + */ + if (HidPage.Get_IsDirectDraw() && !OverlappedVideoBlits){ + Hide_Mouse(); + SeenBuff.Blit(HidPage, + ((oldx < 0) ? -oldx : 0) +TacPixelX, + ((oldy < 0) ? -oldy : 0) +TacPixelY, + ((oldx < 0) ? 0 : oldx) +TacPixelX, + ((oldy < 0) ? 0 : oldy) +TacPixelY, + oldw, + oldh); + Show_Mouse(); + }else{ + HidPage.Blit(HidPage, + ((oldx < 0) ? -oldx : 0) +TacPixelX, + ((oldy < 0) ? -oldy : 0) +TacPixelY, + ((oldx < 0) ? 0 : oldx) +TacPixelX, + ((oldy < 0) ? 0 : oldy) +TacPixelY, + oldw, + oldh); + } + } else { + forced = true; + } + + if (oldx < 0) oldx = 0; + if (oldy < 0) oldy = 0; + + /* + ** Record new map position for future reference. + */ + ScenarioInit++; + Set_Tactical_Position(DesiredTacticalCoord); + ScenarioInit--; + + if (!forced) { + + /* + ** + ** Set the 'redraw stamp' bit for any cells that could not be copied. + ** + */ + int startx = -Lepton_To_Pixel(Coord_XLepton(TacticalCoord)); + int starty = -Lepton_To_Pixel(Coord_YLepton(TacticalCoord)); + oldw -= 24; + oldh -= 24; + + if (abs(oldx) < 0x25 && abs(oldy) < 0x25){ + + /* + ** The width of the area we redraw depends on the scroll speed + */ + int extra_x = (abs(oldx)>=16) ? 2 : 1; + int extra_y = (abs(oldy)>=16) ? 2 : 1; + + /* + ** Flag the cells across the top of the visible area if required + */ + if (redraw_top){ + for (y = starty; y <= starty+CELL_PIXEL_H*extra_y; y += CELL_PIXEL_H) { + for (x = startx; x <= Lepton_To_Pixel(TacLeptonWidth)+((CELL_PIXEL_W*2)); x += CELL_PIXEL_W) { + CELL c = Click_Cell_Calc(Bound(x, 0, Lepton_To_Pixel(TacLeptonWidth)-1) + TacPixelX, + Bound(y, 0, Lepton_To_Pixel(TacLeptonHeight)-1) + TacPixelY); + + if (c > 0) (*this)[c].Redraw_Objects(true); + } + } + } + + /* + ** Flag the cells across the bottom of the visible area if required + */ + if (redraw_bottom){ + for (y = Lepton_To_Pixel(TacLeptonHeight)-CELL_PIXEL_H*(1+extra_y); y <= Lepton_To_Pixel(TacLeptonHeight)+CELL_PIXEL_H*3; y += CELL_PIXEL_H) { + for (x = startx; x <= Lepton_To_Pixel(TacLeptonWidth)+((CELL_PIXEL_W*2)); x += CELL_PIXEL_W) { + CELL c = Click_Cell_Calc(Bound(x, 0, Lepton_To_Pixel(TacLeptonWidth)-1) + TacPixelX, + Bound(y, 0, Lepton_To_Pixel(TacLeptonHeight)-1) + TacPixelY); + + if (c > 0) (*this)[c].Redraw_Objects(true); + } + } + } + + /* + ** Flag the cells down the left of the visible area if required + */ + if (redraw_left){ + for (x = startx; x <= startx + CELL_PIXEL_W*extra_x; x += CELL_PIXEL_W) { + for (y = starty; y <= Lepton_To_Pixel(TacLeptonHeight)+((CELL_PIXEL_H*2)); y += CELL_PIXEL_H) { + CELL c = Click_Cell_Calc(Bound(x, 0, Lepton_To_Pixel(TacLeptonWidth)-1) + TacPixelX, + Bound(y, 0, Lepton_To_Pixel(TacLeptonHeight)-1) + TacPixelY); + + if (c > 0) (*this)[c].Redraw_Objects(true); + } + } + } + + /* + ** Flag the cells down the right of the visible area if required + */ + if (redraw_right){ + for (x = Lepton_To_Pixel(TacLeptonWidth)-CELL_PIXEL_W*(extra_x+1); x <= Lepton_To_Pixel(TacLeptonWidth)+CELL_PIXEL_W*3; x += CELL_PIXEL_W) { + for (y = starty; y <= Lepton_To_Pixel(TacLeptonHeight)+((CELL_PIXEL_H*2)); y += CELL_PIXEL_H) { + CELL c = Click_Cell_Calc(Bound(x, 0, Lepton_To_Pixel(TacLeptonWidth)-1) + TacPixelX, + Bound(y, 0, Lepton_To_Pixel(TacLeptonHeight)-1) + TacPixelY); + + if (c > 0) (*this)[c].Redraw_Objects(true); + } + } + } + + }else{ + + /* + ** Set the 'redraw stamp' bit for any cells that could not be copied. + */ + int startx = -Lepton_To_Pixel(Coord_XLepton(TacticalCoord)); + int starty = -Lepton_To_Pixel(Coord_YLepton(TacticalCoord)); + oldw -= 24; + oldh -= 24; + for (y = starty; y <= Lepton_To_Pixel(TacLeptonHeight)+((CELL_PIXEL_H*2)); y += CELL_PIXEL_H) { + for (x = startx; x <= Lepton_To_Pixel(TacLeptonWidth)+((CELL_PIXEL_W*2)); x += CELL_PIXEL_W) { + if (x <= oldx || x >= oldx+oldw || y <= oldy || y >= oldy+oldh) { + CELL c = Click_Cell_Calc(Bound(x, 0, Lepton_To_Pixel(TacLeptonWidth)-1) + TacPixelX, + Bound(y, 0, Lepton_To_Pixel(TacLeptonHeight)-1) + TacPixelY); + + if (c > 0) { + (*this)[c].Redraw_Objects(true); + } + } + } + } + } + } + + } else { + + /* + ** Set the tactical coordinate just in case the desired tactical has changed but + ** not enough to result in any visible map change. This is likely to occur with very + ** slow scroll rates. + */ + ScenarioInit++; + if (DesiredTacticalCoord != TacticalCoord) { + Set_Tactical_Position(DesiredTacticalCoord); + } + ScenarioInit--; + } + + /* + ** If the entire tactical map is forced to be redrawn, then set all the redraw flags + ** and let the normal processing take care of the rest. + */ + if (forced) { + CellRedraw.Set(); + } + +//Colour_Debug(3); + /* + ** The first order of business is to redraw all the underlying icons that are + ** flagged to be redrawn. + */ + //Redraw_Icons(CELL_BLIT_ONLY); + Redraw_Icons(0); + + /* + ** Once the icons are drawn, duplicate the bottom line of the screen into the phantom + ** area one line below the screen. This causes the predator effect to work on any + ** shape drawn at the bottom of the screen. + */ +//Colour_Debug(4); +#ifdef FIX_ME_LATER +// HidPage.Blit(HidPage, 0, HidPage.Get_Height()-1, 0, HidPage.Get_Height(), HidPage.Get_Width(), 1, false); +#endif //FIX_ME_LATER + if (HidPage.Lock()){ + + //Redraw_Icons(CELL_DRAW_ONLY); + + /* + ** Redraw the game objects layer by layer. The layer drawing occurs on the ground layer + ** first and then followed by all the layers in increasing altituded. + */ + for (LayerType layer = LAYER_GROUND; layer < LAYER_COUNT; layer++) { + for (int index = 0; index < Layer[layer].Count(); index++) { + Layer[layer][index]->Render(forced); + } + } + + /* + ** Finally, redraw the shadow overlay as necessary. + */ +//Colour_Debug(5); + Redraw_Shadow(); + } + + Redraw_Shadow_Rects(); + + HidPage.Unlock(); + +//Colour_Debug(8); + /* + ** Draw the rubber band over the top of it all. + */ + if (IsRubberBand) { + LogicPage->Draw_Rect(BandX+TacPixelX, BandY+TacPixelY, NewX+TacPixelX, NewY+TacPixelY, WHITE); + } + /* + ** Clear the redraw flags so that normal redraw flag setting can resume. + */ + CellRedraw.Reset(); +//Colour_Debug(0); + +#ifdef SCENARIO_EDITOR + /* + ** If we're placing an object (PendingObject is non-NULL), and that object + ** is NOT an icon, smudge, or overlay, draw it here. + ** Terrain, Buildings & Aircraft aren't drawn at the cell's center coord; + ** they're drawn at the upper left coord, so I have to AND the coord value + ** with 0xFF00FF00 to strip off the lepton coordinates, but leave the + ** cell coordinates. + */ + if (Debug_Map && PendingObjectPtr) { + PendingObjectPtr->Coord = PendingObjectPtr->Class_Of().Coord_Fixup(Cell_Coord(ZoneCell + ZoneOffset)); + PendingObjectPtr->Render(true); + } +#endif + } +} + + +/*********************************************************************************************** + * DisplayClass::Redraw_Icons -- Draws all terrain icons necessary. * + * * + * This routine will redraw all of the terrain icons that are flagged * + * to be redrawn. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none. * + * * + * HISTORY: * + * 02/14/1994 JLB : Created. * + * 05/01/1994 JLB : Converted to member function. * + * 06/20/1994 JLB : Uses cell drawing support function. * + * 12/06/1994 JLB : Scans tactical view in separate row/colum loops * + * 12/24/1994 JLB : Uses the cell bit flag array to determine what to redraw. * + *=============================================================================================*/ +void DisplayClass::Redraw_Icons(int draw_flags) +{ + IsShadowPresent = false; + for (int y = -Coord_YLepton(TacticalCoord); y <= TacLeptonHeight; y += CELL_LEPTON_H) { + for (int x = -Coord_XLepton(TacticalCoord); x <= TacLeptonWidth; x += CELL_LEPTON_W) { + COORDINATE coord = Coord_Add(TacticalCoord, XY_Coord(x, y)); + CELL cell = Coord_Cell(coord); + coord = Cell_Coord(cell) & 0xFF00FF00L; + + /* + ** Only cells flagged to be redraw are examined. + */ + if (In_View(cell) && Is_Cell_Flagged(cell)) { + int xpixel; + int ypixel; + + if (Coord_To_Pixel(coord, xpixel, ypixel)) { + CellClass * cellptr = &(*this)[Coord_Cell(coord)]; + + /* + ** If there is a portion of the underlying icon that could be visible, + ** then draw it. Also draw the cell if the shroud is off. + */ + if (cellptr->IsVisible || Debug_Unshroud) { + cellptr->Draw_It(xpixel, ypixel, draw_flags); + } + + /* + ** If any cell is not fully mapped, then flag it so that the shadow drawing + ** process will occur. Only draw the shadow if Debug_Unshroud is false. + */ + if (!cellptr->IsMapped && !Debug_Unshroud) { + IsShadowPresent = true; + } + } + } + } + } +} + + +/*********************************************************************************************** + * DisplayClass::Redraw_Shadow -- Draw the shadow overlay. * + * * + * This routine is called after all other tactical map rendering takes place. It draws * + * the shadow map over the tactical map. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/01/1995 JLB : Created. * + * 08/06/1995 JLB : Clips the fill rect if necessary. * + *=============================================================================================*/ +void DisplayClass::Redraw_Shadow(void) +{ + if (IsShadowPresent) { + for (int y = -Coord_YLepton(TacticalCoord); y <= TacLeptonHeight; y += CELL_LEPTON_H) { + for (int x = -Coord_XLepton(TacticalCoord); x <= TacLeptonWidth; x += CELL_LEPTON_W) { + COORDINATE coord = Coord_Add(TacticalCoord, XY_Coord(x, y)); + CELL cell = Coord_Cell(coord); + coord = Cell_Coord(cell) & 0xFF00FF00; + + /* + ** Only cells flagged to be redraw are examined. + */ + if (In_View(cell) && Is_Cell_Flagged(cell)) { + int xpixel; + int ypixel; + + if (Coord_To_Pixel(coord, xpixel, ypixel)) { + CellClass * cellptr = &(*this)[Coord_Cell(coord)]; + + if (!cellptr->IsMapped) { + if (cellptr->IsVisible) { + int shadow = Cell_Shadow(cell); + if (shadow >= 0) { + CC_Draw_Shape(ShadowShapes, shadow, xpixel, ypixel, WINDOW_TACTICAL, SHAPE_GHOST, NULL, ShadowTrans); + } + } + } + } + } + } + } + } +} + +/*********************************************************************************************** + * DisplayClass::Redraw_Shadow -- Draw the shadow overlay. * + * * + * This routine is called after all other tactical map rendering takes place. It draws * + * the shadow map over the tactical map. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/01/1995 JLB : Created. * + * 08/06/1995 JLB : Clips the fill rect if necessary. * + *=============================================================================================*/ +void DisplayClass::Redraw_Shadow_Rects(void) +{ + if (IsShadowPresent) { + for (int y = -Coord_YLepton(TacticalCoord); y <= TacLeptonHeight; y += CELL_LEPTON_H) { + for (int x = -Coord_XLepton(TacticalCoord); x <= TacLeptonWidth; x += CELL_LEPTON_W) { + COORDINATE coord = Coord_Add(TacticalCoord, XY_Coord(x, y)); + CELL cell = Coord_Cell(coord); + coord = Cell_Coord(cell) & 0xFF00FF00; + + /* + ** Only cells flagged to be redraw are examined. + */ + if (In_View(cell) && Is_Cell_Flagged(cell)) { + int xpixel; + int ypixel; + + if (Coord_To_Pixel(coord, xpixel, ypixel)) { + CellClass * cellptr = &(*this)[Coord_Cell(coord)]; + + if (!cellptr->IsMapped) { + if (!cellptr->IsVisible) { + int ww = CELL_PIXEL_W; + int hh = CELL_PIXEL_H; + + if (Clip_Rect(&xpixel, &ypixel, &ww, &hh, Lepton_To_Pixel(TacLeptonWidth), Lepton_To_Pixel(TacLeptonHeight)) >= 0) { + LogicPage->Fill_Rect(TacPixelX+xpixel, TacPixelY+ypixel, TacPixelX+xpixel+ww-1, TacPixelY+ypixel+hh-1, BLACK); + } + } + } + } + } + } + } + } +} + +/*********************************************************************************************** + * DisplayClass::Next_Object -- Searches for next object on display. * + * * + * This utility routine is used to find the "next" object from the object specified. This * + * is typically used when is pressed and the current object shifts. * + * * + * INPUT: object -- The current object to base the "next" calculation off of. * + * * + * OUTPUT: Returns with a pointer to the next object. If there is no objects available, * + * then NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/20/1994 JLB : Created. * + *=============================================================================================*/ +ObjectClass * DisplayClass::Next_Object(ObjectClass * object) +{ + ObjectClass * firstobj = 0; + bool foundmatch = false; + + if (!object) { + foundmatch = true; + } + for (unsigned uindex = 0; uindex < Layer[LAYER_GROUND].Count(); uindex++) { + ObjectClass * obj = Layer[LAYER_GROUND][uindex]; + + /* + ** Verify that the object can be selected by and is owned by the player. + */ + if (obj && obj->Is_Techno() && ((TechnoClass *)obj)->IsDiscoveredByPlayer && obj->Class_Of().IsSelectable && obj->Owner() == PlayerPtr->Class->House) { + if (!firstobj) firstobj = obj; + if (foundmatch) return(obj); + if (object == obj) foundmatch = true; + } + } + return(firstobj); +} + + +/*********************************************************************************************** + * DisplayClass::Prev_Object -- Searches for the previous object on the map. * + * * + * This routine will search for the previous object. Previous is defined as the one listed * + * before the specified object in the ground layer. If there is no specified object, then * + * the last object in the ground layer is returned. * + * * + * INPUT: object -- Pointer to the object that "previous" is to be defined from. * + * * + * OUTPUT: Returns with a pointer to the object previous to the specified one. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/24/1995 JLB : Created. * + *=============================================================================================*/ +ObjectClass * DisplayClass::Prev_Object(ObjectClass * object) +{ + ObjectClass * firstobj = 0; + bool foundmatch = false; + + if (!object) { + foundmatch = true; + } + for (int uindex = Layer[LAYER_GROUND].Count()-1; uindex >= 0; uindex--) { + ObjectClass * obj = Layer[LAYER_GROUND][uindex]; + + /* + ** Verify that the object can be selected by and is owned by the player. + */ + if (obj && obj->Is_Techno() && ((TechnoClass *)obj)->IsDiscoveredByPlayer && obj->Class_Of().IsSelectable && obj->Owner() == PlayerPtr->Class->House) { + if (!firstobj) firstobj = obj; + if (foundmatch) return(obj); + if (object == obj) foundmatch = true; + } + } + return(firstobj); +} + + +/*********************************************************************************************** + * DisplayClass::Pixel_To_Coord -- converts screen coord to COORDINATE * + * * + * INPUT: * + * x,y pixel coordinates to convert * + * * + * OUTPUT: * + * COORDINATE of pixel * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/09/1994 BR : Created. * + * 12/06/1994 JLB : Uses map dimension variables in display class. * + * 12/10/1994 JLB : Uses union to speed building coordinate value. * + *=============================================================================================*/ +COORDINATE DisplayClass::Pixel_To_Coord(int x, int y) +{ + /* + ** Normalize the pixel coorindates to be relative to the upper left corner + ** of the tactical map. The coordinates are expressed in leptons. + */ + x -= TacPixelX; + x = Pixel_To_Lepton(x); + y -= TacPixelY; + y = Pixel_To_Lepton(y); + + /* + ** If pixel coordinate is over the tactical map, then translate it into a coordinate + ** value. If not, then just return with NULL. + */ + if ((unsigned)x < TacLeptonWidth && (unsigned)y < TacLeptonHeight) { + return(Coord_Add(TacticalCoord, XY_Coord(x, y))); + } + return(0); +} + + +/*********************************************************************************************** + * DisplayClass::Calculated_Cell -- Fetch a map cell based on specified method. * + * * + * Find a cell meeting the specified requirements. This function is * + * used for scenario reinforcements. * + * * + * INPUT: dir -- Method of picking a map cell. * + * * + * house -- The house to base calculation on. * + * * + * OUTPUT: Returns with the calculated cell. If 0, then this indicates * + * that no legal cell was found. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + * 04/11/1994 JLB : Revamped. * + * 05/18/1994 JLB : Converted to member function. * + *=============================================================================================*/ +CELL DisplayClass::Calculated_Cell(SourceType dir, HousesType house) +{ + CELL cell = 0; // Working cell number. + + while (cell == 0) { + int x,y; + int index; + + /* + ** Select a candidate cell based on the desired method. + */ + switch (dir) { + + /* + ** Looks for the northern most straight path shipping lane and returns + ** the cell of one of the ends. + */ + case SOURCE_SHIPPING: + for (y = 0; y < MapCellHeight; y++) { + for (x = 0; x < MapCellWidth; x++) { + if ((*this)[XY_Cell(MapCellX+x, MapCellY+y)].Land_Type() != LAND_WATER) break; + } + if (x == MapCellWidth) { + return(XY_Cell(MapCellX+MapCellWidth, MapCellY+y)); + } + } + return(0); + + /* + ** Select a map edge. + */ + case SOURCE_NORTH: + index = Random_Pick(1, MapCellWidth); + for (x = 0; x < MapCellWidth; x++) { + cell = XY_Cell(MapCellX+((x+index)%MapCellWidth), MapCellY-1); + if ((*this)[cell].Is_Generally_Clear() && (*this)[cell+MAP_CELL_W].Is_Generally_Clear()) break; + } + if (x == MapCellWidth) return(0); + break; + + case SOURCE_EAST: + index = Random_Pick(1, MapCellHeight); + for (y = 0; y < MapCellHeight; y++) { + cell = XY_Cell(MapCellX+MapCellWidth, MapCellY+((y+index)%MapCellHeight)); + if ((*this)[cell].Is_Generally_Clear() && (*this)[cell-1].Is_Generally_Clear()) break; + } + if (y == MapCellHeight) return(0); + break; + + case SOURCE_SOUTH: + index = Random_Pick(1, MapCellWidth); + for (x = 0; x < MapCellWidth; x++) { + cell = XY_Cell(MapCellX+((x+index)%MapCellWidth), MapCellY+MapCellHeight); + if ((*this)[cell].Is_Generally_Clear() && (*this)[cell-MAP_CELL_W].Is_Generally_Clear()) break; + } + if (x == MapCellWidth) return(0); + break; + + case SOURCE_WEST: + index = Random_Pick(1, MapCellHeight); + for (y = 0; y < MapCellHeight; y++) { + cell = XY_Cell(MapCellX-1, MapCellY+((y+index)%MapCellHeight)); + if ((*this)[cell].Is_Generally_Clear() && (*this)[cell+1].Is_Generally_Clear()) break; + } + if (y == MapCellHeight) return(0); + break; + + /* + ** Drop in at a random location. + */ + case SOURCE_AIR: + cell = Waypoint[WAYPT_REINF]; + if (cell < 1) { + cell = Coord_Cell(TacticalCoord); + return(cell); + } else { + if ((*this)[cell].Cell_Techno()) { + for (int radius = 1; radius < 7; radius++) { + CELL newcell = Coord_Cell(Coord_Scatter(Cell_Coord(cell), radius << 8, true)); + if (In_Radar(newcell) && !(*this)[newcell].Cell_Techno()) { + cell = newcell; + break; + } + } + } + } + break; + + /* + ** Dramatic entry point is somewhere on the visible screen as defined + ** by the current tactical map position. + */ + case SOURCE_VISIBLE: + cell = XY_Cell(Coord_XCell(TacticalCoord)+Random_Pick(0, Lepton_To_Cell(TacLeptonWidth)-1), Coord_YCell(TacticalCoord)+Random_Pick(0, Lepton_To_Cell(TacLeptonHeight)-1)); + if (house == PlayerPtr->Class->House && !In_Radar(cell)) { + cell = 0; + } + break; + + /* + ** Drop off near friendly base or near a friendly unit or + ** just randomly if all else fails. + */ + case SOURCE_ENEMYBASE: + case SOURCE_HOMEBASE: + return(0); + + /* + ** Find an unoccupied beach cell. + */ + case SOURCE_BEACH: { + CELL cells[MAP_CELL_W]; + CELL alternate[MAP_CELL_W]; + unsigned counter=0; + + for (x = 0; x < MapCellWidth; x++) { + CELL newcell = 0; + + if ((*this)[XY_Cell(x + MapCellX, MapCellHeight + MapCellY)].Land_Type() != LAND_WATER) continue; + if ((*this)[XY_Cell(x + MapCellX, MapCellHeight + MapCellY-1)].Land_Type() != LAND_WATER) continue; + for (y = MapCellHeight; y >= 0; y--) { + newcell = XY_Cell(x + MapCellX, y + MapCellY); + if ((*this)[newcell].Cell_Techno()) { + break; + } + if ((*this)[newcell].Land_Type() != LAND_WATER) { + break; + } + } + LandType land = (*this)[newcell].Land_Type(); + if (( land == LAND_BEACH || land == LAND_CLEAR || land == LAND_ROAD) && + !(*this)[newcell].Cell_Techno() && + !(*this)[newcell].Cell_Terrain() && + !(*this)[newcell-MAP_CELL_W].Cell_Techno() && + !(*this)[newcell-MAP_CELL_W].Cell_Terrain() && + !(*this)[newcell-(MAP_CELL_W*2)].Cell_Terrain() && + !(*this)[newcell-(MAP_CELL_W*2)].Cell_Techno()) { + + cells[counter++] = newcell; + if (counter >= (sizeof(cells) / sizeof(cells[0]))) { + break; + } + } + } + + /* + ** Fixup entry list so that it doesn't come close to blocking terrain or other + ** units. + */ + int counter2 = 0; + for (int index = 1; index < counter-1; index++) { + if (Cell_X(cells[index-1])+1 == Cell_X(cells[index]) && Cell_X(cells[index+1])-1 == Cell_X(cells[index])) { + alternate[counter2++] = cells[index]; + } + } + + CELL cell = 0; + if (counter2) { + if (counter2 < 4) { + cell = alternate[counter2-1]; + } else { + cell = alternate[counter2 - (counter2 / 4)]; + } + } else { + if (counter) { + if (counter < 4) { + cell = cells[counter-1]; + } else { + cell = cells[counter - (counter / 4)]; + } + } + } + if (cell) { + if (Map.Theater == THEATER_DESERT) { + cell += MAP_CELL_W; + } + } + return(cell); + } + + case SOURCE_OCEAN: + cell = XY_Cell(Random_Pick(0, MapCellWidth-2)+MapCellX, MapCellHeight+MapCellY); + break; + + default: + return(0); + } + + /* + ** The selected edge cell must be unoccupied and if this is for + ** the player, then it must be on an accessible map cell. + */ + cell &= 0x0FFF; + if (cell && (*this)[cell].Cell_Techno()) { + cell = 0; + } + } + return(cell); +} + + +/*********************************************************************************************** + * DisplayClass::Select_These -- All selectable objects in region are selected. * + * * + * Use this routine to simultaneously select all objects within the coordinate region * + * specified. This routine is used by the multi-select rubber band handler. * + * * + * INPUT: coord1 -- Coordinate of one corner of the selection region. * + * * + * coord2 -- The opposite corner of the selection region. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + * 04/25/1995 JLB : Limited to non-building type. * + *=============================================================================================*/ +void DisplayClass::Select_These(COORDINATE coord1, COORDINATE coord2) +{ + COORDINATE tcoord = TacticalCoord; //Cell_Coord(TacticalCell) & 0xFF00FF00L; + + coord1 = Coord_Add(tcoord, coord1); + coord2 = Coord_Add(tcoord, coord2); + int x1 = Coord_X(coord1); + int x2 = Coord_X(coord2); + int y1 = Coord_Y(coord1); + int y2 = Coord_Y(coord2); + + /* + ** Ensure that coordinate number one represents the upper left corner + ** and coordinate number two represents the lower right corner. + */ + if (x1 > x2) { + int temp = x1; + x1 = x2; + x2 = temp; + } + if (y1 > y2) { + int temp = y1; + y1 = y2; + y2 = temp; + } + + /* + ** Sweep through all ground layer objects and select the ones within the + ** bounding box. + */ + Unselect_All(); + AllowVoice = true; + for (int index = 0; index < Layer[LAYER_GROUND].Count(); index++) { + ObjectClass * obj = Layer[LAYER_GROUND][index]; + COORDINATE ocoord = obj->Center_Coord(); + int x = Coord_X(ocoord); + int y = Coord_Y(ocoord); + + /* + ** Only try to select objects that are owned by the player, are allowed to be + ** selected, and are within the bouding box. + */ + if ( obj->Owner() == PlayerPtr->Class->House && + obj->Class_Of().IsSelectable && + obj->What_Am_I() != RTTI_BUILDING && + x >= x1 && x <= x2 && y >= y1 && y <= y2) { + + if (obj->Select()) { + AllowVoice = false; + } + } + } + AllowVoice = true; +} + + +/*********************************************************************************************** + * DisplayClass::Refresh_Band -- Causes all cells under the rubber band to be redrawn. * + * * + * Use this routine to flag all cells that are covered in some fashion by the multi-unit * + * select "rubber band" to be redrawn. This is necessary whenever the rubber band changes * + * size or is being removed. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void DisplayClass::Refresh_Band(void) +{ + if (IsRubberBand) { + + /* + ** In rubber band mode, mark all cells under the "rubber band" to be + ** redrawn. + */ + int x1 = BandX+TacPixelX; + int y1 = BandY+TacPixelY; + int x2 = NewX+TacPixelX; + int y2 = NewY+TacPixelY; + + if (x1 > x2) { + int temp = x1; + x1 = x2; + x2 = temp; + } + if (y1 > y2) { + int temp = y1; + y1 = y2; + y2 = temp; + } + + CELL cell; + for (int y = y1; y <= y2+CELL_PIXEL_H; y += CELL_PIXEL_H) { + cell = Click_Cell_Calc(x1, Bound(y, 0, TacPixelY+Lepton_To_Pixel(TacLeptonHeight))); + if (cell != -1) (*this)[cell].Redraw_Objects(); + + cell = Click_Cell_Calc(x2, Bound(y, 0, TacPixelY+Lepton_To_Pixel(TacLeptonHeight))); + if (cell != -1) (*this)[cell].Redraw_Objects(); + } + + for (int x = x1; x <= x2+CELL_PIXEL_W; x += CELL_PIXEL_W) { + cell = Click_Cell_Calc(Bound(x, 0, TacPixelX+Lepton_To_Pixel(TacLeptonWidth)), y1); + if (cell != -1) (*this)[cell].Redraw_Objects(); + + cell = Click_Cell_Calc(Bound(x, 0, TacPixelX+Lepton_To_Pixel(TacLeptonWidth)), y2); + if (cell != -1) (*this)[cell].Redraw_Objects(); + } + } +} + + +/*********************************************************************************************** + * DisplayClass::TacticalClass::Action -- Processes input for the tactical map. * + * * + * This routine handles the input directed at the tactical map. Since input, in this * + * regard, includes even the presence of the mouse over the tactical map, this routine * + * is called nearly every game frame. It handles adjusting the mouse shape as well as * + * giving orders to units. * + * * + * INPUT: flags -- The gadget event flags that triggered the call to this function. * + * * + * key -- A reference to the keyboard event (if any). * + * * + * OUTPUT: bool; Should processing be aborted on any succeeding buttons in the chain? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/17/1995 JLB : Created. * + *=============================================================================================*/ +int DisplayClass::TacticalClass::Action(unsigned flags, KeyNumType & key) +{ + int x,y; // Sub cell pixel coordinates. + bool shadow; + ObjectClass *object = 0; + ActionType action = ACTION_NONE; // Action possible with currently selected object. + + /* + ** Set some working variables that depend on the mouse position. For the press + ** or release event, special mouse queuing storage variables are used. Other + ** events must use the current mouse position globals. + */ + bool edge = false; + if (flags & (LEFTPRESS|LEFTRELEASE|RIGHTPRESS|RIGHTRELEASE)) { + x = _Kbd->MouseQX; + y = _Kbd->MouseQY; + } else { + x = Get_Mouse_X(); + y = Get_Mouse_Y(); + + if (x == 0 || y == 199 || x == 319) edge = true; + } + COORDINATE coord = Map.Pixel_To_Coord(x, y); + CELL cell = Coord_Cell(coord); +// CELL cell = Map.Click_Cell_Calc(x, y); + if (coord) { + shadow = (!Map[cell].IsVisible && !Debug_Unshroud); + x -= Map.TacPixelX; + y -= Map.TacPixelY; + + /* + ** Cause any displayed cursor to move along with the mouse cursor. + */ + if (cell != Map.ZoneCell) { + Map.Set_Cursor_Pos(cell); + } + + /* + ** Determine the object that the mouse is currently over. + */ + if (!shadow) { +// int xxx = x + Lepton_To_Pixel(Coord_XLepton(Map.TacticalCoord)); +// int yyy = y + Lepton_To_Pixel(Coord_YLepton(Map.TacticalCoord)); +// object = Map.Cell_Object(cell, xxx % CELL_PIXEL_W, yyy % CELL_PIXEL_H); + object = Map.Close_Object(coord); + + /* + ** Special case check to ignore cloaked object if not owned by the player. + */ +// if (object && object->Is_Techno() && !((TechnoClass *)object)->IsOwnedByPlayer && ((TechnoClass *)object)->Cloak == CLOAKED) { +// object = NULL; +// } + } + + /* + ** If there is a currently selected object, then the action to perform if + ** the left mouse button were clicked must be determined. + */ + if (CurrentObject.Count()) { + if (object) { + action = CurrentObject[0]->What_Action(object); + } else { + action = CurrentObject[0]->What_Action(cell); + } + } else { + if (object && object->Class_Of().IsSelectable) { + action = ACTION_SELECT; + } + + if (Map.IsRepairMode) { + if (object && object->Owner() == PlayerPtr->Class->House && object->Can_Repair()) { + action = ACTION_REPAIR; + } else { + action = ACTION_NO_REPAIR; + } + } + + if (Map.IsSellMode) { + if (object && object->Owner() == PlayerPtr->Class->House && object->Can_Demolish()) { + if (object->What_Am_I() == RTTI_BUILDING) { + action = ACTION_SELL; + } else { + action = ACTION_SELL_UNIT; + } + } else { + + /* + ** Check to see if the cursor is over an owned wall. + */ + if (Map[cell].Overlay != OVERLAY_NONE && + OverlayTypeClass::As_Reference(Map[cell].Overlay).IsWall && + Map[cell].Owner == PlayerPtr->Class->House) { + action = ACTION_SELL; + } else { + action = ACTION_NO_SELL; + } + } + } + + if (Map.IsTargettingMode == SPC_ION_CANNON) { + action = ACTION_ION; + } + + if (Map.IsTargettingMode == SPC_NUCLEAR_BOMB) { + action = ACTION_NUKE_BOMB; + } + + if (Map.IsTargettingMode == SPC_AIR_STRIKE) { + action = ACTION_AIR_STRIKE; + } + + if (Map.PendingObject) { + action = ACTION_NONE; + } + } + + /* + ** Move any cursor displayed. + */ + if (cell != Map.ZoneCell) { + Map.Set_Cursor_Pos(cell); + } + + /* + ** A right mouse button press cancels the current action or selection. + */ + if (flags & RIGHTPRESS) { + Map.Mouse_Right_Press(); + } + + /* + ** Make sure that if the mouse button has been released and the map doesn't know about it, + ** then it must be informed. Do this by faking a mouse release event. + */ + if ((flags & LEFTUP) && Map.IsRubberBand) { + flags |= LEFTRELEASE; + } + + /* + ** When the mouse buttons aren't pressed, only the mouse cursor shape is processed. + ** The shape changes depending on what object the mouse is currently over and what + ** object is currently selected. + */ + if (!edge) { + if (flags & LEFTUP) { + Map.Mouse_Left_Up(shadow, object, action); + } + } + + /* + ** Normal actions occur when the mouse button is released. The press event is + ** intercepted and possible rubber-band mode is flagged. + */ + if (flags & LEFTRELEASE) { + Map.Mouse_Left_Release(cell, x, y, object, action); + } + + /* + ** When the mouse is first pressed on the map, then record the mouse + ** position so that a proper check before going into rubber band + ** mode can be made. Rubber band mode starts when the mouse is + ** held down and moved a certain minimum distance. + */ + if (!edge && (flags & LEFTPRESS)) { + Map.Mouse_Left_Press(x, y); + } + + /* + ** While the mouse is being held down, determine if rubber band mode should + ** start. If rubber band mode is already active, then update the size + ** and flag the map to redraw it. + */ + if (flags & LEFTHELD) { + Map.Mouse_Left_Held(x, y); + } + } + + return(GadgetClass::Action(0, key)); +} + + +/*********************************************************************************************** + * DisplayClass::Mouse_Right_Press -- Handles the right mouse button press. * + * * + * This routine is called when the right mouse button is pressed. This action is supposed * + * to cancel whatever mode or process is active. If there is nothing to cancel, then it * + * will default to unselecting any units that might be currently selected. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/24/1995 JLB : Created. * + *=============================================================================================*/ +void DisplayClass::Mouse_Right_Press(void) +{ + if (PendingObjectPtr && PendingObjectPtr->Is_Techno()) { + //PendingObjectPtr->Transmit_Message(RADIO_OVER_OUT); + PendingObjectPtr = 0; + PendingObject = 0; + PendingHouse = HOUSE_NONE; + Set_Cursor_Shape(0); + } else { + if (IsRepairMode) { + IsRepairMode = false; + } else { + if (IsSellMode) { + IsSellMode = false; + } else { + if (IsTargettingMode) { + IsTargettingMode = false; + } else { + Unselect_All(); + } + } + } + } + Set_Default_Mouse(MOUSE_NORMAL, false); +} + + +/*********************************************************************************************** + * DisplayClass::Mouse_Left_Up -- Handles the left mouse "cruising" over the map. * + * * + * This routine is called continuously while the mouse is over the tactical map but there * + * are no mouse buttons pressed. Typically, this adjusts the mouse shape and the pop-up * + * help text. * + * * + * INPUT: shadow -- Is the mouse hovering over shadowed terrain? * + * * + * object -- Pointer to the object that the mouse is currently over (may be NULL). * + * * + * action -- This is the action that the currently selected object (if any) will * + * perform if the left mouse button were clicked at this location. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/24/1995 JLB : Created. * + * 07/05/1995 JLB : Removed pop up help text for shadow and terrain after #3. * + *=============================================================================================*/ +void DisplayClass::Mouse_Left_Up(bool shadow, ObjectClass * object, ActionType action, bool wwsmall) +{ + IsTentative = false; + + /* + ** Don't allow selection of an object that is located in shadowed terrain. + ** In fact, just show the normal move cursor in order to keep the shadowed + ** terrain a mystery. + */ + if (shadow) { + switch (action) { + case ACTION_GUARD_AREA: + Set_Default_Mouse(MOUSE_AREA_GUARD, wwsmall); + break; + + case ACTION_NONE: + Set_Default_Mouse(MOUSE_NORMAL, wwsmall); + break; + + case ACTION_NO_SELL: + case ACTION_SELL: + case ACTION_SELL_UNIT: + Set_Default_Mouse(MOUSE_NO_SELL_BACK, wwsmall); + break; + + case ACTION_NO_REPAIR: + case ACTION_REPAIR: + Set_Default_Mouse(MOUSE_NO_REPAIR, wwsmall); + break; + + case ACTION_ION: + Set_Default_Mouse(MOUSE_ION_CANNON, wwsmall); + break; + + case ACTION_NUKE_BOMB: + Set_Default_Mouse(MOUSE_NUCLEAR_BOMB, wwsmall); + break; + + case ACTION_AIR_STRIKE: + Set_Default_Mouse(MOUSE_AIR_STRIKE, wwsmall); + break; + + case ACTION_NOMOVE: + if (CurrentObject.Count() && CurrentObject[0]->What_Am_I() == RTTI_AIRCRAFT) { + Set_Default_Mouse(MOUSE_NO_MOVE, wwsmall); + break; + } + // Fall into next case for non aircraft object types. + + default: + Set_Default_Mouse(MOUSE_CAN_MOVE, wwsmall); + break; + + } + } else { + + /* + ** Change the mouse shape according to the default action that will occur + ** if the mouse button were clicked at this location. + */ + switch (action) { + case ACTION_TOGGLE_SELECT: + case ACTION_SELECT: + Set_Default_Mouse(MOUSE_CAN_SELECT, wwsmall); + break; + + case ACTION_MOVE: + Set_Default_Mouse(MOUSE_CAN_MOVE, wwsmall); + break; + + case ACTION_GUARD_AREA: + Set_Default_Mouse(MOUSE_AREA_GUARD, wwsmall); + break; + + case ACTION_HARVEST: + case ACTION_ATTACK: + Set_Default_Mouse(MOUSE_CAN_ATTACK, wwsmall); + break; + + case ACTION_SABOTAGE: + Set_Default_Mouse(MOUSE_DEMOLITIONS, wwsmall); + break; + + case ACTION_ENTER: + case ACTION_CAPTURE: + Set_Default_Mouse(MOUSE_ENTER, wwsmall); + break; + + case ACTION_NOMOVE: + Set_Default_Mouse(MOUSE_NO_MOVE, wwsmall); + break; + + case ACTION_NO_SELL: + Set_Default_Mouse(MOUSE_NO_SELL_BACK, wwsmall); + break; + + case ACTION_NO_REPAIR: + Set_Default_Mouse(MOUSE_NO_REPAIR, wwsmall); + break; + + case ACTION_SELF: + Set_Default_Mouse(MOUSE_DEPLOY, wwsmall); + break; + + case ACTION_REPAIR: + Set_Default_Mouse(MOUSE_REPAIR, wwsmall); + break; + + case ACTION_SELL_UNIT: + Set_Default_Mouse(MOUSE_SELL_UNIT, wwsmall); + break; + + case ACTION_SELL: + Set_Default_Mouse(MOUSE_SELL_BACK, wwsmall); + break; + + case ACTION_ION: + Set_Default_Mouse(MOUSE_ION_CANNON, wwsmall); + break; + + case ACTION_NUKE_BOMB: + Set_Default_Mouse(MOUSE_NUCLEAR_BOMB, wwsmall); + break; + + case ACTION_AIR_STRIKE: + Set_Default_Mouse(MOUSE_AIR_STRIKE, wwsmall); + break; + + default: + Set_Default_Mouse(MOUSE_NORMAL, wwsmall); + break; + } + } + + /* + ** Give a generic help message when over shadow terrain. + */ + if (shadow) { + if (Scenario < 4) { + Help_Text(TXT_SHADOW); + } else { + Help_Text(TXT_NONE); + } + } else { + + /* + ** If the mouse is held over objects on the map, then help text may + ** pop up that tells what the object is. This call informs the help + ** system of the text name for the object under the mouse. + */ + if (object) { + int text; + int color = LTGREY; + + /* + ** Fetch the appropriate background color for help text. + */ + if (PlayerPtr->Is_Ally(object)) { + color = CC_GREEN; + } else { + if (object->Owner() == HOUSE_NONE || object->Owner() == HOUSE_NEUTRAL) { + color = LTGREY; + } else { + color = PINK; + } + } + + /* + ** Fetch the name of the object. If it is an enemy object, then + ** the exact identity is glossed over with a generic text. + */ + text = object->Full_Name(); + if (object->Is_Techno() && !((TechnoTypeClass const &)object->Class_Of()).IsNominal) { + + if (!PlayerPtr->Is_Ally(object)) { + switch (object->What_Am_I()) { + case RTTI_INFANTRY: + text = TXT_ENEMY_SOLDIER; + break; + + case RTTI_UNIT: + text = TXT_ENEMY_VEHICLE; + break; + + case RTTI_BUILDING: + if ( *((BuildingClass*)object) != STRUCT_MISSION) { + text = TXT_ENEMY_STRUCTURE; + } + break; + } + } + } + + if (Scenario > 3 || object->What_Am_I() != RTTI_TERRAIN) { + Help_Text(text, -1, -1, color); + } else { + Help_Text(TXT_NONE); + } + } else { + Help_Text(TXT_NONE); + } + } +} + + +/*********************************************************************************************** + * DisplayClass::Mouse_Left_Release -- Handles the left mouse button release. * + * * + * This routine is called when the left mouse button is released over the tactical map. * + * The release event is the workhorse of the game. Most actions occur at the moment of * + * mouse release. * + * * + * INPUT: cell -- The cell that the mouse is over. * + * * + * x,y -- The mouse pixel coordinate. * + * * + * object -- Pointer to the object that the mouse is over. * + * * + * action -- The action that the currently selected object (if any) will * + * perform. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/24/1995 JLB : Created. * + * 03/27/1995 JLB : Handles sell and repair actions. * + *=============================================================================================*/ +void DisplayClass::Mouse_Left_Release(CELL cell, int x, int y, ObjectClass * object, ActionType action, bool wwsmall) +{ + if (PendingObjectPtr) { + + /* + ** Try to place the pending object onto the map. + */ + if (ProximityCheck) { + OutList.Add(EventClass(EventClass::PLACE, PendingObjectPtr->What_Am_I(), cell + ZoneOffset)); + } else { + Speak(VOX_DEPLOY); + } + + } else { + + if (IsRubberBand) { + Refresh_Band(); + Select_These(XYPixel_Coord(BandX, BandY), XYPixel_Coord(x, y)); + + Set_Default_Mouse(MOUSE_NORMAL, wwsmall); +#ifdef NEVER + if (CurrentObject.Count()) { + if (CurrentObject[0]->Can_Player_Fire()) { + Set_Default_Mouse(MOUSE_CAN_ATTACK, wwsmall); + } else { + Set_Default_Mouse(MOUSE_NORMAL, wwsmall); + } + } else { + Set_Default_Mouse(MOUSE_NORMAL, wwsmall); + } +#endif + + IsRubberBand = false; + IsTentative = false; + Map.DisplayClass::IsToRedraw = true; + Map.Flag_To_Redraw(false); + + } else { + + /* + ** Toggle the select state of the object. + */ + if (action == ACTION_TOGGLE_SELECT) { + if (!object || !CurrentObject.Count() || CurrentObject[0]->Owner() != PlayerPtr->Class->House) { + action = ACTION_SELECT; + } else { + if (object->IsSelected) { + object->Unselect(); + } else { + object->Select(); + } + } + } + + /* + ** Selection of other object action. + */ + if (action == ACTION_SELECT || (action == ACTION_NONE && object && object->Class_Of().IsSelectable && !object->IsSelected)) { + Unselect_All(); + object->Select(); + Set_Default_Mouse(MOUSE_NORMAL, wwsmall); +#ifdef NEVER + if (object->Can_Player_Fire()) { + Set_Default_Mouse(MOUSE_CAN_ATTACK, wwsmall); + } else { + Set_Default_Mouse(MOUSE_NORMAL, wwsmall); + } +#endif + } + + /* + ** If an action was detected as possible, then pass this action event + ** to all selected objects. + */ + if (action != ACTION_NONE && action != ACTION_SELECT) { + + /* + ** Pass the action to all the selected objects. But first, redetermine + ** what action that object should perform. This, seemingly redundant + ** process, is necessary since multiple objects could be selected and each + ** might perform a different action when the click occurs. + */ + bool doflash = true; + AllowVoice = true; + for (int index = 0; index < CurrentObject.Count(); index++) { + ObjectClass * tobject = CurrentObject[index]; + if (object) { + tobject->Active_Click_With(tobject->What_Action(object), object); + } else { + tobject->Active_Click_With(tobject->What_Action(cell), cell); + } + AllowVoice = false; + } + AllowVoice = true; + + if (action == ACTION_REPAIR && object->What_Am_I() == RTTI_BUILDING) { + OutList.Add(EventClass(EventClass::REPAIR, object->As_Target())); + } + if (action == ACTION_SELL_UNIT && object) { + switch (object->What_Am_I()) { + case RTTI_AIRCRAFT: + case RTTI_UNIT: + OutList.Add(EventClass(EventClass::SELL, object->As_Target())); + break; + + default: + break; + } + + } + if (action == ACTION_SELL) { + if (object) { + OutList.Add(EventClass(EventClass::SELL, object->As_Target())); + } else { + OutList.Add(EventClass(EventClass::SELL, ::As_Target(cell))); + } + } + if (action == ACTION_ION) { + OutList.Add(EventClass(EventClass::SPECIAL_PLACE, SPC_ION_CANNON, cell)); + } + if (action == ACTION_NUKE_BOMB) { + OutList.Add(EventClass(EventClass::SPECIAL_PLACE, SPC_NUCLEAR_BOMB, cell)); + } + if (action == ACTION_AIR_STRIKE) { + OutList.Add(EventClass(EventClass::SPECIAL_PLACE, SPC_AIR_STRIKE, cell)); + } + } + + IsTentative = false; + } + } +} + + +/*********************************************************************************************** + * DisplayClass::Mouse_Left_Press -- Handles the left mouse button press. * + * * + * Handle the left mouse button press while over the tactical map. If it isn't is * + * repair or sell mode, then a tentative transition to rubber band mode is flagged. If the * + * mouse moves a sufficient distance from this recorded position, then rubber band mode * + * is officially started. * + * * + * INPUT: x,y -- The mouse coordinates at the time of the press. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/24/1995 JLB : Created. * + *=============================================================================================*/ +void DisplayClass::Mouse_Left_Press(int x, int y) +{ + if (!IsRepairMode && !IsSellMode && !IsTargettingMode && !PendingObject) { + IsTentative = true; + BandX = x; + BandY = y; + NewX = x; + NewY = y; + } +} + + +/*********************************************************************************************** + * DisplayClass::Mouse_Left_Held -- Handles the left button held down. * + * * + * This routine is called continuously while the left mouse button is held down over * + * the tactical map. This handles the rubber band mode detection and dragging. * + * * + * INPUT: x,y -- The mouse coordinate. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/24/1995 JLB : Created. * + *=============================================================================================*/ +void DisplayClass::Mouse_Left_Held(int x, int y) +{ + if (IsRubberBand) { + if (x != NewX || y != NewY) { + x = Bound(x, 0, Lepton_To_Pixel(TacLeptonWidth)-1); + y = Bound(y, 0, Lepton_To_Pixel(TacLeptonHeight)-1); + Refresh_Band(); + NewX = x; + NewY = y; + IsToRedraw = true; + Flag_To_Redraw(false); + } + } else { + + /* + ** If the mouse is still held down while a tentative extended select is possible, then + ** check to see if the mouse has moved a sufficient distance in order to activate + ** extended select mode. + */ + if (IsTentative) { + + /* + ** The mouse must have moved a minimum distance before rubber band mode can be + ** initiated. + */ + if (ABS(x - BandX) > 4 || ABS(y - BandY) > 4) { + IsRubberBand = true; + x = Bound(x, 0, Lepton_To_Pixel(TacLeptonWidth)-1); + y = Bound(y, 0, Lepton_To_Pixel(TacLeptonHeight)-1); + NewX = x; + NewY = y; + IsToRedraw = true; + Flag_To_Redraw(false); + } + } + } +} + + +/*********************************************************************************************** + * DisplayClass::Set_Tactical_Position -- Sets the tactical view position. * + * * + * This routine is used to set the tactical view position. The requested position is * + * clipped to the map dimensions as necessary. * + * * + * INPUT: coord -- The coordinate desired for the upper left corner. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +void DisplayClass::Set_Tactical_Position(COORDINATE coord) +{ + /* + ** Bound the desired location to fit the legal map edges. + */ + int xx = Coord_X(coord) - Cell_To_Lepton(MapCellX); + int yy = Coord_Y(coord) - Cell_To_Lepton(MapCellY); + + Confine_Rect(&xx, &yy, TacLeptonWidth, TacLeptonHeight, Cell_To_Lepton(MapCellWidth), Cell_To_Lepton(MapCellHeight)); + coord = XY_Coord(xx + Cell_To_Lepton(MapCellX), yy + Cell_To_Lepton(MapCellY)); + + if (ScenarioInit) { + TacticalCoord = coord; + } + DesiredTacticalCoord = coord; + IsToRedraw = true; + Flag_To_Redraw(false); +} + + +/*********************************************************************************************** + * DisplayClass::Compute_Start_Pos -- Computes player's start pos from unit coords. * + * * + * Use this function in multiplayer games, to compute the scenario starting Tactical Pos. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/28/1995 JLB : Commented. * + * 06/26/1995 JLB : Fixed building loop. * + *=============================================================================================*/ +void DisplayClass::Compute_Start_Pos(void) +{ + /* + ** Find the summation cell-x & cell-y for all the player's units, infantry, + ** and buildings. Buildings are weighted so that they count 16 times more + ** than units or infantry. + */ + long x = 0; + long y = 0; + long num = 0; + for (int i = 0; i < Infantry.Count(); i++) { + InfantryClass * infp = Infantry.Ptr(i); + if (!infp->IsInLimbo && infp->House == PlayerPtr) { + x += (long)Coord_XCell (infp->Coord); + y += (long)Coord_YCell (infp->Coord); + num++; + } + } + + for (i = 0; i < Units.Count(); i++) { + UnitClass * unitp = Units.Ptr(i); + if (!unitp->IsInLimbo && unitp->House == PlayerPtr) { + x += (long)Coord_XCell (unitp->Coord); + y += (long)Coord_YCell (unitp->Coord); + num++; + } + } + + for (i = 0; i < Buildings.Count(); i++) { + BuildingClass * bldgp = Buildings.Ptr(i); + if (!bldgp->IsInLimbo && bldgp->House == PlayerPtr) { + x += (((long)Coord_XCell (bldgp->Coord)) << 4); + y += (((long)Coord_YCell (bldgp->Coord)) << 4); + num += 16; + } + } + + /* + ** Divide each coord by 'num' to compute the average value + */ + if (num > 0) { + x /= num; + } else { + x = 0; + } + + if (num > 0) { + y /= num; + } else { + y = 0; + } + + /* + ** Since the TacticalCell (starting cell) represents the screen's upper-left + ** corner, adjust for that. + */ + x -= (Lepton_To_Cell(TacLeptonWidth) / 2); + y -= (Lepton_To_Cell(TacLeptonHeight) / 2); + + /* + ** Clip the computed x,y cell coords to the map's size. + */ + if (x < MapCellX) { + x = MapCellX; + } + if (x + Lepton_To_Cell(TacLeptonWidth) > MapCellX + MapCellWidth) { + x = MapCellX + MapCellWidth - Lepton_To_Cell(TacLeptonWidth); + } + + if (y < MapCellY) { + y = MapCellY; + } + if (y + Lepton_To_Cell(TacLeptonHeight) > MapCellY + MapCellHeight) { + y = MapCellY + MapCellHeight - Lepton_To_Cell(TacLeptonHeight); + } + + /* + ** Set our TacticalCell + */ + Set_Tactical_Position(Cell_Coord(XY_Cell(x, y))); + for (int index = 0; index < sizeof(Views)/sizeof(Views[0]); index++) { + Views[index] = Coord_Cell(TacticalCoord); + } +} + + +/*********************************************************************************************** + * DisplayClass::Sell_Mode_Control -- Controls the sell mode. * + * * + * This routine will control the sell mode for the player. * + * * + * INPUT: control -- The mode to set the sell state to. * + * 0 = Turn sell mode off. * + * 1 = Turn sell mode on. * + * -1 = Toggle sell mode. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +void DisplayClass::Sell_Mode_Control(int control) +{ + bool mode = IsSellMode; + switch (control) { + case 0: + mode = false; + break; + + case -1: + mode = (IsSellMode == false); + break; + + case 1: + mode = true; + break; + } + + if (mode != IsSellMode && !PendingObject) { + IsRepairMode = false; + if (mode && PlayerPtr->BScan) { + IsSellMode = true; + Unselect_All(); + } else { + IsSellMode = false; + Revert_Mouse_Shape(); + } + } +} + + +/*********************************************************************************************** + * DisplayClass::Repair_Mode_Control -- Controls the repair mode. * + * * + * This routine is used to control the repair mode for the player. * + * * + * INPUT: control -- The mode to set the repair to. * + * 0 = Turn repair off. * + * 1 = Turn repair on. * + * -1= Toggle repair state. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +void DisplayClass::Repair_Mode_Control(int control) +{ + bool mode = IsRepairMode; + switch (control) { + case 0: + mode = false; + break; + + case -1: + mode = (IsRepairMode == false); + break; + + case 1: + mode = true; + break; + } + + if (mode != IsRepairMode && !PendingObject) { + IsSellMode = false; + if (mode && PlayerPtr->BScan) { + IsRepairMode = true; + Unselect_All(); + } else { + IsRepairMode = false; + Revert_Mouse_Shape(); + } + } +} + + +/*********************************************************************************************** + * DisplayClass::In_View -- Determines if cell is visible on screen. * + * * + * Use this routine to determine if the specified cell is visible on * + * the display. This is a useful fact, since many display operations * + * can be skipped if the cell is not visible. * + * * + * INPUT: cell -- The cell number to check. * + * * + * OUTPUT: bool; Is this cell visible on the display? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/30/1994 JLB : Created. * + * 04/30/1994 JLB : Converted to member function. * + *=============================================================================================*/ +bool DisplayClass::In_View(register CELL cell) +{ + COORDINATE coord = Cell_Coord(cell) & 0xFF00FF00L; + COORDINATE tcoord = TacticalCoord & 0xFF00FF00L; + + if ((unsigned)(Coord_X(coord) - Coord_X(tcoord)) > TacLeptonWidth+255) return(false); + if ((unsigned)(Coord_Y(coord) - Coord_Y(tcoord)) > TacLeptonHeight+255) return(false); + return(true); + +#ifdef OBSOLETE + int fudgex = Coord_XLepton(TacticalCoord) ? -1 : 0; + int fudgey = Coord_YLepton(TacticalCoord) ? -1 : 0; + if ((unsigned)(Cell_X(cell)-Coord_XCell(TacticalCoord)) > Lepton_To_Cell(TacLeptonWidth)+fudgex) return(false); + if ((unsigned)(Cell_Y(cell)-Coord_YCell(TacticalCoord)) > Lepton_To_Cell(TacLeptonHeight)+fudgey) return(false); + return(true); +#endif + +#ifdef OBSOLETE + cell -= TacticalCell; + + if (Cell_X(cell) >= TacWidth + (TacPartialX ? 1 : 0)) return(false); + if (Cell_Y(cell) >= TacHeight + (TacPartialY ? 1 : 0)) return(false); + return(true); +#endif +} + + +COORDINATE DisplayClass::Closest_Free_Spot(COORDINATE coord, bool any) const +{ + if (coord & 0xC000C000) { + return(0x00800080); + } + return (*this)[Coord_Cell(coord)].Closest_Free_Spot(coord, any); +} + + +bool DisplayClass::Is_Spot_Free(COORDINATE coord) const +{ + if (coord & 0xC000C000) { + return(0x00800080); + } + return (*this)[Coord_Cell(coord)].Is_Spot_Free(CellClass::Spot_Index(coord)); +} + + +/*********************************************************************************************** + * DisplayClass::Center_Map -- Centers the map about the currently selected objects * + * * + * This routine will average the position of all the selected objects and then center * + * the map about those objects. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: The map position changes by this routine. * + * * + * HISTORY: * + * 08/22/1995 JLB : Created. * + *=============================================================================================*/ +void DisplayClass::Center_Map(void) +{ + if (CurrentObject.Count()) { + unsigned x = 0; + unsigned y = 0; + + for (int index = 0; index < CurrentObject.Count(); index++) { + COORDINATE coord = CurrentObject[index]->Center_Coord(); + + x += Coord_X(coord); + y += Coord_Y(coord); + } + + x /= CurrentObject.Count(); + y /= CurrentObject.Count(); + Set_Tactical_Position(XY_Coord(x - (TacLeptonWidth/2), y - (TacLeptonHeight/2))); + } +} diff --git a/DISPLAY.H b/DISPLAY.H new file mode 100644 index 0000000..be410c5 --- /dev/null +++ b/DISPLAY.H @@ -0,0 +1,320 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\display.h_v 2.15 16 Oct 1995 16:47:42 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DISPLAY.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 1, 1994 * + * * + * Last Update : May 1, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef DISPLAY_H +#define DISPLAY_H + +#include "map.h" +#include "layer.h" + + +#define ICON_PIXEL_W 24 +#define ICON_PIXEL_H 24 +#define ICON_LEPTON_W 256 +#define ICON_LEPTON_H 256 +#define CELL_PIXEL_W ICON_PIXEL_W +#define CELL_PIXEL_H ICON_PIXEL_H +#define CELL_LEPTON_W ICON_LEPTON_W +#define CELL_LEPTON_H ICON_LEPTON_H + +// ----------------------------------------------------------- +#define PIXEL_LEPTON_W (ICON_LEPTON_W/ICON_PIXEL_W) +#define PIXEL_LEPTON_H (ICON_LEPTON_H/ICON_PIXEL_H) + + +extern COORDINATE Coord_Add(COORDINATE coord1, COORDINATE coord2); + +class DisplayClass: public MapClass +{ + public: + + /* + ** This indicates the theater that the display is to represent. + */ + TheaterType Theater; + + /* + ** The tactical map display position is indicated by the cell of the + ** upper left hand corner. These should not be altered directly. Use + ** the Set_Tactical_Position function instead. + */ + COORDINATE TacticalCoord; + + /* + ** The dimensions (in cells) of the visible window onto the game map. This tactical + ** map is how the player interacts and views the game world. + */ + int TacLeptonWidth; + int TacLeptonHeight; + + /* + ** These layer control elements are used to group the displayable objects + ** so that proper overlap can be obtained. + */ + static LayerClass Layer[LAYER_COUNT]; + + /* + ** This records the position and shape of a placement cursor to display + ** over the map. This cursor is used when placing buildings and also used + ** extensively by the scenario editor. + */ + CELL ZoneCell; + short ZoneOffset; + short const *CursorSize; + bool ProximityCheck; // Is proximity check ok? + + /* + ** This holds the building type that is about to be placed upon the map. + ** It is only valid during the building placement state. The PendingLegal + ** flag is updated as the cursor moves and it reflects the legality of + ** placing the building at the desired location. + */ + ObjectClass * PendingObjectPtr; + ObjectTypeClass const * PendingObject; + HousesType PendingHouse; + + static unsigned char FadingBrighten[256]; + static unsigned char FadingShade[256]; + static unsigned char FadingLight[256]; + static unsigned char RemapTables[HOUSE_COUNT][3][256]; + static unsigned char FadingGreen[256]; + static unsigned char FadingYellow[256]; + static unsigned char FadingRed[256]; + static unsigned char TranslucentTable[(MAGIC_COL_COUNT+1)*256]; + static unsigned char WhiteTranslucentTable[(1+1)*256]; + static unsigned char MouseTranslucentTable[(4+1)*256]; + static void const *TransIconset; + static unsigned char UnitShadow[(USHADOW_COL_COUNT+1)*256]; + static unsigned char SpecialGhost[2*256]; + + //------------------------------------------------------------------------- + DisplayClass(void); + + virtual void Read_INI(char *buffer); + void Write_INI(char *buffer); + + /* + ** Initialization + */ + virtual void One_Time(void); // One-time inits + virtual void Init_Clear(void); // Clears all to known state + virtual void Init_IO(void); // Inits button list + virtual void Init_Theater(TheaterType theater); // Theater-specific inits + + /* + ** General display/map/interface support functionality. + */ + virtual void AI(KeyNumType &input, int x, int y); + virtual void Draw_It(bool complete=false); + + /* + ** Added functionality. + */ + void Center_Map(void); + virtual bool Map_Cell(CELL cell, HouseClass *house); + virtual CELL Click_Cell_Calc(int x, int y); + virtual void Help_Text(int , int =-1, int =-1, int =YELLOW, bool =false, int =0) {}; + virtual MouseType Get_Mouse_Shape(void) const = 0; + virtual bool Scroll_Map(DirType facing, int & distance, bool really); + virtual void Refresh_Cells(CELL cell, short const *list); + virtual void Set_View_Dimensions(int x, int y, int width=-1, int height=-1); + + /* + ** Pending object placement control. + */ + virtual void Put_Place_Back(TechnoClass * ) {}; // Affects 'pending' system. + void Cursor_Mark(CELL pos, bool on); + void Set_Cursor_Shape(short const * list); + CELL Set_Cursor_Pos(CELL pos = -1); + void Get_Occupy_Dimensions(int & w, int & h, short const *list); + + /* + ** Tactical map only functionality. + */ + virtual void Set_Tactical_Position(COORDINATE coord); + void Refresh_Band(void); + void Select_These(COORDINATE coord1, COORDINATE coord2); + COORDINATE Pixel_To_Coord(int x, int y); + bool Coord_To_Pixel(COORDINATE coord, int &x, int &y); + bool Push_Onto_TacMap(COORDINATE &source, COORDINATE &dest); + void Remove(ObjectClass const *object, LayerType layer); + void Submit(ObjectClass const *object, LayerType layer); + CELL Calculated_Cell(SourceType dir, HousesType house); + bool In_View(register CELL cell); + bool Passes_Proximity_Check(ObjectTypeClass const *object); + ObjectClass * Cell_Object(CELL cell, int x=0, int y=0); + ObjectClass * Next_Object(ObjectClass * object); + ObjectClass * Prev_Object(ObjectClass * object); + int Cell_Shadow(CELL cell); + short const * Text_Overlap_List(char const * text, int x, int y, int lines = 1); + bool Is_Spot_Free(COORDINATE coord) const; + COORDINATE Closest_Free_Spot(COORDINATE coord, bool any=false) const; + void Sell_Mode_Control(int control); + void Repair_Mode_Control(int control); + + void Flag_Cell(CELL cell) { + Flag_To_Redraw(false); + IsToRedraw = true; + CellRedraw[cell] = true; + }; + bool Is_Cell_Flagged(CELL cell) const {return CellRedraw.Is_True(cell);}; + + /* + ** Computes starting position based on player's units' Coords. + */ + void Compute_Start_Pos(void); + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + protected: + virtual void Mouse_Right_Press(void); + virtual void Mouse_Left_Press(int x, int y); + virtual void Mouse_Left_Up(bool shadow, ObjectClass * object, ActionType action, bool wwsmall = false); + virtual void Mouse_Left_Held(int x, int y); + virtual void Mouse_Left_Release(CELL cell, int x, int y, ObjectClass * object, ActionType action, bool wwsmall = false); + + public: + /* + ** This is the pixel offset for the upper left corner of the tactical map. + */ + int TacPixelX; + int TacPixelY; + + /* + ** This is the coordinate that the tactical map should be in at next available opportunity. + */ + COORDINATE DesiredTacticalCoord; + + /* + ** If something in the tactical map is to be redrawn, this flag is set to true. + */ + unsigned IsToRedraw:1; + + /* + ** If the player is currently wielding a wrench (to select buildings for repair), + ** then this flag is true. In such a state, normal movement and combat orders + ** are preempted. + */ + unsigned IsRepairMode:1; + + /* + ** If the player is currently in "sell back" mode, then this flag will be + ** true. While in this mode, anything clicked on will be sold back to the + ** "factory". + */ + unsigned IsSellMode:1; + + /* + ** If the player is currently in ion cannon targetting mode, then this + ** flag will be true. While in this mode, anything clicked on will be + ** be destroyed by the ION cannon. + */ + unsigned IsTargettingMode:2; + + protected: + + /* + ** If it is currently in rubber band mode (multi unit selection), then this + ** flag will be true. While in such a mode, normal input is prempted while + ** the extended selection is in progress. + */ + unsigned IsRubberBand:1; + + /* + ** The moment the mouse is held down, this flag gets set. If the mouse is dragged + ** a sufficient distance while held down, then true rubber band mode selection + ** can begin. Using a minimum distance prevents accidental rubber band selection + ** mode from being initiated. + */ + unsigned IsTentative:1; + + /* + ** This gadget class is used for capturing input to the tactical map. All mouse input + ** will be routed through this gadget. + */ + class TacticalClass : public GadgetClass { + public: + TacticalClass(void) : GadgetClass(0,0,0,0,LEFTPRESS|LEFTRELEASE|LEFTHELD|LEFTUP|RIGHTPRESS,true) {}; + + protected: + virtual int Action(unsigned flags, KeyNumType & key); + }; + friend class TacticalClass; + + /* + ** This is the "button" that tracks all input to the tactical map. + ** It must be available to derived classes, for Save/Load purposes. + */ + static TacticalClass TacButton; + + private: + + /* + ** This is a utility flag that is set during the icon draw process only if there + ** was at least one shadow icon detected that should be redrawn. When the shadow + ** drawing logic is to take place, but this flag is false, then the shadow drawing + ** will be skipped since it would perform no function. + */ + unsigned IsShadowPresent:1; + + /* + ** Rubber band mode consists of stretching a box from the anchor point (specified + ** here) to the current cursor position. + */ + int BandX,BandY; + int NewX,NewY; + + static void const *ShadowShapes; + static unsigned char ShadowTrans[(SHADOW_COL_COUNT+1)*256]; + + void Redraw_Icons(int draw_flags=0); + void Redraw_Shadow(void); + void Redraw_Shadow_Rects(void); + + /* + ** This bit array is used to flag cells to be redrawn. If the icon needs to + ** be redrawn for a cell, then the corresponding flag will be true. + */ + static BooleanVectorClass CellRedraw; +}; + + +#endif diff --git a/DOOR.CPP b/DOOR.CPP new file mode 100644 index 0000000..d94c491 --- /dev/null +++ b/DOOR.CPP @@ -0,0 +1,202 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\door.cpv 1.4 16 Oct 1995 16:49:16 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DOOR.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 06/11/95 * + * * + * Last Update : June 14, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * DoorClass::AI -- Handles the door processing logic. * + * DoorClass::Close_Door -- Try to close the unit's door. * + * DoorClass::DoorClass -- Constructor for the DoorClass object. * + * DoorClass::Door_Stage -- Fetches the current door animation frame. * + * DoorClass::Open_Door -- Opens the door for this unit. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + +#include "function.h" + + +/*********************************************************************************************** + * DoorClass::DoorClass -- Constructor for the DoorClass object. * + * * + * This constructor sets the door to an initial closed state. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/14/1995 JLB : Created. * + *=============================================================================================*/ +DoorClass::DoorClass(void) +{ + State = IS_CLOSED; + IsToRedraw = false; + Stages = 0; +} + + +/*********************************************************************************************** + * DoorClass::AI -- Handles the door processing logic. * + * * + * This routine should be called every game frame. It handles the door closing and opening * + * logic. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/13/1995 JLB : Created. * + *=============================================================================================*/ +void DoorClass::AI(void) +{ + if (Control.Graphic_Logic()) { + if (Control.Fetch_Stage() >= Stages) { + Control.Set_Rate(0); + switch (State) { + case IS_OPENING: + State = IS_OPEN; + break; + + case IS_CLOSING: + State = IS_CLOSED; + break; + } + } + IsToRedraw = true; + } +} + + +/*********************************************************************************************** + * DoorClass::Open_Door -- Opens the door for this unit. * + * * + * This routine will perform the door open operation for this unit. It will control vehicle * + * rotation if necessary. * + * * + * INPUT: rate -- The animation rate (delay) to use for the door animation logic. * + * * + * stages -- The number of animations stages that this door must pass through. * + * * + * OUTPUT: Was action initiated to open the door? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +bool DoorClass::Open_Door(int rate, int stages) +{ + switch (State) { + case IS_CLOSED: + case IS_CLOSING: + State = IS_OPENING; + Stages = stages-1; + Control.Set_Stage(0); + Control.Set_Rate(rate); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * DoorClass::Close_Door -- Try to close the unit's door. * + * * + * This routine will attempt to close the unit's door. If the door is already closed or * + * in the process of closing, then no action is performed. * + * * + * INPUT: rate -- The animation rate (delay) to use for the door animation logic. * + * * + * stages -- The number of animations stages that this door must pass through. * + * * + * OUTPUT: Action was initiated to close the door? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +bool DoorClass::Close_Door(int rate, int stages) +{ + switch (State) { + case IS_OPEN: + case IS_OPENING: + State = IS_CLOSING; + Stages = stages-1; + Control.Set_Stage(0); + Control.Set_Rate(rate); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * DoorClass::Door_Stage -- Fetches the current door animation frame. * + * * + * Use this routine to fetch the current door animation frame number. Frame zero is the * + * closed frame and frame 'N' is the open frame. If the door is in the process of opening * + * or closing, the appropriate frame number is used. 'N' is defined as the number of * + * stages in the animation minus 1 (e.g., a four frame animation will return a door stage * + * number between 0 and 3, inclusive). * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the door animation frame number. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/14/1995 JLB : Created. * + *=============================================================================================*/ +int DoorClass::Door_Stage(void) const +{ + switch (State) { + case IS_CLOSING: + return((Stages-1) - Control.Fetch_Stage()); + + case IS_CLOSED: + return(0); + + case IS_OPENING: + return(Control.Fetch_Stage()); + + case IS_OPEN: + return(Stages-1); + } + return(0); +} diff --git a/DOOR.H b/DOOR.H new file mode 100644 index 0000000..7f16bb9 --- /dev/null +++ b/DOOR.H @@ -0,0 +1,94 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\door.h_v 1.8 16 Oct 1995 16:47:54 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DOOR.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 06/11/95 * + * * + * Last Update : June 11, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef DOOR_H +#define DOOR_H + +class DoorClass +{ + private: + + /* + ** This is the animation control handler. + */ + StageClass Control; + + /* + ** This is the recorded number of stages of the current + ** door animation process. + */ + unsigned char Stages; + + /* + ** This is the door state. + */ + enum { + IS_CLOSED, // Door is closed. + IS_OPENING, // Door is in the process of opening. + IS_OPEN, // Door is fully open. + IS_CLOSING // Door is in the process of closing. + } State; + + /* + ** If the animation for this door indicates that the object it is + ** attached to should be redrawn, then this flag will be true. + */ + unsigned IsToRedraw:1; + + public: + DoorClass(void); + + bool Time_To_Redraw(void) {return(IsToRedraw);}; + void Clear_Redraw_Flag(void) {IsToRedraw = false;}; + void AI(void); + int Door_Stage(void) const; + bool Is_Door_Opening(void) {return(State == IS_OPENING);}; + bool Is_Door_Closing(void) {return(State == IS_CLOSING);}; + bool Open_Door(int rate, int stages); + bool Close_Door(int rate, int stages); + bool Is_Door_Open(void) {return(State == IS_OPEN);}; + bool Is_Door_Closed(void) {return(State == IS_CLOSED);}; + bool Is_Ready_To_Open(void); + + /* + ** File I/O. + */ + void Code_Pointers(void) { return; } + void Decode_Pointers(void) { return; } +}; + +#endif diff --git a/DPMI.CPP b/DPMI.CPP new file mode 100644 index 0000000..d7fa607 --- /dev/null +++ b/DPMI.CPP @@ -0,0 +1,169 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\dpmi.cpv 2.17 16 Oct 1995 16:49:36 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DPMI.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : July 2, 1994 * + * * + * Last Update : July 2, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifdef __FLAT__ +#pragma inline +#endif + +#include "function.h" +#include "dpmi.h" + +#ifndef __FLAT__ + +void DOSSegmentClass::Swap(DOSSegmentClass &src, int soffset, DOSSegmentClass &dest, int doffset, int size) +{ + if (!size) return; + + unsigned short ssel = src.Selector; + unsigned short dsel = dest.Selector; + + asm { + push es + push ds + + mov si,soffset + mov di,doffset + mov cx,size + mov ax,ssel + mov dx,dsel + mov ds,ax + mov es,dx + } +again: + asm { + mov al,ds:[si] + mov ah,es:[di] + mov ds:[si],ah + mov es:[di],al + inc di + inc si + dec cx + jnz again + + pop ds + pop es + } +} +#endif + + +void DOSSegmentClass::Swap(DOSSegmentClass &src, int soffset, DOSSegmentClass &dest, int doffset, int size) +{ + extern void dss_swap(char *src, char *dest, int size); + + #pragma aux dss_swap = \ + "again: mov al,[esi]" \ + "mov ah,[edi]" \ + "mov [esi],ah" \ + "stosb" \ + "inc esi" \ + "loop again" \ + parm [esi] [edi] [ecx] \ + modify [ax]; + + if (!size) return; + dss_swap((char *)(src.Selector + soffset), (char *)(dest.Selector + doffset), size); +} + +#ifdef OBSOLETE +void DOSSegmentClass::Copy(DOSSegmentClass &src, int soffset, DOSSegmentClass &dest, int doffset, int size) +{ + extern void dss_copy(char *src, char *dest, int size); + #pragma aux dss_copy = \ + "mov ebx,ecx" \ + "shr ecx,2" \ + "jecxz copskip1" \ + "rep movsd" \ +"copskip1: mov ecx,ebx" \ + "and ecx,3" \ + "jecxz copskip2" \ + "rep movsb" \ +"copskip2:" \ + parm [esi edi ecx] \ + modify [ebx]; + + if (!size) return; + dss_copy((char *)(src.Selector + soffset), (char *)(dest.Selector + doffset), size); +} +#endif + +#ifdef OBSOLETE +void DOSSegmentClass::Copy_To(void *source, int dest, int size) +{ + extern void dss_copy_to(void *src, (void *)dest, int size); + + #pragma aux dss_copy_to = \ + "mov ebx,ecx" \ + "shr ecx,2" \ + "jecxz cop2skip1" \ + "rep movsd" \ +"cop2skip1: mov ecx,ebx" \ + "and ecx,3" \ + "jecxz cop2skip2" \ + "rep movsb" \ +"cop2skip2:" \ + parm [esi edi ecx] \ + modify [ebx]; + + if (!size) return; + dss_copy_to(src, (void *)(Selector + dest), size); + +} +#endif + +#ifdef OBSOLETE +void DOSSegmentClass::Copy_From(void *dest, int source, int size) +{ + extern void dss_copy_from(void *dest, (void *)source, int size); + + #pragma aux dss_copy_from = \ + "mov ebx,ecx" \ + "shr ecx,2" \ + "jecxz copfskip1" \ + "rep movsd" \ +"copfskip1: mov ecx,ebx" \ + "and ecx,3" \ + "jecxz copfskip2" \ + "rep movsb" \ +"copfskip2:" \ + parm [edi esi ecx] \ + modify [ebx]; + + if (!size) return; + dss_copy_from(dest, (void *)(Selector + source), size); +} +#endif diff --git a/DPMI.H b/DPMI.H new file mode 100644 index 0000000..a65a0be --- /dev/null +++ b/DPMI.H @@ -0,0 +1,175 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\dpmi.h_v 2.17 16 Oct 1995 16:44:52 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DPMI.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : July 2, 1994 * + * * + * Last Update : July 2, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef DPMI_H +#define DPMI_H +#include +#include +#include +#include + + +extern void output(short port, short data); + + +class DOSSegmentClass { + /* + ** This is the selector/segment value. In real mode it is the segment, in protected + ** mode it is the selector (also 16 bits). This value is moved into DS or ES when + ** accessing memory. + ** Note: in Watcom flat addressing, Selector == Segment<<4 (ex: 0A0000h) + */ + unsigned int Selector; + + /* + ** These are C equivalents for pushing and popping the DS segment register. By using + ** these, it is possible to create very small code that uses a segment and + ** offset without damaging the DS register. These are especially useful in protected + ** mode, but they are legal in real mode as well. + */ + void Push_DS(void) {/*__emit__(0x1E);*/}; + void Pop_DS(void) {/*__emit__(0x1F);*/}; + + public: + DOSSegmentClass(void); + ~DOSSegmentClass(void); + DOSSegmentClass(unsigned short segment, long size=(1024L*64L)); + + unsigned int Get_Selector(void); + + /* + ** This routine is used to assign where the descriptor actually points to in + ** low DOS memory. In real mode, this is a simple segment assignment and the size + ** is always 64K regardless of what is specified. In protected mode, the segment + ** is used to update the selector and the size can be any length. + ** In Watcom flat mode, it sets Selector == segment<<4 + */ + void Assign(unsigned short segment, long size=(1024L*64L)); + + /* + ** These routines will move the data to/from regular memory and the segment/descriptor + ** memory. + */ + void Copy_To(void *source, int dest, int size); + void Copy_From(void *dest, int source, int size); + void Copy_Word_To(short data, int dest); + void Copy_Byte_To(char data, int dest); + void Copy_DWord_To(long data, int dest); + short Copy_Word_From(int source); + char Copy_Byte_From(int source); + long Copy_DWord_From(int source); + + /* + ** These routines move data around between sections of segmented (descriptor) memory. + ** Typically, this is used when accessing DOS memory in protected mode or when dealing + ** with hard memory areas such as the screen. + */ + static void Copy(DOSSegmentClass &src, int soffset, DOSSegmentClass &dest, int doffset, int size); + static void Swap(DOSSegmentClass &src, int soffset, DOSSegmentClass &dest, int doffset, int size); +}; + + +inline DOSSegmentClass::DOSSegmentClass(void) +{ + Selector = 0xB0000; +} + +inline DOSSegmentClass::~DOSSegmentClass(void) +{ +} + +inline void DOSSegmentClass::Copy_Word_To(short data, int dest) +{ + *(short *)(Selector+dest) = data; +} + +inline void DOSSegmentClass::Copy_Byte_To(char data, int dest) +{ + *(char *)(Selector+dest) = data; +} + +inline void DOSSegmentClass::Copy_DWord_To(long data, int dest) +{ + *(long *)(Selector+dest) = data; +} + +inline DOSSegmentClass::DOSSegmentClass(unsigned short segment, long) +{ + Assign(segment); +} + +inline void DOSSegmentClass::Assign(unsigned short segment, long) +{ + Selector = (long)(segment)<<4L; +} + +inline void DOSSegmentClass::Copy_To(void *source, int dest, int size) +{ + memmove((void*)(Selector+dest), source, size); +} + +inline void DOSSegmentClass::Copy_From(void *dest, int source, int size) +{ + memmove(dest, (void*)(Selector+source), size); +} + +inline void DOSSegmentClass::Copy(DOSSegmentClass &src, int soffset, DOSSegmentClass &dest, int doffset, int size) { + memmove((void*)(dest.Selector+doffset), (void*)(src.Selector+soffset), size); +} + +inline short DOSSegmentClass::Copy_Word_From(int source) +{ + return *(short*)(Selector+source); +} + +inline char DOSSegmentClass::Copy_Byte_From(int source) +{ + return *(char*)(Selector+source); +} + +inline long DOSSegmentClass::Copy_DWord_From(int source) +{ + return *(long*)(Selector+source); +} + +inline unsigned int DOSSegmentClass::Get_Selector(void) +{ + return Selector; +} +#endif + + diff --git a/DRIVE.CPP b/DRIVE.CPP new file mode 100644 index 0000000..1c7d5ec --- /dev/null +++ b/DRIVE.CPP @@ -0,0 +1,2213 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\drive.cpv 2.17 16 Oct 1995 16:51:16 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DRIVE.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 22, 1994 * + * * + * Last Update : July 30, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * DriveClass::AI -- Processes unit movement and rotation. * + * DriveClass::Approach_Target -- Handles approaching the target in order to attack it. * + * DriveClass::Assign_Destination -- Set the unit's NavCom. * + * DriveClass::Class_Of -- Fetches a reference to the class type for this object. * + * DriveClass::Debug_Dump -- Displays status information to monochrome screen. * + * DriveClass::Do_Turn -- Tries to turn the vehicle to the specified direction. * + * DriveClass::DriveClass -- Constructor for drive class object. * + * DriveClass::Exit_Map -- Give the unit a movement order to exit the map. * + * DriveClass::Fixup_Path -- Adds smooth start path to normal movement path. * + * DriveClass::Force_Track -- Forces the unit to use the indicated track. * + * DriveClass::Lay_Track -- Handles track laying logic for the unit. * + * DriveClass::Offload_Tiberium_Bail -- Offloads one Tiberium quantum from the object. * + * DriveClass::Ok_To_Move -- Checks to see if this object can begin moving. * + * DriveClass::Overrun_Square -- Handles vehicle overrun of a cell. * + * DriveClass::Per_Cell_Process -- Handles when unit finishes movement into a cell. * + * DriveClass::Smooth_Turn -- Handles the low level coord calc for smooth turn logic. * + * DriveClass::Start_Of_Move -- Tries to get a unit to advance toward cell. * + * DriveClass::Tiberium_Load -- Determine the Tiberium load as a percentage. * + * DriveClass::While_Moving -- Processes unit movement. * + * DriveClass::Mark_Track -- Marks the midpoint of the track as occupied. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +DriveClass::DriveClass(void) : Class(0) {}; + + +/*********************************************************************************************** + * DriveClass::Do_Turn -- Tries to turn the vehicle to the specified direction. * + * * + * This routine will set the vehicle to rotate to the direction specified. For tracked * + * vehicles, it is just a simple rotation. For wheeled vehicles, it performs a series * + * of short drives (three point turn) to face the desired direction. * + * * + * INPUT: dir -- The direction that this vehicle should face. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/29/1995 JLB : Created. * + *=============================================================================================*/ +void DriveClass::Do_Turn(DirType dir) +{ + if (dir != PrimaryFacing) { + + /* + ** Special rotation track is needed for units that + ** cannot rotate in place. + */ + if (Special.IsThreePoint && TrackNumber == -1 && Class->Speed == SPEED_WHEEL) { + int facediff; // Signed difference between current and desired facing. + FacingType face; // Current facing (ordinal value). + + facediff = PrimaryFacing.Difference(dir) >> 5; + facediff = Bound(facediff, -2, 2); + if (facediff) { + face = Dir_Facing(PrimaryFacing); + + IsOnShortTrack = true; + Force_Track(face*FACING_COUNT + (face + facediff), Coord); + + Path[0] = FACING_NONE; + Set_Speed(0xFF); // Full speed. + } + } else { + PrimaryFacing.Set_Desired(dir); + if (Special.IsJurassic && AreThingiesEnabled && What_Am_I() == RTTI_UNIT && ((UnitClass *)this)->Class->IsPieceOfEight) PrimaryFacing.Set_Current(dir); + } + } +} + + +/*********************************************************************************************** + * DriveClass::Force_Track -- Forces the unit to use the indicated track. * + * * + * This override (nuclear bomb) style routine is to be used when a unit needs to start * + * on a movement track but is outside the normal movement system. This occurs when a * + * harvester starts driving off of a refinery. * + * * + * INPUT: track -- The track number to start on. * + * * + * coord -- The coordinate that the unit will end up at when the movement track * + * is completed. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/17/1995 JLB : Created. * + *=============================================================================================*/ +void DriveClass::Force_Track(int track, COORDINATE coord) +{ + TrackNumber = track; + TrackIndex = 0; + Start_Driver(coord); +} + + +/*********************************************************************************************** + * DriveClass::Tiberium_Load -- Determine the Tiberium load as a percentage. * + * * + * Use this routine to determine what the Tiberium load is (as a fixed point percentage). * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the current "fullness" rating for the object. This will be 0x0000 for * + * empty and 0x0100 for full. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/17/1995 JLB : Created. * + *=============================================================================================*/ +int DriveClass::Tiberium_Load(void) const +{ + if (*this == UNIT_HARVESTER) { + return(Cardinal_To_Fixed(UnitTypeClass::STEP_COUNT, Tiberium)); + } + return(0x0000); +} + + +/*********************************************************************************************** + * DriveClass::Approach_Target -- Handles approaching the target in order to attack it. * + * * + * This routine will check to see if the target is infantry and it can be overrun. It will * + * try to overrun the infantry rather than attack it. This only applies to computer * + * controlled vehicles. If it isn't the infantry overrun case, then it falls into the * + * base class for normal (complex) approach algorithm. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/17/1995 JLB : Created. * + * 07/12/1995 JLB : Flamethrower tanks don't overrun -- their weapon is better. * + *=============================================================================================*/ +void DriveClass::Approach_Target(void) +{ + /* + ** Only if there is a legal target should the approach check occur. + */ + if (!House->IsHuman && Target_Legal(TarCom) && !Target_Legal(NavCom)) { + + /* + ** Special case: + ** If this is for a unit that can crush infantry, and the target is + ** infantry, AND the infantry is pretty darn close, then just try + ** to drive over the infantry instead of firing on it. + */ + TechnoClass * target = As_Techno(TarCom); + if (Class->Primary != WEAPON_FLAME_TONGUE && Class->IsCrusher && Distance(TarCom) < 0x0180 && target && ((TechnoTypeClass const &)(target->Class_Of())).IsCrushable) { + Assign_Destination(TarCom); + return; + } + } + + /* + ** In the other cases, uses the more complex "get to just within weapon range" + ** algorithm. + */ + FootClass::Approach_Target(); +} + + +/*********************************************************************************************** + * DriveClass::Overrun_Square -- Handles vehicle overrun of a cell. * + * * + * This routine is called when a vehicle enters a square or when it is about to enter a * + * square (controlled by parameter). When a vehicle that can crush infantry enters a * + * cell that contains infantry, then the infantry will be destroyed (regardless of * + * affiliation). When a vehicle threatens to overrun a square, all occupying infantry * + * will attempt to get out of the way. * + * * + * INPUT: cell -- The cell that is, or soon will be, entered by a vehicle. * + * * + * threaten -- Don't kill, but just threaten to enter the cell. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void DriveClass::Overrun_Square(CELL cell, bool threaten) +{ + CellClass * cellptr = &Map[cell]; + + if (Class->IsCrusher) { + if (threaten) { + + /* + ** If the cell contains infantry, then they will panic when a vehicle tries + ** drive over them. Have the infantry run away instead. + */ + if (cellptr->Flag.Composite & 0x1F) { + + /* + ** Scattering is controlled by the game difficulty level. + */ + if ((Special.IsDifficult || Special.IsScatter || Scenario > 8) && !Special.IsEasy) { + cellptr->Incoming(0, true); + } + } + } else { + ObjectClass * object = cellptr->Cell_Occupier(); + int crushed = false; + while (object) { + if (object->Class_Of().IsCrushable && !House->Is_Ally(object) && Distance(object->Center_Coord()) < 0x80) { + ObjectClass * next = object->Next; + crushed = true; + + /* + ** Record credit for the kill(s) + */ + Sound_Effect(VOC_SQUISH2, Coord); + object->Record_The_Kill(this); + object->Mark(MARK_UP); + object->Limbo(); + delete object; + new OverlayClass(OVERLAY_SQUISH, Coord_Cell(Coord)); + + object = next; + } else { + object = object->Next; + } + } + if (crushed) Do_Uncloak(); + } + } +} + + +/*********************************************************************************************** + * DriveClass::DriveClass -- Constructor for drive class object. * + * * + * This will initialize the drive class to its default state. It is called as a result * + * of creating a unit. * + * * + * INPUT: classid -- The unit's ID class. It is passed on to the foot class constructor. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/13/1994 JLB : Created. * + *=============================================================================================*/ +DriveClass::DriveClass(UnitType classid, HousesType house) : + Class(&UnitTypeClass::As_Reference(classid)), + FootClass(house) +{ + /* + ** For two shooters, clear out the second shot flag -- it will be set the first time + ** the object fires. For non two shooters, set the flag since it will never be cleared + ** and the second shot flag tells the system that normal rearm times apply -- this is + ** what is desired for non two shooters. + */ + if (Class->IsTwoShooter) { + IsSecondShot = false; + } else { + IsSecondShot = true; + } + IsHarvesting = false; + IsTurretLockedDown = false; + IsOnShortTrack = false; + IsReturning = false; + TrackNumber = -1; + TrackIndex = 0; + SpeedAccum = 0; + Tiberium = 0; + Strength = Class->MaxStrength; +} + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * DriveClass::Debug_Dump -- Displays status information to monochrome screen. * + * * + * This debug utility function will display the status of the drive class to the mono * + * screen. It is through this information that bugs can be tracked down. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + *=============================================================================================*/ +void DriveClass::Debug_Dump(MonoClass *mono) const +{ + mono->Set_Cursor(33, 7); + mono->Printf("%2d:%2d", TrackNumber, TrackIndex); + mono->Text_Print("X", 16 + (IsTurretLockedDown?2:0), 10); +// mono->Text_Print("X", 16 + (IsOnShortTrack?2:0), 11); + mono->Set_Cursor(41, 7);mono->Printf("%d", Fixed_To_Cardinal(100, Tiberium_Load())); + FootClass::Debug_Dump(mono); +} +#endif + + +/*********************************************************************************************** + * DriveClass::Exit_Map -- Give the unit a movement order to exit the map. * + * * + * This routine is used to assign an appropriate movement destination for the unit so that * + * it will leave the map. The scripts are usually the one to call this routine when it * + * is determined that the unit has fulfilled its mission and must "depart". * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + *=============================================================================================*/ +void DriveClass::Exit_Map(void) +{ + CELL cell; // Map exit cell number. + + if (*this == UNIT_HOVER && !Target_Legal(NavCom)) { + + /* + ** Scan a swath of cells from current position to the edge of the map and if + ** there is any blocking object, just wait so to try again later. + */ + Mark(MARK_UP); + for (int x = Cell_X(Coord_Cell(Center_Coord()))-1; x <= Cell_X(Coord_Cell(Center_Coord()))+1; x++) { + for (int y = Cell_Y(Coord_Cell(Center_Coord()))+1; y < Map.MapCellY+Map.MapCellHeight; y++) { + cell = XY_Cell(x, y); + if (Map[cell].Cell_Techno()) { + Mark(MARK_DOWN); + return; + } + } + } + Mark(MARK_DOWN); + + /* + ** A clear path to the map edge exists. Assign it as the navigation computer + ** destination and let the transport move. + */ + cell = XY_Cell(Cell_X(Coord_Cell(Coord)), Map.MapCellY+Map.MapCellHeight); + IsReturning = true; + Assign_Destination(::As_Target(cell)); + } +} + + +/*********************************************************************************************** + * DriveClass::Smooth_Turn -- Handles the low level coord calc for smooth turn logic. * + * * + * This routine calculates the new coordinate value needed for the * + * smooth turn logic. The adjustment and flag values must be * + * determined prior to entering this routine. * + * * + * INPUT: adj -- The adjustment coordinate as lifted from the * + * correct smooth turn table. * + * * + * dir -- Pointer to dir for possible modification * + * according to the flag bits. * + * * + * OUTPUT: Returns with the coordinate the unit should positioned to. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/14/1994 JLB : Created. * + * 07/13/1994 JLB : Converted to member function. * + *=============================================================================================*/ +COORDINATE DriveClass::Smooth_Turn(COORDINATE adj, DirType *dir) +{ + DirType workdir = *dir; + int x,y; + int temp; + TrackControlType flags = TrackControl[TrackNumber].Flag; + + x = Coord_X(adj); + y = Coord_Y(adj); + + if (flags & F_T) { + temp = x; + x = y; + y = temp; + workdir = (DirType)(DIR_W - workdir); + } + + if (flags & F_X) { + x = -x; + workdir = (DirType)-workdir; + } + + if (flags & F_Y) { + y = -y; + workdir = (DirType)(DIR_S - workdir); + } + + *dir = workdir; + + return(XY_Coord( Coord_X(Head_To_Coord()) + x, Coord_Y(Head_To_Coord()) + y)); +} + + +/*********************************************************************************************** + * DriveClass::Assign_Destination -- Set the unit's NavCom. * + * * + * This routine is used to set the unit's navigation computer to the * + * specified target. Once the navigation computer is set, the unit * + * will start planning and moving toward the destination. * + * * + * INPUT: target -- The destination target for the unit to head to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/07/1992 JLB : Created. * + * 04/15/1994 JLB : Converted to member function. * + *=============================================================================================*/ +void DriveClass::Assign_Destination(TARGET target) +{ + + /* + ** Abort early if there is anything wrong with the parameters + ** or the unit already is assigned the specified destination. + */ + if (target == NavCom) return; + +#ifdef NEVER + UnitClass *tunit; // Destination unit pointer. + + /* + ** When in move mode, a map position may really indicate + ** a unit to guard. + */ + if (Is_Target_Cell(target)) { + cell = As_Cell(target); + + tunit = Map[cell].Cell_Unit(); + if (tunit) { + + /* + ** Prevent targeting of itself. + */ + if (tunit != this) { + target = tunit->As_Target(); + } + } else { + + tbuilding = Map[cell].Cell_Building(); + if (tbuilding) { + target = tbuilding->As_Target(); + } + } + } +#endif + + /* + ** For harvesting type vehicles, it might go into a dock and unload procedure + ** when the harvester is full and an empty refinery is selected as a target. + */ + BuildingClass * b = As_Building(target); + + /* + ** Transport vehicles must tell all passengers that are about to load, that they + ** cannot proceed. This is accomplished with a radio message to this effect. + */ + //if (tunit && In_Radio_Contact() && Class->IsTransporter && Contact_With_Whom()->Is_Infantry()) { + if (In_Radio_Contact() && Class->IsTransporter && Contact_With_Whom()->Is_Infantry()) { + Transmit_Message(RADIO_OVER_OUT); + } + + /* + ** If the player clicked on a friendly repair facility and the repair + ** facility is currently not involved with some other unit (radio or unloading). + */ + if (b && *b == STRUCT_REPAIR) { + if (b->In_Radio_Contact()) { + target = 0; + } else { + + /* + ** Establish radio contact protocol. If the facility responds correctly, + ** then remain in radio contact and proceed toward the desired destination. + */ + if (Transmit_Message(RADIO_HELLO, b) == RADIO_ROGER) { + + /* + ** Last check to make sure that the loading square is free from permanent + ** occupation (such as a building). + */ + CELL cell = Coord_Cell(b->Center_Coord()) + (MAP_CELL_W-1); + if (Ground[Map[cell].Land_Type()].Cost[Class->Speed] ) { + if (Transmit_Message(RADIO_DOCKING) == RADIO_ROGER) { + FootClass::Assign_Destination(target); + Path[0] = FACING_NONE; + return; + } + + /* + ** Failure to establish a docking relationship with the refinery. + ** Bail & await further instructions. + */ + Transmit_Message(RADIO_OVER_OUT); + } + } + } + } + + /* + ** Set the unit's navigation computer. + */ + FootClass::Assign_Destination(target); + Path[0] = FACING_NONE; // Force recalculation of path. + if (!IsDriving) { + Start_Of_Move(); + } +} + + +/*********************************************************************************************** + * DriveClass::While_Moving -- Processes unit movement. * + * * + * This routine is used to process movement for the units as they move. * + * It is called many times for each cell's worth of movement. This * + * routine only applies after the next cell HeadTo has been determined. * + * * + * INPUT: none * + * * + * OUTPUT: true/false; Should this routine be called again? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/02/1992 JLB : Created. * + * 04/15/1994 JLB : Converted to member function. * + *=============================================================================================*/ +bool DriveClass::While_Moving(void) +{ + int actual; // Working movement addition value. + + /* + ** Perform quick legality checks. + */ + if (!IsDriving || TrackNumber == -1 || (IsRotating && !Class->IsTurretEquipped)) { + SpeedAccum = 0; // Kludge? No speed should accumulate if movement is on hold. + return(false); + } + + /* + ** If enough movement has accumulated so that the unit can + ** visibly move on the map, then process accordingly. + ** Slow the unit down if he's carrying a flag. + */ + if (((UnitClass *)this)->Flagged != HOUSE_NONE) { + actual = SpeedAccum + Fixed_To_Cardinal(Class->MaxSpeed/2, Speed); + } else { + actual = SpeedAccum + Fixed_To_Cardinal(Class->MaxSpeed, Speed); + } + + if (actual > PIXEL_LEPTON_W) { + TurnTrackType const *track; // Track control pointer. + TrackType const *ptr; // Pointer to coord offset values. + int tracknum; // The track number being processed. + FacingType nextface; // Next facing queued in path. + bool adj; // Is a turn coming up? + + track = &TrackControl[TrackNumber]; + if (IsOnShortTrack) { + tracknum = track->StartTrack; + } else { + tracknum = track->Track; + } + ptr = RawTracks[tracknum-1].Track; + nextface = Path[0]; + + /* + ** Determine if there is a turn coming up. If there is + ** a turn, then track jumping might occur. + */ + adj = false; + if (nextface != FACING_NONE && Dir_Facing(track->Facing) != nextface) { + adj = true; + } + + /* + ** Skip ahead the number of track steps required (limited only + ** by track length). Set the unit to the new position and + ** flag the unit accordingly. + */ + Mark(MARK_UP); + while (actual > PIXEL_LEPTON_W) { + COORDINATE offset; + DirType dir; + + actual -= PIXEL_LEPTON_W; + + offset = ptr[TrackIndex].Offset; + if (offset || !TrackIndex) { + dir = ptr[TrackIndex].Facing; + Coord = Smooth_Turn(offset, &dir); + + PrimaryFacing.Set(dir); + + /* + ** See if "per cell" processing is necessary. + */ + if (TrackIndex && RawTracks[tracknum-1].Cell == TrackIndex) { + Per_Cell_Process(false); + if (!IsActive) { + return(false); + } + } + + /* + ** The unit could "jump tracks". Check to see if the unit should + ** do so. + */ + if (*this != UNIT_GUNBOAT && nextface != FACING_NONE && adj && RawTracks[tracknum-1].Jump == TrackIndex && TrackIndex) { + TurnTrackType const *newtrack; // Proposed jump-to track. + int tnum; + + tnum = Dir_Facing(track->Facing)*FACING_COUNT + nextface; + newtrack = &TrackControl[tnum]; + if (newtrack->Track && RawTracks[newtrack->Track-1].Entry) { + COORDINATE c = Head_To_Coord(); + int oldspeed = Speed; + + c = Adjacent_Cell(c, nextface); + + switch(Can_Enter_Cell(Coord_Cell(c), nextface)) { + case MOVE_OK: + IsOnShortTrack = false; // Shouldn't be necessary, but... + TrackNumber = tnum; + track = newtrack; + + // Mono_Printf("**Jumping from track %d to track %d. **\n", tracknum, track->Track);Keyboard::Get(); + + tracknum = track->Track; + TrackIndex = RawTracks[tracknum-1].Entry-1; // Anticipate increment. + ptr = RawTracks[tracknum-1].Track; + adj = false; + + Stop_Driver(); + Per_Cell_Process(true); + if (Start_Driver(c)) { + Set_Speed(oldspeed); + memcpy(&Path[0], &Path[1], CONQUER_PATH_MAX-1); + Path[CONQUER_PATH_MAX-1] = FACING_NONE; + } else { + Path[0] = FACING_NONE; + TrackNumber = -1; + actual = 0; + } + break; + + case MOVE_CLOAK: + Map[Coord_Cell(c)].Shimmer(); + break; + + case MOVE_TEMP: + if (*this == UNIT_HARVESTER || !House->IsHuman) { + bool old = Special.IsScatter; + Special.IsScatter = true; + Map[Coord_Cell(c)].Incoming(0, true); + Special.IsScatter = old; + } + break; + } + } + } + TrackIndex++; + + } else { + actual = 0; + Coord = Head_To_Coord(); + Stop_Driver(); + TrackNumber = -1; + TrackIndex = NULL; + + /* + ** Perform "per cell" activities. + */ + Per_Cell_Process(true); + + break; + } + } + if (IsActive) { + Mark(MARK_DOWN); + } + } + + /* + ** Replace any remainder back into the unit's movement + ** accumulator to be processed next pass. + */ + SpeedAccum = actual; + return(true); +} + + +/*********************************************************************************************** + * DriveClass::Per_Cell_Process -- Handles when unit finishes movement into a cell. * + * * + * This routine is called when a unit has mostly or completely * + * entered a cell. The unit might be in the middle of a movement track * + * when this routine is called. It's primary purpose is to perform * + * sighting and other "per cell" activities. * + * * + * INPUT: center -- Is the unit safely at the center of a cell? If it is merely "close" * + * to the center, then this parameter will be false. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/03/1993 JLB : Created. * + * 03/30/1994 JLB : Revamped for track system. * + * 04/15/1994 JLB : Converted to member function. * + * 06/18/1994 JLB : Converted to virtual function. * + * 06/18/1994 JLB : Distinguishes between center and near-center conditions. * + *=============================================================================================*/ +void DriveClass::Per_Cell_Process(bool center) +{ + CELL cell = Coord_Cell(Coord); + + /* + ** Check to see if it has reached its destination. If so, then clear the NavCom + ** regardless of the remaining path list. + */ + if (center && As_Cell(NavCom) == cell) { + IsTurretLockedDown = false; + NavCom = TARGET_NONE; + Path[0] = FACING_NONE; + } + +#ifdef NEVER + /* + ** A "lemon" vehicle will have a tendency to break down as + ** it moves about the terrain. + */ + if (Is_A_Lemon) { + if (Random_Pick(1, 4) == 1) { + Take_Damage(1); + } + } +#endif + + Lay_Track(); + + FootClass::Per_Cell_Process(center); +} + + +/*********************************************************************************************** + * DriveClass::Start_Of_Move -- Tries to get a unit to advance toward cell. * + * * + * This will try to start a unit advancing toward the cell it is * + * facing. It will check for and handle legality and reserving of the * + * necessary cell. * + * * + * INPUT: none * + * * + * OUTPUT: true/false; Should this routine be called again because * + * initial start operation is temporarily delayed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/02/1992 JLB : Created. * + * 10/18/1993 JLB : This should be called repeatedly until HeadTo is not NULL. * + * 03/16/1994 JLB : Revamped for track logic. * + * 04/15/1994 JLB : Converted to member function. * + * 06/19/1995 JLB : Fixed so that it won't fire on ground unnecessarily. * + * 07/13/1995 JLB : Handles bumping into cloaked objects. * + *=============================================================================================*/ +bool DriveClass::Start_Of_Move(void) +{ + FacingType facing; // Direction movement will commence. + DirType dir; // Desired actual facing toward destination. + int facediff; // Difference between current and desired facing. + int speed; // Speed of unit. + CELL destcell; // Cell of destination. + LandType ground; // Ground unit is entering. + COORDINATE dest; // Destination coordinate. + + facing = Path[0]; + + if (!Target_Legal(NavCom) && facing == FACING_NONE) { + IsTurretLockedDown = false; + Stop_Driver(); + if (Mission == MISSION_MOVE) { + Enter_Idle_Mode(); + } + return(false); // Why is it calling this routine!?! + } + +#ifdef NEVER + /* + ** Movement start logic can't begin until a unit that requires + ** a locked down turret gets to a locked down state (i.e., the + ** turret rotation stops. + */ + if (ClassF & CLASSF_LOCKTURRET) { + Set_Secondary_Facing(facing<<5); + if (Is_Rotating) { + return(true); + } + } +#endif + + /* + ** Reduce the path length if the target is a unit and the + ** range to the unit is less than the precalculated path steps. + */ + if (facing != FACING_NONE) { + int dist; + + if (Is_Target_Unit(NavCom) || Is_Target_Infantry(NavCom)) { + dist = Lepton_To_Cell(Distance(NavCom)); + +// if (dist > CELL_LEPTON_W || +// !As_Techno(NavCom)->Techno_Type_Class()->IsCrushable || +// !Class->IsCrusher) { + + if (dist < CONQUER_PATH_MAX) { + Path[dist] = FACING_NONE; + facing = Path[0]; // Maybe needed. + } +// } + } + } + + /* + ** If the path is invalid at this point, then generate one. If + ** generating a new path fails, then abort NavCom. + */ + if (facing == FACING_NONE) { + + /* + ** If after a path search, there is still no valid path, then set the + ** NavCom to null and let the script take care of assigning a new + ** navigation target. + */ + if (!PathDelay.Expired()) { + return(false); + } + if (!Basic_Path()) { + if (Distance(NavCom) < 0x0280 && (Mission == MISSION_MOVE || Mission == MISSION_GUARD_AREA)) { + Assign_Destination(TARGET_NONE); + } else { + + /* + ** If a basic path could be found, but the immediate move destination is + ** blocked by a friendly temporary blockage, then cause that blockage + ** to scatter. + */ + CELL cell = Adjacent_Cell(Coord_Cell(Center_Coord()), PrimaryFacing.Current()); + if (Map.In_Radar(cell)) { + if (Can_Enter_Cell(cell) == MOVE_TEMP) { + CellClass * cellptr = &Map[cell]; + TechnoClass * blockage = cellptr->Cell_Techno(); + if (blockage && House->Is_Ally(blockage)) { + bool old = Special.IsScatter; + Special.IsScatter = true; + cellptr->Incoming(0, true); + Special.IsScatter = old; + } + } + } + + if (TryTryAgain) { + TryTryAgain--; + } else { + Assign_Destination(TARGET_NONE); + if (IsNewNavCom) Sound_Effect(VOC_SCOLD); + IsNewNavCom = false; + } + } + Stop_Driver(); + TrackNumber = -1; + IsTurretLockedDown = false; + return(false); + } + + /* + ** If a basic path could be found, but the immediate move destination is + ** blocked by a friendly temporary blockage, then cause that blockage + ** to scatter. + */ + CELL cell = Adjacent_Cell(Coord_Cell(Center_Coord()), Path[0]); + if (Map.In_Radar(cell)) { + if (Can_Enter_Cell(cell) == MOVE_TEMP) { + CellClass * cellptr = &Map[cell]; + TechnoClass * blockage = cellptr->Cell_Techno(); + if (blockage && House->Is_Ally(blockage)) { + bool old = Special.IsScatter; + Special.IsScatter = true; + cellptr->Incoming(0, true); + Special.IsScatter = old; + } + } + } + + TryTryAgain = PATH_RETRY; + facing = Path[0]; + } + + if (Class->IsLockTurret || !Class->IsTurretEquipped) { + IsTurretLockedDown = true; + } + +#ifdef NEVER + /* + ** If the turret needs to match the body's facing before + ** movement can occur, then start it's rotation and + ** don't start a movement track until it is aligned. + */ + if (!Ok_To_Move(BodyFacing)) { + return(true); + } +#endif + + /* + ** Determine the coordinate of the next cell to move into. + */ + dest = Adjacent_Cell(Coord, facing); + dir = Facing_Dir(facing); + + /* + ** Set the facing correctly if it isn't already correct. This + ** means starting a rotation track if necessary. + */ + facediff = PrimaryFacing.Difference(dir); + if (facediff) { + + /* + ** Request a change of facing. + */ + Do_Turn(dir); + return(true); + + } else { + + /* NOTE: Beyond this point, actual track assignment can begin. + ** + ** If the cell to move into is impassable (probably for some unexpected + ** reason), then abort the path list and set the speed to zero. The + ** next time this routine is called, a new path will be generated. + */ + destcell = Coord_Cell(dest); + Mark(MARK_UP); + MoveType cando = Can_Enter_Cell(destcell, facing); + Mark(MARK_DOWN); + + if (cando != MOVE_OK) { + + if (Mission == MISSION_MOVE && House->IsHuman && Distance(NavCom) < 0x0200) { + Assign_Destination(TARGET_NONE); + } + + /* + ** If a temporary friendly object is blocking the path, then cause it to + ** get out of the way. + */ + if (cando == MOVE_TEMP) { + bool old = Special.IsScatter; + Special.IsScatter = true; + Map[destcell].Incoming(0, true); + Special.IsScatter = old; + } + + /* + ** If a cloaked object is blocking, then shimmer the cell. + */ + if (cando == MOVE_CLOAK) { + Map[destcell].Shimmer(); + } + + Stop_Driver(); + if (cando != MOVE_MOVING_BLOCK) { + Path[0] = FACING_NONE; // Path is blocked! + } + + /* + ** If blocked by a moving block then just exit start of move and + ** try again next tick. + */ + if (cando == MOVE_DESTROYABLE) { + if (Map[destcell].Cell_Object()) { + if (!House->Is_Ally(Map[destcell].Cell_Object())) { + Override_Mission(MISSION_ATTACK, Map[destcell].Cell_Object()->As_Target(), TARGET_NONE); + } + } else { + if (Map[destcell].Overlay != OVERLAY_NONE && OverlayTypeClass::As_Reference(Map[destcell].Overlay).IsWall) { + Override_Mission(MISSION_ATTACK, ::As_Target(destcell), TARGET_NONE); + } + } + } else { + if (IsNewNavCom) Sound_Effect(VOC_SCOLD); + } + IsNewNavCom = false; + TrackNumber = -1; + return(true); + } + + /* + ** Determine the speed that the unit can travel to the desired square. + */ + ground = Map[destcell].Land_Type(); + speed = Ground[ground].Cost[Class->Speed]; + if (!speed) speed = 128; + +#ifdef NEVER + /* + ** Set the jiggle flag if the terrain would cause the unit + ** to jiggle when travelled over. + */ + BaseF &= ~BASEF_JIGGLE; + if (Ground[ground].Jiggle) { + BaseF |= BASEF_JIGGLE; + } +#endif + + /* + ** A damaged unit has a reduced speed. + */ + if ((Class->MaxStrength>>1) > Strength) { + speed -= (speed>>2); // Three quarters speed. + } + if ((speed != Speed)/* || !SpeedAdd*/) { + Set_Speed(speed); // Full speed. + } + + /* + ** Adjust speed depending on distance to ultimate movement target. The + ** further away the target is, the faster the vehicle will travel. + */ + int dist = Distance(NavCom); + if (dist < 0x0200) { + speed = Fixed_To_Cardinal(speed, 0x00A0); + } else { + if (dist < 0x0700) { + speed = Fixed_To_Cardinal(speed, 0x00D0); + } + } + + /* + ** Reserve the destination cell so that it won't become + ** occupied AS this unit is moving into it. + */ + if (cando != MOVE_OK) { + Path[0] = FACING_NONE; // Path is blocked! + TrackNumber = -1; + dest = NULL; + } else { + + Overrun_Square(Coord_Cell(dest), true); + + /* + ** Determine which track to use (based on recorded path). + */ + FacingType nextface = Path[1]; + if (nextface == FACING_NONE) nextface = facing; + + IsOnShortTrack = false; + TrackNumber = facing * FACING_COUNT + nextface; + if (TrackControl[TrackNumber].Track == 0) { + Path[0] = FACING_NONE; + TrackNumber = -1; + return(true); + } else { + if (TrackControl[TrackNumber].Flag & F_D) { + /* + ** If the middle cell of a two cell track contains a crate, + ** the check for goodies before movement starts. + */ + if (!Map[destcell].Goodie_Check(this)) { + cando = MOVE_NO; + } else { + + dest = Adjacent_Cell(dest, nextface); + destcell = Coord_Cell(dest); + cando = Can_Enter_Cell(destcell); + } + + if (cando != MOVE_OK) { + + /* + ** If a temporary friendly object is blocking the path, then cause it to + ** get out of the way. + */ + if (cando == MOVE_TEMP) { + bool old = Special.IsScatter; + Special.IsScatter = true; + Map[destcell].Incoming(0, true); + Special.IsScatter = old; + } + + /* + ** If a cloaked object is blocking, then shimmer the cell. + */ + if (cando == MOVE_CLOAK) { + Map[destcell].Shimmer(); + } + + Path[0] = FACING_NONE; // Path is blocked! + TrackNumber = -1; + dest = NULL; + if (cando == MOVE_DESTROYABLE) { + + if (Map[destcell].Cell_Object()) { + if (!House->Is_Ally(Map[destcell].Cell_Object())) { + Override_Mission(MISSION_ATTACK, Map[destcell].Cell_Object()->As_Target(), TARGET_NONE); + } + } else { + if (Map[destcell].Overlay != OVERLAY_NONE && OverlayTypeClass::As_Reference(Map[destcell].Overlay).IsWall) { + Override_Mission(MISSION_ATTACK, ::As_Target(destcell), TARGET_NONE); + } + } + IsNewNavCom = false; + TrackIndex = 0; + return(true); + } + } else { + memcpy(&Path[0], &Path[2], CONQUER_PATH_MAX-2); + Path[CONQUER_PATH_MAX-2] = FACING_NONE; + IsPlanningToLook = true; + } + } else { + memcpy(&Path[0], &Path[1], CONQUER_PATH_MAX-1); + } + Path[CONQUER_PATH_MAX-1] = FACING_NONE; + } + } + + IsNewNavCom = false; + TrackIndex = 0; + if (!Start_Driver(dest)) { + TrackNumber = -1; + Path[0] = FACING_NONE; + Set_Speed(0); + } + } + return(false); +} + + +/*********************************************************************************************** + * DriveClass::AI -- Processes unit movement and rotation. * + * * + * This routine is used to process unit movement and rotation. It * + * functions autonomously from the script system. Thus, once a unit * + * is give rotation command or movement path, it will follow this * + * until specifically instructed to stop. The advantage of this * + * method is that it allows smooth movement of units, faster game * + * execution, and reduced script complexity (since actual movement * + * dynamics need not be controlled directly by the scripts). * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine relies on the process control bits for the * + * specified unit (for speed reasons). Thus, only setting * + * movement, rotation, or path list will the unit perform * + * any physics. * + * * + * HISTORY: * + * 09/26/1993 JLB : Created. * + * 04/15/1994 JLB : Converted to member function. * + *=============================================================================================*/ +void DriveClass::AI(void) +{ + FootClass::AI(); + + /* + ** If the unit is following a track, then continue + ** to do so -- mindlessly. + */ + if (TrackNumber != -1) { + + /* + ** Perform the movement accumulation. + */ + While_Moving(); + if (!IsActive) return; + if (TrackNumber == -1 && (Target_Legal(NavCom) || Path[0] != FACING_NONE)) { + Start_Of_Move(); + While_Moving(); + if (!IsActive) return; + } + + } else { + + /* + ** For tracked units that are rotating in place, perform the rotation now. + */ + if ((Class->Speed == SPEED_FLOAT || Class->Speed == SPEED_HOVER || Class->Speed == SPEED_TRACK || (Class->Speed == SPEED_WHEEL && !Special.IsThreePoint)) && PrimaryFacing.Is_Rotating()) { + if (PrimaryFacing.Rotation_Adjust(Class->ROT)) { + Mark(MARK_CHANGE); + } + if (!IsRotating) { + Per_Cell_Process(true); + if (!IsActive) return; + } + + } else { + + /* + ** The unit has no track to follow, but if there + ** is a navigation target or a remaining path, + ** then start on a new track. + */ + if (Mission != MISSION_GUARD || NavCom != TARGET_NONE) { + if (Target_Legal(NavCom) || Path[0] != FACING_NONE) { + Start_Of_Move(); + While_Moving(); + if (!IsActive) return; + } else { + Stop_Driver(); + } + } + } + } +} + + +/*********************************************************************************************** + * DriveClass::Fixup_Path -- Adds smooth start path to normal movement path. * + * * + * This routine modifies the path of the specified unit so that it * + * will not start out with a rotation. This is necessary for those * + * vehicles that have difficulty with rotating in place. Typically, * + * this includes wheeled vehicles. * + * * + * INPUT: unit -- Pointer to the unit to adjust. * + * * + * path -- Pointer to path structure. * + * * + * OUTPUT: none * + * * + * WARNINGS: Only units that require a fixup get modified. The * + * modification only occurs, if there is a legal path to * + * do so. * + * * + * HISTORY: * + * 04/03/1994 JLB : Created. * + * 04/06/1994 JLB : Uses path structure. * + * 04/10/1994 JLB : Diagonal smooth turn added. * + * 04/15/1994 JLB : Converted to member function. * + *=============================================================================================*/ +void DriveClass::Fixup_Path(PathType *path) +{ + FacingType stage[6]={FACING_N,FACING_N,FACING_N,FACING_N,FACING_N,FACING_N}; // Prefix path elements. + int facediff; // The facing difference value (0..4 | 0..-4). + static FacingType _path[4][6] = { + {(FacingType)2,(FacingType)0,(FacingType)2,(FacingType)0,(FacingType)0,(FacingType)0}, + {(FacingType)3,(FacingType)0,(FacingType)2,(FacingType)2,(FacingType)0,(FacingType)0}, + {(FacingType)4,(FacingType)0,(FacingType)2,(FacingType)2,(FacingType)0,(FacingType)0}, + {(FacingType)4,(FacingType)0,(FacingType)2,(FacingType)2,(FacingType)1,(FacingType)0} + }; + static FacingType _dpath[4][6] = { + {(FacingType)0,(FacingType)0,(FacingType)0,(FacingType)0,(FacingType)0,(FacingType)0}, + {(FacingType)3,(FacingType)0,(FacingType)2,(FacingType)2,(FacingType)0,(FacingType)0}, + {(FacingType)4,(FacingType)0,(FacingType)2,(FacingType)2,(FacingType)1,(FacingType)0}, + {(FacingType)5,(FacingType)0,(FacingType)2,(FacingType)2,(FacingType)1,(FacingType)0} + }; + + int index; + int counter; // Path addition + FacingType *ptr; // Path list pointer. + FacingType *ptr2; // Copy of new path list pointer. + FacingType nextpath; // Next path value. + CELL cell; // Working cell value. + bool ok; + + /* + ** Verify that the unit is valid and there is a path problem to resolve. + */ + if (!path || path->Command[0] == FACING_NONE) { + return; + } + + /* + ** Only wheeled vehicles need a path fixup -- to avoid 3 point turns. + */ + if (!Special.IsThreePoint || Class->Speed != SPEED_WHEEL) { + return; + } + + /* + ** If the original path starts in the same direction as the unit, then + ** there is no problem to resolve -- abort. + */ + facediff = PrimaryFacing.Difference((DirType)(path->Command[0]<<5)) >> 5; + + if (!facediff) return; + + if (Dir_Facing(PrimaryFacing) & FACING_NE) { + ptr = &_dpath[(FacingType)ABS((int)facediff)-FACING_NE][1]; // Pointer to path adjust list. + counter = (int)_dpath[(FacingType)ABS((int)facediff)-FACING_NE][0]; // Number of path adjusts. + } else { + ptr = &_path[(FacingType)ABS((int)facediff)-FACING_NE][1]; // Pointer to path adjust list. + counter = (int)_path[(FacingType)ABS((int)facediff)-FACING_NE][0]; // Number of path adjusts. + } + ptr2 = ptr; + + ok = true; // Presume adjustment is all ok. + cell = Coord_Cell(Coord); // Starting cell. + nextpath = Dir_Facing(PrimaryFacing); // Starting path. + for (index = 0; index < counter; index++) { + + /* + ** Determine next path element and add it to the + ** working path list. + */ + if (facediff > 0) { + nextpath = nextpath + *ptr++; + } else { + nextpath = nextpath - *ptr++; + } + stage[index] = nextpath; + cell = Adjacent_Cell(cell, nextpath); + //cell = Coord_Cell(Adjacent_Cell(Cell_Coord(cell), nextpath)); + + /* + ** If it can't enter this cell, then abort the path + ** building operation without adjusting the unit's + ** path. + */ + if (Can_Enter_Cell(cell, nextpath) != MOVE_OK) { + ok = false; + break; + } + } + + /* + ** If veering to the left was not successful, then try veering + ** to the right. This only makes sense if the vehicle is trying + ** to turn 180 degrees. + */ + if (!ok && ABS(facediff) == 4) { + ptr = ptr2; // Pointer to path adjust list. + facediff = -facediff; + ok = true; // Presume adjustment is all ok. + cell = Coord_Cell(Coord); // Starting cell. + nextpath = Dir_Facing(PrimaryFacing); // Starting path. + for (index = 0; index < counter; index++) { + + /* + ** Determine next path element and add it to the + ** working path list. + */ + if (facediff > 0) { + nextpath = nextpath + *ptr++; + } else { + nextpath = nextpath - *ptr++; + } + stage[index] = nextpath; + cell = Coord_Cell(Adjacent_Cell(Cell_Coord(cell), nextpath)); + + /* + ** If it can't enter this cell, then abort the path + ** building operation without adjusting the unit's + ** path. + */ + if (Can_Enter_Cell(cell, nextpath) != MOVE_OK) { + ok = false; + break; + } + } + } + + /* + ** If a legal path addition was created, then install it in place + ** of the first path value. The initial path entry is to be replaced + ** with a sequence of path entries that create smooth turning. + */ + if (ok) { + if (path->Length <= 1) { + movmem(&stage[0], path->Command, MAX(counter, 1)); + path->Length = counter; + } else { + + /* + ** Optimize the transition path step from the smooth turn + ** first part as it joins with the rest of the normal + ** path. The normal prefix path steps are NOT to be optimized. + */ + if (counter) { + counter--; + path->Command[0] = stage[counter]; + Optimize_Moves(path, MOVE_OK); + } + + /* + ** If there is more than one prefix path element, then + ** insert the rest now. + */ + if (counter) { + movmem(&path->Command[0], &path->Command[counter], 40-counter); + movmem(&stage[0], &path->Command[0], counter); + path->Length += counter; + } + } + path->Command[path->Length] = FACING_NONE; + } +} + + +/*********************************************************************************************** + * DriveClass::Lay_Track -- Handles track laying logic for the unit. * + * * + * This routine handles the track laying for the unit. This entails examining the unit's * + * current location as well as the direction and whether this unit is allowed to lay * + * tracks in the first place. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +void DriveClass::Lay_Track(void) +{ +#ifdef NEVER + static IconCommandType *_trackdirs[8] = { + TrackN_S, + TrackNE_SW, + TrackE_W, + TrackNW_SE, + TrackN_S, + TrackNE_SW, + TrackE_W, + TrackNW_SE + }; + + if (!(ClassF & CLASSF_TRACKS)) return; + + Icon_Install(Coord_Cell(Coord), _trackdirs[Facing_To_8(BodyFacing)]); +#endif +} + + +/*********************************************************************************************** + * DriveClass::Mark_Track -- Marks the midpoint of the track as occupied. * + * * + * This routine will ensure that the midpoint (if any) of the track that the unit is * + * following, will be marked according to the mark type specified. * + * * + * INPUT: headto -- The head to coordinate. * + * * + * type -- The type of marking to perform. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/30/1995 JLB : Created. * + *=============================================================================================*/ +void DriveClass::Mark_Track(COORDINATE headto, MarkType type) +{ + int value; + + if (type == MARK_UP) { + value = false; + } else { + value = true; + } + + if (headto) { + if (!IsOnShortTrack && TrackNumber != -1) { + + /* + ** If we have not passed the per cell process point we need + ** to deal with it. + */ + int tracknum = TrackControl[TrackNumber].Track; + if (tracknum) { + TrackType const * ptr = RawTracks[tracknum - 1].Track; + int cellidx = RawTracks[tracknum - 1].Cell; + if (cellidx > -1) { + DirType dir = ptr[cellidx].Facing; + + if (TrackIndex < cellidx && cellidx != -1) { + COORDINATE offset = Smooth_Turn(ptr[cellidx].Offset, &dir); + Map[Coord_Cell(offset)].Flag.Occupy.Vehicle = value; + } + } + } + } + Map[Coord_Cell(headto)].Flag.Occupy.Vehicle = value; + } +} + + +/*********************************************************************************************** + * DriveClass::Offload_Tiberium_Bail -- Offloads one Tiberium quantum from the object. * + * * + * This routine will offload one Tiberium packet/quantum/bail from the object. Multiple * + * calls to this routine are needed in order to fully offload all Tiberium. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of credits offloaded for the one call. If zero is returned,* + * then this indicates that all Tiberium has been offloaded. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1995 JLB : Created. * + *=============================================================================================*/ +int DriveClass::Offload_Tiberium_Bail(void) +{ + if (Tiberium) { + Tiberium--; + if (House->IsHuman) { + return(UnitTypeClass::FULL_LOAD_CREDITS/UnitTypeClass::STEP_COUNT); + } + return(UnitTypeClass::FULL_LOAD_CREDITS+(UnitTypeClass::FULL_LOAD_CREDITS/3)/UnitTypeClass::STEP_COUNT); + } + return(0); +} + + +/*********************************************************************************************** + * DriveClass::Ok_To_Move -- Checks to see if this object can begin moving. * + * * + * This routine is used to verify that this object is allowed to move. Some objects can * + * be temporarily occupied and thus cannot move until the situation permits. * + * * + * INPUT: direction -- The direction that movement would be desired. * + * * + * OUTPUT: Can the unit move in the direction specified? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +bool DriveClass::Ok_To_Move(DirType ) const +{ + return true; +} + + +/*********************************************************************************************** + * DriveClass::Class_Of -- Fetches a reference to the class type for this object. * + * * + * This routine will fetch a reference to the TypeClass of this object. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with reference to the type class of this object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +ObjectTypeClass const & DriveClass::Class_Of(void) const +{ + return *Class; +} + + +/*************************************************************************** +** Smooth turn track tables. These are coordinate offsets from the center +** of the destination cell. These are the raw tracks that are modified +** by negating the X and Y portions as necessary. Also for reverse travelling +** direction, the track list can be processed backward. +** +** Track 1 = N +** Track 2 = NE +** Track 3 = N->NE 45 deg (double path consumption) +** Track 4 = N->E 90 deg (double path consumption) +** Track 5 = NE->SE 90 deg (double path consumption) +** Track 6 = NE->N 45 deg (double path consumption) +** Track 7 = N->NE (facing change only) +** Track 8 = NE->E (facing change only) +** Track 9 = N->E (facing change only) +** Track 10= NE->SE (facing change only) +** Track 11= back up into refinery +** Track 12= drive out of refinery +*/ +#pragma warn -ias +DriveClass::TrackType const DriveClass::Track1[24] = { + {0x00F50000L,(DirType)0}, + {0x00EA0000L,(DirType)0}, + {0x00DF0000L,(DirType)0}, + {0x00D40000L,(DirType)0}, + {0x00C90000L,(DirType)0}, + {0x00BE0000L,(DirType)0}, + {0x00B30000L,(DirType)0}, + {0x00A80000L,(DirType)0}, + {0x009D0000L,(DirType)0}, + {0x00920000L,(DirType)0}, + {0x00870000L,(DirType)0}, + {0x007C0000L,(DirType)0}, // Track jump check here. + {0x00710000L,(DirType)0}, + {0x00660000L,(DirType)0}, + {0x005B0000L,(DirType)0}, + {0x00500000L,(DirType)0}, + {0x00450000L,(DirType)0}, + {0x003A0000L,(DirType)0}, + {0x002F0000L,(DirType)0}, + {0x00240000L,(DirType)0}, + {0x00190000L,(DirType)0}, + {0x000E0000L,(DirType)0}, + {0x00030000L,(DirType)0}, + {0x00000000L,(DirType)0} +}; + +DriveClass::TrackType const DriveClass::Track2[] = { + {0x00F8FF08L,(DirType)32}, + {0x00F0FF10L,(DirType)32}, + {0x00E8FF18L,(DirType)32}, + {0x00E0FF20L,(DirType)32}, + {0x00D8FF28L,(DirType)32}, + {0x00D0FF30L,(DirType)32}, + {0x00C8FF38L,(DirType)32}, + {0x00C0FF40L,(DirType)32}, + {0x00B8FF48L,(DirType)32}, + {0x00B0FF50L,(DirType)32}, + {0x00A8FF58L,(DirType)32}, + {0x00A0FF60L,(DirType)32}, + {0x0098FF68L,(DirType)32}, + {0x0090FF70L,(DirType)32}, + {0x0088FF78L,(DirType)32}, + {0x0080FF80L,(DirType)32}, // Track jump check here. + {0x0078FF88L,(DirType)32}, + {0x0070FF90L,(DirType)32}, + {0x0068FF98L,(DirType)32}, + {0x0060FFA0L,(DirType)32}, + {0x0058FFA8L,(DirType)32}, + {0x0050FFB0L,(DirType)32}, + {0x0048FFB8L,(DirType)32}, + {0x0040FFC0L,(DirType)32}, + {0x0038FFC8L,(DirType)32}, + {0x0030FFD0L,(DirType)32}, + {0x0028FFD8L,(DirType)32}, + {0x0020FFE0L,(DirType)32}, + {0x0018FFE8L,(DirType)32}, + {0x0010FFF0L,(DirType)32}, + {0x0008FFF8L,(DirType)32}, + {0x00000000L,(DirType)32} +}; + +DriveClass::TrackType const DriveClass::Track3[] = { + {0x01F5FF00L,(DirType)0}, + {0x01EAFF00L,(DirType)0}, + {0x01DFFF00L,(DirType)0}, + {0x01D4FF00L,(DirType)0}, + {0x01C9FF00L,(DirType)0}, + {0x01BEFF00L,(DirType)0}, + {0x01B3FF00L,(DirType)0}, + {0x01A8FF00L,(DirType)0}, + {0x019DFF00L,(DirType)0}, + {0x0192FF00L,(DirType)0}, + {0x0187FF00L,(DirType)0}, + {0x0180FF00L,(DirType)0}, + {0x0175FF00L,(DirType)0}, // Jump entry point here. + {0x016BFF00L,(DirType)0}, + {0x0160FF02L,(DirType)1}, + {0x0155FF04L,(DirType)3}, + {0x014CFF06L,(DirType)4}, + {0x0141FF08L,(DirType)5}, + {0x0137FF0BL,(DirType)7}, + {0x012EFF0FL,(DirType)8}, + {0x0124FF13L,(DirType)9}, + {0x011AFF17L,(DirType)11}, + {0x0110FF1BL,(DirType)12}, + {0x0107FF1FL,(DirType)13}, // Center cell processing here. + {0x00FCFF24L,(DirType)15}, + {0x00F3FF28L,(DirType)16}, + {0x00ECFF2CL,(DirType)17}, + {0x00E0FF32L,(DirType)19}, + {0x00D7FF36L,(DirType)20}, + {0x00CFFF3DL,(DirType)21}, + {0x00C6FF42L,(DirType)23}, + {0x00BAFF49L,(DirType)24}, + {0x00B0FF4DL,(DirType)25}, + {0x00A8FF58L,(DirType)27}, + {0x00A0FF60L,(DirType)28}, + {0x0098FF68L,(DirType)29}, + {0x0090FF70L,(DirType)31}, + {0x0088FF78L,(DirType)32}, + {0x0080FF80L,(DirType)32}, // Track jump check here. + {0x0078FF88L,(DirType)32}, + {0x0070FF90L,(DirType)32}, + {0x0068FF98L,(DirType)32}, + {0x0060FFA0L,(DirType)32}, + {0x0058FFA8L,(DirType)32}, + {0x0050FFB0L,(DirType)32}, + {0x0048FFB8L,(DirType)32}, + {0x0040FFC0L,(DirType)32}, + {0x0038FFC8L,(DirType)32}, + {0x0030FFD0L,(DirType)32}, + {0x0028FFD8L,(DirType)32}, + {0x0020FFE0L,(DirType)32}, + {0x0018FFE8L,(DirType)32}, + {0x0010FFF0L,(DirType)32}, + {0x0008FFF8L,(DirType)32}, + {0x00000000L,(DirType)32} +}; + +DriveClass::TrackType const DriveClass::Track4[] = { + {0x00F5FF00L,(DirType)0}, + {0x00EBFF00L,(DirType)0}, + {0x00E0FF00L,(DirType)0}, + {0x00D5FF00L,(DirType)0}, + {0x00CBFF01L,(DirType)0}, + {0x00C0FF03L,(DirType)0}, + {0x00B5FF05L,(DirType)1}, + {0x00ABFF07L,(DirType)1}, + {0x00A0FF0AL,(DirType)2}, + {0x0095FF0DL,(DirType)3}, + {0x008BFF10L,(DirType)4}, + {0x0080FF14L,(DirType)5}, // Track entry here. + {0x0075FF18L,(DirType)8}, + {0x006DFF1CL,(DirType)12}, + {0x0063FF22L,(DirType)16}, + {0x005AFF25L,(DirType)20}, + {0x0052FF2BL,(DirType)23}, + {0x0048FF32L,(DirType)27}, + {0x0040FF37L,(DirType)32}, + {0x0038FF3DL,(DirType)36}, + {0x0030FF46L,(DirType)39}, + {0x002BFF4FL,(DirType)43}, + {0x0024FF58L,(DirType)47}, + {0x0020FF60L,(DirType)51}, + {0x001BFF6DL,(DirType)54}, + {0x0017FF79L,(DirType)57}, + {0x0014FF82L,(DirType)60}, // Track jump here. + {0x0011FF8FL,(DirType)62}, + {0x000DFF98L,(DirType)63}, + {0x0009FFA2L,(DirType)64}, + {0x0006FFACL,(DirType)64}, + {0x0004FFB5L,(DirType)66}, + {0x0003FFC0L,(DirType)64}, + {0x0002FFCBL,(DirType)64}, + {0x0001FFD5L,(DirType)64}, + {0x0000FFE0L,(DirType)64}, + {0x0000FFEBL,(DirType)64}, + {0x0000FFF5L,(DirType)64}, + {0x00000000L,(DirType)64} +}; + +DriveClass::TrackType const DriveClass::Track5[] = { + {0xFFF8FE08L,(DirType)32}, + {0xFFF0FE10L,(DirType)32}, + {0xFFE8FE18L,(DirType)32}, + {0xFFE0FE20L,(DirType)32}, + {0xFFD8FE28L,(DirType)32}, + {0xFFD0FE30L,(DirType)32}, + {0xFFC8FE38L,(DirType)32}, + {0xFFC0FE40L,(DirType)32}, + {0xFFB8FE48L,(DirType)32}, + {0xFFB0FE50L,(DirType)32}, + {0xFFA8FE58L,(DirType)32}, + {0xFFA0FE60L,(DirType)32}, + {0xFF98FE68L,(DirType)32}, + {0xFF90FE70L,(DirType)32}, + {0xFF88FE78L,(DirType)32}, + {0xFF80FE80L,(DirType)32}, // Track entry here. + {0xFF78FE88L,(DirType)32}, + {0xFF71FE90L,(DirType)32}, + {0xFF6AFE97L,(DirType)32}, + {0xFF62FE9FL,(DirType)32}, + {0xFF5AFEA8L,(DirType)32}, + {0xFF53FEB0L,(DirType)35}, + {0xFF4BFEB7L,(DirType)38}, + {0xFF44FEBEL,(DirType)41}, + {0xFF3EFEC4L,(DirType)44}, + {0xFF39FECEL,(DirType)47}, + {0xFF34FED8L,(DirType)50}, + {0xFF30FEE0L,(DirType)53}, + {0xFF2DFEEBL,(DirType)56}, + {0xFF2CFEF5L,(DirType)59}, + {0xFF2BFF00L,(DirType)62}, + {0xFF2CFF0BL,(DirType)66}, + {0xFF2DFF15L,(DirType)69}, + {0xFF30FF1FL,(DirType)72}, + {0xFF34FF28L,(DirType)75}, + {0xFF39FF30L,(DirType)78}, + {0xFF3EFF3AL,(DirType)81}, + {0xFF44FF44L,(DirType)84}, + {0xFF4BFF4BL,(DirType)87}, + {0xFF53FF50L,(DirType)90}, + {0xFF5AFF58L,(DirType)93}, + {0xFF62FF60L,(DirType)96}, + {0xFF6AFF68L,(DirType)96}, + {0xFF71FF70L,(DirType)96}, + {0xFF78FF78L,(DirType)96}, + {0xFF80FF80L,(DirType)96}, // Track jump check here. + {0xFF88FF88L,(DirType)96}, + {0xFF90FF90L,(DirType)96}, + {0xFF98FF98L,(DirType)96}, + {0xFFA0FFA0L,(DirType)96}, + {0xFFA8FFA8L,(DirType)96}, + {0xFFB0FFB0L,(DirType)96}, + {0xFFB8FFB8L,(DirType)96}, + {0xFFC0FFC0L,(DirType)96}, + {0xFFC8FFC8L,(DirType)96}, + {0xFFD0FFD0L,(DirType)96}, + {0xFFD8FFD8L,(DirType)96}, + {0xFFE0FFE0L,(DirType)96}, + {0xFFE8FFE8L,(DirType)96}, + {0xFFF0FFF0L,(DirType)96}, + {0xFFF8FFF8L,(DirType)96}, + {0x00000000L,(DirType)96} +}; + +DriveClass::TrackType const DriveClass::Track6[] = { + {0x0100FE00L,(DirType)32}, + {0x00F8FE08L,(DirType)32}, + {0x00F0FE10L,(DirType)32}, + {0x00E8FE18L,(DirType)32}, + {0x00E0FE20L,(DirType)32}, + {0x00D8FE28L,(DirType)32}, + {0x00D0FE30L,(DirType)32}, + {0x00C8FE38L,(DirType)32}, + {0x00C0FE40L,(DirType)32}, + {0x00B8FE48L,(DirType)32}, + {0x00B0FE50L,(DirType)32}, + {0x00A8FE58L,(DirType)32}, + {0x00A0FE60L,(DirType)32}, + {0x0098FE68L,(DirType)32}, + {0x0090FE70L,(DirType)32}, + {0x0088FE78L,(DirType)32}, + {0x0080FE80L,(DirType)32}, // Jump entry point here. + {0x0078FE88L,(DirType)32}, + {0x0070FE90L,(DirType)32}, + {0x0068FE98L,(DirType)32}, + {0x0060FEA0L,(DirType)32}, + {0x0058FEA8L,(DirType)32}, + {0x0055FEAEL,(DirType)32}, + {0x004EFEB8L,(DirType)35}, + {0x0048FEC0L,(DirType)37}, + {0x0042FEC9L,(DirType)40}, + {0x003BFED2L,(DirType)43}, + {0x0037FEDAL,(DirType)45}, + {0x0032FEE3L,(DirType)48}, + {0x002BFEEBL,(DirType)51}, + {0x0026FEF5L,(DirType)53}, + {0x0022FEFEL,(DirType)56}, + {0x001CFF08L,(DirType)59}, + {0x0019FF12L,(DirType)61}, + {0x0015FF1BL,(DirType)64}, + {0x0011FF26L,(DirType)64}, + {0x000EFF30L,(DirType)64}, + {0x000BFF39L,(DirType)64}, + {0x0009FF43L,(DirType)64}, + {0x0007FF4EL,(DirType)64}, + {0x0005FF57L,(DirType)64}, + {0x0003FF62L,(DirType)64}, + {0x0001FF6DL,(DirType)64}, + {0x0000FF77L,(DirType)64}, + {0x0000FF80L,(DirType)64}, // Track jump check here. + {0x0000FF8BL,(DirType)64}, + {0x0000FF95L,(DirType)64}, + {0x0000FFA0L,(DirType)64}, + {0x0000FFABL,(DirType)64}, + {0x0000FFB5L,(DirType)64}, + {0x0000FFC0L,(DirType)64}, + {0x0000FFCBL,(DirType)64}, + {0x0000FFD5L,(DirType)64}, + {0x0000FFE0L,(DirType)64}, + {0x0000FFEBL,(DirType)64}, + {0x0000FFF5L,(DirType)64}, + {0x00000000L,(DirType)64} +}; + +DriveClass::TrackType const DriveClass::Track7[] = { + {0x0006FFFFL,(DirType)0}, + {0x000CFFFEL,(DirType)4}, + {0x0011FFFCL,(DirType)8}, + {0x0018FFFAL,(DirType)12}, + {0x001FFFF6L,(DirType)16}, + {0x0024FFF3L,(DirType)19}, + {0x002BFFF0L,(DirType)22}, + {0x0030FFFDL,(DirType)23}, + {0x0035FFEBL,(DirType)24}, + {0x0038FFE8L,(DirType)25}, + {0x003CFFE6L,(DirType)26}, + {0x0040FFE3L,(DirType)27}, + {0x0043FFE0L,(DirType)28}, + {0x0046FFDDL,(DirType)29}, + {0x0043FFDFL,(DirType)30}, + {0x0040FFE1L,(DirType)30}, + {0x003CFFE3L,(DirType)30}, + {0x0038FFE5L,(DirType)30}, + {0x0035FFE7L,(DirType)31}, + {0x0030FFE9L,(DirType)31}, + {0x002BFFEBL,(DirType)31}, + {0x0024FFEDL,(DirType)31}, + {0x001FFFF1L,(DirType)31}, + {0x0018FFF4L,(DirType)32}, + {0x0011FFF7L,(DirType)32}, + {0x000CFFFAL,(DirType)32}, + {0x0006FFFDL,(DirType)32}, + {0x00000000L,(DirType)32} +}; + +DriveClass::TrackType const DriveClass::Track8[] = { + {0x0003FFFCL,(DirType)32}, + {0x0006FFF7L,(DirType)36}, + {0x000AFFF1L,(DirType)40}, + {0x000CFFEBL,(DirType)44}, + {0x000DFFE4L,(DirType)46}, + {0x000EFFDCL,(DirType)48}, + {0x000FFFD5L,(DirType)50}, + {0x0010FFD0L,(DirType)52}, + {0x0011FFC9L,(DirType)54}, + {0x0012FFC2L,(DirType)56}, + {0x0011FFC0L,(DirType)58}, + {0x0010FFC2L,(DirType)60}, + {0x000EFFC9L,(DirType)62}, + {0x000CFFCFL,(DirType)64}, + {0x000AFFD5L,(DirType)64}, + {0x0008FFDAL,(DirType)64}, + {0x0006FFE2L,(DirType)64}, + {0x0004FFE9L,(DirType)64}, + {0x0002FFEFL,(DirType)64}, + {0x0001FFF5L,(DirType)64}, + {0x0000FFF9L,(DirType)64}, + {0x00000000L,(DirType)64} +}; + +DriveClass::TrackType const DriveClass::Track9[] = { + {0xFFF50002L,(DirType)0}, + {0xFFEB0004L,(DirType)2}, + {0xFFE00006L,(DirType)4}, + {0xFFD50009L,(DirType)6}, + {0xFFCE000CL,(DirType)9}, + {0xFFC8000FL,(DirType)11}, + {0xFFC00012L,(DirType)13}, + {0xFFB80015L,(DirType)16}, + {0xFFC00012L,(DirType)18}, + {0xFFC8000EL,(DirType)20}, + {0xFFCE000AL,(DirType)22}, + {0xFFD50004L,(DirType)24}, + {0xFFDE0000L,(DirType)26}, + {0xFFE9FFF8L,(DirType)28}, + {0xFFEEFFF2L,(DirType)30}, + {0xFFF5FFEBL,(DirType)32}, + {0xFFFDFFE1L,(DirType)34}, + {0x0002FFD8L,(DirType)36}, + {0x0007FFD2L,(DirType)39}, + {0x000BFFCBL,(DirType)41}, + {0x0010FFC5L,(DirType)43}, + {0x0013FFBEL,(DirType)45}, + {0x0015FFB7L,(DirType)48}, + {0x0013FFBEL,(DirType)50}, + {0x0011FFC5L,(DirType)52}, + {0x000BFFCCL,(DirType)54}, + {0x0008FFD4L,(DirType)56}, + {0x0005FFDFL,(DirType)58}, + {0x0003FFEBL,(DirType)62}, + {0x0001FFF5L,(DirType)64}, + {0x00000000L,(DirType)64} +}; + +DriveClass::TrackType const DriveClass::Track10[] = { + {0xFFF6000BL,(DirType)32}, + {0xFFF00015L,(DirType)37}, + {0xFFEB0020L,(DirType)42}, + {0xFFE9002BL,(DirType)47}, + {0xFFE50032L,(DirType)52}, + {0xFFE30038L,(DirType)57}, + {0xFFE00040L,(DirType)60}, + {0xFFE20038L,(DirType)62}, + {0xFFE40032L,(DirType)64}, + {0xFFE5002AL,(DirType)68}, + {0xFFE6001EL,(DirType)70}, + {0xFFE70015L,(DirType)72}, + {0xFFE8000BL,(DirType)74}, + {0xFFE90000L,(DirType)76}, + {0xFFE8FFF5L,(DirType)78}, + {0xFFE7FFEBL,(DirType)80}, + {0xFFE6FFE0L,(DirType)82}, + {0xFFE5FFD5L,(DirType)84}, + {0xFFE4FFCEL,(DirType)86}, + {0xFFE2FFC5L,(DirType)88}, + {0xFFE0FFC0L,(DirType)90}, + {0xFFE3FFC5L,(DirType)92}, + {0xFFE5FFCEL,(DirType)94}, + {0xFFE9FFD5L,(DirType)95}, + {0xFFEBFFE0L,(DirType)96}, + {0xFFF0FFEBL,(DirType)96}, + {0xFFF6FFF5L,(DirType)96}, + {0x00000000L,(DirType)96} +}; + +DriveClass::TrackType const DriveClass::Track11[] = { + {0x01000000L,DIR_SW}, + {0x00F30008L,DIR_SW}, + {0x00E50010L,DIR_SW_X1}, + {0x00D60018L,DIR_SW_X1}, + {0x00C80020L,DIR_SW_X1}, + {0x00B90028L,DIR_SW_X1}, + {0x00AB0030L,DIR_SW_X2}, + {0x009C0038L,DIR_SW_X2}, + {0x008D0040L,DIR_SW_X2}, + {0x007F0048L,DIR_SW_X2}, + {0x00710050L,DIR_SW_X2}, + {0x00640058L,DIR_SW_X2}, + {0x00550060L,DIR_SW_X2}, + + {0x00000000L,DIR_SW_X2} +}; + +DriveClass::TrackType const DriveClass::Track12[] = { + {0xFF550060L,DIR_SW_X2}, + {0xFF640058L,DIR_SW_X2}, + {0xFF710050L,DIR_SW_X2}, + {0xFF7F0048L,DIR_SW_X2}, + {0xFF8D0040L,DIR_SW_X2}, + {0xFF9C0038L,DIR_SW_X2}, + {0xFFAB0030L,DIR_SW_X2}, + {0xFFB90028L,DIR_SW_X1}, + {0xFFC80020L,DIR_SW_X1}, + {0xFFD60018L,DIR_SW_X1}, + {0xFFE50010L,DIR_SW_X1}, + {0xFFF30008L,DIR_SW}, + + {0x00000000L,DIR_SW} +}; + +/* +** Drive out of weapon's factory. +*/ +DriveClass::TrackType const DriveClass::Track13[] = { + {XYP_COORD(10,-21),(DirType)(DIR_SW-10)}, + {XYP_COORD(10,-21),(DirType)(DIR_SW-10)}, + {XYP_COORD(10,-20),(DirType)(DIR_SW-10)}, + {XYP_COORD(10,-20),(DirType)(DIR_SW-10)}, + {XYP_COORD(9,-18),(DirType)(DIR_SW-10)}, + {XYP_COORD(9,-18),(DirType)(DIR_SW-10)}, + {XYP_COORD(9,-17),(DirType)(DIR_SW-10)}, + {XYP_COORD(8,-16),(DirType)(DIR_SW-10)}, + {XYP_COORD(8,-15),(DirType)(DIR_SW-10)}, + {XYP_COORD(7,-14),(DirType)(DIR_SW-10)}, + {XYP_COORD(7,-13),(DirType)(DIR_SW-10)}, + {XYP_COORD(6,-12),(DirType)(DIR_SW-10)}, + {XYP_COORD(6,-11),(DirType)(DIR_SW-10)}, + {XYP_COORD(5,-10),(DirType)(DIR_SW-10)}, + {XYP_COORD(5,-9),(DirType)(DIR_SW-10)}, + {XYP_COORD(4,-8),(DirType)(DIR_SW-10)}, + {XYP_COORD(4,-7),(DirType)(DIR_SW-10)}, + {XYP_COORD(3,-6),(DirType)(DIR_SW-10)}, + {XYP_COORD(3,-5),(DirType)(DIR_SW-9)}, + {XYP_COORD(2,-4),(DirType)(DIR_SW-7)}, + {XYP_COORD(2,-3),(DirType)(DIR_SW-5)}, + {XYP_COORD(1,-2),(DirType)(DIR_SW-3)}, + {XYP_COORD(1,-1),(DirType)(DIR_SW-1)}, + + {0x00000000L,DIR_SW} +}; + + +/* +** There are a limited basic number of tracks that a vehicle can follow. These +** are they. Each track can be interpreted differently but this is controlled +** by the TrackControl structure elaborated elsewhere. +*/ +DriveClass::RawTrackType const DriveClass::RawTracks[13] = { + {Track1, -1, 0, -1}, + {Track2, -1, 0, -1}, + {Track3, 37, 12, 22}, + {Track4, 26, 11, 19}, + {Track5, 45, 15, 31}, + {Track6, 44, 16, 27}, + {Track7, -1, 0, -1}, + {Track8, -1, 0, -1}, + {Track9, -1, 0, -1}, + {Track10, -1, 0, -1}, + {Track11, -1, 0, -1}, + {Track12, -1, 0, -1}, + {Track13, -1, 0, -1} +}; + + +/*************************************************************************** +** Smooth turning control table. Given two directions in a path list, this +** table determines which track to use and what modifying operations need +** be performed on the track data. +*/ +DriveClass::TurnTrackType const DriveClass::TrackControl[67] = { + {1, 0, DIR_N, F_}, // 0-0 + {3, 7, DIR_NE, F_D}, // 0-1 (raw chart) + {4, 9, DIR_E, F_D}, // 0-2 (raw chart) + {0, 0, DIR_SE, F_}, // 0-3 ! + {0, 0, DIR_S, F_}, // 0-4 ! + {0, 0, DIR_SW, F_}, // 0-5 ! + {4, 9, DIR_W, (DriveClass::TrackControlType)(F_X|F_D)}, // 0-6 + {3, 7, DIR_NW, (DriveClass::TrackControlType)(F_X|F_D)}, // 0-7 + {6, 8, DIR_N, (DriveClass::TrackControlType)(F_T|F_X|F_Y|F_D)}, // 1-0 + {2, 0, DIR_NE, F_}, // 1-1 (raw chart) + {6, 8, DIR_E, F_D}, // 1-2 (raw chart) + {5, 10, DIR_SE, F_D}, // 1-3 (raw chart) + {0, 0, DIR_S, F_}, // 1-4 ! + {0, 0, DIR_SW, F_}, // 1-5 ! + {0, 0, DIR_W, F_}, // 1-6 ! + {5, 10, DIR_NW, (DriveClass::TrackControlType)(F_T|F_X|F_Y|F_D)}, // 1-7 + {4, 9, DIR_N, (DriveClass::TrackControlType)(F_T|F_X|F_Y|F_D)}, // 2-0 + {3, 7, DIR_NE, (DriveClass::TrackControlType)(F_T|F_X|F_Y|F_D)}, // 2-1 + {1, 0, DIR_E, (DriveClass::TrackControlType)(F_T|F_X)}, // 2-2 + {3, 7, DIR_SE, (DriveClass::TrackControlType)(F_T|F_X|F_D)}, // 2-3 + {4, 9, DIR_S, (DriveClass::TrackControlType)(F_T|F_X|F_D)}, // 2-4 + {0, 0, DIR_SW, F_}, // 2-5 ! + {0, 0, DIR_W, F_}, // 2-6 ! + {0, 0, DIR_NW, F_}, // 2-7 ! + {0, 0, DIR_N, F_}, // 3-0 ! + {5, 10, DIR_NE, (DriveClass::TrackControlType)(F_Y|F_D)}, // 3-1 + {6, 8, DIR_E, (DriveClass::TrackControlType)(F_Y|F_D)}, // 3-2 + {2, 0, DIR_SE, F_Y}, // 3-3 + {6, 8, DIR_S, (DriveClass::TrackControlType)(F_T|F_X|F_D)}, // 3-4 + {5, 10, DIR_SW, (DriveClass::TrackControlType)(F_T|F_X|F_D)}, // 3-5 + {0, 0, DIR_W, F_}, // 3-6 ! + {0, 0, DIR_NW, F_}, // 3-7 ! + {0, 0, DIR_N, F_}, // 4-0 ! + {0, 0, DIR_NE, F_}, // 4-1 ! + {4, 9, DIR_E, (DriveClass::TrackControlType)(F_Y|F_D)}, // 4-2 + {3, 7, DIR_SE, (DriveClass::TrackControlType)(F_Y|F_D)}, // 4-3 + {1, 0, DIR_S, F_Y}, // 4-4 + {3, 7, DIR_SW, (DriveClass::TrackControlType)(F_X|F_Y|F_D)}, // 4-5 + {4, 9, DIR_W, (DriveClass::TrackControlType)(F_X|F_Y|F_D)}, // 4-6 + {0, 0, DIR_NW, F_}, // 4-7 ! + {0, 0, DIR_N, F_}, // 5-0 ! + {0, 0, DIR_NE, F_}, // 5-1 ! + {0, 0, DIR_E, F_}, // 5-2 ! + {5, 10, DIR_SE, (DriveClass::TrackControlType)(F_T|F_D)}, // 5-3 + {6, 8, DIR_S, (DriveClass::TrackControlType)(F_T|F_D)}, // 5-4 + {2, 0, DIR_SW, F_T}, // 5-5 + {6, 8, DIR_W, (DriveClass::TrackControlType)(F_X|F_Y|F_D)}, // 5-6 + {5, 10, DIR_NW, (DriveClass::TrackControlType)(F_X|F_Y|F_D)}, // 5-7 + {4, 9, DIR_N, (DriveClass::TrackControlType)(F_T|F_Y|F_D)}, // 6-0 + {0, 0, DIR_NE, F_}, // 6-1 ! + {0, 0, DIR_E, F_}, // 6-2 ! + {0, 0, DIR_SE, F_}, // 6-3 ! + {4, 9, DIR_S, (DriveClass::TrackControlType)(F_T|F_D)}, // 6-4 + {3, 7, DIR_SW, (DriveClass::TrackControlType)(F_T|F_D)}, // 6-5 + {1, 0, DIR_W, F_T}, // 6-6 + {3, 7, DIR_NW, (DriveClass::TrackControlType)(F_T|F_Y|F_D)}, // 6-7 + {6, 8, DIR_N, (DriveClass::TrackControlType)(F_T|F_Y|F_D)}, // 7-0 + {5, 10, DIR_NE, (DriveClass::TrackControlType)(F_T|F_Y|F_D)}, // 7-1 + {0, 0, DIR_E, F_}, // 7-2 ! + {0, 0, DIR_SE, F_}, // 7-3 ! + {0, 0, DIR_S, F_}, // 7-4 ! + {5, 10, DIR_SW, (DriveClass::TrackControlType)(F_X|F_D)}, // 7-5 + {6, 8, DIR_W, (DriveClass::TrackControlType)(F_X|F_D)}, // 7-6 + {2, 0, DIR_NW, F_X}, // 7-7 + + {11, 11, DIR_SW, F_}, // Backup harvester into refinery. + {12, 12, DIR_SW_X2, F_}, // Drive back into refinery. + {13, 13, DIR_SW, F_} // Drive out of weapons factory. +}; + diff --git a/DRIVE.H b/DRIVE.H new file mode 100644 index 0000000..4a8225b --- /dev/null +++ b/DRIVE.H @@ -0,0 +1,215 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\drive.h_v 2.19 16 Oct 1995 16:47:44 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : DRIVE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 14, 1994 * + * * + * Last Update : April 14, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef DRIVE_H +#define DRIVE_H + +#include "foot.h" + +/**************************************************************************** +** Movable objects are handled by this class definition. Moveable objects +** cover everything except buildings. +*/ +class DriveClass : public FootClass +{ + public: + /* + ** This points to the static control data that gives 'this' unit its characteristics. + */ + UnitTypeClass const * const Class; + + /* + ** This records the number of "loads" of Tiberium the unit is carrying. Only + ** harvesters use this field. + */ + unsigned char Tiberium; + + /* + ** If this unit performing harvesting action, then this flag is true. The flag + ** is located here because the other bit flags here give it a free place to + ** reside. + */ + unsigned IsHarvesting:1; + + /* + ** This flags when a transport vehicle could not unload at its designated location + ** and is heading off the map to try again later. When this flag is true, the + ** transport unit is allowed to disappear when it reaches the edge of the map. + */ + unsigned IsReturning:1; + + /* + ** Some units must have their turret locked down to face their body direction. + ** When this flag is set, this condition is in effect. This flag is a more + ** accurate check than examining the TrackNumber since the turret may be + ** rotating into position so that a pending track may start. During this process + ** the track number does not indicate anything. + */ + unsigned IsTurretLockedDown:1; + + /* + ** This vehicle could be processing a "short track". A short track is one that + ** doesn't actually go anywhere. Kind of like turning in place. + */ + unsigned IsOnShortTrack:1; + + /*--------------------------------------------------------------------- + ** Constructors, Destructors, and overloaded operators. + */ + DriveClass(void); + DriveClass(UnitType classid, HousesType house); + virtual ~DriveClass(void) {}; + operator UnitType(void) const {return Class->Type;}; + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + virtual int Offload_Tiberium_Bail(void); + void Do_Turn(DirType dir); + virtual void Approach_Target(void); + virtual ObjectTypeClass const & Class_Of(void) const; + virtual void Overrun_Square(CELL cell, bool threaten=true); + virtual void Assign_Destination(TARGET target); + virtual void Per_Cell_Process(bool center); + virtual bool Ok_To_Move(DirType ) const; + virtual void AI(void); + #ifdef CHEAT_KEYS + virtual void Debug_Dump(MonoClass *mono) const; + #endif + void Force_Track(int track, COORDINATE coord); + virtual int Tiberium_Load(void) const; + + void Exit_Map(void); + void Mark_Track(COORDINATE headto, MarkType type); + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /********************************************************************** + ** These enumerations are used as working constants that exist only + ** in the DriveClass namespace. + */ + enum DriveClassEnum { + BACKUP_INTO_REFINERY=64, // Track to backup into refinery. + OUT_OF_REFINERY, // Track to leave refinery. + OUT_OF_WEAPON_FACTORY // Track to leave weapons factory. + }; + + private: + + /**************************************************************************** + ** Smooth turning tracks are controlled by this structure and these + ** processing bits. + */ + typedef enum TrackControlType { + F_=0x00, // No translation necessary? + F_T=0x01, // Transpose X and Y components? + F_X=0x02, // Reverse X component sign? + F_Y=0x04, // Reverse Y component sign? + F_D=0x08 // Two cell consumption? + } TrackControlType; + //#define F_S 0x10 // Is this a 90 degree turn? + + typedef struct { + char Track; // Which track to use. + char StartTrack; // Track when starting from stand-still. + DirType Facing; // Facing when track has been completed. + DriveClass::TrackControlType Flag; // List processing flag bits. + } TurnTrackType; + + typedef struct { + COORDINATE Offset; // Offset to origin coordinate. + DirType Facing; // Facing (primary track). + } TrackType; + + typedef struct { + DriveClass::TrackType const * Track; // Pointer to track list. + int Jump; // Index where track jumping is allowed. + int Entry; // Entry point if jumping to this track. + int Cell; // Per cell process should occur at this index. + } RawTrackType; + + /* + ** These speed values are used to accumulate movement and then + ** convert them into pixel "steps" that are then translated through + ** the currently running track so that the unit will move. + */ + unsigned char SpeedAccum; + + /* + ** This the track control logic (used for ground vehicles only). The 'Track' + ** variable holds the track being followed (0 == not following track). The + ** 'TrackIndex' variable holds the current index into the specified track + ** (starts at 0). + */ + char TrackNumber; + char TrackIndex; + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + virtual void Fixup_Path(PathType *path); + bool While_Moving(void); + bool Start_Of_Move(void); + void Lay_Track(void); + COORDINATE Smooth_Turn(COORDINATE adj, DirType *dir); + + static TurnTrackType const TrackControl[67]; + static RawTrackType const RawTracks[13]; + static TrackType const Track13[]; + static TrackType const Track12[]; + static TrackType const Track11[]; + static TrackType const Track10[]; + static TrackType const Track9[]; + static TrackType const Track8[]; + static TrackType const Track7[]; + static TrackType const Track6[]; + static TrackType const Track5[]; + static TrackType const Track4[]; + static TrackType const Track3[]; + static TrackType const Track2[]; + static TrackType const Track1[24]; +}; + +inline DriveClass::TrackControlType operator |(DriveClass::TrackControlType, DriveClass::TrackControlType); +inline DriveClass::TrackControlType operator &(DriveClass::TrackControlType, DriveClass::TrackControlType); +inline DriveClass::TrackControlType operator ~(DriveClass::TrackControlType); + + +#endif diff --git a/EDIT.CPP b/EDIT.CPP new file mode 100644 index 0000000..1c099af --- /dev/null +++ b/EDIT.CPP @@ -0,0 +1,471 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\edit.cpv 2.18 16 Oct 1995 16:48:16 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : EDIT.CPP * + * * + * Programmer : Joe L. Bostic, Maria del Mar McCready Legg * + * * + * Start Date : 01/15/95 * + * * + * Last Update : June 25, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * EditClass::Action -- Handles input events. * + * EditClass::Draw_Background -- Draw the background to the edit gadget. * + * EditClass::Draw_Me -- Draws the edit box and embedded text. * + * EditClass::Draw_Text -- Draws the edit gadget text. * + * EditClass::EditClass -- Normal constructor for edit class object. * + * EditClass::Handle_Key -- Handles keyboard input to edit gadget. * + * EditClass::Set_Text -- Sets the text to the edit gadget. * + * EditClass::~EditClass -- Default destructor for the edit gadget. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * EditClass::EditClass -- Normal constructor for edit class object. * + * * + * This is the normal constructor used to create an edit object. * + * * + * INPUT: id -- The ID number for this edit object. This is the ID number that will be * + * returned by the Input() function when the key is pressed if this * + * gadget has the keyboard input focus. * + * * + * text -- Referenct to the text buffer that the edit gadget will modify as keyboard * + * input is processed. The value that this buffer contains is the default * + * text displayed. * + * * + * maxlen-- The maximum size of the text buffer specified. This length INCLUDES the * + * trailing null character so a simple sizeof() function call can be used. * + * * + * flags -- These are the text print control flags. It is used to control how the * + * text looks in the edit box. Use the normal TPF_??? flags. * + * * + * x,y -- The pixel coordinates of the upper left corner of the edit gadget area. * + * * + * w,h -- The pixel dimensions of the edit box. If either of these are no provided, * + * or set to -1, then the dimension is determined from the string itself. * + * * + * sytle -- This style flag parameter control what kind of characters are allowed in * + * the edit box. The initial string in the text buffer may contain illegal * + * characters, but they are NOT removed regardless of this parameter. * + * * + * OUTPUT: none * + * WARNINGS: none * + * HISTORY: * + * 01/05/1995 MML : Created. * + * 01/21/1995 JLB : Modified. * + *=============================================================================================*/ +EditClass::EditClass(int id, char * text, int max_len, TextPrintType flags, int x, int y, int w, int h, EditStyle style) : + ControlClass (id, x, y, w, h, LEFTPRESS), String(text) +{ + TextFlags = flags; + EditFlags = style; + Color = CC_GREEN; + Set_Text(text, max_len); + + if (w == -1 || h == -1) { + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, TextFlags); + + if (h == -1) { + Height = FontHeight+2; + } + if (w == -1) { + if (strlen(String) > 0) { + Width = String_Pixel_Width(String) + 6; + } else { + Width = ((Char_Pixel_Width('X')+FontXSpacing) * (MaxLength+1)) + 2; + } + } + } + IsReadOnly = 0; +} + + +/*********************************************************************************************** + * EditClass::~EditClass -- Default destructor for the edit gadget. * + * * + * This default destructor removes the focus setting if it currently has it. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/24/1995 JLB : Created. * + *=============================================================================================*/ +EditClass::~EditClass(void) +{ + if (Has_Focus()) { + Clear_Focus(); + } +} + + +/*********************************************************************************************** + * EditClass::Set_Text -- Sets the text to the edit gadget. * + * * + * Use this routine to change the text that this edit gadget refers to. * + * * + * INPUT: text -- Reference to the character array that this edit gadget will be * + * modifying. * + * max_len -- The maximum size of the buffer that will be modified. * + * * + * OUTPUT: none * + * WARNINGS: none * + * HISTORY: * + * 01/21/1995 JLB : Created. * + *=============================================================================================*/ +void EditClass::Set_Text(char * text, int max_len) +{ + String = text; + MaxLength = max_len-1; + Length = strlen(String); + Flag_To_Redraw(); +} + + +/*********************************************************************************************** + * EditClass::Draw_Me -- Draws the edit box and embedded text. * + * * + * This routine will render the edit box. This will show the box outline as well as any * + * text it may contain. * + * * + * INPUT: forced -- Should the edit box be drawn even if it thinks it doesn't have to? * + * * + * OUTPUT: Was the edit box drawn? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +int EditClass::Draw_Me(int forced) +{ + if (ControlClass::Draw_Me(forced)) { + /* + ** Hide the mouse. + */ + if (LogicPage == &SeenBuff) { + Conditional_Hide_Mouse(X, Y, X+Width, Y+Height); + } + + /* + ** Draw the body & set text color. + */ + Draw_Background(); + + /* + ** Display the text. + */ + Draw_Text(String); + + /* + ** Display the mouse. + */ + if (LogicPage == &SeenBuff) { + Conditional_Show_Mouse(); + } + + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * EditClass::Action -- Handles input events. * + * * + * This routine will handle all mouse and keyboard events directed at this edit box * + * gadget. For keyboard events, this will insert the characters into the edit box. * + * * + * INPUT: flags -- The event flag that triggered this function call. * + * * + * key -- Reference to the keyboard/mouse event that triggered this function call. * + * * + * OUTPUT: Should the list be processed further? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +int EditClass::Action(unsigned flags, KeyNumType & key) +{ + + /* + ** If this is a read-only edit box, it's a display-only device + */ + if (IsReadOnly) { + return(false); + } + + /* + ** If the left mouse button is pressed over this gadget, then set the focus to + ** this gadget. The event flag is cleared so that no button ID number is returned. + */ + if ((flags & LEFTPRESS)) { + flags &= ~LEFTPRESS; + Set_Focus(); + Flag_To_Redraw(); // force to draw cursor + } + + /* + ** Handle keyboard events here. Normally, the key is added to the string, but if the + ** RETURN key is pressed, then the button ID number is returned from the Input() + ** function. + */ + if ((flags & KEYBOARD) && Has_Focus()) { + + /* + ** Process the keyboard character. If indicated, consume this keyboard event + ** so that the edit gadget ID number is not returned. + */ + if (key == KN_ESC) { + + Clear_Focus(); + flags = 0; + + } else { + + KeyASCIIType ascii = (KeyASCIIType)(Keyboard::To_ASCII(key) & 0x00ff); + + /* + ** Allow numeric keypad presses to map to ascii numbers + */ + if ((key & WWKEY_VK_BIT) && ascii >='0' && ascii <= '9'){ + key &= ~WWKEY_VK_BIT; + + if ( (!(flags & LEFTRELEASE)) && (!(flags & RIGHTRELEASE))){ + if (Handle_Key (ascii) ) { + flags &= ~KEYBOARD; + key = KN_NONE; + } + } + + }else{ + + /* + ** Filter out all special keys except return and backspace + */ + if ((!(key & WWKEY_VK_BIT) && ascii >= ' ' && ascii <= 127) + || key == KN_RETURN || key == KN_BACKSPACE){ + + + if ( (!(flags & LEFTRELEASE)) && (!(flags & RIGHTRELEASE))){ + if (Handle_Key(Keyboard::To_ASCII(key))) { + flags &= ~KEYBOARD; + key = KN_NONE; + } + } + }else{ + //if (key & WWKEY_RLS_BIT){ + // if ( (!(flags & LEFTRELEASE)) && (!(flags & RIGHTRELEASE))){ + flags &= ~KEYBOARD; + key = KN_NONE; + // } + //} + } + } + } + } + + return(ControlClass::Action(flags, key)); +} + + +/*********************************************************************************************** + * EditClass::Draw_Background -- Draw the background to the edit gadget. * + * * + * This routine will redraw the edit gadget background. The overlaying text is handled by * + * a different routine. The mouse is guaranteed to be hidden when this routine is called. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/21/1995 JLB : Created. * + *=============================================================================================*/ +void EditClass::Draw_Background(void) +{ + Draw_Box (X, Y, Width, Height, BOXSTYLE_GREEN_BOX, true); +} + + +/*********************************************************************************************** + * EditClass::Draw_Text -- Draws the edit gadget text. * + * * + * This routine is called when the edit gadget text needs to be drawn. The background has * + * already been drawn by the time this function is called. The mouse is guaranteed to be * + * hidden as well. * + * * + * INPUT: text -- The text to draw in the edit gadget. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/21/1995 JLB : Created. * + *=============================================================================================*/ +void EditClass::Draw_Text(char const * text) +{ + if (FontPtr == GradFont6Ptr) { + TextPrintType flags; + + if (Has_Focus()) { + flags = TPF_BRIGHT_COLOR; + } else { + flags = (TextPrintType)0; + } + + Conquer_Clip_Text_Print(text, X+1, Y+1, Color, TBLACK, TextFlags | flags, Width-2); + + if (Has_Focus() && strlen(text) < MaxLength && + (String_Pixel_Width(text) + String_Pixel_Width ("_") < Width-2) ) { + Conquer_Clip_Text_Print( "_", X+1+String_Pixel_Width(text), Y+1, Color, TBLACK, TextFlags | flags); + } + } else { + Conquer_Clip_Text_Print(text, X+1, Y+1, Has_Focus() ? BLUE : WHITE, TBLACK, TextFlags, Width-2); + + if (Has_Focus() && strlen(text) < MaxLength && + (String_Pixel_Width(text) + String_Pixel_Width ("_") < Width-2) ) { + Conquer_Clip_Text_Print("_",X+1+String_Pixel_Width(text),Y+1,BLUE,TBLACK, TextFlags); + } + } + +} + + +/*********************************************************************************************** + * EditClass::Handle_Key -- Handles keyboard input to edit gadget. * + * * + * This is the gruntwork routine that processes keyboard input to the edit gadget. This * + * routine will be called when keyboard input has been detected and this gadget has the * + * current focus. * + * * + * INPUT: ascii -- The ASCII key code that was fetched from the keyboard buffer. * + * * + * OUTPUT: bool; Should this keyboard input NOT cause the gadget ID number to be returned * + * from the controlling Input() routine? Typically, the return value would be * + * true unless the focus is lost due to the key being pressed. * + * * + * WARNINGS: none * + * HISTORY: * + * 01/21/1995 JLB : Created. * + *=============================================================================================*/ +bool EditClass::Handle_Key(KeyASCIIType ascii) +{ + switch (ascii) { + /* + ** Handle the special case of a non-keyboard event. It is possible that this + ** key code might be passed to this routine if this routine has been overridden + ** and the key event was consumed. + */ + case 0: + break; + + /* + ** If the return key is pressed, then remove the focus from this edit + ** gadget but otherwise let the normal gadget processing proceed. This + ** causes the gadget ID number to be returned from the Input() function + ** so that the controlling program will know that the text can be + ** processed. + */ + case KA_RETURN: + Clear_Focus(); + return(false); + + /* + ** When the BACKSPACE key is pressed, remove the last character in the edit string. + */ + case KA_BACKSPACE: + if (Length) { + Length--; + String[Length] = '\0'; + Flag_To_Redraw(); + } + break; + + /* + ** If the keyboard event was not a recognized special key, then examine to see + ** if it can legally be added to the edit string and do so if possible. + */ + default: + + /* + ** Don't add a character if the length is greater than edit width. + */ + if ((String_Pixel_Width(String) + Char_Pixel_Width(ascii) ) >= (Width-2)) { + break; + } + + /* + ** Don't add a character if the length is already at maximum. + */ + if (Length >= MaxLength) break; + + /* + ** Invisible characters are never added to the string. This is + ** especially true for spaces at the beginning of the string. + */ + if (!isgraph(ascii) && ascii != ' ') break; + if (ascii == ' ' && Length == 0) break; + + /* + ** If this is an upper case only edit gadget, then force the alphabetic + ** character to upper case. + */ + if ((EditFlags & UPPERCASE) && isalpha(ascii)) { + ascii = (KeyASCIIType)toupper(ascii); + } + + if ((!(EditFlags & NUMERIC) || !isdigit(ascii)) && + (!(EditFlags & ALPHA) || !isalpha(ascii)) && + (!(EditFlags & MISC) || isalnum(ascii)) && + ascii != ' ') { + break; + } + + /* + ** The character passed all legality checks, so add it to the edit string + ** and flag this gadget to be redrawn. The manual flag to redraw is needed + ** because the event flag has been cleared. This prevents the gadget's ID + ** number from being returned just because the gadget has been edited. + */ + String[Length++] = ascii; + String[Length] = '\0'; + Flag_To_Redraw(); + break; + } + return(true); +} diff --git a/EDIT.H b/EDIT.H new file mode 100644 index 0000000..fae3928 --- /dev/null +++ b/EDIT.H @@ -0,0 +1,106 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\edit.h_v 2.17 16 Oct 1995 16:46:32 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : EDIT.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 15, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef EDIT_H +#define EDIT_H + +class EditClass : public ControlClass +{ + public: + typedef enum EditStyle { + ALPHA =0x0001, // Edit accepts alphabetic characters. + NUMERIC =0x0002, // Edit accepts numbers. + MISC =0x0004, // Edit accepts misc graphic characters. + UPPERCASE =0x0008, // Force to upper case. + ALPHANUMERIC=(int)ALPHA|(int)NUMERIC|(int)MISC, + } EditStyle; + + EditClass (int id, char * text, int max_len, TextPrintType flags, int x, int y, int w=-1, int h=-1, EditStyle style=ALPHANUMERIC); + virtual ~EditClass(void); + + virtual int Draw_Me(int forced); + virtual void Set_Text(char * text, int max_len); + void Set_Color (int color) { Color = color; } + + void Set_Read_Only(int rdonly) {IsReadOnly = rdonly;} + + protected: + + /* + ** These are the text size and style flags to be used when displaying the text + ** of the edit gadget. + */ + TextPrintType TextFlags; + + /* + ** Input flags that control what characters are allowed in the string. + */ + EditStyle EditFlags; + + /* + ** Pointer to text staging buffer and the maximum length of the string it + ** can contain. + */ + char *String; + int MaxLength; + + /* + ** This is the current length of the string. This length will never exceed the + ** MaxLength allowed. + */ + int Length; + + /* + ** This is the desired color of the edit control. + */ + int Color; + + virtual int Action (unsigned flags, KeyNumType &key); + virtual void Draw_Background(void); + virtual void Draw_Text(char const * text); + virtual bool Handle_Key(KeyASCIIType ascii); + + private: + int IsReadOnly; + +}; + +inline EditClass::EditStyle operator |(EditClass::EditStyle, EditClass::EditStyle); +inline EditClass::EditStyle operator &(EditClass::EditStyle, EditClass::EditStyle); +inline EditClass::EditStyle operator ~(EditClass::EditStyle); + +#endif diff --git a/ENDING.CPP b/ENDING.CPP new file mode 100644 index 0000000..2484ed5 --- /dev/null +++ b/ENDING.CPP @@ -0,0 +1,261 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ending.cpv 1.5 16 Oct 1995 16:50:30 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : ENDING.H * + * * + * Programmer : Barry W. Green * + * * + * Start Date : July 10, 1995 * + * * + * Last Update : July 10, 1995 [BWG] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "textblit.h" + +void GDI_Ending(void) +{ +#ifdef DEMO + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + Load_Title_Screen("DEMOPIC.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Fade_Palette_To(Palette, FADE_PALETTE_MEDIUM, Call_Back); + Clear_KeyBuffer(); + Get_Key_Num(); + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + VisiblePage.Clear(); + +#else + if (TempleIoned) { + Play_Movie("GDIFINB"); + } else { + Play_Movie("GDIFINA"); + } + + Score.Presentation(); + + if (TempleIoned) { + Play_Movie("GDIEND2"); + } else { + Play_Movie("GDIEND1"); + } + + CountDownTimerClass count; + if (CCFileClass("TRAILER.VQA").Is_Available()) { + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + Load_Uncompress(CCFileClass("ATTRACT2.CPS"), SysMemPage, SysMemPage, Palette); + SysMemPage.Scale(SeenBuff, 0, 0, 0, 0, 320, 199, 640, 398); + Fade_Palette_To(Palette, FADE_PALETTE_MEDIUM, Call_Back); + Clear_KeyBuffer(); + count.Set(TIMER_SECOND*3); + while (count.Time()) { + Call_Back(); + } + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + + Play_Movie("TRAILER"); // Red Alert teaser. + } + + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + Load_Uncompress(CCFileClass("ATTRACT2.CPS"), SysMemPage, SysMemPage, Palette); + SysMemPage.Scale(SeenBuff, 0, 0, 0, 0, 320, 199, 640, 398); + Fade_Palette_To(Palette, FADE_PALETTE_MEDIUM, Call_Back); + Clear_KeyBuffer(); +// CountDownTimerClass count; + count.Set(TIMER_SECOND*3); + while (count.Time()) { + Call_Back(); + } + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + + Play_Movie("CC2TEASE"); +#endif +} + + +#ifndef DEMO +/*********************************************************************************************** + * Nod_Ending -- play ending movies for Nod players * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: * + * * + * HISTORY: * + * 7/10/1995 BWG : Created. * + *=============================================================================================*/ +void Nod_Ending(void) +{ + static char const _tanpal[]={0x0,0xED,0xED,0x2C,0x2C,0xFB,0xFB,0xFD,0xFD,0x0,0x0,0x0,0x0,0x0,0x52,0x0}; + + char fname[12]; +#ifdef NOT_FOR_WIN95 + char *satpic = new char[64000]; +#endif //NOT_FOR_WIN95 + int oldfontxspacing = FontXSpacing; + void const *oldfont; + + Score.Presentation(); + + oldfont = Set_Font(ScoreFontPtr); + + PseudoSeenBuff = new GraphicBufferClass(320,200,(void*)NULL); + TextPrintBuffer = new GraphicBufferClass(SeenBuff.Get_Width(), SeenBuff.Get_Height(), (void*)NULL); + TextPrintBuffer->Clear(); + BlitList.Clear(); + SeenBuff.Clear(); + HidPage.Clear(); + PseudoSeenBuff->Clear(); + + void * localpal = Load_Alloc_Data(CCFileClass("SATSEL.PAL")); + Load_Uncompress(CCFileClass("SATSEL.CPS"), SysMemPage, SysMemPage); +#ifdef NOT_FOR_WIN95 + memcpy(satpic, HidPage.Get_Buffer(), 64000); +#else + SysMemPage.Blit(*PseudoSeenBuff); +#endif //NOT_FOR_WIN95 + void *kanefinl = Load_Sample("KANEFINL.AUD"); + void *loopie6m = Load_Sample("LOOPIE6M.AUD"); + + Play_Movie("NODFINAL", THEME_NONE, false); + + Hide_Mouse(); + Wait_Vert_Blank(); + Set_Palette(localpal); +#ifdef NOT_FOR_WIN95 + memcpy(SeenBuff.Get_Buffer(), satpic, 64000); +#endif //NOT_FOR_WIN95 + Show_Mouse(); + + InterpolationPaletteChanged = TRUE; + InterpolationPalette = (unsigned char*)localpal; + Increase_Palette_Luminance(InterpolationPalette , 30,30,30,63); + Read_Interpolation_Palette("SATSELIN.PAL"); + Interpolate_2X_Scale(PseudoSeenBuff, &SeenBuff,"SATSELIN.PAL"); + + Keyboard::Clear(); + Play_Sample(kanefinl,255,128); + Play_Sample(loopie6m,255,128); + + bool mouseshown = false; + bool done = false; + int selection = 1; + bool printedtext = false; + while (!done) { + if (!printedtext && !Is_Sample_Playing(kanefinl)) { + printedtext++; + Alloc_Object(new ScorePrintClass(Text_String(TXT_SEL_TARGET), 0, 180,_tanpal)); + mouseshown = true; + Show_Mouse(); + } + Call_Back_Delay(1); + if (!Keyboard::Check()) { + if (!Is_Sample_Playing(loopie6m)) Play_Sample(loopie6m,255,128); + } else { + if (Is_Sample_Playing(kanefinl)) { + Clear_KeyBuffer(); + } else { + int key = Keyboard::Get(); + if ((key & 0x10FF) == KN_LMOUSE && !(key & KN_RLSE_BIT)) { + int mousex = _Kbd->MouseQX; + int mousey = _Kbd->MouseQY; + if (mousey >= 22*2 && mousey <= 177*2) { + done++; + if (mousex < 160*2 && mousey < 100*2) selection = 2; + if (mousex < 160*2 && mousey >= 100*2) selection = 3; + if (mousex >= 160*2 && mousey >= 100*2) selection = 4; + } + } + } + } + } + if (mouseshown) Hide_Mouse(); +#ifdef NOT_FOR_WIN95 + delete satpic; +#else + delete PseudoSeenBuff; +#endif //NOT_FOR_WIN95 + +/* get rid of all the animating objects */ + for (int i = 0; i < MAXSCOREOBJS; i++) if (ScoreObjs[i]) { + delete ScoreObjs[i]; + ScoreObjs[i] = 0; + } + // erase the "choose a target" text + SeenBuff.Fill_Rect(0,180*2,319*2,199*2,0); + TextPrintBuffer->Fill_Rect(0,180*2,319*2,199*2,0); + + Hide_Mouse(); + Keyboard::Clear(); + + Set_Font(oldfont); + FontXSpacing = oldfontxspacing; + Free_Sample(kanefinl); + Free_Sample(loopie6m); + + sprintf(fname,"NODEND%d",selection); + PreserveVQAScreen = 1; + Play_Movie(fname); + + CountDownTimerClass count; + if (CCFileClass("TRAILER.VQA").Is_Available()) { + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + Load_Uncompress(CCFileClass("ATTRACT2.CPS"), SysMemPage, SysMemPage, Palette); + SysMemPage.Scale(SeenBuff, 0, 0, 0, 0, 320, 199, 640, 398); + Fade_Palette_To(Palette, FADE_PALETTE_MEDIUM, Call_Back); + Clear_KeyBuffer(); + count.Set(TIMER_SECOND*3); + while (count.Time()) { + Call_Back(); + } + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + + Play_Movie("TRAILER"); // Red Alert teaser. + } + + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + Load_Uncompress(CCFileClass("ATTRACT2.CPS"), SysMemPage, SysMemPage, Palette); + SysMemPage.Scale(SeenBuff, 0, 0, 0, 0, 320, 199, 640, 398); + Fade_Palette_To(Palette, FADE_PALETTE_MEDIUM, Call_Back); + Clear_KeyBuffer(); +// CountDownTimerClass count; + count.Set(TIMER_SECOND*3); + while (count.Time()) { + Call_Back(); + } + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + + Play_Movie("CC2TEASE"); + + delete [] localpal; + delete TextPrintBuffer; + BlitList.Clear(); +} +#endif diff --git a/ENDING.H b/ENDING.H new file mode 100644 index 0000000..65a28e4 --- /dev/null +++ b/ENDING.H @@ -0,0 +1,42 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : ENDING.H * + * * + * Programmer : Barry W. Green * + * * + * Start Date : July 10, 1995 * + * * + * Last Update : July 10, 1995 [BWG] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef ENDING_H +#define ENDING_H + +void Nod_Ending(void); + +#endif diff --git a/EVENT.CPP b/EVENT.CPP new file mode 100644 index 0000000..3f05482 --- /dev/null +++ b/EVENT.CPP @@ -0,0 +1,760 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\event.cpv 2.17 16 Oct 1995 16:50:28 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : EVENT.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/09/94 * + * * + * Last Update : June 25, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * EventClass::EventClass -- Construct an id and cell based event. * + * EventClass::EventClass -- Construct simple target type event. * + * EventClass::EventClass -- Constructor for mission change events. * + * EventClass::EventClass -- Constructor for navigation computer events. * + * EventClass::EventClass -- Constructor for object types affecting cells event. * + * EventClass::EventClass -- Constructor for sidebar build events. * + * EventClass::EventClass -- Constructs event to transfer special flags. * + * EventClass::EventClass -- Default constructor for event objects. * + * EventClass::EventClass -- Event for sequencing animations. * + * EventClass::EventClass -- Megamission assigned to unit. * + * EventClass::Execute -- Execute a queued command. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "ccdde.h" + +/*************************************************************************** +** Table of what data is really used in the EventClass struct for different +** events. This table must be kept current with the EventType enum. +*/ +unsigned char EventClass::EventLength[EventClass::LAST_EVENT] = { + 0, // EMPTY + size_of(EventClass, Data.General ), // ALLY + size_of(EventClass, Data.MegaMission ), // MEGAMISSION + size_of(EventClass, Data.Target ), // IDLE + size_of(EventClass, Data.Target ), // SCATTER + 0, // DESTRUCT + 0, // DEPLOY + size_of(EventClass, Data.Place ), // PLACE + 0, // OPTIONS + size_of(EventClass, Data.General ), // GAMESPEED + size_of(EventClass, Data.Specific ), // PRODUCE + size_of(EventClass, Data.Specific.Type ), // SUSPEND + size_of(EventClass, Data.Specific.Type ), // ABANDON + size_of(EventClass, Data.Target ), // PRIMARY + size_of(EventClass, Data.Special ), // SPECIAL_PLACE + 0, // EXIT + size_of(EventClass, Data.Anim ), // ANIMATION + size_of(EventClass, Data.Target ), // REPAIR + size_of(EventClass, Data.Target ), // SELL + size_of(EventClass, Data.Options ), // SPECIAL + 0, // FRAMESYNC + 0, // MESSAGE + size_of(EventClass, Data.FrameInfo.Delay ), // RESPONSE_TIME + size_of(EventClass, Data.FrameInfo ), // FRAMEINFO + size_of(EventClass, Data.Timing ), // TIMING + size_of(EventClass, Data.ProcessTime ), // PROCESS_TIME +}; + +char * EventClass::EventNames[EventClass::LAST_EVENT] = { + "EMPTY", + "ALLY", + "MEGAMISSION", + "IDLE", + "SCATTER", + "DESTRUCT", + "DEPLOY", + "PLACE", + "OPTIONS", + "GAMESPEED", + "PRODUCE", + "SUSPEND", + "ABANDON", + "PRIMARY", + "SPECIAL_PLACE", + "EXIT", + "ANIMATION", + "REPAIR", + "SELL", + "SPECIAL", + "FRAMESYNC", + "MESSAGE", + "RESPONSE_TIME", + "FRAMEINFO", + "TIMING", + "PROCESS_TIME", +}; + + +/*********************************************************************************************** + * EventClass::EventClass -- Constructs event to transfer special flags. * + * * + * This constructs an event that will transfer the special flags. * + * * + * INPUT: data -- The special flags to be transported to all linked computers. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +EventClass::EventClass(SpecialClass data) +{ + ID = Houses.ID(PlayerPtr); + Type = SPECIAL; + Frame = ::Frame; + Data.Options.Data = data; +} + + +/*********************************************************************************************** + * EventClass::EventClass -- Construct simple target type event. * + * * + * This will construct a generic event that needs only a target parameter. The actual * + * event and target values are specified as parameters. * + * * + * INPUT: type -- The event type to construct. * + * * + * target-- The target value that this event is to apply to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +EventClass::EventClass(EventType type, TARGET target) +{ + ID = Houses.ID(PlayerPtr); + Type = type; + Frame = ::Frame; + Data.Target.Whom = target; +} + + +/*********************************************************************************************** + * EventClass::EventClass -- Default constructor for event objects. * + * * + * This constructs a simple event object that requires no parameters other than the * + * type of event it is. * + * * + * INPUT: type -- The type of event to construct. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/27/1994 JLB : Created. * + *=============================================================================================*/ +EventClass::EventClass(EventType type) +{ + ID = Houses.ID(PlayerPtr); + Type = type; + Frame = ::Frame; +} + + +/*********************************************************************************************** + * EventClass::EventClass -- Constructor for general-purpose-data events. * + * * + * INPUT: type -- The type of event to construct. * + * val -- data value * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/27/1994 JLB : Created. * + *=============================================================================================*/ +EventClass::EventClass(EventType type, int val) +{ + ID = Houses.ID(PlayerPtr); + Type = type; + Data.General.Value = val; + Frame = ::Frame; +} + + +/*********************************************************************************************** + * EventClass::EventClass -- Constructor for navigation computer events. * + * * + * Constructor for events that are used to assign the navigation computer. * + * * + * INPUT: type -- The type of event (this constructor can be used by other navigation * + * type events). * + * * + * src -- The object that the event should apply to. * + * * + * dest -- The destination (or target) that the event needs to complete. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/27/1994 JLB : Created. * + *=============================================================================================*/ +EventClass::EventClass(EventType type, TARGET src, TARGET dest) +{ + ID = Houses.ID(PlayerPtr); + Type = type; + Frame = ::Frame; + Data.NavCom.Whom = src; + Data.NavCom.Where = dest; +} + + +/*********************************************************************************************** + * EventClass::EventClass -- Event for sequencing animations. * + * * + * This constructor is used for animations that must be created through the event system. * + * * + * INPUT: anim -- The animation that will be created. * + * * + * coord -- The location where the animation is to be created. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/19/1995 JLB : Created. * + *=============================================================================================*/ +EventClass::EventClass(AnimType anim, HousesType owner, COORDINATE coord) +{ + ID = Houses.ID(PlayerPtr); + Type = ANIMATION; + Frame = ::Frame; + Data.Anim.What = anim; + Data.Anim.Owner = owner; + Data.Anim.Where = coord; +} + + +/*********************************************************************************************** + * EventClass::EventClass -- Megamission assigned to unit. * + * * + * This is the event that is used to assign most missions to units. It combines both the * + * mission and the target (navcom and tarcom). * + * * + * INPUT: src -- The object that this mission is to apply to. * + * * + * mission -- The mission to assign to this object. * + * * + * target -- The target to assign to this object's TarCom. * + * * + * destination -- The destination to assign to this object's NavCom. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : Created. * + *=============================================================================================*/ +EventClass::EventClass(TARGET src, MissionType mission, TARGET target, TARGET destination) +{ + ID = Houses.ID(PlayerPtr); + Type = MEGAMISSION; + Frame = ::Frame; + Data.MegaMission.Whom = src; + Data.MegaMission.Mission = mission; + Data.MegaMission.Target = target; + Data.MegaMission.Destination = destination; +} + + +/*********************************************************************************************** + * EventClass::EventClass -- Constructor for sidebar build events. * + * * + * This constructor is used for events that deal with an object type and an object ID. * + * Typically, this is used exclusively by the sidebar. * + * * + * INPUT: type -- The event type of this object. * + * * + * object -- The object type number. * + * * + * id -- The object sub-type number. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : Created. * + *=============================================================================================*/ +EventClass::EventClass(EventType type, RTTIType object, int id) +{ + ID = Houses.ID(PlayerPtr); + Type = type; + Frame = ::Frame; + Data.Specific.Type = object; + Data.Specific.ID = id; +} + + +/*********************************************************************************************** + * EventClass::EventClass -- Constructor for object types affecting cells event. * + * * + * This constructor is used for those events that have an object type and associated cell. * + * Typically, this is for building placement after construction has completed. * + * * + * INPUT: type -- The event type for this object. * + * * + * object -- The object type number (actual object is probably inferred from the * + * sidebar data). * + * * + * cell -- The cell location where this event is to occur. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : Created. * + *=============================================================================================*/ +EventClass::EventClass(EventType type, RTTIType object, CELL cell) +{ + ID = Houses.ID(PlayerPtr); + Type = type; + Frame = ::Frame; + Data.Place.Type = object; + Data.Place.Cell = cell; +} + + +/*********************************************************************************************** + * EventClass::EventClass -- Construct an id and cell based event. * + * * + * This constructor is used for those events that require an ID number and a cell location. * + * * + * INPUT: type -- The event type this will be. * + * * + * id -- The arbitrary id number to assign. * + * * + * cell -- The location for this event. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : Created. * + *=============================================================================================*/ +EventClass::EventClass(EventType type, int id, CELL cell) +{ + ID = Houses.ID(PlayerPtr); + Type = type; + Frame = ::Frame; + Data.Special.ID = id; + Data.Special.Cell = cell; +} + + +/*********************************************************************************************** + * EventClass::Execute -- Execute a queued command. * + * * + * This routine executes an event. The even must already have been confirmed by any * + * remote machine before calling this routine. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/27/1994 JLB : Created. * + *=============================================================================================*/ +void EventClass::Execute(void) +{ + TechnoClass * techno; + AnimClass * anim = 0; + HouseClass * house = 0; + char txt[80]; + int i; +//#if (0) +if (Type < 0 || Type > PROCESS_TIME){ +char tempbuf[128]; +sprintf (tempbuf, "Packet type %d received\n", Type); +CCDebugString (tempbuf); + +sprintf (tempbuf, " ID = %d\n", ID); +CCDebugString (tempbuf); + +sprintf (tempbuf, " Frame = %d\n", Frame); +CCDebugString (tempbuf); + +sprintf (tempbuf, " MPlayer ID = %d\n", MPlayerID); +CCDebugString (tempbuf); + +} +//#endif //(0) + + + switch (Type) { + /* + ** Make or break alliance. + */ + case ALLY: + house = Houses.Raw_Ptr(Data.General.Value); + if (Houses.Raw_Ptr(ID)->Is_Ally(house)) { + Houses.Raw_Ptr(ID)->Make_Enemy((HousesType)Data.General.Value); + } else { + Houses.Raw_Ptr(ID)->Make_Ally((HousesType)Data.General.Value); + } + break; + + /* + ** Special self destruct action requested. This is active in the multiplayer mode. + */ + case DESTRUCT: +CCDebugString ("C&C95 - Resignation packet received\n"); + Houses.Raw_Ptr(ID)->Flag_To_Die(); + Houses.Raw_Ptr(ID)->Resigned = true; + break; + + /* + ** Update the special control flags. This is necessary so that in a multiplay + ** game, all machines will agree on the rules. If these options change during + ** game play, then all players are informed that options have changed. + */ + case SPECIAL: + { + Special = Data.Options.Data; + HouseClass * house = Houses.Raw_Ptr(ID); + + sprintf(txt, Text_String(TXT_SPECIAL_WARNING), house->Name); + Messages.Add_Message(txt, MPlayerTColors[house->RemapColor], + TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_FULLSHADOW, 1200, 0, 0); + Map.Flag_To_Redraw(false); + } + break; + + /* + ** Starts or stops repair on the specified object. This event is triggered by the + ** player clicking the repair wrench on a building. + */ + case REPAIR: +CCDebugString ("C&C95 - Repair packet received\n"); + techno = As_Techno(Data.Target.Whom); + if (techno && techno->IsActive) { + techno->Repair(-1); + } + break; + + /* + ** Tells a building/unit to sell. This event is triggered by the player clicking the + ** sell animating cursor over the building or unit. + */ + case SELL: +CCDebugString ("C&C95 - Sell packet received\n"); + techno = As_Techno(Data.Target.Whom); + if (techno && techno->IsActive && techno->House == Houses.Raw_Ptr(ID)) { + techno->Sell_Back(-1); + } else { + if (Is_Target_Cell(Data.Target.Whom)) { + Houses.Raw_Ptr(ID)->Sell_Wall(As_Cell(Data.Target.Whom)); + } + } + break; + + /* + ** This even is used to trigger an animation that is generated as a direct + ** result of player intervention. + */ + case ANIMATION: + anim = new AnimClass(Data.Anim.What, Data.Anim.Where); + if (anim) { + if (Data.Anim.Owner != HOUSE_NONE && PlayerPtr->Class->House != Data.Anim.Owner && !Special.IsVisibleTarget) { + anim->Make_Invisible(); + } + } + break; + + /* + ** This event will place the specified object at the specified location. + ** The event is used to place newly constructed buildings down on the map. The + ** object type is specified. From this object type, the house can determine the + ** exact factory and real object pointer to use. + */ + case PLACE: +CCDebugString ("C&C95 - Place packet received\n"); + Houses.Raw_Ptr(ID)->Place_Object(Data.Place.Type, Data.Place.Cell); + break; + + /* + ** This event starts production of the speicified object type. The house can + ** determine from the type and ID value, what object to begin production on and + ** what factory to use. + */ + case PRODUCE: +CCDebugString ("C&C95 - Produce packet received\n"); + Houses.Raw_Ptr(ID)->Begin_Production(Data.Specific.Type, Data.Specific.ID); + break; + + /* + ** This event is generated when the player puts production on hold. From the + ** object type, the factory can be inferred. + */ + case SUSPEND: +CCDebugString ("C&C95 - Suspend packet received\n"); + Houses.Raw_Ptr(ID)->Suspend_Production(Data.Specific.Type); + break; + + /* + ** This event is generated when the player cancels production of the specified + ** object type. From the object type, the exact factory can be inferred. + */ + case ABANDON: +CCDebugString ("C&C95 - Abandon packet received\n"); + Houses.Raw_Ptr(ID)->Abandon_Production(Data.Specific.Type); + break; + + /* + ** Toggles the primary factory state of the specified building. + */ + case PRIMARY:{ +CCDebugString ("C&C95 - Primary building packet received\n"); + BuildingClass * building = As_Building(Data.Target.Whom); + if (building && building->IsActive) { + building->Toggle_Primary(); + } + } + break; + + /* + ** This is the general purpose mission control event. Most player + ** action routes through this event. It sets a unit's mission, TarCom, + ** and NavCom to the values specified. + */ + case MEGAMISSION: + techno = As_Techno(Data.MegaMission.Whom); + if (techno && techno->IsActive) { + + /* + ** Fetch a pointer to the object of the mission. + */ + ObjectClass * object; + if (Target_Legal(Data.MegaMission.Target)) { + object = As_Object(Data.MegaMission.Target); + } else { + object = As_Object(Data.MegaMission.Destination); + } + + /* + ** Break any existing team contact, since it is now invalid. + */ + if (!techno->IsTethered) { + techno->Transmit_Message(RADIO_OVER_OUT); + } + switch (techno->What_Am_I()) { + case RTTI_INFANTRY: + case RTTI_UNIT: + if (((FootClass *)techno)->Team) { + ((FootClass *)techno)->Team->Remove((FootClass *)techno); + } + break; + } + + if (object) { + if (PlayerPtr->Is_Ally(techno) || Special.IsVisibleTarget) { + object->Clicked_As_Target(); + } + } + techno->Assign_Mission(Data.MegaMission.Mission); + + /* + ** Guard area mode is handled with care. The specified target is actually + ** assigned as the location that should be guarded. In addition, the + ** movement destination is immediately set to this new location. + */ + if (Data.MegaMission.Mission == MISSION_GUARD_AREA && +// Target_Legal(Data.MegaMission.Target) && + (techno->What_Am_I() == RTTI_INFANTRY || techno->What_Am_I() == RTTI_UNIT || techno->What_Am_I() == RTTI_AIRCRAFT)) { + + ((FootClass *)techno)->ArchiveTarget = Data.MegaMission.Target; + techno->Assign_Target(TARGET_NONE); + techno->Assign_Destination(Data.MegaMission.Target); + } else { + techno->Assign_Target(Data.MegaMission.Target); + techno->Assign_Destination(Data.MegaMission.Destination); + } + +#ifdef NEVER + if ((techno->What_Am_I() == RTTI_UNIT || techno->What_Am_I() == RTTI_INFANTRY) && + Data.MegaMission.Mission == MISSION_GUARD_AREA) { + + ((FootClass *)techno)->ArchiveTarget = Data.MegaMission.Destination; + } +#endif + } + break; + + /* + ** Request that the unit/infantry/aircraft go into idle mode. + */ + case IDLE: + techno = As_Techno(Data.Target.Whom); + if (techno && techno->IsActive && !techno->IsInLimbo && !techno->IsTethered) { + techno->Assign_Destination(TARGET_NONE); + techno->Assign_Target(TARGET_NONE); + techno->Enter_Idle_Mode(); + } + break; + + /* + ** Request that the unit/infantry/aircraft scatter from its current location. + */ + case SCATTER: + techno = As_Techno(Data.Target.Whom); + if (techno && techno->IsActive && !techno->IsInLimbo && !techno->IsTethered) { + techno->Scatter(0, true); + } + break; + + /* + ** If we are placing down the ion cannon blast then lets take + ** care of it. + */ + case SPECIAL_PLACE: +CCDebugString ("C&C95 - Special blast packet received\n"); + Houses.Raw_Ptr(ID)->Place_Special_Blast((SpecialWeaponType)Data.Special.ID, Data.Special.Cell); + break; + + /* + ** Exit the game. + ** Give parting message while palette is fading to black. + */ + case EXIT: +CCDebugString ("C&C95 - Exit game packet received\n"); + Theme.Queue_Song(THEME_NONE); + Stop_Speaking(); + Speak(VOX_CONTROL_EXIT); + while (Is_Speaking()) { + Call_Back(); + } + GameActive = false; + break; + + /* + ** Process the options menu. + */ + case OPTIONS: + SpecialDialog = SDLG_OPTIONS; + break; + + /* + ** Process the options Game Speed + */ + case GAMESPEED: +CCDebugString ("C&C95 - Game speed packet received\n"); + Options.GameSpeed = Data.General.Value; + break; + + /* + ** Adjust connection timing for multiplayer games + */ + case RESPONSE_TIME: +char flip[128]; +sprintf (flip, "C&C95 - Changing MaxAhead to %d frames\n", Data.FrameInfo.Delay); +CCDebugString (flip); + MPlayerMaxAhead = Data.FrameInfo.Delay; + break; + + // + // This event tells all systems to use new timing values. It's like + // RESPONSE_TIME, only it works. It's only used with the + // COMM_MULTI_E_COMP protocol. + // + case TIMING: +CCDebugString ("C&C95 - Timing packet received\n"); +//#if(TIMING_FIX) + // + // If MaxAhead is about to increase, we're vulnerable to a Packet- + // Received-Too-Late error, if any system generates an event after + // this TIMING event, but before it executes. So, record the + // period of vulnerability's frame start & end values, so we + // can reschedule these events to execute after it's over. + // + if (Data.Timing.MaxAhead > MPlayerMaxAhead) { + NewMaxAheadFrame1 = Frame; + NewMaxAheadFrame2 = Frame + Data.Timing.MaxAhead; + } +//#endif + + DesiredFrameRate = Data.Timing.DesiredFrameRate; + MPlayerMaxAhead = Data.Timing.MaxAhead; + +sprintf (flip, "C&C95 - Timing packet: DesiredFrameRate = %d\n", Data.Timing.DesiredFrameRate); +CCDebugString (flip); +sprintf (flip, "C&C95 - Timing packet: MaxAhead = %d\n", Data.Timing.MaxAhead); +CCDebugString (flip); + + /* + ** If spawned from WChat then we should be getting poked every minute. If not then + ** deliberately break the max ahead value + */ + if (Special.IsFromWChat){ + MPlayerMaxAhead += DDEServer.Time_Since_Heartbeat()/(70*60); +//if (DDEServer.Time_Since_Heartbeat() >= 70*60) CCDebugString ("C&C95 - Missed a heartbeat\n"); + } + break; + + // + // This event tells all systems what the other systems' process + // timing requirements are; it's used to compute a desired frame rate + // for the game. + // + case PROCESS_TIME: + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID == ::MPlayerID[i]) { + TheirProcessTime[i] = Data.ProcessTime.AverageTicks; + +char flip[128]; +sprintf (flip, "C&C95 - Received PROCESS_TIME packet of %04x ticks\n", Data.ProcessTime.AverageTicks); +CCDebugString (flip); + + break; + } + } + break; + + /* + ** Default: do nothing. + */ + default: + break; + } +} diff --git a/EVENT.H b/EVENT.H new file mode 100644 index 0000000..f85f98e --- /dev/null +++ b/EVENT.H @@ -0,0 +1,226 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\event.h_v 2.19 16 Oct 1995 16:46:14 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : EVENT.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/09/94 * + * * + * Last Update : December 9, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef EVENT_H +#define EVENT_H + +/* +** This event class is used to contain all external game events (things that the player can +** do at any time) so that these events can be transported between linked computers. This +** encapsulation is required in order to ensure that each event affects all computers at the +** same time (same game frame). +*/ +class EventClass +{ + public: + + /* + ** All external events are identified by these labels. + */ + typedef enum EventType { + EMPTY, + + ALLY, // Make allie of specified house. + MEGAMISSION, // Full change of mission with target and destination. + IDLE, // Request to enter idle mode. + SCATTER, // Request to scatter from current location. + DESTRUCT, // Self destruct request (surrender action). + DEPLOY, // MCV is to deploy at current location. + PLACE, // Place building at location specified. + OPTIONS, // Bring up options screen. + GAMESPEED, // Set game speed + PRODUCE, // Start or Resume production. + SUSPEND, // Suspend production. + ABANDON, // Abandon production. + PRIMARY, // Primary factory selected. + SPECIAL_PLACE, // Special target location selected + EXIT, // Exit game. + ANIMATION, // Flash ground as movement feedback. + REPAIR, // Repair specified object. + SELL, // Sell specified object. + SPECIAL, // Special options control. + + // Private events. + FRAMESYNC, // Game-connection packet; includes Scenario CRC & sender's frame # + // Used to initiate game connection phase & to reconnect; + // When one of these is received, the receiver knows there are + // no associated commands in this packet. + MESSAGE, // Message to another player (The message is the 40 bytes + // after the event class). + RESPONSE_TIME, // use a new propogation delay value + FRAMEINFO, // Game-heartbeat packet; includes Game CRC & command count + // All packets sent for a frame are prefixed with one of these + TIMING, // new timing values for all systems to use + PROCESS_TIME, // a system's average processing time, in ticks per frame + LAST_EVENT, // one past the last event + } EventType; + + EventType Type; // Type of queue command object. + + /* + ** 'Frame' is the frame that the command should execute on. + ** 27 bits gives over 25 days of playing time without wrapping, + ** at 30 frames per second, so it should be plenty! + */ + unsigned Frame : 27; + + /* + ** House index of the player originating this event + */ + unsigned ID : 4; + + /* + ** This bit tells us if we've already executed this event. + */ + unsigned IsExecuted: 1; + + /* + ** Multiplayer ID of the player originating this event. + ** High nybble: the color index of this player. + ** Low nybble: the HousesType this player is "acting like" (GDI/NOD) + */ + unsigned char MPlayerID; + + /* + ** This union contains the specific data that the event requires. + */ + union { + struct { + SpecialClass Data; // The special option flags. + } Options; + struct { + TARGET Whom; // The object to apply the event to. + } Target; + struct { + AnimType What; // The animation to create. + HousesType Owner; // The owner of the animation (when it matters). + COORDINATE Where; // The location to place the animation. + } Anim; + struct { + int Value; // general-purpose data + } General; + struct { + TARGET Whom; // Whom to apply mission to. + MissionType Mission; // What mission to apply. + TARGET Target; // Target to assign. + TARGET Destination;// Destination to assign. + } MegaMission; + struct { + TARGET Whom; // Whom to apply mission to. + MissionType Mission; // What mission to apply. + } Mission; + struct { + TARGET Whom; // Whom to apply movement change to. + TARGET Where; // Where to set NavCom to. + } NavCom; + struct { + TARGET Whom; // Whom to apply attack change to. + TARGET Target; // What to set TarCom to. + } TarCom; + struct { + RTTIType Type; + int ID; + } Specific; + struct { + RTTIType Type; + CELL Cell; + } Place; + struct { + int ID; + CELL Cell; + } Special; + /* + ** This structure is used for FRAMEINFO, FRAMESYNC, and RESPONSE_TIME + ** events; exactly one of these will be sent each frame, whether there's + ** data that frame or not. + ** CRC: the game CRC when this packet was generated; used to detect sync errors + ** CommandCount: # of commands the sender has sent; used to detect missed packets + ** Delay: sender's propogation delay value for this frame + */ + struct { + unsigned long CRC; + unsigned short CommandCount; // # commands sent so far + unsigned char Delay; // propogation delay used this frame + // (Frame - Delay = sender's current frame #) + } FrameInfo; + + // + // This structure sets new timing values for all systems in a multiplayer + // game. This structure replaces the RESPONSE_TIME event for + // the COMM_MULTI_E_COMP protocol. + // + struct { + unsigned short DesiredFrameRate; + unsigned short MaxAhead; + } Timing; + + // + // This structure is transmitted by all systems, and is used to compute + // the "desired" frame rate for the game. + // + struct { + unsigned short AverageTicks; + } ProcessTime; + + } Data; + + //-------------- Functions --------------------- + EventClass(void) {Type = EMPTY;}; + EventClass(SpecialClass data); + EventClass(EventType type, TARGET target); + EventClass(EventType type); + EventClass(EventType type, int val); + EventClass(EventType type, TARGET src, TARGET dest); +// EventClass(TARGET src, MissionType mission); + EventClass(TARGET src, MissionType mission, TARGET target=TARGET_NONE, TARGET destination=TARGET_NONE); + EventClass(EventType type, RTTIType object, int id); + EventClass(EventType type, RTTIType object, CELL cell); + EventClass(EventType type, int id, CELL cell); + EventClass(AnimType anim, HousesType owner, COORDINATE coord); + + // Process the event. + void Execute(void); + + int operator == (EventClass & q) { + return memcmp(this, &q, sizeof(q)) == 0; + }; + + static unsigned char EventLength[LAST_EVENT]; + static char * EventNames[LAST_EVENT]; +}; + +#endif diff --git a/EXPAND.CPP b/EXPAND.CPP new file mode 100644 index 0000000..1465495 --- /dev/null +++ b/EXPAND.CPP @@ -0,0 +1,422 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header$ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : EXPAND.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 11/03/95 * + * * + * Last Update : November 3, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#ifdef NEWMENU + +bool Expansion_Present(void) +{ + CCFileClass file("EXPAND.DAT"); + + return(file.Is_Available()); +} + + + + +class EListClass : public ListClass +{ + public: + EListClass(int id, int x, int y, int w, int h, TextPrintType flags, void const * up, void const * down) : + ListClass(id, x, y, w, h, flags, up, down) {}; + + protected: + virtual void Draw_Entry(int index, int x, int y, int width, int selected); +}; + + +void EListClass::Draw_Entry(int index, int x, int y, int width, int selected) +{ + if (TextFlags & TPF_6PT_GRAD) { + TextPrintType flags = TextFlags; + + if (selected) { + flags = flags | TPF_BRIGHT_COLOR; + LogicPage->Fill_Rect (x, y, x + width - 1, y + LineHeight - 1, CC_GREEN_SHADOW); + } else { + if (!(flags & TPF_USE_GRAD_PAL)) { + flags = flags | TPF_MEDIUM_COLOR; + } + } + + Conquer_Clip_Text_Print(List[index]+sizeof(int), x, y, CC_GREEN, TBLACK, flags, width, Tabs); + + } else { + Conquer_Clip_Text_Print(List[index]+sizeof(int), x, y, (selected ? BLUE : WHITE), TBLACK, TextFlags, width, Tabs); + } +} + +bool Expansion_Dialog(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + int option_width = 236 * factor; + int option_height = 162 * factor; + int option_x = (320*factor - option_width) /2; + int option_y = (200*factor - option_height) /2; + + GadgetClass * buttons = NULL; + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + TextButtonClass ok(200, TXT_OK, TPF_6PT_GRAD|TPF_NOSHADOW, option_x+25*factor, option_y+option_height-15*factor); + TextButtonClass cancel(201, TXT_CANCEL, TPF_6PT_GRAD|TPF_NOSHADOW, option_x+option_width-50*factor, option_y+option_height-15*factor); + EListClass list(202, option_x+10*factor, option_y+20*factor, option_width-20*factor, option_height-40*factor, TPF_6PT_GRAD|TPF_NOSHADOW, up_button, down_button); + + buttons = &ok; + cancel.Add(*buttons); + list.Add(*buttons); + + + /* + ** Add in all the expansion scenarios. + */ + char * sbuffer = (char*)_ShapeBuffer; + for (int index = 20; index < 60; index++) { + char buffer[128]; + CCFileClass file; + + Set_Scenario_Name(buffer, index, SCEN_PLAYER_GDI, SCEN_DIR_EAST, SCEN_VAR_A); + strcat(buffer, ".INI"); + file.Set_Name(buffer); + if (file.Is_Available()) { + file.Read(sbuffer, 1000); + sbuffer[1000] = '\r'; + sbuffer[1000+1] = '\n'; + sbuffer[1000+2] = '\0'; + + WWGetPrivateProfileString("Basic", "Name", "x", buffer, sizeof(buffer), sbuffer); + char * data = new char [strlen(buffer)+1+sizeof(int)+25]; + *((int*)&data[0]) = index; + strcpy(&data[sizeof(int)], "GDI: "); + strcat(&data[sizeof(int)], buffer); + list.Add_Item(data); + } + } + + for (index = 20; index < 60; index++) { + char buffer[128]; + CCFileClass file; + + Set_Scenario_Name(buffer, index, SCEN_PLAYER_NOD, SCEN_DIR_EAST, SCEN_VAR_A); + strcat(buffer, ".INI"); + file.Set_Name(buffer); + if (file.Is_Available()) { + file.Read(sbuffer, 1000); + sbuffer[1000] = '\r'; + sbuffer[1000+1] = '\n'; + sbuffer[1000+2] = '\0'; + + WWGetPrivateProfileString("Basic", "Name", "x", buffer, sizeof(buffer), sbuffer); + char * data = new char [strlen(buffer)+1+sizeof(int)+25]; + *((int*)&data[0]) = index; + strcpy(&data[sizeof(int)], "NOD: "); + strcat(&data[sizeof(int)], buffer); + list.Add_Item(data); + } + } + + Set_Logic_Page(SeenBuff); + bool recalc = true; + bool display = true; + bool process = true; + bool okval = true; + while (process) { + + Call_Back(); + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=TRUE; + } + + if (display) { + display = false; + + Hide_Mouse(); + + /* + ** Load the background picture. + */ + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + + Dialog_Box(option_x, option_y, option_width, option_height); + Draw_Caption(TXT_MISSION_DESCRIPTION, option_x, option_y, option_width); + buttons->Draw_All(); + Show_Mouse(); + } + + KeyNumType input = buttons->Input(); + switch (input) { + case KN_RETURN: + case 200|KN_BUTTON: + if (list.Current_Item()[sizeof(int)] == 'G') { + ScenPlayer = SCEN_PLAYER_GDI; + } else { + ScenPlayer = SCEN_PLAYER_NOD; + } + ScenDir = SCEN_DIR_EAST; + Whom = HOUSE_GOOD; + Scenario = *(int *)list.Current_Item(); + process = false; + okval = true; + break; + + case KN_ESC: + case 201|KN_BUTTON: + ScenPlayer = SCEN_PLAYER_GDI; + ScenDir = SCEN_DIR_EAST; + Whom = HOUSE_GOOD; + Scenario = *(int *)list.Current_Item(); + process = false; + okval = false; + break; + + default: + break; + } + } + + /* + ** Free up the allocations for the text lines in the list box. + */ + for (index = 0; index < list.Count(); index++) { + delete [] (char *)list.Get_Item(index); + } + + return(okval); +} + + + + + + + + + + + + +/*********************************************************************************************** + * Bonus_Dialog -- Asks the user which bonus mission he wants to play * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 3/26/97 11:07AM ST : Created * + *=============================================================================================*/ +bool Bonus_Dialog(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + int option_width = 236 * factor; + int option_height = 162 * factor; + int option_x = (320*factor - option_width) /2; + int option_y = (200*factor - option_height) /2; + + GadgetClass * buttons = NULL; + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + TextButtonClass ok(200, TXT_OK, TPF_6PT_GRAD|TPF_NOSHADOW, option_x+25*factor, option_y+option_height-15*factor); + TextButtonClass cancel(201, TXT_CANCEL, TPF_6PT_GRAD|TPF_NOSHADOW, option_x+option_width-50*factor, option_y+option_height-15*factor); + EListClass list(202, option_x+10*factor, option_y+20*factor, option_width-20*factor, option_height-40*factor, TPF_6PT_GRAD|TPF_NOSHADOW, up_button, down_button); + + buttons = &ok; + cancel.Add(*buttons); + list.Add(*buttons); + + + /* + ** Add in all the expansion scenarios. + */ + char * sbuffer = (char*)_ShapeBuffer; + int gdi_scen_names[3]={ + TXT_BONUS_MISSION_1, + TXT_BONUS_MISSION_2, + TXT_BONUS_MISSION_3 + }; + + int nod_scen_names[2]={ + TXT_BONUS_MISSION_4, + TXT_BONUS_MISSION_5 + }; + + + for (int index = 60; index < 63; index++) { + char buffer[128]; + CCFileClass file; + + Set_Scenario_Name(buffer, index, SCEN_PLAYER_GDI, SCEN_DIR_EAST, SCEN_VAR_A); + strcat(buffer, ".INI"); + file.Set_Name(buffer); + if (file.Is_Available()) { + memcpy (buffer, Text_String (gdi_scen_names[index-60]), 1+strlen(Text_String (gdi_scen_names[index-60]))); + char * data = new char [strlen(buffer)+1+sizeof(int)+25]; + *((int*)&data[0]) = index; + strcpy(&data[sizeof(int)], "GDI: "); + strcat(&data[sizeof(int)], buffer); + list.Add_Item(data); + } + } + + for (index = 60; index < 62; index++) { + char buffer[128]; + CCFileClass file; + + Set_Scenario_Name(buffer, index, SCEN_PLAYER_NOD, SCEN_DIR_EAST, SCEN_VAR_A); + strcat(buffer, ".INI"); + file.Set_Name(buffer); + if (file.Is_Available()) { + memcpy (buffer, Text_String (nod_scen_names[index-60]), 1+strlen(Text_String (nod_scen_names[index-60]))); + char * data = new char [strlen(buffer)+1+sizeof(int)+25]; + *((int*)&data[0]) = index; + strcpy(&data[sizeof(int)], "NOD: "); + strcat(&data[sizeof(int)], buffer); + list.Add_Item(data); + } + } + + Set_Logic_Page(SeenBuff); + bool recalc = true; + bool display = true; + bool process = true; + bool okval = true; + while (process) { + + Call_Back(); + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=TRUE; + } + + if (display) { + display = false; + + Hide_Mouse(); + + /* + ** Load the background picture. + */ + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + + Dialog_Box(option_x, option_y, option_width, option_height); + Draw_Caption(TXT_BONUS_MISSIONS, option_x, option_y, option_width); + buttons->Draw_All(); + Show_Mouse(); + } + + KeyNumType input = buttons->Input(); + switch (input) { + case KN_RETURN: + case 200|KN_BUTTON: + if (list.Current_Item()[sizeof(int)] == 'G') { + ScenPlayer = SCEN_PLAYER_GDI; + } else { + ScenPlayer = SCEN_PLAYER_NOD; + } + ScenDir = SCEN_DIR_EAST; + Whom = HOUSE_GOOD; + Scenario = *(int *)list.Current_Item(); + process = false; + okval = true; + break; + + case KN_ESC: + case 201|KN_BUTTON: + ScenPlayer = SCEN_PLAYER_GDI; + ScenDir = SCEN_DIR_EAST; + Whom = HOUSE_GOOD; + Scenario = *(int *)list.Current_Item(); + process = false; + okval = false; + break; + + default: + break; + } + } + + /* + ** Free up the allocations for the text lines in the list box. + */ + for (index = 0; index < list.Count(); index++) { + delete [] (char *)list.Get_Item(index); + } + + return(okval); +} + + +#endif diff --git a/EXTERNS.H b/EXTERNS.H new file mode 100644 index 0000000..bf2edce --- /dev/null +++ b/EXTERNS.H @@ -0,0 +1,401 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\externs.h_v 2.15 16 Oct 1995 16:45:34 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : EXTERNS.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 27, 1994 * + * * + * Last Update : May 27, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef EXTERNS_H +#define EXTERNS_H + +#include "cell.h" + +#ifdef SCENARIO_EDITOR +#include "mapedit.h" +#endif +#include "techno.h" +#include "type.h" +#include "building.h" +#include "unit.h" +#include "credits.h" +#include "goptions.h" +#include "options.h" +#include "infantry.H" + +#ifdef JAPANESE +extern bool ForceEnglish; +#endif + +extern bool Debug_Quiet; +extern bool Debug_Cheat; +extern bool Debug_Remap; +extern bool Debug_Flag; +extern bool Debug_Lose; +extern bool Debug_Map; +extern bool Debug_Win; +extern bool Debug_Icon; +extern bool Debug_Passable; +extern bool Debug_Unshroud; +extern bool Debug_Threat; +extern bool Debug_Find_Path; +extern bool Debug_Check_Map; +extern bool Debug_Playtest; + +extern bool Debug_Heap_Dump; +extern bool Debug_Smart_Print; +extern bool Debug_Trap_Check_Heap; +extern bool Debug_Instant_Build; + +extern void const *WarFactoryOverlay; + + +/* +** Dynamic global variables (these change or are initialized at run time). +*/ +#ifdef PATCH +extern bool IsV107; +extern char OverridePath[128]; +#endif +extern bool SlowPalette; +extern char VersionText[16]; +extern bool ScoresPresent; +extern int CrateCount; +extern TCountDownTimerClass CrateTimer; +extern bool CrateMaker; +extern ThemeType TransitTheme; +extern bool AllowVoice; +extern NewConfigType NewConfig; +extern char BriefingText[512]; +extern char IntroMovie[_MAX_FNAME+_MAX_EXT]; +extern char ActionMovie[_MAX_FNAME+_MAX_EXT]; +extern char BriefMovie[_MAX_FNAME+_MAX_EXT]; +extern char WinMovie[_MAX_FNAME+_MAX_EXT]; +extern char LoseMovie[_MAX_FNAME+_MAX_EXT]; +extern VoxType SpeakQueue; +extern bool PlayerWins; +extern bool PlayerLoses; +extern bool PlayerRestarts; +extern StructType SabotagedType; +extern bool TempleIoned; +extern long Frame; +extern void * SpeechBuffer; +extern int PreserveVQAScreen; +extern bool BreakoutAllowed; +extern bool Brokeout; +extern CELL Views[4]; + +extern GameOptionsClass Options; + +extern LogicClass Logic; +#ifdef SCENARIO_EDITOR +extern MapEditClass Map; +#else +extern MouseClass Map; +#endif +extern ScoreClass Score; +extern MonoClass MonoArray[MonoClass::MAX_MONO_PAGES]; +extern MixFileClass * ScoreMix; +extern MixFileClass * TheaterData; +extern MixFileClass * LowTheaterData; +extern MixFileClass * MoviesMix; +extern MixFileClass * GeneralMix; +extern ThemeClass Theme; +extern SpecialClass Special; + +/* +** Game object allocation and tracking classes. +*/ +extern TFixedIHeapClass Units; +extern TFixedIHeapClass Factories; +extern TFixedIHeapClass Terrains; +extern TFixedIHeapClass Templates; +extern TFixedIHeapClass Smudges; +extern TFixedIHeapClass Overlays; +extern TFixedIHeapClass Infantry; +extern TFixedIHeapClass Bullets; +extern TFixedIHeapClass Buildings; +extern TFixedIHeapClass Anims; +extern TFixedIHeapClass Aircraft; +extern TFixedIHeapClass Triggers; +extern TFixedIHeapClass TeamTypes; +extern TFixedIHeapClass Teams; +extern TFixedIHeapClass Houses; + +extern QueueClass OutList; +extern QueueClass DoList; + +extern DynamicVectorClass CurrentObject; +extern DynamicVectorClass CellTriggers; +extern DynamicVectorClass HouseTriggers[HOUSE_COUNT]; + +extern CELL Waypoint[WAYPT_COUNT]; + +extern BaseClass Base; + +/* +** Loaded data file pointers. +*/ +extern void const * Green12FontPtr; +extern void const * Green12GradFontPtr; +extern void const * MapFontPtr; +extern void const * VCRFontPtr; +extern void const * Font3Ptr; +extern void const * Font6Ptr; +extern void const * Font8Ptr; +extern void const * FontLEDPtr; +extern void const * ScoreFontPtr; +extern void const * GradFont6Ptr; +extern char const * SystemStrings; + +/* +** Miscellaneous globals. +*/ +extern HousesType Whom; +extern _VQAConfig AnimControl; +extern long SpareTicks; +extern int MonoPage; +extern unsigned char * OriginalPalette; +extern int EndCountDown; +extern bool GameActive; +extern bool SpecialFlag; +extern bool ScenarioInit; +extern long TutorFlags[2]; +extern HouseClass * PlayerPtr; +extern unsigned char * BlackPalette; +extern unsigned char * WhitePalette; +extern unsigned char * GamePalette; +extern unsigned Scenario; +extern ScenarioPlayerType ScenPlayer; +extern ScenarioDirType ScenDir; +extern ScenarioVarType ScenVar; +extern int CarryOverMoney; +extern int CarryOverCap; +extern int CarryOverPercent; +extern char ScenarioName[_MAX_FNAME+_MAX_EXT]; +extern unsigned BuildLevel; +extern unsigned long ScenarioCRC; + +#ifdef SCENARIO_EDITOR +extern CELL CurrentCell; +#endif + +extern GameType GameToPlay; + +extern CommProtocolType CommProtocol; + +extern CCFileClass RecordFile; +extern int RecordGame; +extern int SuperRecord; +extern int PlaybackGame; +extern int AllowAttract; + +extern GetCDClass CDList; + +/* +** Modem globals +*/ +extern bool ModemService; +extern NullModemClass NullModem; +extern DynamicVectorClass PhoneBook; +extern int CurPhoneIdx; +extern DynamicVectorClass InitStrings; +extern SerialSettingsType SerialDefaults; +extern ModemGameType ModemGameToPlay; +extern char * DialMethodCheck[ DIAL_METHODS ]; +extern char * CallWaitStrings[ CALL_WAIT_STRINGS_NUM ]; + +/* +** Network/Modem globals +*/ +extern int ScenarioIdx; +extern int ColorUsed[]; +extern char MPlayerName[MPLAYER_NAME_MAX]; +extern int MPlayerGColors[]; +extern int MPlayerTColors[]; +extern char MPlayerDescriptions[100][40]; +extern DynamicVectorClass MPlayerScenarios; +extern DynamicVectorClass MPlayerFilenum; +extern int MPlayerMax; +extern int MPlayerPrefColor; +extern int MPlayerColorIdx; +extern HousesType MPlayerHouse; +extern unsigned char MPlayerLocalID; +extern int MPlayerCount; +extern int MPlayerBases; +extern int MPlayerCredits; +extern int MPlayerTiberium; +extern int MPlayerGoodies; +extern int MPlayerGhosts; +extern int MPlayerSolo; +extern int MPlayerUnitCount; +extern int MPlayerCountMin[2]; +extern int MPlayerCountMax[2]; +extern unsigned long MPlayerMaxAhead; +extern unsigned long FrameSendRate; +extern unsigned char MPlayerID[MAX_PLAYERS]; +extern HousesType MPlayerHouses[MAX_PLAYERS]; +extern char MPlayerNames [MAX_PLAYERS][MPLAYER_NAME_MAX]; +extern MessageListClass Messages; +extern IPXAddressClass MessageAddress; +extern char LastMessage[MAX_MESSAGE_LENGTH]; +extern int MPlayerBlitz; +extern int MPlayerObiWan; +extern MPlayerScoreType MPlayerScore[MAX_MULTI_NAMES]; +extern int MPlayerGamesPlayed; +extern int MPlayerNumScores; +extern int MPlayerWinner; +extern int MPlayerCurGame; + +extern int TheirProcessTime[MAX_PLAYERS - 1]; +extern int DesiredFrameRate; + +extern char * GlobalPacketNames[]; +extern char * SerialPacketNames[]; + +typedef struct { + union { + AircraftClass *Aircraft; + AnimClass *Anim; + BuildingClass *Building; + BulletClass *Bullet; + InfantryClass *Infantry; + UnitClass *Unit; + void *All; + } Ptr; +} TrapObjectType; + +extern long TrapFrame; +extern RTTIType TrapObjType; +extern TrapObjectType TrapObject; +extern COORDINATE TrapCoord; +extern void * TrapThis; +extern CellClass * TrapCell; +extern int TrapCheckHeap; + +/* +** Network (IPX) globals +*/ +extern IPXManagerClass Ipx; +extern int IsBridge; +extern IPXAddressClass BridgeNet; +extern bool NetMaster; +extern bool NetStealth; +extern bool NetProtect; +extern bool NetOpen; +extern char MPlayerGameName[MPLAYER_NAME_MAX]; +extern GlobalPacketType GPacket; +extern int GPacketlen; +extern IPXAddressClass GAddress; +extern unsigned short GProductID; +extern char * MetaPacket; +extern int MetaSize; +extern DynamicVectorClass Games; +extern DynamicVectorClass Players; + +extern int Seed; +extern long * RandSeedPtr; +extern int CustomSeed; +extern int NewMaxAheadFrame1; +extern int NewMaxAheadFrame2; + +/* +** Constant externs (data is not modified during game play). +*/ +extern unsigned char const RemapGreen[256]; +extern unsigned char const RemapBlue[256]; +extern unsigned char const RemapOrange[256]; +extern unsigned char const RemapNone[256]; +extern unsigned char const RemapYellow[256]; +extern unsigned char const RemapRed[256]; +extern unsigned char const RemapBlueGreen[256]; +extern WeaponTypeClass const Weapons[WEAPON_COUNT]; +extern WarheadTypeClass const Warheads[WARHEAD_COUNT]; +extern char const * SourceName[SOURCE_COUNT]; +extern GroundType const Ground[LAND_COUNT]; +extern TheaterDataType const Theaters[THEATER_COUNT]; +extern unsigned char const Facing32[256]; +extern unsigned char const Facing8[256]; +extern unsigned char const Pixel2Lepton[24]; +extern COORDINATE const StoppingCoordAbs[5]; +extern CELL const AdjacentCell[FACING_COUNT]; +extern COORDINATE const AdjacentCoord[FACING_COUNT]; + +extern int SoundOn; +//extern GraphicBufferClass SeenPage; +extern GraphicBufferClass VisiblePage; +extern GraphicBufferClass HiddenPage; +extern GraphicViewPortClass SeenBuff; +extern GraphicBufferClass ModeXBuff; +extern GraphicViewPortClass HidPage; +extern GraphicBufferClass LoResHidPage; +extern GraphicBufferClass SysMemPage; +extern int MenuList[][8]; +extern CountDownTimerClass FrameTimer; +extern CountDownTimerClass CountDownTimer; + +extern TimerClass ProcessTimer; +extern int ProcessTicks; +extern int ProcessFrames; + +extern SpecialDialogType SpecialDialog; +//extern bool IsFindPath; + +extern char *DebugFname; // for stoopid debugging purposes +extern int DebugLine; // for stoopid debugging purposes +extern int RequiredCD; +extern int MouseInstalled; +extern int AreThingiesEnabled; + +extern WWKeyboardClass Kbd; +extern int In_Debugger; +extern WWMouseClass *WWMouse; +extern HANDLE hInstance; +extern int AllDone; +extern "C" bool MMXAvailable; +extern int Get_CD_Index (int cd_drive, int timeout); +void Memory_Error_Handler(void); +extern bool GameStatisticsPacketSent; +extern bool ConnectionLost; +extern bool InMainLoop; // True if in game state rather than menu state +void CCDebugString (char *string); +extern void *PacketLater; +void Load_Title_Screen(char *name, GraphicViewPortClass *video_page, unsigned char *palette); + +extern "C"{ + extern bool IsTheaterShape; +} + +extern void Reset_Theater_Shapes(void); +extern TheaterType LastTheater; + +#endif diff --git a/FACING.CPP b/FACING.CPP new file mode 100644 index 0000000..62401ae --- /dev/null +++ b/FACING.CPP @@ -0,0 +1,183 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\facing.cpv 1.9 16 Oct 1995 16:49:40 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FACING.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 03/21/95 * + * * + * Last Update : March 21, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * FacingClass::Rotation_Adjust -- Perform a rotation adjustment to current facing. * + * FacingClass::Set_Current -- Sets the current rotation value. * + * FacingClass::Set_Desired -- Sets the desired facing value. * + * FacingClass::FacingClass -- Default constructor for the facing class. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "facing.h" + + +/*********************************************************************************************** + * FacingClass::FacingClass -- Default constructor for the facing class. * + * * + * This default constructor merely sets the desired and current facing values to be the * + * same (North). * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/21/1995 JLB : Created. * + *=============================================================================================*/ +FacingClass::FacingClass(void) +{ + CurrentFacing = DIR_N; + DesiredFacing = DIR_N; +} + + +/*********************************************************************************************** + * FacingClass::Set_Desired -- Sets the desired facing value. * + * * + * This routine is used to set the desired facing value without altering the current * + * facing setting. Typicall use of this routine is when a vehicle needs to face a * + * direction, but currently isn't facing the correct direction. After this routine is * + * called, it is presumed that subsequent calls to Rotation_Adjust() will result in the * + * eventual alignment of the current facing. * + * * + * INPUT: facing -- The new facing to assign to the desired value. * + * * + * OUTPUT: bool; Did the desired facing value actually change by this routine call? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/21/1995 JLB : Created. * + *=============================================================================================*/ +int FacingClass::Set_Desired(DirType facing) +{ + if (DesiredFacing != facing) { + DesiredFacing = facing; + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * FacingClass::Set_Current -- Sets the current rotation value. * + * * + * This routine will set the current rotation value. It is used to override the facing * + * value without adjusting the desired setting. * + * * + * INPUT: facing -- The new facing to assign to the current facing value. * + * * + * OUTPUT: bool; Was the current setting changed by this routine. Failure means that the * + * current setting was already at the value specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/21/1995 JLB : Created. * + *=============================================================================================*/ +int FacingClass::Set_Current(DirType facing) +{ + if (CurrentFacing != facing) { + CurrentFacing = facing; + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * FacingClass::Rotation_Adjust -- Perform a rotation adjustment to current facing. * + * * + * This routine is used when the current and desired facings differ but the current * + * facing should be adjusted toward the desired facing. The amount of rotation to adjust * + * is provided as a rotation rate parameter. Typical use of this routine is for turrets * + * and other vehicle related rotating. * + * * + * INPUT: rate -- The rotation rate to use when adjusting the current facing toward the * + * desired facing. A value of 127 means instantaneous rotation. * + * * + * OUTPUT: bool; Did the rotation result in the current facing transitioning from one * + * 1/32 zone to another? If true, then the owning object most likely will * + * need to be redrawn to reflect the change. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/21/1995 JLB : Created. * + *=============================================================================================*/ +int FacingClass::Rotation_Adjust(int rate) +{ + /* + ** Only perform the rotation adjustment if the desired facing is not the + ** same as the current facing. + */ + if (Is_Rotating()) { + rate = MIN(rate, 127); + + DirType oldfacing = CurrentFacing; + int diff = Difference(); + + /* + ** If the allowed facing change is greater than the difference between + ** the current facing and the desired facing, then just snap the + ** facing to the new value. + */ + if (ABS(diff) < rate) { + CurrentFacing = DesiredFacing; + } else { + + /* + ** Adjust the current facing clockwise or counterclockwise depending + ** on the shortest distance to the desired facing from the current + ** facing. + */ + if (diff < 0) { + CurrentFacing = (DirType)(CurrentFacing - (DirType)rate); + } else { + CurrentFacing = (DirType)(CurrentFacing + (DirType)rate); + } + } + + /* + ** If this facing adjustment caused the current facing to rotate into a + ** new 1/32 rotation zone (likely to cause a redraw), then return + ** this fact with a true value. + */ + return(Facing_To_32(CurrentFacing) != Facing_To_32(oldfacing)); + } + return(false); +} diff --git a/FACING.H b/FACING.H new file mode 100644 index 0000000..d5cf79e --- /dev/null +++ b/FACING.H @@ -0,0 +1,79 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\facing.h_v 1.14 16 Oct 1995 16:46:02 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FACING.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 03/21/95 * + * * + * Last Update : March 21, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef FACING_H +#define FACING_H + +/* +** This is a general facing handler class. It is used in those cases where facing needs to be +** kept track of, but there could also be an associated desired facing. The current facing +** is supposed to transition to the desired state over time. Using this class facilitates this +** processing as well as isolating the rest of the code from the internals. +*/ +class FacingClass +{ + public: + FacingClass(void); + FacingClass(DirType dir) {CurrentFacing = DesiredFacing = dir;}; + operator DirType(void) const {return CurrentFacing;}; + + DirType Current(void) const {return CurrentFacing;}; + DirType Desired(void) const {return DesiredFacing;}; + + int Set_Desired(DirType facing); + int Set_Current(DirType facing); + + void Set(DirType facing) { + Set_Current(facing); + Set_Desired(facing); + }; + + DirType Get(void) const { return CurrentFacing; } + + int Is_Rotating(void) const {return (DesiredFacing != CurrentFacing);}; + + int Difference(void) const {return (signed char)(*((unsigned char*)&DesiredFacing) - *((unsigned char*)&CurrentFacing));}; + int Difference(DirType facing) const {return (signed char)(*((signed char*)&facing) - *((signed char*)&CurrentFacing));}; + int Rotation_Adjust(int rate); + + private: + DirType CurrentFacing; + DirType DesiredFacing; +}; + + +#endif diff --git a/FACTORY.CPP b/FACTORY.CPP new file mode 100644 index 0000000..ce75b00 --- /dev/null +++ b/FACTORY.CPP @@ -0,0 +1,750 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\factory.cpv 2.18 16 Oct 1995 16:51:26 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FACTORY.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/26/94 * + * * + * Last Update : May 22, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * FactoryClass::AI -- Process factory production logic. * + * FactoryClass::Abandon -- Abandons current construction with money refunded. * + * FactoryClass::Completed -- Clears factory object after a completed production process. * + * FactoryClass::Completion -- Fetchs the completion step for this factory. * + * FactoryClass::Cost_Per_Tick -- Breaks entire production cost into managable chunks. * + * FactoryClass::FactoryClass -- Default constructor for factory objects. * + * FactoryClass::Get_Object -- Fetches pointer to object being constructed. * + * FactoryClass::Get_Special_Item -- gets factorys spc prod item * + * FactoryClass::Has_Changed -- Checks to see if a production step has occurred? * + * FactoryClass::Has_Completed -- Checks to see if object has completed production. * + * FactoryClass::Set -- Assigns a factory to produce an object. * + * FactoryClass::Set -- Fills a factory with an already completed object. * + * FactoryClass::Set -- Force factory to "produce" special object. * + * FactoryClass::Start -- Resumes production after suspension or creation. * + * FactoryClass::Suspend -- Temporarily stop production. * + * FactoryClass::operator delete -- Returns a factory to the free factory pool. * + * FactoryClass::operator new -- Allocates a factory object from the free factory pool. * + * FactoryClass::~FactoryClass -- Default destructor for factory objects. * + * FactoryClass::Validate -- validates factory pointer * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * FactoryClass::Validate -- validates factory pointer * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int FactoryClass::Validate(void) const +{ + int num; + + num = Factories.ID(this); + if (num < 0 || num >= FACTORY_MAX) { + Validate_Error("FACTORY"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*********************************************************************************************** + * FactoryClass::FactoryClass -- Default constructor for factory objects. * + * * + * This brings the factory into a null state. It is called when a factory object is * + * created. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +FactoryClass::FactoryClass(void) +{ + IsSuspended = false; + IsDifferent = false; + Balance = 0; + SpecialItem = SPC_NONE; + Object = NULL; + House = NULL; + Set_Rate(0); + Set_Stage(0); +} + + +/*********************************************************************************************** + * FactoryClass::~FactoryClass -- Default destructor for factory objects. * + * * + * This cleans up a factory object in preparation for deletion. If there is currently * + * an object in production, it is abandoned and money is refunded. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +FactoryClass::~FactoryClass(void) +{ + if (GameActive) { + Abandon(); + } +} + + +/*********************************************************************************************** + * FactoryClass::Init -- Clears all units for scenario preparation. * + * * + * This routine will zero out the factory list and objects. This routine is typically * + * used in preparation for a new scenario load. All factorys are guaranteed to be eliminated* + * by this routine. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/15/1994 JLB : Created. * + *=============================================================================================*/ +void FactoryClass::Init(void) +{ + Factories.Free_All(); +} + + +/*********************************************************************************************** + * FactoryClass::operator new -- Allocates a factory object from the free factory pool. * + * * + * This routine allocates a factory from the free factory pool. If there is no more room * + * to allocate a factory, then NULL is returned. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with pointer to the newly allocated factory object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +void * FactoryClass::operator new(size_t) +{ + void * ptr = Factories.Allocate(); + if (ptr) { + ((FactoryClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +/*********************************************************************************************** + * FactoryClass::operator delete -- Returns a factory to the free factory pool. * + * * + * This returns the factory object back to the factory allocation pool. The factory is then * + * available to be allocated. * + * * + * INPUT: ptr -- Pointer to the factory object to delete. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +void FactoryClass::operator delete(void *ptr) +{ + if (ptr) { + ((FactoryClass *)ptr)->IsActive = false; + } + Factories.Free((FactoryClass *)ptr); +} + + +/*********************************************************************************************** + * FactoryClass::AI -- Process factory production logic. * + * * + * This routine should be called once per game tick. It handles the production process. * + * As production proceeds, money is deducted from the owner object's house. When production * + * completes, the factory stop processing. A call to Abandon, Delete, or Completed is * + * required after that point. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + * 01/04/1995 JLB : Uses exact installment payment method. * + *=============================================================================================*/ +void FactoryClass::AI(void) +{ + Validate(); + if (!IsSuspended && (Object != NULL || SpecialItem)) { + int stages = 1; + + /* + ** Determine the acceleration factor for factory production. + ** This applies only to human players. The computer builds + ** units on a building by building basis -- quantity of building + ** factory types doesn't affect individual factories. + */ + if (Object && House->IsHuman) { + switch (Object->What_Am_I()) { + case RTTI_AIRCRAFT: + stages = House->AircraftFactories; + break; + + case RTTI_INFANTRY: + stages = House->InfantryFactories; + break; + + case RTTI_UNIT: + stages = House->UnitFactories; + break; + + case RTTI_BUILDING: + stages = House->BuildingFactories; + break; + } + stages = MAX(stages, 1); + } + + + for (int index = 0; index < stages; index++) { + if (!Has_Completed() && Graphic_Logic()) { + IsDifferent = true; + + int cost = Cost_Per_Tick(); + + cost = MIN(cost, Balance); + + /* + ** Enough time has expired so that another production step can occur. + ** If there is insufficient funds, then go back one production step and + ** continue the countdown. The idea being that by the time the next + ** production step occurs, there may be sufficient funds available. + */ + if (cost > House->Available_Money()) { + Set_Stage(Fetch_Stage()-1); + } else { + House->Spend_Money(cost); + Balance -= cost; + } + if ( Debug_Instant_Build ) { + Set_Stage(STEP_COUNT); + } + /* + ** If the production has completed, then suspend further production. + */ + if (Fetch_Stage() == STEP_COUNT) { + IsSuspended = true; + Set_Rate(0); + House->Spend_Money(Balance); + Balance = 0; + } + } + } + } +} + + +/*********************************************************************************************** + * FactoryClass::Has_Changed -- Checks to see if a production step has occurred? * + * * + * Use this routine to determine if production has advanced at least one step. By using * + * this function, intelligent rendering may be performed. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Has the production process advanced one step since the last time this * + * function was called? * + * * + * WARNINGS: This function clears the changed status flag as a side effect. * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +bool FactoryClass::Has_Changed(void) +{ + Validate(); + bool changed = IsDifferent; + IsDifferent = false; + return(changed); +} + + +/*********************************************************************************************** + * FactoryClass::Set -- Assigns a factory to produce an object. * + * * + * This routine initializes a factory to produce the object specified. The desired object * + * type is created and placed in suspended animation (limbo) until such time as production * + * completes. Production is not actually started by this routine. An explicit call to * + * Start() is required to begin production. * + * * + * INPUT: object -- Reference to the object type class that is to be produced. * + * * + * house -- Reference to the owner of the object to be produced. * + * * + * OUTPUT: bool; Was production successfully prepared for this factory object. Failure means * + * that the object could not be created. This is catastrophic and in such * + * cases, the factory object should be deleted. * + * * + * WARNINGS: Be sure to examine the return value from this function. Failure to initialize * + * the factory means that the factory is useless and should be deleted. * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +bool FactoryClass::Set(TechnoTypeClass const & object, HouseClass & house) +{ + Validate(); + /* + ** If there is any production currently in progress, abandon it. + */ + Abandon(); + + /* + ** Set up the factory for the new production process. + */ + IsDifferent = true; + IsSuspended = true; + Set_Rate(0); + Set_Stage(0); + + /* + ** Create an object of the type requested. + */ + Object = (TechnoClass *)object.Create_One_Of(&house); + + if (Object) { + House = Object->House; + Balance = object.Cost_Of(); + Object->PurchasePrice = Balance; + } + + /* + ** If all was set up successfully, then return true. + */ + return(Object != NULL); +} + + +/*********************************************************************************************** + * FactoryClass::Set -- Force factory to "produce" special object. * + * * + * Use this routine to force the factory into special production mode. Such production is * + * used for the ion cannon and other timed special weapon events. * + * * + * INPUT: type -- The special weapon type to begin "production" of. * + * * + * house -- The owner of this production object. * + * * + * OUTPUT: Was the assignment successful? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1995 JLB : Created. * + *=============================================================================================*/ +bool FactoryClass::Set(int const & type, HouseClass & house) +{ + Validate(); + /* + ** If there is any production currently in progress, abandon it. + */ + Abandon(); + + /* + ** Set up the factory for the new production process. + */ + IsDifferent = true; + IsSuspended = true; + Set_Rate(0); + Set_Stage(0); + + /* + ** Create an object of the type requested. + */ + SpecialItem = type; + House = &house; + Balance = 0; + + /* + ** If all was set up successfully, then return true. + */ + return(SpecialItem != SPC_NONE); +} + + +/*********************************************************************************************** + * FactoryClass::Set -- Fills a factory with an already completed object. * + * * + * This routine is called when a produced object is in placement mode but then placement * + * is suspended. The object must then return to the factory as if it were newly completed * + * and awaiting removal. * + * * + * INPUT: object -- The object to return to the factory. * + * * + * OUTPUT: none * + * * + * WARNINGS: This will abandon any current object being produced at the factory in order * + * to set the new object into it. * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +void FactoryClass::Set(TechnoClass & object) +{ + Validate(); + Abandon(); + Object = &object; + House = Object->House; + Balance = 0; + Set_Rate(0); + Set_Stage(STEP_COUNT); + IsDifferent = true; + IsSuspended = true; +} + + +/*********************************************************************************************** + * FactoryClass::Suspend -- Temporarily stop production. * + * * + * This routine will suspend production until a subsiquent call to Start() or Abandon(). * + * Typical use of this function is when the player puts production on hold or when there * + * is insufficient funds. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was production actually stopped? A false return value indicates that the * + * factory was empty or production was already stopped (or never started). * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +bool FactoryClass::Suspend(void) +{ + Validate(); + if (!IsSuspended) { + IsSuspended = true; + Set_Rate(0); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * FactoryClass::Start -- Resumes production after suspension or creation. * + * * + * This function will start the production process. It works for newly created factory * + * objects, as well as if production had been suspended previously. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was production started? A false return value means that the factory is * + * empty or there is unsufficient credits to begin production. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +bool FactoryClass::Start(void) +{ + Validate(); + if ((Object || SpecialItem) && IsSuspended && !Has_Completed()) { + if (House->Available_Money() >= Cost_Per_Tick()) { + int time; + + if (Object) { + time = Object->Class_Of().Time_To_Build(House->Class->House); + } else { + time = TICKS_PER_MINUTE * 5; + } + + int frac = House->Power_Fraction(); + frac = Bound(frac, 0x0010, 0x0100); + int rate = (time*256) / frac; + + rate /= STEP_COUNT; + rate = Bound(rate, 1, 255); + + Set_Rate(rate); + IsSuspended = false; + return(true); + } + } + return(false); +} + + +/*********************************************************************************************** + * FactoryClass::Abandon -- Abandons current construction with money refunded. * + * * + * This routine is used when construction is to be abandoned and current money spend is * + * to be refunded. This function effectively clears out this factory of all record of the * + * producing object so that it may either be deleted or started anew with the Set() * + * function. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was an object actually abandoned? A false return value indicates that the * + * factory was not producing any object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +bool FactoryClass::Abandon(void) +{ + Validate(); + if (Object) { + + if (Object) { + /* + ** Refund all money expended so far, back to the owner of the object under construction. + */ + House->Refund_Money(Object->Class_Of().Cost_Of() - Balance); + Balance = 0; + + /* + ** Delete the object under construction. + */ + ScenarioInit++; + delete Object; + Object = NULL; + ScenarioInit--; + } + if (SpecialItem) { + SpecialItem = SPC_NONE; + } + + /* + ** Set the factory back to the idle and empty state. + */ + Set_Rate(0); + Set_Stage(0); + IsSuspended = true; + IsDifferent = true; + + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * FactoryClass::Completion -- Fetchs the completion step for this factory. * + * * + * Use this routine to determine what animation (or completion step) the factory is * + * currently on. * + * * + * INPUT: none * + * * + * OUTPUT: Returns a completion step number beteen 0 (uncompleted), to STEP_COUNT (completed) * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +int FactoryClass::Completion(void) +{ + Validate(); + return(Fetch_Stage()); +} + + +/*********************************************************************************************** + * FactoryClass::Has_Completed -- Checks to see if object has completed production. * + * * + * Use this routine to examine the factory object in order to determine if the associated * + * object has completed production and is awaiting placement. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Is the associated object to the factory completed and ready for placement? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +bool FactoryClass::Has_Completed(void) +{ + Validate(); + if (Object && Fetch_Stage() == STEP_COUNT) { + return(true); + } + if (SpecialItem && Fetch_Stage() == STEP_COUNT) { + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * FactoryClass::Get_Object -- Fetches pointer to object being constructed. * + * * + * This routine gets the pointer to the currently constructing object. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the object undergoing construction. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +TechnoClass * FactoryClass::Get_Object(void) const +{ + Validate(); + return(Object); +} + + +/*************************************************************************** + * FactoryClass::Get_Special_Item -- gets factorys spc prod item * + * * + * INPUT: none * + * * + * OUTPUT: int the item the factory is currently working on * + * * + * HISTORY: * + * 05/05/1995 PWG : Created. * + *=========================================================================*/ +int FactoryClass::Get_Special_Item(void) const +{ + Validate(); + return(SpecialItem); +} + + +/*********************************************************************************************** + * FactoryClass::Cost_Per_Tick -- Breaks entire production cost into managable chunks. * + * * + * Use this routine to determine the cost per game "tick" to produce the object. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the number of credits necessary to advance production one game tick. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +int FactoryClass::Cost_Per_Tick(void) +{ + Validate(); + if (Object) { + int steps = STEP_COUNT - Fetch_Stage(); + if (steps) { + return(Balance / steps); + } + return(Balance); + } + return(0); +} + + +/*********************************************************************************************** + * FactoryClass::Completed -- Clears factory object after a completed production process. * + * * + * This routine is called after production completes, and the object produced has been * + * placed into the game. It resets the factory for deletion or starting of new production. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Did any resetting occur? Failure is the result of the factory not having * + * any completed object. An immediate second call to this routine will also * + * yield false. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +bool FactoryClass::Completed(void) +{ + Validate(); + if (Object && Fetch_Stage() == STEP_COUNT) { + Object = NULL; + IsSuspended = true; + IsDifferent = true; + Set_Stage(0); + Set_Rate(0); + return(true); + } + + if (SpecialItem && Fetch_Stage() == STEP_COUNT) { + SpecialItem = SPC_NONE; + IsSuspended = true; + IsDifferent = true; + Set_Stage(0); + Set_Rate(0); + return(true); + } + return(false); +} + + diff --git a/FACTORY.H b/FACTORY.H new file mode 100644 index 0000000..9c01035 --- /dev/null +++ b/FACTORY.H @@ -0,0 +1,144 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\factory.h_v 2.17 16 Oct 1995 16:45:44 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FACTORY.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/26/94 * + * * + * Last Update : December 26, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef FACTORY_H +#define FACTORY_H + +#include "stage.h" + +class FactoryClass : private StageClass +{ + public: + FactoryClass(void); + ~FactoryClass(void); + static void * operator new(size_t size); + static void operator delete(void *ptr); + + static void Init(void); + + /* + ** File I/O. + */ + bool Load(FileClass & file); + bool Save(FileClass & file); + void Code_Pointers(void); + void Decode_Pointers(void); + + bool Abandon(void); + bool Completed(void); + bool Has_Changed(void); + bool Has_Completed(void); + bool Is_Building(void) const {return(Fetch_Rate() != 0);}; + bool Set(TechnoTypeClass const & object, HouseClass & house); + bool Set(int const & type, HouseClass & house); + bool Start(void); + bool Suspend(void); + int Completion(void); + TechnoClass * Get_Object(void) const; + int Get_Special_Item(void) const; + void AI(void); + void Set(TechnoClass & object); + HouseClass * Get_House(void) {return(House);}; + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + /* + ** This flag is used to maintain the pool of factory class objects. If the object has + ** been allocated, then this flag is true. Otherwise, the object is free to be + ** allocated. + */ + unsigned IsActive:1; + + protected: + enum StepCountEnum { + STEP_COUNT=108 // Number of steps to break production down into. + }; + + int Cost_Per_Tick(void); + + private: + + /* + ** If production is temporarily suspended, then this flag will be true. A factory + ** is suspended when it is first created, when production has completed, and when + ** explicitly instructed to Suspend() production. Suspended production is not + ** abandoned. It may be resumed with a call to Start(). + */ + unsigned IsSuspended:1; + + /* + ** If the AI process detected that the production process has advanced far enough + ** that a change in the building animation would occur, this flag will be true. + ** Examination of this flag (through the Has_Chaged function) allows intelligent + ** updating of any production graphic. + */ + unsigned IsDifferent:1; + + /* + ** This records the balance due on the current production item. This value will + ** be reduced as production proceeds. It will reach zero the moment production has + ** finished. Using this method ensures that the total production cost will be EXACT + ** regardless of the number of installment payments that are made. + */ + int Balance; + int OriginalBalance; + + /* + ** This is the object that is being produced. It is held in a state of limbo while + ** undergoing production. Since the object is created at the time production is + ** started, it is always available when production completes. + */ + TechnoClass * Object; + + /* + ** If the factory is not producing an object and is instead producing + ** a special item, then special item will be set. + */ + int SpecialItem; + + /* + ** The factory has to be doing production for one house or another. + ** The house pointer will point to whichever house it is being done + ** for. + */ + HouseClass * House; +}; + +#endif diff --git a/FIELD.CPP b/FIELD.CPP new file mode 100644 index 0000000..ad77fba --- /dev/null +++ b/FIELD.CPP @@ -0,0 +1,214 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*************************************************************************** + * * + * Project Name : Westwood Auto Registration App * + * * + * File Name : FIELD.CPP * + * * + * Programmer : Philip W. Gorrow * + * * + * Start Date : 04/22/96 * + * * + * Last Update : April 22, 1996 [PWG] * + * * + * Actual member function for the field class. * + *-------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#include +#include "field.h" + +FieldClass::FieldClass(char *id, char data) +{ + strncpy(ID, id, sizeof(ID)); + DataType = TYPE_CHAR; + Size = sizeof(data); + Data = new char[Size]; + memcpy(Data, &data, Size); + Next = NULL; +} + +FieldClass::FieldClass(char *id, unsigned char data) +{ + strncpy(ID, id, sizeof(ID)); + DataType = TYPE_UNSIGNED_CHAR; + Size = sizeof(data); + Data = new char[Size]; + memcpy(Data, &data, Size); + Next = NULL; +} + +FieldClass::FieldClass(char *id, short data) +{ + strncpy(ID, id, sizeof(ID)); + DataType = TYPE_SHORT; + Size = sizeof(data); + Data = new char[Size]; + memcpy(Data, &data, Size); + Next = NULL; +} + +FieldClass::FieldClass(char *id, unsigned short data) +{ + strncpy(ID, id, sizeof(ID)); + DataType = TYPE_UNSIGNED_SHORT; + Size = sizeof(data); + Data = new char[Size]; + memcpy(Data, &data, Size); + Next = NULL; +} + +FieldClass::FieldClass(char *id, long data) +{ + strncpy(ID, id, sizeof(ID)); + DataType = TYPE_LONG; + Size = sizeof(data); + Data = new char[Size]; + memcpy(Data, &data, Size); + Next = NULL; +} + +FieldClass::FieldClass(char *id, unsigned long data) +{ + strncpy(ID, id, sizeof(ID)); + DataType = TYPE_UNSIGNED_LONG; + Size = sizeof(data); + Data = new char[Size]; + memcpy(Data, &data, Size); + Next = NULL; +} + +FieldClass::FieldClass(char *id, char *data) +{ + strncpy(ID, id, sizeof(ID)); + DataType = TYPE_STRING; + Size = (unsigned short)(strlen(data)+1); + Data = new char[Size]; + memcpy(Data, data, Size); + Next = NULL; +} + +FieldClass::FieldClass(char *id, void *data, int length) +{ + strncpy(ID, id, sizeof(ID)); + DataType = TYPE_CHUNK; + Size = (unsigned short)length; + Data = new char[Size]; + memcpy(Data, data, Size); + Next = NULL; +} + + +/************************************************************************** + * PACKETCLASS::HOST_TO_NET_FIELD -- Converts host field to net format * + * * + * INPUT: FIELD * to the data field we need to convert * + * * + * OUTPUT: none * + * * + * HISTORY: * + * 04/22/1996 PWG : Created. * + *========================================================================*/ +void FieldClass::Host_To_Net(void) +{ + // + // Before we convert the data type, we should convert the actual data + // sent. + // + switch (DataType) { + case TYPE_CHAR: + case TYPE_UNSIGNED_CHAR: + case TYPE_STRING: + case TYPE_CHUNK: + break; + + case TYPE_SHORT: + case TYPE_UNSIGNED_SHORT: + *((unsigned short *)Data) = htons(*((unsigned short *)Data)); + break; + + case TYPE_LONG: + case TYPE_UNSIGNED_LONG: + *((unsigned long *)Data) = htonl(*((unsigned long *)Data)); + break; + + // + // Might be good to insert some type of error message here for unknown + // datatypes -- but will leave that for later. + // + default: + break; + } + // + // Finally convert over the data type and the size of the packet. + // + DataType = htons(DataType); + Size = htons(Size); +} +/************************************************************************** + * PACKETCLASS::NET_TO_HOST_FIELD -- Converts net field to host format * + * * + * INPUT: FIELD * to the data field we need to convert * + * * + * OUTPUT: none * + * * + * HISTORY: * + * 04/22/1996 PWG : Created. * + *========================================================================*/ +void FieldClass::Net_To_Host(void) +{ + // + // Convert the variables to host order. This needs to be converted so + // the switch statement does compares on the data that follows. + // + Size = ntohs(Size); + + DataType = ntohs(DataType); + + // + // Before we convert the data type, we should convert the actual data + // sent. + // + switch (DataType) { + case TYPE_CHAR: + case TYPE_UNSIGNED_CHAR: + case TYPE_STRING: + case TYPE_CHUNK: + break; + + case TYPE_SHORT: + case TYPE_UNSIGNED_SHORT: + *((unsigned short *)Data) = ntohs(*((unsigned short *)Data)); + break; + + case TYPE_LONG: + case TYPE_UNSIGNED_LONG: + *((unsigned long *)Data) = ntohl(*((unsigned long *)Data)); + break; + + // + // Might be good to insert some type of error message here for unknown + // datatypes -- but will leave that for later. + // + default: + break; + } +} + diff --git a/FIELD.H b/FIELD.H new file mode 100644 index 0000000..9335437 --- /dev/null +++ b/FIELD.H @@ -0,0 +1,85 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*************************************************************************** + * * + * Project Name : Westwood Auto Registration App * + * * + * File Name : FIELD.H * + * * + * Programmer : Philip W. Gorrow * + * * + * Start Date : 04/22/96 * + * * + * Last Update : April 22, 1996 [PWG] * + * * + * This module takes care of maintaining the field list used to process * + * packets. * + * * + *-------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#ifndef __FIELD_H +#define __FIELD_H + +#include +#include + +#define FIELD_HEADER_SIZE (sizeof(FieldClass) - (sizeof(void *) * 2)) + +#define TYPE_CHAR 1 +#define TYPE_UNSIGNED_CHAR 2 +#define TYPE_SHORT 3 +#define TYPE_UNSIGNED_SHORT 4 +#define TYPE_LONG 5 +#define TYPE_UNSIGNED_LONG 6 +#define TYPE_STRING 7 +#define TYPE_CHUNK 20 + +class PacketClass; + +class FieldClass { + + public: + friend class PacketClass; + // + // Define constructors to be able to create all the different kinds + // of fields. + // + FieldClass(void) {}; + FieldClass(char *id, char data); + FieldClass(char *id, unsigned char data); + FieldClass(char *id, short data); + FieldClass(char *id, unsigned short data); + FieldClass(char *id, long data); + FieldClass(char *id, unsigned long data); + FieldClass(char *id, char *data); + FieldClass(char *id, void *data, int length); + + void Host_To_Net(void); + void Net_To_Host(void); + + private: + char ID[4]; // id value of this field + unsigned short DataType; // id of the data type we are using + unsigned short Size; // size of the data portion of this field + void *Data; // pointer to the data portion of this field + FieldClass *Next; // pointer to the next field in the field list +}; + +#endif diff --git a/FINDPATH.CPP b/FINDPATH.CPP new file mode 100644 index 0000000..ec7a10c --- /dev/null +++ b/FINDPATH.CPP @@ -0,0 +1,1546 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\findpath.cpv 2.17 16 Oct 1995 16:51:04 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FINDPATH.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : May 25, 1995 [PWG] * + * * + * The path algorithm works by following a LOS path to the target. If it * + * collides with an impassable spot, it uses an Edge following routine to * + * get around it. The edge follower moves along the edge in a clockwise or * + * counter clockwise fashion until finding the destination spot. The * + * destination is determined by Find_Path. It is the first passable that * + * can be reached (so it will handle the doughnut case, where there is * + * a passable in the center of an unreachable area). * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Clear_Path_Overlap -- clears the path overlap list * + * Find_Path -- Find a path from point a to point b. * + * Find_Path_Cell -- Finds a given cell on a specified path * + * Follow_Edge -- Follow an edge to get around an impassable spot. * + * FootClass::Unravel_Loop -- Unravels a loop in the movement path * + * Get_New_XY -- Get the new x,y based on current position and direction. * + * Optimize_Moves -- Optimize the move list. * + * Set_Path_Overlap -- Sets the overlap bit for given cell * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +//#include + +/* +** When an edge search is started, it can be performed CLOCKwise or +** COUNTERCLOCKwise direction. +*/ +#define CLOCK (FacingType)1 // Clockwise. +#define COUNTERCLOCK (FacingType)-1 // Counterclockwise. + +/* +** If defined, diagonal moves are allowed, else no diagonals. +*/ +#define DIAGONAL + +/* +** This is the marker to signify the end of the path list. +*/ +#define END FACING_NONE + +/* +** If memory is more important than speed, set this define to +** true. It will then perform intermediate optimizations to get the most +** milage out of a limited movement list staging area. If this value +** is true then it figures paths a bit more intelligently. +*/ +#define SAVEMEM true + +/* +** Modify this macro so that given two cell values, it will return +** a value between 0 and 7, with 0 being North and moving +** clockwise (just like map degrees). +*/ +#define CELL_FACING(a,b) Dir_Facing(::Direction((a),(b))) + + +/*-------------------------------------------------------------------------*/ +/* +** Cells values are really indexes into the 'map'. The following value is +** the X width of the map. +*/ +#define MODULO MAP_CELL_W + +/* +** Maximum lookahead cells. Twice this value in bytes will be +** reserved on the stack. The smaller this number, the faster the processing. +*/ +#define MAX_MLIST_SIZE 300 +#define THREAT_THRESHOLD 5 + +#ifdef NEVER +typedef enum { + FACING_N, // North + FACING_NE, // North-East + FACING_E, // East + FACING_SE, // South-East + FACING_S, // South + FACING_SW, // South-West + FACING_W, // West + FACING_NW, // North-West + + FACING_COUNT // Total of 8 directions (0..7). +} FacingType; +#endif + + +/*-------------------------------------------------------------------------*/ +static bool DrawPath; + +inline FacingType Opposite(FacingType face) +{ + return( (FacingType) (face ^ 4)); +} + +static inline void Draw_Cell_Point(CELL cell, bool passable, int threat_stage, int overide = 0) +{ + if (DrawPath) { + if (!Debug_Find_Path) { + int x, y; + + if (Map.Coord_To_Pixel(Cell_Coord(cell), x, y)) { + if (threat_stage>2) { + SeenBuff.Put_Pixel(x, y, (passable) ? LTGREEN : RED); + } else { + SeenBuff.Put_Pixel(x, y, (passable) ? 9+threat_stage : RED); + } + } + } else { + int x = cell & 63; + int y = cell / 64; + if (!overide) { + SeenBuff.Put_Pixel(64 + (x * 3) + 1, 8 + (y * 3) + 1, (passable) ? WHITE : BLACK); + } else { + SeenBuff.Put_Pixel(64 + (x * 3) + 1, 8 + (y * 3) + 1, overide); + } + } + } +} + +inline static FacingType Next_Direction(FacingType facing, FacingType dir) +{ + facing = facing + dir; + #ifndef DIAGONAL + facing = (FacingType)(facing & 0x06); + #endif + return(facing); +} + +/*=========================================================================*/ +/* Define a couple of variables which are private to the module they are */ +/* declared in. */ +/*=========================================================================*/ +static unsigned long MainOverlap[MAP_CELL_TOTAL/32]; // overlap list for the main path +static unsigned long LeftOverlap[MAP_CELL_TOTAL/32]; // overlap list for the left path +static unsigned long RightOverlap[MAP_CELL_TOTAL/32]; // overlap list for the right path + + +//static CELL MoveMask = 0; +static CELL DestLocation; +static CELL StartLocation; + +/*************************************************************************** + * Point_Relative_To_Line -- Relation between a point and a line * + * * + * If a point is on a line then the following function holds true: * + * (x - x2)(z1 - z2) = (z - z2)(x1 - x2) given x,z a point on the * + * line (x1,z1),(x2,z2). * + * If the right side is > then the left side then the point is on one * + * side of the line and if the right side is < the the left side, then* + * the point is on the other side of the line. By subtracting one side* + * from the other we can determine on what side (if any) the point is on* + * by testing the side of the resulting subtraction. * + * * + * INPUT: * + * int x - x pos of point. * + * int z - z pos of point. * + * int x1 - x pos of first end of line segment. * + * int z1 - z pos of first end of line segment. * + * int x1 - x pos of second end of line segment. * + * int z1 - z pos of second end of line segment. * + * * + * OUTPUT: * + * Assuming (x1,z1) is north, (x2,z2) is south: * + * 0 : point is on line. * + * > 0 : point is east of line. * + * < 0 : point is west of line. * + * * + * WARNINGS: * + * Remember that int means that is assumes 16 bits of persision. * + * * + * HISTORY: * + * 10/28/1994 SKB : Created. * + *=========================================================================*/ +int Point_Relative_To_Line(int x, int z, int x1, int z1, int x2, int z2) +{ + return((((long)x - (long)x2) * ((long)z1 - (long)z2)) - (((long)z - (long)z2) * ((long)x1 - (long)x2))); +} + + +/*************************************************************************** + * FootClass::Unravel_Loop -- Unravels a loop in the movement path * + * * + * While in the midst of the Follow Edge logic, it is possible (due to the * + * fact that we support diagonal movement) to begin looping around a * + * column of some type. The Unravel loop function will scan backward * + * through the list and fixup the path to try to prevent the loop. * + * * + * INPUT: path - pointer to the generated path so we can pull the * + * commands out of it. * + * cell - the cell we tried to enter that generated the * + * double overlap condition. * + * dir - the direction we tried to enter from when we * + * generated the double overlap condition * + * startx - the start x position of this path segment * + * starty - the start y position of this path segment * + * destx - the dest x position for this path segment * + * desty - the dest y position for this path segment * + * * + * OUTPUT: TRUE - loop has been sucessfully unravelled * + * FALSE - loop can not be unravelled so abort follow edge * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/25/1995 PWG : Created. * + *=========================================================================*/ +bool FootClass::Unravel_Loop(PathType *path, CELL &cell, FacingType &dir, int sx, int sy, int dx, int dy, MoveType threshhold) +{ + /* + ** Walk back to the actual cell before we advanced our position + */ + FacingType curr_dir = dir; + CELL curr_pos = Adjacent_Cell(cell, Opposite(curr_dir)); + int idx = path->Length; // start at the last position + FacingType *list = &path->Command[idx-1]; // point to the last command + int checkx; + int checky; + int last_was_line = false; + + /* + ** loop backward through the list searching for a point that is + ** on the line. If the point was a diagonal move then adjust + ** it. + */ + while (idx) { + checkx = Cell_X(curr_pos); + checky = Cell_Y(curr_pos); + + if (!Point_Relative_To_Line(checkx, checky, sx, sy, dx, dy) || last_was_line) { + + /* + ** We have now found a point on the line. Now we must check to see + ** if we left the line on a diagonal. If we did then we need to fix + ** it up. + */ + if (curr_dir & 1 && curr_pos != path->LastFixup) { + cell = curr_pos; + dir = *(list-1); + path->Length = idx; + path->LastFixup = curr_pos; + Draw_Cell_Point(curr_pos, true, -1, CYAN); + return(true); + } + + last_was_line = !last_was_line; + } + + /* + ** Since this cell will not be in the list, then pull out its cost + */ + path->Cost -= Passable_Cell(curr_pos, *list, -1, threshhold); + + /* + ** Remove this cells flag from the overlap list for the path + */ + path->Overlap[curr_pos >> 5] &= ~(1 << ((curr_pos & 31) - 1)); + + /* + ** Mark cell on the map + */ + Draw_Cell_Point(curr_pos, true, -1, LTCYAN); + + /* + ** Adjust to the next list position and direction. + */ + curr_dir = *list--; + curr_pos = Adjacent_Cell(curr_pos, Opposite(curr_dir)); + idx--; + } + + /* + ** If we can't modify the list to eliminate the problem, then we have + ** a larger problem in that we have deleted all of the cells in the + ** list. + */ + return(false); +} + + +/*************************************************************************** + * Register_Cell -- registers a cell on our path and check for backtrack * + * * + * This function adds a new cell to our path. If the cell has already * + * been recorded as part of our path, then this function moves back down * + * the list truncating it at the point we registered that cell. This * + * function will elliminate all backtracking from the list. * + * * + * INPUT: long * list - the list to set the overlap bit for * + * CELL cell - the cell to mark on the overlap list * + * * + * OUTPUT: BOOL - TRUE if bit has been set, FALSE if bit already set * + * * + * HISTORY: * + * 05/23/1995 PWG : Created. * + *=========================================================================*/ +bool FootClass::Register_Cell(PathType *path, CELL cell, FacingType dir, int cost, MoveType threshhold) +{ + FacingType *list; + int pos = cell >> 5; + int bit = (cell & 31) - 1; + + /* + ** See if this point has already been registered as on the list. If so + ** we need to truncate the list back to this point and register the + ** new direction. + */ + if (path->Overlap[pos] & (1 << bit)) { + /* + ** If this is not a case of immediate back tracking then handle + ** by searching the list to see what we find. However is this is + ** an immediate back track, then pop of the last direction + ** and unflag the cell we are in (not the cell we are moving to). + ** Note: That we do not check for a zero length cell because we + ** could not have a duplicate unless there are cells in the list. + */ + + if (path->Command[path->Length - 1] == Opposite(dir)) { + CELL pos = Adjacent_Cell(cell, Opposite(dir)); + path->Overlap[pos >> 5] &= ~(1 << ((pos & 31) - 1)); + path->Length--; + Draw_Cell_Point(pos, true, -1, BLUE); + } else { + /* + ** If this overlap is in the same place as we had our last overlap + ** then we are in a loop condition. We need to signify that we + ** cannot register this cell. + */ + if (path->LastOverlap == cell) { + return(false); + } else { + path->LastOverlap = cell; + } + + CELL pos = path->Start; + int newlen = 0; + int idx = 0; + list = path->Command; + + /* + ** Note that the cell has to be in this list, so theres no sense + ** in checking whether we found it (famous last words). + ** + ** PWG 8/16/95 - However there is no sense searching the list if + ** the cell we have overlapped on is the cell we + ** started in. + */ + + if (pos != cell) { + while (idx < path->Length) { + pos = Adjacent_Cell(pos, *list); + if (pos == cell) { + idx++; + list++; + break; + } + idx++; + list++; + } + newlen = idx; + } + + /* + ** Now we are pointing at the next command in the list. From here on + ** out we need to unmark the fact that we have entered these cells and + ** adjust the cost of our path to reflect that we have not entered + ** then. + */ + while (idx < path->Length) { + pos = Adjacent_Cell(pos, *list); + path->Cost -= Passable_Cell(pos, *list, -1, threshhold); + path->Overlap[pos >> 5] &= ~(1 << ((pos & 31) - 1)); + Draw_Cell_Point(pos, true, -1, LTBLUE); + idx++; + list++; + } + path->Length = newlen; + } + } else { + /* + ** Now we need to register the new direction, updating the cell structure + ** and the cost. + */ + int cpos = path->Length++; + path->Command[cpos] = dir; // save of the direction we moved + path->Cost += cost; // figure new cost for cell + path->Overlap[pos] |= (1 << bit); // mark the we have entered point + } + return(true); +} +#ifdef OBSOLETE +bool FootClass::Register_Cell(PathType *path, CELL cell, FacingType dir, int cost, MoveType threshhold) +{ + FacingType *list; + int pos = cell >> 5; + int bit = (cell & 31) - 1; + int idx; + + /* + ** See if this point has already been registered as on the list. If so + ** we need to truncate the list back to this point and register the + ** new direction. + */ + if (path->Overlap[pos] & (1 << bit)) { + /* + ** If this is not a case of immediate back tracking then handle + ** by searching the list to see what we find. However is this is + ** an immediate back track, then pop of the last direction + ** and unflag the cell we are in (not the cell we are moving to). + ** Note: That we do not check for a zero length cell because we + ** could not have a duplicate unless there are cells in the list. + */ + + if (path->Command[path->Length - 1] == Opposite(dir)) { + CELL pos = Adjacent_Cell(cell, Opposite(dir)); + path->Overlap[pos >> 5] &= ~(1 << ((pos & 31) - 1)); + path->Length--; + Draw_Cell_Point(pos, true, -1, BLUE); + } else { + /* + ** If this overlap is in the same place as we had our last overlap + ** then we are in a loop condition. We need to signify that we + ** cannot register this cell. + */ + if (path->LastOverlap == cell) { + return(false); + } else { + path->LastOverlap = cell; + } + + CELL pos = path->Start; + int newlen = 0; + + /* + ** Note that the cell has to be in this list, so theres no sense + ** in checking whether we found it (famous last words) + */ + for (idx = 0, list = path->Command; idx < path->Length; idx++, list++) { + pos = Adjacent_Cell(pos, *list); + if (pos == cell) { + idx++; + list++; + break; + } + } + newlen = idx; + + /* + ** Now we are pointing at the next command in the list. From here on + ** out we need to unmark the fact that we have entered these cells and + ** adjust the cost of our path to reflect that we have not entered + ** then. + */ + while (idx < path->Length) { + pos = Adjacent_Cell(pos, *list); + path->Cost -= Passable_Cell(pos, *list, -1, threshhold); + path->Overlap[pos >> 5] &= ~(1 << ((pos & 31) - 1)); + Draw_Cell_Point(pos, true, -1, LTBLUE); + idx++; + list++; + } + path->Length = newlen; + } + } else { + /* + ** Now we need to register the new direction, updating the cell structure + ** and the cost. + */ + int cpos = path->Length++; + path->Command[cpos] = dir; // save of the direction we moved + path->Cost += cost; // figure new cost for cell + path->Overlap[pos] |= (1 << bit); // mark the we have entered point + } + return(true); +} +#endif + + +/*********************************************************************************************** + * Find_Path -- Find a path from point a to point b. * + * * + * INPUT: int source x,y, int destination x,y, char *final moves * + * array to store moves, int maximum moves we may attempt * + * * + * OUTPUT: int number of moves it took (IMPOSSIBLE_MOVES if we could * + * not reach the destination * + * * + * WARNINGS: This algorithm assumes that the target is NOT situated * + * inside an impassable. If this case may arise, the do-while * + * statement inside the inner while (true) must be changed * + * to include a check to se if the next_x,y is equal to the * + * dest_x,y. If it is, then return(IMPOSSIBLE_MOVES). * + * * + * HISTORY: * + * 07/08/1991 CY : Created. * + *=============================================================================================*/ +PathType * FootClass::Find_Path(CELL dest, FacingType *final_moves, int maxlen, MoveType threshhold) +{ + CELL source = Coord_Cell(Coord); // Source expressed as cell + static PathType path; // Main path control. + CELL next; // Next cell to enter + CELL startcell; // Cell we started in + FacingType direction; // Working direction of look ahead. + FacingType newdir; // Tentative facing value. + + bool left=false, // Was leftward path legal? + right=false; // Was rightward path legal? + + int len; // Length of detour command list. + int unit_threat; // Calculated unit threat rating + int cost; // Cost to enter the square + FacingType moves_left[MAX_MLIST_SIZE+2], // Counterclockwise move list. + moves_right[MAX_MLIST_SIZE+2]; // Clockwise move list. + PathType pleft,pright; // Path control structures. + PathType *which; // Which path to actually use. + int threat; + int threat_stage; + + /* + ** If we have been provided an illegal place to store our final moves + ** then forget it. + */ + if (!final_moves) return(NULL); +// IsFindPath = true; + + /* + ** Set the draw path variable to draw the path of the selected unit + ** if necessary. + */ + if (!Debug_Find_Path) { + DrawPath = IsSelected && Special.IsShowPath; + } else { + DrawPath = IsSelected; + } + Debug_Draw_Map("Initial Draw", source, dest, false); + +// MoveMask = flags; + if (Team && Team->Class->IsRoundAbout) { + unit_threat = (Team) ? Team->Risk : Risk(); + threat_stage = 0; + threat = 0; + } else { + unit_threat = threat = -1; + } + + StartLocation = source; + DestLocation = dest; + + /* + ** Initialize the path structure so that we can keep track of the + ** path. + */ + path.Start = source; + path.Cost = 0; + path.Length = 0; + path.Command = final_moves; + path.Command[0] = END; + path.Overlap = MainOverlap; + path.LastOverlap = -1; + path.LastFixup = -1; + + memset(path.Overlap, 0, sizeof(MainOverlap)); + + /* + ** Clear the over lap list and then make sure that our starting position is marked + ** on the overlap list. (Otherwise the harvesters will drive in circles... ) + */ +// memset(path.Overlap, 0, 512); + path.Overlap[source >> 5] |= (1 << ((source & 31) - 1)); + + startcell = source; + + /* + ** Account for trailing end of list command, so reduce the maximum + ** allowed legal commands to reflect this. + */ + maxlen--; + + /* + ** As long as there is room to put commands in the movement command list, + ** then put commands in it. We build the path using the following + ** methodology. + ** + ** 1. Scan through the desired strait line path until we eiter hit an + ** impassable or have created a valid path. + ** + ** 2. If we have hit an impassable, walk through the impassable to make + ** sure that there is a passable on the other side. If there is not + ** and we can not change the impassable, then this list is dead. + ** + ** 3. Walk around the impassable on both the left and right edges and + ** take the shorter of the two paths. + ** + ** 4. Taking the new location as our start location start again with + ** step #1. + */ + while (path.Length < maxlen) { + +top_of_list: + /* + ** Have we reached the destination already? If so abort any further + ** command building. + */ + if (startcell == dest) { + break; + } + + /* + ** Find the absolute correct direction to reach the next straight + ** line cell and what cell it is. + */ + direction = CELL_FACING(startcell, dest); + next = Adjacent_Cell(startcell, direction); + + /* + ** If we can move here, then make this our next move. + */ + cost = Passable_Cell(next, direction, threat, threshhold); + if (cost) { + Draw_Cell_Point(next, true, threat_stage); + Register_Cell(&path, next, direction, cost, threshhold); + } else { + if (Debug_Find_Path && DrawPath) { + Debug_Draw_Map("Walk Through Obstacle", startcell, dest, true); + } + Draw_Cell_Point(next, false, threat_stage); + + /* + ** If the impassable location is actually the destination, + ** then stop here and consider this "good enough". + */ + if (next == dest) break; + + /* + ** We could not move to the next cell, so follow through the + ** impassable until we find a passable spot that can be reached. + ** Once we find a passable, figure out the shortest path to it. + ** Since we have variable passable conditions this is not as + ** simple as it used to be. The limiter loop below allows us to + ** step through ten donuts before we give up. + */ + for (int limiter = 0; limiter < 5; limiter++) { + + /* + ** Get the next passable position by zipping through the + ** impassable positions until a passable position is found + ** or the destination is reached. + */ + for (;;) { + + /* + ** Move one step closer toward destination. + */ + newdir = CELL_FACING(next, dest); + next = Adjacent_Cell(next, newdir); + + /* + ** If the cell is passable then we have been completely + ** sucessful. If the cell is not passable then continue. + */ + if ((Passable_Cell(next, FACING_NONE, threat, threshhold)) || (next == dest)) { + Draw_Cell_Point(next, true, threat_stage); + break; + } else { + Draw_Cell_Point(next, false, threat_stage); + } + + /* + ** If we reached destination while in this loop, we + ** know that either the destination is impassible (if + ** we are ignoring) or that we need to up our threat + ** tolerance and try again. + */ + if (next == dest) { + if (threat != -1) { + switch (threat_stage++) { + case 0: + threat = unit_threat >> 1; + break; + + case 1: + threat += unit_threat; + break; + + case 2: + threat = -1; + break; + } + goto top_of_list; + } + goto end_of_list; + } + } + + /* + ** Try to find a path to the passable position by following + ** the edge of the blocking object in both CLOCKwise and + ** COUNTERCLOCKwise fashions. + */ + int follow_len = maxlen + (maxlen >> 1); + + Debug_Draw_Map("Follow left edge", startcell,next,true); + Mem_Copy(&path, &pleft, sizeof(PathType)); + pleft.Command = &moves_left[0]; + pleft.Overlap = LeftOverlap; + Mem_Copy(path.Command, pleft.Command, path.Length); + Mem_Copy(path.Overlap, pleft.Overlap, sizeof(LeftOverlap)); + left = Follow_Edge(startcell, next, &pleft, COUNTERCLOCK, direction, threat, threat_stage, sizeof(moves_left), threshhold); +// left = Follow_Edge(startcell, next, &pleft, COUNTERCLOCK, direction, threat, threat_stage, follow_len, threshhold); + + if (left) { + follow_len = MIN(maxlen, pleft.Length + (pleft.Length >> 1)); + } + + /* + ** If we are in debug mode then let us know how well our left path + ** did. + */ + if (Debug_Find_Path && DrawPath) { + Fancy_Text_Print(" Left", 0, 92, WHITE, BLACK, TPF_6POINT); + Fancy_Text_Print("Total Steps", 0, 100, WHITE, BLACK, TPF_6POINT); + if (left) { + Fancy_Text_Print(" %d", 0, 108, WHITE, BLACK, TPF_6POINT, pleft.Length); + } else { + Fancy_Text_Print(" FAIL", 0, 108, WHITE, BLACK, TPF_6POINT); + } + } + + Debug_Draw_Map("Follow right edge", startcell, next, true); + Mem_Copy(&path, &pright, sizeof(PathType)); + pright.Command = &moves_right[0]; + pright.Overlap = RightOverlap; + Mem_Copy(path.Command, pright.Command, path.Length); + Mem_Copy(path.Overlap, pright.Overlap, sizeof(RightOverlap)); + right = Follow_Edge(startcell, next, &pright, CLOCK, direction, threat, threat_stage, sizeof(moves_right), threshhold); +// right = Follow_Edge(startcell, next, &pright, CLOCK, direction, threat, threat_stage, follow_len, threshhold); + + /* + ** If we are in debug mode then let us know how well our right path + ** did. + */ + if (Debug_Find_Path && DrawPath) { + Fancy_Text_Print(" Right", 0, 92, WHITE, BLACK, TPF_6POINT); + Fancy_Text_Print("Total Steps", 0, 100, WHITE, BLACK, TPF_6POINT); + if (right) { + Fancy_Text_Print(" %d", 0, 108, WHITE, BLACK, TPF_6POINT, pright.Length); + } else { + Fancy_Text_Print(" FAIL", 0, 108, WHITE, BLACK, TPF_6POINT); + } + } + + /* + ** If we could find a path, break from this loop. Otherwise this + ** means that we have found a "hole" of passable terrain that + ** cannot be reached by normal means. Scan forward looking for + ** the other side of the "doughnut". + */ + if (left || right) break; + + /* + ** If no path can be found to the intermediate cell, then + ** presume we have found a doughnut of some sort. Scan + ** forward until the next impassable is found and then + ** process this loop again. + */ + do { + + /* + ** If we reached destination while in this loop, we + ** know that either the destination is impassible (if + ** we are ignoring) or that we need to up our threat + ** tolerance and try again. + */ + if (next == dest) { + if (threat != -1) { + switch (threat_stage++) { + case 0: + threat = unit_threat >> 1; + break; + + case 1: + threat += unit_threat; + break; + + case 2: + threat = -1; + break; + } + goto top_of_list; + } + goto end_of_list; + } + + newdir = CELL_FACING(next, dest); + next = Adjacent_Cell(next, newdir); + } while (Passable_Cell(next, newdir, threat, threshhold)); + } + + if (!left && !right) break; + + /* + ** We found a path around the impassable locations, so figure out + ** which one was the smallest and copy those moves into the + ** path.Command array. + */ + which = &pleft; + if (right) { + which = &pright; + if (left) { + if (pleft.Length < pright.Length) { + which = &pleft; + } else { + which = &pright; + } + } + } + + /* + ** Record as much as possible of the shorter of the two + ** paths. The trailing EOL command is not copied because + ** this may not be the end of the find path logic. + */ + len = which->Length; + len = MIN(len, maxlen); + if (len > 0) { + memcpy(&path.Overlap[0], &which->Overlap[0], sizeof(LeftOverlap)); + memcpy(&path.Command[0], &which->Command[0], len); + path.Length = len; + path.Cost = which->Cost; + path.LastOverlap = -1; + path.LastFixup = -1; + } else { + break; + } + Debug_Draw_Map("Walking to next obstacle", next, dest, true); + } + startcell = next; + } + +end_of_list: + /* + ** Poke in the stop command. + */ + if (path.Length < maxlen) { + path.Command[path.Length++] = END; + } + if (Debug_Find_Path && DrawPath) { + Map.Flag_To_Redraw(true); + } + /* + ** Optimize the move list but only necessary if + ** diagonal moves are allowed. + */ + #ifdef DIAGONAL + Optimize_Moves(&path, threshhold); + #endif + if (Debug_Find_Path && DrawPath) { + Debug_Draw_Map("Final Generated Path", startcell,dest,false); + Debug_Draw_Path(&path); + Get_Key_Num(); + } +// IsFindPath = false; + return(&path); +} + + +/*********************************************************************************************** + * Follow_Edge -- Follow an edge to get around an impassable spot. * + * * + * INPUT: start -- cell to head from * + * * + * target -- Target cell to head to. * + * * + * path -- Pointer to path list structure. * + * * + * search -- Direction of search (1=clock, -1=counterclock). * + * * + * olddir -- Facing impassible direction from start. * + * * + * callback -- Function pointer for determining if a cell is * + * passable or not. * + * * + * OUTPUT: bool: Could a path be found to the desired cell? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1991 CY : Created. * + * 06/01/1992 JLB : Optimized & commented. * + *=============================================================================================*/ +bool FootClass::Follow_Edge(CELL start, CELL target, PathType *path, FacingType search, FacingType olddir, int threat, int threat_stage, int max_cells, MoveType threshhold) +{ + FacingType newdir; // Direction of facing before surrounding cell check. + CELL oldcell, // Current cell. + newcell; // Tentative new cell. + int cost; // Working cost value. + int startx; + int starty; + int online=true; + int targetx; + int targety; + int oldval = 0; + int cellcount=0; + int forceout = false; + FacingType firstdir = (FacingType)-1; + CELL firstcell = -1; + bool stepped_off_line = false; + startx = Cell_X(start); + starty = Cell_Y(start); + targetx = Cell_X(target); + targety = Cell_Y(target); + + if (!path) return(false); + path->LastOverlap = -1; + path->LastFixup = -1; + + #ifndef DIAGONAL + /* + ** The edge following algorithm doesn't "do" diagonals. Force initial facing + ** to be an even 90 degree value. Adjust it in the direction it should be + ** rotating. + */ + if (olddir & 0x01) { + olddir = Next_Direction(olddir, search); + } + #endif + + newdir = Next_Direction(olddir, search); + oldcell = start; + newcell = Adjacent_Cell(oldcell, newdir); + + /* + ** Continue until we find our target, find our original starting spot, + ** or run out of moves. + */ + while (path->Length < max_cells) { + + /* + ** Look in all the adjacent cells to determine a passable one that + ** most closely matches the desired direction (working in the specified + ** direction). + */ + newdir = olddir; + for (;;) { + bool forcefail; // Is failure forced? + + forcefail = false; + + #ifdef DIAGONAL + /* + ** Rotate 45/90 degrees in desired direction. + */ + newdir = Next_Direction(newdir, search); + + /* + ** If facing a diagonal we must check the next 90 degree location + ** to make sure that we don't walk right by the destination. This + ** will happen if the destination it is at the corner edge of an + ** impassable that we are moving around. + */ + if (newdir & FACING_NE) { + CELL checkcell; // Non-diagonal check cell. + //int x,y; + + checkcell = Adjacent_Cell(oldcell, Next_Direction(newdir, search)); + + if (checkcell == target) { + + /* + ** This only works if in fact, it is possible to move to the + ** cell from the current location. + */ + cost = Passable_Cell(checkcell, Next_Direction(newdir, search), threat, threshhold); + if (cost) { + Draw_Cell_Point(checkcell, true, threat_stage); + + /* + ** YES! The destination is at the corner of an impassable, so + ** set the direction to point directly at it and then the + ** scanning will terminate later. + */ + newdir = Next_Direction(newdir, search); + newcell = Adjacent_Cell(oldcell, newdir); + break; + } else { + Draw_Cell_Point(checkcell, false, threat_stage); + } + } + + /* + ** Perform special diagonal check. If the edge follower would cross the + ** diagonal or fall on the diagonal line from the source, then consider + ** that cell impassible. Otherwise, the find path algorithm will fail + ** when there are two impassible locations located on a diagonal + ** that is lined up between the source and destination location. + ** + ** P.S. It might help if you check the right cell rather than using + ** the value that just happened to be in checkcell. + */ + + checkcell = Adjacent_Cell(oldcell, newdir); + + int checkx = Cell_X(checkcell); + int checky = Cell_Y(checkcell); + int checkval = Point_Relative_To_Line(checkx, checky, startx, starty, targetx, targety); + if (checkval && !online) { + forcefail = ((checkval ^ oldval) < 0); + } else { + forcefail = false; + } + /* + ** The only exception to the above is when we are directly backtracking + ** because we could be trying to escape from a culdesack! + */ + if (forcefail && path->Length > 0 && (FacingType)(newdir ^ 4) == path->Command[path->Length - 1]) { +// ST - 12/18/96 5:15PM if (forcefail && (FacingType)(newdir ^ 4) == path->Command[path->Length - 1]) { + forcefail = false; + } + } + + #else + newdir = Next_Direction(newdir, search*2); + #endif + + /* + ** If we have just checked the same heading we started with, + ** we are surrounded by impassable characters and we exit. + */ + if (newdir == olddir) { + return(false); + } + + /* + ** Get the new cell. + */ + newcell = Adjacent_Cell(oldcell, newdir); + + /* + ** If we found a passable position, this is where we should move. + */ + if (!forcefail && ((cost = Passable_Cell(newcell, newdir, threat, threshhold)) != 0)) { + Draw_Cell_Point(newcell, true, threat_stage); + break; + } else { + Draw_Cell_Point(newcell, false, threat_stage, (forcefail) ? BROWN : 0); + if (newcell == target) { + forceout = true; + break; + } + } + + } + + /* + ** Record the direction. + */ + if (!forceout) { + /* + ** Mark the cell because this is where we need to be. If register + ** cell fails then the list has been shortened and we need to adjust + ** the new direction. + */ + if (!Register_Cell(path, newcell, newdir, cost, threshhold)) { + /* + ** The only reason we could not register a cell is that we are in + ** a looping situation. So we need to try and unravel the loop if + ** we can. + */ + if (!Unravel_Loop(path, newcell, newdir, startx, starty, targetx, targety, threshhold)) { + return(false); + } + /* + ** Since we need to eliminate a diagonal we must pretend the upon + ** attaining this square, we were moving turned farther in the + ** search direction then we really were. + */ + newdir = Next_Direction(newdir, (FacingType)(search*2)); + } + /* + ** Find out which side of the line this cell is on. If it is on + ** a side, then store off that side. + */ + int newx = Cell_X(newcell); + int newy = Cell_Y(newcell); + int val = Point_Relative_To_Line(newx, newy, startx, starty, targetx, targety); + if (val) { + oldval = val; + online = false; + } else { + online = true; + } + cellcount++; + if (cellcount==100) { +// DrawPath = true; +// Debug_Find_Path = true; +// Debug_Draw_Map("Loop failure", start, target, false); +// Debug_Draw_Path(path); + return(false); + } + } + + /* + ** If we have found the target spot, we are done. + */ + if (newcell == target) { + path->Command[path->Length] = END; + return(true); + } + + /* + ** If we make a full circle back to our original spot, get out. + */ + if (newcell == firstcell && newdir == firstdir) { + return(false); + } + + if (firstcell == -1) { + firstcell = newcell; + firstdir = newdir; + } + + /* + ** Because we moved, our facing is now incorrect. We want to face toward + ** the impassable edge we are following (well, not actually toward, but + ** a little past so that we can turn corners). We have to turn 45/90 degrees + ** more than expected in anticipation of the pending 45/90 degree turn at + ** the start of this loop. + */ + #ifdef DIAGONAL + olddir = Next_Direction(newdir, (FacingType)(-(int)search*3)); + #else + olddir = Next_Direction(newdir, (FacingType)(-(int)search*4)); + #endif + oldcell = newcell; + } + + /* + ** The maximum search path is exhausted... abort with a failure. + */ + return(false); +} + + +/*********************************************************************************************** + * Optimize_Moves -- Optimize the move list. * + * * + * INPUT: char *moves to optimize * + * * + * OUTPUT: none (list is optimized) * + * * + * WARNINGS: EMPTY moves are used to hold the place of eliminated * + * commands. Also, NEVER call this routine with a list that * + * contains illegal commands. The list MUST be terminated * + * with a EOL command * + * * + * HISTORY: * + * 07/08/1991 CY : Created. * + * 06/01/1992 JLB : Optimized and commented. * + *=============================================================================================*/ +#define EMPTY (FacingType)-2 +int FootClass::Optimize_Moves(PathType *path, MoveType threshhold) +//int Optimize_Moves(PathType *path, int (*callback)(CELL, FacingType), int threshold) +{ + /* + ** Facing command pair adjustment table. Compare the facing difference between + ** the two commands. 0 means no optimization is possible. 3 means backtracking + ** so eliminate both commands. Any other value adjusts the first command facing. + */ +#ifdef DIAGONAL + static FacingType _trans[FACING_COUNT] = {(FacingType)0, (FacingType)0, (FacingType)1, (FacingType)2, (FacingType)3, (FacingType)-2, (FacingType)-1, (FacingType)0}; // Smoothing. +#else + static FacingType _trans[FACING_COUNT] = {(FacingType)0, (FacingType)0, (FacingType)0, (FacingType)2, (FacingType)3, (FacingType)-2, (FacingType)0, (FacingType)0}; +#endif + FacingType *cmd1, // Floating first command pointer. + *cmd2, // Floating second command pointer. + newcmd; // Calculated new optimized command. + FacingType newdir; // Tentative new direction for smoothing. + CELL cell; // Working cell (as it moves along path). + + /* + ** Abort if there is any illegal parameter. + */ + if (!path || !path->Command) return(0); + + /* + ** Optimization loop -- start scanning with the + ** first pair of commands (if there are at least two + ** in the command list). + */ + path->Command[path->Length] = END; // Force end of list. + cell = path->Start; + if (path->Length > 1) { + cmd2 = path->Command + 1; + while (*cmd2 != END) { + + /* + ** Set the cmd1 pointer to point to the valid command closest, but + ** previous to cmd2. Be sure not to go previous to the head of the + ** command list. + */ + cmd1 = cmd2-1; + while (*cmd1 == EMPTY && cmd1 != path->Command) { + cmd1--; + } + + /* + ** If there isn't any valid previous command, then bump the + ** cmd pointers to the next command pair and continue... + */ + if (*cmd1 == EMPTY) { + cmd2++; + continue; + } + + /* + ** Fetch precalculated command change value. 0 means leave + ** command set alone, 3 means backtrack and eliminate two + ** commands. Any other value is new direction and eliminate + ** one command. + */ + newcmd = (FacingType)(*cmd2 - *cmd1); + if (newcmd < FACING_N) newcmd = (FacingType)(newcmd + FACING_COUNT); + newcmd = _trans[newcmd]; + + /* + ** Check for backtracking. If this occurs, then eliminate the + ** two commands. This is the easiest optimization. + */ + if (newcmd == FACING_SE) { + *cmd1 = EMPTY; + *cmd2++ = EMPTY; + continue; + } + + /* + ** If an optimization code was found the process it. The command is a facing + ** offset to more directly travel toward the immediate destination cell. + */ + if (newcmd) { + + /* + ** Optimizations differ when dealing with diagonals. Especially when dealing + ** with diagonals of 90 degrees. In such a case, 90 degree optimizations can + ** only be optimized if the intervening cell is passable. The distance travelled + ** is the same, but the path is less circuitous. + */ + if (*cmd1 & FACING_NE) { + + /* + ** Diagonal optimizations are always only 45 + ** degree adjustments. + */ + newdir = Next_Direction(*cmd1, (newcmd < FACING_N) ? (FacingType)-1 : (FacingType)1); + + /* + ** Diagonal 90 degree changes can be smoothed, although + ** the path isn't any shorter. + */ + if (ABS((int)newcmd) == 1) { + if (Passable_Cell(Adjacent_Cell(cell, newdir), newdir, -1, threshhold)) { + *cmd2 = newdir; + *cmd1 = newdir; + } + // BOB 16.12.92 + cell = Adjacent_Cell(cell, *cmd1); + cmd2++; + continue; + } + } else { + newdir = Next_Direction(*cmd1, newcmd); + } + + /* + ** Allow shortening turn only on right angle moves that are based on + ** 90 degrees. Always allow 135 degree optimizations. + */ + *cmd2 = newdir; + *cmd1 = EMPTY; + + /* + ** Backup what it thinks is the current cell. + */ + while (*cmd1 == EMPTY && cmd1 != path->Command) { + cmd1--; + } + if (*cmd1 != EMPTY) { + cell = Adjacent_Cell(cell, Next_Direction(*cmd1, FACING_S)); + } else { + cell = path->Start; + } + continue; + } + + /* + ** Since we could not make an optimization, we move our + ** head pointer forward. + */ + cell = Adjacent_Cell(cell, *cmd1); + cmd2++; + } + } + + /* + ** Pack the command list to remove any EMPTY command entries. + */ + cmd1 = path->Command; + cmd2 = path->Command; + cell = path->Start; + path->Cost = 0; + path->Length = 0; + while (*cmd2 != END) { + if (*cmd2 != EMPTY) { + +#ifdef NEVER + if (Debug_ShowPath) { + int x,y,x1,y1; + + if (Map.Coord_To_Pixel(Cell_Coord(cell), x, y)) { + Map.Coord_To_Pixel(Cell_Coord(Adjacent_Cell(cell, *cmd2)), x1, y1); + Set_Logic_Page(SeenBuff); + LogicPage->Draw_Line(x, y+8, x1, y1+8, DKGREY); + } + } +#endif + + cell = Adjacent_Cell(cell, *cmd2); + path->Cost+= Passable_Cell(cell, *cmd2, -1, threshhold); + path->Length++; + *cmd1++ = *cmd2; + } + cmd2++; + } + path->Length++; + *cmd1 = END; + return(path->Length); +} + + +CELL FootClass::Safety_Point(CELL src, CELL dst, int start, int max) +{ + FacingType dir; + CELL next; + int lp; + + dir = (FacingType)(CELL_FACING(src, dst) ^ 4) - 1; + + /* + ** Loop through the different acceptable distances. + */ + for (int dist = start; dist < max; dist ++) { + + /* + ** Move to the starting location. + */ + next = dst; + + for (lp = 0; lp < dist; lp ++) { + next = Adjacent_Cell(next, dir); + } + + if (dir & 1) { + /* + ** If our direction is diagonal than we need to check + ** only one side which is as long as both of the old sides + ** together. + */ + for (lp = 0; lp < dist << 1; lp ++) { + next = Adjacent_Cell(next, dir + 3); + if (!Can_Enter_Cell(next)) { + return(next); + } + } + } else { + /* + ** If our direction is not diagonal than we need to check two + ** sides so that we are checking a corner like location. + */ + for (lp = 0; lp < dist; lp ++) { + next = Adjacent_Cell(next, dir + 2); + if (!Can_Enter_Cell(next)) { + return(next); + } + } + + for (lp = 0; lp < dist; lp ++) { + next = Adjacent_Cell(next, dir + 4); + if (!Can_Enter_Cell(next)) { + return(next); + } + } + } + } + return(-1); +} + + + + +int FootClass::Passable_Cell(CELL cell, FacingType face, int threat, MoveType threshhold) +{ + MoveType move = Can_Enter_Cell(cell, face); + + if (move < MOVE_MOVING_BLOCK && Distance(cell) > 1) threshhold = MOVE_MOVING_BLOCK; + + if (move > threshhold) return(0); + + if (GameToPlay == GAME_NORMAL) { + if (threat != -1) { + if (Map.Cell_Distance(cell, DestLocation) > THREAT_THRESHOLD) { + if (Map.Cell_Threat(cell, Owner()) > threat) + return(0); + } + } + } + + static int _value[MOVE_COUNT] = { + 1, // MOVE_OK + 1, // MOVE_CLOAK + 3, // MOVE_MOVING_BLOCK + 8, // MOVE_DESTROYABLE + 10, // MOVE_TEMP + 0 // MOVE_NO + }; + return(_value[move]); + +#ifdef NEVER + int can; + int retval; + + int temp_move_mask = MoveMask; + + if (!House->IsHuman) { + temp_move_mask &= ~MOVEF_TEMP; + } + +#ifdef NEVER + if ((!(MoveMask & MOVEF_MOVING_BLOCK)) && Map.Cell_Distance(StartLocation, cell) > 2) { + temp_move_mask |= MOVEF_MOVING_BLOCK; + } +#endif + + can = (temp_move_mask & Can_Enter_Cell(cell, face)); + if (can & MOVEF_NO) return(0); + + retval = 1; + if (can & MOVEF_MOVING_BLOCK) retval += 3; + if (can & MOVEF_DESTROYABLE) retval += 10; + if (can & MOVEF_TEMP) retval += 10; + + if (threat != -1) { + if (Map.Cell_Distance(cell, DestLocation) > THREAT_THRESHOLD) { + if (Map.Cell_Threat(cell, Owner()) > threat) + return(0); + } + } + + return(retval); +#endif +} + + +void FootClass::Debug_Draw_Map(char *txt, CELL start, CELL dest, bool pause) +{ + if ((!Debug_Find_Path) || (!DrawPath)) return; + + if (pause) Get_Key_Num(); + GraphicViewPortClass * page = Set_Logic_Page(SeenBuff); + + VisiblePage.Clear(); + Fancy_Text_Print(txt, 160, 0, WHITE, BLACK, TPF_8POINT|TPF_CENTER); + for (int x = 0; x < 64; x++) { + for (int y = 0; y < 64; y++) { + int color = 0; + + switch (Can_Enter_Cell( (CELL)((y << 6) + x))) { + case MOVE_OK: + color = GREEN; + break; + case MOVE_MOVING_BLOCK: + color = LTGREEN; + break; + + case MOVE_DESTROYABLE: + color = YELLOW; + break; + case MOVE_TEMP: + color = BROWN; + break; + default: + color = RED; + break; + } + if ((CELL)((y << 6) + x) == start) + color = LTBLUE; + if ((CELL)((y << 6) + x) == dest) + color = BLUE; + Fat_Put_Pixel(64 + (x*3), 8 + (y*3), color, 3, SeenBuff); + } + } + Set_Logic_Page(page); +} + +void FootClass::Debug_Draw_Path(PathType *path) +{ + if (!path) return; + + FacingType *list = path->Command; + CELL pos = path->Start; + + for (int idx = 0; idx < path->Length; idx++) { + pos = Adjacent_Cell(pos, *list++); + Draw_Cell_Point(pos, true, -1, 0); + } +} diff --git a/FLASHER.CPP b/FLASHER.CPP new file mode 100644 index 0000000..a155aee --- /dev/null +++ b/FLASHER.CPP @@ -0,0 +1,97 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\flasher.cpv 2.18 16 Oct 1995 16:49:24 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FLASHER.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 28, 1994 * + * * + * Last Update : October 17, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * FlasherClass::Process -- Performs the logic processing for the flashing ability. * + * FlasherClass::Debug_Dump -- Displays current status to the monochrome screen. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * FlasherClass::Debug_Dump -- Displays current status to the monochrome screen. * + * * + * This utility function will output the current status of the FlasherClass to the mono * + * screen. It is through this display that bugs may be fixed or detected. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + *=============================================================================================*/ +void FlasherClass::Debug_Dump(MonoClass *mono) const +{ + mono->Set_Cursor(50, 7); + mono->Printf("%2d", FlashCount); +} +#endif + + +/*********************************************************************************************** + * FlasherClass::Process -- Performs the logic processing for the flashing ability. * + * * + * The ability for an object to flash is controlled by this logic processing routine. It * + * should be called once per game tick per unit. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Should the associated object be redrawn? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + * 06/20/1994 JLB : Is now independent of object it represents. * + *=============================================================================================*/ +bool FlasherClass::Process(void) +{ + if (FlashCount) { + FlashCount--; + IsBlushing = false; + + if (FlashCount & 0x01) { + IsBlushing = true; + } + return(true); + } + return(false); +} + + diff --git a/FLASHER.H b/FLASHER.H new file mode 100644 index 0000000..be89421 --- /dev/null +++ b/FLASHER.H @@ -0,0 +1,71 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\flasher.h_v 2.17 16 Oct 1995 16:45:12 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FLASHER.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 28, 1994 * + * * + * Last Update : May 28, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef FLASHER_H +#define FLASHER_H + +class FlasherClass { + public: + /* + ** When this object is targeted, it will flash a number of times. This is the + ** flash control number. It counts down to zero and then stops. Odd values + ** cause the object to be rendered in a lighter color. + */ + unsigned FlashCount:7; + + /* + ** When an object is targeted, it flashes several times to give visual feedback + ** to the player. Every other game "frame", this flag is true until the flashing + ** is determined to be completed. + */ + unsigned IsBlushing:1; + + FlasherClass(void) {FlashCount = 0; IsBlushing = false;}; + #ifdef CHEAT_KEYS + void Debug_Dump(MonoClass *mono) const; + #endif + bool Process(void); + + /* + ** File I/O. + */ + void Code_Pointers(void); + void Decode_Pointers(void); + +}; + +#endif diff --git a/FLY.CPP b/FLY.CPP new file mode 100644 index 0000000..b53eed0 --- /dev/null +++ b/FLY.CPP @@ -0,0 +1,133 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\fly.cpv 2.18 16 Oct 1995 16:50:40 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FLY.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 24, 1994 * + * * + * Last Update : June 5, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * FlyClass::Physics -- Performs vector physics (movement). * + * FlyClass::Fly_Speed -- Sets the flying object to the speed specified. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * FlyClass::Physics -- Performs vector physics (movement). * + * * + * This routine performs movement (vector) physics. It takes the * + * specified location and moves it according to the facing and speed * + * of the vector. It returns the status of the move. * + * * + * INPUT: coord -- Reference to the coordinate that the vector will * + * be applied to. * + * * + * OUTPUT: Returns with the status of the vector physics. This could * + * range from no effect, to exiting the edge of the world. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/24/1994 JLB : Created. * + * 06/05/1995 JLB : Simplified to just do movement. * + *=============================================================================================*/ +ImpactType FlyClass::Physics(COORDINATE & coord, DirType facing) +{ + if (SpeedAdd != MPH_IMMOBILE) { + int actual = (int)SpeedAdd + SpeedAccum; + div_t result = div(actual, PIXEL_LEPTON_W); + SpeedAccum = result.rem; + actual -= result.rem; + COORDINATE old = coord; + + /* + ** If movement occurred that is at least one + ** pixel, then check update the coordinate and + ** check for edge of world collision. + */ + if (result.quot) { + COORDINATE newcoord; // New working coordinate. + + newcoord = Coord_Move(coord, facing, actual); + + /* + ** If no movement occurred, then presume it hasn't moved at all + ** and return immediately with this indication. + */ + if (newcoord == coord) { + return(IMPACT_NONE); + } + + /* + ** Remember the new position. + */ + coord = newcoord; + + /* + ** If the new coordinate is off the edge of the world, then report + ** this. + */ + if (newcoord & 0xC000C000L /*|| !Map.In_Radar(Coord_Cell(newcoord))*/) { + coord = old; + return(IMPACT_EDGE); + } + + return(IMPACT_NORMAL); + } + } + return(IMPACT_NONE); +} + + +/*********************************************************************************************** + * FlyClass::Fly_Speed -- Sets the flying object to the speed specified. * + * * + * This sets the speed of the projectile. It basically functions like a throttle value * + * where 0 equals stop and 255 equals maximum speed (whatever that is for the particular * + * object). * + * * + * INPUT: speed -- Speed setting from 0 to 255. * + * * + * maximum -- The maximum speed of the object. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/26/1994 JLB : Created. * + * 07/26/1994 JLB : Added maximum speed as guiding value. * + *=============================================================================================*/ +void FlyClass::Fly_Speed(int speed, MPHType maximum) +{ + SpeedAdd = (MPHType)Fixed_To_Cardinal((int)maximum, speed); +} + diff --git a/FLY.H b/FLY.H new file mode 100644 index 0000000..962755e --- /dev/null +++ b/FLY.H @@ -0,0 +1,83 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\fly.h_v 2.19 16 Oct 1995 16:45:18 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FLY.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 24, 1994 * + * * + * Last Update : April 24, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef FLY_H +#define FLY_H + +typedef enum ImpactType { + IMPACT_NONE, // No movement (of significance) occurred. + IMPACT_NORMAL, // Some (non eventful) movement occurred. + IMPACT_EDGE // The edge of the world was reached. +} ImpactType; + + +/**************************************************************************** +** Flying objects are handled by this class definition. +*/ +class FlyClass { + public: + /*--------------------------------------------------------------------- + ** Constructors, Destructors, and overloaded operators. + */ + FlyClass(void) { + SpeedAdd = MPH_IMMOBILE; + SpeedAccum = 0; + }; + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + void Fly_Speed(int speed, MPHType maximum); + ImpactType Physics(COORDINATE &coord, DirType facing); + MPHType Get_Speed(void) const {return(SpeedAdd);}; + + /* + ** File I/O. + */ + void Code_Pointers(void); + void Decode_Pointers(void); + + private: + /* + ** Object movement consists of incrementing the accumulator until enough "distance" + ** has accumulated so that moving the object becomes reasonable. + */ + unsigned SpeedAccum; // Lepton accumulator. + MPHType SpeedAdd; // Lepton add (per frame). +}; + +#endif diff --git a/FOOT.CPP b/FOOT.CPP new file mode 100644 index 0000000..64e5e2f --- /dev/null +++ b/FOOT.CPP @@ -0,0 +1,2015 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\foot.cpv 2.17 16 Oct 1995 16:51:42 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FOOT.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 22, 1994 * + * * + * Last Update : August 13, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * FootClass::Active_Click_With -- Intiates attack or move according to target clicked on. * + * FootClass::Active_Click_With -- Performs action as a result of left mouse click. * + * FootClass::Approach_Target -- Sets the navigation computer to approach target object. * + * FootClass::Assign_Destination -- Assigns specified destination to NavCom. * + * FootClass::Assign_Mission -- Assign mission to foot class object. * + * FootClass::Basic_Path -- Finds the basic path for a ground object. * + * FootClass::Body_Facing -- Set the body rotation/facing. * + * FootClass::Can_Demolish -- Checks to see if this object can be sold back. * + * FootClass::Can_Enter_Cell -- Checks to see if the object can enter cell specified. * + * FootClass::Death_Announcement -- Announces the death of a unit. * + * FootClass::Debug_Dump -- Displays the status of the FootClass to the mono monitor. * + * FootClass::Detach -- Detaches a target from tracking systems. * + * FootClass::Detach_All -- Removes this object from the game system. * + * FootClass::Enters_Building -- When unit enters a building for some reason. * + * FootClass::FootClass -- Default constructor for foot class objects. * + * FootClass::FootClass -- Normal constructor for the foot class object. * + * FootClass::Greatest_Threat -- Fetches the greatest threat to this object. * + * FootClass::Likely_Coord -- Fetches the coordinate the object will be at shortly. * + * FootClass::Limbo -- Intercepts limbo event and handles FootClass processing. * + * FootClass::Mark -- Unit interface to map rendering system. * + * FootClass::Mission_Attack -- AI for heading towards and firing upon target. * + * FootClass::Mission_Capture -- Handles the capture mission. * + * FootClass::Mission_Enter -- Enter (cooperatively) mission handler. * + * FootClass::Mission_Guard_Area -- Causes unit to guard an area about twice weapon range. * + * FootClass::Mission_Hunt -- Handles the default hunt order. * + * FootClass::Mission_Move -- AI process for moving a vehicle to its destination. * + * FootClass::Offload_Tiberium_Bail -- Fetches the Tiberium to offload per step. * + * FootClass::Override_Mission -- temporarily overides a units mission * + * FootClass::Per_Cell_Process -- Perform action based on once-per-cell condition. * + * FootClass::Receive_Message -- Movement related radio messages are handled here. * + * FootClass::Rescue_Mission -- Calls this unit to the rescue. * + * FootClass::Restore_Mission -- Restores an overidden mission * + * FootClass::Sell_Back -- Causes this object to be sold back. * + * FootClass::Set_Speed -- Initiate unit movement physics. * + * FootClass::Sort_Y -- Determine the sort coordinate for foot class objects. * + * FootClass::Start_Driver -- This starts the driver heading to the destination desired. * + * FootClass::Stop_Driver -- This routine clears the driving state of the object. * + * FootClass::Stun -- Prepares a ground travelling object for removal. * + * FootClass::Take_Damage -- Handles taking damage to this object. * + * FootClass::Unlimbo -- Unlimbos object and performs special fixups. * + * FootClass::~FootClass -- Default destructor for foot class objects. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * FootClass::FootClass -- Default constructor for foot class objects. * + * * + * This is the default constructor for FootClass objects. It sets the foot class values to * + * their default starting settings. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/23/1994 JLB : Created. * + *=============================================================================================*/ +FootClass::FootClass(void) : Speed(0) +{ + ArchiveTarget = TARGET_NONE; + IsDriving = false; + IsInitiated = false; + IsPlanningToLook = false; + IsDeploying = false; + IsNewNavCom = false; + IsFiring = false; + IsRotating = false; + IsUnloading = false; + NavCom = TARGET_NONE; + SuspendedNavCom = TARGET_NONE; + Path[0] = FACING_NONE; + HeadToCoord = NULL; + Member = 0; + Team = 0; + PathDelay = 0; + TryTryAgain = PATH_RETRY; + if (House) { + House->CurUnits++; + } + Group = -1; +} + + +/*********************************************************************************************** + * FootClass::~FootClass -- Default destructor for foot class objects. * + * * + * At this level of the destruction process, the house record of the number of units * + * currently in inventory is decremented to reflect this units destruction. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/10/1995 JLB : Created. * + *=============================================================================================*/ +FootClass::~FootClass(void) +{ + if (GameActive && House) { + House->CurUnits--; + } +} + + +/*********************************************************************************************** + * FootClass::FootClass -- Normal constructor for the foot class object. * + * * + * This is the normal constructor used when creating a foot class object. * + * * + * INPUT: house -- The house that owns this object. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/29/1994 JLB : Created. * + *=============================================================================================*/ +FootClass::FootClass(HousesType house) : + TechnoClass(house), + Speed(0) +{ + ArchiveTarget = TARGET_NONE; + Member = 0; + Team = 0; + Path[0] = FACING_NONE; + NavCom = TARGET_NONE; + SuspendedNavCom = TARGET_NONE; + IsUnloading = false; + IsDriving = false; + IsInitiated = false; + IsRotating = false; + IsFiring = false; + IsDeploying = false; + IsNewNavCom = false; + IsPlanningToLook = false; + HeadToCoord = 0L; + PathDelay = 0; + Group = -1; + TryTryAgain = PATH_RETRY; + House->CurUnits++; +} + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * FootClass::Debug_Dump -- Displays the status of the FootClass to the mono monitor. * + * * + * This routine is used to output the current status of the foot class to the mono * + * monitor. Through this display bugs may be tracked down or eliminated. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/02/1994 JLB : Created. * + * 07/04/1995 JLB : Handles aircraft special case. * + *=============================================================================================*/ +void FootClass::Debug_Dump(MonoClass *mono) const +{ + static char const * _p2c[9] = {"-","0","1","2","3","4","5","6","7"}; +#define Path_To_String(a) _p2c[((ABS((int)a+1))%9)] + + /* + ** Display the common data for all objects that inherity from FootClass. + */ + mono->Set_Cursor(63, 7); + if (Team) { + mono->Printf("%s(%d)", Team->Class->IniName, Teams.ID(Team)); + } else { + mono->Printf("(none)"); + } + mono->Set_Cursor(73, 7);mono->Printf("%04X", ArchiveTarget); + mono->Set_Cursor(42, 1);mono->Printf("%04X", NavCom); + mono->Set_Cursor(44, 3);mono->Printf("%d", Speed); + + /* + ** Although aircraft inherit from FootClass, some of the variables are not + ** used and thus should not be displayed. + */ + if (What_Am_I() != RTTI_AIRCRAFT) { + mono->Set_Cursor(50, 3); + mono->Printf("%s%s%s%s%s%s%s%s%s%s%s%s", + Path_To_String(Path[0]), + Path_To_String(Path[1]), + Path_To_String(Path[2]), + Path_To_String(Path[3]), + Path_To_String(Path[4]), + Path_To_String(Path[5]), + Path_To_String(Path[6]), + Path_To_String(Path[7]), + Path_To_String(Path[8]), + Path_To_String(Path[9]), + Path_To_String(Path[10]), + Path_To_String(Path[11]), + Path_To_String(Path[12])); + + mono->Set_Cursor(65, 1);mono->Printf("%08lX", Head_To_Coord()); + mono->Text_Print("X", 16 + (IsDeploying?2:0), 12); + mono->Text_Print("X", 16 + (IsRotating?2:0), 13); + mono->Text_Print("X", 16 + (IsDriving?2:0), 15); + mono->Text_Print("X", 16 + (IsFiring?2:0), 14); + mono->Text_Print("X", 16 + (IsPlanningToLook?2:0), 16); + } + TechnoClass::Debug_Dump(mono); +} +#endif + + +/*********************************************************************************************** + * FootClass::Set_Speed -- Initiate unit movement physics. * + * * + * This routine is used to set a unit's velocity control structure. * + * The game will then process the unit's movement during the momentum * + * physics calculation. * + * * + * INPUT: unit -- Pointer to the unit to alter. * + * * + * speed -- Throttle setting (0=stop, 255=full throttle). * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/07/1992 JLB : Created. * + * 09/24/1993 JLB : Revised for faster speed. * + * 04/02/1994 JLB : Revised for new system. * + * 04/15/1994 JLB : Converted to member function. * + * 07/21/1994 JLB : Simplified. * + *=============================================================================================*/ +void FootClass::Set_Speed(int speed) +{ + speed &= 0xFF; + ((unsigned char &)Speed) = speed; +} + + +/*********************************************************************************************** + * FootClass::Mark -- Unit interface to map rendering system. * + * * + * This routine is the interface function for units as they relate to * + * the map rendering system. Whenever a unit's imagery changes, this * + * function is called. * + * * + * INPUT: mark -- Type of image change (MARK_UP, _DOWN, _CHANGE) * + * MARK_UP -- Unit is removed. * + * MARK_CHANGE -- Unit alters image but doesn't move. * + * MARK_DOWN -- Unit is overlaid onto existing icons. * + * * + * OUTPUT: bool; Did the marking operation succeed? Failure could be the result of marking * + * down when it is already down, or visa versa. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/14/1991 JLB : Created. * + * 04/15/1994 JLB : Converted to member function. * + * 12/23/1994 JLB : Performs low level check before processing. * + *=============================================================================================*/ +bool FootClass::Mark(MarkType mark) +{ + if (TechnoClass::Mark(mark)) { + CELL cell = Coord_Cell(Coord); + + /* + ** Inform the map of the refresh, occupation, and overlap + ** request. + */ + switch (mark) { + case MARK_UP: + Map.Pick_Up(cell, this); + break; + + case MARK_DOWN: + Map.Place_Down(cell, this); + break; + + default: + Map.Refresh_Cells(cell, Overlap_List()); + Map.Refresh_Cells(cell, Occupy_List()); + break; + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * FootClass::Basic_Path -- Finds the basic path for a ground object. * + * * + * This is a common routine used by both infantry and other ground travelling units. It * + * will fill in the unit's basic path to the NavCom destination. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was a path found? A failure to find a path means either the target cannot * + * be found or the terrain prohibits the unit's movement. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +bool FootClass::Basic_Path(void) +{ + PathType *path; // Pointer to path control structure. + CELL cell; + int skip_path = false; + + Path[0] = FACING_NONE; + + if (Target_Legal(NavCom)) { + cell = As_Cell(NavCom); + + /* + ** When the navigation computer is set to a location that is impassible, then + ** find a nearby cell that can be entered and try to head toward that instead. + ** EXCEPT when that cell is very close -- then just bail. + */ +// IsFindPath = true; + if (Can_Enter_Cell(cell) == MOVE_NO && Distance(NavCom) > 0x0300) { + static int _faceadjust[8] = {0, 1, -1, 2, -2, 3, -3, 4}; + FacingType f2 = (FacingType)(((unsigned)::Direction(cell, Coord_Cell(Coord)))>>5); + + for (unsigned index = 0; index < (sizeof(_faceadjust) / sizeof(_faceadjust[0])); index++) { + CELL cell2; + + cell2 = Adjacent_Cell(cell, (FacingType)((f2+_faceadjust[index])&0x7)); + if (Can_Enter_Cell(cell2, FACING_NONE) <= MOVE_CLOAK) { + cell = cell2; + break; + } + } + } +// IsFindPath = false; + +#ifdef SPECIAL + if (What_Am_I() == RTTI_INFANTRY) { + CELL mycell = Coord_Cell(Center_Coord()); +// Mark(MARK_UP); + ObjectClass *obj = Map[mycell].Cell_Occupier(); + while (obj) { + if (obj != this && obj->What_Am_I() == RTTI_INFANTRY) { + InfantryClass *inf = (InfantryClass *)obj; + if (inf->NavCom == NavCom && inf->Path[0] != FACING_NONE) { + if (Coord_Cell(inf->Head_To_Coord()) == Coord_Cell(inf->Coord)) { + Mem_Copy(&inf->Path[1], Path, sizeof(Path)-sizeof(Path[0])); + } else { + Mem_Copy(inf->Path, Path, sizeof(Path)); + } + if (Path[0] != FACING_NONE) { + skip_path = true; + } + break; + } + } + obj = obj->Next; + } +// Mark(MARK_DOWN); + } +#endif + + if (!skip_path) { + Mark(MARK_UP); + Path[0] = FACING_NONE; // Probably not necessary, but... + + /* + ** Try to find a path to the destination. If a failure occurs, then keep trying + ** with greater determination until either a complete failure occurs, or a decent + ** path was found. + */ + bool found1=false; // Found a best path yet? + PathType path1; + FacingType workpath1[200]; // Staging area for path list. + FacingType workpath2[200]; // Staging area for path list. + MoveType maxtype = MOVE_TEMP; + if (!House->IsHuman) { + maxtype = MOVE_DESTROYABLE; + } else { + + /* + ** For simple movement missions by the human player, then don't + ** consider friendly units as passable if close to the destination. + ** This will prevent a human controlled unit from just sitting next + ** to a destination just because there is another friendly unit + ** occupying the destination location. + */ + if (Mission == MISSION_MOVE && Distance(NavCom) < 0x0280) { + maxtype = MOVE_DESTROYABLE; + } + } + + /* + ** Determine if ANY path could be calculated by first examining the most + ** aggressive case. If this fails, then no path will succeed. Further + ** scanning is unnecessary. + */ + path = Find_Path(cell, &workpath1[0], sizeof(workpath1), maxtype); + if (path && path->Cost) { + memcpy(&path1, path, sizeof(path1)); + found1 = true; + + /* + ** Scan for the best path possible. If this succeeds, then do a simple + ** comparison with the most agressive path. If they are very close, then + ** go with the best (easiest) path method. + */ + path = Find_Path(cell, &workpath2[0], sizeof(workpath2), MOVE_CLOAK); + if (path && path->Cost && path->Cost < MAX((path1.Cost + (path1.Cost/2)), 3)) { + memcpy(&path1, path, sizeof(path1)); + memcpy(workpath1, workpath2, sizeof(workpath1)); + } else { + + /* + ** The easiest path method didn't result in a satisfactory path. Scan through + ** the rest of the path options, looking for the best one. + */ + for (MoveType move = MOVE_MOVING_BLOCK; move < maxtype; move++) { + path = Find_Path(cell, &workpath2[0], sizeof(workpath2), move); + if (path && path->Cost && path->Cost < MAX((path1.Cost + (path1.Cost/2)), 3)) { + memcpy(&path1, path, sizeof(path1)); + memcpy(workpath1, workpath2, sizeof(workpath1)); + } + } + } + } + +#ifdef OBSOLETE + for (MoveType move = MOVE_CLOAK; move <= maxtype; move++) { + if (!found1) { + path = Find_Path(cell, &workpath1[0], sizeof(workpath1), move); + if (path && path->Cost) { + memcpy(&path1, path, sizeof(path1)); + found1 = true; + if (path1.Cost < 5) break; + } + } else { + path = Find_Path(cell, &workpath2[0], sizeof(workpath2), move); + + if (path) { + if (path->Cost && path->Cost <= path1.Cost/2) { + memcpy(&path1, path, sizeof(path1)); + memcpy(workpath1, workpath2, sizeof(workpath1)); + } + } + } + } +#endif + + /* + ** If a good path was found, then record it in the object's path + ** list. + */ + if (found1) { + Fixup_Path(&path1); + memcpy(&Path[0], &workpath1[0], MIN(path->Length, (int)sizeof(Path))); + } + + Mark(MARK_DOWN); + } + + +#ifdef NEVER + /* + ** Patch at this point to see if we are moving directly into a + ** MOVE_TEMP. This allows units to bunch up at a bridge even if there + ** is an enormously long way around. This also allows units to give + ** up trying to move into the MOVE_TEMP using the normal movement + ** retry logic. + */ + CELL cell = Adjacent_Cell(Coord_Cell(Coord), Path[0]); + if (Can_Enter_Cell(cell, FACING_NONE) == MOVEF_TEMP) { + Path[0] = FACING_NONE; + } +#endif + + PathDelay = PATH_DELAY; + if (Path[0] != FACING_NONE) return(true); + + /* + ** If a basic path couldn't be determined, then abort the navigation process. + */ +// NavCom = TARGET_NONE; + Stop_Driver(); + } + return(false); +} + + +/*********************************************************************************************** + * FootClass::Mission_Move -- AI process for moving a vehicle to its destination. * + * * + * This simple AI script handles moving the vehicle to its desired destination. Since * + * simple movement is handled directly by the engine, this routine merely waits until * + * the unit has reached its destination, and then causes the unit to enter idle mode. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the delay before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + *=============================================================================================*/ +int FootClass::Mission_Move(void) +{ + if (!Target_Legal(NavCom) && !IsDriving && MissionQueue == MISSION_NONE) { + Enter_Idle_Mode(); + } + if (!Target_Legal(TarCom) && !House->IsHuman) { + Target_Something_Nearby(THREAT_RANGE); + } + return(TICKS_PER_SECOND+3); +} + + +/*********************************************************************************************** + * FootClass::Mission_Capture -- Handles the capture mission. * + * * + * Capture missions are nearly the same as normal movement missions. The only difference * + * is that the final destination is handled in a special way so that it is not marked as * + * impassable. This allows the object (usually infantry) the ability to walk onto the * + * object and thus capture it. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of game ticks to delay before calling this routine. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/19/1995 JLB : Created. * + *=============================================================================================*/ +int FootClass::Mission_Capture(void) +{ + if (!Target_Legal(NavCom) && !In_Radio_Contact()) { + Enter_Idle_Mode(); + if (Map[Coord_Cell(Center_Coord())].Cell_Building()) { + Scatter(0, true); + } + } + return(TICKS_PER_SECOND-2); +} + + +/*********************************************************************************************** + * FootClass::Mission_Attack -- AI for heading towards and firing upon target. * + * * + * This AI routine handles heading to within range of the target and then firing upon * + * it until it is destroyed. If the target is destroyed, then the unit will change * + * missions to match its "idle mode" of operation (usually guarding). * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the delay before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + *=============================================================================================*/ +int FootClass::Mission_Attack(void) +{ + if (Target_Legal(TarCom)) { + Approach_Target(); + } else { + Enter_Idle_Mode(); + } + return(TICKS_PER_SECOND+2); +} + + +/*********************************************************************************************** + * FootClass::Mission_Guard -- Handles the AI for guarding in place. * + * * + * Units that are performing stationary guard duty use this AI process. They will sit * + * still and target any enemies that get within range. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the delay before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + *=============================================================================================*/ +int FootClass::Mission_Guard(void) +{ + if (!Target_Something_Nearby(THREAT_RANGE)) { + Random_Animate(); + } + return(TICKS_PER_SECOND+Random_Picky((int)0, (int)4, (char*)NULL, (int)0)); +} + + +/*********************************************************************************************** + * FootClass::Mission_Hunt -- Handles the default hunt order. * + * * + * This routine is the default hunt order for game objects. It handles searching for a * + * nearby object and heading toward it. The act of targetting will cause it to attack * + * the target it selects. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the game tick delay before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +int FootClass::Mission_Hunt(void) +{ + if (!Target_Something_Nearby(THREAT_NORMAL)) { + Random_Animate(); + } else { + if (What_Am_I() == RTTI_INFANTRY && ((InfantryTypeClass const &)Class_Of()).Type == INFANTRY_E7) { + Assign_Destination(TarCom); + Assign_Mission(MISSION_CAPTURE); + } else { + Approach_Target(); + } + } + return(TICKS_PER_SECOND+5); +} + + +/*********************************************************************************************** + * FootClass::Mission_Timed_Hunt -- This is the AI process for multiplayer computer units. * + * * + * For multiplayer games, the computer AI can't just blitz the human players; the humans * + * need a little time to set up their base, or whatever. This state just waits for * + * a certain period of time, then goes into hunt mode. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the delay before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + *=============================================================================================*/ +int FootClass::Mission_Timed_Hunt(void) +{ + int rndmax; + int changed = 0; // has the unit changed into Hunt mode? + + if (!House->IsHuman) { + + /* + ** Jump into HUNT mode if we're supposed to Blitz, and the EndCountDown + ** has expired, or if our owning house has lost more than 1/4 of its units + ** (it gets mad at you) + */ + if ( (MPlayerBlitz && House->BlitzTime==0) || + House->CurUnits < ((House->MaxUnit * 4) / 5)) { + Assign_Mission(MISSION_HUNT); + changed = 1; + } + + /* + ** Jump into HUNT mode on a random die roll; the computer units will periodically + ** "snap out" of their daze, and begin hunting. Try to time it so that all + ** units will be hunting within 10 minutes (600 calls to this routine). + */ + if (MPlayerBases) { + rndmax = 5000; + } else { + rndmax = 1000; + } + + if (IRandom(0,rndmax) == 1) { + Assign_Mission(MISSION_HUNT); + changed = 1; + } + + /* + ** If this unit is still just sitting in Timed Hunt mode, call Guard Area + ** so it doesn't just sit there stupidly. + */ + if (!changed) { + Mission_Guard_Area(); + } + } + + return(TICKS_PER_SECOND+Random_Pick(0, 4)); // call me back in 1 second. + +} + + +/*********************************************************************************************** + * FootClass::Stop_Driver -- This routine clears the driving state of the object. * + * * + * This is the counterpart routine to the Start_Driver function. It clears the driving * + * status flags and destination coordinate record. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was driving stopped? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + * 12/12/1994 JLB : Greatly simplified. * + *=============================================================================================*/ +bool FootClass::Stop_Driver(void) +{ + if (HeadToCoord) { + HeadToCoord = NULL; + Set_Speed(0); + IsDriving = false; + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * FootClass::Start_Driver -- This starts the driver heading to the destination desired. * + * * + * Before a unit can move it must be started by this routine. This routine handles * + * reserving the cell and setting the driving flag. * + * * + * INPUT: headto -- The coordinate of the immediate drive destination. This is one cell * + * away from the unit's current location. * + * * + * OUTPUT: bool; Was driving initiated? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + * 12/12/1994 JLB : Uses simple spot index finder. * + *=============================================================================================*/ +bool FootClass::Start_Driver(COORDINATE &headto) +{ + Stop_Driver(); + if (headto) { + HeadToCoord = headto; + IsDriving = true; + + /* + ** Check for crate goodie finder here. + */ + if (Map[Coord_Cell(headto)].Goodie_Check(this)) { + return(true); + } + + HeadToCoord = NULL; + IsDriving = false; + } + return(false); +} + + +/*********************************************************************************************** + * FootClass::Sort_Y -- Determine the sort coordinate for foot class objects. * + * * + * This routine will determine the sort coordinate for foot class object. This coordinate * + * is usually the coordinate of the object. The exception is if the object is teathered. * + * In this case (presumes offloading to the north), the sorting coordinate is adjusted * + * so that the object will be drawn on top of the transport unit. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the coordinate to use for sorting. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + * 11/04/1994 JLB : Sort value is different when unloading from aircraft. * + *=============================================================================================*/ +COORDINATE FootClass::Sort_Y(void) const +{ + if (IsUnloading) { + return(Coord_Add(Coord, 0x01000000L)); + } + if (In_Radio_Contact() && IsTethered && Contact_With_Whom()->What_Am_I() == RTTI_UNIT) { + return(Coord_Add(Coord, 0x01000000L)); + } + return(Coord_Add(Coord, 0x00300000L)); +} + + +/*********************************************************************************************** + * FootClass::Stun -- Prepares a ground travelling object for removal. * + * * + * This routine clears the units' navigation computer in preparation for removal from the * + * game. This is probably called as a result of unit destruction in combat. Clearing the * + * navigation computer ensures that the normal AI process won't start it moving again while * + * the object is undergoing any death animations. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/23/1994 JLB : Created. * + *=============================================================================================*/ +void FootClass::Stun(void) +{ + Assign_Destination(TARGET_NONE); + Path[0] = FACING_NONE; + Stop_Driver(); + TechnoClass::Stun(); +} + + +/*********************************************************************************************** + * FootClass::Approach_Target -- Sets the navigation computer to approach target object. * + * * + * This routine will set the navigation computer to approach the target indicated by the * + * targeting computer. It is through this function that the unit nears the target so * + * that weapon firing may occur. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + * 12/13/1994 JLB : Made part of TechnoClass. * + * 12/22/1994 JLB : Enhanced search algorithm. * + * 05/20/1995 JLB : Always approaches if the object is off the map. * + *=============================================================================================*/ +void FootClass::Approach_Target(void) +{ + /* + ** Determine that if there is an existing target it is still legal + ** and within range. + */ + if (Target_Legal(TarCom)) { + + /* + ** If the target is too far away then head toward it. + */ + int maxrange = MAX(Weapon_Range(0), Weapon_Range(1)); + + if (!Target_Legal(NavCom) && (!In_Range(TarCom) || !IsLocked)) { +// if (!Target_Legal(NavCom) && (Distance(TarCom) > maxrange || !IsLocked)) { + + /* + ** If the object that we are attacking is a building adjust the units + ** max range so that people can stand far away from the buildings and + ** hit them. + */ + BuildingClass * obj = As_Building(TarCom); + if (obj) { + maxrange += ((obj->Class->Width() + obj->Class->Height()) * (0x100 / 4)); + } + + /* + ** Adjust the max range of an infantry unit for where he is standing + ** in the room. + */ + maxrange -= 0x00B7; +#ifdef OBSOLETE + if (What_Am_I() == RTTI_INFANTRY) { + maxrange -= 0x0111; + } else { + maxrange -= 0x00B7; + } +#endif + maxrange = MAX(maxrange, 0); + + COORDINATE tcoord = ::As_Coord(TarCom); + COORDINATE trycoord = 0; + CELL tcell = Coord_Cell(tcoord); + CELL trycell = tcell; + DirType dir = Direction256(tcoord, Center_Coord()); + bool found = false; + + /* + ** Sweep through the cells between the target and the unit, looking for + ** a cell that the unit can enter but which is also within weapon range + ** of the target. If after a reasonable search, no appropriate cell could + ** be found, then the target will be assigned as the movement destination + ** and let "the chips fall where they may." + */ + for (int range = maxrange; range > 0x0080; range -= 0x0100) { + static int _angles[] = {0, 8, -8, 16, -16, 24, -24, 32, -32, 48, -48, 64, -64}; + + for (int index = 0; index < (sizeof(_angles)/sizeof(_angles[0])); index++) { + trycoord = Coord_Move(tcoord, (DirType)(dir + _angles[index]), range); + + if (::Distance(trycoord, tcoord) < range) { + trycell = Coord_Cell(trycoord); + if (Can_Enter_Cell(trycell) <= MOVE_CLOAK && Map.In_Radar(trycell)) { + found = true; + break; + } + } + } + if (found) break; + } + + /* + ** If a suitable intermediate location was found, then head toward it. + ** Otherwise, head toward the enemy unit directly. + */ + if (found) { + Assign_Destination(::As_Target(trycell)); + } else { + Assign_Destination(TarCom); + } + } + } +} + + +/*********************************************************************************************** + * FootClass::Mission_Guard_Area -- Causes unit to guard an area about twice weapon range. * + * * + * This mission routine causes the unit to scan for targets out to twice its weapon range * + * from the home point. If a target was found, then it will be attacked. The unit will * + * chase the target until it gets up to to its weapon range from the home position. * + * In that case, it will return to home position and start scanning for another target. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with time delay before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/23/1994 JLB : Created. * + * 07/27/1995 JLB : Greatly simplified. * + *=============================================================================================*/ +int FootClass::Mission_Guard_Area(void) +{ + if (What_Am_I() == RTTI_UNIT && ((UnitClass *)this)->Class->IsToHarvest) { + Assign_Mission(MISSION_HARVEST); + return(1+Random_Pick(1, 10)); + } + + /* + ** Ensure that the archive target is valid. + */ + if (!Target_Legal(ArchiveTarget)) { + ArchiveTarget = ::As_Target(Coord_Cell(Coord)); + } + + /* + ** Make sure that the unit has not strayed too far from the home position. + ** If it has, then race back to it. + */ + int maxrange = MAX(Weapon_Range(0), Weapon_Range(1))+0x0100; + if (!Target_Legal(NavCom) && (Distance(ArchiveTarget) > maxrange || (!Target_Legal(TarCom) && Distance(ArchiveTarget) > 0x0200))) { + Assign_Target(TARGET_NONE); + Assign_Destination(ArchiveTarget); + } + + if (!Target_Legal(TarCom)) { + COORDINATE old = Coord; + Coord = As_Coord(ArchiveTarget); + Target_Something_Nearby(THREAT_AREA); + Coord = old; + if (Target_Legal(TarCom)) return(1); + } else { + Approach_Target(); + } + return(TICKS_PER_SECOND+Random_Picky((int)0, (int)4, (char*)NULL, (int)0)); +} + + +/*********************************************************************************************** + * FootClass::Unlimbo -- Unlimbos object and performs special fixups. * + * * + * This routine will make sure that the home position for the foot class object gets * + * reset. This is necessary since the home position may change depending on the unit's * + * transition between limbo and non-limbo condition. * + * * + * INPUT: coord -- The coordinate to unlimbo the unit at. * + * * + * dir -- The initial direction to give the unit. * + * * + * OUTPUT: bool; Was the unit unlimboed successfully? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/23/1994 JLB : Created. * + *=============================================================================================*/ +bool FootClass::Unlimbo(COORDINATE coord, DirType dir) +{ + /* + ** Try to unlimbo the unit. + */ + if (TechnoClass::Unlimbo(coord, dir)) { + + /* + ** Mobile units are always revealed to the house that owns them. + */ + Revealed(House); + + /* + ** Start in a still (non-moving) state. + */ + Path[0] = FACING_NONE; + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * FootClass::Assign_Mission -- Assign mission to foot class object. * + * * + * When a new mission is assigned, any precalculated path should be truncated. This is * + * in anticipation that the new mission will result in a change of path. * + * * + * INPUT: order -- The new mission to assign to the unit. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/29/1994 JLB : Created. * + *=============================================================================================*/ +void FootClass::Assign_Mission(MissionType order) +{ + if (What_Am_I() != RTTI_UNIT || *(UnitClass*)this != UNIT_GUNBOAT) { + Path[0] = FACING_NONE; + } + TechnoClass::Assign_Mission(order); +} + + +/*********************************************************************************************** + * FootClass::Limbo -- Intercepts limbo event and handles FootClass processing. * + * * + * When an object of FootClass type is limboed, then it must be removed from any team * + * it may be a member of. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the object successfully limboed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/29/1994 JLB : Created. * + *=============================================================================================*/ +bool FootClass::Limbo(void) +{ + if (!IsInLimbo) { + if (Team) { + Team->Remove(this); + } + } + return(TechnoClass::Limbo()); +} + + +/*********************************************************************************************** + * FootClass::Take_Damage -- Handles taking damage to this object. * + * * + * This routine intercepts the damage assigned to this object and if this object is * + * a member of a team, it informs the team that the damage has occurred. The team may * + * change it's priority or action based on this event. * + * * + * INPUT: damage -- The damage points inflicted on the unit. * + * * + * distance -- The distance from the point of damage to the unit itself. * + * * + * warhead -- The type of damage that is inflicted. * + * * + * source -- The purpitrator of the damage. By knowing who caused the damage, * + * the team know's who to "get even with". * + * * + * OUTPUT: Returns with the result type of the damage. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/30/1994 JLB : Created. * + *=============================================================================================*/ +ResultType FootClass::Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source) +{ + ResultType result = TechnoClass::Take_Damage(damage, distance, warhead, source); + + if (result != RESULT_NONE && Team) { + + if (GameToPlay != GAME_NORMAL || (source && !House->Is_Ally(source))) { + Team->Took_Damage(this, result, source); + } + + } else { + + if (result != RESULT_DESTROYED) { + /* + ** Determine if the target that is current being attacked has a weapon that can + ** do harm to a ground based unit. This information is needed so that an appropriate + ** response will occur when damage is taken. + */ + WeaponType weap = WEAPON_NONE; + if (As_Techno(TarCom)) { + weap = As_Techno(TarCom)->Techno_Type_Class()->Primary; + } + bool tweap = (weap != WEAPON_NONE && weap != WEAPON_NIKE); + + /* + ** This ensures that if a unit is in sticky mode, then it will snap out of + ** it when it takes damage. + */ + if (source && Mission == MISSION_STICKY) { + Enter_Idle_Mode(); + } + + /* + ** If this object is not part of a team and it can retaliate for the damage, then have + ** it try to do so. This prevents it from just sitting there and taking damage. + */ + if ( + source && + !House->Is_Ally(source) && + Techno_Type_Class()->Primary != WEAPON_NONE && + (source->What_Am_I() != RTTI_AIRCRAFT || BulletTypeClass::As_Reference(Weapons[Techno_Type_Class()->Primary].Fires).IsAntiAircraft) && + (!Target_Legal(TarCom) || ((!House->IsHuman || Special.IsSmartDefense) && (!tweap || !In_Range(TarCom)))) && +// !Target_Legal(NavCom) && + (Mission == MISSION_AMBUSH || + Mission == MISSION_GUARD || + Mission == MISSION_RESCUE || + Mission == MISSION_GUARD_AREA || + Mission == MISSION_ATTACK || + Mission == MISSION_TIMED_HUNT)) { + + /* + ** Assign the source of the damage as the new target. This occurs for the computer + ** controled units. For the player, this only occurs if the source of the damage + ** is within range. + */ + if (!House->IsHuman) { + + /* + ** If this unit is in TIMED_HUNT (multiplayer computer-controlled) + ** mode, "snap out of it" into HUNT mode; otherwise, assign + ** HUNT as the next mission through the normal mission queue. + */ + if (Mission == MISSION_TIMED_HUNT) { + Set_Mission(MISSION_HUNT); + } else { + Assign_Mission(MISSION_HUNT); + } + Assign_Target(source->As_Target()); + } else { + if (In_Range(source)) { + Assign_Target(source->As_Target()); + } else { + + /* + ** Simple retaliation cannot occur because the source of the damage + ** is too far away. If scatter logic is enabled, then scatter now. + */ + if (Special.IsScatter) { + Scatter(0, true); + } + } + } + } else { + + /* + ** If this object isn't doing anything important, then scatter. + */ + if (!IsDriving && !Target_Legal(TarCom) && !Target_Legal(NavCom) && Special.IsScatter && What_Am_I() != RTTI_AIRCRAFT) { + Scatter(0, true); + } + } + } + } + return(result); +} + + +/*********************************************************************************************** + * FootClass::Active_Click_With -- Intiates attack or move according to target clicked on. * + * * + * At this level, the object is known to have the ability to attack or move to the * + * target specified (in theory). Perform the attack or move as indicated. * + * * + * INPUT: target -- The target clicked upon that will precipitate action. * + * * + * OUTPUT: Returns with the type of action performed. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/06/1995 JLB : Created. * + *=============================================================================================*/ +void FootClass::Active_Click_With(ActionType action, ObjectClass * object) +{ + switch (action) { + case ACTION_GUARD_AREA: + if (Can_Player_Fire() && Can_Player_Move()) { + Player_Assign_Mission(MISSION_GUARD_AREA, object->As_Target()); + } + break; + + case ACTION_SELF: + Player_Assign_Mission(MISSION_UNLOAD); + break; + + case ACTION_ATTACK: + if (Can_Player_Fire()) { + Player_Assign_Mission(MISSION_ATTACK, object->As_Target()); + } + break; + + case ACTION_ENTER: + if (Can_Player_Move() && object && object->Is_Techno() && !((RadioClass *)object)->In_Radio_Contact()) { + Player_Assign_Mission(MISSION_ENTER, TARGET_NONE, object->As_Target()); + } + break; + + case ACTION_CAPTURE: + if (Can_Player_Move()) { + Player_Assign_Mission(MISSION_CAPTURE, TARGET_NONE, object->As_Target()); + } + break; + + case ACTION_SABOTAGE: + if (Can_Player_Move()) { + Player_Assign_Mission(MISSION_SABOTAGE, TARGET_NONE, object->As_Target()); + } + break; + + case ACTION_NOMOVE: + case ACTION_MOVE: + if (Can_Player_Move()) { + Player_Assign_Mission(MISSION_MOVE, TARGET_NONE, object->As_Target()); + } + break; + + default: + break; + } +} + + +/*********************************************************************************************** + * FootClass::Active_Click_With -- Performs action as a result of left mouse click. * + * * + * This routine performs the action requested when the left mouse button was clicked over * + * a cell. Typically, this is just a move command. * + * * + * INPUT: action -- The predetermined action that should occur. * + * * + * cell -- The cell number that the action should occur at. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void FootClass::Active_Click_With(ActionType action, CELL cell) +{ + switch (action) { + case ACTION_HARVEST: + Player_Assign_Mission(MISSION_HARVEST, TARGET_NONE, ::As_Target(cell)); + break; + + case ACTION_MOVE: + if (AllowVoice) { + COORDINATE coord = Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y()); + OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord)); + } + // Fall into next case. + + case ACTION_NOMOVE: + if (What_Am_I() != RTTI_AIRCRAFT || Map[cell].IsVisible) { + Player_Assign_Mission(MISSION_MOVE, TARGET_NONE, ::As_Target(cell)); + } + break; + + case ACTION_ATTACK: + Player_Assign_Mission(MISSION_ATTACK, ::As_Target(cell)); + break; + } +} + + +/*********************************************************************************************** + * FootClass::Per_Cell_Process -- Perform action based on once-per-cell condition. * + * * + * This routine is called as this object moves from cell to cell. When the center of the * + * cell is reached, check to see if any trigger should be sprung. For moving units, reduce * + * the path to the distance to the target. This forces path recalculation in an effort to * + * avoid units passing each other. * + * * + * INPUT: center -- Is this the center of the cell? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + * 07/08/1995 JLB : Handles generic enter trigger event. * + * 07/16/1995 JLB : If next to a scanner and cloaked, then shimmer. * + *=============================================================================================*/ +void FootClass::Per_Cell_Process(bool center) +{ +// if (center) { + + /* + ** Clear any unloading flag if necessary. + */ + IsUnloading = false; + + /* + ** If adjacent to an enemy building that has the ability to reveal a stealth tank, + ** then shimmer the cloaked object. + */ + if (Cloak == CLOAKED) { + for (FacingType face = FACING_N; face < FACING_COUNT; face++) { + CELL cell = Adjacent_Cell(Coord_Cell(Coord), face); + + if (Map.In_Radar(cell)) { + TechnoClass const * techno = Map[cell].Cell_Techno(); + + if (techno && !House->Is_Ally(techno) && techno->Techno_Type_Class()->IsScanner) { + Do_Shimmer(); + break; + } + } + } + } + + /* + ** Shorten the path if the target is now within weapon range of this + ** unit and this unit is on an attack type mission. + */ + if (What_Am_I() != RTTI_UNIT || *((UnitClass *)this) != UNIT_GUNBOAT) { + bool inrange = In_Range(TarCom); + TechnoClass const * techno = As_Techno(TarCom); + if (techno && techno->What_Am_I() != RTTI_BUILDING) { + inrange = In_Range(((FootClass const *)techno)->Likely_Coord()); + } + + if (Target_Legal(TarCom) && (Mission == MISSION_RESCUE || Mission == MISSION_GUARD_AREA || Mission == MISSION_ATTACK || Mission == MISSION_HUNT) && inrange) { + Assign_Destination(TARGET_NONE); + Path[0] = FACING_NONE; + } + } + + /* + ** Trigger event associated with the player entering the cell. + */ + TriggerClass * trigger = Map[Coord_Cell(Coord)].Get_Trigger(); + if (Cloak != CLOAKED && trigger && trigger->House == Owner()) { + trigger->Spring(EVENT_PLAYER_ENTERED, Coord_Cell(Coord)); + } +// } + + TechnoClass::Per_Cell_Process(center); +} + + +/*************************************************************************** + * FootClass::Override_Mission -- temporarily overides a units mission * + * * + * * + * * + * INPUT: MissionType mission - the mission we want to overide * + * TARGET tarcom - the new target we want to overide * + * TARGET navcom - the new navigation point to overide * + * * + * OUTPUT: none * + * * + * WARNINGS: If a mission is already overidden, the current mission is * + * just re-assigned. * + * * + * HISTORY: * + * 04/28/1995 PWG : Created. * + *=========================================================================*/ +void FootClass::Override_Mission(MissionType mission, TARGET tarcom, TARGET navcom) +{ + SuspendedNavCom = NavCom; + TechnoClass::Override_Mission(mission, tarcom, navcom); + + Assign_Destination(navcom); +} + + +/*************************************************************************** + * FootClass::Restore_Mission -- Restores an overidden mission * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/28/1995 PWG : Created. * + *=========================================================================*/ +bool FootClass::Restore_Mission(void) +{ + if (TechnoClass::Restore_Mission()) { + Assign_Destination(SuspendedNavCom); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * FootClass::Receive_Message -- Movement related radio messages are handled here. * + * * + * This routine handles radio message that are related to movement. These are used for * + * complex coordinated maneuvers. * + * * + * INPUT: from -- Pointer to the originator of this radio message. * + * * + * message -- The radio message that is being received. * + * * + * param -- The optional parameter (could be a movement destination). * + * * + * OUTPUT: Returns with the radio response appropriate to the message received. Usually the * + * response is RADIO_ROGER. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/14/1995 JLB : Created. * + *=============================================================================================*/ +RadioMessageType FootClass::Receive_Message(RadioClass * from, RadioMessageType message, long & param) +{ + switch (message) { + + /* + ** Intercept the repair request and if this object is moving, then no repair + ** is possible. + */ + case RADIO_REPAIR: + if (Target_Legal(NavCom)) return(RADIO_NEGATIVE); + break; + + /* + ** Something bad has happened to the object in contact with. Abort any coordinated + ** activity with this object. Basically, ... run away! Run away! + */ + case RADIO_RUN_AWAY: + if (In_Radio_Contact()) { + if (NavCom == Contact_With_Whom()->As_Target()) { + Assign_Destination(TARGET_NONE); + } + } + break; + + /* + ** Checks to see if this unit needs to move somewhere. If it is already in motion, + ** then it doesn't need furthur movement instructions. + */ + case RADIO_NEED_TO_MOVE: + param = (long)NavCom; + if (!Target_Legal(NavCom)) { + return(RADIO_ROGER); + } + return(RADIO_NEGATIVE); + + /* + ** Radio request to move to location specified. Typically this is used + ** for complex loading and unloading missions. + */ + case RADIO_MOVE_HERE: + if (NavCom != (TARGET)param) { + if (::As_Target(Coord_Cell(Coord)) == (TARGET)param) { + return(RADIO_YEA_NOW_WHAT); + } else { + if (Mission == MISSION_GUARD && MissionQueue == MISSION_NONE) { + Assign_Mission(MISSION_MOVE); + } + Assign_Destination((TARGET)param); + } + } + return(RADIO_ROGER); + + /* + ** Requests if this unit is trying to cooperatively load up. Typically, this occurs + ** for passengers and when vehicles need to be repaired. + */ + case RADIO_TRYING_TO_LOAD: + if (Mission == MISSION_ENTER || MissionQueue == MISSION_ENTER) { + TechnoClass::Receive_Message(from, message, param); + return(RADIO_ROGER); + } + break; + } + return(TechnoClass::Receive_Message(from, message, param)); +} + + +/*********************************************************************************************** + * FootClass::Mission_Enter -- Enter (cooperatively) mission handler. * + * * + * This mission handler will cooperatively coordinate the object to maneuver into the * + * object it is in radio contact with. This is used by infantry when they wish to load * + * into an APC as well as by vehicles when they wish to enter a repair facility. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the number of game ticks before this routine should be called again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/15/1995 JLB : Created. * + *=============================================================================================*/ +int FootClass::Mission_Enter(void) +{ + /* + ** If radio contact has not yet been established with the transport, try to + ** establish contact now. + */ + if (!In_Radio_Contact()) { + TechnoClass * techno = As_Techno(ArchiveTarget); + if (!techno) techno = As_Techno(NavCom); + if (techno) { + + /* + ** If the transport is already in radio contact, do nothing. Try to + ** establish radio contact later. + */ + if (Transmit_Message(RADIO_HELLO, techno) != RADIO_ROGER) { + ArchiveTarget = TARGET_NONE; + Enter_Idle_Mode(); + } else { + Assign_Destination(TARGET_NONE); + } + } else { + ArchiveTarget = TARGET_NONE; + Enter_Idle_Mode(); + } + + } else { + + /* + ** Since radio contact exists with the transport, maintain a dialogue so that + ** the transport can give proper instructions to the passenger. + */ + if (Transmit_Message(RADIO_DOCKING) != RADIO_ROGER) { + Transmit_Message(RADIO_OVER_OUT); + Enter_Idle_Mode(); + } + } + return(TICKS_PER_SECOND/2); +} + + +/*********************************************************************************************** +z * FootClass::Assign_Destination -- Assigns specified destination to NavCom. * + * * + * This routine will assign the specified target to the navigation computer. No legality * + * checks are performed. * + * * + * INPUT: target -- The target value to assign to the navigation computer. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +void FootClass::Assign_Destination(TARGET target) +{ + NavCom = target; +} + + +/*********************************************************************************************** + * FootClass::Detach_All -- Removes this object from the game system. * + * * + * This routine will remove this object from the game system. This routine is called when * + * this object is about to be deleted. All other objects should no longer reference this * + * object in that case. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +void FootClass::Detach_All(bool all) +{ + if (Team) Team->Remove(this); + Team = NULL; + + TechnoClass::Detach_All(all); +} + + +/*********************************************************************************************** + * FootClass::Rescue_Mission -- Calls this unit to the rescue. * + * * + * This routine is called when the house determines that it should attack the specified * + * target. This routine will determine if it can attack the target specified and if so, * + * the amount of power it can throw at it. This returned power value is used to allow * + * intelligent distribution of retaliation. * + * * + * INPUT: target -- The target that this object just might be assigned to attack and thus * + * how much power it can bring to bear should be returned. * + * * + * OUTPUT: Returns with the amount of power that this object can bring to bear against the * + * potential target specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +int FootClass::Rescue_Mission(TARGET tarcom) +{ + /* + ** If the target specified is not legal, then it cannot be attacked. Always return + ** zero in this case. + */ + if (!Target_Legal(tarcom)) return(0); + + /* + ** If the unit is already assigned to destroy the tarcom then we need + ** to return a negative value which tells the computer to lower the + ** desired threat rating. + */ + if (TarCom == tarcom) { + return(-Risk()); + } + + /* + ** If the unit is currently attacking a target that has a weapon then we + ** cannot abandon it as it will destroy us if we return to base. + */ + if (Target_Legal(TarCom)) { + TechnoClass * techno = As_Techno(TarCom); + if (techno && techno->Techno_Type_Class()->Primary != WEAPON_NONE) { + return(0); + } + } + + /* + ** If the unit is in a harvest mission or is currently attacking + ** something, or is not very effective, then it will be of no help + ** at all. + */ + if (Team || Mission == MISSION_HARVEST || !Risk()) { + return(0); + } + + /* + ** Find the distance to the target modified by the range. If the + ** the distance is 0, then things are ok. + */ + int dist = Distance(tarcom) - Weapon_Range(0); + int threat = Risk() * 256; + int speed = -1; + if (dist > 0) { + + /* + ** Next we need to figure out how fast the unit moves because this + ** decreases the distance penalty. + */ + speed = Max((unsigned)Techno_Type_Class()->MaxSpeed, (unsigned)1); + + int ratio = (speed > 0) ? Max(dist / speed, 1) : 1; + + /* + ** Finally modify the threat by the distance the unit is away. + */ + threat = Max(threat/ratio, 1); + } + return(threat); +} + + +/*********************************************************************************************** + * FootClass::Death_Announcement -- Announces the death of a unit. * + * * + * This routine is called when a unit (infantry, vehicle, or aircraft) is destroyed. * + * * + * INPUT: source -- The purpetrator of this death. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/01/1995 JLB : Created. * + *=============================================================================================*/ +void FootClass::Death_Announcement(TechnoClass const * source) const +{ + if (IsDiscoveredByPlayer || IsOwnedByPlayer) { + if (!source || source->What_Am_I() != RTTI_INFANTRY || *((InfantryClass const *)source) != INFANTRY_RAMBO) { + if (What_Am_I() == RTTI_INFANTRY && ((InfantryTypeClass const &)Class_Of()).IsCivilian && !((InfantryClass *)this)->IsTechnician) { + if (Options.IsDeathAnnounce) Speak(VOX_DEAD_CIV); + } else { + if (House != PlayerPtr && GameToPlay != GAME_NORMAL) { + if (Options.IsDeathAnnounce) Speak(VOX_ENEMY_UNIT); + } else { + if (House == PlayerPtr || Options.IsDeathAnnounce) { + if (!Options.IsDeathAnnounce) { + Speak(VOX_UNIT_LOST); + } else { + switch (House->ActLike) { + case HOUSE_GOOD: + Speak(VOX_DEAD_GDI); + break; + + case HOUSE_BAD: + Speak(VOX_DEAD_NOD); + break; + + default: + break; + } + } + } + } + } + } + } +} + + +/*********************************************************************************************** + * FootClass::Greatest_Threat -- Fetches the greatest threat to this object. * + * * + * This routine will return with the greatest threat (best target) for this object. For * + * movable ground object, they won't automatically return ANY target if this object is * + * cloaked. Otherwise, cloaking is relatively useless. * + * * + * INPUT: method -- The request method (bit flags) to use when scanning for a target. * + * * + * OUTPUT: Returns with the best target to attack. If there is no target that qualifies, then * + * TARGET_NONE is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +TARGET FootClass::Greatest_Threat(ThreatType method) const +{ + /* + ** If this object can cloak, then it won't select a target automatically. + */ + if (House->IsHuman && IsCloakable && Mission == MISSION_GUARD) { + return(TARGET_NONE); + } + + if (Techno_Type_Class()->Primary != WEAPON_NONE && BulletTypeClass::As_Reference(Weapons[Techno_Type_Class()->Primary].Fires).IsAntiAircraft) { + method = method | THREAT_AIR; + } + if (Techno_Type_Class()->Secondary != WEAPON_NONE && BulletTypeClass::As_Reference(Weapons[Techno_Type_Class()->Secondary].Fires).IsAntiAircraft) { + method = method | THREAT_AIR; + } + + return(TechnoClass::Greatest_Threat(method|THREAT_GROUND)); +} + + +/*********************************************************************************************** + * FootClass::Detach -- Detaches a target from tracking systems. * + * * + * This routine will detach the specified target from the tracking systems of this object. * + * It will be removed from the navigation computer and any queued mission record. * + * * + * INPUT: target -- The target to be removed from this object. * + * * + * all -- Is the unit really about to be eliminated? If this is true then even * + * friendly contact (i.e., radio) must be eliminated. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1995 JLB : Created. * + *=============================================================================================*/ +void FootClass::Detach(TARGET target, bool all) +{ + TechnoClass::Detach(target, all); + + if (!SpecialFlag) { + if (ArchiveTarget == target) { + ArchiveTarget = TARGET_NONE; + } + } + + if (SuspendedNavCom == target) { + SuspendedNavCom = TARGET_NONE; + SuspendedMission = MISSION_NONE; + } + + /* + ** If the navigation computer is assigned to the target, then the navigation + ** computer must be cleared. + */ + if (NavCom == target) { + NavCom = TARGET_NONE; + Path[0] = FACING_NONE; + Restore_Mission(); + } + + /* + ** If targeting the specified object and this unit is obviously heading + ** toward the target to get within range, then abort the path. + */ + if (TarCom == target && House->IsHuman) { + Path[0] = FACING_NONE; + } +} + + +/*********************************************************************************************** + * FootClass::Offload_Tiberium_Bail -- Fetches the Tiberium to offload per step. * + * * + * This routine is called when a packet/package/bail of Tiberium needs to be offloaded * + * from the object. This function is overridden for those objects that can contain * + * Tiberium. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of credits offloaded from the object. * + * * + * WARNINGS: This routine must be called multiple times in order to completely offload the * + * Tiberium. When this routine return 0, all Tiberium has been offloaded. * + * * + * HISTORY: * + * 07/19/1995 JLB : Created. * + *=============================================================================================*/ +int FootClass::Offload_Tiberium_Bail(void) +{ + return(0); +} + + +/*********************************************************************************************** + * FootClass::Can_Enter_Cell -- Checks to see if the object can enter cell specified. * + * * + * This routine examines the specified cell to see if the object can enter it. This * + * function is to be overridden for objects that could have the possibility of not being * + * allowed to enter the cell. Typical objects at the FootClass level always return * + * MOVE_OK. * + * * + * INPUT: cell -- The cell to examine. * + * * + * facing -- The direction that this cell might be entered from. * + * * + * OUTPUT: Returns with the move check result type. This will be MOVE_OK if there is not * + * blockage. There are various other values that represent other blockage types. * + * The value returned will indicatd the most severe reason why entry into the cell * + * is blocked. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1995 JLB : Created. * + *=============================================================================================*/ +MoveType FootClass::Can_Enter_Cell(CELL , FacingType) const +{ + return MOVE_OK; +} + + +/*********************************************************************************************** + * FootClass::Can_Demolish -- Checks to see if this object can be sold back. * + * * + * This routine determines if it is legal to sell the object back. A foot class object can * + * only be sold back if it is sitting on a repair bay. * + * * + * INPUT: none * + * * + * OUTPUT: Was the object successfully sold back? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +bool FootClass::Can_Demolish(void) const +{ + switch (What_Am_I()) { + case RTTI_UNIT: + case RTTI_AIRCRAFT: + if (In_Radio_Contact() && + Contact_With_Whom()->What_Am_I() == RTTI_BUILDING && + *((BuildingClass *)Contact_With_Whom()) == STRUCT_REPAIR && + Distance(Contact_With_Whom()) < 0x0080) { + + return(true); + } + break; + + default: + break; + } + return(TechnoClass::Can_Demolish()); +} + + +/*********************************************************************************************** + * FootClass::Sell_Back -- Causes this object to be sold back. * + * * + * When an object is sold back, a certain amount of money is refunded to the owner and then * + * the object is removed from the game system. * + * * + * INPUT: control -- The action to perform. The only supported action is "1", which means * + * to sell back. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +void FootClass::Sell_Back(int control) +{ + if (control != 0) { + if (House == PlayerPtr) { + Sound_Effect(VOC_CASHTURN); + } + House->Refund_Money(Refund_Amount()); + Stun(); + Limbo(); + delete this; + } +} + + +/*********************************************************************************************** + * FootClass::Likely_Coord -- Fetches the coordinate the object will be at shortly. * + * * + * This routine comes in handy when determining where a travelling object will be at * + * when considering the amount of time it would take for a normal unit to travel one cell. * + * Using this information, an intelligent "approach target" logic can be employed. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the coordinate the object is at or soon will be. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +COORDINATE FootClass::Likely_Coord(void) const +{ + if (Head_To_Coord()) { + return(Head_To_Coord()); + } + return(Target_Coord()); +} diff --git a/FOOT.H b/FOOT.H new file mode 100644 index 0000000..9069df3 --- /dev/null +++ b/FOOT.H @@ -0,0 +1,311 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\foot.h_v 2.20 16 Oct 1995 16:47:14 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FOOT.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 14, 1994 * + * * + * Last Update : April 14, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef FOOT_H +#define FOOT_H + +#include "target.h" +#include "type.h" +#include "techno.h" +#include "ftimer.h" + +class UnitClass; +class BuildingClass; + + +/**************************************************************************** +** Movable objects are handled by this class definition. Moveable objects +** cover everything except buildings. +*/ +class FootClass : public TechnoClass +{ + public: + /* + ** If this unit has officially joined the team's group, then this flag is + ** true. A newly assigned unit to a team is not considered part of the + ** team until it actually reaches the location where the team is. By + ** using this flag, it allows a team to continue to intelligently attack + ** a target without falling back to regroup the moment a distant member + ** joins. + */ + unsigned IsInitiated:1; + + /* + ** When the player gives this object a navigation target AND that target + ** does not result in any movement of the unit, then a beep should be + ** sounded. This typically occurs when selecting an invalid location for + ** movement. This flag is cleared if any movement was able to be performed. + ** It never gets set for computer controlled units. + */ + unsigned IsNewNavCom:1; + + /* + ** There are certain cases where a unit should perform a full scan rather than + ** the more efficient "ring scan". This situation occurs when a unit first + ** appears on the map or when it finishes a multiple cell movement track. + */ + unsigned IsPlanningToLook:1; + + /* + ** Certain units have the ability to metamorphize into a building. When this + ** operation begins, certain processes must occur. During these operations, this + ** flag will be true. This ensures that any necessary special case code gets + ** properly executed for this unit. + */ + unsigned IsDeploying:1; + + /* + ** This flag tells the system that the unit is doing a firing animation. This is + ** critical to the firing logic. + */ + unsigned IsFiring:1; + + /* + ** This unit could be either rotating its body or rotating its turret. During the + ** process of rotation, this flag is set. By examining this flag, unnecessary logic + ** can be avoided. + */ + unsigned IsRotating:1; + + /* + ** If this object is current driving to a short range destination, this flag is + ** true. A short range destination is either the next cell or the end of the + ** current "curvy" track. An object that is driving is not allowed to do anything + ** else until it reaches its destination. The exception is when infantry wish to + ** head to a different destination, they are allowed to start immediately. + */ + unsigned IsDriving:1; + + /* + ** If this object is unloading from a hover transport, then this flag will be + ** set to true. This handles the unusual case of an object disembarking from the + ** hover lander yet not necessarily tethered but still located in an overlapping + ** position. This flag will be cleared automatically when the object moves to the + ** center of a cell. + */ + unsigned IsUnloading:1; + + /* + ** This is the "throttle setting" of the unit. It is a fractional value with 0 = stop + ** and 255 = full speed. + */ + unsigned char const Speed; + + /* + ** For units in area guard mode, this is the recorded home position. The guarding + ** unit will try to stay near this location in the course of it's maneuvers. This is + ** also used to record a pending transport for those passengers that are waiting for + ** the transport to become available. It is also used by harvesters so that they know + ** where to head back to after unloading. + */ + TARGET ArchiveTarget; + + /* + ** + ** This is the desired destination of the unit. The unit will attempt to head + ** toward this target (avoiding intervening obstacles). + */ + TARGET NavCom; + TARGET SuspendedNavCom; + + /* + ** This points to the team that "owns" this object. This pointer is used to + ** quickly process the team when this object is the source of the change. An + ** example would be if this object were to be destroyed, it would inform the + ** team of this fact by using this pointer. + */ + TeamClass * Team; + + /* + ** If this object is part of a pseudo-team that the player is managing, then + ** this will be set to the team number (0 - 9). If it is not part of any + ** pseudo-team, then the number will be -1. + */ + unsigned char Group; + + /* + ** This points to the next member in the team that this object is part of. This + ** is used to quickly process each team member when the team class is the source + ** of the change. An example would be if the team decided that everyone is going + ** to move to a new location, it would inform each of the objects by chaining + ** through this pointer. + */ + FootClass * Member; + + /* + ** Since all objects derived from this class move according to a path list. + ** This is the path list. It specifies, as a simple list of facings, the + ** path that the object should follow in order to reach its destination. + ** This path list is limited in size, so it might require several generations + ** of path lists before the ultimate destination is reached. The game logic + ** handles regenerating the path list as necessary. + */ + FacingType Path[CONQUER_PATH_MAX]; + + /* + ** When there is a complete findpath failure, this timer is initialized so + ** that a findpath won't be calculated until this timer expires. + */ + TCountDownTimerClass PathDelay; + enum {PATH_DELAY=15,PATH_RETRY=10}; + int TryTryAgain; // Number of retry attempts remaining. + + /* + ** If the object has recently attacked a base, then this timer will not + ** have expired yet. It is used so a building does not keep calling + ** for help from the same attacker. + */ + TCountDownTimerClass BaseAttackTimer; + + /*--------------------------------------------------------------------- + ** Constructors, Destructors, and overloaded operators. + */ + FootClass(void); + virtual ~FootClass(void); + FootClass(HousesType house); + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + bool Basic_Path(void); + + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, long & param); + virtual bool Can_Demolish(void) const; + + /* + ** Coordinate inquiry functions. These are used for both display and + ** combat purposes. + */ + virtual COORDINATE Sort_Y(void) const; + virtual COORDINATE Likely_Coord(void) const; + + /* + ** Driver control support functions. These are used to control cell + ** occupation flags and driver instructions. + */ + COORDINATE Head_To_Coord(void) const {return (HeadToCoord);}; + virtual bool Start_Driver(COORDINATE &headto); + virtual bool Stop_Driver(void); + virtual void Assign_Destination(TARGET target); + + /* + ** Display and rendering support functionality. Supports imagery and how + ** object interacts with the map and thus indirectly controls rendering. + */ + virtual bool Unlimbo(COORDINATE , DirType dir = DIR_N); + virtual bool Limbo(void); + virtual bool Mark(MarkType mark); + + /* + ** User I/O. + */ + virtual void Active_Click_With(ActionType action, ObjectClass * object); + virtual void Active_Click_With(ActionType action, CELL cell); + + /* + ** Combat related. + */ + virtual void Stun(void); + virtual ResultType Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source=0); + virtual void Death_Announcement(TechnoClass const * source=0) const; + + /* + ** AI. + */ + virtual void Sell_Back(int control); + virtual int Offload_Tiberium_Bail(void); + virtual TARGET Greatest_Threat(ThreatType method) const; + virtual void Detach(TARGET target, bool all); + virtual void Detach_All(bool all=true); + virtual void Assign_Mission(MissionType order); + virtual int Mission_Enter(void); + virtual int Mission_Move(void); + virtual int Mission_Capture(void); + virtual int Mission_Attack(void); + virtual int Mission_Guard(void); + virtual int Mission_Hunt(void); + virtual int Mission_Timed_Hunt(void); + virtual int Mission_Guard_Area(void); + + /* + ** Scenario and debug support. + */ + #ifdef CHEAT_KEYS + virtual void Debug_Dump(MonoClass *mono) const; + #endif + + /* + ** Movement and animation. + */ + virtual void Per_Cell_Process(bool center); + virtual void Approach_Target(void); + virtual void Fixup_Path(PathType *) {}; + virtual void Set_Speed(int speed); + virtual MoveType Can_Enter_Cell(CELL , FacingType =FACING_NONE) const; + int Optimize_Moves(PathType *path, MoveType threshhold); + virtual void Override_Mission(MissionType mission, TARGET tarcom, TARGET navcom); + virtual bool Restore_Mission(void); + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + CELL Safety_Point(CELL src, CELL dst, int start, int max); + int Rescue_Mission(TARGET tarcom); + + private: + int Passable_Cell(CELL cell, FacingType face, int threat, MoveType threshhold); + PathType * Find_Path(CELL dest, FacingType *final_moves, int maxlen, MoveType threshhold); + void Debug_Draw_Map(char *txt, CELL start, CELL dest, bool pause); + void Debug_Draw_Path(PathType *path); + bool Follow_Edge(CELL start, CELL target, PathType *path, FacingType search, FacingType olddir, int threat, int threat_stage, int max_cells, MoveType threshhold); + bool Register_Cell(PathType *path, CELL cell, FacingType dir, int cost, MoveType threshhold); + bool Unravel_Loop(PathType *path, CELL &cell, FacingType &dir, int sx, int sy, int dx, int dy, MoveType threshhold); + + /* + ** This is the coordinate that the unit is heading to + ** as an immediate destination. This coordinate is never further + ** than once cell (or track) from the unit's location. When this coordinate + ** is reached, then the next location in the path list becomes the + ** next HeadTo coordinate. + */ + COORDINATE HeadToCoord; +}; + +#endif diff --git a/FTIMER.H b/FTIMER.H new file mode 100644 index 0000000..641a457 --- /dev/null +++ b/FTIMER.H @@ -0,0 +1,91 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ftimer.h_v 2.14 16 Oct 1995 16:47:28 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FTIMER.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 03/16/95 * + * * + * Last Update : March 16, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef FTIMER_H +#define FTIMER_H + +/* +** This timer class is based around an external tick system. As such, it is inherently +** in sync with any connected system (through network or modem) that also keeps the external +** tick system in sync. The game frame number is a good sync value. +*/ +class TCountDownTimerClass { + public: + // Constructor. Timers set before low level init has been done will not + // be able to be 'Started' or 'on' until timer system is in place. + TCountDownTimerClass(long set=0) { + Set(set); + }; + + // No destructor. + ~TCountDownTimerClass(void) {} + + operator long(void) const {return Time();}; + + // Public functions + void Set(long set) { + Started = Frame; + DelayTime = set; + }; // Set count down value. + + void Clear(void) { + Started = -1; + DelayTime = 0; + }; + long Get_Start(void) const { + return(Started); + }; + long Get_Delay(void) const { + return(DelayTime); + }; + bool Active(void) const { + return(Started != -1); + }; + int Expired(void) const {return (Time() == 0);}; + long Time(void) const { + long remain = DelayTime - (Frame-Started); + if (remain < 0) remain = 0; + return(remain); + }; // Fetch current count down value. + + protected: + long Started; // Initial frame time start. + long DelayTime; // Ticks remaining before countdown timer expires. +}; + + +#endif diff --git a/FUNCTION.H b/FUNCTION.H new file mode 100644 index 0000000..9ec6791 --- /dev/null +++ b/FUNCTION.H @@ -0,0 +1,926 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\function.h_v 2.21 16 Oct 1995 16:46:44 JOE_BOSTIC $*/ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FUNCTION.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 27, 1994 * + * * + * Last Update : May 27, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef FUNCTION_H +#define FUNCTION_H + +#ifdef NEVER +Map (screen) class heirarchy. + + MapeditClass (most derived class) -- scenario editor + ³ + MouseClass -- handles mouse animation and display control + ³ + ScrollClass -- map scroll handler + ³ + HelpClass -- pop-up help text handler + ³ + TabClass -- file folder tab screen mode control dispatcher + ³ + SidebarClass -- displays and controls construction list sidebar + ³ + PowerClass -- display power production/consumption bargraph + ³ + RadarClass -- displays and controls radar map + ³ + DisplayClass -- general tactical map display handler + ³ + MapClass -- general tactical map data handler + ³ + GScreenClass (pure virtual base class) -- generic screen control + + AbstractClass + ³ + ³ + ³ + ³ + ObjectClass + ³ + ÚÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄ¿ + AnimClass ³ TemplateClass ³ ÃÄ FuseClass ³ TerrainClass + ³ ³ ÃÄ FlyClass ³ + ³ ³ BulletClass ³ + OverlayClass MissionClass SmudgeClass + ³ + RadioClass + ³ + ÃÄ CrewClass + ÃÄ FlasherClass + ÃÄ StageClass + ÃÄ CargoClass + TechnoClass + ³ + ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ + FootClass BuildingClass + ³ + ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ + DriveClass InfantryClass ÃÄ FlyClass + ³ AircraftClass + TurretClass + ³ + TarComClass + ³ + UnitClass + + + AbstractTypeClass + ³ + ObjectTypeClass + ³ + ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ + ³ ³ ³ ³ + TechnoTypeClass ³ ³ ³ + ³ BulletTypeClass ³ ³ + ³ TemplateTypeClass ³ + ÚÄÄÄÄÄÄÄÄÁÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ TerrainTypeClass + ³ ³ ³ ³ +UnitTypeClass ³ BuildingTypeClass ³ + ³ InfantryTypeClass + AircraftTypeClass +#endif + +/* +** The "bool" integral type was defined by the C++ comittee in +** November of '94. Until the compiler supports this, use the following +** definition. +*/ +#ifndef TRUE_FALSE_DEFINED +enum {false=0,true=1}; +typedef int bool; +#define TRUE_FALSE_DEFINED +#endif //TRUE_FALSE_DEFINED + + +#define _WIN32 +#define WIN32 =1 //_LEAN_AND_MEAN +#include + + +/********************************************************************** +** If the following define is enabled, then the memory checking code +** will be disabled. +*/ +#define NOMEMCHECK + +#include "watcom.h" +#define FILE_H +#define WWMEM_H +#include "compat.h" +#include +#include "jshell.h" + + + +// Should be part of WWLIB.H. This is used in JSHELL.CPP. +typedef struct { + unsigned char SourceColor; + unsigned char DestColor; + unsigned char Fading; + unsigned char reserved; +} TLucentType; + + +// Don't complain if these headers aren't referenced. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* +** VQ player specific includes. +*/ +#include +#include + +extern bool GameActive; +extern long LParam; + +#include "vector.h" +#include "heap.h" +#include "ccfile.h" +#include "monoc.h" +#include "conquer.h" +//#include "debug.h" +#include "special.h" +#include "defines.h" + + +/* +** Greenleaf specific includes. +*/ +#include +#include + + +extern long Frame; +inline CELL Coord_XCell(COORDINATE coord) {return (CELL)(*(((unsigned char*)&coord)+1));} +inline CELL Coord_YCell(COORDINATE coord) {return (CELL)(*(((unsigned char*)&coord)+3));} +//inline CELL Coord_Cell(COORD coord){return (CELL)(((*(((unsigned short *)&coord)+1) & 0xFF00) >> 2) | *(((unsigned char *)&coord)+1));} +CELL Coord_Cell(COORDINATE coord); +#pragma aux Coord_Cell parm [eax] \ + modify [ebx] \ + value [ax] = \ + "mov ebx,eax" \ + "shr eax,010h" \ + "xor al,al" \ + "shr eax,2" \ + "or al,bh" + + +#include "utracker.h" +#include "facing.h" +#include "ftimer.h" +#include "theme.h" +#include "link.h" +#include "gadget.h" +#include "control.h" +#include "toggle.h" +#include "checkbox.h" +#include "shapebtn.h" +#include "textbtn.h" +#include "slider.h" +#include "list.h" +#include "cheklist.h" +#include "colrlist.h" +#include "edit.h" +#include "gauge.h" +#include "msgbox.h" +#include "dial8.h" +#include "txtlabel.h" +#include "super.h" +#include "house.h" +#include "gscreen.h" +#include "map.h" +#include "display.h" +#include "radar.h" +#include "power.h" +#include "sidebar.h" +#include "tab.h" +#include "help.h" +#include "mouse.h" +//#include "mapedit.h" +#include "help.h" +#include "target.h" +#include "theme.h" +#include "team.h" // Team objects. +#include "teamtype.h" // Team type objects. +#include "trigger.h" // Trigger event objects. +#include "mapedit.h" // ??? +#include "abstract.h" +#include "object.h" +#include "mission.h" +#include "door.h" +#include "bullet.h" // Bullet objects. +#include "terrain.h" // Terrain objects. +#include "anim.h" // Animation objects. +#include "template.h" // Icon template objects. +#include "overlay.h" // Overlay objects. +#include "smudge.h" // Stains on the terrain objects. +#include "aircraft.h" // Aircraft objects. +#include "unit.h" // Ground unit objects. +#include "infantry.h" // Infantry objects. +#include "credits.h" // Credit counter class. +#include "score.h" // Scoring system class. +#include "factory.h" // Production manager class. +#include "intro.h" +#include "ending.h" +#include "logic.h" +#include "queue.h" +#include "event.h" +#include "base.h" // defines the AI's pre-built base +#include "ipxmgr.h" +#include "combuf.h" +#include "connect.h" +#include "connmgr.h" +#include "noseqcon.h" +#include "msglist.h" +#include "nullconn.h" +#include "nullmgr.h" +#include "phone.h" +#include "loaddlg.h" +#include "ipxaddr.h" +/**************************************************************************** +** This is a "node", used for the lists of available games & players. The +** 'Game' structure is used for games; the 'Player' structure for players. +*/ +typedef struct NodeNameTag { + char Name[MPLAYER_NAME_MAX]; + IPXAddressClass Address; + union { + struct { + int Version; + unsigned char IsOpen; + unsigned long LastTime; + } Game; + struct { + HousesType House; + unsigned char Color; + } Player; + }; +} NodeNameType; + + +#include "externs.h" + + +extern int Get_CD_Drive(void); +extern void Fatal(char const *message, ...); + + +/* +** ANIM.CPP +*/ +void Shorten_Attached_Anims(ObjectClass * obj); + +/* +** AUDIO.CPP +*/ +int Sound_Effect(VocType voc, VolType volume, int variation=1, signed short panvalue=0); +void Speak(VoxType voice); +void Speak_AI(void); +void Stop_Speaking(void); +void Sound_Effect(VocType voc, COORDINATE coord=NULL, int variation=1); +bool Is_Speaking(void); + +/* +** COMBAT.CPP +*/ +int Modify_Damage(int damage, WarheadType warhead, ArmorType armor, int distance); +void Explosion_Damage(COORDINATE coord, unsigned strength, TechnoClass * source, WarheadType warhead); + +/* +** CONQUER.CPP +*/ +void Center_About_Objects(void); +bool Force_CD_Available(int cd); +void Handle_View(int view, int action=0); +void Handle_Team(int team, int action=0); +TechnoTypeClass const * Fetch_Techno_Type(RTTIType type, int id); +char const * Fading_Table_Name(char const * base, TheaterType theater); +void Unselect_All(void); +void Play_Movie(char const * name, ThemeType theme=THEME_NONE, bool clrscrn=true); +bool Main_Loop(); +TheaterType Theater_From_Name(char const *name); +//DirType Rotation_Calc(DirType current, DirType desired, int rate); +void Main_Game(int argc, char *argv[]); +long VQ_Call_Back(unsigned char * buffer=NULL, long frame=0); +void Call_Back(void); +char const *Language_Name(char const *basename); +SourceType Source_From_Name(char const *name); +char const *Name_From_Source(SourceType source); +FacingType KN_To_Facing(int input); +void const *Get_Radar_Icon(void const *shapefile, int shapenum, int frames, int zoomfactor); +void CC_Draw_Shape(void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata=0, void const * ghostdata=0); +void Go_Editor(bool flag); +long MixFileHandler(VQAHandle *vqa, long action, void *buffer, long nbytes); + +char *CC_Get_Shape_Filename(void const *shapeptr ); +void CC_Add_Shape_To_Global(void const *shapeptr, char *filename, char code ); + +void Bubba_Print(char *format,...); + +void Heap_Dump_Check( char *string ); +void Dump_Heap_Pointers( void ); +unsigned long Disk_Space_Available(void); + +void Validate_Error(char *name); +void const * Hires_Retrieve(char *name); +int Get_Resolution_Factor(void); + + +/* +** INTERPAL.CPP +*/ +#define SIZE_OF_PALETTE 256 +extern "C" unsigned char *InterpolationPalette; +extern BOOL InterpolationPaletteChanged; +extern void Interpolate_2X_Scale( GraphicBufferClass *source, GraphicViewPortClass *dest ,char const *palette_file_name); +void Read_Interpolation_Palette (char const *palette_file_name); +void Write_Interpolation_Palette (char const *palette_file_name); +void Increase_Palette_Luminance(unsigned char *InterpolationPalette , int RedPercentage ,int GreenPercentage ,int BluePercentage ,int cap); +extern "C"{ + extern unsigned char PaletteInterpolationTable[SIZE_OF_PALETTE][SIZE_OF_PALETTE]; + extern unsigned char *InterpolationPalette; + void __cdecl Asm_Create_Palette_Interpolation_Table(void); +} + + +/* +** COORD.CPP +*/ +void Move_Point(short &x, short &y, register DirType dir, unsigned short distance); +COORDINATE Adjacent_Cell(COORDINATE coord, FacingType dir); +COORDINATE Coord_Move(COORDINATE start, DirType facing, unsigned short distance); +COORDINATE Coord_Scatter(COORDINATE coord, unsigned distance, bool lock=false); +DirType Direction(CELL cell1, CELL cell2); +DirType Direction(COORDINATE coord1, COORDINATE coord2); +DirType Direction256(COORDINATE coord1, COORDINATE coord2); +DirType Direction8(COORDINATE coord1, COORDINATE coord2); +int Distance(CELL coord1, CELL coord2); +int Distance(COORDINATE coord1, COORDINATE coord2); +short const * Coord_Spillage_List(COORDINATE coord, int maxsize); +//void Move_Point(unsigned short &x, unsigned short &y, DirType dir, unsigned short distance); + +/* +** COORDA.CPP +*/ +//extern "C" { +//unsigned Cardinal_To_Fixed(unsigned base, unsigned cardinal); +//unsigned Fixed_To_Cardinal(unsigned base, unsigned fixed); +//} + +/* +** DEBUG.CPP +*/ +void Log_Event(char const *text, ...); +void Debug_Key(unsigned input); +void Self_Regulate(void); + +/* +** DIALOG.CPP +*/ +int Format_Window_String(char * string, int maxlinelen, int & width, int & height); +extern void Dialog_Box(int x, int y, int w, int h); +void Conquer_Clip_Text_Print(char const *, unsigned x, unsigned y, unsigned fore, unsigned back=(unsigned)TBLACK, TextPrintType flag=TPF_8POINT|TPF_DROPSHADOW, unsigned width=-1, int const * tabs=0); +void Draw_Box(int x, int y, int w, int h, BoxStyleEnum up, bool filled); +int __cdecl Dialog_Message(char *errormsg, ...); +void Window_Box(WindowNumberType window, BoxStyleEnum style); +void Fancy_Text_Print(char const *text, unsigned x, unsigned y, unsigned fore, unsigned back, TextPrintType flag, ...); +void Fancy_Text_Print(int text, unsigned x, unsigned y, unsigned fore, unsigned back, TextPrintType flag, ...); +void Simple_Text_Print(char const *text, unsigned x, unsigned y, unsigned fore, unsigned back, TextPrintType flag); + +/* +** DISPLAY.CPP +*/ + +/* +** ENDING.CPP +*/ +void GDI_Ending(void); +void Nod_Ending(void); + +/* +** EXPAND.CPP +*/ +bool Expansion_Present(void); +bool Expansion_Dialog(void); +bool Bonus_Dialog(void); + +/* +** FINDPATH.CPP +*/ +//PathType * Find_Path(CELL source, CELL dest, FacingType *final_moves, int maxlen, int (*callback)(CELL, FacingType), int threshhold); +int Optimize_Moves(PathType *path, int (*callback)(CELL, FacingType), int threshhold); + +/* +** GOPTIONS.CPP +*/ +void Draw_Caption(int text, int x, int y, int w); + +/* +** INI.CPP +*/ +void Set_Scenario_Name(char *buf, int scenario, ScenarioPlayerType player, ScenarioDirType dir = SCEN_DIR_NONE, ScenarioVarType var = SCEN_VAR_NONE); +void Write_Scenario_Ini(char *root); +bool Read_Scenario_Ini(char *root, bool fresh=true); +int Scan_Place_Object(ObjectClass *obj, CELL cell); + +/* +** INIT.CPP +*/ +void Uninit_Game(void); +long Obfuscate(char const * string); +void Anim_Init(void); +bool Init_Game(int argc, char *argv[]); +bool Select_Game(bool fade = false); +bool Parse_Command_Line(int argc, char *argv[]); +void Parse_INI_File(void); +int Version_Number(void); +void Save_Recording_Values(void); +void Load_Recording_Values(void); + +/* +** JSHELL.CPP +*/ +void * Small_Icon(void const * iconptr, int iconnum); +void Set_Window(int window, int x, int y, int w, int h); +void * Load_Alloc_Data(FileClass &file); +long Load_Uncompress(FileClass &file, BuffType &uncomp_buff, BuffType &dest_buff, void *reserved_data); +long Translucent_Table_Size(int count); +void *Build_Translucent_Table(void const *palette, TLucentType const *control, int count, void *buffer); +void *Conquer_Build_Translucent_Table(void const *palette, TLucentType const *control, int count, void *buffer); + +/* +** KEYFBUFF.ASM +*/ +#ifdef __cplusplus +extern "C" { +#endif +long __cdecl Buffer_Frame_To_Page(int x, int y, int w, int h, void *Buffer, GraphicViewPortClass &view, int flags, ...); +#ifdef __cplusplus +} +#endif + +/* +** KEYFRAME.CPP +*/ +int Get_Last_Frame_Length(void); +unsigned long Build_Frame(void const *dataptr, unsigned short framenumber, void *buffptr); +unsigned short Get_Build_Frame_Count(void const *dataptr); +unsigned short Get_Build_Frame_X(void const *dataptr); +unsigned short Get_Build_Frame_Y(void const *dataptr); +unsigned short Get_Build_Frame_Width(void const *dataptr); +unsigned short Get_Build_Frame_Height(void const *dataptr); +bool Get_Build_Frame_Palette(void const *dataptr, void *palette); + +/* +** MAP.CPP +*/ +int Terrain_Cost(CELL cell, FacingType facing); +int Coord_Spillage_Number(COORDINATE coord, int maxsize); + +/* +** MENUS.CPP +*/ +void Setup_Menu(int menu, char const *text[], unsigned long field, int index, int skip); +int Check_Menu(int menu, char const *text[], char *selection, long field, int index); +int Do_Menu(char const **strings, bool blue); +extern int UnknownKey; +int Main_Menu(unsigned long timeout); + +/* +** MPLAYER.CPP +*/ +GameType Select_MPlayer_Game (void); +void Read_MultiPlayer_Settings (void); +void Write_MultiPlayer_Settings (void); +void Read_Scenario_Descriptions (void); +void Free_Scenario_Descriptions(void); +void Computer_Message(void); +int Surrender_Dialog(void); + +/* +** NETDLG.CPP +*/ +bool Init_Network (void); +void Shutdown_Network (void); +bool Remote_Connect (void); +void Destroy_Connection(int id, int error); +bool Process_Global_Packet(GlobalPacketType *packet, IPXAddressClass *address); +unsigned long Compute_Name_CRC(char *name); +void Net_Reconnect_Dialog(int reconn, int fresh, int oldest_index, unsigned long timeval); + +/* +** NULLDLG.CPP +*/ +int Init_Null_Modem( SerialSettingsType *settings ); +void Shutdown_Modem( void ); +void Modem_Signoff( void ); +int Test_Null_Modem( void ); +int Reconnect_Modem( void ); +void Destroy_Null_Connection(int id, int error); +GameType Select_Serial_Dialog( void ); +int Com_Scenario_Dialog(void); +int Com_Show_Scenario_Dialog(void); + +void Smart_Printf( char *format, ... ); +void Hex_Dump_Data( char *buffer, int length ); +void itoh( int i, char *s); + +/* +** PROFILE.CPP +*/ +int WWGetPrivateProfileInt(char const *section, char const *entry, int def, char *profile); +bool WWWritePrivateProfileInt(char const *section, char const *entry, int value, char *profile); +bool WWWritePrivateProfileString(char const *section, char const *entry, char const *string, char *profile); +char * WWGetPrivateProfileString(char const *section, char const *entry, char const *def, char *retbuffer, int retlen, char *profile); +unsigned WWGetPrivateProfileHex (char const *section, char const *entry, char *profile); + +/* +** QUEUE.CPP +*/ +bool Queue_Target(TARGET whom, TARGET target); +bool Queue_Destination(TARGET whom, TARGET target); +bool Queue_Mission(TARGET whom, MissionType mission); +bool Queue_Mission(TARGET whom, MissionType mission, TARGET target, TARGET destination); +bool Queue_Options(void); +bool Queue_Exit(void); +void Queue_AI(void); +void Add_CRC(unsigned long *crc, unsigned long val); + +/* +** RAND.CPP +*/ +int Sim_IRandom(int minval, int maxval); +int Sim_Random(void); + +/* +** REINF.CPP +*/ +bool Do_Reinforcements(TeamTypeClass *team); +bool Create_Special_Reinforcement(HouseClass * house, TechnoTypeClass const * type, TechnoTypeClass const * another, TeamMissionType mission = TMISSION_NONE, int argument =0); +int Create_Air_Reinforcement(HouseClass *house, AircraftType air, int number, MissionType mission, TARGET tarcom, TARGET navcom); + +/* +** SAVELOAD.CPP +*/ +bool Load_Misc_Values(FileClass &file); +bool Save_Misc_Values(FileClass &file); +bool Get_Savefile_Info(int id, char *buf, unsigned *scenp, HousesType *housep); +bool Load_Game(int id); +bool Read_Object (void *ptr, int base_size, int class_size, FileClass & file, void * vtable); +bool Save_Game(int id,char *descr); +bool Write_Object (void *ptr, int class_size, FileClass & file); +TARGET TechnoType_To_Target(TechnoTypeClass const * ptr); +TechnoTypeClass const * Target_To_TechnoType(TARGET target); +void * Get_VTable(void *ptr, int base_size); +void Code_All_Pointers(void); +void Decode_All_Pointers(void); +void Dump(void); +void Set_VTable(void *ptr, int base_size, void *vtable); + +/* +** SCENARIO.CPP +*/ +bool End_Game(void); +bool Read_Scenario(char *root); +bool Start_Scenario(char *root, bool briefing=true); +HousesType Select_House(void); +void Clear_Scenario(void); +void Do_Briefing(char const * text); +void Do_Lose(void); +void Do_Win(void); +void Do_Restart(void); +void Fill_In_Data(void); +bool Restate_Mission(char const * name, int button1, int button2); + +/* +** SCORE.CPP +*/ +void Map_Selection(void); +void Bit_It_In_Scale(int x, int y, int w, int h, GraphicBufferClass *src, GraphicBufferClass *dest, GraphicViewPortClass *seen , int delay=0, int dagger=0); +void Bit_It_In(int x, int y, int w, int h, GraphicBufferClass *src, GraphicBufferClass *dest, int delay=0, int dagger=0); +void Call_Back_Delay(int time); +int Alloc_Object(ScoreAnimClass *obj); +extern GraphicBufferClass *PseudoSeenBuff; + +void Window_Dialog_Box(HANDLE hinst, LPCTSTR lpszTemplate, HWND hwndOwner, DLGPROC dlgprc); + +/* +** SPECIAL.CPP +*/ +void Special_Dialog(void); + +/* +** SUPPORT.ASM +*/ +#ifdef __cplusplus +extern "C" { +#endif +void __cdecl Remove_From_List(void **list, int *index, void * ptr); +void * __cdecl Conquer_Build_Fading_Table(void const *palette, void *dest, int color, int frac); +void __cdecl Fat_Put_Pixel(int x, int y, int color, int size, GraphicViewPortClass &); +void __cdecl strtrim(char *buffer); +long __cdecl Get_EAX( void ); +#ifdef __cplusplus +} +#endif + +/* +** TARCOM.CPP +*/ + +/* +** TARGET.CPP +*/ +COORDINATE As_Movement_Coord(TARGET target); +AircraftClass * As_Aircraft(TARGET target); +AnimClass * As_Animation(TARGET target); +BuildingClass * As_Building(TARGET target); +BulletClass * As_Bullet(TARGET target); +CELL As_Cell(TARGET target); +COORDINATE As_Coord(TARGET target); +InfantryClass * As_Infantry(TARGET target); +TeamClass * As_Team(TARGET target); +TeamTypeClass * As_TeamType(TARGET target); +TechnoClass * As_Techno(TARGET target); +//TerrainClass * As_Terrain(TARGET target); +TriggerClass * As_Trigger(TARGET target); +UnitClass * As_Unit(TARGET target); +inline bool Target_Legal(TARGET target) {return(target != TARGET_NONE);}; +ObjectClass * As_Object(TARGET target); + +/* +** ULOGIC.CPP +*/ +int Terrain_Cost(CELL cell, FacingType facing); + +/* +** Inline miscellaneous functions. +*/ +#define XYP_COORD(x,y) (((x)*ICON_LEPTON_W)/CELL_PIXEL_W + ((((y)*ICON_LEPTON_H)/CELL_PIXEL_H)<<16)) +inline FacingType Dir_Facing(DirType facing) {return (FacingType)(((unsigned char)(facing+0x10)&0xFF)>>5);} +inline DirType Facing_Dir(FacingType facing) {return (DirType)((int)facing << 5);} +inline int Cell_To_Lepton(int cell) {return cell<<8;} +inline int Lepton_To_Cell(int lepton) {return ((unsigned)(lepton + 0x0080))>>8;} +inline CELL XY_Cell(int x, int y) {return ((CELL)(((y)<<6)|(x)));} +inline COORDINATE XY_Coord(int x, int y) {return ((COORDINATE)MAKE_LONG(y, x));} +inline int Coord_X(COORDINATE coord) {return (short)(LOW_WORD(coord));} +inline int Coord_Y(COORDINATE coord) {return (short)(HIGH_WORD(coord));} +inline int Cell_X(CELL cell) {return (int)(((unsigned)cell) & 0x3F);} +inline int Cell_Y(CELL cell) {return (int)(((unsigned)cell) >> 6);} +inline int Dir_Diff(DirType dir1, DirType dir2) {return (int)(*((signed char*)&dir2) - *((signed char*)&dir1));} +inline CELL Coord_XLepton(COORDINATE coord) {return (CELL)(*((unsigned char*)&coord));} +inline CELL Coord_YLepton(COORDINATE coord) {return (CELL)(*(((unsigned char*)&coord)+2));} +//inline COORD CellXY_Coord(unsigned x, unsigned y) {return (COORD)(MAKE_LONG(y<<8, x<<8));} +inline COORDINATE Coord_Add(COORDINATE coord1, COORDINATE coord2) {return (COORDINATE)MAKE_LONG((*((short*)(&coord1)+1) + *((short*)(&coord2)+1)), (*((short*)(&coord1)) + *((short*)(&coord2))));} +inline COORDINATE Coord_Sub(COORDINATE coord1, COORDINATE coord2) {return (COORDINATE)MAKE_LONG((*((short*)(&coord1)+1) - *((short*)(&coord2)+1)), (*((short*)(&coord1)) - *((short*)(&coord2))));} +inline COORDINATE Coord_Snap(COORDINATE coord) {return (COORDINATE)MAKE_LONG((((*(((unsigned short *)&coord)+1))&0xFF00)|0x80), (((*((unsigned short *)&coord))&0xFF00)|0x80));} +inline COORDINATE Coord_Mid(COORDINATE coord1, COORDINATE coord2) {return (COORDINATE)MAKE_LONG((*((unsigned short *)(&coord1)+1) + *((unsigned short *)(&coord2)+1))>>1, (*((unsigned short *)(&coord1)) + *((unsigned short *)(&coord2)))>>1);} +inline COORDINATE Cell_Coord(CELL cell) {return (COORDINATE) MAKE_LONG( (((cell & 0x0FC0)<<2)|0x80), ((((cell & 0x003F)<<1)+1)<<7) );} +inline COORDINATE XYPixel_Coord(int x, int y) {return ((COORDINATE)MAKE_LONG((int)(((long)y*(long)ICON_LEPTON_H)/(long)ICON_PIXEL_H)/*+LEPTON_OFFSET_Y*/, (int)(((long)x*(long)ICON_LEPTON_W)/(long)ICON_PIXEL_W)/*+LEPTON_OFFSET_X*/));} +//inline int Facing_To_16(int facing) {return Facing16[facing];} +inline int Facing_To_32(DirType facing) {return Facing32[facing];} +inline DirType Direction256(COORDINATE coord1, COORDINATE coord2) {return ((DirType)Desired_Facing256(Coord_X(coord1), Coord_Y(coord1), Coord_X(coord2), Coord_Y(coord2)));} +inline DirType Direction(COORDINATE coord1, COORDINATE coord2) {return ((DirType)Desired_Facing256(Coord_X(coord1), Coord_Y(coord1), Coord_X(coord2), Coord_Y(coord2)));} +inline DirType Direction8(COORDINATE coord1, COORDINATE coord2) {return ((DirType)Desired_Facing8(Coord_X(coord1), Coord_Y(coord1), Coord_X(coord2), Coord_Y(coord2)));} +//inline int Direction16(COORDINATE coord1, COORD coord2) {return (Desired_Facing16(Coord_X(coord1), Coord_Y(coord1), Coord_X(coord2), Coord_Y(coord2)));} +inline DirType Direction(CELL cell1, CELL cell2) {return (DirType)(Desired_Facing8(Cell_X(cell1), Cell_Y(cell1), Cell_X(cell2), Cell_Y(cell2)));} +inline COORDINATE Adjacent_Cell(COORDINATE coord, FacingType dir) {return (Coord_Snap(Coord_Add(AdjacentCoord[dir & 0x07], coord)));} +inline COORDINATE Adjacent_Cell(COORDINATE coord, DirType dir) {return Adjacent_Cell(coord, Dir_Facing(dir));} +inline CELL Adjacent_Cell(CELL cell, FacingType dir) {return (CELL)(cell + AdjacentCell[dir]);} +inline CELL Adjacent_Cell(CELL cell, DirType dir) {return (CELL)(cell + AdjacentCell[Dir_Facing(dir)]);} +inline int Lepton_To_Pixel(int lepton) {return ((lepton * ICON_PIXEL_W) + (ICON_LEPTON_W / 2)) / ICON_LEPTON_W;} +inline int Pixel_To_Lepton(int pixel) {return ((pixel * ICON_LEPTON_W) + (ICON_PIXEL_W / 2)) / ICON_PIXEL_W;} +//inline FacingType Facing_To_8(DirType facing) {return (FacingType)(((unsigned char)(facing|0x10))>>5);} +inline COORDINATE XYP_Coord(int x,int y) {return XY_Coord(Pixel_To_Lepton(x), Pixel_To_Lepton(y));}; +inline char const * Text_String(int string) {return(Extract_String(SystemStrings, string));}; + + +template inline T Random_Picky(T a, T b, char *sfile, int line) +{ + sfile = sfile; + line = line; + return (T)IRandom((int)a, (int)b); //, sfile, line); +}; + +#define Random_Pick(low, high) Random_Picky ( (low), (high), __FILE__, __LINE__) + + +template inline T Sim_Random_Pick(T a, T b) +{ + return (T)Sim_IRandom((int)a, (int)b); +}; + + +#ifdef CHEAT_KEYS +#define Check_Ptr(ptr,file,line) \ +{ \ + if (!ptr) { \ + Mono_Clear_Screen(); \ + Mono_Printf("NULL Pointer, Module:%s, line:%d!\n",file,line); \ + Prog_End(); \ + exit(EXIT_SUCCESS); \ + } \ +} +#else +#define Check_Ptr(ptr,file,line) +#endif + +/* +** These routines are for coding & decoding multiplayer ID's +*/ +inline PlayerColorType MPlayerID_To_ColorIndex(unsigned short id) {return (PlayerColorType)(id >> 4);} +inline HousesType MPlayerID_To_HousesType(unsigned short id) {return ((HousesType)(id & 0x000f)); } +inline unsigned short Build_MPlayerID(int c_idx, HousesType htype) { return ((c_idx << 4) | htype); } + + + + +// +// True if we are the currently in focus windows app +// +extern bool GameInFocus; + +extern int ScreenWidth; +extern int ScreenHeight; +extern "C" void ModeX_Blit (GraphicBufferClass *source); +extern void Colour_Debug (int call_number); + + +extern unsigned char *InterpolatedPalettes[100]; +extern BOOL PalettesRead; +extern unsigned PaletteCounter; + +extern "C"{ + extern unsigned char PaletteInterpolationTable[SIZE_OF_PALETTE][SIZE_OF_PALETTE]; + extern unsigned char *InterpolationPalette; +} + +extern void Free_Interpolated_Palettes(void); +extern int Load_Interpolated_Palettes(char const *filename, BOOL add=FALSE); + + +#define CELL_BLIT_ONLY 1 +#define CELL_DRAW_ONLY 2 + +/*********************************************************************************************** + * Distance -- Determines the lepton distance between two coordinates. * + * * + * This routine is used to determine the distance between two coordinates. It uses the * + * Dragon Strike method of distance determination and thus it is very fast. * + * * + * INPUT: coord1 -- First coordinate. * + * * + * coord2 -- Second coordinate. * + * * + * OUTPUT: Returns the lepton distance between the two coordinates. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/27/1994 JLB : Created. * + *=============================================================================================*/ +int Distance_Coord(COORDINATE coord1, COORDINATE coord2); +#pragma aux Distance_Coord parm [eax] [ebx] \ + modify [edx ebx] \ + value [eax] = \ + "mov dx,ax" \ + "sub dx,bx" \ + "jg okx" \ + "neg dx" \ + "okx:" \ + "shr eax,16" \ + "shr ebx,16" \ + "sub ax,bx" \ + "jg oky" \ + "neg ax" \ + "oky:" \ + "cmp ax,dx" \ + "jg ok" \ + "xchg ax,dx" \ + "ok:" \ + "shr dx,1" \ + "add ax,dx" + +inline int Distance(COORDINATE coord1, COORDINATE coord2) +{ +#ifdef NEVER + int diff1, diff2; + + diff1 = Coord_Y(coord1) - Coord_Y(coord2); + if (diff1 < 0) diff1 = -diff1; + diff2 = Coord_X(coord1) - Coord_X(coord2); + if (diff2 < 0) diff2 = -diff2; + if (diff1 > diff2) { + return(diff1 + (diff2>>1)); + } + return(diff2 + (diff1>>1)); +#else + return(Distance_Coord(coord1, coord2)); +#endif +} + + + +/*********************************************************************************************** + * Distance -- Determines the cell distance between two cells. * + * * + * Use this routine to determine the distance between the two cells specified. The distance * + * is returned in cells. * + * * + * INPUT: cell1, cell2 -- The two cells to determine the distance between. * + * * + * OUTPUT: Returns with the distance between the two cells in units of cell size. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/23/1994 JLB : Created. * + *=============================================================================================*/ +inline int Distance(CELL coord1, CELL coord2) +{ + int diff1, diff2; + + diff1 = Cell_Y(coord1) - Cell_Y(coord2); + if (diff1 < 0) diff1 = -diff1; + diff2 = Cell_X(coord1) - Cell_X(coord2); + if (diff2 < 0) diff2 = -diff2; + if (diff1 > diff2) { + return(diff1 + (diff2>>1)); + } + return(diff2 + (diff1>>1)); +} + + +/*********************************************************************************************** + * CellClass::Cell_Number -- Returns the cell ID number for this cell object. * + * * + * Call this routine if you wish to determine what the cell number ID is for the currrent * + * cell object. This ID number is the index number into the cell array. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the cell number for this cell object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/19/1995 JLB : Created. * + *=============================================================================================*/ +inline CELL CellClass::Cell_Number(void) const +{ + return(Map.ID(this)); +} +#if(0) +#ifndef NOMEMCHECK +#define NO_INTERCEPT +#include "memcheck.h" +#endif +#endif + +void WWDOS_Shutdown(void); + +#endif diff --git a/FUNCTION.I b/FUNCTION.I new file mode 100644 index 0000000..872a43c --- /dev/null +++ b/FUNCTION.I @@ -0,0 +1,19 @@ +;*************************************************************************** +;** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** +;*************************************************************************** +;* * +;* Project Name : Command & Conquer * +;* * +;* File Name : FUNCTION.I * +;* * +;* Programmer : Joe L. Bostic * +;* * +;* Start Date : November 10, 1993 * +;* * +;* Last Update : November 10, 1993 [JLB] * +;* * +;*-------------------------------------------------------------------------* +;* Functions: * +;* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * + + diff --git a/FUSE.CPP b/FUSE.CPP new file mode 100644 index 0000000..5762148 --- /dev/null +++ b/FUSE.CPP @@ -0,0 +1,196 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\fuse.cpv 2.18 16 Oct 1995 16:50:46 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FUSE.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 24, 1994 * + * * + * Last Update : October 17, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * FuseClass::Arm_Fuse -- Sets up fuse for detonation check. * + * FuseClass::Fuse_Checkup -- Determines if the fuse triggers. * + * FuseClass::Fuse_Write -- Writes the fuse data to the save game file. * + * FuseClass::Fuse_Read -- Reads the fuse class data from the save game file. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * FuseClass::FuseClass -- Constructor. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/27/1995 BRR : Created. Gosh, what a lotta work. * + *=============================================================================================*/ +FuseClass::FuseClass(void) +{ + Timer = 0; + Arming = 0; + HeadTo = 0; + Proximity = 0; +} + + +/*********************************************************************************************** + * FuseClass::Arm_Fuse -- Sets up fuse for detonation check. * + * * + * This starts a fuse. Fuses are proximity detonation variety but * + * can be modified to have a minimum time to elapse before detonation * + * and a maximum time to exist before detonation. Typically, the * + * timing values are used for missiles that have a minimum arming * + * distance and a limited amount of fuel. * + * * + * INPUT: location -- The coordinate where the projectile start. This * + * is needed for proper proximity tracking. * + * * + * target -- The actual impact point. Fuses are based on real * + * word coordinates. * + * * + * time -- The maximum time that the fuse may work before * + * explosion is forced. * + * * + * arming -- The minimum time that must elapse before the * + * fuse may explode. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/24/1994 JLB : Created. * + *=============================================================================================*/ +void FuseClass::Arm_Fuse(COORDINATE location, COORDINATE target, int timeto, int arming) +{ + timeto = MAX(timeto, arming); + Timer = MIN(timeto, 0xFF); + Arming = MIN(arming, 0xFF); + HeadTo = target; + Proximity = Distance(location, target); +} + + +/*********************************************************************************************** + * FuseClass::Fuse_Checkup -- Determines if the fuse triggers. * + * * + * This will process the fuse and update the internal clocks as well * + * as check to see if the fuse should trigger (explode) or not. * + * * + * INPUT: newlocation -- The new location of the fuse. This is needed * + * to determine proximity explosions. * + * * + * OUTPUT: bool; Was the fuse triggered to explode now? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/24/1994 JLB : Created. * + *=============================================================================================*/ +bool FuseClass::Fuse_Checkup(COORDINATE newlocation) +{ + int proximity; + + /* + ** Always decrement the fuse timer. + */ + if (Timer) Timer--; + + /* + ** If the arming countdown has not expired, then do nothing. + */ + if (Arming) { + Arming--; + } else { + + /* + ** If the timer has run out, then the warhead explodes. + */ + if (!Timer) return(true); + + proximity = Distance(newlocation, HeadTo); + if (proximity < 0x0010) return(true); + if (proximity < ICON_LEPTON_W && proximity > Proximity) { + return(true); + } + Proximity = proximity; + } + return(false); +} + + +/*********************************************************************************************** + * FuseClass::Fuse_Write -- Writes the fuse data to the save game file. * + * * + * Use this routine to output the fuse class data to the save game file specified. * + * * + * INPUT: file -- The file to output the data to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +void FuseClass::Fuse_Write(FileClass & file) +{ + file.Write(&Timer, sizeof(Timer)); + file.Write(&Arming, sizeof(Arming)); + file.Write(&HeadTo, sizeof(HeadTo)); + file.Write(&Proximity, sizeof(Proximity)); +} + + +/*********************************************************************************************** + * FuseClass::Fuse_Read -- Reads the fuse class data from the save game file. * + * * + * Use this routine to input the fuse class data from the save game file specified. * + * * + * INPUT: file -- The file to input the data from. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +void FuseClass::Fuse_Read(FileClass & file) +{ + file.Read(&Timer, sizeof(Timer)); + file.Read(&Arming, sizeof(Arming)); + file.Read(&HeadTo, sizeof(HeadTo)); + file.Read(&Proximity, sizeof(Proximity)); +} + diff --git a/FUSE.H b/FUSE.H new file mode 100644 index 0000000..cd48083 --- /dev/null +++ b/FUSE.H @@ -0,0 +1,95 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\fuse.h_v 2.17 16 Oct 1995 16:46:18 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FUSE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 24, 1994 * + * * + * Last Update : April 24, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef FUSE_H +#define FUSE_H + +/**************************************************************************** +** The fuse is used by projectiles to determine whether detonation should +** occur. This is usually determined by tracking the distance to the +** designated target reaches zero or when the timer expires. +*/ +class FuseClass { + public: + FuseClass(void); + void Arm_Fuse(COORDINATE location, COORDINATE target, int time=0xFF, int arming=0); + bool Fuse_Checkup(COORDINATE newlocation); + void Fuse_Write(FileClass & file); + void Fuse_Read(FileClass & file); + COORDINATE Fuse_Target(void); + + /* + ** File I/O. + */ + void Code_Pointers(void); + void Decode_Pointers(void); + + /* + ** Fuses can detonate if enough time has elapsed. This value counts + ** down. When it reaches zero, detonation occurs. + */ + unsigned char Timer; + + private: + + /* + ** Some fuses need a certain amount of time before detonation can + ** occur. This counts down and when it reaches zero, normal fuse + ** detonation checking can occur. + */ + unsigned char Arming; + + /* + ** This is the designated impact point of the projectile. The fuse + ** will trip when the closest point to this location has been reached. + */ + COORDINATE HeadTo; + + /* + ** This is the running proximity value to the impact point. This value + ** will progressively get smaller. Detonation occurs when it reaches + ** zero or when it starts to grow larger. + */ + short Proximity; +}; + +inline COORDINATE FuseClass::Fuse_Target(void) +{ + return(HeadTo); +} + +#endif diff --git a/GADGET.CPP b/GADGET.CPP new file mode 100644 index 0000000..5dd5cb6 --- /dev/null +++ b/GADGET.CPP @@ -0,0 +1,804 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\gadget.cpv 2.18 16 Oct 1995 16:49:40 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : GADGET.CPP * + * * + * Programmer : Maria del Mar McCready Legg * + * Joe L. Bostic * + * * + * Start Date : 01/03/95 * + * * + * Last Update : July 8, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * GadgetClass::Action -- Base action for gadget. * + * GadgetClass::Clear_Focus -- Clears the focus if this gadget has it. * + * GadgetClass::Delete_List -- Deletes all gadget objects in list. * + * GadgetClass::Disable -- Disables the gaget from input processing. * + * GadgetClass::Draw_All -- Forces all gadgets in list to be redrawn. * + * GadgetClass::Draw_Me -- Gadget redraw action (flag control). * + * GadgetClass::Enable -- Enables the gadget. * + * GadgetClass::Extract_Gadget -- Sweeps through the gadget chain to find gadget specified. * + * GadgetClass::Flag_To_Redraw -- Flags this gadget to be redrawn. * + * GadgetClass::GadgetClass -- Constructor for gadget object. * + * GadgetClass::GadgetClass -- Default constructor for a gadget class object. * + * GadgetClass::Get_Next -- Returns a pointer to the next gadget in the chain. * + * GadgetClass::Get_Prev -- Fetches a pointer to the previous gadget. * + * GadgetClass::Has_Focus -- Checks if this object currently has the keyboard focus. * + * GadgetClass::Remove -- Removes the specified gagdet from the list. * + * GadgetClass::Set_Focus -- Sets focus to this gadget. * + * GadgetClass::Sticky_Process -- Handles the sticky flag processing. * + * GadgetClass::~GadgetClass -- Destructor for gadget object. * + * GadgetClass::Is_List_To_Redraw -- tells if any gadget in the list needs redrawing * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include +#include + +/* +** This records the current gadget the the gadget system is "stuck on". Such a +** gadget will be processed to the exclusion of all others until the mouse button +** is no longer pressed. +*/ +GadgetClass * GadgetClass::StuckOn = 0; + +/* +** This is a copy of a pointer to the last list used by the gadget input system. +** If a change of list is detected, then all gadgets are forced to be redrawn. +*/ +GadgetClass * GadgetClass::LastList = 0; + + +/* +** This points to the gadget that is intercepting all keyboard events. +*/ +GadgetClass * GadgetClass::Focused = 0; + + +/*********************************************************************************************** + * GadgetClass::GadgetClass -- Constructor for gadget object. * + * * + * This is the normal constructor for gadget objects. A gadget object is only concerned * + * with the region on the screen to considered "its own" as well as the flags that tell * + * what mouse action should be recognized when the mouse is over this screen area. * + * * + * INPUT: x,y -- Coordinates (in pixels) of the upper left hand corner of the region that * + * will be "owned" by this gadget. * + * * + * w,h -- Width and height (in pixels) of this gadget's region. * + * * + * flags -- The flags (mouse conditions) that will cause this gadget's action * + * function to be called. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/03/1995 MML : Created. * + *=============================================================================================*/ +GadgetClass::GadgetClass(int x, int y, int w, int h, unsigned flags, int sticky) +{ + X = x; + Y = y; + Width = w; + Height = h; + Flags = flags; + IsToRepaint = false; + IsSticky = sticky; + IsDisabled = false; + + if (IsSticky) { + Flags |= LEFTPRESS|LEFTRELEASE; + } +} + + +/*********************************************************************************************** + * GadgetClass::~GadgetClass -- Destructor for gadget object. * + * * + * This is the destructor for the gadget object. It will clear the focus from this gadget * + * if this gadget currently has the focus. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +GadgetClass::~GadgetClass(void) +{ + if (Has_Focus()) { + Clear_Focus(); + } +} + + +/*************************************************************************** + * GADGETCLASS::CLICKEDON -- If a mouse click is detected within gadget's * + * area and the appropriate flag is set, then call Action(). * + * * + * INPUT: int key, int mousex, int mousey * + * * + * OUTPUT: true or false * + * * + * WARNINGS: none. * + * * + * HISTORY: 01/03/1995 MML : Created. * + *=========================================================================*/ +int GadgetClass::Clicked_On(KeyNumType & key, unsigned flags, int mousex, int mousey) +{ + /* + ** Set flags to match only those events that occur AND are being looked for. If + ** the result is NULL, then we know that this button should be ignored. + */ + flags &= Flags; + + /* + ** If keyboard input should be processed by this "gadget" and keyboard input is + ** detected, then always call the action function. It is up to the action function + ** in this case to either ignore the keyboard input or not. + ** + ** For mouse actions, check to see if the mouse is in the region of the button + ** before calling the associated action function. This is the typical action for + ** buttons. + */ + if (this == StuckOn || + (flags & KEYBOARD) || + (flags && (unsigned)(mousex - X) < Width && (unsigned)(mousey - Y) < Height)) { + + return(Action(flags, key)); + } + return(false); +} + + +/*********************************************************************************************** + * GadgetClass::Enable -- Enables the gadget. * + * * + * This function enables the gadget. An enabled gadget will be processed for input * + * purposes. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +void GadgetClass::Enable(void) +{ + IsDisabled = false; + IsToRepaint = true; + Clear_Focus(); +} + + +/*********************************************************************************************** + * GadgetClass::Disable -- Disables the gaget from input processing. * + * * + * This routine will disable the gadget. A disabled gadget might be rendered, but is * + * ignored for input processing. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +void GadgetClass::Disable(void) +{ + IsDisabled = true; + IsToRepaint = true; + Clear_Focus(); +} + + +/*********************************************************************************************** + * GadgetClass::Remove -- Removes the specified gagdet from the list. * + * * + * Use this routine if an individual gadget needs to be removed from the list of gadgets. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the specified gadget found and removed? A false indicates that the * + * gadget wasn't in the list. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +GadgetClass * GadgetClass::Remove(void) +{ + Clear_Focus(); + return(GadgetClass *)LinkClass::Remove(); +} + + +/*********************************************************************************************** + * GadgetClass::Get_Next -- Returns a pointer to the next gadget in the chain. * + * * + * This returns with the next gadget's pointer. It is identical to the base Get_Next() * + * function, but returns a pointer to a GadgetClass object. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the next gadget in the list. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +GadgetClass * GadgetClass::Get_Next(void) const +{ + return(GadgetClass*)LinkClass::Get_Next(); +} + + +/*********************************************************************************************** + * GadgetClass::Get_Prev -- Fetches a pointer to the previous gadget. * + * * + * This routine will return the previous gadget in the list. It is identical to the base * + * function Get_Prev, but returns a pointer to a GadgetClass object. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the previous gadget in the list. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +GadgetClass * GadgetClass::Get_Prev(void) const +{ + return(GadgetClass*)LinkClass::Get_Prev(); +} + + +/*********************************************************************************************** + * GadgetClass::Delete_List -- Deletes all gadget objects in list. * + * * + * This function will delete all gadgets in the list. It is the counterpart to the * + * Create_One_Of functions. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Any references to these gadget become invalidated by this routine. * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +void GadgetClass::Delete_List(void) +{ + GadgetClass * g = this; + + /* + ** Move to head of the list. + */ + while (g->Get_Prev()) { + g = g->Get_Prev(); + } + + /* + ** First delete all the gadgets following the first one. The reason the first one + ** is kept around is that sometimes deleting one gadget will result in related gadgets + ** in the same list also being deleted. The first gadget will always contain the + ** correct gadget pointer. + */ + while (g) { + g->Clear_Focus(); + + GadgetClass * temp = g; + g = g->Get_Next(); + delete temp; + } +} + + +/*********************************************************************************************** + * GadgetClass::Action -- Base action for gadget. * + * * + * This handles the base level action that a gadget performs when a qualifying input event * + * is detected. This sets the redraw flag and returns true (to stop further processing). * + * If no qualifying input event was detected, but this routine was called anyway, then * + * don't perform any action. The call to this routine, in that case, must have been forced * + * for some other reason. * + * * + * INPUT: flag -- The input event bits that qualify for this gadget. A NULL indicates that * + * no qualifying event occured. * + * * + * OUTPUT: bool; Should further gadget list processing be aborted? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +int GadgetClass::Action(unsigned flags, KeyNumType &) +{ + /* + ** If any of the event flags are active, then this indicates that something probably + ** has changed the gadget. Flag the gadget to be redrawn. Also, make sure that + ** any sticky flags are cleared up. + */ + if (flags) { + IsToRepaint = true; + Sticky_Process(flags); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * GadgetClass::Draw_Me -- Gadget redraw action (flag control). * + * * + * At this level, there is no actual rendering taking place with the call to Draw_Me, but * + * the IsToRepaint flag must be cleared. Derived objects will call this routine and if it * + * returns true, they will perform their custom rendering. * + * * + * INPUT: forced -- Is this redraw forced by outside circumstances? * + * * + * OUTPUT: bool; Should the gadget imagery be redrawn? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/14/1995 JLB : Created. * + *=============================================================================================*/ +int GadgetClass::Draw_Me(int forced) +{ + if (forced || IsToRepaint) { + IsToRepaint = false; + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * GadgetClass::Draw_All -- Forces all gadgets in list to be redrawn. * + * * + * Use this function to cause all gadget in the list to be redrawn regardless of the state * + * of the IsToRepaint flag. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/03/1995 MML : Created. * + *=============================================================================================*/ +void GadgetClass::Draw_All(bool forced) +{ + GadgetClass *gadget = this; + + while (gadget != NULL) { + gadget->Draw_Me(forced); + gadget = gadget->Get_Next(); + } +} + + +/*************************************************************************** + * GADGETCLASS::PROCESSLIST -- Check list for a mouse click within a gadget* + * * + * INPUT: none. * + * * + * OUTPUT: key pressed. * + * * + * WARNINGS: none. * + * * + * HISTORY: 01/03/1995 MML : Created. * + *=========================================================================*/ +KeyNumType GadgetClass::Input(void) +{ + int mousex, mousey; + KeyNumType key; + unsigned flags; + int forced = false; + + /* + ** Record this list so that a forced redraw only occurs the FIRST time the + ** gadget list is passed to this routine. + */ + if (LastList != this) { + LastList = this; + forced = true; + StuckOn = NULL; + Focused = NULL; + } + + /* + ** Fetch any pending keyboard input. + */ + key = Keyboard::Check(); + if (key) { + key = Keyboard::Get(); + } + +#ifdef SCENARIO_EDITOR + + if ( key == KN_K ){ + /* + ** time to create a screen shot using the PCX code (if it works) + */ + GraphicBufferClass temp_page( SeenBuff.Get_Width(), + SeenBuff.Get_Height(), + NULL, + SeenBuff.Get_Width() * SeenBuff.Get_Height()); + char filename[30]; + + SeenBuff.Blit(temp_page); + for (int lp = 0; lp < 99; lp ++) { + if (lp < 10) { + sprintf(filename, "scrsht0%d.pcx", lp); + } else { + sprintf(filename, "scrsht%d.pcx", lp); + } + if (access(filename, F_OK) == -1) + break; + } + + Write_PCX_File(filename, temp_page, (unsigned char *)CurrentPalette); + //Map.Place_Random_Crate(); + } + +#endif //SCENARIO_EDITOR + + /* + ** For mouse button clicks, the mouse position is actually held in the MouseQ... + ** globals rather than their normal Mouse... globals. This is because we need to + ** know the position of the mouse at the exact instant when the click occured + ** rather the the mouse position at the time we get around to this function. + */ + if (((key&0x10FF) == KN_LMOUSE) || ((key&0x10FF) == KN_RMOUSE)) { + mousex = _Kbd->MouseQX; + mousey = _Kbd->MouseQY; + } else { + mousex = Get_Mouse_X(); + mousey = Get_Mouse_Y(); + } + + /* + ** Set the mouse button state flags. These will be passed to the individual + ** buttons so that they can determine what action to perform (if any). + */ + flags = 0; + if (key) { + if (key == KN_LMOUSE) { + flags |= LEFTPRESS; + } + if (key == KN_RMOUSE) { + flags |= RIGHTPRESS; + } + if (key == (KN_LMOUSE | KN_RLSE_BIT)) { + flags |= LEFTRELEASE; + } + if (key == (KN_RMOUSE | KN_RLSE_BIT)) { + flags |= RIGHTRELEASE; + } + } + + /* + ** If the mouse wasn't responsible for this key code, then it must be from + ** the keyboard. Flag this fact. + */ + if (key && !flags) { + flags |= KEYBOARD; + } + + /* + ** Mouse button up or down action is ignored if there is a keyboard event. This + ** allows keyboard events to fall through normally even if the mouse is over a + ** gadget that is flagged for LEFTUP or RIGHTUP. + */ + if (!key) { + + /* + ** Check for the mouse being held down. We can't use the normal input system + ** for this, so we must examine the actual current state of the mouse + ** buttons. As a side note, if we determine that the mouse button isn't being + ** held down, then we automatically know that it must be up -- set the flag + ** accordingly. + */ + if (Keyboard::Down(KN_LMOUSE)) { + flags |= LEFTHELD; + } else { + flags |= LEFTUP; + } + if (Keyboard::Down(KN_RMOUSE)) { + flags |= RIGHTHELD; + } else { + flags |= RIGHTUP; + } + } + + /* + ** If "sticky" processing is active, then only process the stuck gadget. + */ + if (StuckOn) { + StuckOn->Draw_Me(false); + StuckOn->Clicked_On(key, flags, mousex, mousey); + if (StuckOn) { + StuckOn->Draw_Me(false); + } + } else { + + /* + ** If there is a gadget that has the keyboard focus, then route all keyboard + ** events to it. + */ + if (Focused && (flags & KEYBOARD)) { + Focused->Draw_Me(false); + Focused->Clicked_On(key, flags, mousex, mousey); + if (Focused) { + Focused->Draw_Me(false); + } + } else { + + /* + ** Sweep through all the buttons in the chain and pass the current button state + ** and keyboard input data to them. These routines will detect whether they should + ** perform some action and return a flag to this effect. They also have the option + ** of changing the key value so that an appropriate return value is use for this + ** processing routine. + */ + GadgetClass *next_button = this; + while (next_button != NULL) { + + /* + ** Maybe redraw the button if it needs to or is being forced to redraw. + */ + next_button->Draw_Me(forced); + + if (!next_button->IsDisabled) { + + /* + ** Process this button. If the button was recognized and action was + ** performed, then bail from further processing (speed reasons?). + */ + if (next_button->Clicked_On(key, flags, mousex, mousey)) { + + /* + ** Some buttons will require repainting when they perform some action. + ** Do so at this time. + */ + next_button->Draw_Me(false); + break; + } + } + + next_button = next_button->Get_Next(); + } + } + } + return(key); +} + + +/*********************************************************************************************** + * GadgetClass::Extract_Gadget -- Sweeps through the gadget chain to find gadget specified. * + * * + * This examines the gadget list looking for on that has the same ID as specified. If that * + * gadget was found, then a pointer to it is returned. Since only ControlClass gadgets * + * or ones derived from it can have an ID value, we know that the returned pointer is at * + * least of the ControlClass type. * + * * + * INPUT: id -- The ID number to scan for. Zero is not a legal ID number and if passed in, * + * a NULL will always be returned. * + * * + * OUTPUT: Returns with a pointer to the ControlClass gadget that has the matching ID number. * + * If no matching gadget was found, then NULL is returned. * + * * + * WARNINGS: If there happens to be more than one gadget with a matching ID, this routine * + * will return a pointer to the first one only. * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +ControlClass * GadgetClass::Extract_Gadget(unsigned id) +{ + GadgetClass * g = this; + + if (id) { + while (g) { + if (g->Get_ID() == id) { + return((ControlClass *)g); + } + g = g->Get_Next(); + } + } + return(0); +} + + +/*********************************************************************************************** + * GadgetClass::Flag_To_Redraw -- Flags this gadget to be redrawn. * + * * + * Use this routine to flag the gadget to be redrawn. A gadget so flagged will have its * + * Draw_Me function called at the next available opportunity. Usually, this is the next * + * time the Input() function is called. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +void GadgetClass::Flag_To_Redraw(void) +{ + IsToRepaint = true; +} + + +/*********************************************************************************************** + * GadgetClass::Sticky_Process -- Handles the sticky flag processing. * + * * + * This function examines the event flags and handles any "sticky" processing required. * + * Sticky processing is when the button is flagged with the "IsSticky" bit and it will * + * be processed to the exclusion of all other gadgets while the mouse button is held * + * down. * + * * + * INPUT: flags -- The event flags that triggered the call to this routine. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +void GadgetClass::Sticky_Process(unsigned flags) +{ + if (IsSticky && (flags & LEFTPRESS)) { + StuckOn = this; + } + if (StuckOn == this && (flags & LEFTRELEASE)) { + StuckOn = 0; + } +} + + +/*********************************************************************************************** + * GadgetClass::Set_Focus -- Sets focus to this gadget. * + * * + * This will set the focus to this gadget regardless of any current focus setting. If there * + * is another gadget that has focus, it will have its focus cleared before this gadget will * + * get the focus. A focused gadget is one that has all keyboard input routed to it. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/25/1995 JLB : Created. * + *=============================================================================================*/ +void GadgetClass::Set_Focus(void) +{ + if (Focused) { + Focused->Flag_To_Redraw(); + Focused->Clear_Focus(); + } + Flags |= KEYBOARD; + Focused = this; +} + + +/*********************************************************************************************** + * GadgetClass::Clear_Focus -- Clears the focus if this gadget has it. * + * * + * Use this function to clear the focus for the gadget. If the gadget doesn't currently * + * have focus, then this routine will do nothing. For added functionality, overload this * + * virtual function so that gadget specific actions may be take when focus is lost. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/25/1995 JLB : Created. * + *=============================================================================================*/ +void GadgetClass::Clear_Focus(void) +{ + if (Focused == this) { + Flags &= ~KEYBOARD; + Focused = 0; + } +} + + +/*********************************************************************************************** + * GadgetClass::Has_Focus -- Checks if this object currently has the keyboard focus. * + * * + * If this object has the keyboard focus, then this routine will return true. When the * + * gadget has keyboard focus, all keyboard events get routed to the gadget. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Does this gadget have the keyboard focus? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/21/1995 JLB : Created. * + *=============================================================================================*/ +bool GadgetClass::Has_Focus(void) +{ + return(this == Focused); +} + +/*********************************************************************************************** + * GadgetClass::Is_List_To_Redraw -- tells if any gadget in the list needs redrawing * + * * + * This function is mostly for supporting HidPage drawing. If it returns true, it means * + * the application needs to re-blit the HidPage forward, after calling the list's Input(). * + * * + * INPUT: none * + * * + * OUTPUT: true = an item needs redrawing, false = no items need redrawing * + * * + * WARNINGS: It is assumed 'this' is the head of the list. * + * * + * HISTORY: * + * 01/03/1995 MML : Created. * + *=============================================================================================*/ +int GadgetClass::Is_List_To_Redraw(void) +{ + GadgetClass *gadget = this; + + while (gadget != NULL) { + if (gadget->IsToRepaint) + return (true); + gadget = gadget->Get_Next(); + } + return (false); +} + + diff --git a/GADGET.H b/GADGET.H new file mode 100644 index 0000000..f6f650b --- /dev/null +++ b/GADGET.H @@ -0,0 +1,239 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\gadget.h_v 2.17 16 Oct 1995 16:46:34 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : GADGET.H * + * * + * Programmer : Maria del Mar McCready Legg * + * * + * Start Date : January 3, 1995 * + * * + * Last Update : January 3, 1995 [MML] * + * * + * * + * LinkClass [This is the linked list manager class. It keeps a record * + * ³ of the next and previous gadget in the list. It is possible * + * ³ delete a gadget out of the middle of the list with this * + * ³ class.] * + * ³ * + * GadgetClass [The is the basic gadget class. It handles processing of * + * ³ input events and dispatching the appropriate functions. * + * ³ All gadgets must be derived from this class.] * + * ÃÄÄÄÄ¿ * + * ³ ³ * + * ³ ListClass [Ths list class functions like a list box does in Windows. It * + * ³ keeps track of a list of text strings. This list can be * + * ³ scrolled and an item selected. If the list becomes larger than * + * ³ can be completely displayed, it will automatically create a * + * ³ slider (at the right edge) to manage the scrolling.] * + * ³ * + * ControlClass [This class adds the concept of giving an ID number to the * + * ³ gadget. This ID can then be returned from the Input() * + * ³ function as if it were a pseudo-keystroke. Additionally, * + * ³ the ability to inform another button that this button has * + * ³ been actioned is allowed. This ability allows one button * + * ³ to watch what happens to another button. Example: a list * + * ³ box gadget can tell when an attached slider has been * + * ³ touched.] * + * ÚÄÄÄÄÄÄÄÅÄÄÄÄ¿ * + * ³ ³ ³ * + * ³ ³ GaugeClass [This class looks similar to Windows slider, but has * + * ³ ³ ³ a different controlling logic. There is no thumb and * + * ³ ³ ³ it serves as a simple variable control setting. This * + * ³ ³ ³ is analagous to a volume slider.] * + * ³ ³ ³ * + * ³ ³ SliderClass [The slider class is similar to the typical Windows slider. It * + * ³ ³ has a current setting, a thumb, and a controlable scale. This * + * ³ ³ is the object created to handle a scrolling list box.] * + * ³ ³ * + * ³ EditClass * + * ³ * + * ³ * + * ToggleClass [The toggle class is used for buttons that have an image and behave just * + * ³ like the buttons in Windows do. That is, they have a separate visual for * + * ³ when they are pressed and raised. They are officially triggered (return * + * ³ their ID number) when the mouse button is released while over the button. * + * ³ This class doesn't perform any rendering itself. It merely provides the * + * ³ logic so that the derived classes will function correctly.] * + * ÚÄÁÄÄÄÄ¿ * + * ³ ³ * + * ³ TextButtonClass [The text button functions like a normal Windows style button, but * + * ³ the imagery is based on text that is displayed on the button. A * + * ³ typical example would be the "OK" or "Cancel" buttons.] * + * ³ * + * ShapeButtonClass [The shape buttons is similar to the TextButton but instead of text * + * being used to give the button its imagery, an actual shape is used * + * instead. This allows graphic buttons. These are similar to the up/down * + * arrows seen in a Windows slider.] * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef GADGET_H +#define GADGET_H + +#include "link.h" + +class ControlClass; + +class GadgetClass : public LinkClass +{ + public: + typedef enum FlagEnum { + LEFTPRESS = 0x0001, // Left mouse button press. + LEFTHELD = 0x0002, // Left mouse button is being held down. + LEFTRELEASE = 0x0004, // Left mouse button released. + LEFTUP = 0x0008, // Left mouse button is being held up. + RIGHTPRESS = 0x0010, // Right mouse button press. + RIGHTHELD = 0x0020, // Right mouse button is being held down. + RIGHTRELEASE = 0x0040, // Right mouse button released. + RIGHTUP = 0x0080, // Right mouse button is being held up. + KEYBOARD = 0x0100, // Keyboard input processing (maybe). + } FlagEnum; + + GadgetClass(int x, int y, int w, int h, unsigned flags, int sticky=false); + GadgetClass(void) {}; + virtual ~GadgetClass(void); +// static GadgetClass * Create_One_Of(int x, int y, int w, int h, unsigned flags, int sticky=false); + + /* + ** Gadget list management functions. + */ + virtual KeyNumType Input(void); + virtual void Draw_All(bool forced=true); + virtual void Delete_List(void); + virtual ControlClass * Extract_Gadget(unsigned id); + virtual void Flag_List_To_Redraw(void) {LastList = 0;}; + virtual GadgetClass * Remove(void); + virtual GadgetClass * Get_Next(void) const; + virtual GadgetClass * Get_Prev(void) const; + + /* + ** Manages individual gadget states and actions. + */ + virtual void Disable(void); + virtual void Enable(void); + virtual unsigned Get_ID(void) const {return 0;}; + virtual void Flag_To_Redraw(void); + virtual void Peer_To_Peer(unsigned , KeyNumType & , ControlClass & ) {}; + virtual void Set_Focus(void); + virtual void Clear_Focus(void); + virtual bool Has_Focus(void); + virtual int Is_List_To_Redraw(void); + + /* + ** General render function. + */ + virtual int Draw_Me(int forced=false); + + /* + ** This is the coordinates and dimensions of the gadget region. These are in + ** absolute screen pixel coordinates. + */ + int X; + int Y; + int Width; + int Height; + + protected: + + /* + ** Processes the event flags so that if this gadget needs to "stick" or + ** "unstick", it will be properly flagged. Call this function if you are + ** going to clear the button press flags before calling the base class + ** Action() function. Otherwise, calling this function manually, is + ** unnecessary since the base class Action() function already does so. + */ + virtual void Sticky_Process(unsigned flags); + + /* + ** This is the action functio that will be called whenever the flags and mouse + ** input indicates. This is the main method by which this button performs a useful + ** function. + */ + virtual int Action(unsigned flags, KeyNumType & key); + + /* + ** If there is a sticky button being processed, then this will point to it. A sticky + ** button is one that will ONLY be processed while the mouse button is being + ** held down. + */ + static GadgetClass * StuckOn; + + /* + ** This is a record of the last list passed to the Input() function. If a list + ** different than the last recorded one is detected, then the draw function is + ** called for every gadget in the list. This causes all buttons to be redrawn the + ** fire time Input() is called without forced a manual call to Draw_All(). + */ + static GadgetClass * LastList; + + /* + ** This points to the gadget that has the keyboard focus. All keyboard only + ** events are fed to this gadget to the exclusion of all others. + */ + static GadgetClass * Focused; + + /* + ** This button should call the Draw_Me function because some graphic element needs + ** to be redrawn. This flag is set by default if the Action function is called. + */ + unsigned IsToRepaint:1; + + public: // HACK HACK HACK.. this is here becuase the sidebar buttons are static. + /* + ** A sticky button is one that is processed to the exclusion of all other buttons + ** IF the mouse was pressed down while over this button and the mouse continues + ** to remain pressed. This is the standard behavior for all normal Windows style + ** buttons. + */ + unsigned IsSticky:1; + + protected: + + /* + ** If the button is disabled, then it won't be processed by the input function. It will + ** have its Draw_Me function called as necessary. In order to not display the button + ** at all, the appropriate draw function should perform no action -- just return. Or, + ** just remove the button from the list. + */ + unsigned IsDisabled:1; + + /* + ** These are the action flags that are used to determine when the action function + ** should be called. Example: If this gadget only wants the action button called when + ** the left mouse button is pressed over the its region, then the flag will be set + ** to LEFTPRESS. + */ + unsigned Flags; + + private: + virtual int Clicked_On(KeyNumType & key, unsigned flags, int x, int y); +}; + +inline GadgetClass::FlagEnum operator |(GadgetClass::FlagEnum, GadgetClass::FlagEnum); +inline GadgetClass::FlagEnum operator &(GadgetClass::FlagEnum, GadgetClass::FlagEnum); +inline GadgetClass::FlagEnum operator ~(GadgetClass::FlagEnum); + + +#endif diff --git a/GAMEDLG.CPP b/GAMEDLG.CPP new file mode 100644 index 0000000..4924b3c --- /dev/null +++ b/GAMEDLG.CPP @@ -0,0 +1,422 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\gamedlg.cpv 2.17 16 Oct 1995 16:52:02 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : GAMEDLG.CPP * + * * + * Programmer : Maria del Mar McCready Legg, Joe L. Bostic * + * * + * Start Date : Jan 8, 1995 * + * * + * Last Update : Jan 18, 1995 [MML] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * OptionsClass::Process -- Handles all the options graphic interface. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "gamedlg.h" +#include "sounddlg.h" +#include "visudlg.h" + + +/*********************************************************************************************** + * OptionsClass::Process -- Handles all the options graphic interface. * + * * + * This routine is the main control for the visual representation of the options * + * screen. It handles the visual overlay and the player input. * + * * + * INPUT: none * + * OUTPUT: none * + * WARNINGS: none * + * HISTORY: * + * 12/31/1994 MML : Created. * + *=============================================================================================*/ +void GameControlsClass::Process(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + /* + ** Dialog & button dimensions + */ + int d_dialog_w = 232 * factor; // dialog width + int d_dialog_h = 141 * factor; // dialog height + int d_dialog_x = ((SeenBuff.Get_Width()- d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((SeenBuff.Get_Height() - d_dialog_h) / 2); // centered y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + int d_top_margin= 30 * factor; + + int d_txt6_h = 7 * factor; // ht of 6-pt text + int d_margin1 = 5 * factor; // large margin + int d_margin2 = 2 * factor; // small margin + + int d_speed_w = d_dialog_w - (20 * factor); + int d_speed_h = 6 * factor; + int d_speed_x = d_dialog_x + (10 * factor); + int d_speed_y = d_dialog_y + d_top_margin + d_margin1 + d_txt6_h; + + int d_scroll_w = d_dialog_w - (20 * factor); + int d_scroll_h = 6 * factor; + int d_scroll_x = d_dialog_x + (10 * factor); + int d_scroll_y = d_speed_y + d_speed_h + d_txt6_h + (d_margin1 * 2) + d_txt6_h; + + int d_visual_w = d_dialog_w - (40 * factor); + int d_visual_h = 9 * factor; + int d_visual_x = d_dialog_x + (20 * factor); + int d_visual_y = d_scroll_y + d_scroll_h + d_txt6_h + (d_margin1 * 2); + + int d_sound_w = d_dialog_w - (40 * factor); + int d_sound_h = 9 * factor; + int d_sound_x = d_dialog_x + (20 * factor); + int d_sound_y = d_visual_y + d_visual_h + d_margin1; + + int d_ok_w = 20 * factor; + int d_ok_h = 9 * factor; + int d_ok_x = d_dialog_cx - (d_ok_w / 2); + int d_ok_y = d_dialog_y + d_dialog_h - d_ok_h - d_margin1; + + /* + ** Button Enumerations + */ + enum { + BUTTON_SPEED = 100, + BUTTON_SCROLLRATE, + BUTTON_VISUAL, + BUTTON_SOUND, + BUTTON_OK, + BUTTON_COUNT, + BUTTON_FIRST = BUTTON_SPEED, + }; + + /* + ** Dialog variables + */ + KeyNumType input; + + int gamespeed = Options.GameSpeed; + int scrollrate = Options.ScrollRate; + int selection; + bool pressed = false; + int curbutton = 0; + TextButtonClass *buttons[BUTTON_COUNT - BUTTON_FIRST]; + TextPrintType style; + + /* + ** Buttons + */ + GadgetClass *commands; // button list + + SliderClass gspeed_btn(BUTTON_SPEED, d_speed_x, d_speed_y, d_speed_w, d_speed_h); + + SliderClass scrate_btn(BUTTON_SCROLLRATE, d_scroll_x, d_scroll_y, d_scroll_w, d_scroll_h); + + TextButtonClass visual_btn(BUTTON_VISUAL, TXT_VISUAL_CONTROLS, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_visual_x, d_visual_y, d_visual_w, d_visual_h); + + TextButtonClass sound_btn(BUTTON_SOUND, TXT_SOUND_CONTROLS, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_sound_x, d_sound_y, d_sound_w, d_sound_h); + + TextButtonClass okbtn(BUTTON_OK, TXT_OPTIONS_MENU, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_ok_x, d_ok_y); + okbtn.X = (SeenBuff.Get_Width()-okbtn.Width)/2; + + /* + ** Various Inits. + */ + Set_Logic_Page(SeenBuff); + + /* + ** Build button list + */ + commands = &okbtn; + gspeed_btn.Add_Tail(*commands); + scrate_btn.Add_Tail(*commands); + visual_btn.Add_Tail(*commands); + sound_btn.Add_Tail(*commands); + + /* + ** Init button states + ** For sliders, the thumb ranges from 0 - (maxval-1), so to convert the + ** thumb value to a real-world value: + ** val = (MAX - slider.Get_Value()) - 1; + ** and, + ** slider.Set_Value(-(val + 1 - MAX)); + */ + gspeed_btn.Set_Maximum(OptionsClass::MAX_SPEED_SETTING); // varies from 0 - 7 + gspeed_btn.Set_Thumb_Size(1); + gspeed_btn.Set_Value((OptionsClass::MAX_SPEED_SETTING-1) - gamespeed); + + scrate_btn.Set_Maximum(OptionsClass::MAX_SCROLL_SETTING); // varies from 0 - 7 + scrate_btn.Set_Thumb_Size(1); + scrate_btn.Set_Value((OptionsClass::MAX_SCROLL_SETTING-1) - scrollrate); + + /* + ** Fill array of button ptrs. + */ + buttons[0] = NULL; + buttons[1] = NULL; + buttons[2] = &visual_btn; + buttons[3] = &sound_btn; + buttons[4] = &okbtn; + + /* + ** Processing loop. + */ + bool process = true; + bool display = true; + bool refresh = true; + while (process) { + + /* + ** Invoke game callback. + */ + if (GameToPlay == GAME_NORMAL) { + Call_Back(); + } else { + if (Main_Loop()) { + process = false; + } + } + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=TRUE; + } + + /* + ** Refresh display if needed. + */ + if (display) { + Hide_Mouse(); + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + Draw_Caption(TXT_GAME_CONTROLS, d_dialog_x, d_dialog_y, d_dialog_w); + Show_Mouse(); + display = false; + refresh = true; + } + + if (refresh) { + Hide_Mouse(); + + /* + ** Label the game speed slider + */ + style = TPF_6PT_GRAD | TPF_NOSHADOW | TPF_USE_GRAD_PAL; + if (curbutton == (BUTTON_SPEED - BUTTON_FIRST)) { + style = (TextPrintType)(style | TPF_BRIGHT_COLOR); + } + Fancy_Text_Print(TXT_SPEED, d_speed_x, d_speed_y - d_txt6_h, CC_GREEN, TBLACK, style); + + Fancy_Text_Print(TXT_SLOWER, d_speed_x, d_speed_y + d_speed_h + 1, CC_GREEN, TBLACK, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW); + Fancy_Text_Print(TXT_FASTER, d_speed_x + d_speed_w, d_speed_y + d_speed_h + 1, CC_GREEN, TBLACK, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW|TPF_RIGHT); + + /* + ** Label the scroll rate slider + */ + style = TPF_6PT_GRAD | TPF_NOSHADOW | TPF_USE_GRAD_PAL; + if (curbutton == (BUTTON_SCROLLRATE - BUTTON_FIRST)) { + style = (TextPrintType)(style | TPF_BRIGHT_COLOR); + } + Fancy_Text_Print(TXT_SCROLLRATE, d_scroll_x, d_scroll_y - d_txt6_h, CC_GREEN, TBLACK, style); + + Fancy_Text_Print (TXT_SLOWER, d_scroll_x, d_scroll_y + d_scroll_h + 1, CC_GREEN, TBLACK, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW); + Fancy_Text_Print (TXT_FASTER, d_scroll_x + d_scroll_w, d_scroll_y + d_scroll_h + 1, CC_GREEN, TBLACK, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW|TPF_RIGHT); + + commands->Draw_All(); + + Show_Mouse(); + refresh = false; + } + + /* + ** Get user input. + */ + input = commands->Input(); + + /* + ** Process input. + */ + switch (input) { + case (BUTTON_SPEED | KN_BUTTON): + curbutton = (BUTTON_SPEED - BUTTON_FIRST); + refresh = true; + break; + + case (BUTTON_SCROLLRATE | KN_BUTTON): + curbutton = (BUTTON_SCROLLRATE - BUTTON_FIRST); + refresh = true; + break; + + case (BUTTON_VISUAL | KN_BUTTON): + selection = BUTTON_VISUAL; + pressed = true; + break; + + case (BUTTON_SOUND | KN_BUTTON): + selection = BUTTON_SOUND; + pressed = true; + break; + + case (BUTTON_OK | KN_BUTTON): + selection = BUTTON_OK; + pressed = true; + break; + + case (KN_ESC): + process = false; + break; + + case (KN_LEFT): + if (curbutton == (BUTTON_SPEED - BUTTON_FIRST) ) { + gspeed_btn.Bump(1); + } else + if (curbutton == (BUTTON_SCROLLRATE - BUTTON_FIRST) ) { + scrate_btn.Bump(1); + } + break; + + case (KN_RIGHT): + if (curbutton == (BUTTON_SPEED - BUTTON_FIRST) ) { + gspeed_btn.Bump(0); + } else + if (curbutton == (BUTTON_SCROLLRATE - BUTTON_FIRST) ) { + scrate_btn.Bump(0); + } + break; + + case (KN_UP): + if (buttons[curbutton]) { + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + } + + curbutton--; + if (curbutton < 0) { + curbutton = (BUTTON_COUNT - BUTTON_FIRST - 1); + } + + if (buttons[curbutton]) { + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + } + refresh = true; + break; + + case (KN_DOWN): + if (buttons[curbutton]) { + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + } + + curbutton++; + if (curbutton > (BUTTON_COUNT - BUTTON_FIRST - 1) ) { + curbutton = 0; + } + + if (buttons[curbutton]) { + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + } + refresh = true; + break; + + case (KN_RETURN): + selection = curbutton + BUTTON_FIRST; + pressed = true; + break; + + default: + break; + } + + /* + ** Perform some action. Either to exit the dialog or bring up another. + */ + if (pressed) { + + /* + ** Record the new options slider settings. + ** The GameSpeed data member MUST NOT BE SET HERE!!! It will cause multiplayer + ** games to go out of sync. It's set by virtue of the event being executed. + */ + if (gamespeed != ((OptionsClass::MAX_SPEED_SETTING-1) - gspeed_btn.Get_Value()) ) { + gamespeed = (OptionsClass::MAX_SPEED_SETTING-1) - gspeed_btn.Get_Value(); + OutList.Add(EventClass(EventClass::GAMESPEED, gamespeed)); + } + + if (scrollrate != ((OptionsClass::MAX_SCROLL_SETTING-1) - scrate_btn.Get_Value()) ) { + scrollrate = (OptionsClass::MAX_SCROLL_SETTING-1) - scrate_btn.Get_Value(); + Options.ScrollRate = scrollrate; + } + process = false; + + /* + ** Save the settings in such a way that the GameSpeed is only set during + ** the save process; restore it when we're done, so multiplayer games don't + ** go out of sync. + */ + int old = Options.GameSpeed; // save orig value + Options.GameSpeed = gamespeed; + Options.Save_Settings(); // save new value + Options.GameSpeed = old; // restore old value + + /* + ** Possibly launch into another dialog if so directed. + */ + switch (selection) { + case (BUTTON_VISUAL): + VisualControlsClass().Process(); + process = true; + display = true; + refresh = true; + break; + + case (BUTTON_SOUND): + if (!SoundType) { + CCMessageBox().Process(Text_String(TXT_NO_SOUND_CARD)); + process = true; + display = true; + refresh = true; + } else { + SoundControlsClass().Process(); + } + break; + + case (BUTTON_OK): + break; + } + + pressed = false; + } + } +} + diff --git a/GAMEDLG.H b/GAMEDLG.H new file mode 100644 index 0000000..e166eb0 --- /dev/null +++ b/GAMEDLG.H @@ -0,0 +1,52 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\gamedlg.h_v 2.17 16 Oct 1995 16:45:22 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : GAMEDLG.H * + * * + * Programmer : Maria del Mar McCready Legg, Joe L. Bostic * + * * + * Start Date : Jan 8, 1995 * + * * + * Last Update : Jan 18, 1995 [MML] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef GAMEDLG_H +#define GAMEDLG_H + +#include "gadget.h" + +class GameControlsClass +{ + public: + GameControlsClass(void) {}; + void Process(void); +}; + + +#endif + diff --git a/GAUGE.CPP b/GAUGE.CPP new file mode 100644 index 0000000..d323cd0 --- /dev/null +++ b/GAUGE.CPP @@ -0,0 +1,539 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\gauge.cpv 2.19 16 Oct 1995 16:50:56 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : GAUGE.CPP * + * * + * Programmer : Joe L. Bostic, Maria del Mar McCready Legg * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 16, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * GaugeClass::Action -- Handles input events for the gauge. * + * GaugeClass::Draw_Me -- Draws the body of the gauge. * + * GaugeClass::Value_To_Pixel -- Convert gauge value to pixel offset. * + * GaugeClass::Pixel_To_Value -- Convert a pixel offset into a gauge value. * + * GaugeClass::Set_Value -- Set the value of the gauge. * + * GaugeClass::Set_Maximum -- Sets the maximum value for the gauge. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*************************************************************************** + * GAUGECLASS::GAUGECLASS -- class constructor * + * * + * INPUT: id -- button ID * + * * + * x,y -- upper-left corner, in pixels * + * * + * w,h -- width, height, in pixels * + * * + * OUTPUT: none. * + * * + * WARNINGS: none. * + * * + * HISTORY: 01/05/1995 MML : Created. * + *=========================================================================*/ +GaugeClass::GaugeClass(unsigned id, int x, int y, int w, int h) + : ControlClass(id, x, y, w, h, LEFTHELD|LEFTPRESS|LEFTRELEASE, true) +{ + Set_Maximum(255); + Set_Value(0); + + HasThumb = true; + IsHorizontal = (w > h); + IsColorized = true; + + ClickDiff = 0; +} + + +/*********************************************************************************************** + * GaugeClass::Set_Maximum -- Sets the maximum value for the gauge. * + * * + * This routine will set the maximum value for the gauge. This is the largest value that * + * the current setting may reach. The ability to change this allows the guage to use and * + * return values that are convenient for the programmer's current needs. * + * * + * INPUT: value -- The value to use as the gauge maximum. * + * * + * OUTPUT: bool; Was the gauge maximum changed? A false indicates that the specified value * + * already matches the current maximum. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +int GaugeClass::Set_Maximum(int value) +{ + if (value != MaxValue) { + MaxValue = value; + Flag_To_Redraw(); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * GaugeClass::Set_Value -- Set the value of the gauge. * + * * + * This routine will set the current value for the gauge. This value is clipped to the * + * limits of the gauge maximum. * + * * + * INPUT: value -- The value to set at the new current value. * + * * + * OUTPUT: bool; Was the current setting changed? A false indicates that the setting * + * specified is the same as what was already there. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +int GaugeClass::Set_Value(int value) +{ + value = Bound(value, 0, MaxValue); +// value = MIN(value, MaxValue); +// value = MAX(value, 0); + if (value != CurValue) { + CurValue = value; + Flag_To_Redraw(); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * GaugeClass::Pixel_To_Value -- Convert a pixel offset into a gauge value. * + * * + * Use this routine to conver the specified pixel offset into a gauge value. This is used * + * in translating mouse clicks into a cooresponding setting for the guage. * + * * + * INPUT: pixel -- The pixel offset form the start of the gauge. * + * * + * OUTPUT: Returns with the setting value in guage coordinates. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +int GaugeClass::Pixel_To_Value(int pixel) +{ + int maximum; + + if (IsHorizontal) { + pixel -= X+1; + maximum = Width; + } else { + pixel -= Y+1; + maximum = Height; + } + maximum -= 2; + pixel = Bound(pixel, 0, maximum); +// pixel = MIN(pixel, maximum); +// pixel = MAX(pixel, 0); + return(Fixed_To_Cardinal(MaxValue, Cardinal_To_Fixed(maximum, pixel))); +} + + +/*********************************************************************************************** + * GaugeClass::Value_To_Pixel -- Convert gauge value to pixel offset. * + * * + * Use this routine to convert the specified gauge value into a pixel offset from the * + * star of the gauge. This is used for thumb positioning. * + * * + * INPUT: value -- The value to convert to a pixel offset. * + * * + * OUTPUT: Returns with the pixel offset of the specified value from the start of the * + * guage. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +int GaugeClass::Value_To_Pixel(int value) +{ + int maximum; + int start; + if (IsHorizontal) { + maximum = Width; + start = X; + } else { + maximum = Height; + start = Y; + } + maximum -= 2; + return(start + Fixed_To_Cardinal(maximum, Cardinal_To_Fixed(MaxValue, value))); +} + + +/*********************************************************************************************** + * GaugeClass::Draw_Me -- Draws the body of the gauge. * + * * + * This routine will draw the body of the gauge if necessary. * + * * + * INPUT: forced -- Should the gauge be redrawn regardless of the current redraw flag? * + * * + * OUTPUT: bool; Was the gauge redrawn? * + * * + * WARNINGS: none * + * * + * HISTORY: 01/16/1995 JLB : Created. * + *=============================================================================================*/ +int GaugeClass::Draw_Me(int forced) +{ + if (ControlClass::Draw_Me(forced)) { + + /* + ===================== Hide the mouse ===================== + */ + if (LogicPage == &SeenBuff) { + Conditional_Hide_Mouse(X, Y, X+Width, Y+Height); + } + + /* + =========== Draw the body & set text color =============== + */ + Draw_Box (X, Y, Width, Height, BOXSTYLE_GREEN_DOWN, true); + + /* + ** Colorize the inside of the gauge if indicated. + */ + if (IsColorized) { + int middle = Value_To_Pixel(CurValue); + int color = CC_BRIGHT_GREEN; + if (IsHorizontal) { + if (middle >= (X + 1)) + LogicPage->Fill_Rect(X+1, Y+1, middle, Y+Height-2, color); + } else { + if (middle >= (Y + 1)) + LogicPage->Fill_Rect(X+1, Y+1, X+Width-2, middle, color); + } + } + + if (HasThumb) + Draw_Thumb(); + + /* + =================== Display the mouse =================== + */ + if (LogicPage == &SeenBuff) { + Conditional_Show_Mouse(); + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * GaugeClass::Action -- Handles input events for the gauge. * + * * + * This routine will handle input event processing for the gauge. It will adjust the * + * current setting of the gauge according to the mouse position. * + * * + * INPUT: flags -- The input event that is the reason for this function call. * + * key -- The key code that caused the event. * + * * + * OUTPUT: bool; Was the even recognized, processed, and no further gadget scanning is * + * desird (for this pass). * + * * + * WARNINGS: none * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +int GaugeClass::Action(unsigned flags, KeyNumType &key) +{ + /* + ** If there's no thumb on this gauge, it's a display-only device; ignore + ** any input. + */ + if (!HasThumb) { + key = KN_NONE; + return(true); + } + + /* + ** We might end up clearing the event bits. Make sure that the sticky + ** process is properly updated anyway. + */ + Sticky_Process(flags); + + /* + ** If the thumb is currently being "dragged around", then update the slider + ** position according to the mouse position. In all other cases, ignore the + ** button being held down. + */ + if ((flags & LEFTPRESS) || ((flags & LEFTHELD) && StuckOn == this)) { + + /* + ** Compute the difference between where we clicked, and the edge of + ** the thumb (only if we clicked on the thumb.) + */ + if (flags & LEFTPRESS) { + int curpix = Value_To_Pixel(CurValue); + int clickpix = (IsHorizontal ? Get_Mouse_X() : Get_Mouse_Y()); + + if ( (clickpix > curpix) && (clickpix - curpix) < Thumb_Pixels()) { + ClickDiff = (clickpix - curpix); + } else { + ClickDiff = 0; + } + + int testval = Pixel_To_Value(IsHorizontal ? + Get_Mouse_X() - ClickDiff : Get_Mouse_Y() - ClickDiff); + + /* + ** Correct for round-down errors in Pixel_To_Value() and + ** Value_To_Pixe(); make ClickDiff exactly right so that + ** at this point, Get_Mouse_n() - ClickDiff converts to + ** CurValue. + */ + while (testval < CurValue && ClickDiff > 0) { + ClickDiff--; + testval = Pixel_To_Value(IsHorizontal ? + Get_Mouse_X() - ClickDiff : Get_Mouse_Y() - ClickDiff); + } + } + + /* + ** If no change occurred in the gauge, just call Control's Action routine, + ** but turn off the flags so it won't fill in 'key' with the button ID. + ** Thus, no button ID will be returned by Input. + */ + if (!Set_Value(Pixel_To_Value(IsHorizontal ? + Get_Mouse_X() - ClickDiff : Get_Mouse_Y() - ClickDiff))) { + + flags &= ~(LEFTHELD|LEFTRELEASE|LEFTPRESS); + ControlClass::Action(0,key); + key = KN_NONE; + return(true); + } + + } else { + + /* + ** Ingore the left mouse button being held down if this gauge is not + ** currently in "sticky" mode. This allows processing of the LEFTPRESS + ** by any derived classes such that this guage can be more closely + ** controlled. + */ + flags &= ~LEFTHELD; + } + return(ControlClass::Action(flags, key)); +} + + +/*********************************************************************************************** + * GaugeClass::Draw_Thumb -- Draws the body of the gauge. * + * * + * This routine will draw the body of the gauge if necessary. * + * * + * INPUT: none. * + * * + * OUTPUT: none. * + * * + * WARNINGS: none. * + * * + * HISTORY: 01/16/1995 MML : Created. * + *=============================================================================================*/ +void GaugeClass::Draw_Thumb(void) +{ + int x = Value_To_Pixel(CurValue); + +// if ((x + 8) > Value_To_Pixel(MaxValue)) { + if ((x + 4) > Value_To_Pixel(MaxValue)) { + x = Value_To_Pixel(MaxValue) - 2; + } + + if (IsHorizontal) { + Draw_Box(x, Y, 4, Height, BOXSTYLE_GREEN_RAISED, true); + //Draw_Box(x, Y, 8, Height, BOXSTYLE_GREEN_RAISED, true); + } else { + Draw_Box(X, x, Width, 4, BOXSTYLE_GREEN_RAISED, true); + //Draw_Box(X, x, Width, 8, BOXSTYLE_GREEN_RAISED, true); + } +} + + +/*********************************************************************************************** + * TriColorGaugeClass::TriColorGaugeClass -- Constructor for 3 color (red\yellow\green) gauge. * + * * + * This routine will draw the body of the gauge if necessary. * + * * + * INPUT: See below. * + * * + * OUTPUT: none. * + * * + * WARNINGS: none. * + * * + * HISTORY: 01/16/1995 MML : Created. * + *=============================================================================================*/ +TriColorGaugeClass::TriColorGaugeClass(unsigned id, int x, int y, int w, int h) + : GaugeClass(id, x, y, w, h) +{ + RedLimit = 0; // maximum value for red + YellowLimit = 0; // maximum value for yellow +} + + +/*********************************************************************************************** + * TriColorGaugeClass::Set_Red_Limit -- Set the value for the red area of gauge. * + * * + * INPUT: int value. * + * * + * OUTPUT: bool true of false. * + * * + * WARNINGS: none. * + * * + * HISTORY: 01/16/1995 MML : Created. * + *=============================================================================================*/ +int TriColorGaugeClass::Set_Red_Limit(int value) +{ + if (value >= 0 && value < MaxValue) { + +// if (value > YellowLimit) { +// RedLimit = YellowLimit; +// YellowLimit = value; +// } else { + RedLimit = value; +// } + Flag_To_Redraw(); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TriColorGaugeClass::Set_Yellow_Limit -- Set the value for the yellow area of gauge. * + * * + * INPUT: int value. * + * * + * OUTPUT: bool true of false. * + * * + * WARNINGS: none. * + * * + * HISTORY: 01/16/1995 MML : Created. * + *=============================================================================================*/ +int TriColorGaugeClass::Set_Yellow_Limit(int value) +{ + if (value >= 0 && value < MaxValue) { + +// if (value < RedLimit) { +// YellowLimit = RedLimit; +// RedLimit = value; +// } else { + YellowLimit = value; +// } + Flag_To_Redraw(); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TriColorGaugeClass::Draw_Me -- Draw the tri color gauge. * + * * + * INPUT: int forced -- draw or not? * + * * + * OUTPUT: bool true of false. * + * * + * WARNINGS: none. * + * * + * HISTORY: 01/16/1995 MML : Created. * + *=============================================================================================*/ +int TriColorGaugeClass::Draw_Me(int forced) +{ + if (ControlClass::Draw_Me(forced)) { + + /* + ===================== Hide the mouse ===================== + */ + if (LogicPage == &SeenBuff) { + Conditional_Hide_Mouse(X, Y, X+Width, Y+Height); + } + /* + =========== Draw the body & set text color =============== + */ + Draw_Box (X, Y, Width, Height, (IsDisabled ? BOXSTYLE_GREEN_RAISED : BOXSTYLE_GREEN_DOWN), true); + + /* + ** Colorize the inside of the gauge if indicated. + */ + int red = Value_To_Pixel(RedLimit); + int yellow = Value_To_Pixel(YellowLimit); + int middle = Value_To_Pixel(CurValue); + + if (CurValue <= RedLimit) { + if (IsHorizontal) { + LogicPage->Fill_Rect(X+1, Y+1, middle, Y+Height-2, PINK); + } else { + LogicPage->Fill_Rect(X+1, Y+1, X+Width-2, middle, PINK); + } + } else if (CurValue > RedLimit && CurValue <= YellowLimit) { + if (IsHorizontal) { + LogicPage->Fill_Rect(X+1, Y+1, red, Y+Height-2, PINK); + LogicPage->Fill_Rect(red, Y+1, middle, Y+Height-2, YELLOW); + } else { + LogicPage->Fill_Rect(X+1, Y+1, X+Width-2, red, PINK); + LogicPage->Fill_Rect(X+1, red, X+Width-2, middle, YELLOW); + } + } else if (CurValue > YellowLimit && CurValue <= MaxValue) { + + if (IsHorizontal) { + LogicPage->Fill_Rect(X+1, Y+1, red, Y+Height-2, PINK); + LogicPage->Fill_Rect(red, Y+1, yellow, Y+Height-2, YELLOW); + LogicPage->Fill_Rect(yellow, Y+1, middle, Y+Height-2, GREEN); + } else { + LogicPage->Fill_Rect(X+1, Y+1, X+Width-2, red, PINK); + LogicPage->Fill_Rect(X+1, red, X+Width-2, yellow, YELLOW); + LogicPage->Fill_Rect(X+1, yellow, X+Width-2, middle, GREEN); + } + } + + if (HasThumb) + Draw_Thumb(); + + /* + =================== Display the mouse =================== + */ + if (LogicPage == &SeenBuff) { + Conditional_Show_Mouse(); + } + return(true); + } + return(false); +} + diff --git a/GAUGE.H b/GAUGE.H new file mode 100644 index 0000000..76f191f --- /dev/null +++ b/GAUGE.H @@ -0,0 +1,111 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\gauge.h_v 2.17 16 Oct 1995 16:45:24 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : GAUGE.H * + * * + * Programmer : Joe L. Bostic, Maria del Mar McCready Legg * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 15, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef GAUGE_H +#define GAUGE_H + +class GaugeClass : public ControlClass +{ + public: + + GaugeClass(unsigned id, int x, int y, int w, int h); +// static GaugeClass * Create_One_Of(unsigned id, int x, int y, int w, int h); + + virtual int Draw_Me(int forced=false); + virtual int Set_Maximum(int value); + virtual int Set_Value(int value); + virtual int Get_Value(void) const {return (CurValue);}; + virtual void Use_Thumb(int value) { HasThumb = value ? true : false; }; + + virtual int Thumb_Pixels(void) { return (8);} + + /* + ** If this gauge has a color to the left of the current setting, then this + ** flag will be true. + */ + unsigned IsColorized:1; + + protected: + + /* + ** If a thumb is desired, set to true. + */ + unsigned HasThumb:1; + + /* + ** Is this a horizontal slider? + */ + unsigned IsHorizontal:1; + + int MaxValue; // maximum value (in application units) + int CurValue; // index of 1st displayed string in box + // (in application units) + + /* + ** This value records the difference between where the user clicked + ** and the edge of the thumb, so that the thumb follows the mouse + ** with the proper offset. + */ + int ClickDiff; + + protected: + virtual void Draw_Thumb(void); + virtual int Action(unsigned flags, KeyNumType &key); + virtual int Pixel_To_Value(int pixel); + virtual int Value_To_Pixel(int value); +}; + + + +class TriColorGaugeClass : public GaugeClass +{ + public: + TriColorGaugeClass(unsigned id, int x, int y, int w, int h); +// static TriColorGaugeClass * Create_One_Of(unsigned id, int x, int y, int w, int h); + virtual int Draw_Me(int forced); + virtual int Set_Red_Limit(int value); + virtual int Set_Yellow_Limit(int value); + + protected: + int RedLimit; // maximum value for red + int YellowLimit; // maximum value for yellow +}; + + + + +#endif diff --git a/GDI.BAT b/GDI.BAT new file mode 100644 index 0000000..de54db5 --- /dev/null +++ b/GDI.BAT @@ -0,0 +1,5 @@ +@echo off +pushd +cd ..\run +conquer -CD..\cd\aud1;..\cd;..\cd\install %1 %2 %3 %4 %5 +popd diff --git a/GLOBALS.CPP b/GLOBALS.CPP new file mode 100644 index 0000000..ebff031 --- /dev/null +++ b/GLOBALS.CPP @@ -0,0 +1,1035 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\globals.cpv 2.17 16 Oct 1995 16:52:22 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : GLOBALS.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : September 10, 1993 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#ifdef JAPANESE +bool ForceEnglish = false; +#endif + +bool Debug_Quiet = false; +bool Debug_Cheat = false; +bool Debug_Remap = false; +bool Debug_Icon = false; +bool Debug_Flag = false; +bool Debug_Lose = false; +bool Debug_Win = false; +bool Debug_Map = false; // true = map editor mode +bool Debug_Passable = false; // true = show passable/impassable terrain +bool Debug_Unshroud = false; // true = hide the shroud +bool Debug_Threat = false; +bool Debug_Find_Path = false; +bool Debug_Check_Map = false; // true = validate the map each frame +bool Debug_Playtest = false; +int In_Debugger = 0; +bool Debug_Heap_Dump = false; // true = print the Heap Dump +bool Debug_Smart_Print = false; // true = print everything that calls Smart_Printf +bool Debug_Trap_Check_Heap = false; // true = check the Heap +bool Debug_Instant_Build = false; + +TFixedIHeapClass Units; +TFixedIHeapClass Factories; +TFixedIHeapClass Terrains; +TFixedIHeapClass Templates; +TFixedIHeapClass Smudges; +TFixedIHeapClass Overlays; +TFixedIHeapClass Infantry; +TFixedIHeapClass Bullets; +TFixedIHeapClass Buildings; +TFixedIHeapClass Anims; +TFixedIHeapClass Aircraft; +TFixedIHeapClass Triggers; +TFixedIHeapClass TeamTypes; +TFixedIHeapClass Teams; +TFixedIHeapClass Houses; + + +#ifdef PATCH +/*************************************************************************** +** Compatibility with version 1.07 flag. +*/ +bool IsV107 = false; +char OverridePath[128]="."; +#endif + + +/*************************************************************************** +** This is a list of all selected objects (for this map). The support functions +** are used to control access to this list. Do not modify it directly. +*/ +DynamicVectorClass CurrentObject; + + +/*************************************************************************** +** This holds the custom version text that is fetched from the version +** text file. This version is displayed on the options dialog. +*/ +char VersionText[16]; + + +/*************************************************************************** +** This is the VQ animation controller structure. It is filled in by reading +** the PLAYER.INI and overridden through program control. +*/ +VQAConfig AnimControl; + +int PreserveVQAScreen; // Used for screen mode transition control. +bool BreakoutAllowed = true; // "true" if aborting of movies is allowed. +bool Brokeout; // Was the movie broken out of? +bool SlowPalette = true; // Slow palette flag set? + + +/*************************************************************************** +** These are the movie names to use for mission briefing, winning, and losing +** sequences. They are read from the INI file. +*/ +char IntroMovie[_MAX_FNAME+_MAX_EXT]; +char BriefMovie[_MAX_FNAME+_MAX_EXT]; +char WinMovie[_MAX_FNAME+_MAX_EXT]; +char LoseMovie[_MAX_FNAME+_MAX_EXT]; +char ActionMovie[_MAX_FNAME+_MAX_EXT]; +char BriefingText[512]; +ThemeType TransitTheme = THEME_NONE; + + +/*************************************************************************** +** This records the view hotspots for the player. These are the cell numbers +** of the upper left corner for the view position. +*/ +CELL Views[4]; + + +/*************************************************************************** +** This is the pending speech sample to play. This sample will be played +** at the first opportunity. +*/ +VoxType SpeakQueue = VOX_NONE; + + +/*************************************************************************** +** This records if the score (music) file is present. If not, then much of +** the streaming score system can be disabled. +*/ +bool ScoresPresent; + + +/*************************************************************************** +** This flag will control whether there is a response from game units. +** By carefully controlling this global, multiple responses are supressed +** when a large group of infantry is given the movement order. +*/ +bool AllowVoice = true; + + +/*************************************************************************** +** This counts the number of crates on the map. When this value reaches zero, +** then a timer is started that will control crate creation. +*/ +int CrateCount; +TCountDownTimerClass CrateTimer; +bool CrateMaker = false; + + +/*************************************************************************** +** This is the current frame number. This number is guaranteed to count +** upward at the rate of one per game logic process. The target rate is 15 +** per second. This value is saved and restored with the saved game. +*/ +long Frame = 0; + + +/*************************************************************************** +** These globals are constantly monitored to determine if the player +** has won or lost. They get set according to the trigger events associated +** with the scenario. +*/ +bool PlayerWins; +bool PlayerLoses; +bool PlayerRestarts; + +/* +** This flag is set if the player neither wins nor loses; it's mostly for +** multiplayer mode. +*/ +bool PlayerAborts; + + +/*************************************************************************** +** This is the pointer for the speech staging buffer. This buffer is used +** to hold the currently speaking voice data. Since only one speech sample +** is played at a time, this buffer is only as big as the largest speech +** sample that can be played. +*/ +void * SpeechBuffer; + + +/*************************************************************************** +** This is a running accumulation of the number of ticks that were unused. +** This accumulates into a useful value that contributes to a +** histogram of game performance. +*/ +long SpareTicks; + + +/*************************************************************************** +** This is a special scenario count down value. End of game condition will +** not be checked until this value reaches zero. +*/ +int EndCountDown; + + +/*************************************************************************** +** When the player sabotages a building (scenario #6 GDI only) then when +** the next scenario starts, that building will already be destroyed. +*/ +StructType SabotagedType; + + +/*************************************************************************** +** If the Nod temple was destroyed by the ion cannon, then this flag will +** be set to true. +*/ +bool TempleIoned = false; + + +/*************************************************************************** +** This is the monochrome debug page array. The various monochrome data +** screens are located here. +*/ +MonoClass MonoArray[MonoClass::MAX_MONO_PAGES]; +int MonoPage; // The current page. + + +/*************************************************************************** +** This is true if the game is the currently in focus windows app +** +*/ +bool GameInFocus; + +/*************************************************************************** +** This holds the theater specific mixfiles. +*/ +MixFileClass *TheaterData = NULL; +MixFileClass *TheaterIcons = NULL; +MixFileClass *LowTheaterData; +MixFileClass *MoviesMix = 0; +MixFileClass *GeneralMix = 0; +MixFileClass *ScoreMix = 0; + + +/*************************************************************************** +** This is the options control class. The options control such things as +** game speed, visual controls, and other user settings. +*/ +GameOptionsClass Options; + + +/*************************************************************************** +** Logic processing is controlled by this element. It handles both graphic +** and AI logic. +*/ +LogicClass Logic; + + +/*************************************************************************** +** This handles the background music. +*/ +ThemeClass Theme; + + +/*************************************************************************** +** This is the main control class for the map. +*/ +#ifdef SCENARIO_EDITOR +MapEditClass Map; +#else +MouseClass Map; +#endif + +/************************************************************************** +** The running game score is handled by this class (and member functions). +*/ +ScoreClass Score; + + +/*************************************************************************** +** The running credit display is controlled by this class (and member +** functions. +*/ +CreditClass CreditDisplay; + + +/*************************************************************************** +** These are the bits that are set when the appropriate tutor message +** has been displayed. Once the message has been displayed, it will not be +** displayed again. +*/ +long TutorFlags[2]; + + +/************************************************************************** +** This class records the special command override options that C&C +** supports. +*/ +SpecialClass Special; + + +/*************************************************************************** +** This is the scenario data for the currently loaded scenario. +** These variables should all be set together. +*/ +HousesType Whom; // Initial command line house choice. +unsigned Scenario; // Scenario # +ScenarioPlayerType ScenPlayer; // GDI, NOD, 2-Player, Multi-Player +ScenarioDirType ScenDir; // East/West +ScenarioVarType ScenVar; // variation A/B/C +char ScenarioName[_MAX_FNAME+_MAX_EXT]; // name of scenario +int CarryOverMoney; // Carry over money from last scenario. +int CarryOverPercent; // Carry over money percentage control. +int CarryOverCap; // Maxmimum carry over money allowed. +bool ScenarioInit; +bool SpecialFlag = false; + + +/*************************************************************************** +** This value tells the sidebar what items it's allowed to add. The +** lower the value, the simpler the sidebar will be. +*/ +unsigned BuildLevel = 3; // Buildable level (1 = simplest) + + +/*************************************************************************** +** This value is computed every time a new scenario is loaded; it's a +** CRC of the INI and binary map files. +*/ +unsigned long ScenarioCRC; + + +/*************************************************************************** +** The various tutor and dialog messages are located in the data block +** referenced by this pointer. +*/ +char const * SystemStrings; + + +/*************************************************************************** +** The game plays as long as this var is true. +*/ +bool GameActive; + + +/*************************************************************************** +** This is a scratch variable that is used to when a reference is needed to +** a long, but the value wasn't supplied to a function. This is used +** specifically for the default reference value. As such, it is not stable. +*/ +long LParam; + + +#ifdef SCENARIO_EDITOR +/*************************************************************************** +** The currently-selected cell for the Scenario Editor +*/ +CELL CurrentCell = 0; +#endif + + +/*************************************************************************** +** Most of the text in the game will use the six point font. These are the +** pointers to the fonts. If it is NULL, then the font hasn't been loaded +** yet. +*/ +void const *Green12FontPtr; // Green font for pressed in tabs +void const *Green12GradFontPtr; // Graduated green font for tabs +void const *MapFontPtr; // Standard very small font. +void const *Font3Ptr; // Standard very small font. +void const *Font6Ptr; // Standard small font. +void const *Font8Ptr; // 8 point proportional. +void const *FontLEDPtr; // LED fixed point font. +void const *VCRFontPtr; // VCR font pointer. +void const *ScoreFontPtr; // font for score & map selection screens +void const *GradFont6Ptr; // gradient 6 point font pointer. + + +/*************************************************************************** +** This is the house that the human player is currently playing. +*/ +HouseClass * PlayerPtr; + + +/*************************************************************************** +** Special palettes for MCGA mode goes here. These palette buffers are used +** for pictures that do not use the game palette or are used for fading to +** black. +*/ +unsigned char *GamePalette; +unsigned char *BlackPalette; +unsigned char *WhitePalette; +unsigned char *OriginalPalette; +unsigned char *Palette; + + +/*************************************************************************** +** These are the event queues. One is for holding events until they are ready to be +** sent to the remote computer for processing. The other list is for incoming events +** that need to be executed when the correct frame has been reached. +*/ +QueueClass OutList; +QueueClass DoList; + + +/*************************************************************************** +** These are arrays/lists of trigger pointers for each cell & the houses. +*/ +DynamicVectorClass CellTriggers; +DynamicVectorClass HouseTriggers[HOUSE_COUNT]; + + +/*************************************************************************** +** This is an array of waypoints; each waypoint corresponds to a letter of +** the alphabet, and points to a cell number. -1 means unassigned. +** The CellClass has a bit that tells if that cell has a waypoint attached to +** it; the only way to find which waypoint it is, is to scan this array. This +** shouldn't be needed often; usually, you know the waypoint & you want the CELL. +*/ +CELL Waypoint[WAYPT_COUNT]; + + +/*************************************************************************** +** This is the list of BuildingTypes that define the AI's base. +*/ +BaseClass Base; + + +/*************************************************************************** +** This value tells what type of multiplayer game we're playing. +*/ +GameType GameToPlay = GAME_NORMAL; + + +/*************************************************************************** +** This is the current communications protocol +*/ +CommProtocolType CommProtocol; + + +/*************************************************************************** +** These values are used for recording & playing back a game. +*/ +CCFileClass RecordFile ("RECORD.BIN"); +int RecordGame = 0; // 1 = record a game +int SuperRecord = 0; // 1 = reopen record file with every write +int PlaybackGame= 0; // 1 = play back a game +int AllowAttract = 0; // 1 = allow attract mode + + +/*************************************************************************** +** This is the null modem manager class. Declaring this class doesn't +** perform any allocations; +** the class itself is ?? bytes. +*/ +bool ModemService = true; // When false disable servicing modem. +NullModemClass NullModem ( + 16, // number of send entries + 64, // number of receive entries +// sizeof (EventClass) * MAX_EVENTS, // maxlen of entry buffer + (200 / sizeof(EventClass) ) * sizeof(EventClass) + sizeof( CommHeaderType ), + 0x1234); // Magic number must have each digit unique + // and different from the queue magic number + +DynamicVectorClass PhoneBook; +int CurPhoneIdx; // current phonebook index, for dialing + +DynamicVectorClass InitStrings; + +SerialSettingsType SerialDefaults; // serial port default settings + +ModemGameType ModemGameToPlay; // type of modem play Dialer, answerer, null + +char *DialMethodCheck[ DIAL_METHODS ] = { + "T", + "P" +}; + +char *CallWaitStrings[ CALL_WAIT_STRINGS_NUM ] = { + "*70,", + "70#,", + "1170,", + "CUSTOM - " +}; + + +/*************************************************************************** +** Index into scenario description list box +*/ +int ScenarioIdx; + + +/*************************************************************************** +** This array of flags tells if the given colors have been used, or are +*/ +int ColorUsed[MAX_MPLAYER_COLORS]; + + +/*************************************************************************** +** This string stores the player's name. +*/ +char MPlayerName[MPLAYER_NAME_MAX]; + + +/*************************************************************************** +** This is the array of remap colors. Each player in a network game is +** assigned one of these colors. The 'G' is for graphics drawing; the 'T' +** is for text printing (indicates a remap table for the font to use). +*/ +int MPlayerGColors[MAX_MPLAYER_COLORS] = { + 5, // Yellow + 127, // Red + 135, // BlueGreen + 26, // Orange + 4, // Green + 202 // Blue-Grey +}; + +int MPlayerTColors[MAX_MPLAYER_COLORS] = { + CC_GDI_COLOR, // Yellow + CC_NOD_COLOR, // Red + CC_BLUE_GREEN, // BlueGreen + CC_ORANGE, // Orange //26 + CC_GREEN, // Green + CC_BLUE_GREY, // Blue +}; + + +/*************************************************************************** +** This is a list of all the names of the multiplayer scenarios that use +** bases (production), and those that don't. There is a list for +** descriptions, and another for actual filenames. +*/ +char MPlayerDescriptions[100][40]; +DynamicVectorClass MPlayerScenarios; +DynamicVectorClass MPlayerFilenum; + + +/*************************************************************************** +** This value determines the max allowable # of players. +*/ +int MPlayerMax = 4; + + +/*************************************************************************** +** Multiplayer game options +*/ +int MPlayerPrefColor; // preferred color index for this player +int MPlayerColorIdx; // actual color index of this player +HousesType MPlayerHouse; // House of this player (GDI/NOD) +unsigned char MPlayerLocalID; // ID of this player +int MPlayerCount; // # of human players in this game +int MPlayerBases; // 1 = bases are on for this scenario +int MPlayerCredits; // # credits everyone gets +int MPlayerTiberium; // 1 = tiberium enabled for this scenario +int MPlayerGoodies; // 1 = goodies enabled for this scenario +int MPlayerGhosts; // 1 = houses with no players will still play +int MPlayerSolo = 0; // 1 = allows a single-player net game +int MPlayerUnitCount = 10; // # units for non-base multiplayer scenarios + + +/*--------------------------------------------------------------------------- +Min & Max unit count values; index0 = bases OFF, index1 = bases ON +---------------------------------------------------------------------------*/ +int MPlayerCountMin[2] = {1,0}; +int MPlayerCountMax[2] = {50,12}; + + +/*--------------------------------------------------------------------------- +MPlayerMaxAhead is the number of frames ahead of this one to execute a given +packet. It's set by the RESPONSE_TIME event. +---------------------------------------------------------------------------*/ +unsigned long MPlayerMaxAhead = 3; + + +/*--------------------------------------------------------------------------- +'FrameSendRate' is the # frames between data packets +'FrameRateDelay' is the time ticks to wait between frames, for smoothing. +---------------------------------------------------------------------------*/ +unsigned long FrameSendRate; + + +/*************************************************************************** +** Multiplayer ID's, stored in order of event execution. +** Format: +** bits 0-3: the "preferred" house of the player (GDI/NOD) +** bits 4-7: the player's Color Index +** These values are used as the IPX connection ID's. +*/ +unsigned char MPlayerID [MAX_PLAYERS]; + + +/*************************************************************************** +** This array stores the actual HousesType for all players (MULT1, etc). +*/ +HousesType MPlayerHouses [MAX_PLAYERS]; + + +/*************************************************************************** +** This array stores the names of all players in a multiplayer game. +*/ +char MPlayerNames [MAX_PLAYERS][MPLAYER_NAME_MAX]; + + +/*************************************************************************** +** This is a list of the messages received from / sent to other players, +** the address to send to (IPX only), and the last message received or +** sent (for the computer's messages). +*/ +MessageListClass Messages; +IPXAddressClass MessageAddress; +char LastMessage[MAX_MESSAGE_LENGTH]; + + +/*************************************************************************** +** If this flag is set, computer AI will blitz the humans all at once; +** otherwise, the computer units trickle gradually out. +*/ +int MPlayerBlitz = 0; + + +/*************************************************************************** +** If this flag is set, we can move around the map, but we can't do anything. +** It means we've been defeated, but we're still allowed to watch the action. +*/ +int MPlayerObiWan = 0; + + +/*************************************************************************** +** These variables keep track of the multiplayer game scores. +*/ +MPlayerScoreType MPlayerScore[MAX_MULTI_NAMES]; +int MPlayerGamesPlayed; // # games played this run +int MPlayerNumScores; // # active entries in MPlayerScore +int MPlayerWinner; // index of winner of last game +int MPlayerCurGame; // index of current game being played + + +// +// This array stores the processing time required by all multiplayer systems. +// The values are stored in the same order as the 'MPlayerID' array. +// +int TheirProcessTime[MAX_PLAYERS - 1]; +int DesiredFrameRate; + + +/*************************************************************************** +** These values are used purely for the Mono debug display. They show the +** names of the Global Channel packet types, and the event types. +*/ +char *GlobalPacketNames[] = { + "Game?", + "Game!", + "Player?", + "Player!", + "Join?", + "Join!", + "Reject", + "GameOptions", + "Sign Off", + "GO!", + "Message", + "Ping" +}; + + +// yeah, there's 100 empty names here, because the SerialCommandType starts at 100. +char *SerialPacketNames[] = { + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "CONNECT", + "GAME_OPTIONS", + "SIGN_OFF", + "GO", + "MESSAGE", + "TIMING", + "SCORE_SCREEN", + "LAST_COMMAND", +}; + + +/*************************************************************************** +** These variables are just to help find sync bugs. +*/ +long TrapFrame = 0x7fffffff; // frame to start trapping object values at +RTTIType TrapObjType = RTTI_NONE; // type of object to trap +TrapObjectType TrapObject = {NULL}; // ptr to object being trapped +COORDINATE TrapCoord = 0; // COORD of object to trap +void *TrapThis = NULL; // 'this' ptr of object to trap +CellClass *TrapCell = NULL; // for trapping a cell +int TrapCheckHeap = 0; // start checking the Heap + + +/*************************************************************************** +** This is the network IPX manager class. It handles multiple remote +** connections. Declaring this class doesn't perform any allocations; +** the class itself is 140 bytes. +*/ +IPXManagerClass Ipx ( + sizeof (GlobalPacketType), // size of Global Channel packets + ((546 - sizeof(CommHeaderType)) / sizeof(EventClass) ) * sizeof(EventClass), + 10, // # entries in Global Queue + 8, // # entries in Private Queues + VIRGIN_SOCKET, // Socket ID # + IPXGlobalConnClass::COMMAND_AND_CONQUER); // Product ID # + + +//#if(TIMING_FIX) +// +// These values store the min & max frame #'s for when MaxAhead >>increases<<. +// If MaxAhead increases, and the other systems free-run to the new MaxAhead +// value, they may miss an event generated after the MaxAhead event was sent, +// but before it executed, since it will have been scheduled with the older, +// shorter MaxAhead value. This will cause a Packet_Received_Too_Late error. +// The frames from the point where the new MaxAhead takes effect, up to that +// frame Plus the new MaxAhead, represent a "period of vulnerability"; any +// events received that are scheduled to execute during this period should +// be re-scheduled for after that period. +// +int NewMaxAheadFrame1; +int NewMaxAheadFrame2; +//#endif + +/*************************************************************************** +** This is the user-specified IPX address of a desired game owner machine. +** Use this to cross a bridge. Only the 1st 4 numbers in the address are +** used; the rest are set to ff's, for broadcasting. 'IsBridge' is set +** if this address should be used. +*/ +int IsBridge = 0; +IPXAddressClass BridgeNet; + + +/*************************************************************************** +** This flag is true if the user has requested that this game be "secret" +** (The game will not appear to other systems just starting up.) +*/ +bool NetStealth = false; + + +/*************************************************************************** +** If this flag is true, the user won't receive messages from any player +** other than those in his own game. It defaults to protected mode. +*/ +bool NetProtect = true; + + +/*************************************************************************** +** This flag indicates whether the game is "open" or not to other network players. +*/ +bool NetOpen = false; + + +/*************************************************************************** +** This string stores the game's network name. +** GameName does not include the "'s Game"; comparing GameName to +** PlayerName can determine if this player is the originator of the game. +*/ +char MPlayerGameName[MPLAYER_NAME_MAX]; + + +/*************************************************************************** +** These variables are for servicing the Global Channel. +*/ +GlobalPacketType GPacket; // Global Channel packet +int GPacketlen; // length of incoming packet +IPXAddressClass GAddress; // address of sender +unsigned short GProductID; // sender's Product ID + + +/*************************************************************************** +** This is the "meta-packet"; it's a bunch of events lumped together. +** The packet's size is IPX's max size (546), rounded down to accommodate +** the max number of events possible. +*/ +char *MetaPacket = 0; +int MetaSize = ((546 - sizeof(CommHeaderType)) / sizeof(EventClass) ) * sizeof(EventClass); + + +/*************************************************************************** +** This is the random-number seed; it's synchronized between systems for +** multiplayer games. +*/ +int Seed = 0; +long *RandSeedPtr; + + +/*************************************************************************** +** If this value is non-zero, use it as the random # seed instead; this should +** help reproduce some bugs. +*/ +int CustomSeed = 0; + +int WindowList[][8] = { +/* xbyte, ypixel, bytewid, pixelht, cursor color, bkgd color, cursor x, cursor y */ + + /* do not change the first 2 entries!! they are necc. to the system */ + {0,0,40,200,WHITE,BLACK,0,0}, /* screen window */ + {1,75,38,100,WHITE,BLACK,0,0}, /* DOS Error window */ + + // Tactical map. + {0, 0, 40, 200, WHITE,LTGREY,0,0}, + + // Initial menu window. + {12, 199-42, 16, 42, LTGREY, DKGREY, 0, 0}, + + // Sidebar clipping window. + {0,0,0,0,0,0,0,0}, + + // Scenario editor window. + {5, 30, 30, 140, 0, 0, 0, 0}, + + // Custom window. + {0, 0, 0, 0, 0, 0, 0, 0}, + +}; + + +/* X,Y,Item Width,Items High,Selected,Norm Color,Sel Color,zero */ +int MenuList[][8]={ + {1, 3, 12, 3, 0, WHITE, PINK, 0}, +}; + +GraphicBufferClass VisiblePage; +GraphicBufferClass HiddenPage; + +GraphicViewPortClass SeenBuff(&VisiblePage, 0,0,640,480); +GraphicBufferClass ModeXBuff; +GraphicViewPortClass HidPage(&HiddenPage, 0,0,640,480); +GraphicBufferClass SysMemPage(DEFAULT_SCREEN_WIDTH, 200, (void*)NULL); +int SoundOn; +CountDownTimerClass FrameTimer(BT_SYSTEM, 0L); +CountDownTimerClass DebugTimer(BT_SYSTEM, 0L); +CountDownTimerClass CountDownTimer(BT_SYSTEM, 0L); + +NewConfigType NewConfig; + +/*************************************************************************** +** This timer measures how long (in ticks) it takes to process the game's +** logic, with no packet processing or artificial delays. +*/ +TimerClass ProcessTimer; +int ProcessTicks; // accumulated ticks +int ProcessFrames; // # frames used to measure 'ProcessTicks' + + +/*************************************************************************** +** This flag is for popping up dialogs that call the main loop. +*/ +SpecialDialogType SpecialDialog = SDLG_NONE; + + +/* +** This flags if used to tell can enter cell that we are in a find path +** check and thus should not uncloak units via Can_Enter_Cell. +*/ +//bool IsFindPath = false; + + +/*************************************************************************** +** Globals for the network Dialogs. +*/ + +/* +** List of all games out there, & the address of the game's owner +*/ +DynamicVectorClass Games; + +/* +** List of names & addresses of all the players in the game I'm joining. +** This is the really critical list, since it's used to form connections with +** all other players in my game. It's updated when I get a response to my +** outgoing query, or when I get a query from another system in my game asking +** who I am. This double-insurance means that if any system knows about me, +** I know about them too. The only catch is that if the game is started very, +** very soon after a player joins, not everyone may know about him; to prevent +** this, a timer restriction is put on the New Game dialog's GO button. +*/ +DynamicVectorClass Players; + +char *DebugFname; // for stoopid debugging purposes +int DebugLine; // for stoopid debugging purposes +#ifdef DEMO +int RequiredCD = -2; +#else +int RequiredCD = -1; +#endif +int MouseInstalled; + +/* +** Certain options must be enabled by both a command-line option, and an +** an entry in an INI file. If this flag is 'true', those options have been +** enabled by the INI file. +*/ +int AreThingiesEnabled = false; + + +/* +** Pointer to windows timer object +** +** +*/ + +WinTimerClass *WindowsTimer=NULL; + + +/* +** Command line arguments +** +** +*/ +char * Argv[20]; //Pointers to command line arguments +int Argc; //Command line argument count + + +WWKeyboardClass Kbd; +int ScreenWidth=640; +int ScreenHeight=400; +WWMouseClass *WWMouse = NULL; +HANDLE hInstance; +int AllDone; +BOOL InMovie = FALSE; //Are we currently playing a VQ movie? +bool MMXAvailable = false; //Does this CPU support MMX extensions? +GetCDClass CDList; +bool GameStatisticsPacketSent; +bool ConnectionLost; + +TheaterType LastTheater = THEATER_NONE; + diff --git a/GOPTIONS.CPP b/GOPTIONS.CPP new file mode 100644 index 0000000..bf1e0ae --- /dev/null +++ b/GOPTIONS.CPP @@ -0,0 +1,587 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\goptions.cpv 2.17 16 Oct 1995 16:50:26 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : OPTIONS.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : June 8, 1994 * + * * + * Last Update : July 27, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * OptionsClass::Process -- Handles all the options graphic interface. * + * Draw_Caption -- Draws a caption on a dialog box. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#include "goptions.h" +#include "loaddlg.h" +#include "sounddlg.h" +#include "visudlg.h" +#include "gamedlg.h" +#include "textbtn.h" +#include "confdlg.h" +#include "descdlg.h" + +void GameOptionsClass::Adjust_Variables_For_Resolution(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + OptionWidth = (216+8) * factor; + OptionHeight = 100 * factor; + OptionX = ((SeenBuff.Get_Width() - OptionWidth) / 2); + OptionY = ((SeenBuff.Get_Height() - OptionHeight) / 2); + ButtonWidth = 130 * factor; + OButtonHeight = 9 * factor; + CaptionYPos = 5 * factor; + ButtonY = 21 * factor; + Border1Len = 72 * factor; + Border2Len = 16 * factor; + ButtonResumeY = (OptionHeight - (15 * factor)); +} +/*********************************************************************************************** + * OptionsClass::Process -- Handles all the options graphic interface. * + * * + * This routine is the main control for the visual representation of the options * + * screen. It handles the visual overlay and the player input. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: 12/31/1994 MML : Created. * + * 06/23/1995 JLB : Handles restating the mission objective. * + * 07/27/1995 JLB : Adjusts menu for multiplay mode. * + *=============================================================================================*/ +void GameOptionsClass::Process(void) +{ + static struct { + int ID; // Button ID to use. + int Text; // Text number to use for this button. + bool Multiplay; // Allowed in multiplayer version? + } _constants[] = { + {BUTTON_LOAD, TXT_LOAD_MISSION, false}, + {BUTTON_SAVE, TXT_SAVE_MISSION, false}, + {BUTTON_DELETE, TXT_DELETE_MISSION, true}, + {BUTTON_GAME, TXT_GAME_CONTROLS, true}, + {BUTTON_QUIT, TXT_QUIT_MISSION, true}, + {BUTTON_RESUME, TXT_RESUME_MISSION, true}, + {BUTTON_RESTATE, TXT_RESTATE_MISSION, false}, + }; + + /* + ** Variables. + */ + TextButtonClass * buttons = 0; + int selection; + bool pressed; + int curbutton = 6; + int y; + TextButtonClass *buttonsel[sizeof(_constants)/sizeof(_constants[0])]; + + Set_Logic_Page(SeenBuff); + + /* + ** Build the button list for all of the buttons for this dialog. + */ + int maxwidth = 0; + int resfactor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + for (int index = 0; index < sizeof(_constants)/sizeof(_constants[0]); index++ ) { + int text = _constants[index].Text; + buttonsel[index] = NULL; + + if (GameToPlay != GAME_NORMAL && !_constants[index].Multiplay) { + buttonsel[index] = 0; + continue; + } + + if (GameToPlay != GAME_NORMAL && text == TXT_DELETE_MISSION) { + text = TXT_RESIGN; + } + + if (index < 5) { + y = (SeenBuff.Get_Height() - OptionHeight)/2 + ButtonY + ((OButtonHeight+2) * index); + } else { + y = OptionY + ButtonResumeY; + } + + TextButtonClass * g = new TextButtonClass(_constants[index].ID, + text, TPF_6PT_GRAD|TPF_NOSHADOW, 0, y); + + if (g->Width > maxwidth) { + maxwidth = g->Width; + } + if (!buttons) { + buttons = g; + } else { + g->Add_Tail(*buttons); + } + + buttonsel[index] = g; + } + + buttonsel[curbutton-1]->Turn_On(); + + /* + ** Force all button lengths to match the maximum length of the widest button. + */ + GadgetClass * g = buttons; + while (g) { + g->Width = MAX(maxwidth, 90 * resfactor); + g->X = OptionX+(OptionWidth-g->Width)/2; + g = g->Get_Next(); + } +#ifdef FRENCH + buttonsel[BUTTON_RESUME-1]->Width = 104 *resfactor; +#else + buttonsel[BUTTON_RESUME-1]->Width = 90 *resfactor; +#endif + buttonsel[BUTTON_RESUME-1]->X = OptionX+(5 * resfactor); + + if (GameToPlay == GAME_NORMAL) { + buttonsel[BUTTON_RESTATE-1]->Width = 90 * resfactor; + buttonsel[BUTTON_RESTATE-1]->X = OptionX+OptionWidth-(buttonsel[BUTTON_RESTATE-1]->Width+(5 * resfactor)); + } + + /* + ** This causes left mouse button clicking within the confines of the dialog to + ** be ignored if it wasn't recognized by any other button or slider. + */ + (new GadgetClass(OptionX, OptionY, OptionWidth, OptionHeight, GadgetClass::LEFTPRESS))->Add_Tail(*buttons); + + /* + ** This cause a right click anywhere or a left click outside the dialog region + ** to be equivalent to clicking on the return to game button. + */ + (new ControlClass(BUTTON_RESUME, 0, 0, SeenBuff.Get_Width(), SeenBuff.Get_Height(), GadgetClass::LEFTPRESS|GadgetClass::RIGHTPRESS))->Add_Tail(*buttons); + + Keyboard::Clear(); + + Fancy_Text_Print(TXT_NONE, 0, 0, CC_GREEN, TBLACK, TPF_CENTER|TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW); + + /* + ** Main Processing Loop. + */ + bool display = true; + bool process = true; + pressed = false; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=TRUE; + } + + /* + ** Invoke game callback. + */ + if (GameToPlay == GAME_NORMAL) { + Call_Back(); + } else { + if (Main_Loop()) { + process = false; + } + } + + /* + ** Refresh display if needed. + */ + if (display) { + + /* + ** Redraw the map. + */ + HiddenPage.Clear(); + Map.Flag_To_Redraw(true); + Map.Render(); + + /* + ** Reset up the window. Window x-coords are in bytes not pixels. + */ + Set_Window(WINDOW_EDITOR, OptionX, OptionY, OptionWidth, OptionHeight); + Hide_Mouse(); + + /* + ** Draw the background. + */ + Window_Box (WINDOW_EDITOR, BOXSTYLE_GREEN_BORDER); // has border, raised up + + /* + ** Draw the arrows border if requested. + */ + Draw_Caption(TXT_OPTIONS, OptionX, OptionY, OptionWidth); + + /* + ** Display the version number at the bottom of the dialog box. + */ +#ifdef DEMO + Version_Number(); + Fancy_Text_Print("DEMO%s", + ((WindowList[WINDOW_EDITOR][WINDOWX]+WindowList[WINDOW_EDITOR][WINDOWWIDTH])<<3)-3*resfactor, + WindowList[WINDOW_EDITOR][WINDOWY]+WindowList[WINDOW_EDITOR][WINDOWHEIGHT]-((GameToPlay == GAME_NORMAL) ? (32*resfactor) : (24*resfactor)), + DKGREY, TBLACK, + TPF_6POINT|TPF_NOSHADOW|TPF_RIGHT, + ScenarioName, + VersionText); +#else + Fancy_Text_Print("%s\rV.%d%s", + ((WindowList[WINDOW_EDITOR][WINDOWX]+WindowList[WINDOW_EDITOR][WINDOWWIDTH])<<3)-3*resfactor, + WindowList[WINDOW_EDITOR][WINDOWY]+WindowList[WINDOW_EDITOR][WINDOWHEIGHT]-((GameToPlay == GAME_NORMAL) ? (32*resfactor) : (24*resfactor)), + DKGREY, TBLACK, + TPF_6POINT|TPF_NOSHADOW|TPF_RIGHT, + ScenarioName, + Version_Number(), + VersionText); +#endif + + buttons->Draw_All(); + TabClass::Hilite_Tab(0); + Show_Mouse(); + display = false; + } + + /* + ** Get user input. + */ + KeyNumType input = buttons->Input(); + + /* + ** Process Input. + */ + switch (input) { + case (BUTTON_RESTATE | KN_BUTTON): + selection = BUTTON_RESTATE; + pressed = true; + break; + + case (BUTTON_LOAD | KN_BUTTON): + selection = BUTTON_LOAD; + pressed = true; + break; + + case (BUTTON_SAVE | KN_BUTTON): + selection = BUTTON_SAVE; + pressed = true; + break; + + case (BUTTON_DELETE | KN_BUTTON): + selection = BUTTON_DELETE; + pressed = true; + break; + + case (BUTTON_QUIT | KN_BUTTON): + selection = BUTTON_QUIT; + pressed = true; + break; + + case (BUTTON_GAME | KN_BUTTON): + selection = BUTTON_GAME; + pressed = true; + break; + + case (KN_ESC): + case (BUTTON_RESUME | KN_BUTTON): + selection = BUTTON_RESUME; + pressed = true; + break; + + case (KN_UP): + buttonsel[curbutton-1]->Turn_Off(); + buttonsel[curbutton-1]->Flag_To_Redraw(); + curbutton--; + if (GameToPlay == GAME_NORMAL) { + if (curbutton < BUTTON_LOAD) { + curbutton = (BUTTON_COUNT - 1); + } + } else { + if (curbutton < BUTTON_DELETE) { + curbutton = BUTTON_RESUME; +// curbutton = (BUTTON_COUNT-1); + } + } + buttonsel[curbutton-1]->Turn_On(); + buttonsel[curbutton-1]->Flag_To_Redraw(); + break; + + case (KN_DOWN): + buttonsel[curbutton-1]->Turn_Off(); + buttonsel[curbutton-1]->Flag_To_Redraw(); + curbutton++; + if (GameToPlay == GAME_NORMAL) { + if (curbutton >= BUTTON_COUNT) { + curbutton = BUTTON_LOAD; + } + } else { + if (curbutton > BUTTON_RESUME) { + curbutton = BUTTON_DELETE; + } + } + buttonsel[curbutton-1]->Turn_On(); + buttonsel[curbutton-1]->Flag_To_Redraw(); + break; + + case (KN_RETURN): + buttonsel[curbutton-1]->IsPressed = true; + buttonsel[curbutton-1]->Draw_Me(true); + selection = curbutton; + pressed = true; + break; + + default: + break; + } + + if (pressed) { + buttonsel[curbutton-1]->Turn_Off(); + buttonsel[curbutton-1]->Flag_To_Redraw(); + curbutton = selection; + buttonsel[curbutton-1]->Turn_On(); + buttonsel[curbutton-1]->Flag_To_Redraw(); + + switch (selection) { + case BUTTON_RESTATE: + display = true; +#ifdef JAPANESE + if (!Restate_Mission(ScenarioName, TXT_VIDEO, TXT_TAB_BUTTON_CONTROLS)) { +#else + if (!Restate_Mission(ScenarioName, TXT_VIDEO, TXT_OPTIONS)) { +#endif + BreakoutAllowed = true; + char buffer[25]; + sprintf(buffer, "%s.VQA", BriefMovie); + if (CCFileClass(buffer).Is_Available()) { + Play_Movie(BriefMovie); + } else { + Play_Movie(ActionMovie); + } + //BreakoutAllowed = false; + memset(BlackPalette, 0x01, 768); + Set_Palette(BlackPalette); + memset(BlackPalette, 0x00, 768); + Set_Palette(BlackPalette); + Map.Flag_To_Redraw(true); + Theme.Queue_Song(THEME_PICK_ANOTHER); + process = false; + } + break; + + case (BUTTON_LOAD): + display = true; + if (LoadOptionsClass(LoadOptionsClass::LOAD).Process()) { + process = false; + } + break; + + case (BUTTON_SAVE): + display = true; + LoadOptionsClass(LoadOptionsClass::SAVE).Process(); + break; + + case (BUTTON_DELETE): + display = true; + if (GameToPlay != GAME_NORMAL) { + if (Surrender_Dialog()) { + OutList.Add(EventClass(EventClass::DESTRUCT)); + } + process = false; + } else { + LoadOptionsClass(LoadOptionsClass::WWDELETE).Process(); + } + break; + + case (BUTTON_QUIT): + if (GameToPlay == GAME_NORMAL) { +#ifdef JAPANESE + switch (CCMessageBox().Process(TXT_CONFIRM_EXIT, TXT_YES, TXT_NO, TXT_RESTART)) { +#else + switch (CCMessageBox().Process(TXT_CONFIRM_EXIT, TXT_ABORT, TXT_CANCEL, TXT_RESTART)) { +#endif + case 2: + display = true; + break; + + case 0: + process = false; + Queue_Exit(); + break; + + case 1: + PlayerRestarts = true; + process = false; + break; + } + } else { + if (ConfirmationClass().Process(TXT_CONFIRM_EXIT)) { + process = false; + Queue_Exit(); + } else { + display = true; + } + } + break; + + case (BUTTON_GAME): + display = true; + GameControlsClass().Process(); + break; + + case (BUTTON_RESUME): + //Save_Settings(); + process = false; + display = true; + break; + } + + pressed = false; + buttonsel[curbutton-1]->IsPressed = false; + //buttonsel[curbutton-1]->Turn_Off(); + buttonsel[curbutton-1]->Turn_On(); + buttonsel[curbutton-1]->Flag_To_Redraw(); + } + } + + /* + ** Clean up and re-enter the game. + */ + buttons->Delete_List(); + + /* + ** Redraw the map. + */ + Keyboard::Clear(); + Call_Back(); + HiddenPage.Clear(); + Call_Back(); + Map.Flag_To_Redraw(true); + Map.Render(); +} + + +/*********************************************************************************************** + * Draw_Caption -- Draws a caption on a dialog box. * + * * + * This routine draws the caption text and any fancy filigree that the dialog may require. * + * * + * INPUT: text -- The text of the caption. This is the text number. * + * * + * x,y -- The dialog box X and Y pixel coordinate of the upper left corner. * + * * + * w -- The width of the dialog box (in pixels). * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/23/1995 JLB : Created. * + *=============================================================================================*/ +void Draw_Caption(int text, int x, int y, int w) +{ + OptionControlType option = OPTION_NONE; + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + + /* + ** Determine the filigree to use depending on the text of the caption. + */ + switch (text) { + case TXT_GAME_CONTROLS: + case TXT_OPTIONS: + option = OPTION_CONTROLS; + break; + + case TXT_LOAD_MISSION: + case TXT_SAVE_MISSION: + case TXT_DELETE_MISSION: + option = OPTION_DELETE; + break; + + case TXT_NONE: + case TXT_MODEM_SERIAL: + case TXT_SELECT_MPLAYER_GAME: + case TXT_SELECT_SERIAL_GAME: + option = OPTION_DIALOG; + break; + + case TXT_HOST_SERIAL_GAME: + case TXT_JOIN_SERIAL_GAME: + option = OPTION_SERIAL; + break; + + case TXT_SETTINGS: + case TXT_PHONE_LIST: + case TXT_PHONE_LISTING: + option = OPTION_PHONE; + break; + + case TXT_JOIN_NETWORK_GAME: + option = OPTION_JOIN_NETWORK; + break; + + case TXT_NETGAME_SETUP: + option = OPTION_NETWORK; + break; + + case TXT_VISUAL_CONTROLS: + option = OPTION_VISUAL; + break; + + case TXT_SOUND_CONTROLS: + option = OPTION_SOUND; + break; + + default: + option = OPTION_DIALOG; + break; + } + + /* + ** Draw the filigree at the corners of the dialog. + */ + if (option != OPTION_NONE) { + CC_Draw_Shape(MixFileClass::Retrieve("OPTIONS.SHP"), (int)option, x+12, y+11, WINDOW_MAIN, SHAPE_CENTER); + CC_Draw_Shape(MixFileClass::Retrieve("OPTIONS.SHP"), (int)option+1, x+w-14, y+11, WINDOW_MAIN, SHAPE_CENTER); + } + + /* + ** Draw the caption. + */ + if (text != TXT_NONE) { + Fancy_Text_Print(text, w/2 + x, 5*factor + y, CC_GREEN, TBLACK, TPF_CENTER|TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW); + + int length = String_Pixel_Width(Text_String(text)); + LogicPage->Draw_Line((x+(w/2))-(length/2), y+FontHeight+FontYSpacing + 5*factor, (x+(w/2))+(length/2), y+FontHeight+FontYSpacing + 5*factor, CC_GREEN); + } +} diff --git a/GOPTIONS.H b/GOPTIONS.H new file mode 100644 index 0000000..9de8a0b --- /dev/null +++ b/GOPTIONS.H @@ -0,0 +1,100 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\goptions.h_v 2.19 16 Oct 1995 16:46:26 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : OPTIONS.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : June 8, 1994 * + * * + * Last Update : June 8, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef GOPTIONS_H +#define GOPTIONS_H + +#include "options.h" +#include "gadget.h" + + +class GameOptionsClass : public OptionsClass { + enum GameOptionsButtonEnum { + BUTTON_LOAD=1, + BUTTON_SAVE, + BUTTON_DELETE, + BUTTON_GAME, + BUTTON_QUIT, + BUTTON_RESUME, + BUTTON_RESTATE, + + BUTTON_COUNT, + }; + + enum GameOptionsEnum { + #if(0) + + OPTION_WIDTH=(216+8), + OPTION_HEIGHT=100, + OPTION_X=((320 - OPTION_WIDTH) / 2), + OPTION_Y=((200 - OPTION_HEIGHT) / 2), +#ifdef FRENCH + BUTTON_WIDTH=142, +#else + BUTTON_WIDTH=130, +#endif +// OBUTTON_HEIGHT=13, + NUMBER_OF_BUTTONS=6, + CAPTION_Y_POS=5, + BUTTON_Y=21, + BORDER1_LEN=72, + BORDER2_LEN=16, + BUTTON_RESUME_Y=(OPTION_HEIGHT-15) + + #endif + }; + + public: + GameOptionsClass(void): OptionsClass () { }; + void Adjust_Variables_For_Resolution(void); + void Process(void); + + private: + int OptionWidth; + int OptionHeight; + int OptionX; + int OptionY; + int ButtonWidth; + int OButtonHeight; + int CaptionYPos; + int ButtonY; + int Border1Len; + int Border2Len; + int ButtonResumeY; +}; + +#endif diff --git a/GSCREEN.CPP b/GSCREEN.CPP new file mode 100644 index 0000000..3c5c5ae --- /dev/null +++ b/GSCREEN.CPP @@ -0,0 +1,529 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\gscreen.cpv 2.17 16 Oct 1995 16:51:34 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : GSCREEN.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/15/94 * + * * + * Last Update : January 19, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * GScreenClass::GScreenClass -- Default constructor for GScreenClass. * + * GScreenClass::One_Time -- Handles one time class setups. * + * GScreenClass::Init -- Init's the entire display hierarchy by calling all Init routines. * + * GScreenClass::Init_Clear -- Sets the map to a known state. * + * GScreenClass::Init_Theater -- Performs theater-specific initializations. * + * GScreenClass::Init_IO -- Initializes the Button list ('Buttons'). * + * GScreenClass::Flag_To_Redraw -- Flags the display to be redrawn. * + * GScreenClass::Blit_Display -- Redraw the display from the hidpage to the seenpage. * + * GScreenClass::Render -- General drawing dispatcher an display update function. * + * GScreenClass::Input -- Fetches input and processes gadgets. * + * GScreenClass::Add_A_Button -- Add a gadget to the game input system. * + * GScreenClass::Remove_A_Button -- Removes a gadget from the game input system. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#include + +GadgetClass * GScreenClass::Buttons = 0; + +GraphicBufferClass * GScreenClass::ShadowPage = 0; + + +/*********************************************************************************************** + * GScreenClass::GScreenClass -- Default constructor for GScreenClass. * + * * + * This constructor merely sets the display system, so that it will redraw the first time * + * the render function is called. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/15/1994 JLB : Created. * + *=============================================================================================*/ +GScreenClass::GScreenClass(void) +{ + IsToUpdate = true; + IsToRedraw = true; +} + + +/*********************************************************************************************** + * GScreenClass::One_Time -- Handles one time class setups. * + * * + * This routine (and all those that overload it) must perform truly one-time initialization. * + * Such init's would normally be done in the constructor, but other aspects of the game may * + * not have been initialized at the time the constructors are called (such as the file system, * + * the display, or other WWLIB subsystems), so many initializations should be deferred to the * + * One_Time init's. * + * * + * Any variables set in this routine should be declared as static, so they won't be modified * + * by the load/save process. Non-static variables will be over-written by a loaded game. * + * * + * This function allocates the shadow buffer that is used for quick screen updates. If * + * there were any data files to load, they would be loaded at this time as well. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Call this routine only ONCE at the beginning of the game. * + * * + * HISTORY: * + * 12/15/1994 JLB : Created. * + *=============================================================================================*/ +void GScreenClass::One_Time(void) +{ + /* + ** Allocate the screen shadow page. This page is used to reduce access to the + ** actual screen memory. It contains a duplicate of what the SEENPAGE is. + */ + Buttons = 0; + ShadowPage = new GraphicBufferClass(320,200); + if (ShadowPage) { + ShadowPage->Clear(); + HiddenPage.Clear(); + } +} + + +/*********************************************************************************************** + * GScreenClass::Init -- Init's the entire display hierarchy by calling all Init routines. * + * * + * This routine shouldn't be overloaded. It's the main map initialization routine, and will * + * perform a complete map initialization, from mixfiles to clearing the buffers. Calling this * + * routine results in calling every initialization routine in the entire map hierarchy. * + * * + * INPUT: * + * theater theater to initialize to * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/28/1994 BR : Created. * + *=============================================================================================*/ +void GScreenClass::Init(TheaterType theater) +{ + Init_Clear(); + Init_IO(); + Init_Theater(theater); +} + + +/*********************************************************************************************** + * GScreenClass::Init_Clear -- Sets the map to a known state. * + * * + * This routine (and those that overload it) clears any buffers and variables to a known * + * state. It assumes that all buffers are allocated & valid. The map should be displayable * + * after calling this function, and should draw basically an empty display. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/28/1994 BR : Created. * + *=============================================================================================*/ +void GScreenClass::Init_Clear(void) +{ + /* + ** Clear the ShadowPage & HidPage to force a complete shadow blit. + */ + if (ShadowPage) { + ShadowPage->Clear(); + HiddenPage.Clear(); + } + + IsToRedraw = true; +} + + +/*********************************************************************************************** + * GScreenClass::Init_Theater -- Performs theater-specific initializations. * + * * + * This routine (and those that overload it) performs any theater-specific initializations * + * needed. This will include setting the palette, setting up remap tables, etc. This routine * + * only needs to be called when the theater has changed. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/28/1994 BR : Created. * + *=============================================================================================*/ +void GScreenClass::Init_Theater(TheaterType ) +{ +} + + +/*********************************************************************************************** + * GScreenClass::Init_IO -- Initializes the Button list ('Buttons'). * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/28/1994 BR : Created. * + *=============================================================================================*/ +void GScreenClass::Init_IO(void) +{ + /* + ** Reset the button list. This means that any other elements of the map that need + ** buttons must attach them after this routine is called! + */ + Buttons = 0; + +} + + +/*********************************************************************************************** + * GScreenClass::Flag_To_Redraw -- Flags the display to be redrawn. * + * * + * This function is used to flag the display system whether any rendering is needed. The * + * parameter tells the system either to redraw EVERYTHING, or just that something somewhere * + * has changed and the individual Draw_It functions must be called. When a sub system * + * determines that it needs to render something local to itself, it would call this routine * + * with a false parameter. If the entire screen gets trashed or needs to be rebuilt, then * + * this routine will be called with a true parameter. * + * * + * INPUT: complete -- bool; Should the ENTIRE screen be redrawn? * + * * + * OUTPUT: none * + * * + * WARNINGS: This doesn't actually draw the screen, it merely sets flags so that when the * + * Render() function is called, the appropriate drawing steps will be performed. * + * * + * HISTORY: * + * 12/15/1994 JLB : Created. * + *=============================================================================================*/ +void GScreenClass::Flag_To_Redraw(bool complete) +{ + IsToUpdate = true; + if (complete) { + IsToRedraw = true; + } +} + + +/*********************************************************************************************** + * GScreenClass::Input -- Fetches input and processes gadgets. * + * * + * This routine will fetch the keyboard/mouse input and dispatch this through the gadget * + * system. * + * * + * INPUT: key -- Reference to the key code (for future examination). * + * * + * x,y -- Reference to mouse coordinates (for future examination). * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void GScreenClass::Input(KeyNumType & key, int & x, int & y) +{ + key = Keyboard::Check(); + + x = Keyboard::Mouse_X(); + y = Keyboard::Mouse_Y(); + + if (Buttons) { + + /* + ** If any buttons need redrawing, they will do so in the Input routine, and + ** they should draw themselves to the HidPage. So, flag ourselves for a Blit + ** to show the newly drawn buttons. + */ + if (Buttons->Is_List_To_Redraw()) { + Flag_To_Redraw(false); + } + + GraphicViewPortClass * oldpage= Set_Logic_Page(HidPage); + + key = Buttons->Input(); + + Set_Logic_Page(oldpage); + + } else { + if (key) { + key = Keyboard::Get(); + } + } + AI(key, x, y); + +} + + +/*********************************************************************************************** + * GScreenClass::Add_A_Button -- Add a gadget to the game input system. * + * * + * This will add a gadget to the game input system. The gadget will be processed in * + * subsiquent calls to the GScreenClass::Input() function. * + * * + * INPUT: gadget -- Reference to the gadget that will be added to the input system. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void GScreenClass::Add_A_Button(GadgetClass & gadget) +{ + /*------------------------------------------------------------------------ + If this gadget is already in the list, remove it before adding it in: + - If 1st gadget in list, use Remove_A_Button to remove it, to reset the + value of 'Buttons' appropriately + - Otherwise, just call the Remove function for that gadget to remove it + from any list it may be in + ------------------------------------------------------------------------*/ + if (Buttons == &gadget) { + Remove_A_Button(gadget); + } else { + gadget.Remove(); + } + + /*------------------------------------------------------------------------ + Now add the gadget to our list: + - If there are not buttons, start the list with this one + - Otherwise, add it to the tail of the existing list + ------------------------------------------------------------------------*/ + if (Buttons) { + gadget.Add_Tail(*Buttons); + } else { + Buttons = &gadget; + } +} + + +/*********************************************************************************************** + * GScreenClass::Remove_A_Button -- Removes a gadget from the game input system. * + * * + * INPUT: gadget -- Reference to the gadget that will be removed from the input system. * + * * + * OUTPUT: none * + * * + * WARNINGS: 'gadget' MUST be already a part of 'Buttons', or the new value of 'Buttons' * + * will be invalid! * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void GScreenClass::Remove_A_Button(GadgetClass & gadget) +{ + Buttons = gadget.Remove(); +} + + +/*********************************************************************************************** + * GScreenClass::Render -- General drawing dispatcher an display update function. * + * * + * This routine should be called in the main game loop (once every game frame). It will * + * call the Draw_It() function if necessary. All rendering is performed to the LogicPage * + * which is set to the HIDPAGE. After rendering has been performed, the HIDPAGE is * + * copied to the visible page. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This actually updates the graphic display. As a result it can take quite a * + * while to perform. * + * * + * HISTORY: * + * 12/15/1994 JLB : Created. * + *=============================================================================================*/ +void GScreenClass::Render(void) +{ + //if (Buttons && Buttons->Is_List_To_Redraw()) { + // IsToRedraw = true; + //} + + + if (IsToUpdate || IsToRedraw) { + + //WWMouse->Erase_Mouse(&HidPage, TRUE); + GraphicViewPortClass * oldpage= Set_Logic_Page(HidPage); + + //if (IsToRedraw) { + // Hide_Mouse(); + // SeenBuff.To_Buffer(0, 0, 320, 200, ShadowPage); + // Show_Mouse(); + //} + Draw_It(IsToRedraw); + + if (Buttons) Buttons->Draw_All(false); + +#ifdef SCENARIO_EDITOR + /* + ** Draw the Editor's buttons + */ + if (Debug_Map) { + if (Buttons) { + Buttons->Draw_All(); + } + } +#endif + /* + ** Draw the multiplayer message system to the Hidpage at this point. + ** This way, they'll Blit along with the rest of the map. + */ + if (Messages.Num_Messages() > 0) { + Messages.Set_Width(Lepton_To_Cell(Map.TacLeptonWidth) * ICON_PIXEL_W); + } + Messages.Draw(); + + Blit_Display(); + IsToUpdate = false; + IsToRedraw = false; + + Set_Logic_Page(oldpage); + } +} + + + +#ifdef CHEAT_KEYS + +#define MAX_SCREENS_SAVED 30*15 // Enough for 30 seconds @ 15 fps + +GraphicBufferClass *ScreenList[MAX_SCREENS_SAVED]; +int CurrentScreen = 0; +bool ScreenRecording = false; + +void Add_Current_Screen(void) +{ + if (ScreenRecording){ + ScreenList[CurrentScreen] = new GraphicBufferClass; + ScreenList[CurrentScreen]->Init ( SeenBuff.Get_Width(), SeenBuff.Get_Height(), NULL, 0, (GBC_Enum) 0); + SeenBuff.Blit (*ScreenList[CurrentScreen]); + + CurrentScreen++; + + if (CurrentScreen == MAX_SCREENS_SAVED){ + + char filename[20]; + for (int i = 0 ; i < MAX_SCREENS_SAVED ; i++){ + sprintf (filename, "SCRN%04d.PCX", i); + Write_PCX_File (filename,*ScreenList[i], (unsigned char *)CurrentPalette); + delete ScreenList[i]; + } + + CurrentScreen = 0; + ScreenRecording = 0; + + } + } +} + +#endif //CHEAT_KEYS + + +extern bool CanVblankSync; + +/*********************************************************************************************** + * GScreenClass::Blit_Display -- Redraw the display from the hidpage to the seenpage. * + * * + * This routine is used to copy the correct display from the HIDPAGE * + * to the SEENPAGE. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/14/1994 JLB : Created. * + * 05/01/1994 JLB : Converted to member function. * + *=============================================================================================*/ +void GScreenClass::Blit_Display(void) +{ + if (SeenBuff.Get_Width()!=320){ +#if (0) + if (HidPage.Get_IsDirectDraw() && (Options.GameSpeed >1 || Options.ScrollRate==6 && CanVblankSync) ){ + WWMouse->Draw_Mouse(&HidPage); + SeenBuff.Get_Graphic_Buffer()->Get_DD_Surface()->Flip(NULL , DDFLIP_WAIT); + SeenBuff.Blit (HidPage , 0 , 0 , 0 , 0 , SeenBuff.Get_Width() , SeenBuff.Get_Height() , (BOOL) FALSE ); +#ifdef CHEAT_KEYS + Add_Current_Screen(); +#endif + //HidPage.Blit ( SeenBuff , 0 , 0 , 0 , 0 , HidPage.Get_Width() , HidPage.Get_Height() , (BOOL) FALSE ); + WWMouse->Erase_Mouse(&HidPage, FALSE); + }else{ +#else //(0) + WWMouse->Draw_Mouse(&HidPage); + HidPage.Blit ( SeenBuff , 0 , 0 , 0 , 0 , HidPage.Get_Width() , HidPage.Get_Height() , (BOOL) FALSE ); +#ifdef CHEAT_KEYS + Add_Current_Screen(); +#endif + WWMouse->Erase_Mouse(&HidPage, FALSE); +#endif //(0) +#if (0) + } +#endif //(0) + + } else { + ModeX_Blit (&HiddenPage); + } + +} + + + + + diff --git a/GSCREEN.H b/GSCREEN.H new file mode 100644 index 0000000..5dd4cb2 --- /dev/null +++ b/GSCREEN.H @@ -0,0 +1,141 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\gscreen.h_v 2.17 16 Oct 1995 16:45:20 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : GSCREEN.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/15/94 * + * * + * Last Update : December 15, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef GSCREEN_H +#define GSCREEN_H + +#include "function.h" +#include "cell.h" + +class GScreenClass : public VectorClass +{ + public: + + GScreenClass(void); + + /* + ** Initialization + */ + virtual void One_Time(void); // One-time initializations + virtual void Init(TheaterType = THEATER_NONE); // Inits everything + virtual void Init_Clear(void); // Clears all to known state + virtual void Init_IO(void); // Inits button list + virtual void Init_Theater(TheaterType theater); // Theater-specific inits + + /* + ** Player I/O is routed through here. It is called every game tick. + */ + virtual void Input(KeyNumType & key, int & x, int & y); + virtual void AI(KeyNumType &, int, int) {}; + virtual void Add_A_Button(GadgetClass & gadget); + virtual void Remove_A_Button(GadgetClass & gadget); + + /* + ** Called when map needs complete updating. + */ + virtual void Flag_To_Redraw(bool complete=false); + + /* + ** Render maintenance routine (call every game tick). Probably no need + ** to override this in derived classes. + */ + virtual void Render(void); + + /* + ** Is called when actual drawing is required. This is the function to + ** override in derived classes. + */ + virtual void Draw_It(bool =false) {}; + + /* + ** This moves the hidpage up to the seenpage. + */ + static void Blit_Display(void); + + /* + ** Changes the mouse shape as indicated. + */ + virtual void Set_Default_Mouse(MouseType mouse, bool wwsmall) = 0; + virtual bool Override_Mouse_Shape(MouseType mouse, bool wwsmall) = 0; + virtual void Revert_Mouse_Shape(void) = 0; + virtual void Mouse_Small(bool wwsmall) = 0; + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /* + ** Misc routines. + */ + virtual void * Shadow_Address(void) {return(ShadowPage);}; + + /* + ** This points to the buttons that are used for input. All of the derived classes will + ** attached their specific buttons to this list. + */ + static GadgetClass * Buttons; + + private: + + /* + ** If the entire map is required to redraw, then this flag is true. This flag + ** is set by the Flag_To_Redraw function. Typically, this occurs when the screen + ** has been trashed or is first created. + */ + unsigned IsToRedraw:1; + + /* + ** If only a sub-system of the map must be redrawn, then this flag will be set. + ** An example of something that would set this flag would be an animating icon + ** in the sidebar. In such a case, complete redrawing of the entire display is not + ** necessary, but the Draw_It function should still be called so that the appropriate + ** class can perform it's rendering. + */ + unsigned IsToUpdate:1; + + /* + ** Pointer to an exact copy of the visible graphic page. This copy is used to speed + ** display rendering by using an only-update-changed-pixels algorithm. + */ + public: + static GraphicBufferClass * ShadowPage; + private: +}; + +#endif diff --git a/HDATA.CPP b/HDATA.CPP new file mode 100644 index 0000000..0057d3a --- /dev/null +++ b/HDATA.CPP @@ -0,0 +1,318 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\hdata.cpv 2.17 16 Oct 1995 16:48:18 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : HDATA.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 22, 1994 * + * * + * Last Update : January 23, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * HouseTypeClass::From_Name -- Fetch house pointer from its name. * + * HouseTypeClass::As_Reference -- Fetches a reference to the house specified. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +/* +** These are the colors used to identify the various owners. +*/ +const int COLOR_GOOD = 180; // GOLD +const int COLOR_BRIGHT_GOOD = 176; // GOLD +const int COLOR_BAD = 123; //RED; +const int COLOR_BRIGHT_BAD = 127; //RED; +const int COLOR_NEUTRAL = 205; //WHITE; +const int COLOR_BRIGHT_NEUTRAL = 202; //WHITE; + + +static HouseTypeClass const HouseGood( + HOUSE_GOOD, + "GoodGuy", // NAME: House name. + TXT_GDI, // FULLNAME: Translated house name. + "GDI", // SUFFIX: House file suffix. + 0, // LEMON: Lemon vehicle frequency. + COLOR_GOOD, // COLOR: Dark Radar map color. + COLOR_BRIGHT_GOOD, // COLOR: Bright Radar map color. + REMAP_YELLOW, // Remap color ID number. + RemapYellow, // Default remap table. + 'G' // VOICE: Voice prefix character. +); + +static HouseTypeClass const HouseBad( + HOUSE_BAD, + "BadGuy", // NAME: House name. + TXT_NOD, // FULLNAME: Translated house name. + "NOD", // SUFFIX: House file suffix. + 0, // LEMON: Lemon vehicle frequency. + COLOR_BAD, // COLOR: Dark Radar map color. + COLOR_BRIGHT_BAD, // COLOR: Bright Radar map color. + REMAP_BLUE, // Remap color ID number. + RemapBlue, // Default remap table. + 'B' // VOICE: Voice prefix character. +); + +static HouseTypeClass const HouseCivilian( + HOUSE_NEUTRAL, + "Neutral", // NAME: House name. + TXT_CIVILIAN, // FULLNAME: Translated house name. + "CIV", // SUFFIX: House file suffix. + 0, // LEMON: Lemon vehicle frequency. + COLOR_NEUTRAL, // COLOR: Dark Radar map color. + COLOR_BRIGHT_NEUTRAL, // COLOR: Bright Radar map color. + REMAP_YELLOW, // Remap color ID number. + RemapNone, // Default remap table. + 'C' // VOICE: Voice prefix character. +); + +static HouseTypeClass const HouseJP( + HOUSE_JP, + "Special", // NAME: House name. + TXT_JP, // FULLNAME: Translated house name. + "JP", // SUFFIX: House file suffix. + 0, // LEMON: Lemon vehicle frequency. + COLOR_NEUTRAL, // COLOR: Dark Radar map color. + COLOR_BRIGHT_NEUTRAL, // COLOR: Bright Radar map color. + REMAP_YELLOW, // Remap color ID number. + RemapNone, // Default remap table. + 'J' // VOICE: Voice prefix character. +); + +static HouseTypeClass const HouseMulti1( + HOUSE_MULTI1, + "Multi1", // NAME: House name. + TXT_CIVILIAN, // FULLNAME: Translated house name. + "MP1", // SUFFIX: House file suffix. + 0, // LEMON: Lemon vehicle frequency. + COLOR_NEUTRAL, // COLOR: Radar map color. + COLOR_BRIGHT_NEUTRAL, // COLOR: Bright Radar map color. + REMAP_AQUA, // Remap color ID number. + RemapBlueGreen, // Default remap table. + 'M' // VOICE: Voice prefix character. +); + +static HouseTypeClass const HouseMulti2( + HOUSE_MULTI2, + "Multi2", // NAME: House name. + TXT_CIVILIAN, // FULLNAME: Translated house name. + "MP2", // SUFFIX: House file suffix. + 0, // LEMON: Lemon vehicle frequency. + COLOR_NEUTRAL, // COLOR: Radar map color. + COLOR_BRIGHT_NEUTRAL, // COLOR: Bright Radar map color. + REMAP_ORANGE, // Remap color ID number. + RemapOrange, // Default remap table. + 'M' // VOICE: Voice prefix character. +); + +static HouseTypeClass const HouseMulti3( + HOUSE_MULTI3, + "Multi3", // NAME: House name. + TXT_CIVILIAN, // FULLNAME: Translated house name. + "MP3", // SUFFIX: House file suffix. + 0, // LEMON: Lemon vehicle frequency. + COLOR_NEUTRAL, // COLOR: Radar map color. + COLOR_BRIGHT_NEUTRAL, // COLOR: Bright Radar map color. + REMAP_GREEN, // Remap color ID number. + RemapGreen, // Default remap table. + 'M' // VOICE: Voice prefix character. +); + +static HouseTypeClass const HouseMulti4( + HOUSE_MULTI4, + "Multi4", // NAME: House name. + TXT_CIVILIAN, // FULLNAME: Translated house name. + "MP4", // SUFFIX: House file suffix. + 0, // LEMON: Lemon vehicle frequency. + COLOR_NEUTRAL, // COLOR: Radar map color. + COLOR_BRIGHT_NEUTRAL, // COLOR: Bright Radar map color. + REMAP_BLUE, // Remap color ID number. + RemapBlue, // Default remap table. + 'M' // VOICE: Voice prefix character. +); + +static HouseTypeClass const HouseMulti5( + HOUSE_MULTI5, + "Multi5", // NAME: House name. + TXT_CIVILIAN, // FULLNAME: Translated house name. + "MP5", // SUFFIX: House file suffix. + 0, // LEMON: Lemon vehicle frequency. + COLOR_NEUTRAL, // COLOR: Radar map color. + COLOR_BRIGHT_NEUTRAL, // COLOR: Bright Radar map color. + REMAP_YELLOW, // Remap color ID number. + RemapYellow, // Default remap table. + 'M' // VOICE: Voice prefix character. +); + +static HouseTypeClass const HouseMulti6( + HOUSE_MULTI6, + "Multi6", // NAME: House name. + TXT_CIVILIAN, // FULLNAME: Translated house name. + "MP6", // SUFFIX: House file suffix. + 0, // LEMON: Lemon vehicle frequency. + COLOR_NEUTRAL, // COLOR: Radar map color. + COLOR_BRIGHT_NEUTRAL, // COLOR: Bright Radar map color. + REMAP_RED, // Remap color ID number. + RemapRed, // Default remap table. + 'M' // VOICE: Voice prefix character. +); + +HouseTypeClass const * const HouseTypeClass::Pointers[HOUSE_COUNT] = { + &HouseGood, + &HouseBad, + &HouseCivilian, + &HouseJP, + &HouseMulti1, + &HouseMulti2, + &HouseMulti3, + &HouseMulti4, + &HouseMulti5, + &HouseMulti6, +}; + + +/*********************************************************************************************** + * HouseTypeClass::HouseTypeClass -- Constructor for house type objects. * + * * + * This is the constructor for house type objects. This object holds the constant data * + * for the house type. * + * * + * INPUT: house -- The ID number for this house type. * + * ini -- The INI name of this house. * + * fullname -- The text number representing the complete name of the house. * + * ext -- The filename extension used when loading data files. * + * lemon -- The percentage for objects of this ownership to be lemon. * + * remapc -- The remap color number to use. * + * color -- The radar color to use for this "house". * + * prefix -- A unique prefix letter used when building custom filenames. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/21/1994 JLB : Created. * + *=============================================================================================*/ +HouseTypeClass::HouseTypeClass(HousesType house, + char const * ini, + int fullname, + char const * ext, + int lemon, + int color, + int bright_color, + PlayerColorType remapcolor, + unsigned char const * remap, + char prefix) +{ + RemapTable = remap; + RemapColor = remapcolor; + House = house; + IniName = ini; + FullName = fullname; + strncpy(Suffix, ext, 3); + Suffix[3] = '\0'; + Lemon = lemon; + Color = color; + BrightColor = bright_color; + Prefix = prefix; +} + + +/*********************************************************************************************** + * HouseTypeClass::From_Name -- Fetch house pointer from its name. * + * * + * This routine will convert the ASCII house name specified into a * + * real house number. Typically, this is used when processing a * + * scenario INI file. * + * * + * INPUT: name -- ASCII name of house to process. * + * * + * OUTPUT: Returns with actual house number represented by the ASCII * + * name specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + * 05/21/1994 JLB : Converted to member function. * + *=============================================================================================*/ +HousesType HouseTypeClass::From_Name(char const *name) +{ + if (name) { + for (HousesType house = HOUSE_FIRST; house < HOUSE_COUNT; house++) { + if (stricmp(Pointers[house]->IniName, name) == 0) { + return(house); + } + } + } + return(HOUSE_NONE); +} + + +/*********************************************************************************************** + * HouseTypeClass::One_Time -- One-time initialization * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/21/1994 JLB : Converted to member function. * + *=============================================================================================*/ +void HouseTypeClass::One_Time(void) +{ + /* + ** Change the radar color for special units; otherwise, they'll be the same + ** color as the player! + */ + if (Special.IsJurassic && AreThingiesEnabled) { + ((unsigned char &)HouseJP.Color) = (unsigned char)COLOR_BAD; + ((unsigned char &)HouseJP.BrightColor) = (unsigned char)COLOR_BRIGHT_BAD; + } +} + + +/*********************************************************************************************** + * HouseTypeClass::As_Reference -- Fetches a reference to the house specified. * + * * + * Use this routine to fetch a reference to the house number specified. * + * * + * INPUT: house -- The house number (HousesType) to look up. * + * * + * OUTPUT: Returns with a reference to the HouseTypeClass object that matches the house * + * number specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +HouseTypeClass const & HouseTypeClass::As_Reference(HousesType house) +{ + return(*Pointers[house]); +} diff --git a/HEADER.MAC b/HEADER.MAC new file mode 100644 index 0000000..ea443db --- /dev/null +++ b/HEADER.MAC @@ -0,0 +1,20 @@ +%home%tof + +%home%tof/* $Header:$ */ +/*********************************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *********************************************************************************************** + * * + * Project Name : %qProject Name$%col96* + * * + * File Name : %-r%-e%col96* + * * + * Programmer : %eProgrammer$%col96* + * * + * Start Date : %date%col96* + * * + * Last Update : %date%col96* + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ diff --git a/HEAP.CPP b/HEAP.CPP new file mode 100644 index 0000000..db1555d --- /dev/null +++ b/HEAP.CPP @@ -0,0 +1,553 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\heap.cpv 2.18 16 Oct 1995 16:49:56 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : HEAP.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 02/18/95 * + * * + * Last Update : May 22, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * FixedIHeapClass::Free -- Frees an object in the heap. * + * FixedHeapClass::FixedHeapClass -- Normal constructor for heap management class. * + * FixedHeapClass::~FixedHeapClass -- Destructor for the heap manager class. * + * FixedHeapClass::Set_Heap -- Assigns a memory block for this heap manager. * + * FixedHeapClass::Allocate -- Allocate a sub-block from the heap. * + * FixedHeapClass::Free -- Frees a sub-block in the heap. * + * FixedHeapClass::ID -- Converts a pointer to a sub-block index number. * + * FixedHeapClass::Clear -- Clears (and frees) the heap manager memory. * + * TFixedIHeapClass::Save -- Saves all active objects * + * TFixedIHeapClass::Load -- Loads all active objects * + * TFixedIHeapClass::Code_Pointers -- codes pointers for every object, to prepare for save * + * TFixedIHeapClass::Decode_Pointers -- Decodes all object pointers, for after loading * + * FixedHeapClass::Free_All -- Frees all objects in the fixed heap. * + * FixedIHeapClass::Free_All -- Frees all objects out of the indexed heap. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + +#include "function.h" +#include "heap.h" +#include +#include +#include +#include +#include + + +/*********************************************************************************************** + * FixedHeapClass::FixedHeapClass -- Normal constructor for heap management class. * + * * + * This is the normal constructor used for the heap manager class. This initializes * + * the class but doesn't yet assign actual heap memory to this manager. That is handled * + * by the Set_Heap() function. * + * * + * INPUT: size -- The size of the individual sub-blocks in this heap. This value is * + * typically the size of some class or structure. * + * * + * OUTPUT: none * + * * + * WARNINGS: The heap must first be assigned a block of memory to manage before it can * + * be used. * + * * + * HISTORY: * + * 02/21/1995 JLB : Created. * + *=============================================================================================*/ +FixedHeapClass::FixedHeapClass(int size) +{ + Size = size; + Buffer = 0; + IsAllocated = false; + TotalCount = 0; + ActiveCount = 0; +} + + +/*********************************************************************************************** + * FixedHeapClass::~FixedHeapClass -- Destructor for the heap manager class. * + * * + * This is the default constructor for the heap manager class. It handles freeing the * + * memory assigned to this heap. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/21/1995 JLB : Created. * + *=============================================================================================*/ +FixedHeapClass::~FixedHeapClass(void) +{ + FixedHeapClass::Clear(); +} + + +/*********************************************************************************************** + * FixedHeapClass::Set_Heap -- Assigns a memory block for this heap manager. * + * * + * This routine is used to assign a memory heap to this object. A memory heap so assigned * + * will start with all sub-blocks unallocated. After this routine is called, normal * + * allocation and freeing may occur. This routine will allocate necessary memory if the * + * buffer parameter is NULL. * + * * + * INPUT: count -- The number of objects that this heap should manage. * + * * + * buffer -- Pointer to pre-allocated buffer that this manager will use. If this * + * parameter is NULL, then memory will be automatically allocated. * + * * + * OUTPUT: bool; Was the heap successfully initialized? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/21/1995 JLB : Created. * + *=============================================================================================*/ +int FixedHeapClass::Set_Heap(int count, void * buffer) +{ + /* + ** Clear out the old heap data. + */ + Clear(); + + /* + ** If there is no size to the objects in the heap, then this block memory + ** handler can NEVER function. Return with a failure condition. + */ + if (!Size) return(false); + + /* + ** If there is no count specified, then this indicates that the heap should + ** be disabled. + */ + if (!count) return(true); + + /* + ** Initialize the free boolean vector and the buffer for the actual + ** allocation objects. + */ + if (FreeFlag.Resize(count)) { + if (!buffer) { + buffer = new char[count * Size]; + if (!buffer) { + FreeFlag.Clear(); + return(false); + } + IsAllocated = true; + } + Buffer = buffer; + TotalCount = count; + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * FixedHeapClass::Allocate -- Allocate a sub-block from the heap. * + * * + * Finds the first available sub-block in the heap and returns a pointer to it. The sub- * + * block is marked as allocated by this routine. If there are no more sub-blocks * + * available, then this routine will return NULL. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the newly allocated sub-block. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/21/1995 JLB : Created. * + *=============================================================================================*/ +void * FixedHeapClass::Allocate(void) +{ + if (ActiveCount < TotalCount) { + int index = FreeFlag.First_False(); + + if (index != -1) { + ActiveCount++; + FreeFlag[index] = true; + return((*this)[index]); + } + } + return(0); +} + + +/*********************************************************************************************** + * FixedHeapClass::Free -- Frees a sub-block in the heap. * + * * + * Use this routine to free a previously allocated sub-block in the heap. * + * * + * INPUT: pointer -- A pointer to the sub-block to free. This is the same pointer that * + * was returned from the Allocate() function. * + * * + * OUTPUT: bool; Was the deallocation successful? Failure could indicate a pointer that * + * doesn't refer to this heap or a null pointer. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/21/1995 JLB : Created. * + *=============================================================================================*/ +int FixedHeapClass::Free(void * pointer) +{ + if (pointer && ActiveCount) { + int index = ID(pointer); + + if ((unsigned)index < TotalCount) { + if (FreeFlag[index]) { + ActiveCount--; + FreeFlag[index] = false; + return(true); + } + } + } + return(false); +} + + +/*********************************************************************************************** + * FixedHeapClass::ID -- Converts a pointer to a sub-block index number. * + * * + * Use this routine to convert a pointer (returned by Allocate) into the sub-block * + * index number. This index number can be used as a form of identifier for the block. * + * * + * INPUT: pointer -- A pointer to the sub-block to conver into an ID number. * + * * + * OUTPUT: Returns with the index (ID) number for the sub-block specified. This number will * + * range between 0 and the sub-block max -1. If -1 is returned, then the pointer * + * was invalid. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/21/1995 JLB : Created. * + *=============================================================================================*/ +int FixedHeapClass::ID(void const * pointer) +{ + if (pointer && Size) { + return((int)(((char *)pointer - (char *)Buffer) / Size)); + } + return(-1); +} + + +/*********************************************************************************************** + * FixedHeapClass::Clear -- Clears (and frees) the heap manager memory. * + * * + * This routine is used to bring the heap manager back into a non-functioning state. All * + * memory allocated by this manager is freeed. Any previous pointers to allocated blocks * + * from this heap are now invalid. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/21/1995 JLB : Created. * + *=============================================================================================*/ +void FixedHeapClass::Clear(void) +{ + /* + ** Free the old buffer (if present). + */ + if (Buffer && IsAllocated) { + delete[] Buffer; + } + Buffer = 0; + IsAllocated = false; + ActiveCount = 0; + TotalCount = 0; + FreeFlag.Clear(); +} + + +/*********************************************************************************************** + * FixedHeapClass::Free_All -- Frees all objects in the fixed heap. * + * * + * This routine will free all previously allocated objects out of the heap. Use this * + * routine to ensure that the heap is empty. * + * * + * INPUT: none * + * * + * OUTPUT: Was the heap successfully cleared of all objects? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1995 JLB : Created. * + *=============================================================================================*/ +int FixedHeapClass::Free_All(void) +{ + ActiveCount = 0; + FreeFlag.Reset(); + return(true); +} + + +///////////////////////////////////////////////////////////////////// + + +/*********************************************************************************************** + * FixedIHeapClass::Free_All -- Frees all objects out of the indexed heap. * + * * + * Use this routine to free all previously allocated objects in the heap. This routine will * + * also clear out the allocated object vector as well. * + * * + * INPUT: none * + * * + * OUTPUT: Was the heap successfully cleared of objects? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1995 JLB : Created. * + *=============================================================================================*/ +int FixedIHeapClass::Free_All(void) +{ + ActivePointers.Delete_All(); + return(FixedHeapClass::Free_All()); +} + + +void FixedIHeapClass::Clear(void) +{ + FixedHeapClass::Clear(); + ActivePointers.Clear(); +} + + +int FixedIHeapClass::Set_Heap(int count, void * buffer) +{ + Clear(); + if (FixedHeapClass::Set_Heap(count, buffer)) { + ActivePointers.Resize(count); + return(true); + } + return(false); +} + + +void * FixedIHeapClass::Allocate(void) +{ + void * ptr = FixedHeapClass::Allocate(); + if (ptr) { + ActivePointers.Add(ptr); + memset (ptr, 0, Size); + } + return(ptr); +} + + +/*********************************************************************************************** + * FixedIHeapClass::Free -- Frees an object in the heap. * + * * + * This routine is used to free an object in the heap. Freeing is accomplished by marking * + * the object's memory as free to be reallocated. The object is also removed from the * + * allocated object pointer vector. * + * * + * INPUT: pointer -- Pointer to the object that is to be removed from the heap. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/21/1995 JLB : Created. * + *=============================================================================================*/ +int FixedIHeapClass::Free(void * pointer) +{ + if (FixedHeapClass::Free(pointer)) { + ActivePointers.Delete(pointer); + } + return(false); +} + + +/*********************************************************************************************** + * TFixedIHeapClass::Save -- Saves all active objects * + * * + * INPUT: file file to write to * + * * + * OUTPUT: true = OK, false = error * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/15/1995 BRR : Created. * + *=============================================================================================*/ +template +int TFixedIHeapClass::Save(FileClass &file) +{ + int i; // loop counter + int idx; // object index + + /* + ** Save the number of instances of this class + */ + if (file.Write(&ActiveCount, sizeof(ActiveCount)) != sizeof(ActiveCount)) { + return(false); + } + + /* + ** Save each instance of this class + */ + for (i = 0; i < ActiveCount; i++) { + /* + ** Save the array index of the object, so it can be loaded back into the + ** same array location (so TARGET translations will work) + */ + idx = ID(Ptr(i)); + if (file.Write(&idx, sizeof(idx)) != sizeof(idx)) { + return(false); + } + + /* + ** Save the object itself + */ + if (!Ptr(i)->Save(file)) { + return(false); + } + } + + return(true); +} + + +/*********************************************************************************************** + * TFixedIHeapClass::Load -- Loads all active objects * + * * + * INPUT: file file to read from * + * * + * OUTPUT: true = OK, false = error * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/15/1995 BRR : Created. * + *=============================================================================================*/ +template +int TFixedIHeapClass::Load(FileClass &file) +{ + int i; // loop counter + int idx; // object index + T *ptr; // object pointer + int a_count; + + /* + ** Read the number of instances of this class + */ + if (file.Read(&a_count, sizeof(a_count)) != sizeof(a_count)) { + return(false); + } + + /* + ** Error if more objects than we can hold + */ + if (a_count > TotalCount) { + return(false); + } + + /* + ** Read each class instance + */ + for (i = 0; i < a_count; i++) { + /* + ** Read the object's array index + */ + if (file.Read (&idx, sizeof(idx)) != sizeof(idx)) { + return(false); + } + + /* + ** Get a pointer to the object, activate that object + */ + ptr = (T *)(*this)[idx]; + FreeFlag[idx] = true; + ActiveCount++; + ActivePointers.Add(ptr); + + /* + ** Load the object + */ + if (!ptr->Load(file)) { + return(false); + } + } + + return(true); +} + + +/*********************************************************************************************** + * TFixedIHeapClass::Code_Pointers -- codes pointers for every object, to prepare for save * + * * + * INPUT: file file to read from * + * * + * OUTPUT: true = OK, false = error * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/15/1995 BRR : Created. * + *=============================================================================================*/ +template +void TFixedIHeapClass::Code_Pointers(void) +{ + int i; + + for (i = 0; i < ActiveCount; i++) { + Ptr(i)->Code_Pointers(); + } +} + + +/*********************************************************************************************** + * TFixedIHeapClass::Decode_Pointers -- Decodes all object pointers, for after loading * + * * + * INPUT: file file to read from * + * * + * OUTPUT: true = OK, false = error * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/15/1995 BRR : Created. * + *=============================================================================================*/ +template +void TFixedIHeapClass::Decode_Pointers(void) +{ + int i; + + for (i = 0; i < ActiveCount; i++) { + Ptr(i)->Decode_Pointers(); + } +} diff --git a/HEAP.H b/HEAP.H new file mode 100644 index 0000000..19ef6a0 --- /dev/null +++ b/HEAP.H @@ -0,0 +1,191 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\heap.h_v 2.15 16 Oct 1995 16:47:08 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : HEAP.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 02/18/95 * + * * + * Last Update : February 18, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef HEAP_H +#define HEAP_H + +#include "vector.h" + +/************************************************************************** +** This is a block memory managment handler. It is used when memory is to +** be treated as a series of blocks of fixed size. This is similar to an +** array of integral types, but unlike such an array, the memory blocks +** are annonymous. This facilitates the use of this class when overloading +** the new and delete operators for a normal class object. +*/ +class FixedHeapClass +{ + public: + FixedHeapClass(int size); + virtual ~FixedHeapClass(void); + + int ID(void const * pointer); + int Count(void) {return ActiveCount;}; + int Length(void) {return TotalCount;}; + int Avail(void) {return TotalCount-ActiveCount;}; + + virtual int Set_Heap(int count, void * buffer=0); + virtual void * Allocate(void); + virtual void Clear(void); + virtual int Free(void * pointer); + virtual int Free_All(void); + + protected: + void * operator[](int index) {return ((char *)Buffer) + (index * Size);}; + + /* + ** If the memory block buffer was allocated by this class, then this flag + ** will be true. The block must be deallocated by this class if true. + */ + unsigned IsAllocated:1; + + /* + ** This is the size of each sub-block within the buffer. + */ + int Size; + + /* + ** This records the absolute number of sub-blocks in the buffer. + */ + int TotalCount; + + /* + ** This is the total blocks allocated out of the heap. This number + ** will never exceed Count. + */ + int ActiveCount; + + /* + ** Pointer to the heap's memory buffer. + */ + void * Buffer; + + /* + ** This is a boolean vector array of allocation flag bits. + */ + BooleanVectorClass FreeFlag; + + private: + // The assignment operator is not supported. + FixedHeapClass & operator = (FixedHeapClass const &); + + // The copy constructor is not supported. + FixedHeapClass(FixedHeapClass const &); +}; + + +/************************************************************************** +** This template serves only as an interface to the heap manager class. By +** using this template, the object pointers are automatically converted +** to the correct type without any code overhead. +*/ +template +class TFixedHeapClass : public FixedHeapClass +{ + public: + TFixedHeapClass(void) : FixedHeapClass(sizeof(T)) {}; + virtual ~TFixedHeapClass(void) {}; + + int ID(T const * pointer) {return FixedHeapClass::ID(pointer);}; + + virtual T * Alloc(void) {return (T*)FixedHeapClass::Allocate();}; + virtual int Free(T * pointer) {FixedHeapClass::Free(pointer);}; + + protected: + T & operator[](int index) {return *(((char *)Buffer) + (index * Size));}; +}; + + +/************************************************************************** +** This is a derivative of the fixed heap class. This class adds the +** ability to quickly iterate through the active (allocated) objects. Since the +** active array is a sequence of pointers, the overhead of this class +** is 4 bytes per potential allocated object (be warned). +*/ +class FixedIHeapClass : public FixedHeapClass +{ + public: + FixedIHeapClass(int size) : FixedHeapClass(size) {}; + virtual ~FixedIHeapClass(void) {}; + + virtual int Set_Heap(int count, void * buffer=0); + virtual void * Allocate(void); + virtual void Clear(void); + virtual int Free(void * pointer); + virtual int Free_All(void); + + virtual void * Active_Ptr(int index) {return ActivePointers[index];}; + + /* + ** This is an array of pointers to allocated objects. Using this array + ** to control iteration through the objects ensures a minimum of processing. + ** It also allows access to this array so that custom sorting can be + ** performed. + */ + DynamicVectorClass ActivePointers; +}; + + +/************************************************************************** +** This template serves only as an interface to the iteratable heap manager +** class. By using this template, the object pointers are automatically converted +** to the correct type without any code overhead. +*/ +template +class TFixedIHeapClass : public FixedIHeapClass +{ + public: + TFixedIHeapClass(void) : FixedIHeapClass(sizeof(T)) {}; + virtual ~TFixedIHeapClass(void) {}; + + int ID(T const * pointer) {return FixedIHeapClass::ID(pointer);}; + virtual T * Alloc(void) {return (T*)FixedIHeapClass::Allocate();}; + virtual int Free(T * pointer) {return FixedIHeapClass::Free(pointer);}; + virtual int Free(void * pointer) {return FixedIHeapClass::Free(pointer);}; + virtual int Save(FileClass & ); + virtual int Load(FileClass & ); + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + virtual T * Ptr(int index) {return (T*)FixedIHeapClass::ActivePointers[index];}; + virtual T * Raw_Ptr(int index) {return (T*)((*this)[index]);}; +}; + + +#endif + + diff --git a/HELP.CPP b/HELP.CPP new file mode 100644 index 0000000..b724ef5 --- /dev/null +++ b/HELP.CPP @@ -0,0 +1,405 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\help.cpv 2.18 16 Oct 1995 16:51:02 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : HELP.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 11/18/94 * + * * + * Last Update : July 16, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * HelpClass::Draw_Help -- Display the help message (if necessary). * + * HelpClass::HelpClass -- Default constructor for the help processor. * + * HelpClass::Help_AI -- Handles the help text logic. * + * HelpClass::Help_Text -- Assigns text as the current help text. * + * HelpClass::Init_Clear -- Sets help system to a known state. * + * HelpClass::Overlap_List -- Returns with offset list for cells under help text. * + * HelpClass::Scroll_Map -- Makes sure scrolling doesn't leave text shards. * + * HelpClass::Set_Cost -- Initiates the second line of help text showing item cost. * + * HelpClass::Set_Tactical_Position -- Sets the tactial map position. * + * HelpClass::Set_Tactical_Position -- Sets the tactical map position. * + * HelpClass::Set_Text -- Determines the overlap list and draw coordinates. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +/* +** This is the holding buffer for the text overlap list. This buffer must be in the near +** data segment. It will be filled in by the Set_Text() function. +*/ +short const HelpClass::OverlapList[30] = { + REFRESH_EOL +}; + +char const * HelpClass::HelpText; + + +CountDownTimerClass HelpClass::CountDownTimer; + + +/*********************************************************************************************** + * HelpClass::HelpClass -- Default constructor for the help processor. * + * * + * The help processor is initialized by this routine. It merely sets up the help engine * + * to the default state. The default state will not display any help text. Call the * + * Help_Text() function to enable help processing. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/18/1994 JLB : Created. * + *=============================================================================================*/ +HelpClass::HelpClass(void) +{ + X = 0; + Y = 0; + Width = 0; + Text = TXT_NONE; + Color = LTGREY; + CountDownTimer.Set(0); + IsRight = false; + Cost = 0; +} + + +/*********************************************************************************************** + * HelpClass::Init_Clear -- Sets help system to a known state. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +void HelpClass::Init_Clear(void) +{ + TabClass::Init_Clear(); + + Set_Text(TXT_NONE); +} + + +/*********************************************************************************************** + * HelpClass::Overlap_List -- Returns with offset list for cells under help text. * + * * + * Use this routine to fetch an offset list for the cells under the text displayed. If * + * there is no text displayed, then the list will consist of just the terminator code. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the offset list for the help text overlap. The offset * + * list is based on the tactical map upper left corner cell. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/18/1994 JLB : Created. * + *=============================================================================================*/ +short const * HelpClass::Overlap_List(void) const +{ + if (Text == TXT_NONE || CountDownTimer.Time()) { + ((short &)(OverlapList[0])) = REFRESH_EOL; + } + return(OverlapList); +} + + +/*********************************************************************************************** + * HelpClass::Help_AI -- Handles the help text logic. * + * * + * This routine handles tracking the mouse position to see if the mouse remains stationary * + * for the required amount of time. If the time requirement has been met, then it flags * + * the help system to display the help text the next time the Draw_Help() function is * + * called. * + * * + * INPUT: key -- Keyboard input code. * + * * + * x,y -- Mouse coordinates. * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine must be called once and only once per game frame (15 times per * + * second). * + * * + * HISTORY: * + * 11/18/1994 JLB : Created. * + * 12/31/1994 JLB : Uses mouse coordinates as passed in. * + *=============================================================================================*/ +void HelpClass::AI(KeyNumType &key, int x, int y) +{ + /* + ** If there is any keyboard input, then the help text goes away. + */ +// if (key) { +// Help_Text(TXT_NONE); +// } + + if (!CountDownTimer.Time() && !IsRight && (x != X || y != Y)) { + Help_Text(TXT_NONE); + } + + /* + ** Process the countdown timer only if it hasn't already expired and there is + ** a real help text message to display. + */ + if (CountDownTimer.Time() && !HelpText && Text != TXT_NONE) { + + /* + ** If the mouse has moved, then reset the timer since a moving mouse is not + ** supposed to bring up the help text. + */ + if (!IsRight && (X != x || Y != y)) { + X = x; + Y = y; + CountDownTimer.Start(); + CountDownTimer.Set(HELP_DELAY); + Set_Text(TXT_NONE); + } else { + + /* + ** If the delay has expired, then the text must be drawn. Build the help text + ** overlay list at this time. Better to do it now, when we KNOW it is needed, then + ** to do it earlier when it might not be needed. + */ + Set_Text(Text); + } + } + + TabClass::AI(key, x, y); +} + + +/*********************************************************************************************** + * HelpClass::Help_Text -- Assigns text as the current help text. * + * * + * Use this routine to change the help text that will pop up if the cursor isn't moved * + * for the help delay duration. Call this routine as often as desired. * + * * + * INPUT: text -- The text number for the help text to use. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/18/1994 JLB : Created. * + *=============================================================================================*/ +void HelpClass::Help_Text(int text, int x, int y, int color, bool quick, int cost) +{ + if (text != Text) { + + /* + ** If there is an existing text message, then flag the map to redraw the underlying + ** icons so that the text message is erased. + */ + if (Text != TXT_NONE) { + Refresh_Cells(Coord_Cell(TacticalCoord), &OverlapList[0]); + } + + /* + ** Record the position of the mouse. This recorded position will be used to determine + ** if the mouse has moved. A moving mouse prevents the help text from popping up. + */ + X = x; + if (x == -1) X = Get_Mouse_X(); + Y = y; + if (y == -1) Y = Get_Mouse_Y(); + IsRight = (y != -1) || (x != -1); + + if (quick) { + CountDownTimer.Set(1); + } else { + CountDownTimer.Set(HELP_DELAY); + } + + Color = color; + Text = text; + Cost = cost; + } +} + + +/*********************************************************************************************** + * HelpClass::Draw_Help -- Display the help message (if necessary). * + * * + * This function will print the help text if it thinks it should. The timer and text * + * message can control whether this occurs. If there is no help text or the countdown timer * + * has not expired, then no text will be printed. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/18/1994 JLB : Created. * + *=============================================================================================*/ +void HelpClass::Draw_It(bool forced) +{ + TabClass::Draw_It(forced); + + if (Text != TXT_NONE && (forced || !CountDownTimer.Time())) { + + if (LogicPage->Lock()){ + + // Fancy_Text_Print(Text, DrawX, DrawY, Color, BLACK, TPF_6POINT|TPF_NOSHADOW); + Fancy_Text_Print(Text, DrawX, DrawY, Color, BLACK, TPF_MAP|TPF_NOSHADOW); + LogicPage->Draw_Rect(DrawX-1, DrawY-1, DrawX+Width+1, DrawY+FontHeight, Color); + + if (Cost) { + char buffer[15]; + sprintf(buffer, "$%d", Cost); + int width = String_Pixel_Width(buffer); + + // Fancy_Text_Print(buffer, DrawX, DrawY+FontHeight, Color, BLACK, TPF_6POINT|TPF_NOSHADOW); + Fancy_Text_Print(buffer, DrawX, DrawY+FontHeight, Color, BLACK, TPF_MAP|TPF_NOSHADOW); + LogicPage->Draw_Rect(DrawX-1, DrawY+FontHeight, DrawX+width+1, DrawY+FontHeight+FontHeight-1, Color); + LogicPage->Draw_Line(DrawX, DrawY+FontHeight, DrawX+MIN(width+1, Width) - 1, DrawY+FontHeight, BLACK); + } + + LogicPage->Unlock(); + } + } + // if (!In_Debugger) HidPage.Unlock(); +} + + +/*********************************************************************************************** + * HelpClass::Set_Text -- Determines the overlap list and draw coordinates. * + * * + * This routine is used to build the overlap list -- used for icon refreshing. It also * + * determines if the text can fit on the screen and makes adjustments so that it will. * + * * + * INPUT: text -- The text number to set the help system to use. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/18/1994 JLB : Created. * + * 12/11/1994 JLB : Won't draw past tactical map edges. * + *=============================================================================================*/ +void HelpClass::Set_Text(int text) +{ + if (text != TXT_NONE) { + Text = text; +// Fancy_Text_Print(TXT_NONE, 0, 0, 0, 0, TPF_6POINT|TPF_NOSHADOW); + Fancy_Text_Print(TXT_NONE, 0, 0, 0, 0, TPF_MAP|TPF_NOSHADOW); + Width = String_Pixel_Width(Text_String(Text)); + if (IsRight) { + DrawX = X - Width; + DrawY = Y; + } else { + int right = TacPixelX + Lepton_To_Pixel(TacLeptonWidth) - 3; + int bottom = TacPixelY + Lepton_To_Pixel(TacLeptonHeight) - 1; + + DrawX = X+X_OFFSET; + DrawY = Y+Y_OFFSET; + if (DrawX + Width > right) { + DrawX -= (DrawX+Width) - right; + } + if (DrawY + FontHeight > bottom) { + DrawY -= (DrawY+FontHeight) - bottom; + } + if (DrawX < TacPixelX+1) DrawX = TacPixelX+1; + if (DrawY < TacPixelY+1) DrawY = TacPixelY+1; + } + int lines = (Cost) ? 2 : 1; + memcpy((void*)OverlapList, Text_Overlap_List(Text_String(Text), DrawX-1, DrawY, lines), sizeof(OverlapList)); + } +} + + +/*********************************************************************************************** + * HelpClass::Scroll_Map -- Makes sure scrolling doesn't leave text shards. * + * * + * This routine intercepts the map scrolling request and then makes sure that if, in fact, * + * the map is going to scroll, then reset and erase the help text so that it doesn't * + * mess up the display. * + * * + * INPUT: facing -- The direction to scroll (unused by this routine). * + * * + * really -- If the scroll is actually going to occur, rather than just be examined * + * for legality, then this parameter will be true. If this parameter is * + * true, then the help text is reset. * + * * + * OUTPUT: Returns if it can, or did, scroll in the requested direction. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/15/1994 JLB : Created. * + *=============================================================================================*/ +bool HelpClass::Scroll_Map(DirType facing, int & distance, bool really) +{ + if (really) { + Help_Text(TXT_NONE); + } + return(TabClass::Scroll_Map(facing, distance, really)); +} + + +/*********************************************************************************************** + * HelpClass::Set_Cost -- Initiates the second line of help text showing item cost. * + * * + * Use this routine after the Help_Text() function to activate the second line. The second * + * line displays a cost. Typically, this is used by the sidebar to display the cost of the * + * specified item. * + * * + * INPUT: cost -- The cost to associate with this help text. If this value is zero, then * + * no second line is displayed, so don't pass in zero. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/09/1995 JLB : Created. * + *=============================================================================================*/ +void HelpClass::Set_Cost(int cost) +{ + Cost = cost; +} + + +void HelpClass::Set_Tactical_Position(COORDINATE coord) +{ + if (TacticalCoord != coord) { + Help_Text(TXT_NONE); + } + TabClass::Set_Tactical_Position(coord); +} diff --git a/HELP.H b/HELP.H new file mode 100644 index 0000000..020b5a9 --- /dev/null +++ b/HELP.H @@ -0,0 +1,144 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\help.h_v 2.17 16 Oct 1995 16:46:28 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : HELP.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 11/18/94 * + * * + * Last Update : November 18, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef HELP_H +#define HELP_H + +#include "tab.h" + +class HelpClass: public TabClass +{ + public: + HelpClass(void); + + /* + ** Initialization + */ + virtual void Init_Clear(void); // Clears all to known state + + virtual void Draw_It(bool complete=false); + virtual void AI(KeyNumType &input, int x, int y); + virtual bool Scroll_Map(DirType facing, int &distance, bool really); + virtual void Set_Tactical_Position(COORDINATE coord); + + void Help_Text(int text, int x=-1, int y=-1, int color=LTGREY, bool quick=false, int cost = 0); + void Set_Cost(int cost); + short const * Overlap_List(void) const; + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + private: + + static char const *HelpText; + int HelpX; + int HelpY; + int HelpWidth; + + + void Set_Text(int text); + + /* + ** If the help text is right justified (as with the help text that pops up over the + ** sidebar icons), then this flag is set to true. + */ + unsigned IsRight:1; + + /* + ** If the optional second line of text that displays cost is desired, then this + ** value will be non-zero. Typically, this is true when the help text is associated + ** with one of the sidebar construction icons. + */ + int Cost; + + /* + ** This is the recorded position of the cursor at the time the help text + ** pops up. The help text is rendered as an offset from this pixel position. + */ + int X; + int Y; + + /* + ** This is the draw X and Y coordinate. This position is relative to the X and + ** Y coordinates but adjusted for screen edges as necessary. + */ + int DrawX; + int DrawY; + + /* + ** The width of the help text (in pixels) is stored here. This is a convenience + ** since calculating the width takes a bit of time. + */ + int Width; + + /* + ** The text number of the help text to display is held here. If no text is to be + ** displayed, then this value will be TXT_NONE. + */ + int Text; + + /* + ** This is the background color to use for the help text. It can change according + ** to the message displayed. + */ + int Color; + + /* + ** This countdown timer controls when the help text will pop up. If the mouse + ** remains stationary while this countdown timer expires, then the help text + ** will pop up. + */ + static CountDownTimerClass CountDownTimer; + + /* + ** This is a calculated cell offset list (from the Map.TacticalCell) that indicates + ** which cells are under the help text and thus which cells need to be redrawn if + ** the help text is to be erased. + */ + static short const OverlapList[30]; + + enum HelpClassEnum { + HELP_DELAY=TIMER_SECOND*1, // The countdown timer delay before help text pops up. + Y_OFFSET=0, // The Y pixel offset from cursor for help text print. + X_OFFSET=10, // The X pixel offset from cursor for help text print. + }; +}; + +#endif diff --git a/HOTLIST.TXT b/HOTLIST.TXT new file mode 100644 index 0000000..b15730e --- /dev/null +++ b/HOTLIST.TXT @@ -0,0 +1,114 @@ +Burn version with new setup/install and scenario files. + +) I've noticed that the Tiberium "tiles" are skragging in the +multiplayer mode. Skragging= a "tile" of random colored pixels. +It happened everytime I would scroll to the bottom and there was +still tiberium on the field, but also, just as I would scroll +around. + +) Vehicles still cross over each other when they are moving. + +) I think you should be able to click a tile partially full of +infantry if the selected unit is an infantry. (e.g. if you have 3 +infantry in a square, you should be able to select 2 infantry and +be able to click in the square where the 3 are.) If you select +more than 2 infantry, the remainder should just move as close as +possible... + +) When multiple units are selected and sent to a location, 1 or 2 +never go on the first try. I suggest that those who can't find +there destination try to get as close as possible to it. + +) The game currently pauses anytime any player gets a new score. +This turns into repeated "lockup scares" when the player is playing three other people. +If we can't solve this problem, perhaps we should give each player +a sound track and never load a new one. + +) The game bogs down when multiple people are scrolling around. + +) Right now, there is no message when a player has been wiped off the +map. Should this be an EVA thing or on screen text? I think both. + +) There is also no "You have won!" message. This should be EVA and +text as well. + +"RUNGAME.EXE" to be renamed to so that it is clear what the player should +type to run the game. Possibly rename to "utility.exe". + +Install new multiplayer EVA speech. Disable enemy destroyed EVA speech +when in multiplayer mode. + +Install new VQ movies. + +Merge with Barry to incorporate map/score screen changes. + +Install these samples: +REPAIR2.WAV use when vehicle goes on repair ppad. +RADAR2.WAV use when radar kicks in. + +Track down Watcom debug problem. Call Tech Rep + +There is a bug with radar in that it doesn't update enough +for fast moving units. + +Fix so that harvester returns to Tiberium field after unloading. + +Add pause option to game (for network play)? + +Prevent placement of buildings off the edge of the map. Especially +critical for the Weapons factory. + +Purple tiberium color on radar map. + +Fix bug with why bases aren't being built by the computer. + +Increase delay for "end of game check" from 20 seconds to 30 seconds. + +Teams arriving by Chinook are not following waypoints. +They will go into Guard upon exiting the helicopter. Fix this. + +Find a solution for: +Chinooks only fly to the Reinforcement cell. +This presents the problem of not being able to bring in troops for the +player, or not having GDI drop troops by helicopter. + +Allow creation of Civilian Teams. + +Check up on the problems with the "ambush" team order. What is it really +supposed to do? + +Fix rocks passibility problems in the desert set. + +Attack cycle should carry a weak machine gun weapon. + +Enemy tanks should drive over infantry if they are very close rather than +merely attacking them with firepower. + +Fix problem with harvester driving over the backside of the refinery -- it looks bad. + +Install two or four small transition scores to reside on the hard drive. + +Control the transition score from the INI file. + +Allow control of the sidebar by way of a trigger event. This will allow certain +scenarios to force the sidebar to be disabled until an event occurs. + +Factories should produce what they originally would produced when constructed, NOT +restricted to what the current owner can produce. This will allow capturing of +a factory so that, otherwise unavailable, units could be produced. + +Show percentage full graphic for the harvester. + +Stealth tank cloaking and decloaking problems. + +Graphic icon glitches on the radar map. + + + + + + + + + + diff --git a/HOUSE.CPP b/HOUSE.CPP new file mode 100644 index 0000000..20583fc --- /dev/null +++ b/HOUSE.CPP @@ -0,0 +1,4349 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\house.cpv 2.13 02 Aug 1995 17:03:50 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : HOUSE.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 21, 1994 * + * * + * Last Update : August 12, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * HouseClass::AI -- Process house logic. * + * HouseClass::Abandon_Production -- Abandons production of item type specified. * + * HouseClass::Add_Nuke_Piece -- Add a nuclear piece to the collection. * + * HouseClass::Adjust_Capacity -- Adjusts the house Tiberium storage capacity. * + * HouseClass::Adjust_Threat -- Adjust threat for the region specified. * + * HouseClass::As_Pointer -- Converts a house number into a house object pointer. * + * HouseClass::Attacked -- Lets player know if base is under attack. * + * HouseClass::Available_Money -- Fetches the total credit worth of the house. * + * HouseClass::Begin_Production -- Starts production of the specified object type. * + * HouseClass::Blowup_All -- blows up everything * + * HouseClass::Can_Build -- Determines if the aircraft type can be built. * + * HouseClass::Can_Build -- Determines if the building type can be built. * + * HouseClass::Can_Build -- Determines if the infantry unit can be built by this house. * + * HouseClass::Can_Build -- Determines if the unit can be built by this house. * + * HouseClass::Can_Build -- General purpose build legality checker. * + * HouseClass::Clobber_All -- removes house & all its objects * + * HouseClass::Debug_Dump -- Dumps the house status data to the mono screen. * + * HouseClass::Detach -- Removes specified object from house tracking systems. * + * HouseClass::Does_Enemy_Building_Exist -- Checks for enemy building of specified type. * + * HouseClass::Flag_Attach -- Attach flag to specified cell (or thereabouts). * + * HouseClass::Flag_Attach -- Attaches the house flag the specified unit. * + * HouseClass::Flag_Remove -- Removes the flag from the specified target. * + * HouseClass::Flag_To_Die -- Flags the house to blow up soon. * + * HouseClass::Flag_To_Lose -- Flags the house to die soon. * + * HouseClass::Flag_To_Win -- Flags the house to win soon. * + * HouseClass::Harvested -- Adds Tiberium to the harvest storage. * + * HouseClass::Has_Nuke_Device -- Deteremines if the house has a nuclear device. * + * HouseClass::HouseClass -- Constructor for a house object. * + * HouseClass::Init -- init's in preparation for new scenario * + * HouseClass::Init_Air_Strike -- Add (or reset) the air strike sidebar button. * + * HouseClass::Init_Data -- Initializes the multiplayer color data. * + * HouseClass::Init_Ion_Cannon -- Initialize the ion cannon countdown. * + * HouseClass::Init_Nuke_Bomb -- Adds (if necessary) the atom bomb to the sidebar. * + * HouseClass::Is_Ally -- Checks to see if the object is an ally. * + * HouseClass::Is_Ally -- Determines if the specified house is an ally. * + * HouseClass::Is_Ally -- Determines if the specified house is an ally. * + * HouseClass::MPlayer_Defeated -- multiplayer; house is defeated * + * HouseClass::Make_Air_Strike_Available -- Make the airstrike available. * + * HouseClass::Make_Ally -- Make the specified house an ally. * + * HouseClass::Make_Enemy -- Make an enemy of the house specified. * + * HouseClass::Manual_Place -- Inform display system of building placement mode. * + * HouseClass::One_Time -- Handles one time initialization of the house array. * + * HouseClass::Place_Object -- Places the object (building) at location specified. * + * HouseClass::Place_Special_Blast -- Place a special blast effect at location specified. * + * HouseClass::Power_Fraction -- Fetches the current power output rating. * + * HouseClass::Read_INI -- Reads house specific data from INI. * + * HouseClass::Refund_Money -- Refunds money to back to the house. * + * HouseClass::Remap_Table -- Fetches the remap table for this house object. * + * HouseClass::Remove_Air_Strike -- Removes the air strike button from the sidebar. * + * HouseClass::Remove_Ion_Cannon -- Disables the ion cannon. * + * HouseClass::Remove_Nuke_Bomb -- Removes the nuclear bomb from the sidebar. * + * HouseClass::Sell_Wall -- Tries to sell the wall at the specified location. * + * HouseClass::Silo_Redraw_Check -- Flags silos to be redrawn if necessary. * + * HouseClass::Special_Weapon_AI -- Fires special weapon. * + * HouseClass::Spend_Money -- Removes money from the house. * + * HouseClass::Suggest_New_Object -- Determine what would the next buildable object be. * + * HouseClass::Suggested_New_Team -- Determine what team should be created. * + * HouseClass::Suspend_Production -- Temporarily puts production on hold. * + * HouseClass::Validate -- validates house pointer * + * HouseClass::Write_INI -- Writes house specific data into INI file. * + * HouseClass::delete -- Deallocator function for a house object. * + * HouseClass::new -- Allocator for a house class. * + * HouseClass::operator HousesType -- Conversion to HousesType operator. * + * HouseClass::~HouseClass -- Default destructor for a house object. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * HouseClass::Validate -- validates house pointer * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int HouseClass::Validate(void) const +{ + int num; + + num = Houses.ID(this); + if (num < 0 || num >= HOUSE_MAX) { + Validate_Error("HOUSE"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*********************************************************************************************** + * HouseClass::operator HousesType -- Conversion to HousesType operator. * + * * + * This operator will automatically convert from a houses class object into the HousesType * + * enumerated value. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the object's HousesType value. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +HouseClass::operator HousesType(void) const +{ + Validate(); + return(Class->House); +} + + +/*********************************************************************************************** + * HouseClass::As_Pointer -- Converts a house number into a house object pointer. * + * * + * Use this routine to convert a house number into the house pointer that it represents. * + * A simple index into the Houses template array is not sufficient, since the array order * + * is arbitrary. An actual scan through the house object is required in order to find the * + * house object desired. * + * * + * INPUT: house -- The house type number to look up. * + * * + * OUTPUT: Returns with a pointer to the house object that the house number represents. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +HouseClass * HouseClass::As_Pointer(HousesType house) +{ + for (int index = 0; index < Houses.Count(); index++) { + if (Houses.Ptr(index)->Class->House == house) { + return(Houses.Ptr(index)); + } + } + return(0); +} + + +/*********************************************************************************************** + * HouseClass::One_Time -- Handles one time initialization of the house array. * + * * + * This basically calls the constructor for each of the houses in the game. All other * + * data specific to the house is initialized when the scenario is loaded. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Only call this ONCE at the beginning of the game. * + * * + * HISTORY: * + * 12/09/1994 JLB : Created. * + *=============================================================================================*/ +void HouseClass::One_Time(void) +{ +// for (HousesType index = HOUSE_FIRST; index < HOUSE_COUNT; index++) { +// new(index) HouseClass; +// } +} + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * HouseClass::Debug_Dump -- Dumps the house status data to the mono screen. * + * * + * This utility function will output the current status of the house class to the mono * + * screen. Through this information bugs may be fixed or detected. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + *=============================================================================================*/ +void HouseClass::Debug_Dump(MonoClass *) const +{ + Validate(); +} +#endif + + +/*********************************************************************************************** + * HouseClass::new -- Allocator for a house class. * + * * + * This is the allocator for a house class. Since there can be only * + * one of each type of house, this is allocator has restricted * + * functionality. Any attempt to allocate a house structure for a * + * house that already exists, just returns a pointer to the previously * + * allocated house. * + * * + * INPUT: house -- The house to allocate a class object for. * + * * + * OUTPUT: Returns with a pointer to the allocated class object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1994 JLB : Created. * + *=============================================================================================*/ +void * HouseClass::operator new(size_t) +{ + void * ptr = Houses.Allocate(); + if (ptr) { + ((HouseClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +/*********************************************************************************************** + * HouseClass::delete -- Deallocator function for a house object. * + * * + * This function marks the house object as "deallocated". Such a * + * house object is available for reallocation later. * + * * + * INPUT: ptr -- Pointer to the house object to deallocate. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1994 JLB : Created. * + *=============================================================================================*/ +void HouseClass::operator delete(void *ptr) +{ + if (ptr) { + ((HouseClass *)ptr)->IsActive = false; + } + Houses.Free((HouseClass *)ptr); +} + + +/*********************************************************************************************** + * HouseClass::HouseClass -- Constructor for a house object. * + * * + * This function is the constructor and it marks the house object * + * as being allocated. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1994 JLB : Created. * + *=============================================================================================*/ +HouseClass::HouseClass(HousesType house) : + Class(&HouseTypeClass::As_Reference(house)), + IonCannon(ION_CANNON_GONE_TIME, VOX_ION_READY, VOX_ION_CHARGING, VOX_ION_CHARGING, VOX_NO_POWER), + AirStrike(AIR_CANNON_GONE_TIME, VOX_AIRSTRIKE_READY, VOX_NONE, VOX_NOT_READY, VOX_NOT_READY), + NukeStrike(NUKE_GONE_TIME, VOX_NUKE_AVAILABLE, VOX_NONE, VOX_NOT_READY, VOX_NO_POWER) +{ + + for (HousesType i = HOUSE_FIRST; i < HOUSE_COUNT; i++) { + UnitsKilled[i] = 0; + BuildingsKilled[i] = 0; + } + WhoLastHurtMe = house; // init this to myself + + IsVisionary = false; + IsFreeHarvester = false; + Blockage = 0; + UnitsLost = 0; + BuildingsLost = 0; + + NewActiveBScan = 0; + ActiveBScan = 0; + NewActiveUScan = 0; + ActiveUScan = 0; + NewActiveIScan = 0; + ActiveIScan = 0; + NewActiveAScan = 0; + ActiveAScan = 0; + + strcpy((char *)Name, "Computer"); // Default computer name. + JustBuilt = STRUCT_NONE; + AlertTime = 0; + IsAlerted = false; + IsAirstrikePending = false; + AircraftFactory = -1; + AircraftFactories = 0; + ActLike = Class->House; + Allies = 0; + AScan = 0; + NukeDest = 0; + BlitzTime.Clear(); + BScan = 0; + BuildingFactories = 0; + BuildingFactory = -1; + Capacity = 0; + Credits = 0; + CreditsSpent = 0; + CurBuildings = 0; + CurUnits = 0; + DamageTime = DAMAGE_DELAY; + Drain = 0; + Edge = SOURCE_NORTH; + FlagHome = 0; + FlagLocation = TARGET_NONE; + HarvestedCredits = 0; + HouseTriggers[house].Clear(); + IGaveUp = false; + InfantryFactories = 0; + InfantryFactory = -1; + InitialCredits = 0; + InitialCredits = 0; + IScan = 0; + IsRecalcNeeded = true; + IsCivEvacuated = false; + IsDefeated = false; + IsDiscovered = false; + IsHuman = false; + IsMaxedOut = false; + IsStarted = false; + IsToDie = false; + IsToLose = false; + IsToWin = false; + Make_Ally(house); + MaxBuilding = 0; + MaxUnit = 0; + NewAScan = 0; + NewBScan = 0; + NewIScan = 0; + NewUScan = 0; + NukePieces = 0x07; + Power = 0; + RemapTable = Class->RemapTable; + RemapColor = Class->RemapColor; + Resigned = false; + SpeakAttackDelay = 1; + SpeakMaxedDelay = 1; + SpeakMoneyDelay = 1; + SpeakPowerDelay = 1; + SpecialFactories = 0; + SpecialFactory = -1; + TeamTime = TEAM_DELAY; + Tiberium = 0; + TriggerTime = 0; + UnitFactories = 0; + UnitFactory = -1; + UScan = 0; + memset((void *)&Regions[0], 0x00, sizeof(Regions)); + + AircraftTotals = new UnitTrackerClass( (int) AIRCRAFT_COUNT); + InfantryTotals = new UnitTrackerClass( (int) INFANTRY_COUNT); + UnitTotals = new UnitTrackerClass ( (int) UNIT_COUNT); + BuildingTotals = new UnitTrackerClass ( (int) STRUCT_COUNT); + + DestroyedAircraft = new UnitTrackerClass ( (int) AIRCRAFT_COUNT); + DestroyedInfantry = new UnitTrackerClass( (int) INFANTRY_COUNT); + DestroyedUnits = new UnitTrackerClass ( (int) UNIT_COUNT); + DestroyedBuildings = new UnitTrackerClass ( (int) STRUCT_COUNT); + + CapturedBuildings = new UnitTrackerClass ( (int) STRUCT_COUNT); + TotalCrates = new UnitTrackerClass ( TOTAL_CRATE_TYPES ); //15 crate types +} + + +HouseClass::~HouseClass (void) +{ + delete AircraftTotals; + delete InfantryTotals; + delete UnitTotals; + delete BuildingTotals; + + delete DestroyedAircraft; + delete DestroyedInfantry; + delete DestroyedUnits; + delete DestroyedBuildings; + + delete CapturedBuildings; + delete TotalCrates; +} + +/*********************************************************************************************** + * HouseClass::Can_Build -- General purpose build legality checker. * + * * + * This routine is called when it needs to be determined if the specified object type can * + * be built by this house. Production and sidebar maintenance use this routine heavily. * + * * + * INPUT: type -- Pointer to the type of object that legality is to be checked for. * + * * + * house -- This is the house to check for legality against. Note that this might * + * not be 'this' house since the check could be from a captured factory. * + * Captured factories build what the original owner of them could build. * + * * + * OUTPUT: Can the specified object be built? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/04/1995 JLB : Created. * + * 08/12/1995 JLB : Updated for GDI building sandbag walls in #9. * + *=============================================================================================*/ +bool HouseClass::Can_Build(TechnoTypeClass const * type, HousesType house) const +{ + Validate(); + if (!type || !type->IsBuildable || !((1L << house) & type->Ownable)) return(false); + + /* + ** The computer can always build everthing. + */ + if (!IsHuman) return(true); + + /* + ** Perform some equivalency fixups for the building existance flags. + */ + long flags = ActiveBScan; + int pre = type->Pre; + if (flags & STRUCTF_ADVANCED_POWER) flags |= STRUCTF_POWER; + if (flags & STRUCTF_HAND) flags |= STRUCTF_BARRACKS; + if (flags & STRUCTF_OBELISK) flags |= STRUCTF_ATOWER; + if (flags & STRUCTF_TEMPLE) flags |= STRUCTF_EYE; + if (flags & STRUCTF_AIRSTRIP) flags |= STRUCTF_WEAP; + if (flags & STRUCTF_SAM) flags |= STRUCTF_HELIPAD; + + /* + ** Multiplayer game uses a different legality check for building. + */ + if (GameToPlay != GAME_NORMAL || (Special.IsJurassic && AreThingiesEnabled)) { + return((pre & flags) == pre && type->Level <= BuildLevel); + } + +#ifdef NEWMENU + int level = BuildLevel; +#else + int level = Scenario; +#endif + + /* + ** Special check to make the mission objective buildings the prerequisite + ** for the stealth tank in mission #11 only. + */ + if (house == HOUSE_BAD && + type->What_Am_I() == RTTI_UNITTYPE && + ((UnitTypeClass const *)type)->Type == UNIT_STANK && + level == 11) { + + pre = STRUCTF_MISSION; + level = type->Scenario; + } + + /* + ** Special case check to ensure that GDI doesn't get the bazooka guy + ** until mission #8. + */ + if (house == HOUSE_GOOD && + type->What_Am_I() == RTTI_INFANTRYTYPE && + ((InfantryTypeClass const *)type)->Type == INFANTRY_E3 && + level < 7) { + + return(false); + } + + /* + ** Special check to allow GDI to build the MSAM by mission #9 + ** and no sooner. + */ + if (house == HOUSE_GOOD && + type->What_Am_I() == RTTI_UNITTYPE && + ((UnitTypeClass const *)type)->Type == UNIT_MLRS && + level < 9) { + + return(false); + } + + /* + ** Special case to disable the APC from the Nod player. + */ + if (house == HOUSE_BAD && + type->What_Am_I() == RTTI_UNITTYPE && + ((UnitTypeClass const *)type)->Type == UNIT_APC) { + + return(false); + } + + /* + ** Ensure that the Temple of Nod cannot be built by GDI even + ** if GDI has captured the Nod construction yard. + */ + if (type->What_Am_I() == RTTI_BUILDINGTYPE && + (((BuildingTypeClass const *)type)->Type == STRUCT_TEMPLE || ((BuildingTypeClass const *)type)->Type == STRUCT_OBELISK) && + Class->House == HOUSE_GOOD) { + + return(false); + } + + /* + ** Ensure that the rocket launcher tank cannot be built by Nod. + */ + if (type->What_Am_I() == RTTI_UNITTYPE && + ((UnitTypeClass const *)type)->Type == UNIT_MLRS && + Class->House == HOUSE_BAD) { + + return(false); + } + + /* + ** Ensure that the ion cannon cannot be built if + ** Nod has captured the GDI construction yard. + */ + if (type->What_Am_I() == RTTI_BUILDINGTYPE && + (((BuildingTypeClass const *)type)->Type == STRUCT_EYE) && + Class->House == HOUSE_BAD) { + + return(false); + } + + /* + ** Nod can build the advanced power plant at scenario #12. + */ + if (house == HOUSE_BAD && + level >= 12 && + type->What_Am_I() == RTTI_BUILDINGTYPE && + ((BuildingTypeClass const *)type)->Type == STRUCT_ADVANCED_POWER) { + + level = type->Scenario; + } + + /* + ** Nod cannot build a helipad in the normal game. + */ + if (house == HOUSE_BAD && + type->What_Am_I() == RTTI_BUILDINGTYPE && + ((BuildingTypeClass const *)type)->Type == STRUCT_HELIPAD) { + + return(false); + } + + /* + ** GDI can build the sandbag wall only from scenario #9 onwards. + */ + if (house == HOUSE_GOOD && + level < 8 && + type->What_Am_I() == RTTI_BUILDINGTYPE && + ((BuildingTypeClass const *)type)->Type == STRUCT_SANDBAG_WALL) { + + return(false); + } + + /* + ** GDI has a special second training mission. Adjust the scenario level so that + ** scenario two will still feel like scenario #1. + */ + if (house == HOUSE_GOOD && level == 2) { + level = 1; + } + + if (Debug_Cheat) level = 98; + return((pre & flags) == pre && type->Scenario <= level); +} + + +/*********************************************************************************************** + * HouseClass::Can_Build -- Determines if the building type can be built. * + * * + * This routine is used by the construction preparation code to building a list of building * + * types that can be built. It determines if a building can be built by checking if the * + * prerequisite buildings have been built (and still exist) as well as checking to see if * + * the house can build the specified structure. * + * * + * INPUT: s -- The structure type number that is being checked. * + * * + * house -- The house number to use when determining if the object can be built. * + * This is necessary because the current owner of the factory doesn't * + * control what the factory can produce. Rather, the original builder of * + * the factory controls this. * + * * + * OUTPUT: bool; Can this structure type be built at this time? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/08/1994 JLB : Created. * + * 05/31/1995 JLB : Handles specified ownership override. * + *=============================================================================================*/ +bool HouseClass::Can_Build(StructType s, HousesType house) const +{ + Validate(); + return(Can_Build(&BuildingTypeClass::As_Reference(s), house)); +} + + +/*********************************************************************************************** + * HouseClass::Can_Build -- Determines if the infantry unit can be built by this house. * + * * + * Use this routine to determine if the infantry type specified can be built by this * + * house. It determines this by checking the ownership allowed bits in the infantry * + * type class. * + * * + * INPUT: infantry -- The infantry type to check against this house. * + * * + * house -- The house number to use when determining if the object can be built. * + * This is necessary because the current owner of the factory doesn't * + * control what the factory can produce. Rather, the original builder of * + * the factory controls this. * + * * + * OUTPUT: bool; Can the infantry be produced by this house? * + * * + * WARNINGS: It does not check to see if there is a functional barracks available, but * + * merely checks to see if it is legal for this house to build that infantry * + * type. * + * * + * HISTORY: * + * 12/09/1994 JLB : Created. * + * 05/31/1995 JLB : Handles specified ownership override. * + *=============================================================================================*/ +bool HouseClass::Can_Build(InfantryType infantry, HousesType house) const +{ + Validate(); + return(Can_Build(&InfantryTypeClass::As_Reference(infantry), house)); +} + + +/*********************************************************************************************** + * HouseClass::Can_Build -- Determines if the unit can be built by this house. * + * * + * This routine is used to determine if the unit type specified can in fact be built by * + * this house. It checks the ownable bits in the unit's type to determine this. * + * * + * INPUT: unit -- The unit type to check against this house. * + * * + * house -- The house number to use when determining if the object can be built. * + * This is necessary because the current owner of the factory doesn't * + * control what the factory can produce. Rather, the original builder of * + * the factory controls this. * + * * + * OUTPUT: bool; Can the unit be built by this house? * + * * + * WARNINGS: This doesn't check to see if there is a functional factory that can build * + * this unit, but merely if the unit can be built according to ownership rules. * + * * + * HISTORY: * + * 12/09/1994 JLB : Created. * + * 05/31/1995 JLB : Handles specified ownership override. * + *=============================================================================================*/ +bool HouseClass::Can_Build(UnitType unit, HousesType house) const +{ + Validate(); + return(Can_Build(&UnitTypeClass::As_Reference(unit), house)); +} + + +/*********************************************************************************************** + * HouseClass::Can_Build -- Determines if the aircraft type can be built. * + * * + * Use this routine to determine if the specified aircraft type can be built. This routine * + * is used by the sidebar and factory to determine what can be built. * + * * + * INPUT: aircraft -- The aircraft type to check for build legality. * + * * + * house -- The house that is performing the check. This is typically the house * + * of the original building of the factory rather than the current * + * owner. * + * * + * OUTPUT: Can this aircraft type be built by the house specified? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/24/1995 JLB : Created. * + *=============================================================================================*/ +bool HouseClass::Can_Build(AircraftType aircraft, HousesType house) const +{ + Validate(); + return(Can_Build(&AircraftTypeClass::As_Reference(aircraft), house)); +} + + +/*************************************************************************** + * HouseClass::Init -- init's in preparation for new scenario * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/07/1994 BR : Created. * + * 12/17/1994 JLB : Resets tracker bits. * + *=========================================================================*/ +void HouseClass::Init(void) +{ + Houses.Free_All(); + + for (HousesType index = HOUSE_FIRST; index < HOUSE_COUNT; index++) { + HouseTriggers[index].Clear(); + } +} + + +/*********************************************************************************************** + * HouseClass::AI -- Process house logic. * + * * + * This handles the AI for the house object. It should be called once per house per game * + * tick. It processes all house global tasks such as low power damage accumulation and * + * house specific trigger events. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/27/1994 JLB : Created. * + * 07/17/1995 JLB : Limits EVA speaking unless the player can do something. * + *=============================================================================================*/ +void HouseClass::AI(void) +{ + Validate(); + + /* + ** Reset the scan accumulation bits for the next logic pass. + */ + IScan = NewIScan; + BScan = NewBScan; + UScan = NewUScan; + AScan = NewAScan; + ActiveIScan = NewActiveIScan; + ActiveBScan = NewActiveBScan; + ActiveUScan = NewActiveUScan; + ActiveAScan = NewActiveAScan; + NewIScan = 0; + NewBScan = 0; + NewUScan = 0; + NewAScan = 0; + NewActiveIScan = 0; + NewActiveBScan = 0; + NewActiveUScan = 0; + NewActiveAScan = 0; + + /* + ** Check to see if the house wins. + */ + if (GameToPlay == GAME_NORMAL && IsToWin && BorrowedTime.Expired() && Blockage <= 0) { + IsToWin = false; + if (this == PlayerPtr) { + PlayerWins = true; + } else { + PlayerLoses = true; + } + } + + /* + ** Check to see if the house loses. + */ + if (GameToPlay == GAME_NORMAL && IsToLose && BorrowedTime.Expired()) { + IsToLose = false; + if (this == PlayerPtr) { + PlayerLoses = true; + } else { + PlayerWins = true; + } + } + + /* + ** Check to see if all objects of this house should be blown up. + */ + if (IsToDie && BorrowedTime.Expired()) { + IsToDie = false; + Blowup_All(); + } + + /* + ** Double check power values to correct illegal conditions. It is possible to + ** get a power output of negative (one usually) as a result of damage sustained + ** and the fixed point fractional math involved with power adjustements. If the + ** power rating drops below zero, then make it zero. + */ + if (GameToPlay == GAME_NORMAL) { + Power = MAX(Power, 0); + Drain = MAX(Drain, 0); + } + + /* + ** If the base has been alerted to the enemy and should be attacking, then + ** see if the attack timer has expired. If it has, then create the attack + ** teams. + */ + if (IsAlerted && AlertTime.Expired()) { + + /* + ** Adjusted to reduce maximum number of teams created. + */ + int maxteams = Random_Pick(2, (int)(((BuildLevel-1)/3)+1)); + for (int index = 0; index < maxteams; index++) { + TeamTypeClass const * ttype = Suggested_New_Team(true); + if (ttype) { + ScenarioInit++; + ttype->Create_One_Of(); + ScenarioInit--; + } + } + if (Special.IsDifficult) { + AlertTime = (TICKS_PER_MINUTE * Random_Pick(4, 10)); + } else { + if (Special.IsEasy) { + AlertTime = (TICKS_PER_MINUTE * Random_Pick(16, 40)); + } else { + AlertTime = (TICKS_PER_MINUTE * Random_Pick(5, 20)); + } + } + } + + /* + ** Create teams for this house if necessary. + ** (Use the same timer for some extra capture-the-flag logic.) + */ + if (TeamTime.Expired()) { + TeamTypeClass const * ttype = Suggested_New_Team(false); + if (ttype) { + ttype->Create_One_Of(); + } + + /* + ** Also use this timer to detect if someone is sitting on my flag cell. + */ + if (Special.IsCaptureTheFlag && GameToPlay != GAME_NORMAL) { + TechnoClass *techno; + int damage; + int count; + int moving; + + /* + ** If this house's flag waypoint is a valid cell, see if there's + ** someone sitting on it. If so, make the scatter. If they refuse, + ** blow them up. + */ + if (FlagHome) { + techno = Map[FlagHome].Cell_Techno(); + if (techno) { + moving = false; + techno->Scatter(0,true); + + /* + ** If the techno doesn't have a valid NavCom, he's not moving, + ** so blow him up. + */ + if (techno->What_Am_I() == RTTI_INFANTRY || + techno->What_Am_I() == RTTI_UNIT) { + if (Target_Legal(((FootClass *)techno)->NavCom)) { + moving = true; + } + } + + /* + ** If the techno wasn't an infantry or unit (ie he's a building), + ** or he refuses to move, blow him up + */ + if (!moving) { + count = 0; + while (!(techno->IsInLimbo) && count++ < 5) { + damage = 0x7fff; + Explosion_Damage(techno->Center_Coord(), damage, NULL, WARHEAD_HE); + } + } + } + } + } + + /* + ** Randomly create a Visceroid or other disastrous multiplayer object. + ** Create the object, and use Scan_Place_Object to place the object near + ** the center of the map. + */ + if (GameToPlay != GAME_NORMAL && Class->House==HOUSE_JP) { + int rlimit; + + if (Special.IsJurassic && AreThingiesEnabled) { + rlimit = 450; + } else { + rlimit = 1000; + } + + if (IRandom(0, rlimit) == 0) { + UnitClass *obj = NULL; + CELL cell; + + if (Special.IsJurassic && AreThingiesEnabled) { + obj = new UnitClass(Random_Pick(UNIT_TRIC, UNIT_STEG), HOUSE_JP); + } else { + if (BuildLevel >= 7) { + if (!(UScan & UNITF_VICE)) { + obj = new UnitClass(UNIT_VICE, HOUSE_JP); + } + } + } + + if (obj) { + cell = XY_Cell (Map.MapCellX + Random_Pick(0, Map.MapCellWidth - 1), + Map.MapCellY + Random_Pick(0, Map.MapCellHeight - 1)); + if (!Scan_Place_Object(obj, cell)) { + delete obj; + } + } + } + } + + TeamTime.Set(TEAM_DELAY); + } + + /* + ** If there is insufficient power, then all buildings that are above + ** half strength take a little bit of damage. + */ + if (DamageTime.Expired()) { + +/* +** No free harvesters for computer or player. - 8/16/95 +*/ +#ifdef OBSOLETE + /* + ** Replace the last harvester if there is a refinery present. + */ + if (GameToPlay == GAME_NORMAL && + Frame > 5 && + (!IsHuman && BuildLevel <= 6) && + (ActiveBScan & STRUCTF_REFINERY) != 0 && + (UScan & UNITF_HARVESTER) == 0 && + !IsFreeHarvester) { + + IsFreeHarvester = true; + FreeHarvester = TICKS_PER_MINUTE * 2; + } +#endif + + /* + ** If a free harvester is to be created and the time is right, then create + ** the harvester and clear the free harvester pending flag. + */ + if (IsFreeHarvester && FreeHarvester.Expired()) { + IsFreeHarvester = false; + Create_Special_Reinforcement(this, (TechnoTypeClass *)&UnitTypeClass::As_Reference(UNIT_HARVESTER), NULL); + } + + /* + ** When the power is below required, then the buildings will take damage over + ** time. + */ + if (Power_Fraction() < 0x100) { + for (int index = 0; index < Buildings.Count(); index++) { + BuildingClass & b = *Buildings.Ptr(index); + + if (b.House == this && b.Health_Ratio() > 0x080) { + int damage = 1; + b.Take_Damage(damage, 0, WARHEAD_AP, 0); + } + } + } + DamageTime.Set(DAMAGE_DELAY); + } + + /* + ** If there are no more buildings to sell, then automatically cancel the + ** sell mode. + */ + if (PlayerPtr == this && !BScan && Map.IsSellMode) { + Map.Sell_Mode_Control(0); + } + + /* + ** Various base conditions may be announced to the player. + */ + if (PlayerPtr == this) { + + if (SpeakMaxedDelay.Expired() && IsMaxedOut) { + IsMaxedOut = false; + if ((Capacity - Tiberium) < 300 && Capacity > 500 && (BScan & (STRUCTF_REFINERY | STRUCTF_CONST))) { + Speak(VOX_NEED_MO_CAPACITY); + SpeakMaxedDelay.Set(Options.Normalize_Delay(SPEAK_DELAY)); + } + } + if (SpeakPowerDelay.Expired() && Power_Fraction() < 0x0100) { + if (BScan & STRUCTF_CONST) { + Speak(VOX_LOW_POWER); + SpeakPowerDelay.Set(Options.Normalize_Delay(SPEAK_DELAY)); + } + } + } + + /* + ** If there is a flag associated with this house, then mark it to be + ** redrawn. + */ + if (Target_Legal(FlagLocation)) { + UnitClass * unit = As_Unit(FlagLocation); + if (unit) { + unit->Mark(MARK_CHANGE); + } else { + CELL cell = As_Cell(FlagLocation); + Map[cell].Redraw_Objects(); + } + } + + bool is_time = false; + + /* + ** Triggers are only checked every so often. If the trigger timer has expired, + ** then set the trigger processing flag. + */ + if (TriggerTime.Expired()) { + is_time = true; + TriggerTime = TICKS_PER_MINUTE/10; + } + + /* + ** Check to see if the ion cannon should be removed from the sidebar + ** because of outside circumstances. The advanced communications facility + ** being destroyed is a good example of this. + */ + if (IonCannon.Is_Present()) { + if (!(ActiveBScan & STRUCTF_EYE) && !IonCannon.Is_One_Time()) { + + /* + ** Remove the ion cannon when there is no advanced communication facility. + ** Note that this will not remove the one time created ion cannon. + */ + if (IonCannon.Remove()) { + if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw(); + IsRecalcNeeded = true; + } + } else { + + /* + ** Turn the ion cannon suspension on or off depending on the available + ** power. Note that one time ion cannon will not be affected by this. + */ + IonCannon.Suspend(Power_Fraction() < 0x0100); + + /* + ** Process the ion cannon AI and if something changed that would affect + ** the sidebar, then flag the sidebar to be redrawn. + */ + if (IonCannon.AI(this == PlayerPtr)) { + if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw(); + } + } + + /* + ** The computer may decide to fire the ion cannon if it is ready. + */ + if (IonCannon.Is_Ready() && !IsHuman) { + Special_Weapon_AI(SPC_ION_CANNON); + } + + } else { + + /* + ** If there is no ion cannon present, but there is an advanced communcation + ** center available, then make the ion cannon available as well. + */ + if ((ActiveBScan & STRUCTF_EYE) && + (ActLike == HOUSE_GOOD || GameToPlay != GAME_NORMAL) && + (IsHuman || GameToPlay != GAME_NORMAL)) { + + IonCannon.Enable(false, this == PlayerPtr, Power_Fraction() < 0x0100); + + /* + ** Flag the sidebar to be redrawn if necessary. + */ + if (this == PlayerPtr) { + Map.Add(RTTI_SPECIAL, SPC_ION_CANNON); + Map.Column[1].Flag_To_Redraw(); + } + } + } + + /* + ** Check to see if the nuke strike should be removed from the sidebar + ** because of outside circumstances. The Temple of Nod + ** being destroyed is a good example of this. + */ + if (NukeStrike.Is_Present()) { + if (!(ActiveBScan & STRUCTF_TEMPLE) && (!NukeStrike.Is_One_Time() || GameToPlay == GAME_NORMAL)) { + + /* + ** Remove the nuke strike when there is no Temple of Nod. + ** Note that this will not remove the one time created nuke strike. + */ + if (NukeStrike.Remove(true)) { + IsRecalcNeeded = true; + if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw(); + } + } else { + + /* + ** Turn the nuke strike suspension on or off depending on the available + ** power. Note that one time nuke strike will not be affected by this. + */ + NukeStrike.Suspend(Power_Fraction() < 0x0100); + + /* + ** Process the nuke strike AI and if something changed that would affect + ** the sidebar, then flag the sidebar to be redrawn. + */ + if (NukeStrike.AI(this == PlayerPtr)) { + if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw(); + } + } + + /* + ** The computer may decide to fire the nuclear missile if it is ready. + */ + if (NukeStrike.Is_Ready() && !IsHuman) { + Special_Weapon_AI(SPC_NUCLEAR_BOMB); + } + + } else { + + /* + ** If there is no nuke strike present, but there is a Temple of Nod + ** available, then make the nuke strike strike available. + */ + if ((ActiveBScan & STRUCTF_TEMPLE) && Has_Nuke_Device() && IsHuman) { + NukeStrike.Enable((GameToPlay == GAME_NORMAL), this == PlayerPtr); + + /* + ** Flag the sidebar to be redrawn if necessary. + */ + if (this == PlayerPtr) { + Map.Add(RTTI_SPECIAL, SPC_NUCLEAR_BOMB); + Map.Column[1].Flag_To_Redraw(); + } + } + } + + /* + ** Process the airstrike AI and if something changed that would affect + ** the sidebar, then flag the sidebar to be redrawn. + */ + if (AirStrike.Is_Present()) { + if (AirStrike.AI(this == PlayerPtr)) { + if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw(); + } + + /* + ** The computer may decide to call in the airstrike if it is ready. + */ + if (AirStrike.Is_Ready() && !IsHuman) { + Special_Weapon_AI(SPC_AIR_STRIKE); + } + } + + /* + ** Add the airstrike option if it is pending. + */ + if (IsAirstrikePending) { + IsAirstrikePending = false; + if (AirStrike.Enable(false, this == PlayerPtr)) { + AirStrike.Forced_Charge(this == PlayerPtr); + if (this == PlayerPtr) { + Map.Add(RTTI_SPECIAL, SPC_AIR_STRIKE); + Map.Column[1].Flag_To_Redraw(); + } + } + } + +#ifdef NEVER + /* + ** The following logic deals with the nuclear warhead state machine. It + ** handles all the different stages of the temple firing and the rocket + ** travelling up and down. The rocket explosion is handled by the anim + ** which is attached to the bullet. + */ + if (!IsHuman && NukePresent) { + Special_Weapon_AI(SPC_NUCLEAR_BOMB); + + } +#endif + + /* + ** Special win/lose check for multiplayer games; by-passes the + ** trigger system. We must wait for non-zero frame, because init + ** may not properly set IScan etc for each house; you have to go + ** through each object's AI before it will be properly set. + */ + if (GameToPlay != GAME_NORMAL && !IsDefeated && + !ActiveBScan && !ActiveAScan && !UScan && !ActiveIScan && Frame > 0) { + MPlayer_Defeated(); + } + + for (int index = 0; index < HouseTriggers[Class->House].Count(); index++) { + TriggerClass * t = HouseTriggers[Class->House][index]; + + /* + ** Check for just built the building trigger event. + */ + if (JustBuilt != STRUCT_NONE) { + if (t->Spring(EVENT_BUILD, Class->House, JustBuilt)) { + JustBuilt = STRUCT_NONE; + continue; + } + } + + /* + ** Check for civilian evacuation trigger event. + */ + if (IsCivEvacuated && t->Spring(EVENT_EVAC_CIVILIAN, Class->House)) { + continue; + } + + /* + ** Number of buildings destroyed checker. + */ + if (t->Spring(EVENT_NBUILDINGS_DESTROYED, Class->House, BuildingsLost)) { + continue; + } + + /* + ** Number of units destroyed checker. + */ + if (t->Spring(EVENT_NUNITS_DESTROYED, Class->House, UnitsLost)) { + continue; + } + + /* + ** House has been revealed trigger event. + */ + if (IsDiscovered && t->Spring(EVENT_HOUSE_DISCOVERED, Class->House)) { + IsDiscovered = false; + continue; + } + + /* + ** The "all destroyed" triggers are only processed after a certain + ** amount of safety time has expired. + */ + if (!EndCountDown) { + + /* + ** All buildings destroyed checker. + */ + if (!ActiveBScan) { + if (t->Spring(EVENT_BUILDINGS_DESTROYED, Class->House)) { + continue; + } + } + + /* + ** All units destroyed checker. + */ + if (!((ActiveUScan & ~(UNITF_GUNBOAT)) | IScan | (ActiveAScan & ~(AIRCRAFTF_TRANSPORT|AIRCRAFTF_CARGO|AIRCRAFTF_A10)))) { + if (t->Spring(EVENT_UNITS_DESTROYED, Class->House)) { + continue; + } + } + + /* + ** All buildings AND units destroyed checker. + */ + if (!(ActiveBScan | (ActiveUScan & ~(UNITF_GUNBOAT)) | IScan | (ActiveAScan & ~(AIRCRAFTF_TRANSPORT|AIRCRAFTF_CARGO|AIRCRAFTF_A10)))) { + if (t->Spring(EVENT_ALL_DESTROYED, Class->House)) { + continue; + } + } + } + + /* + ** Credit check. + */ + if (t->Spring(EVENT_CREDITS, Class->House, Credits)) { + continue; + } + + /* + ** Timeout check. + */ + if (is_time && t->Spring(EVENT_TIME, Class->House)) { + continue; + } + + /* + ** All factories destroyed check. + */ + if (!(BScan & (STRUCTF_AIRSTRIP|STRUCTF_HAND|STRUCTF_WEAP|STRUCTF_BARRACKS)) && t->Spring(EVENT_NOFACTORIES, Class->House)) { + continue; + } + } + + /* + ** If a radar facility is not present, but the radar is active, then turn the radar off. + ** The radar also is turned off when the power gets below 100% capacity. + */ + if (PlayerPtr == this) { + if (Map.Is_Radar_Active()) { + if (BScan & (STRUCTF_RADAR|STRUCTF_EYE)) { + if (Power_Fraction() < 0x0100) { + Map.Radar_Activate(0); + } + } else { + Map.Radar_Activate(0); + } + } else { + if (BScan & (STRUCTF_RADAR|STRUCTF_EYE)) { + if (Power_Fraction() >= 0x0100) { + Map.Radar_Activate(1); + } + } else { + if (Map.Is_Radar_Existing()) { + Map.Radar_Activate(4); + } + } + } + } + + /* + ** If the production possibilities need to be recalculated, then do so now. This must + ** occur after the scan bits have been properly updated. + */ + if (PlayerPtr == this && IsRecalcNeeded) { + IsRecalcNeeded = false; + Map.Recalc(); + +#ifdef NEVER + /* + ** Remove the ion cannon if necessary. + */ + if (IonCannon.Is_Present() && !(BScan & STRUCTF_EYE)) { + IonCannon.Remove(); + } + + /* + ** Remove the nuclear bomb if necessary. + */ + if (NukeStrike.Is_Present() && !(BScan & STRUCTF_TEMPLE)) { + NukeStrike.Remove(); + } +#endif + + /* + ** This placement might affect any prerequisite requirements for construction + ** lists. Update the buildable options accordingly. + */ + for (int index = 0; index < Buildings.Count(); index++) { + BuildingClass * building = Buildings.Ptr(index); + if (building && building->Owner() == Class->House) { + + building->Update_Specials(); + if (PlayerPtr == building->House) { + building->Update_Buildables(); + } + } + } + } +} + + +/*********************************************************************************************** + * HouseClass::Attacked -- Lets player know if base is under attack. * + * * + * Call this function whenever a building is attacked (with malice). This function will * + * then announce to the player that his base is under attack. It checks to make sure that * + * this is referring to the player's house rather than the enemy's. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/27/1994 JLB : Created. * + *=============================================================================================*/ +void HouseClass::Attacked(void) +{ + Validate(); + if (SpeakAttackDelay.Expired() && PlayerPtr->Class->House == Class->House) { + Speak(VOX_BASE_UNDER_ATTACK); + SpeakAttackDelay.Set(Options.Normalize_Delay(SPEAK_DELAY)); + + /* + ** If there is a trigger event associated with being attacked, process it + ** now. + */ + for (int index = 0; index < HouseTriggers[Class->House].Count(); index++) { + HouseTriggers[Class->House][index]->Spring(EVENT_ATTACKED, Class->House); + } + } +} + + +/*********************************************************************************************** + * HouseClass::Harvested -- Adds Tiberium to the harvest storage. * + * * + * Use this routine whenever Tiberium is harvested. The Tiberium is stored equally between * + * all storage capable buildings for the house. Harvested Tiberium adds to the credit * + * value of the house, but only up to the maximum storage capacity that the house can * + * currently maintain. * + * * + * INPUT: tiberium -- The number of Tiberium credits to add to the House's total. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/25/1995 JLB : Created. * + *=============================================================================================*/ +void HouseClass::Harvested(unsigned tiberium) +{ + Validate(); + long oldtib = Tiberium; + + Tiberium += tiberium; + if (Tiberium > Capacity) { + Tiberium = Capacity; + IsMaxedOut = true; + } + HarvestedCredits += tiberium; + Silo_Redraw_Check(oldtib, Capacity); +} + + +/*********************************************************************************************** + * HouseClass::Available_Money -- Fetches the total credit worth of the house. * + * * + * Use this routine to determine the total credit value of the house. This is the sum of * + * the harvested Tiberium in storage and the initial unspent cash reserves. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the total credit value of the house. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/25/1995 JLB : Created. * + *=============================================================================================*/ +long HouseClass::Available_Money(void) const +{ + Validate(); + return(Tiberium + Credits); +} + + +/*********************************************************************************************** + * HouseClass::Spend_Money -- Removes money from the house. * + * * + * Use this routine to extract money from the house. Typically, this is a result of * + * production spending. The money is extracted from available cash reserves first. When * + * cash reserves are exhausted, then Tiberium is consumed. * + * * + * INPUT: money -- The amount of money to spend. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/25/1995 JLB : Created. * + * 06/20/1995 JLB : Spends Tiberium before spending cash. * + *=============================================================================================*/ +void HouseClass::Spend_Money(unsigned money) +{ + Validate(); + long oldtib = Tiberium; + if (money > Tiberium) { + money -= (unsigned)Tiberium; + Tiberium = 0; + Credits -= money; + } else { + Tiberium -= money; + } + Silo_Redraw_Check(oldtib, Capacity); + CreditsSpent += money; +} + + +/*********************************************************************************************** + * HouseClass::Refund_Money -- Refunds money to back to the house. * + * * + * Use this routine when money needs to be refunded back to the house. This can occur when * + * construction is aborted. At this point, the exact breakdown of Tiberium or initial * + * credits used for the orignal purchase is lost. Presume as much of the money is in the * + * form of Tiberium as storage capacity will allow. * + * * + * INPUT: money -- The number of credits to refund back to the house. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/25/1995 JLB : Created. * + * 06/01/1995 JLB : Refunded money is never lost * + *=============================================================================================*/ +void HouseClass::Refund_Money(unsigned money) +{ + Validate(); + Credits += money; +} + + +/*********************************************************************************************** + * HouseClass::Adjust_Capacity -- Adjusts the house Tiberium storage capacity. * + * * + * Use this routine to adjust the maximum storage capacity for the house. This storage * + * capacity will limit the number of Tiberium credits that can be stored at any one time. * + * * + * INPUT: adjust -- The adjustment to the Tiberium storage capacity. * + * * + * inanger -- Is this a forced adjustment to capacity due to some hostile event? * + * * + * OUTPUT: Returns with the number of Tiberium credits lost. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/25/1995 JLB : Created. * + *=============================================================================================*/ +int HouseClass::Adjust_Capacity(int adjust, bool inanger) +{ + Validate(); + long oldcap = Capacity; + int retval = 0; + + Capacity += adjust; + Capacity = MAX(Capacity, 0L); + if (Tiberium > Capacity) { + retval = Tiberium - Capacity; + Tiberium = Capacity; + if (!inanger) { + Refund_Money(retval); + retval = 0; + } else { + IsMaxedOut = true; + } + } + Silo_Redraw_Check(Tiberium, oldcap); + return(retval); +} + + +/*********************************************************************************************** + * HouseClass::Silo_Redraw_Check -- Flags silos to be redrawn if necessary. * + * * + * Call this routine when either the capacity or tiberium levels change for a house. This * + * routine will determine if the aggregate tiberium storage level will result in the * + * silos changing their imagery. If this is detected, then all the silos for this house * + * are flagged to be redrawn. * + * * + * INPUT: oldtib -- Pre-change tiberium level. * + * * + * oldcap -- Pre-change tiberium storage capacity. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/02/1995 JLB : Created. * + *=============================================================================================*/ +void HouseClass::Silo_Redraw_Check(long oldtib, long oldcap) +{ + Validate(); + int oldratio = 0; + if (oldcap) oldratio = (oldtib * 5) / oldcap; + int newratio = 0; + if (Capacity) newratio = (Tiberium * 5) / Capacity; + + if (oldratio != newratio) { + for (int index = 0; index < Buildings.Count(); index++) { + BuildingClass * b = Buildings.Ptr(index); + if (b && !b->IsInLimbo && b->House == this && *b == STRUCT_STORAGE) { + b->Mark(MARK_CHANGE); + } + } + } +} + + +/*********************************************************************************************** + * HouseClass::Read_INI -- Reads house specific data from INI. * + * * + * This routine reads the house specific data for a particular * + * scenario from the scenario INI file. Typical data includes starting * + * credits, maximum unit count, etc. * + * * + * INPUT: buffer -- Pointer to loaded scenario INI file. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/24/1994 JLB : Created. * + * 05/18/1995 JLB : Creates all houses. * + *=============================================================================================*/ +void HouseClass::Read_INI(char *buffer) +{ + HouseClass *p; // Pointer to current player data. + char const *hname; // Pointer to house name. + char buf[128]; + + for (HousesType index = HOUSE_FIRST; index < HOUSE_COUNT; index++) { + hname = HouseTypeClass::As_Reference(index).IniName; + int maxunit = WWGetPrivateProfileInt(hname, "MaxUnit", EACH_UNIT_MAX, buffer); + + maxunit = MAX(maxunit, 150); + + int maxbuilding = WWGetPrivateProfileInt(hname, "MaxBuilding", EACH_BUILDING_MAX, buffer); + + maxbuilding = MAX(maxbuilding, 150); + + int credits = WWGetPrivateProfileInt(hname, "Credits", 0, buffer); + + p = new HouseClass(index); + + p->MaxBuilding = maxbuilding; + p->MaxUnit = maxunit; + p->Credits = (long)credits * 100; + p->InitialCredits = p->Credits; + WWGetPrivateProfileString(hname, "Edge", "", buf, sizeof(buf)-1, buffer); + p->Edge = Source_From_Name(buf); + if (p->Edge == SOURCE_NONE) { + p->Edge = SOURCE_NORTH; + } + + if (GameToPlay == GAME_NORMAL) { + WWGetPrivateProfileString(hname, "Allies", "", buf, sizeof(buf)-1, buffer); + if (strlen(buf)) { + char * tok = strtok(buf, ", \t"); + while (tok) { + HousesType h = HouseTypeClass::From_Name(tok); + p->Make_Ally(h); + tok = strtok(NULL, ", \t"); + } + + } else { + + /* + ** Special case for when no allies are specified in the INI file. + ** The GDI side defaults to considering the neutral side to be + ** friendly. + */ + if (p->Class->House == HOUSE_GOOD) { + p->Make_Ally(HOUSE_NEUTRAL); + } + } + } + } +} + + +/*********************************************************************************************** + * HouseClass::Write_INI -- Writes house specific data into INI file. * + * * + * Use this routine to write the house specific data (for all houses) into the INI file. * + * It is used by the scenario editor when saving the scenario. * + * * + * INPUT: buffer -- INI file staging buffer. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +void HouseClass::Write_INI(char *buffer) +{ + for (HousesType i = HOUSE_FIRST; i < HOUSE_COUNT; i++) { + HouseClass * p = As_Pointer(i); + + if (p) { + WWWritePrivateProfileInt(p->Class->IniName, "Credits", (int)(p->Credits / 100), buffer); + WWWritePrivateProfileString(p->Class->IniName, "Edge", Name_From_Source(p->Edge), buffer); + WWWritePrivateProfileInt(p->Class->IniName, "MaxUnit", p->MaxUnit, buffer); + WWWritePrivateProfileInt(p->Class->IniName, "MaxBuilding", p->MaxBuilding, buffer); + + bool first = true; + char sbuffer[100] = ""; + for (HousesType house = HOUSE_FIRST; house < HOUSE_COUNT; house++) { + if (p->Is_Ally(house)) { + if (!first) strcat(sbuffer, ","); + strcat(sbuffer, As_Pointer(house)->Class->IniName); + first = false; + } + } + WWWritePrivateProfileString(p->Class->IniName, "Allies", sbuffer, buffer); + } + } +} + + +/*********************************************************************************************** + * HouseClass::Is_Ally -- Determines if the specified house is an ally. * + * * + * This routine will determine if the house number specified is a ally to this house. * + * * + * INPUT: house -- The house number to check to see if it is an ally. * + * * + * OUTPUT: Is the house an ally? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +bool HouseClass::Is_Ally(HousesType house) const +{ + Validate(); + if (house != HOUSE_NONE) { + return(((1<Class->House)); + } + return(false); +} + + +/*********************************************************************************************** + * HouseClass::Is_Ally -- Checks to see if the object is an ally. * + * * + * This routine will examine the specified object and return whether it is an ally or not. * + * * + * INPUT: object -- The object to examine to see if it is an ally. * + * * + * OUTPUT: Is the specified object an ally? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +bool HouseClass::Is_Ally(ObjectClass const * object) const +{ + Validate(); + if (object) { + return(Is_Ally(object->Owner())); + } + return(false); +} + + +/*********************************************************************************************** + * HouseClass::Make_Ally -- Make the specified house an ally. * + * * + * This routine will make the specified house an ally to this house. An allied house will * + * not be considered a threat or potential target. * + * * + * INPUT: house -- The house to make an ally of this house. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + * 08/08/1995 JLB : Breaks off combat when ally commences. * + *=============================================================================================*/ +void HouseClass::Make_Ally(HousesType house) +{ + Validate(); + if (house != HOUSE_NONE && !Is_Ally(house)) { + + /* + ** If in normal game play but the house is defeated, then don't allow the ally + ** key to work. + */ + if (!ScenarioInit && (IsDefeated || house == HOUSE_JP)) return; + + Allies |= (1 << house); + +#ifdef CHEAT_KEYS + if (Debug_Flag) { + HouseClass * enemy = HouseClass::As_Pointer(house); + if (enemy && !enemy->Is_Ally(this)) { + enemy->Make_Ally(Class->House); + } + } +#endif + + if ((Debug_Flag || GameToPlay != GAME_NORMAL) && !ScenarioInit) { + char buffer[80]; + + /* + ** Sweep through all techno objects and perform a cheeseball tarcom clear to ensure + ** that fighting will most likely stop when the cease fire begins. + */ + for (int index = 0; index < Logic.Count(); index++) { + ObjectClass * object = Logic[index]; + + if (object && !object->IsInLimbo && object->Owner() == Class->House) { + TARGET target = ((TechnoClass *)object)->TarCom; + if (Target_Legal(target) && As_Techno(target)) { + if (Is_Ally(As_Techno(target))) { + ((TechnoClass *)object)->TarCom = TARGET_NONE; + } + } + } + } + + sprintf(buffer, Text_String(TXT_HAS_ALLIED), Name, HouseClass::As_Pointer(house)->Name); + Messages.Add_Message(buffer, MPlayerTColors[RemapColor], TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_FULLSHADOW, 1200, 0, 0); + Map.Flag_To_Redraw(false); + } + } +} + + +/*********************************************************************************************** + * HouseClass::Make_Enemy -- Make an enemy of the house specified. * + * * + * This routine will flag the house specified so that it will be an enemy to this house. * + * Enemy houses are legal targets for attack. * + * * + * INPUT: house -- The house to make an enemy of this house. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + * 07/27/1995 JLB : Making war is a bilateral aaction. * + *=============================================================================================*/ +void HouseClass::Make_Enemy(HousesType house) +{ + Validate(); + if (house != HOUSE_NONE && Is_Ally(house)) { + HouseClass * enemy = HouseClass::As_Pointer(house); + Allies &= ~(1 << house); + if (enemy && enemy->Is_Ally(this)) { + enemy->Allies &= ~(1 << Class->House); + } + + if ((Debug_Flag || GameToPlay != GAME_NORMAL) && !ScenarioInit) { + char buffer[80]; + + sprintf(buffer, Text_String(TXT_AT_WAR), Name, enemy->Name); + Messages.Add_Message(buffer, MPlayerTColors[RemapColor], TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_FULLSHADOW, 600, 0, 0); + Map.Flag_To_Redraw(false); + } + } +} + + +/*********************************************************************************************** + * HouseClass::Remap_Table -- Fetches the remap table for this house object. * + * * + * This routine will return with the remap table to use when displaying an object owned * + * by this house. If the object is blushing (flashing), then the lightening remap table is * + * always used. The "unit" parameter allows proper remap selection for those houses that * + * have a different remap table for buildings or units. * + * * + * INPUT: blushing -- Is the object blushing (flashing)? * + * * + * unit -- Is the object a vehicle or infantry? * + * * + * OUTPUT: Returns with a pointer to the remap table to use when drawing this object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +unsigned char const * HouseClass::Remap_Table(bool blushing, bool unit) const +{ + Validate(); + if (blushing) return(&Map.FadingLight[0]); + + /* + ** For normal game play, return the TypeClass's remap table for this + ** house type + */ + if (GameToPlay == GAME_NORMAL) { + /* + ** Special case exception for Nod and single player only. Remap + ** buildings to red as opposed to the default color of bluegrey. + */ + if (!unit && Class->House == HOUSE_BAD) { + return(RemapRed); + } + + return(Class->RemapTable); + } else { + + /* + ** For multiplayer games, return the remap table for this exact house instance. + */ + return(RemapTable); + } +} + + +/*********************************************************************************************** + * HouseClass::Suggested_New_Team -- Determine what team should be created. * + * * + * This routine examines the house condition and returns with the team that it thinks * + * should be created. The units that are not currently a member of a team are examined * + * to determine the team needed. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the team type that should be created. If no team should * + * be created, then NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +TeamTypeClass const * HouseClass::Suggested_New_Team(bool alertcheck) +{ + Validate(); + return(TeamTypeClass::Suggested_New_Team(this, UScan, IScan, IsAlerted && alertcheck)); +} + + +/*********************************************************************************************** + * HouseClass::Adjust_Threat -- Adjust threat for the region specified. * + * * + * This routine is called when the threat rating for a region needs to change. The region * + * and threat adjustment are provided. * + * * + * INPUT: region -- The region that adjustment is to occur on. * + * * + * threat -- The threat adjustment to perform. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +void HouseClass::Adjust_Threat(int region, int threat) +{ + Validate(); + static int _val[] = { + -MAP_REGION_WIDTH - 1, -MAP_REGION_WIDTH, -MAP_REGION_WIDTH + 1, + -1, 0, 1, + MAP_REGION_WIDTH -1, MAP_REGION_WIDTH, MAP_REGION_WIDTH +1 + }; + static int _thr[] = { + 2, 1, 2, + 1, 0, 1, + 2, 1, 2 + }; + int neg; + int *val = &_val[0]; + int *thr = &_thr[0]; + + if (threat < 0) { + threat = -threat; + neg = true; + } else { + neg = false; + } + + for (int lp = 0; lp < 9; lp ++) { + Regions[region + *val].Adjust_Threat(threat >> *thr, neg); + val++; + thr++; + } +} + + +/*********************************************************************************************** + * HouseClass::Begin_Production -- Starts production of the specified object type. * + * * + * This routine is called from the event system. It will start production for the object * + * type specified. This will be reflected in the sidebar as well as the house factory * + * tracking variables. * + * * + * INPUT: type -- The type of object to begin production on. * + * * + * id -- The subtype of object. * + * * + * OUTPUT: Returns with the reason why, or why not, production was started. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +ProdFailType HouseClass::Begin_Production(RTTIType type, int id) +{ + Validate(); + int * factory = 0; + int result = true; + bool initial_start = false; + FactoryClass * fptr; + TechnoTypeClass const * tech = Fetch_Techno_Type(type, id); + + switch (type) { + case RTTI_AIRCRAFT: + case RTTI_AIRCRAFTTYPE: + factory = &AircraftFactory; + break; + + case RTTI_UNIT: + case RTTI_UNITTYPE: + factory = &UnitFactory; + break; + + case RTTI_BUILDING: + case RTTI_BUILDINGTYPE: + factory = &BuildingFactory; + break; + + case RTTI_INFANTRY: + case RTTI_INFANTRYTYPE: + factory = &InfantryFactory; + break; + + case RTTI_SPECIAL: + factory = &SpecialFactory; + break; + } + + /* + ** Check for legality of the production object type suggested. + */ + if (!factory) return(PROD_ILLEGAL); + + /* + ** If the house is already busy producing the requested object, then + ** return with this failure code, unless we are restarting production. + */ + if (*factory != -1) { + fptr = Factories.Raw_Ptr(*factory); + if (fptr->Is_Building()) + return(PROD_CANT); + } else { + fptr = new FactoryClass(); + if (!fptr) return(PROD_CANT); + *factory = Factories.ID(fptr); + result = (tech) ? fptr->Set(*tech, *this) : fptr->Set(id, *this); + initial_start = true; + } + + if (result) { + fptr->Start(); + + /* + ** Link this factory to the sidebar so that proper graphic feedback + ** can take place. + */ + if (PlayerPtr == this) { + Map.Factory_Link(*factory, type, id); + } + + return(PROD_OK); + } + return(PROD_CANT); +} + + +/*********************************************************************************************** + * HouseClass::Suspend_Production -- Temporarily puts production on hold. * + * * + * This routine is called from the event system whenever the production of the specified * + * type needs to be suspended. The suspended production will be reflected in the sidebar * + * as well as in the house control structure. * + * * + * INPUT: type -- The type of object that production is being suspended for. * + * * + * OUTPUT: Returns why, or why not, production was suspended. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +ProdFailType HouseClass::Suspend_Production(RTTIType type) +{ + Validate(); + int * factory = 0; + + switch (type) { + case RTTI_AIRCRAFT: + case RTTI_AIRCRAFTTYPE: + factory = &AircraftFactory; + break; + + case RTTI_UNIT: + case RTTI_UNITTYPE: + factory = &UnitFactory; + break; + + case RTTI_BUILDING: + case RTTI_BUILDINGTYPE: + factory = &BuildingFactory; + break; + + case RTTI_INFANTRY: + case RTTI_INFANTRYTYPE: + factory = &InfantryFactory; + break; + + case RTTI_SPECIAL: + factory = &SpecialFactory; + break; + } + + /* + ** Check for legality of the production object type suggested. + */ + if (!factory) return(PROD_ILLEGAL); + + /* + ** If the house is already busy producing the requested object, then + ** return with this failure code. + */ + if (*factory == -1) return(PROD_CANT); + + /* + ** Create the factory pointer object. + ** If the factory could not be created, then report this error condition. + */ + FactoryClass * fptr = Factories.Raw_Ptr(*factory); + if (!fptr) return(PROD_CANT); + + /* + ** Actually suspend the production. + */ + fptr->Suspend(); + + /* + ** Tell the sidebar that it needs to be redrawn because of this. + */ + if (PlayerPtr == this) { + Map.SidebarClass::IsToRedraw = true; + Map.SidebarClass::Column[0].IsToRedraw = true; + Map.SidebarClass::Column[1].IsToRedraw = true; + Map.Flag_To_Redraw(false); + } + + return(PROD_OK); +} + + +/*********************************************************************************************** + * HouseClass::Abandon_Production -- Abandons production of item type specified. * + * * + * This routine is called from the event system whenever production must be abandoned for * + * the type specified. This will remove the factory and pending object from the sidebar as * + * well as from the house factory record. * + * * + * INPUT: type -- The object type that production is being suspended for. * + * * + * OUTPUT: Returns the reason why or why not, production was suspended. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +ProdFailType HouseClass::Abandon_Production(RTTIType type) +{ + Validate(); + int * factory = 0; + + switch (type) { + case RTTI_AIRCRAFT: + case RTTI_AIRCRAFTTYPE: + factory = &AircraftFactory; + break; + + case RTTI_UNIT: + case RTTI_UNITTYPE: + factory = &UnitFactory; + break; + + case RTTI_BUILDING: + case RTTI_BUILDINGTYPE: + factory = &BuildingFactory; + break; + + case RTTI_INFANTRY: + case RTTI_INFANTRYTYPE: + factory = &InfantryFactory; + break; + + case RTTI_SPECIAL: + factory = &SpecialFactory; + break; + } + + /* + ** Check for legality of the production object type suggested. + */ + if (!factory) return(PROD_ILLEGAL); + + /* + ** If there is no factory to abandon, then return with a failure code. + */ + if (*factory == -1) return(PROD_CANT); + + /* + ** Fetch the factory pointer object. + */ + FactoryClass * fptr = Factories.Raw_Ptr(*factory); + if (!fptr) return(PROD_CANT); + + /* + ** Tell the sidebar that it needs to be redrawn because of this. + */ + if (PlayerPtr == this) { + Map.Abandon_Production(type, *factory); + + if (type == RTTI_BUILDINGTYPE || type == RTTI_BUILDING) { + Map.PendingObjectPtr = 0; + Map.PendingObject = 0; + Map.PendingHouse = HOUSE_NONE; + Map.Set_Cursor_Shape(0); + } + } + + /* + ** Abandon production of the object. + */ + fptr->Abandon(); + delete fptr; + *factory = -1; + + return(PROD_OK); +} + + +/*********************************************************************************************** + * HouseClass::Special_Weapon_AI -- Fires special weapon. * + * * + * This routine will pick a good target to fire the special weapon specified. * + * * + * INPUT: id -- The special weapon id to fire. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/24/1995 PWG : Created. * + *=============================================================================================*/ +void HouseClass::Special_Weapon_AI(SpecialWeaponType id) +{ + Validate(); + /* + ** Loop through all of the building objects on the map + ** and see which ones are available. + */ + BuildingClass * bestptr = NULL; + int best = -1; + + for (int index = 0; index < Buildings.Count(); index++) { + BuildingClass * b = Buildings.Ptr(index); + + /* + ** If the building is valid, not in limbo, not in the process of + ** being destroyed and not our ally, then we can consider it. + */ + if (b && !b->IsInLimbo && b->Strength && !Is_Ally(b)) { + if (b->Value() > best || best == -1) { + best = b->Value(); + bestptr = b; + } + } + } + + if (bestptr) { + CELL cell = Coord_Cell(bestptr->Center_Coord()); + Place_Special_Blast(id, cell); + } +} + + +/*********************************************************************************************** + * HouseClass::Place_Special_Blast -- Place a special blast effect at location specified. * + * * + * This routine will create a blast effect at the cell specified. This is the result of * + * the special weapons. * + * * + * INPUT: id -- The special weapon id number. * + * * + * cell -- The location where the special weapon attack is to occur. * + * * + * OUTPUT: Was the special weapon successfully fired at the location specified? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : commented. * + * 07/25/1995 JLB : Added scatter effect for nuclear bomb. * + * 07/28/1995 JLB : Revamped to use super weapon class controller. * + *=============================================================================================*/ +bool HouseClass::Place_Special_Blast(SpecialWeaponType id, CELL cell) +{ + Validate(); + BuildingClass * launchsite = 0; + AnimClass * anim = 0; + int index; + switch (id) { + + case SPC_ION_CANNON: + if (IonCannon.Is_Ready()) { + anim = new AnimClass(ANIM_ION_CANNON, Cell_Coord(cell)); + if (anim) anim->Owner = Class->House; + if (this == PlayerPtr) { + Map.IsTargettingMode = false; + } + IonCannon.Discharged(PlayerPtr == this); + IsRecalcNeeded = true; + } + break; + + case SPC_NUCLEAR_BOMB: + if (NukeStrike.Is_Ready()) { + + +#ifdef NEVER + /* + ** Scatter the nuclear bomb impact point into an adjacent cell. + */ + for (;;) { + CELL newcell = Adjacent_Cell(cell, Random_Pick(FACING_N, FACING_COUNT)); + if (Map.In_Radar(newcell)) { + cell = newcell; + break; + } + } +#endif + + /* + ** Search for a suitable launch site for this missile. + */ + for (index = 0; index < Buildings.Count(); index++) { + BuildingClass * b = Buildings.Ptr(index); + if (b && !b->IsInLimbo && b->House == this && *b == STRUCT_TEMPLE) { + launchsite = b; + break; + } + } + + /* + ** If a launch site was found, then proceed with the normal launch + ** sequence. + */ + if (launchsite) { + launchsite->Assign_Mission(MISSION_MISSILE); + launchsite->Commence(); + NukeDest = cell; + NukePieces = 0; + + } else { + + /* + ** Only in the multiplayer version can the nuclear bomb be + ** sent from some off screen source. + */ + if (GameToPlay == GAME_NORMAL) return(false); + + /* + ** Since no launch site was found, just bring the missile in + ** directly from the North map edge. + */ + BulletClass *bullet = new BulletClass(BULLET_NUKE_DOWN); + if (bullet) { + COORDINATE start = Cell_Coord(XY_Cell(Cell_X(cell), 0)); + bullet->Assign_Target(::As_Target(cell)); + bullet->Payback = NULL; + bullet->Strength = 1; + if (!bullet->Unlimbo(start, DIR_S)) { + delete bullet; + } else { + bullet->PrimaryFacing.Set_Current(DIR_S); + } + Speak(VOX_INCOMING_NUKE); + Sound_Effect(VOC_NUKE_FIRE, start); + } + } + if (this == PlayerPtr) { + Map.IsTargettingMode = false; + } + NukeStrike.Discharged(this == PlayerPtr); + IsRecalcNeeded = true; + } + break; + + case SPC_AIR_STRIKE: + if (AirStrike.Is_Ready()) { + int strike = 1; + if (GameToPlay == GAME_NORMAL) { + strike = Bound(BuildLevel/3, 1, 3); + } else { + strike = Bound(MPlayerUnitCount/5, 1, 3); + } + Create_Air_Reinforcement(this, AIRCRAFT_A10, strike, MISSION_HUNT, ::As_Target(cell), TARGET_NONE); + if (this == PlayerPtr) { + Map.IsTargettingMode = false; + } + AirStrike.Discharged(this == PlayerPtr); + IsRecalcNeeded = true; + } + break; + } + return(true); +} + + +/*********************************************************************************************** + * HouseClass::Place_Object -- Places the object (building) at location specified. * + * * + * This routine is called when a building has been produced and now must be placed on * + * the map. When the player clicks on the map, this routine is ultimately called when the * + * event passes through the event queue system. * + * * + * INPUT: type -- The object type to place. The actual object is lifted from the sidebar. * + * * + * * + * cell -- The location to place the object on the map. * + * * + * OUTPUT: Was the placement successful? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : Created. * + *=============================================================================================*/ +bool HouseClass::Place_Object(RTTIType type, CELL cell) +{ + Validate(); + TechnoClass * tech = 0; + FactoryClass * factory = 0; + + switch (type) { + case RTTI_AIRCRAFT: + case RTTI_AIRCRAFTTYPE: + if (AircraftFactory != -1) { + factory = Factories.Raw_Ptr(AircraftFactory); + } + break; + + case RTTI_INFANTRY: + case RTTI_INFANTRYTYPE: + if (InfantryFactory != -1) { + factory = Factories.Raw_Ptr(InfantryFactory); + } + break; + + case RTTI_UNIT: + case RTTI_UNITTYPE: + if (UnitFactory != -1) { + factory = Factories.Raw_Ptr(UnitFactory); + } + break; + + case RTTI_BUILDING: + case RTTI_BUILDINGTYPE: + if (BuildingFactory != -1) { + factory = Factories.Raw_Ptr(BuildingFactory); + } + break; + } + + /* + ** Only if there is a factory active for this type, can it be "placed". + ** In the case of a missing factory, then this request is completely bogus -- + ** ignore it. This might occur if, between two events to exit the same + ** object, the mouse was clicked on the sidebar to start building again. + ** The second placement event should NOT try to place the object that is + ** just starting construction. + */ + if (factory && factory->Has_Completed()) { + tech = factory->Get_Object(); + + if (cell == -1) { + TechnoClass * pending = factory->Get_Object(); + if (pending) { + TechnoClass * builder = pending->Who_Can_Build_Me(false, false); + + if (builder && builder->Exit_Object(pending)) { + + /* + ** Since the object has left the factory under its own power, delete + ** the production manager tied to this slot in the sidebar. Its job + ** has been completed. + */ + factory->Completed(); + Abandon_Production(type); + } else { + + /* + ** The object could not leave under it's own power. Just wait + ** until the player tries to place the object again. + */ + return(false); + } + } + + } else { + + if (tech) { + TechnoClass * builder = tech->Who_Can_Build_Me(false, false); + if (builder) { + + /* + ** Ensures that the proximity check is performed even when the building is + ** placed by way of a remote event. + */ + if (tech->What_Am_I() != RTTI_BUILDING || ((BuildingClass *)tech)->Passes_Proximity_Check(cell)) { + builder->Transmit_Message(RADIO_HELLO, tech); + if (tech->Unlimbo(Cell_Coord(cell))) { + factory->Completed(); + Abandon_Production(type); + + if (PlayerPtr == this) { + Sound_Effect(VOC_SLAM); + Map.Set_Cursor_Shape(0); + Map.PendingObjectPtr = 0; + Map.PendingObject = 0; + Map.PendingHouse = HOUSE_NONE; + } + return(true); + } else { + if (this == PlayerPtr) { + Speak(VOX_DEPLOY); + } + } + builder->Transmit_Message(RADIO_OVER_OUT); + } + } + return(false); + + } else { + + // Play a bad sound here? + return(false); + } + } + } + + return(true); +} + + +/*********************************************************************************************** + * HouseClass::Manual_Place -- Inform display system of building placement mode. * + * * + * This routine will inform the display system that building placement mode has begun. * + * The cursor will be created that matches the layout of the building shape. * + * * + * INPUT: builder -- The factory that is building this object. * + * * + * object -- The building that is going to be placed down on the map. * + * * + * OUTPUT: Was the building placement mode successfully initiated? * + * * + * WARNINGS: This merely adjusts the cursor shape. Nothing that affects networked games * + * is affected. * + * * + * HISTORY: * + * 05/04/1995 JLB : Created. * + * 05/30/1995 JLB : Uses the Bib_And_Offset() function to determine bib size. * + *=============================================================================================*/ +bool HouseClass::Manual_Place(BuildingClass * builder, BuildingClass * object) +{ + Validate(); + if (this == PlayerPtr && !Map.PendingObject && builder && object) { + + /* + ** Ensures that object selection doesn't remain when + ** building placement takes place. + */ + Unselect_All(); + + Map.Repair_Mode_Control(0); + Map.Sell_Mode_Control(0); + + Map.PendingObject = object->Class; + Map.PendingObjectPtr = object; + Map.PendingHouse = Class->House; + + Map.Set_Cursor_Shape(object->Occupy_List(true)); + Map.Set_Cursor_Pos(Coord_Cell(builder->Coord)); + builder->Mark(MARK_CHANGE); + return(true); + } + return(false); +} + + +#ifdef OBSOLETE +/*********************************************************************************************** + * HouseClass::Init_Ion_Cannon -- Initialize the ion cannon countdown. * + * * + * This routine will initiate the ion cannon charging countdown. It will add the ion * + * cannon to the sidebar if it isn't there and it is specified to be added. * + * * + * INPUT: first_time -- Set to true if the ion cannon must be added to the sidebar. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : commented * + *=============================================================================================*/ +void HouseClass::Init_Ion_Cannon(SpecialControlType control) +{ + Validate(); + switch (control) { + case CONTROL_RESET: + if (IonCannonPresent) { + IonOldStage = -1; + IonControl.Set(ION_CANNON_GONE_TIME); + if (PlayerPtr == this) { + Map.Add(RTTI_SPECIAL, SPC_ION_CANNON); + if (!ScenarioInit) { + Speak(VOX_ION_CHARGING); + } + } + } + break; + + /* + ** Adds the special no-prerequisite ion cannon option. + */ + case CONTROL_ONE_TIME: + if (!IonCannonPresent) { + Init_Ion_Cannon(CONTROL_ADD); + IonOneTimeFlag = true; + } + break; + + /* + ** Adds the normal legitimate ion cannon option. If there was + ** already a one-time ion cannon available, the charging state + ** is NOT interrupted. + */ + case CONTROL_ADD: + IonOneTimeFlag = false; + if (!IconCannonPresent) { + IonCannonPresent = true; + IonReady = false; + Init_Ion_Cannon(CONTROL_RESET); + } + break; + + case CONTROL_REMOVE: + break; + } + + + if (!(first_time && IonCannonPresent)) { + + if (IonCannonPresent && IonOneTimeFlag) { + IonOneTimeFlag = false; + if (this == PlayerPtr) Map.Recalc(); + return; + } + + if (first_time && this == PlayerPtr) { + Map.Add(RTTI_SPECIAL, SPC_ION_CANNON); + } + + if (!ScenarioInit) { + if (this == PlayerPtr) { + Speak(VOX_ION_CHARGING); + } + } + + IonControl.Set(ION_CANNON_GONE_TIME); + IonCannonPresent = true; + IonReady = false; + IonOldStage = -1; + IonOneTimeFlag = one_time_effect; + } else { + if (first_time && IonCannonPresent && !one_time_effect && IonOneTimeFlag) { + IonOneTimeFlag = false; + } + } +} +#ifdef NEVER +void HouseClass::Init_Ion_Cannon(bool first_time, bool one_time_effect) +{ + Validate(); + if (!(first_time && IonCannonPresent)) { + + if (IonCannonPresent && IonOneTimeFlag) { + IonOneTimeFlag = false; + if (this == PlayerPtr) Map.Recalc(); + return; + } + + if (first_time && this == PlayerPtr) { + Map.Add(RTTI_SPECIAL, SPC_ION_CANNON); + } + + if (!ScenarioInit) { + if (this == PlayerPtr) { + Speak(VOX_ION_CHARGING); + } + } + + IonControl.Set(ION_CANNON_GONE_TIME); + IonCannonPresent = true; + IonReady = false; + IonOldStage = -1; + IonOneTimeFlag = one_time_effect; + } else { + if (first_time && IonCannonPresent && !one_time_effect && IonOneTimeFlag) { + IonOneTimeFlag = false; + } + } +} +#endif + + +/*********************************************************************************************** + * HouseClass::Remove_Ion_Cannon -- Disables the ion cannon. * + * * + * This routine will disable the ion cannon. It is called when the ion cannon cannot * + * establish a command link to the ground (usually when there is insufficient power). * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : commented * + *=============================================================================================*/ +void HouseClass::Remove_Ion_Cannon(void) +{ + Validate(); + if (IonCannonPresent) { + IonCannonPresent = false; + IonOneTimeFlag = false; + IonReady = false; + IonControl.Clear(); + IonOldStage = -1; + } +} +#endif + + +/*************************************************************************** + * HouseClass::Clobber_All -- removes house & all its objects * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/16/1995 BRR : Created. * + * 06/09/1995 JLB : Handles aircraft. * + *=========================================================================*/ +void HouseClass::Clobber_All(void) +{ + Validate(); + int i; + + for (i = 0; i < ::Aircraft.Count(); i++) { + if (::Aircraft.Ptr(i)->House == this) { + delete ::Aircraft.Ptr(i); + i--; + } + } + for (i = 0; i < ::Units.Count(); i++) { + if (::Units.Ptr(i)->House == this) { + delete ::Units.Ptr(i); + i--; + } + } + for (i = 0; i < Infantry.Count(); i++) { + if (Infantry.Ptr(i)->House == this) { + delete Infantry.Ptr(i); + i--; + } + } + for (i = 0; i < Buildings.Count(); i++) { + if (Buildings.Ptr(i)->House == this) { + delete Buildings.Ptr(i); + i--; + } + } + for (i = 0; i < TeamTypes.Count(); i++) { + if (TeamTypes.Ptr(i)->House == Class->House) { + delete TeamTypes.Ptr(i); + i--; + } + } + for (i = 0; i < Triggers.Count(); i++) { + if (Triggers.Ptr(i)->House == Class->House) { + delete Triggers.Ptr(i); + i--; + } + } + + delete this; +} + + +#ifdef NEVER +/*********************************************************************************************** + * HouseClass::Init_Nuke_Bomb -- Adds (if necessary) the atom bomb to the sidebar. * + * * + * Use this routine whenever a piece of atom bomb has been discovered (also at scenario * + * start). It will add the nuclear bomb button to the sidebar if necessary. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : commented * + *=============================================================================================*/ +void HouseClass::Init_Nuke_Bomb(bool first_time, bool one_time_effect) +{ + Validate(); + if (!first_time || !NukePresent) { + + if (NukePresent && NukeOneTimeFlag) { + NukeOneTimeFlag = false; + if (this == PlayerPtr) Map.Recalc(); + return; + } + + if (first_time && this == PlayerPtr) { + Map.Add(RTTI_SPECIAL, SPC_NUCLEAR_BOMB); + } + + NukeControl.Set(NUKE_GONE_TIME); + NukePresent = true; + NukeReady = false; + NukeOldStage = -1; + NukeOneTimeFlag = one_time_effect; + + } else { + if (!one_time_effect && NukeOneTimeFlag) { + NukeOneTimeFlag = false; + } + } +} + + +/*********************************************************************************************** + * HouseClass::Remove_Nuke_Bomb -- Removes the nuclear bomb from the sidebar. * + * * + * This routine will remove the nuclear bomb from the sidebar. It should be called when * + * the nuclear strike has been launched. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : commented * + * 07/25/1995 JLB : Handles recharge reset logic. * + *=============================================================================================*/ +void HouseClass::Remove_Nuke_Bomb(void) +{ + Validate(); + if (NukePresent && !NukeOneTimeFlag) { + NukePresent = false; + NukeControl.Clear(); + NukeOldStage = -1; + NukeReady = false; + } +} + + +/*********************************************************************************************** + * HouseClass::Init_Air_Strike -- Add (or reset) the air strike sidebar button. * + * * + * This routine will activate (add if so indicated) the air strike button to the sidebar. * + * Call this routine when events indicate that a special air strike is available. * + * * + * INPUT: first_time -- If the air strike button is to be added, then this will be true. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : commented * + *=============================================================================================*/ +void HouseClass::Init_Air_Strike(bool first_time, bool one_time_effect) +{ + Validate(); + if (!(first_time && AirPresent)) { + + if (AirPresent && AirOneTimeFlag) { + AirOneTimeFlag = false; + AirPresent = false; + Map.Recalc(); + return; + } + + if (first_time) { + if (PlayerPtr == this) { + Map.Add(RTTI_SPECIAL, SPC_AIR_STRIKE); + } + AirControl.Set(0); + } else { + AirControl.Set(AIR_CANNON_GONE_TIME); + } + + AirReady = first_time; + AirPresent = true; + AirOldStage = -1; + AirOneTimeFlag = one_time_effect; + + if (AirReady && !IsHuman) { + Special_Weapon_AI(SPC_AIR_STRIKE); + } + } else { + if (first_time && AirPresent && !one_time_effect && AirOneTimeFlag) { + AirOneTimeFlag = false; + } + } +} + + +/*********************************************************************************************** + * HouseClass::Remove_Air_Strike -- Removes the air strike button from the sidebar. * + * * + * This routine will remove the air strike button from the sidebar. Call this routine when * + * the air strike has been launched. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : commented * + *=============================================================================================*/ +void HouseClass::Remove_Air_Strike(void) +{ + Validate(); + AirPresent = false; + AirReady = false; + AirControl.Clear(); + AirOldStage = -1; +} + + +/*********************************************************************************************** + * HouseClass::Make_Air_Strike_Available -- Make the airstrike available. * + * * + * This routine will make the airstrike available. Typically, this results in a new icon * + * added to the sidebar. * + * * + * INPUT: present -- The the airstrike being added? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/20/1995 JLB : Created. * + *=============================================================================================*/ +void HouseClass::Make_Air_Strike_Available(bool present, bool one_time_effect) +{ + Validate(); + Init_Air_Strike(true, one_time_effect); + AirPresent = present; +} +#endif + + +/*********************************************************************************************** + * HouseClass::Add_Nuke_Piece -- Add a nuclear piece to the collection. * + * * + * This routine will a the specified nuclear piece to the house collection of parts. When * + * all the pieces have been added, a nuclear strike ability is made available. * + * * + * INPUT: piece -- The nuclear piece to add. If equal to "-1", then the next possible piece * + * is added. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/20/1995 JLB : Created. * + *=============================================================================================*/ +void HouseClass::Add_Nuke_Piece(int piece) +{ + Validate(); + if (piece == -1) { + piece = 1; + if (!(NukePieces & 0x01)) { + piece = 1; + } + if (!(NukePieces & 0x02)) { + piece = 2; + } + if (!(NukePieces & 0x04)) { + piece = 3; + } + } + NukePieces |= 1 << (piece - 1); +// Init_Nuke_Bomb(false); +} + + +/*********************************************************************************************** + * HouseClass::Detach -- Removes specified object from house tracking systems. * + * * + * This routine is called when an object is to be removed from the game system. If the * + * specified object is part of the house tracking system, then it will be removed. * + * * + * INPUT: target -- The target value of the object that is to be removed from the game. * + * * + * all -- Is the target going away for good as opposed to just cloaking/hiding? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : commented * + *=============================================================================================*/ +void HouseClass::Detach(TARGET, bool ) +{ + Validate(); +// if (LaunchSite == target) { +// LaunchSite = TARGET_NONE; +// } +} + + +/*********************************************************************************************** + * HouseClass::Does_Enemy_Building_Exist -- Checks for enemy building of specified type. * + * * + * This routine will examine the enemy houses and if there is a building owned by one * + * of those house, true will be returned. * + * * + * INPUT: btype -- The building type to check for. * + * * + * OUTPUT: Does a building of the specified type exist for one of the enemy houses? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1995 JLB : Created. * + *=============================================================================================*/ +bool HouseClass::Does_Enemy_Building_Exist(StructType btype) const +{ + Validate(); + int bflag = 1L << btype; + for (HousesType index = HOUSE_FIRST; index < HOUSE_COUNT; index++) { + HouseClass * house = HouseClass::As_Pointer(index); + + if (house && !Is_Ally(house) && (house->BScan & bflag) != 0) { + return(true); + } + } + return(false); +} + + +/*********************************************************************************************** + * HouseClass::Suggest_New_Object -- Determine what would the next buildable object be. * + * * + * This routine will examine the house status and return with a techno type pointer to * + * the object type that it thinks should be created. The type is restricted to match the * + * type specified. Typical use of this routine is by computer controlled factories. * + * * + * INPUT: objecttype -- The type of object to restrict the scan for. * + * * + * OUTPUT: Returns with a pointer to a techno type for the object type that should be * + * created. If no object should be created, then NULL is returned. * + * * + * WARNINGS: This is a time consuming routine. Only call when necessary. * + * * + * HISTORY: * + * 05/23/1995 JLB : Created. * + *=============================================================================================*/ +TechnoTypeClass const * HouseClass::Suggest_New_Object(RTTIType objecttype) const +{ + Validate(); + TechnoTypeClass const * techno = NULL; + + switch (objecttype) { + + /* + ** Unit construction is based on the rule that up to twice the number required + ** to fill all teams will be created. + */ + case RTTI_UNIT: + case RTTI_UNITTYPE: + if (CurUnits < MaxUnit) { + + /* + ** A computer controlled house will try to build a replacement + ** harvester if possible. Never replace harvesters if the game + ** is in easy mode. + */ + if (!Special.IsEasy && !IsHuman && (ActiveBScan & STRUCTF_REFINERY) && !(UScan & UNITF_HARVESTER)) { + techno = &UnitTypeClass::As_Reference(UNIT_HARVESTER); + if (techno->Scenario <= BuildLevel) break; + techno = 0; + } + + int counter[UNIT_COUNT]; + if (GameToPlay == GAME_NORMAL) { + memset(counter, 0x00, sizeof(counter)); + } else { + for (UnitType index = UNIT_FIRST; index < UNIT_COUNT; index++) { + if (Can_Build(index, Class->House) && UnitTypeClass::As_Reference(index).Level <= BuildLevel) { + counter[index] = 16; + } else { + counter[index] = 0; + } + } + } + + /* + ** Build a list of the maximum of each type we wish to produce. This will be + ** twice the number required to fill all teams. + */ + for (int index = 0; index < Teams.Count(); index++) { + TeamClass * tptr = Teams.Ptr(index); + if (tptr) { + TeamTypeClass const * team = tptr->Class; + + if ((/*team->IsReinforcable || */!tptr->IsFullStrength) && team->House == Class->House) { + for (int subindex = 0; subindex < team->ClassCount; subindex++) { + if (team->Class[subindex]->What_Am_I() == RTTI_UNITTYPE) { + counter[((UnitTypeClass const *)(team->Class[subindex]))->Type] = 1; +// counter[((UnitTypeClass const *)(team->Class[subindex]))->Type] += team->DesiredNum[subindex]*2; + } + } + } + } + } + + /* + ** Team types that are flagged as prebuilt, will always try to produce enough + ** to fill one team of this type regardless of whether there is a team active + ** of that type. + */ + for (index = 0; index < TeamTypes.Count(); index++) { + TeamTypeClass const * team = TeamTypes.Ptr(index); + if (team) { + if (team->House == Class->House && team->IsPrebuilt && (!team->IsAutocreate || IsAlerted)) { + for (int subindex = 0; subindex < team->ClassCount; subindex++) { + if (team->Class[subindex]->What_Am_I() == RTTI_UNITTYPE) { + int subtype = ((UnitTypeClass const *)(team->Class[subindex]))->Type; + counter[subtype] = MAX(counter[subtype], team->DesiredNum[subindex]); + } + } + } + } + } + + /* + ** Reduce the theoretical maximum by the actual number of objects currently + ** in play. + */ + for (int uindex = 0; uindex < Units.Count(); uindex++) { + UnitClass * unit = Units.Ptr(uindex); + if (unit && !unit->Team && unit->House == this && (unit->Mission != MISSION_GUARD_AREA && unit->Mission != MISSION_HUNT && unit->Mission != MISSION_STICKY && unit->Mission != MISSION_SLEEP)) { + counter[unit->Class->Type]--; + } + } + + /* + ** Pick to build the most needed object but don't consider those object that + ** can't be built because of scenario restrictions or insufficient cash. + */ + int bestval = -1; + int bestcount = 0; + UnitType bestlist[UNIT_COUNT]; + for (UnitType utype = UNIT_FIRST; utype < UNIT_COUNT; utype++) { + if (counter[utype] > 0 && Can_Build(utype, Class->House) && UnitTypeClass::As_Reference(utype).Cost_Of() <= Available_Money()) { + if (bestval == -1 || bestval < counter[utype]) { + bestval = counter[utype]; + bestcount = 0; + } + bestlist[bestcount++] = utype; + } + } + + /* + ** The unit type to build is now known. Fetch a pointer to the techno type class. + */ + if (bestcount) { + techno = &UnitTypeClass::As_Reference(bestlist[Random_Pick(0, bestcount-1)]); + } + } + break; + + /* + ** Infantry construction is based on the rule that up to twice the number required + ** to fill all teams will be created. + */ + case RTTI_INFANTRY: + case RTTI_INFANTRYTYPE: + if (CurUnits < MaxUnit) { + int counter[INFANTRY_COUNT]; + if (GameToPlay == GAME_NORMAL) { + memset(counter, 0x00, sizeof(counter)); + } else { + for (InfantryType index = INFANTRY_FIRST; index < INFANTRY_COUNT; index++) { + if (Can_Build(index, Class->House) && InfantryTypeClass::As_Reference(index).Level <= BuildLevel) { + counter[index] = 16; + } else { + counter[index] = 0; + } + } + } + + /* + ** Build a list of the maximum of each type we wish to produce. This will be + ** twice the number required to fill all teams. + */ + for (int index = 0; index < Teams.Count(); index++) { + TeamClass * tptr = Teams.Ptr(index); + if (tptr) { + TeamTypeClass const * team = tptr->Class; + + if ((team->IsReinforcable || !tptr->IsFullStrength) && team->House == Class->House) { + for (int subindex = 0; subindex < team->ClassCount; subindex++) { + if (team->Class[subindex]->What_Am_I() == RTTI_INFANTRYTYPE) { + counter[((InfantryTypeClass const *)(team->Class[subindex]))->Type] += team->DesiredNum[subindex]+1; + } + } + } + } + } + + /* + ** Team types that are flagged as prebuilt, will always try to produce enough + ** to fill one team of this type regardless of whether there is a team active + ** of that type. + */ + for (index = 0; index < TeamTypes.Count(); index++) { + TeamTypeClass const * team = TeamTypes.Ptr(index); + if (team) { + if (team->House == Class->House && team->IsPrebuilt && (!team->IsAutocreate || IsAlerted)) { + for (int subindex = 0; subindex < team->ClassCount; subindex++) { + if (team->Class[subindex]->What_Am_I() == RTTI_INFANTRYTYPE) { + int subtype = ((InfantryTypeClass const *)(team->Class[subindex]))->Type; +// counter[subtype] = 1; + counter[subtype] = MAX(counter[subtype], team->DesiredNum[subindex]); + counter[subtype] = MIN(counter[subtype], 5); + } + } + } + } + } + + /* + ** Reduce the theoretical maximum by the actual number of objects currently + ** in play. + */ + for (int uindex = 0; uindex < Infantry.Count(); uindex++) { + InfantryClass * infantry = Infantry.Ptr(uindex); + if (infantry && !infantry->Team && infantry->House == this && (infantry->Mission != MISSION_GUARD_AREA && infantry->Mission != MISSION_HUNT && infantry->Mission != MISSION_STICKY && infantry->Mission != MISSION_SLEEP)) { + counter[infantry->Class->Type]--; + } + } + + /* + ** Pick to build the most needed object but don't consider those object that + ** can't be built because of scenario restrictions or insufficient cash. + */ + int bestval = -1; + int bestcount = 0; + InfantryType bestlist[INFANTRY_COUNT]; + for (InfantryType utype = INFANTRY_FIRST; utype < INFANTRY_COUNT; utype++) { + if (counter[utype] > 0 && Can_Build(utype, Class->House) && InfantryTypeClass::As_Reference(utype).Cost_Of() <= Available_Money()) { + if (bestval == -1 || bestval < counter[utype]) { + bestval = counter[utype]; + bestcount = 0; + } + bestlist[bestcount++] = utype; + } + } + + /* + ** The infantry type to build is now known. Fetch a pointer to the techno type class. + */ + if (bestcount) { + techno = &InfantryTypeClass::As_Reference(bestlist[Random_Pick(0, bestcount-1)]); + } + } + break; + + /* + ** Building construction is based upon the preconstruction list. + */ + case RTTI_BUILDING: + case RTTI_BUILDINGTYPE: + if (CurBuildings < MaxBuilding) { + BaseNodeClass * node = Base.Next_Buildable(); + if (node) { + techno = &BuildingTypeClass::As_Reference(node->Type); + } + } + break; + } + return(techno); +} + + +/*********************************************************************************************** + * HouseClass::Flag_Remove -- Removes the flag from the specified target. * + * * + * This routine will remove the flag attached to the specified target object or cell. * + * Call this routine before placing the object down. This is called inherently by the * + * the Flag_Attach() functions. * + * * + * INPUT: target -- The target that the flag was attached to but will be removed from. * + * * + * set_home -- if true, clears the flag's waypoint designation * + * * + * OUTPUT: Was the flag successfully removed from the specified target? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1995 JLB : Created. * + *=============================================================================================*/ +bool HouseClass::Flag_Remove(TARGET target, bool set_home) +{ + Validate(); + int rc; + + if (Target_Legal(target)) { + + /* + ** Remove the flag from a unit + */ + UnitClass * object = As_Unit(target); + if (object) { + rc = object->Flag_Remove(); + if (rc && FlagLocation == target) { + FlagLocation = TARGET_NONE; + } + + } else { + + /* + ** Remove the flag from a cell + */ + CELL cell = As_Cell(target); + if (Map.In_Radar(cell)) { + rc = Map[cell].Flag_Remove(); + if (rc && FlagLocation == target) { + FlagLocation = TARGET_NONE; + } + } + } + + /* + ** Handle the flag home cell: + ** If 'set_home' is set, clear the home value & the cell's overlay + */ + if (set_home) { + if (FlagHome) { + Map[FlagHome].Overlay = OVERLAY_NONE; + Map.Flag_Cell(FlagHome); + FlagHome = 0; + } + } + + return(rc); + } + return(false); +} + + +/*********************************************************************************************** + * HouseClass::Flag_Attach -- Attach flag to specified cell (or thereabouts). * + * * + * This routine will attach the house flag to the location specified. If the location * + * cannot contain the flag, then a suitable nearby location will be selected. * + * * + * INPUT: cell -- The desired cell location to place the flag. * + * * + * set_home -- if true, resets the flag's waypoint designation * + * * + * OUTPUT: Was the flag successfully placed? * + * * + * WARNINGS: The cell picked for the flag might very likely not be the cell requested. * + * Check the FlagLocation value to determine the final cell resting spot. * + * * + * HISTORY: * + * 05/23/1995 JLB : Created. * + *=============================================================================================*/ +bool HouseClass::Flag_Attach(CELL cell, bool set_home) +{ + Validate(); + bool rc; + bool clockwise; + FacingType rot; + FacingType fcounter; + + /* + ** Randomly decide if we're going to search cells clockwise or counter- + ** clockwise + */ + clockwise = IRandom(0,1); + + /* + ** Only continue if this cell is a legal placement cell. + */ + if (Map.In_Radar(cell)) { + + /* + ** If the flag already exists, then it must be removed from the object + ** it is attached to. + */ + Flag_Remove(FlagLocation, set_home); + + /* + ** Attach the flag to the cell specified. If it can't be placed, then pick + ** a nearby cell where it can be placed. + */ + CELL newcell = cell; + rc = Map[newcell].Flag_Place(Class->House); + if (!rc) { + + /* + ** Loop for increasing distance from the desired cell. + ** For each distance, randomly pick a starting direction. Between + ** this and the clockwise/counterclockwise random value, the flag + ** should appear to be placed fairly randomly. + */ + for (int dist = 1; dist < 32; dist++) { + + /* + ** Clockwise search. + */ + if (clockwise) { + rot = (FacingType)IRandom(FACING_N, FACING_NW); + for (fcounter = FACING_N; fcounter <= FACING_NW; fcounter++) { + newcell = Coord_Cell(Coord_Move(Cell_Coord(cell), Facing_Dir(rot), dist*256)); + if (Map.In_Radar(newcell) && Map[newcell].Flag_Place(Class->House)) { + dist = 32; + rc = true; + break; + } + rot++; + if (rot > FACING_NW) rot = FACING_N; + } + } else { + + /* + ** Counter-clockwise search + */ + rot = (FacingType)IRandom (FACING_N, FACING_NW); + for (fcounter = FACING_NW; fcounter >= FACING_N; fcounter--) { + newcell = Coord_Cell(Coord_Move(Cell_Coord(cell), Facing_Dir(rot), dist*256)); + if (Map.In_Radar(newcell) && Map[newcell].Flag_Place(Class->House)) { + dist = 32; + rc = true; + break; + } + rot--; + if (rot < FACING_N) + rot = FACING_NW; + } + } + } + } + + /* + ** If we've found a spot for the flag, place the flag at the new cell. + ** if 'set_home' is set, OR this house has no current flag home cell, + ** mark that cell as this house's flag home cell. + */ + if (rc) { + FlagLocation = As_Target(newcell); + + if (set_home || FlagHome == 0) { + Map[newcell].Overlay = OVERLAY_FLAG_SPOT; + FlagHome = newcell; + } + } + + return(rc); + } + return(false); +} + + +/*********************************************************************************************** + * HouseClass::Flag_Attach -- Attaches the house flag the specified unit. * + * * + * This routine will attach the house flag to the specified unit. This routine is called * + * when a unit drives over a cell containing a flag. * + * * + * INPUT: object -- Pointer to the object that the house flag is to be attached to. * + * * + * set_home -- if true, clears the flag's waypoint designation * + * * + * OUTPUT: Was the flag attached successfully? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1995 JLB : Created. * + *=============================================================================================*/ +bool HouseClass::Flag_Attach(UnitClass * object, bool set_home) +{ + Validate(); + if (object && !object->IsInLimbo) { + Flag_Remove(FlagLocation, set_home); + + /* + ** Attach the flag to the object. + */ + object->Flag_Attach(Class->House); + FlagLocation = object->As_Target(); + return(true); + } + return(false); +} + + +/*************************************************************************** + * HouseClass::MPlayer_Defeated -- multiplayer; house is defeated * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/25/1995 BRR : Created. * + *=========================================================================*/ +void HouseClass::MPlayer_Defeated(void) +{ + Validate(); + char txt[80]; + int i,j,k; + unsigned char id; + HousesType house; + HouseClass *hptr; + HouseClass *hptr2; + int num_alive; + int num_humans; + int all_allies; + int max_index; + int max_count; + int count; + int score_index[MAX_PLAYERS]; // array of each multi-player's index into + // the score array + + /*------------------------------------------------------------------------ + Set the defeat flag for this house + ------------------------------------------------------------------------*/ + IsDefeated = true; + + /*------------------------------------------------------------------------ + Remove this house's flag & flag home cell + ------------------------------------------------------------------------*/ + if (Special.IsCaptureTheFlag) { + if (FlagLocation) { + Flag_Remove(FlagLocation,true); + } else { + if (FlagHome) { + Flag_Remove(FlagHome,true); + } + } + } + + /*------------------------------------------------------------------------ + If this is me: + - Set MPlayerObiWan, so I can only send messages to all players, and + not just one (so I can't be obnoxiously omnipotent) + - Reveal the map + - Add my defeat message + ------------------------------------------------------------------------*/ + if (PlayerPtr == this) { + MPlayerObiWan = 1; + Debug_Unshroud = true; + HiddenPage.Clear(); + Map.Flag_To_Redraw(true); + + /*..................................................................... + Pop up a message showing that I was defeated + .....................................................................*/ + sprintf(txt,Text_String(TXT_PLAYER_DEFEATED), MPlayerName); + Messages.Add_Message(txt, MPlayerTColors[MPlayerColorIdx], TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_FULLSHADOW, 600, 0, 0); + Map.Flag_To_Redraw(false); + + } else { + + /*------------------------------------------------------------------------ + If it wasn't me, find out who was defeated + ------------------------------------------------------------------------*/ + if (IsHuman) { + sprintf(txt, Text_String(TXT_PLAYER_DEFEATED), Text_String(TXT_UNKNOWN)); + id = 0; + for (i = 0; i < MPlayerCount; i++) { + house = MPlayerHouses[i]; + if (HouseClass::As_Pointer(house) == this) { + sprintf (txt,Text_String(TXT_PLAYER_DEFEATED), MPlayerNames[i]); + id = MPlayerID[i]; + } + } + + Messages.Add_Message(txt, MPlayerTColors[MPlayerID_To_ColorIndex(id)], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, 0, 0); + Map.Flag_To_Redraw(false); + } + } + + /*------------------------------------------------------------------------ + Find out how many players are left alive. + ------------------------------------------------------------------------*/ + num_alive = 0; + num_humans = 0; + for (i = 0; i < MPlayerMax; i++) { + hptr = HouseClass::As_Pointer((HousesType)(HOUSE_MULTI1 + i)); + if (hptr && hptr->IsDefeated==0) { + if (hptr->IsHuman) + num_humans++; + num_alive++; + } + } + + /*------------------------------------------------------------------------ + If all the houses left alive are allied with each other, then in reality + there's only one player left: + ------------------------------------------------------------------------*/ + all_allies = 1; + for (i = 0; i < MPlayerMax; i++) { + /*..................................................................... + Get a pointer to this house + .....................................................................*/ + hptr = HouseClass::As_Pointer((HousesType)(HOUSE_MULTI1 + i)); + if (!hptr || hptr->IsDefeated) + continue; + + /*..................................................................... + Loop through all houses; if there's one left alive that this house + isn't allied with, then all_allies will be false + .....................................................................*/ + for (j = 0; j < MPlayerMax; j++) { + hptr2 = HouseClass::As_Pointer((HousesType)(HOUSE_MULTI1 + j)); + if (!hptr2) + continue; + if (!hptr2->IsDefeated && !hptr->Is_Ally(hptr2)) { + all_allies = 0; + break; + } + } + if (!all_allies) + break; + } + /*........................................................................ + If all houses left are allies, set 'num_alive' to 1; game over. + ........................................................................*/ + if (all_allies) + num_alive = 1; + + /*------------------------------------------------------------------------ + If there's only one human player left or no humans left, the game is over: + - Determine whether this player wins or loses, based on the state of the + MPlayerObiWan flag + - Find all players' indices in the MPlayerScore array + - Tally up scores for this game + ------------------------------------------------------------------------*/ + if (num_alive == 1 || num_humans == 0) { + if (PlayerPtr->IsDefeated) { + PlayerLoses = true; + } else { + PlayerWins = true; + } + + /*--------------------------------------------------------------------- + Find each player's score index + ---------------------------------------------------------------------*/ + for (i = 0; i < MPlayerCount; i++) { + score_index[i] = -1; + + /*.................................................................. + Search for this player's name in the MPlayerScore array + ..................................................................*/ + for (j = 0; j < MPlayerNumScores; j++) { + if (!stricmp(MPlayerNames[i],MPlayerScore[j].Name)) { + score_index[i] = j; + break; + } + } + + /*.................................................................. + If the index is still -1, the name wasn't found; add a new entry. + ..................................................................*/ + if (score_index[i] == -1) { + if (MPlayerNumScores < MAX_MULTI_NAMES) { + score_index[i] = MPlayerNumScores; + MPlayerNumScores++; + } else { + + /*............................................................... + For each player in the scores array, count the # of '-1' entries + from this game backwards; the one with the most is the one that + hasn't played the longest; replace him with this new guy. + ...............................................................*/ + max_index = 0; + max_count = 0; + for (j = 0; j < MPlayerNumScores; j++) { + count = 0; + for (k = MPlayerNumScores - 1; k >= 0; k--) { + if (MPlayerScore[j].Kills[k]==-1) { + count++; + } else { + break; + } + } + if (count > max_count) { + max_count = count; + max_index = j; + } + } + score_index[i] = max_index; + } + + /*............................................................... + Initialize this score entry + ...............................................................*/ + MPlayerScore[score_index[i]].Wins = 0; + strcpy (MPlayerScore[score_index[i]].Name,MPlayerNames[i]); + for (j = 0; j < MAX_MULTI_GAMES; j++) + MPlayerScore[score_index[i]].Kills[j] = -1; + } + + /*.................................................................. + Init this player's Kills to 0 (-1 means he didn't play this round; + 0 means he played but got no kills). + ..................................................................*/ + MPlayerScore[score_index[i]].Kills[MPlayerCurGame] = 0; + + /*.................................................................. + Init this player's color to his last-used color index + ..................................................................*/ + MPlayerScore[score_index[i]].Color = MPlayerID_To_ColorIndex(MPlayerID[i]); + } + +#if 0 // (This is the old method of tallying scores: + /*--------------------------------------------------------------------- + Tally up the scores for this game: + - For each house: + - If this house is human & wasn't defeated, its the winner + - If this house was defeated, find out who did it & increment their + Kills value. + ---------------------------------------------------------------------*/ + for (house = HOUSE_MULTI1; house < (HOUSE_MULTI1 + MPlayerMax); house++) { + hptr = HouseClass::As_Pointer(house); + if (!hptr) continue; + + if (!hptr->IsDefeated) { + + /*............................................................... + If this is the winning house, find which player it was & increment + their 'Wins' value + ...............................................................*/ + if (hptr->IsHuman) { + for (i = 0; i < MPlayerCount; i++) { + if (house == MPlayerHouses[i]) { + MPlayerScore[score_index[i]].Wins++; + MPlayerWinner = score_index[i]; + } + } + } + } else { + + /*.................................................................. + This house was defeated; find which player who defeated him & increment + his 'Kills' value for this game + ..................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + if (hptr->WhoLastHurtMe == MPlayerHouses[i]) { + MPlayerScore[score_index[i]].Kills[MPlayerCurGame]++; + } + } + } + } + +#else // This is the new method: + + /*--------------------------------------------------------------------- + Tally up the scores for this game: + - For each player: + - If this player is undefeated this round, he's the winner + - Each player's Kills value is the sum of the unit's they killed + ---------------------------------------------------------------------*/ + for (i = 0; i < MPlayerCount; i++) { + hptr = HouseClass::As_Pointer(MPlayerHouses[i]); + + /*.................................................................. + If this house was undefeated, it must have been the winner. (If + no human houses are undefeated, the computer won.) + ..................................................................*/ + if (!hptr->IsDefeated) { + MPlayerScore[score_index[i]].Wins++; + MPlayerWinner = score_index[i]; + } + + /*.................................................................. + Tally up all kills for this player + ..................................................................*/ + for (house = HOUSE_FIRST; house < HOUSE_COUNT; house++) { + + MPlayerScore[score_index[i]].Kills[MPlayerCurGame] += + hptr->UnitsKilled[house]; + + MPlayerScore[score_index[i]].Kills[MPlayerCurGame] += + hptr->BuildingsKilled[house]; + } + } +#endif + + /*--------------------------------------------------------------------- + Destroy all the IPX connections, since we have to go through the rest + of the Main_Loop() before we detect that the game is over, and we'll + end up waiting for frame sync packets from the other machines. + ---------------------------------------------------------------------*/ + if (GameToPlay==GAME_IPX || GameToPlay == GAME_INTERNET) { + i = 0; + while (Ipx.Num_Connections() && (i++ < 1000) ) { + id = Ipx.Connection_ID(0); + Ipx.Delete_Connection(id); + } + MPlayerCount = 0; + } + } + + /*------------------------------------------------------------------------ + Be sure our messages get displayed, even if we're about to exit. + ------------------------------------------------------------------------*/ + Map.Render(); +} + + +/*************************************************************************** + * HouseClass::Blowup_All -- blows up everything * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/16/1995 BRR : Created. * + * 06/09/1995 JLB : Handles aircraft. * + *=========================================================================*/ +void HouseClass::Blowup_All(void) +{ + Validate(); + int i; + int damage; + UnitClass *uptr; + InfantryClass *iptr; + BuildingClass *bptr; + int count; + WarheadType warhead; + + /* + ** Find everything owned by this house & blast it with a huge amount of damage + ** at zero range. Do units before infantry, so the units' drivers are killed + ** too. Using Explosion_Damage is like dropping a big bomb right on the + ** object; it will also damage anything around it. + */ + for (i = 0; i < ::Units.Count(); i++) { + if (::Units.Ptr(i)->House == this && !::Units.Ptr(i)->IsInLimbo) { + uptr = ::Units.Ptr(i); + + /* + ** Some units can't be killed with one shot, so keep damaging them until + ** they're gone. The unit will destroy itself, and put an infantry in + ** its place. When the unit destroys itself, decrement 'i' since + ** its pointer will be removed from the active pointer list. + */ + count = 0; + while (::Units.Ptr(i)==uptr && uptr->Strength) { + damage = 0x7fff; + Explosion_Damage(uptr->Center_Coord(), damage, NULL, WARHEAD_HE); + count++; + if (count > 5) { + delete uptr; + break; + } + } + i--; + } + } + + /* + ** Destroy all aircraft owned by this house. + */ + for (i = 0; i < ::Aircraft.Count(); i++) { + if (::Aircraft.Ptr(i)->House == this && !::Aircraft.Ptr(i)->IsInLimbo) { + AircraftClass * aptr = ::Aircraft.Ptr(i); + + damage = 0x7fff; + aptr->Take_Damage(damage, 0, WARHEAD_HE, NULL); + if (!aptr->IsActive) { + i--; + } + } + } + + /* + ** Buildings don't delete themselves when they die; they shake the screen + ** and begin a countdown, so don't decrement 'i' when it's destroyed. + */ + for (i = 0; i < Buildings.Count(); i++) { + if (Buildings.Ptr(i)->House == this && !Buildings.Ptr(i)->IsInLimbo) { + bptr = Buildings.Ptr(i); + + count = 0; + bptr->IsSurvivorless = true; + while (Buildings.Ptr(i)==bptr && bptr->Strength) { + damage = 0x7fff; + Explosion_Damage(bptr->Center_Coord(), damage, NULL, WARHEAD_HE); + count++; + if (count > 5) { + delete bptr; + break; + } + } + } + } + + /* + ** Infantry don't delete themselves when they die; they go into a death- + ** animation sequence, so there's no need to decrement 'i' when they die. + ** Infantry should die by different types of warheads, so their death + ** anims aren't all synchronized. + */ + for (i = 0; i < Infantry.Count(); i++) { + if (Infantry.Ptr(i)->House == this && !Infantry.Ptr(i)->IsInLimbo) { + iptr = Infantry.Ptr(i); + + count = 0; + while (Infantry.Ptr(i)==iptr && iptr->Strength) { + damage = 0x7fff; + warhead = (WarheadType)IRandom (WARHEAD_SA, WARHEAD_FIRE); + Explosion_Damage(iptr->Center_Coord(), damage, NULL, warhead); + if (iptr->IsActive) { + damage = 0x7fff; + iptr->Take_Damage(damage, 0, warhead); + } + + count++; + if (count > 5) { + delete iptr; + break; + } + } + } + } + +#ifdef NEVER + /* + ** Just delete the teams & triggers for this house. + */ + for (i = 0; i < TeamTypes.Count(); i++) { + if (TeamTypes.Ptr(i)->House == Class->House) { + delete TeamTypes.Ptr(i); + i--; + } + } + for (i = 0; i < Triggers.Count(); i++) { + if (Triggers.Ptr(i)->House == Class->House) { + delete Triggers.Ptr(i); + i--; + } + } +#endif +} + + +/*********************************************************************************************** + * HouseClass::Flag_To_Die -- Flags the house to blow up soon. * + * * + * When this routine is called, the house will blow up after a period of time. Typically * + * this is called when the flag is captured or the HQ destroyed. * + * * + * INPUT: none * + * * + * OUTPUT: Was the house flagged to blow up? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/20/1995 JLB : Created. * + *=============================================================================================*/ +bool HouseClass::Flag_To_Die(void) +{ + Validate(); + if (!IsToWin && !IsToDie && !IsToLose) { + IsToDie = true; + if (IsV107) { + BorrowedTime = TICKS_PER_SECOND * 3; + } else { + BorrowedTime = TICKS_PER_SECOND * 1; + } + } + return(IsToDie); +} + + +/*********************************************************************************************** + * HouseClass::Flag_To_Win -- Flags the house to win soon. * + * * + * When this routine is called, the house will be declared the winner after a period of * + * time. * + * * + * INPUT: none * + * * + * OUTPUT: Was the house flagged to win? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/20/1995 JLB : Created. * + *=============================================================================================*/ +bool HouseClass::Flag_To_Win(void) +{ + Validate(); + if (!IsToWin && !IsToDie && !IsToLose) { + IsToWin = true; + if (IsV107) { + BorrowedTime = TICKS_PER_SECOND * 3; + } else { + BorrowedTime = TICKS_PER_SECOND * 1; + } + } + return(IsToWin); +} + + +/*********************************************************************************************** + * HouseClass::Flag_To_Lose -- Flags the house to die soon. * + * * + * When this routine is called, it will spell the doom of this house. In a short while * + * all of the object owned by this house will explode. Typical use of this routine is when * + * the flag has been captured or the command vehicle has been destroyed. * + * * + * INPUT: none * + * * + * OUTPUT: Has the doom been initiated? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/12/1995 JLB : Created. * + *=============================================================================================*/ +bool HouseClass::Flag_To_Lose(void) +{ + Validate(); + IsToWin = false; + if (!IsToDie && !IsToLose) { + IsToLose = true; + if (IsV107) { + BorrowedTime = TICKS_PER_SECOND * 3; + } else { + BorrowedTime = TICKS_PER_SECOND * 1; + } + } + return(IsToLose); +} + + +/*********************************************************************************************** + * HouseClass::Init_Data -- Initializes the multiplayer color data. * + * * + * This routine is called when initializing the color and remap data for this house. The * + * primary user of this routine is the multiplayer version of the game. * + * * + * INPUT: color -- The color of this house. * + * * + * house -- The house that this should act like. * + * * + * credits -- The initial credits to assign to this house. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +void HouseClass::Init_Data(PlayerColorType color, HousesType house, int credits) +{ + Validate(); + Credits = InitialCredits = credits; + ActLike = house; + RemapColor = color; + switch (color) { + case REMAP_YELLOW: + RemapTable = RemapYellow; + ((unsigned char &)Class->Color) = 157; + ((unsigned char &)Class->BrightColor) = 5; + break; + + case REMAP_RED: + RemapTable = RemapRed; + ((unsigned char &)Class->Color) = 123; + ((unsigned char &)Class->BrightColor) = 127; + break; + + case REMAP_AQUA: + RemapTable = RemapBlueGreen; + ((unsigned char &)Class->Color) = 135; + ((unsigned char &)Class->BrightColor) = 2; + break; + + case REMAP_ORANGE: + RemapTable = RemapOrange; + ((unsigned char &)Class->Color) = 26; + ((unsigned char &)Class->BrightColor) = 24; + break; + + case REMAP_GREEN: + RemapTable = RemapGreen; + ((unsigned char &)Class->Color) = 167; + ((unsigned char &)Class->BrightColor) = 159; + break; + + case REMAP_BLUE: + RemapTable = RemapBlue; + ((unsigned char &)Class->Color) = 203; + ((unsigned char &)Class->BrightColor) = 201; + break; + } +} + + +/*********************************************************************************************** + * HouseClass::Power_Fraction -- Fetches the current power output rating. * + * * + * Use this routine to fetch the current power output as a fixed point fraction. The * + * value 0x0100 is 100% power. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with power rating as a fixed pointer number. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/22/1995 JLB : Created. * + *=============================================================================================*/ +int HouseClass::Power_Fraction(void) const +{ + Validate(); + if (Power) { + if (Drain) { + return(Cardinal_To_Fixed(Drain, Power)); + } else { + return(0x0100); + } + } + return(0); +} + + +/*********************************************************************************************** + * HouseClass::Has_Nuke_Device -- Deteremines if the house has a nuclear device. * + * * + * This routine checks to see if the house has a nuclear device to launch. A nuclear * + * device is available when the necessary parts have been retrieved in earlier scenarios * + * or if this is the multiplayer version. * + * * + * INPUT: none * + * * + * OUTPUT: Does the house have a nuclear device? * + * * + * WARNINGS: This does not check to see if there is a suitable launch facility (i.e., the * + * Temple of Nod), only that there is a nuclear device potential. * + * * + * HISTORY: * + * 07/24/1995 JLB : Created. * + *=============================================================================================*/ +bool HouseClass::Has_Nuke_Device(void) +{ + Validate(); + if (GameToPlay != GAME_NORMAL || !IsHuman) return(true); + return((NukePieces & 0x07) == 0x07); +} + + +/*********************************************************************************************** + * HouseClass::Sell_Wall -- Tries to sell the wall at the specified location. * + * * + * This routine will try to sell the wall at the specified location. If there is a wall * + * present and it is owned by this house, then it can be sold. * + * * + * INPUT: cell -- The cell that wall selling is desired. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/05/1995 JLB : Created. * + *=============================================================================================*/ +void HouseClass::Sell_Wall(CELL cell) +{ + Validate(); + if ((unsigned)cell > 0) { + OverlayType overlay = Map[cell].Overlay; + + if (overlay != OVERLAY_NONE && Map[cell].Owner == Class->House) { + OverlayTypeClass const & optr = OverlayTypeClass::As_Reference(overlay); + + if (optr.IsWall) { + int cost = 0; + switch (overlay) { + case OVERLAY_SANDBAG_WALL: + cost = BuildingTypeClass::As_Reference(STRUCT_SANDBAG_WALL).Cost_Of(); + break; + + case OVERLAY_CYCLONE_WALL: + cost = BuildingTypeClass::As_Reference(STRUCT_CYCLONE_WALL).Cost_Of(); + break; + + case OVERLAY_BRICK_WALL: + cost = BuildingTypeClass::As_Reference(STRUCT_BRICK_WALL).Cost_Of(); + break; + + case OVERLAY_BARBWIRE_WALL: + cost = BuildingTypeClass::As_Reference(STRUCT_BARBWIRE_WALL).Cost_Of(); + break; + + case OVERLAY_WOOD_WALL: + cost = BuildingTypeClass::As_Reference(STRUCT_WOOD_WALL).Cost_Of(); + break; + + default: + cost = 0; + break; + } + Refund_Money(cost/2); + Map[cell].Overlay = OVERLAY_NONE; + Map[cell].OverlayData = 0; + Map[cell].Owner = HOUSE_NONE; + Map[cell].Wall_Update(); + Map[cell].Recalc_Attributes(); + Map[cell].Redraw_Objects(); + ObjectClass::Detach_This_From_All(::As_Target(cell), true); + } + } + } +} diff --git a/HOUSE.H b/HOUSE.H new file mode 100644 index 0000000..89a1aa3 --- /dev/null +++ b/HOUSE.H @@ -0,0 +1,570 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\house.h_v 2.21 16 Oct 1995 16:46:14 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : HOUSE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 21, 1994 * + * * + * Last Update : May 21, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef HOUSE_H +#define HOUSE_H + +#include "type.h" +#include "region.h" +#include "vector.h" + +//extern "C" { +//unsigned Cardinal_To_Fixed(unsigned base, unsigned cardinal); +//} +class TriggerClass; +/**************************************************************************** +** Player control structure. Each player (computer or human) has one of +** these structures associated. These are located in a global array. +*/ +//#define REBUILD_MAX 5 // Maximum number of structures to rebuild. +class HouseClass { + public: + /* + ** Pointer to the HouseTypeClass that this house is "owned" by. + ** All constant data for a house type is stored in that class. + */ + HouseTypeClass const * const Class; + + /* + ** This is the house type that this house object should act like. This + ** value controls production choices and radar cover plate imagery. + */ + HousesType ActLike; + + /* + ** Is this player active? Usually that answer is true, but for civilians, it + ** might possibly be false. + */ + unsigned IsActive:1; + + /* + ** If this house is controlled by the player, then this flag will be true. The + ** computer controls all other active houses. + */ + unsigned IsHuman:1; + + /* + ** When the computer becomes alerted to the presence of the player's forces, it + ** begins production and attack logic. This flag is set to true if the human + ** player has been discovered by the computer. + */ + unsigned IsStarted:1; + + /* + ** When alerted, the house will create teams of the special "auto" type and + ** will generate appropriate units to fill those team types. + */ + unsigned IsAlerted:1; + + /* + ** If the house has been discovered, then this flag will be set + ** to true. However, the trigger even associated with discovery + ** will only be executed during the next house AI process. + */ + unsigned IsDiscovered:1; + + /* + ** If Tiberium storage is maxed out, then this flag will be set. At some point + ** the player is told of this fact and then this flag is cleared. This allows the + ** player to be told, but only occationally rather than continuously. + */ + unsigned IsMaxedOut:1; + + /* + ** If this house is played by a human in a multiplayer game, this flag + ** keeps track of whether this house has been defeated or not. + */ + unsigned IsDefeated:1; + + /* + ** These flags are used in conjunction with the BorrowedTime timer. When + ** that timer expires and one of these flags are set, then that event is + ** applied to the house. This allows a dramatic pause between the event + ** trigger and the result. + */ + unsigned IsToDie:1; + unsigned IsToWin:1; + unsigned IsToLose:1; + + /* + ** This flag is set when a transport carrying a civilian has been + ** successfully evacuated. It is presumed that a possible trigger + ** event will be sprung by this event. + */ + unsigned IsCivEvacuated:1; + + /* + ** If potentially something changed that might affect the sidebar list of + ** buildable objects, then this flag indicates that at the first LEGAL opportunity, + ** the sidebar will be recalculated. + */ + unsigned IsRecalcNeeded:1; + + /* + ** If the map has been completely revealed to the player, then this flag + ** will be set to true. By examining this flag, a second "reveal all map" + ** crate won't be given to the player. + */ + unsigned IsVisionary:1; + + /* + ** If a trigger has indicated that the airstrike option should appear, this flag + ** will be set to true. It is up to the normal house AI processing to actually + ** add the airstrike to the sidebar. + */ + unsigned IsAirstrikePending:1; + + /* + ** This records the existance of the three nuke weapon pieces. + */ + unsigned NukePieces:3; + + /* + ** This flag indicates that a free harvester is pending and will be + ** created when the FreeHarvester timer expires. + */ + unsigned IsFreeHarvester:1; + + TCountDownTimerClass FreeHarvester; + + /* + ** These super weapon control objects are used to control the recharge + ** and availability of these special weapons to this house. + */ + SuperClass IonCannon; + SuperClass AirStrike; + SuperClass NukeStrike; + + /* + ** This is a record of the last building that was built. For buildings that + ** were built as a part of scenario creation, it will be the last one + ** discovered. + */ + StructType JustBuilt; + + /* + ** This records the number of triggers associated with this house that are + ** blocking a win condition. A win will only occur if all the blocking + ** triggers have been deleted. + */ + int Blockage; + + /* + ** This timer controls the computer auto-attack logic. When this timer expires + ** and the house has been alerted, then it will create a set of attack + ** teams. + */ + TCountDownTimerClass AlertTime; + + /* + ** This timer is used to handle the delay between some catastrophic + ** event trigger and when it is actually carried out. + */ + TCountDownTimerClass BorrowedTime; + + /* + ** This is the last working scan bits for buildings. If a building is + ** active and owned by this house, it will have a bit set in this element + ** that corresponds to the building type number. Since this value is + ** accumulated over time, the "New" element contains the under-construction + ** version. + */ + unsigned long BScan; + unsigned long ActiveBScan; + unsigned long NewBScan; + unsigned long NewActiveBScan; + + /* + ** This is the last working scan bits for units. For every existing unit + ** type owned by this house, a corresponding bit is set in this element. As + ** the scan bits are being constructed, they are built into the "New" element + ** and then duplicated into the regular element at the end of every logic cycle. + */ + unsigned long UScan; + unsigned long ActiveUScan; + unsigned long NewUScan; + unsigned long NewActiveUScan; + + /* + ** Infantry type existence bits. Similar to unit and building bits. + */ + unsigned long IScan; + unsigned long ActiveIScan; + unsigned long NewIScan; + unsigned long NewActiveIScan; + + /* + ** Aircraft type existence bits. Similar to unit and building buts. + */ + unsigned long AScan; + unsigned long ActiveAScan; + unsigned long NewAScan; + unsigned long NewActiveAScan; + + /* + ** Record of gains and losses for this house during the course of the + ** scenario. + */ + unsigned CreditsSpent; + unsigned HarvestedCredits; + + /* + ** This is the running count of the number of units owned by this house. This + ** value is used to keep track of ownership limits. + */ + unsigned CurUnits; + unsigned CurBuildings; + + /* + ** This is the maximum number allowed to be built by this house. The + ** value depends on the scenario being played. + */ + unsigned MaxUnit; + unsigned MaxBuilding; + + /* + ** This is the running total of the number of credits this house has accumulated. + */ + long Tiberium; + long Credits; + long InitialCredits; + long Capacity; + + /* + ** Did this house lose via resignation? + */ + unsigned Resigned:1; + + /* + ** Did this house lose because the player quit? + */ + unsigned IGaveUp:1; + + /* + ** Stuff to keep track of the total number of units built by this house. + */ + UnitTrackerClass *AircraftTotals; + UnitTrackerClass *InfantryTotals; + UnitTrackerClass *UnitTotals; + UnitTrackerClass *BuildingTotals; + + /* + ** Total number of units destroyed by this house + */ + UnitTrackerClass *DestroyedAircraft; + UnitTrackerClass *DestroyedInfantry; + UnitTrackerClass *DestroyedUnits; + UnitTrackerClass *DestroyedBuildings; + + /* + ** Total number of enemy buildings captured by this house + */ + UnitTrackerClass *CapturedBuildings; + + /* + ** Total number of crates found by this house + */ + UnitTrackerClass *TotalCrates; + + /* + ** Records the number of infantry and vehicle factories active. This value is + ** used to regulate the speed of production. + */ + int AircraftFactories; + int InfantryFactories; + int UnitFactories; + int BuildingFactories; + int SpecialFactories; + + /* + ** This is the accumulation of the total power and drain factors. From these + ** values a ratio can be derived. This ratio is used to control the rate + ** of building decay. + */ + int Power; // Current power output. + int Drain; // Power consumption. + + /* + ** For generic (unspecified) reinforcements, they arrive by a common method. This + ** specifies which method is to be used. + */ + SourceType Edge; + + /* + ** For human controlled houses, only one type of unit can be produced + ** at any one instant. These factory objects control this production. + */ + int AircraftFactory; + int InfantryFactory; + int UnitFactory; + int BuildingFactory; + int SpecialFactory; + + /* + ** This target value specifies where the flag is located. It might be a cell + ** or it might be an object. + */ + TARGET FlagLocation; + + /* + ** This is the flag-home-cell for this house. This is where we must bring + ** another house's flag back to, to defeat that house. + */ + CELL FlagHome; + + /* + ** For multiplayer games, each house instance has a remap table; the table + ** in the HousesTypeClass isn't used. This variable is set to the remap + ** table for the color the player wants to play. + */ + unsigned char const * RemapTable; + PlayerColorType RemapColor; + char Name[MPLAYER_NAME_MAX]; + + /* + ** For multiplayer games, each house needs to keep track of how many + ** objects of each other house they've killed. + */ + unsigned UnitsKilled[HOUSE_COUNT]; + unsigned UnitsLost; + unsigned BuildingsKilled[HOUSE_COUNT]; + unsigned BuildingsLost; + + /* + ** For multiplayer games, this keeps track of the last house to destroy + ** one of my units. + */ + HousesType WhoLastHurtMe; + + /*--------------------------------------------------------------------- + ** Constructors, Destructors, and overloaded operators. + */ + static void * operator new(size_t size); + static void operator delete(void *ptr); + HouseClass(void) : Class(0) {}; + HouseClass(HousesType house); + ~HouseClass(void); + operator HousesType(void) const; + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + ProdFailType Begin_Production(RTTIType type, int id); + ProdFailType Suspend_Production(RTTIType type); + ProdFailType Abandon_Production(RTTIType type); + bool Place_Object(RTTIType type, CELL cell); + bool Manual_Place(BuildingClass * builder, BuildingClass * object); + void Special_Weapon_AI(SpecialWeaponType id); + bool Place_Special_Blast(SpecialWeaponType id, CELL cell); + bool Flag_Attach(CELL cell, bool set_home = false); + bool Flag_Attach(UnitClass * object, bool set_home = false); + bool Flag_Remove(TARGET target, bool set_home = false); + void Init_Data(PlayerColorType color, HousesType house, int credits); + + void Sell_Wall(CELL cell); + bool Flag_To_Die(void); + bool Flag_To_Win(void); + bool Flag_To_Lose(void); + void Make_Ally(HousesType house); + void Make_Ally(ObjectClass * object) {if (object) Make_Ally(object->Owner());}; + void Make_Enemy(HousesType house); + void Make_Enemy(ObjectClass * object) {if (object) Make_Enemy(object->Owner());}; + bool Is_Ally(HousesType house) const; + bool Is_Ally(HouseClass const * house) const; + bool Is_Ally(ObjectClass const * object) const; + #ifdef CHEAT_KEYS + void Debug_Dump(MonoClass *mono) const; + #endif + void AI(void); + bool Can_Build(StructType structure, HousesType house) const; + bool Can_Build(InfantryType infantry, HousesType house) const; + bool Can_Build(UnitType unit, HousesType) const; + bool Can_Build(AircraftType aircraft, HousesType house) const; + bool Can_Build(TechnoTypeClass const * type, HousesType house) const; + unsigned char const * Remap_Table(bool blushing=false, bool unit=false) const; + + TechnoTypeClass const * Suggest_New_Object(RTTIType objectype) const; + bool Does_Enemy_Building_Exist(StructType) const; + void Harvested(unsigned tiberium); + long Available_Money(void) const; + void Spend_Money(unsigned money); + void Refund_Money(unsigned money); + void Attacked(void); + void Adjust_Power(int adjust) {Power += adjust;}; + void Adjust_Drain(int adjust) {Drain += adjust;}; + int Adjust_Capacity(int adjust, bool inanger=false); + int Power_Fraction(void) const; + int Tiberium_Fraction(void) {return (!Tiberium) ? 0 : Cardinal_To_Fixed(Capacity, Tiberium);}; + void Begin_Production(void) {IsStarted = true;}; + TeamTypeClass const * Suggested_New_Team(bool alertcheck = false); + void Adjust_Threat(int region, int threat); + + static void Init(void); + static void One_Time(void); + static HouseClass * As_Pointer(HousesType house); + + /* + ** File I/O. + */ + static void Read_INI(char *buffer); + static void Write_INI(char *buffer); + static void Read_Flag_INI(char *buffer); + static void Write_Flag_INI(char *buffer); + bool Load(FileClass & file); + bool Save(FileClass & file); + void Code_Pointers(void); + void Decode_Pointers(void); + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + /* + ** Special house actions. + */ +// void Init_Ion_Cannon(bool first_time, bool one_time_effect = false); +// void Init_Air_Strike(bool first_time, bool one_time_effect = false); +// void Init_Nuke_Bomb(bool first_time, bool one_time_effect = false); +// void Remove_Ion_Cannon(void); +// void Remove_Air_Strike(void); +// void Remove_Nuke_Bomb(void); + void Detach(TARGET target, bool all); + void Add_Nuke_Piece(int piece=-1); +// void Make_Air_Strike_Available(bool present, bool one_time_effect = false); + bool Has_Nuke_Device(void); + + /* + ** This vector holds the recorded status of the map regions. It is through + ** this region information that team paths are calculated. + */ + RegionClass Regions[MAP_TOTAL_REGIONS]; + +#ifdef OBSOLETE + /* + ** This count down timer class handles decrements and then changes + ** the ion cannon state. If the Ion cannon was out of range it is + ** now in range. If the Ion cannon was in range it will toggle out + ** of range. + */ + TCountDownTimerClass IonControl; + int IonOldStage; + + TCountDownTimerClass AirControl; + int AirOldStage; + + TCountDownTimerClass NukeControl; + int NukeOldStage; +#endif + + + /* + ** This timer is for multiplayer mode; for a computer-controlled house, + ** it determines how long until this player "blitzes" the hapless humans. + */ + TCountDownTimerClass BlitzTime; + + /* + ** This count down timer class decrements and then changes + ** the Atomic Bomb state. + */ + CELL NukeDest; + + /* + ** This routine completely removes this house & all its objects from the game. + */ + void Clobber_All(void); + + /* + ** This routine blows up everything in this house. Fun! + */ + void Blowup_All(void); + + /* + ** This routine gets called in multiplayer games when every unit, building, + ** and infantry for a house is destroyed. + */ + void MPlayer_Defeated(void); + + private: + void Silo_Redraw_Check(long oldtib, long oldcap); + + /* + ** This is a bit field record of all the other houses that are allies with + ** this house. It is presumed that any house that isn't an ally, is therefore + ** an enemy. A house is always considered allied with itself. + */ + unsigned Allies; + + /* + ** This is the standard delay time between announcements concerning the + ** state of the base or other intermittent house related events. + */ + enum SpeakDelayEnum { + SPEAK_DELAY=TICKS_PER_MINUTE*2, + TEAM_DELAY=TICKS_PER_MINUTE/10, + DAMAGE_DELAY=TICKS_PER_MINUTE + }; + + /* + ** General low-power related damaged is doled out whenever this timer + ** expires. + */ + TCountDownTimerClass DamageTime; + + /* + ** Team creation is done whenever this timer expires. + */ + TCountDownTimerClass TeamTime; + + /* + ** This controls the rate that the trigger time logic is processed. + */ + TCountDownTimerClass TriggerTime; + + /* + ** At various times, the computer may announce the player's condition. The following + ** variables are used as countdown timers so that these announcements are paced + ** far enough appart to reduce annoyance. + */ + TCountDownTimerClass SpeakAttackDelay; + TCountDownTimerClass SpeakPowerDelay; + TCountDownTimerClass SpeakMoneyDelay; + TCountDownTimerClass SpeakMaxedDelay; +}; +#endif + diff --git a/IDATA.CPP b/IDATA.CPP new file mode 100644 index 0000000..538bfb2 --- /dev/null +++ b/IDATA.CPP @@ -0,0 +1,2154 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\idata.cpv 2.12 02 Aug 1995 17:00:30 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IDATA.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : August 15, 1994 * + * * + * Last Update : June 29, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * InfantryTypeClass::Create_And_Place -- Creates and places infantry object onto the map. * + * InfantryTypeClass::Create_One_Of -- Creates an infantry object. * + * InfantryTypeClass::Display -- Displays a generic infantry object. * + * InfantryTypeClass::From_Name -- Converts an ASCII name into an infantry type number. * + * InfantryTypeClass::Full_Name -- Fetches the full name text number. * + * InfantryTypeClass::Get_Cameo_Data -- Fetches the small cameo shape for sidebar strip. * + * InfantryTypeClass::InfantryTypeClass -- Constructor for infantry type class objects. * + * InfantryTypeClass::Occupy_List -- Returns with default infantry occupation list. * + * InfantryTypeClass::One_Time -- Performs any one time processing for infantry system. * + * InfantryTypeClass::Prep_For_Add -- Prepares the scenario editor for adding of infantry object.* + * InfantryTypeClass::Who_Can_Build_Me -- Determines what can build the infantry object. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "type.h" + + + +/* + * There were too many parameters for the InfantryTypeClass constructor so I have + * created a table of Do Controls for each unit type and I am passing a pointer + * to the table to the constructor instead of passing each value as it was before. + * + * If this offends anyones C++ sensibilities then please feel free to implement a + * more elegant oop solution. + * + * Steve T. 10/3/95 10:13AM + * + */ + +// Minigunners + +int MiniGunnerDos [DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 8, 1, 1, // DO_STAND_GUARD + 192, 1, 8, // DO_PRONE + 16, 6, 6, // DO_WALK + 64, 8, 8, // DO_FIRE_WEAPON + 128, 2, 2, // DO_LIE_DOWN + 144, 4, 4, // DO_CRAWL + 176, 2, 2, // DO_GET_UP + 192, 6, 8, // DO_FIRE_PRONE + 256, 16,0, // DO_IDLE1 + 272, 16,0, // DO_IDLE2 + 288, 13,0, // DO_ON_GUARD + 292, 10,0, // DO_FIGHT_READY + 301, 2, 0, // DO_PUNCH + 303, 6, 0, // DO_KICK + 309, 2, 0, // DO_PUNCH_HIT1 + 311, 4, 0, // DO_PUNCH_HIT2 + 315, 5, 0, // DO_PUNCH_DEATH + 319, 2, 0, // DO_KICK_HIT1 + 321, 4, 0, // DO_KICK_HIT2 + 325, 5, 0, // DO_KICK_DEATH + 330, 5, 0, // DO_READY_WEAPON + 382, 8, 0, // DO_GUN_DEATH + 398, 8, 0, // DO_EXPLOSION_DEATH + 398, 8, 0, // DO_EXPLOSION2_DEATH + 406, 12,0, // DO_GRENADE_DEATH + 418, 18,0, // DO_FIRE_DEATH + 436, 3, 3, // DO_GESTURE1 + 460, 3, 3, // DO_SALUTE1 + 484, 3, 3, // DO_GESTURE2 + 508, 3, 3, // DO_SALUTE2 + 0, 1, 1, // DO_PULL_GUN // N/A + 0, 1, 1, // DO_PLEAD // N/A + 0, 1, 1 // DO_PLEAD_DEATH // N/A +}; + + +static InfantryTypeClass const E1( + INFANTRY_E1, // Infantry type number. + TXT_E1, // Translate name number for infantry type. + "E1", // INI name for infantry. + 1, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + true, // Is a leader type? + true, // Has crawling animation frames? + false, // Is this a civlian? + false, // Always use the given name for the infantry? + false, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + -1, // Number of shots it has (default). + &MiniGunnerDos[0][0], // Ptr to minigunner 'DO' table above + 2, // Frame of projectile launch. + 2, // Frame of projectile launch while prone. + 50, // Strength of infantry (in damage points). + 1, // Sight range. + 100, // Cost of infantry (in credits). + 1, // Scenario when they first appear. + 80,10, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // Who can own this infantry unit. + WEAPON_M16,WEAPON_NONE, + MPH_SLOW // Maximum speed of infantry. +); + + +// Grenadiers + + +int GrenadierDos [DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 8, 1, 1, // DO_STAND_GUARD + 288, 1, 12, // DO_PRONE + 16, 6, 6, // DO_WALK + 64, 20,20, // DO_FIRE_WEAPON + 224, 2, 2, // DO_LIE_DOWN + 240, 4, 4, // DO_CRAWL + 272, 2, 2, // DO_GET_UP + 288, 8, 12, // DO_FIRE_PRONE + 384, 16,0, // DO_IDLE1 + 400, 16,0, // DO_IDLE2 + 416, 13,0, // DO_ON_GUARD + 420, 10,0, // DO_FIGHT_READY + 429, 2, 0, // DO_PUNCH + 431, 6, 0, // DO_KICK + 437, 2, 0, // DO_PUNCH_HIT1 + 439, 4, 0, // DO_PUNCH_HIT2 + 443, 4, 0, // DO_PUNCH_DEATH + 447, 2, 0, // DO_KICK_HIT1 + 449, 4, 0, // DO_KICK_HIT2 + 453, 5, 0, // DO_KICK_DEATH + 458, 5, 0, // DO_READY_WEAPON + 510, 8, 0, // DO_GUN_DEATH + 526, 8, 0, // DO_EXPLOSION_DEATH + 526, 8, 0, // DO_EXPLOSION2_DEATH + 534, 12,0, // DO_GRENADE_DEATH + 546, 18,0, // DO_FIRE_DEATH + 564, 3, 3, // DO_GESTURE1 + 588, 3, 3, // DO_SALUTE1 + 612, 3, 3, // DO_GESTURE2 + 636, 3, 3, // DO_SALUTE2 + 0, 1, 1, // DO_PULL_GUN // N/A + 0, 1, 1, // DO_PLEAD // N/A + 0, 1, 1 // DO_PLEAD_DEATH // N/A +}; + +static InfantryTypeClass const E2( + INFANTRY_E2, // Infantry type number. + TXT_E2, // Translate name number for infantry type. + "E2", // INI name for infantry. + 1, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + true, // Is a leader type? + true, // Has crawling animation frames? + false, // Is this a civlian? + false, // Always use the given name for the infantry? + false, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + -1, // Number of shots it has (default). + &GrenadierDos[0][0], // Ptr to grenadier DO table (above) + 14, // Frame of projectile launch. + 6, // Frame of projectile launch while prone. + 50, // Strength of infantry (in damage points). + 1, // Sight range. + 160, // Cost of infantry (in credits). + 3, // Scenario when they first appear. + 80,10, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD, // Who can own this infantry unit. + WEAPON_GRENADE,WEAPON_NONE, + MPH_SLOW_ISH // Maximum speed of infantry. +); + + + + +// Bazooka + +int BazookaDos [DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 8, 1, 1, // DO_STAND_GUARD + 192, 1,10, // DO_PRONE + 16, 6, 6, // DO_WALK + 64, 8, 8, // DO_FIRE_WEAPON + 128, 2, 2, // DO_LIE_DOWN + 144, 4, 4, // DO_CRAWL + 176, 2, 2, // DO_GET_UP + 192, 10,10, // DO_FIRE_PRONE + 272, 16,0, // DO_IDLE1 + 288, 16,0, // DO_IDLE2 + 304, 13,0, // DO_ON_GUARD + 308, 10,0, // DO_FIGHT_READY + 307, 2, 0, // DO_PUNCH + 319, 6, 0, // DO_KICK + 325, 2, 0, // DO_PUNCH_HIT1 + 327, 4, 0, // DO_PUNCH_HIT2 + 331, 4, 0, // DO_PUNCH_DEATH + 335, 2, 0, // DO_KICK_HIT1 + 337, 4, 0, // DO_KICK_HIT2 + 341, 5, 0, // DO_KICK_DEATH + 346, 5, 0, // DO_READY_WEAPON + 398, 8, 0, // DO_GUN_DEATH + 414, 8, 0, // DO_EXPLOSION_DEATH + 414, 8, 0, // DO_EXPLOSION2_DEATH + 422, 12,0, // DO_GRENADE_DEATH + 434, 18,0, // DO_FIRE_DEATH + 452, 3, 3, // DO_GESTURE1 + 476, 3, 3, // DO_SALUTE1 + 500, 3, 3, // DO_GESTURE2 + 524, 3, 3, // DO_SALUTE2 + 0, 1, 1, // DO_PULL_GUN // N/A + 0, 1, 1, // DO_PLEAD // N/A + 0, 1, 1 // DO_PLEAD_DEATH // N/A +}; + + + +static InfantryTypeClass const E3( + INFANTRY_E3, // Infantry type number. + TXT_E3, // Translate name number for infantry type. + "E3", // INI name for infantry. + 2, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + true, // Is a leader type? + true, // Has crawling animation frames? + false, // Is this a civlian? + false, // Always use the given name for the infantry? + false, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + -1, // Number of shots it has (default). + &BazookaDos[0][0], // Ptr to DO table (above) + 3, // Frame of projectile launch. + 3, // Frame of projectile launch while prone. + 25, // Strength of infantry (in damage points). + 2, // Sight range. + 300, // Cost of infantry (in credits). + 3, // Scenario when they first appear. + 80,10, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // Who can own this infantry unit. + WEAPON_DRAGON,WEAPON_NONE, + MPH_KINDA_SLOW // Maximum speed of infantry. +); + +// Flamethrower + +int FlamethrowerDos [DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 8, 1, 1, // DO_STAND_GUARD + 256, 1,16, // DO_PRONE + 16, 6, 6, // DO_WALK + 64, 16,16, // DO_FIRE_WEAPON + 192, 2, 2, // DO_LIE_DOWN + 208, 4, 4, // DO_CRAWL + 240, 2, 2, // DO_GET_UP + 256, 16,16, // DO_FIRE_PRONE + 384, 16,0, // DO_IDLE1 + 400, 16,0, // DO_IDLE2 + 416, 13,0, // DO_ON_GUARD + 420, 10,0, // DO_FIGHT_READY + 429, 2, 0, // DO_PUNCH + 431, 6, 0, // DO_KICK + 437, 2, 0, // DO_PUNCH_HIT1 + 439, 4, 0, // DO_PUNCH_HIT2 + 443, 4, 0, // DO_PUNCH_DEATH + 447, 2, 0, // DO_KICK_HIT1 + 449, 4, 0, // DO_KICK_HIT2 + 453, 5, 0, // DO_KICK_DEATH + 458, 5, 0, // DO_READY_WEAPON + 510, 8, 0, // DO_GUN_DEATH + 526, 8, 0, // DO_EXPLOSION_DEATH + 526, 8, 0, // DO_EXPLOSION2_DEATH + 534, 12,0, // DO_GRENADE_DEATH + 546, 18,0, // DO_FIRE_DEATH + 564, 3, 3, // DO_GESTURE1 + 588, 3, 3, // DO_SALUTE1 + 612, 3, 3, // DO_GESTURE2 + 636, 3, 3, // DO_SALUTE2 + 0, 1, 1, // DO_PULL_GUN // N/A + 0, 1, 1, // DO_PLEAD // N/A + 0, 1, 1 // DO_PLEAD_DEATH // N/A +}; + + + +static InfantryTypeClass const E4( + INFANTRY_E4, // Infantry type number. + TXT_E4, // Translate name number for infantry type. + "E4", // INI name for infantry. + 1, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + true, // Is a leader type? + true, // Has crawling animation frames? + false, // Is this a civlian? + false, // Always use the given name for the infantry? + false, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + -1, // Number of shots it has (default). + &FlamethrowerDos[0][0], // ptr to DO table (above) + 2, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 70, // Strength of infantry (in damage points). + 1, // Sight range. + 200, // Cost of infantry (in credits). + 5, // Scenario when they first appear. + 80,10, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // Who can own this infantry unit. + WEAPON_FLAMETHROWER,WEAPON_NONE, + MPH_SLOW_ISH +); + + +// Chemwarrior + +int ChemwarriorDos [DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 8, 1, 1, // DO_STAND_GUARD + 256, 1,16, // DO_PRONE + 16, 6, 6, // DO_WALK + 64, 16,16, // DO_FIRE_WEAPON + 192, 2, 2, // DO_LIE_DOWN + 208, 4, 4, // DO_CRAWL + 240, 2, 2, // DO_GET_UP + 256, 16,16, // DO_FIRE_PRONE + 384, 16,0, // DO_IDLE1 + 400, 16,0, // DO_IDLE2 + 416, 13,0, // DO_ON_GUARD + 420, 10,0, // DO_FIGHT_READY + 429, 2, 0, // DO_PUNCH + 431, 6, 0, // DO_KICK + 437, 2, 0, // DO_PUNCH_HIT1 + 439, 4, 0, // DO_PUNCH_HIT2 + 443, 4, 0, // DO_PUNCH_DEATH + 447, 2, 0, // DO_KICK_HIT1 + 449, 4, 0, // DO_KICK_HIT2 + 453, 5, 0, // DO_KICK_DEATH + 458, 5, 0, // DO_READY_WEAPON + 510, 8, 0, // DO_GUN_DEATH + 526, 8, 0, // DO_EXPLOSION_DEATH + 526, 8, 0, // DO_EXPLOSION2_DEATH + 534, 12,0, // DO_GRENADE_DEATH + 546, 18,0, // DO_FIRE_DEATH + 564, 3, 3, // DO_GESTURE1 + 588, 3, 3, // DO_SALUTE1 + 612, 3, 3, // DO_GESTURE2 + 636, 3, 3, // DO_SALUTE2 + 0, 1, 1, // DO_PULL_GUN // N/A + 0, 1, 1, // DO_PLEAD // N/A + 0, 1, 1 // DO_PLEAD_DEATH // N/A +}; + + +static InfantryTypeClass const E5( + INFANTRY_E5, // Infantry type number. + TXT_E5, // Translate name number for infantry type. + "E5", // INI name for infantry. + 7, // Build level. + STRUCTF_EYE, // Building prerequisite. + false, // Is this a female type? + true, // Is a leader type? + true, // Has crawling animation frames? + false, // Is this a civlian? + false, // Always use the given name for the infantry? + true, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + -1, // Number of shots it has (default). + &ChemwarriorDos[0][0], // ptr to DO table + 2, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 70, // Strength of infantry (in damage points). + 1, // Sight range. + 300, // Cost of infantry (in credits). + 99, // Scenario when they first appear. + 80,10, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| +// HOUSEF_GOOD| + HOUSEF_BAD, // Who can own this infantry unit. + WEAPON_CHEMSPRAY,WEAPON_NONE, + MPH_SLOW +); + + +// Engineer + +int EngineerDos[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 8, 1, 1, // DO_STAND_GUARD + 82, 1, 4, // DO_PRONE + 16, 6, 6, // DO_WALK + 0, 0, 0, // DO_FIRE_WEAPON + 67, 2, 2, // DO_LIE_DOWN + 82, 4, 4, // DO_CRAWL + 114, 2, 2, // DO_GET_UP + 0, 0, 0, // DO_FIRE_PRONE + 130, 16,0, // DO_IDLE1 + 0, 0, 0, // DO_IDLE2 + 0, 0, 0, // DO_ON_GUARD + 0, 0, 0, // DO_FIGHT_READY + 0, 0, 0, // DO_PUNCH + 0, 0, 0, // DO_KICK + 0, 0, 0, // DO_PUNCH_HIT1 + 0, 0, 0, // DO_PUNCH_HIT2 + 0, 0, 0, // DO_PUNCH_DEATH + 0, 0, 0, // DO_KICK_HIT1 + 0, 0, 0, // DO_KICK_HIT2 + 0, 0, 0, // DO_KICK_DEATH + 0, 0, 0, // DO_READY_WEAPON + 146, 8, 0, // DO_GUN_DEATH + 154, 8, 0, // DO_EXPLOSION_DEATH + 162, 8, 0, // DO_EXPLOSION2_DEATH + 162, 12,0, // DO_GRENADE_DEATH + 182, 18,0, // DO_FIRE_DEATH + 200, 3, 3, // DO_GESTURE1 + 224, 3, 3, // DO_SALUTE1 + 200, 3, 3, // DO_GESTURE2 + 224, 3, 3, // DO_SALUTE2 + 0, 1, 1, // DO_PULL_GUN // N/A + 0, 1, 1, // DO_PLEAD // N/A + 0, 1, 1 // DO_PLEAD_DEATH // N/A +}; + +static InfantryTypeClass const E7( + INFANTRY_E7, // Infantry type number. + TXT_E7, // Translate name number for infantry type. + "E6", // INI name for infantry. + 3, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + false, // Is a leader type? + false, // Has crawling animation frames? + false, // Is this a civlian? + false, // Always use the given name for the infantry? + false, // Is this a "fraidycat" run-away type infantry? + true, // Can this infantry type capture a building? + false, // Theater specific graphic image? + -1, // Number of shots it has (default). + &EngineerDos[0][0], // ptr to DO table + 3, // Frame of projectile launch. + 3, // Frame of projectile launch while prone. + 25, // Strength of infantry (in damage points). + 2, // Sight range. + 500, // Cost of infantry (in credits). + 2, // Scenario when they first appear. + 80,75, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD| + HOUSEF_GOOD, // Who can own this infantry unit. + WEAPON_NONE,WEAPON_NONE, + MPH_SLOW // Maximum speed of infantry. +); + +// Commandos + +int CommandoDos[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 8, 1, 1, // DO_STAND_GUARD + 160, 1, 4, // DO_PRONE + 16, 6, 6, // DO_WALK + 64, 4, 4, // DO_FIRE_WEAPON + 96, 2, 2, // DO_LIE_DOWN + 112, 4, 4, // DO_CRAWL + 144, 2, 2, // DO_GET_UP + 160, 4, 4, // DO_FIRE_PRONE + 192, 16,0, // DO_IDLE1 + 208, 16,0, // DO_IDLE2 + 224, 13,0, // DO_ON_GUARD + 228, 9, 0, // DO_FIGHT_READY + 237, 2, 0, // DO_PUNCH + 239, 6, 0, // DO_KICK + 245, 2, 0, // DO_PUNCH_HIT1 + 247, 4, 0, // DO_PUNCH_HIT2 + 251, 4, 0, // DO_PUNCH_DEATH + 255, 2, 0, // DO_KICK_HIT1 + 257, 4, 0, // DO_KICK_HIT2 + 261, 5, 0, // DO_KICK_DEATH + 266, 5, 0, // DO_READY_WEAPON + 318, 8, 0, // DO_GUN_DEATH + 334, 8, 0, // DO_EXPLOSION_DEATH + 334, 8, 0, // DO_EXPLOSION2_DEATH + 342, 12,0, // DO_GRENADE_DEATH + 354, 18,0, // DO_FIRE_DEATH + 372, 3, 3, // DO_GESTURE1 + 396, 3, 3, // DO_SALUTE1 + 420, 3, 3, // DO_GESTURE2 + 444, 3, 3, // DO_SALUTE2 + 0, 1, 1, // DO_PULL_GUN // N/A + 0, 1, 1, // DO_PLEAD // N/A + 0, 1, 1 // DO_PLEAD_DEATH // N/A + +}; +static InfantryTypeClass const Commando( + INFANTRY_RAMBO, // Infantry type number. + TXT_RAMBO, // Translate name number for infantry type. + "RMBO", // INI name for infantry. + 7, // Build level. + STRUCTF_EYE, // Building prerequisite. + false, // Is this a female type? + true, // Is a leader type? + true, // Has crawling animation frames? + false, // Is this a civlian? + false, // Always use the given name for the infantry? + false, // Is this a "fraidycat" run-away type infantry? + true, // Can this infantry type capture a building? + false, // Theater specific graphic image? + -1, // Number of shots it has (default). + &CommandoDos[0][0], // ptr to DO table + 2, // Frame of projectile launch. + 2, // Frame of projectile launch while prone. + 80, // Strength of infantry (in damage points). + 5, // Sight range. + 1000, // Cost of infantry (in credits). + 98, // Scenario when they first appear. + 80,75, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // Who can own this infantry unit. + WEAPON_RIFLE,WEAPON_NONE, + MPH_SLOW_ISH // Maximum speed of infantry. +); + +// Civilians + +int CivilianDos1[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 0, 1, 1, // DO_STAND_GUARD + 0, 1, 1, // DO_PRONE // N/A + 56, 6, 6, // DO_WALK + 205, 4, 4, // DO_FIRE_WEAPON + 0, 1, 1, // DO_LIE_DOWN // N/A + 8, 6, 6, // DO_CRAWL + 0, 1, 1, // DO_GET_UP // N/A + 205, 4, 4, // DO_FIRE_PRONE + 189, 10,0, // DO_IDLE1 + 199, 6, 0, // DO_IDLE2 + 104, 3, 0, // DO_ON_GUARD + 107, 7, 0, // DO_FIGHT_READY + 114, 2, 0, // DO_PUNCH + 116, 6, 0, // DO_KICK + 122, 2, 0, // DO_PUNCH_HIT1 + 124, 4, 0, // DO_PUNCH_HIT2 + 128, 4, 0, // DO_PUNCH_DEATH + 133, 2, 0, // DO_KICK_HIT1 + 135, 4, 0, // DO_KICK_HIT2 + 139, 5, 0, // DO_KICK_DEATH + 144, 3, 0, // DO_READY_WEAPON + 329, 8, 0, // DO_GUN_DEATH + 337, 8, 0, // DO_EXPLOSION_DEATH + 337, 8, 0, // DO_EXPLOSION2_DEATH + 345, 12,0, // DO_GRENADE_DEATH + 357, 18,0, // DO_FIRE_DEATH + 0, 0, 0, // DO_GESTURE1 // N/A + 0, 0, 0, // DO_SALUTE1 // N/A + 0, 0, 0, // DO_GESTURE2 // N/A + 0, 0, 0, // DO_SALUTE2 // N/A + 199, 6, 0, // DO_PULL_GUN + 237, 40,0, // DO_PLEAD + 277, 6, 0 // DO_PLEAD_DEATH +}; + +static InfantryTypeClass const C1( + INFANTRY_C1, // Infantry type number. + TXT_C1, // Translate name number for infantry type. + "C1", // INI name for infantry. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + true, // Is a leader type? + false, // Has crawling animation frames? + true, // Is this a civlian? + true, // Always use the given name for the infantry? + true, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + 10, // Number of shots it has (default). + &CivilianDos1[0][0], // ptr to DO table + 2, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 25, // Strength of infantry (in damage points). + 0, // Sight range. + 10, // Cost of infantry (in credits). + 99, // Scenario when they first appear. + 0,1, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD| + HOUSEF_NEUTRAL, // Who can own this infantry unit. + WEAPON_PISTOL,WEAPON_NONE, + MPH_SLOW_ISH +); + + +int CivilianDos2[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 0, 1, 1, // DO_STAND_GUARD + 0, 1, 1, // DO_PRONE // N/A + 56, 6, 6, // DO_WALK + 205, 4, 4, // DO_FIRE_WEAPON + 0, 1, 1, // DO_LIE_DOWN // N/A + 8, 6, 6, // DO_CRAWL + 0, 1, 1, // DO_GET_UP // N/A + 205, 4, 4, // DO_FIRE_PRONE + 189, 10,0, // DO_IDLE1 + 199, 6, 0, // DO_IDLE2 + 104, 3, 0, // DO_ON_GUARD + 107, 7, 0, // DO_FIGHT_READY + 114, 2, 0, // DO_PUNCH + 116, 6, 0, // DO_KICK + 122, 2, 0, // DO_PUNCH_HIT1 + 124, 4, 0, // DO_PUNCH_HIT2 + 128, 4, 0, // DO_PUNCH_DEATH + 133, 2, 0, // DO_KICK_HIT1 + 135, 4, 0, // DO_KICK_HIT2 + 139, 5, 0, // DO_KICK_DEATH + 144, 3, 0, // DO_READY_WEAPON + 329, 8, 0, // DO_GUN_DEATH + 337, 8, 0, // DO_EXPLOSION_DEATH + 337, 8, 0, // DO_EXPLOSION2_DEATH + 345, 12,0, // DO_GRENADE_DEATH + 357, 18,0, // DO_FIRE_DEATH + 0, 0, 0, // DO_GESTURE1 // N/A + 0, 0, 0, // DO_SALUTE1 // N/A + 0, 0, 0, // DO_GESTURE2 // N/A + 0, 0, 0, // DO_SALUTE2 // N/A + 199, 6, 0, // DO_PULL_GUN + 237, 40,0, // DO_PLEAD + 277, 6, 0 // DO_PLEAD_DEATH +}; + +static InfantryTypeClass const C2( + INFANTRY_C2, // Infantry type number. + TXT_C2, // Translate name number for infantry type. + "C2", // INI name for infantry. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + false, // Is a leader type? + false, // Has crawling animation frames? + true, // Is this a civlian? + true, // Always use the given name for the infantry? + true, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + 0, // Number of shots it has (default). + &CivilianDos2[0][0], // ptr to DO table + 2, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 5, // Strength of infantry (in damage points). + 0, // Sight range. + 10, // Cost of infantry (in credits). + 99, // Scenario when they first appear. + 0,1, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD| + HOUSEF_NEUTRAL, // Who can own this infantry unit. + WEAPON_NONE,WEAPON_NONE, + MPH_SLOW_ISH +); + + +int CivilianDos3[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 0, 1, 1, // DO_STAND_GUARD + 0, 1, 1, // DO_PRONE // N/A + 56, 6, 6, // DO_WALK + 205, 4, 4, // DO_FIRE_WEAPON + 0, 1, 1, // DO_LIE_DOWN // N/A + 8, 6, 6, // DO_CRAWL + 0, 1, 1, // DO_GET_UP // N/A + 205, 4, 4, // DO_FIRE_PRONE + 189, 10,0, // DO_IDLE1 + 199, 6, 0, // DO_IDLE2 + 104, 3, 0, // DO_ON_GUARD + 107, 7, 0, // DO_FIGHT_READY + 114, 2, 0, // DO_PUNCH + 116, 6, 0, // DO_KICK + 122, 2, 0, // DO_PUNCH_HIT1 + 124, 4, 0, // DO_PUNCH_HIT2 + 128, 4, 0, // DO_PUNCH_DEATH + 133, 2, 0, // DO_KICK_HIT1 + 135, 4, 0, // DO_KICK_HIT2 + 139, 5, 0, // DO_KICK_DEATH + 144, 3, 0, // DO_READY_WEAPON + 329, 8, 0, // DO_GUN_DEATH + 337, 8, 0, // DO_EXPLOSION_DEATH + 337, 8, 0, // DO_EXPLOSION2_DEATH + 345, 12,0, // DO_GRENADE_DEATH + 357, 18,0, // DO_FIRE_DEATH + 0, 0, 0, // DO_GESTURE1 // N/A + 0, 0, 0, // DO_SALUTE1 // N/A + 0, 0, 0, // DO_GESTURE2 // N/A + 0, 0, 0, // DO_SALUTE2 // N/A + 199, 6, 0, // DO_PULL_GUN + 237, 40,0, // DO_PLEAD + 277, 6, 0 // DO_PLEAD_DEATH + +}; + +static InfantryTypeClass const C3( + INFANTRY_C3, // Infantry type number. + TXT_C3, // Translate name number for infantry type. + "C3", // INI name for infantry. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + true, // Is this a female type? + false, // Is a leader type? + false, // Has crawling animation frames? + true, // Is this a civlian? + true, // Always use the given name for the infantry? + true, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + 0, // Number of shots it has (default). + &CivilianDos3[0][0], // ptr to DO table + 2, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 5, // Strength of infantry (in damage points). + 0, // Sight range. + 10, // Cost of infantry (in credits). + 99, // Scenario when they first appear. + 0,1, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD| + HOUSEF_NEUTRAL, // Who can own this infantry unit. + WEAPON_NONE,WEAPON_NONE, + MPH_SLOW_ISH +); + + +int CivilianDos4[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 0, 1, 1, // DO_STAND_GUARD + 0, 1, 1, // DO_PRONE // N/A + 56, 6, 6, // DO_WALK + 205, 4, 4, // DO_FIRE_WEAPON + 0, 1, 1, // DO_LIE_DOWN // N/A + 8, 6, 6, // DO_CRAWL + 0, 1, 1, // DO_GET_UP // N/A + 205, 4, 4, // DO_FIRE_PRONE + 189, 10,0, // DO_IDLE1 + 199, 6, 0, // DO_IDLE2 + 104, 3, 0, // DO_ON_GUARD + 107, 7, 0, // DO_FIGHT_READY + 114, 2, 0, // DO_PUNCH + 116, 6, 0, // DO_KICK + 122, 2, 0, // DO_PUNCH_HIT1 + 124, 4, 0, // DO_PUNCH_HIT2 + 128, 4, 0, // DO_PUNCH_DEATH + 133, 2, 0, // DO_KICK_HIT1 + 135, 4, 0, // DO_KICK_HIT2 + 139, 5, 0, // DO_KICK_DEATH + 144, 3, 0, // DO_READY_WEAPON + 329, 8, 0, // DO_GUN_DEATH + 337, 8, 0, // DO_EXPLOSION_DEATH + 337, 8, 0, // DO_EXPLOSION2_DEATH + 345, 12,0, // DO_GRENADE_DEATH + 357, 18,0, // DO_FIRE_DEATH + 0, 0, 0, // DO_GESTURE1 // N/A + 0, 0, 0, // DO_SALUTE1 // N/A + 0, 0, 0, // DO_GESTURE2 // N/A + 0, 0, 0, // DO_SALUTE2 // N/A + 199, 6, 0, // DO_PULL_GUN + 237, 40,0, // DO_PLEAD + 277, 6, 0, // DO_PLEAD_DEATH +}; + + +static InfantryTypeClass const C4( + INFANTRY_C4, // Infantry type number. + TXT_C4, // Translate name number for infantry type. + "C4", // INI name for infantry. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + true, // Is this a female type? + false, // Is a leader type? + false, // Has crawling animation frames? + true, // Is this a civlian? + true, // Always use the given name for the infantry? + true, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + 0, // Number of shots it has (default). + &CivilianDos4[0][0], // ptr to DO table + 2, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 5, // Strength of infantry (in damage points). + 0, // Sight range. + 10, // Cost of infantry (in credits). + 99, // Scenario when they first appear. + 0,1, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD| + HOUSEF_NEUTRAL, // Who can own this infantry unit. + WEAPON_NONE,WEAPON_NONE, + MPH_SLOW_ISH +); + + +int CivilianDos5[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 0, 1, 1, // DO_STAND_GUARD + 0, 1, 1, // DO_PRONE // N/A + 56, 6, 6, // DO_WALK + 205, 4, 4, // DO_FIRE_WEAPON + 0, 1, 1, // DO_LIE_DOWN // N/A + 8, 6, 6, // DO_CRAWL + 0, 1, 1, // DO_GET_UP // N/A + 205, 4, 4, // DO_FIRE_PRONE + 189, 10,0, // DO_IDLE1 + 199, 6, 0, // DO_IDLE2 + 104, 3, 0, // DO_ON_GUARD + 107, 7, 0, // DO_FIGHT_READY + 114, 2, 0, // DO_PUNCH + 116, 6, 0, // DO_KICK + 122, 2, 0, // DO_PUNCH_HIT1 + 124, 4, 0, // DO_PUNCH_HIT2 + 128, 4, 0, // DO_PUNCH_DEATH + 133, 2, 0, // DO_KICK_HIT1 + 135, 4, 0, // DO_KICK_HIT2 + 139, 5, 0, // DO_KICK_DEATH + 144, 3, 0, // DO_READY_WEAPON + 329, 8, 0, // DO_GUN_DEATH + 337, 8, 0, // DO_EXPLOSION_DEATH + 337, 8, 0, // DO_EXPLOSION2_DEATH + 345, 12,0, // DO_GRENADE_DEATH + 357, 18,0, // DO_FIRE_DEATH + 0, 0, 0, // DO_GESTURE1 // N/A + 0, 0, 0, // DO_SALUTE1 // N/A + 0, 0, 0, // DO_GESTURE2 // N/A + 0, 0, 0, // DO_SALUTE2 // N/A + 199, 6, 0, // DO_PULL_GUN + 237, 40,0, // DO_PLEAD + 277, 6, 0 // DO_PLEAD_DEATH +}; + +static InfantryTypeClass const C5( + INFANTRY_C5, // Infantry type number. + TXT_C5, // Translate name number for infantry type. + "C5", // INI name for infantry. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + false, // Is a leader type? + false, // Has crawling animation frames? + true, // Is this a civlian? + true, // Always use the given name for the infantry? + true, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + 0, // Number of shots it has (default). + &CivilianDos5[0][0], // ptr to DO table + 2, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 5, // Strength of infantry (in damage points). + 0, // Sight range. + 10, // Cost of infantry (in credits). + 99, // Scenario when they first appear. + 0,1, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD| + HOUSEF_NEUTRAL, // Who can own this infantry unit. + WEAPON_NONE,WEAPON_NONE, + MPH_SLOW_ISH +); + + +int CivilianDos6[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 0, 1, 1, // DO_STAND_GUARD + 0, 1, 1, // DO_PRONE // N/A + 56, 6, 6, // DO_WALK + 205, 4, 4, // DO_FIRE_WEAPON + 0, 1, 1, // DO_LIE_DOWN // N/A + 8, 6, 6, // DO_CRAWL + 0, 1, 1, // DO_GET_UP // N/A + 205, 4, 4, // DO_FIRE_PRONE + 189, 10,0, // DO_IDLE1 + 199, 6, 0, // DO_IDLE2 + 104, 3, 0, // DO_ON_GUARD + 107, 7, 0, // DO_FIGHT_READY + 114, 2, 0, // DO_PUNCH + 116, 6, 0, // DO_KICK + 122, 2, 0, // DO_PUNCH_HIT1 + 124, 4, 0, // DO_PUNCH_HIT2 + 128, 4, 0, // DO_PUNCH_DEATH + 133, 2, 0, // DO_KICK_HIT1 + 135, 4, 0, // DO_KICK_HIT2 + 139, 5, 0, // DO_KICK_DEATH + 144, 3, 0, // DO_READY_WEAPON + 329, 8, 0, // DO_GUN_DEATH + 337, 8, 0, // DO_EXPLOSION_DEATH + 337, 8, 0, // DO_EXPLOSION2_DEATH + 345, 12,0, // DO_GRENADE_DEATH + 357, 18,0, // DO_FIRE_DEATH + 0, 0, 0, // DO_GESTURE1 // N/A + 0, 0, 0, // DO_SALUTE1 // N/A + 0, 0, 0, // DO_GESTURE2 // N/A + 0, 0, 0, // DO_SALUTE2 // N/A + 199, 6, 0, // DO_PULL_GUN + 237, 40,0, // DO_PLEAD + 277, 6, 0 // DO_PLEAD_DEATH +}; + +static InfantryTypeClass const C6( + INFANTRY_C6, // Infantry type number. + TXT_C6, // Translate name number for infantry type. + "C6", // INI name for infantry. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + false, // Is a leader type? + false, // Has crawling animation frames? + true, // Is this a civlian? + true, // Always use the given name for the infantry? + true, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + 0, // Number of shots it has (default). + &CivilianDos6[0][0], // ptr to DO table + 2, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 5, // Strength of infantry (in damage points). + 0, // Sight range. + 10, // Cost of infantry (in credits). + 99, // Scenario when they first appear. + 0,1, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD| + HOUSEF_NEUTRAL, // Who can own this infantry unit. + WEAPON_NONE,WEAPON_NONE, + MPH_SLOW_ISH +); + + +int CivilianDos7[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 0, 1, 1, // DO_STAND_GUARD + 0, 1, 1, // DO_PRONE // N/A + 56, 6, 6, // DO_WALK + 205, 4, 4, // DO_FIRE_WEAPON + 0, 1, 1, // DO_LIE_DOWN // N/A + 8, 6, 6, // DO_CRAWL + 0, 1, 1, // DO_GET_UP // N/A + 205, 4, 4, // DO_FIRE_PRONE + 189, 10,0, // DO_IDLE1 + 199, 6, 0, // DO_IDLE2 + 104, 3, 0, // DO_ON_GUARD + 107, 7, 0, // DO_FIGHT_READY + 114, 2, 0, // DO_PUNCH + 116, 6, 0, // DO_KICK + 122, 2, 0, // DO_PUNCH_HIT1 + 124, 4, 0, // DO_PUNCH_HIT2 + 128, 4, 0, // DO_PUNCH_DEATH + 133, 2, 0, // DO_KICK_HIT1 + 135, 4, 0, // DO_KICK_HIT2 + 139, 5, 0, // DO_KICK_DEATH + 144, 3, 0, // DO_READY_WEAPON + 329, 8, 0, // DO_GUN_DEATH + 337, 8, 0, // DO_EXPLOSION_DEATH + 337, 8, 0, // DO_EXPLOSION2_DEATH + 345, 12,0, // DO_GRENADE_DEATH + 357, 18,0, // DO_FIRE_DEATH + 0, 0, 0, // DO_GESTURE1 // N/A + 0, 0, 0, // DO_SALUTE1 // N/A + 0, 0, 0, // DO_GESTURE2 // N/A + 0, 0, 0, // DO_SALUTE2 // N/A + 199, 6, 0, // DO_PULL_GUN + 237, 40,0, // DO_PLEAD + 277, 6, 0 // DO_PLEAD_DEATH +}; + +static InfantryTypeClass const C7( + INFANTRY_C7, // Infantry type number. + TXT_C7, // Translate name number for infantry type. + "C7", // INI name for infantry. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + true, // Is a leader type? + false, // Has crawling animation frames? + true, // Is this a civlian? + true, // Always use the given name for the infantry? + true, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + 10, // Number of shots it has (default). + &CivilianDos7[0][0], // ptr to DO table + 2, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 5, // Strength of infantry (in damage points). + 0, // Sight range. + 10, // Cost of infantry (in credits). + 99, // Scenario when they first appear. + 0,1, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD| + HOUSEF_NEUTRAL, // Who can own this infantry unit. + WEAPON_PISTOL,WEAPON_NONE, + MPH_SLOW_ISH +); + +int CivilianDos8[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 0, 1, 1, // DO_STAND_GUARD + 0, 1, 1, // DO_PRONE // N/A + 56, 6, 6, // DO_WALK + 205, 4, 4, // DO_FIRE_WEAPON + 0, 1, 1, // DO_LIE_DOWN // N/A + 8, 6, 6, // DO_CRAWL + 0, 1, 1, // DO_GET_UP // N/A + 205, 4, 4, // DO_FIRE_PRONE + 189, 10,0, // DO_IDLE1 + 199, 6, 0, // DO_IDLE2 + 104, 3, 0, // DO_ON_GUARD + 107, 7, 0, // DO_FIGHT_READY + 114, 2, 0, // DO_PUNCH + 116, 6, 0, // DO_KICK + 122, 2, 0, // DO_PUNCH_HIT1 + 124, 4, 0, // DO_PUNCH_HIT2 + 128, 4, 0, // DO_PUNCH_DEATH + 133, 2, 0, // DO_KICK_HIT1 + 135, 4, 0, // DO_KICK_HIT2 + 139, 5, 0, // DO_KICK_DEATH + 144, 3, 0, // DO_READY_WEAPON + 329, 8, 0, // DO_GUN_DEATH + 337, 8, 0, // DO_EXPLOSION_DEATH + 337, 8, 0, // DO_EXPLOSION2_DEATH + 345, 12,0, // DO_GRENADE_DEATH + 357, 18,0, // DO_FIRE_DEATH + 0, 0, 0, // DO_GESTURE1 // N/A + 0, 0, 0, // DO_SALUTE1 // N/A + 0, 0, 0, // DO_GESTURE2 // N/A + 0, 0, 0, // DO_SALUTE2 // N/A + 199, 6, 0, // DO_PULL_GUN + 237, 40,0, // DO_PLEAD + 277, 6, 0 // DO_PLEAD_DEATH +}; + +static InfantryTypeClass const C8( + INFANTRY_C8, // Infantry type number. + TXT_C8, // Translate name number for infantry type. + "C8", // INI name for infantry. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + false, // Is a leader type? + false, // Has crawling animation frames? + true, // Is this a civlian? + true, // Always use the given name for the infantry? + true, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + 0, // Number of shots it has (default). + &CivilianDos8[0][0], // ptr to DO table + 2, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 5, // Strength of infantry (in damage points). + 0, // Sight range. + 10, // Cost of infantry (in credits). + 99, // Scenario when they first appear. + 0,1, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD| + HOUSEF_NEUTRAL, // Who can own this infantry unit. + WEAPON_NONE,WEAPON_NONE, + MPH_SLOW_ISH +); + + +int CivilianDos9[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 0, 1, 1, // DO_STAND_GUARD + 0, 1, 1, // DO_PRONE // N/A + 56, 6, 6, // DO_WALK + 205, 4, 4, // DO_FIRE_WEAPON + 0, 1, 1, // DO_LIE_DOWN // N/A + 8, 6, 6, // DO_CRAWL + 0, 1, 1, // DO_GET_UP // N/A + 205, 4, 4, // DO_FIRE_PRONE + 189, 10,0, // DO_IDLE1 + 199, 6, 0, // DO_IDLE2 + 104, 3, 0, // DO_ON_GUARD + 107, 7, 0, // DO_FIGHT_READY + 114, 2, 0, // DO_PUNCH + 116, 6, 0, // DO_KICK + 122, 2, 0, // DO_PUNCH_HIT1 + 124, 4, 0, // DO_PUNCH_HIT2 + 128, 4, 0, // DO_PUNCH_DEATH + 133, 2, 0, // DO_KICK_HIT1 + 135, 4, 0, // DO_KICK_HIT2 + 139, 5, 0, // DO_KICK_DEATH + 144, 3, 0, // DO_READY_WEAPON + 329, 8, 0, // DO_GUN_DEATH + 337, 8, 0, // DO_EXPLOSION_DEATH + 337, 8, 0, // DO_EXPLOSION2_DEATH + 345, 12,0, // DO_GRENADE_DEATH + 357, 18,0, // DO_FIRE_DEATH + 0, 0, 0, // DO_GESTURE1 // N/A + 0, 0, 0, // DO_SALUTE1 // N/A + 0, 0, 0, // DO_GESTURE2 // N/A + 0, 0, 0, // DO_SALUTE2 // N/A + 199, 6, 0, // DO_PULL_GUN + 237, 40,0, // DO_PLEAD + 277, 6, 0 // DO_PLEAD_DEATH +}; + +static InfantryTypeClass const C9( + INFANTRY_C9, // Infantry type number. + TXT_C9, // Translate name number for infantry type. + "C9", // INI name for infantry. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + false, // Is a leader type? + false, // Has crawling animation frames? + true, // Is this a civlian? + true, // Always use the given name for the infantry? + true, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + 0, // Number of shots it has (default). + &CivilianDos9[0][0], // ptr to DO table + 2, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 5, // Strength of infantry (in damage points). + 0, // Sight range. + 10, // Cost of infantry (in credits). + 99, // Scenario when they first appear. + 0,1, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD| + HOUSEF_NEUTRAL, // Who can own this infantry unit. + WEAPON_NONE,WEAPON_NONE, + MPH_SLOW_ISH +); + + +int NikoombaDos[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 0, 1, 1, // DO_STAND_GUARD + 0, 1, 1, // DO_PRONE // N/A + 56, 6, 6, // DO_WALK + 205, 4, 4, // DO_FIRE_WEAPON + 0, 1, 1, // DO_LIE_DOWN // N/A + 8, 6, 6, // DO_CRAWL + 0, 1, 1, // DO_GET_UP // N/A + 205, 4, 4, // DO_FIRE_PRONE + 189, 10,0, // DO_IDLE1 + 199, 6, 0, // DO_IDLE2 + 104, 3, 0, // DO_ON_GUARD + 107, 7, 0, // DO_FIGHT_READY + 114, 2, 0, // DO_PUNCH + 116, 6, 0, // DO_KICK + 122, 2, 0, // DO_PUNCH_HIT1 + 124, 4, 0, // DO_PUNCH_HIT2 + 128, 4, 0, // DO_PUNCH_DEATH + 133, 2, 0, // DO_KICK_HIT1 + 135, 4, 0, // DO_KICK_HIT2 + 139, 5, 0, // DO_KICK_DEATH + 144, 3, 0, // DO_READY_WEAPON + 329, 8, 0, // DO_GUN_DEATH + 337, 8, 0, // DO_EXPLOSION_DEATH + 337, 8, 0, // DO_EXPLOSION2_DEATH + 345, 12,0, // DO_GRENADE_DEATH + 357, 18,0, // DO_FIRE_DEATH + 0, 0, 0, // DO_GESTURE1 // N/A + 0, 0, 0, // DO_SALUTE1 // N/A + 0, 0, 0, // DO_GESTURE2 // N/A + 0, 0, 0, // DO_SALUTE2 // N/A + 199, 6, 0, // DO_PULL_GUN + 237, 40,0, // DO_PLEAD + 277, 6, 0, // DO_PLEAD_DEATH + +}; + +// Nikoomba +static InfantryTypeClass const C10( + INFANTRY_C10, // Infantry type number. + TXT_C10, // Translate name number for infantry type. + "C10", // INI name for infantry. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + false, // Is a leader type? + false, // Has crawling animation frames? + true, // Is this a civlian? + true, // Always use the given name for the infantry? + true, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + 0, // Number of shots it has (default). + &NikoombaDos[0][0], // ptr to DO table + 2, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 50, // Strength of infantry (in damage points). + 0, // Sight range. + 10, // Cost of infantry (in credits). + 99, // Scenario when they first appear. + 0,1, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD| + HOUSEF_NEUTRAL, // Who can own this infantry unit. + WEAPON_NONE,WEAPON_NONE, + MPH_SLOW_ISH +); + +int MoebiusDos[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 0, 1, 1, // DO_STAND_GUARD + 0, 1, 1, // DO_PRONE // N/A + 56, 6, 6, // DO_WALK + 205, 4, 4, // DO_FIRE_WEAPON + 0, 1, 1, // DO_LIE_DOWN // N/A + 8, 6, 6, // DO_CRAWL + 0, 1, 1, // DO_GET_UP // N/A + 0, 0, 0, // DO_FIRE_PRONE + 104, 16,0, // DO_IDLE1 + 120, 20,0, // DO_IDLE2 + 0, 0, 0, // DO_ON_GUARD + 0, 0, 0, // DO_FIGHT_READY + 0, 0, 0, // DO_PUNCH + 0, 0, 0, // DO_KICK + 0, 0, 0, // DO_PUNCH_HIT1 + 0, 0, 0, // DO_PUNCH_HIT2 + 0, 0, 0, // DO_PUNCH_DEATH + 0, 0, 0, // DO_KICK_HIT1 + 0, 0, 0, // DO_KICK_HIT2 + 0, 0, 0, // DO_KICK_DEATH + 0, 0, 0, // DO_READY_WEAPON + 212, 8, 0, // DO_GUN_DEATH + 220, 8, 0, // DO_EXPLOSION_DEATH + 228, 12,0, // DO_EXPLOSION2_DEATH + 228, 12,0, // DO_GRENADE_DEATH + 240, 18,0, // DO_FIRE_DEATH + 0, 0, 0, // DO_GESTURE1 // N/A + 0, 0, 0, // DO_SALUTE1 // N/A + 0, 0, 0, // DO_GESTURE2 // N/A + 0, 0, 0, // DO_SALUTE2 // N/A + 0, 0, 0, // DO_PULL_GUN + 120, 31,0, // DO_PLEAD + 151, 14,0 // DO_PLEAD_DEATH +}; + +static InfantryTypeClass const Moebius( + INFANTRY_MOEBIUS, // Infantry type number. + TXT_MOEBIUS, // Translate name number for infantry type. + "MOEBIUS", // INI name for infantry. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + false, // Is a leader type? + false, // Has crawling animation frames? + true, // Is this a civlian? + true, // Always use the given name for the infantry? + true, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + 0, // Number of shots it has (default). + &MoebiusDos[0][0], // ptr to DO table + 0, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 50, // Strength of infantry (in damage points). + 0, // Sight range. + 10, // Cost of infantry (in credits). + 99, // Scenario when they first appear. + 0,10, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD| + HOUSEF_NEUTRAL, // Who can own this infantry unit. + WEAPON_NONE,WEAPON_NONE, + MPH_SLOW_ISH +); + +int DelphiDos[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 0, 1, 1, // DO_STAND_GUARD + 0, 1, 1, // DO_PRONE // N/A + 56, 6, 6, // DO_WALK + 205, 4, 4, // DO_FIRE_WEAPON + 0, 1, 1, // DO_LIE_DOWN // N/A + 8, 6, 6, // DO_CRAWL + 0, 1, 1, // DO_GET_UP // N/A + 205, 4, 4, // DO_FIRE_PRONE + 189, 10,0, // DO_IDLE1 + 199, 6, 0, // DO_IDLE2 + 104, 3, 0, // DO_ON_GUARD + 107, 7, 0, // DO_FIGHT_READY + 114, 2, 0, // DO_PUNCH + 116, 6, 0, // DO_KICK + 122, 2, 0, // DO_PUNCH_HIT1 + 124, 4, 0, // DO_PUNCH_HIT2 + 128, 4, 0, // DO_PUNCH_DEATH + 133, 2, 0, // DO_KICK_HIT1 + 135, 4, 0, // DO_KICK_HIT2 + 139, 5, 0, // DO_KICK_DEATH + 144, 3, 0, // DO_READY_WEAPON + 329, 8, 0, // DO_GUN_DEATH + 337, 8, 0, // DO_EXPLOSION_DEATH + 337, 8, 0, // DO_EXPLOSION2_DEATH + 345, 12,0, // DO_GRENADE_DEATH + 357, 18,0, // DO_FIRE_DEATH + 0, 0, 0, // DO_GESTURE1 // N/A + 0, 0, 0, // DO_SALUTE1 // N/A + 0, 0, 0, // DO_GESTURE2 // N/A + 0, 0, 0, // DO_SALUTE2 // N/A + 199, 6, 0, // DO_PULL_GUN + 237, 40,0, // DO_PLEAD + 277, 6, 0, // DO_PLEAD_DEATH +}; + +static InfantryTypeClass const Delphi( + INFANTRY_DELPHI, // Infantry type number. + TXT_DELPHI, // Translate name number for infantry type. + "DELPHI", // INI name for infantry. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + false, // Is a leader type? + false, // Has crawling animation frames? + true, // Is this a civlian? + true, // Always use the given name for the infantry? + true, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + 10, // Number of shots it has (default). + &DelphiDos[0][0], // ptr to DO table + 2, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 25, // Strength of infantry (in damage points). + 0, // Sight range. + 10, // Cost of infantry (in credits). + 99, // Scenario when they first appear. + 0,0, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_NEUTRAL, // Who can own this infantry unit. + WEAPON_PISTOL,WEAPON_NONE, + MPH_SLOW_ISH +); + + +int DrChanDos[DO_COUNT][3]={ + 0, 1, 1, // DO_STAND_READY + 0, 1, 1, // DO_STAND_GUARD + 0, 1, 1, // DO_PRONE // N/A + 56, 6, 6, // DO_WALK + 205, 4, 4, // DO_FIRE_WEAPON + 0, 1, 1, // DO_LIE_DOWN // N/A + 8, 6, 6, // DO_CRAWL + 0, 1, 1, // DO_GET_UP // N/A + 0, 0, 0, // DO_FIRE_PRONE + 104, 16,0, // DO_IDLE1 + 120, 20,0, // DO_IDLE2 + 0, 0, 0, // DO_ON_GUARD + 0, 0, 0, // DO_FIGHT_READY + 0, 0, 0, // DO_PUNCH + 0, 0, 0, // DO_KICK + 0, 0, 0, // DO_PUNCH_HIT1 + 0, 0, 0, // DO_PUNCH_HIT2 + 0, 0, 0, // DO_PUNCH_DEATH + 0, 0, 0, // DO_KICK_HIT1 + 0, 0, 0, // DO_KICK_HIT2 + 0, 0, 0, // DO_KICK_DEATH + 0, 0, 0, // DO_READY_WEAPON + 212, 8, 0, // DO_GUN_DEATH + 220, 8, 0, // DO_EXPLOSION_DEATH + 228, 12,0, // DO_EXPLOSION2_DEATH + 228, 12,0, // DO_GRENADE_DEATH + 240, 18,0, // DO_FIRE_DEATH + 0, 0, 0, // DO_GESTURE1 // N/A + 0, 0, 0, // DO_SALUTE1 // N/A + 0, 0, 0, // DO_GESTURE2 // N/A + 0, 0, 0, // DO_SALUTE2 // N/A + 0, 0, 0, // DO_PULL_GUN + 120, 31,0, // DO_PLEAD + 151, 14,0 // DO_PLEAD_DEATH +}; + +static InfantryTypeClass const DrChan( + INFANTRY_CHAN, // Infantry type number. + TXT_CHAN, // Translate name number for infantry type. + "CHAN", // INI name for infantry. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Is this a female type? + false, // Is a leader type? + false, // Has crawling animation frames? + true, // Is this a civlian? + true, // Always use the given name for the infantry? + true, // Is this a "fraidycat" run-away type infantry? + false, // Can this infantry type capture a building? + false, // Theater specific graphic image? + 10, // Number of shots it has (default). + &DrChanDos[0][0], // ptr to DO table + 2, // Frame of projectile launch. + 0, // Frame of projectile launch while prone. + 25, // Strength of infantry (in damage points). + 0, // Sight range. + 10, // Cost of infantry (in credits). + 99, // Scenario when they first appear. + 0,1, // Risk/Reward of this infantry unit. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_NEUTRAL, // Who can own this infantry unit. + WEAPON_NONE,WEAPON_NONE, + MPH_SLOW_ISH +); + + +/* +** This is the array of pointers to the static data associated with each +** infantry type. +*/ +InfantryTypeClass const * const InfantryTypeClass::Pointers[INFANTRY_COUNT] = { + &E1, + &E2, + &E3, + &E4, + &E5, +// &E6, + &E7, + &Commando, + &C1, + &C2, + &C3, + &C4, + &C5, + &C6, + &C7, + &C8, + &C9, + &C10, + &Moebius, + &Delphi, + &DrChan +}; + + +/*********************************************************************************************** + * InfantryTypeClass::InfantryTypeClass -- Constructor for infantry type class objects. * + * * + * This routine will construct the infantry type objects. It is use to create the static * + * infantry types that are used to give each of the infantry objects their characteristics. * + * * + * INPUT: see below... * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +InfantryTypeClass::InfantryTypeClass ( + InfantryType type, int name, char const *ininame, + unsigned char level, long pre, + bool is_female, + bool is_leader, + bool is_crawling, + bool is_civilian, + bool is_nominal, + bool is_fraidycat, + bool is_capture, + bool is_theater, + int ammo, + int *do_table, + int firelaunch, int pronelaunch, + unsigned short strength, int sightrange, + int cost, int scenario, int risk, int reward, int ownable, + WeaponType primary, WeaponType secondary, + MPHType maxspeed) + : TechnoTypeClass(name, ininame, level, pre, + is_leader, true, is_nominal, false, false, true, true, true, true, false, false, + is_theater, false, false, false, true, false, + ammo, strength, maxspeed, sightrange, cost, + scenario, risk, reward, ownable, + primary, secondary, + ARMOR_NONE) +{ + IsFemale = is_female; + IsCrawling = is_crawling; + IsCapture = is_capture; + IsFraidyCat = is_fraidycat; + IsCivilian = is_civilian; + Type = type; + FireLaunch = firelaunch; + ProneLaunch = pronelaunch; + + /* + ** Set the animation sequence custom values. + */ + + for ( int i=0 ; iClass->House)); +} + + +/*********************************************************************************************** + * InfantryTypeClass::Create_And_Place -- Creates and places infantry object onto the map. * + * * + * This routine is used by the scenario editor to create and place an infantry object onto * + * the map at the location specified. * + * * + * INPUT: cell -- The cell location to place the infantry object at. * + * * + * house -- The owner of the infantry object. * + * * + * OUTPUT: bool; Was the infantry object successfully created and placed at the location * + * specified? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +bool InfantryTypeClass::Create_And_Place(CELL cell, HousesType house) const +{ + InfantryClass * i = new InfantryClass(Type, house); + if (i) { + COORDINATE coord = Map[cell].Closest_Free_Spot(Cell_Coord(cell)); + if (coord) { + return(i->Unlimbo(coord, DIR_E)); + } else { + delete i; + } + } + return(false); +} + + +/*********************************************************************************************** + * InfantryTypeClass::Occupy_List -- Returns with default infantry occupation list. * + * * + * This routine will return with a cell offset occupation list for a generic infantry * + * object. This is typically just a single cell since infantry are never bigger than one * + * cell and this routine presumes the infantry is located in the center of the cell. * + * * + * INPUT: placement -- Is this for placement legality checking only? The normal condition * + * is for marking occupation flags. * + * * + * OUTPUT: Returns with a cell offset list for the infantry object as if it were located * + * in the center of a cell. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +short const * InfantryTypeClass::Occupy_List(bool) const +{ + static short const _list[] = {0, REFRESH_EOL}; + + return(&_list[0]); +} + + +#ifdef SCENARIO_EDITOR +/*********************************************************************************************** + * InfantryTypeClass::Display -- Displays a generic infantry object. * + * * + * This routine is used by the scenario editor to display a generic representation of the * + * infantry object for the scenario editor. It simply draws a single (nice profile) view * + * of the infantry type. * + * * + * INPUT: x,y -- The display coordinates to render the infantry object at. * + * * + * window -- The window that the display coordinates are relative to. * + * * + * house -- The house colors to use when rendering this infantry object. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryTypeClass::Display(int x, int y, WindowNumberType window, HousesType house) const +{ + if (house != HOUSE_NONE) { + + int shape = 0; + void const * ptr = Get_Cameo_Data(); + if (!ptr) { + ptr = Get_Image_Data(); + shape = 2; + } + + CC_Draw_Shape(ptr, shape, x, y, window, SHAPE_NORMAL|SHAPE_CENTER|SHAPE_WIN_REL); + } +} + + +/*********************************************************************************************** + * InfantryTypeClass::Prep_For_Add -- Prepares the scenario editor for adding of infantry object.* + * * + * This routine will prepare the scenario editor so that the infantry objects appear on * + * the object list. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryTypeClass::Prep_For_Add(void) +{ + for (InfantryType index = INFANTRY_FIRST; index < INFANTRY_COUNT; index++) { + Map.Add_To_List(&As_Reference(index)); + } +} +#endif + + +/*********************************************************************************************** + * InfantryTypeClass::From_Name -- Converts an ASCII name into an infantry type number. * + * * + * This routine is used to convert the infantry ASCII name as specified into an infantry * + * type number. This is called from the INI reader routine in the process if creating the * + * infantry objects needed for the scenario. * + * * + * INPUT: name -- The ASCII name to convert into an infantry type number. * + * * + * OUTPUT: Returns with the infantry type number that corresponds to the infantry ASCII name * + * specified. If no match could be found, then INFANTRY_NONE is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +InfantryType InfantryTypeClass::From_Name(char const *name) +{ + if (name) { + for (InfantryType classid = INFANTRY_FIRST; classid < INFANTRY_COUNT; classid++) { + if (stricmp(Pointers[classid]->IniName, name) == 0) { + return(classid); + } + } + } + return(INFANTRY_NONE); +} + + +/*********************************************************************************************** + * InfantryTypeClass::One_Time -- Performs any one time processing for infantry system. * + * * + * This routine will perform one time processing for the infantry type system. This is * + * generally restricted to loading of the infantry shape data. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryTypeClass::One_Time(void) +{ + InfantryType index; + + for (index = INFANTRY_FIRST; index < INFANTRY_COUNT; index++) { + char fullname[_MAX_FNAME+_MAX_EXT]; + InfantryTypeClass const *uclass; + CCFileClass file; + + uclass = &As_Reference(index); + + /* + ** Generic shape for all houses load method. + */ + _makepath(fullname, NULL, NULL, uclass->IniName, ".SHP"); + ((void const *&)uclass->ImageData) = MixFileClass::Retrieve(fullname); + + /* + ** The small build image icon sized shapes are always generic. + */ + char buffer[_MAX_FNAME]; + if ( Get_Resolution_Factor() ) { + sprintf(buffer, "%.4sICNH", uclass->IniName); + } else { + sprintf(buffer, "%.4sICON", uclass->IniName); + } + _makepath(fullname, NULL, NULL, buffer, ".SHP"); + ((void const *&)uclass->CameoData) = MixFileClass::Retrieve(fullname); + } +} + + + + + + +/*********************************************************************************************** + * ITC::Init -- load up terrain set dependant sidebar icons * + * * + * * + * * + * INPUT: theater type * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 4/25/96 0:33AM ST : Created * + *=============================================================================================*/ + +void InfantryTypeClass::Init(TheaterType theater) +{ + if ( Get_Resolution_Factor() ) { + + if (theater != LastTheater){ + InfantryType index; + char buffer[_MAX_FNAME]; + char fullname[_MAX_FNAME+_MAX_EXT]; + void const * cameo_ptr; + + for (index = INFANTRY_FIRST; index < INFANTRY_COUNT; index++) { + InfantryTypeClass const *uclass; + CCFileClass file; + + uclass = &As_Reference(index); + + ((void const *&)uclass->CameoData) = NULL; + + sprintf(buffer, "%.4sICNH", uclass->IniName); + _makepath (fullname, NULL, NULL, buffer, Theaters[theater].Suffix); + cameo_ptr = MixFileClass::Retrieve(fullname); + if (cameo_ptr){ + ((void const *&)uclass->CameoData) = cameo_ptr; + } + } + } + } +} + + + + + + + + + +/*********************************************************************************************** + * InfantryTypeClass::Who_Can_Build_Me -- Determines what can build the infantry object. * + * * + * Use this routine to determine what building can produce the infantry object. This is * + * typically used to maintain the construction list sidebar. * + * * + * INPUT: intheory -- If no regard is to be given to whether the construction building is * + * currently busy, then this flag should be true. In such a case, only * + * the existance of the building is sufficient to achieve success. * + * * + * legal -- Should building prerequisite legality checks be performed as well? * + * For building placements, this is usually false. For sidebar button * + * adding, this is usually true. * + * * + * house -- The house of the infantry to be produced. Only construction buildings * + * of the same house are considered. * + * * + * OUTPUT: Returns with a pointer to the object (building) that can produce the infantry * + * type. If there are no available buildings then the return value is NULL. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/29/1994 JLB : Created. * + *=============================================================================================*/ +BuildingClass * InfantryTypeClass::Who_Can_Build_Me(bool intheory, bool legal, HousesType house) const +{ + BuildingClass * anybuilding = NULL; + + for (int index = 0; index < Buildings.Count(); index++) { + BuildingClass * building = Buildings.Ptr(index); + + if (building && + !building->IsInLimbo && + building->House->Class->House == house && + building->Mission != MISSION_DECONSTRUCTION && + building->Class->ToBuild == RTTI_INFANTRYTYPE && + ((1L << building->ActLike) & Ownable) && + (!legal || building->House->Can_Build(Type, building->ActLike)) && + (intheory || !building->In_Radio_Contact())) { + + anybuilding = building; + if (building->IsLeader) return(building); + } + } + return(anybuilding); +} + + +/*********************************************************************************************** + * InfantryTypeClass::Full_Name -- Fetches the full name text number. * + * * + * This routine will fetch the full name text number for this infantry type. It examines * + * the special custom name flag to determine whether the custom name or the generic name * + * is to be used. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with text number for the name to give this infantry type object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/29/1995 JLB : Created. * + *=============================================================================================*/ +int InfantryTypeClass::Full_Name(void) const +{ + if (Debug_Map || !IsNominal || Special.IsNamed || Type == INFANTRY_C10 || Type == INFANTRY_DELPHI || Type == INFANTRY_MOEBIUS) { + return(TechnoTypeClass::Full_Name()); + } + return(TXT_CIVILIAN); +} diff --git a/INFANTRY.CPP b/INFANTRY.CPP new file mode 100644 index 0000000..939cf9f --- /dev/null +++ b/INFANTRY.CPP @@ -0,0 +1,3221 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\infantry.cpv 2.19 16 Oct 1995 16:50:30 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : INFANTRY.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : August 15, 1994 * + * * + * Last Update : August 15, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * InfantryClass::AI -- Handles the infantry non-graphic related AI processing. * + * InfantryClass::Active_Click_With -- Handles action when clicking with infantry soldier. * + * InfantryClass::As_Target -- Converts the infantry unit into a target value. * + * InfantryClass::Assign_Destination -- Gives the infantry a movement destination. * + * InfantryClass::Assign_Mission -- Make sure he's out of boxing mode first * + * InfantryClass::Assign_Target -- Gives the infantry a combat target. * + * InfantryClass::Can_Enter_Cell -- Determines if the infantry can enter the cell specified. * + * InfantryClass::Can_Fire -- Can the infantry fire its weapon? * + * InfantryClass::Clear_Occupy_Bit -- Clears occupy bit and given cell * + * InfantryClass::Debug_Dump -- Displays debug information about infantry unit. * + * InfantryClass::Detach -- Removes the specified target from targeting computer. * + * InfantryClass::Do_Action -- Launches the infantry into an animation sequence. * + * InfantryClass::Draw_It -- Draws a unit object. * + * InfantryClass::Enter_Idle_Mode -- The infantry unit enters idle mode by this routine. * + * InfantryClass::Fire_At -- Fires projectile from infantry unit. * + * InfantryClass::Fire_Coord -- Calculates the origin point for projectiles fired. * + * InfantryClass::Greatest_Threat -- Determines greatest threat (target) for infantry unit. * + * InfantryClass::InfantryClass -- The constructor for infantry objects. * + * InfantryClass::Init -- Initialize the infantry object system. * + * InfantryClass::Limbo -- Performs cleanup operations needed when limboing. * + * InfantryClass::Look -- The infantry performs a look operation. * + * InfantryClass::Made_A_Kill -- Marks a kill caused by this infantry soldier. * + * InfantryClass::Overlap_List -- The list of cells that the infantry overlaps, but doesn't occ* + * InfantryClass::Per_Cell_Process -- Handles special operations that occur once per cell. * + * InfantryClass::Random_Animate -- Randomly animate the infantry (maybe) * + * InfantryClass::Read_INI -- Reads units from scenario INI file. * + * InfantryClass::Rearm_Delay -- Return Arming delay for infantry if boxing * + * InfantryClass::Receive_Message -- Process radio messages * + * InfantryClass::Response_Attack -- Plays infantry audio response to attack order. * + * InfantryClass::Response_Move -- Plays infantry response to movement order. * + * InfantryClass::Response_Select -- Plays infantry audio response due to being selected. * + * InfantryClass::Scatter -- Causes the infantry to scatter to nearby cell. * + * InfantryClass::Set_Occupy_Bit -- Sets the occupy bit cell and bit pos * + * InfantryClass::Set_Primary_Facing -- Change infantry primary facing -- always and instantl* + * InfantryClass::Start_Driver -- Handles giving immediate destination and move orders. * + * InfantryClass::Stop_Driver -- Stops the infantry from moving any further. * + * InfantryClass::Take_Damage -- Applies damage to the infantry unit. * + * InfantryClass::Unlimbo -- Unlimbo infantry unit in legal sub-location. * + * InfantryClass::What_Action -- Infantry units might be able to capture -- check. * + * InfantryClass::Write_INI -- Writes all the infantry out to an INI file. * + * InfantryClass::operator delete -- Returns the infantry object back to the free pool * + * InfantryClass::operator new -- Allocates an infantry object from the free pool. * + * InfantryClass::~InfantryClass -- Default destructor for infantry units. * + * InfantryClass::Full_Name -- Fetches the full name of the infantry unit. * + * InfantryClass::Mission_Attack -- Intercept attack mission for special handling. * + * InfantryClass::Validate -- validates infantry pointer. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +int const InfantryClass::HumanShape[32] = {0,0,7,7,7,7,6,6,6,6,5,5,5,5,5,4,4,4,3,3,3,3,2,2,2,2,1,1,1,1,1,0}; + +int Infantry_Kick_Damage[] = {10,15}; +int Infantry_Punch_Damage[] = { 4, 7}; + +/* +** This contains the value of the Virtual Function Table Pointer +*/ +void * InfantryClass::VTable; + + +/*************************************************************************** +** This is the array of constant data associated with infantry maneuvers. It +** specifies the frame rate as well as if the animation can be aborted. +*/ +// interruptable, mobile, randomstart, rate +DoStruct const InfantryClass::MasterDoControls[DO_COUNT] = { + {true, false, false, 0}, // DO_STAND_READY + {true, false, false, 0}, // DO_STAND_GUARD + {true, false, false, 0}, // DO_PRONE + {true, true, true, 2}, // DO_WALK + {true, false, false, 1}, // DO_FIRE_WEAPON + {false, true, false, 2}, // DO_LIE_DOWN + {true, true, true, 2}, // DO_CRAWL + {false, false, false, 3}, // DO_GET_UP + {true, false, false, 1}, // DO_FIRE_PRONE + {true, false, false, 2}, // DO_IDLE1 + {true, false, false, 2}, // DO_IDLE2 + {false, false, false, 2}, // DO_ON_GUARD + {true, false, false, 2}, // DO_FIGHT_READY + {false, false, false, 2}, // DO_PUNCH + {false, false, false, 2}, // DO_KICK + {false, false, false, 2}, // DO_PUNCH_HIT1 + {false, false, false, 2}, // DO_PUNCH_HIT2 + {false, false, false, 1}, // DO_PUNCH_DEATH + {false, false, false, 2}, // DO_KICK_HIT1 + {false, false, false, 2}, // DO_KICK_HIT2 + {false, false, false, 1}, // DO_KICK_DEATH + {false, false, false, 2}, // DO_READY_WEAPON + {false, false, false, 2}, // DO_GUN_DEATH + {false, false, false, 2}, // DO_EXPLOSION_DEATH + {false, false, false, 2}, // DO_EXPLOSION2_DEATH + {false, false, false, 2}, // DO_GRENADE_DEATH + {false, false, false, 2}, // DO_FIRE_DEATH + {false, false, false, 2}, // DO_GESTURE1 + {false, false, false, 2}, // DO_SALUTE1 + {false, false, false, 2}, // DO_GESTURE2 + {false, false, false, 2}, // DO_SALUTE2 + {true, false, false, 2}, // DO_PULL_GUN + {true, false, false, 2}, // DO_PLEAD + {true, false, false, 2}, // DO_PLEAD_DEATH +}; + + +/*********************************************************************************************** + * InfantryClass::Validate -- validates infantry pointer. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int InfantryClass::Validate(void) const +{ + int num; + + num = Infantry.ID(this); + if (num < 0 || num >= INFANTRY_MAX) { + Validate_Error("INFANTRY"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * InfantryClass::Debug_Dump -- Displays debug information about infantry unit. * + * * + * This routine is used by the debug version to display pertinent information about the * + * infantry unit. * + * * + * INPUT: mono -- The monochrome screen to display the debug information to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryClass::Debug_Dump(MonoClass *mono) const +{ + Validate(); + mono->Set_Cursor(0, 0);mono->Print( + "ÚName:ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂMission:ÄÄÄÂTarCom:ÂNavCom:ÂRadio:ÂCoord:ÄÄÂHeadTo:ÄÂSt:Ä¿\n" + "³ ³ ³ ³ ³ ³ ³ ³ ³\n" + "ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂNÂYÂHealth:ÄÂBody:ÂTurret:ÂSpeed:ÂPath:ÁÄÄÄÄÄÄÂCargo:ÄÄÄÄÁÄÄÄÄ´\n" + "³Active........³ ³ ³ ³ ³ ³ ³ ³ ³\n" + "³Limbo.........³ ³ ÃÄÄÄÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´\n" + "³Owned.........³ ³ ³Last Message: ³\n" + "³Discovered....³ ³ ÃTimer:ÂArm:ÂTrack:ÂTiberium:ÂFlash:ÂStage:ÂTeam:ÄÄÄÄÂArch:´\n" + "³Selected......³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³\n" + "³Teathered.....³ ³ ÃÄÄÄÄÄÄÁÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÙ\n" + "³Locked on Map.³ ³ ³ \n" + "³Is Prone......³ ³ ³ \n" + "³Is A Loner....³ ³ ³ \n" + "³Deploying.....³ ³ ³ \n" + "³Rotating......³ ³ ³ \n" + "³Firing........³ ³ ³ \n" + "³Driving.......³ ³ ³ \n" + "³To Look.......³ ³ ³ \n" + "³Recoiling.....³ ³ ³ \n" + "³To Display....³ ³ ³ \n" + "ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÁÄÙ \n"); + mono->Set_Cursor(1, 1);mono->Printf("%s:%s", House->Class->IniName, Class->IniName); + mono->Text_Print("X", 16 + (IsProne?2:0), 10); + mono->Set_Cursor(33, 7);mono->Printf("%2d", Fear); + mono->Set_Cursor(41, 7);mono->Printf("%2d", Doing); + FootClass::Debug_Dump(mono); +} +#endif + +InfantryClass::InfantryClass(void) : Class(0) {}; // Default constructor does nothing. + + +/*********************************************************************************************** + * InfantryClass::InfantryClass -- The constructor for infantry objects. * + * * + * This is the constructor used when creating an infantry unit. All values are required * + * except for facing and position. If these are absent, then the infantry is created in * + * a state of limbo -- not placed upon the map. * + * * + * INPUT: see below... * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + *=============================================================================================*/ +InfantryClass::InfantryClass(InfantryType classid, HousesType house) : + Class(&InfantryTypeClass::As_Reference(classid)), + FootClass(house) +{ + + /* + ** For two shooters, clear out the second shot flag -- it will be set the first time + ** the object fires. For non two shooters, set the flag since it will never be cleared + ** and the second shot flag tells the system that normal rearm times apply -- this is + ** what is desired for non two shooters. + */ + if (Class->IsTwoShooter) { + IsSecondShot = false; + } else { + IsSecondShot = true; + } + Doing = DO_NOTHING; + Fear = 0; // Starts completely brave. + IsProne = false; + IsStoked = false; + IsBoxing = false; + IsTechnician = false; + Strength = Class->MaxStrength; + + /* + ** Civilians carry much less ammo than soldiers do. + */ + Ammo = Class->MaxAmmo; + + /* + ** Keep count of the number of units created. Dont track civilians. + */ + if (!Class->IsCivilian && GameToPlay == GAME_INTERNET){ + House->InfantryTotals->Increment_Unit_Total((int)classid); + } + +} + + +/*********************************************************************************************** + * InfantryClass::~InfantryClass -- Default destructor for infantry units. * + * * + * This is the default destructor for infantry type units. It will put the infantry into * + * a limbo state if it isn't already in that state and the game is still active. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/10/1995 JLB : Created. * + *=============================================================================================*/ +InfantryClass::~InfantryClass(void) +{ + if (GameActive && Class) { + Limbo(); + } + if (GameActive && Team) Team->Remove(this); +} + + +/*********************************************************************************************** + * InfantryClass::operator new -- Allocates an infantry object from the free pool. * + * * + * This will allocate an infantry object from the infantry object free pool. If there is * + * no available slot, then NULL is returned. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the allocated infantry object or NULL if none could be * + * allocated. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + *=============================================================================================*/ +void * InfantryClass::operator new(size_t) +{ + void * ptr = Infantry.Allocate(); + if (ptr) { + ((InfantryClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +/*********************************************************************************************** + * InfantryClass::operator delete -- Returns the infantry object back to the free pool * + * * + * This routine is used return an infantry object back to the system. * + * * + * INPUT: ptr -- Pointer to the infantry object to delete. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/08/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryClass::operator delete(void *ptr) +{ + if (ptr) { + ((InfantryClass *)ptr)->IsActive = false; + } + Infantry.Free((InfantryClass *)ptr); +} + + +/*********************************************************************************************** + * InfantryClass::Take_Damage -- Applies damage to the infantry unit. * + * * + * This routine applies the damage specified to the infantry object. It is possible that * + * this routine will DESTROY the infantry unit in the process. * + * * + * INPUT: damage -- The damage points to inflict. * + * * + * distance -- The distance from the damage center point to the object's center point.* + * * + * warhead -- The warhead type that is inflicting the damage. * + * * + * source -- Who is responsible for inflicting the damage. * + * * + * OUTPUT: bool; Was the infantry unit destroyed by this damage? * + * * + * WARNINGS: Since the infantry unit could be destroyed by this routine, be sure to check * + * for this in the code that follows the call to Take_Damage(). * + * * + * HISTORY: * + * 09/08/1994 JLB : Created. * + * 11/22/1994 JLB : Shares base damage handler for techno objects. * + * 03/31/1995 JLB : Revenge factor. * + *=============================================================================================*/ +ResultType InfantryClass::Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source) +{ + Validate(); + ResultType res = RESULT_NONE; + + IsFiring = false; + + /* + ** Prone infantry take only half damage, but never below one damage point. + */ + if (IsProne && damage) { + damage >>= 1; +// damage = MAX(damage, 1); + } + + +//Mono_Printf("Infantry Take_Damage(%d, %d, %d, %p)\r", damage, distance, warhead, source); +//Get_Key(); + + res = FootClass::Take_Damage(damage, distance, warhead, source); + + /* + ** Flame thrower guys take more damage because of the exposed pilot light + ** on their flame gun. + */ + if (damage && res != RESULT_DESTROYED && *this == INFANTRY_E4) { + damage = 5; + ResultType newres = FootClass::Take_Damage(damage, distance, warhead, source); + res = MAX(res, newres); + } + + if (res == RESULT_NONE) return(res); + + if (res == RESULT_DESTROYED) { + Death_Announcement(source); + Stop_Driver(); + Stun(); + Mission = MISSION_NONE; + Assign_Mission(MISSION_GUARD); + Commence(); + + /* + ** Flame thrower infantry always go out with a bang. + */ + if (*this == INFANTRY_E4) { + new AnimClass(ANIM_NAPALM1, Coord); + Explosion_Damage(Coord, 80, 0, WARHEAD_FIRE); + } + + if (*this == INFANTRY_E2) { + new AnimClass(ANIM_ART_EXP1, Coord); + Explosion_Damage(Coord, 30, 0, WARHEAD_HE); + } + + if (*this == INFANTRY_E5) { + new AnimClass(ANIM_CHEM_BALL, Coord); + Explosion_Damage(Coord, 80, 0, WARHEAD_HE); + } + + VocType sound; + VocType altsound; + if (*this == INFANTRY_RAMBO) { +// if (Sim_Random_Pick(0, 3) != 1) { + sound = VOC_RAMBO_YELL; +// } else { +// sound = VOC_RAMBO_OHSH; +// } + altsound = sound; + } else { + sound = Sim_Random_Pick(VOC_SCREAM1, VOC_SCREAM5); + altsound = VOC_YELL1; + } + + /* + ** The type of warhead determines the animation the infantry + ** will perform when killed. + */ + switch (warhead) { + case WARHEAD_FEEDME: + if (source) { + source->Strength += 30; + if (source->Strength > source->Class_Of().MaxStrength) { + source->Strength = source->Class_Of().MaxStrength; + } + } + // Fall thru to WARHEAD_SA: + + case WARHEAD_HEADBUTT: + case WARHEAD_SPORE: + case WARHEAD_HOLLOW_POINT: + case WARHEAD_SA: + Sound_Effect(sound, Coord); + Do_Action(DO_GUN_DEATH, true); + break; + + case WARHEAD_HE: + Sound_Effect(sound, Coord); + Do_Action(DO_EXPLOSION_DEATH, true); + break; + + case WARHEAD_AP: + Sound_Effect(sound, Coord); + Do_Action(DO_GRENADE_DEATH, true); + break; + + case WARHEAD_PB: + case WARHEAD_LASER: + case WARHEAD_FIRE: + Sound_Effect(altsound, Coord); + Do_Action(DO_FIRE_DEATH, true); + break; + + case WARHEAD_FIST: + Sound_Effect(sound, Coord); + Do_Action(DO_PUNCH_DEATH,true); + break; + + case WARHEAD_FOOT: + Sound_Effect(sound, Coord); + Do_Action(DO_KICK_DEATH,true); + break; + } + + return(res); + } + + /* + ** When infantry gets hit, it gets scared. + */ + if (res != RESULT_DESTROYED) { + COORDINATE c4 = (source) ? source->Coord : NULL; + if (source) { + Scatter(c4); + } + +#ifdef BOXING + if (IsBoxing) { + int addval = 0; + + switch (warhead) { + case WARHEAD_FIST: + if (damage == Infantry_Punch_Damage[1]) addval++; + Do_Action( (DoType) ( (int)DO_PUNCH_HIT1 + addval),true); + break; + + case WARHEAD_FOOT: + if (damage == Infantry_Kick_Damage[1]) addval++; + Do_Action( (DoType) ( (int)DO_KICK_HIT1 + addval),true); + break; + } + } else { +#endif + if (source && Fear < FEAR_SCARED) { + if (Class->IsFraidyCat) { + Fear = FEAR_PANIC; + } else { + Fear = FEAR_SCARED; + } + } else { + int morefear = FEAR_ANXIOUS; + if (Health_Ratio() > 0x0080) morefear /= 4; + Fear = MIN((int)Fear + morefear, FEAR_MAXIMUM); + } +#ifdef BOXING + } +#endif + } + return(res); +} + + +/*********************************************************************************************** + * InfantryClass::Draw_It -- Draws a unit object. * + * * + * This routine is the one that actually draws a unit object. It displays the unit * + * according to its current state flags and centered at the location specified. * + * * + * INPUT: x,y -- The X and Y coordinate of where to draw the unit. * + * * + * window -- The clipping window to use. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/20/1994 JLB : Created. * + * 06/27/1994 JLB : Takes a window parameter. * + * 08/15/1994 JLB : Converted to infantry support. * + *=============================================================================================*/ +void InfantryClass::Draw_It(int x, int y, WindowNumberType window) +{ + Validate(); + void const * shapefile; // Working shape file pointer. + int facing = Facing_To_32(PrimaryFacing.Current()); + + /* + ** Verify the legality of the unit class. + */ + shapefile = Class->Get_Image_Data(); + if (!shapefile) return; + + y += 4; + x -= 2; + + /* + ** Fetch the basic body shape pointer. This requires taking into account + ** the current animation stage. + */ + int shapenum; + int facenum; + + shapenum = 0; + facenum = HumanShape[facing]; + + /* + ** Fetch the shape pointer to use for the infantry. This is controlled by what + ** choreograph sequence the infantry is performing, it's facing, and whether it + ** is prone. + */ + DoType doit = Doing; + if (doit == DO_NOTHING) doit = DO_STAND_READY; + + shapenum = Class->DoControls[doit].Count; + shapenum = Fetch_Stage() % MAX(shapenum, 1); + if (Class->DoControls[doit].Jump) { + shapenum += facenum * Class->DoControls[doit].Jump; + } + shapenum += Class->DoControls[doit].Frame; + +#ifdef BOXING +// BG hack to get him to face right when he's supposed to. + if (IsBoxing && BodyFacing<128) shapenum += 47; +#endif + + /* + ** Actually draw the root body of the unit. + */ + Techno_Draw_Object(shapefile, shapenum, x, y, window); +// CC_Draw_Shape(shapefile, shapenum, x, y, window, SHAPE_FADING|SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_GHOST, House->Remap_Table(IsBlushing, true), Map.UnitShadow); + + FootClass::Draw_It(x, y, window); +} + + +/*********************************************************************************************** + * InfantryClass::Per_Cell_Process -- Handles special operations that occur once per cell. * + * * + * This routine will handle any special operations that need to be performed once each * + * cell travelled. This includes radioing a transport that is is now clear and the * + * transport is free to leave. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/08/1994 JLB : Created. * + * 03/01/1995 JLB : Capture building options. * + * 05/31/1995 JLB : Capture is always successful now. * + *=============================================================================================*/ +void InfantryClass::Per_Cell_Process(bool center) +{ + Validate(); + CellClass *cellptr = &Map[Coord_Cell(Coord)]; + + /* + ** If the infantry unit is entering a cell that contains the building it is trying to + ** capture, then capture it. + */ + if (center && Mission == MISSION_CAPTURE) { + TechnoClass * tech = cellptr->Cell_Techno(); + if (tech && tech->As_Target() == NavCom) { + tech->Captured(House); + delete this; + return; + } else { +//#ifdef NEVER + if (!Target_Legal(NavCom)) { + Enter_Idle_Mode(); + if (Map[Coord_Cell(Coord)].Cell_Building()) { + Scatter(0, true); + } + } +//#endif + } + } + + /* + ** Infantry entering a transport vehicle will break radio contact + ** at attach itself to the transporter. + */ + TechnoClass * techno = Contact_With_Whom(); + if (center && Mission == MISSION_ENTER && techno && Coord_Cell(Coord) == Coord_Cell(techno->Coord) && techno == As_Techno(NavCom)) { + if (Transmit_Message(RADIO_IM_IN) == RADIO_ATTACH) { + Limbo(); + techno->Attach(this); + } + return; + } + + /* + ** If the infantry unit is entering a cell that contains the building it is trying to + ** sabotage, then sabotage it. + */ + if (center && Mission == MISSION_SABOTAGE) { + BuildingClass *building = cellptr->Cell_Building(); + if (building && building->As_Target() == NavCom) { + int temp = Special.IsScatter; + + building->IsGoingToBlow = true; + building->Clicked_As_Target(20); + building->CountDown.Set(20); + building->WhomToRepay = As_Target(); + Special.IsScatter = true; + NavCom = TARGET_NONE; + Do_Uncloak(); + Arm = Rearm_Delay(true); + Scatter(building->Center_Coord(), true); // RUN AWAY! + Special.IsScatter = temp; + return; + } + } + + /* + ** If this unit is on a teather, then cut it at this time so that + ** the "parent" unit is free to proceed. Note that the parent + ** unit might actually be a building. + */ + if (center && IsTethered) { + Transmit_Message(RADIO_UNLOADED); + if (House->Class->House == HOUSE_GOOD) { + Do_Action(DO_GESTURE1); + } else { + Do_Action(DO_GESTURE2); + } + + /* + ** Rambo types give a gung-ho comment when unloaded. + */ + if (*this == INFANTRY_RAMBO) { + Sound_Effect(VOC_RAMBO_ROCK, Coord); + } + + /* + ** If the cell is now full of infantry, tell them all to scatter + ** in order to make room for more. + */ + if ((cellptr->Flag.Composite & 0x01F) == 0x01F) { + cellptr->Incoming(0, true); + } + } + + /* + ** When the infantry reaches the center of the cell, it may begin a new mission. + */ + if (center) { + Commence(); + } + + Look(true); + FootClass::Per_Cell_Process(center); + + /* + ** If over Tiberium, then this infantry unit will take damage. + */ + if (IsActive && !IsInLimbo && center && cellptr->Land_Type() == LAND_TIBERIUM && *this != INFANTRY_E5) { + int damage = 2; + Take_Damage(damage, 0, WARHEAD_FIRE); + } +} + + +/*********************************************************************************************** + * InfantryClass::Detach -- Removes the specified target from targeting computer. * + * * + * This is a support routine that removes the target specified from any targeting or * + * navigation computers. When a target is destroyed or removed from the game system, * + * the target must be removed from any tracking systems of the other units. This routine * + * handles removal for infantry units. * + * * + * INPUT: target -- The target to remove from the infantry unit's tracking systems. * + * * + * all -- Is the target going away for good as opposed to just cloaking/hiding? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/08/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryClass::Detach(TARGET target, bool all) +{ + Validate(); + if (TarCom == target) { + IsFiring = false; + } + FootClass::Detach(target, all); +} + + +/*********************************************************************************************** + * InfantryClass::As_Target -- Converts the infantry unit into a target value. * + * * + * This support routine is used to convert the infantry object (as a pointer) into a target * + * value (which is a number). * + * * + * INPUT: none * + * * + * OUTPUT: Returns the infantry unit as a target value. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/08/1994 JLB : Created. * + *=============================================================================================*/ +TARGET InfantryClass::As_Target(void) const +{ + Validate(); + return(Build_Target(KIND_INFANTRY, Infantry.ID(this))); +} + + +/*********************************************************************************************** + * InfantryClass::Init -- Initialize the infantry object system. * + * * + * This routine will force the infantry object system into its empty initial state. It * + * is called when the scenario needs to be cleared in preparation for a scenario load. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/08/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryClass::Init(void) +{ + InfantryClass *ptr; + + Infantry.Free_All(); + + ptr = new InfantryClass(); + VTable = ((void **)(((char *)ptr) + sizeof(AbstractClass) - 4))[0]; + delete ptr; +} + + +/*********************************************************************************************** + * InfantryClass::Look -- The infantry performs a look operation. * + * * + * This routine will cause the infantry unit to "look". For player owned infantry, this * + * causes the dark shroud to be pushed back. * + * * + * INPUT: incremental -- If it is known that the infantry performed a look in the last cell * + * it was in AND it has only moved one cell, then setting this * + * parameter to true will perform a faster "incremental" look * + * operation. * + * * + * OUTPUT: none * + * * + * WARNINGS: This is a relatively slow routine. Call ONLY when necessary. * + * * + * HISTORY: * + * 09/08/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryClass::Look(bool incremental) +{ + Validate(); + int sight; // Number of cells to sight. + + if (!IsInLimbo) { + if (IsOwnedByPlayer) { + sight = Class->SightRange; + + if (sight) { + Map.Sight_From(Coord_Cell(Coord), sight, incremental); + } + } + } +} + + +/*********************************************************************************************** + * InfantryClass::Assign_Destination -- Gives the infantry a movement destination. * + * * + * This routine updates the infantry's navigation computer so that the infantry will * + * travel to the destination target specified. * + * * + * INPUT: target -- The target to have the infantry unit move to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/08/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryClass::Assign_Destination(TARGET target) +{ + Validate(); + /* + ** Special flag so that infantry will start heading in the right direction immediately. + */ + if (Target_Legal(target)) { + Stop_Driver(); + } + + /* + ** When telling an infantry soldier to move to a location twice, then this + ** means that movement is more important than safety. Get up and run! + */ + if (House->IsHuman && Target_Legal(target) && NavCom == target && IsProne && !Class->IsFraidyCat) { + Do_Action(DO_GET_UP); + } + + /* + ** Handle entry logic here. + */ + if (Mission == MISSION_ENTER || MissionQueue == MISSION_ENTER) { + + /* + ** If not already in radio contact (presumed with the transport), then + ** either try to establish contact if allowed, or just move close and + ** wait until radio contact can be established. + */ + if (!In_Radio_Contact()) { + TechnoClass * techno = As_Techno(target); + + if (techno) { + + /* + ** Determine if the transport is already in radio contact. If so, then just move + ** toward the transport and try to establish contact at a later time. + */ + if (techno->In_Radio_Contact()) { + +// TCTCTC -- call for an update from the transport to get a good rondezvous position. + + ArchiveTarget = target; + } else { + if (Transmit_Message(RADIO_HELLO, techno) == RADIO_ROGER) { + if (Transmit_Message(RADIO_DOCKING) != RADIO_ROGER) { + Transmit_Message(RADIO_OVER_OUT); + Assign_Mission(MISSION_MOVE); + } + } else { + Assign_Mission(MISSION_MOVE); + } + } + } + } else { + Path[0] = FACING_NONE; + } + } else { + Path[0] = FACING_NONE; + } + FootClass::Assign_Destination(target); +} + + +/*********************************************************************************************** + * InfantryClass::Assign_Target -- Gives the infantry a combat target. * + * * + * This routine will update the infantry's targeting computer so that it will try to * + * attack the target specified. This might result in it moving to be within range and thus * + * also cause adjustment of the navigation computer. * + * * + * INPUT: target -- The target that this infantry should attack. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/08/1994 JLB : Created. * + * 06/30/1995 JLB : Tries to capture target if possible. * + *=============================================================================================*/ +void InfantryClass::Assign_Target(TARGET target) +{ + Validate(); + Path[0] = FACING_NONE; + FootClass::Assign_Target(target); + + /* + ** If this is an infantry that can only capture, then also assign its destination to the + ** target specified. + */ + if (!Target_Legal(NavCom) && Class->IsCapture && Class->Primary == WEAPON_NONE) { + BuildingClass const * building = As_Building(target); + if (building && building->Class->IsCaptureable && (GameToPlay != GAME_NORMAL || *building != STRUCT_EYE && Scenario < 13)) { + Assign_Destination(target); + } + } +} + + +/*********************************************************************************************** + * InfantryClass::AI -- Handles the infantry non-graphic related AI processing. * + * * + * This routine is used to handle the non-graphic AI processing the infantry requires. * + * Call this routine ONCE per game frame. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/08/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryClass::AI(void) +{ + Validate(); + FootClass::AI(); + + if (IsUnloading) Mark(MARK_CHANGE); + + /* + ** Special hack to make sure that if this infantry is in firing animation, but the + ** stage class isn't set, then abort the firing flag. + */ + if (IsFiring && !Fetch_Rate()) { + IsFiring = false; + } + + /* + ** Delete this unit if it finds itself off the edge of the map and it is in + ** guard or other static mission mode. + */ + if (!Team && Mission == MISSION_GUARD && !Map.In_Radar(Coord_Cell(Coord))) { + Stun(); + delete this; + return; + } + + /* + ** Act on new orders if the unit is at a good position to do so. + */ + if (!IsDriving && (Doing == DO_NOTHING || MasterDoControls[Doing].Interrupt)) { + Commence(); + } + + /* + ** After a time, the infantry will gain courage. + */ + if (Fear) { + + /* + ** Nikumba is really a coward at heart. He never becomes un-afraid. + */ + if (*this != INFANTRY_C10) { + Fear--; + + /* + ** When an armed civilian becomes unafraid, he will then reload + ** another clip into his pistol. + */ + if (Fear == 0 && Ammo == 0 && Class->Primary != WEAPON_NONE) { + Ammo = Class->MaxAmmo; + } + } + + /* + ** Stand up if brave and lie down if afraid. + */ + if (IsProne) { + if (Fear < FEAR_ANXIOUS) { + Do_Action(DO_GET_UP); + } + } else { + + /* + ** Drop to the ground if anxious. Don't drop to the ground while moving + ** and the special elite flag is active. + */ + if (Fear >= FEAR_ANXIOUS && ((!Target_Legal(NavCom) && !IsDriving) || !Special.IsDefenderAdvantage)) { + Do_Action(DO_LIE_DOWN); + } + } + } + + /* + ** When is darkness or in doubt, + ** run in circles, scream, and shout. + */ + if (Class->IsFraidyCat && Fear > FEAR_ANXIOUS && !IsDriving && !Target_Legal(NavCom)) { + Scatter(true); + } + + /* + ** Special victory dance action. + */ + if (!Target_Legal(NavCom) && !IsProne && IsStoked && Comment.Expired()) { + IsStoked = false; + Do_Action((Random_Pick(0, 1) == 0) ? DO_GESTURE1 : DO_GESTURE2); + if (*this == INFANTRY_RAMBO) { + VocType _response[] = { + VOC_RAMBO_LEFTY, + VOC_RAMBO_LAUGH, + VOC_RAMBO_COMIN, + VOC_RAMBO_TUFF + }; + Sound_Effect(_response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)], Coord); + } + } + + /* + ** Determine if this infantry unit should fire off an + ** attack or not. + */ + switch (Can_Fire(TarCom, 0)) { + case FIRE_CLOAKED: + Do_Uncloak(); + break; + + case FIRE_OK: +#ifdef BOXING + ObjectClass *object = As_Object(TarCom); + + if (object) { + /* If we're engaged in hand-to-hand combat, keep boxing */ + if (IsBoxing) { + IsFiring = true; + if (((InfantryClass *)object)->Doing == DO_FIGHT_READY) { + Do_Action((DoType) ((int)DO_PUNCH + (DoType)(Random_Pick(0, 1) == 1)),true); + } + } else { + + if (Is_Target_Infantry(TarCom) && (Distance(TarCom)<=0x80) && (Coord_Y(Coord) == Coord_Y(object->Coord))) { + + // Too close to shoot, so start hand-to-hand combat + if (Establish_Contact((TechnoClass *)object)) { + if (Transmit_Message(RADIO_PREPARE_TO_BOX) == RADIO_ROGER) { + IsBoxing = true; + Do_Action(DO_ON_GUARD,true); + } + } + } else { +#endif + + /* + ** Start firing animation. + */ + if (IsProne) { + Do_Action(DO_FIRE_PRONE); + IsFiring = true; + } else { + Do_Action(DO_FIRE_WEAPON); + IsFiring = true; + } +#ifdef BOXING + } +#endif + + PrimaryFacing.Set(Direction8(Center_Coord(), As_Coord(TarCom))); + + /* + ** If the target is in range, and the NavCom is the same, then just + ** stop and keep firing. + */ + if (TarCom == NavCom) { + NavCom = TARGET_NONE; + Path[0] = FACING_NONE; + } +#ifdef BOXING + } + } +#endif + break; + } + + /* + ** If in the middle of firing animation, then only + ** process that. Infantry cannot fire and move simultaneously. + ** At some point in the firing animation process, a projectile + ** will be launched. When the required animation frames have + ** been completed, the firing animation stops. + */ + int firestage = Class->FireLaunch; + if (IsProne) firestage = Class->ProneLaunch; + +#ifdef BOXING + if (IsBoxing) { + firestage = 1; + if (Doing == DO_KICK) firestage = 2; + } +#endif + + if (IsFiring && Fetch_Stage() == firestage) { + Fire_At(TarCom, 0); + + if (Class->Primary == WEAPON_GRENADE) { + Map[::As_Cell(TarCom)].Incoming(Coord, true); + } + } + + /* + ** Handle the completion of the animation sequence. + */ + + if (Doing == DO_NOTHING || Fetch_Stage() >= Class->DoControls[Doing].Count) { + switch (Doing) { + default: + if (IsDriving) { + if (IsProne) { + Do_Action(DO_CRAWL, true); + } else { + Do_Action(DO_WALK, true); + } + } else { + if (IsProne) { + Do_Action(DO_PRONE, true); + } else { + Do_Action(DO_STAND_READY, true); + } + } + break; + +#ifdef BOXING + case DO_FIGHT_READY: + case DO_ON_GUARD: + case DO_PUNCH: + case DO_KICK: + case DO_PUNCH_HIT1: + case DO_PUNCH_HIT2: + case DO_KICK_HIT1: + case DO_KICK_HIT2: + if (In_Radio_Contact()) { + Do_Action(DO_FIGHT_READY, true); + } else { + IsBoxing = false; + Do_Action(DO_READY_WEAPON); + } + break; +#endif + + /* + ** When death is due to hand-to-hand combat, use the gunfire death + ** decay animation since there is no custom animation available - yet. + */ + case DO_PUNCH_DEATH: + case DO_KICK_DEATH: + // Fall into next case. + + case DO_GUN_DEATH: + case DO_EXPLOSION_DEATH: + case DO_EXPLOSION2_DEATH: + case DO_GRENADE_DEATH: + case DO_FIRE_DEATH: + delete this; + return; + } + } + + /* + ** Perform movement operations at this time. + */ + if (!IsFiring /*&& !IsBoxing*/) { + if (!IsDriving) { + + /* + ** When in guard mode, never allow a valid navcom. + */ + if (Mission == MISSION_GUARD && MissionQueue == MISSION_NONE && Target_Legal(NavCom)) { + Assign_Destination(TARGET_NONE); +// if (IsTethered) Scatter(0, true); + } + + /* + ** A head to coordinate is needed. If there is no path + ** available, then create one. + */ + if (Target_Legal(NavCom) && Strength && Mission != MISSION_GUARD) { + + /* + ** Determine if the next cell in the list is available + ** to be entered. If not, then abort the path and try + ** again. + */ + if (Path[0] != FACING_NONE && Can_Enter_Cell(Adjacent_Cell(Coord_Cell(Center_Coord()), Path[0])) != MOVE_OK) { + Path[0] = FACING_NONE; + } + + /* + ** Check to see if the target is closer than expected. This occurs + ** when heading toward a moving object and that object is heading + ** toward the unit. Shorten the precalculated path to be no longer + ** than the distance to the target. + */ + int d = Lepton_To_Cell(Distance(NavCom)); + if (d < CONQUER_PATH_MAX) { + Path[d] = FACING_NONE; + } + + /* + ** Find a path to follow if one isn't already calculated. + */ + if (Path[0] == FACING_NONE) { + + /* + ** Calculate the path from the current location to the + ** destination indicated by the navigation computer. If there + ** was a fundamental error with finding a path, then this + ** indicates that basic path & movement logic needs to be + ** aborted. + */ + if (!PathDelay.Expired()) { + return; + } + if (!Basic_Path()) { +//Mono_Printf("Infantry Basic_Path is failing.\n");Get_Key(); + if (Distance(NavCom) < 0x0280 && !IsTethered) { + Assign_Destination(TARGET_NONE); + } else { + if (TryTryAgain) { + TryTryAgain--; + } else { + if (IsNewNavCom) Sound_Effect(VOC_SCOLD); + IsNewNavCom = false; + } + } + Stop_Driver(); + return; + } + TryTryAgain = PATH_RETRY; + } + + /* + ** Determine the coordinate to head to based on the infantry's + ** current location and the next location in the path. + */ + COORDINATE acoord = Adjacent_Cell(Coord, Path[0]); + CELL acell = Coord_Cell(acoord); + + if (Can_Enter_Cell(acell) != MOVE_OK) { + + if ((Mission == MISSION_MOVE || Mission == MISSION_ENTER) && !IsTethered && House->IsHuman && Distance(NavCom) < 0x0200) { + Assign_Destination(TARGET_NONE); + } + + /* + ** If blocked by a moving block then just exit start of move and + ** try again next tick. + */ + if (Can_Enter_Cell(acell) == MOVE_DESTROYABLE) { + if (Map[acell].Cell_Object()) { + if (!House->Is_Ally(Map[acell].Cell_Object())) { + Override_Mission(MISSION_ATTACK, Map[acell].Cell_Object()->As_Target(), TARGET_NONE); + } + } else { + if (Map[acell].Overlay != OVERLAY_NONE && OverlayTypeClass::As_Reference(Map[acell].Overlay).IsWall) { + Override_Mission(MISSION_ATTACK, ::As_Target(acell), TARGET_NONE); + } + } + } + + Path[0] = FACING_NONE; + Stop_Driver(); + if (IsNewNavCom) Sound_Effect(VOC_SCOLD); + IsNewNavCom = false; + + } else { + if (Start_Driver(acoord)) { + PrimaryFacing.Set(Direction8(Center_Coord(), Head_To_Coord())); + Set_Speed(0xFF); + if (IsProne) { + Do_Action(DO_CRAWL); + } else { + Do_Action(DO_WALK); + } + } + } + } + + } else { + + /* + ** The infantry knows where it should be headed, so head there. Check + ** to see if the infantry is "close enough" to the desired location that + ** it should just consider itself to have arrived. In this case, force + ** the infantry to the destination location and mark this path step + ** as complete. + */ + Mark(MARK_UP); + if (Distance(Head_To_Coord()) < 0x0010) { + + memcpy(&Path[0], &Path[1], sizeof(Path)-sizeof(Path[0])); + Path[(sizeof(Path)/sizeof(Path[0]))-1] = FACING_NONE; + Coord = Head_To_Coord(); + Stop_Driver(); + Per_Cell_Process(true); + + if (!IsActive || IsInLimbo) return; + + if (Coord_Cell(Coord) == As_Cell(NavCom)) { + NavCom = TARGET_NONE; + if (Mission == MISSION_MOVE) { + Enter_Idle_Mode(); + } + //Stop_Driver(); + Path[0] = FACING_NONE; + } + } else { + int movespeed = Speed; + + /* + ** When prone, the infantry moves at half speed or double + ** speed. This depends on whether the infantry actually has + ** prone animation stages. Civilians don't, and so they + ** run instead. + */ + if (IsProne) { + if (Class->IsFraidyCat && !Class->IsCrawling) { + movespeed = Speed*2; + } else { + movespeed = Speed/2; + } + } + + if (IsTethered) { + Transmit_Message(RADIO_REDRAW); + } + + /* + ** Advance the infantry as far as it should go. + */ + Coord = Coord_Move(Coord, Direction(Head_To_Coord()), Fixed_To_Cardinal(Class->MaxSpeed, movespeed)); + } + Mark(MARK_DOWN); + } + IsNewNavCom = false; + } +} + + +#ifdef NEVER +/*************************************************************************** + * InfantryClass::Blocking_Object -- Determines how a object blocks an inf * + * * + * INPUT: TechnoClass * pointer to object that is blocking unit * + * CELL the cell the unit is being blocked in * + * * + * OUTPUT: MoveBitType the way that the object is blocking the unit * + * * + * HISTORY: * + * 06/08/1995 PWG : Created. * + *=========================================================================*/ +MoveBitType InfantryClass::Blocking_Object(TechnoClass const *techno, CELL cell) const +{ + Validate(); + bool inf = (techno->What_Am_I() == RTTI_INFANTRY); + bool unit = (techno->What_Am_I() == RTTI_UNIT) || inf; + + CellClass const * cellptr = &Map[cell]; + + if (House->Is_Ally(techno)) { + + /* + ** Logic to handle a trasport type object. + */ + if (NavCom == techno->As_Target() && Contact_With_Whom() == techno) { + return(MOVEF_OK); + } + + /* + ** If the object is of type infantry, then the square is blocked only + ** if the cell is completely full of infantry. + */ + if (inf && ((cellptr->Flag.Composite & 0x1f) != 0x1f)) { + return(MOVEF_OK); + } + + if (unit) { + + /* + ** If the unit in question has a destination than we should + ** be prepared to wait for the unit to get out of our way. + */ + if (((FootClass *)techno)->NavCom != TARGET_NONE) { + return(MOVEF_MOVING_BLOCK); + } + return(MOVEF_TEMP); + } + } else { + + /* + ** If its an enemy unit, things are dealt with a little differently + */ + if (unit) { + + + /* + ** If the object is cloaked, then consider it passable for findpath purposes, + ** but not so for all other cases. + */ + if (techno->Cloak == CLOAKED) { + if (IsFindPath) return(MOVEF_OK); + return(MOVEF_CLOAK); + } + + /* + ** If our vehicle is weapon equipped, then report that the cell occupier + ** needs only to be destroyed in order to make the cell passable. + */ + if (Class->Primary != WEAPON_NONE) { + return(MOVEF_DESTROYABLE); + } + } + } + return(MOVEF_NO); +} +#endif + + + +/*********************************************************************************************** + * InfantryClass::Can_Enter_Cell -- Determines if the infantry can enter the cell specified. * + * * + * This routine is used to examine the cell specified and determine if the infantry is * + * allowed to enter it. It is used by the path finding algorithm. * + * * + * INPUT: cell -- The cell to examine. * + * * + * OUTPUT: Returns the type of blockage in the cell. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + *=============================================================================================*/ +MoveType InfantryClass::Can_Enter_Cell(CELL cell, FacingType ) const +{ + Validate(); + /* + ** If we are moving into an illegal cell, then we can't do that. + */ + if ((unsigned)cell >= MAP_CELL_TOTAL) return(MOVE_NO); + + /* + ** If moving off the edge of the map, then consider that an illegal move. + */ + if (IsLocked && !IsALoaner && !ScenarioInit && !Map.In_Radar(cell)) { + return(MOVE_NO); + } + CellClass * cellptr = &Map[cell]; + + /* + ** Walls are considered impassable for infantry UNLESS the wall has a hole + ** in it. + */ + if (cellptr->Overlay != OVERLAY_NONE) { + OverlayTypeClass const & otype = OverlayTypeClass::As_Reference(cellptr->Overlay); + + if (otype.IsCrate && !House->IsHuman) { + return(MOVE_NO); + } + + if (otype.IsWall) { + if ((cellptr->OverlayData / 16) != otype.DamageLevels) { + return(MOVE_NO); + } + } + } + + /* + ** Loop through all of the objects in the square setting a bit + ** for how they affect movement. + */ + MoveType retval = MOVE_OK; + ObjectClass *obj = cellptr->Cell_Occupier(); + while (obj) { + + if (obj != this) { + + /* + ** Special case check so that a landed aircraft that is in radio contact, will not block + ** a capture attempt. It is presumed that this case happens when a helicopter is landed + ** at a helipad. + */ + if ((Mission != MISSION_CAPTURE && Mission != MISSION_SABOTAGE) || obj->What_Am_I() != RTTI_AIRCRAFT || !((AircraftClass *)obj)->In_Radio_Contact()) { + + /* + ** Special check to always allow entry into the building that this infantry + ** is trying to capture. + */ + if (obj->What_Am_I() == RTTI_BUILDING || obj->What_Am_I() == RTTI_AIRCRAFT) { + if ((Mission == MISSION_CAPTURE || Mission == MISSION_SABOTAGE) && (obj->As_Target() == NavCom || obj->As_Target() == TarCom)) { + return(MOVE_OK); + } + } + + /* + ** Special check to always allow entry into the building that this infantry + ** is trying to capture. + */ + if (Mission == MISSION_ENTER && obj->As_Target() == NavCom && IsTethered) { + return(MOVE_OK); + } + + /* + ** Allied objects block movement using different rules than for enemy + ** objects. + */ + if (House->Is_Ally(obj)) { + switch (obj->What_Am_I()) { + + /* + ** A unit blocks as either a moving blockage or a stationary temp blockage. + ** This depends on whether the unit is currently moving or not. + */ + case RTTI_UNIT: + if (((UnitClass *)obj)->IsDriving || Target_Legal(((UnitClass *)obj)->NavCom)) { + if (retval < MOVE_MOVING_BLOCK) retval = MOVE_MOVING_BLOCK; + } else { + if (retval < MOVE_TEMP) retval = MOVE_TEMP; + } + break; + + /* + ** Aircraft and buildings always block movement. If for some reason there is an + ** allied terrain object, that blocks movement as well. + */ + case RTTI_TERRAIN: + case RTTI_AIRCRAFT: + case RTTI_BUILDING: + return(MOVE_NO); + + default: + break; + } + + } else { + + /* + ** Cloaked enemy objects are not considered if this is a Find_Path() + ** call. + */ + if (!obj->Is_Techno() || ((TechnoClass *)obj)->Cloak != CLOAKED) { + + /* + ** Any non-allied blockage is considered impassible if the infantry + ** is not equipped with a weapon. + */ + if (Class->Primary == WEAPON_NONE) return(MOVE_NO); + + /* + ** Some kinds of terrain are considered destroyable if the infantry is equipped + ** with the weapon that can destroy it. Otherwise, the terrain is considered + ** impassable. + */ + switch (obj->What_Am_I()) { + case RTTI_TERRAIN: + if (((TerrainClass *)obj)->Class->IsFlammable && + BulletTypeClass::As_Reference(Weapons[Class->Primary].Fires).Warhead == WARHEAD_FIRE) { + + if (retval < MOVE_DESTROYABLE) retval = MOVE_DESTROYABLE; + } else { + return(MOVE_NO); + } + break; + + default: + if (retval < MOVE_DESTROYABLE) retval = MOVE_DESTROYABLE; + break; + } + } else { + if (retval < MOVE_CLOAK) retval = MOVE_CLOAK; + } + } + } + } + + /* + ** Move to next object in chain. + */ + obj = obj->Next; + } + + /* + ** If foot soldiers cannot travel on the cell -- consider it impassible. + */ + if (retval == MOVE_OK && !IsTethered && !Ground[cellptr->Land_Type()].Cost[SPEED_FOOT]) { + return(MOVE_NO); + } + + /* + ** if a unit has the cell reserved then we just can't go in there. + */ + if (retval == MOVE_OK && cellptr->Flag.Occupy.Vehicle) { + return(MOVE_NO); + } + + /* + ** if a block of infantry has the cell reserved then there are two + ** possibilities... + */ + if (cellptr->InfType != HOUSE_NONE) { + if (House->Is_Ally(cellptr->InfType)) { + if ((cellptr->Flag.Composite & 0x1F) == 0x1f) { + if (retval < MOVE_MOVING_BLOCK) retval = MOVE_MOVING_BLOCK; + } + } else { + if (Class->Primary != WEAPON_NONE) { + if (retval < MOVE_DESTROYABLE) retval = MOVE_DESTROYABLE; + } else { + return(MOVE_NO); + } + } + } + + /* + ** If it is still ok to move the infantry, then perform the last check + ** to see if the cell is already full of infantry. + */ + if (retval == MOVE_OK && (cellptr->Flag.Composite & 0x1F) == 0x1F) { + return(MOVE_NO); + } + + /* + ** Return with the most severe reason why this cell would be impassable. + */ + return(retval); +} + + +/*********************************************************************************************** + * InfantryClass::Overlap_List -- The list of cells that the infantry overlaps, but doesn't occ* + * * + * This is a rendering support routine that will return a pointer to a list of cell offsets * + * that specify the cells the infantry unit is currently overlapping (graphic wise) but * + * is not considered to occupy. This list is used to update the map display. * + * * + * INPUT: none * + * * + * OUTPUT: Returns a pointer to an offset list for cells that the unit overlaps but doesn't * + * occupy. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + *=============================================================================================*/ +short const * InfantryClass::Overlap_List(void) const +{ + Validate(); + //return(Coord_Spillage_List(Coord, 24 + ((IsSelected || Doing > DO_WALK)?12:0))); + return(Coord_Spillage_List(Coord, 24 + ((Doing > DO_WALK || IsSelected)?12:0))); +// return(Coord_Spillage_List(Coord, (IsSelected ? 24 : 14))+1); +} + + +/*********************************************************************************************** + * InfantryClass::Can_Fire -- Can the infantry fire its weapon? * + * * + * Determines if the infantry unit can fire on the target. If it can't fire, then the * + * reason why is returned. * + * * + * INPUT: target -- The target to determine if the infantry can fire upon. * + * * + * OUTPUT: Returns the fire error type that indicates if the infantry can fire and if it * + * can't, why not. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + * 06/27/1995 JLB : Flame thrower can fire while prone now. * + *=============================================================================================*/ +FireErrorType InfantryClass::Can_Fire(TARGET target, int which) const +{ + Validate(); + WeaponTypeClass const * weapon = (which == 0) ? &Weapons[Class->Primary] : &Weapons[Class->Secondary]; + +#ifdef BOXING + /* + ** If in hand-to-hand, and we're currently playing a got-hit animation, + ** then we can't punch back yet. + */ + if (IsBoxing) { + if ( (Doing>=DO_PUNCH_HIT1 && Doing<=DO_KICK_DEATH) || (Doing == DO_ON_GUARD) ) return FIRE_BUSY; + if (Arm) return(FIRE_BUSY); // don't let fire if still re-arming + } +#endif + + /* + ** Don't allow firing if the turret is not ready. + */ + if (IsFiring) return(FIRE_REARM); + +#ifdef OBSOLETE + if (weapon->Fires == BULLET_FLAME && IsProne) return(FIRE_ILLEGAL); +#endif + + /* + ** The target must still be legal. + */ + if (!Target_Legal(target)) { + return(FIRE_ILLEGAL); + } + + /* + ** If this unit cannot fire while moving, then bail. + */ + if ((IsDriving && Special.IsDefenderAdvantage) || (Doing != DO_NOTHING && !MasterDoControls[Doing].Interrupt)) { + return(FIRE_MOVING); + } + + /* + ** If we're moving, but not facing the right direction, then exit. + */ + if (!Special.IsDefenderAdvantage && IsDriving) { + int diff = PrimaryFacing.Difference(Direction(TarCom)); + if (ABS(diff) >= 32) { + return(FIRE_MOVING); + } + } + + return(FootClass::Can_Fire(target, which)); +} + + +/*********************************************************************************************** + * InfantryClass::Enter_Idle_Mode -- The infantry unit enters idle mode by this routine. * + * * + * Use this routine when the infantry unit as accomplished its task and needs to find * + * something to do. The default behavior is to enter some idle state such as guarding. * + * * + * INPUT: initial -- Is this called when the unit just leaves a factory or is initially * + * or is initially placed on the map? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryClass::Enter_Idle_Mode(bool ) +{ + Validate(); + MissionType order; + + if (Target_Legal(TarCom)) { + order = MISSION_ATTACK; + } else { + if (Target_Legal(NavCom)) { + order = MISSION_MOVE; + } else { + if (GameToPlay == GAME_NORMAL || House->IsHuman) { + order = MISSION_GUARD; + } else { + order = MISSION_HUNT; + } + } + } + Assign_Mission(order); +} + + +/*********************************************************************************************** + * InfantryClass::Random_Animate -- Randomly animate the infantry (maybe) * + * * + * This routine is the random animator initiator for infantry units. This routine should * + * be called regularly. On occasion, it will cause the infantry to go into an idle * + * animation. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + * 12/13/1994 JLB : Does random facing change. * + * 07/02/1995 JLB : Nikoomba special effects. * + *=============================================================================================*/ +void InfantryClass::Random_Animate(void) +{ + Validate(); + if (!IsDriving && !IsProne && (Doing == DO_STAND_GUARD || Doing == DO_STAND_READY) && !IsFiring) { + + /* + ** Scared infantry will always follow the golden rule of civilians; + ** "When in darkness or in doubt, run in circles, scream, and shout!" + */ + if (Class->IsFraidyCat && !House->IsHuman && Fear > FEAR_ANXIOUS) { + Scatter(NULL, true); + return; + } + + /* + ** If Nikoomba is not scared, then he will be doing his thing with random animations. + */ + if (*this == INFANTRY_C10) { + switch (Random_Pick(0, 3)) { + case 0: + Do_Action(DO_IDLE2); + break; + + default: + break; + } + } + + switch (Random_Picky((int)0, (int)55, (char*)NULL, (int)0)) { + case 10: + Do_Action(DO_SALUTE1); + break; + + case 11: + Do_Action(DO_SALUTE2); + break; + + case 12: + Do_Action(DO_GESTURE1); + break; + + case 13: + Do_Action(DO_GESTURE2); + break; + + case 4: + case 3: + case 0: + PrimaryFacing.Set(Facing_Dir(Random_Pick(FACING_N, FACING_NW))); + Mark(MARK_CHANGE); + break; + + case 1: + Do_Action(DO_IDLE1); + PrimaryFacing.Set(Facing_Dir(Random_Pick(FACING_N, FACING_NW))); + if (Sim_Random_Pick(1,20) == 1 && !IsSelected && *this == INFANTRY_MOEBIUS && IsDiscoveredByPlayer) { + static VocType _response[] = { +// VOC_EXCELLENT1, +// VOC_EXCELLENT2, + VOC_EXCELLENT3, +// VOC_EXCELLENT4, +// VOC_EXCELLENT5, + VOC_QUIP1, +// VOC_QUIP2 + }; + Sound_Effect(_response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)], Coord); + } + break; + + case 2: + Do_Action(DO_IDLE2); + PrimaryFacing.Set(Facing_Dir(Random_Pick(FACING_N, FACING_NW))); + if (!IsSelected && IsOwnedByPlayer && *this == INFANTRY_RAMBO && Sim_Random_Pick(0, 2) == 0) { + Sound_Effect(VOC_RAMBO_CMON, Coord); + } + break; + + /* + ** On occasion, civilian types will wander about. + */ + case 5: + case 6: + case 7: + case 8: + case 9: + if (!House->IsHuman && Class->IsFraidyCat) { + Scatter(NULL, true); + } + break; + } + } +} + + +/*********************************************************************************************** + * InfantryClass::Scatter -- Causes the infantry to scatter to nearby cell. * + * * + * This routine is used when the infantry should scatter to a nearby cell. Scattering * + * occurs as an occasional consequence of being fired upon. It is one of the features * + * that makes infantry so "charming". * + * * + * INPUT: threat -- The coordinate source of the threat that is causing the infantry to * + * scatter. If the threat isn't from a particular direction, then this * + * parameter will be NULL. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + * 12/12/1994 JLB : Flame thrower infantry always scatter. * + *=============================================================================================*/ +void InfantryClass::Scatter(COORDINATE threat, bool forced) +{ + Validate(); + + /* + ** A unit that is in the process of going somewhere will never scatter. + */ + if (IsDriving || Target_Legal(NavCom)) forced = false; + + /* + ** If the infantry is currently engaged in legitimate combat, then don't + ** scatter unless forced to. + */ + if (!Class->IsFraidyCat && Target_Legal(TarCom) && !forced) return; + + /* + ** Don't scatter if performing an action that can't be interrupted. + */ + if (Doing != DO_NOTHING && !MasterDoControls[Doing].Interrupt) return; + + /* + ** For human players, don't scatter the infantry, if the special + ** flag has not been enabled that allows infantry scatter. + */ + if (!Special.IsScatter && House->IsHuman && !forced && !Team) return; + + if (forced || Class->IsFraidyCat /*|| !(Random_Pick(1, 4) == 1)*/) { + FacingType toface; + FacingType newface; + CELL newcell; + + if (threat) { + toface = Dir_Facing(Direction8(threat, Coord)); + toface = toface + (Random_Pick(0, 4)-2); + } else { + COORDINATE coord = Coord & 0x00FF00FFL; + + if (coord != 0x00800080L) { + toface = Dir_Facing((DirType)Desired_Facing8(0x0080, 0x0080, Coord_X(coord), Coord_Y(coord))); + } else { + toface = Dir_Facing(PrimaryFacing.Current()); + } + toface = toface + (Random_Pick(0, 4)-2); + } + + for (FacingType face = FACING_N; face < FACING_COUNT; face++) { + newface = toface + face; + newcell = Adjacent_Cell(Coord_Cell(Coord), newface); + + if (Map.In_Radar(newcell) && Can_Enter_Cell(newcell) == MOVE_OK) { + Assign_Mission(MISSION_MOVE); + Assign_Destination(::As_Target(newcell)); + } + } + } +} + + +/*********************************************************************************************** + * InfantryClass::Do_Action -- Launches the infantry into an animation sequence. * + * * + * This starts the infantry into a choreographed animation sequence. These sequences can * + * be as simple as standing up or lying down, but can also be complex, such as dying or * + * performing some idle animation. * + * * + * INPUT: todo -- The choreographed sequence to start. * + * * + * force -- Force starting this animation even if the current animation is flagged * + * as uninterruptible. This is necessary for death animations. * + * * + * OUTPUT: bool; Was the animation started? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +bool InfantryClass::Do_Action(DoType todo, bool force) +{ + Validate(); + if (todo != Doing && (Doing == DO_NOTHING || force || MasterDoControls[Doing].Interrupt)) { + Mark(MARK_CHANGE); + //Mark(MARK_OVERLAP_UP); + Doing = todo; + //Mark(MARK_OVERLAP_DOWN); + if (todo == DO_IDLE1 || todo == DO_IDLE2) { + Set_Rate(Options.Normalize_Delay(MasterDoControls[Doing].Rate)); + } else { + Set_Rate(MasterDoControls[Doing].Rate); + } + Set_Stage(0); + + /* + ** Kludge to make sure that if infantry is in the dying animation, it isn't still + ** moving as well. + */ + if (!Strength) { + Stop_Driver(); + } + + /* + ** Since the animation sequence might be interrupted. Set any flags + ** necessary so that if interrupted, the affect on the infantry is + ** still accomplished. + */ + switch (todo) { + case DO_LIE_DOWN: + IsProne = true; + break; + + case DO_GET_UP: + IsProne = false; + break; + + case DO_READY_WEAPON: + IsBoxing = false; + break; + + case DO_ON_GUARD: + IsBoxing = true; + PrimaryFacing.Set(Direction8(Center_Coord(), As_Coord(TarCom))); + Path[0] = FACING_NONE; + break; + + default: + break; + } + + return(true); + } + + return(false); +} + + +/*********************************************************************************************** + * InfantryClass::Stop_Driver -- Stops the infantry from moving any further. * + * * + * This is used to stop the infantry from animating in movement. This function will stop * + * the infantry moving and revert it to either a prone or standing. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the driving stopped? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +bool InfantryClass::Stop_Driver(void) +{ + Validate(); + if (Head_To_Coord()) { + + /* + ** Remove the "reservation" bit in the destination location. + */ + Clear_Occupy_Bit(Head_To_Coord()); + } + + /* + ** Set the occupation bit at the current location. + */ + Set_Occupy_Bit(Coord); + + if (IsProne) { + Do_Action(DO_PRONE); + } else { + Do_Action(DO_STAND_READY); + } + + return(FootClass::Stop_Driver()); +} + + +/*********************************************************************************************** + * InfantryClass::Start_Driver -- Handles giving immediate destination and move orders. * + * * + * Use this routine to being the infantry moving toward the destination specified. The * + * destination is first checked to see if there is a free spot available. Then the infantry * + * reserves that spot and begins movement toward it. * + * * + * INPUT: headto -- The coordinate location desired for the infantry to head to. * + * * + * OUTPUT: bool; Was the infantry successfully started on its journey? Failure may be because * + * the specified destination could not contain the infantry unit. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/21/1994 JLB : Created. * + * 05/14/1995 JLB : Tries to move to closest spot possible. * + * 05/15/1995 JLB : Uses closest spot if moving onto transport. * + *=============================================================================================*/ +bool InfantryClass::Start_Driver(COORDINATE & headto) +{ + Validate(); + COORDINATE old = headto; + + /* + ** Convert the head to coordinate to a legal sub-position location. + */ + headto = Map[Coord_Cell(headto)].Closest_Free_Spot(Coord_Move(headto, Direction(headto)+DIR_S, 0x007C)); + if (!headto && Can_Enter_Cell(Coord_Cell(old)) == MOVE_OK) { + headto = Map[Coord_Cell(old)].Closest_Free_Spot(Coord_Move(old, Direction(headto)+DIR_S, 0x0080), true); + } + + /* + ** If the infantry started moving, then fixup the occupation bits. + */ + if (headto && FootClass::Start_Driver(headto)) { + /* + ** Remove the occupation bit from the infantry's current location. + */ + Clear_Occupy_Bit(Coord); + + /* + ** Set the occupation bit for the new headto location. + */ + Set_Occupy_Bit(headto); + return(true); + } + + return(false); +} + + +#ifdef NEVER +/*********************************************************************************************** + * InfantryClass::Set_Primary_Facing -- Change infantry primary facing -- always and instantly * + * * + * This routine ensures that when the infantry primary facing is changes, it is changed * + * instantly and always. There is no provision for infantry facing changing slowly over * + * time as the other vehicles usually do. * + * * + * INPUT: facing -- The desired facing for the infantry unit. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/11/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryClass::Set_Primary_Facing(DirType facing, bool ) +{ + Validate(); + FootClass::Set_Primary_Facing(facing, true); +} +#endif + + +/*********************************************************************************************** + * InfantryClass::Limbo -- Performs cleanup operations needed when limboing. * + * * + * This routine will clean up the infantry occupation bits (as necessary) as well as stop * + * the infantry movement process when it gets limboed. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the infantry unit limboed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/22/1994 JLB : Created. * + *=============================================================================================*/ +bool InfantryClass::Limbo(void) +{ + Validate(); + if (!IsInLimbo) { + Stop_Driver(); + + Clear_Occupy_Bit(Coord); + } + return(FootClass::Limbo()); +} + + +/*********************************************************************************************** + * InfantryClass::Fire_At -- Fires projectile from infantry unit. * + * * + * Use this routine when the infantry unit wishes to fire a projectile. This routine * + * will launch the projectile and perform any other necessary infantry specific operations. * + * * + * INPUT: target -- The target of the attack. * + * * + * which -- Which weapon to use for firing. 0=primary, 1=secondary. * + * * + * OUTPUT: Returns with pointer to the projectile launched. If none could be launched, then * + * NULL is returned. If there is already the maximum bullet objects in play, then * + * this could happen. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +BulletClass * InfantryClass::Fire_At(TARGET target, int which) +{ + Validate(); + BulletClass * bullet = NULL; + WeaponTypeClass const * weapon = (which == 0) ? &Weapons[Class->Primary] : &Weapons[Class->Secondary]; + + IsFiring = false; + +#ifdef BOXING + if (IsBoxing) { + RadioMessageType hitaction = (Doing == DO_KICK) ? RADIO_KICK : RADIO_PUNCH; + + /* + ** When fighting, verify that the target is legal to proceed. If there is some + ** error, then abort fightning mode. Otherwise, tell the target that it has + ** just been hit. + */ + if (In_Radio_Contact() && Target_Legal(target) && Transmit_Message(hitaction) == RADIO_ROGER) { + Arm = Rearm_Delay(true); + } else { + + /* + ** Fighting done for some reason, so pick up gun + */ + IsBoxing = false; + Do_Action(DO_READY_WEAPON,true); + } + } else { +#endif + + bullet = FootClass::Fire_At(target, which); + if (bullet) { + + /* + ** For fraidycat infantry that run out of ammo, always go into + ** a maximum fear state at that time. + */ + if (Class->IsFraidyCat && !Ammo) { + Fear = FEAR_MAXIMUM; + if (Mission == MISSION_ATTACK || Mission == MISSION_HUNT) { + Assign_Mission(MISSION_GUARD); + } + } + + /* + ** Create the projectile. Then process any special operations that + ** need to be performed according to the style of projectile + ** created. + */ + Sound_Effect(weapon->Sound, Coord); + } + +#ifdef BOXING + } +#endif + return(bullet); +} + + +/*********************************************************************************************** + * InfantryClass::Unlimbo -- Unlimbo infantry unit in legal sub-location. * + * * + * This will attempt to unlimbo the infantry unit at the designated coordinate, but will * + * ensure that the coordinate is a legal subposition. * + * * + * INPUT: coord -- The coordinate to unimbo the infantry at. * + * * + * facing -- The desired initial facing for the infantry unit. * + * * + * strength -- The desired initial strength for the infantry unit. * + * * + * mission -- The desired initial mission for the infantry unit. * + * * + * OUTPUT: bool; Was the infantry unlimboed successfully? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +bool InfantryClass::Unlimbo(COORDINATE coord, DirType facing) +{ + Validate(); + /* + ** Make sure that the infantry start in a legal position on the map. + */ + coord = Map[Coord_Cell(coord)].Closest_Free_Spot(coord, ScenarioInit); + if (coord == NULL) { + return(false); + } + + if (FootClass::Unlimbo(coord, facing)) { + + /* + ** Ensure that the owning house knows about the + ** new object. + */ + House->IScan |= (1L << Class->Type); + House->ActiveIScan |= (1L << Class->Type); + + /* + ** If there is no sight range, then this object isn't discovered by the player unless + ** it actually appears in a cell mapped by the player. + */ + if (Class->SightRange == 0) { + IsDiscoveredByPlayer = false; + } + + Set_Occupy_Bit(coord); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * InfantryClass::Greatest_Threat -- Determines greatest threat (target) for infantry unit. * + * * + * This routine intercepts the Greatest_Threat request and adds the appropriate target * + * types to search for. For regular infantry, this consists of all the ground types. For * + * rocket launching infantry, this also includes aircraft. * + * * + * INPUT: threat -- The basic threat control value. * + * * + * OUTPUT: Returns with the best target for this infantry unit to attack. If no suitable * + * target could be found, then TARGET_NONE is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/01/1995 JLB : Created. * + *=============================================================================================*/ +TARGET InfantryClass::Greatest_Threat(ThreatType threat) const +{ + Validate(); + /* + ** Engineers consider only buildings that can be captures as being a threat. All others + ** are ignored. + */ + if (Class->IsCapture && Class->Primary == WEAPON_NONE) { + threat = threat | THREAT_CAPTURE; + } + + switch (Class->Primary) { + case WEAPON_NONE: + if (*this != INFANTRY_E7) { + return(TARGET_NONE); + } + // fall into next case. + + /* + ** Dragon missile equiped soldiers are also assumed to carry a Stinger missile. As such, + ** they will consider aircraft a legal target. + */ + default: + if (BulletTypeClass::As_Reference(Weapons[Class->Primary].Fires).IsAntiAircraft) { + threat = threat | THREAT_AIR; + } + break; + + /* + ** The sniper rifle equipped soldier doesn't go hunting for targets + ** unless specifically in hunt mode. + */ + case WEAPON_RIFLE: + if (House->IsHuman && (Mission == MISSION_GUARD || Mission == MISSION_GUARD_AREA)) { + return(TARGET_NONE); + } + return(TechnoClass::Greatest_Threat(threat | THREAT_INFANTRY|THREAT_BUILDINGS)); + } + return(FootClass::Greatest_Threat(threat)); +} + + +/*********************************************************************************************** + * InfantryClass::Response_Select -- Plays infantry audio response due to being selected. * + * * + * This routine handles playing an audio response as a result of the player selecting the * + * infantry unit. This occurs prior to giving it an order and may not be followed by any * + * order at all. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/01/1995 JLB : Created. * + * 05/05/1995 JLB : Rambo response types added. * + *=============================================================================================*/ +void InfantryClass::Response_Select(void) +{ + Validate(); + VocType response; + if (*this == INFANTRY_RAMBO) { + static VocType _response[] = { + VOC_RAMBO_YEA, + VOC_RAMBO_YES, + VOC_RAMBO_YO + }; + response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; + } else { + if (Class->IsCivilian) { + if (*this == INFANTRY_MOEBIUS) { + static VocType _response[] = { + VOC_YES, + VOC_COMMANDER, + VOC_HELLO, + VOC_HMMM + }; + response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; + } else { + if (Class->IsFemale) { + response = VOC_GIRL_YEAH; + } else { + response = VOC_GUY_YEAH; + } + } + } else { + static VocType _response[] = { + VOC_ACKNOWL, + VOC_REPORT, + VOC_REPORT, + VOC_YESSIR, + VOC_YESSIR, + VOC_READY, + VOC_AWAIT + }; + response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; + } + } + if (AllowVoice) { + Sound_Effect(response, 0, Infantry.ID(this)+1); + } +} + + +/*********************************************************************************************** + * InfantryClass::Response_Move -- Plays infantry response to movement order. * + * * + * When the infantry is given the order to move, this routine handles the audio repsonse * + * generated by the infantry unit. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/01/1995 JLB : Created. * + * 05/05/1995 JLB : Rambo response types added. * + *=============================================================================================*/ +void InfantryClass::Response_Move(void) +{ + Validate(); + VocType response; + if (*this == INFANTRY_RAMBO) { + static VocType _response[] = { + VOC_RAMBO_UGOTIT, + VOC_RAMBO_ONIT, + VOC_RAMBO_NOPROB + }; + response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; + } else { + if (Class->IsCivilian) { + if (*this == INFANTRY_MOEBIUS) { + static VocType _response[] = { + VOC_OF_COURSE, + VOC_YESYES + }; + response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; + } else { + if (Class->IsFemale) { + response = VOC_GIRL_OKAY; + } else { + response = VOC_GUY_OKAY; + } + } + } else { + static VocType _response[] = { + VOC_MOVEOUT, + VOC_MOVEOUT, + VOC_MOVEOUT, + VOC_ROGER, + VOC_RIGHT_AWAY, + VOC_UGOTIT, + VOC_AFFIRM, + VOC_AFFIRM + }; + response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; + } + } + if (AllowVoice) { + Sound_Effect(response, 0, Infantry.ID(this)+1); + } +} + + +/*********************************************************************************************** + * InfantryClass::Response_Attack -- Plays infantry audio response to attack order. * + * * + * When the player gives an infantry unit the order to attack, this routine handles * + * the audio response by that unit. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/01/1995 JLB : Created. * + * 05/05/1995 JLB : Rambo response types added. * + *=============================================================================================*/ +void InfantryClass::Response_Attack(void) +{ + Validate(); + VocType response; + if (*this == INFANTRY_RAMBO) { + static VocType _response[] = { + VOC_RAMBO_NOPROB, + VOC_RAMBO_UGOTIT, + VOC_RAMBO_NOPROB, + VOC_RAMBO_ONIT + }; + response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; + } else { + if (Class->IsCivilian) { + if (Class->IsFemale) { + response = VOC_GIRL_OKAY; + } else { + response = VOC_GUY_OKAY; + } + } else { + static VocType _response[] = { + VOC_RIGHT_AWAY, + VOC_AFFIRM, + VOC_AFFIRM, + VOC_UGOTIT, + VOC_NO_PROB, + VOC_YESSIR, + VOC_YESSIR, + VOC_YESSIR + }; + response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; + } + } + + if (AllowVoice) { + Sound_Effect(response, 0, Infantry.ID(this)+1); + } +} + + +/*********************************************************************************************** + * InfantryClass::Fire_Coord -- Calculates the origin point for projectiles fired. * + * * + * This routine will return with the point of origin for any firing projectiles. Typically, * + * this only includes the rocket launcher and the grenade thrower. The other infantry * + * have either invisible projectiles or special animations. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the coordinate where the projectile will appear as it is fired. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/04/1995 JLB : Created. * + *=============================================================================================*/ +COORDINATE InfantryClass::Fire_Coord(int) const +{ + Validate(); + if (Class->Type == INFANTRY_E4) { + return(Coord); // special case for flame thrower guy + } else { + return(Coord_Add(Coord, XYP_COORD(0, -5))); + } +} + + +/*************************************************************************** + * InfantryClass::Receive_Message -- Process radio messages * + * * + * If the infantry's boxing, it needs to return to a normal state when * + * his opponent moves away. Otherwise fall thru to FootClass processing* + * * + * INPUT: from - Pointer to the originator of this message. * + * * + * message - the message to process * + * * + * param -- Reference to an optional parameter that can be * + * used to transfer more information than is * + * possible with the simple radio message values. * + * * + * OUTPUT: an appropriate response message * + * * + * WARNINGS: * + * * + * HISTORY: * + * 01/19/1995 BWG : Created. * + * 05/14/1995 JLB : Handles loading maneuver messages. * + *=========================================================================*/ +RadioMessageType InfantryClass::Receive_Message(RadioClass * from, RadioMessageType message, long & param) +{ + Validate(); + int damage; + + switch (message) { + + case RADIO_OVER_OUT: +#ifdef BOXING + if (IsBoxing) Do_Action(DO_READY_WEAPON); +#endif + break; + + /* + ** Request a fisticuff fight. If this infantry is already involved in a fight, + ** then refuse the offer. + */ + case RADIO_PREPARE_TO_BOX: +#ifdef BOXING + if (IsBoxing) break; +#endif + if (Contact_With_Whom() == from) { + Do_Action(DO_ON_GUARD, true); + Assign_Target(Contact_With_Whom()->As_Target()); + return(RADIO_ROGER); + } + return(RADIO_NEGATIVE); + + /* + ** Just received a kick! Take some damage. + */ + case RADIO_KICK: + damage = Infantry_Kick_Damage[Random_Pick(0, (int)(sizeof(Infantry_Kick_Damage) / sizeof(Infantry_Kick_Damage[0])))]; + if (Take_Damage(damage, 0, WARHEAD_FOOT, this) == RESULT_DESTROYED) return(RADIO_STATIC); + return(RADIO_ROGER); + + /* + ** Just recieved a punch! Take some damage. + */ + case RADIO_PUNCH: + damage = Infantry_Punch_Damage[Random_Pick(0, (int)(sizeof(Infantry_Punch_Damage) / sizeof(Infantry_Punch_Damage[0])))]; + if (Take_Damage(damage, 0, WARHEAD_FIST, this) == RESULT_DESTROYED) return(RADIO_STATIC); + return(RADIO_ROGER); + + } + return(FootClass::Receive_Message(from, message, param)); +} + + +/*************************************************************************** + * InfantryClass::Rearm_Delay -- Return Arming delay for infantry if boxing* + * * + * If the infantry's in a boxing mode, return an appropriate re-arming * + * delay. Otherwise return the default return val. * + * * + * INPUT: second -- bool; see TechnoClass... * + * * + * OUTPUT: Returns with the # of game frames to delay before shooting * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 BWG : Created. * + *=========================================================================*/ +int InfantryClass::Rearm_Delay(bool second) const +{ + Validate(); +#ifdef BOXING + if (IsBoxing) { + return(Random_Pick(5, 50)); + } +#endif + return(FootClass::Rearm_Delay(second)); +} + + +/*********************************************************************************************** + * InfantryClass::Assign_Mission -- Assign mission to infantry object. * + * * + * When a new mission is assigned, make sure he gets out of boxing mode. * + * * + * INPUT: order -- The new mission to assign to the unit. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/29/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryClass::Assign_Mission(MissionType order) +{ + Validate(); + if (order == MISSION_SABOTAGE) { + Sound_Effect(VOC_RAMBO_PRESENT, Coord); + } + + IsBoxing = false; + FootClass::Assign_Mission(order); +} + + +/*********************************************************************************************** + * InfantryClass::What_Action -- Infantry units might be able to capture -- check. * + * * + * This routine checks to see if the infantry unit can capture the specified object rather * + * than merely attacking it. If this is the case, then ACTION_CAPTURE will be returned. * + * * + * INPUT: object -- The object that the mouse is currently over. * + * * + * OUTPUT: Returns the action that will be performed if the mouse were clicked over the * + * object specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/01/1995 JLB : Created. * + *=============================================================================================*/ +ActionType InfantryClass::What_Action(ObjectClass * object) const +{ + Validate(); + ActionType action = FootClass::What_Action(object); + + /* + ** First see if it's a commando, and if he's attacking a building, have him return ACTION_SABOTAGE instead + */ + if (*this == INFANTRY_RAMBO && action == ACTION_ATTACK && object->What_Am_I() == RTTI_BUILDING) { + return(ACTION_SABOTAGE); + } + + /* + ** There is no self-select action available for infantry types. + */ + if (action == ACTION_SELF) { + action = ACTION_NONE; + } + + /* + ** Check to see if it can enter a transporter. + */ + if ( + House->Is_Ally(object) && + IsOwnedByPlayer && + object->Is_Techno() && + IsOwnedByPlayer && + ((InfantryClass *)this)->Transmit_Message(RADIO_CAN_LOAD, (TechnoClass*)object) == RADIO_ROGER) { +// if (object->Owner() == Owner() && object->What_Am_I() == RTTI_UNIT && ((UnitClass *)object)->Class->IsTransporter && ((UnitClass *)object)->How_Many() < 5) { + action = ACTION_ENTER; + } + + if (Class->IsCapture && action == ACTION_ATTACK) { + if (object->Owner() != Owner() && + ((object->What_Am_I() == RTTI_AIRCRAFT && ((AircraftClass *)object)->Pip_Count() == 0 && *((AircraftClass *)object) == AIRCRAFT_TRANSPORT) || + (object->What_Am_I() == RTTI_BUILDING && + ((BuildingClass *)object)->Class->IsCaptureable && (GameToPlay != GAME_NORMAL || *((BuildingClass *)object) != STRUCT_EYE || Scenario < 13) )) + ) { + + action = ACTION_CAPTURE; + } else { + if (Class->Primary == WEAPON_NONE) { + action = ACTION_NONE; + } + } + } + return(action); +} + + +/*********************************************************************************************** + * InfantryClass::Read_INI -- Reads units from scenario INI file. * + * * + * This routine is used to read all the starting units from the * + * scenario control INI file. The units are created and placed on the * + * map by this routine. * + * * + * INI entry format: * + * Housename, Typename, Strength, Cellnum, CellSublocation, Missionname, * + * Facingnum, Triggername * + * * + * INPUT: buffer -- Pointer to the loaded scenario INI file. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/24/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryClass::Read_INI(char *buffer) +{ + InfantryClass *infantry; // Working infantry pointer. + char *tbuffer; // Accumulation buffer of infantry IDs. + HousesType inhouse; // Infantry house. + InfantryType classid; // Infantry class. + int len; // Length of data in buffer. + char buf[128]; + + len = strlen(buffer) + 2; + tbuffer = buffer + len; + + /*------------------------------------------------------------------------ + Read the entire INFANTRY INI section into HIDBUF + ------------------------------------------------------------------------*/ + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-len, buffer); + + while (*tbuffer != '\0') { + + /* + ** Get an infantry entry + */ + WWGetPrivateProfileString(INI_Name(), tbuffer, NULL, buf, sizeof(buf)-1, buffer); + + /* + ** 1st token: house name. + */ + inhouse = HouseTypeClass::From_Name(strtok(buf, ",\n\r")); + if (inhouse != HOUSE_NONE) { + + /* + ** 2nd token: infantry type name. + */ + classid = InfantryTypeClass::From_Name(strtok(NULL, ",\n\r")); + + if (classid != INFANTRY_NONE) { + + infantry = new InfantryClass(classid, inhouse); + if (infantry) { + + /* + ** 3rd token: strength. + */ + int strength = atoi(strtok(NULL, ",\n\r")); + + /* + ** 4th token: cell #. + */ + COORDINATE coord = Cell_Coord((CELL)atoi(strtok(NULL, ",\n\r"))); + + /* + ** 5th token: cell sub-location. + */ + coord = Coord_Add(coord & 0xFF00FF00L, StoppingCoordAbs[atoi(strtok(NULL, ","))]); + + /* + ** Fetch the mission and facing. + */ + MissionType mission = MissionClass::Mission_From_Name(strtok(NULL, ",\n\r")); + DirType dir = (DirType)atoi(strtok(NULL,",\n\r")); + infantry->Trigger = TriggerClass::As_Pointer(strtok(NULL,",\n\r")); + if (infantry->Trigger) { + infantry->Trigger->AttachCount++; + } + + if (infantry->Unlimbo(coord, dir)) { + infantry->Strength = Fixed_To_Cardinal(infantry->Class_Of().MaxStrength, strength); + if (GameToPlay == GAME_NORMAL || infantry->House->IsHuman) { + infantry->Assign_Mission(mission); + infantry->Commence(); + } else { + infantry->Enter_Idle_Mode(); + } + } else { + + /* + ** If the infantry could not be unlimboed, then this is a big error. + ** Delete the infantry. + */ + delete infantry; + } + } + } + } + tbuffer += strlen(tbuffer)+1; + } +} + + +/*********************************************************************************************** + * InfantryClass::Write_INI -- Writes all the infantry out to an INI file. * + * * + * This routine writes all of the infantry in the game out to an INI file. This is used * + * in the scenario editor when the game needs to be saved. * + * * + * INI entry format: * + * Housename, Typename, Strength, Cellnum, CellSublocation, Missionname, * + * Facingnum, Triggername * + * * + * INPUT: buffer -- A pointer to the loaded INI file staging area. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +void InfantryClass::Write_INI(char *buffer) +{ + int index; + char uname[10]; + char buf[128]; + char *tbuffer; // Accumulation buffer of infantry IDs. + + /* + ** First, clear out all existing infantry data from the ini file. + */ + tbuffer = buffer + strlen(buffer) + 2; + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-strlen(buffer), buffer); + while (*tbuffer != '\0') { + WWWritePrivateProfileString(INI_Name(), tbuffer, NULL, buffer); + tbuffer += strlen(tbuffer)+1; + } + + /* + ** Write the infantry data out. + */ + for (index = 0; index < Infantry.Count(); index++) { + InfantryClass * infantry; + + infantry = Infantry.Ptr(index); + if (!infantry->IsInLimbo) { + + sprintf(uname, "%03d", index); + sprintf(buf, "%s,%s,%d,%u,%d,%s,%d,%s", + infantry->House->Class->IniName, + infantry->Class->IniName, + infantry->Health_Ratio(), + Coord_Cell(infantry->Coord), + CellClass::Spot_Index(infantry->Coord), + MissionClass::Mission_Name((infantry->Mission == MISSION_NONE) ? + infantry->MissionQueue : infantry->Mission), + infantry->PrimaryFacing.Current(), + infantry->Trigger ? infantry->Trigger->Get_Name() : "None" + ); + WWWritePrivateProfileString(INI_Name(), uname, buf, buffer); + } + } +} + + +/*********************************************************************************************** + * InfantryClass::Active_Click_With -- Handles action when clicking with infantry soldier. * + * * + * This routine is called when the player clicks over an object while this infantry soldier * + * is selected. Capture attempts are prohibited if the infantry cannot capture. The * + * command might respond if told to sabotage something. * + * * + * INPUT: action -- The action that is nominally to be performed. * + * * + * object -- The object over which the mouse was clicked. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +void InfantryClass::Active_Click_With(ActionType action, ObjectClass * object) +{ + Validate(); + if (What_Action(object) != action) { + switch (action) { + case ACTION_SABOTAGE: + case ACTION_CAPTURE: + action = ACTION_ATTACK; + break; + + case ACTION_ENTER: + action = ACTION_MOVE; + break; + + default: + action = ACTION_NONE; + break; + } + } + + FootClass::Active_Click_With(action, object); +} + + +/*********************************************************************************************** + * InfantryClass::Made_A_Kill -- Marks a kill caused by this infantry soldier. * + * * + * When the infantry soldier is responsible for a kill, this routine is called. It checks * + * to see if the soldier should make some comment or perform some action. The commando * + * infantry is most likely to respond. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the number of kills this infantry soldier has made. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +int InfantryClass::Made_A_Kill(void) +{ + Validate(); + if (*this == INFANTRY_RAMBO || Random_Pick(0, 5) < Kills) { + IsStoked = true; + Comment = TICKS_PER_SECOND*2; + } + return(FootClass::Made_A_Kill()); +} + + +/*********************************************************************************************** + * InfantryClass::Set_Occupy_Bit -- Sets the occupy bit cell and bit pos * + * * + * INPUT: CELL - the cell we are setting the bit in * + * * + * int - the spot index we are setting the bit for * + * * + * OUTPUT: none * + * * + * HISTORY: * + * 06/08/1995 PWG : Created. * + *=============================================================================================*/ +void InfantryClass::Set_Occupy_Bit(CELL cell, int spot_index) +{ + Validate(); + /* + ** Set the occupy postion for the spot that we passed in + */ + Map[cell].Flag.Composite |= (1 << spot_index); + + /* + ** Record the type of infantry that now owns the cell + */ + Map[cell].InfType = Owner(); +} + + +/*************************************************************************** + * InfantryClass::Clear_Occupy_Bit -- Clears occupy bit and given cell * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 06/08/1995 PWG : Created. * + *=========================================================================*/ +void InfantryClass::Clear_Occupy_Bit(CELL cell, int spot_index) +{ + Validate(); + /* + ** Clear the occupy bit for the infantry in that cell + */ + Map[cell].Flag.Composite &= ~(1 << spot_index); + + /* + ** If he was the last infantry recorded in the cell then + ** remove the infantry ownership flag. + */ + if (!(Map[cell].Flag.Composite & 0x1F)) { + Map[cell].InfType = HOUSE_NONE; + } +} + + +/*********************************************************************************************** + * InfantryClass::Full_Name -- Fetches the full name of the infantry unit. * + * * + * This routine will return with the full name (as a text number) for this infantry * + * unit. Typically, this is the normal name, but in cases of civilian type survivors from * + * a building explosion, it might be a technician instead. In such a case, the special * + * technician name number is returned instead. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the full name to use for this infantry unit. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/30/1995 JLB : Created. * + *=============================================================================================*/ +int InfantryClass::Full_Name(void) const +{ + Validate(); + if (IsTechnician) { + return(TXT_TECHNICIAN); + } + return(Class->Full_Name()); +} + + +/*********************************************************************************************** + * InfantryClass::Mission_Attack -- Intercept attack mission for special handling. * + * * + * This routine intercepts the normal attack mission and if an engineer is detected and the * + * target is a building, then the engineer will be automatically assigned the capture * + * mission. In other cases, the normal attack logic will proceed. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of game frames to delay before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/07/1995 JLB : Created. * + *=============================================================================================*/ +int InfantryClass::Mission_Attack(void) +{ + Validate(); + if (*this == INFANTRY_E7 && As_Building(TarCom)) { + Assign_Destination(TarCom); + Assign_Mission(MISSION_CAPTURE); + return(1); + } + return(FootClass::Mission_Attack()); +} + + +RTTIType InfantryClass::What_Am_I(void) const +{ + Validate(); + return(RTTI_INFANTRY); +} + +ActionType InfantryClass::What_Action(CELL cell) const +{ + Validate(); + return FootClass::What_Action(cell); +} + +ObjectTypeClass const & InfantryClass::Class_Of(void) const +{ + Validate(); + return(*Class); +} + +bool InfantryClass::Is_Infantry(void) const +{ + Validate(); + return(true); +} diff --git a/INFANTRY.H b/INFANTRY.H new file mode 100644 index 0000000..072069b --- /dev/null +++ b/INFANTRY.H @@ -0,0 +1,244 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\infantry.h_v 2.18 16 Oct 1995 16:48:08 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : INFANTRY.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : August 15, 1994 * + * * + * Last Update : August 15, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef INFANTRY_H +#define INFANTRY_H + +/********************************************************************** +** Infantry can be afraid. These defines are for the various infantry +** fear levels. When infantry be come scared enough they take cover and +** even run away in panic. +*/ +#define FEAR_ANXIOUS 10 // Something makes them scared. +#define FEAR_SCARED 100 // Scared enough to take cover. +#define FEAR_PANIC 200 // Run away! Run away! +#define FEAR_MAXIMUM 255 // Scared to death. + + +class InfantryClass : public FootClass +{ + public: + InfantryTypeClass const * const Class; + operator InfantryType(void) const {return Class->Type;}; + + /* + ** If the infantry is undergoing some choreographed animation sequence, then + ** this holds the particular sequence number. The frame of animation is kept + ** track of by the regular frame tracking system. When performing an animation + ** sequence, the infantry cannot perform anything else (even move). + */ + DoType Doing; + + /* + ** Certain infantry will either perform some comment or say something after an + ** amount of time has expired subsiquent to an significant event. This is the + ** timer the counts down. + */ + TCountDownTimerClass Comment; + + /* + ** If this civilian is actually a technician, then this flag will be true. + ** It should only be set for the civilian type infantry. Typically, the + ** technician appears after a building is destroyed. + */ + unsigned IsTechnician:1; + + /* + ** If the infantry just performed some feat, then it may respond with an action. + ** This flag will be true if an action is to be performed when the Comment timer + ** has expired. + */ + unsigned IsStoked:1; + + /* + ** This flag indicates if the infantry unit is prone. Prone infantry become that way + ** when they are fired upon. Infantry in the prone position are less vulnerable to + ** combat. + */ + unsigned IsProne:1; + + /* + ** This flag is set when the infantryman is engaged in hand-to-hand + ** combat. By setting this flag, it'll play the put-down-the-gun + ** sequence only once, and it'll know to pick up the gun when the + ** fight is over. + */ + unsigned IsBoxing:1; + + /* + ** The fear rating of this infantry unit. The more afraid the infantry, the more + ** likely it is to panic and seek cover. + */ + unsigned char Fear; + + /*--------------------------------------------------------------------- + ** Constructors, Destructors, and overloaded operators. + */ + static void * operator new(size_t size); + static void operator delete(void *ptr); + InfantryClass(void); + InfantryClass(InfantryType classid, HousesType house); + virtual ~InfantryClass(void); + virtual RTTIType What_Am_I(void) const; + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + static void Init(void); + + virtual void Assign_Destination(TARGET); + + /* + ** Query functions. + */ + virtual bool Is_Infantry(void) const; + virtual ObjectTypeClass const & Class_Of(void) const; + virtual int Full_Name(void) const; + + /* + ** Coordinate inquiry functions. These are used for both display and + ** combat purposes. + */ + virtual COORDINATE Fire_Coord(int which) const; + + /* + ** Object entry and exit from the game system. + */ + virtual bool Unlimbo(COORDINATE coord, DirType facing); + virtual bool Limbo(void); + virtual void Detach(TARGET target, bool all); + + /* + ** Display and rendering support functionality. Supports imagery and how + ** object interacts with the map and thus indirectly controls rendering. + */ + virtual short const * Overlap_List(void) const; + virtual void Draw_It(int x, int y, WindowNumberType window); + virtual void Look(bool incremental=false); + + /* + ** User I/O. + */ + virtual void Response_Select(void); + virtual void Response_Move(void); + virtual void Response_Attack(void); + virtual void Active_Click_With(ActionType action, ObjectClass * object); + + /* + ** Combat related. + */ + virtual int Made_A_Kill(void); + virtual ActionType What_Action(ObjectClass * object) const; + virtual ActionType What_Action(CELL cell) const; + virtual void Assign_Mission(MissionType order); + virtual BulletClass * Fire_At(TARGET target, int which); + virtual ResultType Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source=0); + virtual TARGET As_Target(void) const; + virtual FireErrorType Can_Fire(TARGET target, int which) const; + virtual void Assign_Target(TARGET); + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, long & param); + virtual int Rearm_Delay(bool second) const; + void Set_Occupy_Bit(COORDINATE coord) {Set_Occupy_Bit(Coord_Cell(coord), CellClass::Spot_Index(coord));}; + void Set_Occupy_Bit(CELL cell, int spot_index); + void Clear_Occupy_Bit(COORDINATE coord) {Clear_Occupy_Bit(Coord_Cell(coord), CellClass::Spot_Index(coord));}; + void Clear_Occupy_Bit(CELL cell, int spot_index); + + /* + ** Driver control support functions. These are used to control cell + ** occupation flags and driver instructions. + */ + virtual bool Stop_Driver(void); + virtual bool Start_Driver(COORDINATE & coord); + + /* + ** AI. + */ + virtual void AI(void); + virtual TARGET Greatest_Threat(ThreatType threat) const; + virtual int Mission_Attack(void); + + /* + ** Scenario and debug support. + */ + #ifdef CHEAT_KEYS + virtual void Debug_Dump(MonoClass *mono) const; + #endif + + /* + ** File I/O. + */ + static void Read_INI(char *buffer); + static void Write_INI(char *buffer); + static char *INI_Name(void) {return "INFANTRY";}; + bool Load(FileClass & file); + bool Save(FileClass & file); + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /* + ** Movement and animation. + */ + virtual bool Do_Action(DoType todo, bool force=false); + virtual void Random_Animate(void); + virtual MoveType Can_Enter_Cell(CELL , FacingType =FACING_NONE) const; + virtual void Per_Cell_Process(bool center); + virtual void Enter_Idle_Mode(bool initial=false); + virtual void Scatter(COORDINATE threat, bool forced =false); + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + /* + ** Translation table to convert facing into infantry shape number. This special + ** table is needed since several facing stages are reused and flipped about the Y + ** axis. + */ + static int const HumanShape[32]; + + private: + + static DoStruct const MasterDoControls[DO_COUNT]; + + /* + ** This contains the value of the Virtual Function Table Pointer + */ + static void * VTable; +}; + +#endif diff --git a/INI.CPP b/INI.CPP new file mode 100644 index 0000000..02358d5 --- /dev/null +++ b/INI.CPP @@ -0,0 +1,1664 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ini.cpv 2.18 16 Oct 1995 16:48:50 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : INI.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : July 30, 1995 [BRR] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Assign_Houses -- Assigns multiplayer houses to various players * + * Clear_Flag_Spots -- Clears flag overlays off the map * + * Clip_Move -- moves in given direction from given cell; clips to map * + * Clip_Scatter -- randomly scatters from given cell; won't fall off map * + * Create_Units -- Creates infantry & units, for non-base multiplayer * + * Furthest_Cell -- Finds cell furthest from a group of cells * + * Place_Flags -- Places flags for multiplayer games * + * Read_Scenario_Ini -- Read specified scenario INI file. * + * Remove_AI_Players -- Removes the computer AI houses & their units * + * Scan_Place_Object -- places an object >near< the given cell * + * Set_Scenario_Name -- Creates the INI scenario name string. * + * Sort_Cells -- sorts an array of cells by distance * + * Write_Scenario_Ini -- Write the scenario INI file. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +/************************************* Prototypes *********************************************/ +static void Assign_Houses(void); +static void Remove_AI_Players(void); +static void Create_Units(void); +static void Sort_Cells(CELL *cells, int numcells, CELL *outcells); +static int Furthest_Cell(CELL *cells, int numcells, CELL *tcells, int numtcells); +static CELL Clip_Scatter(CELL cell, int maxdist); +static CELL Clip_Move(CELL cell, FacingType facing, int dist); + + +/*********************************************************************************************** + * Set_Scenario_Name -- Creates the INI scenario name string. * + * * + * This routine is used by the scenario loading and saving code. It generates the scenario * + * INI root file name for the specified scenario parameters. * + * * + * INPUT: * + * buf buffer to store filename in; must be long enough for root.ext * + * scenario scenario number * + * player player type for this game (GDI, NOD, multi-player, ...) * + * dir directional parameter for this game (East/West) * + * var variation of this game (Lose, A/B/C/D, etc) * + * * + * OUTPUT: none. * + * * + * WARNINGS: none. * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + * 05/01/1995 BRR : 2-player scenarios use same names as multiplayer * + *=============================================================================================*/ +void Set_Scenario_Name(char *buf, int scenario, ScenarioPlayerType player, ScenarioDirType dir, ScenarioVarType var) +{ + char c_player; // character representing player type + char c_dir; // character representing direction type + char c_var; // character representing variation type + ScenarioVarType i; + char fname[_MAX_FNAME+_MAX_EXT]; + + /* + ** Set the player-type value. + */ + switch (player) { + case SCEN_PLAYER_GDI: + c_player = HouseTypeClass::As_Reference(HOUSE_GOOD).Prefix; +// c_player = 'G'; + break; + + case SCEN_PLAYER_NOD: + c_player = HouseTypeClass::As_Reference(HOUSE_BAD).Prefix; +// c_player = 'B'; + break; + + case SCEN_PLAYER_JP: + c_player = HouseTypeClass::As_Reference(HOUSE_JP).Prefix; +// c_player = 'J'; + break; + + /* + ** Multi player scenario. + */ + default: + c_player = HouseTypeClass::As_Reference(HOUSE_MULTI1).Prefix; +// c_player = 'M'; + break; + } + + /* + ** Set the directional character value. + ** If SCEN_DIR_NONE is specified, randomly pick a direction; otherwise, use 'E' or 'W' + */ + switch (dir) { + case SCEN_DIR_EAST: + c_dir = 'E'; + break; + + case SCEN_DIR_WEST: + c_dir = 'W'; + break; + + default: + case SCEN_DIR_NONE: + c_dir = (Random_Pick(0, 1) == 0) ? 'W' : 'E'; + break; + } + + /* + ** Set the variation value. + */ + if (var == SCEN_VAR_NONE) { + + /* + ** Find which variations are available for this scenario + */ + for (i = SCEN_VAR_FIRST; i < SCEN_VAR_COUNT; i++) { + sprintf(fname, "SC%c%02d%c%c.INI", c_player, scenario, c_dir, 'A' + i); + if (!CCFileClass(fname).Is_Available()) { + break; + } + } + + if (i==SCEN_VAR_FIRST) { + c_var = 'X'; // indicates an error + } else { + c_var = 'A' + Random_Pick(0, i-1); + } + } else { + switch (var) { + case SCEN_VAR_A: + c_var = 'A'; + break; + + case SCEN_VAR_B: + c_var = 'B'; + break; + + case SCEN_VAR_C: + c_var = 'C'; + break; + + case SCEN_VAR_D: + c_var = 'D'; + break; + + default: + c_var = 'L'; + break; + + } + } + + /* + ** generate the filename + */ + sprintf(buf, "SC%c%02d%c%c", c_player, scenario, c_dir, c_var); +} + + +/*********************************************************************************************** + * Read_Scenario_Ini -- Read specified scenario INI file. * + * * + * Read in the scenario INI file. This routine only sets the game * + * globals with that data that is explicitly defined in the INI file. * + * The remaining necessary interpolated data is generated elsewhere. * + * * + * INPUT: * + * root root filename for scenario file to read * + * * + * fresh true = should the current scenario be cleared? * + * * + * OUTPUT: bool; Was the scenario read successful? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + *=============================================================================================*/ +bool Read_Scenario_Ini(char *root, bool fresh) +{ + char *buffer; // Scenario.ini staging buffer pointer. + char fname[_MAX_FNAME+_MAX_EXT]; // full INI filename + char buf[128]; // Working string staging buffer. + int rndmax; + int rndmin; + int len; + unsigned char val; + + ScenarioInit++; + + /* + ** Fetch working pointer to the INI staging buffer. Make sure that the buffer + ** is cleared out before proceeding. (Don't use the HidPage for this, since + ** the HidPage may be needed for various uncompressions during the INI + ** parsing.) + */ + buffer = (char *)_ShapeBuffer; + memset(buffer, '\0', _ShapeBufferSize); + + if (fresh) { + Clear_Scenario(); + } + + /* + ** If we are not dealing with scenario 1, or a multi player scenario + ** then make sure the correct disk is in the drive. + */ + if (RequiredCD != -2) { + if (Scenario >= 20 && Scenario <60 && GameToPlay == GAME_NORMAL) { + RequiredCD = 2; + } else { + if (Scenario != 1) { + if (Scenario >=60){ + RequiredCD = -1; + }else{ + switch (ScenPlayer) { + case SCEN_PLAYER_GDI: + RequiredCD = 0; + break; + case SCEN_PLAYER_NOD: + RequiredCD = 1; + break; + default: + RequiredCD = -1; + break; + } + } + } else { + RequiredCD = -1; + } + } + } + if (!Force_CD_Available(RequiredCD)) { + Prog_End(); + exit(EXIT_FAILURE); + } + + /* + ** Create scenario filename and read the file. + */ + + sprintf(fname,"%s.INI",root); + CCFileClass file(fname); + if (!file.Is_Available()) { + return(false); + } else { + file.Read(buffer, _ShapeBufferSize-1); + } + + /* + ** Init the Scenario CRC value + */ + ScenarioCRC = 0; + len = strlen(buffer); + for (int i = 0; i < len; i++) { + val = (unsigned char)buffer[i]; +#ifndef DEMO + Add_CRC(&ScenarioCRC, (unsigned long)val); +#endif + } + + /* + ** Fetch the appropriate movie names from the INI file. + */ + WWGetPrivateProfileString("Basic", "Intro", "x", IntroMovie, sizeof(IntroMovie), buffer); + WWGetPrivateProfileString("Basic", "Brief", "x", BriefMovie, sizeof(BriefMovie), buffer); + WWGetPrivateProfileString("Basic", "Win", "x", WinMovie, sizeof(WinMovie), buffer); + WWGetPrivateProfileString("Basic", "Lose", "x", LoseMovie, sizeof(LoseMovie), buffer); + WWGetPrivateProfileString("Basic", "Action", "x", ActionMovie, sizeof(ActionMovie), buffer); + + /* + ** For single-player scenarios, 'BuildLevel' is the scenario number. + ** This must be set before any buildings are created (if a factory is created, + ** it needs to know the BuildLevel for the sidebar.) + */ + if (GameToPlay == GAME_NORMAL) { +#ifdef NEWMENU + if (Scenario <= 15) { + BuildLevel = Scenario; + } else { + BuildLevel = WWGetPrivateProfileInt("Basic", "BuildLevel", Scenario, buffer); + } +#else + BuildLevel = Scenario; +#endif + } + + /* + ** Jurassic scenarios are allowed to build the full multiplayer set + ** of objects. + */ + if (Special.IsJurassic && AreThingiesEnabled) { + BuildLevel = 98; + } + + /* + ** Fetch the transition theme for this scenario. + */ + TransitTheme = THEME_NONE; + WWGetPrivateProfileString("Basic", "Theme", "No Theme", buf, sizeof(buf), buffer); + TransitTheme = Theme.From_Name(buf); + + /* + ** Read in the team-type data. The team types must be created before any + ** triggers can be created. + */ + TeamTypeClass::Read_INI(buffer); + Call_Back(); + + /* + ** Read in the specific information for each of the house types. This creates + ** the houses of different types. + */ + HouseClass::Read_INI(buffer); + Call_Back(); + + /* + ** Assign PlayerPtr by reading the player's house from the INI; + ** Must be done before any TechnoClass objects are created. + */ +// if (GameToPlay == GAME_NORMAL && (ScenPlayer == SCEN_PLAYER_GDI || ScenPlayer == SCEN_PLAYER_NOD)) { + if (GameToPlay == GAME_NORMAL) { + WWGetPrivateProfileString("Basic", "Player", "GoodGuy", buf, 127, buffer); + CarryOverPercent = WWGetPrivateProfileInt("Basic", "CarryOverMoney", 100, buffer); + CarryOverPercent = Cardinal_To_Fixed(100, CarryOverPercent); + CarryOverCap = WWGetPrivateProfileInt("Basic", "CarryOverCap", -1, buffer); + + PlayerPtr = HouseClass::As_Pointer(HouseTypeClass::From_Name(buf)); + PlayerPtr->IsHuman = true; + int carryover; + if (CarryOverCap != -1) { + carryover = MIN(Fixed_To_Cardinal(CarryOverMoney, CarryOverPercent), CarryOverCap); + } else { + carryover = Fixed_To_Cardinal(CarryOverMoney, CarryOverPercent); + } + PlayerPtr->Credits += carryover; + PlayerPtr->InitialCredits += carryover; + + if (Special.IsJurassic) { + PlayerPtr->ActLike = Whom; + } + } else { + +#ifdef OBSOLETE + if (GameToPlay==GAME_NORMAL && ScenPlayer==SCEN_PLAYER_JP) { + PlayerPtr = HouseClass::As_Pointer(HOUSE_MULTI4); + PlayerPtr->IsHuman = true; + PlayerPtr->Credits += CarryOverMoney; + PlayerPtr->InitialCredits += CarryOverMoney; + PlayerPtr->ActLike = Whom; + } else { + Assign_Houses(); + } +#endif + Assign_Houses(); + } + + /* + ** Read in the trigger data. The triggers must be created before any other + ** objects can be initialized. + */ + TriggerClass::Read_INI(buffer); + Call_Back(); + + /* + ** Read in the map control values. This includes dimensions + ** as well as theater information. + */ + Map.Read_INI(buffer); + Call_Back(); + + /* + ** Attempt to read the map's binary image file; if fails, read the + ** template data from the INI, for backward compatibility + */ + if (fresh) { + if (!Map.Read_Binary(root, &ScenarioCRC)) { + TemplateClass::Read_INI(buffer); + } + } + Call_Back(); + + /* + ** Read in and place the 3D terrain objects. + */ + TerrainClass::Read_INI(buffer); + Call_Back(); + + /* + ** Read in and place the units (all sides). + */ + UnitClass::Read_INI(buffer); + Call_Back(); + + /* + ** Read in and place the infantry units (all sides). + */ + InfantryClass::Read_INI(buffer); + Call_Back(); + + /* + ** Read in and place all the buildings on the map. + */ + BuildingClass::Read_INI(buffer); + Call_Back(); + + /* + ** Read in the AI's base information. + */ + Base.Read_INI(buffer); + Call_Back(); + + /* + ** Read in any normal overlay objects. + */ + OverlayClass::Read_INI(buffer); + Call_Back(); + + /* + ** Read in any smudge overlays. + */ + SmudgeClass::Read_INI(buffer); + Call_Back(); + + /* + ** Read in any briefing text. + */ + char * stage = &BriefingText[0]; + *stage = '\0'; + int index = 1; + + /* + ** Build the full text of the mission objective. + */ + for (;;) { + char buff[16]; + + sprintf(buff, "%d", index++); + *stage = '\0'; + WWGetPrivateProfileString("Briefing", buff, "", stage, (sizeof(BriefingText)-strlen(BriefingText))-1, buffer); + if (strlen(stage) == 0) break; + strcat(stage, " "); + stage += strlen(stage); + } + + /* + ** If the briefing text could not be found in the INI file, then search + ** the mission.ini file. + */ + if (BriefingText[0] == '\0') { + memset(_ShapeBuffer, '\0', _ShapeBufferSize); + CCFileClass("MISSION.INI").Read(_ShapeBuffer, _ShapeBufferSize); + + char * buffer = (char *)Add_Long_To_Pointer(_ShapeBuffer, strlen(_ShapeBuffer)); + char * work = &BriefingText[0]; + int index = 1; + + /* + ** Build the full text of the mission objective. + */ + for (;;) { + char buff[16]; + + sprintf(buff, "%d", index++); + *work = '\0'; + WWGetPrivateProfileString(root, buff, "", work, (sizeof(BriefingText)-strlen(BriefingText))-1, _ShapeBuffer); + if (strlen(work) == 0) break; + strcat(work, " "); + work += strlen(work); + } + } + + /* + ** Perform a final overpass of the map. This handles smoothing of certain + ** types of terrain (tiberium). + */ + Map.Overpass(); + Call_Back(); + + /* + ** Multi-player last-minute fixups: + ** - If computer players are disabled, remove all computer-owned houses + ** - Otherwise, set MPlayerBlitz to 0 or 1, randomly + ** - If bases are disabled, create the scenario dynamically + ** - Remove any flag spot overlays lying around + ** - If capture-the-flag is enabled, assign flags to cells. + */ + if (GameToPlay != GAME_NORMAL || ScenPlayer == SCEN_PLAYER_2PLAYER || + ScenPlayer == SCEN_PLAYER_MPLAYER) { + + /* + ** If Ghosts are disabled and we're not editing, remove computer players + ** (Must be done after all objects are read in from the INI) + */ + if (!MPlayerGhosts && !Debug_Map) { + Remove_AI_Players(); + } else { + + /* + ** If Ghosts are on, set up their houses for blitzing the humans + */ + MPlayerBlitz = IRandom (0,1); // 1 = computer will blitz + if (MPlayerBlitz) { + if (MPlayerBases) { + rndmax = 14000; + rndmin = 10000; + } else { + rndmax = 8000; + rndmin = 4000; + } + + for (i = 0; i < MPlayerMax; i++) { + HousesType house = (HousesType)(i + (int)HOUSE_MULTI1); + HouseClass *housep = HouseClass::As_Pointer (house); + housep->BlitzTime = IRandom (rndmin,rndmax); + } + + } + } + + /* + ** Units must be created for each house. If bases are ON, this routine + ** will create an MCV along with the units; otherwise, it will just create + ** a whole bunch of units. MPlayerUnitCount is the total # of units + ** to create. + */ + if (!Debug_Map) { + int save_init = ScenarioInit; // turn ScenarioInit off + ScenarioInit = 0; + Create_Units(); + ScenarioInit = save_init; // turn ScenarioInit back on + } + + /* + ** Place crates if MPlayerGoodies is on. + */ + if (MPlayerGoodies) { + for (int index = 0; index < MPlayerCount; index++) { + Map.Place_Random_Crate(); + } + } + + /* + ** Compute my starting location as the average Coord of all my stuff. + */ + Map.Compute_Start_Pos(); + } + + Call_Back(); + + /* + ** Return with flag saying that the scenario file was read. + */ + ScenarioInit--; + return(true); +} + + +/*********************************************************************************************** + * Write_Scenario_Ini -- Write the scenario INI file. * + * * + * INPUT: * + * root root filename for the scenario * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + * 05/11/1995 JLB : Updates movie data. * + *=============================================================================================*/ +void Write_Scenario_Ini(char *root) +{ +#ifndef CHEAT_KEYS + root = root; +#else + char * buffer; // Scenario.ini staging buffer pointer. + char fname[_MAX_FNAME+_MAX_EXT]; // full scenario name + HousesType house; + CCFileClass file; + + /* + ** Get a working pointer to the INI staging buffer. Make sure that the buffer + ** starts cleared out of any data. + */ + buffer = (char *)_ShapeBuffer; + memset(buffer, '\0', _ShapeBufferSize); + + switch (ScenPlayer) { + case SCEN_PLAYER_GDI: + house = HOUSE_GOOD; + break; + + case SCEN_PLAYER_NOD: + house = HOUSE_BAD; + break; + + case SCEN_PLAYER_JP: + house = HOUSE_JP; + break; + + default: + house = HOUSE_MULTI1; + break; + } + + /* + ** Create scenario filename and clear the buffer to empty. + */ + sprintf(fname,"%s.INI",root); + file.Set_Name(fname); + if (file.Is_Available()) { +// file.Open(READ); + file.Read(buffer, _ShapeBufferSize-1); +// file.Close(); + } else { + sprintf(buffer, "; Scenario %d control for house %s.\r\n", Scenario, HouseTypeClass::As_Reference(house).IniName); + } + + WWWritePrivateProfileString("Basic", "Intro", IntroMovie, buffer); + WWWritePrivateProfileString("Basic", "Brief", BriefMovie, buffer); + WWWritePrivateProfileString("Basic", "Win", WinMovie, buffer); + WWWritePrivateProfileString("Basic", "Lose", LoseMovie, buffer); + WWWritePrivateProfileString("Basic", "Action", ActionMovie, buffer); + WWWritePrivateProfileString("Basic", "Player", PlayerPtr->Class->IniName, buffer); + WWWritePrivateProfileString("Basic", "Theme", Theme.Base_Name(TransitTheme), buffer); + WWWritePrivateProfileInt("Basic", "BuildLevel", BuildLevel, buffer); + WWWritePrivateProfileInt("Basic", "CarryOverMoney", Fixed_To_Cardinal(100, CarryOverPercent), buffer); + WWWritePrivateProfileInt("Basic", "CarryOverCap", CarryOverCap, buffer); + + TeamTypeClass::Write_INI(buffer, true); + TriggerClass::Write_INI(buffer, true); + Map.Write_INI(buffer); + Map.Write_Binary(root); + HouseClass::Write_INI(buffer); + UnitClass::Write_INI(buffer); + InfantryClass::Write_INI(buffer); + BuildingClass::Write_INI(buffer); + TerrainClass::Write_INI(buffer); + OverlayClass::Write_INI(buffer); + SmudgeClass::Write_INI(buffer); + + Base.Write_INI(buffer); + + /* + ** Write the scenario data out to a file. + */ +// file.Open(WRITE); + file.Write(buffer, strlen(buffer)); +// file.Close(); + + /* + ** Now update the Master INI file, containing the master list of triggers & teams + */ + memset(buffer, '\0', _ShapeBufferSize); + + file.Set_Name("MASTER.INI"); + if (file.Is_Available()) { +// file.Open(READ); + file.Read(buffer, _ShapeBufferSize-1); +// file.Close(); + } else { + sprintf(buffer, "; Master Trigger & Team List.\r\n"); + } + + TeamTypeClass::Write_INI(buffer, false); + TriggerClass::Write_INI(buffer, false); + +// file.Open(WRITE); + file.Write(buffer,strlen(buffer)); +// file.Close(); +#endif +} + + +/*********************************************************************************************** + * Assign_Houses -- Assigns multiplayer houses to various players * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/09/1995 BRR : Created. * + * 07/14/1995 JLB : Records name of player in house structure. * + *=============================================================================================*/ +static void Assign_Houses(void) +{ + HousesType house; + HousesType pref_house; + HouseClass *housep; + bool house_used[MAX_PLAYERS]; // true = this house is in use + bool color_used[6]; // true = this color is in use + int i,j; + PlayerColorType color; + HousesType house2; + HouseClass *housep2; + +char wibble [256]; +sprintf (wibble, "C&C95 - In 'Assign_Houses'. Number of players:%d\n",MPlayerCount); +CCDebugString (wibble); + + /* + ** Init the 'used' flag for all houses & colors to 0 + */ + for (i = 0; i < MAX_PLAYERS; i++) { + house_used[i] = false; + } + for (i = 0; i < 6; i++) { + color_used[i] = false; + } + + /* + ** For each player, randomly pick a house + */ + for (i = 0; i < MPlayerCount; i++) { + j = Random_Pick(0, MPlayerMax-1); + + /* + ** If this house was already selected, decrement 'i' & keep looping. + */ + if (house_used[j]) { + i--; + continue; + } + + /* + ** Set the house, preferred house (GDI/NOD), color, and actual house; + ** get a pointer to the house instance + */ + house = (HousesType)(j + (int)HOUSE_MULTI1); + pref_house = MPlayerID_To_HousesType(MPlayerID[i]); + color = MPlayerID_To_ColorIndex(MPlayerID[i]); + housep = HouseClass::As_Pointer(house); + MPlayerHouses[i] = house; + + /* + ** Mark this house & color as used + */ + house_used[j] = true; + color_used[color] = true; + + /* + ** Set the house's IsHuman, Credits, ActLike, & RemapTable + */ + memset((char *)housep->Name, 0, MPLAYER_NAME_MAX); + strncpy((char *)housep->Name, MPlayerNames[i], MPLAYER_NAME_MAX-1); + housep->IsHuman = true; + housep->Init_Data(color, pref_house, MPlayerCredits); + + /* + ** If this ID is for myself, set up PlayerPtr + */ + if (MPlayerID[i] == MPlayerLocalID) { + PlayerPtr = housep; + } + } + + /* + ** For all houses not assigned to a player, set them up for computer use + */ + for (i = 0; i < MPlayerMax; i++) { + if (house_used[i] == false) { + + /* + ** Set the house, preferred house (GDI/NOD), and color; get a pointer + ** to the house instance + */ + house = (HousesType)(i + (int)HOUSE_MULTI1); + pref_house = (HousesType)(IRandom(0, 1) + (int)HOUSE_GOOD); + for (;;) { + color = Random_Pick(REMAP_FIRST, REMAP_LAST); + if (color_used[color] == false) { + break; + } + } + housep = HouseClass::As_Pointer (house); + + /* + ** Mark this house & color as used + */ + house_used[i] = true; + color_used[color] = true; + + /* + ** Set the house's IsHuman, Credits, ActLike, & RemapTable + */ + housep->IsHuman = false; + housep->Init_Data(color, pref_house, MPlayerCredits); + } + } + + /* + ** Now make all computer-owned houses allies of each other. + */ + for (house = HOUSE_MULTI1; house < (HOUSE_MULTI1 + MPlayerMax); house++) { + housep = HouseClass::As_Pointer(house); + if (housep->IsHuman) + continue; + + for (house2 = HOUSE_MULTI1; house2 < (HOUSE_MULTI1 + MPlayerMax); house2++) { + housep2 = HouseClass::As_Pointer (house2); + if (housep2->IsHuman) + continue; + housep->Make_Ally(house2); + } + } +} + + +/*********************************************************************************************** + * Remove_AI_Players -- Removes the computer AI houses & their units * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/09/1995 BRR : Created. * + *=============================================================================================*/ +static void Remove_AI_Players(void) +{ + int i; + HousesType house; + HouseClass *housep; + + for (i = 0; i < MAX_PLAYERS; i++) { + house = (HousesType)(i + (int)HOUSE_MULTI1); + housep = HouseClass::As_Pointer (house); + if (housep->IsHuman == false) { + housep->Clobber_All(); + } + } +} + + +/*********************************************************************************************** + * Create_Units -- Creates infantry & units, for non-base multiplayer * + * * + * This routine uses data tables to determine which units to create for either * + * a GDI or NOD house, and how many of each. * + * * + * It also sets each house's FlagHome & FlagLocation to the Waypoint selected * + * as that house's "home" cell. * + * * + * ------------------ Unit Summary: ------------------------------- * + * UNIT_MTANK Medium tank (M1). GDI 7 * + * UNIT_JEEP 4x4 jeep replacement. GDI 5 * + * UNIT_MLRS MLRS rocket launcher. GDI 99 * + * UNIT_APC APC. GDI 10 * + * UNIT_HTANK Heavy tank (Mammoth). GDI 13 * + * * + * UNIT_LTANK Light tank ('Bradly'). NOD 5 * + * UNIT_BUGGY Rat patrol dune buggy type NOD 5 * + * UNIT_ARTY Artillery unit. NOD 10 * + * UNIT_FTANK Flame thrower tank. NOD 11 * + * UNIT_STANK Stealth tank (Romulan). NOD 13 * + * UNIT_BIKE Nod recon motor-bike. NOD 99 * + * * + * ~1/3 chance of getting: {UNIT_MHQ, Mobile Head Quarters. * + * * + * ------------------ Infantry Summary: ------------------------------- * + * INFANTRY_E1, Mini-gun armed. GDI/NOD * + * INFANTRY_E2, Grenade thrower. GDI * + * INFANTRY_E3, Rocket launcher. NOD * + * INFANTRY_E6, Rocket launcher GDI * + * INFANTRY_E4, Flame thrower equipped. NOD * + * INFANTRY_RAMBO, Commando. GDI/NOD * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/09/1995 BRR : Created. * + *=============================================================================================*/ +static void Create_Units(void) +{ + enum { + NUM_UNIT_CATEGORIES = 8, + NUM_INFANTRY_CATEGORIES = 5, + }; + + static struct { + int MinLevel; + int GDICount; + UnitType GDIType; + int NODCount; + UnitType NODType; + } utable[] = { + {0, 1,UNIT_MTANK, 2,UNIT_LTANK}, + {2, 1,UNIT_JEEP, 1,UNIT_BUGGY}, + {3, 1,UNIT_MLRS, 1,UNIT_ARTY}, + {4, 1,UNIT_APC, 2,UNIT_BUGGY}, + {5, 1,UNIT_JEEP, 1,UNIT_BIKE}, + {5, 2,UNIT_JEEP, 1,UNIT_FTANK}, + {6, 1,UNIT_MSAM, 1,UNIT_MSAM}, + {7, 1,UNIT_HTANK, 2,UNIT_STANK}, + }; + static int num_units[NUM_UNIT_CATEGORIES]; // # of each type of unit to create + int tot_units; // total # units to create + + static struct { + int MinLevel; + int GDICount; + InfantryType GDIType; + int NODCount; + InfantryType NODType; + } itable[] = { + {0, 1,INFANTRY_E1, 1,INFANTRY_E1}, + {1, 1,INFANTRY_E2, 1,INFANTRY_E3}, + {3, 1,INFANTRY_E3, 1,INFANTRY_E3}, + {5, 1,INFANTRY_E3, 1,INFANTRY_E4}, + {7, 1,INFANTRY_RAMBO, 1,INFANTRY_RAMBO}, + }; + static int num_infantry[NUM_INFANTRY_CATEGORIES];// # of each type of infantry to create + int tot_infantry; // total # infantry to create + + CELL waypts[26]; + CELL sorted_waypts[26]; + int num_waypts; + + HousesType h; // house loop counter + HouseClass *hptr; // ptr to house being processed + + CELL centroid; // centroid of this house's stuff + int try_count; // # times we've tried to select a centroid + CELL centerpt; // centroid for a category of objects, as a CELL + + int u_limit; // last allowable index of units for this BuildLevel + int i_limit; // last allowable index of infantry for this BuildLevel + TechnoClass *obj; // newly-created object + int i,j,k; // loop counters + int scaleval; // value to scale # units or infantry + + /*------------------------------------------------------------------------ + For the current BuildLevel, find the max allowable index into the tables + ------------------------------------------------------------------------*/ + for (i = 0; i < NUM_UNIT_CATEGORIES; i++) { + if (BuildLevel >= utable[i].MinLevel) + u_limit = i; + } + for (i = 0; i < NUM_INFANTRY_CATEGORIES; i++) { + if (BuildLevel >= utable[i].MinLevel) + i_limit = i; + } + + /*------------------------------------------------------------------------ + Compute how many of each buildable category to create + ------------------------------------------------------------------------*/ + /*........................................................................ + Compute allowed # units + ........................................................................*/ + tot_units = (MPlayerUnitCount * 2) / 3; +// tot_units = MAX(tot_units, 1); + + /*........................................................................ + Init # of each category to 0 + ........................................................................*/ + for (i = 0; i <= u_limit; i++) + num_units[i] = 0; + + /*........................................................................ + Increment # of each category, until we've used up all units + ........................................................................*/ + j = 0; + for (i = 0; i < tot_units; i++) { + num_units[j]++; + j++; + if (j > u_limit) + j = 0; + } + + /*........................................................................ + Compute allowed # infantry + ........................................................................*/ + tot_infantry = MPlayerUnitCount - tot_units; + + /*........................................................................ + Init # of each category to 0 + ........................................................................*/ + for (i = 0; i <= i_limit; i++) + num_infantry[i] = 0; + + /*........................................................................ + Increment # of each category, until we've used up all infantry + ........................................................................*/ + j = 0; + for (i = 0; i < tot_infantry; i++) { + num_infantry[j]++; + j++; + if (j > i_limit) + j = 0; + } + + /*------------------------------------------------------------------------ + Now sort all the Waypoints on the map by distance. + ------------------------------------------------------------------------*/ + num_waypts = 0; // counts # waypoints + + /*........................................................................ + First, copy all valid waytpoints into my 'waypts' array + ........................................................................*/ + for (i = 0; i < 26; i++) { + if (Waypoint[i] != -1) { + waypts[num_waypts] = Waypoint[i]; + num_waypts++; + } + } + + /*........................................................................ + Now sort the 'waypts' array + ........................................................................*/ + Sort_Cells (waypts, num_waypts, sorted_waypts); + + /*------------------------------------------------------------------------ + Loop through all houses. Computer-controlled houses, with MPlayerBases + ON, are treated as though bases are OFF (since we have no base-building + AI logic.) + ------------------------------------------------------------------------*/ + for (h = HOUSE_MULTI1; h < (HOUSE_MULTI1 + MPlayerMax); h++) { + + /*..................................................................... + Get a pointer to this house; if there is none, go to the next house + .....................................................................*/ + hptr = HouseClass::As_Pointer(h); + if (!hptr) + continue; + + /*..................................................................... + Pick a random waypoint; if the chosen waypoint isn't valid, try again. + 'centroid' will be the centroid of all this house's stuff. + .....................................................................*/ + try_count = 0; + while (1) { + j = IRandom(0,MPlayerMax - 1); + if (sorted_waypts[j] != -1) { + centroid = sorted_waypts[j]; + sorted_waypts[j] = -1; + break; + } + try_count++; + + /*.................................................................. + OK, we've tried enough; just pick any old cell at random, as long + as it's mappable. + ..................................................................*/ + if (try_count > 200) { + while (1) { + centroid = IRandom(0,MAP_CELL_TOTAL - 1); + if (Map.In_Radar(centroid)) + break; + } + break; + } + } + + /*--------------------------------------------------------------------- + If Bases are ON, human & computer houses are treated differently + ---------------------------------------------------------------------*/ + if (MPlayerBases) { + /*.................................................................. + - For a human-controlled house: + - Set 'scaleval' to 1 + - Create an MCV + - Attach a flag to it for capture-the-flag mode + ..................................................................*/ + if (hptr->IsHuman) { + scaleval = 1; + obj = new UnitClass (UNIT_MCV, h); + if (!obj->Unlimbo(Cell_Coord(centroid),DIR_N)) { + if (!Scan_Place_Object(obj, centroid)) { + delete obj; + obj = NULL; + } + } + if (obj) { + hptr->FlagHome = 0; + hptr->FlagLocation = 0; + if (Special.IsCaptureTheFlag) { + hptr->Flag_Attach((UnitClass *)obj,true); + } + } + } else { + + /*.................................................................. + - For computer-controlled house: + - Set 'scaleval' to 3 + - Create a Mobile HQ for capture-the-flag mode + ..................................................................*/ + scaleval = 3 / (MPlayerMax - MPlayerCount); + if (scaleval==0) { + scaleval = 1; + } + + if (Special.IsCaptureTheFlag) { + obj = new UnitClass (UNIT_MHQ, h); + if (!obj->Unlimbo(Cell_Coord(centroid),DIR_N)) { + if (!Scan_Place_Object(obj, centroid)) { + delete obj; + obj = NULL; + } + } + hptr->FlagHome = 0; // turn house's flag off + hptr->FlagLocation = 0; + } + } + } else { + + /*--------------------------------------------------------------------- + If bases are OFF, set 'scaleval' to 1 & create a Mobile HQ for + capture-the-flag mode. + ---------------------------------------------------------------------*/ + scaleval = 1; + if (Special.IsCaptureTheFlag) { + obj = new UnitClass (UNIT_MHQ, h); + obj->Unlimbo(Cell_Coord(centroid),DIR_N); + hptr->FlagHome = 0; // turn house's flag off + hptr->FlagLocation = 0; + } + } + + /*--------------------------------------------------------------------- + Set the house's max # units (this is used in the Mission_Timed_Hunt()) + ---------------------------------------------------------------------*/ + hptr->MaxUnit = MPlayerUnitCount * scaleval; + + /*--------------------------------------------------------------------- + Create units for this house + ---------------------------------------------------------------------*/ + for (i = 0; i <= u_limit; i++) { + /*.................................................................. + Find the center point for this category. + ..................................................................*/ + centerpt = Clip_Scatter(centroid,4); + + /*.................................................................. + Place objects; loop through all unit in this category + ..................................................................*/ + for (j = 0; j < num_units[i] * scaleval; j++) { + /*............................................................... + Create a GDI unit + ...............................................................*/ + if (hptr->ActLike == HOUSE_GOOD) { + for (k = 0; k < utable[i].GDICount; k++) { + obj = new UnitClass (utable[i].GDIType, h); + if (!Scan_Place_Object(obj, centerpt)) { + delete obj; + } else { + if (!hptr->IsHuman) { + obj->Set_Mission(MISSION_TIMED_HUNT); + } + } + } + } else { + + /*............................................................... + Create a NOD unit + ...............................................................*/ + for (k = 0; k < utable[i].NODCount; k++) { + obj = new UnitClass (utable[i].NODType, h); + if (!Scan_Place_Object(obj, centerpt)) { + delete obj; + } else { + if (!hptr->IsHuman) { + obj->Set_Mission(MISSION_TIMED_HUNT); + } + } + } + } + } + } + + /*--------------------------------------------------------------------- + Create infantry + ---------------------------------------------------------------------*/ + for (i = 0; i <= i_limit; i++) { + /*.................................................................. + Find the center point for this category. + ..................................................................*/ + centerpt = Clip_Scatter(centroid,4); + + /*.................................................................. + Place objects; loop through all unit in this category + ..................................................................*/ + for (j = 0; j < num_infantry[i] * scaleval; j++) { + /*............................................................... + Create GDI infantry (Note: Unlimbo calls Enter_Idle_Mode(), which + assigns the infantry to HUNT; we must use Set_Mission() to override + this state.) + ...............................................................*/ + if (hptr->ActLike == HOUSE_GOOD) { + for (k = 0; k < itable[i].GDICount; k++) { + obj = new InfantryClass (itable[i].GDIType, h); + if (!Scan_Place_Object(obj, centerpt)) { + delete obj; + } else { + if (!hptr->IsHuman) { + obj->Set_Mission(MISSION_TIMED_HUNT); + } + } + } + } else { + + /*............................................................... + Create NOD infantry + ...............................................................*/ + for (k = 0; k < itable[i].NODCount; k++) { + obj = new InfantryClass (itable[i].NODType, h); + if (!Scan_Place_Object(obj, centerpt)) { + delete obj; + } else { + if (!hptr->IsHuman) { + obj->Set_Mission(MISSION_TIMED_HUNT); + } + } + } + } + } + } + } +} + + +/*********************************************************************************************** + * Scan_Place_Object -- places an object >near< the given cell * + * * + * INPUT: * + * obj ptr to object to Unlimbo * + * cell center of search area * + * * + * OUTPUT: * + * true = object was placed; false = it wasn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/09/1995 BRR : Created. * + *=============================================================================================*/ +int Scan_Place_Object(ObjectClass *obj, CELL cell) +{ + int dist; // for object placement + FacingType rot; // for object placement + FacingType fcounter; // for object placement + int tryval; + CELL newcell; + TechnoClass *techno; + int skipit; + + /*------------------------------------------------------------------------ + First try to unlimbo the object in the given cell. + ------------------------------------------------------------------------*/ + if (Map.In_Radar(cell)) { + techno = Map[cell].Cell_Techno(); + if (!techno || (techno->What_Am_I()==RTTI_INFANTRY && + obj->What_Am_I()==RTTI_INFANTRY)) { + if (obj->Unlimbo(Cell_Coord(cell),DIR_N)) { + return(true); + } + } + } + + /*------------------------------------------------------------------------ + Loop through distances from the given center cell; skip the center cell. + For each distance, try placing the object along each rotational direction; + if none are available, try each direction with a random scatter value. + If that fails, go to the next distance. + This ensures that the closest coordinates are filled first. + ------------------------------------------------------------------------*/ + for (dist = 1; dist < 32; dist++) { + /*..................................................................... + Pick a random starting direction + .....................................................................*/ + rot = (FacingType)IRandom (FACING_N, FACING_NW); + /*..................................................................... + Try all directions twice + .....................................................................*/ + for (tryval = 0 ; tryval < 2; tryval++) { + /*.................................................................. + Loop through all directions, at this distance. + ..................................................................*/ + for (fcounter = FACING_N; fcounter <= FACING_NW; fcounter++) { + + skipit = false; + + /*............................................................... + Pick a coordinate along this directional axis + ...............................................................*/ + newcell = Clip_Move(cell, rot, dist); + + /*............................................................... + If this is our second try at this distance, add a random scatter + to the desired cell, so our units aren't all aligned along spokes. + ...............................................................*/ + if (tryval > 0) + newcell = Clip_Scatter (newcell, 1); + + /*............................................................... + If, by randomly scattering, we've chosen the exact center, skip + it & try another direction. + ...............................................................*/ + if (newcell==cell) + skipit = true; + + if (!skipit) { + /*............................................................ + Only attempt to Unlimbo the object if: + - there is no techno in the cell + - the techno in the cell & the object are both infantry + ............................................................*/ + techno = Map[newcell].Cell_Techno(); + if (!techno || (techno->What_Am_I()==RTTI_INFANTRY && + obj->What_Am_I()==RTTI_INFANTRY)) { + if (obj->Unlimbo(Cell_Coord(newcell),DIR_N)) { + return(true); + } + } + } + + rot++; + if (rot > FACING_NW) + rot = FACING_N; + } + } + } + + return(false); +} + + +/*********************************************************************************************** + * Sort_Cells -- sorts an array of cells by distance * + * * + * INPUT: * + * cells array to sort * + * numcells # entries in 'cells' * + * outcells array to store sorted values in * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/19/1995 BRR : Created. * + *=============================================================================================*/ +static void Sort_Cells(CELL *cells, int numcells, CELL *outcells) +{ + int i,j,k; + int num_sorted = 0; + int num_unsorted = numcells; + + /*------------------------------------------------------------------------ + Pick the first cell at random + ------------------------------------------------------------------------*/ + j = Random_Pick(0,numcells - 1); + outcells[0] = cells[j]; + num_sorted++; + + for (k = j; k < num_unsorted - 1; k++) { + cells[k] = cells[k + 1]; + } + num_unsorted--; + + /*------------------------------------------------------------------------ + After the first cell, assign the other cells based on who's furthest away + from the chosen ones. + ------------------------------------------------------------------------*/ + for (i = 0; i < numcells; i++) { + j = Furthest_Cell (outcells, num_sorted, cells, num_unsorted); + outcells[num_sorted] = cells[j]; + num_sorted++; + + for (k = j; k < num_unsorted - 1; k++) { + cells[k] = cells[k + 1]; + } + num_unsorted--; + } +} + + +/*********************************************************************************************** + * Furthest_Cell -- Finds cell furthest from a group of cells * + * * + * INPUT: * + * cells array of cells to find furthest-cell-away-from * + * numcells # entries in 'cells' * + * tcells array of cells to test; one of these will be selected as being * + * "furthest" from all the cells in 'cells' * + * numtcells # entries in 'tcells' * + * * + * OUTPUT: * + * index of 'tcell' that's furthest away from 'cells' * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/19/1995 BRR : Created. * + *=============================================================================================*/ +static int Furthest_Cell(CELL *cells, int numcells, CELL *tcells, int numtcells) +{ + int i; + int j; + int mindist; // minimum distance a 'tcell' is from a 'cell' + int maxmindist; // the highest mindist value of all tcells + int maxmin_idx; // index of the tcell with largest mindist + int dist; // working distance measure + + /*------------------------------------------------------------------------ + Initialize + ------------------------------------------------------------------------*/ + maxmindist = 0; + maxmin_idx = 0; + + /*------------------------------------------------------------------------ + Loop through all test cells, finding the furthest one from all entries in + the 'cells' array + ------------------------------------------------------------------------*/ + for (i = 0; i < numtcells; i++) { + + /*..................................................................... + Find the 'cell' closest to this 'tcell' + .....................................................................*/ + mindist = 0xffff; + for (j = 0; j < numcells; j++) { + dist = Distance (tcells[i],cells[j]); + if (dist <= mindist) { + mindist = dist; + } + } + + /*..................................................................... + If this tcell is further away than the others, save its distance & + index value + .....................................................................*/ + if (mindist >= maxmindist) { + maxmindist = mindist; + maxmin_idx = i; + } + } + + return (maxmin_idx); +} + + +/*********************************************************************************************** + * Clip_Scatter -- randomly scatters from given cell; won't fall off map * + * * + * INPUT: * + * cell cell to scatter from * + * maxdist max distance to scatter * + * * + * OUTPUT: * + * new cell number * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/30/1995 BRR : Created. * + *=============================================================================================*/ +static CELL Clip_Scatter(CELL cell, int maxdist) +{ + int x,y; + int xdist; + int ydist; + int xmin,xmax; + int ymin,ymax; + + /*------------------------------------------------------------------------ + Get X & Y coords of given starting cell + ------------------------------------------------------------------------*/ + x = Cell_X(cell); + y = Cell_Y(cell); + + /*------------------------------------------------------------------------ + Compute our x & y limits + ------------------------------------------------------------------------*/ + xmin = Map.MapCellX; + xmax = xmin + Map.MapCellWidth - 1; + ymin = Map.MapCellY; + ymax = ymin + Map.MapCellHeight - 1; + + /*------------------------------------------------------------------------ + Adjust the x-coordinate + ------------------------------------------------------------------------*/ + xdist = IRandom(0,maxdist); + if (IRandom(0,1)==0) { + x += xdist; + if (x > xmax) { + x = xmax; + } + } else { + x -= xdist; + if (x < xmin) { + x = xmin; + } + } + + /*------------------------------------------------------------------------ + Adjust the y-coordinate + ------------------------------------------------------------------------*/ + ydist = IRandom(0,maxdist); + if (IRandom(0,1)==0) { + y += ydist; + if (y > ymax) { + y = ymax; + } + } else { + y -= ydist; + if (y < ymin) { + y = ymin; + } + } + + return (XY_Cell(x,y)); +} + + +/*********************************************************************************************** + * Clip_Move -- moves in given direction from given cell; clips to map * + * * + * INPUT: * + * cell cell to start from * + * facing direction to move * + * dist distance to move * + * * + * OUTPUT: * + * new cell number * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/30/1995 BRR : Created. * + *=============================================================================================*/ +static CELL Clip_Move(CELL cell, FacingType facing, int dist) +{ + int x,y; + int xmin,xmax; + int ymin,ymax; + + /*------------------------------------------------------------------------ + Get X & Y coords of given starting cell + ------------------------------------------------------------------------*/ + x = Cell_X(cell); + y = Cell_Y(cell); + + /*------------------------------------------------------------------------ + Compute our x & y limits + ------------------------------------------------------------------------*/ + xmin = Map.MapCellX; + xmax = xmin + Map.MapCellWidth - 1; + ymin = Map.MapCellY; + ymax = ymin + Map.MapCellHeight - 1; + + /*------------------------------------------------------------------------ + Adjust the x-coordinate + ------------------------------------------------------------------------*/ + switch (facing) { + case FACING_N: + y -= dist; + break; + + case FACING_NE: + x += dist; + y -= dist; + break; + + case FACING_E: + x += dist; + break; + + case FACING_SE: + x += dist; + y += dist; + break; + + case FACING_S: + y += dist; + break; + + case FACING_SW: + x -= dist; + y += dist; + break; + + case FACING_W: + x -= dist; + break; + + case FACING_NW: + x -= dist; + y -= dist; + break; + } + + /*------------------------------------------------------------------------ + Clip to the map + ------------------------------------------------------------------------*/ + if (x > xmax) + x = xmax; + if (x < xmin) + x = xmin; + + if (y > ymax) + y = ymax; + if (y < ymin) + y = ymin; + + return (XY_Cell(x,y)); +} diff --git a/INIT.CPP b/INIT.CPP new file mode 100644 index 0000000..87ccc4a --- /dev/null +++ b/INIT.CPP @@ -0,0 +1,2958 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\init.cpv 2.18 16 Oct 1995 16:50:16 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : INIT.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : January 20, 1992 * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Anim_Init -- Initialize the VQ animation control structure. * + * Init_Game -- Main game initialization routine. * + * Load_Recording_Values -- Loads recording values from recording file * + * Obfuscate -- Sufficiently transform parameter to thwart casual hackers. * + * Parse_Command_Line -- Parses the command line parameters. * + * Parse_INI_File -- Parses CONQUER.INI for special options * + * Play_Intro -- plays the introduction & logo movies * + * Save_Recording_Values -- Saves recording values to a recording file * + * Select_Game -- The game's main menu * + * Version_Number -- Determines the version number. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "loaddlg.h" +#include "tcpip.h" +#include +#include +#include "ccdde.h" + +static HANDLE hCCLibrary; + +/**************************************** +** Function prototypes for this module ** +*****************************************/ +static void Play_Intro(bool for_real = false); + +extern "C" { +extern long RandNumb; +} + +extern int SimRandIndex; + +#define ATTRACT_MODE_TIMEOUT 3600 // timeout for attract mode +#if(0) + +long FAR PASCAL _export Start_Game_Proc(HWND hwnd, UINT message, UINT wParam, LONG lParam) +{ + switch (message) { + case WM_CREATE: + break; + + case WM_COMMAND: + EndDialog(hwnd, TRUE); + AllDone = TRUE; + break; + + case WM_DESTROY: + EndDialog(hwnd, TRUE); + break; + } + return(DefWindowProc(hwnd, message, wParam, lParam)); +} +#endif + + +extern bool Server_Remote_Connect(void); +extern bool Client_Remote_Connect(void); +extern bool SpawnedFromWChat; + + +/*********************************************************************************************** + * Init_Game -- Main game initialization routine. * + * * + * Perform all one-time game initializations here. This includes all * + * allocations and table setups. The intro and other one-time startup * + * tasks are also performed here. * + * * + * INPUT: argc,argv -- Command line arguments. * + * * + * OUTPUT: none * + * * + * WARNINGS: Only call this ONCE! * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + *=============================================================================================*/ +bool Init_Game(int , char *[]) +{ + void const *temp_mouse_shapes; + + CCDebugString ("C&C95 - About to load reslib.dll\n"); + hCCLibrary = LoadLibrary("reslib.dll"); + + /* + ** Initialize the game object heaps. + */ + CCDebugString ("C&C95 - About to enter Units.Set_Heap\n"); + Units.Set_Heap(UNIT_MAX); + CCDebugString ("C&C95 - About to enter Factories.Set_Heap\n"); + Factories.Set_Heap(FACTORY_MAX); + CCDebugString ("C&C95 - About to enter Terrains.Set_Heap\n"); + Terrains.Set_Heap(TERRAIN_MAX); + CCDebugString ("C&C95 - About to enter Templates.Set_Heap\n"); + Templates.Set_Heap(TEMPLATE_MAX); + CCDebugString ("C&C95 - About to enter Smudges.Set_Heap\n"); + Smudges.Set_Heap(SMUDGE_MAX); + CCDebugString ("C&C95 - About to enter Overlays.Set_Heap\n"); + Overlays.Set_Heap(OVERLAY_MAX); + CCDebugString ("C&C95 - About to enter Infantry.Set_Heap\n"); + Infantry.Set_Heap(INFANTRY_MAX); + CCDebugString ("C&C95 - About to enter Bullets.Set_Heap\n"); + Bullets.Set_Heap(BULLET_MAX); + CCDebugString ("C&C95 - About to enter Buildings.Set_Heap\n"); + Buildings.Set_Heap(BUILDING_MAX); + CCDebugString ("C&C95 - About to enter Anims.Set_Heap\n"); + Anims.Set_Heap(ANIM_MAX); + CCDebugString ("C&C95 - About to enter Aircraft.Set_Heap\n"); + Aircraft.Set_Heap(AIRCRAFT_MAX); + CCDebugString ("C&C95 - About to enter Triggers.Set_Heap\n"); + Triggers.Set_Heap(TRIGGER_MAX); + CCDebugString ("C&C95 - About to enter TeamTypes.Set_Heap\n"); + TeamTypes.Set_Heap(TEAMTYPE_MAX); + CCDebugString ("C&C95 - About to enter Teams.Set_Heap\n"); + Teams.Set_Heap(TEAM_MAX); + CCDebugString ("C&C95 - About to enter Houses.Set_Heap\n"); + Houses.Set_Heap(HOUSE_MAX); + + /* + ** Initialize all the waypoints to invalid values. + */ + CCDebugString ("C&C95 - About to clear waypoints\n"); + memset(Waypoint, 0xFF, sizeof(Waypoint)); + + /* + ** Setup the keyboard processor in preparation for the game. + */ + CCDebugString ("C&C95 - About to do various keyboard inits\n"); +#ifdef FIX_ME_LATER + Keyboard_Attributes_Off(TRACKEXT | PAUSEON | BREAKON | SCROLLLOCKON | CTRLSON | CTRLCON | PASSBREAKS | FILTERONLY | TASKSWITCHABLE); +#endif //FIX_ME_LATER + Keyboard::Clear(); + Kbd.Clear(); + + /* + ** This is the shape staging buffer. It must always be available, so it is + ** allocated here and never freed. The library sets the globals ShapeBuffer + ** and ShapeBufferSize to these values, so it can be accessed for other + ** purposes. + */ + CCDebugString ("C&C95 - About to call Set_Shape_Buffer\n"); + Set_Shape_Buffer(new unsigned char[SHAPE_BUFFER_SIZE], SHAPE_BUFFER_SIZE); + + /* + ** Bootstrap enough of the system so that the error dialog box can sucessfully + ** be displayed. + */ + CCDebugString ("C&C95 - About to register CCLOCAL.MIX\n"); +#ifdef DEMO + new MixFileClass("DEMOL.MIX"); + MixFileClass::Cache("DEMOL.MIX"); +#else + int temp = RequiredCD; + RequiredCD = -2; + new MixFileClass("CCLOCAL.MIX"); // Cached. + MixFileClass::Cache("CCLOCAL.MIX"); + CCDebugString ("C&C95 - About to register UPDATE.MIX\n"); + new MixFileClass("UPDATE.MIX"); // Cached. + CCDebugString ("C&C95 - About to register UPDATEC.MIX\n"); + new MixFileClass("UPDATEC.MIX"); // Cached. + MixFileClass::Cache("UPDATEC.MIX"); +#ifdef JAPANESE + CCDebugString ("C&C95 - About to register LANGUAGE.MIX\n"); + new MixFileClass("LANGUAGE.MIX"); +#endif //JAPANESE + + RequiredCD = temp; + +#endif + CCDebugString ("C&C95 - About to load fonts\n"); + Green12FontPtr = Load_Alloc_Data(CCFileClass("12GREEN.FNT")); + Green12GradFontPtr = Load_Alloc_Data(CCFileClass("12GRNGRD.FNT")); + MapFontPtr = Load_Alloc_Data(CCFileClass("8FAT.FNT")); + Font8Ptr = MixFileClass::Retrieve(FONT8); + FontPtr = (char *)Font8Ptr; + Set_Font(FontPtr); + Font3Ptr = MixFileClass::Retrieve(FONT3); +// Font6Ptr = MixFileClass::Retrieve(FONT6); + Font6Ptr = Load_Alloc_Data(CCFileClass("6POINT.FNT")); + //ScoreFontPtr = MixFileClass::Retrieve("12GRNGRD.FNT"); //GRAD12FN"); //("SCOREFNT.FNT"); + ScoreFontPtr = Load_Alloc_Data(CCFileClass("12GRNGRD.FNT")); + FontLEDPtr = MixFileClass::Retrieve("LED.FNT"); + VCRFontPtr = MixFileClass::Retrieve("VCR.FNT"); +// GradFont6Ptr = MixFileClass::Retrieve("GRAD6FNT.FNT"); + GradFont6Ptr = Load_Alloc_Data(CCFileClass("GRAD6FNT.FNT")); + BlackPalette = new(MEM_CLEAR|MEM_REAL) unsigned char[768]; + GamePalette = new(MEM_CLEAR|MEM_REAL) unsigned char[768]; + OriginalPalette = new(MEM_CLEAR|MEM_REAL) unsigned char[768]; + WhitePalette = new(MEM_CLEAR|MEM_REAL) unsigned char[768]; + memset(WhitePalette, 63, 768); + + CCDebugString ("C&C95 - About to set palette\n"); + memset(BlackPalette, 0x01, 768); + if (!Special.IsFromInstall) Set_Palette(BlackPalette); + memset(BlackPalette, 0, 768); + if (!Special.IsFromInstall) { + Set_Palette(BlackPalette); + CCDebugString ("C&C95 - About to clear visible page\n"); + VisiblePage.Clear(); + } + + Set_Palette(GamePalette); + + CCDebugString ("C&C95 - About to set the mouse shape\n"); + /* + ** Since there is no mouse shape currently available we need' + ** to set one of our own. + */ + ShowCursor (FALSE); + if (MouseInstalled) { + temp_mouse_shapes = MixFileClass::Retrieve("MOUSE.SHP"); + if (temp_mouse_shapes) { + Set_Mouse_Cursor(0, 0, Extract_Shape(temp_mouse_shapes,0)); + while (Get_Mouse_State() > 1) { + Show_Mouse(); + } + } + } + + CCDebugString ("C&C95 - About to enter wait for focus loop\n"); + /* + ** Process the message loop until we are in focus. + */ + do { + CCDebugString ("C&C95 - About to call Keyboard::Check\n"); + Keyboard::Check(); + }while (!GameInFocus); + AllSurfaces.SurfacesRestored=FALSE; + + CCDebugString ("C&C95 - About to load the language file\n"); + /* + ** Fetch the language text from the hard drive first. If it cannot be + ** found on the hard drive, then look for it in the mixfile. + */ + if (RawFileClass(Language_Name("CONQUER")).Is_Available()) { + SystemStrings = (char const *)Load_Alloc_Data(RawFileClass(Language_Name("CONQUER"))); + } else { + SystemStrings = (char const *)MixFileClass::Retrieve(Language_Name("CONQUER")); + } + + /* + ** Default palette initialization. Uses the desert palette for convenience, + ** but only the non terrain specific colors matter. + */ + //Mem_Copy((void *)MixFileClass::Retrieve("TEMPERAT.PAL"), GamePalette, 768L); + CCFileClass palfile ("TEMPERAT.PAL"); + palfile.Read (GamePalette, 768L); + + if (!MouseInstalled) { + char buffer[255]; + Set_Palette(GamePalette); +#ifdef GERMAN + sprintf(buffer, "Command & Conquer kann Ihren Maustreiber nicht finden.."); +#else +#ifdef FRENCH + sprintf(buffer, "Command & Conquer ne peut pas d‚tecter votre gestionnaire de souris."); +#else + sprintf(buffer, "Command & Conquer is unable to detect your mouse driver."); +#endif +#endif + CCMessageBox().Process(buffer, TXT_OK); + Prog_End(); + exit(1); + } + +#ifdef DEMO + /* + ** Add in any override path specified in the conquer.ini file. + */ + if (strlen(OverridePath)) { + CCFileClass::Set_Search_Drives(OverridePath); + } +#endif + + CCDebugString ("C&C95 - About to search for CD drives\n"); + /* + ** Always try to look at the CD-ROM for data files. + */ + if (!CCFileClass::Is_There_Search_Drives()) { + + /* + ** If there are no search drives specified then we must be playing + ** off cd, so read files from there. + */ + int error; + + do { + if (!CDList.Get_Number_Of_Drives()){ + Set_Palette(GamePalette); + Show_Mouse(); + CCMessageBox().Process(TXT_CD_ERROR1, TXT_OK); + Prog_End(); + exit(EXIT_FAILURE); + } + CCFileClass::Set_CD_Drive( CDList.Get_First_CD_Drive() ); + + error = CCFileClass::Set_Search_Drives("?:\\"); + switch(error) { + case 1: + Set_Palette(GamePalette); + Show_Mouse(); + CCMessageBox().Process(TXT_CD_ERROR1, TXT_OK); + Prog_End(); + exit(EXIT_FAILURE); + + case 2: + Set_Palette(GamePalette); + Show_Mouse(); + if (CCMessageBox().Process(TXT_CD_DIALOG_1, TXT_OK, TXT_CANCEL) == 1) { + Prog_End(); + exit(EXIT_FAILURE); + } + Hide_Mouse(); + break; + + default: + Show_Mouse(); + if (!Force_CD_Available(RequiredCD)) { + Prog_End(); + exit(EXIT_FAILURE); + } + Hide_Mouse(); + break; + } + } while (error); + +#ifdef DEMO + RequiredCD = -2; +#else + RequiredCD = -1; +#endif + } else { + + /* + ** If there are search drives specified then all files are to be + ** considered local. + */ + RequiredCD = -2; + } +#ifndef DEMO + CCDebugString ("C&C95 - About to register addon mixfiles\n"); + /* + ** Before all else, cache any additional mixfiles. + */ + struct find_t ff; // for _dos_findfirst + if (!_dos_findfirst("SC*.MIX", _A_NORMAL, &ff)) { + char * ptr; + do { + ptr = strdup(ff.name); + new MixFileClass(ptr); + MixFileClass::Cache(ptr); +// free(ptr); + } while(!_dos_findnext(&ff)); + } + if (!_dos_findfirst("SS*.MIX", _A_NORMAL, &ff)) { + char * ptr; + do { + ptr = strdup(ff.name); + new MixFileClass(ptr); +// free(ptr); + } while(!_dos_findnext(&ff)); + } +#endif //DEMO + + CCDebugString ("C&C95 - About to register GENERAL.MIX\n"); + if (GeneralMix) delete GeneralMix; + GeneralMix = new MixFileClass("GENERAL.MIX"); + +// if (!_dos_findfirst("SC*.MIX", _A_NORMAL, &ff)) { +// do { +// new MixFileClass(ff.name); +// MixFileClass::Cache(ff.name); +// } while(!_dos_findnext(&ff)); +// } + + /* + ** Inform the file system of the various MIX files. + */ +#ifdef DEMO + new MixFileClass("DEMO.MIX"); + if (CCFileClass("DEMOM.MIX").Is_Available()) { + if (!MoviesMix) MoviesMix = new MixFileClass("DEMOM.MIX"); + ScoresPresent = true; + ThemeClass::Scan(); + } + +#else + CCDebugString ("C&C95 - About to register CONQUER.MIX\n"); + new MixFileClass("CONQUER.MIX"); // Cached. + CCDebugString ("C&C95 - About to register TRANSIT.MIX\n"); + new MixFileClass("TRANSIT.MIX"); + + CCDebugString ("C&C95 - About to register GENERAL.MIX\n"); + if (!GeneralMix) GeneralMix = new MixFileClass("GENERAL.MIX"); // Never cached. + +// if (CCFileClass("MOVIES.MIX").Is_Available()) { + CCDebugString ("C&C95 - About to register MOVIES.MIX\n"); + if (!MoviesMix) MoviesMix = new MixFileClass("MOVIES.MIX"); // Never cached. +// } + + + +#if (0) + + /* + ** Extract a movie from a mixfile. + */ + char *file_ptr = (char*)Alloc (32 * 1024 * 1024, MEM_NORMAL); + CCFileClass whatever ("PINTLE.VQA"); + + int len = whatever.Size(); + + whatever.Open(); + + DWORD actual; + HANDLE sfile = CreateFile("c:\\temp\\PINTLE.VQA", GENERIC_WRITE, 0, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if (sfile != INVALID_HANDLE_VALUE){ + SetFilePointer (sfile, 0, NULL, FILE_END); + + do{ + whatever.Read (file_ptr, MIN (len, 1024*64)); + WriteFile(sfile, file_ptr, MIN (len, 1024*64), &actual, NULL); + len -= MIN (len, 1024*64); + }while ( len >0 ); + + CloseHandle (sfile); + } + + whatever.Close(); + + Free (file_ptr); + +#endif //(0) + + + + /* + ** Register the score mixfile. + */ + CCDebugString ("C&C95 - About to register SCORES.MIX\n"); + ScoresPresent = false; +// if (CCFileClass("SCORES.MIX").Is_Available()) { + ScoresPresent = true; + if (!ScoreMix) { + ScoreMix = new MixFileClass("SCORES.MIX"); + ThemeClass::Scan(); + } +// } +#endif + + /* + ** These are sound card specific, but the install program would have + ** copied the coorect versions to the hard drive. + */ + CCDebugString ("C&C95 - About to register SPEECH.MIX\n"); + if (CCFileClass("SPEECH.MIX").Is_Available()) { + new MixFileClass("SPEECH.MIX"); // Never cached. + } + CCDebugString ("C&C95 - About to register SOUNDS.MIX\n"); + new MixFileClass("SOUNDS.MIX"); // Cached. + + /* + ** Initialize the animation system. + */ + CCDebugString ("C&C95 - About to initialise the animation system\n"); + Anim_Init(); + + if (SpawnedFromWChat){ + Special.IsFromWChat = true; + } + + /* + ** Play the introduction movies. + */ + CCDebugString ("C&C95 - About to play the intro movie\n"); + if (!Special.IsFromInstall && !Special.IsFromWChat) Play_Intro(true); + + /* + ** Wait for a VSync; during the vertical blank, set the game palette & blit + ** the title screen. We must ensure no RGB values in the game palette match + ** those in the WWLIB's 'CurrentPalette', or the WWLIB palette-set routine + ** will skip that color; the VQ player will have changed that color (behind + ** WWLIB's back), so it will be incorrect. + */ + memset(CurrentPalette, 0x01, 768); + + if (!Special.IsFromInstall) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + } + + Hide_Mouse(); + Wait_Vert_Blank(); + if (!Special.IsFromInstall) { + Set_Palette(Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + } + Call_Back(); +// Window_Dialog_Box(hCCLibrary, "DIALOG_1", MainWindow, MakeProcInstance((FARPROC)Start_Game_Proc, hInstance)); +// if (hCCLibrary) FreeLibrary(hCCLibrary); + +#ifdef DEMO + MixFileClass::Cache("DEMO.MIX"); + MixFileClass::Cache("SOUNDS.MIX"); +#else + /* + ** Cache the main game data. This operation can take a very long time. + */ + MixFileClass::Cache("CONQUER.MIX"); + if (SampleType != 0 && !Debug_Quiet) { + MixFileClass::Cache("SOUNDS.MIX"); + if (Special.IsJuvenile) { + new MixFileClass("ZOUNDS.MIX"); // Cached. + MixFileClass::Cache("ZOUNDS.MIX"); + } + } + Call_Back(); +#endif + +// malloc(2); + + /* + ** Perform any special debug-only processing. This includes preparing the + ** monochrome screen. + */ + Mono_Clear_Screen(); + +#ifdef ONHOLD + /* + ** Check for addition options not specified on the command-line. This must + ** be done before the One_Time calls, but after the shape buffer is set up. + */ + Parse_INI_File(); +#endif + + /* + ** Perform one-time game system initializations. + */ + Call_Back(); +// malloc(3); + Map.One_Time(); +// malloc(4); + Logic.One_Time(); +// malloc(5); + Options.One_Time(); + +// malloc(6); + + ObjectTypeClass::One_Time(); + BuildingTypeClass::One_Time(); + BulletTypeClass::One_Time(); + HouseTypeClass::One_Time(); + + TemplateTypeClass::One_Time(); + OverlayTypeClass::One_Time(); + SmudgeTypeClass::One_Time(); + TerrainTypeClass::One_Time(); + UnitTypeClass::One_Time(); + + InfantryTypeClass::One_Time(); + AnimTypeClass::One_Time(); + AircraftTypeClass::One_Time(); + HouseClass::One_Time(); + + /* + ** Speech holding tank buffer. Since speech does not mix, it can be placed + ** into a custom holding tank only as large as the largest speech file to + ** be played. + */ + SpeechBuffer = new char [SPEECH_BUFFER_SIZE]; + Call_Back(); + + /* + ** WWLIB bug: MouseState is in some undefined state; show the mouse until + ** it really shows. + */ + Map.Set_Default_Mouse(MOUSE_NORMAL, false); + Show_Mouse(); +//#ifdef FIX_ME_LATER + while (Get_Mouse_State() > 0) Show_Mouse(); +//#endif //FIX_ME_LATER + Call_Back(); + +#ifndef DEMO + /* + ** Load multiplayer scenario descriptions + */ + Read_Scenario_Descriptions(); +#endif + + /* + ** Initialize the multiplayer score values + */ + MPlayerGamesPlayed = 0; + MPlayerNumScores = 0; + MPlayerCurGame = 0; + for (int i = 0; i < MAX_MULTI_NAMES; i++) { + MPlayerScore[i].Name[0] = '\0'; + MPlayerScore[i].Wins = 0; + for (int j = 0; j < MAX_MULTI_GAMES; j++) { + MPlayerScore[i].Kills[j] = -1; // -1 = this player didn't play this round + } + } + + /* + ** Copy the title screen's palette into the GamePalette & OriginalPalette, + ** because the options Load routine uses these palettes to set the brightness, etc. + */ + memcpy (GamePalette, Palette, 768); + memcpy (OriginalPalette, Palette, 768); + + /* + ** Read game options, so the GameSpeed is initialized when multiplayer + ** dialogs are invoked. (GameSpeed must be synchronized between systems.) + */ + Options.Load_Settings(); + + return(true); +} + + +#ifndef NOMEMCHECK +void Uninit_Game(void) +{ + delete Map.ShadowPage; + Map.ShadowPage = NULL; + Map.Free_Cells(); + + delete [] SpeechBuffer; + + CCFileClass::Clear_Search_Drives(); + MixFileClass::Free_All(); + + Units.Set_Heap(0); + Factories.Set_Heap(0); + Terrains.Set_Heap(0); + Templates.Set_Heap(0); + Smudges.Set_Heap(0); + Overlays.Set_Heap(0); + Infantry.Set_Heap(0); + Bullets.Set_Heap(0); + Buildings.Set_Heap(0); + Anims.Set_Heap(0); + Aircraft.Set_Heap(0); + Triggers.Set_Heap(0); + TeamTypes.Set_Heap(0); + Teams.Set_Heap(0); + Houses.Set_Heap(0); + + delete [] _ShapeBuffer; + Set_Shape_Buffer(NULL, 0); + delete [] BlackPalette; + delete [] GamePalette; + delete [] OriginalPalette; + delete [] WhitePalette; + + WWDOS_Shutdown(); + delete [] Palette; +} +#endif + +extern bool Do_The_Internet_Menu_Thang(void); +extern int ShowCommand; + +/*********************************************************************************************** + * Select_Game -- The game's main menu * + * * + * INPUT: * + * fade if true, will fade the palette in gradually * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/05/1995 BRR : Created. * + *=============================================================================================*/ +extern int Com_Fake_Scenario_Dialog(void); +extern int Com_Show_Fake_Scenario_Dialog(void); +extern int WChatMaxAhead; +extern int WChatSendRate; +void Check_From_WChat(char *wchat_name); + +bool Select_Game(bool fade) +{ + enum { + SEL_TIMEOUT = -1, // main menu timeout--go into attract mode +#ifdef NEWMENU + SEL_NEW_SCENARIO, // Expansion scenario to play. +#endif + SEL_START_NEW_GAME, // start a new game +#ifdef BONUS_MISSIONS + SEL_BONUS_MISSIONS, +#endif //BONUS_MISSIONS + SEL_INTERNET, + SEL_LOAD_MISSION, // load a saved game + SEL_MULTIPLAYER_GAME, // play modem/null-modem/network game + SEL_INTRO, // replay the intro + SEL_EXIT, // exit to DOS + SEL_FAME, // view the hall of fame + SEL_NONE, // placeholder default value + }; + bool gameloaded=false; // Has the game been loaded from the menu? + int selection; // the default selection + bool process = true; // false = break out of while loop + bool display = true; + CountDownTimerClass count; + int cd_index; + + MEMORYSTATUS mem_info; + mem_info.dwLength=sizeof(mem_info); + GlobalMemoryStatus(&mem_info); + + /* + ** Enable the DDE Server so we can get internet start game packets from WChat + */ + DDEServer.Enable(); + + if (Special.IsFromInstall) { + /* + ** Special case for machines with 12 megs or less - just play intro, no choose side screen + */ + if (mem_info.dwTotalPhys < 12*1024*1024){ + VisiblePage.Clear(); + Play_Movie("INTRO2", THEME_NONE, false); + BreakoutAllowed = true; + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + fade = true; + VisiblePage.Clear(); + }else{ + display = false; + Show_Mouse(); + } + } + + /* + ** [Re]set any globals that need it, in preparation for a new scenario + */ + GameActive = true; + DoList.Init(); + OutList.Init(); + Frame = 0; + PlayerWins = false; + PlayerLoses = false; + MPlayerObiWan = false; + Debug_Unshroud = false; + Map.Set_Cursor_Shape(0); + Map.PendingObjectPtr = 0; + Map.PendingObject = 0; + Map.PendingHouse = HOUSE_NONE; + + /* + ** Initialize multiplayer-protocol-specific variables: + ** If CommProtocol MULTI_E_COMP is used, you must: + ** Init FrameSendRate to a sensible value (3 is good) + ** Init MPlayerMaxAhead to an even multiple of FrameSendRate, and it must + ** be at least 2 * MPlayerMaxAhead + */ + CommProtocol = COMM_PROTOCOL_SINGLE_NO_COMP; + if (!Special.IsFromWChat){ + FrameSendRate = 3; + } + + ProcessTicks = 0; + ProcessFrames = 0; + DesiredFrameRate = 30; +//#if(TIMING_FIX) + NewMaxAheadFrame1 = 0; + NewMaxAheadFrame2 = 0; +//#endif + + /* + ** Init multiplayer game scores. Let Wins accumulate; just init the current + ** Kills for this game. Kills of -1 means this player didn't play this round. + */ + for (int i = 0 ; i < MAX_MULTI_GAMES; i++) { + MPlayerScore[i].Kills[MPlayerCurGame] = -1; + } + + /* + ** Set default mouse shape + */ + Map.Set_Default_Mouse(MOUSE_NORMAL, false); + + /* + ** If the last game we played was a multiplayer game, jump right to that + ** menu by pre-setting 'selection'. + */ + if (GameToPlay == GAME_NORMAL) { + selection = SEL_NONE; + } else { + selection = SEL_MULTIPLAYER_GAME; + } + + /* + ** Main menu processing; only do this if we're not in editor mode. + */ + if (!Debug_Map) { + + /* + ** Menu selection processing loop + */ + ScenarioInit++; + Theme.Queue_Song(THEME_MAP1); + ScenarioInit--; + + /* + ** If we're playing back a recording, load all pertinant values & skip + ** the menu loop. Hide the now-useless mouse pointer. + */ + if (PlaybackGame && RecordFile.Is_Available()) { + if (RecordFile.Open(READ)) { + Load_Recording_Values(); + process = false; + Theme.Fade_Out(); + } else + PlaybackGame = false; + } + + + /* + ** Handle case where we were spawned from Wchat + */ + if (SpawnedFromWChat){ + Special.IsFromInstall = false; //Dont play intro if we were spawned from wchat + selection = SEL_INTERNET; + Theme.Queue_Song(THEME_NONE); + GameToPlay = GAME_INTERNET; + display = false; + Set_Logic_Page(SeenBuff); + } + + + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=TRUE; + } + + /* + ** Redraw the title page if needed + */ + if (display) { + Hide_Mouse(); + + /* + ** Display the title page; fade it in if this is the first time + ** through the loop, and the 'fade' flag is true + */ + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + memcpy (GamePalette, Palette, 768); + HidPage.Blit(SeenBuff); + + if (fade) { + Fade_Palette_To(Palette, FADE_PALETTE_SLOW, Call_Back); + fade = false; + } + + Set_Logic_Page(SeenBuff); +#ifdef VIRGIN_CHEAT_KEYS + Fancy_Text_Print("V.%d%s", SeenBuff.Get_Width() - 1, SeenBuff.Get_Height() - 10, DKGREY, TBLACK, TPF_6POINT|TPF_FULLSHADOW|TPF_RIGHT, Version_Number(), VersionText, FOREIGN_VERSION_NUMBER); +// Fancy_Text_Print("V.%d%s%02d", 319, 190, DKGREY, TBLACK, TPF_6POINT|TPF_FULLSHADOW|TPF_RIGHT, Version_Number(), VersionText, FOREIGN_VERSION_NUMBER); +#else + +#ifdef DEMO + Version_Number(); + Fancy_Text_Print("DEMO V%s", SeenBuff.Get_Width() - 1, SeenBuff.Get_Height() - 10, DKGREY, TBLACK, TPF_6POINT|TPF_FULLSHADOW|TPF_RIGHT, VersionText); +#else + Fancy_Text_Print("V.%d%s", SeenBuff.Get_Width() - 1, SeenBuff.Get_Height() - 10, DKGREY, TBLACK, TPF_6POINT|TPF_FULLSHADOW|TPF_RIGHT, Version_Number(), VersionText); +#endif +#endif + display = false; + Show_Mouse(); + } + + /* + ** Display menu and fetch selection from player. + */ + if (Special.IsFromInstall && mem_info.dwTotalPhys >= 12*1024*1024){ + selection = SEL_START_NEW_GAME; + Theme.Queue_Song(THEME_NONE); + } + + /* + ** Handle case where we were spawned from Wchat + */ + if (Special.IsFromWChat && DDEServer.Get_MPlayer_Game_Info()){ + Check_From_WChat(NULL); + selection = SEL_MULTIPLAYER_GAME; + Theme.Queue_Song(THEME_NONE); + GameToPlay = GAME_INTERNET; + }else{ + /* + ** We werent spawned but we could still receive a DDE packet from wchat + */ + if (DDEServer.Get_MPlayer_Game_Info()){ + Check_From_WChat(NULL); + /* + ** Make sure top and bottom of screen are clear in 640x480 mode + */ + if (ScreenHeight == 480){ + VisiblePage.Fill_Rect (0, 0, 639, 40, 0); + VisiblePage.Fill_Rect (0, 440, 639, 479, 0); + } + } + } + + if (selection == SEL_NONE) { +// selection = Main_Menu(0); + selection = Main_Menu(ATTRACT_MODE_TIMEOUT); + } + Call_Back(); + + switch (selection) { + +#ifdef NEWMENU + + case SEL_INTERNET: + /* + ** Only call up the internet menu code if we dont already have connect info from WChat + */ + if (!DDEServer.Get_MPlayer_Game_Info()){ + CCDebugString ("C&C95 - About to call Internet Menu.\n"); + if (Do_The_Internet_Menu_Thang() && DDEServer.Get_MPlayer_Game_Info()){ + CCDebugString ("C&C95 - About to call Check_From_WChat.\n"); + Check_From_WChat(NULL); + selection = SEL_MULTIPLAYER_GAME; + display = false; + GameToPlay = GAME_INTERNET; + }else{ + selection = SEL_NONE; + display = true; + } + }else{ + CCDebugString ("C&C95 - About to call Check_From_WChat.\n"); + Check_From_WChat(NULL); + display = false; + GameToPlay = GAME_INTERNET; + selection = SEL_MULTIPLAYER_GAME; + } + break; + + + /* + ** Pick an expansion scenario. + */ + case SEL_NEW_SCENARIO: + CarryOverMoney = 0; + if (Expansion_Dialog()) { + Theme.Fade_Out(); +// Theme.Queue_Song(THEME_AOI); + GameToPlay = GAME_NORMAL; + process = false; + } else { + display = true; + selection = SEL_NONE; + } + break; + + +#ifdef BONUS_MISSIONS + + /* + ** User selected to play a bonus scenario. + */ + case SEL_BONUS_MISSIONS: + CarryOverMoney = 0; + + /* + ** Ensure that CD1 or CD2 is in the drive. These missions + ** are not on the covert CD. + */ + cd_index = Get_CD_Index(CCFileClass::Get_CD_Drive(), 1*60); + /* + ** If cd_index == 2 then its a covert CD + */ + if (cd_index == 2){ + RequiredCD = 0; + if (!Force_CD_Available (RequiredCD)){ + Prog_End(); + exit(EXIT_FAILURE); + } + } + + if (Bonus_Dialog()) { + Theme.Fade_Out(); + GameToPlay = GAME_NORMAL; + process = false; + } else { + display = true; + selection = SEL_NONE; + } + break; + + +#endif //BONUS_MISSIONS + +#endif + + /* + ** SEL_START_NEW_GAME: Play the game + */ + case SEL_START_NEW_GAME: + CarryOverMoney = 0; + +#ifdef DEMO + Hide_Mouse(); + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + Load_Title_Screen("PREPICK.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Fade_Palette_To(Palette, FADE_PALETTE_MEDIUM, Call_Back); + Clear_KeyBuffer(); + while (!Check_Key_Num()) { + Call_Back(); + } + Get_Key_Num(); + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + Show_Mouse(); + + Scenario = 1; + BuildLevel = 1; +#else + Scenario = 1; + BuildLevel = 1; +#endif + ScenPlayer = SCEN_PLAYER_GDI; + ScenDir = SCEN_DIR_EAST; + Whom = HOUSE_GOOD; + +#ifndef DEMO + Theme.Fade_Out(); + Choose_Side(); +#endif + + /* + ** If user is playing special mode, do NOT change Whom; leave it set to + ** GDI or NOD. Ini.cpp will set the player's ActLike to mirror the + ** Whom value. + */ + if (Special.IsJurassic && AreThingiesEnabled) { + ScenPlayer = SCEN_PLAYER_JP; + ScenDir = SCEN_DIR_EAST; + } + + GameToPlay = GAME_NORMAL; + process = false; + break; + + /* + ** Load a saved game. + */ + case SEL_LOAD_MISSION: + if (LoadOptionsClass(LoadOptionsClass::LOAD).Process()) { + //Theme.Fade_Out(); + Theme.Queue_Song(THEME_AOI); + process = false; + gameloaded = true; + } else { + display = true; + selection = SEL_NONE; + } + break; + + /* + ** SEL_MULTIPLAYER_GAME: set 'GameToPlay' to NULL-modem, modem, or + ** network play. + */ + case SEL_MULTIPLAYER_GAME: + +#ifdef DEMO + Hide_Mouse(); + Set_Palette(BlackPalette); + Load_Title_Screen("DEMOPIC.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Fade_Palette_To(Palette, FADE_PALETTE_MEDIUM, Call_Back); + Clear_KeyBuffer(); + while (!Check_Key()) { + Call_Back(); + } + Get_Key(); + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + Show_Mouse(); + display = true; + fade = true; + selection = SEL_NONE; +#else + switch (GameToPlay) { + + /* + ** If 'GameToPlay' isn't already set up for a multiplayer game, + ** we must prompt the user for which type of multiplayer game + ** they want. + */ + case GAME_NORMAL: + GameToPlay = Select_MPlayer_Game(); + if (GameToPlay == GAME_NORMAL) { // 'Cancel' + display = true; + selection = SEL_NONE; + } + break; + + case GAME_NULL_MODEM: + case GAME_MODEM: + if ( NullModem.Num_Connections() ) { + NullModem.Init_Send_Queue(); + + if ((GameToPlay == GAME_NULL_MODEM && + ModemGameToPlay == MODEM_NULL_HOST) || + (GameToPlay == GAME_MODEM && + ModemGameToPlay == MODEM_DIALER) ) { + + if ( !Com_Scenario_Dialog() ) { + GameToPlay = Select_Serial_Dialog(); + if (GameToPlay == GAME_NORMAL) { // user hit Cancel + display = true; + selection = SEL_NONE; + } + } + } else { + if ( !Com_Show_Scenario_Dialog() ) { + GameToPlay = Select_Serial_Dialog(); + if (GameToPlay == GAME_NORMAL) { // user hit Cancel + display = true; + selection = SEL_NONE; + } + } + } + } else { + GameToPlay = Select_MPlayer_Game(); + if (GameToPlay == GAME_NORMAL) { // 'Cancel' + display = true; + selection = SEL_NONE; + } + } + break; + + + +#ifdef FORCE_WINSOCK + /* + ** Handle being spawned from WChat. Intermnet play based on IPX code now. + */ + case GAME_INTERNET: + CCDebugString ("C&C95 - case GAME_INTERNET:\n"); + if (Special.IsFromWChat){ + //MessageBox (NULL, "About to restore focus to C&C95", "C&C95", MB_OK); + CCDebugString ("C&C95 - About to give myself focus.\n"); + SetForegroundWindow ( MainWindow ); + ShowWindow ( MainWindow, ShowCommand ); + + CCDebugString ("C&C95 - About to initialise Winsock.\n"); + if (Winsock.Init()){ + CCDebugString ("C&C95 - About to read multiplayer settings.\n"); + Read_MultiPlayer_Settings (); + Server = PlanetWestwoodIsHost; + + CCDebugString ("C&C95 - About to set addresses.\n"); + Winsock.Set_Host_Address(PlanetWestwoodIPAddress); + + CCDebugString ("C&C95 - About to call Start_Server or Start_Client.\n"); + if (Server){ + ModemGameToPlay = INTERNET_HOST; + Winsock.Start_Server(); + }else{ + ModemGameToPlay = INTERNET_JOIN; + Winsock.Start_Client(); + } + + +//#if (0) + /* + ** Flush out any pending packets from a previous game. + */ + CCDebugString ("C&C95 - About to flush packet queue.\n"); + CCDebugString ("C&C95 - Allocating scrap memory.\n"); + char *temp_buffer = new char[1024]; + + CCDebugString ("C&C95 - Creating timer class instance.\n"); + CountDownTimerClass ptimer; + + CCDebugString ("C&C95 - Entering read loop.\n"); + while (Winsock.Read(temp_buffer, 1024)){ + + CCDebugString ("C&C95 - Discarding a packet.\n"); + ptimer.Set (30, true); + while (ptimer.Time()){}; + CCDebugString ("C&C95 - Ready to check for more packets.\n"); + + } + CCDebugString ("C&C95 - About to delete scrap memory.\n"); + delete temp_buffer; +//#endif //(0) + + + + }else{ + CCDebugString ("C&C95 - Winsock failed to initialise.\n"); + GameToPlay = GAME_NORMAL; + selection = SEL_EXIT; + Special.IsFromWChat = false; + break; + } + + CCDebugString ("C&C95 - About to call Init_Network.\n"); + Init_Network(); + + if (DDEServer.Get_MPlayer_Game_Info()){ + CCDebugString ("C&C95 - About to call Read_Game_Options.\n"); + Read_Game_Options( NULL ); + }else{ + Read_Game_Options( "C&CSPAWN.INI" ); + } + + if (Server){ + + CCDebugString ("C&C95 - About to call Server_Remote_Connect.\n"); + if (Server_Remote_Connect()){ + CCDebugString ("C&C95 - Server_Remote_Connect returned success.\n"); + break; + }else{ + /* + ** We failed to connect to the other player + * + * SEND FAILURE PACKET TO WCHAT HERE !!!!! + * + */ + Winsock.Close(); + GameToPlay = GAME_NORMAL; + selection = SEL_NONE; + DDEServer.Delete_MPlayer_Game_Info(); // Make sure we dont go round in an infinite loop + //Special.IsFromWChat = false; + break; + } + }else{ + CCDebugString ("C&C95 - About to call Client_Remote_Connect.\n"); + if (Client_Remote_Connect()){ + CCDebugString ("C&C95 - Client_Remote_Connect returned success.\n"); + break; + }else{ + /* + ** We failed to connect to the other player + * + * SEND FAILURE PACKET TO WCHAT HERE !!!!! + * + */ + Winsock.Close(); + GameToPlay = GAME_NORMAL; + selection = SEL_NONE; + DDEServer.Delete_MPlayer_Game_Info(); // Make sure we dont go round in an infinite loop + //Special.IsFromWChat = false; + break; + } + } + + }else{ + GameToPlay = Select_MPlayer_Game(); + if (GameToPlay == GAME_NORMAL) { // 'Cancel' + display = true; + selection = SEL_NONE; + } + } + break; + +#endif //FORCE_WINSOCK + + } + + + switch (GameToPlay) { + /* + ** Internet, Modem or Null-Modem + */ + case GAME_MODEM: + case GAME_NULL_MODEM: + case GAME_INTERNET: + Theme.Fade_Out(); + ScenPlayer = SCEN_PLAYER_2PLAYER; + ScenDir = SCEN_DIR_EAST; + process = false; + Options.ScoreVolume = 0; + break; + + + /* + ** Network (IPX): start a new network game. + */ + case GAME_IPX: + /* + ** Init network system & remote-connect + */ + if (Init_Network() && Remote_Connect()) { +#if (0) + char seed[80]; + sprintf (seed, "Seed: %08x\n", Seed); + CCDebugString (seed); + + sprintf (seed, "rand: %04x\n", rand()); + CCDebugString (seed); + + sprintf (seed, "rand: %04x\n", rand()); + CCDebugString (seed); + + sprintf (seed, "rand: %04x\n", rand()); + CCDebugString (seed); +#endif + Options.ScoreVolume = 0; + ScenPlayer = SCEN_PLAYER_MPLAYER; + ScenDir = SCEN_DIR_EAST; + process = false; + Theme.Fade_Out(); + } else { // user hit cancel, or init failed + GameToPlay = GAME_NORMAL; + display = true; + selection = SEL_NONE; + } + break; + } +#endif + break; + + /* + ** Play a VQ + */ + case SEL_INTRO: + Theme.Fade_Out(); + Theme.Stop(); + Call_Back(); + + Force_CD_Available(-1); + Play_Intro(false); + Hide_Mouse(); + +// verify existance of movie file before playing this sequence. + if (CCFileClass("TRAILER.VQA").Is_Available()) { + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + VisiblePage.Clear(); + if (CCFileClass("ATTRACT2.CPS").Is_Available()){ + Load_Uncompress(CCFileClass("ATTRACT2.CPS"), SysMemPage, SysMemPage, Palette); + SysMemPage.Scale(SeenBuff, 0, 0, 0, 0, 320, 199, 640, 398); + Fade_Palette_To(Palette, FADE_PALETTE_MEDIUM, Call_Back); + } + Clear_KeyBuffer(); + count.Set(TIMER_SECOND*3); + while (count.Time()) { + Call_Back(); + } + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + + Play_Movie("TRAILER"); // Red Alert teaser. + } + + if (CCFileClass("SIZZLE.VQA").Is_Available()) { + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + VisiblePage.Clear(); + if (CCFileClass("ATTRACT2.CPS").Is_Available()){ + Load_Uncompress(CCFileClass("ATTRACT2.CPS"), SysMemPage, SysMemPage, Palette); + SysMemPage.Scale(SeenBuff, 0, 0, 0, 0, 320, 199, 640, 398); + Fade_Palette_To(Palette, FADE_PALETTE_MEDIUM, Call_Back); + } + Clear_KeyBuffer(); + count.Set(TIMER_SECOND*3); + while (count.Time()) { + Call_Back(); + } + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + + Play_Movie("SIZZLE"); // Red Alert teaser. + } + + if (CCFileClass("SIZZLE2.VQA").Is_Available()) { + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + VisiblePage.Clear(); + if (CCFileClass("ATTRACT2.CPS").Is_Available()){ + Load_Uncompress(CCFileClass("ATTRACT2.CPS"), SysMemPage, SysMemPage, Palette); + SysMemPage.Scale(SeenBuff, 0, 0, 0, 0, 320, 199, 640, 398); + Fade_Palette_To(Palette, FADE_PALETTE_MEDIUM, Call_Back); + } + Clear_KeyBuffer(); + count.Set(TIMER_SECOND*3); + while (count.Time()) { + Call_Back(); + } + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + + Play_Movie("SIZZLE2"); // Red Alert teaser. + } + + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + VisiblePage.Clear(); + if (CCFileClass("ATTRACT2.CPS").Is_Available()){ + Load_Uncompress(CCFileClass("ATTRACT2.CPS"), SysMemPage, SysMemPage, Palette); + SysMemPage.Scale(SeenBuff, 0, 0, 0, 0, 320, 199, 640, 398); + Fade_Palette_To(Palette, FADE_PALETTE_MEDIUM, Call_Back); + } + Clear_KeyBuffer(); + count.Set(TIMER_SECOND*3); + while (count.Time()) { + Call_Back(); + } + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + + Play_Movie("CC2TEASE"); + Show_Mouse(); + + ScenarioInit++; + Theme.Play_Song (THEME_MAP1); + ScenarioInit--; + display = true; + fade = true; + selection = SEL_NONE; + break; + + /* + ** Exit to DOS. + */ + case SEL_EXIT: +#ifdef JAPANESE + Hide_Mouse(); +#endif + Theme.Fade_Out(); + Fade_Palette_To(BlackPalette, FADE_PALETTE_SLOW, NULL); +#ifdef JAPANESE + VisiblePage.Clear(); +#endif + return(false); + + /* + ** Display the hall of fame. + */ + case SEL_FAME: + break; + + case SEL_TIMEOUT: + if (AllowAttract && RecordFile.Is_Available()) { + PlaybackGame = true; + if (RecordFile.Open(READ)) { + Load_Recording_Values(); + process = false; + Theme.Fade_Out(); + } else { + PlaybackGame = false; + selection = SEL_NONE; + } + } else { + selection = SEL_NONE; + } + break; + + default: + break; + } + } + } else { + + /* + ** For Debug_Map (editor) mode, if JP option is on, set to load that scenario + */ + Scenario = 1; + if (Special.IsJurassic && AreThingiesEnabled) { + ScenPlayer = SCEN_PLAYER_JP; + ScenDir = SCEN_DIR_EAST; + } + } + CCDebugString ("C&C95 - About to start game initialisation.\n"); +#ifdef FORCE_WINSOCK + if (GameToPlay == GAME_INTERNET){ + CommProtocol = COMM_PROTOCOL_MULTI_E_COMP; + if (!Special.IsFromWChat){ + FrameSendRate = 5; //3; + } + } +#endif //FORCE_WINSOCK + /* + ** Don't carry stray keystrokes into game. + */ + Kbd.Clear(); + + /* + ** Get a pointer to the compiler's random number seed. + ** the Get_EAX() must follow immediately after the srand(0) in order to save + ** the address of the random seed. (Currently not used.) + */ + srand(0); + RandSeedPtr = (long *)Get_EAX(); + + /* + ** Initialize the random number Seed. For multiplayer, this will have been done + ** in the connection dialogs. For single-player games, AND if we're not playing + ** back a recording, init the Seed to a random value. + */ + if (GameToPlay == GAME_NORMAL && !PlaybackGame) { + randomize(); + Seed = rand(); + } + + /* + ** If user has specified a desired random number seed, use it for multiplayer games + */ + if (CustomSeed != 0) { + Seed = CustomSeed; + } + + /* + ** Save initialization values if we're recording this game. + ** This must be done after 'Seed' has been initialized. + */ + if (RecordGame) { + if (RecordFile.Open(WRITE)) { + Save_Recording_Values(); + } else { + RecordGame = false; + } + } + + /* + ** Initialize the random-number generator. + */ + //Seed = 1; + + srand(Seed); + RandNumb = Seed; + SimRandIndex = 0; +#if (0) + DWORD actual; + HANDLE sfile = CreateFile("random.txt", GENERIC_WRITE, 0, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if (sfile != INVALID_HANDLE_VALUE){ + SetFilePointer (sfile, 0, NULL, FILE_END); + + int minimum; + int maximum; + char whatever[80]; + for ( int i=0 ; i<1000 ; i++){ + minimum = rand(); + maximum = rand(); + + sprintf (whatever, "%04x\n", Random_Pick(minimum, maximum)); + WriteFile(sfile, whatever, strlen(whatever), &actual, NULL); + } + CloseHandle (sfile); + } +#endif + + + + /* + ** Load the scenario. Specify variation 'A' for the editor; for the game, + ** don't specify a variation, to make 'Set_Scenario_Name()' pick a random one. + ** Skip this if we've already loaded a save-game. + */ + if (!gameloaded) { + if (Debug_Map) { + Set_Scenario_Name(ScenarioName, Scenario, ScenPlayer, ScenDir, SCEN_VAR_A); + } else { + Set_Scenario_Name(ScenarioName, Scenario, ScenPlayer, ScenDir); + } + + /* + ** Start_Scenario() changes the palette; so, fade out & clear the screen + ** before calling it. + */ + Hide_Mouse(); + + if (selection != SEL_START_NEW_GAME) { + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + HiddenPage.Clear(); + VisiblePage.Clear(); + } + Show_Mouse(); + + Special.IsFromInstall = 0; + CCDebugString ("C&C95 - Starting scenario.\n"); + if (!Start_Scenario(ScenarioName)) { + return(false); + } + CCDebugString ("C&C95 - Scenario started OK.\n"); + } + + /* + ** For multiplayer games, initialize the inter-player message system. + ** Do this after loading the scenario, so the map's upper-left corner is + ** properly set. + */ + CCDebugString ("C&C95 - Initialising message system.\n"); + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + Messages.Init(Map.TacPixelX, Map.TacPixelY, 6, MAX_MESSAGE_LENGTH, 6*factor+1); + + /* + ** Hide the SeenBuff; force the map to render one frame. The caller can + ** then fade the palette in. + ** (If we loaded a game, this step will fade out the title screen. If we + ** started a scenario, Start_Scenario() will have played a couple of VQ + ** movies, which will have cleared the screen to black already.) + */ + CCDebugString ("C&C95 - About to call Call_Back.\n"); + Call_Back(); + + /* + ** This is desperately sad isnt it? + */ + Hide_Mouse(); + Hide_Mouse(); + Hide_Mouse(); + Hide_Mouse(); + WWMouse->Erase_Mouse(&HidPage, TRUE); + + + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + HiddenPage.Clear(); + VisiblePage.Clear(); + Set_Logic_Page(SeenBuff); + Map.Flag_To_Redraw(); + Call_Back(); + Map.Render(); + //Show_Mouse(); + + /* + ** Special hack initialization of 'MPlayerMaxAhead' to accommodate the + ** compression protocol technology. + */ +#ifdef FORCE_WINSOCK + if (CommProtocol==COMM_PROTOCOL_MULTI_E_COMP && GameToPlay!=GAME_NORMAL) { + if (!Special.IsFromWChat){ + MPlayerMaxAhead = FrameSendRate * 3; //2; + }else{ + MPlayerMaxAhead = WChatMaxAhead; + FrameSendRate = WChatSendRate; + } + } +#endif //FORCE_WINSOCK + + if (Debug_Map) { + while (Get_Mouse_State() > 1) { + Show_Mouse(); + } + } + + return(true); +} + + +/*********************************************************************************************** + * Play_Intro -- plays the introduction & logo movies * + * * + * INPUT: * + * for_real if true, this function plays the "real" intro; otherwise, it plays * + * a delicious smorgasbord of visual delights, guaranteed to titillate * + * the ocular & auditory nerve pathways. * + * Well, it plays movies, anyway. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/06/1995 BRR : Created. * + *=============================================================================================*/ +static void Play_Intro(bool for_real) +{ + bool playright = !Key_Down(KN_LCTRL) || !Key_Down(KN_RCTRL); + static int _counter = -1; + static char * _names[] = { +#ifdef DEMO + "LOGO", + +#else + + "INTRO2", +//#ifdef CHEAT_KEYS + "GDIEND1", + "GDIEND2", + "GDIFINA", + "GDIFINB", + "AIRSTRK", + "AKIRA", + "BANNER", + "BCANYON", + "BKGROUND", + "BOMBAWAY", + "BOMBFLEE", + "BURDET1", + "BURDET2", + "CC2TEASE", + "CONSYARD", + "DESFLEES", + "DESKILL", + "DESOLAT", + "DESSWEEP", + "FLAG", + "FLYY", + "FORESTKL", + "GAMEOVER", + "GDI1", + "GDI10", + "GDI11", + "GDI12", + "GDI13", + "GDI14", + "GDI15", + "GDI2", + "GDI3", + "GDI3LOSE", + "GDI4A", + "GDI4B", + "GDI5", + "GDI6", + "GDI7", + "GDI8A", + "GDI8B", + "GDI9", + "GDILOSE", + "GUNBOAT", + "HELLVALY", + "INSITES", + "KANEPRE", + "LANDING", + "LOGO", + "NAPALM", + "NITEJUMP", + "NOD1", + "NOD10A", + "NOD10B", + "NOD11", + "NOD12", + "NOD13", + "NOD1PRE", + "NOD2", + "NOD3", + "NOD4A", + "NOD4B", + "NOD5", + "NOD6", + "NOD7A", + "NOD7B", + "NOD8", + "NOD9", + "NODEND1", + "NODEND2", + "NODEND3", + "NODEND4", + "NODFINAL", + "NODFLEES", + "NODLOSE", + "NODSWEEP", + "NUKE", + "OBEL", + "PARATROP", + "PINTLE", + "PLANECRA", + "PODIUM", + "REFINT", + "RETRO", + "SABOTAGE", + "SAMDIE", + "SAMSITE", + "SEIGE", + "SETHPRE", + "SPYCRASH", + "STEALTH", + "SUNDIAL", + "TANKGO", + "TANKKILL", + "TBRINFO1", + "TBRINFO2", + "TBRINFO3", + "TIBERFX", + "TRTKIL_D", + "TURTKILL", + "VISOR", +//#endif +#endif + NULL + }; + + Keyboard::Clear(); + if (for_real) { + Hide_Mouse(); + Play_Movie("LOGO", THEME_NONE, false); + Show_Mouse(); + } else { + if (!Debug_Flag) { + _counter = 0; + } else { + if (playright) _counter++; + if (_counter == -1) _counter = 0; + } + Hide_Mouse(); + Play_Movie(_names[_counter], THEME_NONE); + Show_Mouse(); + if (!_names[_counter]) { + _counter = -1; + } + } +} + + +/*********************************************************************************************** + * Anim_Init -- Initialize the VQ animation control structure. * + * * + * VQ animations are controlled by a structure passed to the VQ player. This routine * + * initializes the structure to values required by C&C. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Only need to call this routine once at the beginning of the game. * + * * + * HISTORY: * + * 12/20/1994 JLB : Created. * + *=============================================================================================*/ +extern LPDIRECTSOUND SoundObject; +extern LPDIRECTSOUNDBUFFER PrimaryBufferPtr; +void Anim_Init(void) +{ + /* Configure player with INI file */ + VQA_DefaultConfig(&AnimControl); +// void const * font = Load_Font(FONT8); +// AnimControl.EVAFont = (char *)font; +// AnimControl.CapFont = (char *)font; + + AnimControl.DrawFlags = VQACFGF_TOPLEFT; + AnimControl.DrawFlags |= VQACFGF_BUFFER; + + AnimControl.DrawFlags |= VQACFGF_NOSKIP; + + //AnimControl.X1 =0; + //AnimControl.Y1 =0; + AnimControl.FrameRate = -1; + AnimControl.DrawRate = -1; + + AnimControl.DrawerCallback = VQ_Call_Back; + AnimControl.ImageWidth = 320; + AnimControl.ImageHeight = 200; + AnimControl.Vmode = 0; + AnimControl.ImageBuf = (unsigned char *)SysMemPage.Get_Offset(); + //AnimControl.VBIBit = VertBlank; + //AnimControl.DrawFlags |= VQACFGF_TOPLEFT; + AnimControl.OptionFlags |= VQAOPTF_CAPTIONS|VQAOPTF_EVA; + + if (SlowPalette) { + AnimControl.OptionFlags |= VQAOPTF_SLOWPAL; + } + +// AnimControl.AudioBuf = (unsigned char *)HidPage.Get_Buffer(); +// AnimControl.AudioBufSize = 32768U; + //AnimControl.DigiCard = NewConfig.DigitCard; + //AnimControl.HMIBufSize = 8192; + //AnimControl.DigiHandle = Get_Digi_Handle(); + //AnimControl.Volume = 0x00FF; + //AnimControl.AudioRate = 22050; +// if (NewConfig.Speed) AnimControl.AudioRate = 11025; + AnimControl.SoundObject = SoundObject; //Get_Sound_Object(); + AnimControl.PrimaryBufferPtr = PrimaryBufferPtr; //Get_Primart_Buffer(); + //if (!Debug_Quiet && Get_Digi_Handle() != -1) { + //AnimControl.OptionFlags |= VQAOPTF_AUDIO; + //} + + if (MonoClass::Is_Enabled()) { + AnimControl.OptionFlags |= VQAOPTF_MONO; + } +} + + +/*********************************************************************************************** + * Parse_Command_Line -- Parses the command line parameters. * + * * + * This routine should be called before the graphic mode is initialized. It examines the * + * command line parameters and sets the appropriate globals. If there is an error, then * + * it outputs a command summary and then returns false. * + * * + * INPUT: argc -- The number of command line arguments. * + * * + * argv -- Pointer to character string array that holds the individual arguments. * + * * + * OUTPUT: bool; Was the command line parsed successfully? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/18/1995 JLB : Created. * + *=============================================================================================*/ +bool Parse_Command_Line(int argc, char *argv[]) +{ + /* + ** Parse the command line and set globals to reflect the parameters + ** passed in. + */ +#ifdef DEMO + Scenario = 3; +#else + Scenario = 1; +#endif + ScenPlayer = SCEN_PLAYER_GDI; + ScenDir = SCEN_DIR_EAST; + Whom = HOUSE_GOOD; + Special.Init(); + + Debug_Map = false; +// Debug_Play_Map = false; + Debug_Unshroud = false; + + for (int index = 1; index < argc; index++) { + char * string; // Pointer to argument. + long code = 0; + + string = strupr(argv[index]); + + /* + ** Print usage text only if requested. + */ + if (stricmp("/?", string) == 0 || stricmp("-?", string) == 0 || stricmp("-h", string) == 0 || stricmp("/h", string) == 0) { + /* + ** Unrecognized command line parameter... Display usage + ** and then exit. + */ + #ifdef GERMAN + puts("Command & Conquer (c) 1995,1996 Westwood Studios\r\n" + "Parameter:\r\n" +// " -CD = Suchpfad fr Daten-Dateien festlegen.\r\n" + " -DESTNET = Netzwerkkennung des Zielrechners festlegen\r\n" + " (Syntax: DESTNETxx.xx.xx.xx)\r\n" + " -SOCKET = Kennung des Netzwerk-Sockets (0 - 16383)\n" + " -STEALTH = Namen im Mehrspieler-Modus verstecken (\"Boss-Modus\")\r\n" + " -MESSAGES = Mitteilungen von auáerhalb des Spiels zulassen\r\n" + // " -ELITE = Fortgeschrittene KI und Gefechtstechniken.\r\n" + "\r\n"); + #else + #ifdef FRENCH + puts("Command & Conquer (c) 1995, Westwood Studios\r\n" + "ParamŠtres:\r\n" +// " -CD = Recherche des fichiers dans le\r\n" +// " r‚pertoire indiqu‚.\r\n" + " -DESTNET = Sp‚cifier le num‚ro de r‚seau du systŠme de destination\r\n" + " (Syntaxe: DESTNETxx.xx.xx.xx)\r\n" + " -SOCKET = ID poste r‚seau (0 … 16383)\r\n" + " -STEALTH = Cacher les noms en mode multijoueurs (\"Mode Boss\")\r\n" + " -MESSAGES = Autorise les messages ext‚rieurs … ce jeu.\r\n" + "\r\n"); + #else + puts("Command & Conquer (c) 1995, 1996 Westwood Studios\r\n" + "Parameters:\r\n" + #ifdef NEVER + " CHEAT = Enable debug keys.\r\n" + " -EDITOR = Enable scenario editor.\r\n" + #endif +// " -CD = Set search path for data files.\r\n" + " -DESTNET = Specify Network Number of destination system\r\n" + " (Syntax: DESTNETxx.xx.xx.xx)\r\n" + " -SOCKET = Network Socket ID (0 - 16383)\n" + " -STEALTH = Hide multiplayer names (\"Boss mode\")\r\n" + " -MESSAGES = Allow messages from outside this game.\r\n" + " -o = Enable compatability with version 1.07.\r\n" +#ifdef JAPANESE + " -ENGLISH = Enable English keyboard compatibility.\r\n" +#endif + // " -ELITE = Advanced AI and combat characteristics.\r\n" + #ifdef NEVER + " -O[options]= Special control options;\r\n" + " 1 : Tiberium grows.\r\n" + " 2 : Tiberium grows and spreads.\r\n" + " A : Aggressive player unit defense enabled.\r\n" + " B : Bargraphs always displayed.\r\n" + " C : Capture the flag mode.\r\n" + " E : Elite defense mode disable (attacker advantage).\r\n" + " D : Deploy reversal allowed for construction yard.\r\n" + " F : Fleeing from direct immediate threats is enabled.\r\n" + " H : Hussled recharge time.\r\n" + " G : Growth for Tiberium slowed in multiplay.\r\n" + " I : Inert weapons -- no damage occurs.\r\n" + " J : 7th grade sound effects.\r\n" + " M : Monochrome debug messages.\r\n" + " N : Name the civilians and buildings.\r\n" + " P : Path algorithm displayed as it works.\r\n" + " Q : Quiet mode (no sound).\r\n" + " R : Road pieces are not added to buildings.\r\n" + " T : Three point turns for wheeled vehicles.\r\n" + " U : U can target and burn trees.\r\n" + " V : Show target selection by opponent.\r\n" + " X : Make a recording of a multiplayer game.\r\n" + " Y : Play a recording of a multiplayer game.\r\n" + " Z : Disaster containment team.\r\n" + #endif + "\r\n"); + #endif + #endif + return(false); + } + + + bool processed = true; + switch (Obfuscate(string)) { + + /* + ** Signal that easy mode is active. + */ + case PARM_EASY: + Special.IsHealthBar = true; + Special.IsEasy = true; + Special.IsDifficult = false; + break; + + /* + ** Signal that hard mode is active. + */ + case PARM_HARD: + Special.IsHealthBar = false; + Special.IsEasy = false; + Special.IsDifficult = true; + break; + +#ifdef VIRGIN_CHEAT_KEYS + case PARM_PLAYTEST: + Debug_Playtest = true; + break; +#endif + +#ifdef PARM_CHEATERIK + case PARM_CHEATERIK: + Debug_Playtest = true; + Debug_Flag = true; + break; +#endif + +#ifdef PARM_CHEATADAM + case PARM_CHEATADAM: + Debug_Playtest = true; + Debug_Flag = true; + break; +#endif + +#ifdef PARM_CHEATMIKE + case PARM_CHEATMIKE: + Debug_Playtest = true; + Debug_Flag = true; + break; +#endif + +#ifdef PARM_CHEATDAVID + case PARM_CHEATDAVID: + Debug_Playtest = true; + Debug_Flag = true; + break; +#endif + +#ifdef PARM_CHEATPHIL + case PARM_CHEATPHIL: + Debug_Playtest = true; + Debug_Flag = true; + break; +#endif + +#ifdef PARM_CHEATBILL + case PARM_CHEATBILL: + Debug_Playtest = true; + Debug_Flag = true; + break; +#endif + +#ifdef PARM_CHEAT_STEVET + + case PARM_CHEAT_STEVET: + Debug_Playtest = true; + Debug_Flag = true; + break; + +#endif + +#ifdef PARM_EDITORBILL + case PARM_EDITORBILL: + Debug_Map = true; + Debug_Unshroud = true; + Debug_Flag = true; + break; +#endif + +#ifdef PARM_EDITORERIK + case PARM_EDITORERIK: + Debug_Map = true; + Debug_Unshroud = true; + Debug_Flag = true; + break; +#endif + + case PARM_SPECIAL: + Special.IsJurassic = true; + AreThingiesEnabled = true; + break; + + /* + ** Special flag - is C&C being run from the install program? + */ + case PARM_INSTALL: +#ifndef DEMO + Special.IsFromInstall = true; +#endif + break; + + default: + processed = false; + break; + } + if (processed) continue; + + +#ifdef CHEAT_KEYS + /* + ** Scenario Editor Mode + */ + if (stricmp(string, "-CHECKMAP") == 0) { + Debug_Check_Map = true; + continue; + } + +#endif + + /* + ** Older version override. + */ + if (stricmp(string, "-O") == 0 || stricmp(string, "-0") == 0) { + IsV107 = true; + continue; + } + + /* + ** File search path override. + */ + if (strstr(string, "-CD")) { + CCFileClass::Set_Search_Drives(&string[3]); + continue; + } +#ifdef JAPANESE + /* + ** Enable english-compatible keyboard + */ + if (!stricmp(string,"-ENGLISH")) { + ForceEnglish = true; + continue; + } +#endif + + /* + ** Specify destination connection for network play + */ + if (strstr(string, "-DESTNET")) { + NetNumType net; + NetNodeType node; + + /* + ** Scan the command-line string, pulling off each address piece + */ + int i = 0; + char * p = strtok(string + 8,"."); + while (p) { + int x; + + sscanf(p,"%x",&x); // convert from hex string to int + if (i < 4) { + net[i] = (char)x; // fill NetNum + } else { + node[i-4] = (char)x; // fill NetNode + } + i++; + p = strtok(NULL,"."); + } + + /* + ** If all the address components were successfully read, fill in the + ** BridgeNet with a broadcast address to the network across the bridge. + */ + if (i >= 4) { + IsBridge = 1; + memset(node, 0xff, 6); + BridgeNet = IPXAddressClass(net, node); + } + continue; + } + + /* + ** Specify socket ID, as an offset from 0x4000. + */ + if (strstr(string, "-SOCKET")) { + unsigned short socket; + + socket = (unsigned short)(atoi(string + strlen("SOCKET"))); + socket += 0x4000; + if (socket >= 0x4000 && socket < 0x8000) { + Ipx.Set_Socket (socket); + } + continue; + } + + /* + ** Set the Net Stealth option + */ + if (strstr(string, "-STEALTH")) { + NetStealth = true; + continue; + } + + /* + ** Set the Net Protection option + */ + if (strstr(string, "-MESSAGES")) { + NetProtect = false; + continue; + } + + /* + ** Allow "attract" mode + */ + if (strstr(string, "-ATTRACT")) { + AllowAttract = true; + continue; + } + + /* + ** Set screen to 640x480 instead of 640x400 + */ + if (strstr(string,"-480")){ + ScreenHeight = 480; + continue; + } + + /* + ** Check for spawn from WChat + */ + if (strstr(string,"-WCHAT")){ + SpawnedFromWChat = true; + } + + /* + ** Allow use of MMX instructions + */ + if (strstr(string,"-MMX")){ + MMXAvailable = true; + continue; + } + + +#ifdef CHEAT_KEYS + /* + ** Allow solo net play + */ + if (stricmp(string, "-HANSOLO") == 0) { + MPlayerSolo = true; + continue; + } + + /* + ** Specify the random number seed (for debugging) + */ + if (strstr(string, "-SEED")) { + CustomSeed = (unsigned short)(atoi(string + strlen("SEED"))); + continue; + } +#endif + +#ifdef NEVER + /* + ** Handle the prog init differently in this case. + */ + if (strstr(string, "-V")) { + continue; + } +#endif + +#ifdef FIX_ME_LATER + /* + ** look for passed-in video mode to default to + */ + if (strnicmp(string, "-V", strlen("-V")) == 0) { + Set_Video_Mode(MCGA_MODE); // do this to get around first_time variable... + Set_Original_Video_Mode(atoi(string+2)); + continue; + } +#endif //FIX_ME_LATER + + /* + ** Special command line control parsing. + */ + if (strnicmp(string, "-X", strlen("-O")) == 0) { + string += strlen("-X"); + while (*string) { + char code = *string++; + switch (toupper(code)) { + +#ifdef ONHOLD + /* + ** Should human generated sound effects be used? + */ + case 'J': + Special.IsJuvenile = true; + break; +#endif + +#ifdef CHEAT_KEYS + /* + ** Monochrome debug screen enable. + */ + case 'M': + Special.IsMonoEnabled = true; + break; + + /* + ** Inert weapons -- no units take damage. + */ + case 'I': + Special.IsInert = true; + break; +#endif + +#ifdef CHEAT_KEYS + /* + ** Hussled recharge timer. + */ + case 'H': + Special.IsSpeedBuild = true; + break; + + /* + ** Turn on super-record mode, which thrashes your disk terribly, + ** but is really really cool. Well, sometimes it is, anyway. + ** At least, it can be. Once in a while. + ** This flag tells the recording system to re-open the file for + ** each write, so the recording survives a crash. + */ + case 'S': + SuperRecord = 1; + break; + + /* + ** "Record" a multi-player game + */ + case 'X': + RecordGame = 1; + break; + + /* + ** "Play Back" a multi-player game + */ + case 'Y': + PlaybackGame = 1; + break; +#endif + +#ifdef ONHOLD + /* + ** Bonus scenario enable. + */ + case 'Z': + Special.IsJurassic = true; + break; +#endif + + /* + ** Quiet mode override control. + */ + case 'Q': + Debug_Quiet = true; + break; + +#ifdef CHEAT_KEYS + /* + ** Target selection by human opponent (network/modem play) will + ** be visible to the player? + */ + case 'V': + Special.IsVisibleTarget = true; + break; +#endif + + default: +#ifdef GERMAN + puts("Ungltiger Parameter.\n"); +#else +#ifdef FRENCH + puts("Commande d'option invalide.\n"); +#else + puts("Invalid option switch.\n"); +#endif +#endif + return(false); + } + + } + + if (Special.IsMonoEnabled) { + MonoClass::Enable(); + } + continue; + } + } + return(true); +} + + +#ifdef ONHOLD +/*********************************************************************************************** + * Parse_INI_File -- Parses CONQUER.INI for certain options * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/18/1995 BRR : Created. * + *=============================================================================================*/ +void Parse_INI_File(void) +{ + char *buffer; // INI staging buffer pointer. + char buf[128]; + static char section[40]; + static char entry[40]; + static char name[40]; + int len; + int i; + + /* + ** These arrays store the coded version of the names Geologic, Period, & Jurassic. + ** Decode them by subtracting 83. For you curious types, the names look like: + ** š¸Â¿Âº¼¶ + ** £¸Å¼Â· + ** ÈÅ´ÆƼ¶ + ** If these INI entries aren't found, the IsJurassic flag does nothing. + */ + static char coded_section[] = {154,184,194,191,194,186,188,182,0}; + static char coded_entry[] = {163,184,197,188,194,183,0}; + static char coded_name[] = {157,200,197,180,198,198,188,182,0}; + + /*------------------------------------------------------------------------ + Fetch working pointer to the INI staging buffer. Make sure that the buffer + is cleared out before proceeding. + ------------------------------------------------------------------------*/ + buffer = (char *)_ShapeBuffer; + memset(buffer, '\0', _ShapeBufferSize); + + /*------------------------------------------------------------------------ + Decode the desired section, entry, & name + ------------------------------------------------------------------------*/ + strcpy (section,coded_section); + len = strlen(coded_section); + for (i = 0; i < len; i++) { + section[i] -= 83; + } + + strcpy (entry,coded_entry); + len = strlen(coded_entry); + for (i = 0; i < len; i++) { + entry[i] -= 83; + } + + strcpy (name,coded_name); + len = strlen(coded_name); + for (i = 0; i < len; i++) { + name[i] -= 83; + } + + /*------------------------------------------------------------------------ + Create filename and read the file. + ------------------------------------------------------------------------*/ + CCFileClass file ("CONQUER.INI"); + if (!file.Is_Available()) { + return; + } else { + file.Read(buffer, _ShapeBufferSize-1); + } + file.Close(); + + WWGetPrivateProfileString(section, entry, "", buf, sizeof(buf), buffer); + + if (!stricmp (buf,name)) + AreThingiesEnabled = true; + + memset (section, 0, sizeof(section)); + memset (entry, 0, sizeof(entry)); + memset (name, 0, sizeof(name)); +} +#endif + + +/*********************************************************************************************** + * Version_Number -- Determines the version number. * + * * + * This routine will determine the version number by analyzing the date and teim that the * + * program was compiled and then generating a unique version number based on it. The * + * version numbers are guaranteed to be larger for later dates. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the version number. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/24/1995 JLB : Created. * + *=============================================================================================*/ +int Version_Number(void) +{ +#ifdef OBSOLETE + static bool initialized = false; + static int version; + static char * date = __DATE__; + static char * time = __TIME__; + static char const * months = "JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC"; + + if (!initialized) { + char * ptr; + char * tok; + + /* + ** Fetch the month and place in the first two digit positions. + */ + strupr(date); + tok = strtok(date, " "); + ptr = strstr(months, tok); + if (ptr) { + version = (((ptr - months) / 3)+1) * 10000; + } + + /* + ** Fetch the date and place that in the next two digit positions. + */ + tok = strtok(NULL, " "); + if (tok) { + version += atoi(tok) * 100; + } + + /* + ** Fetch the time and place that in the last two digit positions. + */ + tok = strtok(time, ": "); + if (tok) { + version += atoi(tok); + } + + + /* + ** Fetch the virgin text file (if present). + */ + RawFileClass file("VERSION.TXT"); + if (file.Is_Available()) { + file.Read(VersionText, sizeof(VersionText)); + VersionText[sizeof(VersionText)-1] = '\0'; + while (VersionText[sizeof(VersionText)-1] == '\r') { + VersionText[sizeof(VersionText)-1] = '\0'; + } + } else { + VersionText[0] = '\0'; + } + + initialized = true; + } + return(version); +#endif + +#ifdef WIN32 + +#if (FRENCH) + sprintf(VersionText, ".02"); // Win95 french version number +#endif //FRENCH + +#if (GERMAN) + sprintf(VersionText, ".01"); // Win95 german version number +#endif //GERMAN + +#if (JAPANESE) + sprintf(VersionText, ".01"); // Win95 german version number +#endif //GERMAN + +#if !(FRENCH | GERMAN | JAPANESE) + sprintf(VersionText, ".07"); // Win95 USA version number +#endif //FRENCH | GERMAN + + RawFileClass file("VERSION.TXT"); + char version [16]; + memset (version, 0, sizeof (version)); + if (file.Is_Available()){ + file.Read (version, sizeof (version)); + } + strncat (VersionText, version, sizeof (VersionText) - strlen (VersionText) - 1); + +#if (FRENCH) + return (1); // Win95 french version number +#endif //FRENCH + +#if (GERMAN) + return (1); // Win95 german version number +#endif //GERMAN + +#if (JAPANESE) + return (1); // Win95 german version number +#endif //GERMAN + +#if !(FRENCH | GERMAN | JAPANESE) + return (1); // Win95 USA version number +#endif //FRENCH | GERMAN + +#else + + +#ifdef PATCH + + #ifdef DEMO + sprintf(VersionText, " 1.0a"); // Demo version. + #else + strcpy(VersionText, ".34 "); + #endif + return(1); + +#else + + #ifdef DEMO + sprintf(VersionText, " 1.0a"); // Demo version. + #else + // sprintf(VersionText, ".%02dp", 13); // Patch version. + sprintf(VersionText, ".%02d", 14); // Master version. + #endif + return(1); +#endif + +#endif //WIN32 +} + + +/*************************************************************************** + * Save_Recording_Values -- Saves recording values to a recording file * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/15/1995 BRR : Created. * + *=========================================================================*/ +void Save_Recording_Values(void) +{ + RecordFile.Write(&GameToPlay, sizeof(GameToPlay)); + RecordFile.Write(&ModemGameToPlay, sizeof(ModemGameToPlay)); + RecordFile.Write(&BuildLevel, sizeof(BuildLevel)); + RecordFile.Write(MPlayerName, sizeof(MPlayerName)); + RecordFile.Write(&MPlayerPrefColor, sizeof(MPlayerPrefColor)); + RecordFile.Write(&MPlayerColorIdx, sizeof(MPlayerColorIdx)); + RecordFile.Write(&MPlayerHouse, sizeof(MPlayerHouse)); + RecordFile.Write(&MPlayerLocalID, sizeof(MPlayerLocalID)); + RecordFile.Write(&MPlayerCount, sizeof(MPlayerCount)); + RecordFile.Write(&MPlayerBases, sizeof(MPlayerBases)); + RecordFile.Write(&MPlayerCredits, sizeof(MPlayerCredits)); + RecordFile.Write(&MPlayerTiberium, sizeof(MPlayerTiberium)); + RecordFile.Write(&MPlayerGoodies, sizeof(MPlayerGoodies)); + RecordFile.Write(&MPlayerGhosts, sizeof(MPlayerGhosts)); + RecordFile.Write(&MPlayerUnitCount, sizeof(MPlayerUnitCount)); + RecordFile.Write(MPlayerID, sizeof(MPlayerID)); + RecordFile.Write(MPlayerHouses, sizeof(MPlayerHouses)); + RecordFile.Write(&Seed, sizeof(Seed)); + RecordFile.Write(&Scenario, sizeof(Scenario)); + RecordFile.Write(&ScenPlayer, sizeof(ScenPlayer)); + RecordFile.Write(&ScenDir, sizeof(ScenDir)); + RecordFile.Write(&Whom, sizeof(Whom)); + RecordFile.Write(&Special, sizeof(SpecialClass)); + RecordFile.Write(&Options, sizeof(GameOptionsClass)); + RecordFile.Write(&FrameSendRate, sizeof(FrameSendRate)); + RecordFile.Write(&CommProtocol, sizeof(CommProtocol)); + + if (SuperRecord) { + RecordFile.Close(); + } +} + + +/*************************************************************************** + * Load_Recording_Values -- Loads recording values from recording file * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/15/1995 BRR : Created. * + *=========================================================================*/ +void Load_Recording_Values(void) +{ + Read_MultiPlayer_Settings(); + + RecordFile.Read(&GameToPlay, sizeof(GameToPlay)); + RecordFile.Read(&ModemGameToPlay, sizeof(ModemGameToPlay)); + RecordFile.Read(&BuildLevel, sizeof(BuildLevel)); + RecordFile.Read(MPlayerName, sizeof(MPlayerName)); + RecordFile.Read(&MPlayerPrefColor, sizeof(MPlayerPrefColor)); + RecordFile.Read(&MPlayerColorIdx, sizeof(MPlayerColorIdx)); + RecordFile.Read(&MPlayerHouse, sizeof(MPlayerHouse)); + RecordFile.Read(&MPlayerLocalID, sizeof(MPlayerLocalID)); + RecordFile.Read(&MPlayerCount, sizeof(MPlayerCount)); + RecordFile.Read(&MPlayerBases, sizeof(MPlayerBases)); + RecordFile.Read(&MPlayerCredits, sizeof(MPlayerCredits)); + RecordFile.Read(&MPlayerTiberium, sizeof(MPlayerTiberium)); + RecordFile.Read(&MPlayerGoodies, sizeof(MPlayerGoodies)); + RecordFile.Read(&MPlayerGhosts, sizeof(MPlayerGhosts)); + RecordFile.Read(&MPlayerUnitCount, sizeof(MPlayerUnitCount)); + RecordFile.Read(MPlayerID, sizeof(MPlayerID)); + RecordFile.Read(MPlayerHouses, sizeof(MPlayerHouses)); + RecordFile.Read(&Seed, sizeof(Seed)); + RecordFile.Read(&Scenario, sizeof(Scenario)); + RecordFile.Read(&ScenPlayer, sizeof(ScenPlayer)); + RecordFile.Read(&ScenDir, sizeof(ScenDir)); + RecordFile.Read(&Whom, sizeof(Whom)); + RecordFile.Read(&Special, sizeof(SpecialClass)); + RecordFile.Read(&Options, sizeof(GameOptionsClass)); + RecordFile.Read(&FrameSendRate, sizeof(FrameSendRate)); + RecordFile.Read(&CommProtocol, sizeof(CommProtocol)); +} + + +/*********************************************************************************************** + * Obfuscate -- Sufficiently transform parameter to thwart casual hackers. * + * * + * This routine borrows from CRC and PGP technology to sufficiently alter the parameter * + * in order to make it difficult to reverse engineer the key phrase. This is designed to * + * be used for hidden game options that will be released at a later time over Westwood's * + * Web page or through magazine hint articles. * + * * + * Since this is a one way transformation, it becomes much more difficult to reverse * + * engineer the pass phrase even if the resultant pass code is known. This has an added * + * benefit of making this algorithm immune to traditional cyrptographic attacks. * + * * + * The largest strength of this transformation algorithm lies in the restriction on the * + * source vector being legal ASCII uppercase characters. This restriction alone makes even * + * a simple CRC transformation practically impossible to reverse engineer. This algorithm * + * uses far more than a simple CRC transformation to achieve added strength from advanced * + * attack methods. * + * * + * INPUT: string -- Pointer to the key phrase that will be transformed into a code. * + * * + * OUTPUT: Returns with the code that the key phrase is translated into. * + * * + * WARNINGS: A zero length pass phrase results in a 0x00000000 result code. * + * * + * HISTORY: * + * 08/19/1995 JLB : Created. * + *=============================================================================================*/ +long Obfuscate(char const * string) +{ + char buffer[128]; + + if (!string) return(0); + memset(buffer, '\xA5', sizeof(buffer)); + + /* + ** Copy key phrase into a working buffer. This hides any transformation done + ** to the string. + */ + strncpy(buffer, string, sizeof(buffer)); + buffer[sizeof(buffer)-1] = '\0'; + int length = strlen(buffer); + + /* + ** Only upper case letters are significant. + */ + strupr(buffer); + + /* + ** Ensure that only visible ASCII characters compose the key phrase. This + ** discourages the direct forced illegal character input method of attack. + */ + for (int index = 0; index < length; index++) { + if (!isgraph(buffer[index])) { + buffer[index] = 'A' + (index%26); + } + } + + /* + ** Increase the strength of even short pass phrases by extending the + ** length to be at least a minimum number of characters. This helps prevent + ** a weak pass phrase from compromising the obfuscation process. This + ** process also forces the key phrase to be an even multiple of four. + ** This is necessary to support the cypher process that occurs later. + */ + if (length < 16 || (length & 0x03)) { + int maxlen = 16; + if (((length+3) & 0x00FC) > maxlen) { + maxlen = ((length+3) & 0x00FC); + } + for (index = length; index < maxlen; index++) { + buffer[index] = 'A' + ((('?' ^ buffer[index-length]) + index) % 26); + } + length = index; + buffer[length] = '\0'; + } + + /* + ** Transform the buffer into a number. This transformation is character + ** order dependant. + */ + long code = Calculate_CRC(buffer, length); + + /* + ** Record a copy of this initial transformation to be used in a later + ** self referential transformation. + */ + long copy = code; + + /* + ** Reverse the character string and combine with the previous transformation. + ** This doubles the workload of trying to reverse engineer the CRC calculation. + */ + strrev(buffer); + code ^= Calculate_CRC(buffer, length); + + /* + ** Perform a self referential transformation. This makes a reverse engineering + ** by using a cause and effect attack more difficult. + */ + code = code ^ copy; + + /* + ** Unroll and combine the code value into the pass phrase and then perform + ** another self referential transformation. Although this is a trivial cypher + ** process, it gives the sophisticated hacker false hope since the strong + ** cypher process occurs later. + */ + strrev(buffer); // Restore original string order. + for (index = 0; index < length; index++) { + code ^= (unsigned char)buffer[index]; + unsigned char temp = (unsigned char)code; + buffer[index] ^= temp; + code >>= 8; + code |= (((long)temp)<<24); + } + + /* + ** Introduce loss into the vector. This strengthens the key against traditional + ** cryptographic attack engines. Since this also weakens the key against + ** unconventional attacks, the loss is limited to less than 10%. + */ + for (index = 0; index < length; index++) { + static unsigned char _lossbits[] = {0x00,0x08,0x00,0x20,0x00,0x04,0x10,0x00}; + static unsigned char _addbits[] = {0x10,0x00,0x00,0x80,0x40,0x00,0x00,0x04}; + + buffer[index] |= _addbits[index % (sizeof(_addbits)/sizeof(_addbits[0]))]; + buffer[index] &= ~_lossbits[index % (sizeof(_lossbits)/sizeof(_lossbits[0]))]; + } + + /* + ** Perform a general cypher transformation on the vector + ** and use the vector itself as the cypher key. This is a variation on the + ** cypher process used in PGP. It is a very strong cypher process with no known + ** weaknesses. However, in this case, the cypher key is the vector itself and this + ** opens up a weakness against attacks that have access to this transformation + ** algorithm. The sheer workload of reversing this transformation should be enough + ** to discourage even the most determined hackers. + */ + for (index = 0; index < length; index += 4) { + short key1 = buffer[index]; + short key2 = buffer[index+1]; + short key3 = buffer[index+2]; + short key4 = buffer[index+3]; + short val1 = key1; + short val2 = key2; + short val3 = key3; + short val4 = key4; + + val1 *= key1; + val2 += key2; + val3 += key3; + val4 *= key4; + + short s3 = val3; + val3 ^= val1; + val3 *= key1; + short s2 = val2; + val2 ^= val4; + val2 += val3; + val2 *= key3; + val3 += val2; + + val1 ^= val2; + val4 ^= val3; + + val2 ^= s3; + val3 ^= s2; + + buffer[index] = val1; + buffer[index+1] = val2; + buffer[index+2] = val3; + buffer[index+3] = val4; + } + + /* + ** Convert this final vector into a cypher key code to be + ** returned by this routine. + */ + code = Calculate_CRC(buffer, length); + + /* + ** Return the final code value. + */ + return(code); +} diff --git a/INTERNET.CPP b/INTERNET.CPP new file mode 100644 index 0000000..12e44f6 --- /dev/null +++ b/INTERNET.CPP @@ -0,0 +1,876 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/************************************************************************************* + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + ************************************************************************************* + * * + * Project Name : Command & Conquer - Red Alert * + * * + * File Name : INTERNET.CPP * + * * + * Programmer : Steve Tall * + * * + * Start Date : March 11th, 1996 * + * * + * Last Update : August 5th, 1996 [ST] * + * * + *-----------------------------------------------------------------------------------* + * Overview: * + * * + * Miscellaneous junk related to H2H internet connection. * + * * + *-----------------------------------------------------------------------------------* + * Functions: * + * Check_From_WChat -- Interprets start game packet from WChat * + * Read_Game_Options -- Read the game setup options from the wchat packet * + * Is_User_WChat_Registered -- retrieve the users wchat entry from registry * + * Spawn_WChat -- spawns or switches focus to wchat * + * Spawn_Registration_App -- spawns the C&C/Planet westwood registration app * + * Do_The_Internet_Menu_Thang -- Handle case where user clicks on 'Internet' button * + * * + * * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + +#include "function.h" +#include "tcpip.h" +#include "ccdde.h" + + + +/*************************************************************************** +** Internet specific globals +*/ +char PlanetWestwoodHandle[] = {"Handle"}; //Planet WW user name +char PlanetWestwoodPassword[] = {"Password"}; //Planet WW password +char PlanetWestwoodIPAddress[IP_ADDRESS_MAX] = {"206.154.108.87"}; //IP of server or other player +long PlanetWestwoodPortNumber = 1234; //Port number to send to +bool PlanetWestwoodIsHost = false; //Flag true if player has control of game options +unsigned long PlanetWestwoodGameID; //Game ID +unsigned long PlanetWestwoodStartTime; //Time that game was started +HWND WChatHWND = 0; //Handle to Wchat window. +bool UseVirtualSubnetServer; +int InternetMaxPlayers; +int WChatMaxAhead; +int WChatSendRate; + + +int Read_Game_Options(void); + +extern bool SpawnedFromWChat; + + + + + + +/*********************************************************************************************** + * Check_From_WChat -- This function reads in C&CSPAWN.INI and interprets it * + * C&CSPAWN.INI is now sent to us by WCHAT via DDE * + * * + * * + * * + * INPUT: Name of C&CSPAWN.INI file. If NULL then get file from DDE Server * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/9/96 1:44PM ST : Created * + *=============================================================================================*/ +void Check_From_WChat(char *wchat_name) +{ +#ifndef DEMO + + char default_string[] = {"Error"}; + char key_string[256]; + char *ini_file; + RawFileClass wchat_file; + + /* + ** Get a pointer to C&CSPAWN.INI either by reading it from disk or getting it from + ** the DDE server. + */ + if (wchat_name){ + ini_file = new char [8192]; + }else{ + ini_file = DDEServer.Get_MPlayer_Game_Info(); +#if (0) + /* + ** Save it to disk as well so I can see it + */ + RawFileClass anotherfile ("FROMCHAT.TXT"); + anotherfile.Write(ini_file, DDEServer.Get_MPlayer_Game_Info_Length()); +#endif //(0) + } + + if (wchat_name){ + wchat_file.Set_Name(wchat_name); + } + + if (!wchat_name || wchat_file.Is_Available()){ + + /* + ** Read the ini file from disk if we founf it there + */ + if (wchat_name){ + wchat_file.Read(ini_file, wchat_file.Size()); + } + + /* + ** Get the IP address + */ + key_string[0] = 0; + + WWGetPrivateProfileString("Internet", + "Address", + default_string, + key_string, + sizeof(key_string), + ini_file); + + + if (!strcmp (key_string, default_string)) { + if (wchat_name) delete ini_file; + return; + } + strcpy (PlanetWestwoodIPAddress, key_string); + + + + /* + ** Get the port number + */ + key_string[0] = 0; + + WWGetPrivateProfileString("Internet", + "Port", + default_string, + key_string, + sizeof(key_string), + ini_file); + + + if (!strcmp (key_string, default_string)) { + if (wchat_name) delete ini_file; + return; + } + + PlanetWestwoodPortNumber = atol(key_string); + + + /* + ** Get host or client + */ + key_string[0] = 0; + + WWGetPrivateProfileString("Internet", + "Host", + default_string, + key_string, + sizeof(key_string), + ini_file); + + + if (!strcmp (key_string, default_string)) { + if (wchat_name) delete ini_file; + return; + } + + if (strchr (key_string, '1')){ + PlanetWestwoodIsHost = true; + }else{ + PlanetWestwoodIsHost = false; + } + + UseVirtualSubnetServer = WWGetPrivateProfileInt("Internet", "UseVSS", 0, ini_file); + + Special.IsFromWChat = true; + } + + if (wchat_name) delete ini_file; + +#else //DEMO + + wchat_name = wchat_name; + +#endif //DEMO + +} + +//EventClass Wibble; + + +/*************************************************************************** + * Read_Game_Options -- reads multiplayer game options from disk * + * * + * This routine is used for multiplayer games which read the game options * + * from disk, rather than through a connection dialog. * + * * + * INPUT: * + * name of C&CSPAWN.INI file. Null if data should be got from DDE server* * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: \ * + * none. * + * * + * HISTORY: * + * 01/11/1996 BRR : Created. * + *=========================================================================*/ +int Read_Game_Options(char *name) +{ + char *buffer; + + char filename[256] = {"INVALID.123"}; + + if (name){ + strcpy (filename, name); + } + + /*------------------------------------------------------------------------ + Create filename and read the file. + ------------------------------------------------------------------------*/ + CCFileClass file (filename); + + if (name && !file.Is_Available()) { + return(0); + } else { + if (name){ + buffer = new char [8192]; // INI staging buffer pointer. + memset(buffer, '\0', 8192); + file.Read(buffer, 8192-1); + file.Close(); + }else{ + buffer = DDEServer.Get_MPlayer_Game_Info(); + } + } + + /*------------------------------------------------------------------------ + Get the player's name + ------------------------------------------------------------------------*/ + WWGetPrivateProfileString("Options", "Handle", "Noname", MPlayerName, + sizeof(MPlayerName), buffer); + strcpy(MPlayerGameName, MPlayerName); + MPlayerColorIdx = WWGetPrivateProfileInt("Options", "Color", 0, buffer); + MPlayerPrefColor = MPlayerColorIdx; + MPlayerHouse = (HousesType)WWGetPrivateProfileInt("Options", "Side", + HOUSE_GOOD, buffer); + + MPlayerCredits = WWGetPrivateProfileInt("Options", "Credits", 0, buffer); + MPlayerBases = WWGetPrivateProfileInt("Options", "Bases", 0, buffer); + MPlayerTiberium = WWGetPrivateProfileInt("Options", "Tiberium", 0, buffer); + MPlayerGoodies = WWGetPrivateProfileInt("Options", "Crates", 0, buffer); + MPlayerGhosts = WWGetPrivateProfileInt("Options", "AI", 0, buffer); + BuildLevel = WWGetPrivateProfileInt("Options", "BuildLevel", 0, buffer); + MPlayerUnitCount = WWGetPrivateProfileInt("Options", "UnitCount", 0, buffer); + Seed = WWGetPrivateProfileInt("Options", "Seed", 0, buffer); + Special.IsCaptureTheFlag = WWGetPrivateProfileInt("Options", "CaptureTheFlag", 0, buffer); + PlanetWestwoodGameID = WWGetPrivateProfileInt("Internet", "GameID", 0, buffer); + PlanetWestwoodStartTime = WWGetPrivateProfileInt ("Internet", "StartTime", 0, buffer); + WChatHWND = (HWND) WWGetPrivateProfileInt("Internet", "HWND", (int)FindWindow("OWL_Window", "Westwood Chat"), buffer); + + InternetMaxPlayers = WWGetPrivateProfileInt("Internet", "MaxPlayers", 2, buffer); + + + if (MPlayerTiberium) { + Special.IsTGrowth = 1; + Special.IsTSpread = 1; + } else { + Special.IsTGrowth = 0; + Special.IsTSpread = 0; + } + ScenarioIdx = WWGetPrivateProfileInt("Options", "Scenario", 0, buffer); + Scenario = ScenarioIdx; //MPlayerFilenum[ScenarioIdx]; + + Options.GameSpeed = 0; + + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + MPlayerMaxAhead = WChatMaxAhead = WWGetPrivateProfileInt("Timing", "MaxAhead", 9, buffer); + FrameSendRate = WChatSendRate = WWGetPrivateProfileInt("Timing", "SendRate", 3, buffer); + + if (name) delete buffer; + return (1); + +} + + + + +/*********************************************************************************************** + * Get_Registry_Sub_Key -- search a registry key for a sub-key * + * * + * * + * * + * INPUT: handle of key to search * + * text to search for * + * true if old key should be closed when new key opened * + * * + * OUTPUT: handle to the key we found or 0 * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 1/12/96 2:11PM ST : Created * + *=============================================================================================*/ + +extern HKEY Get_Registry_Sub_Key (HKEY base_key, char *search_key, BOOL close); + + + +void Just_Path(char *path, char *destpath) +{ + char *terminator = NULL; //He'll be back. + + strcpy (destpath, path); + terminator = strrchr (destpath, '\\'); + if (terminator){ + *terminator = 0; + } +} + + + + + +/*********************************************************************************************** + * Is_User_WChat_Registered -- retrieve the users wchat entry from the registry * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: TRUE if users wchat entry was found in the registry * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 1/12/96 2:13PM ST : Created * + *=============================================================================================*/ +bool Is_User_WChat_Registered(char *buffer, int buffer_len) +{ + HKEY key; + char user_handle[256]; + DWORD user_handle_size = sizeof (user_handle); + char user_pword[256]; + DWORD user_pword_size = sizeof (user_pword); + + + /* + ** Check HKEY_CLASSES_ROOT first. Old versions of Wchat register there + */ + key = Get_Registry_Sub_Key (HKEY_CLASSES_ROOT, "Wchat", FALSE); + + if (key){ + key = Get_Registry_Sub_Key (key, "Nick1", TRUE); + if (key){ + + if (RegQueryValue(key, "Nick", user_handle, (long*)&user_handle_size) == ERROR_SUCCESS){ + + if (RegQueryValue(key, "Pass", user_pword, (long*)&user_pword_size) == ERROR_SUCCESS){ + + /* + ** If the first char of the users name is non-numberic and there is a password + ** then return success + */ + if ((user_handle[0] < '0' || user_handle[0] > '9') && user_pword[0]){ + RegCloseKey( key ); + return (TRUE); + } + } + } + } + + RegCloseKey ( key ); + } + + + + /* + ** Check HKEY_LOCAL_MACKINE/Software + */ + user_handle_size = sizeof (user_handle); + + key = Get_Registry_Sub_Key (HKEY_LOCAL_MACHINE, "SOFTWARE", FALSE); + if (!key) return (FALSE); + + key = Get_Registry_Sub_Key (key, "Westwood", TRUE); + if (!key) return (FALSE); + + key = Get_Registry_Sub_Key (key, "InetReg", TRUE); + if (!key) return (FALSE); + + //key = Get_Registry_Sub_Key (key, "UserName", TRUE); + //if (!key) return (FALSE); + + //key = Get_Registry_Sub_Key (key, "Nick", TRUE); + //if (!key) return (FALSE); + + if (RegQueryValueEx(key, "UserName", NULL, NULL, (unsigned char*)user_handle, &user_handle_size) != ERROR_SUCCESS){ + RegCloseKey(key); + return (FALSE); + } + + RegCloseKey(key); + memcpy (buffer, user_handle, min(buffer_len, user_handle_size)); + + /* + ** If the first char of the users name is non-numeric then return success + */ + if (user_handle[0] < '0' || user_handle[0] > '9'){ + return (TRUE); + }else{ + return (FALSE); + } +} + + + +/*********************************************************************************************** + * Spawn_WChat -- spawns or switches focus to wchat * + * * + * * + * * + * INPUT: can launch. If set then we are allowed to launch WChat if not already running * + * * + * OUTPUT: True if wchat was spawned * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/8/96 12:33PM ST : Created * + *=============================================================================================*/ +bool Poke_WChat(void); +bool Spawn_WChat(bool can_launch) +{ + CCDebugString ("C&C95 - In Spawn_WChat.\n"); + char packet[10] = {"Hello"}; + HWND chat_window = NULL; + + /* + ** See if WChat is already running... + */ + if (WChatHWND && IsWindow (WChatHWND) ){ + chat_window = WChatHWND; + }else{ + chat_window = FindWindow ( "OWL_Window", "Westwood Chat" ); + } + + if (chat_window){ + /* + ** WChat is already running. Minimize myself then try to give it focus. + */ + Set_Palette(BlackPalette); + VisiblePage.Clear(); + ShowWindow (MainWindow, SW_MINIMIZE); + /* + ** Give windoze a couple of secs to sort itself out. + */ + CountDownTimerClass wibble_timer; + wibble_timer.Set ( 60 * 3, true); + + while (wibble_timer.Time()){ + /* + ** Call our message loop to make sure we get all the messages that are sent to us + ** when we minimise. + */ + Keyboard::Check(); + } + + /* + ** Send chat a tickle message so it knows to send the game stats to the server. + */ + if (GameStatisticsPacketSent && !PlanetWestwoodIsHost) { + Send_Data_To_DDE_Server (packet, strlen(packet), DDEServerClass::DDE_TICKLE); + } + /* + ** Give the focus to WChat + */ + SetForegroundWindow ( chat_window ); + ShowWindow ( chat_window, SW_RESTORE ); + return(true); + } + + /* + ** Fail if we aren't allowed to launch wchat and we couldnt find its window. + */ + if (!can_launch) return (false); + + /* + ** Find where WChat was installed to + */ + + HKEY key; + char wchat_loc[256]; + DWORD wchat_loc_size = 256; + + key = Get_Registry_Sub_Key (HKEY_LOCAL_MACHINE, "SOFTWARE", FALSE); + if (!key) return (FALSE); + + key = Get_Registry_Sub_Key (key, "Westwood", TRUE); + if (!key) return (FALSE); + + key = Get_Registry_Sub_Key (key, "WChat", TRUE); + if (!key) return (FALSE); + + //key = Get_Registry_Sub_Key (key, "UserName", TRUE); + //if (!key) return (FALSE); + + //key = Get_Registry_Sub_Key (key, "Nick", TRUE); + //if (!key) return (FALSE); + + if (RegQueryValueEx(key, "InstallPath", NULL, NULL, (unsigned char*)wchat_loc, &wchat_loc_size) != ERROR_SUCCESS){ + RegCloseKey(key); + return (FALSE); + } + + RegCloseKey(key); + + PROCESS_INFORMATION process_info; + STARTUPINFO start_info; + memset ((void*)&start_info, 0, sizeof(start_info)); + start_info.cb = sizeof(start_info); + char justpath [256]; + Just_Path(wchat_loc, justpath); + + /* + ** We found WChat in the registry. Minimize myself then try to spawn it. + */ + Set_Palette(BlackPalette); + VisiblePage.Clear(); + ShowWindow (MainWindow, SW_MINIMIZE); + /* + ** Give windoze a couple of secs to sort itself out. + */ + CountDownTimerClass wibble_timer; + wibble_timer.Set ( 60 * 3, true); + + while (wibble_timer.Time()){ + /* + ** Call our message loop to make sure we get all the messages that are sent to us + ** when we minimise. + */ + Keyboard::Check(); + } + bool success = CreateProcess (wchat_loc, NULL, NULL, NULL, false, 0, NULL, justpath, &start_info, &process_info); + + if (success){ + return (true); + }else{ + ShowWindow (MainWindow, SW_RESTORE); + while ( Keyboard::Check() ) {}; + return (false); + } +} + + + + +/*********************************************************************************************** + * Spawn_Registration_App -- spawns the C&C/Planet westwood registration app * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: True if app was spawned * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/8/96 12:33PM ST : Created * + *=============================================================================================*/ +bool Spawn_Registration_App(void) +{ + + /* + ** Find where inetreg was installed to + */ + + HKEY key; + char inetreg_loc[256]; + DWORD inetreg_loc_size = 256; + + key = Get_Registry_Sub_Key (HKEY_LOCAL_MACHINE, "SOFTWARE", FALSE); + if (!key) return (FALSE); + + key = Get_Registry_Sub_Key (key, "Westwood", TRUE); + if (!key) return (FALSE); + + key = Get_Registry_Sub_Key (key, "InetReg", TRUE); + if (!key) return (FALSE); + + if (RegQueryValueEx(key, "InstallPath", NULL, NULL, (unsigned char*)inetreg_loc, &inetreg_loc_size) != ERROR_SUCCESS){ + RegCloseKey(key); + return (FALSE); + } + + RegCloseKey(key); + + PROCESS_INFORMATION process_info; + STARTUPINFO start_info; + char justpath [256]; + memset ((void*)&start_info, 0, sizeof(start_info)); + start_info.cb = sizeof(start_info); + Just_Path(inetreg_loc, justpath); + + BOOL success = CreateProcess (inetreg_loc, NULL, NULL, NULL, false, 0, NULL, justpath, &start_info, &process_info); + if (success){ + //WaitForSingleObject (process_info.hProcess, 1000*10000); + //SetForegroundWindow ( MainWindow ); + //ShowWindow ( MainWindow, SW_RESTORE ); + } + return (success); + +} + + + + + +/*********************************************************************************************** + * Do_The_Internet_Menu_Thang -- Handle case where user clicks on 'Internet' button * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/7/96 8:30PM ST : Created * + *=============================================================================================*/ +bool Do_The_Internet_Menu_Thang(void) +{ +#ifndef DEMO + + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + /* + ** Dialog & button dimensions + */ + int d_dialog_w = 120 *factor; // dialog width + int d_dialog_h = 80*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200*factor - d_dialog_h) / 2); // centered y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_margin1=10; + int d_txt6_h=15; + +#if (GERMAN | FRENCH) + int d_cancel_w = 50*factor; +#else + int d_cancel_w = 40*factor; +#endif + int d_cancel_h = 9*factor; + int d_cancel_x = d_dialog_cx - d_cancel_w / 2; + int d_cancel_y = d_dialog_y + d_dialog_h - 20*factor; + + char packet[10] = {"Hello"}; + +#if (GERMAN | FRENCH) + int width=160*factor; + int height=80*factor; +#else + int width=120*factor; + int height=80*factor; +#endif //GERMAN | FRENCH + + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + Format_Window_String((char*)Text_String (TXT_CONNECTING), SeenBuff.Get_Height(), width, height); + +#if (GERMAN | FRENCH) + d_dialog_w = width + 25*factor; + d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + d_cancel_x = d_dialog_cx - (d_cancel_w / 2); +#endif + + /* + ** Button Enumerations + */ + enum { + BUTTON_CANCEL = 100, + }; + + /* + ** Buttons + */ + //TextButtonClass *buttons; // button list + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_cancel_x, d_cancel_y); +//#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +//#endif + + //buttons = &cancelbtn; + + Fancy_Text_Print(TXT_NONE, 0, 0, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + + char users_name[256]; + int buffer_len = sizeof (users_name); + bool process; + bool display; + KeyNumType input; + + + if (!Special.IsFromWChat && !SpawnedFromWChat){ + /* + ** If the user is registered with Planet Westwood then spawn WChat. + */ + if (Is_User_WChat_Registered(users_name, buffer_len)){ + GameStatisticsPacketSent = false; + if (!Spawn_WChat(true)){ + Set_Logic_Page(SeenBuff); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + CCMessageBox().Process(TXT_ERROR_UNABLE_TO_RUN_WCHAT, TXT_OK); + LogicPage->Clear(); + return(false); + } + }else{ + Set_Logic_Page(SeenBuff); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + if (CCMessageBox().Process(TXT_EXPLAIN_REGISTRATION, TXT_REGISTER, TXT_CANCEL)){ + LogicPage->Clear(); + return(false); + }else{ + LogicPage->Clear(); + Spawn_Registration_App(); + return(false); + } + } + } + + /* + ** + ** User is registered and we spawned WChat. Wait for a game start message from WChat. + ** + */ + + process = true; + display = true; + + while (process){ + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored = FALSE; + display = true; + } + + if (display) { + + Set_Logic_Page(SeenBuff); + + Hide_Mouse(); + /* + ** Redraw backgound & dialog box + */ + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + /* + ** Dialog & Field labels + */ + Draw_Caption (TXT_NONE, d_dialog_x, d_dialog_y, d_dialog_w); + + Fancy_Text_Print(TXT_CONNECTING, d_dialog_cx-width/2, d_dialog_y + 25*factor, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + //cancelbtn.Zap(); + //buttons = &cancelbtn; + + /* + .................... Rebuild the button list .................... + */ + //buttons->Draw_All(); + cancelbtn.Draw_Me(true); + + Show_Mouse(); + display = false; + } + + + + /* + ** See if the game start packet has arrived from wchat yet. + */ + if (DDEServer.Get_MPlayer_Game_Info()){ + //MessageBox (NULL, "About to restore focus to C&C95", "C&C95", MB_OK); + //SetForegroundWindow ( MainWindow ); + //ShowWindow ( MainWindow, SW_SHOWMAXIMIZED ); + return(true); + } + + //input = buttons->Input(); + input = cancelbtn.Input(); + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + + /* + ** Cancel. Just return to the main menu + */ + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + process = false; + Send_Data_To_DDE_Server (packet, strlen(packet), DDEServerClass::DDE_CONNECTION_FAILED); + GameStatisticsPacketSent = false; + Spawn_WChat(false); + break; + } + + } + +#endif //DEMO + + return (false); + + +} + + + + + + + + diff --git a/INTERPAL.CPP b/INTERPAL.CPP new file mode 100644 index 0000000..712a06e --- /dev/null +++ b/INTERPAL.CPP @@ -0,0 +1,458 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : INTERPAL.CPP * + * * + * Programmer : Steve Tall * + * * + * Start Date : December 7th 1995 * + * * + *---------------------------------------------------------------------------------------------* + * Overview: * + * This module contains functions to allow use of old 320x200 animations on a 640x400 screen * + * * + * Functions: * + * Read_Interpolation_Palette -- reads an interpolation palette table from disk * + * Write_Interpolation_Palette -- writes an interpolation palette to disk * + * Create_Palette_Interpolation_Table -- build the palette interpolation table * + * Increase_Palette_Luminance -- increase the contrast of a palette * + * Interpolate_2X_Scale -- Stretch a 320x200 graphic buffer into 640x400 * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +BOOL InterpolationPaletteChanged = FALSE; +extern "C" { +extern void __cdecl Asm_Interpolate (unsigned char* src_ptr , + unsigned char* dest_ptr , + int lines , + int src_width , + int dest_width); + +extern void __cdecl Asm_Interpolate_Line_Double (unsigned char* src_ptr , + unsigned char* dest_ptr , + int lines , + int src_width , + int dest_width); + +extern void __cdecl Asm_Interpolate_Line_Interpolate (unsigned char* src_ptr , + unsigned char* dest_ptr , + int lines , + int src_width , + int dest_width); + +} + +extern "C"{ + unsigned char PaletteInterpolationTable[SIZE_OF_PALETTE][SIZE_OF_PALETTE]; + unsigned char *InterpolationPalette; +} + + + +/*********************************************************************************************** + * Read_Interpolatioin_Palette -- reads an interpolation palette table from disk * + * * + * * + * * + * INPUT: name of palette file * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 12/12/95 12:15PM ST : Created * + *=============================================================================================*/ + +void Read_Interpolation_Palette (char const *palette_file_name) +{ + CCFileClass palette_file(palette_file_name); + + if (palette_file.Is_Available()){ + palette_file.Open(READ); + palette_file.Read(&PaletteInterpolationTable[0][0],256*256); + palette_file.Close(); + InterpolationPaletteChanged = FALSE; + } +} + + +/*********************************************************************************************** + * Write_Interpolatioin_Palette -- writes an interpolation palette table to disk * + * * + * * + * * + * INPUT: name of palette file * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 12/12/95 12:15PM ST : Created * + *=============================================================================================*/ + +void Write_Interpolation_Palette (char const *palette_file_name) +{ + CCFileClass palette_file(palette_file_name); + + if (!palette_file.Is_Available()){ + palette_file.Open(WRITE); + palette_file.Write(&PaletteInterpolationTable[0][0],256*256); + palette_file.Close(); + } +} + + + + + +/*************************************************************************** + * CREATE_PALETTE_INTERPOLATION_TABLE * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 12/06/1995 MG : Created. * + *=========================================================================*/ +void Create_Palette_Interpolation_Table( void ) +{ + + Asm_Create_Palette_Interpolation_Table(); + + #if (0) + + int i; + int j; + int p; + unsigned char *first_palette_ptr; + unsigned char *second_palette_ptr; + unsigned char *match_pal_ptr; + int first_r; + int first_g; + int first_b; + int second_r; + int second_g; + int second_b; + int diff_r; + int diff_g; + int diff_b; + int dest_r; + int dest_g; + int dest_b; + int distance; + int closest_distance; + int index_of_closest_color; + + // + // Create an interpolation table for the current palette. + // + first_palette_ptr = (unsigned char *) InterpolationPalette; + for ( i = 0; i < SIZE_OF_PALETTE; i ++ ) { + + // + // Get the first palette entry's RGB. + // + first_r = *first_palette_ptr; + first_palette_ptr ++; + first_g = *first_palette_ptr; + first_palette_ptr ++; + first_b = *first_palette_ptr; + first_palette_ptr ++; + + second_palette_ptr = (unsigned char *) InterpolationPalette; + for ( j = 0; j < SIZE_OF_PALETTE; j ++ ) { + // + // Get the second palette entry's RGB. + // + second_r = *second_palette_ptr; + second_palette_ptr ++; + second_g = *second_palette_ptr; + second_palette_ptr ++; + second_b = *second_palette_ptr; + second_palette_ptr ++; + + // + // Now calculate the RGB halfway between the first and second colors. + // + dest_r = ( first_r + second_r ) >> 1; + dest_g = ( first_g + second_g ) >> 1; + dest_b = ( first_b + second_b ) >> 1; + + // + // Now find the color in the palette that most closely matches the interpolated color. + // + index_of_closest_color = 0; +// closest_distance = (256 * 256) * 3; + closest_distance = 500000; + match_pal_ptr = (unsigned char *) InterpolationPalette; + for ( p = 0; p < SIZE_OF_PALETTE; p ++ ) { + diff_r = ( ((int) (*match_pal_ptr)) - dest_r ); + match_pal_ptr ++; + diff_g = ( ((int) (*match_pal_ptr)) - dest_g ); + match_pal_ptr ++; + diff_b = ( ((int) (*match_pal_ptr)) - dest_b ); + match_pal_ptr ++; + + distance = ( diff_r * diff_r ) + ( diff_g * diff_g ) + ( diff_b * diff_b ); + if ( distance < closest_distance ) { + closest_distance = distance; + index_of_closest_color = p; + } + } + + PaletteInterpolationTable[ i ][ j ] = (unsigned char) index_of_closest_color; + } + } + + #endif + InterpolationPaletteChanged = FALSE; + return; + +} + + + + + + + + + +/*********************************************************************************************** + * Increase_Palette_Luminance -- increase contrast of colours in a palette * + * * + * * + * * + * INPUT: ptr to palette * + * percentage increase of red * + * percentage increase of green * + * percentage increase of blue * + * cap value for colours * + * * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 12/12/95 12:16PM ST : Created * + *=============================================================================================*/ + +void Increase_Palette_Luminance (unsigned char *palette , int red_percentage , int green_percentage , int blue_percentage ,int cap) +{ + + unsigned int red; + unsigned int green; + unsigned int blue; + for (int i=0 ; iGet_IsDirectDraw()){ + if (!source->Lock()){ + if (dest == &SeenBuff) Show_Mouse(); + return; + } + source_locked = TRUE; + } + if (dest->Get_IsDirectDraw()){ + if (!dest->Lock()) { + if (source_locked){ + source->Unlock(); + } + if (dest == &SeenBuff) Show_Mouse(); + return; + } + dest_locked = TRUE; + } + + + // + // Get pointers to the source and destination buffers. + // + src_ptr = (unsigned char *) source->Get_Offset(); + dest_ptr = (unsigned char *) dest->Get_Offset(); + end_of_source = src_ptr + ( source->Get_Width() * source->Get_Height() ); + + // + // Get width of source and dest buffers. + // + src_width = source->Get_Width(); + dest_width = 2*(dest->Get_Width() + dest->Get_XAdd() + dest->Get_Pitch()); + last_dest_ptr = dest_ptr; + + /* + ** Call the appropriate assembly language copy routine + */ +#if (1) + switch (CopyType){ + case 0: + Asm_Interpolate ( src_ptr , dest_ptr , source->Get_Height() , src_width , dest_width); + break; + + case 1: + Asm_Interpolate_Line_Double( src_ptr , dest_ptr , source->Get_Height() , src_width , dest_width); + break; + + case 2: + Asm_Interpolate_Line_Interpolate( src_ptr , dest_ptr , source->Get_Height() , src_width , dest_width); + break; + } +#endif + +#if (0) + // + // Copy over the first pixel (upper left). + // + *dest_ptr = *src_ptr; + src_ptr ++; + dest_ptr ++; + + // + // Scale copy. + // + width_counter = 0; + while ( src_ptr < end_of_source ) { + + // + // Blend this pixel with the one to the left and place this new color in the dest buffer. + // + *dest_ptr = PaletteInterpolationTable[ (*src_ptr) ][ (*( src_ptr - 1 )) ]; + dest_ptr ++; + + // + // Now place the source pixel into the dest buffer. + // + *dest_ptr = *src_ptr; + + src_ptr ++; + dest_ptr ++; + + width_counter ++; + if ( width_counter == src_width ) { + width_counter = 0; + last_dest_ptr += dest_width; + dest_ptr = last_dest_ptr; + } + } + +#endif + if (source_locked) source->Unlock(); + if (dest_locked) dest->Unlock(); + if (dest == &SeenBuff) Show_Mouse(); + +} +#endif + + + + diff --git a/INTRO.CPP b/INTRO.CPP new file mode 100644 index 0000000..8559343 --- /dev/null +++ b/INTRO.CPP @@ -0,0 +1,313 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\intro.cpv 1.6 16 Oct 1995 16:50:18 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : INTRO.H * + * * + * Programmer : Barry W. Green * + * * + * Start Date : May 8, 1995 * + * * + * Last Update : May 8, 1995 [BWG] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "textblit.h" + +#ifndef DEMO + +VQAHandle *Open_Movie(char *name); +VQAHandle *Open_Movie(char *name) +{ + if (!Debug_Quiet && Get_Digi_Handle() != -1) { + AnimControl.OptionFlags |= VQAOPTF_AUDIO; + } else { + AnimControl.OptionFlags &= ~VQAOPTF_AUDIO; + } + + VQAHandle * vqa = VQA_Alloc(); + if (vqa) { + VQA_Init(vqa, MixFileHandler); + + if (VQA_Open(vqa, name, &AnimControl) != 0) { + VQA_Free(vqa); + vqa = 0; + } + } + return(vqa); +} + + +/*********************************************************************************************** + * Choose_Side -- play the introduction movies, select house * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: * + * * + * HISTORY: * + * 5/08/1995 BWG : Created. * + *=============================================================================================*/ +void Choose_Side(void) +{ + //static char const _yellowpal[]={0x0,0x0,0xC9,0x0,0xBA,0x0,0x93,0x0,0x61,0x0,0x0,0x0,0x0,0x0,0xEE,0x0}; + //static char const _redpal[] ={0x0,0x0,0xA8,0x0,0xD9,0x0,0xDA,0x0,0xE1,0x0,0x0,0x0,0x0,0x0,0xD4,0x0}; + //static char const _graypal[] ={0x0,0x0,0x17,0x0,0x10,0x0,0x12,0x0,0x14,0x0,0x0,0x0,0x0,0x0,0x1C,0x0}; + + static char const _yellowpal[]={0x0,0xC9,0xBA,0x93,0x61,0xEE,0xee,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0}; + static char const _redpal[] ={0x0,0xa8,0xd9,0xda,0xe1,0xd4,0xDA,0x0,0xE1,0x0,0x0,0x0,0x0,0x0,0xD4,0x0}; + static char const _graypal[] ={0x0,0x17,0x10,0x12,0x14,0x1c,0x12,0x1c,0x14,0x0,0x0,0x0,0x0,0x0,0x1C,0x0}; + + + void *anim; + VQAHandle *gdibrief=0, *nodbrief=0; + void const *staticaud, *oldfont; + void const *speechg, *speechn, *speech; + int statichandle, speechhandle, speechplaying = 0; + int oldfontxspacing = FontXSpacing; + int setpalette = 0; + int gdi_start_palette; + + MEMORYSTATUS mem_info; + mem_info.dwLength=sizeof(mem_info); + GlobalMemoryStatus(&mem_info); + + TextPrintBuffer = new GraphicBufferClass(SeenBuff.Get_Width(), SeenBuff.Get_Height(), (void*)NULL); + TextPrintBuffer->Clear(); + BlitList.Clear(); + PseudoSeenBuff = new GraphicBufferClass(320,200,(void*)NULL); + int frame = 0, endframe = 255, selection = 0, lettersdone = 0; + + Hide_Mouse(); +/* Change to the six-point font for Text_Print */ + oldfont = Set_Font(ScoreFontPtr); + + Call_Back(); + + staticaud = Load_Alloc_Data(CCFileClass("STRUGGLE.AUD")); + speechg = Load_Alloc_Data(CCFileClass("GDI_SLCT.AUD")); + speechn = Load_Alloc_Data(CCFileClass("NOD_SLCT.AUD")); + +// staticaud = MixFileClass::Retrieve("STRUGGLE.AUD"); +// speechg = MixFileClass::Retrieve("GDI_SLCT.AUD"); +// speechn = MixFileClass::Retrieve("NOD_SLCT.AUD"); + + if (Special.IsFromInstall){ + if (mem_info.dwTotalPhys >= 12*1024*1024){ + VisiblePage.Clear(); + PreserveVQAScreen = 1; + Play_Movie("INTRO2", THEME_NONE, false); + } + BreakoutAllowed = true; + } + + //anim = Open_Animation("CHOOSE.WSA",NULL,0L,(WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE),Palette); + anim = Open_Animation("CHOOSE.WSA",NULL,0L,(WSAOpenType)(WSA_OPEN_FROM_DISK | WSA_OPEN_TO_PAGE),Palette); + Call_Back(); + InterpolationPaletteChanged = TRUE; + InterpolationPalette = Palette; + Read_Interpolation_Palette("SIDES.PAL"); + + nodbrief = Open_Movie("NOD1PRE.VQA"); + gdi_start_palette = Load_Interpolated_Palettes("NOD1PRE.VQP"); + Call_Back(); + gdibrief = Open_Movie("GDI1.VQA"); + Load_Interpolated_Palettes("GDI1.VQP" , TRUE); + + WWMouse->Erase_Mouse(&HidPage, TRUE); + HiddenPage.Clear(); + PseudoSeenBuff->Clear(); + SysMemPage.Clear(); + //if (!Special.IsFromInstall) { + VisiblePage.Clear(); + Set_Palette(Palette); + //} else { + //setpalette = 1; + //} + + statichandle = Play_Sample(staticaud,255,64); + CountDownTimerClass sample_timer; + sample_timer.Set(0x3f); + Alloc_Object(new ScorePrintClass(TXT_GDI_NAME, 0, 180,_yellowpal)); +#ifdef FRENCH + Alloc_Object(new ScorePrintClass(TXT_GDI_NAME2, 0, 187,_yellowpal)); +#endif + Alloc_Object(new ScorePrintClass(TXT_NOD_NAME, 180, 180,_redpal)); + +#ifdef GERMAN + Alloc_Object(new ScorePrintClass(TXT_SEL_TRANS,57, 190,_graypal)); +#else +#ifdef FRENCH + Alloc_Object(new ScorePrintClass(TXT_SEL_TRANS,103, 194,_graypal)); +#else + Alloc_Object(new ScorePrintClass(TXT_SEL_TRANS,103, 190,_graypal)); +#endif +#endif + Keyboard::Clear(); + + while (Get_Mouse_State()) Show_Mouse(); + + while (endframe != frame || (speechplaying && Is_Sample_Playing(speech)) ) { + Animate_Frame(anim, SysMemPage, frame++); + if (setpalette) { + Wait_Vert_Blank(); + Set_Palette(Palette); + setpalette = 0; + } + SysMemPage.Blit(*PseudoSeenBuff,0,22, 0,22, 320,156); + + /* + ** If the sample has stopped or is about to then restart it + */ + if (!Is_Sample_Playing(staticaud) || !sample_timer.Time()) { + Stop_Sample(statichandle); + statichandle = Play_Sample(staticaud,255,64); + sample_timer.Set(0x3f); + } + Call_Back_Delay(3); // delay only if haven't clicked + + /* keep the mouse hidden until the letters are thru printing */ + if (!lettersdone) { + lettersdone = true; + for(int i=0; i < MAXSCOREOBJS; i++) if (ScoreObjs[i]) lettersdone = 0; + if (lettersdone) { + Show_Mouse(); + } + } + if (frame >= Get_Animation_Frame_Count(anim)) frame = 0; + if (Keyboard::Check() && endframe == 255) { + if ((Keyboard::Get() & 0x10FF) == KN_LMOUSE) { + if ((_Kbd->MouseQY > 48*2) && (_Kbd->MouseQY < 150*2)) { + if ((_Kbd->MouseQX > 18*2) && (_Kbd->MouseQX < 148*2)) { + + // Chose GDI + Whom = HOUSE_GOOD; + ScenPlayer = SCEN_PLAYER_GDI; + endframe = 0; + speechhandle = Play_Sample(speechg); + speechplaying = true; + speech = speechg; + + } else if ((_Kbd->MouseQX > 160*2) && (_Kbd->MouseQX < 300*2)) { + // Chose Nod + selection = 1; + endframe = 14; + Whom = HOUSE_BAD; + ScenPlayer = SCEN_PLAYER_NOD; + speechhandle = Play_Sample(speechn); + speechplaying = true; + speech = speechn; + } + } + } + } + } + + Hide_Mouse(); + Close_Animation(anim); + + // erase the "choose side" text + PseudoSeenBuff->Fill_Rect(0,180,319,199,0); + SeenBuff.Fill_Rect(0,180*2, 319*2, 199*2, 0); + Interpolate_2X_Scale (PseudoSeenBuff , &SeenBuff ,"SIDES.PAL"); + Keyboard::Clear(); + SysMemPage.Clear(); + + /* + ** Skip the briefings if we're in special mode. + */ + if (Special.IsJurassic && AreThingiesEnabled) { + if (nodbrief) { + VQA_Close(nodbrief); + VQA_Free(nodbrief); + nodbrief = NULL; + } + if (gdibrief) { + VQA_Close(gdibrief); + VQA_Free(gdibrief); + gdibrief = NULL; + } + } + + /* play the scenario 1 briefing movie */ + if (Whom == HOUSE_GOOD) { + if (nodbrief) { + VQA_Close(nodbrief); + VQA_Free(nodbrief); + } + if (gdibrief) { + PaletteCounter = gdi_start_palette; + VQA_Play(gdibrief, VQAMODE_RUN); + VQA_Close(gdibrief); + VQA_Free(gdibrief); + } + } else { + if (gdibrief) { + VQA_Close(gdibrief); + VQA_Free(gdibrief); + } + if (nodbrief) { + VQA_Play(nodbrief, VQAMODE_RUN); + VQA_Close(nodbrief); + VQA_Free(nodbrief); + } + } + + Free_Interpolated_Palettes(); + Set_Primary_Buffer_Format(); +/* get rid of all the animating objects */ + for (int i = 0; i < MAXSCOREOBJS; i++) if (ScoreObjs[i]) { + delete ScoreObjs[i]; + ScoreObjs[i] = 0; + } + + if (Whom == HOUSE_GOOD) { + /* + ** Make sure the screen's fully clear after the movie plays + */ + VisiblePage.Clear(); + memset(BlackPalette, 0x01, 768); + Set_Palette(BlackPalette); + memset(BlackPalette, 0x00, 768); + } else { + PreserveVQAScreen = 1; + } + Free(staticaud); + Free(speechg); + Free(speechn); + + Set_Font(oldfont); + FontXSpacing = oldfontxspacing; + + delete PseudoSeenBuff; + delete TextPrintBuffer; + TextPrintBuffer = NULL; + BlitList.Clear(); +} +#endif diff --git a/INTRO.H b/INTRO.H new file mode 100644 index 0000000..50a3a95 --- /dev/null +++ b/INTRO.H @@ -0,0 +1,42 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : INTRO.H * + * * + * Programmer : Barry W. Green * + * * + * Start Date : May 8, 1995 * + * * + * Last Update : May 8, 1995 [BWG] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef INTRO_H +#define INTRO_H + +void Choose_Side(void); + +#endif diff --git a/IOMAP.CPP b/IOMAP.CPP new file mode 100644 index 0000000..8463c11 --- /dev/null +++ b/IOMAP.CPP @@ -0,0 +1,1051 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\iomap.cpv 2.18 16 Oct 1995 16:50:34 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IOMAP.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : January 16, 1995 * + * * + * Last Update : January 16, 1995 [BR] * + * * + *---------------------------------------------------------------------------------------------* + * All map-related loading/saving routines should go in this module, so it can be overlayed. * + *---------------------------------------------------------------------------------------------* + * Functions: * + * CellClass::Code_Pointers -- codes class's pointers for load/save * + * CellClass::Decode_Pointers -- decodes pointers for load/save * + * CellClass::Load -- Reads from a save game file. * + * CellClass::Save -- Write to a save game file. * + * CellClass::Should_Save -- Should the cell be written to disk? * + * DisplayClass::Code_Pointers -- codes class's pointers for load/save * + * DisplayClass::Decode_Pointers -- decodes pointers for load/save * + * GScreenClass::Code_Pointers -- codes class's pointers for load/save * + * GScreenClass::Decode_Pointers -- decodes pointers for load/save * + * HelpClass::Code_Pointers -- codes class's pointers for load/save * + * HelpClass::Decode_Pointers -- decodes pointers for load/save * + * MapClass::Code_Pointers -- codes class's pointers for load/save * + * MapClass::Decode_Pointers -- decodes pointers for load/save * + * MouseClass::Code_Pointers -- codes class's pointers for load/save * + * MouseClass::Decode_Pointers -- decodes pointers for load/save * + * MouseClass::Load -- Loads from a save game file. * + * MouseClass::Save -- Saves to a save game file. * + * PowerClass::Code_Pointers -- codes class's pointers for load/save * + * PowerClass::Decode_Pointers -- decodes pointers for load/save * + * RadarClass::Code_Pointers -- codes class's pointers for load/save * + * RadarClass::Decode_Pointers -- decodes pointers for load/save * + * ScrollClass::Code_Pointers -- codes class's pointers for load/save * + * ScrollClass::Decode_Pointers -- decodes pointers for load/save * + * SidebarClass::Code_Pointers -- codes class's pointers for load/save * + * SidebarClass::Decode_Pointers -- decodes pointers for load/save * + * SidebarClass::StripClass::Code_Pointers -- codes class's pointers for load/save * + * SidebarClass::StripClass::Decode_Pointers -- decodes pointers for load/save * + * TabClass::Code_Pointers -- codes class's pointers for load/save * + * TabClass::Decode_Pointers -- decodes pointers for load/save * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * CellClass::Should_Save -- Should the cell be written to disk? * + * * + * This function will determine if the cell needs to be written to disk. Any cell that * + * contains special data should be written to disk. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Should this cell's data be written to disk? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool CellClass::Should_Save(void) const +{ + return( + (Smudge != SMUDGE_NONE) || + (TType != TEMPLATE_NONE) || + (Overlay != OVERLAY_NONE) || + IsMapped || + IsVisible || + IsTrigger || + Flag.Composite || + OccupierPtr || + Overlapper[0] || Overlapper[1] || Overlapper[2] + ); +} + + +/*********************************************************************************************** + * CellClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool CellClass::Load(FileClass & file) +{ + int rc; + TriggerClass * trig; + + /* + -------------------------- Load the object data -------------------------- + */ + rc = Read_Object(this, sizeof(CellClass), sizeof(CellClass), file, 0); + + /* + ------------------------ Load the trigger pointer ------------------------ + */ + if (rc) { + if (IsTrigger) { + if (file.Read(&trig,sizeof(trig)) != sizeof(trig)) + return(false); + CellTriggers[Cell_Number()] = trig; + } + } + + return(rc); +} + + +/*********************************************************************************************** + * CellClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool CellClass::Save(FileClass & file) +{ + int rc; + TriggerClass * trig; + + /* + -------------------------- Save the object data -------------------------- + */ + rc = Write_Object(this, sizeof(CellClass), file); + + /* + ------------------------ Save the trigger pointer ------------------------ + */ + if (rc) { + if (IsTrigger) { + trig = CellTriggers[Cell_Number()]; + if (file.Write(&trig,sizeof(trig)) != sizeof(trig)) + return(false); + } + } + + return(rc); +} + + +/*********************************************************************************************** + * CellClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void CellClass::Code_Pointers(void) +{ + if (Cell_Occupier()) { + OccupierPtr = (ObjectClass *)OccupierPtr->As_Target(); + } + + if (Overlapper[0]) { + Overlapper[0] = (ObjectClass *)Overlapper[0]->As_Target(); + } + + if (Overlapper[1]) { + Overlapper[1] = (ObjectClass *)Overlapper[1]->As_Target(); + } + + if (Overlapper[2]) { + Overlapper[2] = (ObjectClass *)Overlapper[2]->As_Target(); + } + + /* + ------------------------ Convert trigger pointer ------------------------- + */ + if (IsTrigger) { + CellTriggers[Cell_Number()] = (TriggerClass *)CellTriggers[Cell_Number()]->As_Target(); + } +} + + +/*********************************************************************************************** + * CellClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void CellClass::Decode_Pointers(void) +{ + if (OccupierPtr) { + OccupierPtr = As_Object((TARGET)OccupierPtr); + Check_Ptr((void *)OccupierPtr,__FILE__,__LINE__); + } + + if (Overlapper[0]) { + Overlapper[0] = As_Object((TARGET)Overlapper[0]); + Check_Ptr((void *)Overlapper[0],__FILE__,__LINE__); + } + + if (Overlapper[1]) { + Overlapper[1] = As_Object((TARGET)Overlapper[1]); + Check_Ptr((void *)Overlapper[1],__FILE__,__LINE__); + } + + if (Overlapper[2]) { + Overlapper[2] = As_Object((TARGET)Overlapper[2]); + Check_Ptr((void *)Overlapper[2],__FILE__,__LINE__); + } + + /* + ** Convert trigger pointer. + */ + if (IsTrigger) { + CellTriggers[Cell_Number()] = As_Trigger( (TARGET)CellTriggers[Cell_Number()] ); + Check_Ptr((void *)CellTriggers[Cell_Number()],__FILE__,__LINE__); + } +} + + +/*********************************************************************************************** + * MouseClass::Load -- Loads from a save game file. * + * * + * Loading the map is very complicated. Here are the steps: * + * - Read the Theater for this save-game * + * - call Init_Theater to perform theater-specific inits * + * - call Free_Cells to free the cell array, because loading the map object will overwrite * + * the pointer to the cell array * + * - read the map object from disk * + * - call Alloc_Cells to re-allocate the cell array * + * - call Init_Cells to set the cells to a known state, because not every cell will be loaded * + * - read the cell objects into the cell array * + * - After the map & all objects have been loaded & the pointers decoded, Init_IO() >MUST< be * + * called to restore the map's button list to the proper state. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool MouseClass::Load(FileClass & file) +{ + unsigned count; + CELL cell = 0; + int index; +// int rc; +// int i; +// int j; + + /*------------------------------------------------------------------------ + Load Theater: Even though this value is located in the DisplayClass, + it must be loaded first so initialization can be done before any other + map data is loaded. If initialization isn't done first, data read from + disk will be over-written when initialization occurs. This code must + go in the most-derived Map class. + ------------------------------------------------------------------------*/ + if (file.Read (&Theater,sizeof(Theater)) != sizeof(Theater)) + return(false); + + /* + ** Remove any old theater specific uncompressed shapes + */ + if (Theater != LastTheater){ + Reset_Theater_Shapes(); + } + + /* + ------------------------- Init display mixfiles -------------------------- + */ + Init_Theater(Theater); + TerrainTypeClass::Init(Theater); + TemplateTypeClass::Init(Theater); + OverlayTypeClass::Init(Theater); + UnitTypeClass::Init(Theater); + InfantryTypeClass::Init(Theater); + BuildingTypeClass::Init(Theater); + BulletTypeClass::Init(Theater); + AnimTypeClass::Init(Theater); + AircraftTypeClass::Init(Theater); + SmudgeTypeClass::Init(Theater); + + LastTheater = Theater; + + /* + ** Free the cell array, because we're about to overwrite its pointers + */ + Free_Cells(); + + /* + ** Read the entire map object in. Only read in sizeof(MouseClass), so if we're + ** in editor mode, none of the map editor object is read in. + */ + if (!Read_Object(this, sizeof(VectorClass), sizeof(MouseClass), file, VTable)) { + return(false); + } + + /* + ** Reallocate the cell array + */ + Alloc_Cells(); + + /* + ** Init all cells to empty + */ + Init_Cells(); + + /* + --------------------------- Read # cells saved --------------------------- + */ + if (file.Read(&count, sizeof(count)) != sizeof(count)) { + return(false); + } + + /* + ------------------------------- Read cells ------------------------------- + */ + for (index = 0; index < count; index++) { + if (file.Read(&cell, sizeof(cell)) != sizeof(cell)) + return(false); + + if (!(*this)[cell].Load(file)) + return(false); + } + + return(true); +} + + +/*********************************************************************************************** + * MouseClass::Save -- Save to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool MouseClass::Save(FileClass & file) +{ + unsigned count; + long pos; + + /* + -------------------------- Save Theater >first< -------------------------- + */ + if (file.Write (&Theater,sizeof(Theater)) != sizeof(Theater)) + return(false); + + if (!Write_Object(this, sizeof(MouseClass), file)) + return(false); + + /* + ---------------------- Record current file position ---------------------- + */ + pos = file.Seek(0, SEEK_CUR); + + /* + ---------------------- write out placeholder bytes ----------------------- + */ + if (file.Write(&count, sizeof(count)) != sizeof(count)) + return(false); + + /* + ------------------------ Save cells that need it ------------------------- + */ + count = 0; + for (CELL cell = 0; cell < MAP_CELL_TOTAL; cell++) { + if ((*this)[cell].Should_Save()) { + if (file.Write(&cell, sizeof(cell)) != sizeof(cell)) + return(false); + + count++; + + if (!(*this)[cell].Save(file)) + return(false); + } + } + + /* + -------------------------- Save # cells written -------------------------- + */ + file.Seek(pos, SEEK_SET); + + if (file.Write(&count, sizeof(count)) != sizeof(count)) + return(false); + + file.Seek(0, SEEK_END); + + return(true); +} + + +/*********************************************************************************************** + * MouseClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void MouseClass::Code_Pointers(void) +{ +// Control.Code_Pointers(); + + ScrollClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * MouseClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void MouseClass::Decode_Pointers(void) +{ +// Control.Decode_Pointers(); + + ScrollClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * ScrollClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void ScrollClass::Code_Pointers(void) +{ + HelpClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * ScrollClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void ScrollClass::Decode_Pointers(void) +{ + HelpClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * HelpClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void HelpClass::Code_Pointers(void) +{ + TabClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * HelpClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void HelpClass::Decode_Pointers(void) +{ + TabClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * TabClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TabClass::Code_Pointers(void) +{ + SidebarClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * TabClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TabClass::Decode_Pointers(void) +{ + SidebarClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * PowerClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void PowerClass::Code_Pointers(void) +{ + RadarClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * PowerClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void PowerClass::Decode_Pointers(void) +{ + RadarClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * SidebarClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void SidebarClass::Code_Pointers(void) +{ + for (int i = 0; i < COLUMNS; i++) { + Column[i].Code_Pointers(); + } + + PowerClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * SidebarClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void SidebarClass::Decode_Pointers(void) +{ + for (int i = 0; i < COLUMNS; i++) { + Column[i].Decode_Pointers(); + } + + PowerClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void SidebarClass::StripClass::Code_Pointers(void) +{ +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void SidebarClass::StripClass::Decode_Pointers(void) +{ +} + + +/*********************************************************************************************** + * RadarClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void RadarClass::Code_Pointers(void) +{ + DisplayClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * RadarClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void RadarClass::Decode_Pointers(void) +{ + DisplayClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * DisplayClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void DisplayClass::Code_Pointers(void) +{ + /* + ** Code PendingObjectPtr. + */ + if (PendingObjectPtr) { + PendingObjectPtr = (ObjectClass *)PendingObjectPtr->As_Target(); + } + + /* + ** Chain to parent. + */ + MapClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * DisplayClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void DisplayClass::Decode_Pointers(void) +{ + /* + ** Decode PendingObjectPtr. We can't decode PendingObject here, because we'd + ** have to reference PendingObjectPtr->Class_Of(), and the object that + ** PendingObjectPtr is pointing to hasn't been decoded yet. Since we can't + ** decode PendingObjectPtr, we can't set the placement cursor shape here + ** either. These have to be done as last-minute fixups. + */ + if (PendingObjectPtr) { + PendingObjectPtr = As_Object((TARGET)PendingObjectPtr); + Check_Ptr((void *)PendingObjectPtr,__FILE__,__LINE__); + } + + /* + ** Chain to parent. + */ + MapClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * MapClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void MapClass::Code_Pointers(void) +{ + CELL cell; + + /* + ------------------------- Code the cell pointers ------------------------- + */ + for (cell = 0; cell < MAP_CELL_TOTAL; cell++) { + (*this)[cell].Code_Pointers(); + } + + GScreenClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * MapClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void MapClass::Decode_Pointers(void) +{ + CELL cell; + + /* + ------------------------ Decode the cell pointers ------------------------ + */ + for (cell = 0; cell < MAP_CELL_TOTAL; cell++) { + (*this)[cell].Decode_Pointers(); + } + + GScreenClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * GScreenClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void GScreenClass::Code_Pointers(void) +{ +} + + +/*********************************************************************************************** + * GScreenClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void GScreenClass::Decode_Pointers(void) +{ +} diff --git a/IOOBJ.CPP b/IOOBJ.CPP new file mode 100644 index 0000000..5062600 --- /dev/null +++ b/IOOBJ.CPP @@ -0,0 +1,2637 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ioobj.cpv 2.18 16 Oct 1995 16:51:22 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IOOBJ.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : January 16, 1995 * + * * + * Last Update : January 16, 1995 [BR] * + * * + *---------------------------------------------------------------------------------------------* + * All object-related loading/saving routines should go in this module, so it can be overlayed.* + *---------------------------------------------------------------------------------------------* + * Functions: * + * TeamTypeClass::Load -- Reads from a save game file. * + * TeamTypeClass::Save -- Write to a save game file. * + * TeamTypeClass::Code_Pointers -- codes class's pointers for load/save * + * TeamTypeClass::Decode_Pointers -- decodes pointers for load/save * + * TeamClass::Load -- Reads from a save game file. * + * TeamClass::Save -- Write to a save game file. * + * TeamClass::Code_Pointers -- codes class's pointers for load/save * + * TeamClass::Decode_Pointers -- decodes pointers for load/save * + * TriggerClass::Load -- Reads from a save game file. * + * TriggerClass::Save -- Write to a save game file. * + * TriggerClass::Code_Pointers -- codes class's pointers for load/save * + * TriggerClass::Decode_Pointers -- decodes pointers for load/save * + * AircraftClass::Load -- Reads from a save game file. * + * AircraftClass::Save -- Write to a save game file. * + * AircraftClass::Code_Pointers -- codes class's pointers for load/save * + * AircraftClass::Decode_Pointers -- decodes pointers for load/save * + * AnimClass::Load -- Reads from a save game file. * + * AnimClass::Save -- Write to a save game file. * + * AnimClass::Code_Pointers -- codes class's pointers for load/save * + * AnimClass::Decode_Pointers -- decodes pointers for load/save * + * BuildingClass::Load -- Reads from a save game file. * + * BuildingClass::Save -- Write to a save game file. * + * BuildingClass::Code_Pointers -- codes class's pointers for load/save * + * BuildingClass::Decode_Pointers -- decodes pointers for load/save * + * BulletClass::Load -- Reads from a save game file. * + * BulletClass::Save -- Write to a save game file. * + * BulletClass::Code_Pointers -- codes class's pointers for load/save * + * BulletClass::Decode_Pointers -- decodes pointers for load/save * + * InfantryClass::Load -- Reads from a save game file. * + * InfantryClass::Save -- Write to a save game file. * + * InfantryClass::Code_Pointers -- codes class's pointers for load/save * + * InfantryClass::Decode_Pointers -- decodes pointers for load/save * + * OverlayClass::Load -- Reads from a save game file. * + * OverlayClass::Save -- Write to a save game file. * + * OverlayClass::Code_Pointers -- codes class's pointers for load/save * + * OverlayClass::Decode_Pointers -- decodes pointers for load/save * + * ReinforcementClass::Load -- Reads from a save game file. * + * ReinforcementClass::Save -- Write to a save game file. * + * ReinforcementClass::Code_Pointers -- codes class's pointers for load/save * + * ReinforcementClass::Decode_Pointers -- decodes pointers for load/save * + * SmudgeClass::Load -- Reads from a save game file. * + * SmudgeClass::Save -- Write to a save game file. * + * SmudgeClass::Code_Pointers -- codes class's pointers for load/save * + * SmudgeClass::Decode_Pointers -- decodes pointers for load/save * + * TemplateClass::Load -- Reads from a save game file. * + * TemplateClass::Save -- Write to a save game file. * + * TemplateClass::Code_Pointers -- codes class's pointers for load/save * + * TemplateClass::Decode_Pointers -- decodes pointers for load/save * + * TerrainClass::Load -- Reads from a save game file. * + * TerrainClass::Save -- Write to a save game file. * + * TerrainClass::Code_Pointers -- codes class's pointers for load/save * + * TerrainClass::Decode_Pointers -- decodes pointers for load/save * + * UnitClass::Load -- Reads from a save game file. * + * UnitClass::Save -- Write to a save game file. * + * UnitClass::Code_Pointers -- codes class's pointers for load/save * + * UnitClass::Decode_Pointers -- decodes pointers for load/save * + * FactoryClass::Load -- Reads from a save game file. * + * FactoryClass::Save -- Write to a save game file. * + * FactoryClass::Code_Pointers -- codes class's pointers for load/save * + * FactoryClass::Decode_Pointers -- decodes pointers for load/save * + * LayerClass::Load -- Reads from a save game file. * + * LayerClass::Save -- Write to a save game file. * + * LayerClass::Code_Pointers -- codes class's pointers for load/save * + * LayerClass::Decode_Pointers -- decodes pointers for load/save * + * HouseClass::Load -- Reads from a save game file. * + * HouseClass::Save -- Write to a save game file. * + * HouseClass::Code_Pointers -- codes class's pointers for load/save * + * HouseClass::Decode_Pointers -- decodes pointers for load/save * + * ScoreClass::Load -- Reads from a save game file. * + * ScoreClass::Save -- Write to a save game file. * + * ScoreClass::Code_Pointers -- codes class's pointers for load/save * + * ScoreClass::Decode_Pointers -- decodes pointers for load/save * + * FlyClass::Code_Pointers -- codes class's pointers for load/save * + * FlyClass::Decode_Pointers -- decodes pointers for load/save * + * FuseClass::Code_Pointers -- codes class's pointers for load/save * + * FuseClass::Decode_Pointers -- decodes pointers for load/save * + * TarComClass::Code_Pointers -- codes class's pointers for load/save * + * TarComClass::Decode_Pointers -- decodes pointers for load/save * + * TurretClass::Code_Pointers -- codes class's pointers for load/save * + * TurretClass::Decode_Pointers -- decodes pointers for load/save * + * DriveClass::Code_Pointers -- codes class's pointers for load/save * + * DriveClass::Decode_Pointers -- decodes pointers for load/save * + * FootClass::Code_Pointers -- codes class's pointers for load/save * + * FootClass::Decode_Pointers -- decodes pointers for load/save * + * RadioClass::Code_Pointers -- codes class's pointers for load/save * + * RadioClass::Decode_Pointers -- decodes pointers for load/save * + * TechnoClass::Code_Pointers -- codes class's pointers for load/save * + * TechnoClass::Decode_Pointers -- decodes pointers for load/save * + * FlasherClass::Code_Pointers -- codes class's pointers for load/save * + * FlasherClass::Decode_Pointers -- decodes pointers for load/save * + * CargoClass::Code_Pointers -- codes class's pointers for load/save * + * CargoClass::Decode_Pointers -- decodes pointers for load/save * + * MissionClass::Code_Pointers -- codes class's pointers for load/save * + * MissionClass::Decode_Pointers -- decodes pointers for load/save * + * ObjectClass::Code_Pointers -- codes class's pointers for load/save * + * ObjectClass::Decode_Pointers -- decodes pointers for load/save * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * TeamTypeClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool TeamTypeClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(AbstractTypeClass), sizeof(*this), file, VTable)); +} + + +/*********************************************************************************************** + * TeamTypeClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool TeamTypeClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * TeamTypeClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TeamTypeClass::Code_Pointers(void) +{ + /* + -------------------------- Code the Class array -------------------------- + */ + for (int i = 0; i < ClassCount; i++) { + Class[i] = (TechnoTypeClass *)TechnoType_To_Target(Class[i]); + } +} + + +/*********************************************************************************************** + * TeamTypeClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TeamTypeClass::Decode_Pointers(void) +{ + /* + ------------------------- Decode the Class array ------------------------- + */ + for (int i = 0; i < ClassCount; i++) { + Class[i] = Target_To_TechnoType((TARGET)Class[i]); + Check_Ptr((void *)Class[i],__FILE__,__LINE__); + } +} + + +/*********************************************************************************************** + * TeamClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool TeamClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(AbstractClass), sizeof(*this), file, VTable)); +} + + +/*********************************************************************************************** + * TeamClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool TeamClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * TeamClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TeamClass::Code_Pointers(void) +{ + TeamTypeClass const * cls; + + /* + -------------------- Code Class & House for this team -------------------- + */ + cls = Class; + ((TeamTypeClass *&)Class) = (TeamTypeClass *)cls->As_Target(); + ((HouseClass *&)House) = (HouseClass *)House->Class->House; + + /* + --------------------------- Code the 'Member' ---------------------------- + */ + if (Member) { + Member = (FootClass *)Member->As_Target(); + } +} + + +/*********************************************************************************************** + * TeamClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TeamClass::Decode_Pointers(void) +{ + /* + ------------------- Decode Class & House for this team ------------------- + */ + ((TeamTypeClass *&)Class) = As_TeamType((TARGET)Class); + Check_Ptr((void *)Class,__FILE__,__LINE__); + ((HouseClass *&)House) = HouseClass::As_Pointer((HousesType)House); + Check_Ptr((void *)House,__FILE__,__LINE__); + + /* + -------------------------- Decode the 'Member' --------------------------- + */ + if (Member) { + switch (Target_Kind((TARGET)Member)) { + case KIND_INFANTRY: + Member = As_Infantry((TARGET)Member); + break; + + case KIND_UNIT: + Member = As_Unit((TARGET)Member); + break; + + case KIND_AIRCRAFT: + Member = As_Aircraft((TARGET)Member); + break; + + default: + Member = 0; + break; + } + + Check_Ptr((void *)Member,__FILE__,__LINE__); + } +} + + +/*********************************************************************************************** + * TriggerClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool TriggerClass::Load(FileClass & file) +{ + int rc = Read_Object(this, sizeof(*this), sizeof(*this), file, 0); + + /* + -------------------------- Add to HouseTriggers -------------------------- + */ + if (rc) { + if (House != HOUSE_NONE) { + HouseTriggers[House].Add(this); + } + } + + return(rc); +} + + +/*********************************************************************************************** + * TriggerClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool TriggerClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * TriggerClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TriggerClass::Code_Pointers(void) +{ + if (Team) { + Team = (TeamTypeClass *)Team->As_Target(); + } +} + + +/*********************************************************************************************** + * TriggerClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TriggerClass::Decode_Pointers(void) +{ + if (Team) { + Team = As_TeamType((TARGET)Team); + Check_Ptr((void *)Team,__FILE__,__LINE__); + } +} + + +/*********************************************************************************************** + * AircraftClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool AircraftClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(AbstractClass), sizeof(*this), file, VTable)); +} + + +/*********************************************************************************************** + * AircraftClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool AircraftClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * AircraftClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void AircraftClass::Code_Pointers(void) +{ + /* + ------------------------------ Code 'Class' ------------------------------ + */ + ((AircraftTypeClass *&)Class) = (AircraftTypeClass *)Class->Type; + + /* + ---------------------------- Chain to parent ----------------------------- + */ + FootClass::Code_Pointers(); + FlyClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * AircraftClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void AircraftClass::Decode_Pointers(void) +{ + /* + ----------------------------- Decode 'Class' ----------------------------- + */ + ((AircraftTypeClass const *&)Class) = &AircraftTypeClass::As_Reference((AircraftType)Class); + Check_Ptr((void *)Class,__FILE__,__LINE__); + + /* + ---------------------------- Chain to parent ----------------------------- + */ + FootClass::Decode_Pointers(); + FlyClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * AnimClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool AnimClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(AbstractClass), sizeof(*this), file, VTable)); +} + + +/*********************************************************************************************** + * AnimClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool AnimClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * AnimClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void AnimClass::Code_Pointers(void) +{ + /* + ------------------------------ Code 'Class' ------------------------------ + */ + ((AnimTypeClass *&)Class) = (AnimTypeClass *)Class->Type; + + /* + ----------------------------- Code 'Object' ------------------------------ + */ + if (Object) { + Object = (ObjectClass *)Object->As_Target(); + } + + /* + ---------------------------- Chain to parent ----------------------------- + */ + ObjectClass::Code_Pointers(); + StageClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * AnimClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void AnimClass::Decode_Pointers(void) +{ + /* + ----------------------------- Decode 'Class' ----------------------------- + */ + ((AnimTypeClass const *&)Class) = &AnimTypeClass::As_Reference((AnimType)Class); + Check_Ptr((void *)Class,__FILE__,__LINE__); + + /* + ---------------------------- Decode 'Object' ----------------------------- + */ + if (Object) { + Object = As_Object((TARGET)Object); + Check_Ptr((void *)Object,__FILE__,__LINE__); + } + + /* + ---------------------------- Chain to parent ----------------------------- + */ + ObjectClass::Decode_Pointers(); + StageClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * BuildingClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool BuildingClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(AbstractClass), sizeof(*this), file, VTable)); +} + + +/*********************************************************************************************** + * BuildingClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool BuildingClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * BuildingClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void BuildingClass::Code_Pointers(void) +{ + /* + ------------------------------ Code 'Class' ------------------------------ + */ + ((BuildingTypeClass const *&)Class) = (BuildingTypeClass *)Class->Type; + + /*------------------------------------------------------------------------ + Code the Factory value; there's not target conversion routine for factories, + so just use its Array ID, plus 1 so it doesn't look like a NULL value when + it's converted back + ------------------------------------------------------------------------*/ + if (Factory) { + Factory = (FactoryClass *)(Factories.ID(Factory) + 1); + } + + /* + ---------------------------- Chain to parent ----------------------------- + */ + TechnoClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * BuildingClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void BuildingClass::Decode_Pointers(void) +{ + /* + ----------------------------- Decode 'Class' ----------------------------- + */ + ((BuildingTypeClass const *&)Class) = &BuildingTypeClass::As_Reference((StructType)Class); + Check_Ptr((void *)Class,__FILE__,__LINE__); + + /*------------------------------------------------------------------------ + Decode the Factory value, subtracting off the '1' we added when coding it + ------------------------------------------------------------------------*/ + if (Factory) { + Factory = Factories.Raw_Ptr((int)Factory - 1); + Check_Ptr((void *)Factory,__FILE__,__LINE__); + } + + /* + ---------------------------- Chain to parent ----------------------------- + */ + TechnoClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * BulletClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool BulletClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(AbstractClass), sizeof(*this), file, VTable)); +} + + +/*********************************************************************************************** + * BulletClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool BulletClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * BulletClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void BulletClass::Code_Pointers(void) +{ + /* + ------------------------------ Code 'Class' ------------------------------ + */ + ((BulletTypeClass *&)Class) = (BulletTypeClass *)Class->Type; + + /* + ----------------------------- Code 'Payback' ----------------------------- + */ + if (Payback) + Payback = (TechnoClass *)Payback->As_Target(); + + /* + ---------------------------- Chain to parent ----------------------------- + */ + ObjectClass::Code_Pointers(); + FlyClass::Code_Pointers(); + FuseClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * BulletClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void BulletClass::Decode_Pointers(void) +{ + /* + ----------------------------- Decode 'Class' ----------------------------- + */ + ((BulletTypeClass const *&)Class) = &BulletTypeClass::As_Reference((BulletType)Class); + Check_Ptr((void *)Class,__FILE__,__LINE__); + + /* + ---------------------------- Decode 'Payback' ---------------------------- + */ + if (Payback) { + Payback = As_Techno((TARGET)Payback); + Check_Ptr((void *)Payback,__FILE__,__LINE__); + } + + /* + ---------------------------- Chain to parent ----------------------------- + */ + ObjectClass::Decode_Pointers(); + FlyClass::Decode_Pointers(); + FuseClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * InfantryClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool InfantryClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(AbstractClass), sizeof(*this), file, VTable)); +} + + +/*********************************************************************************************** + * InfantryClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool InfantryClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * InfantryClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void InfantryClass::Code_Pointers(void) +{ + /* + ------------------------------ Code 'Class' ------------------------------ + */ + ((InfantryTypeClass *&)Class) = (InfantryTypeClass *)Class->Type; + + /* + ---------------------------- Chain to parent ----------------------------- + */ + FootClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * InfantryClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void InfantryClass::Decode_Pointers(void) +{ + /* + ----------------------------- Decode 'Class' ----------------------------- + */ + ((InfantryTypeClass const *&)Class) = &InfantryTypeClass::As_Reference((InfantryType)Class); + Check_Ptr((void *)Class,__FILE__,__LINE__); + + /* + ---------------------------- Chain to parent ----------------------------- + */ + FootClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * OverlayClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool OverlayClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(AbstractClass), sizeof(*this), file, VTable)); +} + + +/*********************************************************************************************** + * OverlayClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool OverlayClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * OverlayClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void OverlayClass::Code_Pointers(void) +{ + /* + ------------------------------ Code 'Class' ------------------------------ + */ + ((OverlayTypeClass *&)Class) = (OverlayTypeClass *)Class->Type; + + /* + ---------------------------- Chain to parent ----------------------------- + */ + ObjectClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * OverlayClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void OverlayClass::Decode_Pointers(void) +{ + /* + ----------------------------- Decode 'Class' ----------------------------- + */ + ((OverlayTypeClass const *&)Class) = &OverlayTypeClass::As_Reference((OverlayType)Class); + Check_Ptr((void *)Class,__FILE__,__LINE__); + + /* + ---------------------------- Chain to parent ----------------------------- + */ + ObjectClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * SmudgeClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool SmudgeClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(AbstractClass), sizeof(*this), file, VTable)); +} + + +/*********************************************************************************************** + * SmudgeClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool SmudgeClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * SmudgeClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void SmudgeClass::Code_Pointers(void) +{ + /* + ------------------------------ Code 'Class' ------------------------------ + */ + ((SmudgeTypeClass const *&)Class) = (SmudgeTypeClass *)Class->Type; + + /* + ---------------------------- Chain to parent ----------------------------- + */ + ObjectClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * SmudgeClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void SmudgeClass::Decode_Pointers(void) +{ + /* + ----------------------------- Decode 'Class' ----------------------------- + */ + ((SmudgeTypeClass const *&)Class) = &SmudgeTypeClass::As_Reference((SmudgeType)Class); + Check_Ptr((void *)Class,__FILE__,__LINE__); + + /* + ---------------------------- Chain to parent ----------------------------- + */ + ObjectClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * TemplateClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool TemplateClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(AbstractClass), sizeof(*this), file, VTable)); +} + + +/*********************************************************************************************** + * TemplateClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool TemplateClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * TemplateClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TemplateClass::Code_Pointers(void) +{ + /* + ------------------------------ Code 'Class' ------------------------------ + */ + ((TemplateTypeClass *&)Class) = (TemplateTypeClass *)Class->Type; + + /* + ---------------------------- Chain to parent ----------------------------- + */ + ObjectClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * TemplateClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TemplateClass::Decode_Pointers(void) +{ + /* + ----------------------------- Decode 'Class' ----------------------------- + */ + ((TemplateTypeClass const *&)Class) = &TemplateTypeClass::As_Reference((TemplateType)Class); + Check_Ptr((void *)Class,__FILE__,__LINE__); + + /* + ---------------------------- Chain to parent ----------------------------- + */ + ObjectClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * TerrainClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool TerrainClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(AbstractClass), sizeof(*this), file, VTable)); +} + + +/*********************************************************************************************** + * TerrainClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool TerrainClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * TerrainClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TerrainClass::Code_Pointers(void) +{ + /* + ------------------------------ Code 'Class' ------------------------------ + */ + ((TerrainTypeClass *&)Class) = (TerrainTypeClass *)Class->Type; + + /* + ---------------------------- Chain to parent ----------------------------- + */ + ObjectClass::Code_Pointers(); + StageClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * TerrainClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TerrainClass::Decode_Pointers(void) +{ + /* + ----------------------------- Decode 'Class' ----------------------------- + */ + ((TerrainTypeClass const *&)Class) = &TerrainTypeClass::As_Reference((TerrainType)Class); + Check_Ptr((void *)Class,__FILE__,__LINE__); + + /* + ---------------------------- Chain to parent ----------------------------- + */ + ObjectClass::Decode_Pointers(); + StageClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * UnitClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool UnitClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(AbstractClass), sizeof(*this), file, VTable)); +} + + +/*********************************************************************************************** + * UnitClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool UnitClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * UnitClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void UnitClass::Code_Pointers(void) +{ + TarComClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * UnitClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void UnitClass::Decode_Pointers(void) +{ + TarComClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * FactoryClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool FactoryClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(StageClass), sizeof(*this), file, 0)); +} + + +/*********************************************************************************************** + * FactoryClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool FactoryClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * FactoryClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void FactoryClass::Code_Pointers(void) +{ + if (Object) { + Object = (TechnoClass *)Object->As_Target(); + } + + ((HouseClass *&)House) = (HouseClass *)House->Class->House; + + StageClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * FactoryClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void FactoryClass::Decode_Pointers(void) +{ + if (Object) { + Object = As_Techno((TARGET)Object); + Check_Ptr((void *)Object,__FILE__,__LINE__); + } + + ((HouseClass *&)House) = HouseClass::As_Pointer((HousesType)House); + Check_Ptr((void *)House,__FILE__,__LINE__); + + StageClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * LayerClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool LayerClass::Load(FileClass & file) +{ + int count; + int i; + ObjectClass * ptr; + + /* + ---------------------- Read # elements in the layer ---------------------- + */ + if (file.Read(&count,sizeof(count)) != sizeof(count)) { + return(false); + } + + /* + ---------------------------- Clear the array ----------------------------- + */ + Clear(); + + /* + ----------------------- Read in all array elements ----------------------- + */ + for (i = 0; i < count; i++) { + if (file.Read(&ptr, sizeof(ObjectClass *)) != sizeof(ObjectClass *)) { + return(false); + } + Add(ptr); + } + + return(true); +} + + +/*********************************************************************************************** + * LayerClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool LayerClass::Save(FileClass & file) +{ + int count; + int i; + ObjectClass * ptr; + + /* + ------------------------- Save # array elements -------------------------- + */ + count = Count(); + if (file.Write(&count, sizeof(count)) != sizeof(count)) + return(false); + + /* + --------------------------- Save all elements ---------------------------- + */ + for (i = 0; i < count; i++) { + ptr = (*this)[i]; + if (file.Write(&ptr, sizeof(ObjectClass *)) != sizeof(ObjectClass *)) + return(false); + } + + return(true); +} + + +/*********************************************************************************************** + * LayerClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void LayerClass::Code_Pointers(void) +{ + ObjectClass *obj; + + for (int i = 0; i < Count(); i++) { + obj = (*this)[i]; + (*this)[i] = (ObjectClass *)(obj->As_Target()); + } +} + + +/*********************************************************************************************** + * LayerClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void LayerClass::Decode_Pointers(void) +{ + TARGET target; + + for (int i = 0; i < Count(); i++) { + target = (TARGET)(*this)[i]; + (*this)[i] = (ObjectClass *)As_Object(target); + Check_Ptr((*this)[i],__FILE__,__LINE__); + } +} + + +/*********************************************************************************************** + * HouseClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool HouseClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(*this), sizeof(*this), file, 0)); +} + + +/*********************************************************************************************** + * HouseClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool HouseClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * HouseClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void HouseClass::Code_Pointers(void) +{ + /* + ------------------------------ Code 'Class' ------------------------------ + */ + ((HouseTypeClass const *&)Class) = (HouseTypeClass const *)Class->House; +} + + +/*********************************************************************************************** + * HouseClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void HouseClass::Decode_Pointers(void) +{ + /* + ----------------------------- Decode 'Class' ----------------------------- + */ + ((HouseTypeClass const *&)Class) = &HouseTypeClass::As_Reference((HousesType)Class); + Check_Ptr((void *)Class,__FILE__,__LINE__); +} + + +/*********************************************************************************************** + * ScoreClass::Load -- Loads from a save game file. * + * * + * INPUT: file -- The file to read the cell's data from. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool ScoreClass::Load(FileClass & file) +{ + return(Read_Object(this, sizeof(*this), sizeof(*this), file, 0)); +} + + +/*********************************************************************************************** + * ScoreClass::Save -- Write to a save game file. * + * * + * INPUT: file -- The file to write the cell's data to. * + * * + * OUTPUT: true = success, false = failure * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +bool ScoreClass::Save(FileClass & file) +{ + return(Write_Object(this, sizeof(*this), file)); +} + + +/*********************************************************************************************** + * ScoreClass::Code_Pointers -- codes class's pointers for load/save * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void ScoreClass::Code_Pointers(void) +{ +} + + +/*********************************************************************************************** + * ScoreClass::Decode_Pointers -- decodes pointers for load/save * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void ScoreClass::Decode_Pointers(void) +{ +} + + +/*********************************************************************************************** + * FlyClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void FlyClass::Code_Pointers(void) +{ +} + + +/*********************************************************************************************** + * FlyClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void FlyClass::Decode_Pointers(void) +{ +} + + +/*********************************************************************************************** + * FuseClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void FuseClass::Code_Pointers(void) +{ +} + + +/*********************************************************************************************** + * FuseClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void FuseClass::Decode_Pointers(void) +{ +} + + +/*********************************************************************************************** + * TarComClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TarComClass::Code_Pointers(void) +{ + TurretClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * TarComClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TarComClass::Decode_Pointers(void) +{ + TurretClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * TurretClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TurretClass::Code_Pointers(void) +{ + DriveClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * TurretClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TurretClass::Decode_Pointers(void) +{ + DriveClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * DriveClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void DriveClass::Code_Pointers(void) +{ + /* + ------------------------------ Code 'Class' ------------------------------ + */ + ((UnitTypeClass *&)Class) = (UnitTypeClass *)Class->Type; + + /* + ---------------------------- Chain to parent ----------------------------- + */ + FootClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * DriveClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void DriveClass::Decode_Pointers(void) +{ + /* + ----------------------------- Decode 'Class' ----------------------------- + */ + ((UnitTypeClass const *&)Class) = &UnitTypeClass::As_Reference((UnitType)Class); + Check_Ptr((void *)Class,__FILE__,__LINE__); + + /* + ---------------------------- Chain to parent ----------------------------- + */ + FootClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * FootClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void FootClass::Code_Pointers(void) +{ + if (Team) + Team = (TeamClass *)Team->As_Target(); + + if (Member) { + Member = (FootClass *)Member->As_Target(); + } + + TechnoClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * FootClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void FootClass::Decode_Pointers(void) +{ + if (Team) { + Team = As_Team((TARGET)Team); + Check_Ptr((void *)Team,__FILE__,__LINE__); + } + + if (Member) { + Member = (FootClass *)As_Techno((TARGET)Member); + Check_Ptr((void *)Member,__FILE__,__LINE__); + } + + TechnoClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * RadioClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void RadioClass::Code_Pointers(void) +{ + /* + ------------------------------ Code 'Radio' ------------------------------ + */ + if (Radio) { + Radio = (RadioClass *)Radio->As_Target(); + } + + MissionClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * RadioClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void RadioClass::Decode_Pointers(void) +{ + /* + ----------------------------- Decode 'Radio' ----------------------------- + */ + if (Radio) { + Radio = As_Techno((TARGET)Radio); + Check_Ptr((void *)Radio,__FILE__,__LINE__); + } + + MissionClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * TechnoClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TechnoClass::Code_Pointers(void) +{ + /* + ------------------------------ Code 'House' ------------------------------ + */ + ((HouseClass *&)House) = (HouseClass *)(House->Class->House); + + FlasherClass::Code_Pointers(); + StageClass::Code_Pointers(); + CargoClass::Code_Pointers(); + DoorClass::Code_Pointers(); + + RadioClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * TechnoClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void TechnoClass::Decode_Pointers(void) +{ + /* + ----------------------------- Decode 'House' ----------------------------- + */ + ((HouseClass *&)House) = HouseClass::As_Pointer((HousesType)House); + Check_Ptr((void *)House,__FILE__,__LINE__); + + FlasherClass::Decode_Pointers(); + StageClass::Decode_Pointers(); + CargoClass::Decode_Pointers(); + DoorClass::Decode_Pointers(); + + RadioClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * FlasherClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void FlasherClass::Code_Pointers(void) +{ +} + + +/*********************************************************************************************** + * FlasherClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void FlasherClass::Decode_Pointers(void) +{ +} + + +/*********************************************************************************************** + * CargoClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void CargoClass::Code_Pointers(void) +{ + /* + ---------------------------- Code 'CargoHold' ---------------------------- + */ + if (CargoHold) { + CargoHold = (FootClass *)CargoHold->As_Target(); + } +} + + +/*********************************************************************************************** + * CargoClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void CargoClass::Decode_Pointers(void) +{ + /* + --------------------------- Decode 'CargoHold' --------------------------- + */ + if (CargoHold) { + CargoHold = (FootClass *)As_Techno((TARGET)CargoHold); + Check_Ptr((void *)CargoHold,__FILE__,__LINE__); + } +} + + +/*********************************************************************************************** + * MissionClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void MissionClass::Code_Pointers(void) +{ + ObjectClass::Code_Pointers(); +} + + +/*********************************************************************************************** + * MissionClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void MissionClass::Decode_Pointers(void) +{ + ObjectClass::Decode_Pointers(); +} + + +/*********************************************************************************************** + * ObjectClass::Code_Pointers -- codes class's pointers for load/save * + * * + * This routine "codes" the pointers in the class by converting them to a number * + * that still represents the object pointed to, but isn't actually a pointer. This * + * allows a saved game to properly load without relying on the games data still * + * being in the exact same location. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void ObjectClass::Code_Pointers(void) +{ + if (Next) { + Next = (ObjectClass *)Next->As_Target(); + } + + if (Trigger) { + Trigger = (TriggerClass *)Trigger->As_Target(); + } +} + + +/*********************************************************************************************** + * ObjectClass::Decode_Pointers -- decodes pointers for load/save * + * * + * This routine "decodes" the pointers coded in Code_Pointers by converting the * + * code values back into object pointers. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/02/1995 BR : Created. * + *=============================================================================================*/ +void ObjectClass::Decode_Pointers(void) +{ + if (Next) { + Next = As_Object((TARGET)Next); + Check_Ptr((void *)Next,__FILE__,__LINE__); + } + + if (Trigger) { + Trigger = As_Trigger((TARGET)Trigger); + Check_Ptr((void *)Trigger,__FILE__,__LINE__); + } +} + diff --git a/IPX.CPP b/IPX.CPP new file mode 100644 index 0000000..6ab3d2c --- /dev/null +++ b/IPX.CPP @@ -0,0 +1,1111 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ipx.cpv 2.17 16 Oct 1995 16:49:34 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IPX.CPP * + * * + * Programmer : Barry Nance * + * from Client/Server LAN Programming * + * Westwood-ized by Bill Randolph * + * * + * Start Date : December 14, 1994 * + * * + * Last Update : December 15, 1994 [BR] * + * * + *-------------------------------------------------------------------------* + * Pitfalls: * + * - Never try to use a closed socket; always check the return code from * + * IPX_Open_Socket(). * + * - Always give IPX an outstanding ECB for listening, before you send. * + * - It turns out that IPX is pretty bad about saving registers, so if * + * you have any register variables in your program, they may get * + * trashed. To circumvent this, all functions in this module save & * + * restore the registers before invoking any IPX or NETX function. * + * * + *-------------------------------------------------------------------------* + * Functions: * + * IPX_SPX_Installed -- checks for installation of IPX/SPX * + * IPX_Open_Socket -- opens an IPX socket for sending or receiving * + * IPX_Close_Socket -- closes an open socket * + * IPX_Get_Connection_Number -- gets local Connection Number * + * IPX_Get_1st_Connection_Num -- gets 1st Connect Number for given user * + * IPX_Get_Internet_Address -- gets Network Number & Node Address * + * IPX_Get_User_ID -- gets user ID from Connection Number * + * IPX_Listen_For_Packet -- commands IPX to listen for a packet * + * IPX_Send_Packet -- commands IPX to send a packet * + * IPX_Get_Local_Target -- fills in ImmediateAddress field of ECB * + * IPX_Cancel_Event -- cancels an operation in progress * + * Let_IPX_Breath -- gives IPX some CPU time * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "ipx95.h" + + +/*************************************************************************** + * IPX_SPX_Installed -- checks for installation of IPX/SPX * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = not installed; 1 = IPX only, 2 = IPX and SPX are installed * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/14/1994 BR : Created. * + *=========================================================================*/ +int IPX_SPX_Installed(void) +{ + +#ifndef NOT_FOR_WIN95 + + return (IPX_Initialise()); + +#else //NOT_FOR_WIN95 + + union REGS regs; + struct SREGS sregs; + RMIType rmi; + + /*------------------------------------------------------------------------ + Init all registers to 0's + ------------------------------------------------------------------------*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + memset (&rmi, 0 ,sizeof(rmi)); + + /*------------------------------------------------------------------------ + Fill in registers for the DPMI call, function 0x300 + ------------------------------------------------------------------------*/ + regs.w.ax = DPMI_CALL_REAL_INT; // DPMI function to call + regs.w.bx = 0x002f; // interrupt # to invoke + sregs.es = FP_SEG(&rmi); // tell DPMI where the RMI is + regs.x.edi = FP_OFF(&rmi); + + /*------------------------------------------------------------------------ + Fill in registers for the real-mode interrupt handler. + To test for the presence of IPX, set AH to 0x7a, AL to 0, and invoke + interrupt 0x2f (the "multiplex" interrupt). If IPX is installed, + AL will be 0xff, and ES:DI will contain the IPX/SPX function address. + ------------------------------------------------------------------------*/ + rmi.eax = 0x00007a00; + + /*------------------------------------------------------------------------ + call DPMI + ------------------------------------------------------------------------*/ + int386x(DPMI_INT, ®s, ®s, &sregs); + + /*------------------------------------------------------------------------ + If IPX isn't there, return 0 + ------------------------------------------------------------------------*/ + if ( (rmi.eax & 0x00ff) != 0xff) { + return(0); + } + + /*------------------------------------------------------------------------ + Test for SPX by invoking the IPX_SPX function with BX = 0x10, and AL = 0. + If SPX is present, AL will be 0xff. + ------------------------------------------------------------------------*/ + /*........................................................................ + Fill in registers for the DPMI call + ........................................................................*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + memset (&rmi, 0 ,sizeof(rmi)); + regs.w.ax = DPMI_CALL_REAL_INT; // DPMI function to call + regs.w.bx = IPX_INT; // interrupt # to invoke + sregs.es = FP_SEG(&rmi); // tell DPMI where the RMI is + regs.x.edi = FP_OFF(&rmi); + /*........................................................................ + Fill in registers for the interrupt call + ........................................................................*/ + rmi.ebx = 0x00000010; + /*........................................................................ + call DPMI + ........................................................................*/ + int386x(DPMI_INT, ®s, ®s, &sregs); + + /*------------------------------------------------------------------------ + SPX is installed; return '2' + ------------------------------------------------------------------------*/ + if ( (rmi.eax & 0x00ff) == 0xff) { + return(2); + } + + /*------------------------------------------------------------------------ + SPX is not installed; return '1' + ------------------------------------------------------------------------*/ + return(1); +#endif //NOT_FOR_WIN95 +} /* end of IPX_SPX_Installed */ + + +/*************************************************************************** + * IPX_Open_Socket -- opens an IPX socket for sending or receiving * + * * + * INPUT: * + * socket the socket number to open * + * * + * OUTPUT: * + * 0 = OK * + * -1 = IPX not installed * + * 0xfe = socket table is full * + * 0xff = socket is already open * + * * + * WARNINGS: * + * The application must define its socket number carefully. Use * + * values from 0x4000 to 0x8000 for custom socket numbers. The app * + * must know its own socket number as well as the socket number of * + * a destination workstation. * + * * + * HISTORY: * + * 12/15/1994 BR : Created. * + *=========================================================================*/ +#ifdef NOT_FOR_WIN95 +int IPX_Open_Socket(unsigned short socket) +{ + union REGS regs; + struct SREGS sregs; + RMIType rmi; + int rc; + + /*------------------------------------------------------------------------ + Open the socket: + DX = socket number + AL = 0 for short-lived socket, 0xff for long-lived socket + ------------------------------------------------------------------------*/ + /*........................................................................ + Fill in registers for the DPMI call + ........................................................................*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + memset (&rmi, 0 ,sizeof(rmi)); + regs.w.ax = DPMI_CALL_REAL_INT; // DPMI function to call + regs.w.bx = IPX_INT; // interrupt # to invoke + sregs.es = FP_SEG (&rmi); // tell DPMI where the RMI is + regs.x.edi = FP_OFF (&rmi); + /*........................................................................ + Fill in registers for the interrupt call + ........................................................................*/ + rmi.ebx = IPX_OPEN_SOCKET; // function code + rmi.edx = socket; // desired socket # + rmi.eax = 0x00ff; // make this a long-lived socket + /*........................................................................ + call DPMI + ........................................................................*/ + int386x (DPMI_INT, ®s, ®s, &sregs); + + rc = (rmi.eax & 0xff); + + return(rc); + +} /* end of IPX_Open_Socket */ + +#endif +/*************************************************************************** + * IPX_Close_Socket -- closes an open socket * + * * + * INPUT: * + * socket socket number to close * + * * + * OUTPUT: * + * 0 = ok, -1 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/15/1994 BR : Created. * + *=========================================================================*/ +#ifdef NOT_FOR_WIN95 +int IPX_Close_Socket(unsigned short socket) +{ + union REGS regs; + struct SREGS sregs; + RMIType rmi; + + /*------------------------------------------------------------------------ + Close the socket: + DX = socket number + ------------------------------------------------------------------------*/ + /*........................................................................ + Fill in registers for the DPMI call + ........................................................................*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + memset (&rmi, 0 ,sizeof(rmi)); + regs.w.ax = DPMI_CALL_REAL_INT; // DPMI function to call + regs.w.bx = IPX_INT; // interrupt # to invoke + sregs.es = FP_SEG(&rmi); // tell DPMI where the RMI is + regs.x.edi = FP_OFF(&rmi); + /*........................................................................ + Fill in registers for the interrupt call + ........................................................................*/ + rmi.ebx = IPX_CLOSE_SOCKET; + rmi.edx = socket; + /*........................................................................ + call DPMI + ........................................................................*/ + int386x(DPMI_INT, ®s, ®s, &sregs); + + return(0); + +} /* end of IPX_Close_Socket */ +#endif //NOT_FOR_WIN95 + +/*************************************************************************** + * IPX_Get_Connection_Number -- gets local Connection Number * + * * + * This Novell call will the return the user's local "Connection Number". * + * This value will be 0 if the user isn't logged into Novell, so this * + * routine can be used to detect if other calls (such as Get_Local_Target) * + * will be OK. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * Connection Number, 0 = none * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/15/1994 BR : Created. * + *=========================================================================*/ +#ifdef NOT_FOR_WIN95 +int IPX_Get_Connection_Number(void) +{ + union REGS regs; + struct SREGS sregs; + RMIType rmi; + int num; + + /*------------------------------------------------------------------------ + Call Interrupt 0x21, with AH = 0xdc. This tells Novell to put the local + connection number into AL. + ------------------------------------------------------------------------*/ + /*........................................................................ + Fill in registers for the DPMI call + ........................................................................*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + memset (&rmi, 0 ,sizeof(rmi)); + regs.w.ax = DPMI_CALL_REAL_INT; // DPMI function to call + regs.w.bx = 0x21; // interrupt # to invoke + sregs.es = FP_SEG(&rmi); // tell DPMI where the RMI is + regs.x.edi = FP_OFF(&rmi); + /*........................................................................ + Fill in registers for the interrupt call + ........................................................................*/ + rmi.eax = 0x0000dc00; + /*........................................................................ + call DPMI + ........................................................................*/ + int386x(DPMI_INT, ®s, ®s, &sregs); + + num = rmi.eax & 0x00ff; + + return(num); + +} /* end of IPX_Get_Connection_Number */ + +#endif //NOT_FOR_WIN95 + +/*************************************************************************** + * IPX_Get_1st_Connection_Num -- gets 1st Connect Number for given user * + * * + * This gets the Connection Number for the given User ID. Since a user * + * may be logged in more than once, this just returns the first connection * + * found and ignores the others. * + * * + * INPUT: * + * username name of the user to get the Connection Number for * + * * + * OUTPUT: * + * first-found Connection Number for that user, 0 if user not logged in * + * * + * WARNINGS: * + * * + * HISTORY: * + * 12/15/1994 BR : Created. * + *=========================================================================*/ +#ifdef NOT_FOR_WIN95 +int IPX_Get_1st_Connection_Num (char *username) +{ + struct request_buffer { + unsigned short len; // username length + 5 + unsigned char buffer_type; // ConnectionNum = 0x15 + unsigned short object_type; // set ot 0x0100 + unsigned char name_len; // length of username + char name [48]; // copy of username + unsigned short reserved; + }; + struct reply_buffer { + unsigned short len; + unsigned char number_connections; // will be 0 - 100 + unsigned char connection_num [100]; // array of connection numbers + unsigned short reserved[2]; + }; + union REGS regs; + struct SREGS sregs; + RMIType rmi; + struct request_buffer *reqbuf; + struct reply_buffer *replybuf; + unsigned short segment; // for DOS allocation + unsigned short selector; // for DOS allocation + int num_conns; // # connections returned + int conn_num; // connection number + int rc; + + /*------------------------------------------------------------------------ + Allocate DOS memory to store the buffers passed to the interrupt + ------------------------------------------------------------------------*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_ALLOC_DOS_MEM; // DPMI function to call + regs.x.ebx = (sizeof(struct request_buffer) + // # paragraphs to allocate + sizeof(struct reply_buffer) + 15) >> 4; + int386x (DPMI_INT, ®s, ®s, &sregs); // allocate the memory + /*........................................................................ + If the carry flag is set, DPMI is indicating an error. + ........................................................................*/ + if (regs.x.cflag) { + return(0); + } + + /*........................................................................ + Get pointers to allocated memory. + 'reqbuf' is just the returned real-mode segment, multiplied by 16. + 'replybuf' is an offset from 'reqbuf'. + ........................................................................*/ + segment = regs.w.ax; + selector = regs.w.dx; + reqbuf = (struct request_buffer *)(segment << 4); + replybuf = (struct reply_buffer *) + (((char *)reqbuf) + sizeof (struct request_buffer)); + + /*------------------------------------------------------------------------ + Init the contents of the request & reply buffers + ------------------------------------------------------------------------*/ + reqbuf->len = (unsigned short)(strlen(username) + 5); + reqbuf->buffer_type = 0x15; + reqbuf->object_type = 0x0100; + reqbuf->name_len = (unsigned char) strlen(username); + strcpy(reqbuf->name, username); + reqbuf->reserved = reqbuf->reserved; // prevent compiler warning + replybuf->len = 101; + replybuf->reserved[0] = replybuf->reserved[0]; // prevent compiler warning + replybuf->reserved[0] = replybuf->reserved[1]; // prevent compiler warning + + /*------------------------------------------------------------------------ + Invoke Int 21 with AH=0xe3, DS:SI=&request_buffer, ES:DI=&reply_buffer + ------------------------------------------------------------------------*/ + /*........................................................................ + Fill in registers for the DPMI call + ........................................................................*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + memset (&rmi, 0 ,sizeof(rmi)); + regs.w.ax = DPMI_CALL_REAL_INT; // DPMI function to call + regs.w.bx = 0x21; // interrupt # to invoke + sregs.es = FP_SEG(&rmi); // tell DPMI where the RMI is + regs.x.edi = FP_OFF(&rmi); + /*........................................................................ + Fill in registers for the interrupt call + ........................................................................*/ + rmi.eax = 0x0000e300; + rmi.ds = segment; + rmi.esi = 0; + rmi.es = segment; + rmi.edi = sizeof(struct request_buffer); + /*........................................................................ + call DPMI + ........................................................................*/ + int386x(DPMI_INT, ®s, ®s, &sregs); + + /*------------------------------------------------------------------------ + Stash the 1st connection number + ------------------------------------------------------------------------*/ + rc = (rmi.eax & 0x00ff); // if AL !=0, error + num_conns = replybuf->number_connections; // # times user is logged in + conn_num = (int )replybuf->connection_num[0]; // 1st connection # + + /*------------------------------------------------------------------------ + Free DOS memory + ------------------------------------------------------------------------*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_FREE_DOS_MEM; // DPMI function to call + regs.x.edx = selector; // ptr to free + int386x (DPMI_INT, ®s, ®s, &sregs); // allocate the memory + + /*------------------------------------------------------------------------ + Return error if function failed, or user not logged in + ------------------------------------------------------------------------*/ + if (rc != 0 || num_conns==0) { + return(0); + } else { + return(conn_num); + } +} +#endif //NOT_FOR_WIN95 + +/*************************************************************************** + * IPX_Get_Internet_Address -- gets Network Number & Node Address * + * * + * Once you've obtained a Connection Number from IPX_Get_Connection_Number * + * or IPX_Get_1st_Connection_Num, use this function to translate it into * + * a Network Number and Node Address; then, place those numbers in the * + * IPX header for outgoing packets. * + * * + * INPUT: * + * connection_number Connection Number to translate * + * network_number ptr: will hold Network Number * + * physical_node ptr: will hold Node Address * + * * + * OUTPUT: * + * 0 = OK, -1 = error * + * * + * WARNINGS: * + * If connection_number is 0 and NETX isn't running, this routine * + * will just put garbage into the network_number and physical_node. * + * * + * HISTORY: * + * 12/15/1994 BR : Created. * + *=========================================================================*/ +#ifdef NOT_FOR_WIN95 +int IPX_Get_Internet_Address(int connection_number, + unsigned char *network_number, unsigned char *physical_node) +{ + struct request_buffer { + unsigned short len; + unsigned char buffer_type; // Internet = 0x13 + unsigned char connection_number; // Conn. Number to translate + }; + struct reply_buffer { + unsigned short len; + unsigned char network_number [4]; // filled in by IPX + unsigned char physical_node [6]; // filled in by IPX + unsigned short server_socket; // filled in by IPX, but don't use! + }; + union REGS regs; + struct SREGS sregs; + RMIType rmi; + struct request_buffer *reqbuf; + struct reply_buffer *replybuf; + unsigned short segment; // for DOS allocation + unsigned short selector; // for DOS allocation + + /*------------------------------------------------------------------------ + Error if invalid connection is given + ------------------------------------------------------------------------*/ + if (connection_number==0) + return(-1); + + /*------------------------------------------------------------------------ + Allocate DOS memory to store the buffers passed to the interrupt + ------------------------------------------------------------------------*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_ALLOC_DOS_MEM; // DPMI function to call + regs.x.ebx = (sizeof(struct request_buffer) + // # paragraphs to allocate + sizeof(struct reply_buffer) + 15) >> 4; + int386x (DPMI_INT, ®s, ®s, &sregs); // allocate the memory + /*........................................................................ + If the carry flag is set, DPMI is indicating an error. + ........................................................................*/ + if (regs.x.cflag) { + return(-1); + } + + /*........................................................................ + Get pointers to allocated memory. + 'reqbuf' is just the returned real-mode segment, multiplied by 16. + 'replybuf' is an offset from 'reqbuf'. + ........................................................................*/ + segment = regs.w.ax; + selector = regs.w.dx; + reqbuf = (struct request_buffer *)(segment << 4); + replybuf = (struct reply_buffer *) + (((char *)reqbuf) + sizeof (struct request_buffer)); + + /*------------------------------------------------------------------------ + Init the contents of the request & reply buffers + ------------------------------------------------------------------------*/ + reqbuf->len = 2; + reqbuf->buffer_type = 0x13; + reqbuf->connection_number = (unsigned char)connection_number; + replybuf->len = 12; + replybuf->network_number[0] = replybuf->network_number[0]; // suppress warning + replybuf->physical_node[0] = replybuf->physical_node[0]; // suppress warning + replybuf->server_socket = replybuf->server_socket; // suppress warning + + /*------------------------------------------------------------------------ + Invoke Int 21 with AH=0xe3, DS:SI=&request_buffer, ES:DI=&reply_buffer + ------------------------------------------------------------------------*/ + /*........................................................................ + Fill in registers for the DPMI call + ........................................................................*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + memset (&rmi, 0 ,sizeof(rmi)); + regs.w.ax = DPMI_CALL_REAL_INT; // DPMI function to call + regs.w.bx = 0x21; // interrupt # to invoke + sregs.es = FP_SEG(&rmi); // tell DPMI where the RMI is + regs.x.edi = FP_OFF(&rmi); + /*........................................................................ + Fill in registers for the interrupt call + ........................................................................*/ + rmi.eax = 0x0000e300; + rmi.ds = segment; + rmi.esi = 0; + rmi.es = segment; + rmi.edi = sizeof(struct request_buffer); + /*........................................................................ + call DPMI + ........................................................................*/ + int386x(DPMI_INT, ®s, ®s, &sregs); + + memcpy(network_number, replybuf->network_number, 4); + memcpy(physical_node, replybuf->physical_node, 6); + + /*------------------------------------------------------------------------ + Free DOS memory + ------------------------------------------------------------------------*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_FREE_DOS_MEM; // DPMI function to call + regs.x.edx = selector; // ptr to free + int386x (DPMI_INT, ®s, ®s, &sregs); // allocate the memory + + return(0); + +} /* end of IPX_Get_Internet_Address */ + +#endif // NOT_FOR_WIN95 + +/*************************************************************************** + * IPX_Get_User_ID -- gets user ID from Connection Number * + * * + * INPUT: * + * connection_number Connection Number to get User ID for * + * user_id ptr to buffer to put User ID into; * + * size must be >= 48 chars * + * * + * OUTPUT: * + * 0 = OK, -1 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/15/1994 BR : Created. * + *=========================================================================*/ +#ifdef NOT_FOR_WIN95 +int IPX_Get_User_ID(int connection_number, char *user_id) +{ + struct request_buffer { + unsigned short len; + unsigned char buffer_type; // 0x16 = UserID buffer type + unsigned char connection_number; // Connection Number to get ID for + }; + struct reply_buffer { + unsigned short len; + unsigned char object_id[4]; + unsigned char object_type[2]; + char object_name[48]; + char login_time[7]; + unsigned short reserved; + }; + union REGS regs; + struct SREGS sregs; + RMIType rmi; + struct request_buffer *reqbuf; + struct reply_buffer *replybuf; + unsigned short segment; // for DOS allocation + unsigned short selector; // for DOS allocation + + /*------------------------------------------------------------------------ + Error if invalid connection is given + ------------------------------------------------------------------------*/ + if (connection_number==0) + return(-1); + + /*------------------------------------------------------------------------ + Allocate DOS memory to store the buffers passed to the interrupt + ------------------------------------------------------------------------*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_ALLOC_DOS_MEM; // DPMI function to call + regs.x.ebx = (sizeof(struct request_buffer) + // # paragraphs to allocate + sizeof(struct reply_buffer) + 15) >> 4; + int386x (DPMI_INT, ®s, ®s, &sregs); // allocate the memory + /*........................................................................ + If the carry flag is set, DPMI is indicating an error. + ........................................................................*/ + if (regs.x.cflag) { + return(-1); + } + + /*........................................................................ + Get pointers to allocated memory. + 'reqbuf' is just the returned real-mode segment, multiplied by 16. + 'replybuf' is an offset from 'reqbuf'. + ........................................................................*/ + segment = regs.w.ax; + selector = regs.w.dx; + reqbuf = (struct request_buffer *)(segment << 4); + replybuf = (struct reply_buffer *) + (((char *)reqbuf) + sizeof (struct request_buffer)); + + /*------------------------------------------------------------------------ + Init the contents of the request & reply buffers + ------------------------------------------------------------------------*/ + reqbuf->len = 2; + reqbuf->buffer_type = 0x16; + reqbuf->connection_number = (unsigned char)connection_number; + replybuf->len = sizeof(struct reply_buffer) - 2; + replybuf->object_id[0] = replybuf->object_id[0]; // suppress warnings + replybuf->object_type[0] = replybuf->object_type[0]; // suppress warnings + replybuf->login_time[0] = replybuf->login_time[0]; // suppress warnings + replybuf->reserved = replybuf->reserved; // suppress warnings + + /*------------------------------------------------------------------------ + Invoke Int 21 with AH=0xe3, DS:SI=&request_buffer, ES:DI=&reply_buffer + ------------------------------------------------------------------------*/ + /*........................................................................ + Fill in registers for the DPMI call + ........................................................................*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + memset (&rmi, 0 ,sizeof(rmi)); + regs.w.ax = DPMI_CALL_REAL_INT; // DPMI function to call + regs.w.bx = 0x21; // interrupt # to invoke + sregs.es = FP_SEG(&rmi); // tell DPMI where the RMI is + regs.x.edi = FP_OFF(&rmi); + /*........................................................................ + Fill in registers for the interrupt call + ........................................................................*/ + rmi.eax = 0x0000e300; + rmi.ds = segment; + rmi.esi = 0; + rmi.es = segment; + rmi.edi = sizeof(struct request_buffer); + /*........................................................................ + call DPMI + ........................................................................*/ + int386x(DPMI_INT, ®s, ®s, &sregs); + + /*------------------------------------------------------------------------ + Fill in the caller's buffer with the user name + ------------------------------------------------------------------------*/ + strncpy(user_id, replybuf->object_name, 48); + + /*------------------------------------------------------------------------ + Free DOS memory + ------------------------------------------------------------------------*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_FREE_DOS_MEM; // DPMI function to call + regs.x.edx = selector; // ptr to free + int386x (DPMI_INT, ®s, ®s, &sregs); // allocate the memory + + return(0); + +} /* end of IPX_Get_User_ID */ +#endif //NOT_FOR_WIN95 + + +/*************************************************************************** + * IPX_Listen_For_Packet -- commands IPX to listen for a packet * + * * + * Before calling this function, you must fill in an ECB: * + * SocketNumber: must contain the socket you've opened, * + * and are "listening" on * + * Event_Service_Routine: optionally points to a callback routine * + * PacketCount: set to 2, to tell IPX there are 2 areas to * + * store the incoming data in * + * Packet[0].Address: set to the address of an IPXHeaderType * + * Packet[0].Length: sizeof(IPXHeaderType) * + * Packet[1].Address: address of data buffer, for the packet * + * Packet[1].Length: size of the data buffer * + * * + * When the packet is received, ECBType.CompletionCode will be 0 if * + * successful. Otherwise, some error occurred. * + * * + * You should initialize the ECB to 0's before filling it in. * + * * + * INPUT: * + * ecb_ptr pointer to a filled-in ECB; MUST be real-mode memory * + * * + * OUTPUT: * + * 0 = OK, IPX error otherwise * + * * + * WARNINGS: * + * The ECB must be located in real-mode memory, as well as the values * + * pointed to in the ECB (the IPX Header, the buffer to send, etc.) * + * * + * HISTORY: * + * 12/15/1994 BR : Created. * + *=========================================================================*/ +#ifdef NOT_FOR_WIN95 +int IPX_Listen_For_Packet(struct ECB *ecb_ptr) +{ + union REGS regs; + struct SREGS sregs; + RMIType rmi; + + /*------------------------------------------------------------------------ + Call IPX with ES:SI=ecb_ptr + ------------------------------------------------------------------------*/ + /*........................................................................ + Fill in registers for the DPMI call + ........................................................................*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + memset (&rmi, 0 ,sizeof(rmi)); + regs.w.ax = DPMI_CALL_REAL_INT; // DPMI function to call + regs.w.bx = IPX_INT; // interrupt # to invoke + sregs.es = FP_SEG(&rmi); // tell DPMI where the RMI is + regs.x.edi = FP_OFF(&rmi); + /*........................................................................ + Fill in registers for the interrupt call + ........................................................................*/ + rmi.ebx = IPX_LISTEN_FOR_PACKET; + rmi.es = (short)((long)ecb_ptr >> 4); + rmi.esi = (long)((long)ecb_ptr & 0x000f); + /*........................................................................ + call DPMI + ........................................................................*/ + int386x(DPMI_INT, ®s, ®s, &sregs); + + return(rmi.eax & 0x00ff); + +} /* end of IPX_Listen_For_Packet */ +#endif //NOT_FOR_WIN95 + +/*************************************************************************** + * IPX_Send_Packet -- commands IPX to send a packet * + * * + * Before calling this function, you must fill in an ECB: * + * SocketNumber: must contain the socket you've opened, * + * and are sending on * + * Event_Service_Routine: optionally points to a callback routine * + * PacketCount: set to 2, to tell IPX there are 2 areas the * + * outgoing data is stored in * + * Packet[0].Address: set to the address of an IPXHeaderType * + * Packet[0].Length: sizeof(IPXHeaderType) * + * Packet[1].Address: address of buffer containing data to send * + * Packet[1].Length: size of the data buffer * + * ImmediateAddress: must be filled in with the node address of * + * the bridge that will route the message; * + * fill this in by calling IPX_Get_Local_Target * + * * + * Also, you must fill in the IPXHeaderType with certain values: * + * PacketType: set to 4 for IPX * + * DestNetworkNumber: Network Number of the destination system * + * DestNetworkNode: Node Address of the destination system * + * DestNetworkSocket: the destination system's socket to send to; * + * this doesn't have to be the same as the * + * socket opened on the local machine. * + * * + * You should initialize the ECB & IPXHeader to 0's before filling them in.* + * * + * INPUT: * + * ecb_ptr pointer to a filled-in ECB * + * * + * OUTPUT: * + * none. This function doesn't return anything; the caller must check * + * its ECB.CompletionCode for: 0 = OK, IPX Error otherwise. * + * * + * WARNINGS: * + * The ECB must be located in real-mode memory, as well as the values * + * pointed to in the ECB (the IPX Header, the buffer to send, etc.) * + * * + * HISTORY: * + * 12/15/1994 BR : Created. * + *=========================================================================*/ +#ifdef NOT_FOR_WIN95 +void IPX_Send_Packet(struct ECB *ecb_ptr) +{ + union REGS regs; + struct SREGS sregs; + RMIType rmi; + + /*------------------------------------------------------------------------ + Call IPX with ES:SI=ecb_ptr + ------------------------------------------------------------------------*/ + /*........................................................................ + Fill in registers for the DPMI call + ........................................................................*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + memset (&rmi, 0 ,sizeof(rmi)); + regs.w.ax = DPMI_CALL_REAL_INT; // DPMI function to call + regs.w.bx = IPX_INT; // interrupt # to invoke + sregs.es = FP_SEG(&rmi); // tell DPMI where the RMI is + regs.x.edi = FP_OFF(&rmi); + /*........................................................................ + Fill in registers for the interrupt call + ........................................................................*/ + rmi.ebx = IPX_SEND_PACKET; + rmi.es = (short)((long)ecb_ptr >> 4); + rmi.esi = (long)((long)ecb_ptr & 0x000f); + /*........................................................................ + call DPMI + ........................................................................*/ + int386x(DPMI_INT, ®s, ®s, &sregs); + +} /* end of IPX_Send_Packet */ +#endif //NOT_FOR_WIN95 + +/*************************************************************************** + * IPX_Get_Local_Target -- fills in ImmediateAddress field of ECB * + * * + * Use this function to fill in the ECB's ImmediateAddress field when * + * sending a packet. The Immediate Address is the node address of the * + * bridge that must route the message. If there is no bridge, it's * + * filled in with the destination Node Address. In either case, it * + * will contain the proper value for sending. * + * * + * INPUT: * + * dest_network destination Network Number * + * dest_node destination Node Address * + * dest_socket destination Socket Number * + * bridge_address field to fill in with Immediate Address * + * * + * OUTPUT: * + * 0 = OK, error otherwise * + * * + * WARNINGS: * + * If the Connection Number is 0 (user not logged in), dest_network * + * and dest_node will be garbage, and this routine will probably crash. * + * * + * HISTORY: * + * 12/15/1994 BR : Created. * + *=========================================================================*/ +#ifdef NOT_FOR_WIN95 +int IPX_Get_Local_Target(unsigned char *dest_network, unsigned char *dest_node, + unsigned short dest_socket, unsigned char *bridge_address) +{ + struct request_buffer { + unsigned char network_number [4]; + unsigned char physical_node [6]; + unsigned short socket; + }; + struct reply_buffer { + unsigned char local_target [6]; + }; + unsigned int rc; + union REGS regs; + struct SREGS sregs; + RMIType rmi; + struct request_buffer *reqbuf; + struct reply_buffer *replybuf; + unsigned short segment; // for DOS allocation + unsigned short selector; // for DOS allocation + + /*------------------------------------------------------------------------ + Allocate DOS memory to store the buffers passed to the interrupt + ------------------------------------------------------------------------*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_ALLOC_DOS_MEM; // DPMI function to call + regs.x.ebx = (sizeof(struct request_buffer) + // # paragraphs to allocate + sizeof(struct reply_buffer) + 15) >> 4; + int386x (DPMI_INT, ®s, ®s, &sregs); // allocate the memory + /*........................................................................ + If the carry flag is set, DPMI is indicating an error. + ........................................................................*/ + if (regs.x.cflag) { + return(-1); + } + + /*........................................................................ + Get pointers to allocated memory. + 'reqbuf' is just the returned real-mode segment, multiplied by 16. + 'replybuf' is an offset from 'reqbuf'. + ........................................................................*/ + segment = regs.w.ax; + selector = regs.w.dx; + reqbuf = (struct request_buffer *)(segment << 4); + replybuf = (struct reply_buffer *) + (((char *)reqbuf) + sizeof (struct request_buffer)); + + /*------------------------------------------------------------------------ + Init the contents of the request & reply buffers + ------------------------------------------------------------------------*/ + memcpy(reqbuf->network_number, dest_network, 4); + memcpy(reqbuf->physical_node, dest_node, 6); + reqbuf->socket = dest_socket; + + /*------------------------------------------------------------------------ + Invoke IPX with DS:SI=&request_buffer, ES:DI=&reply_buffer + ------------------------------------------------------------------------*/ + /*........................................................................ + Fill in registers for the DPMI call + ........................................................................*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + memset (&rmi, 0 ,sizeof(rmi)); + regs.w.ax = DPMI_CALL_REAL_INT; // DPMI function to call + regs.w.bx = IPX_INT; // interrupt # to invoke + sregs.es = FP_SEG(&rmi); // tell DPMI where the RMI is + regs.x.edi = FP_OFF(&rmi); + /*........................................................................ + Fill in registers for the interrupt call + ........................................................................*/ + rmi.ebx = IPX_GET_LOCAL_TARGET; + rmi.ds = segment; + rmi.esi = 0; + rmi.es = segment; + rmi.edi = sizeof(struct request_buffer); + /*........................................................................ + call DPMI + ........................................................................*/ + int386x(DPMI_INT, ®s, ®s, &sregs); + + rc = (rmi.eax & 0x00ff); + memcpy(bridge_address, replybuf->local_target, 6); + + /*------------------------------------------------------------------------ + Free DOS memory + ------------------------------------------------------------------------*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_FREE_DOS_MEM; // DPMI function to call + regs.x.edx = selector; // ptr to free + int386x (DPMI_INT, ®s, ®s, &sregs); // allocate the memory + + return(rc); + +} /* end of IPX_Get_Local_Target */ + +#endif //NOT_FOR_WIN95 + +/*************************************************************************** + * IPX_Cancel_Event -- cancels an operation in progress * + * * + * INPUT: * + * ecb_ptr pointer to ECB event to cancel * + * * + * OUTPUT: * + * ??? * + * * + * WARNINGS: * + * The ECB must be located in real-mode memory, as well as the values * + * pointed to in the ECB (the IPX Header, the buffer to send, etc.) * + * * + * HISTORY: * + * 12/15/1994 BR : Created. * + *=========================================================================*/ +#ifdef NOT_FOR_WIN95 +int IPX_Cancel_Event(struct ECB *ecb_ptr) +{ + union REGS regs; + struct SREGS sregs; + RMIType rmi; + unsigned int rc; + + /*........................................................................ + Fill in registers for the DPMI call + ........................................................................*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + memset (&rmi, 0 ,sizeof(rmi)); + regs.w.ax = DPMI_CALL_REAL_INT; // DPMI function to call + regs.w.bx = IPX_INT; // interrupt # to invoke + sregs.es = FP_SEG(&rmi); // tell DPMI where the RMI is + regs.x.edi = FP_OFF(&rmi); + /*........................................................................ + Fill in registers for the interrupt call + ........................................................................*/ + rmi.ebx = IPX_CANCEL_EVENT; + rmi.es = (short)((long)ecb_ptr >> 4); + rmi.esi = (long)((long)ecb_ptr & 0x000f); + /*........................................................................ + call DPMI + ........................................................................*/ + int386x(DPMI_INT, ®s, ®s, &sregs); + + rc = (rmi.eax & 0x00ff); + + return(rc); + +} /* end of IPX_Cancel_Event */ +#endif //NOT_FOR_WIN95 + +/*************************************************************************** + * Let_IPX_Breath -- gives IPX some CPU time * + * * + * Use this function if you're polling the ECB's InUse flag, waiting * + * for it to go to 0: * + * * + * while ECBType.InUse * + * Let_IPX_Breath(); * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/15/1994 BR : Created. * + *=========================================================================*/ +#ifdef NOT_FOR_WIN95 +void Let_IPX_Breath(void) +{ + union REGS regs; + struct SREGS sregs; + RMIType rmi; + + /*........................................................................ + Fill in registers for the DPMI call + ........................................................................*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + memset (&rmi, 0 ,sizeof(rmi)); + regs.w.ax = DPMI_CALL_REAL_INT; // DPMI function to call + regs.w.bx = IPX_INT; // interrupt # to invoke + sregs.es = FP_SEG(&rmi); // tell DPMI where the RMI is + regs.x.edi = FP_OFF(&rmi); + /*........................................................................ + Fill in registers for the interrupt call + ........................................................................*/ + rmi.ebx = IPX_RELINQUISH_CONTROL; + /*........................................................................ + call DPMI + ........................................................................*/ + int386x(DPMI_INT, ®s, ®s, &sregs); + +} /* end of Let_IPX_Breath */ +#endif //NOT_FOR_WIN95 diff --git a/IPX.H b/IPX.H new file mode 100644 index 0000000..d4e3962 --- /dev/null +++ b/IPX.H @@ -0,0 +1,188 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ipx.h_v 2.17 16 Oct 1995 16:46:26 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IPX.H * + * * + * Programmer : Barry Nance * + * from Client/Server LAN Programming * + * Westwood-ized by Bill Randolph * + * * + * Start Date : December 14, 1994 * + * * + * Last Update : December 14, 1994 [BR] * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef IPX_H +#define IPX_H + +/* +******************************** Structures ********************************* +*/ +typedef unsigned char NetNumType[4]; +typedef unsigned char NetNodeType[6]; +typedef char UserID[48]; + +/*--------------------------------------------------------------------------- +This is the IPX Packet structure. It's followed by the data itself, which +can be up to 546 bytes long. Annotation of 'IPX' means IPX will set this +field; annotation of 'APP' means the application must set the field. +NOTE: All header fields are ordered high-byte,low-byte. +---------------------------------------------------------------------------*/ +typedef struct IPXHEADER { + unsigned short CheckSum; // IPX: Not used; always 0xffff + unsigned short Length; // IPX: Total size, incl header & data + unsigned char TransportControl; // IPX: # bridges message crossed + unsigned char PacketType; // APP: Set to 4 for IPX (5 for SPX) + unsigned char DestNetworkNumber[4]; // APP: destination Network Number + unsigned char DestNetworkNode[6]; // APP: destination Node Address + unsigned short DestNetworkSocket; // APP: destination Socket Number + unsigned char SourceNetworkNumber[4]; // IPX: source Network Number + unsigned char SourceNetworkNode[6]; // IPX: source Node Address + unsigned short SourceNetworkSocket; // IPX: source Socket Number +} IPXHeaderType; + +/*--------------------------------------------------------------------------- +This is the IPX Event Control Block. It serves as a communications area +between IPX and the application for a single IPX operation. You should set +up a separate ECB for each IPX operation you perform. +---------------------------------------------------------------------------*/ +typedef struct ECB { + void *Link_Address; + void (*Event_Service_Routine)(void); // APP: event handler (NULL=none) + unsigned char InUse; // IPX: 0 = event complete + unsigned char CompletionCode; // IPX: event's return code + unsigned short SocketNumber; // APP: socket to send data through + unsigned short ConnectionID; // returned by Listen (???) + unsigned short RestOfWorkspace; + unsigned char DriverWorkspace[12]; + unsigned char ImmediateAddress[6]; // returned by Get_Local_Target + unsigned short PacketCount; + struct { + void *Address; + unsigned short Length; + } Packet[2]; +} ECBType; + + +/*--------------------------------------------------------------------------- +This structure is used for calling DPMI function 0x300, Call-Real-Mode- +Interrupt. It passes register values to & from the interrupt handler. +---------------------------------------------------------------------------*/ +typedef struct { + long edi; + long esi; + long ebp; + long Reserved; + long ebx; + long edx; + long ecx; + long eax; + short Flags; + short es; + short ds; + short fs; + short gs; + short ip; + short cs; + short sp; + short ss; +} RMIType; + +/* +********************************** Defines ********************************** +*/ +/*--------------------------------------------------------------------------- +These defines are for the IPX functions. The function number is specified +by placing it in BX when IPX is called. There are two ways to invoke IPX: +use interrupt 0x7a, or use a function whose address is obtained by calling +interrupt 0x2f with AX=0x7a00; the function address is returned in ES:DI. +This is the preferred method, since other apps are known to use int 0x7a. +---------------------------------------------------------------------------*/ +#define IPX_OPEN_SOCKET 0x0000 +#define IPX_CLOSE_SOCKET 0x0001 +#define IPX_GET_LOCAL_TARGET 0x0002 +#define IPX_SEND_PACKET 0x0003 +#define IPX_LISTEN_FOR_PACKET 0x0004 +#define IPX_SCHEDULE_EVENT 0x0005 +#define IPX_CANCEL_EVENT 0x0006 +#define IPX_GET_INTERVAL_MARKER 0x0008 +#define IPX_GET_INTERNETWORK_ADDRESS 0x0009 +#define IPX_RELINQUISH_CONTROL 0x000A +#define IPX_DISCONNECT_FROM_TARGET 0x000B + +/*--------------------------------------------------------------------------- +These defines are for various IPX error codes: +---------------------------------------------------------------------------*/ +#define IPXERR_CONNECTION_SEVERED 0x00ec +#define IPXERR_CONNECTION_FAILED 0x00ed +#define IPXERR_NO_CONNECTION 0x00ee +#define IPXERR_CONNECTION_TABLE_FULL 0x00ef +#define IPXERR_NO_CANCEL_ECB 0x00f9 +#define IPXERR_NO_PATH 0x00fa +#define IPXERR_ECB_INACTIVE 0x00fc +#define IPXERR_INVALID_PACKET_LENGTH 0x00fd +#define IPXERR_SOCKET_TABLE_FULL 0x00fe +#define IPXERR_SOCKET_ERROR 0x00ff + +/*--------------------------------------------------------------------------- +These defines are for various interrupt vectors and DPMI functions: +---------------------------------------------------------------------------*/ +#define IPX_INT 0x007a +#define DPMI_INT 0x0031 +#define DPMI_ALLOC_DOS_MEM 0x0100 +#define DPMI_FREE_DOS_MEM 0x0101 +#define DPMI_CALL_REAL_INT 0x0300 +#define DPMI_LOCK_MEM 0x0600 +#define DPMI_UNLOCK_MEM 0x0601 + +/* +******************************** Prototypes ********************************* +*/ +/*--------------------------------------------------------------------------- +NOTE: All routines return 0 for a success code and one of the above IPX +error codes if there's an error, EXCEPT: +- IPX_SPX_Installed (0 = not installed) +- Get_Connection_Number / Get_1st_Connection_Number (0 = no connection) +---------------------------------------------------------------------------*/ +/* +.................................. ipx.cpp .................................. +*/ +int IPX_SPX_Installed(void); +int IPX_Open_Socket(unsigned short socket); +int IPX_Close_Socket(unsigned short socket); +int IPX_Get_Connection_Number(void); +int IPX_Get_1st_Connection_Num (char *username); +int IPX_Get_Internet_Address(int connection_number, + unsigned char *network_number, unsigned char *physical_node); +int IPX_Get_User_ID(int connection_number, char *user_id); +int IPX_Listen_For_Packet(struct ECB *ecb_ptr); +void IPX_Send_Packet(struct ECB *ecb_ptr); +int IPX_Get_Local_Target(unsigned char *dest_network, unsigned char *dest_node, + unsigned short dest_socket, unsigned char *bridge_address); +int IPX_Cancel_Event(struct ECB *ecb_ptr); +void Let_IPX_Breath(void); + +#endif diff --git a/IPX95.CPP b/IPX95.CPP new file mode 100644 index 0000000..29678cc --- /dev/null +++ b/IPX95.CPP @@ -0,0 +1,85 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IPX95PP * + * * + * Programmer : Steve Tall * + * * + * Start Date : January 22nd, 1996 * + * * + * Last Update : January 22nd, 1996 [ST] * + * * + *-------------------------------------------------------------------------* + * * + * * + * * + *-------------------------------------------------------------------------* + * Functions: * + * * + * * + * * + * * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "ipx95.h" + +int IPX_Open_Socket(unsigned short socket) +{ + return (IPX_Open_Socket95((int)socket)); +} + + +int IPX_Close_Socket(unsigned short socket) +{ + IPX_Close_Socket95((int)socket); + return (0); +} + + +int IPX_Get_Connection_Number(void) +{ + return (IPX_Get_Connection_Number95()); +} + + + +int IPX_Broadcast_Packet(unsigned char *buf, int buflen) +{ + return(IPX_Broadcast_Packet95(buf, buflen)); +} + +extern "C"{ + extern void __cdecl Int3(void); +} + +int IPX_Get_Local_Target(unsigned char *dest_network, unsigned char *dest_node, + unsigned short dest_socket, unsigned char *bridge_address) +{ + //Int3(); + return (IPX_Get_Local_Target95(dest_network, dest_node, dest_socket, bridge_address)); +} + + diff --git a/IPX95.H b/IPX95.H new file mode 100644 index 0000000..4282c0b --- /dev/null +++ b/IPX95.H @@ -0,0 +1,57 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IPX95PP * + * * + * Programmer : Steve Tall * + * * + * Start Date : January 22nd, 1996 * + * * + * Last Update : January 22nd, 1996 [ST] * + * * + *-------------------------------------------------------------------------* + * * + * * + * * + *-------------------------------------------------------------------------* + * Functions: * + * * + * * + * * + * * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +extern "C"{ + extern BOOL __stdcall IPX_Initialise(void); + extern BOOL __stdcall IPX_Get_Outstanding_Buffer95(unsigned char *buffer); + extern void __stdcall IPX_Shut_Down95(void); + extern int __stdcall IPX_Send_Packet95(unsigned char *, unsigned char *, int, unsigned char*, unsigned char*); + extern int __stdcall IPX_Broadcast_Packet95(unsigned char *, int); + extern BOOL __stdcall IPX_Start_Listening95(void); + extern int __stdcall IPX_Open_Socket95(int socket); + extern void __stdcall IPX_Close_Socket95(int socket); + extern int __stdcall IPX_Get_Connection_Number95(void); + extern int __stdcall IPX_Get_Local_Target95(unsigned char *, unsigned char*, unsigned short, unsigned char*); +} diff --git a/IPXADDR.CPP b/IPXADDR.CPP new file mode 100644 index 0000000..b52494a --- /dev/null +++ b/IPXADDR.CPP @@ -0,0 +1,494 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ipxaddr.cpv 2.17 16 Oct 1995 16:50:58 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IPXADDR.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : December 19, 1994 [BR] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * IPXAddressClass::IPXAddressClass -- class constructor * + * IPXAddressClass::IPXAddressClass -- class constructor form 2 * + * IPXAddressClass::IPXAddressClass -- class constructor form 3 * + * IPXAddressClass::Set_Address -- sets the IPX address values * + * IPXAddressClass::Set_Address -- sets the IPX values from a header * + * IPXAddressClass::Get_Address -- retrieves the IPX address values * + * IPXAddressClass::Get_Address -- copies address into an IPX header * + * IPXAddressClass::Is_Broadcast -- tells if this is a broadcast address * + * IPXAddressClass::operator== -- overloaded comparison operator * + * IPXAddressClass::operator!= -- overloaded comparison operator * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "tcpip.h" + +/*************************************************************************** + * IPXAddressClass::IPXAddressClass -- class constructor * + * * + * This default constructor generates a broadcast address. * + * * + * INPUT: * + * net Network Number for this address * + * node Node Address for this address * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +IPXAddressClass::IPXAddressClass(void) +{ + NetworkNumber[0] = 0xff; + NetworkNumber[1] = 0xff; + NetworkNumber[2] = 0xff; + NetworkNumber[3] = 0xff; + NodeAddress[0] = 0xff; + NodeAddress[1] = 0xff; + NodeAddress[2] = 0xff; + NodeAddress[3] = 0xff; + NodeAddress[4] = 0xff; + NodeAddress[5] = 0xff; + +} /* end of IPXAddressClass */ + + +/*************************************************************************** + * IPXAddressClass::IPXAddressClass -- class constructor form 2 * + * * + * INPUT: * + * net Network Number for this address * + * node Node Address for this address * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +IPXAddressClass::IPXAddressClass(NetNumType net, NetNodeType node) +{ + memcpy(NetworkNumber,net,4); + memcpy(NodeAddress,node,6); + +} /* end of IPXAddressClass */ + + +/*************************************************************************** + * IPXAddressClass::IPXAddressClass -- class constructor form 3 * + * * + * This form of the constructor takes an IPX header as an argument. It * + * extracts the address from the Source address fields in the header. * + * * + * INPUT: * + * header Header from which to extract the address * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +IPXAddressClass::IPXAddressClass(IPXHeaderType *header) +{ + memcpy(NetworkNumber,header->SourceNetworkNumber,4); + memcpy(NodeAddress,header->SourceNetworkNode,6); + +} /* end of IPXAddressClass */ + + +/*************************************************************************** + * IPXAddressClass::Set_Address -- sets the IPX address values * + * * + * INPUT: * + * net Network Number for this address * + * node Node Address for this address * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +void IPXAddressClass::Set_Address(NetNumType net, NetNodeType node) +{ + memcpy(NetworkNumber,net,4); + memcpy(NodeAddress,node,6); + +} /* end of Set_Address */ + + +/*************************************************************************** + * IPXAddressClass::Set_Address -- sets the IPX values from a header * + * * + * This routine extracts the source addresses from the given IPX header. * + * * + * INPUT: * + * net Network Number for this address * + * node Node Address for this address * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +void IPXAddressClass::Set_Address(IPXHeaderType *header) +{ + +#ifdef VIRTUAL_SUBNET_SERVER + if (Winsock.Get_Connected()){ + memset(NetworkNumber, 1 ,4); + memset(NodeAddress, 0, 6); + unsigned short target_mask = *(unsigned short*)header; + /* + ** If this is a head to head game (no VSS) -- + ** If mask is 0 then this packet was broadcast from the other player + ** Otherwise exclusive or with 3 to get other players mask + */ + if (!UseVirtualSubnetServer){ + if (target_mask == 0){ + target_mask = 1 << PlanetWestwoodIsHost; + } + target_mask ^= 3; + } + + *(unsigned short*) &NodeAddress[0] = target_mask; + + }else{ + memcpy(NetworkNumber,header->SourceNetworkNumber,4); + memcpy(NodeAddress,header->SourceNetworkNode,6); + } +#else //VIRTUAL_SUBNET_SERVER + if (header){ + memcpy(NetworkNumber,header->SourceNetworkNumber,4); + memcpy(NodeAddress,header->SourceNetworkNode,6); + }else{ + /* + ** Address is meaningless when using winsock + */ + memset(NetworkNumber, 1 ,4); + memset(NodeAddress, 1, 6); + } +#endif //VIRTUAL_SUBNET_SERVER +} /* end of Set_Address */ + + +/*************************************************************************** + * IPXAddressClass::Get_Address -- retrieves the IPX address values * + * * + * INPUT: * + * net Network Number for this address * + * node Node Address for this address * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +void IPXAddressClass::Get_Address(NetNumType net, NetNodeType node) +{ + memcpy(net,NetworkNumber,4); + memcpy(node,NodeAddress,6); + +} /* end of Get_Address */ + + +/*************************************************************************** + * IPXAddressClass::Get_Address -- copies address into an IPX header * + * * + * INPUT: * + * net Network Number for this address * + * node Node Address for this address * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +void IPXAddressClass::Get_Address(IPXHeaderType *header) +{ + memcpy(header->DestNetworkNumber,NetworkNumber,4); + memcpy(header->DestNetworkNode,NodeAddress,6); + +} /* end of Get_Address */ + + +/*************************************************************************** + * IPXAddressClass::Is_Broadcast -- tells if this is a broadcast address * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +bool IPXAddressClass::Is_Broadcast(void) +{ + if ( NetworkNumber[0] == 0xff && + NetworkNumber[1] == 0xff && + NetworkNumber[2] == 0xff && + NetworkNumber[3] == 0xff && + NodeAddress[0] == 0xff && + NodeAddress[1] == 0xff && + NodeAddress[2] == 0xff && + NodeAddress[3] == 0xff && + NodeAddress[4] == 0xff && + NodeAddress[5] == 0xff) { + return(true); + } else { + return(false); + } +} + + +/*************************************************************************** + * IPXAddressClass::operator== -- overloaded comparison operator * + * * + * Since, if NETX isn't running, the network number on a received packet * + * can be bogus (all 0's), only the node address is used for comparison * + * purposes here. * + * * + * INPUT: * + * addr address to compare to * + * * + * OUTPUT: * + * 0 = not equal, 1 = equal * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +int IPXAddressClass::operator == (IPXAddressClass & addr) +{ + /*------------------------------------------------------------------------ + If either Network Number is all 0's (which can happen if the system is + not running NETX), compare only the Node Addresses. + ------------------------------------------------------------------------*/ + if ( (NetworkNumber[0]==0 && + NetworkNumber[1]==0 && + NetworkNumber[2]==0 && + NetworkNumber[3]==0) || + (addr.NetworkNumber[0]==0 && + addr.NetworkNumber[1]==0 && + addr.NetworkNumber[2]==0 && + addr.NetworkNumber[3]==0) ) { + + if (memcmp(NodeAddress,addr.NodeAddress,6)==0) { + return(true); + } else { + return(false); + } + + } else { + + /*------------------------------------------------------------------------ + Otherwise, compare both the Network Numbers and Node Addresses + ------------------------------------------------------------------------*/ + if (memcmp(NodeAddress,addr.NodeAddress,6)==0 && memcmp(NetworkNumber,addr.NetworkNumber,4)==0) { + return(true); + } else { + return(false); + } + } +} + + +/*************************************************************************** + * IPXAddressClass::operator!= -- overloaded comparison operator * + * * + * Since, if NETX isn't running, the network number on a received packet * + * can be bogus (all 0's), only the node address is used for comparison * + * purposes here. * + * * + * INPUT: * + * addr address to compare to * + * * + * OUTPUT: * + * 0 = equal, 1 = not equal * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +int IPXAddressClass::operator != (IPXAddressClass & addr) +{ + /*------------------------------------------------------------------------ + If either Network Number is all 0's (which can happen if the system is + not running NETX), compare only the Node Addresses. + ------------------------------------------------------------------------*/ + if ( (NetworkNumber[0]==0 && + NetworkNumber[1]==0 && + NetworkNumber[2]==0 && + NetworkNumber[3]==0) || + (addr.NetworkNumber[0]==0 && + addr.NetworkNumber[1]==0 && + addr.NetworkNumber[2]==0 && + addr.NetworkNumber[3]==0) ) { + + if (memcmp(NodeAddress,addr.NodeAddress,6)==0) { + return(false); + } else { + return(true); + } + } else { + + /*------------------------------------------------------------------------ + Otherwise, compare both the Network Numbers and Node Addresses + ------------------------------------------------------------------------*/ + if (memcmp(NodeAddress,addr.NodeAddress,6)==0 && memcmp(NetworkNumber,addr.NetworkNumber,4)==0) { + return(false); + } else { + return(true); + } + } +} + + +/*************************************************************************** + * IPXAddressClass::operator > -- overloaded comparison operator * + * * + * INPUT: * + * addr address to compare to * + * * + * OUTPUT: * + * TRUE = greater, FALSE = not * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +int IPXAddressClass::operator > (IPXAddressClass & addr) +{ + return(memcmp(this, &addr, 10) > 0); + +} /* end of operator != */ + + +/*************************************************************************** + * IPXAddressClass::operator < -- overloaded comparison operator * + * * + * INPUT: * + * addr address to compare to * + * * + * OUTPUT: * + * TRUE = less, FALSE = not * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +int IPXAddressClass::operator < (IPXAddressClass & addr) +{ + return(memcmp(this, &addr, 10) < 0); + +} /* end of operator != */ + + +/*************************************************************************** + * IPXAddressClass::operator >= -- overloaded comparison operator * + * * + * INPUT: * + * addr address to compare to * + * * + * OUTPUT: * + * TRUE = greater or equal, FALSE = not * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +int IPXAddressClass::operator >= (IPXAddressClass & addr) +{ + return(memcmp(this, &addr, 10) >= 0); + +} /* end of operator != */ + + +/*************************************************************************** + * IPXAddressClass::operator <= -- overloaded comparison operator * + * * + * INPUT: * + * addr address to compare to * + * * + * OUTPUT: * + * TRUE = less or equal, FALSE = not * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/19/1994 BR : Created. * + *=========================================================================*/ +int IPXAddressClass::operator <= (IPXAddressClass & addr) +{ + return(memcmp(this, &addr, 10) <= 0); + +} /* end of operator != */ + + diff --git a/IPXADDR.H b/IPXADDR.H new file mode 100644 index 0000000..28a7c13 --- /dev/null +++ b/IPXADDR.H @@ -0,0 +1,106 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ipxaddr.h_v 2.19 16 Oct 1995 16:44:54 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IPXADDR.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : December 19, 1994 [BR] * + * * + * This class is useful for any IPX-related code. It's just a utility * + * to help manage those annoying IPX address fields. This class lets you * + * compare addresses, copy addresses to & from the IPX header, etc. * + * * + * The class has no virtual functions, so you can treat this class just * + * like a data structure; it can be loaded & saved, and even transmitted * + * across the net. * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef IPXADDR_H +#define IPXADDR_H + +#include "ipx.h" // for NetNumType & NetNodeType + + +/* +***************************** Class Declaration ***************************** +*/ +class IPXAddressClass +{ + /* + ---------------------------- Public Interface ---------------------------- + */ + public: + /*..................................................................... + Constructors: + .....................................................................*/ + IPXAddressClass(void); + IPXAddressClass(NetNumType net, NetNodeType node); + IPXAddressClass(IPXHeaderType *header); + + /*..................................................................... + Set the address from explicit variables, or from the SOURCE values + in an IPX packet header. + .....................................................................*/ + void Set_Address(NetNumType net, NetNodeType node); + void Set_Address(IPXHeaderType *header); + /*..................................................................... + Get the address values explicitly, or copy them into the DESTINATION + values in an IPX packet header. + .....................................................................*/ + void Get_Address (NetNumType net, NetNodeType node); + void Get_Address(IPXHeaderType *header); + + /*..................................................................... + Tells if this address is a broadcast address + .....................................................................*/ + bool Is_Broadcast(void); + + /*..................................................................... + Overloaded operators: + .....................................................................*/ + int operator == (IPXAddressClass & addr); + int operator != (IPXAddressClass & addr); + int operator > (IPXAddressClass &addr); + int operator < (IPXAddressClass &addr); + int operator >= (IPXAddressClass &addr); + int operator <= (IPXAddressClass &addr); + /* + -------------------------- Protected Interface --------------------------- + */ + protected: + /* + --------------------------- Private Interface ---------------------------- + */ + private: + NetNumType NetworkNumber; + NetNodeType NodeAddress; +}; + +#endif +/**************************** end of ipxaddr.h *****************************/ diff --git a/IPXCONN.CPP b/IPXCONN.CPP new file mode 100644 index 0000000..26b8873 --- /dev/null +++ b/IPXCONN.CPP @@ -0,0 +1,680 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ipxconn.cpv 1.9 16 Oct 1995 16:50:52 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IPXCONN.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 20, 1994 * + * * + * Last Update : April 9, 1995 [BRR] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * IPXConnClass::IPXConnClass -- class constructor * + * IPXConnClass::~IPXConnClass -- class destructor * + * IPXConnClass::Init -- hardware-specific initialization routine * + * IPXConnClass::Configure -- One-time initialization routine * + * IPXConnClass::Start_Listening -- commands IPX to listen * + * IPXConnClass::Stop_Listening -- commands IPX to stop listen * + * IPXConnClass::Send -- sends a packet; invoked by SequencedConnection * + * IPXConnClass::Open_Socket -- opens communications socket * + * IPXConnClass::Close_Socket -- closes the socket * + * IPXConnClass::Send_To -- sends the packet to the given address * + * IPXConnClass::Broadcast -- broadcasts the given packet * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "ipx95.h" +#include "tcpip.h" + +/* +********************************* Globals *********************************** +*/ +unsigned short IPXConnClass::Socket; +int IPXConnClass::ConnectionNum; +ECBType * IPXConnClass::ListenECB; +IPXHeaderType * IPXConnClass::ListenHeader; +char * IPXConnClass::ListenBuf; +ECBType * IPXConnClass::SendECB; +IPXHeaderType * IPXConnClass::SendHeader; +char * IPXConnClass::SendBuf; +long IPXConnClass::Handler; +int IPXConnClass::Configured = 0; +int IPXConnClass::SocketOpen = 0; +int IPXConnClass::Listening = 0; +int IPXConnClass::PacketLen; + + +/*************************************************************************** + * IPXConnClass::IPXConnClass -- class constructor * + * * + * INPUT: * + * numsend desired # of entries for the send queue * + * numreceive desired # of entries for the recieve queue * + * maxlen max length of an application packet * + * magicnum the packet "magic number" for this connection * + * address address of destination (NULL = no address) * + * id connection's unique numerical ID * + * name connection's name * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +IPXConnClass::IPXConnClass (int numsend, int numreceive, int maxlen, + unsigned short magicnum, IPXAddressClass *address, int id, char *name) : +#ifdef SEQ_NET + SequencedConnClass (numsend, numreceive, maxlen, magicnum, +#else + NonSequencedConnClass (numsend, numreceive, maxlen, magicnum, +#endif + 2, // retry delta + -1, // max retries + 60) // timeout +{ + NetNumType net; + NetNodeType node; + + /*------------------------------------------------------------------------ + Save the values passed in + ------------------------------------------------------------------------*/ + if (address) + Address = (*address); + ID = id; + strcpy (Name, name); + + if (!Winsock.Get_Connected()){ + /*------------------------------------------------------------------------ + If our Address field is an actual address (ie NULL wasn't passed to the + constructor), pre-compute the ImmediateAddress value for the SendECB. + This allows pre-computing of the ImmediateAddress for all connections + created after Configure() is called. + ------------------------------------------------------------------------*/ + if (!Address.Is_Broadcast() && Configured==1) { + Address.Get_Address(net,node); + /*..................................................................... + If the user is logged in & has a valid Novell Connection Number, get the + bridge address the "official" way + .....................................................................*/ + if (ConnectionNum != 0) { + if (IPX_Get_Local_Target (net, node, Socket, ImmediateAddress)!=0) + memcpy(ImmediateAddress,node,6); + } else { + + /*..................................................................... + Otherwise, use the destination node address as the ImmediateAddress, and + just hope there's no network bridge in the path. + .....................................................................*/ + memcpy(ImmediateAddress,node,6); + } + + Immed_Set = 1; + } else { + memset (ImmediateAddress, 0, 6); + Immed_Set = 0; + } + } + +} /* end of IPXConnClass */ + + +/*************************************************************************** + * IPXConnClass::Init -- hardware-specific initialization routine * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +void IPXConnClass::Init (void) +{ + /*------------------------------------------------------------------------ + Invoke the parent's Init routine + ------------------------------------------------------------------------*/ +#ifdef SEQ_NET + SequencedConnClass::Init(); +#else + NonSequencedConnClass::Init(); +#endif +} + + +/*************************************************************************** + * IPXConnClass::Configure -- One-time initialization routine * + * * + * This routine sets up static members that are shared by all IPX * + * connections (ie those variables used by the Send/Listen/Broadcast * + * routines). * + * * + * INPUT: * + * socket socket ID for sending & receiving * + * conn_num local IPX Connection Number (0 = not logged in) * + * listen_ecb ptr to ECBType for listening * + * send_ecb ptr to ECBType for sending * + * listen_header ptr to IPXHeaderType for listening * + * send_header ptr to IPXHeaderType for sending * + * listen_buf ptr to buffer for listening * + * send_buf ptr to buffer for sending * + * handler_rm_ptr REAL-MODE pointer to event service routine * + * (high word = segment, low word = offset) * + * maxpacketlen max packet size to listen for * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * - All pointers must be protected-mode pointers, but must point to * + * DOS real-mode memory (except the Handler segment/offset) * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +void IPXConnClass::Configure (unsigned short socket, int conn_num, + ECBType *listen_ecb, ECBType *send_ecb, IPXHeaderType *listen_header, + IPXHeaderType *send_header, char *listen_buf, char *send_buf, + long handler_rm_ptr, int maxpacketlen) +{ + /*------------------------------------------------------------------------ + Save the values passed in + ------------------------------------------------------------------------*/ + Socket = socket; + ConnectionNum = conn_num; + ListenECB = listen_ecb; + SendECB = send_ecb; + ListenHeader = listen_header; + SendHeader = send_header; + ListenBuf = listen_buf; + SendBuf = send_buf; + Handler = handler_rm_ptr; + PacketLen = maxpacketlen; + + Configured = 1; + +} /* end of Configure */ + + +/*************************************************************************** + * IPXConnClass::Start_Listening -- commands IPX to listen * + * * + * This routine may be used to start listening in polled mode (if the * + * ECB's Event_Service_Routine is NULL), or in interrupt mode; it's * + * up to the caller to fill the ECB in. If in polled mode, Listening * + * must be restarted every time a packet comes in. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * - The ListenECB must have been properly filled in by the IPX Manager.* + * - Configure must be called before calling this routine. * + * * + * HISTORY: * + * 12/16/1994 BR : Created. * + *=========================================================================*/ +bool IPXConnClass::Start_Listening(void) +{ + +#ifndef NOT_FOR_WIN95 + + if (Winsock.Get_Connected()) return (true); + + /*------------------------------------------------------------------------ + Open the Socket + ------------------------------------------------------------------------*/ + if (!Open_Socket(Socket)) + return(false); + + if (IPX_Start_Listening95()){ + Listening =1; + return (TRUE); + }else{ + return (FALSE); + } + +#else + + void *hdr_ptr; + unsigned long hdr_val; + void *buf_ptr; + unsigned long buf_val; + int rc; + + /*------------------------------------------------------------------------ + Don't do a thing unless we've been configured, and we're not listening. + ------------------------------------------------------------------------*/ + if (Configured==0 || Listening==1) + return(false); + + /*------------------------------------------------------------------------ + Open the Socket + ------------------------------------------------------------------------*/ + if (!Open_Socket(Socket)) + return(false); + + /*------------------------------------------------------------------------ + Clear the ECB & header + ------------------------------------------------------------------------*/ + memset(ListenECB, 0, sizeof(ECBType)); + memset(ListenHeader, 0, sizeof(IPXHeaderType)); + + /*------------------------------------------------------------------------ + Convert protected-mode ptrs to real-mode ptrs + ------------------------------------------------------------------------*/ + hdr_val = (unsigned long)ListenHeader; + hdr_ptr = (void *)(((hdr_val & 0xffff0) << 12) | (hdr_val & 0x000f)); + + buf_val = (unsigned long)ListenBuf; + buf_ptr = (void *)(((buf_val & 0xffff0) << 12) | (buf_val & 0x000f)); + + /*------------------------------------------------------------------------ + Fill in the ECB + ------------------------------------------------------------------------*/ + ListenECB->SocketNumber = Socket; + ListenECB->PacketCount = 2; + ListenECB->Packet[0].Address = hdr_ptr; + ListenECB->Packet[0].Length = sizeof(IPXHeaderType); + ListenECB->Packet[1].Address = buf_ptr; + ListenECB->Packet[1].Length = (unsigned short)PacketLen; + + ((long &)ListenECB->Event_Service_Routine) = Handler; + + /*------------------------------------------------------------------------ + Command IPX to listen + ------------------------------------------------------------------------*/ + rc = IPX_Listen_For_Packet(ListenECB); + if (rc!=0) { + Close_Socket(Socket); + return(false); + } else { + Listening = 1; + return(true); + } + +#endif //NOT_FOR_WIN95 +} /* end of Start_Listening */ + + +/*************************************************************************** + * IPXConnClass::Stop_Listening -- commands IPX to stop listen * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * - This routine MUST NOT be called if IPX is not listening already! * + * * + * HISTORY: * + * 12/16/1994 BR : Created. * + *=========================================================================*/ +bool IPXConnClass::Stop_Listening(void) +{ + /*------------------------------------------------------------------------ + Don't do anything unless we're already Listening. + ------------------------------------------------------------------------*/ + if (Listening==0) + return(false); + +#ifndef NOT_FOR_WIN95 + + if (Winsock.Get_Connected()){ + Listening = 0; + return (true); + }else{ + IPX_Shut_Down95(); + Close_Socket(Socket); + } + +#else //NOT_FOR_WIN95 + + /*------------------------------------------------------------------------ + Shut IPX down. + ------------------------------------------------------------------------*/ + IPX_Cancel_Event(ListenECB); + Close_Socket(Socket); + +#endif //NOT_FOR_WIN95 + + Listening = 0; + + /*------------------------------------------------------------------------ + All done. + ------------------------------------------------------------------------*/ + return(true); + +} /* end of Stop_Listening */ + + +/*************************************************************************** + * IPXConnClass::Send -- sends a packet; invoked by SequencedConnection * + * * + * INPUT: * + * socket desired socket ID number * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/16/1994 BR : Created. * + *=========================================================================*/ +int IPXConnClass::Send(char *buf, int buflen) +{ + /*------------------------------------------------------------------------ + Invoke our own Send_To routine, filling in our Address as the destination. + ------------------------------------------------------------------------*/ + if (Immed_Set) { + return(Send_To (buf, buflen, &Address, ImmediateAddress)); + } else { + return(Send_To (buf, buflen, &Address, NULL)); + } + +} /* end of Send */ + + +/*************************************************************************** + * IPXConnClass::Open_Socket -- opens communications socket * + * * + * INPUT: * + * socket desired socket ID number * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/16/1994 BR : Created. * + *=========================================================================*/ +int IPXConnClass::Open_Socket(unsigned short socket) +{ + int rc; + + if (Winsock.Get_Connected()){ + SocketOpen = 1; + return (true); + } + + SocketOpen = 0; + + /*------------------------------------------------------------------------ + Try to open a listen socket. The socket may have been left open by + a previously-crashed program, so ignore the state of the SocketOpen + flag for this call; use IPX to determine if the socket was already open. + ------------------------------------------------------------------------*/ + rc = IPX_Open_Socket(socket); + if (rc) { + /* + ................. If already open, close & reopen it .................. + */ + if (rc==IPXERR_SOCKET_ERROR) { + IPX_Close_Socket(socket); + rc = IPX_Open_Socket(socket); + /* + .................. Still can't open: return error .................. + */ + if (rc) { + return(false); + } + } + } + + SocketOpen = 1; + + return(true); +} + + +/*************************************************************************** + * IPXConnClass::Close_Socket -- closes the socket * + * * + * INPUT: * + * socket desired socket ID number * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * Calling this routine when the sockets aren't open may crash! * + * * + * HISTORY: * + * 12/16/1994 BR : Created. * + *=========================================================================*/ +void IPXConnClass::Close_Socket(unsigned short socket) +{ + if (Winsock.Get_Connected()){ + SocketOpen = 0; + return; + } + /*------------------------------------------------------------------------ + Never, ever, ever, under any circumstances whatsoever, close a socket + that isn't open. You'll regret it forever (or until at least until you're + through rebooting, which, if you're on a Pentium is the same thing). + ------------------------------------------------------------------------*/ + if (SocketOpen==1) + IPX_Close_Socket(socket); + + SocketOpen = 0; + +} /* end of Close_Socket */ + + +/*************************************************************************** + * IPXConnClass::Send_To -- sends the packet to the given address * + * * + * The "ImmediateAddress" field of the SendECB must be filled in with the * + * address of a bridge, or the node address of the destination if there * + * is no bridge. The NETX call to find this address will always crash * + * if NETX isn't loaded (ConnectionNum is 0), so this case is trapped & * + * prevented. * + * Also, if the address of this IPX connection is known when the * + * constructor is called, and Configure has been called, Get_Local_Target * + * is called to precompute the ImmediateAddress; this case is detected & * + * if the value is already computed, it's just memcpy'd over. * + * * + * INPUT: * + * buf buffer to send * + * buflen length of buffer * + * address Address to send to * + * immed ImmediateAddress value, NULL if none * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/16/1994 BR : Created. * + *=========================================================================*/ +#pragma off (unreferenced) +int IPXConnClass::Send_To(char *buf, int buflen, IPXAddressClass *address, + NetNodeType immed) +{ + + void *hdr_ptr; + void *buf_ptr; + unsigned long hdr_val; + unsigned long buf_val; + NetNumType net; + NetNodeType node; + int rc; + + unsigned short target_mask; + + unsigned char send_address[6]; + + if (Winsock.Get_Connected()){ + +#ifdef VIRTUAL_SUBNET_SERVER + if (immed){ + memcpy(send_address, immed, 6); + }else{ + address->Get_Address(net,node); + memcpy (send_address, node, 6); + } + /* + ** Use first two bytes of ipx address as target mask + */ + unsigned short *maskptr = (unsigned short*)&send_address[0]; + target_mask = *maskptr; + + char *tempsend = new char [buflen + sizeof (target_mask)]; + + *(unsigned short*)tempsend = htons(target_mask); + memcpy (tempsend+2, buf, buflen); +#if (0) +char tempbuf[256]; +CommHeaderType *packet = (CommHeaderType *)(&tempsend[2]); +static char pcode [4][18]={ + "PACKET_DATA_ACK", // this is a data packet requiring an ACK + "PACKET_DATA_NOACK", // this is a data packet not requiring an ACK + "PACKET_ACK", // this is an ACK for a packet + "PACKET_COUNT" // for computational purposes +}; + +sprintf (tempbuf, "Sending unicast packet type %d, ID=%d, code=%s, length=%d\n", tempsend[sizeof(CommHeaderType)+2], + packet->PacketID, + pcode[packet->Code], + buflen + sizeof (target_mask)); +CCDebugString (tempbuf); +#endif //(0) + + Winsock.Write((void*)tempsend, buflen + sizeof (target_mask)); + delete [] tempsend; +#else // VIRTUAL_SUBNET_SERVER + Winsock.Write((void*)buf, buflen); +#endif // VIRTUAL_SUBNET_SERVER + + return (true); + } + + + + if (immed) { + memcpy(send_address, immed, 6); + //memcpy(node, immed, 6); + //memset (net, 0, sizeof(net) ); + address->Get_Address(net,node); + } else { + address->Get_Address(net,node); + /*..................................................................... + If the user is logged in & has a valid Novell Connection Number, get the + bridge address the "official" way + .....................................................................*/ + if (ConnectionNum != 0) { + rc = IPX_Get_Local_Target (net, node, Socket, &send_address[0]); + if (rc!=0) { + return(false); + } + } else { + /*..................................................................... + Otherwise, use the destination node address as the ImmediateAddress, and + just hope there's no network bridge in the path. + .....................................................................*/ + memcpy(send_address,node,6); + } + } + + return (IPX_Send_Packet95(&send_address[0], (unsigned char*)buf, buflen, (unsigned char*)net, (unsigned char*)node)); + + +} +#pragma on (unreferenced) + +/*************************************************************************** + * IPXConnClass::Broadcast -- broadcasts the given packet * + * * + * INPUT: * + * socket desired socket ID number * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/16/1994 BR : Created. * + *=========================================================================*/ +int IPXConnClass::Broadcast(char *buf, int buflen) +{ + + if (Winsock.Get_Connected()){ +#ifdef VIRTUAL_SUBNET_SERVER + char *tempsend = new char [buflen + sizeof (unsigned short)]; + memcpy (tempsend+2, buf, buflen); + *tempsend = 0; + *(tempsend+1) = 0; +#if (0) +char tempbuf[256]; +CommHeaderType *packet = (CommHeaderType *)(&tempsend[2]); +static char pcode [4][18]={ + "PACKET_DATA_ACK", // this is a data packet requiring an ACK + "PACKET_DATA_NOACK", // this is a data packet not requiring an ACK + "PACKET_ACK", // this is an ACK for a packet + "PACKET_COUNT" // for computational purposes +}; + +sprintf (tempbuf, "Sending multicast packet type %d, ID=%d, code=%s, length=%d\n", tempsend[sizeof(CommHeaderType)+2], + packet->PacketID, + pcode[packet->Code], + buflen + sizeof (unsigned short)); +CCDebugString (tempbuf); +#endif //(0) + + Winsock.Write((void*)tempsend, buflen + sizeof (unsigned short)); + delete [] tempsend; +#else // VIRTUAL_SUBNET_SERVER + Winsock.Write((void*)buf, buflen); +#endif // VIRTUAL_SUBNET_SERVER + return(true); + }else{ + return (IPX_Broadcast_Packet95((unsigned char*)buf, buflen)); + } + +} + diff --git a/IPXCONN.H b/IPXCONN.H new file mode 100644 index 0000000..19b31ad --- /dev/null +++ b/IPXCONN.H @@ -0,0 +1,211 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ipxconn.h_v 1.12 16 Oct 1995 16:45:10 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IPXCONN.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : April 9, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * * + * This is the Connection Class for IPX communications. It inherits * + * a Queue, PacketBuf, timeout variables from ConnectionClass. It * + * inherits its Send_/Receive_/Get_Packet functions, and the sequenced * + * ACK/Retry logic in Service_Send_Queue & Service_Recieve_Queue from * + * SequencedConnClass. It guarantees order of delivery of packets. * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef IPXCONN_H +#define IPXCONN_H + + +/* +********************************* Includes ********************************** +*/ +#ifdef SEQ_NET +#include "seqconn.h" +#else +#include "noseqcon.h" +#endif +#include "ipxaddr.h" + + +/* +***************************** Class Declaration ***************************** +*/ +#ifdef SEQ_NET +class IPXConnClass : public SequencedConnClass +#else +class IPXConnClass : public NonSequencedConnClass +#endif +{ + /* + ---------------------------- Public Interface ---------------------------- + */ + public: + /*..................................................................... + Various useful enums: + .....................................................................*/ + enum IPXConnTag { + CONN_NAME_MAX = 40, // max # chars allowed for connection name + CONNECTION_NONE = -1, // value of an invalid connection ID + }; + + /*..................................................................... + Constructor/destructor. + .....................................................................*/ + IPXConnClass(int numsend, int numrecieve, int maxlen, + unsigned short magicnum, IPXAddressClass *address, int id, char *name); + virtual ~IPXConnClass () {}; + + /*..................................................................... + Initialization. + .....................................................................*/ + virtual void Init (void); + + /*..................................................................... + The Configure function is for configuring all connections at once. + It's static because it doesn't apply to any specific connection, but + all of them. + .....................................................................*/ + static void Configure(unsigned short socket, + int conn_num, ECBType *listen_ecb, ECBType *send_ecb, + IPXHeaderType *listen_header, IPXHeaderType *send_header, + char *listen_buf, char *send_buf, long handler_rm_ptr, + int maxpacketlen); + + /*..................................................................... + These routines tell IPX to start listening for packets, and to stop + listening for packets. They're static because they affect all + connections at once (there's no way to turn listening on for only one + connection; it's all or nothing). + .....................................................................*/ + static bool Start_Listening (void); + static bool Stop_Listening (void); + + /*..................................................................... + The Destination IPX Address for this connection + .....................................................................*/ + IPXAddressClass Address; + + /*..................................................................... + The "Immediate" (Bridge) address for this connection, and a flag + telling if the address has been precomputed. + .....................................................................*/ + NetNodeType ImmediateAddress; + int Immed_Set; + + /*..................................................................... + Each IPX Connection can have a Name & Unique numerical ID + .....................................................................*/ + int ID; + char Name[CONN_NAME_MAX]; + + /* + -------------------------- Protected Interface --------------------------- + */ + protected: + + /*..................................................................... + This is the overloaded Send routine declared in ConnectionClass, and + used in SequencedConnClass. + .....................................................................*/ + virtual int Send (char *buf, int buflen); + + /*..................................................................... + These are the routines that access IPX. Open_Socket & Close_Socket are + static because they're called by Start_Listening & Stop_Listening. + Send_To & Broadcast are static since they're direct interfaces to IPX, + and there's only one IPX instance running. + .....................................................................*/ + static int Open_Socket(unsigned short socket); + static void Close_Socket(unsigned short socket); + static int Send_To(char *buf, int buflen, IPXAddressClass *address, NetNodeType immed); + static int Broadcast(char *buf, int buflen); + + /*..................................................................... + The socket ID for this connection + .....................................................................*/ + static unsigned short Socket; + + /*..................................................................... + User's local Connection # (0 = not logged in) + .....................................................................*/ + static int ConnectionNum; + + /*..................................................................... + This is a static version of MaxPacketLen, which is the size of the + app's packets, plus our internal CommHeaderType. It's used in the + Start_Listening routine. + .....................................................................*/ + static int PacketLen; + + /*..................................................................... + Variables for Listening (created by the IPXManagerClass, and passed + in via Init). All IPX connections share these buffers. + .....................................................................*/ + static ECBType *ListenECB; + static IPXHeaderType *ListenHeader; + static char *ListenBuf; + + /*..................................................................... + Variables for Sending (created by the IPXManagerClass, and passed + in via Init). All IPX connections share these buffers. + .....................................................................*/ + static ECBType *SendECB; + static IPXHeaderType *SendHeader; + static char *SendBuf; + + /*..................................................................... + This is a REAL-MODE pointer to the event-service-routine for IPX. + If it's 0, IPX will operate in polled mode. Otherwise, the high word + must contain the segment, and the low word must contain the offset. + CS will be the high word value when the routine is called. (Requiring + the segment/offset to be computed by the caller gives the caller + control over CS.) + .....................................................................*/ + static long Handler; + + /*..................................................................... + This status flag tells us if Configure() has been called or not. + .....................................................................*/ + static int Configured; + + /*..................................................................... + This status flag tells us if the socket has been opened or not. + .....................................................................*/ + static int SocketOpen; + + /*..................................................................... + This status flag tells us if Start_Listening() has been called or not. + .....................................................................*/ + static int Listening; +}; + +#endif diff --git a/IPXGCONN.CPP b/IPXGCONN.CPP new file mode 100644 index 0000000..39ee31d --- /dev/null +++ b/IPXGCONN.CPP @@ -0,0 +1,473 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ipxgconn.cpv 1.9 16 Oct 1995 16:51:00 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IPXGCONN.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 20, 1994 * + * * + * Last Update : July 6, 1995 [BRR] * + *-------------------------------------------------------------------------* + * Functions: * + * IPXGlobalConnClass::IPXGlobalConnClass -- class constructor * + * IPXGlobalConnClass::~IPXGlobalConnClass -- class destructor * + * IPXGlobalConnClass::Send_Packet -- adds a packet to the send queue * + * IPXGlobalConnClass::Receive_Packet -- adds packet to the receive queue* + * IPXGlobalConnClass::Get_Packet -- gets a packet from the receive queue* + * IPXGlobalConnClass::Send -- sends a packet * + * IPXGlobalConnClass::Service_Receive_Queue -- services recieve queue * + * IPXGlobalConnClass::Set_Bridge -- Sets up connection to cross a bridge* +* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*************************************************************************** + * IPXGlobalConnClass::IPXGlobalConnClass -- class constructor * + * * + * This routine chains to the parent constructor, but it adjusts the size * + * of the packet by the added bytes in the GlobalHeaderType structure. * + * This forces the parent classes to allocate the proper sized PacketBuf * + * for outgoing packets, and to set MaxPacketLen to the proper value. * + * * + * INPUT: * + * numsend desired # of entries for the send queue * + * numreceive desired # of entries for the recieve queue * + * maxlen max length of an application packet * + * product_id unique ID for this product * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +IPXGlobalConnClass::IPXGlobalConnClass (int numsend, int numreceive, int maxlen, + unsigned short product_id) : + IPXConnClass (numsend, numreceive, + maxlen + sizeof(GlobalHeaderType) - sizeof(CommHeaderType), + GLOBAL_MAGICNUM, // magic number for this connection + NULL, // IPX Address (none) + 0, // Connection ID + "") // Connection Name +{ + ProductID = product_id; + IsBridge = 0; + +} /* end of IPXGlobalConnClass */ + + +/*************************************************************************** + * IPXGlobalConnClass::Send_Packet -- adds a packet to the send queue * + * * + * This routine prefixes the given buffer with a GlobalHeaderType and * + * queues the resulting packet into the Send Queue. The packet's * + * MagicNumber, Code, PacketID, destination Address and ProductID are set * + * here. * + * * + * INPUT: * + * buf buffer to send * + * buflen length of buffer * + * address address to send the packet to (NULL = Broadcast) * + * ack_req true = ACK is required for this packet; false = isn't * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int IPXGlobalConnClass::Send_Packet (void * buf, int buflen, + IPXAddressClass *address, int ack_req) +{ + /*------------------------------------------------------------------------ + Store the packet's Magic Number + ------------------------------------------------------------------------*/ + ((GlobalHeaderType *)PacketBuf)->Header.MagicNumber = MagicNum; + + /*------------------------------------------------------------------------ + If this is a ACK-required packet, sent to a specific system, mark it as + ACK-required; otherwise, mark as no-ACK-required. + ------------------------------------------------------------------------*/ + if (ack_req && address != NULL) { + ((GlobalHeaderType *)PacketBuf)->Header.Code = PACKET_DATA_ACK; + } else { + ((GlobalHeaderType *)PacketBuf)->Header.Code = PACKET_DATA_NOACK; + } + + /*------------------------------------------------------------------------ + Fill in the packet ID. This will have very limited meaning; it only + allows us to determine if an ACK packet we receive later goes with this + packet; it doesn't let us detect re-sends of other systems' packets. + ------------------------------------------------------------------------*/ + ((GlobalHeaderType *)PacketBuf)->Header.PacketID = Queue->Send_Total(); + + /*------------------------------------------------------------------------ + Set the product ID for this packet. + ------------------------------------------------------------------------*/ + ((GlobalHeaderType *)PacketBuf)->ProductID = ProductID; + + /*------------------------------------------------------------------------ + Set this packet's destination address. If no address is specified, use + a Broadcast address (which IPXAddressClass's default constructor creates). + ------------------------------------------------------------------------*/ + if (address != NULL) { + ((GlobalHeaderType *)PacketBuf)->Address = (*address); + } else { + ((GlobalHeaderType *)PacketBuf)->Address = IPXAddressClass(); + } + + /*------------------------------------------------------------------------ + Copy the application's data + ------------------------------------------------------------------------*/ + memcpy(PacketBuf + sizeof(GlobalHeaderType), buf, buflen); + + /*------------------------------------------------------------------------ + Queue it + ------------------------------------------------------------------------*/ + return(Queue->Queue_Send(PacketBuf,buflen + sizeof(GlobalHeaderType))); + +} /* end of Send_Packet */ + + +/*************************************************************************** + * IPXGlobalConnClass::Receive_Packet -- adds packet to the receive queue * + * * + * INPUT: * + * buf buffer to process (already includes GlobalHeaderType) * + * buflen length of buffer to process * + * address the address of the sender (the IPX Manager class must * + * extract this from the IPX Header of the received packet.) * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int IPXGlobalConnClass::Receive_Packet (void * buf, int buflen, + IPXAddressClass *address) +{ + GlobalHeaderType *packet; // ptr to this packet + SendQueueType *send_entry; // ptr to send entry header + GlobalHeaderType *entry_data; // ptr to queue entry data + int i; + + /* + --------------------------- Check the magic # ---------------------------- + */ + packet = (GlobalHeaderType *)buf; + if (packet->Header.MagicNumber!=MagicNum) { + return(false); + } + + /*------------------------------------------------------------------------ + Process the packet based on its Code + ------------------------------------------------------------------------*/ + switch (packet->Header.Code) { + /*..................................................................... + DATA: Save the given address in the message buffer (so Get_Message() + can extract it later), and queue this message. + Don't bother checking for a Re-Send; since this queue is receiving data + from multiple systems, the Total_Receive() value for this queue will + have nothing to do with the packet's ID. The application must deal + with this by being able to handle multiple receipts of the same packet. + .....................................................................*/ + case PACKET_DATA_ACK: + case PACKET_DATA_NOACK: + packet->Address = (*address); + Queue->Queue_Receive (buf, buflen); + break; + + /*..................................................................... + ACK: If this ACK is for any of my packets, mark that packet as + acknowledged, then throw this packet away. Otherwise, ignore the ACK + (if we re-sent before we received the other system's first ACK, this + ACK will be a leftover) + .....................................................................*/ + case PACKET_ACK: + for (i = 0; i < Queue->Num_Send(); i++) { + /* + ..................... Get queue entry ptr ....................... + */ + send_entry = Queue->Get_Send(i); + /* + ............. If ptr is valid, get ptr to its data .............. + */ + entry_data = (GlobalHeaderType *)(send_entry->Buffer); + /* + .............. If ACK is for this entry, mark it ................ + */ + if (packet->Header.PacketID==entry_data->Header.PacketID && + entry_data->Header.Code == PACKET_DATA_ACK) { + send_entry->IsACK = 1; + break; + } + } + break; + + /*..................................................................... + Default: ignore the packet + .....................................................................*/ + default: + break; + + } /* end of switch */ + + return(true); +} + + +/*************************************************************************** + * IPXGlobalConnClass::Get_Packet -- gets a packet from the receive queue * + * * + * INPUT: * + * buf location to store buffer * + * buflen filled in with length of 'buf' * + * address filled in with sender's address * + * product_id filled in with sender's ProductID * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int IPXGlobalConnClass::Get_Packet (void * buf, int *buflen, + IPXAddressClass *address, unsigned short *product_id) +{ + ReceiveQueueType *rec_entry; // ptr to receive entry header + GlobalHeaderType *packet; + int packetlen; // size of received packet + + /* + ------------------------ Return if nothing to do ------------------------- + */ + if (Queue->Num_Receive() == 0) { + return(false); + } + + /* + ------------------ Get ptr to the next available entry ------------------- + */ + rec_entry = Queue->Get_Receive(0); + + /* + ------------------------ Read it if it's un-read ------------------------- + */ + if (rec_entry!=NULL && rec_entry->IsRead==0) { + /* + ........................... Mark as read .............................. + */ + rec_entry->IsRead = 1; + + /* + .......................... Copy data packet ........................... + */ + packet = (GlobalHeaderType *)(rec_entry->Buffer); + packetlen = rec_entry->BufLen - sizeof(GlobalHeaderType); + if (packetlen > 0) + memcpy(buf, rec_entry->Buffer + sizeof(GlobalHeaderType), packetlen); + (*buflen) = packetlen; + (*address) = packet->Address; + (*product_id) = packet->ProductID; + + return(true); + } + + return(false); +} + + +/*************************************************************************** + * IPXGlobalConnClass::Send -- sends a packet * + * * + * This routine gets invoked by NonSequencedConn, when it's processing * + * the Send & Receive Queues. The buffer provided will already have the * + * GlobalHeaderType header embedded in it. * + * * + * INPUT: * + * buf buffer to send * + * buflen length of buffer * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int IPXGlobalConnClass::Send(char *buf, int buflen) +{ + IPXAddressClass *addr; + int rc; + + /*------------------------------------------------------------------------ + Extract the packet's embedded IPX address + ------------------------------------------------------------------------*/ + addr = &(((GlobalHeaderType *)buf)->Address); + + /*------------------------------------------------------------------------ + If it's a broadcast address, broadcast it + ------------------------------------------------------------------------*/ + if (addr->Is_Broadcast()) { + return(Broadcast (buf, buflen)); + } else { + /*------------------------------------------------------------------------ + Otherwise, send it + ------------------------------------------------------------------------*/ + if (IsBridge && !memcmp (addr, BridgeNet, 4)) { + rc = Send_To (buf, buflen, &(((GlobalHeaderType *)buf)->Address), + BridgeNode); + } else { + rc = Send_To (buf, buflen, &(((GlobalHeaderType *)buf)->Address), NULL); + } + return (rc); + } + +} /* end of Send */ + + +/*************************************************************************** + * IPXGlobalConnClass::Service_Receive_Queue -- services the recieve queue * + * * + * This routine is necessary because the Global Connection has to ACK * + * a packet differently from other types of connections; its Send routine * + * assumes that the destination address is embedded within the outgoing * + * packet, so we have to create our ACK Packet using the GlobalHeaderType, * + * not the CommHeaderType. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int IPXGlobalConnClass::Service_Receive_Queue (void) +{ + GlobalHeaderType ackpacket; // ACK packet to send + ReceiveQueueType *rec_entry; // ptr to receive entry header + GlobalHeaderType *packet_hdr; // packet header + + /*------------------------------------------------------------------------ + Get a pointer to the next received entry + ------------------------------------------------------------------------*/ + rec_entry = Queue->Get_Receive(0); + if (rec_entry==NULL) + return(1); + + /*------------------------------------------------------------------------ + If this packet doesn't require an ACK, mark it as ACK'd. + ------------------------------------------------------------------------*/ + packet_hdr = (GlobalHeaderType *)(rec_entry->Buffer); + if (packet_hdr->Header.Code==PACKET_DATA_NOACK) + rec_entry->IsACK = 1; + + /*------------------------------------------------------------------------ + If this packet hasn't been ACK'd, send an ACK: + - Fill in the MagicNum & the Code + - Set the PacketID to the same ID that the sending system used, so the + sending system knows which packet the ACK is for + ------------------------------------------------------------------------*/ + if (rec_entry->IsACK==0) { + ackpacket.Header.MagicNumber = MagicNum; + ackpacket.Header.Code = PACKET_ACK; + ackpacket.Header.PacketID = packet_hdr->Header.PacketID; + ackpacket.Address = packet_hdr->Address; + ackpacket.ProductID = ProductID; + + Send ((char *)&ackpacket, sizeof(GlobalHeaderType)); + + rec_entry->IsACK = 1; + } + + /*------------------------------------------------------------------------ + If this packet has been read by the application, and has been ACK'd, and + there is another packet in the queue behind this one, it means the other + system got the ACK we sent for this packet; remove this packet from the + queue. + ------------------------------------------------------------------------*/ + if (rec_entry!=NULL && rec_entry->IsRead && rec_entry->IsACK && + Queue->Num_Receive() > 1) + Queue->UnQueue_Receive(NULL,NULL,0); + + return(1); + +} /* end of Service_Receive_Queue */ + +/*************************************************************************** + * Set_Bridge -- Sets up connection to cross a bridge * + * * + * This routine is designed to prevent the connection from having to * + * call Get_Local_Target, except the minimum number of times, since that * + * routine is buggy & goes away for long periods sometimes. * + * * + * INPUT: * + * bridge network number of the destination bridge * + * * + * OUTPUT: * + * none * + * * + * WARNINGS: * + * none * + * * + * HISTORY: * + * 07/06/1995 BRR : Created. * + *=========================================================================*/ +void IPXGlobalConnClass::Set_Bridge(NetNumType bridge) +{ + if (Configured) { + memcpy (BridgeNet, bridge, 4); + memset (BridgeNode, 0xff, 6); + + if (IPX_Get_Local_Target (BridgeNet, BridgeNode, Socket, BridgeNode)==0) { + IsBridge = 1; + } else { + IsBridge = 0; + } + } +} + diff --git a/IPXGCONN.H b/IPXGCONN.H new file mode 100644 index 0000000..1266140 --- /dev/null +++ b/IPXGCONN.H @@ -0,0 +1,161 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ipxgconn.h_v 1.10 16 Oct 1995 16:47:30 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IPXGCONN.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : April 11, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * * + * This class is a special type of IPX Connection. It can talk to more * + * than one system at a time. It can Broadcast packets to all systems, * + * or send a packet to one individual system. The packets it sends to * + * individual systems can be DATA_NOACK or DATA_ACK packets, but the * + * packets broadcast have to be DATA_NOACK packets. This class is for * + * only the crudest "Who-are-you" type of network communications. Once * + * the IPX Address of another system is identified, a "real" IPX * + * Connection should be created, & further communications done through it. * + * * + * This means that the packet ID field no longer can be used to detect * + * resends, since the receive queue may recieve a lot more packets than * + * we send out. So, re-sends cannot be detected; the application must be * + * designed so that it can handle multiple copies of the same packet. * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef IPXGLOBALCONN_H +#define IPXGLOBALCONN_H + + +#include "ipxconn.h" + + +/* +********************************** Defines ********************************** +*/ +/*--------------------------------------------------------------------------- +This is the header for Global Connection messages. It includes the usual +"standard" header that the other connections do; but it also includes an +IPX address field, so the application can get the address of the sender +of this message. This address field must be provided in by the IXP +Connection Manager class, when it calls this class's Receive_Packet function. +---------------------------------------------------------------------------*/ +typedef struct { + CommHeaderType Header; + IPXAddressClass Address; + unsigned short ProductID; +} GlobalHeaderType; + + +/* +***************************** Class Declaration ***************************** +*/ +class IPXGlobalConnClass : public IPXConnClass +{ + /* + ---------------------------- Public Interface ---------------------------- + */ + public: + /*..................................................................... + Here are some useful enums: + .....................................................................*/ + enum GlobalConnectionEnum { + /*.................................................................. + This is the magic number for all Global Connections. Having the + same magic number across products lets us ID different products + on the net. + ..................................................................*/ + GLOBAL_MAGICNUM = 0x1234, + /*.................................................................. + These are the values used for the ProductID field in the Global + Message structure. It also should be the Magic Number used for the + private connections within that product. + This list should be continually updated & kept current. Never ever + ever use an old product ID for your product! + ..................................................................*/ + COMMAND_AND_CONQUER = 0xaa01, + }; + + /*..................................................................... + Constructor/destructor. + .....................................................................*/ + IPXGlobalConnClass (int numsend, int numrecieve, int maxlen, + unsigned short product_id); + virtual ~IPXGlobalConnClass () {}; + + /*..................................................................... + Send/Receive routines. + .....................................................................*/ + virtual int Send_Packet (void * buf, int buflen, + IPXAddressClass *address, int ack_req); + virtual int Receive_Packet (void * buf, int buflen, + IPXAddressClass *address); + virtual int Get_Packet (void * buf, int *buflen, + IPXAddressClass *address, unsigned short *product_id); + + /*..................................................................... + This is for telling the connection it can cross a bridge. + .....................................................................*/ + void Set_Bridge (NetNumType bridge); + + /*..................................................................... + The Product ID for this product. + .....................................................................*/ + unsigned short ProductID; + + /*..................................................................... + This describes the address of a bridge we have to cross. This class + supports crossing only one bridge. Storing the bridge's network number + allows us to obtain its local target address only once, then re-use it. + .....................................................................*/ + NetNumType BridgeNet; + NetNodeType BridgeNode; + int IsBridge; + + /* + -------------------------- Protected Interface --------------------------- + */ + protected: + + /*..................................................................... + This is the overloaded Send routine declared in ConnectionClass, and + used in SequencedConnClass. This special version sends to the address + embedded within the GlobalHeaderType. + .....................................................................*/ + virtual int Send (char *buf, int buflen); + + /*..................................................................... + This routine is overloaded from SequencedConnClass, because the + Global Connection needs to ACK its packets differently from the + other connections. + .....................................................................*/ + virtual int Service_Receive_Queue (void); +}; + +#endif diff --git a/IPXMGR.CPP b/IPXMGR.CPP new file mode 100644 index 0000000..93d8a4e --- /dev/null +++ b/IPXMGR.CPP @@ -0,0 +1,1972 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ipxmgr.cpv 1.9 16 Oct 1995 16:48:22 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IPXMGR.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 20, 1994 * + * * + * Last Update : May 4, 1995 [BRR] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * IPXManagerClass::IPXManagerClass -- class constructor * + * IPXManagerClass::~IPXManagerClass -- class destructor * + * IPXManagerClass::Init -- initialization routine * + * IPXManagerClass::Is_IPX -- tells if IPX is installed or not * + * IPXManagerClass::Set_Timing -- sets timing for all connections * + * IPXManagerClass::Create_Connection -- creates a new connection * + * IPXManagerClass::Delete_Connection -- deletes a connection * + * IPXManagerClass::Num_Connections -- gets the # of connections * + * IPXManagerClass::Connection_ID -- gets the given connection's ID * + * IPXManagerClass::Connection_Name -- gets name for given connection * + * IPXManagerClass::Connection_Address -- retrieves connection's address * + * IPXManagerClass::Connection_Index -- gets given connection's index * + * IPXManagerClass::Send_Global_Message -- sends a Global Message * + * IPXManagerClass::Get_Global_Message -- polls the Global Message queue * + * IPXManagerClass::Send_Private_Message -- Sends a Private Message * + * IPXManagerClass::Get_Private_Message -- Polls Private Message queue * + * IPXManagerClass::Service -- main polling routine for IPX Connections * + * IPXManagerClass::Get_Bad_Connection -- returns bad connection ID * + * IPXManagerClass::Global_Num_Send -- gets # entries in send queue * + * IPXManagerClass::Global_Num_Receive -- gets # entries in recv queue * + * IPXManagerClass::Private_Num_Send -- gets # entries in send queue * + * IPXManagerClass::Private_Num_Receive -- gets # entries in recv queue * + * IPXManagerClass::Set_Bridge -- prepares to cross a bridge * + * IPXManagerClass::Set_Socket -- sets socket ID for all connections * + * IPXManagerClass::Response_Time -- Returns largest Avg Response Time * + * IPXManagerClass::Global_Response_Time -- Returns Avg Response Time * + * IPXManagerClass::Reset_Response_Time -- Reset response time * + * IPXManagerClass::Oldest_Send -- gets ptr to oldest send buf * + * IPXManagerClass::Mono_Debug_Print -- debug output routine * + * IPXManagerClass::Alloc_RealMode_Mem -- allocates real-mode memory * + * IPXManagerClass::Free_RealMode_Mem -- frees real-mode memory * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "ipx95.h" +#include "tcpip.h" + +/*************************************************************************** + * IPXManagerClass::IPXManagerClass -- class constructor * + * * + * INPUT: * + * glb_maxlen Global Channel maximum packet length * + * pvt_maxlen Private Channel maximum packet length * + * socket socket ID to use * + * product_id a unique numerical ID for this product * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * The socket number is byte-swapped, since IPX requires socket ID's * + * to be stored high/low. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +IPXManagerClass::IPXManagerClass (int glb_maxlen, int pvt_maxlen, + int glb_num_packets, int pvt_num_packets, unsigned short socket, + unsigned short product_id) +{ + int i; + + /*------------------------------------------------------------------------ + Initialize data members + ------------------------------------------------------------------------*/ + /*........................................................................ + IPXStatus = 1 if IPX is installed, 0 if not + ........................................................................*/ + if (IPX_SPX_Installed()==0) { + IPXStatus = 0; + } else { + IPXStatus = 1; + } + + /*........................................................................ + Set listening state flag to off + ........................................................................*/ + Listening = 0; + + /*........................................................................ + No memory has been alloc'd yet + ........................................................................*/ + RealMemAllocd = 0; + + /*........................................................................ + Set max packet sizes, for allocating real-mode memory + ........................................................................*/ + Glb_MaxPacketLen = glb_maxlen; + Glb_NumPackets = glb_num_packets; + Pvt_MaxPacketLen = pvt_maxlen; + Pvt_NumPackets = pvt_num_packets; + + /*........................................................................ + Save the app's product ID + ........................................................................*/ + ProductID = product_id; + + /*........................................................................ + Save our socket ID number + ........................................................................*/ + Socket = (unsigned short)( (((unsigned long)socket & 0x00ff) << 8) | + (((unsigned long)socket & 0xff00) >> 8)); + + /*........................................................................ + Get the user's IPX local connection number + ........................................................................*/ + if (IPXStatus) { + ConnectionNum = IPX_Get_Connection_Number(); + } else { + ConnectionNum = 0; + } + + /*........................................................................ + Init connection states + ........................................................................*/ + NumConnections = 0; + CurConnection = 0; + for (i = 0; i < CONNECT_MAX; i++) + Connection[i] = 0; + GlobalChannel = 0; + + SendOverflows = 0; + ReceiveOverflows = 0; + BadConnection = IPXConnClass::CONNECTION_NONE; + + /*........................................................................ + Init timing parameters + ........................................................................*/ + RetryDelta = 2; // 2 ticks between retries + MaxRetries = -1; // disregard # retries + Timeout = 60; // report bad connection after 1 second + +} /* end of IPXManagerClass */ + + +/*************************************************************************** + * IPXManagerClass::~IPXManagerClass -- class destructor * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +IPXManagerClass::~IPXManagerClass() +{ + int i; + + /*------------------------------------------------------------------------ + Stop all IPX events + ------------------------------------------------------------------------*/ + if (Listening) { + IPXConnClass::Stop_Listening(); + Listening = 0; + } + + /*------------------------------------------------------------------------ + Free all protected-mode memory + ------------------------------------------------------------------------*/ + if (GlobalChannel) { + delete GlobalChannel; + GlobalChannel = 0; + } + for (i = 0; i < NumConnections; i++) { + delete Connection[i]; + Connection[i] = 0; + } + NumConnections = 0; + + /*------------------------------------------------------------------------ + Free all real-mode memory + ------------------------------------------------------------------------*/ + if (RealMemAllocd) { + Free_RealMode_Mem(); + RealMemAllocd = 0; + } + +} /* end of ~IPXManagerClass */ + + +/*************************************************************************** + * IPXManagerClass::Init -- initialization routine * + * * + * This routine allocates memory, + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Init() +{ + int i; + + if (!(GameToPlay == GAME_INTERNET)){ + /* + ----------------------- Error if IPX not installed ----------------------- + */ + if (!IPXStatus) { + return(false); + } + + /*------------------------------------------------------------------------ + Stop Listening + ------------------------------------------------------------------------*/ + if (Listening) { + IPXConnClass::Stop_Listening(); + Listening = 0; + } + + /*------------------------------------------------------------------------ + Free Real-mode memory + ------------------------------------------------------------------------*/ + if (RealMemAllocd) { + Free_RealMode_Mem(); + RealMemAllocd = 0; + } + }else{ + /* + ** Pretend IPX is available for Internet games whether it is or not + */ + IPXStatus = 1; + } + + /*------------------------------------------------------------------------ + Free protected-mode memory + ------------------------------------------------------------------------*/ + if (GlobalChannel) { + delete GlobalChannel; + GlobalChannel = 0; + } + for (i = 0; i < NumConnections; i++) { + delete Connection[i]; + Connection[i] = 0; + } + NumConnections = 0; + + + if (!(GameToPlay == GAME_INTERNET)){ + /*------------------------------------------------------------------------ + Allocate real-mode memory + ------------------------------------------------------------------------*/ + if (!Alloc_RealMode_Mem()) return(false); + RealMemAllocd = 1; + } + + /*------------------------------------------------------------------------ + Allocate the Global Channel + ------------------------------------------------------------------------*/ + GlobalChannel = new IPXGlobalConnClass (Glb_NumPackets, Glb_NumPackets, + Glb_MaxPacketLen, ProductID); + if (!GlobalChannel) + return(false); + GlobalChannel->Init(); + GlobalChannel->Set_Retry_Delta (RetryDelta); + GlobalChannel->Set_Max_Retries (MaxRetries); + GlobalChannel->Set_TimeOut (Timeout); + + /*------------------------------------------------------------------------ + Configure the IPX Connections + ------------------------------------------------------------------------*/ + IPXConnClass::Configure(Socket, ConnectionNum, ListenECB, SendECB, + FirstHeaderBuf, SendHeader, FirstDataBuf, SendBuf, Handler, PacketLen); + + /*------------------------------------------------------------------------ + Start Listening + ------------------------------------------------------------------------*/ + if (!(GameToPlay == GAME_INTERNET)){ + if (!IPXConnClass::Start_Listening()) return(false); + } + + Listening = 1; + + return(true); +} + + +/*************************************************************************** + * IPXManagerClass::Is_IPX -- tells if IPX is installed or not * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = IPX is installed; 0 = isn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Is_IPX(void) +{ + return(IPXStatus); + +} /* end of Is_IPX */ + + +/*************************************************************************** + * IPXManagerClass::Set_Timing -- sets timing for all connections * + * * + * This will set the timing parameters for all existing connections, and * + * all connections created from now on. This allows an application to * + * measure the Response_Time while running, and adjust timing accordingly. * + * * + * INPUT: * + * retrydelta value to set for retry delta * + * maxretries value to set for max # retries * + * timeout value to set for connection timeout * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/02/1995 BR : Created. * + *=========================================================================*/ +void IPXManagerClass::Set_Timing (unsigned long retrydelta, + unsigned long maxretries, unsigned long timeout) +{ + int i; + + RetryDelta = retrydelta; + MaxRetries = maxretries; + Timeout = timeout; + + if (GlobalChannel) { + GlobalChannel->Set_Retry_Delta (RetryDelta); + GlobalChannel->Set_Max_Retries (MaxRetries); + GlobalChannel->Set_TimeOut (Timeout); + } + + for (i = 0; i < NumConnections; i++) { + Connection[i]->Set_Retry_Delta (RetryDelta); + Connection[i]->Set_Max_Retries (MaxRetries); + Connection[i]->Set_TimeOut (Timeout); + } + +} /* end of Set_Timing */ + + +/*************************************************************************** + * IPXManagerClass::Create_Connection -- creates a new connection * + * * + * INPUT: * + * id application-specific numerical ID for this connection * + * node ptr to IPXNodeIDType (name & address) * + * address IPX address for this connection * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * Never create a connection with an 'id' of -1. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +bool IPXManagerClass::Create_Connection(int id, char *name, IPXAddressClass *address) +{ +#if (0) + char temp[80]; + + NetNumType num; + NetNodeType node; + + address->Get_Address (num, node); + + sprintf (temp, "Address:%02x %02x %02x %02x %02x %02x\n", node[0], + node[1], + node[2], + node[3], + node[4], + node[5]); + CCDebugString (temp); + + sprintf (temp, "Network:%02x %02x %02x %02x\n", num[0], + num[1], + num[2], + num[3]); + CCDebugString (temp); +#endif //(0) + + /* + ----------------------- Error if IPX not installed ----------------------- + */ + if (!IPXStatus) + return(false); + + /* + ------------------------- Error if no more room -------------------------- + */ + if (NumConnections==CONNECT_MAX) + return(false); + + /* + ------------------------- Create new connection -------------------------- + */ + Connection[NumConnections] = new IPXConnClass(Pvt_NumPackets, + Pvt_NumPackets, Pvt_MaxPacketLen, ProductID, address, id, name); + if (!Connection[NumConnections]) + return(false); + + Connection[NumConnections]->Init (); + Connection[NumConnections]->Set_Retry_Delta (RetryDelta); + Connection[NumConnections]->Set_Max_Retries (MaxRetries); + Connection[NumConnections]->Set_TimeOut (Timeout); + + NumConnections++; + + return(true); + +} /* end of Create_Connection */ + + +/*************************************************************************** + * IPXManagerClass::Delete_Connection -- deletes a connection * + * * + * INPUT: * + * id ID of connection to delete * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +bool IPXManagerClass::Delete_Connection(int id) +{ + int i,j; + + /* + ----------------------- Error if IPX not installed ----------------------- + */ + if (!IPXStatus) + return(false); + + /* + ------------------- Error if no connections to delete -------------------- + */ + if (NumConnections==0) + return(false); + + /* + ---------------------- Loop through all connections ---------------------- + */ + for (i = 0; i < NumConnections; i++) { + /* + ........................ If a match, delete it ........................ + */ + if (Connection[i]->ID==id) { + delete Connection[i]; + /* + ................ Move array elements back one index ................ + */ + for (j = i; j < NumConnections - 1; j++) { + Connection[j] = Connection[j+1]; + } + /* + ......................... Adjust counters .......................... + */ + NumConnections--; + if (CurConnection >= NumConnections) + CurConnection = 0; + return(true); + } + } + + /* + ---------------------------- No match; error ----------------------------- + */ + return(false); + +} /* end of Delete_Connection */ + + +/*************************************************************************** + * IPXManagerClass::Num_Connections -- gets the # of connections * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * # of connections * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/25/1995 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Num_Connections(void) +{ +#ifdef VIRTUAL_SUBNET_SERVER + /* + ** If we are connected to the VSS then dont coumt that in the number of connections. + */ + if (Connection_Index (VSS_ID) == IPXConnClass::CONNECTION_NONE){ + return(NumConnections); + }else{ + return(NumConnections -1); + } +#else // VIRTUAL_SUBNET_SERVER + + return(NumConnections); + +#endif //VIRTUAL_SUBNET_SERVER + +} /* end of Num_Connections */ + + +/*************************************************************************** + * IPXManagerClass::Connection_ID -- gets the given connection's ID * + * * + * INPUT: * + * index index of connection to retrieve * + * * + * OUTPUT: * + * ID for that connection, CONNECTION_NONE if invalid index * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/25/1995 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Connection_ID(int index) +{ + if (index >= 0 && index < NumConnections) { + return(Connection[index]->ID); + } else { + return(IPXConnClass::CONNECTION_NONE); + } +} + + +/*************************************************************************** + * IPXManagerClass::Connection_Name -- retrieves name for given connection * + * * + * INPUT: * + * id ID of connection to get name of * + * * + * OUTPUT: * + * ptr to connection's name, NULL if not found * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/19/1995 BR : Created. * + *=========================================================================*/ +char *IPXManagerClass::Connection_Name(int id) +{ + int i; + + for (i = 0; i < NumConnections; i++) { + if (Connection[i]->ID==id) { + return(Connection[i]->Name); + } + } + + return(NULL); + +} /* end of Connection_Name */ + + +/*************************************************************************** + * IPXManagerClass::Connection_Address -- retrieves connection's address * + * * + * INPUT: * + * id ID of connection to get address for * + * * + * OUTPUT: * + * pointer to IXPAddressClass, NULL if not found * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/19/1995 BR : Created. * + *=========================================================================*/ +IPXAddressClass * IPXManagerClass::Connection_Address(int id) +{ + int i; + + for (i = 0; i < NumConnections; i++) { + if (Connection[i]->ID==id) { + return(&Connection[i]->Address); + } + } + + return(NULL); + +} /* end of Connection_Address */ + + +/*************************************************************************** + * IPXManagerClass::Connection_Index -- gets given connection's index * + * * + * INPUT: * + * ID to retrieve index for * + * * + * OUTPUT: * + * index for this connection, CONNECTION_NONE if not found * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/25/1995 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Connection_Index(int id) +{ + int i; + + for (i = 0; i < NumConnections; i++) { + if (Connection[i]->ID==id) + return(i); + } + + return(IPXConnClass::CONNECTION_NONE); + +} /* end of Connection_Index */ + + +/*************************************************************************** + * IPXManagerClass::Send_Global_Message -- sends a Global Message * + * * + * INPUT: * + * buf buffer to send * + * buflen length of buf * + * ack_req 1 = ACK required; 0 = no ACK required * + * address address to send to; NULL = broadcast * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/25/1995 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Send_Global_Message(void *buf, int buflen, + int ack_req, IPXAddressClass *address) +{ + int rc; + + /* + ------------ Error if IPX not installed or not Listening ----------------- + */ + if (!IPXStatus || !Listening) return(false); + + rc = GlobalChannel->Send_Packet (buf, buflen, address, ack_req); + if (!rc) + SendOverflows++; + + return(rc); +} + + +/*************************************************************************** + * IPXManagerClass::Get_Global_Message -- polls the Global Message queue * + * * + * INPUT: * + * buf buffer to store received packet * + * buflen length of data placed in 'buf' * + * address IPX address of sender * + * product_id product ID of sender * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/25/1995 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Get_Global_Message(void *buf, int *buflen, + IPXAddressClass *address, unsigned short *product_id) +{ + /* + ------------ Error if IPX not installed or not Listening ----------------- + */ + if (!IPXStatus || !Listening) return(false); + + return(GlobalChannel->Get_Packet (buf, buflen, address, product_id)); +} + + +/*************************************************************************** + * IPXManagerClass::Send_Private_Message -- Sends a Private Message * + * * + * INPUT: * + * buf buffer to send * + * buflen length of 'buf' * + * conn_id connection ID to send to (CONNECTION_NONE = all) * + * ack_req 1 = ACK required; 0 = no ACK required * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/25/1995 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Send_Private_Message(void *buf, int buflen, int ack_req, + int conn_id) +{ + int i; // loop counter + int connect_idx; // index of channel to send to, if specified + + /* + ------------ Error if IPX not installed or not Listening ----------------- + */ + if (!IPXStatus || !Listening || (NumConnections==0)) return(false); + + /*------------------------------------------------------------------------ + Send the message to all connections + ------------------------------------------------------------------------*/ + if (conn_id==IPXConnClass::CONNECTION_NONE) { + + /* + ** If this is an internet game and no ack is reqired then we only need to send + ** the packet to the VSS and it will forward it to all the other players. + */ + +#ifdef VIRTUAL_SUBNET_SERVER + if (ack_req || (!Winsock.Get_Connected() || !UseVirtualSubnetServer)){ +#endif //VIRTUAL_SUBNET_SERVER + /*..................................................................... + Check for room in all connections + .....................................................................*/ + for (i = 0; i < NumConnections; i++) { +#ifdef VIRTUAL_SUBNET_SERVER + if (Connection[i]->ID != VSS_ID){ +#endif //VIRTUAL_SUBNET_SERVER + if (Connection[i]->Queue->Num_Send()==Connection[i]->Queue->Max_Send()) { + SendOverflows++; + return(false); + } +#ifdef VIRTUAL_SUBNET_SERVER + } +#endif // VIRTUAL_SUBNET_SERVER + } + + /*..................................................................... + Send packet to all connections + .....................................................................*/ + + for (i = 0; i < NumConnections; i++) { +#ifdef VIRTUAL_SUBNET_SERVER + if (Connection[i]->ID != VSS_ID){ +#endif //VIRTUAL_SUBNET_SERVER + Connection[i]->Send_Packet (buf, buflen, ack_req); +#ifdef VIRTUAL_SUBNET_SERVER + } +#endif //VIRTUAL_SUBNET_SERVER + } + +#ifdef VIRTUAL_SUBNET_SERVER + }else{ + /* + ** Send the packet to the VSS with a 0 header so it gets forwarded to all players. + */ + if (Connection[ Connection_Index(VSS_ID) ]->Queue->Num_Send() == Connection[ Connection_Index(VSS_ID) ]->Queue->Max_Send()) { + SendOverflows++; + return(false); + } + Connection[ Connection_Index(VSS_ID) ]->Send_Packet (buf, buflen, 0); + } +#endif // VIRTUAL_SUBNET_SERVER + + return(true); + + + } else { + + /*------------------------------------------------------------------------ + Send the message to the specified connection + ------------------------------------------------------------------------*/ + connect_idx = Connection_Index (conn_id); + if (connect_idx == IPXConnClass::CONNECTION_NONE) { + SendOverflows++; + return(false); + } + + /*..................................................................... + Check for room in the connection + .....................................................................*/ + if (Connection[connect_idx]->Queue->Num_Send() == + Connection[connect_idx]->Queue->Max_Send()) { + SendOverflows++; + return(false); + } + + /*..................................................................... + Send the packet to that connection + .....................................................................*/ + Connection[connect_idx]->Send_Packet (buf, buflen, ack_req); + return(true); + } +} + + +/*************************************************************************** + * IPXManagerClass::Get_Private_Message -- Polls the Private Message queue * + * * + * INPUT: * + * buf buffer to store incoming packet * + * buflen length of data placed in 'buf' * + * conn_id filled in with connection ID of sender * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/25/1995 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Get_Private_Message(void *buf, int *buflen, int *conn_id) +{ + int i; + int rc; + int c_id; +#ifdef VIRTUAL_SUBNET_SERVER + int vss = 0; +#endif // VIRTUAL_SUBNET_SERVER + + /* + ------------ Error if IPX not installed or not Listening ----------------- + */ + if (!IPXStatus || !Listening || (NumConnections==0)) return(false); + +#ifdef VIRTUAL_SUBNET_SERVER + if (Winsock.Get_Connected()){ + vss = (int) UseVirtualSubnetServer; + } +#endif //VIRTUAL_SUBNET_SERVER + + /*------------------------------------------------------------------------ + Safety check: ensure CurConnection is in range. + ------------------------------------------------------------------------*/ +#ifdef VIRTUAL_SUBNET_SERVER + if (CurConnection >= NumConnections - vss) +#else // VIRTUAL_SUBNET_SERVER + if (CurConnection >= NumConnections) +#endif //VIRTUAL_SUBNET_SERVER + CurConnection = 0; + + /*------------------------------------------------------------------------ + Scan all connections for a received packet, starting with 'CurConnection' + ------------------------------------------------------------------------*/ +#ifdef VIRTUAL_SUBNET_SERVER + for (i = 0; i < NumConnections - vss; i++) { +#else // VIRTUAL_SUBNET_SERVER + for (i = 0; i < NumConnections; i++) { +#endif // VIRTUAL_SUBNET_SERVER + /*..................................................................... + Check this connection for a packet + .....................................................................*/ + rc = Connection[CurConnection]->Get_Packet (buf, buflen); + c_id = Connection[CurConnection]->ID; + + /*..................................................................... + Increment CurConnection to the next connection index + .....................................................................*/ + CurConnection++; +#ifdef VIRTUAL_SUBNET_SERVER + if (CurConnection >= NumConnections - vss) +#else // VIRTUAL_SUBNET_SERVER + if (CurConnection >= NumConnections) +#endif // VIRTUAL_SUBNET_SERVER + CurConnection = 0; + + /*..................................................................... + If we got a packet, return the connection ID + .....................................................................*/ + if (rc) { + (*conn_id) = c_id; + return(true); + } + } + + return(false); +} + + +/*************************************************************************** + * IPXManagerClass::Service -- main polling routine for IPX Connections * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/25/1995 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Service(void) +{ + + int rc = 1; + int i; + CommHeaderType *packet; + int packetlen; + IPXAddressClass address; + + + +#ifndef NOT_FOR_WIN95 + + unsigned char temp_receive_buffer[1024]; + int recv_length; + + if (Winsock.Get_Connected()){ + + while ((recv_length = Winsock.Read(temp_receive_buffer, 1024))!=0){ + +#ifdef VIRTUAL_SUBNET_SERVER + /* + ** Get a pointer to the data header and swap the bit mask + */ + CurHeaderBuf = (IPXHEADER*)&temp_receive_buffer[0]; //NULL; + unsigned short *swapptr = (unsigned short*)CurHeaderBuf; + *swapptr = ntohs (*swapptr); + + CurDataBuf = (char*)&temp_receive_buffer[2]; + + /*..................................................................... + Compute the length of the packet (byte-swap the length in the IPX hdr) + .....................................................................*/ + packetlen = recv_length-2; +#else //VIRTUAL_SUBNET_SERVER + CurHeaderBuf = NULL; + CurDataBuf = (char*)&temp_receive_buffer[0]; + + /*..................................................................... + Compute the length of the packet (byte-swap the length in the IPX hdr) + .....................................................................*/ + packetlen = recv_length; + +#endif //VIRTUAL_SUBNET_SERVER + + + /*..................................................................... + Extract the sender's address from the IPX header + .....................................................................*/ + address.Set_Address (CurHeaderBuf); + + /*..................................................................... + Examine the Magic Number of the received packet to determine if this + packet goes into the Global Queue, or into one of the Private Queues + .....................................................................*/ + packet = (CommHeaderType *)CurDataBuf; +#if (0) +char tempbuf[256]; + +static char pcode [4][18]={ + "PACKET_DATA_ACK", // this is a data packet requiring an ACK + "PACKET_DATA_NOACK", // this is a data packet not requiring an ACK + "PACKET_ACK", // this is an ACK for a packet + "PACKET_COUNT" // for computational purposes +}; + +sprintf (tempbuf, "Received packet type %d, ID=%d, code=%s, length=%d \n", CurDataBuf[sizeof(CommHeaderType)], + packet->PacketID, + pcode[packet->Code], + recv_length); +CCDebugString (tempbuf); +#endif //(0) + if (packet->MagicNumber == GlobalChannel->Magic_Num()) { + /*.................................................................. + Put the packet in the Global Queue + ..................................................................*/ + if (!GlobalChannel->Receive_Packet (packet, packetlen, &address)) + ReceiveOverflows++; + } else { + if (packet->MagicNumber == ProductID) { + /*.................................................................. + Find the Private Queue that this packet is for + ..................................................................*/ + for (i = 0; i < NumConnections; i++) { +#ifdef VIRTUAL_SUBNET_SERVER + if (Connection[i]->Address == address && Connection[i]->ID != VSS_ID) { +#else //VIRTUAL_SUBNET_SERVER + if (Connection[i]->Address == address) { +#endif //VIRTUAL_SUBNET_SERVER + if (!Connection[i]->Receive_Packet (packet, packetlen)) + ReceiveOverflows++; + break; + } + } + } + } + + } + }else{ + + while (IPX_Get_Outstanding_Buffer95(&temp_receive_buffer[0])){ + + CurHeaderBuf = (IPXHEADER*)&temp_receive_buffer[0]; + CurDataBuf = (char*)&temp_receive_buffer[sizeof(IPXHeaderType)]; + + /*..................................................................... + Compute the length of the packet (byte-swap the length in the IPX hdr) + .....................................................................*/ + packetlen = ((CurHeaderBuf->Length & 0xff) << 8) | + (CurHeaderBuf->Length >> 8); + packetlen -= sizeof(IPXHeaderType); + + /*..................................................................... + Extract the sender's address from the IPX header + .....................................................................*/ + address.Set_Address (CurHeaderBuf); + + /*..................................................................... + Examine the Magic Number of the received packet to determine if this + packet goes into the Global Queue, or into one of the Private Queues + .....................................................................*/ + packet = (CommHeaderType *)CurDataBuf; + + if (packet->MagicNumber == GlobalChannel->Magic_Num()) { + /*.................................................................. + Put the packet in the Global Queue + ..................................................................*/ + if (!GlobalChannel->Receive_Packet (packet, packetlen, &address)) + ReceiveOverflows++; + } else { + if (packet->MagicNumber == ProductID) { + /*.................................................................. + Find the Private Queue that this packet is for + ..................................................................*/ + for (i = 0; i < NumConnections; i++) { + if (Connection[i]->Address == address) { + if (!Connection[i]->Receive_Packet (packet, packetlen)) + ReceiveOverflows++; + break; + } + } + } + } + + } + } + + //IPX_Start_Listening95(); + + +#else //NOT_FOR_WIN95 + + + /* + ------------ Error if IPX not installed or not Listening ----------------- + */ + if (!IPXStatus || !Listening) + return(false); + + /*------------------------------------------------------------------------ + Loop until there are no more packets to process. + ------------------------------------------------------------------------*/ + for (;;) { + /*..................................................................... + Check the BufferFlags for the "current" buffer; if it's empty, + break; out of the loop. + .....................................................................*/ + if (BufferFlags[CurIndex]==0) + break; + + /*..................................................................... + Compute the length of the packet (byte-swap the length in the IPX hdr) + .....................................................................*/ + packetlen = ((CurHeaderBuf->Length & 0xff) << 8) | + (CurHeaderBuf->Length >> 8); + packetlen -= sizeof(IPXHeaderType); + + /*..................................................................... + Extract the sender's address from the IPX header + .....................................................................*/ + address.Set_Address (CurHeaderBuf); + + /*..................................................................... + Examine the Magic Number of the received packet to determine if this + packet goes into the Global Queue, or into one of the Private Queues + .....................................................................*/ + packet = (CommHeaderType *)CurDataBuf; + if (packet->MagicNumber == GlobalChannel->Magic_Num()) { + /*.................................................................. + Put the packet in the Global Queue + ..................................................................*/ + if (!GlobalChannel->Receive_Packet (packet, packetlen, &address)) + ReceiveOverflows++; + } else { + if (packet->MagicNumber == ProductID) { + /*.................................................................. + Find the Private Queue that this packet is for + ..................................................................*/ + for (i = 0; i < NumConnections; i++) { + if (Connection[i]->Address == address) { + if (!Connection[i]->Receive_Packet (packet, packetlen)) + ReceiveOverflows++; + break; + } + } + } + } + + /*..................................................................... + Set the current BufferFlags to 0 (since we've cleaned out this buffer) + .....................................................................*/ + BufferFlags[CurIndex] = 0; + + /*..................................................................... + Go to the next packet buffer + .....................................................................*/ + CurIndex++; + CurHeaderBuf = (IPXHeaderType *)(((char *)CurHeaderBuf) + FullPacketLen); + CurDataBuf = ((char *)CurDataBuf) + FullPacketLen; + if (CurIndex >= NumBufs) { + CurHeaderBuf = FirstHeaderBuf; + CurDataBuf = FirstDataBuf; + CurIndex = 0; + } + + } + +#endif //NOT_FOR_WIN95 + + /*------------------------------------------------------------------------ + Service all connections. If a connection reports that it's gone "bad", + report an error to the caller. If it's the Global Channel, un-queue the + send entry that's holding things up. This will keep the Global Channel + from being clogged by one un-ACK'd outgoing packet. + ------------------------------------------------------------------------*/ + if (GlobalChannel) { + if (!GlobalChannel->Service()) { + GlobalChannel->Queue->UnQueue_Send (NULL, NULL, 0); + rc = 0; + } + } + for (i = 0; i < NumConnections; i++) { + if (!Connection[i]->Service()) { +#ifdef VIRTUAL_SUBNET_SERVER + if (Connection[i]->ID != VSS_ID){ +#endif //VIRTUAL_SUBNET_SERVER + rc = 0; + BadConnection = Connection[i]->ID; +#ifdef VIRTUAL_SUBNET_SERVER + } +#endif //VIRTUAL_SUBNET_SERVER + } + } + + if (rc) + BadConnection = IPXConnClass::CONNECTION_NONE; + + return(rc); + + +} /* end of Service */ + + +/*************************************************************************** + * IPXManagerClass::Get_Bad_Connection -- returns bad connection ID * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * ID of bad connection; CONNECTION_NONE if none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/04/1995 BRR : Created. * + *=========================================================================*/ +int IPXManagerClass::Get_Bad_Connection(void) +{ + return(BadConnection); + +} /* end of Get_Bad_Connection */ + + +/*************************************************************************** + * IPXManagerClass::Global_Num_Send -- reports # entries in send queue * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * # entries in the Global Send Queue * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/25/1995 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Global_Num_Send(void) +{ + /* + ------------ Error if IPX not installed or not Listening ----------------- + */ + if (!IPXStatus || !Listening) + return(0); + + return(GlobalChannel->Queue->Num_Send()); + +} /* end of Global_Num_Send */ + + +/*************************************************************************** + * IPXManagerClass::Global_Num_Receive -- reports # entries in recv queue * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * # entries in the Global Receive Queue * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/25/1995 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Global_Num_Receive(void) +{ + /* + ------------ Error if IPX not installed or not Listening ----------------- + */ + if (!IPXStatus || !Listening) + return(0); + + return(GlobalChannel->Queue->Num_Receive()); + +} /* end of Global_Num_Receive */ + + +/*************************************************************************** + * IPXManagerClass::Private_Num_Send -- reports # entries in send queue * + * * + * INPUT: * + * # entries in the Private Send Queue * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/25/1995 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Private_Num_Send(int id) +{ + int i; + int maxnum; + + /* + ------------ Error if IPX not installed or not Listening ----------------- + */ + if (!IPXStatus || !Listening || (NumConnections==0)) return(false); + + /*------------------------------------------------------------------------ + If connection ID specified, return that connection's # of packets + ------------------------------------------------------------------------*/ + if (id != IPXConnClass::CONNECTION_NONE) { + i = Connection_Index(id); + if (i != IPXConnClass::CONNECTION_NONE) { + return(Connection[i]->Queue->Num_Send()); + } else { + return(false); + } + } else { + + /*------------------------------------------------------------------------ + Otherwise, return the max # of all connections + ------------------------------------------------------------------------*/ + maxnum = 0; + for (i = 0; i < NumConnections; i++) { + if (Connection[i]->Queue->Num_Send() > maxnum) { + maxnum = Connection[i]->Queue->Num_Send(); + } + } + return(maxnum); + } +} + + +/*************************************************************************** + * IPXManagerClass::Private_Num_Receive -- reports # entries in recv queue * + * * + * INPUT: * + * id ID of connection to query * + * * + * OUTPUT: * + * # entries in the Private Receive Queue * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/25/1995 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Private_Num_Receive(int id) +{ + int i; + int maxnum; + + /* + ------------ Error if IPX not installed or not Listening ----------------- + */ + if (!IPXStatus || !Listening || (NumConnections==0)) + return(0); + + /*------------------------------------------------------------------------ + If connection ID specified, return that connection's # of packets + ------------------------------------------------------------------------*/ + if (id != IPXConnClass::CONNECTION_NONE) { + i = Connection_Index(id); + if (i != IPXConnClass::CONNECTION_NONE) { + return(Connection[i]->Queue->Num_Receive()); + } else { + return(0); + } + } else { + + /*------------------------------------------------------------------------ + Otherwise, return the max # of all connections + ------------------------------------------------------------------------*/ + maxnum = 0; + for (i = 0; i < NumConnections; i++) { + if (Connection[i]->Queue->Num_Receive() > maxnum) + maxnum = Connection[i]->Queue->Num_Receive(); + } + return(maxnum); + } +} + + +/*************************************************************************** + * IPXManagerClass::Set_Socket -- sets socket ID for all connections * + * * + * INPUT: * + * socket new socket ID to use * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * Do not call this function after communications have started; you * + * must call it before calling Init(). * + * The socket number is byte-swapped, since IPX requires socket ID's * + * to be stored high/low. * + * * + * HISTORY: * + * 01/25/1995 BR : Created. * + *=========================================================================*/ +void IPXManagerClass::Set_Socket(unsigned short socket) +{ + Socket = (unsigned short)( (((unsigned long)socket & 0x00ff) << 8) | + (((unsigned long)socket & 0xff00) >> 8)); + +} /* end of Set_Socket */ + + +/*************************************************************************** + * IPXManagerClass::Response_Time -- Returns largest Avg Response Time * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * largest avg response time * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/04/1995 BRR : Created. * + *=========================================================================*/ +unsigned long IPXManagerClass::Response_Time(void) +{ + unsigned long resp; + unsigned long maxresp = 0; + int i; +#ifdef VIRTUAL_SUBNET_SERVER + int vss=0; + + if (Winsock.Get_Connected()){ + vss = (int) UseVirtualSubnetServer; + } + + for (i = 0; i < NumConnections - vss; i++) { +#else //VIRTUAL_SUBNET_SERVER + for (i = 0; i < NumConnections; i++) { +#endif // VIRTUAL_SUBNET_SERVER + resp = Connection[i]->Queue->Avg_Response_Time(); + if (resp > maxresp) + maxresp = resp; + } + + return(maxresp); +} + + +/*************************************************************************** + * IPXManagerClass::Global_Response_Time -- Returns Avg Response Time * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * avg global channel response time * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/04/1995 BRR : Created. * + *=========================================================================*/ +unsigned long IPXManagerClass::Global_Response_Time(void) +{ + if (GlobalChannel) { + return (GlobalChannel->Queue->Avg_Response_Time()); + } else { + return (0); + } +} + + +/*************************************************************************** + * IPXManagerClass::Reset_Response_Time -- Reset response time * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/04/1995 BRR : Created. * + *=========================================================================*/ +void IPXManagerClass::Reset_Response_Time(void) +{ + int i; + + for (i = 0; i < NumConnections; i++) { + Connection[i]->Queue->Reset_Response_Time(); + } + + if (GlobalChannel) + GlobalChannel->Queue->Reset_Response_Time(); + +} /* end of Reset_Response_Time */ + + +/*************************************************************************** + * IPXManagerClass::Oldest_Send -- gets ptr to oldest send buf * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * buf ptr * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/04/1995 BRR : Created. * + *=========================================================================*/ +void * IPXManagerClass::Oldest_Send(void) +{ + int i,j; + unsigned long time; + unsigned long mintime = 0xffffffff; + SendQueueType *send_entry; // ptr to send entry header + CommHeaderType *packet; + void *buf = NULL; + + for (i = 0; i < NumConnections; i++) { + + send_entry = NULL; + + for (j = 0; j < Connection[i]->Queue->Num_Send(); j++) { + send_entry = Connection[i]->Queue->Get_Send(j); + if (send_entry) { + packet = (CommHeaderType *)send_entry->Buffer; + if (packet->Code == ConnectionClass::PACKET_DATA_ACK && send_entry->IsACK == 0) { + break; + } else { + send_entry = NULL; + } + } + } + + if (send_entry!=NULL) { + + time = send_entry->FirstTime; + + if (time < mintime) { + mintime = time; + buf = send_entry->Buffer; + } + } + } + + return(buf); + +} /* end of Oldest_Send */ + + +/*************************************************************************** + * IPXManagerClass::Set_Bridge -- prepares to cross a bridge * + * * + * This routine is designed to prevent the connection from having to * + * call Get_Local_Target, except the minimum number of times, since that * + * routine is buggy & goes away for long periods sometimes. * + * * + * INPUT: * + * bridge network number of the destination bridge * + * * + * OUTPUT: * + * none * + * * + * WARNINGS: * + * none * + * * + * HISTORY: * + * 07/06/1995 BRR : Created. * + *=========================================================================*/ +void IPXManagerClass::Set_Bridge(NetNumType bridge) +{ + if (GlobalChannel) { + GlobalChannel->Set_Bridge(bridge); + } +} + + +/*************************************************************************** + * IPXManagerClass::Configure_Debug -- sets up special debug values * + * * + * Mono_Debug_Print2() can look into a packet to pull out a particular * + * ID, and can print both that ID and a string corresponding to * + * that ID. This routine configures these values so it can find * + * and decode the ID. This ID is used in addition to the normal * + * CommHeaderType values. * + * * + * INPUT: * + * index connection index to configure (-1 = Global Channel) * + * offset ID's byte offset into packet * + * size size of ID, in bytes; 0 if none * + * names ptr to array of names; use ID as an index into this * + * maxnames max # in the names array; 0 if none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * Names shouldn't be longer than 12 characters. * + * * + * HISTORY: * + * 05/31/1995 BRR : Created. * + *=========================================================================*/ +void IPXManagerClass::Configure_Debug(int index, int offset, int size, + char **names, int maxnames) +{ + if (index == -1) { + GlobalChannel->Queue->Configure_Debug (offset, size, names, maxnames); + } else { + if (Connection[index]) { + Connection[index]->Queue->Configure_Debug (offset, size, names, maxnames); + } + } +} + + +/*************************************************************************** + * IPXManagerClass::Mono_Debug_Print -- debug output routine * + * * + * INPUT: * + * index index of connection to display (-1 = Global Channel) * + * refresh 1 = complete screen refresh * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/25/1995 BR : Created. * + *=========================================================================*/ +void IPXManagerClass::Mono_Debug_Print(int index, int refresh) +{ +#ifdef WWLIB32_H + char txt[80]; + int i; + + if (index == -1) + GlobalChannel->Queue->Mono_Debug_Print (refresh); + else if (Connection[index]) + Connection[index]->Queue->Mono_Debug_Print (refresh); + + if (refresh) { + Mono_Set_Cursor (20,1); + Mono_Printf ("IPX Queue:"); + + Mono_Set_Cursor (9,2); + Mono_Printf ("Average Response Time:"); + + Mono_Set_Cursor (43,1); + Mono_Printf ("Send Overflows:"); + + Mono_Set_Cursor (40,2); + Mono_Printf ("Receive Overflows:"); + + } + + Mono_Set_Cursor (32,1); + Mono_Printf ("%d",index); + + Mono_Set_Cursor (32,2); + if (index == -1) { + Mono_Printf ("%d ", GlobalChannel->Queue->Avg_Response_Time()); + } else { + Mono_Printf ("%d ", Connection[index]->Queue->Avg_Response_Time()); + } + + Mono_Set_Cursor (59,1); + Mono_Printf ("%d ", SendOverflows); + + Mono_Set_Cursor (59,2); + Mono_Printf ("%d ", ReceiveOverflows); + + for (i = 0; i < NumBufs; i++) { + if (BufferFlags[i]) { + txt[i] = 'X'; + } else { + txt[i] = '_'; + } + } + txt[i] = 0; + Mono_Set_Cursor ((80-NumBufs)/2,3); + Mono_Printf ("%s",txt); + +#else + index = index; + refresh = refresh; +#endif +} /* end of Mono_Debug_Print */ + + +/*************************************************************************** + * IPXManagerClass::Alloc_RealMode_Mem -- allocates real-mode memory * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Alloc_RealMode_Mem(void) +{ +#ifdef NOT_FOR_WIN95 + union REGS regs; + struct SREGS sregs; + int size; // required size of allocation + unsigned char *realmode; // start addresses of real-mode data + int realmodelen; // length of real-mode data + unsigned long func_val; + char *p; // for parsing buffer + int i; + + /*------------------------------------------------------------------------ + Compute # of buffers we need to allocate, & the max size of each one + ------------------------------------------------------------------------*/ + NumBufs = Glb_NumPackets + (Pvt_NumPackets * CONNECT_MAX); + + PacketLen = Glb_MaxPacketLen + sizeof (GlobalHeaderType); + if (Pvt_MaxPacketLen + sizeof (CommHeaderType) > PacketLen) + PacketLen = Pvt_MaxPacketLen + sizeof (CommHeaderType); + + FullPacketLen = PacketLen + sizeof(IPXHeaderType); + + /*------------------------------------------------------------------------ + Compute the size of everything we'll ever need, allocate it in one big + chunk. The memory is used as follows: + - Real-mode assembly IPX callback routine, plus its data, + (which includes the ListenECB) + - Array of IPX Packet buffers (IPXHeader plus data buffer) + - SendECB: ECB for sending + - SendHeader: IPX Header for sending + - SendBuf: Packet buffer for sending + - BufferFlags: 1 byte for each incoming packet buffer; 1=in use, 0=free + ------------------------------------------------------------------------*/ + realmode = (unsigned char *)Get_RM_IPX_Address(); + realmodelen = Get_RM_IPX_Size(); + size = realmodelen + // assembly routine & its data + (FullPacketLen * NumBufs) + // array of packet buffers + sizeof(ECBType) + // SendECB + FullPacketLen + // SendHeader & SendBuf + NumBufs; // BufferFlags + if (size > 65535) + return(false); + + /*------------------------------------------------------------------------ + Allocate DOS memory for the ECB, IPXHeader & packet buffers: + AX = 0x100 + BX = # paragraphs to allocate + - if Success, AX = real-mode segment, DX = selector + - if Failure, carry flag is set + ------------------------------------------------------------------------*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_ALLOC_DOS_MEM; // DPMI function to call + regs.x.ebx = ((size + 15) >> 4); // # paragraphs to allocate + int386x (DPMI_INT, ®s, ®s, &sregs); // allocate the memory + /*........................................................................ + If the carry flag is set, DPMI is indicating an error. + ........................................................................*/ + if (regs.x.cflag) { + return(false); + } + + /*........................................................................ + Save the values of the returned segment & selector + ........................................................................*/ + Selector = regs.w.dx; + Segment = regs.w.ax; + RealMemSize = size; + RealModeData = (RealModeDataType *)(((long)Segment) << 4); + + /*------------------------------------------------------------------------ + Lock the memory (since we're servicing interrupts with it) + AX = 0x600 + BX:CX = starting linear address of memory to lock + SI:DI = size of region to lock (in bytes) + - If Failure, carry flag is set. + ------------------------------------------------------------------------*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_LOCK_MEM; // DPMI function to call + regs.x.ebx = ((long)RealModeData & 0xffff0000) >> 16; + regs.x.ecx = ((long)RealModeData & 0x0000ffff); + regs.x.esi = ((long)RealMemSize & 0xffff0000) >> 16; + regs.x.edi = ((long)RealMemSize & 0x0000ffff); + int386x (DPMI_INT, ®s, ®s, &sregs); // call DPMI + /*........................................................................ + If the carry flag is set, DPMI is indicating an error. + ........................................................................*/ + if (regs.x.cflag) { + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_FREE_DOS_MEM; // DPMI function to call + regs.x.edx = Selector; // ptr to free + int386x (DPMI_INT, ®s, ®s, &sregs); // free the memory + return(false); + } + + /*------------------------------------------------------------------------ + Copy the Real-mode code into our memory buffer + ------------------------------------------------------------------------*/ + p = (char *)(((long)Segment) << 4); + memcpy (p,realmode,realmodelen); + p += realmodelen; + + /*------------------------------------------------------------------------ + Compute & save the entry point for the real-mode packet handler + ------------------------------------------------------------------------*/ + func_val = (unsigned long)RealModeData; + Handler = (((func_val & 0xffff0) << 12) | + ((func_val & 0x000f) + RealModeData->FuncOffset)); + + /*------------------------------------------------------------------------ + Fill in buffer pointers + ------------------------------------------------------------------------*/ + ListenECB = &(RealModeData->ListenECB); + + FirstHeaderBuf = (IPXHeaderType *)p; + FirstDataBuf = (((char *)FirstHeaderBuf) + sizeof(IPXHeaderType)); + CurIndex = 0; + CurHeaderBuf = FirstHeaderBuf; + CurDataBuf = FirstDataBuf; + p += FullPacketLen * NumBufs; + + SendECB = (ECBType *)p; + p += sizeof (ECBType); + + SendHeader = (IPXHeaderType *)p; + p += sizeof (IPXHeaderType); + + SendBuf = (char *)p; + p += PacketLen; + + BufferFlags = (char *)p; + + /*------------------------------------------------------------------------ + Fill in the real-mode routine's data (The ECB will be filled in when we + command IPX to Listen). + ------------------------------------------------------------------------*/ + RealModeData->NumBufs = (short)NumBufs; + RealModeData->BufferFlags = (char *) + ((((long)BufferFlags & 0xffff0) << 12) | + ((long)BufferFlags & 0x0000f)); + RealModeData->PacketSize = (short)FullPacketLen; + RealModeData->FirstPacketBuf = (IPXHeaderType *) + ((((long)FirstHeaderBuf & 0xffff0) << 12) | + ((long)FirstHeaderBuf & 0x0000f)); + RealModeData->CurIndex = 0; + RealModeData->CurPacketBuf = RealModeData->FirstPacketBuf; + RealModeData->Semaphore = 0; + RealModeData->ReEntrantCount = 0; + + /*------------------------------------------------------------------------ + Init state of all buffers to empty + ------------------------------------------------------------------------*/ + for (i = 0; i < NumBufs; i++) + BufferFlags[i] = 0; + + /*------------------------------------------------------------------------ + Check the start & end markers in the real-mode memory area + ------------------------------------------------------------------------*/ + if (RealModeData->Marker1 != 0x1111 || + RealModeData->Marker2 != 0x2222) { + Free_RealMode_Mem(); + return(false); + } else { + return(true); + } +#else //NOT_FOR_WIN95 + + return (TRUE); + +#endif //NOT_FOR_WIN95 +} + + +/*************************************************************************** + * IPXManagerClass::Free_RealMode_Mem -- frees real-mode memory * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int IPXManagerClass::Free_RealMode_Mem(void) +{ +#ifdef NOT_FOR_WIN95 + union REGS regs; + struct SREGS sregs; + int rc = 1; + + /*------------------------------------------------------------------------ + Unlock the memory + ------------------------------------------------------------------------*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_UNLOCK_MEM; // DPMI function to call + regs.x.ebx = ((long)RealModeData & 0xffff0000) >> 16; + regs.x.ecx = ((long)RealModeData & 0x0000ffff); + regs.x.esi = ((long)RealMemSize & 0xffff0000) >> 16; + regs.x.edi = ((long)RealMemSize & 0x0000ffff); + int386x (DPMI_INT, ®s, ®s, &sregs); // call DPMI + /*........................................................................ + If the carry flag is set, DPMI is indicating an error. + ........................................................................*/ + if (regs.x.cflag) { + rc = 0; + } + + /*------------------------------------------------------------------------ + Free DOS memory + ------------------------------------------------------------------------*/ + memset (®s, 0 ,sizeof(regs)); + segread (&sregs); + regs.x.eax = DPMI_FREE_DOS_MEM; // DPMI function to call + regs.x.edx = Selector; // ptr to free + int386x (DPMI_INT, ®s, ®s, &sregs); // free the memory + + return(rc); + +#else // NOT_FOR_WIN95 + + return (1); + +#endif // NOT_FOR_WIN95 +} /* end of Free_RealMode_Mem */ + + diff --git a/IPXMGR.H b/IPXMGR.H new file mode 100644 index 0000000..87305e4 --- /dev/null +++ b/IPXMGR.H @@ -0,0 +1,395 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\ipxmgr.h_v 1.10 16 Oct 1995 16:47:34 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : IPXMGR.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : April 3, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * * + * This is the Connection Manager for IPX network communications. It * + * creates, manages, & orchestrates multiple IPX connections, as well as * + * the "global" connection ("Global Channel"), which can talk to any * + * system on the net. * + * * + * Use the Global Channel to query systems for their names, ID's, & * + * IPX addresses. Then, create a Private Connection with each system * + * that joins your game, and use the Private Channel to send game packets * + * (the private channel will perform somewhat faster, & gives you better * + * control than the Global Channel; it can detect retries, and the Global * + * Channel can't). * + * * + * HOW THIS CLASS WORKS: * + * This class has to set up an IPX Event Service Routine in low (DOS) * + * memory. So, it uses DPMI to allocate & lock a chunk of DOS memory; * + * this memory is used for all incoming packet buffers, the outgoing * + * packet buffer, and the actual code for the event handler. The real- * + * mode handler code & this class share a portion of memory that's mapped * + * into a "RealModeDataType" structure. As packets come in, the handler * + * points IPX to the next available packet buffer & restarts listening; * + * it sets a flag to tell this class that a packet is present at that * + * buffer slot. This class must read all the packets & determine which * + * connection they go with (the Global Channel, or one of the Private * + * Channels). This parsing is done in the Service routine for this class. * + * * + * Constructor: Just inits some variables, checks to see if IPX is there * + * Destructor: Complete shutdown; stops IPX listening, frees all memory * + * Init: Should only be called once (but can be called more); * + * allocates all memory, creates the Global Channel * + * connection, starts IPX listening. By not placing this * + * step in the constructor, the app can control when * + * listening actually starts; also, you don't get a bunch * + * of allocations just by declaring an IPXManagerClass * + * instance. You have to call Init() for the allocations * + * to occur. * + * Connection utilities: Create & manage Private Connections. Each * + * connection has its own IPX address, numerical ID, and * + * character name (presumably the name of the other * + * player). * + * Send/Get_Global_Message: adds a packet to the Global Connection queue, * + * or reads from the queue. The caller should check the * + * ProductID value from returned packets to be sure it's * + * talking to the right product. * + * Send/Get_Private_Message: adds a packet to a Private Connection queue, * + * or reads from the queue * + * Service: Checks the Real-Mode-Memory packet array to see if any * + * new packets have come in; if they have, it parses them * + * & distributes them to the right connection queue. The * + * queue's Service routine handles ACK'ing or Resending * + * packets. * + * * + * Here's a memory map of the Real-Mode memory block. 'N' is the number * + * of packet buffers allocated in low memory: * + * * + * ---------------------------------- * + * | Shared-memory data | * + * |--------------------------------| * + * | Real-mode event handler code | * + * |--------------------------------| * + * | IPX Header & Packet Buffer 0 | * + * |--------------------------------| * + * | IPX Header & Packet Buffer 1 | * + * |--------------------------------| * + * | IPX Header & Packet Buffer 2 | * + * |--------------------------------| * + * | . . . | * + * |--------------------------------| * + * | IPX Header & Packet Buffer N | * + * |--------------------------------| * + * | Send Event Control Block | * + * |--------------------------------| * + * | Send IPX Header | * + * |--------------------------------| * + * | Send Packet Buffer | * + * |--------------------------------| * + * | Flags Array [N] | * + * ---------------------------------- * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef IPXMANAGER_H +#define IPXMANAGER_H + + +/* +********************************* Includes ********************************** +*/ +#include "ipxconn.h" +#include "ipxgconn.h" +#include "ipxaddr.h" +#include "connmgr.h" + +/* +********************************** Defines ********************************** +*/ +/*--------------------------------------------------------------------------- +This is Virgin Interactive Entertainment's registered socket ID. +---------------------------------------------------------------------------*/ +#define VIRGIN_SOCKET 0x8813 + +/*--------------------------------------------------------------------------- +This is the maximum number of IPX connections supported. Just change this +value to support more. +---------------------------------------------------------------------------*/ +#define CONNECT_MAX 6 + +/*--------------------------------------------------------------------------- +These routines report the location & length of the real-mode routine, as +it's stored in protected-mode memory. +---------------------------------------------------------------------------*/ +extern "C" { + void *Get_RM_IPX_Address(void); + long Get_RM_IPX_Size(void); +} + +/* +***************************** Class Declaration ***************************** +*/ +class IPXManagerClass : public ConnManClass +{ + /* + ---------------------------- Public Interface ---------------------------- + */ + public: + /*..................................................................... + Constructor/destructor. + .....................................................................*/ + IPXManagerClass (int glb_maxlen, int pvt_maxlen, int glb_num_packets, + int pvt_num_packets, unsigned short socket, unsigned short product_id); + virtual ~IPXManagerClass (); // stop listening + + /*..................................................................... + Initialization routines. + .....................................................................*/ + int Init (void); + int Is_IPX(void); + virtual void Set_Timing (unsigned long retrydelta, unsigned long maxretries, + unsigned long timeout); + void Set_Bridge(NetNumType bridge); + + /*..................................................................... + These routines control creation of the "Connections" (data queues) for + each remote system. + .....................................................................*/ + bool Create_Connection(int id, char *name, IPXAddressClass *address); + bool Delete_Connection(int id); + virtual int Num_Connections(void); + virtual int Connection_ID(int index); + char *Connection_Name(int id); + IPXAddressClass * Connection_Address(int id); + virtual int Connection_Index(int id); + + /*..................................................................... + This is how the application sends & receives messages. + .....................................................................*/ + int Send_Global_Message (void *buf, int buflen, int ack_req = 0, + IPXAddressClass *address = NULL); + int Get_Global_Message (void *buf, int *buflen, IPXAddressClass *address, + unsigned short *product_id); + + virtual int Send_Private_Message (void *buf, int buflen, + int ack_req = 1, int conn_id = CONNECTION_NONE); + virtual int Get_Private_Message (void *buf, int *buflen, int *conn_id); + + /*..................................................................... + The main polling routine; should be called as often as possible. + .....................................................................*/ + virtual int Service (void); + + /*..................................................................... + This routine reports which connection has an error on it. + .....................................................................*/ + int Get_Bad_Connection(void); + + /*..................................................................... + Queue utility routines. The application can determine how many + messages are in the send/receive queues. + .....................................................................*/ + virtual int Global_Num_Send(void); + virtual int Global_Num_Receive(void); + virtual int Private_Num_Send(int id = CONNECTION_NONE); + virtual int Private_Num_Receive(int id = CONNECTION_NONE); + + /*..................................................................... + This routine changes the socket ID assigned the IPX Manager when it + was constructed. Do not call this function after calling Init()! + The Socket ID should be known by both ends of the communications before + any packets are sent. + .....................................................................*/ + void Set_Socket(unsigned short socket); + + /*..................................................................... + Routines to return the largest average queue response time, and to + reset the response time for all queues. + .....................................................................*/ + virtual unsigned long Response_Time(void); + unsigned long Global_Response_Time(void); + virtual void Reset_Response_Time(void); + + /*..................................................................... + This routine returns a pointer to the oldest non-ACK'd buffer I've sent. + .....................................................................*/ + void * Oldest_Send(void); + + /*..................................................................... + Debug routines + .....................................................................*/ + virtual void Configure_Debug(int index, int offset, int size, + char **names, int maxnames); + virtual void Mono_Debug_Print(int index, int refresh = 0); + + /* + --------------------------- Private Interface ---------------------------- + */ + private: + /*..................................................................... + These routines allocate & free the DOS Real-mode memory block. + .....................................................................*/ + int Alloc_RealMode_Mem(void); + int Free_RealMode_Mem(void); + + /*..................................................................... + Misc variables + .....................................................................*/ + unsigned int IPXStatus : 1; // 0 = no IPX, 1 = IPX found + unsigned int Listening : 1; // 1 = Listening is on + unsigned int RealMemAllocd : 1; // 1 = Real-mode memory has been alloc'd + + /*..................................................................... + Packet Sizes, used for allocating real-mode memory + .....................................................................*/ + int Glb_MaxPacketLen; // Global Channel maximum packet size + int Glb_NumPackets; // # Global send/receive packets + int Pvt_MaxPacketLen; // Private Channel maximum packet size + int Pvt_NumPackets; // # Private send/receive packets + + /*..................................................................... + The ProductID is used in the Global Channel's packet header, and it's + used for the Private Channels' Magic Number. + .....................................................................*/ + unsigned short ProductID; // product ID + + /*..................................................................... + The Socket ID, and local Novell Connection Number + .....................................................................*/ + unsigned short Socket; // Our socket ID for sending/receiving + int ConnectionNum; // local connection #, 0=not logged in + + /*..................................................................... + Array of connection queues + .....................................................................*/ + IPXConnClass * Connection[CONNECT_MAX]; // array of connection object ptrs + int NumConnections; // # connection objects in use + IPXGlobalConnClass *GlobalChannel; // the Global Channel + + /*..................................................................... + Current queue for polling for received packets + .....................................................................*/ + int CurConnection; + + /*..................................................................... + Timing parameters for all connections + .....................................................................*/ + unsigned long RetryDelta; + unsigned long MaxRetries; + unsigned long Timeout; + + /*--------------------------------------------------------------------- + Real-mode memory pointers and such + ---------------------------------------------------------------------*/ + /*..................................................................... + This is a structure that mirrors data in real-mode memory: + .....................................................................*/ + typedef struct { + short Marker1; // the byte ID marker + ECBType ListenECB; // the Listening ECB + short NumBufs; // # of buffers we're giving to the handler + char *BufferFlags; // array of buffer-avail flags + short PacketSize; // size of packet including IPX header + IPXHeaderType *FirstPacketBuf; // ptr to 1st packet buffer + short CurIndex; // handler's current packet index + IPXHeaderType *CurPacketBuf; // handler's current packet buf + short FuncOffset; // contains offset of code + char Semaphore; // prevents re-entrancy + short ReEntrantCount; // times we've been called re-entrantly + short StackPtr; // real-mode stack pointer + short StackSeg; // real-mode stack segment + short StackPtr_int; // internal stack pointer + short StackSeg_int; // internal stack segment + short StackCheck; // stack check value (0x1234) + short Stack[256]; // actual stack space + short StackSpace; // label for top of stack + short Marker2; // the byte ID marker + } RealModeDataType; + + /*..................................................................... + The number & size of packet buffers in low memory + .....................................................................*/ + int NumBufs; // # packet buffers allocated + int PacketLen; // size of packet without IPX header + int FullPacketLen; // size of packet including IPX header + + /*..................................................................... + Selector & Segment of the DOS allocation; + Size of the allocation; + Ptr to the real-mode assembly data area + .....................................................................*/ + unsigned short Selector; // selector of DOS allocation pointer + unsigned short Segment; // real-mode segment of DOS allocation + int RealMemSize; // size of real mode memory allocated + RealModeDataType *RealModeData; // assembly routine & its data + + /*..................................................................... + This is a real-mode pointer to the address of the real-mode assembly + entry point. + .....................................................................*/ + long Handler; + + /*..................................................................... + Event Control Block for listening; contained within the real-mode + assembly routine's data area + .....................................................................*/ + ECBType *ListenECB; // ECB for listening + + /*..................................................................... + ptr to the 1st header & data buffers in the packet buffer array + .....................................................................*/ + IPXHeaderType *FirstHeaderBuf; // array of packet headers & buffers + char *FirstDataBuf; // 1st data buffer area + + /*..................................................................... + Current packet index & ptrs for parsing packets + .....................................................................*/ + int CurIndex; // Current packet index, for reading + IPXHeaderType *CurHeaderBuf; // Current packet ptr, for reading + char *CurDataBuf; // Current actual data ptr + + /*..................................................................... + ECB, header, & buffer for sending + .....................................................................*/ + ECBType *SendECB; // ECB for sending + IPXHeaderType *SendHeader; // Header for sending + char *SendBuf; // buffer for sending + + /*..................................................................... + Flags indicating whether a buffer contains data or not (1 = full) + The IPXManager must clear this flag; the real-mode routine will set it. + .....................................................................*/ + char *BufferFlags; // array of rx-buffer-avail flags + + /*..................................................................... + Various Statistics + .....................................................................*/ + int SendOverflows; + int ReceiveOverflows; + int BadConnection; +}; + +#endif +/*************************** end of ipxmgr.h *******************************/ diff --git a/IPXPROT.ASM b/IPXPROT.ASM new file mode 100644 index 0000000..8ec1156 --- /dev/null +++ b/IPXPROT.ASM @@ -0,0 +1,113 @@ +; +; Command & Conquer(tm) +; Copyright 2025 Electronic Arts Inc. +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; + + +;!!!!!!!!!!!!!!!!!!! lock the allocation + +;*************************************************************************** +;** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** +;*************************************************************************** +;* * +;* Project Name : VQLIB * +;* * +;* File Name : HANDLER.ASM * +;* * +;* Programmer : Bill Randolph * +;* * +;* Start Date : April 7, 1995 * +;* * +;* Last Update : April 7, 1995 [BRR] * +;* * +;*-------------------------------------------------------------------------* +;* Functions: * +;* IPXHandler -- callback routine for IPX * +;* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * +;********************* Model & Processor Directives ************************ +IDEAL +P386 +MODEL USE32 FLAT +LOCALS ?? + + +;******************************** Includes ********************************* + + +;******************************** Defines *********************************** + + +;****************************** Declarations ******************************** +global C Get_RM_IPX_Address:NEAR +global C Get_RM_IPX_Size:NEAR + +;********************************* Data ************************************ + DATASEG + +LABEL RealBinStart BYTE +include "obj\ipxreal.ibn" +LABEL RealBinEnd BYTE + +;********************************* Data ************************************ + CODESEG + +;*************************************************************************** +;* Get_RM_IPX_Address -- Return address of real mode code for copy. * +;* * +;* INPUT: * +;* none * +;* * +;* OUTPUT: * +;* VOID * to the address of the real mode IPX code * +;* * +;* PROTO: * +;* VOID *Get_RM_IPX_Address(VOID); * +;* * +;* HISTORY: * +;* 07/06/1994 SKB : Created. * +;*=========================================================================* + PROC Get_RM_IPX_Address C Near + + mov eax, OFFSET RealBinStart + ret + + ENDP + +;*************************************************************************** +;* Get_RM_IPX_Size -- return size of real mode IPX code. * +;* * +;* INPUT: * +;* none * +;* * +;* OUTPUT: * +;* LONG size of the real mode IPX code * +;* * +;* PROTO: * +;* LONG Get_RM_IPX_Size(VOID); * +;* * +;* HISTORY: * +;* 07/06/1994 SKB : Created. * +;*=========================================================================* + PROC Get_RM_IPX_Size C Near + + mov eax, OFFSET RealBinEnd - OFFSET RealBinStart + ret + + ENDP + + END + +;************************** End of handler.asm ***************************** diff --git a/IPXREAL.ASM b/IPXREAL.ASM new file mode 100644 index 0000000..10bb56b --- /dev/null +++ b/IPXREAL.ASM @@ -0,0 +1,319 @@ +; +; Command & Conquer(tm) +; Copyright 2025 Electronic Arts Inc. +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; + +;*************************************************************************** +;** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** +;*************************************************************************** +;* * +;* Project Name : VQLIB * +;* * +;* File Name : HANDLER.ASM * +;* * +;* Programmer : Bill Randolph * +;* * +;* Start Date : April 7, 1995 * +;* * +;* Last Update : April 7, 1995 [BRR] * +;* * +;*-------------------------------------------------------------------------* +;* Functions: * +;* IPXHandler -- callback routine for IPX * +;* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * +;********************* Model & Processor Directives ************************ +IDEAL +MODEL LARGE +P386N +LOCALS ?? + + +;******************************** Includes ********************************* + + +;******************************** Defines *********************************** + + +;****************************** Declarations ******************************** +global C IPXHandler:FAR + + +;********************************* Code ************************************ + CODESEG + + +;--------------------------------------------------------------------------- +; The markers let the application verify that it's mapping this memory +; correctly. +;--------------------------------------------------------------------------- +Marker1 DW 1111h ; placeholder to find data start + +;--------------------------------------------------------------------------- +; This is the IPX Event Control Block: +;--------------------------------------------------------------------------- +ECB_LinkAddress DD ? +ECB_EventServiceRoutine DD ? ; Event Handler ptr +ECB_InUse DB ? ; 0 = event is complete +ECB_CompletionCode DB ? ; 0 = OK, IPX error otherwise +ECB_SocketNumber DW ? ; socket to listen/send on +ECB_ConnectionID DW ? +ECB_RestOfWorkspace DW ? +ECB_DriverWorkSpace DB 12 DUP (?) +ECB_ImmediateAddress DB 6 DUP (?) ; bridge address +ECB_PacketCount DW ? ; # data areas (2) +ECB_HeaderAddress DD ? ; ptr to IPX header buffer +ECB_HeaderLength DW ? ; length of IPX header buffer +ECB_PacketAddress DD ? ; ptr to packet buffer +ECB_PacketLength DW ? ; length of packet buffer + +;--------------------------------------------------------------------------- +; The rest of the variables are for telling IPX which buffer to store the +; next incoming packet in. They must be initialized by the application. +;--------------------------------------------------------------------------- +NumBufs DW 0 ; # buffers provided by app +BufferFlags DD 0 ; array of in-use flags (1 = in use) +PacketSize DW 0 ; total size of 1 buf (incl IPX hdr) +FirstPacketBuf DD 0 ; ptr to 1st packet buffer +CurIndex DW 0 ; current packet/flag index +CurPacketBuf DD 0 ; ptr to current packet buf +FuncOffset DW StartLabel ; offset of our routine + +;--------------------------------------------------------------------------- +; These values are for preventing re-entrancy; they're currently not used. +;--------------------------------------------------------------------------- +Semaphore DB 0 ; prevents re-entrancy +ReEntrantCount DW 0 ; times we've been called re-entrantly + +;--------------------------------------------------------------------------- +; Local stack space +;--------------------------------------------------------------------------- +StackPtr DW 0 ; saved copy of stack ptr +StackSeg DW 0 ; saved copy of stack seg +StackPtr_int DW 0 ; our internal stack ptr +StackSeg_int DW 0 ; our internal stack seg +StackCheck DW 1234h ; check for stack overflow + DW 256 DUP (0) ; stack storage space +StackSpace DW 0 ; label for our stack space + +;--------------------------------------------------------------------------- +; These bytes mark the end of the real-mode data area +;--------------------------------------------------------------------------- +Marker2 DW 2222h ; placeholder to find data end + + +;*************************************************************************** +;* IPXHandler -- IPX callback routine * +;* * +;* This routine is assembled as a stand-alone executable, then loaded * +;* into low DOS memory by a protected-mode application. * +;* * +;* INPUT: * +;* none. * +;* * +;* OUTPUT: * +;* none. * +;* * +;* WARNINGS: * +;* none. * +;* * +;* HISTORY: * +;* 04/07/1995 BRR : Created. * +;*=========================================================================* + label StartLabel + PROC IPXHandler C FAR USES + + ;................................................................... + ; Turn off interrupts; make sure memory copies go forward + ;................................................................... + pushf + cli + cld + + ;................................................................... + ; Set up segment registers to point DS to CS + ;................................................................... + push ds + push ax + mov ax,cs + mov ds,ax + + ;................................................................... + ; Set up our local stack; save SS & SP first. + ;................................................................... + mov [StackSeg],ss + mov [StackPtr],sp + mov [StackPtr_int], OFFSET StackSpace + mov [StackSeg_int], SEG StackSpace + lss sp, [DWORD PTR StackPtr_int] + + + ;................................................................... + ; Save all registers + ;................................................................... + pushad + push es + + ;................................................................... + ; If we've been called re-entrantly, just exit + ;................................................................... + cmp [Semaphore],0 + jz ??Start_Handler + add [ReEntrantCount],1 + jmp ??Exit_Handler + +??Start_Handler: + ;................................................................... + ; Set our semaphore + ;................................................................... + mov [Semaphore],1 + + ;------------------------------------------------------------------- + ; Set 'CurIndex' to the index of the next-available receive buffer, + ; and 'CurPacketBuf to the next-available packet buffer + ;------------------------------------------------------------------- + ;................................................................... + ; Get 'CurIndex' & increment it. Wrap to 0 if we reach 'NumBufs' + ; Since I'm treating 'CurPacketBuf' as a long integer (and not as + ; a segment:offset), the entire data area can't be larger than 64K. + ;................................................................... + mov dx,[CurIndex] ; DX = CurIndex + mov eax,[CurPacketBuf] ; EAX = current packet buffer addr + inc dx ; DX = next index + add ax,[PacketSize] ; EAX = next buffer ptr + cmp dx,[NumBufs] ; see if DX is past # buffers + jb ??Get_Flag + mov dx,0 ; wrap to 1st index + mov eax,[FirstPacketBuf] ; wrap to 1st packet buffer + +??Get_Flag: + ;................................................................... + ; Get the next buffer-in-use flag; if it's 0, load [CurIndex] with + ; the value of SI (the next index). If it's 1, skip the updating of + ; the index, flag & buffer ptr. + ; DX = new CurIndex + ; EAX = new CurPacketBuf + ;................................................................... + les di,[BufferFlags] ; ES:DI = BufferFlags address + mov bx,di ; BX = DI + new CurIndex + add bx,dx + + cmp [BYTE PTR es:bx],0 ; compare next flag to 0 (avail) + jne ??Set_ECB ; if not avail, skip setting new values + + ;................................................................... + ; The next buffer is available; so, set this buffer's In-Use flag + ; to 1, and move on to the next buffer. Do not set this buffer's + ; flag to 1 until we move on to the next buffer, to prevent the + ; application from reading the currently-in-use packet buffer. + ; DX = new CurIndex + ; EAX = new CurPacketBuf + ; ES:DI = BufferFlags address + ;................................................................... + mov bx,di ; BX = DI + old CurIndex + add bx,[CurIndex] + mov [BYTE PTR es:bx],1 ; set old BufferFlags value to in-use + + mov [CurIndex],dx ; save new index + mov [CurPacketBuf],eax ; save new packet address + + ;------------------------------------------------------------------- + ; Set up the Event Control Block to tell IPX to start listening. + ; The following entries are filled in by the app, and should be left + ; alone: + ; - EventServiceRoutine + ; - SocketNumber + ; The rest should be re-initialized. Note that EBX is now pointing + ; to an unavailable buffer if the next buffer was already in use; + ; so it must be reloaded with the correct buffer address from + ; [CurPacketBuf]. + ;------------------------------------------------------------------- +??Set_ECB: + mov [ECB_LinkAddress],0 ; default + mov [ECB_InUse],0 ; default + mov [ECB_CompletionCode],0 ; default + mov [ECB_ConnectionID],0 ; default + mov [ECB_RestOfWorkspace],0 ; default + mov [ECB_DriverWorkSpace],0 ; default + mov [ECB_ImmediateAddress],0 ; default + mov [ECB_PacketCount],2 ; use 2 data areas + mov ebx,[CurPacketBuf] ; get current buffer address + mov [ECB_HeaderAddress],ebx ; set header address + mov [ECB_HeaderLength],30 ; size of IPX header + add ebx,30 ; point to past the header + mov [ECB_PacketAddress],ebx ; set packet data address + mov ax,[PacketSize] ; get size of one buffer + sub ax,30 ; remove size of IPX header + mov [ECB_PacketLength],ax ; set size of packet data + + ;------------------------------------------------------------------- + ; Clear the IPX header for this packet + ;------------------------------------------------------------------- + les di,[ECB_HeaderAddress] ; ES:DI = IPX Listen Header + mov cx,30 ; (30 bytes = size of header) + mov al,0 + rep stosb ; clear to 0's + + ;------------------------------------------------------------------- + ; Command IPX to start listening again. + ;------------------------------------------------------------------- + mov bx,4 ; IPX code for Listen + mov ax,ds ; ES = segment of ListenECB + mov es,ax + mov ax,OFFSET ECB_LinkAddress + mov si,ax ; ES:SI = address of ECB + int 07ah ; call IPX interrupt + + ;................................................................... + ; Clear our semaphore + ;................................................................... + mov [Semaphore],0 + +??Exit_Handler: + ;................................................................... + ; Pop values from our local stack + ;................................................................... + pop es + popad + + ;................................................................... + ; Check our stack-check value; if the stack has overflowed, generate + ; a debugger break. + ;................................................................... + cmp [StackCheck],1234h + je ??Restore_Stack + int 3 + + ;................................................................... + ; Restore the stack to its previous value + ;................................................................... +??Restore_Stack: + lss sp, [DWORD PTR StackPtr] + + ;................................................................... + ; Pop the rest of the registers + ;................................................................... + pop ax + pop ds + + popf + + ret + +ENDP IPXHandler + +END + +;************************** End of handler.asm ***************************** diff --git a/JAP.BAT b/JAP.BAT new file mode 100644 index 0000000..0700cef --- /dev/null +++ b/JAP.BAT @@ -0,0 +1,5 @@ +@echo off +pushd +cd ..\run +conquer -CDf:\projects\c&c\cdjapan\cd1;f:\projects\c&c\cdjapan\cd1\install %1 %2 %3 %4 %5 +popd diff --git a/JSHELL.CPP b/JSHELL.CPP new file mode 100644 index 0000000..91cdb0d --- /dev/null +++ b/JSHELL.CPP @@ -0,0 +1,429 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\jshell.cpv 2.18 16 Oct 1995 16:51:12 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : JSHELL.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 2, 1994 * + * * + * Last Update : May 11, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Build_Translucent_Table -- Creates a translucent control table. * + * Translucent_Table_Size -- Determines the size of a translucent table. * + * Conquer_Build_Translucent_Table -- Builds fading table for shadow colors only. * + * Load_Alloc_Data -- Allocates a buffer and loads the file into it. * + * Load_Uncompress -- Loads and uncompresses data to a buffer. * + * Fatal -- General purpose fatal error handler. * + * Set_Window -- Sets the window dimensions to that specified. * + * Small_Icon -- Create a small icon from a big one. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "wwfile.h" + + +/*********************************************************************************************** + * Small_Icon -- Create a small icon from a big one. * + * * + * This routine will extract the specified icon from the icon data file and convert that * + * incon into a small (3x3) representation. Typicall use of this mini-icon is for the radar * + * map. * + * * + * INPUT: iconptr -- Pointer to the icon data file. * + * * + * iconnum -- The embedded icon number to convert into a small image. * + * * + * OUTPUT: Returns with a pointer to the small icon imagery. This is exactly 9 bytes long. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/11/1995 JLB : Created. * + *=============================================================================================*/ +void * Small_Icon(void const * iconptr, int iconnum) +{ + static unsigned char _icon[9]; + IControl_Type const * iptr = (IControl_Type const *)iconptr; + unsigned char * data; + + if (iconptr) { + iconnum = iptr->Map[iconnum]; + data = &iptr->Icons[iconnum*(24*24)]; + + for (int index = 0; index < 9; index++) { + int _offsets[9] = { + 4+4*24, + 12+4*24, + 20+4*24, + 4+12*24, + 12+12*24, + 20+12*24, + 4+20*24, + 12+20*24, + 20+20*24 + }; + _icon[index] = data[_offsets[index]]; + } + } + + return(_icon); +} + + +/*********************************************************************************************** + * Set_Window -- Sets the window dimensions to that specified. * + * * + * Use this routine to set the windows dimensions to the coordinates and dimensions * + * specified. * + * * + * INPUT: x -- Window X pixel position. * + * * + * y -- Window Y pixel position. * + * * + * w -- Window width in pixels. * + * * + * h -- Window height in pixels. * + * * + * OUTPUT: none * + * * + * WARNINGS: The X and width values are truncated to an even 8 pixel boundary. This is * + * the same as stripping off the lower 3 bits. * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +void Set_Window(int window, int x, int y, int w, int h) +{ + WindowList[window][WINDOWWIDTH] = w >> 3; + WindowList[window][WINDOWHEIGHT] = h; + WindowList[window][WINDOWX] = x >> 3; + WindowList[window][WINDOWY] = y; +} + + +/*********************************************************************************************** + * Fatal -- General purpose fatal error handler. * + * * + * This is a very simple general purpose fatal error handler. It goes directly to text * + * mode, prints the error, and then aborts with a failure code. * + * * + * INPUT: message -- The text message to display. * + * * + * ... -- Any optional parameters that are used in formatting the message. * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine never returns. The game exits immediately. * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +void Fatal(char const *message, ...) +{ + va_list va; + + va_start(va, message); + Prog_End(); + vfprintf(stderr, message, va); + Mono_Printf(message); + exit(EXIT_FAILURE); +} + + +#ifdef NEVER +void File_Fatal(char const *message) +{ + Prog_End(); + perror(message); + exit(EXIT_FAILURE); +} +#endif + + + +/*********************************************************************************************** + * Load_Uncompress -- Loads and uncompresses data to a buffer. * + * * + * This is the C++ counterpart to the Load_Uncompress function. It will load the file * + * specified into the graphic buffer indicated and uncompress it. * + * * + * INPUT: file -- The file to load and uncompress. * + * * + * uncomp_buff -- The graphic buffer that initial loading will use. * + * * + * dest_buff -- The buffer that will hold the uncompressed data. * + * * + * reserved_data -- This is an optional pointer to a buffer that will hold any * + * reserved data the compressed file may contain. This is * + * typically a palette. * + * * + * OUTPUT: Returns with the size of the uncompressed data in the destination buffer. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +long Load_Uncompress(FileClass &file, BuffType &uncomp_buff, BuffType &dest_buff, void *reserved_data) +{ + unsigned short size; + void *sptr = uncomp_buff.Get_Buffer(); + void *dptr = dest_buff.Get_Buffer(); + int opened = false; + CompHeaderType header; + + /* + ** The file must be opened in order to be read from. If the file + ** isn't opened, then open it. Record this fact so that it can be + ** restored to its closed state at the end. + */ + if (!file.Is_Open()) { + if (!file.Open()) { + return(0); + } + opened = true; + } + + /* + ** Read in the size of the file (supposedly). + */ + file.Read(&size, sizeof(size)); + + /* + ** Read in the header block. This block contains the compression type + ** and skip data (among other things). + */ + file.Read(&header, sizeof(header)); + size -= sizeof(header); + + /* + ** If there are skip bytes then they must be processed. Either read + ** them into the buffer provided or skip past them. No check is made + ** to ensure that the reserved data buffer is big enough (watch out!). + */ + if (header.Skip) { + size -= header.Skip; + if (reserved_data) { + file.Read(reserved_data, header.Skip); + } else { + file.Seek(header.Skip, SEEK_CUR); + } + header.Skip = 0; + } + + /* + ** Determine where is the proper place to load the data. If both buffers + ** specified are identical, then the data should be loaded at the end of + ** the buffer and decompressed at the beginning. + */ + if (uncomp_buff.Get_Buffer() == dest_buff.Get_Buffer()) { + sptr = Add_Long_To_Pointer(sptr, uncomp_buff.Get_Size()-(size+sizeof(header))); + } + + /* + ** Read in the bulk of the data. + */ + Mem_Copy(&header, sptr, sizeof(header)); + file.Read(Add_Long_To_Pointer(sptr, sizeof(header)), size); + + /* + ** Decompress the data. + */ + size = (unsigned int) Uncompress_Data(sptr, dptr); + + /* + ** Close the file if necessary. + */ + if (opened) { + file.Close(); + } + return((long)size); +} + + +/*********************************************************************************************** + * Load_Alloc_Data -- Allocates a buffer and loads the file into it. * + * * + * This is the C++ replacement for the Load_Alloc_Data function. It will allocate the * + * memory big enough to hold the file and then read the file into it. * + * * + * INPUT: file -- The file to read. * + * * + * mem -- The memory system to use for allocation. * + * * + * OUTPUT: Returns with a pointer to the allocated and filled memory block. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +void * Load_Alloc_Data(FileClass &file) +{ + void *ptr = 0; + long size = file.Size(); + + ptr = new char [size]; + if (ptr) { + file.Read(ptr, size); + } + return(ptr); +} + + +/*********************************************************************************************** + * Translucent_Table_Size -- Determines the size of a translucent table. * + * * + * Use this routine to determine how big the translucent table needs * + * to be given the specified number of colors. This value is typically * + * used when allocating the buffer for the translucent table. * + * * + * INPUT: count -- The number of colors that are translucent. * + * * + * OUTPUT: Returns the size of the translucent table. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/02/1994 JLB : Created. * + *=============================================================================================*/ +long Translucent_Table_Size(int count) +{ + return(256L + (256L * count)); +} + + +/*********************************************************************************************** + * Build_Translucent_Table -- Creates a translucent control table. * + * * + * The table created by this routine is used by Draw_Shape (GHOST) to * + * achieve a translucent affect. The original color of the shape will * + * show through. This differs from the fading effect, since that * + * affect only alters the background color toward a single destination * + * color. * + * * + * INPUT: palette -- Pointer to the control palette. * + * * + * control -- Pointer to array of structures that control how * + * the translucent table will be built. * + * * + * count -- The number of entries in the control array. * + * * + * buffer -- Pointer to buffer to place the translucent table. * + * If NULL is passed in, then the buffer will be * + * allocated. * + * * + * OUTPUT: Returns with pointer to the translucent table. * + * * + * WARNINGS: This routine is exceedingly slow. Use sparingly. * + * * + * HISTORY: * + * 04/02/1994 JLB : Created. * + *=============================================================================================*/ +void *Build_Translucent_Table(void const *palette, TLucentType const *control, int count, void *buffer) +{ + unsigned char const *table; // Remap table pointer. + int index; // Working color index. + + if (count && control && palette) { + if (!buffer) { + buffer = new char [Translucent_Table_Size(count)]; + } + + if (buffer) { + memset(buffer, -1, 256); + table = (unsigned char*)Add_Long_To_Pointer((void*)buffer, 256); + + /* + ** Build the individual remap tables for each translucent color. + */ + for (index = 0; index < count; index++) { + ((unsigned char*)buffer)[control[index].SourceColor] = index; + Build_Fading_Table(palette, (void*)table, control[index].DestColor, control[index].Fading); + table = (unsigned char*)Add_Long_To_Pointer((void*)table, 256); + } + } + } + return(buffer); +} + + +/*********************************************************************************************** + * Conquer_Build_Translucent_Table -- Builds fading table for shadow colors only. * + * * + * This routine will build a translucent (fading) table to remap colors into the shadow * + * color region of the palette. Shadow colors are not affected by this translucent table. * + * This means that a shape can be overlapped any number of times and the imagery will * + * remain deterministic (and constant). * + * * + * INPUT: palette -- Pointer to the palette to base the translucent process on. * + * * + * control -- Pointer to special control structure that specifies the * + * target color, and percentage of fade. * + * * + * count -- The number of colors to be remapped (entries in the control array). * + * * + * buffer -- Pointer to the staging buffer that will hold the translucent table * + * data. If this parameter is NULL, then an appropriate sized table * + * will be allocated. * + * * + * OUTPUT: Returns with a pointer to the translucent table data. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/27/1994 JLB : Created. * + *=============================================================================================*/ +void *Conquer_Build_Translucent_Table(void const *palette, TLucentType const *control, int count, void *buffer) +{ + unsigned char const *table; // Remap table pointer. + int index; // Working color index. + + if (count && control && palette) { + if (!buffer) { + buffer = new char [Translucent_Table_Size(count)]; + } + + if (buffer) { + memset(buffer, -1, 256); + table = (unsigned char*)Add_Long_To_Pointer((void*)buffer, 256); + + /* + ** Build the individual remap tables for each translucent color. + */ + for (index = 0; index < count; index++) { + ((unsigned char*)buffer)[control[index].SourceColor] = index; + Conquer_Build_Fading_Table(palette, (void*)table, control[index].DestColor, control[index].Fading); + table = (unsigned char*)Add_Long_To_Pointer((void*)table, 256); + } + } + } + return(buffer); +} + + diff --git a/JSHELL.H b/JSHELL.H new file mode 100644 index 0000000..275f844 --- /dev/null +++ b/JSHELL.H @@ -0,0 +1,229 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\jshell.h_v 2.16 16 Oct 1995 16:45:06 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : JSHELL.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 03/13/95 * + * * + * Last Update : March 13, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef JSHELL_H +#define JSHELL_H + +/* +** Interface class to the keyboard. This insulates the game from library vagaries. Most +** notable being the return values are declared as "int" in the library whereas C&C +** expects it to be of KeyNumType. +*/ +class Keyboard +{ + public: + static KeyNumType Get(void) {return (KeyNumType)Get_Key_Num();}; + static KeyNumType Check(void) {return (KeyNumType)Check_Key_Num();}; + static KeyASCIIType To_ASCII(KeyNumType key) {return (KeyASCIIType)KN_To_KA(key);}; + static void Clear(void) {Clear_KeyBuffer();}; + static void Stuff(KeyNumType key) {Stuff_Key_Num(key);}; + static int Down(KeyNumType key) {return Key_Down(key);}; + static int Mouse_X(void) {return Get_Mouse_X();}; + static int Mouse_Y(void) {return Get_Mouse_Y();}; +}; + + +#ifdef NEVER +inline void * operator delete(void * data) +{ + Free(data); +} + +inline void * operator delete[] (void * data) +{ + Free(data); +} +#endif + + +/* +** These templates allow enumeration types to have simple bitwise +** arithmatic performed. The operators must be instatiated for the +** enumerated types desired. +*/ +template inline T operator ++(T & a) +{ + a = (T)((int)a + (int)1); + return(a); +} +template inline T operator ++(T & a, int) +{ + T aa = a; + a = (T)((int)a + (int)1); + return(aa); +} +template inline T operator --(T & a) +{ + a = (T)((int)a - (int)1); + return(a); +} +template inline T operator --(T & a, int) +{ + T aa = a; + a = (T)((int)a - (int)1); + return(aa); +} +template inline T operator |(T t1, T t2) +{ + return((T)((int)t1 | (int)t2)); +} +template inline T operator &(T t1, T t2) +{ + return((T)((int)t1 & (int)t2)); +} +template inline T operator ~(T t1) +{ + return((T)(~(int)t1)); +} + + +/* +** The shape flags are likely to be "or"ed together and other such bitwise +** manipulations. These instatiated operator templates allow this. +*/ +inline ShapeFlags_Type operator |(ShapeFlags_Type, ShapeFlags_Type); +inline ShapeFlags_Type operator &(ShapeFlags_Type, ShapeFlags_Type); +inline ShapeFlags_Type operator ~(ShapeFlags_Type); + + +void Set_Bit(void * array, int bit, int value); +#pragma aux Set_Bit parm [esi] [ecx] [eax] \ + modify [esi ebx] = \ + "mov ebx,ecx" \ + "shr ebx,5" \ + "and ecx,01Fh" \ + "btr [esi+ebx*4],ecx" \ + "or eax,eax" \ + "jz ok" \ + "bts [esi+ebx*4],ecx" \ + "ok:" + +int Get_Bit(void const * array, int bit); +#pragma aux Get_Bit parm [esi] [eax] \ + modify [esi ebx] \ + value [eax] = \ + "mov ebx,eax" \ + "shr ebx,5" \ + "and eax,01Fh" \ + "bt [esi+ebx*4],eax" \ + "setc al" + +int First_True_Bit(void const * array); +#pragma aux First_True_Bit parm [esi] \ + modify [esi ebx] \ + value [eax] = \ + "mov eax,-32" \ + "again:" \ + "add eax,32" \ + "mov ebx,[esi]" \ + "add esi,4" \ + "bsf ebx,ebx" \ + "jz again" \ + "add eax,ebx" + +int First_False_Bit(void const * array); +#pragma aux First_False_Bit parm [esi] \ + modify [esi ebx] \ + value [eax] = \ + "mov eax,-32" \ + "again:" \ + "add eax,32" \ + "mov ebx,[esi]" \ + "not ebx" \ + "add esi,4" \ + "bsf ebx,ebx" \ + "jz again" \ + "add eax,ebx" + +extern int Bound(int original, int min, int max); +#pragma aux Bound parm [eax] [ebx] [ecx] \ + modify [eax] \ + value [eax] = \ + "cmp ebx,ecx" \ + "jl okorder" \ + "xchg ebx,ecx" \ + "okorder: cmp eax,ebx" \ + "jg okmin" \ + "mov eax,ebx" \ + "okmin: cmp eax,ecx" \ + "jl okmax" \ + "mov eax,ecx" \ + "okmax:" + +#ifdef NEVER +extern unsigned Bound(unsigned original, unsigned min, unsigned max); +#pragma aux Bound parm [eax] [ebx] [ecx] \ + modify [eax] \ + value [eax] = \ + "cmp ebx,ecx" \ + "jb okorder" \ + "xchg ebx,ecx" \ + "okorder: cmp eax,ebx" \ + "ja okmin" \ + "mov eax,ebx" \ + "okmin: cmp eax,ecx" \ + "jb okmax" \ + "mov eax,ecx" \ + "okmax:" +#endif + + +unsigned Fixed_To_Cardinal(unsigned base, unsigned fixed); +#pragma aux Fixed_To_Cardinal parm [eax] [edx] \ + modify [edx] \ + value [eax] = \ + "mul edx" \ + "add eax,080h" \ + "test eax,0FF000000h" \ + "jz ok" \ + "mov eax,000FFFFFFh" \ + "ok:" \ + "shr eax,8" + + +unsigned Cardinal_To_Fixed(unsigned base, unsigned cardinal); +#pragma aux Cardinal_To_Fixed parm [ebx] [eax] \ + modify [edx] \ + value [eax] = \ + "or ebx,ebx" \ + "jz fini" \ + "shl eax,8" \ + "xor edx,edx" \ + "div ebx" \ + "fini:" + +#endif diff --git a/KEYFBUFF.ASM b/KEYFBUFF.ASM new file mode 100644 index 0000000..7e73b04 --- /dev/null +++ b/KEYFBUFF.ASM @@ -0,0 +1,4854 @@ +; +; Command & Conquer(tm) +; Copyright 2025 Electronic Arts Inc. +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; + +;*************************************************************************** +;** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S ** +;*************************************************************************** +;* * +;* Project Name : Command & Conquer * +;* * +;* File Name : KEYFBUFF.ASM * +;* * +;* Programmer : David R. Dettmer * +;* * +;* Start Date : March 3, 1995 * +;* * +;* Last Update : * +;* * +;*-------------------------------------------------------------------------* +;* Functions: * +;* Buffer_Frame_To_Page -- Copies a linear buffer to a virtual viewport * +;* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * + +;********************** Model & Processor Directives *********************** +IDEAL +P386 +MODEL USE32 FLAT +jumps + +;******************************** Includes ********************************* +INCLUDE "gbuffer.inc" +include "profile.inc" + + +;******************************** Equates ********************************** + +TRUE equ 1 ; Boolean 'true' value +FALSE equ 0 ; Boolean 'false' value + +;*=========================================================================*/ +;* The following are defines used to control what functions are linked * +;* in for Buffer_Frame_To_Page. * +;*=========================================================================*/ +;USE_NORMAL EQU TRUE +;USE_HORZ_REV EQU TRUE +;USE_VERT_REV EQU TRUE +;USE_SCALING EQU TRUE + + +FLAG_NORMAL EQU 0 +FLAG_TRANS EQU 1 +FLAG_GHOST EQU 2 +FLAG_FADING EQU 4 +FLAG_PREDATOR EQU 8 + +FLAG_MASK EQU 0Fh + + +SHAPE_NORMAL EQU 0000h ; Standard shape +;SHAPE_HORZ_REV EQU 0001h ; Flipped horizontally +;SHAPE_VERT_REV EQU 0002h ; Flipped vertically +;SHAPE_SCALING EQU 0004h ; Scaled (WORD scale_x, WORD scale_y) +;SHAPE_VIEWPORT_REL EQU 0010h ; Coords are window-relative +;SHAPE_WIN_REL EQU 0010h ; Coordinates are window relative instead of absolute. +SHAPE_CENTER EQU 0020h ; Coords are based on shape's center pt +SHAPE_TRANS EQU 0040h ; has transparency + +SHAPE_FADING EQU 0100h ; Fading effect (VOID * fading_table, + ; WORD fading_num) +SHAPE_PREDATOR EQU 0200h ; Transparent warping effect +;SHAPE_COMPACT EQU 0400h ; Never use this bit +;SHAPE_PRIORITY EQU 0800h ; Use priority system when drawing +SHAPE_GHOST EQU 1000h ; Shape is drawn ghosted +;SHAPE_SHADOW EQU 2000h +SHAPE_PARTIAL EQU 4000h +;SHAPE_COLOR EQU 8000h ; Remap the shape's colors + ; (VOID * color_table) + + +; +;.......................... Shadow Effect .................................. +; +SHADOW_COL EQU 00FFh ; magic number for shadows + +;......................... Priority System ................................. +; +CLEAR_UNUSED_BITS EQU 0007h ; and with 0000-0111 to clear + ; non-walkable high bit and + ; scaling id bits +NON_WALKABLE_BIT EQU 0080h ; and with 1000-0000 to clear all + ; but non-walkable bit +; +;......................... Predator Effect ................................. +; +;PRED_MASK EQU 0007h ; mask used for predator pixel puts +PRED_MASK EQU 000Eh ; mask used for predator pixel puts + + +;--------------------------------------------------------------------------- +; +; Use a macro to make code a little cleaner. +; The parameter varname is optional. +; Syntax to use macro is : +; WANT equ expression +; USE func [,varname] +; If the 'varname' is defined, a table declaration is created like: +; GLOBAL TableName:DWORD +; Then, the table entry is created: +; If WANT is true, the table entry is created for the given function: +; varname DD func +; If WANT is not TRUE, a Not_Supported entry is put in the table: +; varname DD Not_Supported +; The resulting tables look like: +; +; GLOBAL ExampTable:DWORD +; ExampTable DD routine1 +; DD routine2 +; DD routine3 +; ... +; Thus, each table is an array of function pointers. +; +;--------------------------------------------------------------------------- +MACRO USE func, varname + IF WANT + varname DD func + ELSE + varname DD Not_Supported + ENDIF +ENDM + +; IFNB +; GLOBAL varname:DWORD +; ENDIF + +;--------------------------------------------------------------------------- + + +DATASEG + +;--------------------------------------------------------------------------- +; Predator effect variables +;--------------------------------------------------------------------------- +; make table for negative offset and use the used space for other variables + +BFPredNegTable DW -1, -3, -2, -5, -2, -4, -3, -1 + ; 8 words below calculated + DW 0, 0, 0, 0, 0, 0, 0, 0 ; index ffffff00 + DD 0, 0, 0, 0 ; index ffffff10 +BFPredOffset DD 0, 0, 0, 0 ; index ffffff20 + DD 0, 0, 0, 0 ; index ffffff30 + ; partially faded predator effect value +BFPartialPred DD 0, 0, 0, 0 ; index ffffff40 +BFPartialCount DD 0, 0, 0, 0 ; index ffffff50 + DD 0, 0, 0, 0 ; index ffffff60 + DD 0, 0, 0, 0 ; index ffffff70 + DD 0, 0, 0, 0 ; index ffffff80 + DD 0, 0, 0, 0 ; index ffffff90 + DD 0, 0, 0, 0 ; index ffffffa0 + DD 0, 0, 0, 0 ; index ffffffb0 + DD 0, 0, 0, 0 ; index ffffffc0 + DD 0, 0, 0, 0 ; index ffffffd0 + DD 0, 0, 0, 0 ; index ffffffe0 + DD 0, 0, 0, 0 ; index fffffff0 +BFPredTable DW 1, 3, 2, 5, 2, 3, 4, 1 +;BFPredTable DB 1, 3, 2, 5, 4, 3, 2, 1 + + + + + + + global C BigShapeBufferStart:dword + global C UseBigShapeBuffer:dword + global C TheaterShapeBufferStart:dword + global C IsTheaterShape:dword + global C Single_Line_Trans_Entry:near + global C Next_Line:near + global C MMX_Done:near + global C MMXAvailable:dword + global EndNewShapeJumpTable:byte + global NewShapeJumpTable:dword + + +;********************************************************************************** +; +; Jump tables for new line header system +; +; Each line of shape data now has a header byte which describes the data on the line. +; + +; +; Header byte control bits +; +BLIT_TRANSPARENT =1 +BLIT_GHOST =2 +BLIT_FADING =4 +BLIT_PREDATOR =8 +BLIT_SKIP =16 +BLIT_ALL =BLIT_TRANSPARENT or BLIT_GHOST or BLIT_FADING or BLIT_PREDATOR or BLIT_SKIP + + + struc ShapeHeaderType + + draw_flags dd ? + shape_data dd ? + shape_buffer dd ? + + ends + +; +; Global definitions for routines that draw a single line of a shape +; + global Short_Single_Line_Copy:near + global Single_Line_Trans:near + global Single_Line_Ghost:near + global Single_Line_Ghost_Trans:near + global Single_Line_Fading:near + global Single_Line_Fading_Trans:near + global Single_Line_Ghost_Fading:near + global Single_Line_Ghost_Fading_Trans:near + global Single_Line_Predator:near + global Single_Line_Predator_Trans:near + global Single_Line_Predator_Ghost:near + global Single_Line_Predator_Ghost_Trans:near + global Single_Line_Predator_Fading:near + global Single_Line_Predator_Fading_Trans:near + global Single_Line_Predator_Ghost_Fading:near + global Single_Line_Predator_Ghost_Fading_Trans:near + global Single_Line_Skip:near + + global Single_Line_Single_Fade:near + global Single_Line_Single_Fade_Trans:near + + + +label NewShapeJumpTable dword + +; +; Jumptable for shape line drawing with no flags set +; + + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy +label CriticalFadeRedirections dword + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + +; +; Jumptable for shape line drawing routines with transparent flags set +; + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + +; +; Jumptable for shape line drawing routines with ghost flags set +; + + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Single_Line_Ghost + dd Single_Line_Ghost + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Single_Line_Ghost + dd Single_Line_Ghost + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Single_Line_Ghost + dd Single_Line_Ghost + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Single_Line_Ghost + dd Single_Line_Ghost + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + +; +; Jumptable for shape line drawing routines with ghost and transparent flags set +; + + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Single_Line_Ghost + dd Single_Line_Ghost_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Single_Line_Ghost + dd Single_Line_Ghost_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Single_Line_Ghost + dd Single_Line_Ghost_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Single_Line_Ghost + dd Single_Line_Ghost_Trans + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + + + + +; +; Jumptable for shape line drawing routines with fading flag set +; + + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Single_Line_Single_Fade + dd Single_Line_Single_Fade + dd Single_Line_Fading + dd Single_Line_Fading + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Single_Line_Fading + dd Single_Line_Fading + dd Single_Line_Fading + dd Single_Line_Fading + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + +; +; Jumptable for shape line drawing routines with fading and transparent flags set +; + + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Single_Line_Single_Fade + dd Single_Line_Single_Fade_Trans + dd Single_Line_Fading + dd Single_Line_Fading_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Single_Line_Fading + dd Single_Line_Fading_Trans + dd Single_Line_Fading + dd Single_Line_Fading_Trans + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + +; +; Jumptable for shape line drawing routines with fading and ghost flags set +; + + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Single_Line_Ghost + dd Single_Line_Ghost + dd Single_Line_Single_Fade + dd Single_Line_Single_Fade + dd Single_Line_Ghost_Fading + dd Single_Line_Ghost_Fading + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Single_Line_Ghost + dd Single_Line_Ghost + dd Single_Line_Fading + dd Single_Line_Fading + dd Single_Line_Ghost_Fading + dd Single_Line_Ghost_Fading + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + + +; +; Jumptable for shape line drawing routines with fading, transparent and ghost flags set +; + + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Single_Line_Ghost + dd Single_Line_Ghost_Trans + dd Single_Line_Single_Fade + dd Single_Line_Single_Fade_Trans + dd Single_Line_Ghost_Fading + dd Single_Line_Ghost_Fading_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Single_Line_Ghost + dd Single_Line_Ghost_Trans + dd Single_Line_Fading + dd Single_Line_Fading_Trans + dd Single_Line_Ghost_Fading + dd Single_Line_Ghost_Fading_Trans + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + + + + + +; +; Jumptable for shape line drawing with predator flag set +; + + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Single_Line_Predator + dd Single_Line_Predator + dd Single_Line_Predator + dd Single_Line_Predator + dd Single_Line_Predator + dd Single_Line_Predator + dd Single_Line_Predator + dd Single_Line_Predator + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + +; +; Jumptable for shape line drawing routines with transparent and predator flags set +; + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Single_Line_Predator + dd Single_Line_Predator_Trans + dd Single_Line_Predator + dd Single_Line_Predator_Trans + dd Single_Line_Predator + dd Single_Line_Predator_Trans + dd Single_Line_Predator + dd Single_Line_Predator_Trans + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + +; +; Jumptable for shape line drawing routines with ghost and predator flags set +; + + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Single_Line_Ghost + dd Single_Line_Ghost + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Single_Line_Ghost + dd Single_Line_Ghost + dd Single_Line_Predator + dd Single_Line_Predator + dd Single_Line_Predator_Ghost + dd Single_Line_Predator_Ghost + dd Single_Line_Predator + dd Single_Line_Predator + dd Single_Line_Predator_Ghost + dd Single_Line_Predator_Ghost + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + +; +; Jumptable for shape line drawing routines with ghost and transparent and predator flags set +; + + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Single_Line_Ghost + dd Single_Line_Ghost_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Single_Line_Ghost + dd Single_Line_Ghost_Trans + dd Single_Line_Predator + dd Single_Line_Predator_Trans + dd Single_Line_Predator_Ghost + dd Single_Line_Predator_Ghost_Trans + dd Single_Line_Predator + dd Single_Line_Predator_Trans + dd Single_Line_Predator_Ghost + dd Single_Line_Predator_Ghost_Trans + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + + + + +; +; Jumptable for shape line drawing routines with fading and predator flags set +; + + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Single_Line_Single_Fade + dd Single_Line_Single_Fade + dd Single_Line_Fading + dd Single_Line_Fading + dd Single_Line_Predator + dd Single_Line_Predator + dd Single_Line_Predator + dd Single_Line_Predator + dd Single_Line_Predator_Fading + dd Single_Line_Predator_Fading + dd Single_Line_Predator_Fading + dd Single_Line_Predator_Fading + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + +; +; Jumptable for shape line drawing routines with fading and transparent and predator flags set +; + + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Single_Line_Single_Fade + dd Single_Line_Single_Fade_Trans + dd Single_Line_Fading + dd Single_Line_Fading_Trans + dd Single_Line_Predator + dd Single_Line_Predator_Trans + dd Single_Line_Predator + dd Single_Line_Predator_Trans + dd Single_Line_Predator_Fading + dd Single_Line_Predator_Fading_Trans + dd Single_Line_Predator_Fading + dd Single_Line_Predator_Fading_Trans + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + +; +; Jumptable for shape line drawing routines with fading and ghost and predator flags set +; + + dd Short_Single_Line_Copy + dd Short_Single_Line_Copy + dd Single_Line_Ghost + dd Single_Line_Ghost + dd Single_Line_Single_Fade + dd Single_Line_Single_Fade + dd Single_Line_Ghost_Fading + dd Single_Line_Ghost_Fading + dd Single_Line_Predator + dd Single_Line_Predator + dd Single_Line_Predator_Ghost + dd Single_Line_Predator_Ghost + dd Single_Line_Predator_Fading + dd Single_Line_Predator_Fading + dd Single_Line_Predator_Ghost_Fading + dd Single_Line_Predator_Ghost_Fading + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + + + + + + +; +; Jumptable for shape line drawing routines with all flags set +; + +label AllFlagsJumpTable dword + dd Short_Single_Line_Copy + dd Single_Line_Trans + dd Single_Line_Ghost + dd Single_Line_Ghost_Trans + dd Single_Line_Single_Fade + dd Single_Line_Single_Fade_Trans + dd Single_Line_Ghost_Fading + dd Single_Line_Ghost_Fading_Trans + dd Single_Line_Predator + dd Single_Line_Predator_Trans + dd Single_Line_Predator_Ghost + dd Single_Line_Predator_Ghost_Trans + dd Single_Line_Predator_Fading + dd Single_Line_Predator_Fading_Trans + dd Single_Line_Predator_Ghost_Fading + dd Single_Line_Predator_Ghost_Fading_Trans + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + dd Single_Line_Skip + + +label EndNewShapeJumpTable byte + +CODESEG + +;--------------------------------------------------------------------------- +; Code Segment Tables: +; This code uses the USE macro to set up tables of function addresses. +; The tables have the following format: +; Tables defined are: +; BufferFrameTable +;--------------------------------------------------------------------------- + +WANT equ +USE BF_Copy, BufferFrameTable + +WANT equ +USE BF_Trans + +WANT equ +USE BF_Ghost + +WANT equ +USE BF_Ghost_Trans + +WANT equ +USE BF_Fading + +WANT equ +USE BF_Fading_Trans + +WANT equ +USE BF_Ghost_Fading + +WANT equ +USE BF_Ghost_Fading_Trans + +WANT equ +USE BF_Predator + +WANT equ +USE BF_Predator_Trans + +WANT equ +USE BF_Predator_Ghost + +WANT equ +USE BF_Predator_Ghost_Trans + +WANT equ +USE BF_Predator_Fading + +WANT equ +USE BF_Predator_Fading_Trans + +WANT equ +USE BF_Predator_Ghost_Fading + +WANT equ +USE BF_Predator_Ghost_Fading_Trans + + + + + +;--------------------------------------------------------------------------- + + + + +;********************************************************************************************* +;* Set_Shape_Header -- create the line header bytes for a shape * +;* * +;* INPUT: Shape width * +;* Shape height * +;* ptr to raw shape data * +;* ptr to shape headers * +;* shape flags * +;* ptr to translucency table * +;* IsTranslucent * +;* * +;* OUTPUT: none * +;* * +;* Warnings: * +;* * +;* HISTORY: * +;* 11/29/95 10:09AM ST : Created. * +;*===========================================================================================* + + proc Setup_Shape_Header C near + + ARG pixel_width :DWORD ; width of rectangle to blit + ARG pixel_height :DWORD ; height of rectangle to blit + ARG src :DWORD ; this is a member function + ARG headers :DWORD + ARG flags :DWORD + ARG Translucent :DWORD + ARG IsTranslucent :DWORD + LOCAL trans_count :DWORD + + pushad + + + mov esi,[src] ;ptr to raw shape data + mov edi,[headers] ;ptr to headers we are going to set up + mov eax,[flags] + and eax,SHAPE_TRANS or SHAPE_FADING or SHAPE_PREDATOR or SHAPE_GHOST + mov [(ShapeHeaderType edi).draw_flags],eax ;save old flags in header + add edi,size ShapeHeaderType + mov edx,[pixel_height] ;number of shape lines to scan + +??outer_loop: mov ecx,[pixel_width] ;number of pixels in shape line + xor bl,bl ;flag the we dont know anything about this line yet + mov [trans_count],0 ;we havnt scanned any transparent pixels yet + +; +; Scan one shape line to see what kind of data it contains +; +??inner_loop: xor eax,eax + mov al,[esi] + inc esi + +; +; Check for transparent pixel +; + test al,al + jnz ??not_transp + test [flags],SHAPE_TRANS + jz ??not_transp + or bl,BLIT_TRANSPARENT ;flag that pixel is transparent + inc [trans_count] ;keep count of the number of transparent pixels on the line + jmp ??end_lp + +; +; Check for predator effect on this line +; +??not_transp: test [flags],SHAPE_PREDATOR + jz ??not_pred + or bl,BLIT_PREDATOR + +; +; Check for ghost effects +; +??not_pred: test [flags],SHAPE_GHOST + jz ??not_ghost + push edi + mov edi,[IsTranslucent] + cmp [byte edi+eax],-1 + pop edi + jz ??not_ghost + or bl,BLIT_GHOST + +; +; Check if fading is required +; +??not_ghost: test [flags],SHAPE_FADING + jz ??end_lp + or bl,BLIT_FADING + +??end_lp: dec ecx + jnz ??inner_loop + + +; +; Interpret the info we have collected and decide which routine will be +; used to draw this line +; + xor bh,bh + + test bl,BLIT_TRANSPARENT + jz ??no_transparencies + or bh,BLIT_TRANSPARENT + mov ecx,[pixel_width] + cmp ecx,[trans_count] + jnz ??not_all_trans + +; all pixels in the line were transparent so we dont need to draw it at all + mov bh,BLIT_SKIP + jmp ??got_line_type + +??not_all_trans: +??no_transparencies: + mov al,bl + and al,BLIT_PREDATOR + or bh,al + mov al,bl + and al,BLIT_GHOST + or bh,al + mov al,bl + and al,BLIT_FADING + or bh,al + +; +; Save the line header and do the next line +; +??got_line_type:mov [edi],bh + inc edi + + dec edx + jnz ??outer_loop + + + popad + ret + + endp Setup_Shape_Header + + + + +;************************************************************** +; +; Macro to fetch the header of the next line and jump to the appropriate routine +; + macro next_line + + add edi , [ dest_adjust_width ] ;add in dest modulo + dec edx ;line counter + jz ??real_out ;return + mov ecx,[save_ecx] ;ecx is pixel count + mov eax,[header_pointer] ;ptr to next header byte + mov al,[eax] + inc [header_pointer] + and eax,BLIT_ALL ;Make sure we dont jump to some spurious address + ; if the header is wrong then its better to draw with the wrong + ; shape routine than to die + shl eax,2 + add eax,[ShapeJumpTableAddress] ;get the address to jump to + jmp [dword eax] ;do the jump + + endm + + + + + + +;*************************************************************************** +;* VVC::TOPAGE -- Copies a linear buffer to a virtual viewport * +;* * +;* INPUT: WORD x_pixel - x pixel on viewport to copy from * +;* WORD y_pixel - y pixel on viewport to copy from * +;* WORD pixel_width - the width of copy region * +;* WORD pixel_height - the height of copy region * +;* BYTE * src - buffer to copy from * +;* VVPC * dest - virtual viewport to copy to * +;* * +;* OUTPUT: none * +;* * +;* WARNINGS: Coordinates and dimensions will be adjusted if they exceed * +;* the boundaries. In the event that no adjustment is * +;* possible this routine will abort. If the size of the * +;* region to copy exceeds the size passed in for the buffer * +;* the routine will automatically abort. * +;* * +;* HISTORY: * +;* 06/15/1994 PWG : Created. * +;*=========================================================================* + GLOBAL C Buffer_Frame_To_Page:NEAR + PROC Buffer_Frame_To_Page C near + USES eax,ebx,ecx,edx,esi,edi + + ;*=================================================================== + ;* define the arguements that our function takes. + ;*=================================================================== + ARG x_pixel :DWORD ; x pixel position in source + ARG y_pixel :DWORD ; y pixel position in source + ARG pixel_width :DWORD ; width of rectangle to blit + ARG pixel_height:DWORD ; height of rectangle to blit + ARG src :DWORD ; this is a member function + ARG dest :DWORD ; what are we blitting to + + ARG flags :DWORD ; flags passed + + ;*=================================================================== + ; Define some locals so that we can handle things quickly + ;*=================================================================== + LOCAL IsTranslucent :DWORD ; ptr to the is_translucent table + LOCAL Translucent :DWORD ; ptr to the actual translucent table + LOCAL FadingTable :DWORD ; extracted fading table pointer + LOCAL FadingNum :DWORD ; get the number of times to fade + + LOCAL StashECX :DWORD ; temp variable for ECX register + + LOCAL jflags :DWORD ; flags used to goto correct buff frame routine + LOCAL BufferFrameRout :DWORD ; ptr to the buffer frame routine + + LOCAL jmp_loc :DWORD ; calculated jump location + LOCAL loop_cnt :DWORD + + LOCAL x1_pixel :DWORD + LOCAL y1_pixel :DWORD + LOCAL scr_x :DWORD + LOCAL scr_y :DWORD + LOCAL dest_adjust_width :DWORD + LOCAL scr_adjust_width :DWORD + LOCAL header_pointer :DWORD + LOCAL use_old_draw :DWORD + LOCAL save_ecx :DWORD + LOCAL ShapeJumpTableAddress :DWORD + LOCAL shape_buffer_start :DWORD + + prologue + cmp [ src ] , 0 + jz ??real_out + + ; + ; Save the line attributes pointers and + ; Modify the src pointer to point to the actual image + ; + cmp [UseBigShapeBuffer],0 + jz ??do_args ;just use the old shape drawing system + + mov edi,[src] + mov [header_pointer],edi + + mov eax,[BigShapeBufferStart] + cmp [(ShapeHeaderType edi).shape_buffer],0 + jz ??is_ordinary_shape + mov eax,[TheaterShapeBufferStart] +??is_ordinary_shape: + mov [shape_buffer_start],eax + + mov edi,[(ShapeHeaderType edi).shape_data] + add edi,[shape_buffer_start] + mov [src],edi + mov [use_old_draw],0 + + + ;==================================================================== + ; Pull off optional arguments: + ; EDI is used as an offset from the 'flags' parameter, to point + ; to the optional argument currently being processed. + ;==================================================================== +??do_args: + mov edi , 4 ; optional params start past flags + mov [ jflags ] , 0 ; clear jump flags + +??check_centering: + ;------------------------------------------------------------------- + ; See if we need to center the frame + ;------------------------------------------------------------------- + test [ flags ] , SHAPE_CENTER ; does this need to be centered? + je ??check_trans ; if not the skip over this stuff + + mov eax , [ pixel_width ] + mov ebx , [ pixel_height ] + sar eax , 1 + sar ebx , 1 + sub [ x_pixel ] , eax + sub [ y_pixel ] , ebx + +??check_trans: + test [ flags ] , SHAPE_TRANS + jz ??check_ghost + + or [ jflags ] , FLAG_TRANS + + ;-------------------------------------------------------------------- + ; SHAPE_GHOST: DWORD is_translucent tbl + ;-------------------------------------------------------------------- +??check_ghost: + test [ flags ] , SHAPE_GHOST ; are we ghosting this shape + jz ??check_fading + + mov eax , [ flags + edi ] + or [ jflags ] , FLAG_GHOST + mov [ IsTranslucent ] , eax ; save ptr to is_trans. tbl + add eax , 0100h ; add 256 for first table + add edi , 4 ; next argument + mov [ Translucent ] , eax ; save ptr to translucent tbl + + + +??check_fading: + ;______________________________________________________________________ + ; If this is the first time through for this shape then + ; set up the shape header + ;______________________________________________________________________ + pushad + + cmp [UseBigShapeBuffer],0 + jz ??new_shape + + mov edi,[header_pointer] + cmp [(ShapeHeaderType edi).draw_flags],-1 + jz ??setup_headers + mov eax,[flags] ;Redo the shape headers if this shape was + and eax,SHAPE_TRANS or SHAPE_FADING or SHAPE_PREDATOR or SHAPE_GHOST ;initially set up with different flags + cmp eax,[(ShapeHeaderType edi).draw_flags] + jz ??no_header_setup +??new_shape: + mov [use_old_draw],1 + jmp ??no_header_setup + +??setup_headers: + push [IsTranslucent] + push [Translucent] + push [flags] + push [header_pointer] + push [src] + push [pixel_height] + push [pixel_width] + call Setup_Shape_Header + add esp,7*4 + mov [ShapeJumpTableAddress],offset AllFlagsJumpTable + jmp ??headers_set +??no_header_setup: + + xor eax,eax + test [flags],SHAPE_PREDATOR + jz ??not_shape_predator + or al,BLIT_PREDATOR + +??not_shape_predator: + test [flags],SHAPE_FADING + jz ??not_shape_fading + or al,BLIT_FADING + +??not_shape_fading: + + test [flags],SHAPE_TRANS + jz ??not_shape_transparent + or al,BLIT_TRANSPARENT + +??not_shape_transparent: + + test [flags],SHAPE_GHOST + jz ??not_shape_ghost + or al,BLIT_GHOST + +??not_shape_ghost: + + shl eax,7 + add eax,offset NewShapeJumpTable + mov [ShapeJumpTableAddress],eax + + +??headers_set: + popad + + ;-------------------------------------------------------------------- + ; SHAPE_FADING: DWORD fade_table[256], DWORD fade_count + ;-------------------------------------------------------------------- + test [ flags ] , SHAPE_FADING ; are we fading this shape + jz ??check_predator + + mov eax , [ flags + edi ] + mov [ FadingTable ] , eax ; save address of fading tbl + mov eax , [ flags + edi + 4 ] ; get fade num + or [ jflags ] , FLAG_FADING + and eax , 03fh ; no need for more than 63 + add edi , 8 ; next argument + cmp eax , 0 ; check if it's 0 + jnz ??set_fading ; if not, store fade num + + and [ flags ] , NOT SHAPE_FADING ; otherwise, don't fade + +??set_fading: + mov [ FadingNum ] , eax + + mov ebx,[ShapeJumpTableAddress] + mov [dword ebx+CriticalFadeRedirections-NewShapeJumpTable],offset Single_Line_Single_Fade + mov [dword ebx+CriticalFadeRedirections-NewShapeJumpTable+4],offset Single_Line_Single_Fade_Trans + cmp eax,1 + jz ??single_fade + mov [dword ebx+CriticalFadeRedirections-NewShapeJumpTable],offset Single_Line_Fading + mov [dword ebx+CriticalFadeRedirections-NewShapeJumpTable+4],offset Single_Line_Fading_Trans + +??single_fade: + + ;-------------------------------------------------------------------- + ; SHAPE_PREDATOR: DWORD init_pred_lookup_offset (0-7) + ;-------------------------------------------------------------------- +??check_predator: + test [ flags ] , SHAPE_PREDATOR ; is predator effect on + jz ??check_partial + + mov eax , [ flags + edi ] ; pull the partial value + or [ jflags ] , FLAG_PREDATOR + shl eax , 1 + cmp eax , 0 + jge ??check_range + + neg eax + mov ebx , -1 + and eax , PRED_MASK ; keep entries within bounds + mov bl , al + mov eax , ebx ; will be ffffff00-ffffff07 + jmp ??pred_cont + +??check_range: + and eax , PRED_MASK ; keep entries within bounds + +??pred_cont: + add edi , 4 ; next argument + mov [ BFPredOffset ] , eax + mov [ BFPartialCount ] , 0 ; clear the partial count + mov [ BFPartialPred ] , 100h ; init partial to off + +??pred_neg_init: + mov esi , [ dest ] ; get ptr to dest + mov ebx, 7 * 2 + +??pred_loop: + movzx eax , [ WORD PTR BFPredNegTable + ebx ] + add eax , [ (GraphicViewPort esi) . GVPWidth ] ; add width + add eax , [ (GraphicViewPort esi) . GVPXAdd ] ; add x add + add eax , [ (GraphicViewPort esi) . GVPPitch ] ; extra pitch of DD surface ST - 9/29/95 1:08PM + mov [ WORD PTR BFPredNegTable + 16 + ebx ] , ax + dec ebx + dec ebx + jge ??pred_loop + + ;-------------------------------------------------------------------- + ; SHAPE_PARTIAL: DWORD partial_pred_value (0-255) + ;-------------------------------------------------------------------- +??check_partial: + test [ flags ] , SHAPE_PARTIAL ; is this a partial pred? + jz ??setupfunc + + mov eax , [ flags + edi ] ; pull the partial value + add edi , 4 ; next argument + and eax , 0ffh ; make sure 0-255 + mov [ BFPartialPred ] , eax ; store it off + +??setupfunc: + mov ebx , [ jflags ] ; load flags value + and ebx , FLAG_MASK ; clip high bits + add ebx , ebx ; mult by 4 to get DWORD offset + add ebx , ebx + mov ebx , [ BufferFrameTable + ebx ] ; get table value + mov [ BufferFrameRout ] , ebx ; store it in the function pointer + +; Clip dest Rectangle against source Window boundaries. + + mov [ scr_x ] , 0 + mov [ scr_y ] , 0 + mov esi , [ dest ] ; get ptr to dest + xor ecx , ecx + xor edx , edx + mov edi , [ (GraphicViewPort esi) . GVPWidth ] ; get width into register + mov ebx , [ x_pixel ] + mov eax , [ x_pixel ] + add ebx , [ pixel_width ] + shld ecx , eax , 1 + mov [ x1_pixel ] , ebx + inc edi + shld edx , ebx , 1 + sub eax , edi + sub ebx , edi + shld ecx , eax , 1 + shld edx , ebx , 1 + + mov edi,[ ( GraphicViewPort esi) . GVPHeight ] ; get height into register + mov ebx , [ y_pixel ] + mov eax , [ y_pixel ] + add ebx , [ pixel_height ] + shld ecx , eax , 1 + mov [ y1_pixel ] , ebx + inc edi + shld edx , ebx , 1 + sub eax , edi + sub ebx , edi + shld ecx , eax , 1 + shld edx , ebx , 1 + + xor cl , 5 + xor dl , 5 + mov al , cl + test dl , cl + jnz ??real_out + + or al , dl + jz ??do_blit + + mov [use_old_draw],1 + test cl , 1000b + jz ??dest_left_ok + + mov eax , [ x_pixel ] + neg eax + mov [ x_pixel ] , 0 + mov [ scr_x ] , eax + +??dest_left_ok: + test cl , 0010b + jz ??dest_bottom_ok + + mov eax , [ y_pixel ] + neg eax + mov [ y_pixel ] , 0 + mov [ scr_y ] , eax + +??dest_bottom_ok: + test dl , 0100b + jz ??dest_right_ok + + mov eax , [ (GraphicViewPort esi) . GVPWidth ] ; get width into register + mov [ x1_pixel ] , eax + +??dest_right_ok: + test dl , 0001b + jz ??do_blit + + mov eax , [ (GraphicViewPort esi) . GVPHeight ] ; get width into register + mov [ y1_pixel ] , eax + +??do_blit: + cld + mov eax , [ (GraphicViewPort esi) . GVPXAdd ] + add eax , [ (GraphicViewPort esi) . GVPPitch ] + add eax , [ (GraphicViewPort esi) . GVPWidth ] + mov edi , [ (GraphicViewPort esi) . GVPOffset ] + + mov ecx , eax + mul [ y_pixel ] + add edi , [ x_pixel ] + add edi , eax + + add ecx , [ x_pixel ] + sub ecx , [ x1_pixel ] + mov [ dest_adjust_width ] , ecx + + mov esi , [ src ] + mov eax , [ pixel_width ] + sub eax , [ x1_pixel ] + add eax , [ x_pixel ] + mov [ scr_adjust_width ] , eax + + mov eax , [ scr_y ] + mul [ pixel_width ] + add eax , [ scr_x ] + add esi , eax + +; +; If the shape needs to be clipped then we cant handle it with the new header systen +; so draw it with the old shape drawer +; + cmp [use_old_draw],0 + jnz ??use_old_stuff + + add [header_pointer],size ShapeHeaderType + mov edx,[pixel_height] + mov ecx,[pixel_width] + mov eax,[header_pointer] + mov al,[eax] + mov [save_ecx],ecx + inc [header_pointer] + and eax,BLIT_ALL + shl eax,2 + add eax,[ShapeJumpTableAddress] + jmp [dword eax] + + +??use_old_stuff: + mov edx , [ y1_pixel ] + mov eax , [ x1_pixel ] + + sub edx , [ y_pixel ] + jle ??real_out + + sub eax , [ x_pixel ] + jle ??real_out + + jmp [ BufferFrameRout ] ; buffer frame to viewport routine + +??real_out: + + cmp [MMXAvailable],0 + jz ??no_mmx_cleanup + call MMX_Done + +??no_mmx_cleanup: + epilogue + + ret + + +; ******************************************************************** +; Forward bitblit only +; the inner loop is so efficient that +; the optimal consept no longer apply because +; the optimal byte have to by a number greather than 9 bytes +; ******************************************************************** +global BF_Copy:near + +BF_Copy: + prologue + cmp eax , 10 + jl ??forward_loop_bytes + +??forward_loop_dword: + mov ecx , edi + mov ebx , eax + neg ecx + and ecx , 3 + sub ebx , ecx + rep movsb + mov ecx , ebx + shr ecx , 2 + rep movsd + mov ecx , ebx + and ecx , 3 + rep movsb + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??forward_loop_dword + + ret + +??forward_loop_bytes: + mov ecx , eax + rep movsb + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx ; decrement the height + jnz ??forward_loop_bytes + epilogue + + ret + + +;******************************************************************** +;******************************************************************** + + segment code page public use32 'code' ; Need stricter segment alignment + ; for pentium optimisations + + +; +; Expand the 'next_line' macro so we can jump to it +; +; +Next_Line: next_line + + + +;***************************************************************************** +; Draw a single line with transparent pixels +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Trans: + prologue + +Single_Line_Trans_Entry: + +??slt_mask_map_lp: ; Pentium pipeline usage + ;Pipe Cycles + mov al,[esi] ;U 1 + inc esi ;Vee 1 + + test al,al ;U 1 + jz ??slt_skip ;Vee 1/5 + +??slt_not_trans:mov [edi],al ;u 1 + + inc edi ;vee 1 + dec ecx ;u 1 + + jnz ??slt_mask_map_lp ;vee (maybe) 1 + +??slt_end_line: epilogue + next_line + + align 32 + +??slt_skip: inc edi + dec ecx + jz ??slt_skip_end_line + + mov al,[esi] + inc esi + test al,al + jz ??slt_skip2 + mov [edi],al + inc edi + dec ecx + jnz ??slt_mask_map_lp + + epilogue + next_line + + align 32 + +??slt_skip2: inc edi + dec ecx + jz ??slt_end_line + +; +; If we have hit two transparent pixels in a row then we go into +; the transparent optimised bit +; +??slt_round_again: + rept 64 + mov al,[esi] ; ;pipe 1 + inc esi ;1 ;pipe 2 + test al,al ; ;pipe 1 + jnz ??slt_not_trans;pipe 2 (not pairable in 1) + ;2 + inc edi ; ;pipe 1 + dec ecx ;3 ;pipe 2 + jz ??slt_end_line ;4 ;pipe 1 (not pairable) + endm ; best case is 4 cycles per iteration + jmp ??slt_round_again + + + +??slt_skip_end_line: + epilogue + next_line + + + +;***************************************************************************** +; Draw a single line with no transparent pixels +; +; 11/29/95 10:21AM - ST +; +; We have to align the destination for cards that dont bankswitch correctly +; when you write non-aligned data. +; + align 32 +Long_Single_Line_Copy: + prologue + + rept 3 + test edi,3 + jz ??LSLC_aligned + movsb + dec ecx + endm + +??LSLC_aligned: + mov ebx,ecx + + shr ecx,2 + rep movsd + and ebx,3 + jz ??out + movsb + dec bl + jz ??out + movsb + dec bl + jz ??out + movsb +??out: epilogue + next_line + + + +;***************************************************************************** +; Draw a single short line with no transparent pixels +; +; 11/29/95 10:21AM - ST +; + align 32 +Short_Single_Line_Copy: + prologue + cmp ecx,16 + jge Long_Single_Line_Copy + mov ebx,ecx + rep movsb + mov ecx,ebx + epilogue + next_line + + +;***************************************************************************** +; Skip a line of source that is all transparent +; +; 11/29/95 10:21AM - ST +; + + align 32 +Single_Line_Skip: + prologue + add esi,ecx + add edi,ecx + epilogue + next_line + + + +;***************************************************************************** +; Draw a single line with ghosting +; +; 11/29/95 10:21AM - ST +; + align 32 +Single_Line_Ghost: + + prologue + xor eax,eax +??slg_loop: mov al,[esi] + mov ebx,[IsTranslucent] + inc esi + mov bh,[eax+ebx] + cmp bh,-1 + jz ??slg_store_pixel + + and ebx,0ff00h + mov al,[edi] + add ebx,[Translucent] + mov al,[eax+ebx] + +??slg_store_pixel: + mov [edi],al + + inc edi + dec ecx + jnz ??slg_loop + epilogue + next_line + + + +;***************************************************************************** +; Draw a single line with transparent pixels and ghosting +; +; 11/29/95 10:21AM - ST +; + align 32 +Single_Line_Ghost_Trans: + prologue + xor eax,eax +; cmp ecx,3 +; ja ??slgt4 + +??slgt_loop: mov al,[esi] + inc esi + test al,al + jz ??slgt_transparent + +??slgt_not_transparent: + mov ebx,[IsTranslucent] + mov bh,[eax+ebx] + cmp bh,-1 + jz ??slgt_store_pixel + + and ebx,0ff00h + mov al,[edi] + add ebx,[Translucent] + mov al,[eax+ebx] + +??slgt_store_pixel: + mov [edi],al + inc edi + dec ecx + jnz ??slgt_loop + epilogue + next_line + + + align 32 + +??slgt_transparent: + inc edi ;1 + dec ecx ;2 + jz ??slgt_out ;1 (not pairable) + +??slgt_round_again: + rept 64 + mov al,[esi] ; ;pipe 1 + inc esi ;1 ;pipe 2 + test al,al ; ;pipe 1 + jnz ??slgt_not_transparent ;pipe 2 (not pairable in 1) + ;2 + inc edi ; ;pipe 1 + dec ecx ;3 ;pipe 2 + jz ??slgt_out ;4 ;pipe 1 (not pairable) + endm ; best case is 4 cycles per iteration + jmp ??slgt_round_again + +??slgt_out: epilogue + next_line + + + +; +; Optimised video memory access version +; + align 32 + +??slgt4: push edx + mov edx,[edi] + + rept 4 + local slgt4_store1 + local slgt4_trans1 + mov al,[esi] + inc esi + test al,al + jz slgt4_trans1 + + mov ebx,[IsTranslucent] + mov bh,[eax+ebx] + cmp bh,-1 + jz slgt4_store1 + + and ebx,0ff00h + mov al,dl + add ebx,[Translucent] + mov al,[eax+ebx] + +slgt4_store1: mov dl,al + +slgt4_trans1: ror edx,8 + endm + mov [edi],edx + pop edx + lea edi,[edi+4] + lea ecx,[ecx+0fffffffch] + cmp ecx,3 + ja ??slgt4 + test ecx,ecx + jnz ??slgt_loop + + epilogue + next_line + + + + + + + + + + +;***************************************************************************** +; Draw a single line with fading (colour remapping) +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Fading: + prologue + xor eax,eax + mov ebx,[FadingTable] + push ebp + mov ebp,[FadingNum] + push ebp + +??slf_loop: mov al,[esi] + inc esi + + mov ebp,[esp] + +??slf_fade_loop:mov al,[ebx+eax] + dec ebp + jnz ??slf_fade_loop + + mov [edi],al + inc edi + + dec ecx + jnz ??slf_loop + add esp,4 + pop ebp + epilogue + next_line + + +;***************************************************************************** +; Draw a single line with transparent pixels and fading (colour remapping) +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Fading_Trans: + prologue + xor eax,eax + mov ebx,[FadingTable] + push ebp + mov ebp,[FadingNum] + push ebp + +??slft_loop: mov al,[esi] + inc esi + test al,al + jz ??slft_transparent + + mov ebp,[esp] + +??slft_fade_loop: + mov al,[ebx+eax] + dec ebp + jnz ??slft_fade_loop + + mov [edi],al +??slft_transparent: + inc edi + + dec ecx + jnz ??slft_loop + add esp,4 + pop ebp + epilogue + next_line + + + + + +;***************************************************************************** +; Draw a single line with a single fade level (colour remap) +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Single_Fade: + prologue + xor eax,eax + mov ebx,[FadingTable] + +??slsf_loop: mov al,[esi] + mov al,[ebx+eax] + mov [edi],al + inc esi + inc edi + + dec ecx + jnz ??slsf_loop + epilogue + next_line + + + +;***************************************************************************** +; Draw a single line with transparent pixels and a single fade level (colour remap) +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Single_Fade_Trans: + prologue + xor eax,eax + mov ebx,[FadingTable] + +??slsft_loop: mov al,[esi] + inc esi + test al,al + jz ??slsft_transparent + mov al,[ebx+eax] + mov [edi],al + inc edi + dec ecx + jnz ??slsft_loop + epilogue + next_line + + align 32 + +??slsft_transparent: + inc edi + + dec ecx + jz ??slsft_next_line + mov al,[esi] + inc esi + test al,al + jz ??slsft_transparent + mov al,[ebx+eax] + mov [edi],al + inc edi + dec ecx + jnz ??slsft_loop + +??slsft_next_line: + epilogue + next_line + + + + + +;***************************************************************************** +; Draw a single line with ghosting and fading (colour remapping) +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Ghost_Fading: + + prologue + mov [StashECX],ecx + +??SLGF_loop: xor eax,eax + mov al,[esi] + mov ebx,[IsTranslucent] + mov bh,[eax+ebx] + cmp bh,-1 + jz ??slgf_do_fading + + and ebx,0ff00h + + mov al,[edi] + add ebx,[Translucent] + mov al,[ebx+eax] + +??slgf_do_fading: + mov ebx,[FadingTable] + mov ecx,[FadingNum] + +??slgf_fade_loop: + mov al,[eax+ebx] + dec ecx + jnz ??slgf_fade_loop + + mov [edi],al + inc esi + inc edi + + dec [StashECX] + jnz ??SLGF_loop + epilogue + next_line + + +;***************************************************************************** +; Draw a single line with transparent pixels, ghosting and fading +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Ghost_Fading_Trans: + prologue + mov [StashECX],ecx + xor eax,eax + +; cmp ecx,3 +; ja ??slgft4 + +??SLGFT_loop: mov al,[esi] + inc esi + test al,al + jz ??slgft_trans_pixel + mov ebx,[IsTranslucent] + mov bh,[eax+ebx] + cmp bh,-1 + jz ??slgft_do_fading + + and ebx,0ff00h + + mov al,[edi] + add ebx,[Translucent] + mov al,[ebx+eax] + +??slgft_do_fading: + mov ebx,[FadingTable] + mov ecx,[FadingNum] + +??slgft_fade_loop: + mov al,[eax+ebx] + dec ecx + jnz ??slgft_fade_loop + + mov [edi],al +??slgft_trans_pixel: + inc edi + + dec [StashECX] + jnz ??SLGFT_loop + epilogue + next_line + + + align 32 + +??slgft4: push edx + mov edx,[edi] + + rept 4 + local slgft4_fade + local slgft4_fade_lp + local slgft4_trans + mov al,[esi] + inc esi + test al,al + jz slgft4_trans + mov ebx,[IsTranslucent] + mov bh,[eax+ebx] + cmp bh,-1 + jz slgft4_fade + + and ebx,0ff00h + + mov al,dl + add ebx,[Translucent] + mov al,[ebx+eax] + +slgft4_fade: mov ebx,[FadingTable] + mov ecx,[FadingNum] + +slgft4_fade_lp: mov al,[eax+ebx] + dec ecx + jnz slgft4_fade_lp + + mov dl,al + +slgft4_trans: ror edx,8 + endm + mov [edi],edx + pop edx + lea edi,[edi+4] + sub [StashECX],4 + jz ??slgft4_out + cmp [StashECX],3 + ja ??slgft4 + jmp ??SLGFT_loop + +??slgft4_out: epilogue + next_line + + +;***************************************************************************** +; Draw a single line with predator effect +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Predator: + + prologue + +??slp_loop: mov al,[esi] + + mov ebx,[BFPartialCount] + add ebx,[BFPartialPred] + or bh,bh + jnz ??slp_get_pred + + mov [BFPartialCount] , ebx + jmp ??slp_skip_pixel + +??slp_get_pred: xor bh , bh + mov eax,[BFPredOffset] + mov [BFPartialCount] , ebx + add [BYTE BFPredOffset],2 + mov eax,[DWORD BFPredTable+eax] + and [BYTE BFPredOffset],PRED_MASK + and eax,0ffffh + + mov al,[edi+eax] + mov [edi],al + +??slp_skip_pixel: + inc esi + inc edi + + dec ecx + jnz ??slp_loop + + epilogue + next_line + + + + +;***************************************************************************** +; Draw a single line with transparent pixels and predator effect +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Predator_Trans: + + prologue + +??slpt_loop: mov al,[esi] + inc esi + test al,al + jz ??slpt_skip_pixel + + mov ebx,[BFPartialCount] + add ebx,[BFPartialPred] + or bh,bh + jnz ??slpt_get_pred + + mov [BFPartialCount] , ebx + jmp ??slpt_skip_pixel + +??slpt_get_pred:xor bh , bh + mov eax,[BFPredOffset] + mov [BFPartialCount] , ebx + add [BYTE BFPredOffset],2 + mov eax,[DWORD BFPredTable+eax] + and [BYTE PTR BFPredOffset ] , PRED_MASK + and eax,0ffffh + + mov al,[edi+eax] + mov [edi],al + +??slpt_skip_pixel: + inc edi + + dec ecx + jnz ??slpt_loop + + epilogue + next_line + + +;***************************************************************************** +; Draw a single line with predator and ghosting +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Predator_Ghost: + + prologue + +??slpg_loop: mov al,[esi] + mov ebx,[BFPartialCount] + add ebx,[BFPartialPred] + test bh,bh + jnz ??slpg_get_pred ; is this a predator pixel? + + mov [BFPartialCount],ebx + jmp ??slpg_check_ghost + +??slpg_get_pred: + xor bh,bh + mov eax,[BFPredOffset] + mov [BFPartialCount],ebx + add [BYTE BFPredOffset],2 + mov eax,[DWORD BFPredTable+eax ] + and [BYTE BFPredOffset],PRED_MASK + and eax,0ffffh + mov al,[edi+eax] + +??slpg_check_ghost: + mov ebx,[IsTranslucent] + mov bh,[ebx+eax] + cmp bh,0ffh + je ??slpg_store_pixel + + xor eax,eax + and ebx,0FF00h + + mov al,[edi] + add ebx,[Translucent] + + mov al,[ebx+eax] + +??slpg_store_pixel: + mov [edi],al + inc esi + inc edi + + dec ecx + jnz ??slpg_loop + + epilogue + next_line + + + +;***************************************************************************** +; Draw a single line with transparent pixels, predator and ghosting +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Predator_Ghost_Trans: + prologue + +??slpgt_loop: mov al,[esi] + inc esi + test al,al + jz ??slpgt_transparent + + mov ebx,[BFPartialCount] + add ebx,[BFPartialPred] + test bh,bh + jnz ??slpgt_get_pred ; is this a predator pixel? + + mov [BFPartialCount],ebx + jmp ??slpgt_check_ghost + +??slpgt_get_pred: + xor bh,bh + mov eax,[BFPredOffset] + mov [BFPartialCount],ebx + add [BYTE BFPredOffset],2 + mov eax,[DWORD BFPredTable+eax ] + and [BYTE BFPredOffset],PRED_MASK + and eax,0ffffh + mov al,[edi+eax] + +??slpgt_check_ghost: + mov ebx,[IsTranslucent] + mov bh,[ebx+eax] + cmp bh,0ffh + je ??slpgt_store_pixel + + xor eax,eax + and ebx,0FF00h + + mov al,[edi] + add ebx,[Translucent] + + mov al,[ebx+eax] + +??slpgt_store_pixel: + mov [edi],al +??slpgt_transparent: + inc edi + + dec ecx + jnz ??slpgt_loop + + pop ecx + epilogue + next_line + + +;***************************************************************************** +; Draw a single line with predator and fading +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Predator_Fading: + + prologue + mov [StashECX],ecx + +??slpf_loop: mov al,[esi] + mov ebx,[BFPartialCount] + inc esi + add ebx,[BFPartialPred] + test bh,bh + jnz ??slpf_get_pred + + mov [BFPartialCount],ebx + jmp ??slpf_do_fading + +??slpf_get_pred:xor bh,bh + mov eax,[BFPredOffset] + mov [BFPartialCount],ebx + and [BYTE BFPredOffset],2 + mov eax,[DWORD BFPredTable+eax] + and [BYTE BFPredOffset],PRED_MASK + + and eax,0ffffh + mov al,[eax+edi] + +??slpf_do_fading: + and eax,255 + mov ebx,[FadingTable] + mov ecx,[FadingNum] + +??slpf_fade_loop: + mov al,[eax+ebx] + dec ecx + jnz ??slpf_fade_loop + + mov [edi],al + inc edi + + dec [StashECX] + jnz ??slpf_loop + + epilogue + next_line + + + +;***************************************************************************** +; Draw a single line with transparent pixels, fading and predator +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Predator_Fading_Trans: + prologue + mov [StashECX],ecx + +??slpft_loop: mov al,[esi] + inc esi + test al,al + jz ??slpft_transparent + mov ebx,[BFPartialCount] + add ebx,[BFPartialPred] + test bh,bh + jnz ??slpft_get_pred + + mov [BFPartialCount],ebx + jmp ??slpft_do_fading + +??slpft_get_pred: + xor bh,bh + mov eax,[BFPredOffset] + mov [BFPartialCount],ebx + and [BYTE BFPredOffset],2 + mov eax,[DWORD BFPredTable+eax] + and [BYTE BFPredOffset],PRED_MASK + + and eax,0ffffh + mov al,[eax+edi] + +??slpft_do_fading: + and eax,255 + mov ebx,[FadingTable] + mov ecx,[FadingNum] + +??slpft_fade_loop: + mov al,[eax+ebx] + dec ecx + jnz ??slpft_fade_loop + + mov [edi],al +??slpft_transparent: + inc edi + + dec [StashECX] + jnz ??slpft_loop + + epilogue + next_line + + + +;***************************************************************************** +; Draw a single line with predator, ghosting and fading +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Predator_Ghost_Fading: + + prologue + mov [StashECX],ecx + +??slpgf_loop: mov al,[esi] + mov ebx,[BFPartialCount] + inc esi + add ebx,[BFPartialPred] + test bh , bh + jnz ??slpgf_get_pred ; is this a predator pixel? + + mov [BFPartialCount],ebx + jmp ??slpgf_check_ghost + +??slpgf_get_pred: + xor bh,bh + mov eax,[BFPredOffset] + mov [BFPartialCount],ebx + add [BYTE BFPredOffset],2 + mov eax,[DWORD BFPredTable+eax] + and [BYTE BFPredOffset],PRED_MASK + and eax,0ffffh + + mov al,[edi+eax] + +??slpgf_check_ghost: + and eax,255 + mov ebx,[IsTranslucent] + mov bh,[ebx+eax] + cmp bh,0ffh + je ??slpgf_do_fading + + and ebx , 0FF00h + + mov al,[edi] + add ebx,[Translucent] + + mov al,[ebx+eax] + +??slpgf_do_fading: + xor eax,eax + mov ebx,[FadingTable] + mov ecx,[FadingNum] + +??slpgf_fade_loop: + mov al,[ebx+eax] + dec ecx + jnz ??slpgf_fade_loop + +??slpgf_store_pixel: + mov [edi],al + inc edi + + dec [StashECX] + jnz ??slpgf_loop + + epilogue + next_line + + + +;***************************************************************************** +; Draw a single line with transparent pixels, predator, ghosting and fading +; +; 11/29/95 10:21AM - ST +; + align 32 + +Single_Line_Predator_Ghost_Fading_Trans: + + prologue + mov [StashECX],ecx + +??slpgft_loop: mov al,[esi] + inc esi + test al,al + jz ??slpgft_transparent + + mov ebx,[BFPartialCount] + add ebx,[BFPartialPred] + test bh , bh + jnz ??slpgft_get_pred ; is this a predator pixel? + + mov [BFPartialCount],ebx + jmp ??slpgft_check_ghost + +??slpgft_get_pred: + xor bh,bh + mov eax,[BFPredOffset] + mov [BFPartialCount],ebx + add [BYTE BFPredOffset],2 + mov eax,[DWORD BFPredTable+eax] + and [BYTE BFPredOffset],PRED_MASK + and eax,0ffffh + + mov al,[edi+eax] + +??slpgft_check_ghost: + and eax,255 + mov ebx,[IsTranslucent] + mov bh,[ebx+eax] + cmp bh,0ffh + je ??slpgft_do_fading + + and ebx , 0FF00h + + mov al,[edi] + add ebx,[Translucent] + + mov al,[ebx+eax] + +??slpgft_do_fading: + xor eax,eax + mov ebx,[FadingTable] + mov ecx,[FadingNum] + +??slpgft_fade_loop: + mov al,[ebx+eax] + dec ecx + jnz ??slpgft_fade_loop + +??slpgft_store_pixel: + mov [edi],al +??slpgft_transparent: + inc edi + + dec [StashECX] + jnz ??slpgft_loop + + epilogue + next_line + + + + + ends ;end of strict alignment segment + + codeseg + + + +global BF_Trans:near + +BF_Trans: + + prologue +; calc the code location to skip to 10 bytes per REPT below!!!! + mov ecx , eax + and ecx , 01fh + lea ecx , [ ecx + ecx * 4 ] ; quick multiply by 5 + neg ecx + shr eax , 5 + lea ecx , [ ??trans_reference + ecx * 2 ] ; next multiply by 2 + mov [ loop_cnt ] , eax + mov [ jmp_loc ] , ecx + +??trans_loop: + mov ecx , [ loop_cnt ] + jmp [ jmp_loc ] + +; the following code should NOT be changed without changing the calculation +; above!!!!!! + +??trans_line: + + REPT 32 + local trans_pixel + mov bl , [ esi ] + inc esi + test bl , bl + jz trans_pixel + + mov [ edi ] , bl + + trans_pixel: + inc edi + ENDM + +??trans_reference: + dec ecx + jge ??trans_line + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??trans_loop + epilogue + + ret + +;******************************************************************** +;******************************************************************** + +global BF_Ghost:near +BF_Ghost: + + prologue + mov ebx , eax ; width + + ; NOTE: the below calculation assumes a group of instructions is + ; less than 256 bytes + + ; get length of the 32 groups of instructions + + lea ecx , [ ??ghost_reference - ??ghost_line ] + + shr ebx , 5 ; width / 32 + shr ecx , 5 ; length of instructions / 32 + and eax , 01fh ; mod of width / 32 + mul cl ; calc offset to start of group + neg eax ; inverse of width + mov [ loop_cnt ] , ebx ; save width / 32 + lea ecx , [ ??ghost_reference + eax ] + mov eax , 0 + mov [ jmp_loc ] , ecx + +??ghost_loop: + mov ecx , [ loop_cnt ] + jmp [ jmp_loc ] + +??ghost_line: + + REPT 32 + local store_pixel + mov al , [ esi ] + inc esi + mov ebx , [ IsTranslucent ] ; is it a translucent color? + mov bh , [ BYTE PTR ebx + eax ] + cmp bh , 0ffh + je store_pixel + + and ebx , 0FF00h ; clear all of ebx except bh + ; we have the index to the translation table + ; ((trans_colour * 256) + dest colour) + mov al , [ edi ] ; mov pixel at destination to al + add ebx , [ Translucent ] ; get the ptr to it! + ; Add the (trans_color * 256) of the translation equ. + mov al , [ BYTE PTR ebx + eax ] ; get new pixel in al + + store_pixel: + mov [ edi ] , al + inc edi + + ENDM + +??ghost_reference: + dec ecx + jge ??ghost_line + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??ghost_loop + + epilogue + ret + + +;******************************************************************** +;******************************************************************** + +global BF_Ghost_Trans:near +BF_Ghost_Trans: + + prologue + mov ebx , eax ; width + + ; NOTE: the below calculation assumes a group of instructions is + ; less than 256 bytes + + ; get length of the 32 groups of instructions + + lea ecx , [ ??ghost_t_reference - ??ghost_t_line ] + + shr ebx , 5 ; width / 32 + shr ecx , 5 ; length of instructions / 32 + and eax , 01fh ; mod of width / 32 + mul cl ; calc offset to start of group + neg eax ; inverse of width + mov [ loop_cnt ] , ebx ; save width / 32 + lea ecx , [ ??ghost_t_reference + eax ] + mov eax , 0 + mov [ jmp_loc ] , ecx + +??ghost_t_loop: + mov ecx , [ loop_cnt ] + jmp [ jmp_loc ] + +??ghost_t_line: + + REPT 32 + local transp_pixel + local store_pixel + mov al , [ esi ] + inc esi + test al , al + jz transp_pixel + + mov ebx , [ IsTranslucent ] ; is it a translucent color? + mov bh , [ BYTE PTR ebx + eax ] + cmp bh , 0ffh + je store_pixel + + and ebx , 0FF00h ; clear all of ebx except bh + ; we have the index to the translation table + ; ((trans_colour * 256) + dest colour) + mov al , [ edi ] ; mov pixel at destination to al + add ebx , [ Translucent ] ; get the ptr to it! + ; Add the (trans_color * 256) of the translation equ. + mov al , [ BYTE PTR ebx + eax ] ; get new pixel in al + + store_pixel: + mov [ edi ] , al + + transp_pixel: + inc edi + + ENDM + +??ghost_t_reference: + dec ecx + jge ??ghost_t_line + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??ghost_t_loop + + epilogue + ret + + +;******************************************************************** +;******************************************************************** + +global BF_Fading:near +BF_Fading: + + prologue + mov ebx , eax ; width + + ; NOTE: the below calculation assumes a group of instructions is + ; less than 256 bytes + + ; get length of the 32 groups of instructions + + lea ecx , [ ??fading_reference - ??fading_line ] + + shr ebx , 5 ; width / 32 + shr ecx , 5 ; length of instructions / 32 + and eax , 01fh ; mod of width / 32 + mul cl ; calc offset to start of group + neg eax ; inverse of width + mov [ loop_cnt ] , ebx ; save width / 32 + lea ecx , [ ??fading_reference + eax ] + mov eax , 0 + mov [ jmp_loc ] , ecx + +??fading_loop: + mov ecx , [ loop_cnt ] + mov [ StashECX ] , ecx ; preserve ecx for later + mov ebx , [ FadingTable ] ; run color through fading table + jmp [ jmp_loc ] + +??fading_line_begin: + mov [ StashECX ] , ecx ; preserve ecx for later + +??fading_line: + + REPT 32 + local fade_loop + mov al , [ esi ] + inc esi + mov ecx , [ FadingNum ] + + fade_loop: + mov al, [BYTE PTR ebx + eax] + dec ecx + jnz fade_loop + + mov [ edi ] , al + inc edi + + ENDM + +??fading_reference: + mov ecx , [ StashECX ] ; restore ecx for main draw loop + dec ecx + jge ??fading_line_begin + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??fading_loop + + epilogue + ret + + +;******************************************************************** +;******************************************************************** + +global BF_Fading_Trans:near +BF_Fading_Trans: + + prologue + mov ebx , eax ; width + + ; NOTE: the below calculation assumes a group of instructions is + ; less than 256 bytes + + ; get length of the 32 groups of instructions + + lea ecx , [ ??fading_t_reference - ??fading_t_line ] + + shr ebx , 5 ; width / 32 + shr ecx , 5 ; length of instructions / 32 + and eax , 01fh ; mod of width / 32 + mul cl ; calc offset to start of group + neg eax ; inverse of width + mov [ loop_cnt ] , ebx ; save width / 32 + lea ecx , [ ??fading_t_reference + eax ] + mov eax , 0 + mov [ jmp_loc ] , ecx + +??fading_t_loop: + mov ecx , [ loop_cnt ] + mov [ StashECX ] , ecx ; preserve ecx for later + mov ebx , [ FadingTable ] ; run color through fading table + jmp [ jmp_loc ] + +??fading_t_line_begin: + mov [ StashECX ] , ecx ; preserve ecx for later + +??fading_t_line: + + REPT 32 + local transp_pixel + local fade_loop + mov al , [ esi ] + inc esi + test al , al + jz transp_pixel + + mov ecx , [ FadingNum ] + + fade_loop: + mov al, [BYTE PTR ebx + eax] + dec ecx + jnz fade_loop + + mov [ edi ] , al + + transp_pixel: + inc edi + + ENDM + +??fading_t_reference: + mov ecx , [ StashECX ] ; restore ecx for main draw loop + dec ecx + jge ??fading_t_line_begin + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??fading_t_loop + + epilogue + ret + + +;******************************************************************** +;******************************************************************** + +global BF_Ghost_Fading:near +BF_Ghost_Fading: + + prologue + mov ebx , eax ; width + + ; NOTE: the below calculation assumes a group of instructions is + ; less than 256 bytes + + ; get length of the 32 groups of instructions + + lea ecx , [ ??ghost_f_reference - ??ghost_f_line ] + + shr ebx , 5 ; width / 32 + shr ecx , 5 ; length of instructions / 32 + and eax , 01fh ; mod of width / 32 + mul cl ; calc offset to start of group + neg eax ; inverse of width + mov [ loop_cnt ] , ebx ; save width / 32 + lea ecx , [ ??ghost_f_reference + eax ] + mov eax , 0 + mov [ jmp_loc ] , ecx + +??ghost_f_loop: + mov ecx , [ loop_cnt ] + mov [ StashECX ] , ecx ; preserve ecx for later + jmp [ jmp_loc ] + +??ghost_f_line_begin: + mov [ StashECX ] , ecx ; preserve ecx for later + +??ghost_f_line: + + REPT 32 + local store_pixel + local do_fading + local fade_loop + mov al , [ esi ] + inc esi + mov ebx , [ IsTranslucent ] ; is it a lucent color? + mov bh , [ BYTE PTR ebx + eax ] + cmp bh , 0ffh + je do_fading + + and ebx , 0FF00h ; clear all of ebx except bh + ; we have the index to the lation table + ; ((_colour * 256) + dest colour) + mov al , [ edi ] ; mov pixel at destination to al + add ebx , [ Translucent ] ; get the ptr to it! + ; Add the (_color * 256) of the lation equ. + mov al , [ BYTE PTR ebx + eax ] ; get new pixel in al +; DRD jmp store_pixel + + do_fading: + mov ebx , [ FadingTable ] ; run color through fading table + mov ecx , [ FadingNum ] + + fade_loop: + mov al, [BYTE PTR ebx + eax] + dec ecx + jnz fade_loop + + store_pixel: + mov [ edi ] , al + inc edi + + ENDM + +??ghost_f_reference: + mov ecx , [ StashECX ] ; restore ecx for main draw loop + dec ecx + jge ??ghost_f_line_begin + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??ghost_f_loop + + epilogue + ret + + +;******************************************************************** +;******************************************************************** + +global BF_Ghost_Fading_Trans:near +BF_Ghost_Fading_Trans: + + prologue + mov ebx , eax ; width + + ; NOTE: the below calculation assumes a group of instructions is + ; less than 256 bytes + + ; get length of the 32 groups of instructions + + lea ecx , [ ??ghost_f_t_reference - ??ghost_f_t_line ] + + shr ebx , 5 ; width / 32 + shr ecx , 5 ; length of instructions / 32 + and eax , 01fh ; mod of width / 32 + mul cl ; calc offset to start of group + neg eax ; inverse of width + mov [ loop_cnt ] , ebx ; save width / 32 + lea ecx , [ ??ghost_f_t_reference + eax ] + mov eax , 0 + mov [ jmp_loc ] , ecx + +??ghost_f_t_loop: + mov ecx , [ loop_cnt ] + mov [ StashECX ] , ecx ; preserve ecx for later + jmp [ jmp_loc ] + +??ghost_f_t_line_begin: + mov [ StashECX ] , ecx ; preserve ecx for later + +??ghost_f_t_line: + + REPT 32 + local transp_pixel + local store_pixel + local do_fading + local fade_loop + mov al , [ esi ] + inc esi + test al , al + jz transp_pixel + + mov ebx , [ IsTranslucent ] ; is it a translucent color? + mov bh , [ BYTE PTR ebx + eax ] + cmp bh , 0ffh + je do_fading + + and ebx , 0FF00h ; clear all of ebx except bh + ; we have the index to the translation table + ; ((trans_colour * 256) + dest colour) + mov al , [ edi ] ; mov pixel at destination to al + add ebx , [ Translucent ] ; get the ptr to it! + ; Add the (trans_color * 256) of the translation equ. + mov al , [ BYTE PTR ebx + eax ] ; get new pixel in al +; DRD jmp store_pixel + + do_fading: + mov ebx , [ FadingTable ] ; run color through fading table + mov ecx , [ FadingNum ] + + fade_loop: + mov al, [BYTE PTR ebx + eax] + dec ecx + jnz fade_loop + + store_pixel: + mov [ edi ] , al + + transp_pixel: + inc edi + + ENDM + +??ghost_f_t_reference: + mov ecx , [ StashECX ] ; restore ecx for main draw loop + dec ecx + jge ??ghost_f_t_line_begin + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??ghost_f_t_loop + + epilogue + ret + + +;******************************************************************** +;******************************************************************** + +global BF_Predator:near +BF_Predator: + + prologue + mov ebx , eax ; width + + ; NOTE: the below calculation assumes a group of instructions is + ; less than 256 bytes + + ; get length of the 32 groups of instructions + + lea ecx , [ ??predator_reference - ??predator_line ] + + shr ebx , 5 ; width / 32 + shr ecx , 5 ; length of instructions / 32 + and eax , 01fh ; mod of width / 32 + mul cl ; calc offset to start of group + neg eax ; inverse of width + mov [ loop_cnt ] , ebx ; save width / 32 + lea ecx , [ ??predator_reference + eax ] + mov eax , 0 + mov [ jmp_loc ] , ecx + +??predator_loop: + mov ecx , [ loop_cnt ] + jmp [ jmp_loc ] + +??predator_line: + + REPT 32 + local get_pred + local skip_pixel + mov al , [ esi ] + inc esi + mov ebx , [ BFPartialCount ] + add ebx , [ BFPartialPred ] + or bh , bh + jnz get_pred ; is this a predator pixel? + + mov [ BFPartialCount ] , ebx + jmp skip_pixel + + get_pred: + xor bh , bh + mov eax, [ BFPredOffset ] + mov [ BFPartialCount ] , ebx + add [ BYTE PTR BFPredOffset ] , 2 + movzx eax , [ WORD PTR BFPredTable + eax ] + and [ BYTE PTR BFPredOffset ] , PRED_MASK + ; pick up a color offset a pseudo- + ; random amount from the current + movzx eax , [ BYTE PTR edi + eax ] ; viewport address + +; xor bh , bh +; mov eax , [ BFPredValue ] ; pick up a color offset a pseudo- +; ; random amount from the current +; mov [ BFPartialCount ] , ebx +; mov al , [ edi + eax ] ; viewport address + + mov [ edi ] , al + + skip_pixel: + inc edi + + ENDM + +??predator_reference: + dec ecx + jge ??predator_line + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??predator_loop + + epilogue + ret + + +;******************************************************************** +;******************************************************************** + +global BF_Predator_Trans:near +BF_Predator_Trans: + + prologue + mov ebx , eax ; width + + ; NOTE: the below calculation assumes a group of instructions is + ; less than 256 bytes + + ; get length of the 32 groups of instructions + + lea ecx , [ ??predator_t_reference - ??predator_t_line ] + + shr ebx , 5 ; width / 32 + shr ecx , 5 ; length of instructions / 32 + and eax , 01fh ; mod of width / 32 + mul cl ; calc offset to start of group + neg eax ; inverse of width + mov [ loop_cnt ] , ebx ; save width / 32 + lea ecx , [ ??predator_t_reference + eax ] + mov eax , 0 + mov [ jmp_loc ] , ecx + +??predator_t_loop: + mov ecx , [ loop_cnt ] + jmp [ jmp_loc ] + +??predator_t_line: + + REPT 32 + local trans_pixel + local get_pred + local store_pixel + mov al , [ esi ] + inc esi + test al , al + jz trans_pixel + + mov ebx , [ BFPartialCount ] + add ebx , [ BFPartialPred ] + or bh , bh + jnz get_pred ; is this a predator pixel? + + mov [ BFPartialCount ] , ebx + jmp store_pixel + + get_pred: + xor bh , bh + mov eax, [ BFPredOffset ] + mov [ BFPartialCount ] , ebx + add [ BYTE PTR BFPredOffset ] , 2 + movzx eax , [ WORD PTR BFPredTable + eax ] + and [ BYTE PTR BFPredOffset ] , PRED_MASK + ; pick up a color offset a pseudo- + ; random amount from the current + movzx eax , [ BYTE PTR edi + eax ] ; viewport address + + store_pixel: + mov [ edi ] , al + + trans_pixel: + inc edi + + ENDM + +??predator_t_reference: + dec ecx + jge ??predator_t_line + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??predator_t_loop + + epilogue + ret + + +;******************************************************************** +;******************************************************************** + +global BF_Predator_Ghost:near +BF_Predator_Ghost: + + prologue + mov ebx , eax ; width + + ; NOTE: the below calculation assumes a group of instructions is + ; less than 256 bytes + + ; get length of the 32 groups of instructions + + lea ecx , [ ??predator_g_reference - ??predator_g_line ] + + shr ebx , 5 ; width / 32 + shr ecx , 5 ; length of instructions / 32 + and eax , 01fh ; mod of width / 32 + mul cl ; calc offset to start of group + neg eax ; inverse of width + mov [ loop_cnt ] , ebx ; save width / 32 + lea ecx , [ ??predator_g_reference + eax ] + mov eax , 0 + mov [ jmp_loc ] , ecx + +??predator_g_loop: + mov ecx , [ loop_cnt ] + jmp [ jmp_loc ] + +??predator_g_line: + + REPT 32 + local get_pred + local check_ghost + local store_pixel + mov al , [ esi ] + mov ebx , [ BFPartialCount ] + inc esi + add ebx , [ BFPartialPred ] + or bh , bh + jnz get_pred ; is this a predator pixel? + + mov [ BFPartialCount ] , ebx + jmp check_ghost + + get_pred: + xor bh , bh + mov eax, [ BFPredOffset ] + mov [ BFPartialCount ] , ebx + add [ BYTE PTR BFPredOffset ] , 2 + movzx eax , [ WORD PTR BFPredTable + eax ] + and [ BYTE PTR BFPredOffset ] , PRED_MASK + ; pick up a color offset a pseudo- + ; random amount from the current + movzx eax , [ BYTE PTR edi + eax ] ; viewport address + + check_ghost: + mov ebx , [ IsTranslucent ] ; is it a translucent color? + mov bh , [ BYTE PTR ebx + eax ] + cmp bh , 0ffh + je store_pixel + + and ebx , 0FF00h ; clear all of ebx except bh + ; we have the index to the translation table + ; ((trans_colour * 256) + dest colour) + mov al , [ edi ] ; mov pixel at destination to al + add ebx , [ Translucent ] ; get the ptr to it! + ; Add the (trans_color * 256) of the translation equ. + mov al , [ BYTE PTR ebx + eax ] ; get new pixel in al + + store_pixel: + mov [ edi ] , al + inc edi + + ENDM + +??predator_g_reference: + dec ecx + jge ??predator_g_line + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??predator_g_loop + + epilogue + ret + + +;******************************************************************** +;******************************************************************** + +global BF_Predator_Ghost_Trans:near +BF_Predator_Ghost_Trans: + + prologue + mov ebx , eax ; width + + ; NOTE: the below calculation assumes a group of instructions is + ; less than 256 bytes + + ; get length of the 32 groups of instructions + + lea ecx , [ ??predator_g_t_reference - ??predator_g_t_line ] + + shr ebx , 5 ; width / 32 + shr ecx , 5 ; length of instructions / 32 + and eax , 01fh ; mod of width / 32 + mul cl ; calc offset to start of group + neg eax ; inverse of width + mov [ loop_cnt ] , ebx ; save width / 32 + lea ecx , [ ??predator_g_t_reference + eax ] + mov eax , 0 + mov [ jmp_loc ] , ecx + +??predator_g_t_loop: + mov ecx , [ loop_cnt ] + jmp [ jmp_loc ] + +??predator_g_t_line: + + REPT 32 + local trans_pixel + local get_pred + local check_ghost + local store_pixel + mov al , [ esi ] + inc esi + test al , al + jz trans_pixel + + mov ebx , [ BFPartialCount ] + add ebx , [ BFPartialPred ] + or bh , bh + jnz get_pred ; is this a predator pixel? + + mov [ BFPartialCount ] , ebx + jmp check_ghost + + get_pred: + xor bh , bh + mov eax, [ BFPredOffset ] + mov [ BFPartialCount ] , ebx + add [ BYTE PTR BFPredOffset ] , 2 + movzx eax , [ WORD PTR BFPredTable + eax ] + and [ BYTE PTR BFPredOffset ] , PRED_MASK + ; pick up a color offset a pseudo- + ; random amount from the current + movzx eax , [ BYTE PTR edi + eax ] ; viewport address + + check_ghost: + mov ebx , [ IsTranslucent ] ; is it a translucent color? + mov bh , [ BYTE PTR ebx + eax ] + cmp bh , 0ffh + je store_pixel + + and ebx , 0FF00h ; clear all of ebx except bh + ; we have the index to the translation table + ; ((trans_colour * 256) + dest colour) + mov al , [ edi ] ; mov pixel at destination to al + add ebx , [ Translucent ] ; get the ptr to it! + ; Add the (trans_color * 256) of the translation equ. + mov al , [ BYTE PTR ebx + eax ] ; get new pixel in al + + store_pixel: + mov [ edi ] , al + + trans_pixel: + inc edi + + ENDM + +??predator_g_t_reference: + dec ecx + jge ??predator_g_t_line + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??predator_g_t_loop + + epilogue + ret + + +;******************************************************************** +;******************************************************************** + +global BF_Predator_Fading:near +BF_Predator_Fading: + + prologue + mov ebx , eax ; width + + ; NOTE: the below calculation assumes a group of instructions is + ; less than 256 bytes + + ; get length of the 32 groups of instructions + + lea ecx , [ ??predator_f_reference - ??predator_f_line ] + + shr ebx , 5 ; width / 32 + shr ecx , 5 ; length of instructions / 32 + and eax , 01fh ; mod of width / 32 + mul cl ; calc offset to start of group + neg eax ; inverse of width + mov [ loop_cnt ] , ebx ; save width / 32 + lea ecx , [ ??predator_f_reference + eax ] + mov eax , 0 + mov [ jmp_loc ] , ecx + +??predator_f_loop: + mov ecx , [ loop_cnt ] + mov [ StashECX ] , ecx ; preserve ecx for later + jmp [ jmp_loc ] + +??predator_f_line_begin: + mov [ StashECX ] , ecx ; preserve ecx for later + +??predator_f_line: + + REPT 32 + local get_pred + local do_fading + local fade_loop + mov al , [ esi ] + mov ebx , [ BFPartialCount ] + inc esi + add ebx , [ BFPartialPred ] + or bh , bh + jnz get_pred ; is this a predator pixel? + + mov [ BFPartialCount ] , ebx + jmp do_fading + + get_pred: + xor bh , bh + mov eax, [ BFPredOffset ] + mov [ BFPartialCount ] , ebx + add [ BYTE PTR BFPredOffset ] , 2 + movzx eax , [ WORD PTR BFPredTable + eax ] + and [ BYTE PTR BFPredOffset ] , PRED_MASK + ; pick up a color offset a pseudo- + ; random amount from the current + movzx eax , [ BYTE PTR edi + eax ] ; viewport address + + do_fading: + mov ebx , [ FadingTable ] ; run color through fading table + mov ecx , [ FadingNum ] + + fade_loop: + mov al, [BYTE PTR ebx + eax] + dec ecx + jnz fade_loop + + mov [ edi ] , al + inc edi + + ENDM + +??predator_f_reference: + mov ecx , [ StashECX ] ; restore ecx for main draw loop + dec ecx + jge ??predator_f_line_begin + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??predator_f_loop + + epilogue + ret + + +;******************************************************************** +;******************************************************************** + +global BF_Predator_Fading_Trans:near +BF_Predator_Fading_Trans: + + prologue + mov ebx , eax ; width + + ; NOTE: the below calculation assumes a group of instructions is + ; less than 256 bytes + + ; get length of the 32 groups of instructions + + lea ecx , [ ??predator_f_t_reference - ??predator_f_t_line ] + + shr ebx , 5 ; width / 32 + shr ecx , 5 ; length of instructions / 32 + and eax , 01fh ; mod of width / 32 + mul cl ; calc offset to start of group + neg eax ; inverse of width + mov [ loop_cnt ] , ebx ; save width / 32 + lea ecx , [ ??predator_f_t_reference + eax ] + mov eax , 0 + mov [ jmp_loc ] , ecx + +??predator_f_t_loop: + mov ecx , [ loop_cnt ] + mov [ StashECX ] , ecx ; preserve ecx for later + jmp [ jmp_loc ] + +??predator_f_t_line_begin: + mov [ StashECX ] , ecx ; preserve ecx for later + +??predator_f_t_line: + + REPT 32 + local trans_pixel + local get_pred + local do_fading + local fade_loop + mov al , [ esi ] + inc esi + test al , al + jz trans_pixel + + mov ebx , [ BFPartialCount ] + add ebx , [ BFPartialPred ] + or bh , bh + jnz get_pred ; is this a predator pixel? + + mov [ BFPartialCount ] , ebx + jmp do_fading + + get_pred: + xor bh , bh + mov eax, [ BFPredOffset ] + mov [ BFPartialCount ] , ebx + add [ BYTE PTR BFPredOffset ] , 2 + movzx eax , [ WORD PTR BFPredTable + eax ] + and [ BYTE PTR BFPredOffset ] , PRED_MASK + ; pick up a color offset a pseudo- + ; random amount from the current + movzx eax , [ BYTE PTR edi + eax ] ; viewport address + + do_fading: + mov ebx , [ FadingTable ] ; run color through fading table + mov ecx , [ FadingNum ] + + fade_loop: + mov al, [BYTE PTR ebx + eax] + dec ecx + jnz fade_loop + + mov [ edi ] , al + + trans_pixel: + inc edi + + ENDM + +??predator_f_t_reference: + mov ecx , [ StashECX ] ; restore ecx for main draw loop + dec ecx + jge ??predator_f_t_line_begin + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??predator_f_t_loop + + epilogue + ret + + +;******************************************************************** +;******************************************************************** + +global BF_Predator_Ghost_Fading:near +BF_Predator_Ghost_Fading: + + prologue + mov ebx , eax ; width + + ; NOTE: the below calculation assumes a group of instructions is + ; less than 256 bytes + + ; get length of the 32 groups of instructions + + lea ecx , [ ??predator_g_f_reference - ??predator_g_f_line ] + + shr ebx , 5 ; width / 32 + shr ecx , 5 ; length of instructions / 32 + and eax , 01fh ; mod of width / 32 + mul cl ; calc offset to start of group + neg eax ; inverse of width + mov [ loop_cnt ] , ebx ; save width / 32 + lea ecx , [ ??predator_g_f_reference + eax ] + mov eax , 0 + mov [ jmp_loc ] , ecx + +??predator_g_f_loop: + mov ecx , [ loop_cnt ] + mov [ StashECX ] , ecx ; preserve ecx for later + jmp [ jmp_loc ] + +??predator_g_f_line_begin: + mov [ StashECX ] , ecx ; preserve ecx for later + +??predator_g_f_line: + + REPT 32 + local get_pred + local check_ghost + local store_pixel + local do_fading + local fade_loop + mov al , [ esi ] + mov ebx , [ BFPartialCount ] + inc esi + add ebx , [ BFPartialPred ] + or bh , bh + jnz get_pred ; is this a predator pixel? + + mov [ BFPartialCount ] , ebx + jmp check_ghost + + get_pred: + xor bh , bh + mov eax, [ BFPredOffset ] + mov [ BFPartialCount ] , ebx + add [ BYTE PTR BFPredOffset ] , 2 + movzx eax , [ WORD PTR BFPredTable + eax ] + and [ BYTE PTR BFPredOffset ] , PRED_MASK + ; pick up a color offset a pseudo- + ; random amount from the current + movzx eax , [ BYTE PTR edi + eax ] ; viewport address + + check_ghost: + mov ebx , [ IsTranslucent ] ; is it a translucent color? + mov bh , [ BYTE PTR ebx + eax ] + cmp bh , 0ffh + je do_fading + + and ebx , 0FF00h ; clear all of ebx except bh + ; we have the index to the translation table + ; ((trans_colour * 256) + dest colour) + mov al , [ edi ] ; mov pixel at destination to al + add ebx , [ Translucent ] ; get the ptr to it! + ; Add the (trans_color * 256) of the translation equ. + mov al , [ BYTE PTR ebx + eax ] ; get new pixel in al +; DRD jmp store_pixel + + do_fading: + mov ebx , [ FadingTable ] ; run color through fading table + mov ecx , [ FadingNum ] + + fade_loop: + mov al, [BYTE PTR ebx + eax] + dec ecx + jnz fade_loop + + store_pixel: + mov [ edi ] , al + inc edi + + ENDM + +??predator_g_f_reference: + mov ecx , [ StashECX ] ; restore ecx for main draw loop + dec ecx + jge ??predator_g_f_line_begin + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??predator_g_f_loop + + epilogue + ret + + +;******************************************************************** +;******************************************************************** + +global BF_Predator_Ghost_Fading_Trans:near +BF_Predator_Ghost_Fading_Trans: + + prologue + mov ebx , eax ; width + + ; NOTE: the below calculation assumes a group of instructions is + ; less than 256 bytes + + ; get length of the 32 groups of instructions + + lea ecx , [ ??predator_g_f_t_reference - ??predator_g_f_t_line ] + + shr ebx , 5 ; width / 32 + shr ecx , 5 ; length of instructions / 32 + and eax , 01fh ; mod of width / 32 + mul cl ; calc offset to start of group + neg eax ; inverse of width + mov [ loop_cnt ] , ebx ; save width / 32 + lea ecx , [ ??predator_g_f_t_reference + eax ] + mov eax , 0 + mov [ jmp_loc ] , ecx + +??predator_g_f_t_loop: + mov ecx , [ loop_cnt ] + mov [ StashECX ] , ecx ; preserve ecx for later + jmp [ jmp_loc ] + +??predator_g_f_t_line_begin: + mov [ StashECX ] , ecx ; preserve ecx for later + +??predator_g_f_t_line: + + REPT 32 + local trans_pixel + local get_pred + local check_ghost + local store_pixel + local do_fading + local fade_loop + mov al , [ esi ] + inc esi + test al , al + jz trans_pixel + + mov ebx , [ BFPartialCount ] + add ebx , [ BFPartialPred ] + or bh , bh + jnz get_pred ; is this a predator pixel? + + mov [ BFPartialCount ] , ebx + jmp check_ghost + + get_pred: + xor bh , bh + mov eax, [ BFPredOffset ] + mov [ BFPartialCount ] , ebx + add [ BYTE PTR BFPredOffset ] , 2 + movzx eax , [ WORD PTR BFPredTable + eax ] + and [ BYTE PTR BFPredOffset ] , PRED_MASK + ; pick up a color offset a pseudo- + ; random amount from the current + movzx eax , [ BYTE PTR edi + eax ] ; viewport address + + check_ghost: + mov ebx , [ IsTranslucent ] ; is it a translucent color? + mov bh , [ BYTE PTR ebx + eax ] + cmp bh , 0ffh + je do_fading + + and ebx , 0FF00h ; clear all of ebx except bh + ; we have the index to the translation table + ; ((trans_colour * 256) + dest colour) + mov al , [ edi ] ; mov pixel at destination to al + add ebx , [ Translucent ] ; get the ptr to it! + ; Add the (trans_color * 256) of the translation equ. + mov al , [ BYTE PTR ebx + eax ] ; get new pixel in al +; DRD jmp store_pixel + + do_fading: + mov ebx , [ FadingTable ] ; run color through fading table + mov ecx , [ FadingNum ] + + fade_loop: + mov al, [BYTE PTR ebx + eax] + dec ecx + jnz fade_loop + + store_pixel: + mov [ edi ] , al + + trans_pixel: + inc edi + + ENDM + +??predator_g_f_t_reference: + mov ecx , [ StashECX ] ; restore ecx for main draw loop + dec ecx + jge ??predator_g_f_t_line_begin + + add esi , [ scr_adjust_width ] + add edi , [ dest_adjust_width ] + dec edx + jnz ??predator_g_f_t_loop + + epilogue + ret + + +;******************************************************************** +;******************************************************************** + +Not_Supported: + ret + + ENDP Buffer_Frame_To_Page +END + +;*************************************************************************** +;** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S ** +;*************************************************************************** +;* * +;* Project Name : Westwood Library * +;* * +;* File Name : KEYFBUFF.ASM * +;* * +;* Programmer : Phil W. Gorrow * +;* * +;* Start Date : July 16, 1992 * +;* * +;* Last Update : October 2, 1994 [JLB] * +;* * +;*-------------------------------------------------------------------------* +;* Functions: * +;* BUFFER_FRAME_TO_LOGICPAGE -- * +;* Normal_Draw -- Function that writes a normal pixel line * +;* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * + + IDEAL + P386 +IDEAL_MODE EQU 1 + INCLUDE "wwlib.i" + + ;------------------------------------------------------------------- + ; Extern all the library variables that this module requires + ;------------------------------------------------------------------- + + EXTRN C MaskPage:WORD + EXTRN C BackGroundPage:WORD + + ;------------------------------------------------------------------- + ; Define all the equates that this module requires + ;------------------------------------------------------------------- + +WIN_X EQU 0 ; offset for the x coordinate +WIN_Y EQU 2 ; offset for the y coordinate +WIN_WIDTH EQU 4 ; offset for the window width +WIN_HEIGHT EQU 6 ; offset for the window height +BYTESPERROW EQU 320 ; number of bytes per row + +FLAG_NORMAL EQU 0 ; flag for normal draw + +FLAG_GHOST EQU 1 ; This flag enables the ghost +FLAG_PRIORITY_TRANS EQU 2 ; flag for priority and transparent +FLAG_TRANS EQU 4 ; flag for transparent draw +FLAG_PRIORITY EQU 8 ; flag for priority draw + + ; fx on the above flags + +FLAG_MASK EQU 15 ; used to and of uneeded bits + +SHAPE_NORMAL EQU 0000h ; Standard shape. +;SHAPE_HORZ_REV EQU 0001h ; Flipped horizontally. +;SHAPE_VERT_REV EQU 0002h ; Flipped vertically. +;SHAPE_SCALING EQU 0004h ; Scaled (WORD scale_x, WORD scale_y) + +SHAPE_WIN_REL EQU 0010h ; Coordinates are window relative instead of absolute. +SHAPE_CENTER EQU 0020h ; Coordinates are based on shape's center point. +SHAPE_TRANS EQU 0040h ; has transparency + + +;SHAPE_FADING EQU 0100h ; Fading effect active (VOID * fading_table, WORD fading_num). +;SHAPE_PREDATOR EQU 0200h ; Transparent warping effect. +;SHAPE_COMPACT EQU 0400h ; Never use this bit. +SHAPE_PRIORITY EQU 0800h ; Use priority system when drawing. + +SHAPE_GHOST EQU 1000h ; Transluscent table process. +;SHAPE_SHADOW EQU 2000h ; ???? +;SHAPE_PARTIAL EQU 4000h ; ???? +;SHAPE_COLOR EQU 8000h ; Remap the shape's colors (VOID * color_table). + + +; MBL MOD 12.1.92 + +CLEAR_NON_WALK_BIT_AND_SCALE_BITS EQU 7 ; Makes it one AND per pixel in Priority_Trans display +CLEAR_NON_WALK_BIT EQU 7fh ; and with 0111-1111 to clear non-walkable high bit +CLEAR_SCALE_BITS EQU 87h ; and with 1000-0111 to clear scaling id bits +NON_WALKABLE_BIT EQU 80h ; and with 1000-0000 to clear all but non-walkable bit + +; END MBL MOD + + + CODESEG + + ; 1 = GHOST (all odd entrys are prefixed with Ghost_) + ; 2 = BLAAAH + ; 4 = Trans (prfx) + ; 8 = Prior (prfx) + + +;--------------------------------------------------------------------------- +; Define the table of different line draw types +;--------------------------------------------------------------------------- + +LineTable DW WSA_Normal_Draw ;0 + DW Ghost_Normal_Draw ;1 + DW 0 ;2 + DW 0 ;3 + + DW Transparent_Draw ;4 + DW Ghost_Transparent_Draw ;5 + DW 0 ;6 + DW 0 ;7 + + DW Priority_Draw ;8 + DW Ghost_Priority_Draw ;9 + DW 0 ;10 + DW 0 ;11 + + DW Priority_Transparent_Draw ;12 + DW Ghost_Priority_Transparent_Draw ;13 + DW 0 ;14 + DW 0 ;15 + + + +;*************************************************************************** +;* BUFFER_FRAME_TO_LOGICPAGE -- * +;* * +;* * +;* * +;* INPUT: * +;* * +;* OUTPUT: * +;* * +;* WARNINGS: * +;* * +;* HISTORY: * +;* 07/16/1992 PWG : Created. * +;*=========================================================================* + PUBLIC C Buffer_Frame_To_LogicPage + PROC C Buffer_Frame_To_LogicPage FAR USES ax bx ecx dx ds esi es edi + + ;------------------------------------------------------------------- + ; Define the arguements that our program takes. + ;------------------------------------------------------------------- + + ARG x_pixel:WORD ; x pixel position to draw at + ARG y_pixel:WORD ; y pixel position to draw at + ARG pixel_w:WORD ; pixel width of draw region + ARG pixel_h:WORD ; pixel height of draw region + ARG win:WORD ; window to clip around + ARG flags:WORD ; flags that this routine will take + ARG buffer:DWORD ; pointer to the buffer with data + ARG args:WORD + + ;------------------------------------------------------------------- + ; Define the local variables that our program uses + ;------------------------------------------------------------------- + + LOCAL IsTranslucent:DWORD ; ptr to the is_translucent table + LOCAL Translucent:DWORD ; ptr to the actual translucent table + + LOCAL win_x1:WORD ; clip window left x pixel position + LOCAL win_x2:WORD ; clip window right x pixel position + LOCAL win_y1:WORD ; clip window top y pixel position + LOCAL win_y2:WORD ; clip window bottom y pixel position + LOCAL clipleft:WORD ; number of pixels to clip on left + LOCAL clipright:WORD ; number of pixels to clip on right + LOCAL nextline:WORD ; offset to the next line + LOCAL putmiddle:WORD ; routine to call to put the middle + LOCAL maskpage:WORD ; location of the depth masks + LOCAL background:WORD ; location of the background data + LOCAL jflags:WORD ; location of the background data + + LOCAL priority:BYTE ; the priority level of the back + + push fs + + xor ecx,ecx + + ;-------------------------------------------------------------------- + ; Check to see if we have supplied any GHOST tables. + ;-------------------------------------------------------------------- + push di + + mov di,6 + mov [jflags],0 + +??ghost: + test [flags],SHAPE_GHOST ; are we ghosting this shape + jz short ??no_ghost ; if not then skip and do more + + or [jflags],FLAG_GHOST + + les ax,[DWORD PTR buffer + di] + + ; get the "are we really translucent?" table + mov [WORD PTR IsTranslucent],ax + mov [WORD PTR IsTranslucent + 2],es + add ax,0100h ; add to offset for tables + + ; get the "ok we are translucent!!" table + mov [WORD PTR Translucent],ax + mov [WORD PTR Translucent + 2],es + + add di,4 + +??no_ghost: + + pop di + + ;------------------------------------------------------------------- + ; See if we need to center the frame + ;------------------------------------------------------------------- + test [flags],SHAPE_CENTER ; does this need to be centered? + je short ??no_centering ; if not the skip over this stuff + + mov ax,[pixel_w] + mov bx,[pixel_h] + sar ax,1 + sar bx,1 + sub [x_pixel],ax + sub [y_pixel],bx + +??no_centering: + mov ax,[flags] + and ax,SHAPE_PRIORITY+SHAPE_TRANS + cmp ax,SHAPE_PRIORITY+SHAPE_TRANS + jne short ??test_trans + + or [jflags],FLAG_PRIORITY_TRANS + jmp short ??priority + + ;------------------------------------------------------------------- + ; Get the trans information if we need to get it + ;------------------------------------------------------------------- +??test_trans: + test [flags],SHAPE_TRANS ; does this draw use transparencies? + je short ??test_priority ; if not the skip over this junk + + or [jflags],FLAG_TRANS + +??test_priority: + ;------------------------------------------------------------------- + ; Get the priority information if we need to get it + ;------------------------------------------------------------------- + test [flags],SHAPE_PRIORITY ; does this draw use priorities? + je short ??no_priority ; if not the skip over this junk + + or [jflags],FLAG_PRIORITY + +??priority: + mov ax,[BackGroundPage] ; get the background page from ds + mov [background],ax ; and store it on the stack + mov ax,[MaskPage] ; get the mask page from ds + mov [maskpage],ax ; and store it on the stack + mov ax,[WORD PTR buffer + 4]; get the priority level from args + mov [priority],al ; and store it in a local + + ;------------------------------------------------------------------- + ; Get the draw routine that we are going to draw with + ;------------------------------------------------------------------- +??no_priority: +; mov bx,[flags] ; load in the current flags byte +; and bx,FLAG_MASK ; prevent lockup on bad value + mov bx,[jflags] ; load in the jump table flags + shl bx,1 + mov ax,[WORD PTR LineTable + bx] ; get the offset of the skip table + mov [putmiddle],ax ; store off the new offset + + ;------------------------------------------------------------------- + ; Get a pointer to the logic page to where we will draw our buffer + ;------------------------------------------------------------------- + push [LogicPage] ; push the current logic page + call FAR PTR Get_Page ; get the physical page address + add sp,2 ; pull the parameter from the stack + mov es,dx ; store the address in the dest + + ;-------------------------------------------------------------------- + ; Point DI to the beginning of the window that we need to look at. + ; that way we can access all of the info through di. + ;-------------------------------------------------------------------- + mov si,OFFSET WindowList ; get the offset of the window list + mov cl,4 ; shift 3 times = multiply by 16 + mov ax,[win] ; get the window number we are using + shl ax,cl ; each window is 8 words long + add si,ax ; add that into the offset of window + + ;-------------------------------------------------------------------- + ; Place all the clipping values on the stack so our function will + ; be truly re-entrant and will not need to shadow these values. + ;-------------------------------------------------------------------- + mov cl,3 ; to convert x to pixel mult by 8 + mov ax,[si + WIN_X] ; get the left clip position + shl ax,cl ; convert to a pixel x position + mov [win_x1],ax ; store the left edge of window + mov [win_x2],ax + + mov ax,[si + WIN_WIDTH] ; get the width of the window + shl ax,cl ; convert to a pixel width + add [win_x2],ax ; add to get the right window edge + + mov ax,[si + WIN_Y] ; get the win y coordinate to clip + mov [win_y1],ax ; and save it onto the stack + + add ax,[si + WIN_HEIGHT] ; calculate the bottom win y coord + mov [win_y2],ax ; and save it onto the stack + + test [flags],SHAPE_WIN_REL ; is this window relative? + je short ??get_buffer ; if not the skip over + + mov ax,[win_x1] ; get left edge of window + add [x_pixel],ax ; add to x pixel position + mov ax,[win_y1] ; get top edge of window + add [y_pixel],ax ; add to y pixel position + + ;-------------------------------------------------------------------- + ; Get a pointer to the source buffer so we can handle the clipping + ;-------------------------------------------------------------------- +??get_buffer: + lds si,[buffer] ; get a pointer to the buffer + + ;-------------------------------------------------------------------- + ; Check the top of our shape and clip any lines that are necessary + ;-------------------------------------------------------------------- + mov ax,[y_pixel] ; get the y_pixel draw position + sub ax,[win_y1] ; subtract out the window y top + jns short ??check_bottom ; skip if y below window top + add ax,[pixel_h] ; add in the height of the region + jg short ??clip_top ; if positive then clip top lines + +??jump_exit: + jmp ??exit ; otherwise completely clipped + +??clip_top: + xchg [pixel_h],ax + sub ax,[pixel_h] + add [y_pixel],ax + mul [pixel_w] ; convert to number of bytes to skip + add si,ax ; skip past the necessary bytes + + ;-------------------------------------------------------------------- + ; Check the bottom of our shape and clip it if necessary + ;-------------------------------------------------------------------- +??check_bottom: + mov ax,[win_y2] ; get the bottom y of the window + sub ax,[y_pixel] ; subtract of the y to draw at + js ??jump_exit ; if its signed then nothing to draw + jz ??jump_exit ; if its zero then nothing to draw + + cmp ax,[pixel_h] ; if more room to draw then height + jae short ??clip_x_left ; then go check the left clip + mov [pixel_h],ax ; clip all but amount that will fit + +??clip_x_left: + mov [clipleft],0 ; clear clip on left of region + mov ax,[x_pixel] ; get the pixel x of draw region + sub ax,[win_x1] ; pull out the window coordinate + jns short ??clip_x_right + neg ax ; negate to get amnt to skip in buf + mov [clipleft],ax ; store it in the left clip info + add [x_pixel],ax ; move to the edge of the window + sub [pixel_w],ax ; pull it out of the pixel width + +??clip_x_right: + mov [clipright],0 ; clear clip on right of region + mov ax,[win_x2] ; get the window x of clip region + sub ax,[x_pixel] ; subtract the draw edge of region + js ??jump_exit ; if its negative then get out + jz ??jump_exit ; if its zero then get out + + cmp ax,[pixel_w] ; is space available larger than w + jae short ??draw_prep ; if so then go get drawing + + + xchg [pixel_w],ax ; amt to draw in pixel_w (wid in ax) + sub ax,[pixel_w] ; pull out the amount to draw + mov [clipright],ax ; this is the amount to clip on right + +??draw_prep: + push si ; save off source pos in buffer + push ds ; both offset and segment + mov ax,@data + mov ds,ax + mov bx,[y_pixel] + shl bx,1 ; shift left by 1 for word table look + lds si,[YTable] ; get the address of the ytable + mov di,[ds:si+bx] ; look up the multiplied value + pop ds ; restore source pos in buffer + pop si ; both offset and segment + + add di,[x_pixel] ; add in the x pixel position + mov [nextline],di ; save it off in the next line + + ;-------------------------------------------------------------------- + ; Now determine the type of the shape and process it in the proper + ; way. + ;-------------------------------------------------------------------- + mov dx,[pixel_h] + + ; Check to see if the WSA is the screen width and there is no + ; clipping. In this case, then a special single call to the + ; line processing routine is possible. + mov ax,[clipleft] + add ax,[clipright] + jne short ??top_of_loop + cmp [pixel_w],BYTESPERROW + jne short ??top_of_loop + + ;------------------------------------ + ; The width of the WSA is the screen width, so just process as + ; one large WSA line. + mov ax,BYTESPERROW + imul dx + mov cx,ax + call [putmiddle] + jmp short ??exit + + ;------------------------------------ + ; Process line by line. +??top_of_loop: + add si,[clipleft] ; skip whats necessary on left edge + mov cx,[pixel_w] ; get the width we need to draw + + ; Copy the source to the destination as appropriate. This routine can + ; trash AX, BX, CX, and DI. It must properly modify SI to point one byte past + ; the end of the data. + call [putmiddle] + + add si,[clipright] ; skip past the left clip + add [nextline],BYTESPERROW + mov di,[nextline] + + dec dx + jnz ??top_of_loop + +??exit: + pop fs + ret + ENDP + + +;*************************************************************************** +;* NORMAL_DRAW -- Function that writes a normal pixel line * +;* * +;* INPUT: cx - number of pixels to write * +;* ds:si - buffer which holds the pixels to write * +;* es:di - place to put the pixels we are writing * +;* * +;* OUTPUT: ds:si - points to next pixel past last pixel read * +;* es:di - points to next pixel past last pixel written * +;* * +;* WARNINGS: none * +;* * +;* HISTORY: * +;* 07/17/1992 PWG : Created. * +;*=========================================================================* + + PROC NOLANGUAGE WSA_Normal_Draw NEAR + + IF 1 + ; This version is marginally faster than the later version. + mov ax,cx + shr cx,2 + rep movsd + and ax,011b + mov cx,ax + shr cx,1 + rep movsw + adc cx,cx + rep movsb + ret + + ELSE + + shr cx,1 ; convert to words (odd pix in carry) + rep movsw ; write out the needed words + adc cx,0 ; add the carry into cx + rep movsb ; write out the odd byte if any + ret + ENDIF + + ENDP + + +;*************************************************************************** +;* TRANSPARENT_DRAW -- Function that writes a transparent pixel line * +;* * +;* INPUT: cx - number of pixels to write * +;* ds:si - buffer which holds the pixels to write * +;* es:di - place to put the pixels we are writing * +;* * +;* OUTPUT: ds:si - points to next pixel past last pixel read * +;* es:di - points to next pixel past last pixel written * +;* * +;* WARNINGS: none * +;* * +;* HISTORY: * +;* 07/17/1992 PWG : Created. * +;* 10/02/1994 JLB : Optimized for 250% speed improvement. * +;*=========================================================================* + PROC NOLANGUAGE Transparent_Draw NEAR + + IF 1 + ; Preserve DX since it is used as a scratch register. + push dx + +??loop: + ; Swap DS:SI and ES:DI back in preparation for the REP SCASB + ; instruction. + xchg di,si + mov dx,es + mov ax,ds + mov ds,dx + mov es,ax + + ; Remember the bytes remaining in order to calculate the position + ; of the scan when it stops. + mov bx,cx + + ; Scan looking for a non-zero value in the source buffer. + xor al,al + repe scasb + + ; When the loop ends, if the EQ flag is set then the scanning is + ; complete. Jump to the end of the routine in order to fixup the + ; pointers. + je short ??fini + + ; Advance the destination pointer by the amount necessary to match + ; the source movement. DS:SI points to where data should be written. + add si,bx + inc cx ; SCASB leaves CX one too low, fix it. + dec di ; SCASB leaves DI one byte too far, fix it. + sub si,cx + + ; Scan for the duration of non-zero pixels. This yields a count which + ; is used to copy the source data to the destination. Preserve DI. + mov dx,di + mov bx,cx + repne scasb + mov di,dx + + ; Set BX to equal the number of bytes to copy from source to dest. + inc cx ; SCASB leaves CX one too low, fix it. + sub bx,cx + + ; Move the data from ES:DI to DS:SI for BX bytes. + xchg cx,bx ; Make CX=bytes to move, BX=bytes remaining. + + ; Swap DS:SI and ES:DI in preparation for the REP MOV instruction. + xchg di,si + mov dx,es + mov ax,ds + mov ds,dx + mov es,ax + + ; Move the data from source to dest. First try to move double + ; words. Then copy the remainder bytes (if any). Putting jumps in + ; this section doesn't result in any savings -- oh well. + mov ax,cx + shr cx,2 + rep movsd + and ax,0011b + mov cx,ax + shr cx,1 + rep movsw + adc cx,cx + rep movsb + + ; Restore CX with the remaining bytes to process. + mov cx,bx + + ; If there are more bytes to process, then loop back. + or cx,cx + jne short ??loop + +??fini: + ; Swap ES:DI and DS:SI back to original orientation. + mov ax,ds + mov bx,es + mov es,ax + mov ds,bx + xchg di,si + + ; Restore DX and return. + pop dx + ret + + ELSE + +??loop_top: + lodsb + or al,al + jz short ??skip + + mov [es:di],al ; store the pixel to the screen +??skip: + inc di + loop ??loop_top + ret + + ENDIF + + ENDP + + +;*************************************************************************** +;* PRIORITY_DRAW -- Function that writes a pixels if they are in front of * +;* the given plate. * +;* * +;* INPUT: cx - number of pixels to write * +;* ds:si - buffer which holds the pixels to write * +;* es:di - place to put the pixels we are writing * +;* * +;* OUTPUT: ds:si - points to next pixel past last pixel read * +;* es:di - points to next pixel past last pixel written * +;* * +;* WARNINGS: none * +;* * +;* HISTORY: * +;* 07/17/1992 PWG : Created. * +;* 12/01/1992 MBL : Updated to work with latest mask data encoding. * +;* 17/01/1993 MCC : Updated for 386, and optimized * +;*=========================================================================* + + PROC NOLANGUAGE Priority_Draw NEAR + + mov fs,[background] ; get the SEG of the background page + mov gs,[maskpage] ; get the SEG of the mask info + mov ah,[priority] ; keep a copy of priority varible for faster cmp + + +??loop_top: + lodsb ; get the pixel to draw on the screen + + ; get the mask byte for our pixel + mov bl,[ds:di] + ; get rid of non-walkable bit and + ; get rid of scaling id bits + and bl,CLEAR_NON_WALK_BIT_AND_SCALE_BITS + + cmp ah,bl ; are we more toward the front? + jge short ??out_pixel ; if so then write the pixel + + mov al,[fs:di] ; get the pixel to write +??out_pixel: + stosb ; write the pixel and inc the DI + loop ??loop_top + ret + + ENDP + + +;*************************************************************************** +;* PRIORITY_TRANSPARENT_DRAW -- Function that writes a pixels if they are * +;* in front of the given plate. It also deals with * +;* transparent pixels. * +;* * +;* INPUT: cx - number of pixels to write * +;* ds:si - buffer which holds the pixels to write * +;* es:di - place to put the pixels we are writing * +;* * +;* OUTPUT: ds:si - points to next pixel past last pixel read * +;* es:di - points to next pixel past last pixel written * +;* * +;* WARNINGS: none * +;* * +;* HISTORY: * +;* 07/17/1992 PWG : Created. * +;* 12/01/1992 MBL : Updated to work with latest mask data encoding. * +;* 17/01/1993 MCC : Updated for 386, and optimized * +;*=========================================================================* + + PROC NOLANGUAGE Priority_Transparent_Draw NEAR + + mov fs,[background] ; get the SEG of the background page + mov gs,[maskpage] ; get the SEG of the mask info + mov ah,[priority] ; keep a copy of priority varible for faster cmp + +??loop_top: + lodsb ; get the pixel on the screen + or al,al ; check to see if al is transparent + je short ??write_back ; if it is go write background + + mov bl,[gs:di] ; get the mask byte for our pixel + + ; get rid of non-walkable bit and + ; get rid of scaling id bits + and bl,CLEAR_NON_WALK_BIT_AND_SCALE_BITS + + cmp ah,bl ; are we more toward the front? + jge short ??out_pixel ; if so then write the pixel + +??write_back: + mov al,[fs:di] ; get the pixel to write +??out_pixel: + stosb ; write the pixel + loop ??loop_top + ret + + ENDP + + +;*************************************************************************** +;* GHOST_NORMAL_DRAW -- Function that writes a normal pixel line * +;* * +;* INPUT: cx - number of pixels to write * +;* ds:si - buffer which holds the pixels to write * +;* es:di - place to put the pixels we are writing * +;* * +;* OUTPUT: ds:si - points to next pixel past last pixel read * +;* es:di - points to next pixel past last pixel written * +;* * +;* WARNINGS: none * +;* * +;* HISTORY: * +;* 05/27/1993 MCC : Created. * +;*=========================================================================* + + PROC NOLANGUAGE Ghost_Normal_Draw NEAR + +??loop_top: + lodsb + +;--- + ; Ok, find out if the colour is a Translucent colour + push ax + push ds + + lds bx,[IsTranslucent] + mov ah,al ; preserve real pixel + xlat ; get new al (transluecent pixel + xchg ah,al ; get real pixel back into AL just in case + cmp ah,255 + je short ??normal_pixel ; is it a translucent ? + ; if we get passed here value in + ; AH should be 0-15 + + ; yes, it is a translucent colour so goto our translucent translation + ; table and set up a ptr to the correct table + + mov al,[es:di] + ; mov pixel at destination to al and we have + ; the index to the translation table + ; ((trans_colour * 256) + dest colour) + lds bx,[Translucent] ; get the ptr to it! + add bh,ah ; Add the (trans_color * 256) of the translation equ. + ; XLAT only uses AL so no need to clear AH + xlat ; get new pixel in AL + +??normal_pixel: + pop ds + pop bx + mov ah,bh +;--- + + mov [es:di],al ; store the pixel to the screen + +??skip: + inc di + loop ??loop_top + + ret + + ENDP + + +;*************************************************************************** +;* GHOST_TRANSPARENT_DRAW -- Function that writes a transparent pixel line * +;* * +;* INPUT: cx - number of pixels to write * +;* ds:si - buffer which holds the pixels to write * +;* es:di - place to put the pixels we are writing * +;* * +;* OUTPUT: ds:si - points to next pixel past last pixel read * +;* es:di - points to next pixel past last pixel written * +;* * +;* WARNINGS: none * +;* * +;* HISTORY: * +;* 05/27/1993 MCC : Created. * +;*=========================================================================* + PROC NOLANGUAGE Ghost_Transparent_Draw NEAR + +??loop_top: + lodsb + or al,al + jz short ??skip + +;--- + ; Ok, find out if the colour is a Translucent colour + push ax + push ds + + lds bx,[IsTranslucent] + mov ah,al ; preserve real pixel + xlat ; get new al (transluecent pixel + xchg ah,al ; get real pixel back into AL just in case + cmp ah,255 + je short ??normal_pixel ; is it a translucent ? + ; if we get passed here value in + ; AH should be 0-15 + + ; yes, it is a translucent colour so goto our translucent translation + ; table and set up a ptr to the correct table + + mov al,[es:di] + ; mov pixel at destination to al and we have + ; the index to the translation table + ; ((trans_colour * 256) + dest colour) + lds bx,[Translucent] ; get the ptr to it! + add bh,ah ; Add the (trans_color * 256) of the translation equ. + ; XLAT only uses AL so no need to clear AH + xlat ; get new pixel in AL + +??normal_pixel: + pop ds + pop bx + mov ah,bh +;--- + + mov [es:di],al ; store the pixel to the screen + +??skip: + inc di + loop ??loop_top + ret + + ENDP + + +;*************************************************************************** +;* GHOST_PRIORITY_DRAW -- Function that writes a pixels if they are in fron* +;* the given plate. * +;* * +;* INPUT: cx - number of pixels to write * +;* ds:si - buffer which holds the pixels to write * +;* es:di - place to put the pixels we are writing * +;* * +;* OUTPUT: ds:si - points to next pixel past last pixel read * +;* es:di - points to next pixel past last pixel written * +;* * +;* WARNINGS: none * +;* * +;* HISTORY: * +;* 07/17/1992 PWG : Created. * +;* 12/01/1992 MBL : Updated to work with latest mask data encoding. * +;* 05/27/1993 MCC : Updated to use the new Ghosting fx * +;* 17/01/1993 MCC : Updated for 386, and optimized * +;*=========================================================================* + PROC NOLANGUAGE Ghost_Priority_Draw NEAR + + mov fs,[background] ; get the SEG of the background page + mov gs,[maskpage] ; get the SEG of the mask info + mov ah,[priority] ; keep a copy of priority varible for faster cmp + + +??loop_top: + lodsb ; get the pixel to draw on the screen + ; get the mask byte for our pixel + mov bl,[ds:di] + ; get rid of non-walkable bit and + ; get rid of scaling id bits + and bl,CLEAR_NON_WALK_BIT_AND_SCALE_BITS + cmp ah,bl ; are we more toward the front? + jge short ??out_pixel ; if so then write the pixel + + mov al,[fs:di] ; get the pixel to write +??out_pixel: + stosb ; write the pixel and inc the DI + loop ??loop_top + + ret + + ENDP + + +;*************************************************************************** +;* GHOST_PRIORITY_TRANSPARENT_DRAW -- Function that writes a pixels if they* +;* in front of the given plate. It also deals with * +;* transparent pixels. * +;* * +;* INPUT: cx - number of pixels to write * +;* ds:si - buffer which holds the pixels to write * +;* es:di - place to put the pixels we are writing * +;* * +;* OUTPUT: ds:si - points to next pixel past last pixel read * +;* es:di - points to next pixel past last pixel written * +;* * +;* WARNINGS: none * +;* * +;* HISTORY: * +;* 07/17/1992 PWG : Created. * +;* 12/01/1992 MBL : Updated to work with latest mask data encoding. * +;* 05/27/1993 MCC : Updated to use the new Ghosting fx * +;* 17/01/1993 MCC : Updated for 386, and optimized * +;*=========================================================================* + PROC NOLANGUAGE Ghost_Priority_Transparent_Draw NEAR + + mov fs,[background] ; get the SEG of the background page + mov gs,[maskpage] ; get the SEG of the mask info + mov ah,[priority] ; keep a copy of priority varible for faster cmp + +??loop_top: + lodsb ; get the pixel on the screen + or al,al ; check to see if al is transparent + je short ??write_back ; if it is go write background + mov bl,[gs:di] ; get the mask byte for our pixel + ; get rid of non-walkable bit and + ; get rid of scaling id bits + and bl,CLEAR_NON_WALK_BIT_AND_SCALE_BITS + cmp ah,bl ; are we more toward the front? + jge short ??out_pixel ; if so then write the pixel +??write_back: + mov al,[fs:di] ; get the pixel to write +??out_pixel: + stosb ; write the pixel + loop ??loop_top + + ret + + ENDP + + END diff --git a/KEYFBUFF.INC b/KEYFBUFF.INC new file mode 100644 index 0000000..c503ff3 --- /dev/null +++ b/KEYFBUFF.INC @@ -0,0 +1,40 @@ +; +; Command & Conquer(tm) +; Copyright 2025 Electronic Arts Inc. +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; + +;************************************************************** +; +; Macro to fetch the header of the next line and jump to the appropriate routine +; +next_line macro + + add edi,[dest_adjust_width] ;add in dest modulo + dec edx ;line counter + jz real_out ;return + mov ecx,[save_ecx] ;ecx is pixel count + mov eax,[header_pointer] ;ptr to next header byte + mov al,[eax] + inc [header_pointer] + and eax,BLIT_ALL ;Make sure we dont jump to some spurious address + ; if the header is wrong then its better to draw with the wrong + ; shape routine than to die + shl eax,2 + add eax,[ShapeJumpTableAddress] ;get the address to jump to + jmp [dword eax] ;do the jump + + endm + diff --git a/KEYFRAME.CPP b/KEYFRAME.CPP new file mode 100644 index 0000000..9c6f266 --- /dev/null +++ b/KEYFRAME.CPP @@ -0,0 +1,593 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\keyframe.cpv 2.14 16 Oct 1995 16:48:54 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : KEYFRAME.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 06/25/95 * + * * + * Last Update : June 25, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Get_Build_Frame_Count -- Fetches the number of frames in data block. * + * Get_Build_Frame_Width -- Fetches the width of the shape image. * + * Get_Build_Frame_Height -- Fetches the height of the shape image. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + +#include "function.h" + +#define SUBFRAMEOFFS 7 // 3 1/2 frame offsets loaded (2 offsets/frame) + + +#define Apply_Delta(buffer, delta) Apply_XOR_Delta((char*)(buffer), (char*)(delta)) + +typedef struct { + unsigned short frames; + unsigned short x; + unsigned short y; + unsigned short width; + unsigned short height; + unsigned short largest_frame_size; + short flags; +} KeyFrameHeaderType; + +#define INITIAL_BIG_SHAPE_BUFFER_SIZE 12000*1024 +#define THEATER_BIG_SHAPE_BUFFER_SIZE 1000*1024 +#define UNCOMPRESS_MAGIC_NUMBER 56789 + +unsigned BigShapeBufferLength = INITIAL_BIG_SHAPE_BUFFER_SIZE; +unsigned TheaterShapeBufferLength = THEATER_BIG_SHAPE_BUFFER_SIZE; +extern "C"{ + char *BigShapeBufferStart = NULL; + char *TheaterShapeBufferStart = NULL; + BOOL UseBigShapeBuffer = FALSE; + bool IsTheaterShape = false; +} +char *BigShapeBufferPtr = NULL; +int TotalBigShapes=0; +BOOL ReallocShapeBufferFlag = FALSE; +bool OriginalUseBigShapeBuffer = false; + +char *TheaterShapeBufferPtr = NULL; +int TotalTheaterShapes = 0; + + + +#define MAX_SLOTS 1500 +#define THEATER_SLOT_START 1000 + +char **KeyFrameSlots [MAX_SLOTS]; +int TotalSlotsUsed=0; +int TheaterSlotsUsed = THEATER_SLOT_START; + + +typedef struct tShapeHeaderType{ + unsigned draw_flags; + char *shape_data; + int shape_buffer; //1 if shape is in theater buffer +} ShapeHeaderType; + +static int Length; + +void *Get_Shape_Header_Data(void *ptr) +{ + if (UseBigShapeBuffer){ + + ShapeHeaderType *header = (ShapeHeaderType*) ptr; + return ((void*) (header->shape_data + (long)(header->shape_buffer ? TheaterShapeBufferStart : BigShapeBufferStart) ) ); + + }else{ + return (ptr); + } +} + +int Get_Last_Frame_Length(void) +{ + return(Length); +} + + + +void Reset_Theater_Shapes (void) +{ + /* + ** Delete any previously allocated slots + */ + for (int i=THEATER_SLOT_START ; i 16*1024*1024) ? TRUE : FALSE; + OriginalUseBigShapeBuffer = UseBigShapeBuffer; + + // UseBigShapeBuffer = false; +} + + + + +/*********************************************************************************************** + * Disable_Uncompressed_Shapes -- Temporarily turns off shape decompression * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 11/19/96 2:37PM ST : Created * + *=============================================================================================*/ +void Disable_Uncompressed_Shapes (void) +{ + UseBigShapeBuffer = false; +} + + + +/*********************************************************************************************** + * Enable_Uncompressed_Shapes -- Restores state of shape decompression before it was disabled * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 11/19/96 2:37PM ST : Created * + *=============================================================================================*/ +void Enable_Uncompressed_Shapes (void) +{ + UseBigShapeBuffer = OriginalUseBigShapeBuffer; +} + + + + +unsigned long Build_Frame(void const *dataptr, unsigned short framenumber, void *buffptr) +{ + char *ptr, *lockptr;//, *uncomp_ptr; + unsigned long offset[SUBFRAMEOFFS]; + unsigned long offcurr, off16, offdiff; + KeyFrameHeaderType *keyfr; + unsigned short buffsize, currframe, subframe; + unsigned long length = 0; + char frameflags; + unsigned long return_value; + char *temp_shape_ptr; + + // + // valid pointer?? + // + Length = 0; + if ( !dataptr || !buffptr ) { + return(0); + } + + // + // look at header then check that frame to build is not greater + // than total frames + // + keyfr = (KeyFrameHeaderType *) dataptr; + + if ( framenumber >= keyfr->frames ) { + return(0); + } + + + if (UseBigShapeBuffer){ + /* + ** If we havnt yet allocated memory for uncompressed shapes then do so now. + ** + */ + if (!BigShapeBufferStart){ + BigShapeBufferStart = (char*)Alloc(BigShapeBufferLength, MEM_NORMAL); + BigShapeBufferPtr = BigShapeBufferStart; + /* + ** Allocate memory for theater specific uncompressed shapes + */ + TheaterShapeBufferStart = (char*) Alloc (TheaterShapeBufferLength, MEM_NORMAL); + TheaterShapeBufferPtr = TheaterShapeBufferStart; + } + + + /* + ** Track memory usage in uncompressed shape buffers. + */ + static bool show_info = true; + + if ((Frame & 0xff) == 0){ + + if (show_info){ + + char crap [128]; + sprintf (crap, "C&C95 - Big shape buffer is now %d Kb.\n", BigShapeBufferLength / 1024); + CCDebugString (crap); + + sprintf (crap, "C&C95 - %d Kb Used in big shape buffer.\n", (unsigned)((unsigned)BigShapeBufferPtr - (unsigned)BigShapeBufferStart)/1024); + CCDebugString (crap); + + sprintf (crap, "C&C95 - %d Kb Used in theater shape buffer.\n", (unsigned)((unsigned)TheaterShapeBufferPtr - (unsigned)TheaterShapeBufferStart)/1024); + CCDebugString (crap); + show_info = false; + } + + }else{ + show_info = true; + + } + + + /* + ** If we are running out of memory (<128k left) for uncompressed shapes + ** then allocate some more. + */ + if (( (unsigned)BigShapeBufferStart + BigShapeBufferLength) - (unsigned)BigShapeBufferPtr < 128*1024){ + ReallocShapeBufferFlag = TRUE; + } + + /* + ** If this animation was not previously uncompressed then + ** allocate memory to keep the pointers to the uncompressed data + ** for these animation frames + */ + if (keyfr->x != UNCOMPRESS_MAGIC_NUMBER){ + keyfr->x = UNCOMPRESS_MAGIC_NUMBER; + if (IsTheaterShape){ + keyfr->y = TheaterSlotsUsed; + TheaterSlotsUsed++; + }else{ + keyfr->y = TotalSlotsUsed; + TotalSlotsUsed++; + } + /* + ** Allocate and clear the memory for the shape info + */ + KeyFrameSlots[keyfr->y]= new char *[keyfr->frames]; + memset (KeyFrameSlots[keyfr->y] , 0 , keyfr->frames*4); + } + + /* + ** If this frame was previously uncompressed then just return + ** a pointer to the raw data + */ + if (*(KeyFrameSlots[keyfr->y]+framenumber)){ + if (IsTheaterShape){ + return ((unsigned long)TheaterShapeBufferStart + (unsigned long)*(KeyFrameSlots[keyfr->y]+framenumber)); + }else{ + return ((unsigned long)BigShapeBufferStart + (unsigned long)*(KeyFrameSlots[keyfr->y]+framenumber)); + } + } + } + + // calc buff size + buffsize = keyfr->width * keyfr->height; + + // get offset into data + ptr = (char *)Add_Long_To_Pointer( dataptr, (((unsigned long)framenumber << 3) + sizeof(KeyFrameHeaderType)) ); + Mem_Copy( ptr, &offset[0], 12L ); + frameflags = (char)(offset[0] >> 24); + + + if ( (frameflags & KF_KEYFRAME) ) { + + ptr = (char *)Add_Long_To_Pointer( dataptr, (offset[0] & 0x00FFFFFFL) ); + + if (keyfr->flags & 1 ) { + ptr = (char *)Add_Long_To_Pointer( ptr, 768L ); + } + length = LCW_Uncompress( ptr, buffptr, buffsize ); + } else { // key delta or delta + + if ( (frameflags & KF_DELTA) ) { + currframe = (unsigned short)offset[1]; + + ptr = (char *)Add_Long_To_Pointer( dataptr, (((unsigned long)currframe << 3) + sizeof(KeyFrameHeaderType)) ); + Mem_Copy( ptr, &offset[0], (long)(SUBFRAMEOFFS * sizeof(unsigned long)) ); + } + + // key frame + offcurr = offset[1] & 0x00FFFFFFL; + + // key delta + offdiff = (offset[0] & 0x00FFFFFFL) - offcurr; + + ptr = (char *)Add_Long_To_Pointer( dataptr, offcurr ); + + if (keyfr->flags & 1 ) { + ptr = (char *)Add_Long_To_Pointer( ptr, 768L ); + } + + off16 = (unsigned long)lockptr & 0x00003FFFL; + + length = LCW_Uncompress( ptr, buffptr, buffsize ); + + if (length > buffsize) { + return(0); + } + + if ( ((offset[2] & 0x00FFFFFFL) - offcurr) >= (0x00010000L - off16) ) { + + ptr = (char *)Add_Long_To_Pointer( ptr, offdiff ); + off16 = (unsigned long)ptr & 0x00003FFFL; + + offcurr += offdiff; + offdiff = 0; + } + + length = buffsize; + Apply_Delta(buffptr, Add_Long_To_Pointer(ptr, offdiff)); + + if ( (frameflags & KF_DELTA) ) { + // adjust to delta after the keydelta + + currframe++; + subframe = 2; + + while (currframe <= framenumber) { + offdiff = (offset[subframe] & 0x00FFFFFFL) - offcurr; + + if ( ((offset[subframe+2] & 0x00FFFFFFL) - offcurr) >= (0x00010000L - off16) ) { + + ptr = (char *)Add_Long_To_Pointer( ptr, offdiff ); + off16 = (unsigned long)lockptr & 0x00003FFFL; + + offcurr += offdiff; + offdiff = 0; + } + + length = buffsize; + Apply_Delta(buffptr, Add_Long_To_Pointer(ptr, offdiff)); + + currframe++; + subframe += 2; + + if ( subframe >= (SUBFRAMEOFFS - 1) && + currframe <= framenumber ) { + Mem_Copy( Add_Long_To_Pointer( dataptr, + (((unsigned long)currframe << 3) + + sizeof(KeyFrameHeaderType)) ), + &offset[0], (long)(SUBFRAMEOFFS * sizeof(unsigned long)) ); + subframe = 0; + } + } + } + } + + if (UseBigShapeBuffer){ + /* + ** Save the uncompressed shape data so we dont have to uncompress it + ** again next time its drawn. + ** We keep a space free before the raw shape data so we can add line + ** header info before the shape is drawn for the first time + */ + + if (IsTheaterShape){ + /* + ** Shape is a theater specific shape + */ + return_value = (unsigned long) TheaterShapeBufferPtr; + temp_shape_ptr = TheaterShapeBufferPtr + keyfr->height+sizeof(ShapeHeaderType); + /* + ** align the actual shape data + */ + if (3 & (unsigned)temp_shape_ptr){ + temp_shape_ptr = (char *) ((unsigned)(temp_shape_ptr + 3) & 0xfffffffc); + } + + memcpy (temp_shape_ptr , buffptr , length); + ((ShapeHeaderType *)TheaterShapeBufferPtr)->draw_flags = -1; //Flag that headers need to be generated + ((ShapeHeaderType *)TheaterShapeBufferPtr)->shape_data = temp_shape_ptr - (unsigned)TheaterShapeBufferStart; //pointer to old raw shape data + ((ShapeHeaderType *)TheaterShapeBufferPtr)->shape_buffer = 1; //Theater buffer + *(KeyFrameSlots[keyfr->y]+framenumber) = TheaterShapeBufferPtr - (unsigned)TheaterShapeBufferStart; + TheaterShapeBufferPtr = (char*)(length + (unsigned)temp_shape_ptr); + /* + ** Align the next shape + */ + if (3 & (unsigned)TheaterShapeBufferPtr){ + TheaterShapeBufferPtr = (char *)((unsigned)(TheaterShapeBufferPtr + 3) & 0xfffffffc); + } + Length = length; + return (return_value); + + }else{ + + + return_value=(unsigned long)BigShapeBufferPtr; + temp_shape_ptr = BigShapeBufferPtr + keyfr->height+sizeof(ShapeHeaderType); + /* + ** align the actual shape data + */ + if (3 & (unsigned)temp_shape_ptr){ + temp_shape_ptr = (char *) ((unsigned)(temp_shape_ptr + 3) & 0xfffffffc); + } + memcpy (temp_shape_ptr , buffptr , length); + ((ShapeHeaderType *)BigShapeBufferPtr)->draw_flags = -1; //Flag that headers need to be generated + ((ShapeHeaderType *)BigShapeBufferPtr)->shape_data = temp_shape_ptr - (unsigned)BigShapeBufferStart; //pointer to old raw shape data + ((ShapeHeaderType *)BigShapeBufferPtr)->shape_buffer = 0; //Normal Big Shape Buffer + *(KeyFrameSlots[keyfr->y]+framenumber) = BigShapeBufferPtr - (unsigned)BigShapeBufferStart; + BigShapeBufferPtr = (char*)(length + (unsigned)temp_shape_ptr); + // Align the next shape + if (3 & (unsigned)BigShapeBufferPtr){ + BigShapeBufferPtr = (char *)((unsigned)(BigShapeBufferPtr + 3) & 0xfffffffc); + } + Length = length; + return (return_value); + } + + }else{ + return ((unsigned long)buffptr); + } +} + + +/*********************************************************************************************** + * Get_Build_Frame_Count -- Fetches the number of frames in data block. * + * * + * Use this routine to determine the number of shapes within the data block. * + * * + * INPUT: dataptr -- Pointer to the keyframe shape data block. * + * * + * OUTPUT: Returns with the number of shapes in the data block. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Commented. * + *=============================================================================================*/ +unsigned short Get_Build_Frame_Count(void const *dataptr) +{ + if (dataptr) { + return(((KeyFrameHeaderType const *)dataptr)->frames); + } + return(0); +} + + +unsigned short Get_Build_Frame_X(void const *dataptr) +{ + if (dataptr) { + return(((KeyFrameHeaderType const *)dataptr)->x); + } + return(0); +} + + +unsigned short Get_Build_Frame_Y(void const *dataptr) +{ + if (dataptr) { + return(((KeyFrameHeaderType const *)dataptr)->y); + } + return(0); +} + + +/*********************************************************************************************** + * Get_Build_Frame_Width -- Fetches the width of the shape image. * + * * + * Use this routine to fetch the width of the shapes within the keyframe shape data block. * + * All shapes within the block have the same width. * + * * + * INPUT: dataptr -- Pointer to the keyframe shape data block. * + * * + * OUTPUT: Returns with the width of the shapes in the block -- expressed in pixels. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Commented * + *=============================================================================================*/ +unsigned short Get_Build_Frame_Width(void const *dataptr) +{ + if (dataptr) { + return(((KeyFrameHeaderType const *)dataptr)->width); + } + return(0); +} + + +/*********************************************************************************************** + * Get_Build_Frame_Height -- Fetches the height of the shape image. * + * * + * Use this routine to fetch the height of the shapes within the keyframe shape data block. * + * All shapes within the block have the same height. * + * * + * INPUT: dataptr -- Pointer to the keyframe shape data block. * + * * + * OUTPUT: Returns with the height of the shapes in the block -- expressed in pixels. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Commented * + *=============================================================================================*/ +unsigned short Get_Build_Frame_Height(void const *dataptr) +{ + if (dataptr) { + return(((KeyFrameHeaderType const *)dataptr)->height); + } + return(0); +} + + +bool Get_Build_Frame_Palette(void const * dataptr, void * palette) +{ + if (dataptr && (((KeyFrameHeaderType const *)dataptr)->flags & 1)) { + char const * ptr = (char const *)Add_Long_To_Pointer( dataptr, + ( (( (long)sizeof(unsigned long) << 1) * + ((KeyFrameHeaderType *) dataptr)->frames ) + + 16 + sizeof(KeyFrameHeaderType) ) ); + + memcpy(palette, ptr, 768L); + return(true); + } + return(false); +} diff --git a/LAYER.CPP b/LAYER.CPP new file mode 100644 index 0000000..ec707f2 --- /dev/null +++ b/LAYER.CPP @@ -0,0 +1,163 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\layer.cpv 2.18 16 Oct 1995 16:50:14 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : LAYER.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 31, 1994 * + * * + * Last Update : March 10, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * LayerClass::Submit -- Adds an object to a layer list. * + * LayerClass::Sort -- Perform an incremental sort pass on the layer's objects. * + * LayerClass::Sorted_Add -- Adds object in sorted order to layer. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "layer.h" + + +/*********************************************************************************************** + * LayerClass::Submit -- Adds an object to a layer list. * + * * + * This routine is used to add an object to the layer list. If the list is full, then the * + * object is not added. * + * * + * INPUT: object -- Pointer to the object to add. * + * * + * OUTPUT: bool; Was the object added successfully? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + * 05/31/1994 JLB : Allows sorted insert. * + * 01/02/1995 JLB : Fixed to work with EMSListOf template. * + *=============================================================================================*/ +bool LayerClass::Submit(ObjectClass const * object, bool sort) +{ + /* + ** Add the object to the layer. Either at the end (if "sort" is false) or at the + ** appropriately sorted position. + */ + if (sort) { + return(Sorted_Add(object)); + } + return(Add((ObjectClass *)object)); +} + + +/*********************************************************************************************** + * LayerClass::Sort -- Handles sorting the objects in the layer. * + * * + * This routine is used if the layer objects must be sorted and sorting is to occur now. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Don't call this routine too often since it does take a bit of time to * + * execute. It is a single pass binary sort and thus isn't horribly slow, * + * but it does take some time. * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + * 03/10/1995 JLB : Uses comparison operator. * + *=============================================================================================*/ +void LayerClass::Sort(void) +{ + for (int index = 0; index < Count()-1; index++) { + if (*(*this)[index+1] < *(*this)[index]) { + ObjectClass * temp; + + temp = (*this)[index+1]; + (*this)[index+1] = (*this)[index]; + (*this)[index] = temp; + } + } +} + + +/*********************************************************************************************** + * DynamicVectorClass::Sorted_Add -- Adds object in sorted order to vector. * + * * + * Use this routine to add an object to the vector but it will be inserted in sorted * + * order. This depends on the ">" operator being defined for the vector object. * + * * + * INPUT: object -- Reference to the object that will be added to the vector. * + * * + * OUTPUT: bool; Was the object added to the vector successfully? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +int LayerClass::Sorted_Add(ObjectClass const * const object) +{ + if (ActiveCount >= Length()) { + if ((IsAllocated || !VectorMax) && GrowthStep > 0) { + if (!Resize(Length() + GrowthStep)) { + + /* + ** Failure to increase the size of the vector is an error condition. + ** Return with the error flag. + */ + return(false); + } + } else { + + /* + ** Increasing the size of this vector is not allowed! Bail this + ** routine with the error code. + */ + return(false); + } + } + + /* + ** There is room for the new object now. Add it to the right sorted position. + */ + for (int index = 0; index < ActiveCount; index++) { + if ((*(*this)[index]) > (*object)) { + break; + } + } + + /* + ** Make room if the insertion spot is not at the end of the vector. + */ + for (int i = ActiveCount-1; i >= index; i--) { + (*this)[i+1] = (*this)[i]; + } + (*this)[index] = (ObjectClass *)object; + ActiveCount++; + return(true); +} + + diff --git a/LAYER.H b/LAYER.H new file mode 100644 index 0000000..36094f7 --- /dev/null +++ b/LAYER.H @@ -0,0 +1,67 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\layer.h_v 2.19 16 Oct 1995 16:46:22 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : LAYER.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 31, 1994 * + * * + * Last Update : May 31, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef LAYER_H +#define LAYER_H + +#include "vector.h" + +class ObjectClass; + +class LayerClass : public DynamicVectorClass +{ + public: + + //----------------------------------------------------------------- + void Sort(void); + bool Submit(ObjectClass const * object, bool sort=false); + int Sorted_Add(ObjectClass const * const object); + + + virtual void Init(void) {Clear();}; + virtual void One_Time(void) {}; + + /* + ** File I/O. + */ + bool Load(FileClass & file); + bool Save(FileClass & file); + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); +}; + +#endif diff --git a/LED.H b/LED.H new file mode 100644 index 0000000..a81abf7 --- /dev/null +++ b/LED.H @@ -0,0 +1,46 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\led.h_v 2.17 16 Oct 1995 16:45:50 JOE_BOSTIC $ */ + +#ifndef LED_H +#define LED_H + +class LEDClass +{ + public: + typedef enum ControlType { + LED_NOCHANGE, // Do nothing (just query). + LED_OFF, // Turn LED off. + LED_ON, // Turn LED on. + LED_TOGGLE // Toggle LED state. + } ControlType; + + protected: + static int Shift_Control(ControlType control, char bit); + + public: + static int Scroll_Lock(ControlType control=LED_TOGGLE) {return Shift_Control(control, 0x01);}; + static int Caps_Lock(ControlType control=LED_TOGGLE) {return Shift_Control(control, 0x02);}; + static int Num_Lock(ControlType control=LED_TOGGLE) {return Shift_Control(control, 0x04);}; + + private: + static void Send_To_Keyboard(unsigned char val); +}; + +#endif diff --git a/LINK.CPP b/LINK.CPP new file mode 100644 index 0000000..db57361 --- /dev/null +++ b/LINK.CPP @@ -0,0 +1,416 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\link.cpv 2.18 16 Oct 1995 16:52:18 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : LINK.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 19, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * LinkClass::LinkClass -- Default constructor for linked list object. * + * LinkClass::LinkClass -- Copy constructor for linked list object. * + * LinkClass::~LinkClass -- Default destructor for linked list object. * + * LinkClass::Zap -- Forces the link pointers to NULL. * + * LinkClass::operator= -- Assignment operator for linked list class object. * + * LinkClass::Get_Next -- Fetches the next object in list. * + * LinkClass::Get_Prev -- Fetches previous object in linked list. * + * LinkClass::Head_Of_List -- Finds the head of the list. * + * LinkClass::Tail_Of_List -- Scans for the object at the end of the list. * + * LinkClass::Add -- This object adds itself to the given list * + * LinkClass::Add_Head -- This gadget makes itself the head of the given list. * + * LinkClass::Add_Tail -- Add myself to the end of the given list. * + * LinkClass::Remove -- Removes the specified object from the list. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "link.h" + + +/*********************************************************************************************** + * LinkClass::LinkClass -- Default constructor for linked list object. * + * * + * This is the default constructor for a linked list object. It merely initializes the * + * next and previous pointers to NULL. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass::LinkClass(void) +{ + LinkClass::Zap(); +} + + +/*********************************************************************************************** + * LinkClass::LinkClass -- Copy constructor for linked list object. * + * * + * This copy constructor, unlike the assigment operator, doesn't have to deal with an * + * already initialized and legal link object to the left of the "=". It merely puts the * + * destination object into the same list as the source object. * + * * + * INPUT: link -- Reference to the object on the right of the "=". * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass::LinkClass(LinkClass & link) +{ + LinkClass::Zap(); + Add(link); +} + + +/*********************************************************************************************** + * LinkClass::~LinkClass -- Default destructor for linked list object. * + * * + * This default destructor will remove the object from any linked list it may be part of. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass::~LinkClass(void) +{ + Remove(); +} + + +/*********************************************************************************************** + * LinkClass::Zap -- Forces the link pointers to NULL. * + * * + * This routine will "zap" out the link pointers. This is usually necessary when the link * + * pointers start in an undefined state, but we KNOW that they aren't pointing to anything * + * valid. In such a case it becomes necessary to zap them so that when the object is added * + * to a list, it will be added corrrectly. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void LinkClass::Zap(void) +{ + Next = 0; + Prev = 0; +} + + +/*********************************************************************************************** + * LinkClass::operator= -- Assignment operator for linked list class object. * + * * + * The assignment operator makes sure that the previous and next pointers remain valid. * + * Because this class only consists of pointers, the assignment operator doesn't actually * + * transfer any data from the source object. It merely makes the destination object part * + * of the same list as the source object. In essence, this is transferring information * + * but not the actual values. * + * * + * If the destination object is already part of another list, it is removed from that list * + * before being added to the source object's list. This ensures that either list remains * + * in a valid condition. * + * * + * INPUT: link -- The object to the right of the "=" operator. * + * * + * OUTPUT: Returns a reference to the rightmost object -- per standard assigment rules. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass & LinkClass::operator=(LinkClass & link) +{ + Remove(); + Add(link); + return(link); +} + + +/*********************************************************************************************** + * LinkClass::Get_Next -- Fetches the next object in list. * + * * + * This routine will return with a pointer to the next object in the list. If there are * + * no more objects, then NULL is returned. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with pointer to next object in list or NULL if at end of list. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass * LinkClass::Get_Next(void) const +{ + return(Next); +} + + +/*********************************************************************************************** + * LinkClass::Get_Prev -- Fetches previous object in linked list. * + * * + * Use this routine to get a pointer to the previous object in the linked list. If there * + * are no previous objects (such as at the head of the list), then NULL is returned. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the previous object in the list or NULL if none. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass * LinkClass::Get_Prev(void) const +{ + return(Prev); +} + + +/*********************************************************************************************** + * LinkClass::Head_Of_List -- Finds the head of the list. * + * * + * Use this routine to scan for and return a reference to the object at the head of the * + * list. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a reference to the object at the head of the list. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass const & LinkClass::Head_Of_List(void) const +{ + LinkClass const * link = this; + while (link->Prev) { + link = link->Prev; + if (link == this) break; // Safety check + } + return(*link); +} + + +/*********************************************************************************************** + * LinkClass::Tail_Of_List -- Scans for the object at the end of the list. * + * * + * Use this routine to scan for and return a reference to the object at the end of the * + * list. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a reference to the object at the end of the list. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass const & LinkClass::Tail_Of_List(void) const +{ + LinkClass const * link = this; + while (link->Next) { + link = link->Next; + if (link == this) break; // Safety check + } + return(*link); +} + + +/*********************************************************************************************** + * LinkClass::Add -- This object adds itself to the given list * + * * + * Use this routine to add a link object to the list, but to be added right after the * + * given link. This allows inserting a link in the middle of the chain. A quite necessary * + * ability if the chain is order dependant (e.g., the gadget system). * + * * + * INPUT: list -- gadget object to add this one to * + * * + * OUTPUT: Returns with a pointer to the head of the list. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass & LinkClass::Add(LinkClass & list) +{ + LinkClass *ptr; + + /* + ** Save ptr to next gadget. + */ + ptr = list.Next; + + /* + ** Link myself in after 'list'. + */ + list.Next = this; + Prev = &list; + + /* + ** Link myself to next gadget, if there is one. + */ + Next = ptr; + if (ptr) { + ptr->Prev = this; + } + + return(Head_Of_List()); +} + + +/*********************************************************************************************** + * LinkClass::Add_Head -- This gadget makes itself the head of the given list. * + * * + * INPUT: list -- the list to make myself the head of * + * * + * OUTPUT: Returns with a reference to the object at the head of the list. This should be * + * the same object that is passed in. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass & LinkClass::Add_Head(LinkClass & list) +{ + LinkClass *ptr; + + /* + ** Get head of given list. + */ + ptr = &list.Head_Of_List(); + + /* + ** Link myself in front of it. + */ + ptr->Prev = this; + Next = ptr; + Prev = NULL; + + return(*this); +} + + +/*********************************************************************************************** + * LinkClass::Add_Tail -- Add myself to the end of the given list. * + * * + * INPUT: list -- list to add myself to * + * * + * OUTPUT: the head of the list * + * * + * WARNINGS: The previous and next pointers for the added object MUST have been properly * + * initialized for this routine to work correctly. * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass & LinkClass::Add_Tail(LinkClass & list) +{ + LinkClass *ptr; + + /* + ** Get head of given list. + */ + ptr = &list.Tail_Of_List(); + + /* + ** Link myself in front of it. + */ + ptr->Next = this; + Prev = ptr; + Next = NULL; + + return(Head_Of_List()); +} + + +/*********************************************************************************************** + * LinkClass::Remove -- Removes the specified object from the list. * + * * + * This routine will remove the specified object from the list of objects. Because of the * + * previous and next pointers, it is possible to remove an object from the list without * + * knowing the head of the list. To do this, just call Remove() with the parameter of * + * "this". * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the new head of list. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass * LinkClass::Remove(void) +{ + LinkClass * head = &Head_Of_List(); + LinkClass * tail = &Tail_Of_List(); + + if (Prev) { + Prev->Next = Next; + } + if (Next) { + Next->Prev = Prev; + } + Prev = 0; + Next = 0; + + if (head==this) { + if (tail==this) { + return(0); + } + return(&tail->Head_Of_List()); + } + return(head); +} + + diff --git a/LINK.H b/LINK.H new file mode 100644 index 0000000..8a975f9 --- /dev/null +++ b/LINK.H @@ -0,0 +1,81 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\link.h_v 2.17 16 Oct 1995 16:45:52 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : LINK.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 15, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef LINK_H +#define LINK_H + +/* +** This implements a simple linked list. It is possible to add, remove, and traverse the +** list. Since this is a doubly linked list, it is possible to remove an entry from the +** middle of an existing list. +*/ +class LinkClass +{ + public: + LinkClass(void); + virtual ~LinkClass(void); + + virtual LinkClass * Get_Next(void) const; + virtual LinkClass * Get_Prev(void) const; + virtual LinkClass & Add(LinkClass & object); + virtual LinkClass & Add_Tail(LinkClass & object); + virtual LinkClass & Add_Head(LinkClass & object); + virtual LinkClass const & Head_Of_List(void) const; + virtual LinkClass & Head_Of_List(void) + { + return (LinkClass &)(((LinkClass const *)this)->Head_Of_List()); + }; + virtual LinkClass const & Tail_Of_List(void) const; + virtual LinkClass & Tail_Of_List(void) + { + return (LinkClass &)(((LinkClass const *)this)->Tail_Of_List()); + }; + virtual void Zap(void); + virtual LinkClass * Remove(void); + + LinkClass & operator=(LinkClass & link); // Assignment operator. + LinkClass(LinkClass & link); // Copy constructor. + + private: + /* + ** Pointers to previous and next link objects in chain. + */ + LinkClass * Next; + LinkClass * Prev; +}; + +#endif diff --git a/LIST.CPP b/LIST.CPP new file mode 100644 index 0000000..47bf758 --- /dev/null +++ b/LIST.CPP @@ -0,0 +1,897 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\list.cpv 2.17 16 Oct 1995 16:51:36 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : LIST.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : June 25, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * ListClass::Add -- This object adds itself to the given list * + * ListClass::Add_Head -- This gadget makes itself the head of the given list. * + * ListClass::Add_Item -- Adds a text item (as number) to the list box. * + * ListClass::Add_Item -- Adds an item to the list box. * + * ListClass::Add_Scroll_Bar -- Adds a scroll bar to the list box. * + * ListClass::Add_Tail -- Add myself to the end of the given list. * + * ListClass::Bump -- Bumps the list box up/down one "page". * + * ListClass::Current_Index -- Fetches the current selected index. * + * ListClass::Current_Item -- Fetches pointer to current item string. * + * ListClass::Draw_Entry -- Draws a list box text line as indicated. * + * ListClass::Draw_Me -- Draws the listbox. * + * ListClass::Get_Item -- Fetches an arbitrary item string. * + * ListClass::Peer_To_Peer -- A peer gadget was touched -- make adjustments. * + * ListClass::Remove -- Removes the specified object from the list. * + * ListClass::Remove_Item -- Remove specified text from list box. * + * ListClass::Remove_Scroll_Bar -- Removes the scroll bar if present * + * ListClass::Set_Selected_Index -- Set the top of the listbox to index specified. * + * ListClass::Set_Tabs -- Sets the tab stop list to be used for text printing. * + * ListClass::Set_View_Index -- Sets the top line for the current list view. * + * ListClass::Step -- Moves the list view one line in direction specified. * + * ListClass::Step_Selected_Index -- Change the listbox top line in direction specified. * + * ListClass::~ListClass -- Destructor for list class objects. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*************************************************************************** + * ListClass::ListClass -- class constructor * + * * + * INPUT: id button ID * + * * + * x,y upper-left corner, in pixels * + * * + * w,h width, height, in pixels * + * * + * list ptr to array of char strings to list* + * * + * flags, style flags for mouse, style of listbox * + * * + * OUTPUT: none. * + * * + * WARNINGS: none. * + * * + * HISTORY: 01/05/1995 MML : Created. * + *=========================================================================*/ +ListClass::ListClass (int id, int x, int y, int w, int h, TextPrintType flags, void const * up, void const * down) : + ControlClass(id, x, y, w, h, LEFTPRESS | LEFTRELEASE | KEYBOARD, false), + UpGadget(0, up, x+w, y), + DownGadget(0, down, x+w, y+h), + ScrollGadget(0, x+w, y, 0, h, true) +{ + /* + ** Set preliminary values for the slider related gadgets. They don't automatically + ** appear at this time, but there are some values that can be pre-filled in. + */ + UpGadget.X -= UpGadget.Width; + DownGadget.X -= DownGadget.Width; + DownGadget.Y -= DownGadget.Height; + ScrollGadget.X -= MAX(UpGadget.Width, DownGadget.Width); + ScrollGadget.Y = Y+UpGadget.Height; + ScrollGadget.Height -= UpGadget.Height + DownGadget.Height; + ScrollGadget.Width = MAX(UpGadget.Width, DownGadget.Width); + + /* + ** Set the list box to a default state. + */ + TextFlags = flags; + IsScrollActive = false; + Tabs = 0; + SelectedIndex = 0; + CurrentTopIndex = 0; + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, TextFlags); + LineHeight = FontHeight+FontYSpacing-1; + LineCount = (h-1) / LineHeight; +} + + +/*********************************************************************************************** + * ListClass::~ListClass -- Destructor for list class objects. * + * * + * This is the destructor for list objects. It handles removing anything it might have * + * allocated. This is typically the scroll bar. * + * * + * INPUT: none * + * OUTPUT: none * + * WARNINGS: none * + * HISTORY: 01/16/1995 JLB : Created. * + *=============================================================================================*/ +ListClass::~ListClass(void) +{ + Remove_Scroll_Bar(); +} + + +/*********************************************************************************************** + * ListClass::Add_Item -- Adds an item to the list box. * + * * + * This will add the specified string to the list box. The string is added to the end * + * of the list. * + * * + * INPUT: text -- Pointer to the string to add to the list box. * + * OUTPUT: none * + * WARNINGS: none * + * HISTORY: 01/15/1995 JLB : Created. * + *=============================================================================================*/ +int ListClass::Add_Item(char const * text) +{ + if (text) { + List.Add(text); + Flag_To_Redraw(); + + /* + ** Add scroll gadget if the list gets too large to display all of the items + ** at the same time. + */ + if (List.Count() > LineCount) { + Add_Scroll_Bar(); + } + + /* + ** Tell the slider that there is one more entry in the list. + */ + if (IsScrollActive) { + ScrollGadget.Set_Maximum(List.Count()); + } + } + return(List.Count() - 1); +} + + +/*********************************************************************************************** + * ListClass::Add_Item -- Adds a text item (as number) to the list box. * + * * + * This will add the text as specified by the text number provided, to the list box. * + * The string is added to the end of the list. * + * * + * INPUT: text -- The text number for the string to add to the list box. * + * OUTPUT: none * + * WARNINGS: Once a string is added to the list box in this fashion, there is no method of * + * retrieving the text number as it relates to any particular index in the list. * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +int ListClass::Add_Item(int text) +{ + if (text != TXT_NONE) { + Add_Item(Text_String(text)); + } + return(List.Count() - 1); +} + + +/*********************************************************************************************** + * ListClass::Remove_Item -- Remove specified text from list box. * + * * + * This routine will remove the specified text string from the list box. * + * * + * INPUT: text -- Pointer to the string to remove. * + * OUTPUT: none * + * WARNINGS: The text pointer passed into this routine MUST be the same text pointer that * + * was used to add the string to the list. * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +void ListClass::Remove_Item(char const * text) +{ + if (text) { + List.Delete(text); + + /* + ** If the list is now small enough to display completely within the list box region, + ** then delete the slider gadget (if they are present). + */ + if (List.Count() <= LineCount) { + Remove_Scroll_Bar(); + } + + /* + ** Tell the slider that there is one less entry in the list. + */ + if (IsScrollActive) { + ScrollGadget.Set_Maximum(List.Count()); + } + + /* + ** If we just removed the selected entry, select the previous one + */ + if (SelectedIndex >= List.Count()) { + SelectedIndex--; + if (SelectedIndex < 0) + SelectedIndex = 0; + } + + /* + ** If we just removed the top-displayed entry, step up one item + */ + if (CurrentTopIndex >= List.Count()) { + CurrentTopIndex--; + if (CurrentTopIndex < 0) + CurrentTopIndex = 0; + if (IsScrollActive) + ScrollGadget.Step(1); + } + } +} + + +/*************************************************************************** + * ListClass::Action -- If clicked on, do this! * + * * + * INPUT: int flags -- combination of mouse flags indicating * + * what action to take. * + * * + * OUTPUT: bool result. * + * * + * WARNINGS: none. * + * * + * HISTORY: 01/05/1995 MML : Created. * + *=========================================================================*/ +int ListClass::Action(unsigned flags, KeyNumType & key) +{ + if (flags & LEFTRELEASE) { + key = KN_NONE; + flags &= (~LEFTRELEASE); + ControlClass::Action(flags, key); + return(true); + } else { + + /* -------------------------------------------------- + ** Handle keyboard events here. + */ + if (flags & KEYBOARD) { + + /* + ** Process the keyboard character. If indicated, consume this keyboard event + ** so that the edit gadget ID number is not returned. + */ + if (key == KN_UP) { + Step_Selected_Index(-1); + key = KN_NONE; + } else if (key == KN_DOWN) { + Step_Selected_Index(1); + key = KN_NONE; + } else { + flags &= ~KEYBOARD; + } + + } else { + + int index = Get_Mouse_Y() - (Y+1); + index = index / LineHeight; + SelectedIndex = CurrentTopIndex + index; + SelectedIndex = MIN(SelectedIndex, List.Count()-1); + } + } + return(ControlClass::Action(flags, key)); +} + + +/*********************************************************************************************** + * ListClass::Draw_Me -- Draws the listbox. * + * * + * This routine will render the listbox. * + * * + * INPUT: forced -- Should the listbox be redrawn even if it already thinks it doesn't * + * need to be? This is true when something outside of the gadget system * + * has trashed the screen. * + * * + * OUTPUT: Was the listbox redrawn? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +int ListClass::Draw_Me(int forced) +{ + if (GadgetClass::Draw_Me(forced)) { + + /* + ** Turn off the mouse. + */ + if (LogicPage == &SeenBuff) { + Conditional_Hide_Mouse(X, Y, X+Width, Y+Height); + } + + Draw_Box (X, Y, Width, Height, BOXSTYLE_GREEN_BOX, true); + + /* + ** Draw List. + */ + if (List.Count()) { + for (int index = 0; index < LineCount; index++) { + int line = CurrentTopIndex + index; + + if (List.Count() > line) { + + /* + ** Prints the text and handles right edge clipping and tabs. + */ + Draw_Entry(line, X+1, Y+(LineHeight*index)+1, Width-2, (line == SelectedIndex)); + } + } + } + + /* + ** Turn on the mouse. + */ + if (LogicPage == &SeenBuff) { + Conditional_Show_Mouse(); + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * ListClass::Bump -- Bumps the list box up/down one "page". * + * * + * Use this routine to adjust the "page" that is being viewed in the list box. The view * + * will move up or down (as specified) one page (screen full) of text strings. * + * * + * INPUT: up -- Should the adjustment be up? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +void ListClass::Bump(int up) +{ + if (IsScrollActive) { + if (ScrollGadget.Step(up)) { + CurrentTopIndex = ScrollGadget.Get_Value(); + Flag_To_Redraw(); + } + } +} + + +/*********************************************************************************************** + * ListClass::Step -- Moves the list view one line in direction specified. * + * * + * This routine will move the current view "page" one line in the direction specified. * + * * + * INPUT: up -- Should the view be moved upward? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +void ListClass::Step(int up) +{ + if (IsScrollActive) { + if (ScrollGadget.Step(up)) { + CurrentTopIndex = ScrollGadget.Get_Value(); + Flag_To_Redraw(); + } + } +} + + +/*********************************************************************************************** + * ListClass::Get_Item -- Fetches an arbitrary item string. * + * * + * This routine will fetch an item string from the list box. The item fetched can be any * + * one of the ones in the list. * + * * + * INPUT: index -- The index to examine and return the text pointer from. * + * * + * OUTPUT: Returns with the text pointer to the string at the index position specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +char const * ListClass::Get_Item(int index) const +{ + if (List.Count() == 0) return (NULL); + + index = MIN(index, List.Count()-1 ); + return(List[index]); +} + + +/*********************************************************************************************** + * ListClass::Current_Item -- Fetches pointer to current item string. * + * * + * This routine will fetch a pointer to the currently selected item's text. * + * * + * INPUT: none * + * * + * OUTPUT: Return with pointer to currently selected text. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +char const * ListClass::Current_Item(void) +{ + return(List[SelectedIndex]); +} + + +/*********************************************************************************************** + * ListClass::Current_Index -- Fetches the current selected index. * + * * + * This routine will fetch the index number for the currently selected line. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the index of the currently selected line. This ranges from zero to * + * the number of items in the list minus one. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +int ListClass::Current_Index(void) +{ + return(SelectedIndex); +} + + +/*********************************************************************************************** + * ListClass::Peer_To_Peer -- A peer gadget was touched -- make adjustments. * + * * + * This routine is called when one of the peer gadgets (the scroll arrows or the slider) * + * was touched in some fashion. This routine will sort out whom and why and then make * + * any necessary adjustments to the list box. * + * * + * INPUT: flags -- The event flags that affected the peer gadget. * + * * + * key -- The key value at the time of the event. * + * * + * whom -- Which gadget is being touched. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +void ListClass::Peer_To_Peer(unsigned flags, KeyNumType &, ControlClass & whom) +{ + if (flags & LEFTRELEASE) { + if (&whom == &UpGadget) { + Step(true); + } + if (&whom == &DownGadget) { + Step(false); + } + } + + /* + ** The slider has changed, so reflect the current list position + ** according to the slider setting. + */ + if (&whom == &ScrollGadget) { + Set_View_Index(ScrollGadget.Get_Value()); + } +} + + +/*********************************************************************************************** + * ListClass::Set_View_Index -- Sets the top line for the current list view. * + * * + * This routine is used to set the line that will be at the top of the list view. This is * + * how the view can be scrolled up and down. This does not affect the currently selected * + * item. * + * * + * INPUT: index -- The line (index) to move to the top of the list view. * + * * + * OUTPUT: bool; Was the view actually changed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +int ListClass::Set_View_Index(int index) +{ + index = Bound(index, 0, List.Count() - LineCount); + if (index != CurrentTopIndex) { + CurrentTopIndex = index; + Flag_To_Redraw(); + if (IsScrollActive) { + ScrollGadget.Set_Value(CurrentTopIndex); + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * ListClass::Add_Scroll_Bar -- Adds a scroll bar to the list box. * + * * + * This routine will add a scroll bar (with matching arrows) to the list box. They are * + * added to the right edge and cause the interior of the list box to become narrower. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the scroll bar added? * + * * + * WARNINGS: The list box becomes narrower when the scroll bar is added. * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +int ListClass::Add_Scroll_Bar(void) +{ + if (!IsScrollActive) { + IsScrollActive = true; + + /* + ** Everything has been created successfully. Flag the list box to be + ** redrawn because it now must be made narrower to accomodate the new + ** slider gadgets. + */ + Flag_To_Redraw(); + Width -= ScrollGadget.Width; + + /* + ** Tell the newly created gadgets that they should inform this list box + ** whenever they get touched. In this way, the list box will automatically + ** be updated under control of the slider buttons. + */ + UpGadget.Make_Peer(*this); + DownGadget.Make_Peer(*this); + ScrollGadget.Make_Peer(*this); + + /* + ** Add these newly created gadgets to the same gadget list that the + ** list box is part of. + */ + UpGadget.Add(*this); + DownGadget.Add(*this); + ScrollGadget.Add(*this); + + /* + ** Make sure these added gadgets get redrawn at the next opportunity. + */ + UpGadget.Flag_To_Redraw(); + DownGadget.Flag_To_Redraw(); + ScrollGadget.Flag_To_Redraw(); + + /* + ** Inform the slider of the size of the window and the current view position. + */ + ScrollGadget.Set_Maximum(List.Count()); + ScrollGadget.Set_Thumb_Size(LineCount); + ScrollGadget.Set_Value(CurrentTopIndex); + + /* + ** Return with success flag. + */ + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * ListClass::Remove_Scroll_Bar -- Removes the scroll bar if present * + * * + * Use this routine to remove any attached scroll bar to this list box. If the scroll bar * + * is not present, then no action occurs. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the scroll bar removed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +int ListClass::Remove_Scroll_Bar(void) +{ + if (IsScrollActive) { + IsScrollActive = false; + Width += ScrollGadget.Width; + ScrollGadget.Remove(); + UpGadget.Remove(); + DownGadget.Remove(); + Flag_To_Redraw(); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * ListClass::Set_Tabs -- Sets the tab stop list to be used for text printing. * + * * + * This sets the tab stop list to be used for text printing. It specifies a series of * + * pixel offsets for each tab stop. The offsets are from the starting pixel position that * + * the text begins at. * + * * + * INPUT: tabs -- Pointer to a list of tab pixel offsets. * + * * + * OUTPUT: none * + * * + * WARNINGS: Only a pointer to the tabs is recorded by the ListClass object. Make sure that * + * the list remains intact for the duration of the existence of this object. * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +void ListClass::Set_Tabs(int const * tabs) +{ + Tabs = tabs; +} + + +/*********************************************************************************************** + * ListClass::Draw_Entry -- Draws a list box text line as indicated. * + * * + * This routine is called by the Draw_Me function when it desired to redraw a particular * + * text line in the list box. * + * * + * INPUT: index -- The index of the list entry to draw. This index is based on the * + * total list and NOT the current visible view page. * + * * + * x,y -- Pixel coordinates for the upper left corner of the text entry. * + * * + * width -- The maximum width that the text may draw over. It is expected that * + * this drawing routine entirely fills this length. * + * * + * selected -- bool; Is this a selected (highlighted) listbox entry? * + * * + * OUTPUT: none * + * WARNINGS: none * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +void ListClass::Draw_Entry(int index, int x, int y, int width, int selected) +{ + if (TextFlags & TPF_6PT_GRAD) { + TextPrintType flags = TextFlags; + + if (selected) { + flags = flags | TPF_BRIGHT_COLOR; + LogicPage->Fill_Rect (x, y, x + width - 1, y + LineHeight - 1, CC_GREEN_SHADOW); + } else { + if (!(flags & TPF_USE_GRAD_PAL)) { + flags = flags | TPF_MEDIUM_COLOR; + } + } + + Conquer_Clip_Text_Print(List[index], x, y, CC_GREEN, TBLACK, flags, width, Tabs); + + } else { + Conquer_Clip_Text_Print(List[index], x, y, (selected ? BLUE : WHITE), TBLACK, TextFlags, width, Tabs); + } +} + + +/*********************************************************************************************** + * ListClass::Add -- Adds myself to list immediately after given object * + * * + * Adds the list box to the chain, immemdiately after the given object. The order will be: * + * - Listbox * + * - Up arrow (if active) * + * - Down arrow (if active) * + * - Scroll gadget (if active) * + * * * + * INPUT: object -- Pointer to the object to be added right after this one. * + * * + * OUTPUT: Returns with a pointer to the head of the list. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass & ListClass::Add(LinkClass & list) +{ + /* + ** Add the scroll bar gadgets if they're active. + */ + if (IsScrollActive) { + ScrollGadget.Add(list); + DownGadget.Add(list); + UpGadget.Add(list); + } + + /* + ** Add myself to the list, then return. + */ + return(ControlClass::Add(list)); +} + + +/*********************************************************************************************** + * ListClass::Add_Head -- Adds myself to head of the given list * + * * + * INPUT: list -- list to add myself to * + * * + * OUTPUT: Returns with a reference to the object at the head of the list. This should be * + * the same object that is passed in. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass & ListClass::Add_Head(LinkClass & list) +{ + /* + ** Add the scroll bar gadgets if they're active. + */ + if (IsScrollActive) { + ScrollGadget.Add_Head(list); + DownGadget.Add_Head(list); + UpGadget.Add_Head(list); + } + + /* + ** Add myself to the list, then return. + */ + return(ControlClass::Add_Head(list)); +} + + +/*********************************************************************************************** + * ListClass::Add_Tail -- Adds myself to tail of given list * + * * + * Adds the list box to the tail of the give chain. The order will be: * + * - Listbox * + * - Up arrow (if active) * + * - Down arrow (if active) * + * - Scroll gadget (if active) * + * * + * INPUT: list -- list to add myself to * + * * + * OUTPUT: none * + * * + * WARNINGS: The previous and next pointers for the added object MUST have been properly * + * initialized for this routine to work correctly. * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +LinkClass & ListClass::Add_Tail(LinkClass & list) +{ + /* + ** Add myself to the list. + */ + ControlClass::Add_Tail(list); + + /* + ** Add the scroll bar gadgets if they're active. + */ + if (IsScrollActive) { + UpGadget.Add_Tail(list); + DownGadget.Add_Tail(list); + ScrollGadget.Add_Tail(list); + } + + return(Head_Of_List()); +} + + +/*********************************************************************************************** + * ListClass::Remove -- Removes the specified object from the list. * + * * + * This routine will remove the specified object from the list of objects. Because of the * + * previous and next pointers, it is possible to remove an object from the list without * + * knowing the head of the list. To do this, just call Remove() with the parameter of * + * "this". * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the new head of list. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +GadgetClass * ListClass::Remove(void) +{ + /* + ** Remove the scroll bar if it's active + */ + if (IsScrollActive) { + ScrollGadget.Remove(); + DownGadget.Remove(); + UpGadget.Remove(); + } + + /* + ** Remove myself & return + */ + return(ControlClass::Remove()); +} + + +/*********************************************************************************************** + * ListClass::Set_Selected_Index -- Set the top of the listbox to index specified. * + * * + * This routine will set the top line of the listbox to the index value specified. * + * * + * INPUT: index -- The index to set the top of the listbox to. * + * * + * OUTPUT: none * + * * + * WARNINGS: The requested index may be adjusted to fit within legal parameters. * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +void ListClass::Set_Selected_Index(int index) +{ + if ((unsigned)index < List.Count()) { + SelectedIndex = index; + Flag_To_Redraw(); + if (SelectedIndex < CurrentTopIndex) { + Set_View_Index(SelectedIndex); + } + if (SelectedIndex >= CurrentTopIndex+LineCount) { + Set_View_Index(SelectedIndex-(LineCount-1)); + } + } +} + + +/*********************************************************************************************** + * ListClass::Step_Selected_Index -- Change the listbox top line in direction specified. * + * * + * This routine will scroll the top line of the listbox in the direction specified. * + * * + * INPUT: step -- The direction (and amount) to adjust the listbox. If negative value, then * + * the top line is scrolled upward. * + * * + * OUTPUT: Returns with the original top line index number. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +int ListClass::Step_Selected_Index(int step) +{ + int old = SelectedIndex; + + Set_Selected_Index(old + step); + return(old); +} diff --git a/LIST.H b/LIST.H new file mode 100644 index 0000000..279fa83 --- /dev/null +++ b/LIST.H @@ -0,0 +1,149 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\list.h_v 2.17 16 Oct 1995 16:46:24 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : LIST.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 15, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef LIST_H +#define LIST_H + +#include "control.h" +#include "shapebtn.h" +#include "slider.h" + + +/*************************************************************************** + * ListClass -- Like a Windows ListBox structure * + * * + * INPUT: int x -- x position of gadget * + * int y -- y position of gadget * + * int w -- width of gadget * + * int h -- height of gadget * + * UWORD flags -- see enumeration choices * + * * + * OUTPUT: none. * + * WARNINGS: * + * HISTORY: 01/03/1995 MML : Created. * + *=========================================================================*/ +class ListClass : public ControlClass +{ + public: + ListClass(int id, int x, int y, int w, int h, TextPrintType flags, void const * up, void const * down); + virtual ~ListClass(void); + +// static ListClass * Create_One_Of(int id, int x, int y, int w, int h, TextPrintType flags, void const * up, void const * down); + virtual int Add_Item(char const * text); + virtual int Add_Item(int text); + virtual int Add_Scroll_Bar(void); + virtual void Bump(int up); + virtual int Count(void) {return List.Count();}; + virtual int Current_Index(void); + virtual char const * Current_Item(void); + virtual int Draw_Me(int forced); + virtual char const * Get_Item(int index) const; + virtual int Step_Selected_Index(int forward); + + virtual void Peer_To_Peer(unsigned flags, KeyNumType & key, ControlClass & whom); + virtual void Remove_Item(char const * text); + virtual int Remove_Scroll_Bar(void); + virtual void Set_Selected_Index(int index); + virtual void Set_Tabs(int const * tabs); + virtual int Set_View_Index(int index); + virtual void Step(int up); + + /* + ** These overloaded list routines handle adding/removing the scroll bar + ** automatically when the list box is added or removed. + */ + virtual LinkClass & Add(LinkClass & object); + virtual LinkClass & Add_Tail(LinkClass & object); + virtual LinkClass & Add_Head(LinkClass & object); + virtual GadgetClass * Remove(void); + + protected: + virtual int Action(unsigned flags, KeyNumType &key); + virtual void Draw_Entry(int index, int x, int y, int width, int selected); + + /* + ** This controls what the text looks like. It uses the basic TPF_ flags that + ** are used to control Fancy_Text_Print(). + */ + TextPrintType TextFlags; + + /* + ** This is a series of tabstop pixel positions to use when processing any + ** characters found in a list box string. The tabs are a series of + ** pixel offsets from the starting pixel position of the text. + */ + int const *Tabs; + + /* + ** The actual list of text pointers is maintained by this list manager. The pointers + ** are stored in EMS. The text that is pointed to may also be in EMS. + */ + DynamicVectorClass List; + //EMSListOf List; + + /* + ** This is the total pixel height of a standar line of text. This is greatly + ** influenced by the TextFlags value. + */ + int LineHeight; + + /* + ** This is the number of text lines that can fit within the list box. + */ + int LineCount; + + /* + ** If the slider bar has been created, these point to the respective gadgets + ** that it is composed of. + */ + unsigned IsScrollActive:1; + ShapeButtonClass UpGadget; + ShapeButtonClass DownGadget; + SliderClass ScrollGadget; + + /* + ** This is the currently selected index. It is highlighted. + */ + int SelectedIndex; + + /* + ** This specifies the line (index) that is at the top of the list box. + */ + int CurrentTopIndex; +}; + +#endif diff --git a/LOADDLG.CPP b/LOADDLG.CPP new file mode 100644 index 0000000..33b3178 --- /dev/null +++ b/LOADDLG.CPP @@ -0,0 +1,734 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\loaddlg.cpv 2.18 16 Oct 1995 16:51:18 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : LOADDLG.CPP * + * * + * Programmer : Maria Legg, Joe Bostic, Bill Randolph * + * * + * Start Date : March 19, 1995 * + * * + * Last Update : June 25, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * LoadOptionsClass::Clear_List -- clears the list box & Files arrays * + * LoadOptionsClass::Compare -- for qsort * + * LoadOptionsClass::Fill_List -- fills the list box & GameNum arrays * + * LoadOptionsClass::LoadOptionsClass -- class constructor * + * LoadOptionsClass::Num_From_Ext -- clears the list box & GameNum arrays * + * LoadOptionsClass::Process -- main processing routine * + * LoadOptionsClass::~LoadOptionsClass -- class destructor * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include // for unlink + + +/*********************************************************************************************** + * LoadOptionsClass::LoadOptionsClass -- class constructor * + * * + * INPUT: * + * style style for this load/save dialog (LOAD/SAVE/DELETE) * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +LoadOptionsClass::LoadOptionsClass(LoadStyleType style) +{ + Style = style; + Files.Clear(); +} + + +/*********************************************************************************************** + * LoadOptionsClass::~LoadOptionsClass -- class destructor * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +LoadOptionsClass::~LoadOptionsClass() +{ + for (int i = 0; i < Files.Count(); i++) { + delete Files[i]; + } + Files.Clear(); +} + + +/*********************************************************************************************** + * LoadOptionsClass::Process -- main processing routine * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * false = User cancelled, true = operation completed * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +int LoadOptionsClass::Process(void) +{ + /* + ** Dialog & button dimensions + */ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + int d_dialog_w = 250 *factor; + int d_dialog_h = 156 * factor; + int d_dialog_x = (SeenBuff.Get_Width() - d_dialog_w) >> 1; + int d_dialog_y = (SeenBuff.Get_Height() - d_dialog_h) >> 1; + int d_dialog_cx = d_dialog_x + (d_dialog_w >> 1); + int d_txt8_h = 11 * factor; + int d_margin = 7 * factor; + + int d_list_w = d_dialog_w - (d_margin * 2); + int d_list_h = 104 * factor; + int d_list_x = d_dialog_x + d_margin; + int d_list_y = d_dialog_y + d_margin + d_txt8_h + d_margin; + + int d_edit_w = d_dialog_w - (d_margin * 2); + int d_edit_h = 13 * factor; + int d_edit_x = d_dialog_x + d_margin; + int d_edit_y = d_list_y + d_list_h - (30 * factor) + d_margin + d_txt8_h; + +#ifdef german + int d_button_w = 50 * factor; +#else + int d_button_w = 40 * factor; +#endif + int d_button_h = 13 * factor; + int d_button_x = d_dialog_cx - d_button_w - d_margin; + int d_button_y = d_dialog_y + d_dialog_h - d_button_h - d_margin; + +#ifdef german + int d_cancel_w = 50 * factor; +#else + int d_cancel_w = 40 * factor; +#endif + int d_cancel_h = 13 * factor; + int d_cancel_x = d_dialog_cx + d_margin; + int d_cancel_y = d_dialog_y + d_dialog_h - d_cancel_h - d_margin; +#if(0) + enum { + D_DIALOG_W = 250, // dialog width + D_DIALOG_H = 156, // dialog height + D_DIALOG_X = ((320 - D_DIALOG_W) / 2), // centered x-coord + D_DIALOG_Y = ((200 - D_DIALOG_H) / 2), // centered y-coord + D_DIALOG_CX = D_DIALOG_X + (D_DIALOG_W / 2), // coord of x-center + + D_TXT8_H = 11, // ht of 8-pt text + D_MARGIN = 7, // margin width/height + + D_LIST_W = D_DIALOG_W - (D_MARGIN * 2), + D_LIST_H = 104, + D_LIST_X = D_DIALOG_X + D_MARGIN, + D_LIST_Y = D_DIALOG_Y + D_MARGIN + D_TXT8_H + D_MARGIN, + + D_EDIT_W = D_DIALOG_W - (D_MARGIN * 2), + D_EDIT_H = 13, + D_EDIT_X = D_DIALOG_X + D_MARGIN, + D_EDIT_Y = D_LIST_Y + D_LIST_H - 30 + D_MARGIN + D_TXT8_H, + +#if (GERMAN | FRENCH) + D_BUTTON_W = 50, +#else + D_BUTTON_W = 40, +#endif + D_BUTTON_H = 13, + D_BUTTON_X = D_DIALOG_CX - D_BUTTON_W - D_MARGIN, + D_BUTTON_Y = D_DIALOG_Y + D_DIALOG_H - D_BUTTON_H - D_MARGIN, + +#if (GERMAN | FRENCH) + D_CANCEL_W = 50,//BG:40 +#else + D_CANCEL_W = 40, +#endif + D_CANCEL_H = 13, + D_CANCEL_X = D_DIALOG_CX + D_MARGIN, + D_CANCEL_Y = D_DIALOG_Y + D_DIALOG_H - D_CANCEL_H - D_MARGIN, + }; +#endif + /* + ** Button enumerations + */ + enum { + BUTTON_LOAD = 100, + BUTTON_SAVE, + BUTTON_DELETE, + BUTTON_CANCEL, + BUTTON_LIST, + BUTTON_EDIT, + }; + + /* + ** Redraw values: in order from "top" to "bottom" layer of the dialog + */ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /* + ** Dialog variables + */ + bool cancel = false; // true = user cancels + int list_ht = d_list_h; // adjusted list box height + + /* + ** Other Variables + */ + int btn_txt; // text on the 'OK' button + int btn_id; // ID of 'OK' button + int caption; // dialog caption + int game_idx = 0; // index of game to save/load/etc + int game_num = 0; // file number of game to load/save/etc + char game_descr[40] = {0}; // save-game description + char fname[13]; // for generating filename to delete + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + /* + ** Buttons + */ + ControlClass *commands = NULL; // the button list + + if (Style == LOAD) { + btn_txt = TXT_LOAD_BUTTON; + btn_id = BUTTON_LOAD; + caption = TXT_LOAD_MISSION; + } else { + if (Style == SAVE) { + btn_txt = TXT_SAVE_BUTTON; + btn_id = BUTTON_SAVE; + caption = TXT_SAVE_MISSION; + list_ht -= 30; + } else { + btn_txt = TXT_DELETE_BUTTON; + btn_id = BUTTON_DELETE; + caption = TXT_DELETE_MISSION; + } + } + + TextButtonClass button (btn_id, btn_txt, TPF_6PT_GRAD|TPF_CENTER|TPF_NOSHADOW, + d_button_x, d_button_y, d_button_w); + + TextButtonClass cancelbtn (BUTTON_CANCEL, TXT_CANCEL, TPF_6PT_GRAD|TPF_CENTER|TPF_NOSHADOW, + d_cancel_x, d_cancel_y, d_cancel_w); + + ListClass listbtn (BUTTON_LIST, d_list_x, d_list_y, d_list_w, list_ht, + TPF_6PT_GRAD | TPF_NOSHADOW, + up_button, + down_button); + + EditClass editbtn (BUTTON_EDIT, game_descr, 40, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW, + d_edit_x, d_edit_y, d_edit_w, -1, EditClass::ALPHANUMERIC); + + /* + ** Initialize. + */ + Set_Logic_Page(SeenBuff); + + Fill_List(&listbtn); + + /* + ** Do nothing if list is empty. + */ + if ((Style == LOAD || Style == WWDELETE) && listbtn.Count()==0) { + Clear_List(&listbtn); + CCMessageBox().Process(TXT_NO_SAVES); + return(false); + } + + /* + ** Create the button list. + */ + commands = &button; + cancelbtn.Add_Tail(*commands); + listbtn.Add_Tail(*commands); + if (Style == SAVE) { + editbtn.Add_Tail(*commands); + editbtn.Set_Focus(); + } + + /* + ** Main Processing Loop. + */ + bool firsttime = true; + bool display = true; + bool process = true; + while (process) { + + /* + ** Invoke game callback. + */ + if (GameToPlay == GAME_NORMAL) { + Call_Back(); + } else { + if (Main_Loop()) { + process = false; + cancel = true; + } + } + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=TRUE; + } + + /* + ** Refresh display if needed. + */ + if (display) { + + Hide_Mouse(); + /* + ** Redraw the map. + */ + if (InMainLoop){ + HiddenPage.Clear(); + Map.Flag_To_Redraw(true); + Map.Render(); + }else{ + HiddenPage.Clear(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + } + + + /* + ** Display the dialog box. + */ + if (display) { + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + Draw_Caption(caption, d_dialog_x, d_dialog_y, d_dialog_w); + + if (Style == SAVE) { + Fancy_Text_Print(TXT_MISSION_DESCRIPTION, d_dialog_cx, + d_edit_y - d_txt8_h, CC_GREEN, TBLACK, TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_CENTER | TPF_NOSHADOW); + } + } + + /* + ** Redraw the buttons. + */ + if (display) { + commands->Flag_List_To_Redraw(); + } + Show_Mouse(); + display = false; + } + + /* + ** Get user input. + */ + KeyNumType input = commands->Input(); + + /* + ** The first time through the processing loop, set the edit + ** gadget to have the focus if this is the save dialog. The + ** focus must be set here since the gadget list has changed + ** and this change will cause any previous focus setting to be + ** cleared by the input processing routine. + */ + if (firsttime && Style == SAVE) { + firsttime = false; + editbtn.Set_Focus(); + editbtn.Flag_To_Redraw(); + } + + /* + ** If the key was pressed, then default to the appropriate + ** action button according to the style of this dialog box. + */ + if (input == KN_RETURN) { + switch (Style) { + case SAVE: + input = (KeyNumType)(BUTTON_SAVE|KN_BUTTON); + break; + + case LOAD: + input = (KeyNumType)(BUTTON_LOAD|KN_BUTTON); + break; + + case WWDELETE: + input = (KeyNumType)(BUTTON_DELETE|KN_BUTTON); + break; + } + } + + /* + ** Process input. + */ + switch (input) { + /* + ** Load: if load fails, present a message, and stay in the dialog + ** to allow the user to try another game + */ + case (BUTTON_LOAD | KN_BUTTON): + game_idx = listbtn.Current_Index(); + game_num = Files[game_idx]->Num; + if (Files[game_idx]->Valid) { + CCMessageBox().Process(TXT_LOADING, TXT_NONE); + if (!Load_Game(game_num)) { + CCMessageBox().Process(TXT_ERROR_LOADING_GAME); + } else { + Hide_Mouse(); + VisiblePage.Clear(); + Set_Palette(GamePalette); + Show_Mouse(); + process = false; + } + } else { + CCMessageBox().Process(TXT_OBSOLETE_SAVEGAME); + } + break; + + /* + ** Save: Save the game & exit the dialog + */ + case (BUTTON_SAVE | KN_BUTTON): + if (!strlen(game_descr)) { + CCMessageBox().Process(TXT_MUSTENTER_DESCRIPTION); + firsttime = true; + display = true; + break; + } + game_idx = listbtn.Current_Index(); + if (Disk_Space_Available() < SAVE_GAME_DISK_SPACE && game_idx == 0) { +// CCMessageBox().Process("Insuficent disk space to save a game. Please delete a previous save to free up some disk space and try again."); + CCMessageBox().Process(TXT_SPACE_CANT_SAVE); + firsttime = true; + display = true; + break; + } + + game_num = Files[game_idx]->Num; + if (!Save_Game(game_num,game_descr)) { + CCMessageBox().Process(TXT_ERROR_SAVING_GAME); + } else { + CCMessageBox().Process(TXT_GAME_WAS_SAVED, TXT_NONE, TXT_NONE); + } + process = false; + break; + + /* + ** Delete: delete the file & stay in the dialog, to allow the user + ** to delete multiple files. + */ + case (BUTTON_DELETE | KN_BUTTON): + game_idx = listbtn.Current_Index(); + game_num = Files[game_idx]->Num; + if (CCMessageBox().Process(TXT_DELETE_FILE_QUERY,TXT_YES,TXT_NO)==0) { + sprintf(fname,"SAVEGAME.%03d",game_num); + unlink(fname); + Clear_List(&listbtn); + Fill_List(&listbtn); + if (listbtn.Count() == 0) { + process = false; + } + } + display = true; + break; + + /* + ** If the user clicks on the list, see if the there is a new current + ** item; if so, and if we're in SAVE mode, copy the list item into + ** the save-game description field. + */ + case (BUTTON_LIST | KN_BUTTON): + if (Style != SAVE) { + break; + } + + if (listbtn.Count() && listbtn.Current_Index() != game_idx) { + game_idx = listbtn.Current_Index(); + /* + ** Copy the game's description, UNLESS it's the empty slot; if + ** it is, set the edit buffer to empty. + */ + if (game_idx != 0) { + strcpy(game_descr,listbtn.Get_Item(game_idx)); + } else { + game_descr[0] = 0; + } + editbtn.Set_Text(game_descr,40); + } + break; + + /* + ** ESC/Cancel: break + */ + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + cancel = true; + process = false; + break; + + default: + break; + } + } + + Clear_List(&listbtn); + + if (cancel) return(false); + + return(true); +} + + +/*********************************************************************************************** + * LoadOptionsClass::Clear_List -- clears the list box & Files arrays * + * * + * This step is essential, because it frees all the strings allocated for list items. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +void LoadOptionsClass::Clear_List(ListClass *list) +{ + /* + ** For every item in the list, free its buffer & remove it from the list. + */ + int j = list->Count(); + for (int i = 0; i < j; i++) { + list->Remove_Item(list->Get_Item(0)); + } + + /* + ** Clear the array of game numbers + */ + for (i = 0; i < Files.Count(); i++) { + delete Files[i]; + } + Files.Clear(); +} + + +/*********************************************************************************************** + * LoadOptionsClass::Fill_List -- fills the list box & GameNum arrays * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + * 06/25/1995 JLB : Shows which saved games are "(old)". * + *=============================================================================================*/ +void LoadOptionsClass::Fill_List(ListClass *list) +{ + FileEntryClass *fdata; // for adding entries to 'Files' + char descr[DESCRIP_MAX]; + unsigned scenario; // scenario # + HousesType house; // house + struct find_t ff; // for _dos_findfirst + int id; + + /* + ** Make sure the list is empty + */ + Clear_List(list); + + /* + ** Add the Empty Slot entry + */ + if (Style == SAVE) { + fdata = new FileEntryClass; + strcpy(fdata->Descr,Text_String(TXT_EMPTY_SLOT)); + fdata->DateTime = 0xffffffff; // will always be first + Files.Add(fdata); + } + + /* + ** Find all savegame files + */ + int rc = _dos_findfirst("SAVEGAME.*", _A_NORMAL, &ff); + + while (!rc) { + /* + ** Extract the game ID from the filename + */ + id = Num_From_Ext(ff.name); + + /* + ** get the game's info; if success, add it to the list + */ + bool ok = Get_Savefile_Info(id, descr, &scenario, &house); + + fdata = new FileEntryClass; + + fdata->Descr[0] = '\0'; + if (!ok) strcpy(fdata->Descr, Text_String(TXT_OLD_GAME)); + strncat(fdata->Descr, descr, (sizeof(fdata->Descr)-strlen(fdata->Descr))-1); + fdata->Valid = ok; + fdata->Scenario = scenario; + fdata->House = house; + fdata->Num = id; + fdata->DateTime = (((unsigned long)ff.wr_date) << 16) | (unsigned long)ff.wr_time; + Files.Add(fdata); + + /* + ** Find the next file + */ + rc = _dos_findnext(&ff); + } + + /* + ** If saving a game, determine a unique file ID for the empty slot + */ + if (Style == SAVE) { + /* + ** Find an un-used number to associate with the Empty Slot by looking in + ** GameNum for each number from 0 to 'N', where 'N' is the # of entries + ** in the list; if any number isn't found, use that number; otherwise, + ** use 'N + 1'. + */ + for (int i = 0; i < Files.Count(); i++) { // i = the # we're searching for + id = -1; // mark as 'not found' + for (int j = 0; j < Files.Count(); j++) { // loop through all game ID's + if (Files[j]->Num==i) { // if found, mark as found + id = j; + break; + } + } + if (id == -1) break; // if ID not found, use this one + } + + Files[0]->Num = i; // set the empty slot's ID + } + + /* + ** Now sort the list in order of Date/Time (newest first, oldest last) + */ + qsort((void *)(&Files[0]), Files.Count(), sizeof(class FileEntryClass *), LoadOptionsClass::Compare); + + /* + ** Now add every file's name to the list box + */ + for (int i = 0; i < Files.Count(); i++) { + list->Add_Item(Files[i]->Descr); + } +} + + +/*********************************************************************************************** + * LoadOptionsClass::Num_From_Ext -- clears the list box & GameNum arrays * + * * + * INPUT: * + * fname filename to parse * + * * + * OUTPUT: * + * File number for this name. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +int LoadOptionsClass::Num_From_Ext(char *fname) +{ + char ext[_MAX_EXT]; + + _splitpath(fname, NULL, NULL, NULL, ext); + int num = atoi(ext + 1); // skip the '.' + return(num); +} + + +/*********************************************************************************************** + * LoadOptionsClass::Compare -- for qsort * + * * + * INPUT: * + * p1,p2 ptrs to elements to compare * + * * + * OUTPUT: * + * 0 = same, -1 = (*p1) goes BEFORE (*p2), 1 = (*p1) goes AFTER (*p2) * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +int LoadOptionsClass::Compare(const void *p1, const void *p2) +{ + class FileEntryClass *fe1,*fe2; + + fe1 = *((class FileEntryClass **)p1); + fe2 = *((class FileEntryClass **)p2); + + if (fe1->DateTime > fe2->DateTime) return(-1); + if (fe1->DateTime < fe2->DateTime) return(1); + return(0); +} diff --git a/LOADDLG.H b/LOADDLG.H new file mode 100644 index 0000000..4e7caa2 --- /dev/null +++ b/LOADDLG.H @@ -0,0 +1,92 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\loaddlg.h_v 2.17 16 Oct 1995 16:48:02 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : LOADDLG.H * + * * + * Programmer : Maria Legg, Joe Bostic, Bill Randolph * + * * + * Start Date : March 19, 1995 * + * * + * Last Update : March 19, 1995 * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef LOADDLG_H +#define LOADDLG_H + +class FileEntryClass { + public: + char Descr[40]; // save-game description + unsigned Scenario; // scenario # + HousesType House; // house + int Num; // save file number (from the extension) + unsigned long DateTime; // date/time stamp of file + bool Valid; // Is the scenario valid? +}; + +class LoadOptionsClass +{ + public: + /* + ** This defines the style of the dialog + */ + typedef enum OperationModeEnum { + NONE = 0, + LOAD, + SAVE, + WWDELETE, + } LoadStyleType; + + LoadOptionsClass (LoadStyleType style = LoadOptionsClass::NONE); + ~LoadOptionsClass (); + int Process (void); + + + protected: + /* + ** Internal routines + */ + void Clear_List (ListClass *list); // clears the list & game # array + void Fill_List (ListClass *list); // fills the list & game # array + int Num_From_Ext (char *fname); // translates filename to file # + static int Compare(const void *p1, const void *p2); // for qsort() + + /* + ** This is the requested style of the dialog + */ + LoadStyleType Style; + + /* + ** This is an array of pointers to FileEntryClass objects. These objects + ** are allocated on the fly as files are found, and pointers to them are + ** added to the vector list. Thus, all the objects must be free'd before + ** the vector list is cleared. This list is used for sorting the files + ** by date/time. + */ + DynamicVectorClass Files; +}; + + +#endif diff --git a/LOGIC.CPP b/LOGIC.CPP new file mode 100644 index 0000000..b846751 --- /dev/null +++ b/LOGIC.CPP @@ -0,0 +1,275 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\logic.cpv 2.17 16 Oct 1995 16:50:52 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : LOGIC.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 27, 1993 * + * * + * Last Update : December 23, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * LogicClass::AI -- Handles AI logic processing for game objects. * + * LogicClass::Debug_Dump -- Displays logic class status to the mono screen. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "logic.h" + +static unsigned FramesPerSecond=0; + +#ifdef CHEAT_KEYS + +static unsigned TotalFrames; +static unsigned FPSDivider = 1; +static unsigned AverageFramesPerSecond; + +/*********************************************************************************************** + * LogicClass::Debug_Dump -- Displays logic class status to the mono screen. * + * * + * This is a debugging support routine. It displays the current state of the logic class * + * to the monochrome monitor. It assumes that it is being called once per second. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Call this routine only once per second. * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + *=============================================================================================*/ +void LogicClass::Debug_Dump(MonoClass *mono) const +{ + #define RECORDCOUNT 40 + #define RECORDHEIGHT 21 + static struct { + int Graphic; + } _record[RECORDCOUNT]; + static int _framecounter = 0; + + TotalFrames+= FramesPerSecond; + AverageFramesPerSecond = TotalFrames/FPSDivider++; + + mono->Set_Cursor(21, 9); + mono->Print( + "ÚÄÄÄÄÄÄÄÄÄÄÂÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r" + "³Units.....³ ³Frame Rate: Avg: Frame: ³\r" + "³Infantry..³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´\r" + "³Aircraft..³ ³ ³\r" + "³Buildings.³ ³ ³\r" + "³Terrain...³ Ã ´\r" + "³Bullets...³ ³ ³\r" + "³Anims.....³ ³ ³\r" + "³Teams.....³ Ã Ä´\r" + "³Triggers..³ ³ ³\r" + "³Factories.³ ³ ³\r" + "³ ³ Ã ´\r" + "³ ³ ³ ³\r" + "ÀÄÄÄÄÄÄÄÄÄÄÁÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄ´Spare CPU TimeÃÄÄÄÄÄÄÄÄÄÄÄÄÙ\r"); + + _framecounter++; + mono->Set_Cursor(70, 10);mono->Printf("%ld", Frame); + if (ScenarioInit) { + mono->Set_Cursor(21, 9);mono->Printf("%d", ScenarioInit); + } + + mono->Set_Cursor(33, 10);mono->Printf("%3d", Units.Count()); + mono->Set_Cursor(33, 11);mono->Printf("%3d", Infantry.Count()); + mono->Set_Cursor(33, 12);mono->Printf("%3d", Aircraft.Count()); + mono->Set_Cursor(33, 13);mono->Printf("%3d", Buildings.Count()); + mono->Set_Cursor(33, 14);mono->Printf("%3d", Terrains.Count()); + mono->Set_Cursor(33, 15);mono->Printf("%3d", Bullets.Count()); + mono->Set_Cursor(33, 16);mono->Printf("%3d", Anims.Count()); + mono->Set_Cursor(33, 17);mono->Printf("%3d", Teams.Count()); + mono->Set_Cursor(33, 18);mono->Printf("%3d", Triggers.Count()); + mono->Set_Cursor(33, 19);mono->Printf("%3d", Factories.Count()); + + mono->Set_Cursor(48, 10);mono->Printf("%d", FramesPerSecond); + mono->Set_Cursor(58, 10);mono->Printf("%d", AverageFramesPerSecond); + + /* + ** Advance to the next recorded performance record. If the record buffer + ** is full then throw out the oldest record. + */ + memcpy(&_record[0], &_record[1], sizeof(_record[0])*(RECORDCOUNT-1)); + + /* + ** Fill in the data for the current frame's performance record. + */ + SpareTicks = MIN((long)SpareTicks, (long)TIMER_SECOND); + _record[RECORDCOUNT-1].Graphic = Fixed_To_Cardinal(RECORDHEIGHT, Cardinal_To_Fixed(TIMER_SECOND, SpareTicks)); + + /* + ** Draw the bars across the performance record screen. + */ + for (int column = 0; column < RECORDCOUNT; column++) { + for (int row = 1; row < RECORDHEIGHT; row += 2) { + static char _barchar[4] = {' ', 220, 0, 219}; + char str[2]; + int index = 0; + + index |= (_record[column].Graphic >= row) ? 0x01 : 0x00; + index |= (_record[column].Graphic >= row+1) ? 0x02: 0x00; + + str[1] = '\0'; + str[0] = _barchar[index]; + mono->Text_Print(str, 37+column, 21-(row/2)); + } + } + + SpareTicks = 0; + FramesPerSecond = 0; +} +#endif + + +/*********************************************************************************************** + * LogicClass::AI -- Handles AI logic processing for game objects. * + * * + * This routine is used to perform the AI processing for all game objects. This includes * + * all houses, factories, objects, and teams. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/29/1994 JLB : Created. * + * 12/17/1994 JLB : Must perform one complete pass rather than bailing early. * + * 12/23/1994 JLB : Esures that no object gets skipped if it was deleted. * + *=============================================================================================*/ +void LogicClass::AI(void) +{ + int index; + + FramesPerSecond++; + + /* + ** Crate regeneration is handled here. + */ + if (GameToPlay != GAME_NORMAL && CrateMaker && CrateTimer.Expired()) { + Map.Place_Random_Crate(); + CrateTimer = TICKS_PER_MINUTE * Random_Pick(7, 15); + } + + /* + ** Team AI is processed. + */ + for (index = 0; index < Teams.Count(); index++) { + Teams.Ptr(index)->AI(); + } + +// Heap_Dump_Check( "After Team AI" ); + + /* + ** AI for all sentient objects is processed. + */ + for (index = 0; index < Count(); index++) { + ObjectClass * obj = (*this)[index]; + + obj->AI(); + + /* + ** If the object was destroyed in the process of performing its AI, then + ** adjust the index so that no object gets skipped. + */ + if (obj != (*this)[index]) { +// if (!obj->IsActive) { + index--; + } + } + +// Heap_Dump_Check( "After Object AI" ); + + /* + ** A second pass through the sentient objects is required so that the appropriate scan + ** bits will be set for the owner house. + */ + for (index = 0; index < Units.Count(); index++) { + UnitClass const * unit = Units.Ptr(index); + if (unit->IsLocked && (GameToPlay != GAME_NORMAL || !unit->House->IsHuman || unit->IsDiscoveredByPlayer)) { + unit->House->NewUScan |= (1L << unit->Class->Type); + if (!unit->IsInLimbo) unit->House->NewActiveUScan |= (1L << unit->Class->Type); + } + } + for (index = 0; index < Infantry.Count(); index++) { + InfantryClass const * infantry = Infantry.Ptr(index); + if (infantry->IsLocked && (GameToPlay != GAME_NORMAL || !infantry->House->IsHuman || infantry->IsDiscoveredByPlayer)) { + infantry->House->NewIScan |= (1L << infantry->Class->Type); + if (!infantry->IsInLimbo) infantry->House->NewActiveIScan |= (1L << infantry->Class->Type); + } + } + for (index = 0; index < Aircraft.Count(); index++) { + AircraftClass const * aircraft = Aircraft.Ptr(index); + if (aircraft->IsLocked && (GameToPlay != GAME_NORMAL || !aircraft->House->IsHuman || aircraft->IsDiscoveredByPlayer)) { + aircraft->House->NewAScan |= (1L << aircraft->Class->Type); + if (!aircraft->IsInLimbo) aircraft->House->NewActiveAScan |= (1L << aircraft->Class->Type); + } + } + for (index = 0; index < Buildings.Count(); index++) { + BuildingClass const * building = Buildings.Ptr(index); + if (building->IsLocked && (GameToPlay != GAME_NORMAL || !building->House->IsHuman || building->IsDiscoveredByPlayer)) { + building->House->NewBScan |= (1L << building->Class->Type); + if (!building->IsInLimbo) building->House->NewActiveBScan |= (1L << building->Class->Type); + } + } + +// Heap_Dump_Check( "After Object AI 2" ); + + /* + ** Map related logic is performed. + */ + Map.Logic(); + +// Heap_Dump_Check( "After Map.Logic" ); + + /* + ** Factory processing is performed. + */ + for (index = 0; index < Factories.Count(); index++) { + Factories.Ptr(index)->AI(); + } + +// Heap_Dump_Check( "After Factory AI" ); + + /* + ** House processing is performed. + */ + for (HousesType house = HOUSE_FIRST; house < HOUSE_COUNT; house++) { + HouseClass * hptr = HouseClass::As_Pointer(house); + if (hptr && hptr->IsActive) { + hptr->AI(); + } + } + +// Heap_Dump_Check( "After House AI" ); +} + + diff --git a/LOGIC.H b/LOGIC.H new file mode 100644 index 0000000..e79dbca --- /dev/null +++ b/LOGIC.H @@ -0,0 +1,55 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\logic.h_v 2.17 16 Oct 1995 16:45:52 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : LOGIC.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 29, 1994 * + * * + * Last Update : May 29, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef LOGIC_H +#define LOGIC_H + +#include "layer.h" + +/*********************************************************************************************** +** Game logic processing is controlled by this class. The graphic and AI logic is handled +** separately so that on slower machines, the graphic display is least affected. +*/ +class LogicClass : public LayerClass +{ + public: + void AI(void); + #ifdef CHEAT_KEYS + void Debug_Dump(MonoClass *mono) const; + #endif +}; +#endif diff --git a/MAKEFILE b/MAKEFILE new file mode 100644 index 0000000..bef05c5 --- /dev/null +++ b/MAKEFILE @@ -0,0 +1,950 @@ +# +# Command & Conquer(tm) +# Copyright 2025 Electronic Arts Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +#* $Header$ +#*********************************************************************************************** +#*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** +#*********************************************************************************************** +#* * +#* Project Name : Command & Conquer * +#* * +#* File Name : MAKEFILE * +#* * +#* Programmer : Joe L. Bostic * +#* * +#* Start Date : 03/02/95 * +#* * +#* Last Update : March 2, 1995 [JLB] * +#* * +#*---------------------------------------------------------------------------------------------* +#* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * + +.SILENT +.OPTIMIZE +.ERASE + +#--------------------------------------------------------------------------- +# Verify user's environment +#--------------------------------------------------------------------------- + +!ifndef %WWFLAT +!error WWFLAT must be set to the root of the library directory. +!else +WWFLAT=$(%WWFLAT) +!endif + +!ifndef %WATCOM +#WATCOM=c:\projects\c&c\code\watcom +!error WATCOM must be set to the Watcom root directory. +!else +WATCOM=$(%WATCOM) +!endif + +!ifndef %CODEDIR +#CODEDIR=c:\projects\code +!error CODEDIR must be set to the root code directory. +!else +CODEDIR=$(%CODEDIR) +!endif + +!ifndef %CDDIR +!error CODEDIR must be set. +#CDDIR=..\cd +!else +CDDIR=$(%CDDIR) +!endif + +!ifndef %VQDIR +#VQDIR=c:\VQA +!error VQDIR must be set to the root VQ directory. +!else +VQDIR=$(%VQDIR) +!endif + +#--------------------------------------------------------------------------- +# Path macros: one path for each file type. +# These paths are used to tell make where to find/put each file type. +#--------------------------------------------------------------------------- +.asm: . +.c: . +.cpp: . +.h: . +.obj: $(%WWOBJ)obj +.lib: $(WWFLAT)\lib +.exe: ..\run + + +#=========================================================================== +# Compiler and assembler flags. +#=========================================================================== +CC_CFG = /i=$(VQDIR)\include # Includes player (VQ) directory. +CC_CFG += /i=$(WWFLAT)\INCLUDE # Normal library include directory. +CC_CFG += /i=$(WATCOM)\H\nt # Normal Watcom include directory. +CC_CFG += /i=$(WATCOM)\H # Normal Watcom include directory. +CC_CFG += /i=..\gcl510\H # Includes Greenleaf headers. + +VCT_CFG = /i=$(VQDIR)\include # Includes player (VQ) directory. +VCT_CFG += /i=$(WWFLAT)\INCLUDE # Normal library include directory. +VCT_CFG += /i=$(WATCOM)\H\nt # Normal Watcom include directory. +VCT_CFG += /i=$(WATCOM)\H # Normal Watcom include directory. +VCT_CFG += /i=..\gcl510\H # Includes Greenleaf headers. + +#CC_CFG += /DDOS4G # Must be defined for Greenleaf +#CC_CFG += /DGF_WATCOM_S # Must be defined for Greenleaf with /3s +#CC_CFG += /d3 # Debugging information. +#CC_CFG += /d1 # Debugging information. +#CC_CFG += /of+ # Generate traceable stack frames. +#CC_CFG += /DOPTION=$(%OPTION) # Optional option define. +#CC_CFG += /zp1 # Pack structures on byte boundary. +#CC_CFG += /5s # Pentium optimized stack calling conventions. +#CC_CFG += /xs # Exception handling enabled. +#CC_CFG += /s # Remove stack check calls. +#CC_CFG += /j # char is now signed. +#CC_CFG += /fh=$(%WWOBJ)conquer.pch # Use precompiled headers. +#CC_CFG += /we # Treat all warnings as errors. +#CC_CFG += /w8 # Most warnings enabled. +#CC_CFG += /ri # char and shorts are returned as int. +CC_CFG += /zq # Operate quietly. +#CC_CFG += /zm # Each routine to be in its own segment. +#CC_CFG += /zld # Disable autodependency information in object file. + +#CC_CFG += /bm # build target is a multi-thread environment +#CC_CFG += /mf # flat model +#CC_CFG += /ze # enable language extensions +#CC_CFG += /zw # create windows code + +#CC_CFG += /od # *** Disable all optimizations *** +#CC_CFG += /ol # Loop optimizations enabled. +#CC_CFG += /or # Reorder instructions for best pipeline usage. +#CC_CFG += /oe # Inline is enabled. +#CC_CFG += /oi # Expand intrisic functions inline. +#CC_CFG += /on # Allow numerically unstable operations. +#CC_CFG += /oo # Compile even if low on memory (i.e. less than 64meg). +#CC_CFG += /oa # Relax aliasing constraints. + +#CC_CFG += -bt=NT /i=q:\include -j -os -zz -W3 -d1 /5 -s -fh=c:\projects\code\conquer.pch +#CC_CFG += -bt=NT /i=q:\include -j -otexan -ol+ -zz -W3 -d1 /5 -s -fh=c:\projects\code\conquer.pch +#CC_CFG += -bt=NT /i=q:\include -zq -j -zz -W3 -d1 /5 -s -fh=c:\projects\code\conquer.pch +CC_CFG += -bt=NT /i=q:\include -j -W3 -zz -d1 -otxan -ol+ /5 -s -fh=d:\projects\ccgold\code\conquer.pch +#CC_CFG += -bt=NT /i=q:\include -j -W3 -zz -d2 -od /5 -s -fh=c:\projects\code\conquer.pch +#CC_CFG += -bt=95 /i=q:\include -j -W3 -hc -od -d3 /4 -s +#CC_CFG += -bt=NT /i=q:\include -j -W3 -d1 -orilt /4 -s -fh=c:\projects\code\conquer.pch -fhq +#CC_CFG += -bt=NT /i=q:\include -j -W3 -d2 -orilt /4 -s -ep -ee -fh=c:\projects\code\conquer.pch -fhq + +ASM_CFG = /i$(WWFLAT)\INCLUDE # Include directory. +ASM_CFG += /zd # Debugging information line numbers. +ASM_CFG += /t # Quiet operation. +ASM_CFG += /m # Allow multiple passes. +ASM_CFG += /w+ # Enable maximum warnings. +ASM_CFG += /jJUMPS # Enable jump optimizations. +ASM_CFG += /ml # Case sensitivity on code. +#ASM_CFG += /zi # Full debugging information. + + +VCT_CFG += -bt=NT /i=q:\include -j -W3 -zz -d2 -od /5 -s -fh=d:\projects\ccgold\code\conquer.pch + + +#--------------------------------------------------------------------------- +# Implicit rules +# Compiler: +# ($< = full dependent with path) +# Assembler: +# output obj's are constructed from .obj: & the $& macro +# ($< = full dependent with path) +# tasm's cfg file is not invoked as a response file. +#--------------------------------------------------------------------------- + +.c.obj:#.AUTODEPEND + echo Compiling $< + *watcom\wcc $(C_CFG) -fo$(%WWOBJ)obj\$^. $< + +.cpp.obj: #.AUTODEPEND + echo Compiling $< +!ifdef %NETWORK + f:\projects\c&c95\slaves\NETEXEC /v f:\projects\c&c95\slaves\$(%NETWHO)c $^& + if exist $(%CCNETDIR)\code\netmake.err %abort +!else + *$(WATCOM)\binnt\wpp386 $(CC_CFG) -fo$(%WWOBJ)obj\$^. $(CODEDIR)\$< +!endif + +.asm.obj: + echo Assembling $< +!ifdef %NETWORK + f:\projects\c&c95\slaves\NETEXEC /v f:\projects\c&c95\slaves\$(%NETWHO)a $^& +!else + tasm $(ASM_CFG) $<, $(%WWOBJ)obj\$^. +!endif + + + + +#--------------------------------------------------------------------------- +# Project-dependent variables +#--------------------------------------------------------------------------- +OBJECTS = & + SUPER.OBJ & + AADATA.OBJ & + WINSTUB.OBJ & + WINASM.OBJ & + ABSTRACT.OBJ & + ADATA.OBJ & + AIRCRAFT.OBJ & + ANIM.OBJ & + AUDIO.OBJ & + BASE.OBJ & + BBDATA.OBJ & + BDATA.OBJ & + BUILDING.OBJ & + BULLET.OBJ & + CARGO.OBJ & + CCFILE.OBJ & + CDATA.OBJ & + CDFILE.OBJ & + CELL.OBJ & + CHECKBOX.OBJ & + CHEKLIST.OBJ & + COLRLIST.OBJ & + COMBAT.OBJ & + COMBUF.OBJ & + CONFDLG.OBJ & + CONNECT.OBJ & + CONQUER.OBJ & + CONST.OBJ & + CONTROL.OBJ & + COORD.OBJ & + CREDITS.OBJ & + CREW.OBJ & + DEBUG.OBJ & + DIAL8.OBJ & + DIALOG.OBJ & + DISPLAY.OBJ & + DOOR.OBJ & + DRIVE.OBJ & + EDIT.OBJ & + EVENT.OBJ & + ENDING.OBJ & + EXPAND.OBJ & + FACING.OBJ & + FACTORY.OBJ & + FINDPATH.OBJ & + FLASHER.OBJ & + FLY.OBJ & + FOOT.OBJ & + FUSE.OBJ & + GADGET.OBJ & + GAMEDLG.OBJ & + GAUGE.OBJ & + GLOBALS.OBJ & + GOPTIONS.OBJ & + GSCREEN.OBJ & + HDATA.OBJ & + HEAP.OBJ & + HELP.OBJ & + HOUSE.OBJ & + IDATA.OBJ & + INFANTRY.OBJ & + INI.OBJ & + INIT.OBJ & + INTERNET.OBJ & + INTERPAL.OBJ & + INTRO.OBJ & + IOMAP.OBJ & + IOOBJ.OBJ & + IPX.OBJ & + IPXADDR.OBJ & + IPXCONN.OBJ & + IPXGCONN.OBJ & + IPXMGR.OBJ & + IPX95.OBJ & + JSHELL.OBJ & + KEYFBUFF.OBJ & + KEYFRAME.OBJ & + LAYER.OBJ & + LINK.OBJ & + LIST.OBJ & + LOADDLG.OBJ & + LOGIC.OBJ & + MAP.OBJ & + MAPEDDLG.OBJ & + MAPEDIT.OBJ & + MAPEDPLC.OBJ & + MAPEDTM.OBJ & + MAPSEL.OBJ & + MENUS.OBJ & + MISSION.OBJ & + MIXFILE.OBJ & + MOUSE.OBJ & + MPLAYER.OBJ & + MSGBOX.OBJ & + MSGLIST.OBJ & + NETDLG.OBJ & + NOSEQCON.OBJ & + NULLCONN.OBJ & + NULLDLG.OBJ & + NULLMGR.OBJ & + OBJECT.OBJ & + ODATA.OBJ & + OPTIONS.OBJ & + OVERLAY.OBJ & + POWER.OBJ & + PROFILE.OBJ & + QUEUE.OBJ & + RADAR.OBJ & + RADIO.OBJ & + RAND.OBJ & + REINF.OBJ & + SAVELOAD.OBJ & + SCENARIO.OBJ & + SCORE.OBJ & + SCROLL.OBJ & + SDATA.OBJ & + SHAPEBTN.OBJ & + SIDEBAR.OBJ & + SLIDER.OBJ & + SMUDGE.OBJ & + SOUNDDLG.OBJ & + SPECIAL.OBJ & + STARTUP.OBJ & + SUPPORT.OBJ & + TAB.OBJ & + TARCOM.OBJ & + TARGET.OBJ & + TCPIP.OBJ & + TDATA.OBJ & + TEAM.OBJ & + TEAMTYPE.OBJ & + TECHNO.OBJ & + TEMPLATE.OBJ & + TERRAIN.OBJ & + TEXTBTN.OBJ & + THEME.OBJ & + TOGGLE.OBJ & + TRIGGER.OBJ & + TURRET.OBJ & + TXTLABEL.OBJ & + TXTPRNT.OBJ & + UDATA.OBJ & + UNIT.OBJ & + VECTOR.OBJ & + VISUDLG.OBJ & + UTRACKER.OBJ & + PACKET.OBJ & + FIELD.OBJ & + STATS.OBJ & + CCDDE.OBJ & + DDE.OBJ & +# ALLOC.OBJ +# DESCDLG.OBJ & +# COORDA.OBJ & + +PROJ_LIBS =# & + #wwflat32.lib + +VQ_LIBS = & + vqa32wp.lib & + vqm32wp.lib + +GCL_LIBS = & + gclfr3s.lib + + +############################################################################ +# Pre-compilation process. Move old files to backup directory and switch +# to monochrome screen. +.BEFORE +!ifndef %SLAVE +# mode mono +!endif + -if exist $(%WWOBJ)*.pch del $(%WWOBJ)*.pch + -if exist $(%CCNETDIR)\code\netmake.err del $(%CCNETDIR)\code\netmake.err + -if exist *.bak move *.bak bak + +# Switch back to normal screen at compilation end. +.AFTER +!ifndef %SLAVE +# mode co80 +# ncc /50 +!endif + +# Switch back to normal screen if there was an error. +.ERROR +!ifndef %SLAVE +# mode co80 +# ncc /50 +!endif + + +############################################################################# +# Default target +all: conquer.exe + + +############################################################################# +# Builds the stub replacement program. +CWSTUB.OBJ: CWSTUB.C + *watcom\wcc /i=watcom\h /dQUIET /dVMM /ms /zQ -fo$(%WWOBJ)obj\$^. $< + +CWSTUB.EXE: CWSTUB.OBJ + *watcom\wlink system dos file $(%WWOBJ)obj\cwstub.obj name cwstub.exe option quiet library watcom\lib286\dos\clibs.lib, watcom\lib286\math87s.lib, watcom\lib286\dos\emu87.lib + + +############################################################################# +# Build the EXE +conquer.exe: $(OBJECTS) obj\mmx.obj conquer.lnk $(WWFLAT)\lib\win32lib.lib $(VQDIR)\lib\vqa32wp.lib $(VQDIR)\lib\vqm32wp.lib + + Echo "conquer.exe" linking phase. +!ifdef %NETWORK + echo Waiting for objects... + for %index in ($(OBJECTS)) do f:\projects\c&c95\slaves\WAITFILE $(%CCNETDIR)\code\obj\%index + ndos.com /c copy $(%CCNETDIR)\code\obj\*.obj $(%CCLOCALDIR)\code\obj /U +!endif + echo Linking the executable. + $(WATCOM)\binnt\wlink name ..\run\$@ @conquer.lnk + $(WATCOM)\binnt\WRC cc_icon ..\run\conquer.exe + Echo "conquer.exe" executable completed. +!ifdef %WWOBJ + xcopy /M e:\obj\*.* c:\projects\c&c\code\obj +!endif +# watcom\wlink $(LINK_CFG) name ..\run\$@ @conquer.lnk + +############################################################################# +# This creates the linker command file. +conquer.lnk : makefile + %create $^@ + %append $^@ system win95 + %append $^@ option stack=128k + %append $^@ option redefsok + %append $^@ option quiet + %append $^@ option map +# %append $^@ option cache + %append $^@ option eliminate + %append $^@ option caseexact +# %append $^@ option stub=cwstub.exe +# %append $^@ debug all + %append $^@ debug all + for %index in ($(OBJECTS)) do %append $^@ file $(%WWOBJ)obj\%index + %append $^@ file $(%WWOBJ)obj\mmx.obj +# %append $^@ file $(%WWOBJ)obj\vector.obj +# for %index in ($(PROJ_LIBS)) do %append $^@ library $(WWFLAT)\lib\%index +# for %index in ($(VQ_LIBS)) do %append $^@ library ..\vq\lib\%index +# for %index in ($(GCL_LIBS)) do %append $^@ library ..\gcl510\w10\%index + %append $^@ library $(WWFLAT)\lib\ddraw.lib + %append $^@ library $(WWFLAT)\lib\dsound.lib + %append $^@ library $(WWFLAT)\lib\keyboard.lib + %append $^@ library $(WWFLAT)\lib\win32lib.lib + %append $^@ library $(VQDIR)\lib\vqa32wp.lib + %append $^@ library $(VQDIR)\lib\vqm32wp.lib + %append $^@ library ipx\wwipx32.lib + + + +############################################################################## +# Creates a symbol-less executable and copies it to the net cd directories +cd: .SYMBOLIC + -wstrip ..\run\conquer.exe ..\run\c&c95.exe + -copy ..\run\c&c95.exe f:\projects\c&c95\setup\data + + +################################################################## +# +# The MMX stuff requires MASM 6.11d so it needs its own rule +# +obj\mmx.obj: mmx.asm mmx.inc + d:\masm611\bin\ml /I. /c /Cx /Zd /Cp /Flmmx.txt /Sc /Foobj\mmx.obj mmx.asm + + + + +############################################################# +# Creates a bound executable in the install directory. +bind: .SYMBOLIC + -copy ..\run\conquer.exe ..\run\temp.exe + -wstrip ..\run\temp.exe + -watcom\4gwbind watcom\4gwpro.exe ..\run\temp.exe ..\cd1\install\c&c.exe -f + -copy ..\cd1\install\c&c.exe ..\cd2\install /u /v + -del ..\run\temp.exe + + +############################################################# +# Update source and art to network. +update: bind .SYMBOLIC +!ifdef %CDDIR + -copy ..\cd1\*.* $(CDDIR)cd1 /v /u /s + -copy ..\cd2\*.* $(CDDIR)cd2 /v /u /s + -copy $(CDDIR)cd1\*.* f:\projects\c&c\cd\cd1 /v /u /s + -copy $(CDDIR)cd2\*.* f:\projects\c&c\cd\cd2 /v /u /s +!else + -copy ..\cd1\*.* f:\projects\c&c\cd\cd1 /v /u /s + -copy ..\cd2\*.* f:\projects\c&c\cd\cd2 /v /u /s +!endif + -copy watcom\dos4gw.exe f:\projects\c&c\playtest + -copy ..\run\conquer.exe f:\projects\c&c\playtest /u /v + -copy conquer.map f:\projects\c&c\playtest /u /v + -mkdir f:\projects\c&c\playtest\%_DATE + -copy ..\run\conquer.exe f:\projects\c&c\playtest\%_DATE /u /v + -copy conquer.map f:\projects\c&c\playtest\%_DATE /u /v + -copy ..\art\ingame\*.* f:\projects\c&c\art\ingame /u /v /s + -copy *.* f:\projects\c&c\code /v /s /u + +############################################################################# +# Explicit rules to build the master zip files (used by Codewrite merge). +BILL_R.ZIP: pkzip.dat .SYMBOLIC + -del f:\projects\c&c\bill_r.zip + -pkzip -p -u -x@pkzip.dat f:\projects\c&c\bill_r.zip *.* eng\*.* + +MARIA_L.ZIP: pkzip.dat .SYMBOLIC + -del f:\projects\c&c\maria_l.zip + -pkzip -p -u -x@pkzip.dat f:\projects\c&c\maria_l.zip *.* eng\*.* + +BARRY_G.ZIP: pkzip.dat .SYMBOLIC + -del f:\projects\c&c\barry_g.zip + -pkzip -p -u -x@pkzip.dat f:\projects\c&c\barry_g.zip *.* eng\*.* + +PHIL_G.ZIP: pkzip.dat .SYMBOLIC + -del f:\projects\c&c95\phil_g.zip + -pkzip -p -u -x@pkzip.dat f:\projects\c&c95\phil_g.zip *.* + +win32lib.ZIP: pkzip.dat .SYMBOLIC + -del f:\projects\c&c95\win32lib.zip + -pkzip -p -r f:\projects\c&c95\win32lib.zip d:\win32lib\*.* + +DAVID_D.ZIP: pkzip.dat .SYMBOLIC + -del f:\projects\c&c\david_d.zip + -pkzip -p -u -x@pkzip.dat f:\projects\c&c\david_d.zip *.* eng\*.* + +BILL_P.ZIP: pkzip.dat .SYMBOLIC + -del f:\projects\c&c\bill_p.zip + -pkzip -p -u -x@pkzip.dat f:\projects\c&c\bill_p.zip *.* eng\*.* + +# Special "mega-zip" process. +JOE_B.ZIP: pkzip.dat .SYMBOLIC + -pkzip -rp -u -xcps\*.* -x@pkzip.dat f:\projects\c&c\joe_b.zip + + +############################################################################# +# Rebuilds the master zip control file. This is used by the zip process. +PKZIP.DAT: makefile .SYMBOLIC + %create $^@ + %append $^@ *.000 + %append $^@ *.@@@ + %append $^@ *.bak + %append $^@ *.bat + %append $^@ *.cfg + %append $^@ *.dat + %append $^@ *.def + %append $^@ *.doc + %append $^@ *.dsw + %append $^@ *.err + %append $^@ *.ewp + %append $^@ *.ext + %append $^@ *.i + %append $^@ *.ide + %append $^@ *.lnk + %append $^@ *.log + %append $^@ *.lst + %append $^@ *.mac + %append $^@ *.map + %append $^@ *.mk + %append $^@ *.mk1 + %append $^@ *.obj + %append $^@ *.out + %append $^@ *.pch + %append $^@ *.pfs + %append $^@ *.pif + %append $^@ *.pjt + %append $^@ *.prf + %append $^@ *.pro + %append $^@ *.ptg + %append $^@ *.rc + %append $^@ *.rep + %append $^@ *.rpt + %append $^@ *.rst + %append $^@ *.sym + %append $^@ *.tag + %append $^@ *.td + %append $^@ *.td + %append $^@ *.tgt + %append $^@ *.tmp + %append $^@ *.tr + %append $^@ *.tr + %append $^@ *.vec + %append $^@ *.wpj + %append $^@ *.zip + %append $^@ state.rst + + +#--------------------------------------------------------------------------- +# Dependency macros (makes defining dependencies easier) +#--------------------------------------------------------------------------- +GENERAL_H = defines.h function.h externs.h conquer.h vector.h heap.h & + debug.h jshell.h compat.h + +TECHNO_H = facing.h techno.h mission.h stage.h cargo.h object.h abstract.h + +UNIT_H = unit.h tarcom.h turret.h drive.h foot.h radio.h $(TECHNO_H) + +INFANTRY_H = infantry.h foot.h radio.h $(TECHNO_H) + +AIRCRAFT_H = aircraft.h fly.h radio.h $(TECHNO_H) + +BUILDING_H = building.h radio.h $(TECHNO_H) + +BULLET_H = bullet.h fly.h fuse.h object.h abstract.h + +OBJ_H = $(UNIT_H) $(INFANTRY_H) $(AIRCRAFT_H) $(BUILDING_H) $(BULLET_H) + +MAP_H = base.h mapedit.h mouse.h scroll.h help.h tab.h power.h sidebar.h & + radar.h display.h map.h gscreen.h cell.h + +GADGET_H = textbtn.h shapebtn.h slider.h gauge.h dial8.h edit.h & + toggle.h list.h cheklist.h control.h gadget.h link.h + +FILE_H = ccfile.h cdfile.h mixfile.h rawfile.h wwfile.h link.h + +TEAM_H = team.h teamtype.h trigger.h + +IPX_H = ipx.h ipxaddr.h + +NET_H = combuf.h connect.h connmgr.h ipx.h ipxaddr.h ipxconn.h ipxgconn.h & + ipxmgr.h noseqcon.h nullconn.h nullmgr.h + +MISC_H = ftimer.h logic.h score.h theme.h event.h queue.h special.h + +#--------------------------------------------------------------------------- +# Dependencies (This is not totally accurate; if you're not sure, rebuild +# everything!) +#--------------------------------------------------------------------------- +aadata.obj: aadata.cpp $(GENERAL_H) type.h + +winstub.obj: winstub.cpp tcpip.h $(GENERAL_H) + +winasm.obj: winasm.asm + +abstract.obj: abstract.cpp $(GENERAL_H) abstract.h + +adata.obj: adata.cpp $(GENERAL_H) type.h + +aircraft.obj: aircraft.cpp $(GENERAL_H) $(AIRCRAFT_H) + +anim.obj: anim.cpp $(GENERAL_H) anim.h stage.h object.h + +audio.obj: audio.cpp $(GENERAL_H) audio.h + +base.obj: base.cpp $(GENERAL_H) type.h + +bbdata.obj: bbdata.cpp $(GENERAL_H) type.h + +bdata.obj: bdata.cpp $(GENERAL_H) type.h + +building.obj: building.cpp $(GENERAL_H) $(BUILDING_H) + +bullet.obj: bullet.cpp $(GENERAL_H) $(BULLET_H) + +cargo.obj: cargo.cpp $(GENERAL_H) $(TECHNO_H) cargo.h + +ccfile.obj: ccfile.cpp $(GENERAL_H) $(FILE_H) + +cdata.obj: cdata.cpp $(GENERAL_H) type.h + +cdfile.obj: cdfile.cpp $(GENERAL_H) $(FILE_H) + +cell.obj: cell.cpp $(GENERAL_H) $(MAP_H) + +checkbox.obj: checkbox.cpp $(GENERAL_H) $(GADGET_H) + +cheklist.obj: cheklist.cpp $(GENERAL_H) $(GADGET_H) + +colrlist.obj: colrlist.cpp $(GENERAL_H) $(GADGET_H) + +combat.obj: combat.cpp $(GENERAL_H) + +combuf.obj: combuf.cpp $(GENERAL_H) combuf.h + +confdlg.obj: confdlg.cpp $(GENERAL_H) $(GADGET_H) + +connect.obj: connect.cpp $(GENERAL_H) connect.h combuf.h + +conquer.obj: conquer.cpp $(GENERAL_H) $(MISC_H) $(OBJ_H) tcpip.h ccdde.h + +const.obj: const.cpp $(GENERAL_H) + +control.obj: control.cpp $(GENERAL_H) $(GADGET_H) + +coord.obj: coord.cpp $(GENERAL_H) + +coorda.obj: coorda.asm + +credits.obj: credits.cpp $(GENERAL_H) credits.h + +crew.obj: crew.cpp $(GENERAL_H) crew.h + +debug.obj: debug.cpp $(GENERAL_H) debug.h + +deldlg.obj: deldlg.cpp $(GENERAL_H) $(GADGET_H) + +#descdlg.obj: descdlg.cpp $(GENERAL_H) $(GADGET_H) + +dial8.obj: dial8.cpp $(GENERAL_H) $(GADGET_H) + +dialog.obj: dialog.cpp $(GENERAL_H) + +display.obj: display.cpp $(GENERAL_H) $(MAP_H) + +door.obj: door.cpp $(GENERAL_H) + +drive.obj: drive.cpp $(GENERAL_H) $(TECHNO_H) + +edit.obj: edit.cpp $(GENERAL_H) $(GADGET_H) + +event.obj: event.cpp $(GENERAL_H) $(MISC_H) ccdde.h + +ending.obj: ending.cpp $(GENERAL_H) $(MISC_H) + +expand.obj: expand.cpp $(GENERAL) + +facing.obj: facing.cpp $(GENERAL_H) facing.h + +factory.obj: factory.cpp $(GENERAL_H) factory.h + +findpath.obj: findpath.cpp $(GENERAL_H) + +flasher.obj: flasher.cpp $(GENERAL_H) flasher.h + +fly.obj: fly.cpp $(GENERAL_H) fly.h + +foot.obj: foot.cpp $(GENERAL_H) $(INFANTRY_H) + +fuse.obj: fuse.cpp $(GENERAL_H) fuse.h + +gadget.obj: gadget.cpp $(GENERAL_H) $(GADGET_H) + +gamedlg.obj: gamedlg.cpp $(GENERAL_H) $(GADGET_H) + +gauge.obj: gauge.cpp $(GENERAL_H) $(GADGET_H) + +globals.obj: globals.cpp $(GENERAL_H) + +goptions.obj: goptions.cpp $(GENERAL_H) $(GADGET_H) + +gscreen.obj: gscreen.cpp $(GENERAL_H) $(MAP_H) + +hdata.obj: hdata.cpp $(GENERAL_H) type.h + +heap.obj: heap.cpp $(GENERAL_H) $(MISC_H) + +help.obj: help.cpp $(GENERAL_H) $(MAP_H) + +house.obj: house.cpp $(GENERAL_H) house.h + +idata.obj: idata.cpp $(GENERAL_H) type.h + +infantry.obj: infantry.cpp $(GENERAL_H) $(INFANTRY_H) + +ini.obj: ini.cpp $(GENERAL_H) $(MISC_H) + +init.obj: init.cpp $(GENERAL_H) $(MISC_H) $(OBJ_H) tcpip.h ccdde.h + +internet.obj: internet.cpp $(GENERAL_H) $(MISC_H) tcpip.h ccdde.h + +interpal.obj: interpal.cpp $(GENERAL_H) $(MISC_H) + +intro.obj: intro.cpp $(GENERAL_H) $(MISC_H) + +iomap.obj: iomap.cpp $(GENERAL_H) $(MAP_H) $(FILE_H) + +ioobj.obj: ioobj.cpp $(GENERAL_H) $(FILE_H) $(OBJ_H) + +ipx.obj: ipx.cpp $(GENERAL_H) $(IPX_H) + +ipxaddr.obj: ipxaddr.cpp $(GENERAL_H) $(IPX_H) + +ipxconn.obj: ipxconn.cpp $(GENERAL_H) $(NET_H) + +ipxgconn.obj: ipxgconn.cpp $(GENERAL_H) $(NET_H) + +ipxmgr.obj: ipxmgr.cpp $(GENERAL_H) $(NET_H) + +ipx95.obj: ipx95.cpp $(GENERAL_H) $(NET_H) + +jshell.obj: jshell.cpp $(GENERAL_H) $(MISC_H) + +keyfbuff.obj: keyfbuff.asm + +keyframe.obj: keyframe.cpp $(GENERAL_H) + +layer.obj: layer.cpp $(GENERAL_H) $(MISC_H) + +link.obj: link.cpp $(GENERAL_H) link.h + +list.obj: list.cpp $(GENERAL_H) $(GADGET_H) + +loaddlg.obj: loaddlg.cpp $(GENERAL_H) $(GADGET_H) + +logic.obj: logic.cpp $(GENERAL_H) $(MISC_H) + +map.obj: map.cpp $(GENERAL_H) $(MAP_H) + +mapsel.obj: mapsel.cpp $(GENERAL_H) + +mapeddlg.obj: mapeddlg.cpp $(GENERAL_H) $(MAP_H) $(OBJ_H) + +mapedit.obj: mapedit.cpp mapedsel.cpp $(GENERAL_H) $(MAP_H) $(OBJ_H) + +mapedplc.obj: mapedplc.cpp $(GENERAL_H) $(MAP_H) $(OBJ_H) + +mapedtm.obj: mapedtm.cpp $(GENERAL_H) $(MAP_H) $(OBJ_H) + +menus.obj: menus.cpp $(GENERAL_H) ccdde.h + +mission.obj: mission.cpp $(GENERAL_H) mission.h stage.h cargo.h object.h abstract.h + +mixfile.obj: mixfile.cpp $(GENERAL_H) $(FILE_H) + +monoc.obj: monoc.cpp $(GENERAL_H) + +mouse.obj: mouse.cpp $(GENERAL_H) $(MAP_H) + +mplayer.obj: mplayer.cpp tcpip.h $(GENERAL_H) + +msgbox.obj: msgbox.cpp $(GENERAL_H) $(GADGET_H) + +msglist.obj: msglist.cpp $(GENERAL_H) $(GADGET_H) + +netdlg.obj: netdlg.cpp $(GENERAL_H) $(GADGET_H) $(NET_H) + +noseqcon.obj: noseqcon.cpp $(GENERAL_H) noseqcon.h connect.h combuf.h + +nullconn.obj: nullconn.cpp $(GENERAL_H) nullconn.h noseqcon.h connect.h combuf.h tcpip.h + +nulldlg.obj: nulldlg.cpp $(GENERAL_H) nullmgr.h nullconn.h connmgr.h noseqcon.h connect.h combuf.h tcpip.h + +nullmgr.obj: nullmgr.cpp $(GENERAL_H) nullmgr.h nullconn.h connmgr.h noseqcon.h connect.h combuf.h tcpip.h + +object.obj: object.cpp $(GENERAL_H) object.h abstract.h + +odata.obj: odata.cpp $(GENERAL_H) type.h + +options.obj: options.cpp $(GENERAL_H) $(GADGET_H) + +overlay.obj: overlay.cpp $(GENERAL_H) overlay.h object.h + +power.obj: power.cpp $(GENERAL_H) $(MAP_H) + +profile.obj: profile.cpp $(GENERAL_H) + +queue.obj: queue.cpp $(GENERAL_H) $(MISC_H) tcpip.h + +rand.obj: rand.cpp $(GENERAL_H) + +radar.obj: radar.cpp $(GENERAL_H) $(MAP_H) + +radio.obj: radio.cpp $(GENERAL_H) $(TECHNO_H) + +reinf.obj: reinf.cpp $(GENERAL_H) $(MISC_H) $(TEAM_H) + +savedlg.obj: savedlg.cpp $(GENERAL_H) $(GADGET_H) + +saveload.obj: saveload.cpp $(GENERAL_H) $(MISC_H) + +scenario.obj: scenario.cpp $(GENERAL_H) $(MISC_H) + +score.obj: score.cpp $(GENERAL_H) + +scroll.obj: scroll.cpp $(GENERAL_H) $(MAP_H) + +sdata.obj: sdata.cpp $(GENERAL_H) type.h + +shapebtn.obj: shapebtn.cpp $(GENERAL_H) $(GADGET_H) + +sidebar.obj: sidebar.cpp $(GENERAL_H) $(MAP_H) + +slider.obj: slider.cpp $(GENERAL_H) $(GADGET_H) + +smudge.obj: smudge.cpp $(GENERAL_H) smudge.h object.h + +sounddlg.obj: sounddlg.cpp $(GENERAL_H) $(GADGET_H) sounddlg.h + +special.obj: special.cpp $(GENERAL_H) $(GADGET_H) special.h + +startup.obj: startup.cpp $(GENERAL_H) ccdde.h + +stuff.obj: stuff.cpp $(GENERAL_H) $(MISC_H) + +support.obj: support.asm + +super.obj: super.cpp $(GENERAL_H) $(MISC_H) + +tab.obj: tab.cpp $(GENERAL_H) $(MAP_H) + +tarcom.obj: tarcom.cpp $(GENERAL_H) $(UNIT_H) + +target.obj: target.cpp $(GENERAL_H) target.h + +tcpip.obj: tcpip.cpp $(GENERAL_H) tcpip.h + +tdata.obj: tdata.cpp $(GENERAL_H) type.h + +team.obj: team.cpp $(GENERAL_H) $(TEAM_H) + +teamtype.obj: teamtype.cpp $(GENERAL_H) $(TEAM_H) + +techno.obj: techno.cpp $(GENERAL_H) $(TECHNO_H) + +template.obj: template.cpp $(GENERAL_H) template.h object.h + +terrain.obj: terrain.cpp $(GENERAL_H) terrain.h stage.h object.h + +textbtn.obj: textbtn.cpp $(GENERAL_H) $(GADGET_H) + +theme.obj: theme.cpp $(GENERAL_H) theme.h + +toggle.obj: toggle.cpp $(GENERAL_H) $(GADGET_H) + +trigger.obj: trigger.cpp $(GENERAL_H) $(TEAM_H) + +turret.obj: turret.cpp $(GENERAL_H) $(UNIT_H) + +txtlabel.obj: txtlabel.cpp $(GENERAL_H) $(GADGET_H) + +txtprnt.obj: txtprnt.asm + +udata.obj: udata.cpp $(GENERAL_H) type.h + +unit.obj: unit.cpp $(GENERAL_H) $(UNIT_H) + +visudlg.obj: visudlg.cpp $(GENERAL_H) $(GADGET_H) + +utracker.obj: utracker.cpp utracker.h + +packet.obj: packet.cpp packet.h field.h + +field.obj: field.cpp field.h + +stats.obj: stats.cpp $(GENERAL_H) packet.h field.h ccdde.h + +ccdde.obj: ccdde.cpp ccdde.h dde.h + +dde.obj: dde.cpp dde.h + + + +vector.obj: vector.cpp $(GENERAL_H) $(MISC_H) + *$(WATCOM)\binnt\wpp386 $(VCT_CFG) -foobj\vector.obj vector.cpp + + +#**************************** End of makefile ****************************** diff --git a/MAKEFILE.BAK b/MAKEFILE.BAK new file mode 100644 index 0000000..501575d --- /dev/null +++ b/MAKEFILE.BAK @@ -0,0 +1,861 @@ +# +# Command & Conquer(tm) +# Copyright 2025 Electronic Arts Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +#* $Header$ +#*********************************************************************************************** +#*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** +#*********************************************************************************************** +#* * +#* Project Name : Command & Conquer * +#* * +#* File Name : MAKEFILE * +#* * +#* Programmer : Joe L. Bostic * +#* * +#* Start Date : 03/02/95 * +#* * +#* Last Update : March 2, 1995 [JLB] * +#* * +#*---------------------------------------------------------------------------------------------* +#* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * + +.SILENT +.OPTIMIZE +.ERASE + +#--------------------------------------------------------------------------- +# Verify user's environment +#--------------------------------------------------------------------------- +!ifndef %WWFLAT +WWFLAT=c:\wwflat32 +#!error WWFLAT must be set to the root of the library directory. +!else +WWFLAT=$(%WWFLAT) +!endif + +!ifndef %WATCOM +WATCOM=c:\projects\c&c\code\watcom +#!error WATCOM must be set to the Watcom root directory. +!else +WATCOM=$(%WATCOM) +!endif + +#--------------------------------------------------------------------------- +# Path macros: one path for each file type. +# These paths are used to tell make where to find/put each file type. +#--------------------------------------------------------------------------- +.asm: . +.c: . +.cpp: . +.h: . +#.obj: obj +.obj: r: +.lib: $(WWFLAT)\lib +.exe: ..\run + + +#=========================================================================== +# Flags for the different project assembly tools. +#=========================================================================== +CC_CFG = /i=..\vq\include # Includes player (VQ) directory. +CC_CFG += /i=$(WWFLAT)\INCLUDE # Normal library include directory. +CC_CFG += /i=watcom\H # Normal Watcom include directory. +CC_CFG += /i=..\gcl510\H # Includes Greenleaf headers. +CC_CFG += /d2 # Debugging information. +CC_CFG += /of+ # Generate tracable stack frames. +CC_CFG += /zp1 # Pack structures on byte boundary. +CC_CFG += /5s # Pentium optimized stack calling conventions. +CC_CFG += /xs # Exception handling enabled. +CC_CFG += /s # Remove stack check calls. +CC_CFG += /j # char is now signed. +CC_CFG += /fh=conquer.pch # Use precompiled headers. +CC_CFG += /we # Treat all warnings as errors. +CC_CFG += /w8 # Most warnings enabled. +CC_CFG += /ri # char and shorts are returned as int. +CC_CFG += /zq # Operate quietly. +CC_CFG += /zm # Each routine to be in its own segment. +CC_CFG += /zld # Disable autodependency information in object file. +CC_CFG += /od # *** Disable all optimizations *** +CC_CFG += /DDOS4G # Must be defined for Greenleaf +CC_CFG += /DGF_WATCOM_S # Must be defined for Greenleaf with /3s +CC_CFG += /ol # Loop optimizations enabled. +CC_CFG += /or # Reorder instructions for best pipeline usage. +CC_CFG += /oe # Inline is enabled. +CC_CFG += /oi # Expand intrisic functions inline. +CC_CFG += /on # Allow numerically unstable operations. +CC_CFG += /oo # Compile even if low on memory (i.e. less than 64meg). +CC_CFG += /oa # Relax aliasing constraints. +#CC_CFG += /ot # Speed is more important than space optimizations. +#CC_CFG += /fi=watcom.h # Special Watcom control file. + +ASM_CFG = /i$(WWFLAT)\INCLUDE # Include directory. +ASM_CFG += /zd # Debugging information line numbers. +ASM_CFG += /t # Quiet operation. +ASM_CFG += /m # Allow multiple passes. +ASM_CFG += /w+ # Enable maximum warnings. +ASM_CFG += /jJUMPS # Enable jump optimizations. +ASM_CFG += /ml # Case sensitivity on code. +#ASM_CFG += /zi # Full debugging information. + + +#--------------------------------------------------------------------------- +# Implicit rules +# Compiler: +# ($< = full dependent with path) +# Assembler: +# output obj's are constructed from .obj: & the $& macro +# ($< = full dependent with path) +# tasm's cfg file is not invoked as a response file. +#--------------------------------------------------------------------------- + +.c.obj:#.AUTODEPEND + echo Compiling $< + *watcom\wcc $(C_CFG) -foobj\$^. $< + +.cpp.obj: #.AUTODEPEND + echo Compiling $< +!ifdef %NETWORK + f:\projects\c&c\slaves\NETEXEC /v $(%NETWHO)c $^& + if exist $(%CCNETDIR)\code\netmake.err %abort +!else + *watcom\wpp386 $(CC_CFG) -foobj\$^. $< +!endif + +.asm.obj: + echo Assembling $< +!ifdef %NETWORK + f:\projects\c&c\slaves\NETEXEC /v $(%NETWHO)a $^& +!else + watcom\tasm32 $(ASM_CFG) $<, obj\$^. +!endif + +#--------------------------------------------------------------------------- +# Project-dependent variables +#--------------------------------------------------------------------------- +OBJECTS = & + AADATA.OBJ & + ABSTRACT.OBJ & + ADATA.OBJ & + AIRCRAFT.OBJ & + ANIM.OBJ & + AUDIO.OBJ & + BASE.OBJ & + BBDATA.OBJ & + BDATA.OBJ & + BUILDING.OBJ & + BULLET.OBJ & + CARGO.OBJ & + CCFILE.OBJ & + CDATA.OBJ & + CDFILE.OBJ & + CELL.OBJ & + CHEKLIST.OBJ & + COLRLIST.OBJ & + COMBAT.OBJ & + COMBUF.OBJ & + CONFDLG.OBJ & + CONNECT.OBJ & + CONQUER.OBJ & + CONST.OBJ & + CONTROL.OBJ & + COORD.OBJ & + COORDA.OBJ & + CREDITS.OBJ & + CREW.OBJ & + DEBUG.OBJ & + DESCDLG.OBJ & + DIAL8.OBJ & + DIALOG.OBJ & + DISPLAY.OBJ & + DPMI.OBJ & + DRIVE.OBJ & + EDIT.OBJ & + EVENT.OBJ & + FACING.OBJ & + FACTORY.OBJ & + FINDPATH.OBJ & + FLASHER.OBJ & + FLY.OBJ & + FOOT.OBJ & + FUSE.OBJ & + GADGET.OBJ & + GAMEDLG.OBJ & + GAUGE.OBJ & + GLOBALS.OBJ & + GOPTIONS.OBJ & + GSCREEN.OBJ & + HDATA.OBJ & + HEAP.OBJ & + HELP.OBJ & + HOUSE.OBJ & + IDATA.OBJ & + INFANTRY.OBJ & + INI.OBJ & + INIT.OBJ & + INTRO.OBJ & + IOMAP.OBJ & + IOOBJ.OBJ & + IPX.OBJ & + IPXADDR.OBJ & + IPXCONN.OBJ & + IPXGCONN.OBJ & + IPXMGR.OBJ & + IPXPROT.OBJ & + JSHELL.OBJ & + KEYFBUFF.OBJ & + KEYFRAME.OBJ & + LAYER.OBJ & + LINK.OBJ & + LIST.OBJ & + LOADDLG.OBJ & + LOGIC.OBJ & + MAP.OBJ & + MAPEDDLG.OBJ & + MAPEDIT.OBJ & + MAPEDPLC.OBJ & + MAPEDSEL.OBJ & + MAPEDTM.OBJ & + MAPSEL.OBJ & + MENUS.OBJ & + MISSION.OBJ & + MIXFILE.OBJ & + MONOC.OBJ & + MOUSE.OBJ & + MPLAYER.OBJ & + MSGBOX.OBJ & + MSGLIST.OBJ & + NETDLG.OBJ & + NOSEQCON.OBJ & + NULLCONN.OBJ & + NULLDLG.OBJ & + NULLMGR.OBJ & + OBJECT.OBJ & + ODATA.OBJ & + OPTIONS.OBJ & + OVERLAY.OBJ & + POWER.OBJ & + PROFILE.OBJ & + QUEUE.OBJ & + RADAR.OBJ & + RADIO.OBJ & + RAND.OBJ & + RAWFILE.OBJ & + REINF.OBJ & + SAVELOAD.OBJ & + SCENARIO.OBJ & + SCORE.OBJ & + SCROLL.OBJ & + SDATA.OBJ & + SHAPEBTN.OBJ & + SIDEBAR.OBJ & + SLIDER.OBJ & + SMUDGE.OBJ & + SOUNDDLG.OBJ & + STARTUP.OBJ & + SUPPORT.OBJ & + TAB.OBJ & + TARCOM.OBJ & + TARGET.OBJ & + TDATA.OBJ & + TEAM.OBJ & + TEAMTYPE.OBJ & + TECHNO.OBJ & + TEMPLATE.OBJ & + TERRAIN.OBJ & + TEXTBTN.OBJ & + THEME.OBJ & + TOGGLE.OBJ & + TRIGGER.OBJ & + TURRET.OBJ & + TXTLABEL.OBJ & + TXTPRNT.OBJ & + UDATA.OBJ & + UNIT.OBJ & + VECTOR.OBJ & + VISUDLG.OBJ + +PROJ_LIBS = & + wwflat32.lib + +VQ_LIBS = & + vqa32wp.lib & + vqm32wp.lib + +GCL_LIBS = & + gclfr3s.lib + + +############################################################################ +# Pre-compilation process. Move old files to backup directory and switch +# to monochrome screen. +.BEFORE + mode mono + -if exist *.pch del *.pch + -if exist $(%CCNETDIR)\code\netmake.err del $(%CCNETDIR)\code\netmake.err + -if exist *.bak move *.bak bak + +# Switch back to normal screen at compilation end. +.AFTER + mode co80 + +# Switch back to normal screen if there was an error. +.ERROR + mode co80 + + +############################################################################# +# Default target +all: conquer.exe + + +############################################################################# +# Builds the stub replacement program. +CWSTUB.OBJ: CWSTUB.C + *watcom\wcc /i=watcom\h /dQUIET /dVMM /ms /zQ -foobj\$^. $< + +CWSTUB.EXE: CWSTUB.OBJ + *watcom\wlink system dos file obj\cwstub.obj name cwstub.exe option quiet library watcom\lib286\dos\clibs.lib, watcom\lib286\math87s.lib, watcom\lib286\dos\emu87.lib + + +############################################################################# +# Build the EXE +conquer.exe: $(OBJECTS) cwstub.exe conquer.lnk $(WWFLAT)\lib\wwflat32.lib + + Echo "conquer.exe" linking phase. +!ifdef %NETWORK + echo Waiting for objects... + for %index in ($(OBJECTS)) do WAITFILE $(%CCNETDIR)\code\obj\%index +# copy $(%CCNETDIR)\code\obj\*.obj $(%CCLOCALDIR)\code\obj /U + copy $(%CCNETDIR)\code\obj\*.obj r: /U +!endif + watcom\wlink name ..\run\$@ @conquer.lnk + Echo "conquer.exe" executable completed. +!ifdef %WWOBJ + xcopy /M $(WWOBJ)obj\*.obj obj +!endif +# watcom\wlink $(LINK_CFG) name ..\run\$@ @conquer.lnk + +############################################################################# +# This creates the linker command file. +conquer.lnk : makefile + %create $^@ + %append $^@ system dos4g + %append $^@ debug all + %append $^@ option stack=8k + %append $^@ option redefsok + %append $^@ option quiet + %append $^@ option map + %append $^@ option cache + %append $^@ option eliminate + %append $^@ option caseexact +# %append $^@ option stub=cwstub.exe +# for %index in ($(OBJECTS)) do %append $^@ file obj\%index + for %index in ($(OBJECTS)) do %append $^@ file r:\%index + for %index in ($(PROJ_LIBS)) do %append $^@ library $(WWFLAT)\lib\%index + for %index in ($(VQ_LIBS)) do %append $^@ library ..\vq\lib\%index + for %index in ($(GCL_LIBS)) do %append $^@ library ..\gcl510\w10\%index + +bind: .SYMBOLIC + -copy ..\run\conquer.exe ..\run\temp.exe + -wstrip ..\run\temp.exe + -watcom\4gwbind watcom\4gwpro.exe ..\run\temp.exe ..\run\c&c.exe -f + -del ..\run\temp.exe +# -wstrip ..\run\install.exe +# -move ..\run\install.exe ..\run\temp.exe +# -watcom\4gwbind watcom\4gwpro.exe ..\run\temp.exe ..\run\install.exe -f +# -del ..\run\temp.exe + +############################################################# +# Update source and art to network. +update: bind .SYMBOLIC + -del *.pch + -del *.tr + -del *.td + -del *.bak + -del *.rst + -del ..\run\*.swp + -attrib -r f:\projects\c&c\cd\*.* /s + -copy ..\cd\*.* f:\projects\c&c\cd /v /u /s + -deltree /Y f:\projects\c&c\cd\install + -md f:\projects\c&c\cd\install + -copy ..\run\*.* f:\projects\c&c\cd\install /v /u + -del f:\projects\c&c\cd\install\*.cfg + -del f:\projects\c&c\cd\install\*.dat + -del f:\projects\c&c\cd\install\*.doc + -del f:\projects\c&c\cd\install\*.ini + -del f:\projects\c&c\cd\install\*.bin + -del f:\projects\c&c\cd\install\*.lbm + -del f:\projects\c&c\cd\install\*.out + -del f:\projects\c&c\cd\install\*.sym + -del f:\projects\c&c\cd\install\*.log + -del f:\projects\c&c\cd\install\*.txt + -del f:\projects\c&c\cd\install\*.bak + -del f:\projects\c&c\cd\install\*.rst + -del f:\projects\c&c\cd\install\savegame.* + -move f:\projects\c&c\cd\install\conquer.exe f:\projects\c&c\playtest + -attrib +r f:\projects\c&c\cd\*.* /s + -copy watcom\dos4gw.exe f:\projects\c&c\playtest + -deltree /Y f:\projects\c&c\art\ingame + -md f:\projects\c&c\art\ingame + -xcopy ..\art\ingame\*.* f:\projects\c&c\art\ingame /e /v /s + -del /Y f:\projects\c&c\code\*.* + -copy *.* f:\projects\c&c\code /v /s /u + + +############################################################################# +# Explicit rules to build the master zip files (used by Codewrite merge). +BILL_R.ZIP: pkzip.dat .SYMBOLIC + -del f:\projects\c&c\bill_r.zip + -pkzip -p -u -x@pkzip.dat f:\projects\c&c\bill_r.zip *.* eng\*.* + +MARIA_L.ZIP: pkzip.dat .SYMBOLIC + -del f:\projects\c&c\maria_l.zip + -pkzip -p -u -x@pkzip.dat f:\projects\c&c\maria_l.zip *.* eng\*.* + +BARRY_G.ZIP: pkzip.dat .SYMBOLIC + -del f:\projects\c&c\barry_g.zip + -pkzip -p -u -x@pkzip.dat f:\projects\c&c\barry_g.zip *.* eng\*.* + +PHIL_G.ZIP: pkzip.dat .SYMBOLIC + -del f:\projects\c&c\phil_g.zip + -pkzip -p -u -x@pkzip.dat f:\projects\c&c\phil_g.zip *.* eng\*.* + +DAVID_D.ZIP: pkzip.dat .SYMBOLIC + -del f:\projects\c&c\david_d.zip + -pkzip -p -u -x@pkzip.dat f:\projects\c&c\david_d.zip *.* eng\*.* + +BILL_P.ZIP: pkzip.dat .SYMBOLIC + -del f:\projects\c&c\bill_p.zip + -pkzip -p -u -x@pkzip.dat f:\projects\c&c\bill_p.zip *.* eng\*.* + +# Special "mega-zip" process. +JOE_B.ZIP: pkzip.dat .SYMBOLIC + -pkzip -rp -u -xcps\*.* -x@pkzip.dat f:\projects\c&c\joe_b.zip + + +############################################################################# +# Rebuilds the master zip control file. This is used by the zip process. +PKZIP.DAT: makefile .SYMBOLIC + %create $^@ + %append $^@ *.000 + %append $^@ *.@@@ + %append $^@ *.bak + %append $^@ *.bat + %append $^@ *.cfg + %append $^@ *.dat + %append $^@ *.def + %append $^@ *.doc + %append $^@ *.dsw + %append $^@ *.err + %append $^@ *.ewp + %append $^@ *.ext + %append $^@ *.i + %append $^@ *.ide + %append $^@ *.lnk + %append $^@ *.log + %append $^@ *.lst + %append $^@ *.mac + %append $^@ *.map + %append $^@ *.mk + %append $^@ *.mk1 + %append $^@ *.obj + %append $^@ *.out + %append $^@ *.pch + %append $^@ *.pfs + %append $^@ *.pif + %append $^@ *.pjt + %append $^@ *.prf + %append $^@ *.pro + %append $^@ *.ptg + %append $^@ *.rc + %append $^@ *.rep + %append $^@ *.rpt + %append $^@ *.rst + %append $^@ *.sym + %append $^@ *.tag + %append $^@ *.td + %append $^@ *.td + %append $^@ *.tgt + %append $^@ *.tmp + %append $^@ *.tr + %append $^@ *.tr + %append $^@ *.vec + %append $^@ *.wpj + %append $^@ *.zip + %append $^@ state.rst + + +#--------------------------------------------------------------------------- +# Dependency macros (makes defining dependencies easier) +#--------------------------------------------------------------------------- +GENERAL_H = defines.h function.h externs.h conquer.h vector.h heap.h & + debug.h jshell.h compat.h + +TECHNO_H = facing.h techno.h mission.h stage.h cargo.h object.h abstract.h + +UNIT_H = unit.h tarcom.h turret.h drive.h foot.h radio.h $(TECHNO_H) + +INFANTRY_H = infantry.h foot.h radio.h $(TECHNO_H) + +AIRCRAFT_H = aircraft.h fly.h radio.h $(TECHNO_H) + +BUILDING_H = building.h radio.h $(TECHNO_H) + +BULLET_H = bullet.h fly.h fuse.h object.h abstract.h + +OBJ_H = $(UNIT_H) $(INFANTRY_H) $(AIRCRAFT_H) $(BUILDING_H) $(BULLET_H) + +MAP_H = base.h mapedit.h mouse.h scroll.h help.h tab.h power.h sidebar.h & + radar.h display.h map.h gscreen.h cell.h + +GADGET_H = textbtn.h shapebtn.h slider.h gauge.h dial8.h edit.h & + toggle.h list.h cheklist.h control.h gadget.h link.h + +FILE_H = ccfile.h cdfile.h mixfile.h rawfile.h wwfile.h link.h + +TEAM_H = team.h teamtype.h trigger.h + +IPX_H = ipx.h ipxaddr.h + +NET_H = combuf.h connect.h connmgr.h ipx.h ipxaddr.h ipxconn.h ipxgconn.h & + ipxmgr.h noseqcon.h nullconn.h nullmgr.h + +MISC_H = ftimer.h logic.h score.h theme.h event.h queue.h special.h + +#--------------------------------------------------------------------------- +# Dependencies (This is not totally accurate; if you're not sure, rebuild +# everything!) +#--------------------------------------------------------------------------- +aadata.obj: aadata.cpp $(GENERAL_H) type.h + +abstract.obj: abstract.cpp $(GENERAL_H) abstract.h + +adata.obj: adata.cpp $(GENERAL_H) type.h + +aircraft.obj: aircraft.cpp $(GENERAL_H) $(AIRCRAFT_H) + +anim.obj: anim.cpp $(GENERAL_H) anim.h stage.h object.h + +audio.obj: audio.cpp $(GENERAL_H) audio.h + +base.obj: base.cpp $(GENERAL_H) type.h + +bbdata.obj: bbdata.cpp $(GENERAL_H) type.h + +bdata.obj: bdata.cpp $(GENERAL_H) type.h + +building.obj: building.cpp $(GENERAL_H) $(BUILDING_H) + +bullet.obj: bullet.cpp $(GENERAL_H) $(BULLET_H) + +cargo.obj: cargo.cpp $(GENERAL_H) $(TECHNO_H) cargo.h + +ccfile.obj: ccfile.cpp $(GENERAL_H) $(FILE_H) + +cdata.obj: cdata.cpp $(GENERAL_H) type.h + +cdfile.obj: cdfile.cpp $(GENERAL_H) $(FILE_H) + +cell.obj: cell.cpp $(GENERAL_H) $(MAP_H) + +cheklist.obj: cheklist.cpp $(GENERAL_H) $(GADGET_H) + +colrlist.obj: colrlist.cpp $(GENERAL_H) $(GADGET_H) + +combat.obj: combat.cpp $(GENERAL_H) + +combuf.obj: combuf.cpp $(GENERAL_H) combuf.h + +confdlg.obj: confdlg.cpp $(GENERAL_H) $(GADGET_H) + +connect.obj: connect.cpp $(GENERAL_H) connect.h combuf.h + +conquer.obj: conquer.cpp $(GENERAL_H) $(MISC_H) $(OBJ_H) + +const.obj: const.cpp $(GENERAL_H) + +control.obj: control.cpp $(GENERAL_H) $(GADGET_H) + +coord.obj: coord.cpp $(GENERAL_H) + +coorda.obj: coorda.asm + +credits.obj: credits.cpp $(GENERAL_H) credits.h + +crew.obj: crew.cpp $(GENERAL_H) crew.h + +debug.obj: debug.cpp $(GENERAL_H) debug.h + +deldlg.obj: deldlg.cpp $(GENERAL_H) $(GADGET_H) + +descdlg.obj: descdlg.cpp $(GENERAL_H) $(GADGET_H) + +dial8.obj: dial8.cpp $(GENERAL_H) $(GADGET_H) + +dialog.obj: dialog.cpp $(GENERAL_H) + +display.obj: display.cpp $(GENERAL_H) $(MAP_H) + +dpmi.obj: dpmi.cpp $(GENERAL_H) + +drive.obj: drive.cpp $(GENERAL_H) $(TECHNO_H) + +edit.obj: edit.cpp $(GENERAL_H) $(GADGET_H) + +event.obj: event.cpp $(GENERAL_H) $(MISC_H) + +facing.obj: facing.cpp $(GENERAL_H) facing.h + +factory.obj: factory.cpp $(GENERAL_H) factory.h + +findpath.obj: findpath.cpp $(GENERAL_H) + +flasher.obj: flasher.cpp $(GENERAL_H) flasher.h + +fly.obj: fly.cpp $(GENERAL_H) fly.h + +foot.obj: foot.cpp $(GENERAL_H) $(INFANTRY_H) + +fuse.obj: fuse.cpp $(GENERAL_H) fuse.h + +gadget.obj: gadget.cpp $(GENERAL_H) $(GADGET_H) + +gamedlg.obj: gamedlg.cpp $(GENERAL_H) $(GADGET_H) + +gauge.obj: gauge.cpp $(GENERAL_H) $(GADGET_H) + +globals.obj: globals.cpp $(GENERAL_H) + +goptions.obj: goptions.cpp $(GENERAL_H) $(GADGET_H) + +gscreen.obj: gscreen.cpp $(GENERAL_H) $(MAP_H) + +hdata.obj: hdata.cpp $(GENERAL_H) type.h + +heap.obj: heap.cpp $(GENERAL_H) $(MISC_H) + +help.obj: help.cpp $(GENERAL_H) $(MAP_H) + +house.obj: house.cpp $(GENERAL_H) house.h + +idata.obj: idata.cpp $(GENERAL_H) type.h + +infantry.obj: infantry.cpp $(GENERAL_H) $(INFANTRY_H) + +ini.obj: ini.cpp $(GENERAL_H) $(MISC_H) + +init.obj: init.cpp $(GENERAL_H) $(MISC_H) $(OBJ_H) + +iomap.obj: iomap.cpp $(GENERAL_H) $(MAP_H) $(FILE_H) + +ioobj.obj: ioobj.cpp $(GENERAL_H) $(FILE_H) $(OBJ_H) + +ipx.obj: ipx.cpp $(GENERAL_H) $(IPX_H) + +ipxaddr.obj: ipxaddr.cpp $(GENERAL_H) $(IPX_H) + +ipxconn.obj: ipxconn.cpp $(GENERAL_H) $(NET_H) + +ipxgconn.obj: ipxgconn.cpp $(GENERAL_H) $(NET_H) + +ipxmgr.obj: ipxmgr.cpp $(GENERAL_H) $(NET_H) + +ipxreal.ibn: + +jshell.obj: jshell.cpp $(GENERAL_H) $(MISC_H) + +keyfbuff.obj: keyfbuff.asm + +keyframe.obj: keyframe.cpp $(GENERAL_H) + +layer.obj: layer.cpp $(GENERAL_H) $(MISC_H) + +link.obj: link.cpp $(GENERAL_H) link.h + +list.obj: list.cpp $(GENERAL_H) $(GADGET_H) + +loaddlg.obj: loaddlg.cpp $(GENERAL_H) $(GADGET_H) + +logic.obj: logic.cpp $(GENERAL_H) $(MISC_H) + +map.obj: map.cpp $(GENERAL_H) $(MAP_H) + +mapsel.obj: mapsel.cpp $(GENERAL_H) + +mapeddlg.obj: mapeddlg.cpp $(GENERAL_H) $(MAP_H) $(OBJ_H) + +mapedit.obj: mapedit.cpp $(GENERAL_H) $(MAP_H) $(OBJ_H) + +mapedplc.obj: mapedplc.cpp $(GENERAL_H) $(MAP_H) $(OBJ_H) + +mapedsel.obj: mapedsel.cpp $(GENERAL_H) $(MAP_H) $(OBJ_H) + +mapedtm.obj: mapedtm.cpp $(GENERAL_H) $(MAP_H) $(OBJ_H) + +menus.obj: menus.cpp $(GENERAL_H) + +mission.obj: mission.cpp $(GENERAL_H) mission.h stage.h cargo.h object.h abstract.h + +mixfile.obj: mixfile.cpp $(GENERAL_H) $(FILE_H) + +monoc.obj: monoc.cpp $(GENERAL_H) + +mouse.obj: mouse.cpp $(GENERAL_H) $(MAP_H) + +mplayer.obj: mplayer.cpp $(GENERAL_H) + +msgbox.obj: msgbox.cpp $(GENERAL_H) $(GADGET_H) + +msglist.obj: msglist.cpp $(GENERAL_H) $(GADGET_H) + +netdlg.obj: netdlg.cpp $(GENERAL_H) $(GADGET_H) $(NET_H) + +noseqcon.obj: noseqcon.cpp $(GENERAL_H) noseqcon.h connect.h combuf.h + +nullconn.obj: nullconn.cpp $(GENERAL_H) nullconn.h noseqcon.h connect.h combuf.h + +nulldlg.obj: nulldlg.cpp $(GENERAL_H) nullmgr.h nullconn.h connmgr.h noseqcon.h connect.h combuf.h + +nullmgr.obj: nullmgr.cpp $(GENERAL_H) nullmgr.h nullconn.h connmgr.h noseqcon.h connect.h combuf.h + +object.obj: object.cpp $(GENERAL_H) object.h abstract.h + +odata.obj: odata.cpp $(GENERAL_H) type.h + +options.obj: options.cpp $(GENERAL_H) $(GADGET_H) + +overlay.obj: overlay.cpp $(GENERAL_H) overlay.h object.h + +power.obj: power.cpp $(GENERAL_H) $(MAP_H) + +profile.obj: profile.cpp $(GENERAL_H) + +queue.obj: queue.cpp $(GENERAL_H) $(MISC_H) + +rand.obj: rand.cpp $(GENERAL_H) + +radar.obj: radar.cpp $(GENERAL_H) $(MAP_H) + +radio.obj: radio.cpp $(GENERAL_H) $(TECHNO_H) + +rawfile.obj: rawfile.cpp $(GENERAL_H) $(FILE_H) + +reinf.obj: reinf.cpp $(GENERAL_H) $(MISC_H) + +savedlg.obj: savedlg.cpp $(GENERAL_H) $(GADGET_H) + +saveload.obj: saveload.cpp $(GENERAL_H) $(MISC_H) + +scenario.obj: scenario.cpp $(GENERAL_H) $(MISC_H) + +score.obj: score.cpp $(GENERAL_H) + +scroll.obj: scroll.cpp $(GENERAL_H) $(MAP_H) + +sdata.obj: sdata.cpp $(GENERAL_H) type.h + +shapebtn.obj: shapebtn.cpp $(GENERAL_H) $(GADGET_H) + +sidebar.obj: sidebar.cpp $(GENERAL_H) $(MAP_H) + +slider.obj: slider.cpp $(GENERAL_H) $(GADGET_H) + +smudge.obj: smudge.cpp $(GENERAL_H) smudge.h object.h + +sounddlg.obj: sounddlg.cpp $(GENERAL_H) $(GADGET_H) sounddlg.h + +startup.obj: startup.cpp $(GENERAL_H) + +stuff.obj: stuff.cpp $(GENERAL_H) $(MISC_H) + +support.obj: support.asm + +tab.obj: tab.cpp $(GENERAL_H) $(MAP_H) + +tarcom.obj: tarcom.cpp $(GENERAL_H) $(UNIT_H) + +target.obj: target.cpp $(GENERAL_H) target.h + +tdata.obj: tdata.cpp $(GENERAL_H) type.h + +team.obj: team.cpp $(GENERAL_H) $(TEAM_H) + +teamtype.obj: teamtype.cpp $(GENERAL_H) $(TEAM_H) + +techno.obj: techno.cpp $(GENERAL_H) $(TECHNO_H) + +template.obj: template.cpp $(GENERAL_H) template.h object.h + +terrain.obj: terrain.cpp $(GENERAL_H) terrain.h stage.h object.h + +textbtn.obj: textbtn.cpp $(GENERAL_H) $(GADGET_H) + +theme.obj: theme.cpp $(GENERAL_H) theme.h + +toggle.obj: toggle.cpp $(GENERAL_H) $(GADGET_H) + +trigger.obj: trigger.cpp $(GENERAL_H) $(TEAM_H) + +turret.obj: turret.cpp $(GENERAL_H) $(UNIT_H) + +txtlabel.obj: txtlabel.cpp $(GENERAL_H) $(GADGET_H) + +txtprnt.obj: txtprnt.asm + +udata.obj: udata.cpp $(GENERAL_H) type.h + +unit.obj: unit.cpp $(GENERAL_H) $(UNIT_H) + +vector.obj: vector.cpp $(GENERAL_H) $(MISC_H) + +visudlg.obj: visudlg.cpp $(GENERAL_H) $(GADGET_H) + +#-------------------------------------------------------------------------- +# The IPX assembly object files are created in a special way: +# IPXREAL is the real-mode code that gets stuffed into memory by protected- +# mode code. It's assembled, then converted into a big header file by +# the 'EBN' utility. +# IPXPROT is the protected-mode code that includes IPXREAL.IBN, and +# provides routines to let C++ read the code's address & size. +#-------------------------------------------------------------------------- +#obj\ipxreal.ibn: obj\ipxreal.obj +# %create $^*.rsp +# %append $^*.rsp obj\$^&.obj +# %append $^*.rsp obj\$^&.exe +# %append $^*.rsp obj\$^&.map +# tlink @$^*.rsp +# tdstrip obj\ipxreal.exe +# utils\ebn obj\ipxreal.exe + +r:\ipxreal.ibn: r:\ipxreal.obj + %create $^*.rsp + %append $^*.rsp r:\$^&.obj + %append $^*.rsp r:\$^&.exe + %append $^*.rsp r:\$^&.map + tlink @$^*.rsp + tdstrip r:\ipxreal.exe + utils\ebn r:\ipxreal.exe + +ipxreal.obj: ipxreal.asm + tasm /zn /la /ml /m2 ipxreal.asm, r:\ipxreal.obj +# tasm /zn /la /ml /m2 ipxreal.asm, obj\ipxreal.obj + +#ipxprot.obj: obj\ipxreal.ibn ipxprot.asm +ipxprot.obj: r:\ipxreal.ibn ipxprot.asm + +#**************************** End of makefile ****************************** diff --git a/MAP.CPP b/MAP.CPP new file mode 100644 index 0000000..8eaf100 --- /dev/null +++ b/MAP.CPP @@ -0,0 +1,1183 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\map.cpv 2.17 16 Oct 1995 16:52:22 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MAP.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : August 20, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * MapClass::Cell_Distance -- Determines the distance between two cells. * + * MapClass::Cell_Region -- Determines the region from a specified cell number. * + * MapClass::Cell_Threat -- Gets a houses threat value for a cell * + * MapClass::Close_Object -- Finds a clickable close object to the specified coordinate. * + * MapClass::In_Radar -- Is specified cell in the radar map? * + * MapClass::Init -- clears all cells * + * MapClass::Logic -- Handles map related logic functions. * + * MapClass::One_Time -- Performs special one time initializations for the map. * + * MapClass::Overlap_Down -- computes & marks object's overlap cells * + * MapClass::Overlap_Up -- Computes & clears object's overlap cells * + * MapClass::Overpass -- Performs any final cleanup to a freshly constructed map. * + * MapClass::Pick_Up -- Removes specified object from the map. * + * MapClass::Place_Down -- Places the specified object onto the map. * + * MapClass::Place_Random_Crate -- Places a crate at random location on map. * + * MapClass::Read_Binary -- reads the map's binary image file * + * MapClass::Set_Map_Dimensions -- Initialize the map. * + * MapClass::Sight_From -- Mark as visible the cells within a specified radius. * + * MapClass::Validate -- validates every cell on the map * + * MapClass::Write_Binary -- writes the map's binary image file * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#define MCW MAP_CELL_W +int const MapClass::RadiusOffset[] = { + /* 0 */ 0, + /* 1 */ (-MCW*1)-1,(-MCW*1)+0,(-MCW*1)+1,-1,1,(MCW*1)-1,(MCW*1)+0,(MCW*1)+1, + /* 2 */ (-MCW*2)-1,(-MCW*2)+0,(-MCW*2)+1,(-MCW*1)-2,(-MCW*1)+2,-2,2,(MCW*1)-2,(MCW*1)+2,(MCW*2)-1,(MCW*2)+0,(MCW*2)+1, + /* 3 */ (-MCW*3)-1,(-MCW*3)+0,(-MCW*3)+1,(-MCW*2)-2,(-MCW*2)+2,(-MCW*1)-3,(-MCW*1)+3,-3,3,(MCW*1)-3,(MCW*1)+3,(MCW*2)-2,(MCW*2)+2,(MCW*3)-1,(MCW*3)+0,(MCW*3)+1, + /* 4 */ (-MCW*4)-1,(-MCW*4)+0,(-MCW*4)+1,(-MCW*3)-3,(-MCW*3)-2,(-MCW*3)+2,(-MCW*3)+3,(-MCW*2)-3,(-MCW*2)+3,(-MCW*1)-4,(-MCW*1)+4,-4,4,(MCW*1)-4,(MCW*1)+4,(MCW*2)-3,(MCW*2)+3,(MCW*3)-3,(MCW*3)-2,(MCW*3)+2,(MCW*3)+3,(MCW*4)-1,(MCW*4)+0,(MCW*4)+1, + /* 5 */ (-MCW*5)-1,(-MCW*5)+0,(-MCW*5)+1,(-MCW*4)-3,(-MCW*4)-2,(-MCW*4)+2,(-MCW*4)+3,(-MCW*3)-4,(-MCW*3)+4,(-MCW*2)-4,(-MCW*2)+4,(-MCW*1)-5,(-MCW*1)+5,-5,5,(MCW*1)-5,(MCW*1)+5,(MCW*2)-4,(MCW*2)+4,(MCW*3)-4,(MCW*3)+4,(MCW*4)-3,(MCW*4)-2,(MCW*4)+2,(MCW*4)+3,(MCW*5)-1,(MCW*5)+0,(MCW*5)+1, + /* 6 */ (-MCW*6)-1,(-MCW*6)+0,(-MCW*6)+1,(-MCW*5)-3,(-MCW*5)-2,(-MCW*5)+2,(-MCW*5)+3,(-MCW*4)-4,(-MCW*4)+4,(-MCW*3)-5,(-MCW*3)+5,(-MCW*2)-5,(-MCW*2)+5,(-MCW*1)-6,(-MCW*1)+6,-6,6,(MCW*1)-6,(MCW*1)+6,(MCW*2)-5,(MCW*2)+5,(MCW*3)-5,(MCW*3)+5,(MCW*4)-4,(MCW*4)+4,(MCW*5)-3,(MCW*5)-2,(MCW*5)+2,(MCW*5)+3,(MCW*6)-1,(MCW*6)+0,(MCW*6)+1, + /* 7 */ (-MCW*7)-1,(-MCW*7)+0,(-MCW*7)+1,(-MCW*6)-3,(-MCW*6)-2,(-MCW*6)+2,(-MCW*6)+3,(-MCW*5)-5,(-MCW*5)-4,(-MCW*5)+4,(-MCW*5)+5,(-MCW*4)-5,(-MCW*4)+5,(-MCW*3)-6,(-MCW*3)+6,(-MCW*2)-6,(-MCW*2)+6,(-MCW*1)-7,(-MCW*1)+7,-7,7,(MCW*1)-7,(MCW*1)+7,(MCW*2)-6,(MCW*2)+6,(MCW*3)-6,(MCW*3)+6,(MCW*4)-5,(MCW*4)+5,(MCW*5)-5,(MCW*5)-4,(MCW*5)+4,(MCW*5)+5,(MCW*6)-3,(MCW*6)-2,(MCW*6)+2,(MCW*6)+3,(MCW*7)-1,(MCW*7)+0,(MCW*7)+1, + /* 8 */ (-MCW*8)-1,(-MCW*8)+0,(-MCW*8)+1,(-MCW*7)-3,(-MCW*7)-2,(-MCW*7)+2,(-MCW*7)+3,(-MCW*6)-5,(-MCW*6)-4,(-MCW*6)+4,(-MCW*6)+5,(-MCW*5)-6,(-MCW*5)+6,(-MCW*4)-6,(-MCW*4)+6,(-MCW*3)-7,(-MCW*3)+7,(-MCW*2)-7,(-MCW*2)+7,(-MCW*1)-8,(-MCW*1)+8,-8,8,(MCW*1)-8,(MCW*1)+8,(MCW*2)-7,(MCW*2)+7,(MCW*3)-7,(MCW*3)+7,(MCW*4)-6,(MCW*4)+6,(MCW*5)-6,(MCW*5)+6,(MCW*6)-5,(MCW*6)-4,(MCW*6)+4,(MCW*6)+5,(MCW*7)-3,(MCW*7)-2,(MCW*7)+2,(MCW*7)+3,(MCW*8)-1,(MCW*8)+0,(MCW*8)+1, + /* 9 */ (-MCW*9)-1,(-MCW*9)+0,(-MCW*9)+1,(-MCW*8)-3,(-MCW*8)-2,(-MCW*8)+2,(-MCW*8)+3,(-MCW*7)-5,(-MCW*7)-4,(-MCW*7)+4,(-MCW*7)+5,(-MCW*6)-6,(-MCW*6)+6,(-MCW*5)-7,(-MCW*5)+7,(-MCW*4)-7,(-MCW*4)+7,(-MCW*3)-8,(-MCW*3)+8,(-MCW*2)-8,(-MCW*2)+8,(-MCW*1)-9,(-MCW*1)+9,-9,9,(MCW*1)-9,(MCW*1)+9,(MCW*2)-8,(MCW*2)+8,(MCW*3)-8,(MCW*3)+8,(MCW*4)-7,(MCW*4)+7,(MCW*5)-7,(MCW*5)+7,(MCW*6)-6,(MCW*6)+6,(MCW*7)-5,(MCW*7)-4,(MCW*7)+4,(MCW*7)+5,(MCW*8)-3,(MCW*8)-2,(MCW*8)+2,(MCW*8)+3,(MCW*9)-1,(MCW*9)+0,(MCW*9)+1, + /* 10 */ (-MCW*10)-1,(-MCW*10)+0,(-MCW*10)+1,(-MCW*9)-3,(-MCW*9)-2,(-MCW*9)+2,(-MCW*9)+3,(-MCW*8)-5,(-MCW*8)-4,(-MCW*8)+4,(-MCW*8)+5,(-MCW*7)-7,(-MCW*7)-6,(-MCW*7)+6,(-MCW*7)+7,(-MCW*6)-7,(-MCW*6)+7,(-MCW*5)-8,(-MCW*5)+8,(-MCW*4)-8,(-MCW*4)+8,(-MCW*3)-9,(-MCW*3)+9,(-MCW*2)-9,(-MCW*2)+9,(-MCW*1)-10,(-MCW*1)+10,-10,10,(MCW*1)-10,(MCW*1)+10,(MCW*2)-9,(MCW*2)+9,(MCW*3)-9,(MCW*3)+9,(MCW*4)-8,(MCW*4)+8,(MCW*5)-8,(MCW*5)+8,(MCW*6)-7,(MCW*6)+7,(MCW*7)-7,(MCW*7)-6,(MCW*7)+6,(MCW*7)+7,(MCW*8)-5,(MCW*8)-4, + (MCW*8)+4,(MCW*8)+5,(MCW*9)-3,(MCW*9)-2,(MCW*9)+2,(MCW*9)+3,(MCW*10)-1,(MCW*10)+0,(MCW*10)+1, +}; + +int const MapClass::RadiusCount[11] = {1,9,21,37,61,89,121,161,205,253,309}; + + +CellClass *BlubCell; + +/*********************************************************************************************** + * MapClass::One_Time -- Performs special one time initializations for the map. * + * * + * This routine is used by the game initialization function in order to perform any one * + * time initializations required for the map. This includes allocation of the map and * + * setting up its default dimensions. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine MUST be called once and only once. * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + * 12/01/1994 BR : Added CellTriggers initialization * + *=============================================================================================*/ +void MapClass::One_Time(void) +{ + GScreenClass::One_Time(); + + XSize = MAP_CELL_W; + YSize = MAP_CELL_H; + Size = XSize * YSize; + + /* + ** Allocate the cell array. + */ + Alloc_Cells(); + + /* + ** Init the CellTriggers array to the required size. + */ + CellTriggers.Resize(MAP_CELL_TOTAL); +} + + +/*********************************************************************************************** + * MapClass::Init_Clear -- clears the map & buffers to a known state * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/17/1995 BRR : Created. * + *=============================================================================================*/ +void MapClass::Init_Clear(void) +{ + GScreenClass::Init_Clear(); + Init_Cells(); + TiberiumScan = 0; + IsForwardScan = true; + TiberiumGrowthCount = 0; + TiberiumSpreadCount = 0; +} + + +/*********************************************************************************************** + * MapClass::Alloc_Cells -- allocates the cell array * + * * + * This routine should be called at One_Time, and after loading the Map object from a save * + * game, but prior to loading the cell objects. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/17/1995 BRR : Created. * + *=============================================================================================*/ +void MapClass::Alloc_Cells(void) +{ + /* + ** Assume that whatever the contents of the VectorClass are is garbage + ** (it may have been loaded from a save-game file), so zero it out first. + */ + Vector = 0; + VectorMax = 0; + IsAllocated = 0; + Resize(Size); +} + + +/*********************************************************************************************** + * MapClass::Free_Cells -- frees the cell array * + * * + * This routine is used by the Load_Game routine to free the map's cell array before loading * + * the map object from disk; the array is then re-allocated & cleared before the cell objects * + * are loaded. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/17/1995 BRR : Created. * + *=============================================================================================*/ +void MapClass::Free_Cells(void) +{ + Clear(); +} + + +/*********************************************************************************************** + * MapClass::Init_Cells -- Initializes the cell array to a fresh state. * + * * + * This routine is used by Init_Clear to set the cells to a known state; it's also used by * + * the Load_Game routine to init all cells before loading a set of cells from disk, so it * + * needs to be called separately from the other Init_xxx() routines. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/17/1995 BRR : Created. * + *=============================================================================================*/ +void MapClass::Init_Cells(void) +{ + TotalValue = 0; +#ifdef NEVER + Free_Cells(); + Alloc_Cells(); +#else + memset(&Map[0], 0, sizeof(CellClass) * (MAP_CELL_TOTAL/2)); + memset(&Map[MAP_CELL_TOTAL/2], 0, sizeof(CellClass) * (MAP_CELL_TOTAL/2)); + for (int index = 0; index < MAP_CELL_TOTAL; index++) { + Map[index].Overlay = OVERLAY_NONE; + Map[index].Smudge = SMUDGE_NONE; + Map[index].TType = TEMPLATE_NONE; + Map[index].Owner = HOUSE_NONE; + Map[index].InfType = HOUSE_NONE; + } +#endif +} + + +/*********************************************************************************************** + * MapClass::Set_Map_Dimensions -- Set map dimensions. * + * * + * This routine is used to set the legal limits and position of the * + * map as it relates to the overall map array. Typically, this is * + * called by the scenario loading code. * + * * + * INPUT: x,y -- The X and Y coordinate of the "upper left" corner * + * of the map. * + * * + * w,h -- The width and height of the legal map. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/14/1994 JLB : Created. * + *=============================================================================================*/ +void MapClass::Set_Map_Dimensions(int x, int y, int w, int h) +{ + MapCellX = x; + MapCellY = y; + MapCellWidth = w; + MapCellHeight = h; +} + + +/*********************************************************************************************** + * MapClass::Sight_From -- Mark as visible the cells within a specified radius. * + * * + * This routine is used to reveal the cells around a specific location. * + * Typically, as a unit moves or is deployed, this routine will be * + * called. Since it deals with MANY cells, it needs to be extremely * + * fast. * + * * + * INPUT: cell -- The coordinate that the sighting originates from. * + * * + * sightrange-- The distance in cells that sighting extends. * + * * + * incremental-- Is this an incremental sighting. In other * + * words, has this function been called before where * + * the center coordinate is no more than one cell * + * distant from the last time? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/19/1992 JLB : Created. * + * 03/08/1994 JLB : Updated to use sight table and incremental flag. * + * 05/18/1994 JLB : Converted to member function. * + *=============================================================================================*/ +void MapClass::Sight_From(CELL cell, int sightrange, bool incremental) +{ + int xx; // Center cell X coordinate (bounds checking). + int const *ptr; // Offset pointer. + int count; // Counter for number of offsets to process. + + /* + ** Units that are off-map cannot sight. + */ + if (!In_Radar(cell)) return; + if (!sightrange || sightrange > 10) return; + + /* + ** Determine logical cell coordinate for center scan point. + */ + xx = Cell_X(cell); + + /* + ** Incremental scans only scan the outer rings. Full scans + ** scan all internal cells as well. + */ + count = RadiusCount[sightrange]; + ptr = &RadiusOffset[0]; + if (incremental) { + if (sightrange > 1) { + ptr += RadiusCount[sightrange-2]; + count -= RadiusCount[sightrange-2]; + } + } + + /* + ** Process all offsets required for the desired scan. + */ + while (count--) { + CELL newcell; // New cell with offset. + int xdiff; // New cell's X coordinate distance from center. + + newcell = cell + *ptr++; + + /* + ** Determine if the map edge has been wrapped. If so, + ** then don't process the cell. + */ + if ((unsigned)newcell >= MAP_CELL_TOTAL) continue; + xdiff = Cell_X(newcell) - xx; + xdiff = ABS(xdiff); + if (xdiff > sightrange) continue; + if (Distance(newcell, cell) > sightrange) continue; + + /* + ** Map the cell. For incremental scans, then update + ** adjacent cells as well. For full scans, just update + ** the cell itself. + */ + if (!(*this)[newcell].IsMapped) { + Map.Map_Cell(newcell, PlayerPtr); + } + } +} + + +/*********************************************************************************************** + * MapClass::Cell_Distance -- Determines the distance between two cells. * + * * + * This routine will return with the calculated "straight line" * + * distance between the two cells specified. It uses the dragon strike * + * method of distance calculation. * + * * + * INPUT: cell1 -- First cell. * + * * + * cell2 -- Second cell. * + * * + * OUTPUT: Returns with the "cell" distance between the two cells * + * specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/29/1994 JLB : Created. * + * 04/30/1994 JLB : Converted to member function. * + *=============================================================================================*/ +int MapClass::Cell_Distance(CELL cell1, CELL cell2) +{ + register int x,y; // Difference on X and Y axis. + + x = Cell_X(cell1) - Cell_X(cell2); + y = Cell_Y(cell1) - Cell_Y(cell2); + + if (x < 0) x = -x; + if (y < 0) y = -y; + + if (x > y) { + return(x + (y>>1)); + } + return(y + (x>>1)); +} + + +/*********************************************************************************************** + * MapClass::In_Radar -- Is specified cell in the radar map? * + * * + * This determines if the specified cell can be within the navigable * + * bounds of the map. Technically, this means, any cell that can be * + * scanned by radar. If a cell returns false from this function, then * + * the player could never move to or pass over this cell. * + * * + * INPUT: cell -- The cell to examine. * + * * + * OUTPUT: bool; Is this cell possible to be displayed on radar? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + * 04/30/1994 JLB : Converted to member function. * + * 05/01/1994 JLB : Speeded up. * + *=============================================================================================*/ +bool MapClass::In_Radar(CELL cell) const +{ + if (cell & 0xF000) return(false); + return((unsigned)(Cell_X(cell) - MapCellX) < (unsigned)MapCellWidth && (unsigned)(Cell_Y(cell) - MapCellY) < (unsigned)MapCellHeight); +} + + +/*********************************************************************************************** + * MapClass::Place_Down -- Places the specified object onto the map. * + * * + * This routine is used to place an object onto the map. It updates the "occupier" of the * + * cells that this object covers. The cells are determined from the Occupy_List function * + * provided by the object. Only one cell can have an occupier and this routine is the only * + * place that sets this condition. * + * * + * INPUT: cell -- The cell to base object occupation around. * + * * + * object -- The object to place onto the map. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/31/1994 JLB : Created. * + *=============================================================================================*/ +void MapClass::Place_Down(CELL cell, ObjectClass * object) +{ + if (!object) return; + + short const *list = object->Occupy_List(); + while (*list != REFRESH_EOL) { + CELL newcell = cell + *list++; + if ((unsigned)newcell < MAP_CELL_TOTAL) { + (*this)[newcell].Occupy_Down(object); + (*this)[newcell].Recalc_Attributes(); + (*this)[newcell].Redraw_Objects(); + } + } + + list = object->Overlap_List(); + while (*list != REFRESH_EOL) { + CELL newcell = cell + *list++; + if ((unsigned)newcell < MAP_CELL_TOTAL) { + (*this)[newcell].Overlap_Down(object); + (*this)[newcell].Redraw_Objects(); + } + } +} + + +/*********************************************************************************************** + * MapClass::Pick_Up -- Removes specified object from the map. * + * * + * The object specified is removed from the map by this routine. This will remove the * + * occupation flag for all the cells that the object covers. The cells that are covered * + * are determined from the Occupy_List function. * + * * + * INPUT: cell -- The cell that the object is centered about. * + * * + * object -- Pointer to the object that will be removed. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/31/1994 JLB : Created. * + *=============================================================================================*/ +void MapClass::Pick_Up(CELL cell, ObjectClass * object) +{ + if (!object) return; + + short const *list = object->Occupy_List(); + while (*list != REFRESH_EOL) { + CELL newcell = cell + *list++; + if ((unsigned)newcell < MAP_CELL_TOTAL) { + (*this)[newcell].Occupy_Up(object); + (*this)[newcell].Recalc_Attributes(); + (*this)[newcell].Redraw_Objects(); + } + } + + list = object->Overlap_List(); + while (*list != REFRESH_EOL) { + CELL newcell = cell + *list++; + if ((unsigned)newcell < MAP_CELL_TOTAL) { + (*this)[newcell].Overlap_Up(object); + (*this)[newcell].Redraw_Objects(); + } + } +} + + +/*********************************************************************************************** + * MapClass::Overlap_Down -- computes & marks object's overlap cells * + * * + * This routine is just like Place_Down, but it doesn't mark the cell's Occupier. * + * This routine is used to implement MARK_OVERLAP_DOWN, which is useful for changing * + * an object's render size, but not its logical size (ie when it's selected or an * + * animation is attached to it). * + * * + * INPUT: * + * cell -- The cell to base object overlap around. * + * object -- The object to place onto the map. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/12/1995 BRR : Created. * + *=============================================================================================*/ +void MapClass::Overlap_Down(CELL cell, ObjectClass * object) +{ + if (!object) return; + + short const *list = object->Overlap_List(); + while (*list != REFRESH_EOL) { + CELL newcell = cell + *list++; + if ((unsigned)newcell < MAP_CELL_TOTAL) { + (*this)[newcell].Overlap_Down(object); + (*this)[newcell].Redraw_Objects(); + } + } +} + + +/*********************************************************************************************** + * MapClass::Overlap_Up -- Computes & clears object's overlap cells * + * * + * This routine is just like Pick_Up, but it doesn't mark the cell's Occupier. * + * This routine is used to implement MARK_OVERLAP_UP, which is useful for changing * + * an object's render size, but not its logical size (ie when it's selected or an * + * animation is attached to it). * + * * + * INPUT: * + * cell -- The cell to base object overlap around. * + * object -- The object to place onto the map. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/12/1995 BRR : Created. * + *=============================================================================================*/ +void MapClass::Overlap_Up(CELL cell, ObjectClass * object) +{ + if (!object) return; + + short const *list = object->Overlap_List(); + while (*list != REFRESH_EOL) { + CELL newcell = cell + *list++; + if ((unsigned)newcell < MAP_CELL_TOTAL) { + (*this)[newcell].Overlap_Up(object); + (*this)[newcell].Redraw_Objects(); + } + } +} + + +/*********************************************************************************************** + * MapClass::Overpass -- Performs any final cleanup to a freshly constructed map. * + * * + * This routine will clean up anything necessary with the presumption that the map has * + * been freshly created. Such things to clean up include various tiberium concentrations. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the total credit value of the tiberium on the map. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + * 02/13/1995 JLB : Returns total tiberium worth. * + * 02/15/1995 JLB : Optimal scan. * + *=============================================================================================*/ +long MapClass::Overpass(void) +{ + long value = 0; + + /* + ** Smooth out Tiberium. Cells that are not surrounded by other tiberium + ** will be reduced in density. + */ + for (int y = 0; y < MapCellHeight-1; y++) { + for (int x = 0; x < MapCellWidth; x++) { + value += (*this)[(MapCellY+y) * MAP_CELL_W + (MapCellX+x)].Tiberium_Adjust(true); + } + } + return(value); +} + + +/*********************************************************************************************** + * MapClass::Read_Binary -- reads the map's binary image file * + * * + * INPUT: * + * root root filename for scenario * + * crc ptr to CRC value to update * + * * + * OUTPUT: * + * 1 = success, 0 = failure * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/14/1994 BR : Created. * + * 01/08/1995 JLB : Fixup any obsolete icons detected. * + *=============================================================================================*/ +#ifdef DEMO +bool MapClass::Read_Binary(char const * root, unsigned long *) +#else +bool MapClass::Read_Binary(char const * root, unsigned long *crc) +#endif +{ + CCFileClass file; + char fname[_MAX_FNAME+_MAX_EXT]; + int i; + char *map; + void *rawmap; + void const * shape; + + /* + ** Filename = INI name with BIN extension. + */ + sprintf(fname,"%s.BIN",root); + + /* + ** Create object & open file. + */ + file.Set_Name(fname); + if (!file.Is_Available()) { + return(false); + } + file.Open(READ); + + /* + ** Loop through all cells. + */ + CellClass * cellptr = &Map[0]; + for (i = 0; i < MAP_CELL_TOTAL; i++) { + struct { + TemplateType TType; // Template type. + unsigned char TIcon; // Template icon number. + } temp; + + if (file.Read(&temp, sizeof(temp)) != sizeof(temp)) break; + if (temp.TType == (TemplateType)255) { + temp.TType = TEMPLATE_NONE; + } + + /* + ** Verify that the template type actually contains the template number specified. If + ** an illegal icon was specified, then replace it with clear terrain. + */ + if (temp.TType != TEMPLATE_CLEAR1 && temp.TType != TEMPLATE_NONE) { + shape = TemplateTypeClass::As_Reference(temp.TType).Get_Image_Data(); + if (shape) { + rawmap = Get_Icon_Set_Map(shape); + if (rawmap) { + map = (char*)rawmap; + if (map[temp.TIcon] == -1) { + temp.TIcon = 0; + temp.TType = TEMPLATE_NONE; + } + } + } + } + + cellptr->TType = temp.TType; + cellptr->TIcon = temp.TIcon; + cellptr->Recalc_Attributes(); + +#ifndef DEMO + Add_CRC(crc, (unsigned long)cellptr->TType); + Add_CRC(crc, (unsigned long)cellptr->TIcon); +#endif + + cellptr++; + } + + /* + ** Close the file. + */ + file.Close(); + + return(i == MAP_CELL_TOTAL); +} + + +/*********************************************************************************************** + * MapClass::Write_Binary -- writes the map's binary image file * + * * + * INPUT: * + * root root filename for scenario * + * * + * OUTPUT: * + * 1 = success, 0 = failure * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/14/1994 BR : Created. * + *=============================================================================================*/ +bool MapClass::Write_Binary(char const * root) +{ + CCFileClass *file; + char fname[_MAX_FNAME+_MAX_EXT]; + int i; + + /* + ** Filename = INI name with BIN extension. + */ + sprintf(fname,"%s.BIN",root); + + /* + ** Create object & open file. + */ + file = new CCFileClass(fname); + file->Open(WRITE); + + /* + ** Loop through all cells. + */ + for (i = 0; i < MAP_CELL_TOTAL; i++) { + /* + ** Save TType. + */ + if (file->Write (&(Map[i].TType), sizeof(TemplateType)) != sizeof(TemplateType)) { + file->Close(); + delete file; + return(false); + } + + /* + ** Save TIcon. + */ + if (file->Write (&(Map[i].TIcon), sizeof(unsigned char)) != sizeof(unsigned char)) { + file->Close(); + delete file; + return(false); + } + } + + /* + ** Close the file. + */ + file->Close(); + delete file; + + return(true); +} + + +/*********************************************************************************************** + * MapClass::Logic -- Handles map related logic functions. * + * * + * Manages tiberium growth and spread. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/11/1995 JLB : Created. * + * 07/09/1995 JLB : Handles two directional scan. * + * 08/01/1995 JLB : Gives stronger weight to blossom trees. * + *=============================================================================================*/ +void MapClass::Logic(void) +{ + /* + ** Bail early if there is no allowed growth or spread of Tiberium. + */ + if (!Special.IsTGrowth && !Special.IsTSpread) return; + + /* + ** Scan another block of the map in order to accumulate the potential + ** Tiberium cells that can grow or spread. + */ + int subcount = 30; + for (int index = TiberiumScan; index < MAP_CELL_TOTAL; index++) { + CELL cell = index; + if (!IsForwardScan) cell = (MAP_CELL_TOTAL-1) - index; + CellClass *ptr = &(*this)[cell]; + + if (Special.IsTGrowth && ptr->Land_Type() == LAND_TIBERIUM && ptr->OverlayData < 11) { + if (TiberiumGrowthCount < sizeof(TiberiumGrowth)/sizeof(TiberiumGrowth[0])) { + TiberiumGrowth[TiberiumGrowthCount++] = cell; + } else { + TiberiumGrowth[Random_Pick(0, TiberiumGrowthCount-1)] = cell; + } + } + + /* + ** Heavy Tiberium growth can spread. + */ + TerrainClass * terrain = ptr->Cell_Terrain(); + if (Special.IsTSpread && + (ptr->Land_Type() == LAND_TIBERIUM && ptr->OverlayData > 6) || + (terrain && terrain->Class->IsTiberiumSpawn)) { + + int tries = 1; + if (terrain) tries = 3; + for (int i = 0; i < tries; i++) { + if (TiberiumSpreadCount < sizeof(TiberiumSpread)/sizeof(TiberiumSpread[0])) { + TiberiumSpread[TiberiumSpreadCount++] = cell; + } else { + TiberiumSpread[Random_Pick(0, TiberiumSpreadCount-1)] = cell; + } + } + } + subcount--; + if (!subcount) break; + } + TiberiumScan = index; + + if (TiberiumScan >= MAP_CELL_TOTAL) { + int tries = 1; + if (Special.IsTFast || GameToPlay != GAME_NORMAL) tries = 2; + TiberiumScan = 0; + IsForwardScan = (IsForwardScan == false); + + /* + ** Growth logic. + */ + if (TiberiumGrowthCount) { + for (int i = 0; i < tries; i++) { + CELL cell = TiberiumGrowth[Random_Pick(0, TiberiumGrowthCount-1)]; + CellClass * newcell = &(*this)[cell]; + if (newcell->Land_Type() == LAND_TIBERIUM && newcell->OverlayData < 12-1) { + newcell->OverlayData++; + } + } + } + TiberiumGrowthCount = 0; + + /* + ** Spread logic. + */ + if (TiberiumSpreadCount) { + for (int i = 0; i < tries; i++) { + CELL cell = TiberiumSpread[Random_Pick(0, TiberiumSpreadCount-1)]; + + /* + ** Find a pseudo-random adjacent cell that doesn't contain any tiberium. + */ + if (Map.In_Radar(cell)) { + FacingType offset = Random_Pick(FACING_N, FACING_NW); + for (FacingType index = FACING_N; index < FACING_COUNT; index++) { + CellClass *newcell = &(*this)[cell].Adjacent_Cell(index+offset); + + if (newcell && newcell->Cell_Object() == NULL && newcell->Land_Type() == LAND_CLEAR && newcell->Overlay == OVERLAY_NONE) { + bool found = false; + + switch (newcell->TType) { + case TEMPLATE_BRIDGE1: + case TEMPLATE_BRIDGE2: + case TEMPLATE_BRIDGE3: + case TEMPLATE_BRIDGE4: + break; + + default: + found = true; + new OverlayClass(Random_Pick(OVERLAY_TIBERIUM1, OVERLAY_TIBERIUM12), newcell->Cell_Number()); + newcell->OverlayData = 1; + break; + + } + if (found) break; + } + } + } + } + } + TiberiumSpreadCount = 0; + } +} + + +/*********************************************************************************************** + * MapClass::Cell_Region -- Determines the region from a specified cell number. * + * * + * Use this routine to determine what region a particular cell lies in. * + * * + * INPUT: cell -- The cell number to examine. * + * * + * OUTPUT: Returns with the region that the specified cell occupies. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/15/1995 JLB : Created. * + *=============================================================================================*/ +int MapClass::Cell_Region(CELL cell) +{ + return((Cell_X(cell) / REGION_WIDTH) + 1) + (((Cell_Y(cell) / REGION_HEIGHT) + 1) * MAP_REGION_WIDTH); +} + + +/*************************************************************************** + * MapClass::Cell_Threat -- Gets a houses threat value for a cell * + * * + * INPUT: CELL cell - the cell number to check * + * HouseType house - the house to check * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/25/1995 PWG : Created. * + *=========================================================================*/ +int MapClass::Cell_Threat(CELL cell, HousesType house) +{ + int threat = HouseClass::As_Pointer(house)->Regions[Map.Cell_Region(Map[cell].Cell_Number())].Threat_Value(); + if (!threat && Map[cell].IsVisible) { + threat = 1; + } + return(threat); +} + + +/*********************************************************************************************** + * MapClass::Place_Random_Crate -- Places a crate at random location on map. * + * * + * This routine will place a crate at a random location on the map. This routine will only * + * make a limited number of attempts to place and if unsuccessful, it will not place any. * + * * + * INPUT: none * + * * + * OUTPUT: Was a crate successfully placed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +bool MapClass::Place_Random_Crate(void) +{ + int old = ScenarioInit; + ScenarioInit = 0; + for (int index = 0; index < 100; index++) { + int x = Random_Pick(0, MapCellWidth-1); + int y = Random_Pick(0, MapCellHeight-1); + CELL cell = XY_Cell(MapCellX+x, MapCellY+y); + + CellClass * ptr = &(*this)[cell]; + if (ptr->Is_Generally_Clear() && ptr->Overlay == OVERLAY_NONE) { + ptr->Overlay = OVERLAY_WOOD_CRATE; + ptr->OverlayData = 0; + ptr->Redraw_Objects(); + ScenarioInit = old; + return(true); + } + } + ScenarioInit = old; + return(false); +} + + +/*************************************************************************** + * MapClass::Validate -- validates every cell on the map * + * * + * This is a debugging routine, designed to detect memory trashers that * + * alter the map. This routine is slow, but thorough. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = map is OK, false = an error was found * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/08/1995 BRR : Created. * + *=========================================================================*/ +int MapClass::Validate(void) +{ + CELL cell; + TemplateType ttype; + unsigned char ticon; + TemplateTypeClass const *tclass; + unsigned char map[13*8]; + OverlayType overlay; + SmudgeType smudge; + ObjectClass *obj; + LandType land; + int i; + +BlubCell = &((*this)[797]); + +if (BlubCell->Overlapper[1]) { + obj = BlubCell->Overlapper[1]; + if (obj) { + if (obj->IsInLimbo) + obj = obj; + } +} + + /*------------------------------------------------------------------------ + Check every cell on the map, even those that aren't displayed, + in the hopes of detecting a memory trasher. + ------------------------------------------------------------------------*/ + for (cell = 0; cell < MAP_CELL_TOTAL; cell++) { + /*..................................................................... + Validate Template & Icon data + .....................................................................*/ + ttype = (*this)[cell].TType; + ticon = (*this)[cell].TIcon; + if (ttype >= TEMPLATE_COUNT && ttype != TEMPLATE_NONE) + return(false); + + /*..................................................................... + To validate the icon value, we have to get a copy of the template's + "icon map"; this map will have 0xff's in spots where there is no + icon. If the icon value is out of range or points to an invalide spot, + return an error. + .....................................................................*/ + if (ttype != TEMPLATE_NONE) { + tclass = &TemplateTypeClass::As_Reference(ttype); + ticon = (*this)[cell].TIcon; + Mem_Copy(Get_Icon_Set_Map(tclass->Get_Image_Data()), map, + tclass->Width * tclass->Height); + if (ticon < 0 || + ticon >= (tclass->Width * tclass->Height) || + map[ticon]==0xff) + return (false); + } + + /*..................................................................... + Validate Overlay + .....................................................................*/ + overlay = (*this)[cell].Overlay; + if (overlay < OVERLAY_NONE || overlay >= OVERLAY_COUNT) + return(false); + + /*..................................................................... + Validate Smudge + .....................................................................*/ + smudge = (*this)[cell].Smudge; + if (smudge < SMUDGE_NONE || smudge >= SMUDGE_COUNT) + return(false); + + /*..................................................................... + Validate LandType + .....................................................................*/ + land = (*this)[cell].Land_Type(); + if (land < LAND_CLEAR || land >= LAND_COUNT) + return(false); + + /*..................................................................... + Validate Occupier + .....................................................................*/ + obj = (*this)[cell].Cell_Occupier(); + if (obj) { + if ( + ((unsigned int)obj & 0xff000000) || + ((unsigned int)obj->Next & 0xff000000) || + ((unsigned int)obj->Trigger & 0xff000000) || + obj->IsInLimbo || + ((unsigned int)Coord_Cell(obj->Coord) > 4095) + ) + return (false); + } + + /*..................................................................... + Validate Overlappers + .....................................................................*/ + for (i = 0; i < 3; i++) { + obj = (*this)[cell].Overlapper[i]; + if (obj) { + if ( + ((unsigned int)obj & 0xff000000) || + ((unsigned int)obj->Next & 0xff000000) || + ((unsigned int)obj->Trigger & 0xff000000) || + obj->IsInLimbo || + ((unsigned int)Coord_Cell(obj->Coord) > 4095) + ) + return (false); + } + } + } + + return (true); +} + + +/*********************************************************************************************** + * MapClass::Close_Object -- Finds a clickable close object to the specified coordinate. * + * * + * This routine is used by the mouse input processing code to find a clickable object * + * close to coordinate specified. This is for targeting as well as selection determination. * + * * + * INPUT: coord -- The coordinate to scan for close object from. * + * * + * OUTPUT: Returns with a pointer to an object that is nearby the specified coordinate. * + * * + * WARNINGS: There could be a cloaked object at the location, but it won't be considered * + * if it is not owned by the player. * + * * + * HISTORY: * + * 08/20/1995 JLB : Created. * + *=============================================================================================*/ +ObjectClass * MapClass::Close_Object(COORDINATE coord) const +{ + ObjectClass * object = 0; + int distance = 0; + CELL cell = Coord_Cell(coord); + + /* + ** Scan through current and adjacent cells, looking for the + ** closest object (within reason) to the specified coordinate. + */ + static int _offsets[] = {0, -1, 1, -MAP_CELL_W, MAP_CELL_W, MAP_CELL_W-1, MAP_CELL_W+1, -(MAP_CELL_W-1), -(MAP_CELL_W+1)}; + for (int index = 0; index < (sizeof(_offsets) / sizeof(_offsets[0])); index++) { + + /* + ** Examine the cell for close object. Make sure that the cell actually is a + ** legal one. + */ + CELL newcell = cell + _offsets[index]; + if (In_Radar(newcell)) { + + /* + ** Search through all objects that occupy this cell and then + ** find the closest object. Check against any previously found object + ** to ensure that it is actually closer. + */ + ObjectClass * o = (*this)[newcell].Cell_Occupier(); + while (o) { + + /* + ** Special case check to ignore cloaked object if not owned by the player. + */ + if (!o->Is_Techno() || ((TechnoClass *)o)->IsOwnedByPlayer || ((TechnoClass *)o)->Cloak != CLOAKED) { + int d=-1; + if (o->What_Am_I() == RTTI_BUILDING) { + d = Distance(coord, Cell_Coord(newcell)); + if (d > 0x00B5) d = -1; + } else { + d = Distance(coord, o->Center_Coord()); + } + if (d >= 0 && (!object || d < distance)) { + distance = d; + object = o; + } + } + o = o->Next; + } + } + } + + /* + ** Only return the object if it is within 1/4 cell distance from the specified + ** coordinate. + */ + if (object && distance > 0xB5) { + object = 0; + } + return(object); +} + diff --git a/MAP.H b/MAP.H new file mode 100644 index 0000000..031abd9 --- /dev/null +++ b/MAP.H @@ -0,0 +1,154 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\map.h_v 2.19 16 Oct 1995 16:46:12 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MAP.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 29, 1994 * + * * + * Last Update : April 29, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef MAP_H +#define MAP_H + +#include "gscreen.h" + +#define BIGMAP 0 + + +class MapClass: public GScreenClass +{ + public: + + /* + ** Initialization + */ + virtual void One_Time(void); // Theater-specific inits + virtual void Init_Clear(void); // Clears all to known state + virtual void Alloc_Cells(void); // Allocates buffers + virtual void Free_Cells(void); // Frees buffers + virtual void Init_Cells(void); // Frees buffers + + /*-------------------------------------------------------- + ** Main functions that deal with groupings of cells within the map or deals with the cell + ** as it relates to the map - not what the cell contains. + */ + ObjectClass * Close_Object(COORDINATE coord) const; + virtual void Detach(ObjectClass * ) {}; + int Cell_Region(CELL cell); + int Cell_Threat(CELL cell, HousesType house); + int Cell_Distance(CELL cell1, CELL cell2); + bool In_Radar(CELL cell) const; + void Sight_From(CELL cell, int sightrange, bool incremental=false); + void Place_Down(CELL cell, ObjectClass * object); + void Pick_Up(CELL cell, ObjectClass * object); + void Overlap_Down(CELL cell, ObjectClass * object); + void Overlap_Up(CELL cell, ObjectClass * object); + bool Read_Binary(char const *root, unsigned long *crc); + bool Write_Binary(char const *root); + bool Place_Random_Crate(void); + + long Overpass(void); + + virtual void Logic(void); + virtual void Set_Map_Dimensions(int x, int y, int w, int h); + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /* + ** Debug routine + */ + int Validate(void); + + /* + ** This is the dimensions and position of the sub section of the global map. + ** It is this region that appears on the radar map and constrains normal + ** movement. + */ + int MapCellX; + int MapCellY; + int MapCellWidth; + int MapCellHeight; + + /* + ** This is the total value of all harvestable Tiberium on the map. + */ + long TotalValue; + + protected: + + /* + ** These are the size dimensions of the underlying array of cell objects. + ** This is the dimensions of the "map" that the tactical view is + ** restricted to. + */ + int XSize; + int YSize; + int Size; + + static int const RadiusCount[11]; + static int const RadiusOffset[]; + + private: + friend class CellClass; + + /* + ** Tiberium growth potiential cells are recorded here. + */ + CELL TiberiumGrowth[50]; + int TiberiumGrowthCount; + + /* + ** List of cells that are full enough strength that they could spread + ** Tiberium to adjacent cells. + */ + CELL TiberiumSpread[50]; + int TiberiumSpreadCount; + + /* + ** This is the current cell number in the incremental map scan process. + */ + CELL TiberiumScan; + + /* + ** If the Tiberium map scan is processing forward, then this flag + ** will be true. It alternates between forward and backward scanning + ** in order to avoid the "Tiberium Creep". + */ + unsigned IsForwardScan:1; + + enum MapEnum {SCAN_AMOUNT=MAP_CELL_TOTAL}; +}; + +#endif diff --git a/MAPEDDLG.CPP b/MAPEDDLG.CPP new file mode 100644 index 0000000..e2e22f9 --- /dev/null +++ b/MAPEDDLG.CPP @@ -0,0 +1,3850 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\mapeddlg.cpv 2.18 16 Oct 1995 16:49:00 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MAPEDDLG.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : November 18, 1994 * + * * + * Last Update : December 12, 1994 [BR] * + * * + *-------------------------------------------------------------------------* + * Map Editor dialogs & main menu options * + *-------------------------------------------------------------------------* + * Functions: * + * MapEditClass::New_Scenario -- creates a new scenario * + * MapEditClass::Load_Scenario -- loads a scenario INI file * + * MapEditClass::Save_Scenario -- saves current scenario to an INI file * + * MapEditClass::Pick_Scenario -- dialog for choosing scenario * + * MapEditClass::Size_Map -- lets user set size & location of map * + * MapEditClass::Scenario_Dialog -- scenario global parameters dialog * + * MapEditClass::Handle_Triggers -- processes the trigger dialogs * + * MapEditClass::Select_Trigger -- lets user select a trigger * + * MapEditClass::Edit_Trigger -- lets user edit a [new] trigger * + * MapEditClass::Import_Triggers -- lets user import triggers * + * MapEditClass::Import_Teams -- lets user import teams * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#ifdef SCENARIO_EDITOR + + +/*************************************************************************** + * MapEditClass::New_Scenario -- creates a new scenario * + * * + * - Prompts user for scenario data (house, scenario #); sets globals * + * PlayerPtr (for house) & Scenario (for scenario #) * + * - Prompts user for map size * + * - Initializes the scenario by calling Clear_Scenario(), which calls * + * everybody's Init() routine * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = new scenario created, -1 = not * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 10/21/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::New_Scenario(void) +{ + int scen_num = Scenario; + ScenarioPlayerType player = ScenPlayer; + ScenarioDirType dir = ScenDir; + ScenarioVarType var = ScenVar; + int rc; + HousesType house; + + /* + ------------------------ Prompt for scenario info ------------------------ + */ + rc = Pick_Scenario("New Scenario", &scen_num, &player, &dir, &var, 1); + if (rc != 0) { + return(-1); + } + + /* + -------------------------- Blow away everything -------------------------- + */ + Clear_Scenario(); + + /* + ----------------------------- Set parameters ----------------------------- + */ + Scenario = scen_num; + ScenPlayer = player; + ScenDir = dir; + ScenVar = var; + Set_Scenario_Name(ScenarioName,scen_num,player,dir,var); + + /* + ----------------------------- Create houses ------------------------------ + */ + for (house = HOUSE_FIRST; house < HOUSE_COUNT; house++) { + new HouseClass(house); + } + + if (ScenPlayer == SCEN_PLAYER_MPLAYER) { + PlayerPtr = HouseClass::As_Pointer(HOUSE_MULTI1); + PlayerPtr->IsHuman = true; + LastHouse = HOUSE_MULTI1; + } else { + if (player == SCEN_PLAYER_GDI) { + PlayerPtr = HouseClass::As_Pointer(HOUSE_GOOD); + PlayerPtr->IsHuman = true; + Base.House = HOUSE_BAD; + } else { + if (player == SCEN_PLAYER_NOD) { + PlayerPtr = HouseClass::As_Pointer(HOUSE_BAD); + PlayerPtr->IsHuman = true; + Base.House = HOUSE_GOOD; + } else { + PlayerPtr = HouseClass::As_Pointer(HOUSE_MULTI4); + PlayerPtr->IsHuman = true; + Base.House = HOUSE_MULTI4; + } + } + LastHouse = HOUSE_GOOD; + } + + /* + -------------------------- Init the entire map --------------------------- + */ + Init_Clear(); + Fill_In_Data(); + + /* + -------------------------- Prompt for map size --------------------------- + */ + Size_Map(-1,-1,20,20); + + /* + ------ Set the Home & Reinforcement Cells to the center of the map ------- + */ + Waypoint[WAYPT_REINF] = XY_Cell(MapCellX + MapCellWidth / 2, MapCellY + MapCellHeight / 2); + Waypoint[WAYPT_HOME] = XY_Cell(MapCellX + MapCellWidth / 2, MapCellY + MapCellHeight / 2); + (*this)[Coord_Cell(TacticalCoord)].IsWaypoint = 1; + Flag_Cell(Coord_Cell(TacticalCoord)); + + ScenarioInit++; + Set_Tactical_Position(Cell_Coord(Waypoint[WAYPT_HOME])); + ScenarioInit--; + + return(0); +} + + +/*************************************************************************** + * MapEditClass::Load_Scenario -- loads a scenario INI file * + * * + * - Prompts user for scenario data (house, scenario #); sets globals * + * PlayerPtr (for house) & Scenario (for scenario #) * + * - Loads the INI file for that scenario * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 10/21/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::Load_Scenario(void) +{ + int scen_num = Scenario; + ScenarioPlayerType player = ScenPlayer; + ScenarioDirType dir = ScenDir; + ScenarioVarType var = ScenVar; + int rc; + + /* + ------------------------ Prompt for scenario info ------------------------ + */ + rc = Pick_Scenario("Load Scenario", &scen_num, &player, &dir, &var, 1); + if (rc != 0) { + return(-1); + } + + /* + ----------------------------- Set parameters ----------------------------- + */ + Scenario = scen_num; + ScenPlayer = player; + ScenDir = dir; + ScenVar = var; + Set_Scenario_Name(ScenarioName,scen_num,player,dir,var); + + /*------------------------------------------------------------------------ + Read_Scenario_Ini() must be able to set PlayerPtr to the right house: + - Reading the INI will create the house objects + - PlayerPtr must be set before any Techno objects are created + - For GDI or NOD scenarios, PlayerPtr is set by reading the INI; + but for multiplayer, it's set via the MPlayerLocalID; so, here we have + to set various multiplayer variables to fool the Assign_Houses() routine + into working properly. + ------------------------------------------------------------------------*/ + if (ScenPlayer == SCEN_PLAYER_MPLAYER) { + MPlayerLocalID = Build_MPlayerID(2,HOUSE_GOOD); + MPlayerCount = 1; + LastHouse = HOUSE_MULTI1; + } + else if (ScenPlayer==SCEN_PLAYER_JP) { + PlayerPtr = HouseClass::As_Pointer(HOUSE_MULTI4); + PlayerPtr->IsHuman = true; + Base.House = HOUSE_MULTI4; + } + else { + LastHouse = HOUSE_GOOD; + } + + /* + -------------------------- Blow away everything -------------------------- + */ + Clear_Scenario(); + + /* + ------------------------------ Read the INI ------------------------------ + */ + if (Read_Scenario_Ini(ScenarioName) == 0) { + CCMessageBox().Process("Unable to read scenario!"); + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + } else { + Fill_In_Data(); + Set_Palette(GamePalette); + } + + return(0); +} + + +/*************************************************************************** + * MapEditClass::Save_Scenario -- saves current scenario to an INI file * + * * + * - Prompts user for scenario data (house, scenario #); sets globals * + * PlayerPtr (for house) & Scenario (for scenario #) * + * - Saves the INI file for this scenario * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = OK, -1 = error/cancel * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 10/21/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::Save_Scenario(void) +{ + int scen_num = Scenario; + ScenarioPlayerType player = ScenPlayer; + ScenarioDirType dir = ScenDir; + ScenarioVarType var = ScenVar; + int rc; + FILE *fp; + char fname[13]; + + /* + ------------------------ Prompt for scenario info ------------------------ + */ + rc = Pick_Scenario("Save Scenario", &scen_num, &player, &dir, &var, 0); + if (rc != 0) { + return(-1); + } + + /* + ------------------- Warning if scenario already exists ------------------- + */ + Set_Scenario_Name(fname, scen_num, player, dir, var); + fp = fopen(fname,"rb"); + if (fp) { + fclose(fp); + rc = CCMessageBox().Process("File exists. Replace?", TXT_YES, TXT_NO); + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + if (rc==1) + return(-1); + } + + /* + ----------------------------- Set parameters ----------------------------- + */ + Scenario = scen_num; + ScenPlayer = player; + ScenDir = dir; + ScenVar = var; + Set_Scenario_Name(ScenarioName,scen_num,player,dir,var); + + /*------------------------------------------------------------------------ + Player may have changed from GDI to NOD, so change playerptr accordingly + ------------------------------------------------------------------------*/ + if (ScenPlayer == SCEN_PLAYER_GDI || ScenPlayer==SCEN_PLAYER_NOD) { + if (ScenPlayer==SCEN_PLAYER_GDI) { + PlayerPtr = HouseClass::As_Pointer(HOUSE_GOOD); + PlayerPtr->IsHuman = true; + Base.House = HOUSE_BAD; + } else { + if (ScenPlayer == SCEN_PLAYER_NOD) { + PlayerPtr = HouseClass::As_Pointer(HOUSE_BAD); + PlayerPtr->IsHuman = true; + Base.House = HOUSE_GOOD; + } else { + PlayerPtr = HouseClass::As_Pointer(HOUSE_MULTI4); + PlayerPtr->IsHuman = true; + Base.House = HOUSE_MULTI4; + } + } + LastHouse = HOUSE_GOOD; + } + + /* + ----------------------------- Write the INI ------------------------------ + */ + Write_Scenario_Ini(ScenarioName); + + return(0); +} + + +/*************************************************************************** + * MapEditClass::Pick_Scenario -- dialog for choosing scenario * + * * + * Prompts user for: * + * - House (GDI, NOD) * + * - Scenario # * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Caption ³ * + * ³ ³ * + * ³ Scenario ___ ³ * + * ³ Version ___ ³ * + * ³ ³ * + * ³ [East] [West] ³ * + * ³ ³ * + * ³ [ GDI ] ³ * + * ³ [ NOD ] ³ * + * ³ [Multi-Player] ³ * + * ³ ³ * + * ³ [OK] [Cancel] ³ * + * ³ ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * caption string to use as a title * + * scen_nump output: ptr to scenario # * + * playerp output: ptr to player type * + * dirp output: ptr to direction * + * varp output: ptr to variation * + * multi 1 = allow to change single/multiplayer; 0 = not * + * * + * OUTPUT: * + * 0 = OK, -1 = cancel * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 10/21/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::Pick_Scenario(char const * caption, int *scen_nump, + ScenarioPlayerType *playerp, ScenarioDirType *dirp, ScenarioVarType *varp, + int multi) +{ + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + enum { + D_DIALOG_W = 400, // dialog width + D_DIALOG_H = 328, // dialog height + D_DIALOG_X = ((640 - D_DIALOG_W) / 2), // centered x-coord + D_DIALOG_Y = ((400 - D_DIALOG_H) / 2), // centered y-coord + D_DIALOG_CX = D_DIALOG_X + (D_DIALOG_W / 2), // coord of x-center + + D_TXT8_H = 22, // ht of 8-pt text + D_MARGIN = 14, // margin width/height + + D_SCEN_W = 90, // Scenario # width + D_SCEN_H = 18, // Scenario # height + D_SCEN_X = D_DIALOG_CX + 5, // Scenario # x + D_SCEN_Y = D_DIALOG_Y + D_MARGIN + D_TXT8_H + D_MARGIN, // Scenario # y + + D_VARA_W = 26, // Version A width + D_VARA_H = 18, // Version A height + D_VARA_X = D_DIALOG_CX - (D_VARA_W * 5) / 2, // Version A x + D_VARA_Y = D_SCEN_Y + D_SCEN_H + D_MARGIN, // Version A y + + D_VARB_W = 26, // Version B width + D_VARB_H = 18, // Version B height + D_VARB_X = D_VARA_X + D_VARA_W, // Version B x + D_VARB_Y = D_SCEN_Y + D_SCEN_H + D_MARGIN, // Version B y + + D_VARC_W = 26, // Version C width + D_VARC_H = 18, // Version C height + D_VARC_X = D_VARB_X + D_VARB_W, // Version C x + D_VARC_Y = D_SCEN_Y + D_SCEN_H + D_MARGIN, // Version C y + + D_VARD_W = 26, // Version D width + D_VARD_H = 18, // Version D height + D_VARD_X = D_VARC_X + D_VARC_W, // Version D x + D_VARD_Y = D_SCEN_Y + D_SCEN_H + D_MARGIN, // Version D y + + D_VARLOSE_W = 26, // Version Lose width + D_VARLOSE_H = 18, // Version Lose height + D_VARLOSE_X = D_VARD_X + D_VARD_W, // Version Lose x + D_VARLOSE_Y = D_SCEN_Y + D_SCEN_H + D_MARGIN, // Version Lose y + + D_EAST_W = 100, // EAST width + D_EAST_H = 18, // EAST height + D_EAST_X = D_DIALOG_CX - D_EAST_W - 5, // EAST x + D_EAST_Y = D_VARLOSE_Y + D_VARLOSE_H + D_MARGIN,// EAST y + + D_WEST_W = 100, // WEST width + D_WEST_H = 18, // WEST height + D_WEST_X = D_DIALOG_CX + 5, // WEST x + D_WEST_Y = D_VARLOSE_Y + D_VARLOSE_H + D_MARGIN,// EAST y + + D_GDI_W = 140, // GDI width + D_GDI_H = 18, // GDI height + D_GDI_X = D_DIALOG_CX - (D_GDI_W / 2), // GDI x + D_GDI_Y = D_EAST_Y + D_EAST_H + D_MARGIN, // GDI y + + D_NOD_W = 140, // NOD width + D_NOD_H = 18, // NOD height + D_NOD_X = D_DIALOG_CX - (D_NOD_W / 2), // NOD x + D_NOD_Y = D_GDI_Y + D_GDI_H, // NOD y + + D_NEU_W = 140, // Neutral width + D_NEU_H = 18, // Neutral height + D_NEU_X = D_DIALOG_CX - (D_NOD_W / 2), // Neutral x + D_NEU_Y = D_NOD_Y + D_NOD_H, // Neutral y + + D_MPLAYER_W = 140, // Multi-Player width + D_MPLAYER_H = 18, // Multi-Player height + D_MPLAYER_X = D_DIALOG_CX - (D_MPLAYER_W / 2), // Multi-Player x + D_MPLAYER_Y = D_NEU_Y + D_NEU_H, // Multi-Player y + + D_OK_W = 90, // OK width + D_OK_H = 18, // OK height + D_OK_X = D_DIALOG_CX - D_OK_W - 5, // OK x + D_OK_Y = D_DIALOG_Y + D_DIALOG_H - D_OK_H - D_MARGIN, // OK y + + D_CANCEL_W = 90, // Cancel width + D_CANCEL_H = 18, // Cancel height + D_CANCEL_X = D_DIALOG_CX + 5, // Cancel x + D_CANCEL_Y = D_DIALOG_Y + D_DIALOG_H - D_CANCEL_H - D_MARGIN, // Cancel y + + }; + /*........................................................................ + Button enumerations + ........................................................................*/ + enum { + BUTTON_GDI=100, + BUTTON_NOD, + BUTTON_NEUTRAL, + BUTTON_MPLAYER, + BUTTON_EAST, + BUTTON_WEST, + BUTTON_OK, + BUTTON_CANCEL, + BUTTON_SCENARIO, + BUTTON_VAR_A, + BUTTON_VAR_B, + BUTTON_VAR_C, + BUTTON_VAR_D, + BUTTON_VAR_L, + }; + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display; // requested redraw level + bool process; // loop while true + KeyNumType input; + bool cancel = false; // true = user cancels + /*........................................................................ + Other Variables + ........................................................................*/ + char scen_buf[10]={0}; // buffer for editing scenario # + /*........................................................................ + Buttons + ........................................................................*/ + ControlClass *commands = NULL; // the button list + + EditClass editbtn (BUTTON_SCENARIO, + scen_buf, 5, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_SCEN_X, D_SCEN_Y, D_SCEN_W, D_SCEN_H, EditClass::NUMERIC); + + TextButtonClass varabtn (BUTTON_VAR_A, "A", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_VARA_X, D_VARA_Y, D_VARA_W, D_VARA_H); + + TextButtonClass varbbtn (BUTTON_VAR_B, "B", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_VARB_X, D_VARB_Y, D_VARB_W, D_VARB_H); + + TextButtonClass varcbtn (BUTTON_VAR_C, "C", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_VARC_X, D_VARC_Y, D_VARC_W, D_VARC_H); + + TextButtonClass vardbtn (BUTTON_VAR_D, "D", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_VARD_X, D_VARD_Y, D_VARD_W, D_VARD_H); + + TextButtonClass varlbtn (BUTTON_VAR_L, "L", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_VARLOSE_X, D_VARLOSE_Y, D_VARLOSE_W, D_VARLOSE_H); + + TextButtonClass gdibtn (BUTTON_GDI, "GDI", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_GDI_X, D_GDI_Y, D_GDI_W, D_GDI_H); + + TextButtonClass nodbtn (BUTTON_NOD, "NOD", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NOD_X, D_NOD_Y, D_NOD_W, D_NOD_H); + + TextButtonClass playermbtn (BUTTON_MPLAYER, "Multi Player", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MPLAYER_X, D_MPLAYER_Y, D_MPLAYER_W, D_MPLAYER_H); + + TextButtonClass eastbtn (BUTTON_EAST, "East", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_EAST_X, D_EAST_Y, D_EAST_W, D_EAST_H); + + TextButtonClass westbtn (BUTTON_WEST, "West", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_WEST_X, D_WEST_Y, D_WEST_W, D_WEST_H); + + TextButtonClass okbtn (BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_OK_X, D_OK_Y, D_OK_W, D_OK_H); + + TextButtonClass cancelbtn (BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_CANCEL_X, D_CANCEL_Y, D_CANCEL_W, D_CANCEL_H); + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + + sprintf(scen_buf,"%d",(*scen_nump)); // init edit buffer + editbtn.Set_Text(scen_buf,5); + + varabtn.Turn_Off(); + varbbtn.Turn_Off(); + varcbtn.Turn_Off(); + vardbtn.Turn_Off(); + varlbtn.Turn_Off(); + switch (*varp) { + case SCEN_VAR_A: + varabtn.Turn_On(); + break; + + case SCEN_VAR_B: + varbbtn.Turn_On(); + break; + + case SCEN_VAR_C: + varcbtn.Turn_On(); + break; + + case SCEN_VAR_D: + vardbtn.Turn_On(); + break; + + case SCEN_VAR_LOSE: + varlbtn.Turn_On(); + break; + } + + /* + ......................... Create the button list ......................... + */ + commands = &editbtn; + varabtn.Add_Tail(*commands); + varbbtn.Add_Tail(*commands); + varcbtn.Add_Tail(*commands); + vardbtn.Add_Tail(*commands); + varlbtn.Add_Tail(*commands); + if (multi) { + gdibtn.Add_Tail(*commands); + nodbtn.Add_Tail(*commands); + playermbtn.Add_Tail(*commands); + } else { + if ((*playerp) == SCEN_PLAYER_MPLAYER) { + playermbtn.Add_Tail(*commands); + } else { + gdibtn.Add_Tail(*commands); + nodbtn.Add_Tail(*commands); + } + } + eastbtn.Add_Tail(*commands); + westbtn.Add_Tail(*commands); + okbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + /* + ......................... Init the button states ......................... + */ + if ((*playerp) == SCEN_PLAYER_GDI) { + gdibtn.Turn_On(); + nodbtn.Turn_Off(); + playermbtn.Turn_Off(); + } else { + if ((*playerp) == SCEN_PLAYER_NOD) { + gdibtn.Turn_Off(); + nodbtn.Turn_On(); + playermbtn.Turn_Off(); + } else { + gdibtn.Turn_Off(); + nodbtn.Turn_Off(); + playermbtn.Turn_On(); + } + } + + if ((*dirp)==SCEN_DIR_EAST) { + eastbtn.Turn_On(); + westbtn.Turn_Off(); + } else { + eastbtn.Turn_Off(); + westbtn.Turn_On(); + } + + /* + -------------------------- Main Processing Loop -------------------------- + */ + display = REDRAW_ALL; + process = true; + while (process) { + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + + /* + ...................... Display the dialog box ...................... + */ + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + Dialog_Box(D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W, D_DIALOG_H); + Draw_Caption(TXT_NONE, D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W); + + /* + ....................... Draw the captions ....................... + */ + Fancy_Text_Print(caption, D_DIALOG_CX, D_DIALOG_Y + D_MARGIN, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print("Scenario", D_DIALOG_CX - 5, + D_SCEN_Y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + + /* + ........................ Redraw the buttons ........................ + */ + if (display >= REDRAW_BUTTONS) { + commands->Draw_All(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (BUTTON_VAR_A | KN_BUTTON): + (*varp) = SCEN_VAR_A; + varabtn.Turn_On(); + varbbtn.Turn_Off(); + varcbtn.Turn_Off(); + vardbtn.Turn_Off(); + varlbtn.Turn_Off(); + break; + + case (BUTTON_VAR_B | KN_BUTTON): + (*varp) = SCEN_VAR_B; + varabtn.Turn_Off(); + varbbtn.Turn_On(); + varcbtn.Turn_Off(); + vardbtn.Turn_Off(); + varlbtn.Turn_Off(); + break; + + case (BUTTON_VAR_C | KN_BUTTON): + (*varp) = SCEN_VAR_C; + varabtn.Turn_Off(); + varbbtn.Turn_Off(); + varcbtn.Turn_On(); + vardbtn.Turn_Off(); + varlbtn.Turn_Off(); + break; + + case (BUTTON_VAR_D | KN_BUTTON): + (*varp) = SCEN_VAR_D; + varabtn.Turn_Off(); + varbbtn.Turn_Off(); + varcbtn.Turn_Off(); + vardbtn.Turn_On(); + varlbtn.Turn_Off(); + break; + + case (BUTTON_VAR_L | KN_BUTTON): + (*varp) = SCEN_VAR_LOSE; + varabtn.Turn_Off(); + varbbtn.Turn_Off(); + varcbtn.Turn_Off(); + vardbtn.Turn_Off(); + varlbtn.Turn_On(); + break; + + case (BUTTON_EAST | KN_BUTTON): + (*dirp) = SCEN_DIR_EAST; + eastbtn.Turn_On(); + westbtn.Turn_Off(); + break; + + case (BUTTON_WEST | KN_BUTTON): + (*dirp) = SCEN_DIR_WEST; + eastbtn.Turn_Off(); + westbtn.Turn_On(); + break; + + case (BUTTON_GDI | KN_BUTTON): + (*playerp) = SCEN_PLAYER_GDI; + gdibtn.Turn_On(); + nodbtn.Turn_Off(); + playermbtn.Turn_Off(); + break; + + case (BUTTON_NOD | KN_BUTTON): + (*playerp) = SCEN_PLAYER_NOD; + gdibtn.Turn_Off(); + nodbtn.Turn_On(); + playermbtn.Turn_Off(); + break; + + case (BUTTON_MPLAYER | KN_BUTTON): + (*playerp) = SCEN_PLAYER_MPLAYER; + gdibtn.Turn_Off(); + nodbtn.Turn_Off(); + playermbtn.Turn_On(); + break; + + case (KN_RETURN): + case (BUTTON_OK | KN_BUTTON): + cancel = false; + process = false; + break; + + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + cancel = true; + process = false; + break; + + case (BUTTON_SCENARIO | KN_BUTTON): + break; + + default: + break; + } + } + + /* + --------------------------- Redraw the display --------------------------- + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + + /* + ------------------------- If cancel, just return ------------------------- + */ + if (cancel) + return(-1); + + /* + ------------------------ Save selections & return ------------------------ + */ + (*scen_nump) = atoi(scen_buf); + + return(0); +} + + +/*************************************************************************** + * MapEditClass::Size_Map -- lets user set size & location of map * + * * + * Lets the user select a side of the map and expand/shrink it to the * + * desired size, or move the whole map around the available map area. * + * * + * The entire available map area is displayed, but the map is limited such * + * that there's always one blank cell around the map; this lets objects * + * properly exit the screen, since they have a blank undisplayed cell to * + * exit onto. * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ Clear Terrain ³ * + * ³ ³ ³ Water ³ * + * ³ ³ ³ Tiberium ³ * + * ³ ³ ³ Rock/Wall/Road ³ * + * ³ ³ (Map Area) ³ GDI Unit ³ * + * ³ ³ ³ NOD Unit ³ * + * ³ ³ ³ Neutral Unit ³ * + * ³ ³ ³ Terrain Object ³ * + * ³ ³ ³ Starting Cell ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ ³ * + * ³ X Y Width Height ³ * + * ³ ## ## ## ## ³ * + * ³ ³ * + * ³ ³ * + * ³ [OK] [Cancel] ³ * + * ³ ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * x,y,w,h: initial size parameters (-1 = center the thing) * + * * + * OUTPUT: * + * 0 = OK, -1 = cancel * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 10/21/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::Size_Map(int x, int y, int w, int h) +{ + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + enum { + D_DIALOG_W = 480, // dialog width + D_DIALOG_H = 280, // dialog height + D_DIALOG_X = ((640 - D_DIALOG_W) / 2), // centered x-coord + D_DIALOG_Y = ((400 - D_DIALOG_H) / 2), // centered y-coord + D_DIALOG_CX = D_DIALOG_X + (D_DIALOG_W / 2), // coord of x-center + + D_TXT8_H = 22, // ht of 8-pt text + D_MARGIN = 14, // margin width/height + + D_BORD_X1 = D_DIALOG_X + (D_DIALOG_W / 2 - MAP_CELL_W) / 2, + D_BORD_Y1 = D_DIALOG_Y + 10, + D_BORD_X2 = D_BORD_X1 + MAP_CELL_W + 1, + D_BORD_Y2 = D_BORD_Y1 + MAP_CELL_H + 1, + + D_OK_W = 90, // OK width + D_OK_H = 18, // OK height + D_OK_X = D_DIALOG_CX - D_OK_W - 5, // OK x + D_OK_Y = D_DIALOG_Y + D_DIALOG_H - D_OK_H - D_MARGIN, // OK y + + D_CANCEL_W = 90, // Cancel width + D_CANCEL_H = 18, // Cancel height + D_CANCEL_X = D_DIALOG_CX + 5, // Cancel x + D_CANCEL_Y = D_DIALOG_Y + D_DIALOG_H - D_CANCEL_H - D_MARGIN, // Cancel y + + }; + /*........................................................................ + Button enumerations: + ........................................................................*/ + enum { + BUTTON_OK=100, + BUTTON_CANCEL, + }; + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_MAP, // includes map interior & coord values + REDRAW_BACKGROUND, // includes box, map bord, key, coord labels, btns + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + /*........................................................................ + Dialog variables: + ........................................................................*/ + bool process; // Loop while true + RedrawType display; // requested redraw level + bool cancel = false; // true = user cancels + KeyNumType input; // user input + int grabbed = 0; // 1=TLeft,2=TRight,3=BRight,4=BLeft + int map_x1; // map coords x1, pixel coords + int map_x2; // map coords x2, pixel coords + int map_y1; // map coords y1, pixel coords + int map_y2; // map coords y2, pixel coords + int delta1, delta2; // mouse-click proximity + int mx,my; // last-saved mouse coords + char txt[40]; + int txt_x,txt_y; // for displaying text + unsigned index; // for drawing map symbology + CELL cell; // for drawing map symbology + int color; // for drawing map symbology + ObjectClass * occupier; // cell's occupier + /*........................................................................ + Buttons + ........................................................................*/ + ControlClass *commands = NULL; + + TextButtonClass okbtn (BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_OK_X, D_OK_Y, D_OK_W, D_OK_H); + + TextButtonClass cancelbtn (BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_CANCEL_X, D_CANCEL_Y, D_CANCEL_W, D_CANCEL_H); + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + + /*........................................................................ + Set up the actual map area relative to the map's border coords + ........................................................................*/ + if (x==-1) { + map_x1 = D_BORD_X1 + (MAP_CELL_W - w) / 2 + 1; + } else { + map_x1 = D_BORD_X1 + x + 1; + } + + if (y==-1) { + map_y1 = D_BORD_Y1 + (MAP_CELL_H - h) / 2 + 1; + } else { + map_y1 = D_BORD_Y1 + y + 1; + } + + map_x2 = map_x1 + w - 1; + map_y2 = map_y1 + h - 1; + + /* + ------------------------- Build the button list -------------------------- + */ + commands = &okbtn; + cancelbtn.Add_Tail(*commands); + + /*------------------------------------------------------------------------ + Main processing loop + ------------------------------------------------------------------------*/ + display = REDRAW_ALL; + process = true; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ------------------------ Invoke game callback ------------------------- + */ + Call_Back(); + + /* + ---------------------- Refresh display if needed ---------------------- + */ + if (display) { + Hide_Mouse(); + /*------------------------------------------------------------------ + Redraw the background, map border, key, and coord labels + ------------------------------------------------------------------*/ + if (display >= REDRAW_BACKGROUND) { + /* + .......................... Background ........................... + */ + Dialog_Box(D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W, D_DIALOG_H); + Draw_Caption(TXT_NONE, D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W); + + /* + ..................... Draw the map border ....................... + */ + LogicPage->Lock(); + LogicPage->Draw_Rect(D_BORD_X1, D_BORD_Y1, D_BORD_X2, D_BORD_Y2, CC_GREEN_SHADOW); + for (index = D_BORD_X1; index < D_BORD_X2; + index += (320/ICON_PIXEL_W)) { + LogicPage->Put_Pixel(index, D_BORD_Y1-1, CC_GREEN_SHADOW); + LogicPage->Put_Pixel(index, D_BORD_Y2+1, CC_GREEN_SHADOW); + } + for (index = D_BORD_Y1; index < D_BORD_Y2-8; + index += (200/ICON_PIXEL_H)) { + LogicPage->Put_Pixel(D_BORD_X1-1, index, CC_GREEN_SHADOW); + LogicPage->Put_Pixel(D_BORD_X2+1, index, CC_GREEN_SHADOW); + } + + /*............................................................... + Draw the map "key" + ...............................................................*/ + txt_x = D_DIALOG_CX; + txt_y = D_DIALOG_Y + 8; + Fancy_Text_Print("Clear Terrain", txt_x, txt_y, LTGREY, TBLACK, TPF_DROPSHADOW | TPF_6POINT); + txt_y += 16; + Fancy_Text_Print("Water", txt_x, txt_y, BLUE, TBLACK, TPF_DROPSHADOW | TPF_6POINT); + txt_y += 16; + Fancy_Text_Print("Tiberium", txt_x, txt_y, GREY, TBLACK, TPF_DROPSHADOW | TPF_6POINT); + txt_y += 16; + Fancy_Text_Print("Rock/Wall/Road", txt_x, txt_y, BROWN, TBLACK, TPF_DROPSHADOW | TPF_6POINT); + txt_y += 16; + Fancy_Text_Print("GDI Unit", txt_x, txt_y, YELLOW, TBLACK, TPF_DROPSHADOW | TPF_6POINT); + txt_y += 16; + Fancy_Text_Print("Nod Unit", txt_x, txt_y, RED, TBLACK, TPF_DROPSHADOW | TPF_6POINT); + txt_y += 16; + Fancy_Text_Print("Neutral Unit", txt_x, txt_y, PURPLE, TBLACK, TPF_DROPSHADOW | TPF_6POINT); + txt_y += 16; + Fancy_Text_Print("Terrain Object", txt_x, txt_y, DKGREEN, TBLACK, TPF_DROPSHADOW | TPF_6POINT); + txt_y += 16; + Fancy_Text_Print("Starting Cell", txt_x, txt_y, WHITE, TBLACK, TPF_DROPSHADOW | TPF_6POINT); + /* + .................. Draw the coordinate labels ................... + */ + txt_x = D_DIALOG_X + D_DIALOG_W / 8; + txt_y = D_DIALOG_Y + D_DIALOG_H - D_OK_H - 10 - 33; + Fancy_Text_Print("X", txt_x, txt_y, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + txt_x += (D_DIALOG_W - 20) / 4; + Fancy_Text_Print("Y", txt_x, txt_y, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + txt_x += (D_DIALOG_W - 20) / 4; + Fancy_Text_Print("Width", txt_x, txt_y, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + txt_x += (D_DIALOG_W - 20) / 4; + Fancy_Text_Print("Height", txt_x, txt_y, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + LogicPage->Unlock(); + + /* + ...................... Redraw the buttons ....................... + */ + commands->Flag_List_To_Redraw(); + + } + + /*------------------------------------------------------------------ + Redraw the map symbology & location + ------------------------------------------------------------------*/ + if (display >= REDRAW_MAP) { + + LogicPage->Lock(); + + /* + .................... Erase the map interior ..................... + */ + LogicPage->Fill_Rect(D_BORD_X1 + 1, D_BORD_Y1 + 1, D_BORD_X2 - 1, D_BORD_Y2 - 1, BLACK); + + /*............................................................... + Draw Land map symbols (use color according to Ground[] array). + ...............................................................*/ + for (cell=0; cell < MAP_CELL_TOTAL; cell++) { + occupier = (*this)[cell].Cell_Occupier(); + if (occupier == NULL) { + color = Ground[(*this)[cell].Land_Type()].Color; + LogicPage->Put_Pixel(D_BORD_X1 + Cell_X(cell) + 1, D_BORD_Y1 + Cell_Y(cell) + 1, color); + } + } + + LogicPage->Unlock(); + + /* + ................. Draw the actual map location .................. + */ + LogicPage->Draw_Rect(map_x1, map_y1, map_x2, map_y2, WHITE); + switch (grabbed) { + case 1: + LogicPage->Draw_Line(map_x1, map_y1, map_x1 + 5, map_y1, BLUE); + LogicPage->Draw_Line(map_x1, map_y1, map_x1, map_y1 + 5, BLUE); + break; + + case 2: + LogicPage->Draw_Line(map_x2, map_y1, map_x2 - 5, map_y1, BLUE); + LogicPage->Draw_Line(map_x2, map_y1, map_x2, map_y1 + 5, BLUE); + break; + + case 3: + LogicPage->Draw_Line(map_x2, map_y2, map_x2 - 5, map_y2, BLUE); + LogicPage->Draw_Line(map_x2, map_y2, map_x2, map_y2 - 5, BLUE); + break; + + case 4: + LogicPage->Draw_Line(map_x1, map_y2, map_x1 + 5, map_y2, BLUE); + LogicPage->Draw_Line(map_x1, map_y2, map_x1, map_y2 - 5, BLUE); + break; + + case 5: + LogicPage->Draw_Rect(map_x1, map_y1, map_x2, map_y2, BLUE); + break; + + default: + break; + } + + /*............................................................... + Draw Unit map symbols (Use the radar map color according to + that specified in the house type class object. + DKGREEN = terrain object + ...............................................................*/ + for (cell=0; cell < MAP_CELL_TOTAL; cell++) { + occupier = (*this)[cell].Cell_Occupier(); + if (occupier) { + color = DKGREEN; + if (occupier && occupier->Owner() != HOUSE_NONE) { + color = HouseClass::As_Pointer(occupier->Owner())->Class->Color; + } + LogicPage->Put_Pixel(D_BORD_X1 + Cell_X(cell) + 1, D_BORD_Y1 + Cell_Y(cell) + 1, color); + } + } + + /* + ...................... Draw Home location ....................... + */ + LogicPage->Put_Pixel(D_BORD_X1 + Cell_X(Waypoint[WAYPT_HOME]) + 1, D_BORD_Y1 + Cell_Y(Waypoint[WAYPT_HOME]) + 1, WHITE); + + /* + ..................... Erase old coordinates ..................... + */ + LogicPage->Fill_Rect( D_DIALOG_X + 7, + D_DIALOG_Y + D_DIALOG_H - D_OK_H - 10 - 22, + D_DIALOG_X + D_DIALOG_W - 7, + D_DIALOG_Y + D_DIALOG_H - D_OK_H - 10 - 22 + 10, BLACK); + + /* + ..................... Draw the coordinates ...................... + */ + txt_x = D_DIALOG_X + D_DIALOG_W / 8; + txt_y = D_DIALOG_Y + D_DIALOG_H - D_OK_H - 10 - 22; + sprintf(txt,"%d",map_x1 - D_BORD_X1 - 1); + Fancy_Text_Print(txt, txt_x, txt_y, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + txt_x += (D_DIALOG_W - 20) / 4; + sprintf(txt,"%d",map_y1 - D_BORD_Y1 - 1); + Fancy_Text_Print(txt, txt_x, txt_y, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + txt_x += (D_DIALOG_W - 20) / 4; + sprintf(txt,"%d",map_x2 - map_x1 + 1); + Fancy_Text_Print(txt, txt_x, txt_y, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + txt_x += (D_DIALOG_W - 20) / 4; + sprintf(txt,"%d",map_y2 - map_y1 + 1); + Fancy_Text_Print(txt, txt_x, txt_y, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ------------------------- Process user input -------------------------- + */ + input = commands->Input(); + /*..................................................................... + Normal button processing: This is done when the mouse button is NOT + being held down ('grabbed' is 0). + .....................................................................*/ + if (grabbed == 0) { + switch (input) { + case (KN_RETURN): + case (BUTTON_OK | KN_BUTTON): + cancel = false; + process = false; + break; + + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + cancel = true; + process = false; + break; + + case KN_LMOUSE: + /* + ....................... Grab top left ........................ + */ + delta1 = abs(_Kbd->MouseQX - map_x1); + delta2 = abs(_Kbd->MouseQY - map_y1); + if (delta1 < 3 && delta2 < 3) { + grabbed = 1; + mx = _Kbd->MouseQX; + my = _Kbd->MouseQY; + display = REDRAW_MAP; + break; + } + /* + ...................... Grab top right ........................ + */ + delta1 = abs(_Kbd->MouseQX - map_x2); + delta2 = abs(_Kbd->MouseQY - map_y1); + if (delta1 < 3 && delta2 < 3) { + grabbed = 2; + mx = _Kbd->MouseQX; + my = _Kbd->MouseQY; + display = REDRAW_MAP; + break; + } + /* + ..................... Grab bottom right ...................... + */ + delta1 = abs(_Kbd->MouseQX - map_x2); + delta2 = abs(_Kbd->MouseQY - map_y2); + if (delta1 < 3 && delta2 < 3) { + grabbed = 3; + mx = _Kbd->MouseQX; + my = _Kbd->MouseQY; + display = REDRAW_MAP; + break; + } + /* + ..................... Grab bottom left ....................... + */ + delta1 = abs(_Kbd->MouseQX - map_x1); + delta2 = abs(_Kbd->MouseQY - map_y2); + if (delta1 < 3 && delta2 < 3) { + grabbed = 4; + mx = _Kbd->MouseQX; + my = _Kbd->MouseQY; + display = REDRAW_MAP; + break; + } + /* + ..................... Grab the whole map ..................... + */ + delta1 = abs(_Kbd->MouseQX - ((map_x1 + map_x2) / 2)); + delta2 = abs(_Kbd->MouseQY - ((map_y1 + map_y2) / 2)); + if (delta1 < (map_x2 - map_x1) / 4 && + delta2 < (map_y2 - map_y1) / 4) { + grabbed = 5; + mx = _Kbd->MouseQX; + my = _Kbd->MouseQY; + display = REDRAW_MAP; + } + break; + + default: + break; + } + } else { + /*..................................................................... + Mouse motion processing: This is done while the left mouse button IS + being held down. + - First, check for the button release; if detected, un-grab + - Then, handle mouse motion. WWLIB doesn't pass through a KN_MOUSE_MOVE + value while the button is being held down, so this case must be + trapped as a default. + .....................................................................*/ + switch (input) { + case ((int)KN_LMOUSE | (int)KN_RLSE_BIT): + grabbed = 0; + display = REDRAW_MAP; + break; + + default: + delta1 = Get_Mouse_X() - mx; + delta2 = Get_Mouse_Y() - my; + if (delta1==0 && delta2==0) { + break; + } + + /* + ....................... Move top left ........................ + */ + if (grabbed==1) { + map_x1 += delta1; + if (map_x1 > map_x2 - 2) { + map_x1 = map_x2 - 2; + } else { + if (map_x1 < D_BORD_X1 + 2) { + map_x1 = D_BORD_X1 + 2; + } + } + + map_y1 += delta2; + if (map_y1 > map_y2 - 2) { + map_y1 = map_y2 - 2; + } else { + if (map_y1 < D_BORD_Y1 + 2) { + map_y1 = D_BORD_Y1 + 2; + } + } + display = REDRAW_MAP; + mx = Get_Mouse_X(); + my = Get_Mouse_Y(); + } + + /* + ....................... Move top right ....................... + */ + if (grabbed==2) { + map_x2 += delta1; + if (map_x2 < map_x1 + 2) { + map_x2 = map_x1 + 2; + } else { + if (map_x2 > D_BORD_X2 - 2) { + map_x2 = D_BORD_X2 - 2; + } + } + + map_y1 += delta2; + if (map_y1 > map_y2 - 2) { + map_y1 = map_y2 - 2; + } else { + if (map_y1 < D_BORD_Y1 + 2) { + map_y1 = D_BORD_Y1 + 2; + } + } + display = REDRAW_MAP; + mx = Get_Mouse_X(); + my = Get_Mouse_Y(); + } + + /* + ..................... Move bottom right ...................... + */ + if (grabbed==3) { + map_x2 += delta1; + if (map_x2 < map_x1 + 2) { + map_x2 = map_x1 + 2; + } else { + if (map_x2 > D_BORD_X2 - 2) { + map_x2 = D_BORD_X2 - 2; + } + } + + map_y2 += delta2; + if (map_y2 < map_y1 + 2) { + map_y2 = map_y1 + 2; + } else { + if (map_y2 > D_BORD_Y2 - 2) { + map_y2 = D_BORD_Y2 - 2; + } + } + display = REDRAW_MAP; + mx = Get_Mouse_X(); + my = Get_Mouse_Y(); + } + + /* + ...................... Move bottom left ...................... + */ + if (grabbed==4) { + map_x1 += delta1; + if (map_x1 > map_x2 - 2) { + map_x1 = map_x2 - 2; + } else { + if (map_x1 < D_BORD_X1 + 2) { + map_x1 = D_BORD_X1 + 2; + } + } + + map_y2 += delta2; + if (map_y2 < map_y1 + 2) { + map_y2 = map_y1 + 2; + } else { + if (map_y2 > D_BORD_Y2 - 2) { + map_y2 = D_BORD_Y2 - 2; + } + } + display = REDRAW_MAP; + mx = Get_Mouse_X(); + my = Get_Mouse_Y(); + } + + /* + ....................... Move whole map ....................... + */ + if (grabbed==5) { + if (map_x1 + delta1 > D_BORD_X1 + 1 && map_x2 + delta1 < D_BORD_X2 - 1) { + map_x1 += delta1; + map_x2 += delta1; + } + + if (map_y1 + delta2 > D_BORD_Y1 + 1 && map_y2 + delta2 < D_BORD_Y2 - 1) { + map_y1 += delta2; + map_y2 += delta2; + } + display = REDRAW_MAP; + mx = Get_Mouse_X(); + my = Get_Mouse_Y(); + } + break; + } + } + } + + /* + --------------------------- Redraw the display --------------------------- + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + + /* + ------------------------- If cancel, just return ------------------------- + */ + if (cancel) { + return(-1); + } + + /* + ---------------------------- Save selections ----------------------------- + */ + MapCellX = map_x1 - D_BORD_X1 - 1; + MapCellY = map_y1 - D_BORD_Y1 - 1; + MapCellWidth = map_x2 - map_x1 + 1; + MapCellHeight = map_y2 - map_y1 + 1; + + /* + --------------------- Clip Home Cell to new map size --------------------- + */ + if (Cell_X(Waypoint[WAYPT_HOME]) < MapCellX) { + Waypoint[WAYPT_HOME] = XY_Cell(MapCellX, Cell_Y(Waypoint[WAYPT_HOME])); + } + + if (Cell_X(Waypoint[WAYPT_HOME]) > MapCellX + MapCellWidth - 1) { + Waypoint[WAYPT_HOME] = XY_Cell(MapCellX + MapCellWidth - 1, Cell_Y(Waypoint[WAYPT_HOME])); + } + + if (Cell_Y(Waypoint[WAYPT_HOME]) < MapCellY) { + Waypoint[WAYPT_HOME] = XY_Cell(Cell_X(Waypoint[WAYPT_HOME]), MapCellY); + } + + if (Cell_Y(Waypoint[WAYPT_HOME]) > MapCellY + MapCellHeight - 1) { + Waypoint[WAYPT_HOME] = XY_Cell(Cell_X(Waypoint[WAYPT_HOME]), MapCellY + MapCellHeight - 1); + } + + return(0); +} + + +/*************************************************************************** + * MapEditClass::Scenario_Dialog -- scenario global parameters dialog * + * * + * Lets the user edit the Theater, starting credits for houses, and the * + * Edge for HOUSE_GOOD & HOUSE_BAD. * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Theater Credits / 1000 ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ * + * ³ ³ Temperate ³ GDI: _____ ³ * + * ³ ³ Desert ³ NOD: _____ ³ * + * ³ ³ Jungle ³ Neutral: _____ ³ * + * ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ Build Level:___ ³ * + * ³ ³ * + * ³ Reinforcements ³ * + * ³ ³ * + * ³ GDI NOD ³ * + * ³ ³ * + * ³   ³ * + * ³ <- -> <- -> ³ * + * ³   ³ * + * ³ ³ * + * ³ ³ * + * ³ [OK] [Cancel] ³ * + * ³ ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = OK, -1 = cancel * + * * + * WARNINGS: * + * Uses HIDBUFF. * + * * + * HISTORY: * + * 11/14/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::Scenario_Dialog(void) +{ + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + enum { + D_DIALOG_W = 544, + D_DIALOG_H = 320, + D_DIALOG_X = ((640 - D_DIALOG_W) / 2), + D_DIALOG_Y = ((400 - D_DIALOG_H) / 2), + D_DIALOG_CX = D_DIALOG_X + (D_DIALOG_W / 2), + + D_TXT8_H = 22, + D_MARGIN = 14, + + D_THEATER_W = 200, + D_THEATER_H = 68, + D_THEATER_X = D_DIALOG_X + D_MARGIN, + D_THEATER_Y = D_DIALOG_Y + D_MARGIN + D_TXT8_H, + + D_LEVEL_W = 80, + D_LEVEL_H = 18, + D_LEVEL_X = D_THEATER_X + D_THEATER_W - D_LEVEL_W, + D_LEVEL_Y = D_THEATER_Y + D_THEATER_H + D_MARGIN, + + D_GDICRED_W = 120, + D_GDICRED_H = 18, + D_GDICRED_X = D_DIALOG_X + D_DIALOG_W - D_MARGIN - D_GDICRED_W, + D_GDICRED_Y = D_DIALOG_Y + D_MARGIN + D_TXT8_H, + + D_NODCRED_W = 120, + D_NODCRED_H = 18, + D_NODCRED_X = D_GDICRED_X, + D_NODCRED_Y = D_GDICRED_Y + D_GDICRED_H, + + D_NEUTCRED_W = 120, + D_NEUTCRED_H = 18, + D_NEUTCRED_X = D_GDICRED_X, + D_NEUTCRED_Y = D_NODCRED_Y + D_NODCRED_H, + + D_GDIN_W = 26, + D_GDIN_H = 18, + D_GDIN_X = D_DIALOG_CX - 5 - D_GDIN_W * 2, + D_GDIN_Y = D_LEVEL_Y + D_LEVEL_H + D_MARGIN + D_TXT8_H + D_MARGIN + D_TXT8_H, + + D_GDIS_W = 26, + D_GDIS_H = 18, + D_GDIS_X = D_GDIN_X, + D_GDIS_Y = D_GDIN_Y + D_GDIN_H * 2, + + D_GDIW_W = 26, + D_GDIW_H = 18, + D_GDIW_X = D_DIALOG_CX - 5 - D_GDIN_W * 3, + D_GDIW_Y = D_GDIN_Y + D_GDIN_H, + + D_GDIE_W = 26, + D_GDIE_H = 18, + D_GDIE_X = D_DIALOG_CX - 5 - D_GDIN_W, + D_GDIE_Y = D_GDIN_Y + D_GDIN_H, + + D_NODN_W = 26, + D_NODN_H = 18, + D_NODN_X = D_DIALOG_CX + 5 + D_NODN_W, + D_NODN_Y = D_LEVEL_Y + D_LEVEL_H + D_MARGIN + D_TXT8_H + D_MARGIN + D_TXT8_H, + + D_NODS_W = 26, + D_NODS_H = 18, + D_NODS_X = D_NODN_X, + D_NODS_Y = D_NODN_Y + D_NODN_H * 2, + + D_NODW_W = 26, + D_NODW_H = 18, + D_NODW_X = D_DIALOG_CX + 5, + D_NODW_Y = D_NODN_Y + D_NODN_H, + + D_NODE_W = 26, + D_NODE_H = 18, + D_NODE_X = D_DIALOG_CX + 5 + D_NODN_W * 2, + D_NODE_Y = D_NODN_Y + D_NODN_H, + + D_OK_W = 90, + D_OK_H = 18, + D_OK_X = D_DIALOG_CX - D_OK_W - 5, + D_OK_Y = D_DIALOG_Y + D_DIALOG_H - D_OK_H - D_MARGIN, + + D_CANCEL_W = 90, + D_CANCEL_H = 18, + D_CANCEL_X = D_DIALOG_CX + 5, + D_CANCEL_Y = D_DIALOG_Y + D_DIALOG_H - D_CANCEL_H - D_MARGIN, + }; + /*........................................................................ + Button enumerations: + ........................................................................*/ + enum { + LIST_THEATER=100, + TEDIT_LEVEL, + TEDIT_GDICRED, + TEDIT_NODCRED, + TEDIT_NEUTCRED, + BUTTON_GDI_N, + BUTTON_GDI_E, + BUTTON_GDI_S, + BUTTON_GDI_W, + BUTTON_NOD_N, + BUTTON_NOD_E, + BUTTON_NOD_S, + BUTTON_NOD_W, + BUTTON_OK, + BUTTON_CANCEL, + }; + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, // includes map interior & coord values + REDRAW_BACKGROUND, // includes box, map bord, key, coord labels, btns + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + /*........................................................................ + Dialog variables: + ........................................................................*/ + KeyNumType input; // input from user + bool process; // loop while true + RedrawType display; // true = re-draw everything + bool cancel = false; // true = user cancels + /* + .......................... Scenario parameters ........................... + */ + TheaterType theater; // DisplayClass::Theater + TheaterType orig_theater; // original theater + long gdi_credits; // HouseClass::As_Pointer(HouseType)->Credits + long nod_credits; // HouseClass::As_Pointer(HouseType)->Credits + long neut_credits; // HouseClass::As_Pointer(HouseType)->Credits + SourceType gdi_edge; // HouseClass::As_Pointer(HouseType)->Edge + SourceType nod_edge; // HouseClass::As_Pointer(HouseType)->Edge + char level_buf[10] = {0}; + char gdicred_buf[10] = {0}; + char nodcred_buf[10] = {0}; + char neutcred_buf[10] = {0}; + /* + ....................... Theater-changing variables ....................... + */ + unsigned char theater_mask; // template/terrain mask + TerrainClass * terrain; // cell's terrain pointer + CELL i; // loop counter + /*........................................................................ + Buttons + ........................................................................*/ + ControlClass *commands = NULL; // the button list + ListClass theaterbtn (LIST_THEATER, + D_THEATER_X, D_THEATER_Y, D_THEATER_W, D_THEATER_H, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + Hires_Retrieve("BTN-UP.SHP"), + Hires_Retrieve("BTN-DN.SHP")); + + EditClass leveledt (TEDIT_GDICRED, level_buf, 4, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_LEVEL_X, D_LEVEL_Y, D_LEVEL_W, D_LEVEL_H, EditClass::NUMERIC); + + EditClass gdicred (TEDIT_GDICRED, gdicred_buf, 8, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_GDICRED_X, D_GDICRED_Y, D_GDICRED_W, D_GDICRED_H, EditClass::NUMERIC); + + EditClass nodcred (TEDIT_NODCRED, nodcred_buf, 8, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NODCRED_X, D_NODCRED_Y, D_NODCRED_W, D_NODCRED_H, EditClass::NUMERIC); + + EditClass neutcred (TEDIT_NEUTCRED, neutcred_buf, 8, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NEUTCRED_X, D_NEUTCRED_Y, D_NEUTCRED_W, D_NEUTCRED_H, EditClass::NUMERIC); + + TextButtonClass gdinbtn (BUTTON_GDI_N, TXT_UP, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_GDIN_X, D_GDIN_Y, D_GDIN_W, D_GDIN_H); + + TextButtonClass gdiebtn (BUTTON_GDI_E, TXT_RIGHT, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_GDIE_X, D_GDIE_Y, D_GDIE_W, D_GDIE_H); + + TextButtonClass gdisbtn (BUTTON_GDI_S, TXT_DOWN, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_GDIS_X, D_GDIS_Y, D_GDIS_W, D_GDIS_H); + + TextButtonClass gdiwbtn (BUTTON_GDI_W, TXT_LEFT, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_GDIW_X, D_GDIW_Y, D_GDIW_W, D_GDIW_H); + + TextButtonClass nodnbtn (BUTTON_NOD_N, TXT_UP, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NODN_X, D_NODN_Y, D_NODN_W, D_NODN_H); + + TextButtonClass nodebtn (BUTTON_NOD_E, TXT_RIGHT, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NODE_X, D_NODE_Y, D_NODE_W, D_NODE_H); + + TextButtonClass nodsbtn (BUTTON_NOD_S, TXT_DOWN, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NODS_X, D_NODS_Y, D_NODS_W, D_NODS_H); + + TextButtonClass nodwbtn (BUTTON_NOD_W, TXT_LEFT, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NODW_X, D_NODW_Y, D_NODW_W, D_NODW_H); + + TextButtonClass okbtn (BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_OK_X, D_OK_Y, D_OK_W, D_OK_H); + + TextButtonClass cancelbtn (BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_CANCEL_X, D_CANCEL_Y, D_CANCEL_W, D_CANCEL_H); + + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + + /* + .......................... Fill in theater items ......................... + */ + theaterbtn.Add_Item("Desert"); + theaterbtn.Add_Item("Jungle"); + theaterbtn.Add_Item("Temperate"); + theaterbtn.Add_Item("Winter"); + + /* + ............................ Init parameters ............................. + */ + orig_theater = theater = Theater; + if (ScenPlayer != SCEN_PLAYER_MPLAYER) { + gdi_credits = HouseClass::As_Pointer(HOUSE_GOOD)->Credits / 1000L; + nod_credits = HouseClass::As_Pointer(HOUSE_BAD)->Credits / 1000L; + neut_credits = HouseClass::As_Pointer(HOUSE_NEUTRAL)->Credits / 1000L; + gdi_edge = HouseClass::As_Pointer(HOUSE_GOOD)->Edge; + nod_edge = HouseClass::As_Pointer(HOUSE_BAD)->Edge; + } else { + gdi_credits = 0; + nod_credits = 0; + neut_credits = 0; + gdi_edge = SOURCE_NONE; + nod_edge = SOURCE_NONE; + } + + /* + ............................ Create the list ............................. + */ + commands = &theaterbtn; + leveledt.Add_Tail(*commands); + gdicred.Add_Tail(*commands); + nodcred.Add_Tail(*commands); + neutcred.Add_Tail(*commands); + gdinbtn.Add_Tail(*commands); + gdiebtn.Add_Tail(*commands); + gdisbtn.Add_Tail(*commands); + gdiwbtn.Add_Tail(*commands); + nodnbtn.Add_Tail(*commands); + nodebtn.Add_Tail(*commands); + nodsbtn.Add_Tail(*commands); + nodwbtn.Add_Tail(*commands); + okbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + + /* + ...................... Init GDI Edge button states ....................... + */ + if (gdi_edge==SOURCE_NORTH) gdinbtn.Turn_On(); + if (gdi_edge==SOURCE_EAST) gdiebtn.Turn_On(); + if (gdi_edge==SOURCE_SOUTH) gdisbtn.Turn_On(); + if (gdi_edge==SOURCE_WEST) gdiwbtn.Turn_On(); + + /* + ...................... Init NOD Edge button states ....................... + */ + if (nod_edge==SOURCE_NORTH) nodnbtn.Turn_On(); + if (nod_edge==SOURCE_EAST) nodebtn.Turn_On(); + if (nod_edge==SOURCE_SOUTH) nodsbtn.Turn_On(); + if (nod_edge==SOURCE_WEST) nodwbtn.Turn_On(); + + /* + .......................... Init credits buffers .......................... + */ + sprintf(level_buf,"%ld",BuildLevel); + leveledt.Set_Text(level_buf,4); + + sprintf(gdicred_buf,"%ld",gdi_credits); + gdicred.Set_Text(gdicred_buf,8); + + sprintf(nodcred_buf,"%ld",nod_credits); + nodcred.Set_Text(nodcred_buf,8); + + sprintf(neutcred_buf,"%ld",neut_credits); + neutcred.Set_Text(neutcred_buf,8); + + theaterbtn.Set_Selected_Index(orig_theater - THEATER_NONE - 1); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + display = REDRAW_ALL; + process = true; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + + /* + ..................... Draw the background ....................... + */ + Dialog_Box(D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W, D_DIALOG_H); + Draw_Caption(TXT_NONE, D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W); + + /* + ....................... Draw the labels ......................... + */ + Fancy_Text_Print("Theater", D_THEATER_X + D_THEATER_W / 2, + D_THEATER_Y - D_TXT8_H, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print("Build Level", D_LEVEL_X, D_LEVEL_Y, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print("Credits/1000", D_GDICRED_X + D_GDICRED_W / 2, + D_GDICRED_Y - D_TXT8_H, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print ("GDI", D_GDICRED_X - 5, D_GDICRED_Y, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print ("NOD", D_NODCRED_X - 5, D_NODCRED_Y, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print ("Neutral", D_NEUTCRED_X - 5, D_NEUTCRED_Y, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print ("Reinforcements", D_DIALOG_CX, + D_LEVEL_Y + D_LEVEL_H + D_MARGIN, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print ("GDI", D_GDIN_X + D_GDIN_W / 2, + D_GDIN_Y - D_TXT8_H, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print ("NOD", D_NODN_X + D_NODN_W / 2, + D_NODN_Y - D_TXT8_H, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + + /*.................................................................. + Credit edit boxes: no need for any action + ..................................................................*/ + case (TEDIT_GDICRED | KN_BUTTON): + break; + + case (TEDIT_NODCRED | KN_BUTTON): + break; + + case (TEDIT_NEUTCRED | KN_BUTTON): + break; + + /*.................................................................. + GDI Edge buttons: turn this one on, others off, save the edge value + ..................................................................*/ + case (BUTTON_GDI_N | KN_BUTTON): + gdi_edge = SOURCE_NORTH; + gdinbtn.Turn_On(); + gdiebtn.Turn_Off(); + gdisbtn.Turn_Off(); + gdiwbtn.Turn_Off(); + break; + + case (BUTTON_GDI_E | KN_BUTTON): + gdi_edge = SOURCE_EAST; + gdinbtn.Turn_Off(); + gdiebtn.Turn_On(); + gdisbtn.Turn_Off(); + gdiwbtn.Turn_Off(); + break; + + case (BUTTON_GDI_S | KN_BUTTON): + gdi_edge = SOURCE_SOUTH; + gdinbtn.Turn_Off(); + gdiebtn.Turn_Off(); + gdisbtn.Turn_On(); + gdiwbtn.Turn_Off(); + break; + + case (BUTTON_GDI_W | KN_BUTTON): + gdi_edge = SOURCE_WEST; + gdinbtn.Turn_Off(); + gdiebtn.Turn_Off(); + gdisbtn.Turn_Off(); + gdiwbtn.Turn_On(); + break; + + /*.................................................................. + NOD Edge buttons: turn this one on, others off, save the edge value + ..................................................................*/ + case (BUTTON_NOD_N | KN_BUTTON): + nod_edge = SOURCE_NORTH; + nodnbtn.Turn_On(); + nodebtn.Turn_Off(); + nodsbtn.Turn_Off(); + nodwbtn.Turn_Off(); + break; + + case (BUTTON_NOD_E | KN_BUTTON): + nod_edge = SOURCE_EAST; + nodnbtn.Turn_Off(); + nodebtn.Turn_On(); + nodsbtn.Turn_Off(); + nodwbtn.Turn_Off(); + break; + + case (BUTTON_NOD_S | KN_BUTTON): + nod_edge = SOURCE_SOUTH; + nodnbtn.Turn_Off(); + nodebtn.Turn_Off(); + nodsbtn.Turn_On(); + nodwbtn.Turn_Off(); + break; + + case (BUTTON_NOD_W | KN_BUTTON): + nod_edge = SOURCE_WEST; + nodnbtn.Turn_Off(); + nodebtn.Turn_Off(); + nodsbtn.Turn_Off(); + nodwbtn.Turn_On(); + break; + + case (KN_RETURN): + case (BUTTON_OK | KN_BUTTON): + cancel = false; + process = false; + break; + + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + cancel = true; + process = false; + break; + + default: + break; + } + } + + /* + ----------------------------- Redraw the map ----------------------------- + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + + /* + ------------------------- If cancel, just return ------------------------- + */ + if (cancel) { + return(-1); + } + + /* + ------------------------ Save selections & return ------------------------ + */ + if (ScenPlayer != SCEN_PLAYER_MPLAYER) { + /* + .............................. Credits ................................ + */ + gdi_credits = atol(gdicred_buf); + nod_credits = atol(nodcred_buf); + neut_credits = atol(neutcred_buf); + HouseClass::As_Pointer(HOUSE_GOOD)->Credits = gdi_credits * 1000L; + HouseClass::As_Pointer(HOUSE_BAD)->Credits = nod_credits * 1000L; + HouseClass::As_Pointer(HOUSE_NEUTRAL)->Credits = neut_credits * 1000L; + /* + ............................... Edges ................................. + */ + HouseClass::As_Pointer(HOUSE_GOOD)->Edge = gdi_edge; + HouseClass::As_Pointer(HOUSE_BAD)->Edge = nod_edge; + } + + /* + ........................... Sidebar build level .......................... + */ + BuildLevel = atoi(level_buf); + + /*........................................................................ + Change the theater: + - 1st set the Theater global + - scan all cells to check their TType for compatibility with the new + theater; if not compatible, set TType to TEMPLATE_NONE & TIcon to 0 + - Then, re-initialize the TypeClasses for the new Theater + ........................................................................*/ + theater = (TheaterType)(THEATER_NONE + 1 + theaterbtn.Current_Index()); + if (theater != orig_theater) { + /* + ....................... Loop through all cells ........................ + */ + for (i =0;iClass->Theater; + if ( (theater_mask & (1<Remove(); + CurTrigger = NULL; + Changed = 1; + } + } + } + + /*------------------------------------------------------------------------ + Don't allow trigger placement if the trigger is house-specific; such + triggers cannot be "placed". + ------------------------------------------------------------------------*/ + if (CurTrigger) { + if (!TriggerClass::Event_Need_Object(CurTrigger->Event)) { + CurTrigger = NULL; + } + } +} + + +/*************************************************************************** + * MapEditClass::Select_Trigger -- lets user select a trigger * + * * + * CurTrigger can be NULL when this function is called. * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Triggers ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ³ * + * ³ ³ Name Event Action House Team ³³ ³ * + * ³ ³ Name Event Action House Team ÃÄ´ ³ * + * ³ ³ Name Event Action House Team ³ ³ ³ * + * ³ ³ Name Event Action House Team ³ ³ ³ * + * ³ ³ ³ ³ ³ * + * ³ ³ ³ ³ ³ * + * ³ ³ ÃÄ´ ³ * + * ³ ³ ³³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ³ * + * ³ ³ * + * ³ [Edit] [New] [Delete] [OK] ³ * + * ³ ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = OK, 1 = Edit, 2 = New, 3 = Delete * + * * + * WARNINGS: * + * Uses HIDBUFF. * + * * + * HISTORY: * + * 11/29/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::Select_Trigger(void) +{ + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + enum { + D_DIALOG_W = 640, + D_DIALOG_H = 290, + D_DIALOG_X = ((640 - D_DIALOG_W) / 2), + D_DIALOG_Y = ((400 - D_DIALOG_H) / 2), + D_DIALOG_CX = D_DIALOG_X + (D_DIALOG_W / 2), + + D_TXT8_H = 22, + D_MARGIN = 14, + + D_LIST_W = 612, + D_LIST_H = 208, + D_LIST_X = D_DIALOG_X + D_MARGIN, + D_LIST_Y = D_DIALOG_Y + D_MARGIN + D_TXT8_H, + + D_EDIT_W = 90, + D_EDIT_H = 18, + D_EDIT_X = D_DIALOG_X + (D_DIALOG_W / 8) - (D_EDIT_W / 2), + D_EDIT_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_EDIT_H, + + D_NEW_W = 90, + D_NEW_H = 18, + D_NEW_X = D_DIALOG_X + (D_DIALOG_W / 8) * 3 - (D_NEW_W / 2), + D_NEW_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_NEW_H, + + D_DELETE_W = 90, + D_DELETE_H = 18, + D_DELETE_X = D_DIALOG_X + (D_DIALOG_W / 8) * 5 - (D_DELETE_W / 2), + D_DELETE_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_DELETE_H, + + D_OK_W = 90, + D_OK_H = 18, + D_OK_X = D_DIALOG_X + (D_DIALOG_W / 8) * 7 - (D_OK_W / 2), + D_OK_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_OK_H, + + }; + + /*........................................................................ + Button enumerations: + ........................................................................*/ + enum { + TRIGGER_LIST=100, + BUTTON_EDIT, + BUTTON_NEW, + BUTTON_DELETE, + BUTTON_OK, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables: + ........................................................................*/ + RedrawType display; // requested redraw level + bool process; // loop while true + char *trigtext[TRIGGER_MAX + 1]; // text for defined triggers + KeyNumType input; // user input + bool edit_trig = false; // true = user wants to edit + bool new_trig = false; // true = user wants to new + bool del_trig = false; // true = user wants to new + int i; // loop counter + int def_idx; // default list index + static int tabs[] = {70, 240, 390, 440}; // list box tab stops + + /*........................................................................ + Buttons + ........................................................................*/ + ControlClass *commands = NULL; // the button list + + ListClass triggerlist (TRIGGER_LIST, D_LIST_X, D_LIST_Y, D_LIST_W, D_LIST_H, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + Hires_Retrieve("BTN-UP.SHP"), + Hires_Retrieve("BTN-DN.SHP")); + + TextButtonClass editbtn (BUTTON_EDIT, "Edit", TPF_CENTER|TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW, + D_EDIT_X, D_EDIT_Y, D_EDIT_W, D_EDIT_H); + + TextButtonClass newbtn (BUTTON_NEW, "New", TPF_CENTER|TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW, + D_NEW_X, D_NEW_Y, D_NEW_W, D_NEW_H); + + TextButtonClass deletebtn (BUTTON_DELETE, "Delete", TPF_CENTER|TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW, + D_DELETE_X, D_DELETE_Y, D_DELETE_W, D_DELETE_H); + + TextButtonClass okbtn (BUTTON_OK, TXT_OK, TPF_CENTER|TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW, + D_OK_X, D_OK_Y, D_OK_W, D_OK_H); + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + + /* + ......................... Fill in trigger names .......................... + */ + def_idx = 0; + for (i = 0; i < Triggers.Count(); i++) { + /*..................................................................... + Generate string for this trigger + - Name can be up to 4 characters + - Event can be up to 15 characters + - Action can be up to 15 characters + - House is 3 characters + - Team name is up to 11 characters + .....................................................................*/ + //trigtext[i] = (char *)HidPage.Get_Graphic_Buffer()->Get_Buffer() + 60 * i; + trigtext[i] = new char[255]; + sprintf(trigtext[i],"%s\t%s\t%s\t", + Triggers.Ptr(i)->Get_Name(), + TriggerClass::Name_From_Event(Triggers.Ptr(i)->Event), + TriggerClass::Name_From_Action(Triggers.Ptr(i)->Action)); + + /* + ......................... Add on the house ID ......................... + */ + if (TriggerClass::Event_Need_House(Triggers.Ptr(i)->Event)) { + if (Triggers.Ptr(i)->House != HOUSE_NONE) { + strcat(trigtext[i], HouseTypeClass::As_Reference(Triggers.Ptr(i)->House).Suffix); + } else { + strcat(trigtext[i], "!!!"); + } + } else { + strcat(trigtext[i]," "); + } + + /* + .......................... Add the team name .......................... + */ + strcat(trigtext[i],"\t"); + if (TriggerClass::Action_Need_Team(Triggers.Ptr(i)->Action)) { + if (Triggers.Ptr(i)->Team) { + strcat(trigtext[i],Triggers.Ptr(i)->Team->IniName); + } else { + strcat(trigtext[i], "!!!"); + } + } + + /* + ................. Set def_idx if this is CurTrigger ................... + */ + if (Triggers.Ptr(i) == CurTrigger) { + def_idx = i; + } + } + + /* + .......................... Fill in the list box .......................... + */ + for (i = 0; i < Triggers.Count(); i++) { + triggerlist.Add_Item(trigtext[i]); + } + triggerlist.Set_Selected_Index(def_idx); + + /* + ....................... Set CurTrigger if it isn't ....................... + */ + if (Triggers.Count()==0) { + CurTrigger = NULL; + } else { + if (!CurTrigger) { + CurTrigger = Triggers.Ptr(def_idx); + } + } + + /* + ............................ Create the list ............................. + */ + commands = &triggerlist; + editbtn.Add_Tail(*commands); + newbtn.Add_Tail(*commands); + deletebtn.Add_Tail(*commands); + okbtn.Add_Tail(*commands); + + /* + ------------------------ Init tab stops for list ------------------------- + */ + triggerlist.Set_Tabs(tabs); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + display = REDRAW_ALL; + process = true; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + + /* + ...................... Display the dialog box ...................... + */ + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + Dialog_Box(D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W, D_DIALOG_H); + Draw_Caption(TXT_NONE, D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W); + + /* + ....................... Draw the captions ....................... + */ + Fancy_Text_Print("Triggers", D_DIALOG_CX, D_DIALOG_Y + D_MARGIN, CC_GREEN, TBLACK, + TPF_CENTER|TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW); + } + + /* + ........................ Redraw the buttons ........................ + */ + if (display >= REDRAW_BUTTONS) { + commands->Draw_All(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (TRIGGER_LIST | KN_BUTTON): + def_idx = triggerlist.Current_Index(); + if (def_idx < Triggers.Count()) { + CurTrigger = Triggers.Ptr(def_idx); + } + break; + + case (BUTTON_EDIT | KN_BUTTON): + if (CurTrigger) { // only allow if there's one selected + process = false; + edit_trig = true; + } + break; + + case (BUTTON_NEW | KN_BUTTON): + process = false; + new_trig = true; + break; + + case (BUTTON_DELETE | KN_BUTTON): + process = false; + del_trig = true; + break; + + case (KN_RETURN): + case (BUTTON_OK | KN_BUTTON): + process = false; + break; + } + } + + /* + --------------------------- Redraw the display --------------------------- + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + + for (i = 0; i < Triggers.Count(); i++) { + delete [] trigtext[i]; + } + + if (edit_trig) return(1); + if (new_trig) return(2); + if (del_trig) return(3); + return(0); +} + + +/*************************************************************************** + * MapEditClass::Edit_Trigger -- lets user edit a [new] trigger * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Trigger Editor ³ * + * ³ ³ * + * ³ Events Actions ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ³ * + * ³ ³ ³³ ³ ³³ ³ * + * ³ ³ ÃÄ´ ³ ÃÄ´ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ³ ÃÄ´ ³ ÃÄ´ ³ * + * ³ ³ ³³ ³ ³³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ³ * + * ³ ³ * + * ³ Name: _______ [ Volatile ] ³ * + * ³ [GDI] [ Persistent ] ³ * + * ³ Time / Credits: _______ [NOD] [SemiPersistent] ³ * + * ³ ³ * + * ³ [Team] Team_Name ³ * + * ³ ³ * + * ³ [OK] [Cancel] ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = OK, -1 = cancel * + * * + * WARNINGS: * + * CurTrigger must NOT be NULL when this function is called. * + * * + * HISTORY: * + * 11/29/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::Edit_Trigger(void) +{ + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + enum { + D_DIALOG_W = 528, + D_DIALOG_H = 376, + D_DIALOG_X = ((640 - D_DIALOG_W) / 2), + D_DIALOG_Y = ((400 - D_DIALOG_H) / 2), + D_DIALOG_CX = D_DIALOG_X + (D_DIALOG_W / 2), + + D_TXT8_H = 22, + D_MARGIN = 14, + + D_EVENT_W = 240, + D_EVENT_H = 88, + D_EVENT_X = D_DIALOG_X + D_MARGIN, + D_EVENT_Y = D_DIALOG_Y + D_MARGIN + D_TXT8_H + D_MARGIN + D_TXT8_H, + + D_ACTION_W = 240, + D_ACTION_H = 88, + D_ACTION_X = D_DIALOG_X + D_DIALOG_W - D_MARGIN - D_ACTION_W, + D_ACTION_Y = D_DIALOG_Y + D_MARGIN + D_TXT8_H + D_MARGIN + D_TXT8_H, + + D_NAME_W = 80, + D_NAME_H = 18, + D_NAME_X = D_EVENT_X + (D_EVENT_W / 2) - 10, + D_NAME_Y = D_EVENT_Y + D_EVENT_H + D_MARGIN, + + D_DATA_W = 80, + D_DATA_H = 18, + D_DATA_X = D_NAME_X, + D_DATA_Y = D_NAME_Y + D_NAME_H + D_MARGIN, + + D_TEAM_W = 80, + D_TEAM_H = 18, + D_TEAM_X = D_NAME_X - D_TEAM_W - 5, + D_TEAM_Y = D_DATA_Y + D_DATA_H + D_MARGIN, + + D_GDI_W = 90, + D_GDI_H = 18, + D_GDI_X = D_DIALOG_CX - (D_GDI_W / 2), + D_GDI_Y = D_NAME_Y, + + D_NOD_W = 90, + D_NOD_H = 18, + D_NOD_X = D_GDI_X, + D_NOD_Y = D_GDI_Y + D_GDI_H, + + D_NEU_W = 90, + D_NEU_H = 18, + D_NEU_X = D_NOD_X, + D_NEU_Y = D_NOD_Y + D_NOD_H, + + D_MULTI1_W = 44, + D_MULTI1_H = 18, + D_MULTI1_X = D_GDI_X, + D_MULTI1_Y = D_GDI_Y, + + D_MULTI2_W = 44, + D_MULTI2_H = 18, + D_MULTI2_X = D_GDI_X + D_MULTI1_W, + D_MULTI2_Y = D_GDI_Y, + + D_MULTI3_W = 44, + D_MULTI3_H = 18, + D_MULTI3_X = D_NOD_X, + D_MULTI3_Y = D_NOD_Y, + + D_MULTI4_W = 44, + D_MULTI4_H = 18, + D_MULTI4_X = D_NOD_X + D_MULTI1_W, + D_MULTI4_Y = D_NOD_Y, + + D_VOLATILE_W = 100, + D_VOLATILE_H = 18, + D_VOLATILE_X = D_ACTION_X + (D_ACTION_W / 2) - (D_VOLATILE_W / 2) + 10, + D_VOLATILE_Y = D_NAME_Y, + + D_PERSIST_W = 100, + D_PERSIST_H = 18, + D_PERSIST_X = D_ACTION_X + (D_ACTION_W / 2) - (D_PERSIST_W / 2) + 10, + D_PERSIST_Y = D_VOLATILE_Y + D_VOLATILE_H, + + D_SEMIPERSIST_W = 100, + D_SEMIPERSIST_H = 18, + D_SEMIPERSIST_X = D_ACTION_X + (D_ACTION_W / 2) - (D_SEMIPERSIST_W / 2) + 10, + D_SEMIPERSIST_Y = D_PERSIST_Y + D_PERSIST_H, + + D_OK_W = 90, + D_OK_H = 18, + D_OK_X = D_DIALOG_CX - 5 - D_OK_W, + D_OK_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_OK_H, + + D_CANCEL_W = 90, + D_CANCEL_H = 18, + D_CANCEL_X = D_DIALOG_CX + 5, + D_CANCEL_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_CANCEL_H, + + }; + + /*........................................................................ + Button enumerations: + ........................................................................*/ + enum { + EVENT_LIST=100, + ACTION_LIST, + NAME_EDIT, + DATA_EDIT, + BUTTON_TEAM, + BUTTON_GDI, + BUTTON_NOD, + BUTTON_NEUTRAL, + BUTTON_JP, // placeholder + BUTTON_MULTI1, + BUTTON_MULTI2, + BUTTON_MULTI3, + BUTTON_MULTI4, + BUTTON_MULTI5, + BUTTON_MULTI6, + BUTTON_VOLATILE, + BUTTON_PERSIST, + BUTTON_SEMIPERSIST, + BUTTON_OK, + BUTTON_CANCEL, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables: + ........................................................................*/ + RedrawType display; // requested redraw level + bool process; // loop while true + KeyNumType input; // user input + bool cancel = false; // true = user cancels + int i; // loop counter + EventType event_idx; // index for event list + TriggerClass::ActionType action_idx; // index for action list + char namebuf[5]; // name of this trigger + char databuf[10]; // for credit/time-based triggers + HousesType house; // house for this trigger + const char *eventnames[EVENT_COUNT + 1]; // names of events + const char *actionnames[TriggerClass::ACTION_COUNT + 1]; // names of actions + TriggerClass::PersistantType persistant; // trigger's persistence level + + /*........................................................................ + These flags enable various controls for each EventType. + ........................................................................*/ +// static char data_enabled[EVENT_COUNT] = {0,0,0,0,0,0,0,0,0,1,1,1,1,0,0}; +// static char house_enabled[EVENT_COUNT] = {1,0,0,0,0,1,1,1,1,1,1,1,1,1,1}; +// static char team_enabled[TriggerClass::ACTION_COUNT] = {0,0,0,1,1,0,1,0,0,0,0,0,0,0}; + + /*........................................................................ + Buttons + ........................................................................*/ + ControlClass *commands = NULL; // the button list + + ListClass eventlist(EVENT_LIST, + D_EVENT_X, D_EVENT_Y, D_EVENT_W, D_EVENT_H, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + Hires_Retrieve("BTN-UP.SHP"), + Hires_Retrieve("BTN-DN.SHP")); + + ListClass actionlist(ACTION_LIST, + D_ACTION_X, D_ACTION_Y, D_ACTION_W, D_ACTION_H, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + Hires_Retrieve("BTN-UP.SHP"), + Hires_Retrieve("BTN-DN.SHP")); + + EditClass name_edt(NAME_EDIT, namebuf, 5, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NAME_X, D_NAME_Y, D_NAME_W, D_NAME_H, EditClass::ALPHANUMERIC); + + EditClass data_edt(DATA_EDIT, databuf, 8, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_DATA_X, D_DATA_Y, D_DATA_W, D_DATA_H, EditClass::ALPHANUMERIC); + + TextButtonClass teambtn(BUTTON_TEAM, "Team", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_TEAM_X, D_TEAM_Y, D_TEAM_W, D_TEAM_H); + + TextButtonClass gdibtn(BUTTON_GDI, "GDI", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_GDI_X, D_GDI_Y, D_GDI_W, D_GDI_H); + + TextButtonClass nodbtn(BUTTON_NOD, "NOD", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NOD_X, D_NOD_Y, D_NOD_W, D_NOD_H); + + TextButtonClass neutralbtn(BUTTON_NEUTRAL, "Neutral", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NEU_X, D_NEU_Y, D_NEU_W, D_NEU_H); + + TextButtonClass multi1btn(BUTTON_MULTI1, "M1", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI1_X, D_MULTI1_Y, D_MULTI1_W, D_MULTI1_H); + + TextButtonClass multi2btn(BUTTON_MULTI2, "M2", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI2_X, D_MULTI2_Y, D_MULTI2_W, D_MULTI2_H); + + TextButtonClass multi3btn(BUTTON_MULTI3, "M3", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI3_X, D_MULTI3_Y, D_MULTI3_W, D_MULTI3_H); + + TextButtonClass multi4btn(BUTTON_MULTI4, "M4", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI4_X, D_MULTI4_Y, D_MULTI4_W, D_MULTI4_H); + + TextButtonClass volatilebtn(BUTTON_VOLATILE, "Volatile", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_VOLATILE_X, D_VOLATILE_Y, D_VOLATILE_W, D_VOLATILE_H); + + TextButtonClass persistbtn(BUTTON_PERSIST, "Persistant", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_PERSIST_X, D_PERSIST_Y, D_PERSIST_W, D_PERSIST_H); + + TextButtonClass semipersistbtn(BUTTON_SEMIPERSIST, "SemiPersistant", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_SEMIPERSIST_X, D_SEMIPERSIST_Y, D_SEMIPERSIST_W, D_SEMIPERSIST_H); + + TextButtonClass okbtn(BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_OK_X, D_OK_Y, D_OK_W, D_OK_H); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_CANCEL_X, D_CANCEL_Y, D_CANCEL_W, D_CANCEL_H); + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + + /* + ....................... Set default button states ........................ + */ + event_idx = CurTrigger->Event; // event list + if (event_idx == EVENT_NONE) event_idx = EVENT_FIRST; + + action_idx = CurTrigger->Action; // action list + if (action_idx == TriggerClass::ACTION_NONE) action_idx = TriggerClass::ACTION_FIRST; + + strcpy(namebuf,CurTrigger->Get_Name()); // Name + name_edt.Set_Text(namebuf,5); + + if (TriggerClass::Event_Need_Data(event_idx)) { + sprintf(databuf,"%ld",CurTrigger->Data); // Credits/Time + data_edt.Set_Text(databuf,8); + } + + house = CurTrigger->House; // House + + persistant = CurTrigger->IsPersistant; + + volatilebtn.Turn_Off(); + persistbtn.Turn_Off(); + semipersistbtn.Turn_Off(); + switch (CurTrigger->IsPersistant) { + case TriggerClass::VOLATILE: + volatilebtn.Turn_On(); + break; + + case TriggerClass::SEMIPERSISTANT: + semipersistbtn.Turn_On(); + break; + + case TriggerClass::PERSISTANT: + persistbtn.Turn_On(); + break; + } + + /* + ......................... Fill in the list boxes ......................... + */ + for (i = 0; i < EVENT_COUNT; i++) { + eventnames[i] = TriggerClass::Name_From_Event( (EventType)i); + eventlist.Add_Item(eventnames[i]); + } + eventlist.Set_Selected_Index(event_idx); + + for (i = 0; i < TriggerClass::ACTION_COUNT; i++) { + actionnames[i] = TriggerClass::Name_From_Action( (TriggerClass::ActionType)i); + actionlist.Add_Item(actionnames[i]); + } + actionlist.Set_Selected_Index(action_idx); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + display = REDRAW_ALL; + process = true; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + + /* + ...................... Display the dialog box ...................... + */ + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + Dialog_Box(D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W, D_DIALOG_H); + Draw_Caption(TXT_NONE, D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W); + + /* + ....................... Draw the captions ....................... + */ + Fancy_Text_Print("Trigger Editor", D_DIALOG_CX, D_DIALOG_Y + D_MARGIN, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print("Events", D_EVENT_X + D_EVENT_W / 2, + D_EVENT_Y - D_TXT8_H, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print("Actions", D_ACTION_X + D_ACTION_W / 2, + D_ACTION_Y - D_TXT8_H, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print("Name", D_NAME_X - 5, D_NAME_Y, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if ((EventType)event_idx==EVENT_CREDITS) { // use 'Data' for Credits + Fancy_Text_Print("Credits", D_DATA_X - 5, D_DATA_Y, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + } else { + if ((EventType)event_idx==EVENT_TIME) { // use 'Data' for Time + Fancy_Text_Print("1/10 Min", D_DATA_X - 5, D_DATA_Y, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + } + + if (TriggerClass::Action_Need_Team(action_idx)) { + if (CurTrigger->Team) { + Fancy_Text_Print(CurTrigger->Team->IniName, + D_TEAM_X + D_TEAM_W + 5, D_TEAM_Y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } else { + Fancy_Text_Print( "!!!", + D_TEAM_X + D_TEAM_W + 5, D_TEAM_Y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + } + } + + /* + ..................... Rebuild the button list ...................... + */ + eventlist.Zap(); + actionlist.Zap(); + name_edt.Zap(); + data_edt.Zap(); + teambtn.Zap(); + gdibtn.Zap(); + nodbtn.Zap(); + neutralbtn.Zap(); + volatilebtn.Zap(); + persistbtn.Zap(); + semipersistbtn.Zap(); + okbtn.Zap(); + cancelbtn.Zap(); + + commands = &okbtn; + cancelbtn.Add_Tail(*commands); + eventlist.Add_Tail(*commands); + actionlist.Add_Tail(*commands); + name_edt.Add_Tail(*commands); + volatilebtn.Add_Tail(*commands); + persistbtn.Add_Tail(*commands); + semipersistbtn.Add_Tail(*commands); + if (TriggerClass::Event_Need_Data(event_idx)) { + data_edt.Add_Tail(*commands); + sprintf(databuf,"%ld",CurTrigger->Data); + data_edt.Set_Text(databuf,8); + } + if (TriggerClass::Event_Need_House(event_idx)) { + gdibtn.Add_Tail(*commands); + nodbtn.Add_Tail(*commands); + neutralbtn.Add_Tail(*commands); + Set_House_Buttons(house, commands, BUTTON_GDI); + } + if (TriggerClass::Action_Need_Team(action_idx)) teambtn.Add_Tail(*commands); + + /* + ........................ Redraw the buttons ........................ + */ + if (display >= REDRAW_BUTTONS) { + commands->Flag_List_To_Redraw(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (EVENT_LIST | KN_BUTTON): + if (eventlist.Current_Index() != event_idx) { + event_idx = EventType(eventlist.Current_Index()); + databuf[0] = 0; + CurTrigger->Data = 0; + if (!TriggerClass::Event_Need_House(event_idx)) { + CurTrigger->House = HOUSE_NONE; + } + display = REDRAW_ALL; + } + break; + + case (ACTION_LIST | KN_BUTTON): + if (actionlist.Current_Index() != action_idx) { + action_idx = TriggerClass::ActionType(actionlist.Current_Index()); + display = REDRAW_ALL; + } + break; + + case (NAME_EDIT | KN_BUTTON): + break; + + case (DATA_EDIT | KN_BUTTON): + break; + + case (BUTTON_GDI | KN_BUTTON): + case (BUTTON_NOD | KN_BUTTON): + case (BUTTON_NEUTRAL | KN_BUTTON): + case (BUTTON_MULTI1 | KN_BUTTON): + case (BUTTON_MULTI2 | KN_BUTTON): + case (BUTTON_MULTI3 | KN_BUTTON): + case (BUTTON_MULTI4 | KN_BUTTON): + case (BUTTON_MULTI5 | KN_BUTTON): + case (BUTTON_MULTI6 | KN_BUTTON): + house = (HousesType)( (input & (~KN_BUTTON)) - BUTTON_GDI); + Set_House_Buttons(house, commands, BUTTON_GDI); + break; + + case (BUTTON_TEAM | KN_BUTTON): + Handle_Teams("Select a Team"); + if (CurTeam) { + CurTrigger->Team = CurTeam; + } + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + display = REDRAW_ALL; + break; + + case (BUTTON_VOLATILE | KN_BUTTON): + persistant = TriggerClass::VOLATILE; + volatilebtn.Turn_On(); + persistbtn.Turn_Off(); + semipersistbtn.Turn_Off(); + break; + + case (BUTTON_PERSIST | KN_BUTTON): + persistant = TriggerClass::PERSISTANT; + volatilebtn.Turn_Off(); + persistbtn.Turn_On(); + semipersistbtn.Turn_Off(); + break; + + case (BUTTON_SEMIPERSIST | KN_BUTTON): + persistant = TriggerClass::SEMIPERSISTANT; + volatilebtn.Turn_Off(); + persistbtn.Turn_Off(); + semipersistbtn.Turn_On(); + break; + + case (KN_RETURN): + case (BUTTON_OK | KN_BUTTON): + process = false; + break; + + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + cancel = true; + process = false; + break; + + default: + break; + } + } + + /* + ------------------------------ Save values ------------------------------- + */ + if (!cancel) { + + /* + .......................... Get list indices ........................... + */ + event_idx = EventType(eventlist.Current_Index()); + action_idx = TriggerClass::ActionType(actionlist.Current_Index()); + + /* + ......................... Set Event & Action .......................... + */ + CurTrigger->Event = EventType(event_idx); + CurTrigger->Action = TriggerClass::ActionType(action_idx); + + /* + .............................. Set name ............................... + */ + if (strlen(namebuf)==0) { + CurTrigger->Set_Name("____"); + } else { + CurTrigger->Set_Name(namebuf); + } + + /* + .............................. Set Data ............................... + */ + if (TriggerClass::Event_Need_Data(event_idx)) { + CurTrigger->Data = atol(databuf); + } + + /* + .............................. Set House .............................. + */ + if (TriggerClass::Event_Need_House(event_idx)) { + CurTrigger->House = house; + } else { + CurTrigger->House = HOUSE_NONE; + } + + /* + ........................... Set Persistence .......................... + */ + CurTrigger->IsPersistant = persistant; + } + + /* + --------------------------- Redraw the display --------------------------- + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + + if (cancel) { + return(-1); + } else { + return(0); + } +} + + +/*************************************************************************** + * MapEditClass::Import_Triggers -- lets user import triggers * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Triggers ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ³ * + * ³ ³x Name Event Action House ³³ ³ * + * ³ ³ Name Event Action House ÃÄ´ ³ * + * ³ ³x Name Event Action House ³ ³ ³ * + * ³ ³ Name Event Action House ³ ³ ³ * + * ³ ³ ³ ³ ³ * + * ³ ³ ³ ³ ³ * + * ³ ³ ÃÄ´ ³ * + * ³ ³ ³³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ³ * + * ³ ³ * + * ³ [OK] [Cancel] ³ * + * ³ ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = OK, -1 = user cancelled * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/29/1995 BRR : Created. * + *=========================================================================*/ +int MapEditClass::Import_Triggers(void) +{ + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + enum { + D_DIALOG_W = 480, + D_DIALOG_H = 290, + D_DIALOG_X = ((640 - D_DIALOG_W) / 2), + D_DIALOG_Y = ((400 - D_DIALOG_H) / 2), + D_DIALOG_CX = D_DIALOG_X + (D_DIALOG_W / 2), + + D_TXT8_H = 22, + D_MARGIN = 14, + + D_LIST_W = 452, + D_LIST_H = 208, + D_LIST_X = D_DIALOG_X + D_MARGIN, + D_LIST_Y = D_DIALOG_Y + D_MARGIN + D_TXT8_H, + + D_OK_W = 90, + D_OK_H = 18, + D_OK_X = D_DIALOG_CX - D_OK_W - 5, + D_OK_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_OK_H, + + D_CANCEL_W = 90, + D_CANCEL_H = 18, + D_CANCEL_X = D_DIALOG_CX + 5, + D_CANCEL_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_OK_H, + + }; + /*........................................................................ + Button enumerations: + ........................................................................*/ + enum { + TRIGGER_LIST=100, + BUTTON_OK, + BUTTON_CANCEL, + }; + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + /*........................................................................ + Dialog variables: + ........................................................................*/ + RedrawType display; // requested redraw level + bool process; // loop while true + KeyNumType input; // user input + bool cancel = false; + static int tabs[] = + {70, 220, 370, 420}; // list box tab stops + DynamicVectorClass trignames; // list of INI trigger names + char *inibuf; // working INI buffer + CCFileClass file; // file for reading the INI file + char buf[128]; // for reading an INI entry + char *tbuffer; // Accumulation buffer of trigger IDs. + int len; // Length of data in buffer. + TriggerClass *trigger; // Working trigger pointer. + char *item; // for adding to list box + char *eventptr; + char *actionptr; + char *houseptr; + int i; + /*........................................................................ + Buttons + ........................................................................*/ + ControlClass *commands = NULL; // the button list + + CheckListClass triggerlist (TRIGGER_LIST, + D_LIST_X, D_LIST_Y, D_LIST_W, D_LIST_H, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + Hires_Retrieve("BTN-UP.SHP"), + Hires_Retrieve("BTN-DN.SHP")); + + TextButtonClass okbtn (BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_OK_X, D_OK_Y, D_OK_W, D_OK_H); + + TextButtonClass cancelbtn (BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_CANCEL_X, D_CANCEL_Y, D_CANCEL_W, D_CANCEL_H); + + Set_Logic_Page(SeenBuff); + + /*------------------------------------------------------------------------ + Read the MASTER.INI file + ------------------------------------------------------------------------*/ + /*........................................................................ + Read the file into the staging buffer + ........................................................................*/ + inibuf = new char [30000]; + memset(inibuf, '\0', 30000); + file.Set_Name("MASTER.INI"); + if (!file.Is_Available()) { + file.Close(); + delete [] inibuf; + return(-1); + } else { + file.Read(inibuf, 30000 - 1); + } + file.Close(); + + /*........................................................................ + Read all entry names in the Triggers section into a temp buffer + ........................................................................*/ + len = strlen(inibuf) + 2; + tbuffer = inibuf + len; + WWGetPrivateProfileString(TriggerClass::INI_Name(), NULL, NULL, tbuffer, + 30000 - len, inibuf); + + /*........................................................................ + For each entry in the INI section: + - Get the entry + - Generate a string describing the trigger + - Add that string to the list box + - Add a ptr to the INI entry name to our 'trignames' list + ........................................................................*/ + while (*tbuffer != '\0') { + WWGetPrivateProfileString(TriggerClass::INI_Name(), tbuffer, NULL, buf, sizeof(buf)-1, inibuf); + item = new char [60]; + + /* + ** Parse the INI entry + */ + eventptr = strtok(buf,","); + actionptr = strtok(NULL,","); + strtok(NULL,","); + houseptr = strtok(NULL,","); + + /* + ** Generate the descriptive string + */ + sprintf(item, " %s\t%s\t%s\t", tbuffer, eventptr, actionptr); + + /* + ** Add house name if needed + */ + if (TriggerClass::Event_Need_House(TriggerClass::Event_From_Name(eventptr))) { + HousesType house = HouseTypeClass::From_Name(houseptr); + if (house != HOUSE_NONE) { + strcat(item, HouseTypeClass::As_Reference(house).Suffix); + } else { + strcat(item, "!!!"); + } + } else { + strcat(item," "); + } + + /* + ** Add the item to the list box + */ + triggerlist.Add_Item(item); + + /* + ** Add the name to our internal name list + */ + trignames.Add(tbuffer); + + tbuffer += strlen(tbuffer)+1; + } + + /* + ............................ Create the list ............................. + */ + commands = &triggerlist; + okbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + + /* + ------------------------ Init tab stops for list ------------------------- + */ + triggerlist.Set_Tabs(tabs); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + display = REDRAW_ALL; + process = true; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + /* + ...................... Display the dialog box ...................... + */ + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + Dialog_Box(D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W, D_DIALOG_H); + Draw_Caption(TXT_NONE, D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W); + /* + ....................... Draw the captions ....................... + */ + Fancy_Text_Print("Import Triggers", D_DIALOG_CX, D_DIALOG_Y + D_MARGIN, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + /* + ........................ Redraw the buttons ........................ + */ + if (display >= REDRAW_BUTTONS) + commands->Flag_List_To_Redraw(); + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (TRIGGER_LIST | KN_BUTTON): + break; + + case (KN_RETURN): + case (BUTTON_OK | KN_BUTTON): + process = false; + break; + + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + cancel = true; + process = false; + break; + } + } + + /* + --------------------------- Redraw the display --------------------------- + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + + /*........................................................................ + Re-parse the INI section; if any item is checked in the list box, create + that trigger for this scenario. + ........................................................................*/ + if (!cancel) { + tbuffer = inibuf + len; + i = 0; + while (*tbuffer != '\0') { + + /* + ** If this item is checked on the list, create a new trigger + ** and fill it in. + */ + if (triggerlist.Is_Checked(i)) { + WWGetPrivateProfileString(TriggerClass::INI_Name(), tbuffer, NULL, + buf, sizeof(buf)-1, inibuf); + + trigger = new TriggerClass(); + trigger->Fill_In(tbuffer, buf); + + if (trigger->House != HOUSE_NONE) + HouseTriggers[trigger->House].Add(trigger); + } + + tbuffer += strlen(tbuffer)+1; + i++; + } + } + + + /*........................................................................ + Clean up memory + ........................................................................*/ + trignames.Clear(); + while (triggerlist.Count()) { + item = (char *)triggerlist.Get_Item(0); + triggerlist.Remove_Item(item); + delete [] item; + } + delete [] inibuf; + + if (cancel) { + return(-1); + } else { + return(0); + } +} + + +/*************************************************************************** + * MapEditClass::Import_Teams -- lets the user import teams * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Teams ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ³ * + * ³ ³ Name House Class:Count,Class:Count ³³ ³ * + * ³ ³ Name House Class:Count,Class:Count ÃÄ´ ³ * + * ³ ³ Name House Class:Count,Class:Count ³ ³ ³ * + * ³ ³ Name House Class:Count,Class:Count ³ ³ ³ * + * ³ ³ ³ ³ ³ * + * ³ ³ ³ ³ ³ * + * ³ ³ ÃÄ´ ³ * + * ³ ³ ³³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ³ * + * ³ ³ * + * ³ [OK] [Cancel] ³ * + * ³ ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = OK, -1 = user cancelled * + * * + * WARNINGS: * + * Uses HIDBUFF. * + * * + * HISTORY: * + * 12/08/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::Import_Teams(void) +{ + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + enum { + D_DIALOG_W = 528, + D_DIALOG_H = 290, + D_DIALOG_X = ((640 - D_DIALOG_W) / 2), + D_DIALOG_Y = ((400 - D_DIALOG_H) / 2), + D_DIALOG_CX = D_DIALOG_X + (D_DIALOG_W / 2), + + D_TXT8_H = 22, + D_MARGIN = 14, + + D_LIST_W = 500, + D_LIST_H = 208, + D_LIST_X = D_DIALOG_X + D_MARGIN, + D_LIST_Y = D_DIALOG_Y + D_MARGIN + D_TXT8_H, + + D_OK_W = 90, + D_OK_H = 18, + D_OK_X = D_DIALOG_CX - D_OK_W - 5, + D_OK_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_OK_H, + + D_CANCEL_W = 90, + D_CANCEL_H = 18, + D_CANCEL_X = D_DIALOG_CX + 5, + D_CANCEL_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_OK_H, + + TEAMTXT_LEN = 43, // max length of a team entry + }; + /*........................................................................ + Button enumerations: + ........................................................................*/ + enum { + TEAM_LIST=100, + BUTTON_OK, + BUTTON_CANCEL, + }; + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + /*........................................................................ + Dialog variables: + ........................................................................*/ + RedrawType display; // requested redraw level + bool process; // loop while true + KeyNumType input; // user input + bool cancel = false; + static int tabs[] = {120, 180}; // list box tab stops + DynamicVectorClass teamnames; // list of INI team names + char *inibuf; // working INI buffer + CCFileClass file; // file for reading the INI file + char buf[128]; // for reading an INI entry + char *tbuffer; // Accumulation buffer of team IDs. + int len; // Length of data in buffer. + TeamTypeClass *team; // Working team pointer. + char *item; // for adding to list box + char *houseptr; + char *classptr; + int numclasses; + int i; + /*........................................................................ + Buttons + ........................................................................*/ + ControlClass *commands = NULL; // the button list + + CheckListClass teamlist (TEAM_LIST, + D_LIST_X, D_LIST_Y, D_LIST_W, D_LIST_H, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + Hires_Retrieve("BTN-UP.SHP"), + Hires_Retrieve("BTN-DN.SHP")); + + TextButtonClass okbtn (BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_OK_X, D_OK_Y, D_OK_W, D_OK_H); + + TextButtonClass cancelbtn (BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_CANCEL_X, D_CANCEL_Y, D_CANCEL_W, D_CANCEL_H); + + Set_Logic_Page(SeenBuff); + + /*------------------------------------------------------------------------ + Read the MASTER.INI file + ------------------------------------------------------------------------*/ + /*........................................................................ + Read the file into the staging buffer + ........................................................................*/ + inibuf = new char [30000]; + memset(inibuf, '\0', 30000); + file.Set_Name("MASTER.INI"); + if (!file.Is_Available()) { + file.Close(); + delete [] inibuf; + return(-1); + } else { + file.Read(inibuf, 30000 - 1); + } + + file.Close(); + /*........................................................................ + Read all entry names in the TeamTypes section into a temp buffer + ........................................................................*/ + len = strlen(inibuf) + 2; + tbuffer = inibuf + len; + WWGetPrivateProfileString(TeamTypeClass::INI_Name(), NULL, NULL, tbuffer, + 30000 - len, inibuf); + + /*........................................................................ + For each entry in the INI section: + - Get the entry + - Generate a string describing the team + - Add that string to the list box + - Add a ptr to the INI entry name to our 'teamnames' list + ........................................................................*/ + while (*tbuffer != '\0') { + WWGetPrivateProfileString(TeamTypeClass::INI_Name(), tbuffer, NULL, buf, sizeof(buf)-1, inibuf); + item = new char [60]; + + /* + ** Parse the INI entry + */ + houseptr = strtok(buf,","); + for (i = 0; i < 9; i++) { + strtok(NULL,","); + } + numclasses = atoi(strtok(NULL,",")); + + /* + ** Generate the descriptive string + */ + sprintf(item," %s\t",tbuffer); + HousesType house = HouseTypeClass::From_Name(houseptr); + if (house != HOUSE_NONE) { + strcat(item, HouseTypeClass::As_Reference(house).Suffix); + } else { + strcat(item, "!!!"); + } + strcat(item, "\t"); + + classptr = strtok(NULL,","); + for (i = 0; i < numclasses; i++) { + if (strlen(item) + strlen(classptr) < 60) { + strcat(item,classptr); + classptr = strtok(NULL,","); + } else { + break; + } + } + + /* + ** Add the item to the list box + */ + teamlist.Add_Item(item); + /* + ** Add the name to our internal name list + */ + teamnames.Add(tbuffer); + + tbuffer += strlen(tbuffer)+1; + } + + /* + ............................ Create the list ............................. + */ + commands = &teamlist; + okbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + + /* + ------------------------ Init tab stops for list ------------------------- + */ + teamlist.Set_Tabs(tabs); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + display = REDRAW_ALL; + process = true; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + /* + ...................... Display the dialog box ...................... + */ + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + Dialog_Box(D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W, D_DIALOG_H); + Draw_Caption(TXT_NONE, D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W); + /* + ....................... Draw the captions ....................... + */ + Fancy_Text_Print("Import Teams", D_DIALOG_CX, D_DIALOG_Y + D_MARGIN, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + /* + ........................ Redraw the buttons ........................ + */ + if (display >= REDRAW_BUTTONS) + commands->Flag_List_To_Redraw(); + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (TEAM_LIST | KN_BUTTON): + break; + + case (KN_RETURN): + case (BUTTON_OK | KN_BUTTON): + process = false; + break; + + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + cancel = true; + process = false; + break; + } + } + + /* + --------------------------- Redraw the display --------------------------- + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + + /*........................................................................ + Re-parse the INI section; if any item is checked in the list box, create + that team for this scenario. + ........................................................................*/ + if (!cancel) { + tbuffer = inibuf + len; + i = 0; + while (*tbuffer != '\0') { + /* + ** If this item is checked on the list, create a new team + ** and fill it in. + */ + if (teamlist.Is_Checked(i)) { + WWGetPrivateProfileString(TeamTypeClass::INI_Name(), tbuffer, NULL, + buf, sizeof(buf)-1, inibuf); + + team = new TeamTypeClass(); + team->Fill_In(tbuffer,buf); + } + + tbuffer += strlen(tbuffer)+1; + i++; + } + } + + /*........................................................................ + Clean up memory + ........................................................................*/ + teamnames.Clear(); + while (teamlist.Count()) { + item = (char *)teamlist.Get_Item(0); + teamlist.Remove_Item(item); + delete [] item; + } + delete [] inibuf; + + if (cancel) { + return(-1); + } else { + return(0); + } +} + +#endif diff --git a/MAPEDIT.CPP b/MAPEDIT.CPP new file mode 100644 index 0000000..d6c40a1 --- /dev/null +++ b/MAPEDIT.CPP @@ -0,0 +1,1924 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\mapedit.cpv 2.18 16 Oct 1995 16:48:40 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MAPEDIT.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : October 20, 1994 * + * * + * Last Update : February 2, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * Map Editor overloaded routines & utility routines * + *-------------------------------------------------------------------------* + * Map Editor modules: * + * (Yes, they're all one huge class.) * + * mapedit.cpp: overloaded routines, utility routines * + * mapeddlg.cpp: map editor dialogs, most of the main menu options * + * mapedplc.cpp: object-placing routines * + * mapedsel.cpp: object-selection & manipulation routines * + * mapedtm.cpp: team-editing routines * + *-------------------------------------------------------------------------* + * Functions: * + * MapEditClass::MapEditClass -- class constructor * + * MapEditClass::One_Time -- one-time initialization * + * MapEditClass::Read_INI -- overloaded Read_INI function * + * MapEditClass::Clear_List -- clears the internal choosable object list * + * MapEditClass::Add_To_List -- adds a TypeClass to the choosable list * + * MapEditClass::AI -- The map editor's main logic * + * MapEditClass::Draw_It -- overloaded Redraw routine * + * MapEditClass::Main_Menu -- main menu processor for map editor * + * MapEditClass::AI_Menu -- menu of AI options * + * MapEditClass::Mouse_Moved -- checks for mouse motion * + * MapEditClass::Verify_House -- sees if given house can own given obj * + * MapEditClass::Cycle_House -- finds next valid house for object type * + * MapEditClass::Trigger_Needs_Team -- tells if a trigger needs a team * + * MapEditClass::Fatal -- exits with error message * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#ifdef SCENARIO_EDITOR + +/* +****************************** Globals/Externs ****************************** +*/ +/*........................................................................... +Array of all missions supported by the map editor +...........................................................................*/ +MissionType MapEditClass::MapEditMissions[] = { + MISSION_GUARD, + MISSION_STICKY, + MISSION_HARVEST, + MISSION_GUARD_AREA, + MISSION_RETURN, + MISSION_AMBUSH, + MISSION_HUNT, + MISSION_SLEEP, +}; +#define NUM_EDIT_MISSIONS (sizeof(MapEditClass::MapEditMissions) / sizeof(MapEditClass::MapEditMissions[0])) + + +/*........................................................................... +For menu processing +...........................................................................*/ +extern int UnknownKey; // in menus.cpp + +char MapEditClass::HealthBuf[20]; + + +/*************************************************************************** + * MapEditClass::MapEditClass -- class constructor * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 10/20/1994 BR : Created. * + *=========================================================================*/ +MapEditClass::MapEditClass(void) +{ + /* + ** Init data members. + */ + ScenVar = SCEN_VAR_A; + ObjCount = 0; + LastChoice = 0; + LastHouse = HOUSE_GOOD; + GrabbedObject = 0; + for (int i=0; i < NUM_EDIT_CLASSES; i++) { + NumType[i] = 0; + TypeOffset[i] = 0; + } + Waypoint[WAYPT_HOME] = 0; + CurrentCell = 0; + CurTrigger = NULL; + Changed = 0; + LMouseDown = 0; + BaseBuilding = 0; + BasePercent = 100; +} + + +/*************************************************************************** + * MapEditClass::One_Time -- one-time initialization * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/02/1995 BR : Created. * + *=========================================================================*/ +void MapEditClass::One_Time(void) +{ + MouseClass::One_Time(); + + /*------------------------------------------------------------------------ + Create the pop-up controls + ------------------------------------------------------------------------*/ + /*........................................................................ + The map: a single large "button" + ........................................................................*/ + //MapArea = new ControlClass(MAP_AREA,0,8,312,192, GadgetClass::LEFTPRESS | + //GadgetClass::LEFTRELEASE, false); + MapArea = new ControlClass(MAP_AREA,0,16,624,384, GadgetClass::LEFTPRESS | + GadgetClass::LEFTRELEASE, false); + + /*........................................................................ + House buttons + ........................................................................*/ + GDIButton = new TextButtonClass (POPUP_GDI, "GDI", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + POPUP_GDI_X, POPUP_GDI_Y, POPUP_GDI_W, POPUP_GDI_H); + + NODButton = new TextButtonClass (POPUP_NOD, "NOD", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + POPUP_NOD_X, POPUP_NOD_Y, POPUP_NOD_W, POPUP_NOD_H); + + NeutralButton = new TextButtonClass (POPUP_NEUTRAL, "Neutral", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + POPUP_NEUTRAL_X, POPUP_NEUTRAL_Y, POPUP_NEUTRAL_W, POPUP_NEUTRAL_H); + + Multi1Button = new TextButtonClass (POPUP_MULTI1, "M1", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + POPUP_MULTI1_X, POPUP_MULTI1_Y, POPUP_MULTI1_W, POPUP_MULTI1_H); + + Multi2Button = new TextButtonClass (POPUP_MULTI2, "M2", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + POPUP_MULTI2_X, POPUP_MULTI2_Y, POPUP_MULTI2_W, POPUP_MULTI2_H); + + Multi3Button = new TextButtonClass (POPUP_MULTI3, "M3", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + POPUP_MULTI3_X, POPUP_MULTI3_Y, POPUP_MULTI3_W, POPUP_MULTI3_H); + + Multi4Button = new TextButtonClass (POPUP_MULTI4, "M4", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + POPUP_MULTI4_X, POPUP_MULTI4_Y, POPUP_MULTI4_W, POPUP_MULTI4_H); + + /*........................................................................ + The mission list box + ........................................................................*/ + MissionList = new ListClass (POPUP_MISSIONLIST, + POPUP_MISSION_X, POPUP_MISSION_Y, POPUP_MISSION_W, POPUP_MISSION_H, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + Hires_Retrieve("BTN-UP.SHP"), + Hires_Retrieve("BTN-DN.SHP")); + + for (int i = 0; i < NUM_EDIT_MISSIONS; i++) { + MissionList->Add_Item (MissionClass::Mission_Name(MapEditMissions[i])); + } + + /*........................................................................ + The health bar + ........................................................................*/ + HealthGauge = new TriColorGaugeClass (POPUP_HEALTHGAUGE, + POPUP_HEALTH_X, POPUP_HEALTH_Y, POPUP_HEALTH_W, POPUP_HEALTH_H); + HealthGauge->Use_Thumb(true); + HealthGauge->Set_Maximum(0x100); + HealthGauge->Set_Red_Limit(0x3f - 1); + HealthGauge->Set_Yellow_Limit(0x7f - 1); + + /*........................................................................ + The health text label + ........................................................................*/ + HealthBuf[0] = 0; + HealthText = new TextLabelClass (HealthBuf, + POPUP_HEALTH_X + POPUP_HEALTH_W / 2, + POPUP_HEALTH_Y + POPUP_HEALTH_H + 1, + CC_GREEN, TPF_CENTER | TPF_FULLSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL); + + /*........................................................................ + The facing dial + ........................................................................*/ + FacingDial = new Dial8Class (POPUP_FACINGDIAL, POPUP_FACEBOX_X, + POPUP_FACEBOX_Y, POPUP_FACEBOX_W, POPUP_FACEBOX_H, (DirType)0); + + /*........................................................................ + The base percent-built slider & its label + ........................................................................*/ + BaseGauge = new GaugeClass (POPUP_BASEPERCENT, + POPUP_BASE_X, POPUP_BASE_Y, POPUP_BASE_W, POPUP_BASE_H); + BaseLabel = new TextLabelClass ("Base:", POPUP_BASE_X - 3, POPUP_BASE_Y, + CC_GREEN, TPF_RIGHT | TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL); + BaseGauge->Set_Maximum(100); + BaseGauge->Set_Value(BasePercent); +} + + +/*********************************************************************************************** + * MapeditClass::Init_IO -- Reinitializes the radar map at scenario start. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/22/1994 JLB : Created. * + *=============================================================================================*/ +void MapEditClass::Init_IO(void) +{ + /*------------------------------------------------------------------------ + For normal game mode, jump to the parent's Init routine. + ------------------------------------------------------------------------*/ + if (!Debug_Map) { + + MouseClass::Init_IO(); + + } else { + + /*------------------------------------------------------------------------ + For editor mode, add the map area to the button input list + ------------------------------------------------------------------------*/ + Buttons = 0; + Add_A_Button(*BaseGauge); + Add_A_Button(*BaseLabel); + Add_A_Button(*MapArea); + } +} + + +/*************************************************************************** + * MapEditClass::Read_INI -- overloaded Read_INI function * + * * + * Overloading this function gives the map editor a chance to initialize * + * certain values every time a new INI is read. * + * * + * INPUT: * + * buffer INI staging area * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/16/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Read_INI(char *buffer) +{ + /* + ------------------------ Invoke parent's Read_INI ------------------------ + */ + MouseClass::Read_INI(buffer); + + BasePercent = WWGetPrivateProfileInt("Basic","Percent",0,buffer); + BaseGauge->Set_Value(BasePercent); +} + + +/*************************************************************************** + * MapEditClass::Write_INI -- overloaded Read_INI function * + * * + * INPUT: * + * buffer INI staging area * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/16/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Write_INI(char *buffer) +{ + /* + ----------------------- Invoke parent's Write_INI ------------------------ + */ + MouseClass::Write_INI(buffer); + + /* + ** Save the base's percent-built value; this must be saved into the BASIC + ** section of the INI, since the Base section will be entirely erased + ** by the Base's Write_INI routine. + */ + WWWritePrivateProfileInt("Basic", "Percent", BasePercent, buffer); +} + + +/*************************************************************************** + * MapEditClass::Clear_List -- clears the internal choosable object list * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 10/20/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Clear_List(void) +{ + /*------------------------------------------------------------------------ + Set # object type ptrs to 0, set NumType for each type to 0 + ------------------------------------------------------------------------*/ + ObjCount = 0; + for (int i = 0; i < NUM_EDIT_CLASSES; i++) { + NumType[i] = 0; + } +} + + +/*************************************************************************** + * MapEditClass::Add_To_List -- adds a TypeClass to the choosable list * + * * + * Use this routine to add an object to the game object selection list. * + * This list is used by the Add_Object function. All items located in the * + * list will appear and be chooseable by that function. Make sure to * + * clear the list before adding a sequence of items to it. Clearing * + * the list is accomplished by the Clear_List() function. * + * * + * INPUT: * + * object ptr to ObjectTypeClass to add * + * * + * OUTPUT: * + * bool: was the object added to the list? A failure could occur if * + * NULL were passed in or the list is full. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/04/1994 JLB : Created. * + *=========================================================================*/ +bool MapEditClass::Add_To_List(ObjectTypeClass const *object) +{ + /* + ** Add the object if there's room. + */ + if (object && ObjCount < MAX_EDIT_OBJECTS) { + Objects[ObjCount++] = object; + + /* + ** Update type counters. + */ + switch (object->What_Am_I()) { + case RTTI_TEMPLATETYPE: + NumType[0]++; + break; + + case RTTI_OVERLAYTYPE: + NumType[1]++; + break; + + case RTTI_SMUDGETYPE: + NumType[2]++; + break; + + case RTTI_TERRAINTYPE: + NumType[3]++; + break; + + case RTTI_UNITTYPE: + NumType[4]++; + break; + + case RTTI_INFANTRYTYPE: + NumType[5]++; + break; + + case RTTI_AIRCRAFTTYPE: + NumType[6]++; + break; + + case RTTI_BUILDINGTYPE: + NumType[7]++; + break; + } + return(true); + } + + return(false); +} + + +/*************************************************************************** + * MapEditClass::AI -- The map editor's main logic * + * * + * This routine overloads the parent's (DisplayClass) AI function. * + * It checks for any input specific to map editing, and calls the parent * + * AI routine to handle scrolling and other mainstream map stuff. * + * * + * If this detects one of its special input keys, it sets 'input' to 0 * + * before calling the parent AI routine; this prevents input conflict. * + * * + * SUPPORTED INPUT: * + * General: * + * F2/RMOUSE: main menu * + * F6: toggles show-passable mode * + * HOME: go to the Home Cell (scenario's start position)* + * SHIFT-HOME: set the Home Cell to the current TacticalCell* + * ESC: exits to DOS * + * Object Placement: * + * INSERT: go into placement mode * + * ESC: exit placement mode * + * LEFT/RIGHT: prev/next placement object * + * PGUP/PGDN: prev/next placement category * + * HOME: 1st placement object (clear template) * + * h/H: toggle house of placement object * + * LMOUSE: place the placement object * + * MOUSE MOTION: "paint" with the placement object * + * Object selection: * + * LMOUSE: select & "grab" current object * + * If no object is present where the mouse is * + * clicked, the current object is de-selected * + * If the same object is clicked on, it stays * + * selected. Also displays the object-editing * + * gadgets. * + * LMOUSE RLSE: release currently-grabbed object * + * MOUSE MOTION: if an object is grabbed, moves the object * + * SHIFT|ALT|ARROW: moves object in that direction * + * DELETE deletes currently-selected object * + * Object-editing controls: * + * POPUP_GDI: makes GDI the owner of this object * + * POPUP_NOD: makes NOD the owner of this object * + * POPUP_MISSIONLIST: sets that mission for this object * + * POPUP_HEALTHGAUGE: sets that health value for this object * + * POPUP_FACINGDIAL: sets the object's facing * + * * + * Changed is set when you: * + * - place an object * + * - move a grabbed object * + * - delete an object * + * - size the map * + * - create a new scenario * + * Changed is cleared when you: * + * - Save the scenario * + * - Load a scenario * + * - Play the scenario * + * * + * INPUT: * + * input KN_ value, 0 if none * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 10/20/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::AI(KeyNumType &input, int x, int y) +{ + int rc; + MissionType mission; + int strength; + CELL cell; + int i; + int found; // for removing a waypoint label + int waypt_idx; // for labelling a waypoint + BaseNodeClass *node; // for removing from an AI Base + HousesType house; + + /*------------------------------------------------------------------------ + Trap 'F2' regardless of whether we're in game or editor mode + ------------------------------------------------------------------------*/ + if (Debug_Flag) { + if (/*(input == KN_F2 && Session == GAME_SOLO) ||*/ input == (KN_F2 | KN_CTRL_BIT)) { + ScenarioInit = 0; + + /* + ** If we're in editor mode & Changed is set, prompt for saving changes + */ + if (Debug_Map && Changed) { + rc = CCMessageBox().Process("Save Changes?", TXT_YES, TXT_NO); + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + /* + ........................ User wants to save ........................ + */ + if (rc == 0) { + + /* + ................ If save cancelled, abort game .................. + */ + if (Save_Scenario()!=0) { + input = KN_NONE; + } else { + Changed = 0; + Go_Editor(!Debug_Map); + } + } else { + + /* + .................... User doesn't want to save ..................... + */ + Go_Editor(!Debug_Map); + } + } else { + /* + ** If we're in game mode, set Changed to 0 (so if we didn't save our + ** changes above, they won't keep coming back to haunt us with continual + ** Save Changes? prompts!) + */ + if (!Debug_Map) { + Changed = 0; + } + Go_Editor(!Debug_Map); + } + } + } + + /*------------------------------------------------------------------------ + For normal game mode, jump to the parent's AI routine. + ------------------------------------------------------------------------*/ + if (!Debug_Map) { + MouseClass::AI(input, x, y); + return; + } + + ::Frame++; + + /*------------------------------------------------------------------------ + Do special mouse processing if the mouse is over the map + ------------------------------------------------------------------------*/ + if (Get_Mouse_X() > TacPixelX && Get_Mouse_X() < + TacPixelX + Lepton_To_Pixel(TacLeptonWidth) && + Get_Mouse_Y() > TacPixelY && Get_Mouse_Y() < + TacPixelY + Lepton_To_Pixel(TacLeptonHeight)) { + /*..................................................................... + When the mouse moves over a scrolling edge, ScrollClass changes its + shape to the appropriate arrow or NO symbol; it's our job to change it + back to normal (or whatever the shape is set to by Set_Default_Mouse()) + when it re-enters the map area. + .....................................................................*/ + if (CurTrigger) { + Override_Mouse_Shape(MOUSE_CAN_MOVE); + } else { + Override_Mouse_Shape(MOUSE_NORMAL); + } + } + + /*..................................................................... + Set 'ZoneCell' to track the mouse cursor around over the map. Do this + even if the map is scrolling. + .....................................................................*/ + if (Get_Mouse_X() >= TacPixelX && Get_Mouse_X() <= + TacPixelX + Lepton_To_Pixel(TacLeptonWidth) && + Get_Mouse_Y() >= TacPixelY && Get_Mouse_Y() <= + TacPixelY + Lepton_To_Pixel(TacLeptonHeight)) { + + cell = Click_Cell_Calc(Get_Mouse_X(), Get_Mouse_Y()); + if (cell != -1) { + Set_Cursor_Pos(cell); + if (PendingObject) { + Flag_To_Redraw(true); + } + } + } + + /*------------------------------------------------------------------------ + Check for mouse motion while left button is down. + ------------------------------------------------------------------------*/ + rc = Mouse_Moved(); + if (LMouseDown && rc) { + /*..................................................................... + "Paint" mode: place current object, and restart placement + .....................................................................*/ + if (PendingObject) { + Flag_To_Redraw(true); + if (Place_Object() == 0) { + Changed = 1; + Start_Placement(); + } + } else { + /*..................................................................... + Move the currently-grabbed object + .....................................................................*/ + if (GrabbedObject) { + GrabbedObject->Mark(MARK_CHANGE); + if (Move_Grabbed_Object() == 0) { + Changed = 1; + } + } + } + } + + /*------------------------------------------------------------------------ + Trap special editing keys; if one is detected, set 'input' to 0 to + prevent a conflict with parent's AI(). + ------------------------------------------------------------------------*/ + switch (input) { + /*--------------------------------------------------------------------- + F2/RMOUSE = pop up main menu + ---------------------------------------------------------------------*/ + case KN_RMOUSE: + /* + ..................... Turn off placement mode ...................... + */ + if (PendingObject) { + if (BaseBuilding) { + Cancel_Base_Building(); + } else { + Cancel_Placement(); + } + } + + /* + ................. Turn off trigger placement mode .................. + */ + if (CurTrigger) { + Stop_Trigger_Placement(); + } + + /* + .............. Unselect object & hide popup controls ............... + */ + if (CurrentObject.Count()) { + CurrentObject[0]->Unselect(); + Popup_Controls(); + } + Main_Menu(); + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + F6 = toggle passable/impassable display + ---------------------------------------------------------------------*/ + case KN_F6: + Debug_Passable = (Debug_Passable == false); + HiddenPage.Clear(); + Flag_To_Redraw(true); + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + INSERT = go into object-placement mode + ---------------------------------------------------------------------*/ + case KN_INSERT: + if (!PendingObject) { + /* + ......... Unselect current object, hide popup controls .......... + */ + if (CurrentObject.Count()) { + CurrentObject[0]->Unselect(); + Popup_Controls(); + } + /* + .................... Go into placement mode ..................... + */ + Start_Placement(); + } + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + ESC = exit placement mode, or exit to DOS + ---------------------------------------------------------------------*/ + case KN_ESC: + + /* + .................... Exit object placement mode .................... + */ + if (PendingObject) { + if (BaseBuilding) { + Cancel_Base_Building(); + } else { + Cancel_Placement(); + } + input = KN_NONE; + break; + } else { + + /* + ................... Exit trigger placement mode .................... + */ + if (CurTrigger) { + Stop_Trigger_Placement(); + input = KN_NONE; + break; + } else { + rc = CCMessageBox().Process("Exit Scenario Editor?", TXT_YES, TXT_NO); + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + + /* + .......... User doesn't want to exit; return to editor .......... + */ + if (rc==1) { + input = KN_NONE; + break; + } + + /* + ................. If changed, prompt for saving ................. + */ + if (Changed) { + rc = CCMessageBox().Process("Save Changes?", TXT_YES, TXT_NO); + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + + /* + ..................... User wants to save ..................... + */ + if (rc == 0) { + + /* + .............. If save cancelled, abort exit .............. + */ + if (Save_Scenario()!=0) { + input = KN_NONE; + break; + } else { + Changed = 0; + } + } + } + } + } + Prog_End(); + exit (0); + break; + + /*--------------------------------------------------------------------- + LEFT = go to previous placement object + ---------------------------------------------------------------------*/ + case KN_LEFT: + if (PendingObject) { + Place_Prev(); + } + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + RIGHT = go to next placement object + ---------------------------------------------------------------------*/ + case KN_RIGHT: + if (PendingObject) { + Place_Next(); + } + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + PGUP = go to previous placement category + ---------------------------------------------------------------------*/ + case KN_PGUP: + if (PendingObject) { + Place_Prev_Category(); + } + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + PGDN = go to next placement category + ---------------------------------------------------------------------*/ + case KN_PGDN: + if (PendingObject) { + Place_Next_Category(); + } + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + HOME = jump to first placement object, or go to Home Cell + ---------------------------------------------------------------------*/ + case KN_HOME: + if (PendingObject) { + Place_Home(); + } else { + + /* + ....................... Set map position ........................ + */ + ScenarioInit++; + Set_Tactical_Position(Waypoint[WAYPT_HOME]); + ScenarioInit--; + + /* + ...................... Force map to redraw ...................... + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + } + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + SHIFT-HOME: set new Home Cell position + ---------------------------------------------------------------------*/ + case ((int)KN_HOME | (int)KN_SHIFT_BIT): + /* + ** Unflag the old Home Cell, if there are no other waypoints + ** pointing to it + */ + cell = Waypoint[WAYPT_HOME]; + + if (cell != -1) { + found = 0; + for (i = 0; i < WAYPT_COUNT; i++) { + if (i != WAYPT_HOME && Waypoint[i]==cell) { + found = 1; + } + } + + if (found==0) { + (*this)[cell].IsWaypoint = 0; + Flag_Cell(cell); + } + + } + + /* + ** Now set the new Home cell + */ + Waypoint[WAYPT_HOME] = Coord_Cell(TacticalCoord); + (*this)[Coord_Cell(TacticalCoord)].IsWaypoint = 1; + Flag_Cell(Coord_Cell(TacticalCoord)); + Changed = 1; + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + SHIFT-R: set new Reinforcement Cell position. Don't allow setting + the Reinf. Cell to the same as the Home Cell (for display purposes.) + ---------------------------------------------------------------------*/ + case ((int)KN_R | (int)KN_SHIFT_BIT): + if (CurrentCell==0 || CurrentCell==Waypoint[WAYPT_HOME]) { + break; + } + + /* + ** Unflag the old Reinforcement Cell, if there are no other waypoints + ** pointing to it + */ + cell = Waypoint[WAYPT_REINF]; + + if (cell != -1) { + found = 0; + for (i = 0; i < WAYPT_COUNT; i++) { + if (i != WAYPT_REINF && Waypoint[i]==cell) { + found = 1; + } + } + + if (found==0) { + (*this)[cell].IsWaypoint = 0; + Flag_Cell(cell); + } + + } + /* + ** Now set the new Reinforcement cell + */ + Waypoint[WAYPT_REINF] = CurrentCell; + (*this)[CurrentCell].IsWaypoint = 1; + Flag_Cell(CurrentCell); + Changed = 1; + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + ALT-Letter: Label a waypoint cell + ---------------------------------------------------------------------*/ + case ((int)KN_A | (int)KN_ALT_BIT): + case ((int)KN_B | (int)KN_ALT_BIT): + case ((int)KN_C | (int)KN_ALT_BIT): + case ((int)KN_D | (int)KN_ALT_BIT): + case ((int)KN_E | (int)KN_ALT_BIT): + case ((int)KN_F | (int)KN_ALT_BIT): + case ((int)KN_G | (int)KN_ALT_BIT): + case ((int)KN_H | (int)KN_ALT_BIT): + case ((int)KN_I | (int)KN_ALT_BIT): + case ((int)KN_J | (int)KN_ALT_BIT): + case ((int)KN_K | (int)KN_ALT_BIT): + case ((int)KN_L | (int)KN_ALT_BIT): + case ((int)KN_M | (int)KN_ALT_BIT): + case ((int)KN_N | (int)KN_ALT_BIT): + case ((int)KN_O | (int)KN_ALT_BIT): + case ((int)KN_P | (int)KN_ALT_BIT): + case ((int)KN_Q | (int)KN_ALT_BIT): + case ((int)KN_R | (int)KN_ALT_BIT): + case ((int)KN_S | (int)KN_ALT_BIT): + case ((int)KN_T | (int)KN_ALT_BIT): + case ((int)KN_U | (int)KN_ALT_BIT): + case ((int)KN_V | (int)KN_ALT_BIT): + case ((int)KN_W | (int)KN_ALT_BIT): + case ((int)KN_X | (int)KN_ALT_BIT): + case ((int)KN_Y | (int)KN_ALT_BIT): + case ((int)KN_Z | (int)KN_ALT_BIT): + if (CurrentCell != 0) { + waypt_idx = KN_To_KA(input & 0xff) - KA_a; + /*............................................................... + Unflag cell for this waypoint if there is one + ...............................................................*/ + cell = Waypoint[waypt_idx]; + if (cell != -1) { + if (Waypoint[WAYPT_HOME] != cell && + Waypoint[WAYPT_REINF] != cell) + (*this)[cell].IsWaypoint = 0; + Flag_Cell(cell); + } + Waypoint[waypt_idx] = CurrentCell; + (*this)[CurrentCell].IsWaypoint = 1; + Changed = 1; + Flag_Cell(CurrentCell); + } + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + ALT-1-4: Designate a cell as a capture-the-flag cell. + ---------------------------------------------------------------------*/ + case ((int)KN_1 | (int)KN_ALT_BIT): + case ((int)KN_2 | (int)KN_ALT_BIT): + case ((int)KN_3 | (int)KN_ALT_BIT): + case ((int)KN_4 | (int)KN_ALT_BIT): + /*------------------------------------------------------------------ + If there's a current cell, place the flag & waypoint there. + ------------------------------------------------------------------*/ + if (CurrentCell != 0) { + waypt_idx = (KN_To_KA(input & 0xff) - KA_1); + house = (HousesType)(HOUSE_MULTI1 + waypt_idx); + if (HouseClass::As_Pointer(house)) { + HouseClass::As_Pointer(house)->Flag_Attach(CurrentCell,true); + } + } else { + /*------------------------------------------------------------------ + If there's a current object, attach the flag to it and clear the + waypoint. + ------------------------------------------------------------------*/ + if (CurrentObject[0] != 0) { + waypt_idx = (KN_To_KA(input & 0xff) - KA_1); + house = (HousesType)(HOUSE_MULTI1 + waypt_idx); + if (HouseClass::As_Pointer(house) && CurrentObject[0]->What_Am_I() == RTTI_UNIT) { + HouseClass::As_Pointer(house)->Flag_Attach((UnitClass *)CurrentObject[0], true); + } + } + } + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + ALT-Space: Remove a waypoint designation + ---------------------------------------------------------------------*/ + case ((int)KN_SPACE | (int)KN_ALT_BIT): + if (CurrentCell != 0) { + /*............................................................... + Loop through letter waypoints; if this cell is one of them, + clear that waypoint. + ...............................................................*/ + for (i = 0 ; i < 26; i++) { + if (Waypoint[i]==CurrentCell) + Waypoint[i] = -1; + } + + /*............................................................... + Loop through flag home values; if this cell is one of them, clear + that waypoint. + ...............................................................*/ + for (i = 0; i < MAX_PLAYERS; i++) { + house = (HousesType)(HOUSE_MULTI1 + i); + if (HouseClass::As_Pointer(house) && + CurrentCell == HouseClass::As_Pointer(house)->FlagHome) + HouseClass::As_Pointer(house)->Flag_Remove(As_Target(CurrentCell),true); + } + + /*............................................................... + If there are no more waypoints on this cell, clear the cell's + waypoint designation. + ...............................................................*/ + if (Waypoint[WAYPT_HOME]!=CurrentCell && + Waypoint[WAYPT_REINF]!=CurrentCell) + (*this)[CurrentCell].IsWaypoint = 0; + Changed = 1; + Flag_Cell(CurrentCell); + } + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + 'H' = toggle current placement object's house + ---------------------------------------------------------------------*/ + case KN_H: + case ((int)KN_H | (int)KN_SHIFT_BIT): + if (PendingObject) { + Toggle_House(); + } + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + Left-mouse click: + Button DOWN: + - Toggle LMouseDown + - If we're in placement mode, try to place the current object + - If success, re-enter placement mode + - Otherwise, try to select an object, and "grab" it if there is one + - If no object, then select that cell as the "current" cell + Button UP: + - Toggle LMouseDown + - release any grabbed object + ---------------------------------------------------------------------*/ + case ((int)MAP_AREA | (int)KN_BUTTON): + /* + ------------------------- Left Button DOWN ------------------------- + */ + if (Keyboard::Down(KN_LMOUSE)) { + LMouseDown = 1; + /* + ............... Placement mode: place an object ................. + */ + if (PendingObject) { + if (Place_Object()==0) { + Changed = 1; + Start_Placement(); + } + } else { + /* + ....................... Place a trigger ......................... + */ + if (CurTrigger) { + Place_Trigger(); + Changed = 1; + } else { + /* + ................. Select an object or a cell ................. + .................. Check for double-click .................... + */ + if (CurrentObject.Count() && + ( (TickCount.Time() - LastClickTime) < 15)) { + ; // stub + + } else { + /* + ................ Single-click: select object ................. + */ + if (Select_Object()==0) { + CurrentCell = 0; + Grab_Object(); + } else { + /* + ................ No object: select the cell .................. + */ + CurrentCell = Click_Cell_Calc(_Kbd->MouseQX,_Kbd->MouseQY); + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + } + } + } + } + LastClickTime = TickCount.Time(); + input = KN_NONE; + } else { + + /* + -------------------------- Left Button UP -------------------------- + */ + LMouseDown = 0; + GrabbedObject = 0; + input = KN_NONE; + } + break; + + /*--------------------------------------------------------------------- + SHIFT-ALT-Arrow: move the current object + ---------------------------------------------------------------------*/ + case (int)KN_UP | (int)KN_ALT_BIT | (int)KN_SHIFT_BIT: + case (int)KN_DOWN | (int)KN_ALT_BIT | (int)KN_SHIFT_BIT: + case (int)KN_LEFT | (int)KN_ALT_BIT | (int)KN_SHIFT_BIT: + case (int)KN_RIGHT | (int)KN_ALT_BIT | (int)KN_SHIFT_BIT: + if (CurrentObject.Count()) { + CurrentObject[0]->Move(KN_To_Facing(input)); + Changed = 1; + } + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + DELETE: delete currently-selected object + ---------------------------------------------------------------------*/ + case KN_DELETE: + /*.................................................................. + Delete currently-selected object's trigger, or the object + ..................................................................*/ + if (CurrentObject.Count()) { + + /* + ........................ Delete trigger ......................... + */ + if (CurrentObject[0]->Trigger) { + CurrentObject[0]->Trigger = NULL; + } else { + /* + ** If the current object is part of the AI's Base, remove it + ** from the Base's Node list. + */ + if (CurrentObject[0]->What_Am_I()==RTTI_BUILDING && + Base.Is_Node((BuildingClass *)CurrentObject[0])) { + node = Base.Get_Node((BuildingClass *)CurrentObject[0]); + Base.Nodes.Delete(*node); + } + + /* + ................... Delete current object .................... + */ + delete CurrentObject[0]; + + /* + .................. Hide the popup controls ................... + */ + Popup_Controls(); + } + + /* + ........................ Force a redraw ......................... + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Changed = 1; + } else { + /* + ................. Remove trigger from current cell ................. + */ + if (CurrentCell) { + if ((*this)[CurrentCell].IsTrigger) { + (*this)[CurrentCell].IsTrigger = 0; + CellTriggers[CurrentCell] = NULL; + /* + ...................... Force a redraw ........................ + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Changed = 1; + } + } + } + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + TAB: select next object on the map + ---------------------------------------------------------------------*/ + case KN_TAB: + Select_Next(); + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + Object-Editing button: House Button + ---------------------------------------------------------------------*/ + case (POPUP_GDI | KN_BUTTON): + case (POPUP_NOD | KN_BUTTON): + case (POPUP_NEUTRAL | KN_BUTTON): + case (POPUP_MULTI1 | KN_BUTTON): + case (POPUP_MULTI2 | KN_BUTTON): + case (POPUP_MULTI3 | KN_BUTTON): + case (POPUP_MULTI4 | KN_BUTTON): + /*.................................................................. + Convert input value into a house value; assume HOUSE_GOOD is 0 + ..................................................................*/ + house = (HousesType)( (input & (~KN_BUTTON)) - POPUP_GDI); + /*.................................................................. + If that house doesn't own this object, try to transfer it + ..................................................................*/ + if (CurrentObject[0]->Owner()!=house) { + if (Change_House(house)) { + Changed = 1; + } + } + Set_House_Buttons(CurrentObject[0]->Owner(), Buttons, POPUP_GDI); + HiddenPage.Clear(); + Flag_To_Redraw(true); + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + Object-Editing button: Mission + ---------------------------------------------------------------------*/ + case (POPUP_MISSIONLIST | KN_BUTTON): + if (CurrentObject[0]->Is_Techno()) { + /* + ........................ Set new mission ........................ + */ + mission = MapEditMissions[MissionList->Current_Index()]; + if (CurrentObject[0]->Get_Mission() != mission) { + ((TechnoClass *)CurrentObject[0])->Set_Mission(mission); + Changed = 1; + } + } + Flag_To_Redraw(true); + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + Object-Editing button: Health + ---------------------------------------------------------------------*/ + case (POPUP_HEALTHGAUGE | KN_BUTTON): + if (CurrentObject[0]->Is_Techno()) { + /* + .......... Derive strength from current gauge reading ........... + */ + strength = Fixed_To_Cardinal( + (unsigned)CurrentObject[0]->Class_Of().MaxStrength, + (unsigned)HealthGauge->Get_Value()); + + /* + ........................... Clip to 1 ........................... + */ + if (strength <= 0) { + strength = 1; + } + + /* + ....................... Set new strength ........................ + */ + if (strength != CurrentObject[0]->Strength) { + CurrentObject[0]->Strength = strength; + HiddenPage.Clear(); + Flag_To_Redraw(true); + Changed = 1; + } + + /* + ....................... Update text label ....................... + */ + sprintf(HealthBuf,"%d",strength); + } + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + Object-Editing button: Facing + ---------------------------------------------------------------------*/ + case (POPUP_FACINGDIAL | KN_BUTTON): + if (CurrentObject[0]->Is_Techno()) { + /* + ........................ Set new facing ......................... + */ + if (FacingDial->Get_Direction() != + ((TechnoClass *)CurrentObject[0])->PrimaryFacing.Get()) { + /* + ..................... Set body's facing ...................... + */ + ((TechnoClass *)CurrentObject[0])->PrimaryFacing.Set(FacingDial->Get_Direction()); + + /* + ............. Set turret facing, if there is one ............. + */ + if (CurrentObject[0]->What_Am_I()==RTTI_UNIT) { + ((UnitClass *)CurrentObject[0])->SecondaryFacing.Set(FacingDial->Get_Direction()); + } + + HiddenPage.Clear(); + Flag_To_Redraw(true); + Changed = 1; + } + } + input = KN_NONE; + break; + + /*--------------------------------------------------------------------- + Object-Editing button: Facing + ---------------------------------------------------------------------*/ + case (POPUP_BASEPERCENT | KN_BUTTON): + if (BaseGauge->Get_Value() != BasePercent) { + BasePercent = BaseGauge->Get_Value(); + Build_Base_To(BasePercent); + HiddenPage.Clear(); + Flag_To_Redraw(true); + } + input = KN_NONE; + break; + + case (KN_LMOUSE): + input = KN_NONE; + break; + + default: + break; + } + + /* + ------------------------ Call parent's AI routine ------------------------ + */ + MouseClass::AI(input, x, y); +} + + +/*************************************************************************** + * MapEditClass::Draw_It -- overloaded Redraw routine * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 11/17/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Draw_It(bool forced) +{ + char const *label; + char buf[40]; + char const *tptr; + + MouseClass::Draw_It(forced); + + if (!Debug_Map) { + return; + } + + // + // Erase scrags at top of screen + // + LogicPage->Fill_Rect(0, 0, 640, 16, BLACK); + + /* + ** Display the total value of all Tiberium on the map. + */ + Fancy_Text_Print("Tiberium=%ld ", 0, 0, CC_GREEN, BLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, TotalValue); + + /*------------------------------------------------------------------------ + If there are no object controls displayed, just invoke parent's Redraw + and return. + ------------------------------------------------------------------------*/ + if (!Buttons) { + return; + } + + /*------------------------------------------------------------------------ + Otherwise, if 'display' is set, invoke the parent's Redraw to refresh + the HIDPAGE; then, update the buttons & text labels onto HIDPAGE; + then invoke the parent's Redraw to blit the HIDPAGE to SEENPAGE. + ------------------------------------------------------------------------*/ + if (forced) { + + /* + ....................... Update the text labels ........................ + */ + if (CurrentObject.Count()) { + /* + ------------------ Display the object's name & ID ------------------ + */ + label = Text_String(CurrentObject[0]->Full_Name()); + tptr = label; + sprintf(buf,"%s (%d)",tptr,CurrentObject[0]->As_Target()); + + /* + ......................... print the label .......................... + */ + Fancy_Text_Print (buf, 320, 0, CC_TAN, TBLACK, + TPF_CENTER | TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL); + } + } +} + + +/*************************************************************************** + * MapEditClass::Mouse_Moved -- checks for mouse motion * + * * + * Reports whether the mouse has moved or not. This varies based on the * + * type of object currently selected. If there's an infantry object * + * selected, mouse motion counts even within a cell; for all other types,* + * mouse motion counts only if the mouse changes cells. * + * * + * The reason this routine is needed is to prevent Paint-Mode from putting* + * gobs of trees and such into the same cell if the mouse moves just * + * a little bit. * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 11/08/1994 BR : Created. * + *=========================================================================*/ +bool MapEditClass::Mouse_Moved(void) +{ + static int old_mx = 0; + static int old_my = 0; + static CELL old_zonecell = 0; + const ObjectTypeClass * objtype = NULL; + bool retcode = false; + + /* + -------------------------- Return if no motion --------------------------- + */ + if (old_mx == Get_Mouse_X() && old_my == Get_Mouse_Y()) { + return(false); + } + + /* + ---------------------- Get a ptr to ObjectTypeClass ---------------------- + */ + if (PendingObject) { + objtype = PendingObject; + } else { + if (GrabbedObject) { + objtype = &GrabbedObject->Class_Of(); + } else { + old_mx = Get_Mouse_X(); + old_my = Get_Mouse_Y(); + old_zonecell = ZoneCell; + return(false); + } + } + + /* + --------------------- Check for motion based on type --------------------- + */ + /* + ............... Infantry: mouse moved if any motion at all ............... + */ + if (objtype->What_Am_I() == RTTI_INFANTRYTYPE) { + retcode = true; + } else { + /* + ................ Others: mouse moved only if cell changed ................ + */ + if (old_zonecell!=ZoneCell) { + retcode = true; + } else { + retcode = false; + } + } + + old_mx = Get_Mouse_X(); + old_my = Get_Mouse_Y(); + old_zonecell = ZoneCell; + return(retcode); +} + + +/*************************************************************************** + * MapEditClass::Main_Menu -- main menu processor for map editor * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 10/20/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Main_Menu(void) +{ + char const *_menus[MAX_MAIN_MENU_NUM + 1]; + int selection; // option the user picks + bool process; // menu stays up while true + int rc; + + /* + --------------------------- Fill in menu items --------------------------- + */ + _menus[0] = "New Scenario"; + _menus[1] = "Load Scenario"; + _menus[2] = "Save Scenario"; + _menus[3] = "Size Map"; + _menus[4] = "Add Game Object"; + _menus[5] = "Scenario Options"; + _menus[6] = "AI Options"; + _menus[7] = "Play Scenario"; + _menus[8] = NULL; + + /* + ----------------------------- Main Menu loop ----------------------------- + */ + Override_Mouse_Shape(MOUSE_NORMAL); // display default mouse cursor + process = true; + while (process) { + + /* + ................ Invoke game callback, to update music ................ + */ + Call_Back(); + + /* + ............................. Invoke menu ............................. + */ + Hide_Mouse(); // Do_Menu assumes the mouse is already hidden + selection = Do_Menu(&_menus[0], true); + Show_Mouse(); + if (UnknownKey==KN_ESC || UnknownKey==KN_LMOUSE || UnknownKey==KN_RMOUSE) { + break; + } + + /* + .......................... Process selection .......................... + */ + switch (selection) { + /* + ........................... New scenario ........................... + */ + case 0: + if (Changed) { + rc = CCMessageBox().Process("Save Changes?", TXT_YES, TXT_NO); + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + if (rc==0) { + if (Save_Scenario()!=0) { + break; + } else { + Changed = 0; + } + } + } + if (New_Scenario()==0) { + CarryOverMoney = 0; + Changed = 1; + } + process = false; + break; + + /* + .......................... Load scenario ........................... + */ + case 1: + if (Changed) { + rc = CCMessageBox().Process("Save Changes?", TXT_YES, TXT_NO); + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + if (rc==0) { + if (Save_Scenario()!=0) { + break; + } else { + Changed = 0; + } + } + } + if (Load_Scenario()==0) { + CarryOverMoney = 0; + Changed = 0; + } + process = false; + break; + + /* + .......................... Save scenario ........................... + */ + case 2: + if (Save_Scenario() == 0) { + Changed = 0; + } + process = false; + break; + + /* + .......................... Edit map size ........................... + */ + case 3: + if (Size_Map(MapCellX, MapCellY, MapCellWidth, MapCellHeight)==0) { + process = false; + Changed = 1; + } + break; + + /* + .......................... Add an object ........................... + */ + case 4: + if (Placement_Dialog() == 0) { + Start_Placement(); + process = false; + } + break; + + /* + ......................... Scenario options ......................... + */ + case 5: + if (Scenario_Dialog() == 0) { + Changed = 1; + process = false; + } + break; + + /* + .......................... Other options ........................... + */ + case 6: + AI_Menu(); + process = false; + break; + + /* + ...................... Test-drive this scenario .................... + */ + case 7: + if (Changed) { + rc = CCMessageBox().Process("Save Changes?", TXT_YES, TXT_NO); + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + if (rc==0) { + if (Save_Scenario()!=0) { + break; + } else { + Changed = 0; + } + } + } + Changed = 0; + Debug_Map = false; + Start_Scenario(ScenarioName); + return; + } + } + + /*------------------------------------------------------------------------ + Restore the display: + - Clear HIDPAGE to erase any spurious drawing done by the menu system + - Invoke Flag_To_Redraw to tell DisplayClass to re-render the whole screen + - Invoke Redraw() to update the display + ------------------------------------------------------------------------*/ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); +} + + +/*************************************************************************** + * MapEditClass::AI_Menu -- menu of AI options * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 11/29/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::AI_Menu(void) +{ + int selection; // option the user picks + bool process; // menu stays up while true + char const *_menus[MAX_AI_MENU_NUM + 1]; + + /* + -------------------------- Fill in menu strings -------------------------- + */ + _menus[0] = "Pre-Build a Base"; + _menus[1] = "Import Triggers"; + _menus[2] = "Edit Triggers"; + _menus[3] = "Import Teams"; + _menus[4] = "Edit Teams"; + _menus[5] = NULL; + + /* + ----------------------------- Main Menu loop ----------------------------- + */ + Override_Mouse_Shape(MOUSE_NORMAL); // display default mouse cursor + process = true; + while (process) { + + /* + ................ Invoke game callback, to update music ................ + */ + Call_Back(); + + /* + ............................. Invoke menu ............................. + */ + Hide_Mouse(); // Do_Menu assumes the mouse is already hidden + selection = Do_Menu(&_menus[0], true); + Show_Mouse(); + if (UnknownKey==KN_ESC || UnknownKey==KN_LMOUSE || UnknownKey==KN_RMOUSE) { + break; + } + + /* + .......................... Process selection .......................... + */ + switch (selection) { + /* + ......................... Pre-Build a Base ......................... + */ + case 0: + Start_Base_Building(); + process = false; + break; + + /* + ......................... Import Triggers .......................... + */ + case 1: + if (Import_Triggers()==0) + process = false; + break; + + /* + ......................... Trigger Editing .......................... + */ + case 2: + Handle_Triggers(); + /* + ................ Go into trigger placement mode ................. + */ + if (CurTrigger) { + Start_Trigger_Placement(); + } + process = false; + break; + + /* + ........................... Import Teams ........................... + */ + case 3: + if (Import_Teams()==0) + process = false; + break; + + /* + ........................... Team Editing ........................... + */ + case 4: + Handle_Teams("Teams"); + process = false; + break; + } + } +} + + +/*************************************************************************** + * MapEditClass::Verify_House -- is this objtype ownable by this house? * + * * + * INPUT: * + * house house to check * + * objtype ObjectTypeClass to check * + * * + * OUTPUT: * + * 0 = isn't ownable, 1 = it is * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/16/1994 BR : Created. * + *=========================================================================*/ +bool MapEditClass::Verify_House(HousesType house, ObjectTypeClass const *objtype) +{ + /* + --------------- Verify that new house can own this object ---------------- + */ + return((objtype->Get_Ownable() & (1 << house)) != 0); +} + + +/*************************************************************************** + * MapEditClass::Cycle_House -- finds next valid house for object type * + * * + * INPUT: * + * objtype ObjectTypeClass ptr to get house for * + * curhouse current house value to start with * + * * + * OUTPUT: * + * HousesType that's valid for this object type * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/23/1994 BR : Created. * + *=========================================================================*/ +HousesType MapEditClass::Cycle_House(HousesType curhouse, + ObjectTypeClass const *objtype) +{ + HousesType count; // prevents an infinite loop + + /*------------------------------------------------------------------------ + Loop through all house types, starting with the one after 'curhouse'; + return the first one that's valid + ------------------------------------------------------------------------*/ + count = HOUSE_NONE; + while (1) { + + /* + .......................... Go to next house ........................... + */ + curhouse++; + if (curhouse == HOUSE_COUNT) { + curhouse = HOUSE_FIRST; + } + + /* + ................ Count # iterations; don't go forever ................. + */ + count++; + if (count == HOUSE_COUNT) { + curhouse = HOUSE_NONE; + break; + } + + /* + ................... Break if this is a valid house .................... + */ + if (HouseClass::As_Pointer(curhouse) && Verify_House(curhouse,objtype)) { + break; + } + } + + return(curhouse); +} + + +/*************************************************************************** + * MapEditClass::Fatal -- exits with error message * + * * + * INPUT: * + * code tells which message to display; this minimizes the * + * use of character strings in the code. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/12/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Fatal(int txt) +{ + Prog_End(); + printf("%s\n",txt); + exit(EXIT_FAILURE); +} + + +bool MapEditClass::Scroll_Map(DirType facing, int & distance, bool really) +{ + if (Debug_Map) { + /* + ** The popup gadgets require the entire map to be redrawn if we scroll. + */ + if (really) { + Flag_To_Redraw(true); + } + } + return(MouseClass::Scroll_Map(facing, distance, really)); +} + + +void MapEditClass::Detach(ObjectClass * object) +{ + if (GrabbedObject == object) { + GrabbedObject = 0; + } +} + + +#endif + +#include "mapedsel.cpp" diff --git a/MAPEDIT.H b/MAPEDIT.H new file mode 100644 index 0000000..f69d78c --- /dev/null +++ b/MAPEDIT.H @@ -0,0 +1,355 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\mapedit.h_v 2.19 16 Oct 1995 16:46:36 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MAPEDIT.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 14, 1994 * + * * + * Last Update : May 14, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * This class is derived from the normal display map class. It exists * + * only to allow editing and adding items to the map. * + *---------------------------------------------------------------------------------------------* + * House-setting functions: The editor contains several house maintenance routines: * + * Verify_House: tells if the given ObjectType can be owned by the given HousesType * + * Cycle_House: Finds the next valid house for the given ObjectType; used when a new object * + * can't be owned by the current editor HousesType. * + * Change_House: attempts to change the owner of the currently-selected object * + * Toggle_House: cycles the HousesType of a pending placement object * + * Set_House_Buttons: sets house buttons in accordance with the given HousesType * + *---------------------------------------------------------------------------------------------* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef MAPEDIT_H +#define MAPEDIT_H + +/* +********************************* Includes ********************************** +*/ +#include "function.h" + +/* +********************************** Defines ********************************** +*/ +/*........................................................................... +This is the maximum # of ObjectTypeClasses the editor has to deal with. +...........................................................................*/ +enum MapEdit1Enum { + MAX_EDIT_OBJECTS = // max # of ObjectTypeClasses allowed + (int)TEMPLATE_COUNT + + (int)OVERLAY_COUNT + + (int)SMUDGE_COUNT + + (int)TERRAIN_COUNT + + (int)UNIT_COUNT + + (int)INFANTRY_COUNT + + (int)AIRCRAFT_COUNT + + (int)STRUCT_COUNT, + + MAX_TEAM_CLASSES = // max # ObjectTypeClasses for a team + (int)UNIT_COUNT + + (int)INFANTRY_COUNT + + (int)AIRCRAFT_COUNT, + +// NUM_EDIT_MISSIONS = 6, // # missions that can be assigned an object + + NUM_EDIT_CLASSES = 8, // # different classes (templates, terrain, etc) + + MAX_MAIN_MENU_NUM = 8, + MAX_MAIN_MENU_LEN = 20, + + MAX_AI_MENU_NUM = 6, + MAX_AI_MENU_LEN = 20, + + POPUP_GDI_W = 100, + POPUP_GDI_H = 18, + POPUP_GDI_X = 20, + POPUP_GDI_Y = 320, + + POPUP_NOD_W = 100, + POPUP_NOD_H = 18, + POPUP_NOD_X = 20, + POPUP_NOD_Y = 338, + + POPUP_NEUTRAL_W = 100, + POPUP_NEUTRAL_H = 18, + POPUP_NEUTRAL_X = 20, + POPUP_NEUTRAL_Y = 356, + + POPUP_MULTI1_W = 50, + POPUP_MULTI1_H = 18, + POPUP_MULTI1_X = 20, + POPUP_MULTI1_Y = 320, + + POPUP_MULTI2_W = 50, + POPUP_MULTI2_H = 18, + POPUP_MULTI2_X = 70, + POPUP_MULTI2_Y = 320, + + POPUP_MULTI3_W = 50, + POPUP_MULTI3_H = 18, + POPUP_MULTI3_X = 20, + POPUP_MULTI3_Y = 330, + + POPUP_MULTI4_W = 50, + POPUP_MULTI4_H = 18, + POPUP_MULTI4_X = 70, + POPUP_MULTI4_Y = 338, + + POPUP_MISSION_W = 160, + POPUP_MISSION_H = 80, + POPUP_MISSION_X = 140, + POPUP_MISSION_Y = 300, + + POPUP_FACEBOX_W = 60, + POPUP_FACEBOX_H = 60, + POPUP_FACEBOX_X = 320, + POPUP_FACEBOX_Y = 320, + + POPUP_HEALTH_W = 100, + POPUP_HEALTH_H = 20, + POPUP_HEALTH_X = 400, + POPUP_HEALTH_Y = 340, + + POPUP_BASE_W = 100, + POPUP_BASE_H = 16, + POPUP_BASE_X = 600 - POPUP_BASE_W, + POPUP_BASE_Y = 0, +}; + +/*........................................................................... +These are the button ID's for the pop-up object-editing gizmos. +The house button ID's must be sequential, with a 1-to-1 correspondence to +the HousesType values. +...........................................................................*/ +enum MapEditButtonIDEnum{ + POPUP_GDI=500, // GDI house button + POPUP_NOD, // NOD house button + POPUP_NEUTRAL, // Neutral house button + POPUP_HOUSE_JP, // not used + POPUP_MULTI1, // Multiplayer 1 house button + POPUP_MULTI2, // Multiplayer 2 house button + POPUP_MULTI3, // Multiplayer 3 house button + POPUP_MULTI4, // Multiplayer 4 house button + POPUP_MULTI5, // Multiplayer 4 house button + POPUP_MULTI6, // Multiplayer 4 house button + POPUP_MISSIONLIST, // list box for missions + POPUP_HEALTHGAUGE, // health of object + POPUP_FACINGDIAL, // object's facing + POPUP_BASEPERCENT, // Base's percent-built slider + MAP_AREA, // map as a click-able thingy + BUTTON_FLAG=0x8000 +}; + + +/* +******************************* Declarations ******************************** +*/ +class TeamTypeClass; + +/* +***************************** Class Declaration ***************************** +*/ +class MapEditClass : public MouseClass +{ + /* + ---------------------------- Public Interface ---------------------------- + */ + public: + /* + ............................. mapedit.cpp ............................. + */ + MapEditClass(void); + virtual void One_Time(void); // One-time init + virtual void Init_IO(void); // Inits button list + virtual void AI(KeyNumType &input, int x, int y); + virtual void Draw_It(bool forced = true); + virtual bool Scroll_Map(DirType facing, int & distance, bool really=true); +// virtual void Flag_To_Redraw(bool complete); + virtual void Read_INI(char *buffer); + virtual void Write_INI(char *buffer); + virtual void Detach(ObjectClass * object); + void Clear_List(void); + bool Add_To_List(ObjectTypeClass const *object); + void Main_Menu(void); + void AI_Menu(void); + bool Mouse_Moved(void); + bool Verify_House(HousesType house, ObjectTypeClass const * objtype); + HousesType Cycle_House(HousesType curhouse, ObjectTypeClass const * objtype); +// int Trigger_Needs_Team(TriggerClass *trigger); + void Fatal(int txt); + + /* + ............................ mapeddlg.cpp ............................. + */ + int New_Scenario(void); + int Load_Scenario(void); + int Save_Scenario(void); + int Pick_Scenario(char const * caption, int *scen_nump, + ScenarioPlayerType *playerp, ScenarioDirType *dirp, + ScenarioVarType *varp, int multi); + int Size_Map(int x, int y, int w, int h); + int Scenario_Dialog(void); + void Handle_Triggers(void); + int Select_Trigger(void); + int Edit_Trigger(void); + int Import_Triggers(void); + int Import_Teams(void); + /* + ............................ mapedplc.cpp ............................. + */ + int Placement_Dialog(void); + void Start_Placement(void); + int Place_Object(void); + void Cancel_Placement(void); + void Place_Next(void); + void Place_Prev(void); + void Place_Next_Category(void); + void Place_Prev_Category(void); + void Place_Home(void); + void Toggle_House(void); + void Set_House_Buttons(HousesType house, GadgetClass *btnlist, int base_id); + void Start_Trigger_Placement(void); + void Stop_Trigger_Placement(void); + void Place_Trigger(void); + void Start_Base_Building(void); + void Cancel_Base_Building(void); + void Build_Base_To(int percent); + + /* + ............................ mapedsel.cpp ............................. + */ + int Select_Object(void); + void Select_Next(void); + void Popup_Controls(void); + void Grab_Object(void); + int Move_Grabbed_Object(void); + bool Change_House(HousesType newhouse); + + /* + ............................. mapedtm.cpp ............................. + */ + void Draw_Member(TechnoTypeClass const * ptr, int index, + int quant, HousesType house, int pic_x, int pic_y); + void Handle_Teams(char const * caption); + int Select_Team(char const * caption); + int Edit_Team(void); + int Team_Members(HousesType house); + void Build_Mission_List(int missioncount, TeamMissionStruct *missions, + char missionbuf[TeamTypeClass::MAX_TEAM_MISSIONS][20], ListClass *list); + + /* + --------------------------- Private Interface ---------------------------- + */ + private: + /*..................................................................... + This is the last-requested variation of a loaded/saved/new scenario. + .....................................................................*/ + ScenarioVarType ScenVar; + + /*..................................................................... + Array of all TypeClasses the user can add to the map; cleared by + Clear_List(), added to by Add_To_List() + .....................................................................*/ + ObjectTypeClass const * Objects[MAX_EDIT_OBJECTS]; + int ObjCount; // # of objects in the Objects array + + /*..................................................................... + Last-selected object to place, and last-selected house of object + .....................................................................*/ + int LastChoice; // index of item user picked last + HousesType LastHouse; // house of last item picked + + /*..................................................................... + Variables for grabbing/moving objects + .....................................................................*/ + ObjectClass * GrabbedObject; // object "grabbed" with mouse + CELL GrabOffset; // offset to grabbed obj's upper-left + unsigned long LastClickTime; // time of last LMOUSE click + + /*..................................................................... + Number of each type of object in Objects, so we can switch categories + .....................................................................*/ + int NumType[NUM_EDIT_CLASSES]; // # of each type of class: + // 0 = Template + // 1 = Overlay + // 2 = Smudge + // 3 = Terrain + // 4 = Unit + // 5 = Infantry + // 6 = Aircraft + // 7 = Building + + /*..................................................................... + The offset of each type of object within the Objects[] array + .....................................................................*/ + int TypeOffset[NUM_EDIT_CLASSES]; // offsets within Objects[] + + /*..................................................................... + The "current" trigger for point-and-click trigger setting + .....................................................................*/ + TriggerClass * CurTrigger; // current trigger + + /*..................................................................... + The "current" team type for editing & associating with a trigger + .....................................................................*/ + TeamTypeClass * CurTeam; // current team + + /*..................................................................... + Bitfields for flags & such + .....................................................................*/ + int Changed : 1; // 1 = changes are unsaved + int LMouseDown : 1; // 1 = left mouse is held down + int BaseBuilding : 1; // 1 = we're in base-building mode + + /*..................................................................... + Variables for pre-building a base + .....................................................................*/ + int BasePercent; // Percentage the base will be built + + /*..................................................................... + Variables for supporting the object-editing controls at screen bottom + .....................................................................*/ + TextButtonClass *GDIButton; + TextButtonClass *NODButton; + TextButtonClass *NeutralButton; + TextButtonClass *Multi1Button; + TextButtonClass *Multi2Button; + TextButtonClass *Multi3Button; + TextButtonClass *Multi4Button; + ListClass *MissionList; + TriColorGaugeClass *HealthGauge; + Dial8Class *FacingDial; + ControlClass *MapArea; + TextLabelClass *HealthText; + static char HealthBuf[20]; + GaugeClass *BaseGauge; + TextLabelClass *BaseLabel; + static MissionType MapEditMissions[]; +}; + +#endif diff --git a/MAPEDPLC.CPP b/MAPEDPLC.CPP new file mode 100644 index 0000000..fe4687e --- /dev/null +++ b/MAPEDPLC.CPP @@ -0,0 +1,1999 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\mapedplc.cpv 2.16 16 Oct 1995 16:51:00 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MAPEDPLC.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : November 18, 1994 * + * * + * Last Update : July 4, 1995 [JLB] * + * * + *-------------------------------------------------------------------------* + * Object-placement routines * + *-------------------------------------------------------------------------* + * Functions: * + * MapEditClass::Placement_Dialog -- adds an object to the scenario * + * MapEditClass::Start_Placement -- enters placement mode * + * MapEditClass::Place_Object -- attempts to place the current object * + * MapEditClass::Cancel_Placement -- cancels placement mode * + * MapEditClass::Place_Next -- while placing object, goes to next * + * MapEditClass::Place_Prev -- while placing object, goes to previous * + * MapEditClass::Place_Next_Category -- places next object category * + * MapEditClass::Place_Prev_Category -- places previous object category * + * MapEditClass::Place_Home -- homes the placement object * + * MapEditClass::Toggle_House -- toggles current placement object's house* + * MapEditClass::Set_House_Buttons -- toggles house buttons for btn list * + * MapEditClass::Start_Trigger_Placement -- enters trigger placement mode* + * MapEditClass::Stop_Trigger_Placement -- exits trigger placement mode * + * MapEditClass::Place_Trigger -- assigns trigger to object or cell * + * MapEditClass::Start_Base_Building -- starts base-building mode * + * MapEditClass::Cancel_Base_Building -- stops base-building mode * + * MapEditClass::Build_Base_To -- builds the AI base to the given percent* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#ifdef SCENARIO_EDITOR + + +/*************************************************************************** + * MapEditClass::Placement_Dialog -- adds an object to the scenario * + * * + * This function sets LastChoice & LastHouse to the values chosen * + * by the user. It's up to the caller to call Start_Placement to enter * + * placement mode. * + * This routine does not modify PendingObject or PendingHouse. * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ [GDI] [NOD] [Neutral] ³ * + * ³ ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ * + * ³ ³ ³ [Template] ³ * + * ³ ³ ³ [Overlay ] ³ * + * ³ ³ ³ [Smudge ] ³ * + * ³ ³ ³ [Terrain ] ³ * + * ³ ³ (Object picture) ³ [Unit ] ³ * + * ³ ³ ³ [Infantry] ³ * + * ³ ³ ³ [Aircraft] ³ * + * ³ ³ ³ [Building] ³ * + * ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄ¿ ³ * + * ³ [<-] [->] ³(Grid)³ ³ * + * ³ ³ ³ ³ * + * ³ [OK] [Cancel] ÀÄÄÄÄÄÄÙ ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = OK, -1 = cancel * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 10/21/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::Placement_Dialog(void) +{ + HousesType house; + + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + enum { + D_DIALOG_W = 480, + D_DIALOG_H = 360, + D_DIALOG_X = ((640 - D_DIALOG_W) / 2), + D_DIALOG_Y = ((400 - D_DIALOG_H) / 2), + D_DIALOG_CX = D_DIALOG_X + (D_DIALOG_W / 2), + + D_TXT8_H = 22, + D_MARGIN = 14, + + D_PICTURE_W = 304, // must be divisible by 8! + D_PICTURE_H = 210, + D_PICTURE_X = D_DIALOG_X + 16, // must start on a byte boundary! + D_PICTURE_Y = D_DIALOG_Y + D_MARGIN + D_TXT8_H + D_MARGIN, + D_PICTURE_CX = D_PICTURE_X + D_PICTURE_W / 2, + + D_GDI_W = 90, + D_GDI_H = 18, + D_GDI_X = D_DIALOG_X + D_MARGIN, + D_GDI_Y = D_DIALOG_Y + D_MARGIN, + + D_NOD_W = 90, + D_NOD_H = 18, + D_NOD_X = D_GDI_X + D_GDI_W, + D_NOD_Y = D_DIALOG_Y + D_MARGIN, + + D_NEUTRAL_W = 90, + D_NEUTRAL_H = 18, + D_NEUTRAL_X = D_NOD_X + D_NOD_W, + D_NEUTRAL_Y = D_DIALOG_Y + D_MARGIN, + + D_MULTI1_W = 44, + D_MULTI1_H = 18, + D_MULTI1_X = D_GDI_X, + D_MULTI1_Y = D_GDI_Y, + + D_MULTI2_W = 44, + D_MULTI2_H = 18, + D_MULTI2_X = D_MULTI1_X + D_MULTI1_W, + D_MULTI2_Y = D_GDI_Y, + + D_MULTI3_W = 44, + D_MULTI3_H = 18, + D_MULTI3_X = D_MULTI2_X + D_MULTI2_W, + D_MULTI3_Y = D_GDI_Y, + + D_MULTI4_W = 44, + D_MULTI4_H = 18, + D_MULTI4_X = D_MULTI3_X + D_MULTI3_W, + D_MULTI4_Y = D_GDI_Y, + + D_LEFT_W = 90, + D_LEFT_H = 18, + D_LEFT_X = D_PICTURE_CX - 5 - D_LEFT_W, + D_LEFT_Y = D_PICTURE_Y + D_PICTURE_H + D_MARGIN, + + D_RIGHT_W = 90, + D_RIGHT_H = 18, + D_RIGHT_X = D_PICTURE_CX + 5, + D_RIGHT_Y = D_PICTURE_Y + D_PICTURE_H + D_MARGIN, + + D_TEMPLATE_W = 140, + D_TEMPLATE_H = 18, + D_TEMPLATE_X = D_DIALOG_X + D_DIALOG_W - D_MARGIN - D_TEMPLATE_W, + D_TEMPLATE_Y = D_PICTURE_Y, + + D_OVERLAY_W = 140, + D_OVERLAY_H = 18, + D_OVERLAY_X = D_DIALOG_X + D_DIALOG_W - D_MARGIN - D_OVERLAY_W, + D_OVERLAY_Y = D_TEMPLATE_Y + D_TEMPLATE_H, + + D_SMUDGE_W = 140, + D_SMUDGE_H = 18, + D_SMUDGE_X = D_DIALOG_X + D_DIALOG_W - D_MARGIN - D_SMUDGE_W, + D_SMUDGE_Y = D_OVERLAY_Y + D_OVERLAY_H, + + D_TERRAIN_W = 140, + D_TERRAIN_H = 18, + D_TERRAIN_X = D_DIALOG_X + D_DIALOG_W - D_MARGIN - D_TERRAIN_W, + D_TERRAIN_Y = D_SMUDGE_Y + D_SMUDGE_H, + + D_UNIT_W = 140, + D_UNIT_H = 18, + D_UNIT_X = D_DIALOG_X + D_DIALOG_W - D_MARGIN - D_UNIT_W, + D_UNIT_Y = D_TERRAIN_Y + D_TERRAIN_H, + + D_INFANTRY_W = 140, + D_INFANTRY_H = 18, + D_INFANTRY_X = D_DIALOG_X + D_DIALOG_W - D_MARGIN - D_INFANTRY_W, + D_INFANTRY_Y = D_UNIT_Y + D_UNIT_H, + + D_AIRCRAFT_W = 140, + D_AIRCRAFT_H = 18, + D_AIRCRAFT_X = D_DIALOG_X + D_DIALOG_W - D_MARGIN - D_AIRCRAFT_W, + D_AIRCRAFT_Y = D_INFANTRY_Y + D_INFANTRY_H, + + D_BUILDING_W = 140, + D_BUILDING_H = 18, + D_BUILDING_X = D_DIALOG_X + D_DIALOG_W - D_MARGIN - D_BUILDING_W, + D_BUILDING_Y = D_AIRCRAFT_Y + D_AIRCRAFT_H, + + D_OK_W = 90, + D_OK_H = 18, + D_OK_X = D_PICTURE_CX - D_OK_W - 5, + D_OK_Y = D_DIALOG_Y + D_DIALOG_H - D_OK_H - D_MARGIN, + + D_CANCEL_W = 90, + D_CANCEL_H = 18, + D_CANCEL_X = D_PICTURE_CX + 5, + D_CANCEL_Y = D_DIALOG_Y + D_DIALOG_H - D_CANCEL_H - D_MARGIN, + + }; + /*........................................................................ + Grid Dimensions + ........................................................................*/ + enum { + GRIDSIZE = 10, + GRIDBLOCK_W = 6, + GRIDBLOCK_H = 6, + D_GRID_X = D_DIALOG_X + D_DIALOG_W - (GRIDSIZE * GRIDBLOCK_W) - D_MARGIN, + D_GRID_Y = D_DIALOG_Y + D_DIALOG_H - (GRIDSIZE * GRIDBLOCK_H) - D_MARGIN, + }; + /*........................................................................ + Button enumerations: + ........................................................................*/ + enum { + BUTTON_GDI=100, + BUTTON_NOD, + BUTTON_NEUTRAL, + BUTTON_JP, // placeholder + BUTTON_MULTI1, + BUTTON_MULTI2, + BUTTON_MULTI3, + BUTTON_MULTI4, + BUTTON_NEXT, + BUTTON_PREV, + BUTTON_OK, + BUTTON_CANCEL, + BUTTON_TEMPLATE, + BUTTON_OVERLAY, + BUTTON_SMUDGE, + BUTTON_TERRAIN, + BUTTON_UNIT, + BUTTON_INFANTRY, + BUTTON_AIRCRAFT, + BUTTON_BUILDING, + }; + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_OBJECT, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display; // display level + bool process; // loop while true + bool cancel = false; // true = user cancels + const ObjectTypeClass * curobj; // Working object pointer. + int x,y; // for drawing the grid + KeyNumType input; // user input + short const *occupy; // ptr into object's OccupyList + int cell; // cell index for parsing OccupyList + int i; + int typeindex; // index of class type + /*........................................................................ + Buttons + ........................................................................*/ + ControlClass *commands; + + TextButtonClass gdibtn (BUTTON_GDI, "GDI", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_GDI_X, D_GDI_Y, D_GDI_W, D_GDI_H); + + TextButtonClass nodbtn (BUTTON_NOD, "NOD", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NOD_X, D_NOD_Y, D_NOD_W, D_NOD_H); + + TextButtonClass neutbtn (BUTTON_NEUTRAL, "Neutral", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NEUTRAL_X, D_NEUTRAL_Y, D_NEUTRAL_W, D_NEUTRAL_H); + + TextButtonClass multi1btn (BUTTON_MULTI1, "M1", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI1_X, D_MULTI1_Y, D_MULTI1_W, D_MULTI1_H); + + TextButtonClass multi2btn (BUTTON_MULTI2, "M2", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI2_X, D_MULTI2_Y, D_MULTI2_W, D_MULTI2_H); + + TextButtonClass multi3btn (BUTTON_MULTI3, "M3", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI3_X, D_MULTI3_Y, D_MULTI3_W, D_MULTI3_H); + + TextButtonClass multi4btn (BUTTON_MULTI4, "M4", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI4_X, D_MULTI4_Y, D_MULTI4_W, D_MULTI4_H); + + TextButtonClass nextbtn (BUTTON_NEXT, TXT_RIGHT, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_RIGHT_X, D_RIGHT_Y, D_RIGHT_W, D_RIGHT_H); + + TextButtonClass prevbtn (BUTTON_PREV, TXT_LEFT, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_LEFT_X, D_LEFT_Y, D_LEFT_W, D_LEFT_H); + + TextButtonClass okbtn (BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_OK_X, D_OK_Y, D_OK_W, D_OK_H); + + TextButtonClass cancelbtn (BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_CANCEL_X, D_CANCEL_Y, D_CANCEL_W, D_CANCEL_H); + + TextButtonClass templatebtn (BUTTON_TEMPLATE, "Template", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_TEMPLATE_X, D_TEMPLATE_Y, D_TEMPLATE_W, D_TEMPLATE_H); + + TextButtonClass overlaybtn (BUTTON_OVERLAY, "Overlay", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_OVERLAY_X, D_OVERLAY_Y, D_OVERLAY_W, D_OVERLAY_H); + + TextButtonClass smudgebtn (BUTTON_SMUDGE, "Smudge", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_SMUDGE_X, D_SMUDGE_Y, D_SMUDGE_W, D_SMUDGE_H); + + TextButtonClass terrainbtn (BUTTON_TERRAIN, "Terrain", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_TERRAIN_X, D_TERRAIN_Y, D_TERRAIN_W, D_TERRAIN_H); + + TextButtonClass unitbtn (BUTTON_UNIT, "Unit", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_UNIT_X, D_UNIT_Y, D_UNIT_W, D_UNIT_H); + + TextButtonClass infantrybtn (BUTTON_INFANTRY, "Infantry", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_INFANTRY_X, D_INFANTRY_Y, D_INFANTRY_W, D_INFANTRY_H); + + TextButtonClass aircraftbtn (BUTTON_AIRCRAFT, "Aircraft", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_AIRCRAFT_X, D_AIRCRAFT_Y, D_AIRCRAFT_W, D_AIRCRAFT_H); + + TextButtonClass buildingbtn (BUTTON_BUILDING, "Building", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_BUILDING_X, D_BUILDING_Y, D_BUILDING_W, D_BUILDING_H); + + /*------------------------------------------------------------------------ + Initialize addable objects list; we must do this every time in case one + of the object pools has become exhausted; that object won't be available + for adding. (Skip aircraft, since they won't be used in the editor.) + ------------------------------------------------------------------------*/ + Clear_List(); + TemplateTypeClass::Prep_For_Add(); + OverlayTypeClass::Prep_For_Add(); + SmudgeTypeClass::Prep_For_Add(); + TerrainTypeClass::Prep_For_Add(); + UnitTypeClass::Prep_For_Add(); + InfantryTypeClass::Prep_For_Add(); + BuildingTypeClass::Prep_For_Add(); + + /*........................................................................ + Compute offset of each class type in the Objects array + ........................................................................*/ + TypeOffset[0] = 0; + for (i=1; i= ObjCount) { + LastChoice = 0; + } + curobj = Objects[LastChoice]; // current object to choose + + commands = &neutbtn; + if (ScenPlayer == SCEN_PLAYER_MPLAYER) { + multi1btn.Add_Tail(*commands); + multi2btn.Add_Tail(*commands); + multi3btn.Add_Tail(*commands); + multi4btn.Add_Tail(*commands); + } else { + gdibtn.Add_Tail(*commands); + nodbtn.Add_Tail(*commands); + } + nextbtn.Add_Tail(*commands); + prevbtn.Add_Tail(*commands); + okbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + templatebtn.Add_Tail(*commands); + overlaybtn.Add_Tail(*commands); + smudgebtn.Add_Tail(*commands); + terrainbtn.Add_Tail(*commands); + unitbtn.Add_Tail(*commands); + infantrybtn.Add_Tail(*commands); + aircraftbtn.Add_Tail(*commands); + buildingbtn.Add_Tail(*commands); + + /*........................................................................ + If the current house isn't valid for the current object type, cycle to + the next house. + ........................................................................*/ + if (!Verify_House(LastHouse,curobj)) { + LastHouse = Cycle_House(LastHouse,curobj); + } + + /* + ..................... Set the buttons for this house ..................... + */ + Set_House_Buttons(LastHouse, commands, BUTTON_GDI); + + /* + -------------------------- Main processing loop -------------------------- + */ + display = REDRAW_ALL; + process = true; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + /* + ---------------------- Refresh display if needed ---------------------- + */ + if (display > REDRAW_NONE) { + /* + ---------------------- Display the dialog box ---------------------- + */ + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + Dialog_Box(D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W, D_DIALOG_H); + Draw_Caption(TXT_NONE, D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W); + } + + /*------------------------------------------------------------------ + Display the current object: + - save the current window dimensions + - adjust the window size to the actual drawable area + - draw the shape + - reset the window dimensions + ------------------------------------------------------------------*/ + if (display >= REDRAW_OBJECT) { + WindowList[WINDOW_EDITOR][WINDOWX] = D_PICTURE_X >> 3; + WindowList[WINDOW_EDITOR][WINDOWY] = D_PICTURE_Y; + WindowList[WINDOW_EDITOR][WINDOWWIDTH] = D_PICTURE_W >> 3; + WindowList[WINDOW_EDITOR][WINDOWHEIGHT] = D_PICTURE_H; + Change_Window((int)WINDOW_EDITOR); + Draw_Box(D_PICTURE_X, D_PICTURE_Y, D_PICTURE_W, D_PICTURE_H, + BOXSTYLE_GREEN_DOWN, true); + curobj->Display(WinW<<2, WinH>>1, WINDOW_EDITOR, LastHouse); + + /* + ........................ Erase the grid ......................... + */ + LogicPage->Fill_Rect(D_GRID_X - GRIDBLOCK_W * 2, D_GRID_Y, + D_GRID_X + GRIDSIZE * GRIDBLOCK_W, + D_GRID_Y + GRIDSIZE * GRIDBLOCK_H, BLACK); + + /* + .............. Draw a box for every cell occupied ............... + */ + occupy = curobj->Occupy_List(); + while ( (*occupy) != REFRESH_EOL) { + cell = (*occupy); + occupy++; + x = D_GRID_X + ((cell % MAP_CELL_W) * GRIDBLOCK_W); + y = D_GRID_Y + ((cell / MAP_CELL_W) * GRIDBLOCK_H); + LogicPage->Fill_Rect(x, y, x + GRIDBLOCK_W - 1, y + GRIDBLOCK_H - 1, + CC_BRIGHT_GREEN); + } + + /* + ..................... Draw the grid itself ...................... + */ + for (y = 0; y <= GRIDSIZE; y++) { + for (x = 0; x <= GRIDSIZE; x++) { + LogicPage->Draw_Line(D_GRID_X + x * GRIDBLOCK_W, D_GRID_Y, + D_GRID_X + x * GRIDBLOCK_W, + D_GRID_Y + GRIDSIZE * GRIDBLOCK_H, CC_GREEN_SHADOW); + } + LogicPage->Draw_Line(D_GRID_X, D_GRID_Y + y * GRIDBLOCK_H, + D_GRID_X + GRIDSIZE * GRIDBLOCK_W, D_GRID_Y + y * GRIDBLOCK_H, + CC_GREEN_SHADOW); + } + + /*............................................................... + Print the object's label from the class's Full_Name(). + Warning: Text_String returns an EMS pointer, so standard string + functions won't work! + ...............................................................*/ + Fancy_Text_Print (curobj->Full_Name(), + D_PICTURE_CX, D_PICTURE_Y + D_MARGIN, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + + /* + -------------------------- Redraw buttons -------------------------- + */ + if (display >= REDRAW_BUTTONS) { + /*............................................................... + Figure out which class category we're in & highlight that button + This updates 'typeindex', which is used below, and it also updates + the category button states. + ...............................................................*/ + i = 0; + for (typeindex = 0; typeindex < NUM_EDIT_CLASSES; typeindex++) { + i += NumType[typeindex]; + if (LastChoice < i) break; + } + templatebtn.Turn_Off(); + overlaybtn.Turn_Off(); + smudgebtn.Turn_Off(); + terrainbtn.Turn_Off(); + unitbtn.Turn_Off(); + infantrybtn.Turn_Off(); + aircraftbtn.Turn_Off(); + buildingbtn.Turn_Off(); + switch (typeindex + BUTTON_TEMPLATE) { + case BUTTON_TEMPLATE: + templatebtn.Turn_On(); + break; + + case BUTTON_OVERLAY: + overlaybtn.Turn_On(); + break; + + case BUTTON_SMUDGE: + smudgebtn.Turn_On(); + break; + + case BUTTON_TERRAIN: + terrainbtn.Turn_On(); + break; + + case BUTTON_UNIT: + unitbtn.Turn_On(); + break; + + case BUTTON_INFANTRY: + infantrybtn.Turn_On(); + break; + + case BUTTON_AIRCRAFT: + aircraftbtn.Turn_On(); + break; + + case BUTTON_BUILDING: + buildingbtn.Turn_On(); + break; + } + } + + /* + .......................... Redraw buttons .......................... + */ + commands->Draw_All(); + Show_Mouse(); + display = REDRAW_NONE; + + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ------------------------- Process user input -------------------------- + */ + switch (input) { + /* + ---------------------------- GDI House ----------------------------- + */ + case (BUTTON_GDI | KN_BUTTON): + case (BUTTON_NOD | KN_BUTTON): + case (BUTTON_NEUTRAL | KN_BUTTON): + case (BUTTON_MULTI1 | KN_BUTTON): + case (BUTTON_MULTI2 | KN_BUTTON): + case (BUTTON_MULTI3 | KN_BUTTON): + case (BUTTON_MULTI4 | KN_BUTTON): + house = (HousesType)( (input & (~KN_BUTTON)) - BUTTON_GDI); + /* + ............... ignore if invalid for this object ............... + */ + if (!Verify_House(house,curobj)) { + Set_House_Buttons(LastHouse, commands, BUTTON_GDI); + break; + } + + /* + ...................... Set flags & buttons ...................... + */ + LastHouse = house; + Set_House_Buttons(LastHouse, commands, BUTTON_GDI); + display = REDRAW_OBJECT; + break; + + /* + --------------------------- Next in list --------------------------- + */ + case (KN_RIGHT): + case (BUTTON_NEXT | KN_BUTTON): + /* + ..................... Increment to next obj ..................... + */ + LastChoice++; + if (LastChoice == ObjCount) { + LastChoice = 0; + } + curobj = Objects[LastChoice]; + + /* + .................... Get valid house for obj .................... + */ + if (!Verify_House(LastHouse,curobj)) { + LastHouse = Cycle_House(LastHouse,curobj); + Set_House_Buttons(LastHouse, commands, BUTTON_GDI); + } + + nextbtn.Turn_Off(); + display = REDRAW_OBJECT; + break; + + /* + ------------------------- Previous in list ------------------------- + */ + case (KN_LEFT): + case (BUTTON_PREV | KN_BUTTON): + /* + ..................... Decrement to prev obj ..................... + */ + LastChoice--; + if (LastChoice < 0) { + LastChoice = ObjCount-1; + } + curobj = Objects[LastChoice]; + + /* + .................... Get valid house for obj .................... + */ + if (!Verify_House(LastHouse,curobj)) { + LastHouse = Cycle_House(LastHouse,curobj); + Set_House_Buttons(LastHouse, commands, BUTTON_GDI); + } + + prevbtn.Turn_Off(); + display = REDRAW_OBJECT; + break; + + /* + ----------------------- Select a class type ------------------------ + */ + case (BUTTON_TEMPLATE | KN_BUTTON): + case (BUTTON_OVERLAY | KN_BUTTON): + case (BUTTON_SMUDGE | KN_BUTTON): + case (BUTTON_TERRAIN | KN_BUTTON): + case (BUTTON_UNIT | KN_BUTTON): + case (BUTTON_INFANTRY | KN_BUTTON): + case (BUTTON_AIRCRAFT | KN_BUTTON): + case (BUTTON_BUILDING | KN_BUTTON): + /* + ...................... Find index of class ...................... + */ + typeindex = input - (BUTTON_TEMPLATE | KN_BUTTON); + + /* + ............ If no objects of that type, do nothing ............. + */ + if (NumType[typeindex]==0) { + display = REDRAW_BUTTONS; // force to reset button states + break; + } + + /* + ...................... Set current object ....................... + */ + LastChoice = TypeOffset[typeindex]; + curobj = Objects[LastChoice]; + + /* + .................... Get valid house for obj .................... + */ + if (!Verify_House(LastHouse,curobj)) { + LastHouse = Cycle_House(LastHouse,curobj); + Set_House_Buttons(LastHouse, commands, BUTTON_GDI); + } + + display = REDRAW_OBJECT; + break; + + /* + -------------------------- Next category --------------------------- + */ + case KN_PGDN: + typeindex++; + if (typeindex==NUM_EDIT_CLASSES) { + typeindex = 0; + } + + /* + ...................... Set current object ....................... + */ + LastChoice = TypeOffset[typeindex]; + curobj = Objects[LastChoice]; + + /* + .................... Get valid house for obj .................... + */ + if (!Verify_House(LastHouse,curobj)) { + LastHouse = Cycle_House(LastHouse,curobj); + Set_House_Buttons(LastHouse, commands, BUTTON_GDI); + } + + display = REDRAW_OBJECT; + break; + + /* + ------------------------ Previous category ------------------------- + */ + case KN_PGUP: + typeindex--; + if (typeindex < 0) { + typeindex = NUM_EDIT_CLASSES - 1; + } + + /* + ...................... Set current object ....................... + */ + LastChoice = TypeOffset[typeindex]; + curobj = Objects[LastChoice]; + + /* + .................... Get valid house for obj .................... + */ + if (!Verify_House(LastHouse,curobj)) { + LastHouse = Cycle_House(LastHouse,curobj); + Set_House_Buttons(LastHouse, commands, BUTTON_GDI); + } + + display = REDRAW_OBJECT; + break; + + /* + ------------------------ Jump to 1st choice ------------------------ + */ + case KN_HOME: + LastChoice = 0; + /* + ...................... Set current object ....................... + */ + curobj = Objects[LastChoice]; + /* + .................... Get valid house for obj .................... + */ + if (!Verify_House(LastHouse,curobj)) { + LastHouse = Cycle_House(LastHouse,curobj); + Set_House_Buttons(LastHouse, commands, BUTTON_GDI); + } + display = REDRAW_OBJECT; + break; + + /* + -------------------------------- OK -------------------------------- + */ + case (KN_RETURN): + case (BUTTON_OK | KN_BUTTON): + cancel = false; + process = false; + break; + + /* + ------------------------------ Cancel ------------------------------ + */ + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + cancel = true; + process = false; + break; + + default: + break; + } + + } + + /* + --------------------------- Redraw the display --------------------------- + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + + if (cancel) { + return(-1); + } + + return(0); +} + + +/*************************************************************************** + * MapEditClass::Start_Placement -- enters placement mode * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/04/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Start_Placement(void) +{ + int i; + + /*------------------------------------------------------------------------ + Initialize addable objects list; we must do this every time in case one + of the object pools has become exhausted; that object won't be available + for adding. + ------------------------------------------------------------------------*/ + Clear_List(); + TemplateTypeClass::Prep_For_Add(); + OverlayTypeClass::Prep_For_Add(); + SmudgeTypeClass::Prep_For_Add(); + TerrainTypeClass::Prep_For_Add(); + UnitTypeClass::Prep_For_Add(); + InfantryTypeClass::Prep_For_Add(); + //AircraftTypeClass::Prep_For_Add(); + BuildingTypeClass::Prep_For_Add(); + /*........................................................................ + Compute offset of each class type in the Objects array + ........................................................................*/ + TypeOffset[0] = 0; + for (i=1; i= ObjCount) + LastChoice = ObjCount - 1; + PendingObject = Objects[LastChoice]; + PendingHouse = LastHouse; + PendingObjectPtr = PendingObject->Create_One_Of(HouseClass::As_Pointer(LastHouse)); + } else { + if (LastChoice < TypeOffset[7]) + LastChoice = TypeOffset[7]; + if (LastChoice >= ObjCount) + LastChoice = ObjCount - 1; + PendingObject = Objects[LastChoice]; + PendingHouse = LastHouse = Base.House; + PendingObjectPtr = PendingObject->Create_One_Of(HouseClass::As_Pointer(LastHouse)); + } + + + /* + ------------------- Error if no more objects available ------------------- + */ + if (!PendingObjectPtr) { + CCMessageBox().Process("No more objects of this type available."); + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + PendingObject = NULL; + if (BaseBuilding) + Cancel_Base_Building(); + return; + } + + /* + ------------------------ Set the placement cursor ------------------------ + */ + Set_Cursor_Pos(); + Set_Cursor_Shape(PendingObject->Occupy_List()); +} + + +/*************************************************************************** + * MapEditClass::Place_Object -- attempts to place the current object * + * * + * Placement of "real" objects is simply checked via their Unlimbo routine.* + * Placement of templates is more complex: * + * - for every cell in the template's OccupyList, check for objects * + * already in that cell by looking at the cell's OccupyList & * + * OverlapList * + * - "lift" all the objects in the cell by Mark'ing them * + * - temporarily place the template in that cell * + * - try to Unlimbo all the objects that were in the cell. If any * + * objects fail to Unlimbo onto that template, the template cannot * + * be placed here * + * * + * It is assumed that the object being placed is a "new" object; the * + * object's strength & mission are not set during Unlimbo. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = OK, -1 = unable to place * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/04/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::Place_Object(void) +{ + CELL template_cell; // cell being checked for template + COORDINATE obj_coord; // coord of occupier object + int okflag; // OK to place a template? + short const *occupy; // ptr into template's OccupyList + ObjectClass *occupier; // occupying object + TemplateType save_ttype; // for saving cell's TType + unsigned char save_ticon; // for saving cell's TIcon + BaseNodeClass node; // for adding to an AI Base + + /*------------------------------------------------------------------------ + Placing a template: + - first lift up any objects in the cell + - place the template, and try to replace the objects; if they won't go + back, the template can't go there + ------------------------------------------------------------------------*/ + //ScenarioInit++; + if (PendingObject->What_Am_I() == RTTI_TEMPLATETYPE) { + + /* + .......... Loop through all cells this template will occupy ........... + */ + okflag = true; + occupy = PendingObject->Occupy_List(); + while ((*occupy) != REFRESH_EOL) { + + /* + ................. Check this cell for an occupier .................. + */ + template_cell = (ZoneCell+ZoneOffset) + (*occupy); + if ((*this)[template_cell].Cell_Occupier()) { + occupier = (*this)[template_cell].Cell_Occupier(); + + /* + .................. Save object's coordinates .................... + */ + obj_coord = occupier->Coord; + + /* + ................... Place the object in limbo ................... + */ + occupier->Mark(MARK_UP); + + /* + ................ Set the cell's template values ................. + */ + save_ttype = (*this)[template_cell].TType; + save_ticon = (*this)[template_cell].TIcon; + (*this)[template_cell].TType = + ((TemplateTypeClass *)PendingObject)->Type; + (*this)[template_cell].TIcon = Cell_X(*occupy) + Cell_Y(*occupy) * + ((TemplateTypeClass *)PendingObject)->Width; + (*this)[template_cell].Recalc_Attributes(); + /* + ................ Try to put the object back down ................ + */ + if (occupier->Can_Enter_Cell(Coord_Cell(obj_coord)) != MOVE_OK) { + okflag = false; + } + + /* + .............. Put everything back the way it was ............... + */ + (*this)[template_cell].TType = save_ttype; + (*this)[template_cell].TIcon = save_ticon; + (*this)[template_cell].Recalc_Attributes(); + + /* + .......... Major error if can't replace the object now .......... + */ + occupier->Mark(MARK_DOWN); + } + occupy++; + } + + /* + ......... If it's still OK after ALL THAT, place the template ......... + */ + if (okflag) { + if (PendingObjectPtr->Unlimbo(Cell_Coord(ZoneCell + ZoneOffset))) { + /*............................................................... + Loop through all cells occupied by this template, and clear the + smudge & overlay. + ...............................................................*/ + occupy = PendingObject->Occupy_List(); + while ((*occupy) != REFRESH_EOL) { + /* + ............... Get cell for this occupy item ................ + */ + template_cell = (ZoneCell+ZoneOffset) + (*occupy); + + /* + ................... Clear smudge & overlay ................... + */ + (*this)[template_cell].Overlay = OVERLAY_NONE; + (*this)[template_cell].OverlayData = 0; + (*this)[template_cell].Smudge = SMUDGE_NONE; + + /* + ............ make adjacent cells recalc attrib's ............. + */ + (*this)[template_cell].Recalc_Attributes(); + (*this)[template_cell].Wall_Update(); + (*this)[template_cell].Concrete_Calc(); + + occupy++; + } + + /* + ......................... Set flags etc ......................... + */ + PendingObjectPtr = 0; + PendingObject = 0; + PendingHouse = HOUSE_NONE; + Set_Cursor_Shape(0); + //ScenarioInit--; + TotalValue = Overpass(); + Flag_To_Redraw(false); + return(0); + } + + /* + ** Failure to deploy results in a returned failure code. + */ + //ScenarioInit--; + return(-1); + } + + /* + ........................ Not OK; return error ......................... + */ + //ScenarioInit--; + return(-1); + } + + /*------------------------------------------------------------------------ + Placing infantry: Infantry can go into cell sub-positions, so find the + sub-position closest to the mouse & put him there + ------------------------------------------------------------------------*/ + if (PendingObject->What_Am_I() == RTTI_INFANTRYTYPE) { + /* + ....................... Find cell sub-position ........................ + */ + if (Is_Spot_Free(Pixel_To_Coord(Get_Mouse_X(),Get_Mouse_Y()))) { + obj_coord = Closest_Free_Spot(Pixel_To_Coord(Get_Mouse_X(), + Get_Mouse_Y())); + } else { + obj_coord = NULL; + } + + /* + ................ No free spots; don't place the object ................ + */ + if (obj_coord == NULL) { + //ScenarioInit--; + return(-1); + } + + /* + ......................... Unlimbo the object .......................... + */ + if (PendingObjectPtr->Unlimbo(obj_coord)) { + ((InfantryClass *)PendingObjectPtr)->Set_Occupy_Bit(obj_coord); +// Map[Coord_Cell(obj_coord)].Flag.Composite |= +// (1 << CellClass::Spot_Index(obj_coord)); + PendingObjectPtr = 0; + PendingObject = 0; + PendingHouse = HOUSE_NONE; + Set_Cursor_Shape(0); + //ScenarioInit--; + return(0); + } + + //ScenarioInit--; + return(-1); + } + + /*------------------------------------------------------------------------ + Placing an object + ------------------------------------------------------------------------*/ + if (PendingObjectPtr->Unlimbo(Cell_Coord(ZoneCell + ZoneOffset))) { + + /* + ** Update the Tiberium computation if we're placing an overlay + */ + if (PendingObject->What_Am_I() == RTTI_OVERLAYTYPE && + ((OverlayTypeClass *)PendingObject)->IsTiberium) { + + TotalValue = Overpass(); + Flag_To_Redraw(false); + } + + /* + ** If we're building a base, add this building to the base's Node list. + */ + if (BaseBuilding && PendingObject->What_Am_I() == RTTI_BUILDINGTYPE) { + node.Type = ((BuildingTypeClass *)PendingObject)->Type; + node.Coord = PendingObjectPtr->Coord; + Base.Nodes.Add(node); + } + + PendingObjectPtr = 0; + PendingObject = 0; + PendingHouse = HOUSE_NONE; + Set_Cursor_Shape(0); + //ScenarioInit--; + return(0); + } + + return(-1); +} + + +/*************************************************************************** + * MapEditClass::Cancel_Placement -- cancels placement mode * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/04/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Cancel_Placement(void) +{ + /* + ---------------------- Delete the placement object ----------------------- + */ + delete PendingObjectPtr; + PendingObject = 0; + PendingObjectPtr = 0; + PendingHouse = HOUSE_NONE; + + /* + -------------------------- Restore cursor shape -------------------------- + */ + Set_Cursor_Shape(0); + + /* + ----------------- Redraw the map to erase old leftovers ------------------ + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); +} + + +/*************************************************************************** + * MapEditClass::Place_Next -- while placing object, goes to next * + * * + * - Deletes the current 'PendingObjectPtr' * + * - Increments LastChoice * + * - Tries to create a new 'PendingObjectPtr'; if fails, keeps * + * incrementing until it gets it * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/03/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Place_Next(void) +{ + delete PendingObjectPtr; + PendingObjectPtr = NULL; + PendingObject = NULL; + + /* + ------------------ Loop until we create a valid object ------------------- + */ + while (!PendingObjectPtr) { + /* + ................. Go to next object in Objects list ................... + */ + LastChoice++; + if (LastChoice == ObjCount) { + /* + ** If we're in normal placement mode, wrap to the 1st object; + ** if we're in base-building mode, wrap to the 1st building + */ + if (!BaseBuilding) { + LastChoice = 0; + } else { + LastChoice = TypeOffset[7]; + } + } + + /* + ................... Get house for this object type .................... + */ + if (!Verify_House(LastHouse,Objects[LastChoice])) { + /* + ** If we're in normal placement mode, change the current + ** placement house to the one that can own this object. + ** If we're building a base, skip ahead to the next object if the + ** base's house can't own this one. + */ + if (!BaseBuilding) { + LastHouse = Cycle_House(LastHouse,Objects[LastChoice]); + } else { + continue; + } + } + + /* + ....................... Create placement object ....................... + */ + PendingObject = Objects[LastChoice]; + PendingHouse = LastHouse; + PendingObjectPtr = PendingObject->Create_One_Of(HouseClass::As_Pointer(PendingHouse)); + if (!PendingObjectPtr) { + PendingObject = NULL; + } + } + + /* + ------------------------ Set the new cursor shape ------------------------ + */ + Set_Cursor_Pos(); + Set_Cursor_Shape(0); + Set_Cursor_Shape(PendingObject->Occupy_List()); + + /* + ----------------- Redraw the map to erase old leftovers ------------------ + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); +} + + +/*************************************************************************** + * MapEditClass::Place_Prev -- while placing object, goes to previous * + * * + * - Deletes the current 'PendingObjectPtr' * + * - Decrements LastChoice * + * - Tries to create a new 'PendingObjectPtr'; if fails, keeps * + * decrementing until it gets it * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/03/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Place_Prev(void) +{ + delete PendingObjectPtr; + PendingObjectPtr = NULL; + PendingObject = NULL; + + /* + ------------------ Loop until we create a valid object ------------------- + */ + while (!PendingObjectPtr) { + + /* + ................. Go to prev object in Objects list .................. + */ + LastChoice--; + /* + ** If we're in normal placement mode, wrap at the 1st object. + ** If we're building a base, wrap at the 1st building. + */ + if (!BaseBuilding) { + if (LastChoice < 0) + LastChoice = ObjCount - 1; + } else { + if (LastChoice < TypeOffset[7]) + LastChoice = ObjCount - 1; + } + + /* + ................... Get house for this object type .................... + */ + if (!Verify_House(LastHouse,Objects[LastChoice])) { + /* + ** If we're in normal placement mode, change the current + ** placement house to the one that can own this object. + ** If we're building a base, skip ahead to the next object if the + ** base's house can't own this one. + */ + if (!BaseBuilding) { + LastHouse = Cycle_House(LastHouse,Objects[LastChoice]); + } else { + continue; + } + } + + /* + ....................... Create placement object ....................... + */ + PendingObject = Objects[LastChoice]; + PendingHouse = LastHouse; + PendingObjectPtr = PendingObject->Create_One_Of(HouseClass::As_Pointer(PendingHouse)); + if (!PendingObjectPtr) { + PendingObject = NULL; + } + } + + /* + ------------------------ Set the new cursor shape ------------------------ + */ + Set_Cursor_Pos(); + Set_Cursor_Shape(0); + Set_Cursor_Shape(PendingObject->Occupy_List()); + + /* + ----------------- Redraw the map to erase old leftovers ------------------ + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); +} + + +/*************************************************************************** + * MapEditClass::Place_Next_Category -- places next category of object * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/03/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Place_Next_Category(void) +{ + int i; + + /* + ** Don't allow this command if we're building a base; the only valid + ** category for base-building is buildings. + */ + if (BaseBuilding) { + return; + } + + delete PendingObjectPtr; + PendingObjectPtr = NULL; + PendingObject = NULL; + + /* + ------------------ Go to next category in Objects list ------------------- + */ + i = LastChoice; + while (Objects[i]->What_Am_I() == Objects[LastChoice]->What_Am_I()) { + i++; + if (i == ObjCount) { + i = 0; + } + } + LastChoice = i; + + /* + ------------------ Loop until we create a valid object ------------------- + */ + while (!PendingObjectPtr) { + + /* + ................... Get house for this object type .................... + */ + if (!Verify_House(LastHouse,Objects[LastChoice])) { + LastHouse = Cycle_House(LastHouse,Objects[LastChoice]); + } + + /* + ....................... Create placement object ....................... + */ + PendingObject = Objects[LastChoice]; + PendingHouse = LastHouse; + PendingObjectPtr = PendingObject->Create_One_Of(HouseClass::As_Pointer(PendingHouse)); + + /* + .................. If this one failed, try the next ................... + */ + if (!PendingObjectPtr) { + PendingObject = NULL; + LastChoice++; + if (LastChoice == ObjCount) { + LastChoice = 0; + } + } + } + + /* + ------------------------ Set the new cursor shape ------------------------ + */ + Set_Cursor_Pos(); + Set_Cursor_Shape(0); + Set_Cursor_Shape(PendingObject->Occupy_List()); + + /* + ----------------- Redraw the map to erase old leftovers ------------------ + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); +} + + +/*************************************************************************** + * MapEditClass::Place_Prev_Category -- places previous category of object * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/03/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Place_Prev_Category(void) +{ + int i; + + /* + ** Don't allow this command if we're building a base; the only valid + ** category for base-building is buildings. + */ + if (BaseBuilding) { + return; + } + + delete PendingObjectPtr; + PendingObjectPtr = NULL; + PendingObject = NULL; + + /* + ------------------ Go to prev category in Objects list ------------------- + */ + i = LastChoice; + /* + ..................... Scan for the previous category ..................... + */ + while (Objects[i]->What_Am_I() == Objects[LastChoice]->What_Am_I()) { + i--; + if (i < 0) { + i = ObjCount - 1; + } + } + /* + .................... Scan for start of this category ..................... + */ + LastChoice = i; + while (Objects[i]->What_Am_I() == Objects[LastChoice]->What_Am_I()) { + LastChoice = i; + i--; + if (i < 0) { + i = ObjCount - 1; + } + } + + /* + ------------------ Loop until we create a valid object ------------------- + */ + while (!PendingObjectPtr) { + /* + ................... Get house for this object type .................... + */ + if (!Verify_House(LastHouse,Objects[LastChoice])) { + LastHouse = Cycle_House(LastHouse,Objects[LastChoice]); + } + + /* + ....................... Create placement object ....................... + */ + PendingObject = Objects[LastChoice]; + PendingHouse = LastHouse; + PendingObjectPtr = PendingObject->Create_One_Of(HouseClass::As_Pointer(PendingHouse)); + + /* + .................. If this one failed, try the next ................... + */ + if (!PendingObjectPtr) { + PendingObject = NULL; + LastChoice--; + if (LastChoice < 0) { + LastChoice = ObjCount - 1; + } + } + } + + /* + ------------------------ Set the new cursor shape ------------------------ + */ + Set_Cursor_Pos(); + Set_Cursor_Shape(0); + Set_Cursor_Shape(PendingObject->Occupy_List()); + + /* + ----------------- Redraw the map to erase old leftovers ------------------ + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); +} + + +/*************************************************************************** + * MapEditClass::Place_Home -- homes the placement object * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/03/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Place_Home(void) +{ + delete PendingObjectPtr; + PendingObjectPtr = NULL; + PendingObject = NULL; + + /* + ** Don't allow this command if we're building a base; the only valid + ** category for base-building is buildings. + */ + if (BaseBuilding) { + return; + } + + /* + ------------------ Loop until we create a valid object ------------------- + */ + LastChoice = 0; + while (!PendingObjectPtr) { + /* + ................... Get house for this object type .................... + */ + if (!Verify_House(LastHouse,Objects[LastChoice])) { + LastHouse = Cycle_House(LastHouse,Objects[LastChoice]); + } + + /* + ....................... Create placement object ....................... + */ + PendingObject = Objects[LastChoice]; + PendingHouse = LastHouse; + PendingObjectPtr = PendingObject->Create_One_Of(HouseClass::As_Pointer(PendingHouse)); + + /* + .................. If this one failed, try the next ................... + */ + if (!PendingObjectPtr) { + PendingObject = NULL; + LastChoice++; + if (LastChoice == ObjCount) { + LastChoice = 0; + } + } + } + + /* + ------------------------ Set the new cursor shape ------------------------ + */ + Set_Cursor_Pos(); + Set_Cursor_Shape(0); + Set_Cursor_Shape(PendingObject->Occupy_List()); + + /* + ----------------- Redraw the map to erase old leftovers ------------------ + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); +} + + +/*************************************************************************** + * MapEditClass::Toggle_House -- toggles current placement object's house * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 11/04/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Toggle_House(void) +{ + TechnoClass *tp; + + /* + ** Don't allow this command if we're building a base; the only valid + ** house for base-building is the one assigned to the base. + */ + if (BaseBuilding) { + return; + } + + /*------------------------------------------------------------------------ + Only techno objects can be owned by a house; return if not a techno + ------------------------------------------------------------------------*/ + if (!PendingObjectPtr->Is_Techno()) { + return; + } + + /*------------------------------------------------------------------------ + Select the house that will own this object + ------------------------------------------------------------------------*/ + LastHouse = Cycle_House(PendingObjectPtr->Owner(), PendingObject); + + /*------------------------------------------------------------------------ + Change the house + ------------------------------------------------------------------------*/ + tp = (TechnoClass *)PendingObjectPtr; + tp->House = HouseClass::As_Pointer(LastHouse); + + /*------------------------------------------------------------------------ + Set house variables to new house + ------------------------------------------------------------------------*/ + PendingHouse = LastHouse; +} + + +/*************************************************************************** + * MapEditClass::Set_House_Buttons -- toggles house buttons for btn list * + * * + * Looks in the given button list for the given GDI, NOD & Neutral button * + * id's. Sets the On/Off state of the buttons based on the given house, * + * only if that button is found in the list. * + * * + * INPUT: * + * house house to set buttons to * + * btnlist ptr to button list to search * + * base_id button ID for GDI; assumes other id's are sequential* + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/23/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Set_House_Buttons(HousesType house, GadgetClass *btnlist, int base_id) +{ + HousesType h; + int id; + TextButtonClass *btn; + + /* + ** Loop through all houses, searching the button list for each one. + */ + for (h = HOUSE_FIRST; h < HOUSE_COUNT; h++) { + + /* + ** Compute the desired button ID; get a pointer to the button + */ + id = (int)h + base_id; + btn = (TextButtonClass *)btnlist->Extract_Gadget(id); + if (btn) { + + /* + ** If this house value is the desired one, turn the button on; + ** otherwise, turn it off. + */ + if (h == house) { + btn->Turn_On(); + } else { + btn->Turn_Off(); + } + } + } +} + + +/*************************************************************************** + * MapEditClass::Start_Trigger_Placement -- enters trigger placement mode * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 12/01/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Start_Trigger_Placement(void) +{ + Set_Default_Mouse(MOUSE_CAN_MOVE); + Override_Mouse_Shape(MOUSE_CAN_MOVE); +} + + +/*************************************************************************** + * MapEditClass::Stop_Trigger_Placement -- exits trigger placement mode * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/01/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Stop_Trigger_Placement(void) +{ + CurTrigger = NULL; + Set_Default_Mouse(MOUSE_NORMAL); + Override_Mouse_Shape(MOUSE_NORMAL); +} + + +/*************************************************************************** + * MapEditClass::Place_Trigger -- assigns trigger to object or cell * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/01/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Place_Trigger(void) +{ + ObjectClass * object=NULL; // Generic object clicked on. + int x,y; + CELL cell; // Cell that was selected. + + /* + -------------------- See if an object was clicked on --------------------- + */ + x = _Kbd->MouseQX; + y = _Kbd->MouseQY; + + /* + ............................ Get cell for x,y ............................ + */ + cell = Click_Cell_Calc(x, y); + + /* + ............... Convert x,y to offset from cell upper-left ............... + */ + x = (x-TacPixelX) % ICON_PIXEL_W; + y = (y-TacPixelY) % ICON_PIXEL_H; + + /* + ......................... Get object at that x,y ......................... + */ + object = Cell_Object(cell, x, y); + + /* + ---------------------- Assign trigger to an object ----------------------- + */ + if (object && TriggerClass::Event_Need_Object(CurTrigger->Event)) { + object->Trigger = CurTrigger; + } else { + + /* + ------------------------ Assign trigger to a cell ------------------------ + */ + if (CurTrigger->Event <= EVENT_OBJECTFIRST) { + Map[cell].IsTrigger = 1; + CellTriggers[cell] = CurTrigger; + } + } + + /* + -------------------------- Force map to redraw --------------------------- + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); +} + + +/*************************************************************************** + * MapEditClass::Start_Base_Building -- starts base-building mode * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/01/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Start_Base_Building(void) +{ + /* + ** Fully build the base so the user can edit it + */ + Build_Base_To(100); + + /* + ** Start placement mode + */ + BaseBuilding = 1; + Start_Placement(); + + /* + ** Force map to redraw + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); +} + + +/*************************************************************************** + * MapEditClass::Cancel_Base_Building -- stops base-building mode * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/01/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Cancel_Base_Building(void) +{ + /* + ** Build the base to the proper amount + */ + Build_Base_To(BasePercent); + + /* + ** Cancel placement mode + */ + Cancel_Placement(); + BaseBuilding = 0; + + /* + ** Force map to redraw + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); +} + + +/*************************************************************************** + * MapEditClass::Build_Base_To -- builds the AI base to the given percent * + * * + * INPUT: * + * percent percentage to build base to * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/01/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Build_Base_To(int percent) +{ + int i; + int num_buildings; + BuildingTypeClass const *objtype; + BuildingClass *obj; + + //ScenarioInit++; + + /* + ** Completely dismantle the base, so we start at a known point + */ + for (i = 0; i < Base.Nodes.Count(); i++) { + if (Base.Is_Built(i)) { + obj = Base.Get_Building(i); + delete obj; + } + } + + /* + ** Compute number of buildings to build + */ + num_buildings = (Base.Nodes.Count() * percent) / 100; + + /* + ** Build the base to the desired amount + */ + for (i = 0; i < num_buildings; i++) { + /* + ** Get a ptr to the type of building to build, create one, and unlimbo it. + */ + objtype = &BuildingTypeClass::As_Reference(Base.Nodes[i].Type); + obj = (BuildingClass *)objtype->Create_One_Of(HouseClass::As_Pointer(Base.House)); + /* + ** If unlimbo fails, error out + */ + if (!obj->Unlimbo(Base.Nodes[i].Coord)) { + delete obj; + CCMessageBox().Process("Unable to build base!"); + return; + } + } + + //ScenarioInit--; +} + + +#endif diff --git a/MAPEDSEL.CPP b/MAPEDSEL.CPP new file mode 100644 index 0000000..e850b4b --- /dev/null +++ b/MAPEDSEL.CPP @@ -0,0 +1,607 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\mapedsel.cpv 2.18 16 Oct 1995 16:49:58 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MAPEDSEL.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : November 18, 1994 * + * * + * Last Update : February 2, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * Object-selection & manipulation routines * + *-------------------------------------------------------------------------* + * Functions: * + * MapEditClass::Select_Object -- selects an object for processing * + * MapEditClass::Select_Next -- selects next object on the map * + * MapEditClass::Popup_Controls -- shows/hides the pop-up object controls* + * MapEditClass::Grab_Object -- grabs the current object * + * MapEditClass::Move_Grabbed_Object -- moves the grabbed object * + * MapEditClass::Change_House -- changes CurrentObject's house * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#ifdef SCENARIO_EDITOR + + +/*************************************************************************** + * Select_Object -- selects an object for processing * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = object selected, -1 = none * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/04/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::Select_Object(void) +{ + ObjectClass *object=NULL; // Generic object clicked on. + int x,y; + CELL cell; // Cell that was selected. + int rc=0; + + /* + -------------------- See if an object was clicked on --------------------- + */ + x = _Kbd->MouseQX; + y = _Kbd->MouseQY; + + /* + ............................ Get cell for x,y ............................ + */ + cell = Click_Cell_Calc(x, y); + + /* + ............... Convert x,y to offset from cell upper-left ............... + */ + x = (x-TacPixelX) % ICON_PIXEL_W; + y = (y-TacPixelY) % ICON_PIXEL_H; + + /* + ......................... Get object at that x,y ......................... + */ + object = Cell_Object(cell, x, y); + + /* + ----------------- If no object, unselect the current one ----------------- + */ + if (!object) { + if (CurrentObject.Count()) { + /* + ................... Unselect all current objects ................... + */ + Unselect_All(); + + /* + ..................... Turn off object controls ..................... + */ + Popup_Controls(); + } + rc = -1; + } else { + + /* + ------------------ Select object only if it's different ------------------ + */ + if (!CurrentObject.Count() || (CurrentObject.Count() && object != CurrentObject[0])) { + /* + ..................... Unselect all current objects .................... + */ + Unselect_All(); + object->Select(); + + /* + ................... Set mouse shape back to normal .................... + */ + Set_Default_Mouse(MOUSE_NORMAL); + Override_Mouse_Shape(MOUSE_NORMAL); + + /* + ....................... Show the popup controls ....................... + */ + Popup_Controls(); + } + } + + /* + -------------------------- Force map to redraw --------------------------- + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + + return(rc); +} + + +/*************************************************************************** + * MapEditClass::Select_Next -- selects next object on the map * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/22/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Select_Next(void) +{ + ObjectClass * obj; + CELL obj_cell; + int smap_w; // screen map width in icons + int smap_h; // screen map height in icons + int cell_x; // cell-x of next object + int cell_y; // cell-y of next object + int tcell_x; // cell-x of TacticalCell + int tcell_y; // cell-y of TacticalCell + + /* + ----------------------- Get next object on the map ----------------------- + */ + obj = Map.Next_Object(CurrentObject[0]); + + if (obj) { + /* + ............... Unselect current object if there is one ............... + */ + Unselect_All(); + + /* + ......................... Select this object .......................... + */ + obj->Select(); + } + + /* + --------------------- Restore mouse shape to normal ---------------------- + */ + Set_Default_Mouse(MOUSE_NORMAL); + Override_Mouse_Shape(MOUSE_NORMAL); + + /* + -------------------------- Show pop-up controls -------------------------- + */ + Popup_Controls(); + + /* + ---------------- Make sure object is shown on the screen ----------------- + */ + /* + ..................... compute screen map dimensions ...................... + */ + smap_w = Lepton_To_Cell(TacLeptonWidth); + smap_h = Lepton_To_Cell(TacLeptonHeight); + + /* + ...................... compute x,y of object's cell ...................... + */ + obj_cell = Coord_Cell(CurrentObject[0]->Coord); + cell_x = Cell_X(obj_cell); + cell_y = Cell_Y(obj_cell); + tcell_x = Coord_XCell(TacticalCoord); + tcell_y = Coord_YCell(TacticalCoord); + + /* + ................... If object is off-screen, move map .................... + */ + if (cell_x < tcell_x) { + tcell_x = cell_x; + } else { + if (cell_x >= tcell_x + smap_w) { + tcell_x = cell_x - smap_w + 1; + } + } + + if (cell_y < tcell_y) { + tcell_y = cell_y; + } else { + if (cell_y >= tcell_y + smap_h) { + tcell_y = cell_y - smap_h + 1; + } + } + + ScenarioInit++; + Set_Tactical_Position(XY_Coord(Cell_To_Lepton(tcell_x), Cell_To_Lepton(tcell_y))); + ScenarioInit--; + + /* + -------------------------- Force map to redraw --------------------------- + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); +} + + +/*************************************************************************** + * MapEditClass::Popup_Controls -- shows/hides the pop-up object controls * + * * + * Call this routine whenever the CurrentObject changes. The routine will * + * selectively enable or disable the popup controls based on whether * + * CurrentObject is NULL, or if it's a Techno object, or what type of * + * Techno object it is. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/22/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Popup_Controls(void) +{ + const TechnoTypeClass * objtype = NULL; + HousesType owner; // object's current owner + int mission_index; // object's current mission + int strength; // object's 0-255 strength value + int i; + + /*------------------------------------------------------------------------ + Remove all buttons from GScreen's button list (so none of them provide + input any more); then, destroy the list by Zapping each button. Then, + we'll have to add at least the MapArea button back to the Input button + list before we return, plus any other buttons to process input for. We + always must add MapArea LAST in the list, so it doesn't intercept the + other buttons' input. + ------------------------------------------------------------------------*/ + Remove_A_Button(*GDIButton); + Remove_A_Button(*NODButton); + Remove_A_Button(*NeutralButton); + Remove_A_Button(*Multi1Button); + Remove_A_Button(*Multi2Button); + Remove_A_Button(*Multi3Button); + Remove_A_Button(*Multi4Button); + Remove_A_Button(*MissionList); + Remove_A_Button(*HealthGauge); + Remove_A_Button(*HealthText); + Remove_A_Button(*FacingDial); + Remove_A_Button(*BaseGauge); + Remove_A_Button(*BaseLabel); + Remove_A_Button(*MapArea); + + /* + ------------------ If no current object, hide the list ------------------- + */ + if (!CurrentObject.Count()) { + Add_A_Button(*BaseGauge); + Add_A_Button(*BaseLabel); + Add_A_Button(*MapArea); + return; + } + + /* + --------------- If not Techno, no need for editing buttons --------------- + */ + if (!CurrentObject[0]->Is_Techno()) { + Add_A_Button(*BaseGauge); + Add_A_Button(*BaseLabel); + Add_A_Button(*MapArea); + return; + } + + objtype = (TechnoTypeClass const *)&CurrentObject[0]->Class_Of(); + + /* + ---------------------- Get object's current values ----------------------- + */ + owner = CurrentObject[0]->Owner(); + mission_index = 0; + for (i = 0; i < NUM_EDIT_MISSIONS; i++) { + if (CurrentObject[0]->Get_Mission() == MapEditMissions[i]) { + mission_index = i; + } + } + strength = CurrentObject[0]->Health_Ratio(); + + + /* + ----------------------------- House buttons ------------------------------ + */ + if (ScenPlayer == SCEN_PLAYER_MPLAYER) { + if (Verify_House(HOUSE_NEUTRAL, &CurrentObject[0]->Class_Of())) { + Add_A_Button(*NeutralButton); + } + if (Verify_House(HOUSE_MULTI1, &CurrentObject[0]->Class_Of())) { + Add_A_Button(*Multi1Button); + } + if (Verify_House(HOUSE_MULTI2, &CurrentObject[0]->Class_Of())) { + Add_A_Button(*Multi2Button); + } + if (Verify_House(HOUSE_MULTI3, &CurrentObject[0]->Class_Of())) { + Add_A_Button(*Multi3Button); + } + if (Verify_House(HOUSE_MULTI4, &CurrentObject[0]->Class_Of())) { + Add_A_Button(*Multi4Button); + } + } else { + if (Verify_House(HOUSE_NEUTRAL, &CurrentObject[0]->Class_Of())) { + Add_A_Button(*NeutralButton); + } + if (Verify_House(HOUSE_BAD, &CurrentObject[0]->Class_Of())) { + Add_A_Button(*NODButton); + } + if (Verify_House(HOUSE_GOOD, &CurrentObject[0]->Class_Of())) { + Add_A_Button(*GDIButton); + } + } + + /* + ........................ Set house button states ......................... + */ + if (Buttons) { + Set_House_Buttons(owner, Buttons, POPUP_GDI); + } + + switch (objtype->What_Am_I()) { + case RTTI_UNITTYPE: + case RTTI_INFANTRYTYPE: + case RTTI_AIRCRAFTTYPE: + MissionList->Set_Selected_Index(mission_index); + HealthGauge->Set_Value(strength); + sprintf(HealthBuf, "%d", CurrentObject[0]->Strength); + FacingDial->Set_Direction(((TechnoClass *)CurrentObject[0])->PrimaryFacing); + + /* + ** Make the list. + */ + Add_A_Button(*MissionList); + Add_A_Button(*HealthGauge); + Add_A_Button(*HealthText); + Add_A_Button(*FacingDial); + break; + + case RTTI_BUILDINGTYPE: + HealthGauge->Set_Value(strength); + sprintf(HealthBuf, "%d", CurrentObject[0]->Strength); + Add_A_Button(*HealthGauge); + Add_A_Button(*HealthText); + + if (objtype->IsTurretEquipped) { + FacingDial->Set_Direction(((TechnoClass *) CurrentObject[0])->PrimaryFacing); + Add_A_Button(*FacingDial); + } + break; + } + + /*------------------------------------------------------------------------ + Add the map area last, so it's "underneath" the other buttons, and won't + intercept input for those buttons. + ------------------------------------------------------------------------*/ + Add_A_Button(*BaseGauge); + Add_A_Button(*BaseLabel); + Add_A_Button(*MapArea); +} + + +/*************************************************************************** + * MapEditClass::Grab_Object -- grabs the current object * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/07/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Grab_Object(void) +{ + CELL cell; + + if (CurrentObject.Count()) { + GrabbedObject = CurrentObject[0]; + + /*------------------------------------------------------------------------ + Find out which cell 'ZoneCell' is in relation to the object's current cell + ------------------------------------------------------------------------*/ + cell = Coord_Cell(GrabbedObject->Coord); + GrabOffset = cell - ZoneCell; + } +} + + +/*************************************************************************** + * MapEditClass::Move_Grabbed_Object -- moves the grabbed object * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = object moved, -1 = it didn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/07/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::Move_Grabbed_Object(void) +{ + COORDINATE new_coord = 0; + int retval = -1; + + /* + --------------------------- Lift up the object --------------------------- + */ + GrabbedObject->Mark(MARK_UP); + + /*------------------------------------------------------------------------ + If infantry, use a free spot in this cell + ------------------------------------------------------------------------*/ + if (GrabbedObject->Is_Infantry()) { + + if (Is_Spot_Free(Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y()))) { + new_coord = Closest_Free_Spot(Pixel_To_Coord(Get_Mouse_X(), + Get_Mouse_Y())); + /*.................................................................. + Clear the occupied bit in this infantry's cell. + ..................................................................*/ + ((InfantryClass *)GrabbedObject)->Clear_Occupy_Bit(GrabbedObject->Coord); +// Map[Coord_Cell(GrabbedObject->Coord)].Flag.Composite &= +// ~(1 << CellClass::Spot_Index(GrabbedObject->Coord)); + } else { + new_coord = NULL; + } + + } else { + + /*------------------------------------------------------------------------ + Non-infantry: use cell's center coordinate + ------------------------------------------------------------------------*/ + new_coord = Cell_Coord(ZoneCell + GrabOffset); + + if (GrabbedObject->What_Am_I() == RTTI_BUILDING || + GrabbedObject->What_Am_I() == RTTI_TERRAIN) { + + new_coord &= 0xFF00FF00L; + } + + /* + ................ Try to place object at new coordinate ................ + */ + if (GrabbedObject->Can_Enter_Cell(Coord_Cell(new_coord)) != MOVE_OK) { + new_coord = NULL; + } + } + if (new_coord != NULL) { + /* + ** If this object is part of the AI's Base list, change the coordinate + ** in the Base's Node list. + */ + if (GrabbedObject->What_Am_I()==RTTI_BUILDING && + Base.Get_Node((BuildingClass *)GrabbedObject)) + Base.Get_Node((BuildingClass *)GrabbedObject)->Coord = new_coord; + + GrabbedObject->Coord = new_coord; + retval = 0; + } + GrabbedObject->Mark(MARK_DOWN); + + /*------------------------------------------------------------------------ + For infantry, set the bit in its new cell marking that spot as occupied. + ------------------------------------------------------------------------*/ + if (GrabbedObject->Is_Infantry()) { + ((InfantryClass *)GrabbedObject)->Set_Occupy_Bit(new_coord); +// Map[Coord_Cell(new_coord)].Flag.Composite |= +// (1 << CellClass::Spot_Index(new_coord)); + } + + /*------------------------------------------------------------------------ + Re-select the object, and reset the mouse pointer + ------------------------------------------------------------------------*/ + Set_Default_Mouse(MOUSE_NORMAL); + Override_Mouse_Shape(MOUSE_NORMAL); + + Flag_To_Redraw(true); + + return(retval); +} + + +/*************************************************************************** + * MapEditClass::Change_House -- changes CurrentObject's house * + * * + * INPUT: * + * newhouse house to change to * + * * + * OUTPUT: * + * 1 = house was changed, 0 = it wasn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/17/1994 BR : Created. * + *=========================================================================*/ +bool MapEditClass::Change_House(HousesType newhouse) +{ + TechnoClass *tp; + + /*------------------------------------------------------------------------ + Return if no current object + ------------------------------------------------------------------------*/ + if (!CurrentObject.Count()) { + return(false); + } + + /*------------------------------------------------------------------------ + Only techno objects can be owned by a house; return if not a techno + ------------------------------------------------------------------------*/ + if (!CurrentObject[0]->Is_Techno()) { + return(false); + } + + /*------------------------------------------------------------------------ + You can't change the house if the object is part of the AI's Base. + ------------------------------------------------------------------------*/ + if (CurrentObject[0]->What_Am_I()==RTTI_BUILDING && Base.Is_Node((BuildingClass *)CurrentObject[0])) { + return(false); + } + + /*------------------------------------------------------------------------ + Verify that the target house exists + ------------------------------------------------------------------------*/ + if (HouseClass::As_Pointer(newhouse)==NULL) { + return(false); + } + + /*------------------------------------------------------------------------ + Verify that this is a valid owner + ------------------------------------------------------------------------*/ + if (!Verify_House(newhouse, &CurrentObject[0]->Class_Of())) { + return(false); + } + + /*------------------------------------------------------------------------ + Change the house + ------------------------------------------------------------------------*/ + tp = (TechnoClass *)CurrentObject[0]; + tp->House = HouseClass::As_Pointer(newhouse); + + return(true); +} + + +#endif diff --git a/MAPEDTM.CPP b/MAPEDTM.CPP new file mode 100644 index 0000000..5edc2dc --- /dev/null +++ b/MAPEDTM.CPP @@ -0,0 +1,2077 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\mapedtm.cpv 2.18 16 Oct 1995 16:52:16 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MAPEDTM.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 7, 1994 * + * * + * Last Update : April 9, 1996 [BRR] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * MapEditClass::Handle_Teams -- main team-dialog-handling function * + * MapEditClass::Select_Team -- user selects a team from a list * + * MapEditClass::Edit_Team -- user edits a team's options * + * MapEditClass::Team_Members -- user picks makeup of a team * + * MapEditClass::Build_Mission_list -- fills in mission list box * + * MapEditClass::Draw_Member -- Draws a member of the team dialog box. * + * MapEditClass::Team_Members -- Team members dialog * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#ifdef SCENARIO_EDITOR + + +/*************************************************************************** + * MapEditClass::Handle_Teams -- main team-dialog-handling function * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/08/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Handle_Teams(char const * caption) +{ + int rc; + + /*------------------------------------------------------------------------ + Team dialog processing loop: + - Invoke the team selection dialog. If a team's selected, break + & return + - If user wants to edit the current team, do so + - If user wants to create new team, new a TeamTypeClass & edit it + - If user wants to delete team, delete the current team + - Keep looping until 'OK' + ------------------------------------------------------------------------*/ + for (;;) { + + /* + ............................. Select team ............................. + */ + rc = Select_Team(caption); + + /* + ............................. 'OK'; break ............................. + */ + if (rc == 0) { + break; + } else { + + /* + ............................... 'Edit' ................................ + */ + if (rc == 1 && CurTeam) { + if (Edit_Team()==0) { + Changed = 1; + } + } else { + + /* + ................................ 'New' ................................ + */ + if (rc == 2) { + /* + ........................ Create a new team ......................... + */ + CurTeam = new TeamTypeClass(); + if (CurTeam) { + /* + ................... delete it if user cancels ................... + */ + if (Edit_Team()==-1) { + delete CurTeam; + CurTeam = NULL; + } else { + Changed = 1; + } + } else { + + /* + ................. Unable to create; issue warning .................. + */ + CCMessageBox().Process("No more teams available."); + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + } + } else { + + /* + .............................. 'Delete' ............................... + */ + if (rc==3) { + if (CurTeam) { + CurTeam->Remove(); + CurTeam = NULL; + } + } + } + } + } + } +} + + +/*************************************************************************** + * MapEditClass::Select_Team -- user selects a team from a list * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Teams ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ³ * + * ³ ³ Name House Class:Count,Class:Count ³³ ³ * + * ³ ³ Name House Class:Count,Class:Count ÃÄ´ ³ * + * ³ ³ Name House Class:Count,Class:Count ³ ³ ³ * + * ³ ³ Name House Class:Count,Class:Count ³ ³ ³ * + * ³ ³ ³ ³ ³ * + * ³ ³ ³ ³ ³ * + * ³ ³ ÃÄ´ ³ * + * ³ ³ ³³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ³ * + * ³ ³ * + * ³ [Edit] [New] [Delete] [OK] ³ * + * ³ ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = OK, 1 = Edit, 2 = New, 3 = Delete * + * * + * WARNINGS: * + * Uses HIDBUFF. * + * * + * HISTORY: * + * 12/08/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::Select_Team(char const * caption) +{ + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + enum { + D_DIALOG_W = 528, // dialog width + D_DIALOG_H = 290, // dialog height + D_DIALOG_X = ((640 - D_DIALOG_W) / 2), // centered x-coord + D_DIALOG_Y = ((400 - D_DIALOG_H) / 2), // centered y-coord + D_DIALOG_CX = D_DIALOG_X + (D_DIALOG_W / 2), // coord of x-center + + D_TXT8_H = 22, // ht of 8-pt text + D_MARGIN = 14, // margin width/height + + D_LIST_W = 500, + D_LIST_H = 208, + D_LIST_X = D_DIALOG_X + D_MARGIN, + D_LIST_Y = D_DIALOG_Y + D_MARGIN + D_TXT8_H, + + D_EDIT_W = 90, + D_EDIT_H = 18, + D_EDIT_X = D_DIALOG_X + (D_DIALOG_W / 8) - (D_EDIT_W / 2), + D_EDIT_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_EDIT_H, + + D_NEW_W = 90, + D_NEW_H = 18, + D_NEW_X = D_DIALOG_X + (D_DIALOG_W / 8) * 3 - (D_NEW_W / 2), + D_NEW_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_NEW_H, + + D_DELETE_W = 90, + D_DELETE_H = 18, + D_DELETE_X = D_DIALOG_X + (D_DIALOG_W / 8) * 5 - (D_DELETE_W / 2), + D_DELETE_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_DELETE_H, + + D_OK_W = 90, + D_OK_H = 18, + D_OK_X = D_DIALOG_X + (D_DIALOG_W / 8) * 7 - (D_OK_W / 2), + D_OK_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_OK_H, + + TEAMTXT_LEN = 43, // max length of a team entry + }; + + /*........................................................................ + Button enumerations: + ........................................................................*/ + enum { + TEAM_LIST=100, + BUTTON_EDIT, + BUTTON_NEW, + BUTTON_DELETE, + BUTTON_OK, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display; // requested redraw level + bool process; // loop while true + char *teamtext[TEAMTYPE_MAX + 1]; // text for defined teams + KeyNumType input; // user input + bool edit_team = false; // true = user wants to edit + bool new_team = false; // true = user wants to new + bool del_team = false; // true = user wants to new + int i; // loop counters + int j; + int def_idx; // default list index + static int tabs[] = {120, 180}; // list box tab stops + char txt[10]; +// int housetxt; + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands = NULL; // the button list + + ListClass teamlist (TEAM_LIST, + D_LIST_X, D_LIST_Y, D_LIST_W, D_LIST_H, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + Hires_Retrieve("BTN-UP.SHP"), + Hires_Retrieve("BTN-DN.SHP")); + + TextButtonClass editbtn (BUTTON_EDIT, "Edit", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_EDIT_X, D_EDIT_Y, D_EDIT_W, D_EDIT_H); + + TextButtonClass newbtn (BUTTON_NEW, "New", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NEW_X, D_NEW_Y, D_NEW_W, D_NEW_H); + + TextButtonClass deletebtn (BUTTON_DELETE, "Delete", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_DELETE_X, D_DELETE_Y, D_DELETE_W, D_DELETE_H); + + TextButtonClass okbtn (BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_OK_X, D_OK_Y, D_OK_W, D_OK_H); + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + + /* + ........................... Fill in team names ........................... + */ + def_idx = 0; + for (i = 0; i < TeamTypes.Count(); i++) { + /* + ................... Generate string for this team ..................... + */ + //teamtext[i] = (char *)HidPage.Get_Graphic_Buffer()->Get_Buffer() + TEAMTXT_LEN * i; + teamtext[i] = new char[255]; + + /* + ........................ Fill in name & house ......................... + */ + strcpy(teamtext[i],TeamTypes.Ptr(i)->IniName); + strcat(teamtext[i],"\t"); + strcat(teamtext[i], HouseTypeClass::As_Reference(TeamTypes.Ptr(i)->House).Suffix); + strcat(teamtext[i],"\t"); + + /* + ................ Fill in class & count for all classes ................ + */ + for (j = 0; j < TeamTypes.Ptr(i)->ClassCount; j++) { + sprintf (txt,"%s:%d", TeamTypes.Ptr(i)->Class[j]->IniName, TeamTypes.Ptr(i)->DesiredNum[j]); + + /*.................................................................. + Add entry if there's room; break otherwise + (+ 3 for the ", " and the NULL; +3 again for the "..." for the next + entry) + ..................................................................*/ + if (strlen(txt) + strlen(teamtext[i]) + 6 < TEAMTXT_LEN) { + if (j > 0) { + strcat(teamtext[i],", "); + } + strcat(teamtext[i],txt); + } else { + strcat(teamtext[i], "..."); + break; + } + } + + /* + .................. Set def_idx if this is CurTeam ..................... + */ + if (TeamTypes.Ptr(i)==CurTeam) { + def_idx = i; + } + + /* + ........................... Add to list box ........................... + */ + teamlist.Add_Item(teamtext[i]); + } + + /* + ....................... Set CurTeam if it isn't .......................... + */ + if (TeamTypes.Count()==0) { + CurTeam = NULL; + } else { + if (!CurTeam) { + CurTeam = TeamTypes.Ptr(def_idx); + } + } + + /* + ............................ Create the list ............................. + */ + commands = &teamlist; + editbtn.Add_Tail(*commands); + newbtn.Add_Tail(*commands); + deletebtn.Add_Tail(*commands); + okbtn.Add_Tail(*commands); + + /* + ------------------------ Init tab stops for list ------------------------- + */ + teamlist.Set_Tabs(tabs); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + display = REDRAW_ALL; + process = true; + while (process) { + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + + /* + ...................... Display the dialog box ...................... + */ + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + Dialog_Box(D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W, D_DIALOG_H); + Draw_Caption(TXT_NONE, D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W); + + /* + ....................... Draw the captions ....................... + */ + Fancy_Text_Print(caption, D_DIALOG_CX, D_DIALOG_Y + D_MARGIN, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + /* + ........................ Redraw the buttons ........................ + */ + if (display >= REDRAW_BUTTONS) { + commands->Draw_All(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (TEAM_LIST | KN_BUTTON): + def_idx = teamlist.Current_Index(); + if (def_idx < TeamTypes.Count()) + CurTeam = TeamTypes.Ptr(def_idx); + break; + + case (BUTTON_EDIT | KN_BUTTON): + if (CurTeam) { // only allow if there's one selected + process = false; + edit_team = true; + } + break; + + case (BUTTON_NEW | KN_BUTTON): + process = false; + new_team = true; + break; + + case (BUTTON_DELETE | KN_BUTTON): + process = false; + del_team = true; + break; + + case (KN_RETURN): + case (BUTTON_OK | KN_BUTTON): + process = false; + break; + } + } + + /* + --------------------------- Redraw the display --------------------------- + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + + for (i = 0; i < TeamTypes.Count(); i++) { + delete [] teamtext[i]; + } + if (edit_team) return(1); + if (new_team) return(2); + if (del_team) return(3); + return(0); +} + + +/*************************************************************************** + * MapEditClass::Edit_Team -- user edits a team's options * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Team Editor ³ * + * ³ ³ * + * ³ Name ______ [Roundabout] ³ * + * ³ Priority ______ [ GDI ] [Learning ] ³ * + * ³ Max Num ______ [ NOD ] [Suicide ] ³ * + * ³ Init Num ______ [Autocreate] ³ * + * ³ Fear ______ [Mercenary ] ³ * + * ³ [Prebuild ] ³ * + * ³ [Reinforce ] ³ * + * ³ ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ³ * + * ³ ³ ³^³ ³ ³^³ ³ * + * ³ ³ ÃÄ´ [Add >>] ³ ÃÄ´ ³ * + * ³ ³ ³ ³ [Insert] ³ ³ ³ ³ * + * ³ ³ ³ ³ [Delete] ³ ³ ³ ³ * + * ³ ³ ÃÄ´ ____ ³ ÃÄ´ ³ * + * ³ ³ ³v³ ³ ³v³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ³ * + * ³ ³ * + * ³ [Members] [Cancel] [OK] ³ * + * ³ ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = OK, -1 = cancel * + * * + * WARNINGS: * + * CurTeam must NOT be NULL when this function is called. * + * This routine invokes the Members dialog, which uses HIDBUFF. * + * * + * HISTORY: * + * 12/08/1994 BR : Created. * + *=========================================================================*/ +int MapEditClass::Edit_Team(void) +{ + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + enum { + D_DIALOG_W = 516, + D_DIALOG_H = 376, + D_DIALOG_X = ((640 - D_DIALOG_W) / 2), + D_DIALOG_Y = ((400 - D_DIALOG_H) / 2), + D_DIALOG_CX = D_DIALOG_X + (D_DIALOG_W / 2), + + D_TXT8_H = 22, + D_MARGIN = 14, + + D_NAME_W = 120, + D_NAME_H = 18, + D_NAME_X = D_DIALOG_X + D_MARGIN + 100, + D_NAME_Y = D_DIALOG_Y + D_MARGIN + D_TXT8_H, + + D_PRIORITY_W = 120, + D_PRIORITY_H = 18, + D_PRIORITY_X = D_DIALOG_X + D_MARGIN + 100, + D_PRIORITY_Y = D_NAME_Y + D_NAME_H, + + D_MAXNUM_W = 120, + D_MAXNUM_H = 18, + D_MAXNUM_X = D_DIALOG_X + D_MARGIN + 100, + D_MAXNUM_Y = D_PRIORITY_Y + D_PRIORITY_H, + + D_INITNUM_W = 120, + D_INITNUM_H = 18, + D_INITNUM_X = D_DIALOG_X + D_MARGIN + 100, + D_INITNUM_Y = D_MAXNUM_Y + D_MAXNUM_H, + + D_FEAR_W = 120, + D_FEAR_H = 18, + D_FEAR_X = D_DIALOG_X + D_MARGIN + 100, + D_FEAR_Y = D_INITNUM_Y + D_INITNUM_H, + + D_GDI_W = 100, + D_GDI_H = 18, + D_GDI_X = D_NAME_X + D_NAME_W + D_MARGIN, + D_GDI_Y = D_NAME_Y + D_NAME_H + D_NAME_H / 2, + + D_NOD_W = 100, + D_NOD_H = 18, + D_NOD_X = D_NAME_X + D_NAME_W + D_MARGIN, + D_NOD_Y = D_GDI_Y + D_GDI_H, + + D_NEU_W = 100, + D_NEU_H = 18, + D_NEU_X = D_NAME_X + D_NAME_W + D_MARGIN, + D_NEU_Y = D_NOD_Y + D_NOD_H, + + D_MULTI1_W = 50, + D_MULTI1_H = 18, + D_MULTI1_X = D_GDI_X, + D_MULTI1_Y = D_GDI_Y, + + D_MULTI2_W = 50, + D_MULTI2_H = 18, + D_MULTI2_X = D_GDI_X + D_MULTI2_W, + D_MULTI2_Y = D_GDI_Y, + + D_MULTI3_W = 50, + D_MULTI3_H = 18, + D_MULTI3_X = D_NOD_X, + D_MULTI3_Y = D_NOD_Y, + + D_MULTI4_W = 50, + D_MULTI4_H = 18, + D_MULTI4_X = D_NOD_X + D_MULTI4_W, + D_MULTI4_Y = D_NOD_Y, + + D_ROUNDABOUT_W = 130, + D_ROUNDABOUT_H = 18, + D_ROUNDABOUT_X = D_DIALOG_X + D_DIALOG_W - D_MARGIN - D_ROUNDABOUT_W, + D_ROUNDABOUT_Y = D_DIALOG_Y + D_MARGIN + D_TXT8_H - 10, + + D_LEARNING_W = D_ROUNDABOUT_W, + D_LEARNING_H = 18, + D_LEARNING_X = D_ROUNDABOUT_X, + D_LEARNING_Y = D_ROUNDABOUT_Y + D_ROUNDABOUT_H, + + D_SUICIDE_W = D_ROUNDABOUT_W, + D_SUICIDE_H = 18, + D_SUICIDE_X = D_ROUNDABOUT_X, + D_SUICIDE_Y = D_LEARNING_Y + D_LEARNING_H, + + D_AUTOCREATE_W = D_ROUNDABOUT_W, + D_AUTOCREATE_H = 18, + D_AUTOCREATE_X = D_ROUNDABOUT_X, + D_AUTOCREATE_Y = D_SUICIDE_Y + D_SUICIDE_H, + + D_MERCENARY_W = D_ROUNDABOUT_W, + D_MERCENARY_H = 18, + D_MERCENARY_X = D_ROUNDABOUT_X, + D_MERCENARY_Y = D_AUTOCREATE_Y + D_AUTOCREATE_H, + + D_PREBUILT_W = D_ROUNDABOUT_W, + D_PREBUILT_H = 18, + D_PREBUILT_X = D_ROUNDABOUT_X, + D_PREBUILT_Y = D_MERCENARY_Y + D_MERCENARY_H, + + D_REINFORCE_W = D_ROUNDABOUT_W, + D_REINFORCE_H = 18, + D_REINFORCE_X = D_ROUNDABOUT_X, + D_REINFORCE_Y = D_PREBUILT_Y + D_PREBUILT_H, + + D_MISSION1_W = 180, + D_MISSION1_H = 128, + D_MISSION1_X = D_DIALOG_X + D_MARGIN, + D_MISSION1_Y = D_REINFORCE_Y + D_REINFORCE_H + D_MARGIN, + + D_MISSION2_W = 180, + D_MISSION2_H = 128, + D_MISSION2_X = D_DIALOG_X + D_DIALOG_W - D_MARGIN - D_MISSION2_W, + D_MISSION2_Y = D_MISSION1_Y, + + D_ADD_W = 100, + D_ADD_H = 18, + D_ADD_X = D_MISSION1_X + D_MISSION1_W + D_MARGIN, + D_ADD_Y = D_MISSION1_Y + D_ADD_H, + + D_INSERT_W = 100, + D_INSERT_H = 18, + D_INSERT_X = D_MISSION1_X + D_MISSION1_W + D_MARGIN, + D_INSERT_Y = D_ADD_Y + D_ADD_H, + + D_DEL_W = 100, + D_DEL_H = 18, + D_DEL_X = D_MISSION1_X + D_MISSION1_W + D_MARGIN, + D_DEL_Y = D_INSERT_Y + D_INSERT_H, + + D_ARG_W = 100, + D_ARG_H = 18, + D_ARG_X = D_MISSION1_X + D_MISSION1_W + D_MARGIN, + D_ARG_Y = D_DEL_Y + D_DEL_H, + + D_MEMBERS_W = 100, + D_MEMBERS_H = 18, + D_MEMBERS_X = D_DIALOG_X + (D_DIALOG_W / 6) - D_MEMBERS_W / 2, + D_MEMBERS_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_MEMBERS_H, + + D_CANCEL_W = 100, + D_CANCEL_H = 18, + D_CANCEL_X = D_DIALOG_X + (D_DIALOG_W / 6) * 3 - D_CANCEL_W / 2, + D_CANCEL_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_CANCEL_H, + + D_OK_W = 100, + D_OK_H = 18, + D_OK_X = D_DIALOG_X + (D_DIALOG_W / 6) * 5 - D_OK_W / 2, + D_OK_Y = D_DIALOG_Y + D_DIALOG_H - D_MARGIN - D_OK_H, + }; + + /*........................................................................ + Button enumerations: + ........................................................................*/ + enum { + BUTTON_NAME=100, + BUTTON_RECRUIT, + BUTTON_MAXNUM, + BUTTON_INITNUM, + BUTTON_FEAR, + BUTTON_GDI, + BUTTON_NOD, + BUTTON_NEU, + BUTTON_JP, // placeholder + BUTTON_MULTI1, + BUTTON_MULTI2, + BUTTON_MULTI3, + BUTTON_MULTI4, + BUTTON_MULTI5, + BUTTON_MULTI6, + BUTTON_ROUNDABOUT, + BUTTON_LEARNING, + BUTTON_SUICIDE, + BUTTON_AUTO, + BUTTON_MERCENARY, + BUTTON_PREBUILT, + BUTTON_REINFORCE, + BUTTON_MISSION1, + BUTTON_MISSION2, + BUTTON_ADD, + BUTTON_INSERT, + BUTTON_DEL, + BUTTON_ARG, + BUTTON_MEMBERS, + BUTTON_OK, + BUTTON_CANCEL, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables: + ........................................................................*/ + RedrawType display; // requested redraw level + bool process; // loop while true + KeyNumType input; + bool cancel = false; // true = user cancels + char name_buf[12]; + char recr_buf[4]; + char maxnum_buf[4]; + char initnum_buf[4]; + char fear_buf[4]; + HousesType house; + int roundabout; + int learning; + int suicide; + int autocreate; + int mercenary; + int prebuilt; + int reinforce; + int missioncount; + TeamMissionStruct missions[TeamTypeClass::MAX_TEAM_MISSIONS]; + char missionbuf[TeamTypeClass::MAX_TEAM_MISSIONS][20]; + int curmission; // currently-selected mission index + + char arg_buf[4] = {0}; + static int tabs[] = {130, 180}; // list box tab stops + int i,j; + + /*........................................................................ + Buttons: + ........................................................................*/ + ControlClass *commands; + EditClass name_edt (BUTTON_NAME, + name_buf, 8, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NAME_X, D_NAME_Y, D_NAME_W, D_NAME_H, EditClass::ALPHANUMERIC); + + EditClass recr_edt (BUTTON_RECRUIT, + recr_buf, 3, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_PRIORITY_X, D_PRIORITY_Y, D_PRIORITY_W, D_PRIORITY_H, EditClass::NUMERIC); + + EditClass maxnum_edt (BUTTON_MAXNUM, + maxnum_buf, 3, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MAXNUM_X, D_MAXNUM_Y, D_MAXNUM_W, D_MAXNUM_H, EditClass::NUMERIC); + + EditClass initnum_edt (BUTTON_INITNUM, + initnum_buf, 3, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_INITNUM_X, D_INITNUM_Y, D_INITNUM_W, D_INITNUM_H, EditClass::NUMERIC); + + EditClass fear_edt (BUTTON_FEAR, + fear_buf, 3, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_FEAR_X, D_FEAR_Y, D_FEAR_W, D_FEAR_H, EditClass::NUMERIC); + + TextButtonClass gdibtn (BUTTON_GDI, "GDI", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_GDI_X, D_GDI_Y, D_GDI_W, D_GDI_H); + + TextButtonClass nodbtn (BUTTON_NOD, "NOD", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NOD_X, D_NOD_Y, D_NOD_W, D_NOD_H); + + TextButtonClass neubtn (BUTTON_NEU, "NEUTRAL", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_NEU_X, D_NEU_Y, D_NEU_W, D_NEU_H); + + TextButtonClass multi1btn (BUTTON_MULTI1, "M1", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI1_X, D_MULTI1_Y, D_MULTI1_W, D_MULTI1_H); + + TextButtonClass multi2btn (BUTTON_MULTI2, "M2", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI2_X, D_MULTI2_Y, D_MULTI2_W, D_MULTI2_H); + + TextButtonClass multi3btn (BUTTON_MULTI3, "M3", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI3_X, D_MULTI3_Y, D_MULTI3_W, D_MULTI3_H); + + TextButtonClass multi4btn (BUTTON_MULTI4, "M4", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI4_X, D_MULTI4_Y, D_MULTI4_W, D_MULTI4_H); + + TextButtonClass roundbtn (BUTTON_ROUNDABOUT, "Roundabout", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_ROUNDABOUT_X, D_ROUNDABOUT_Y, D_ROUNDABOUT_W, D_ROUNDABOUT_H); + + TextButtonClass learnbtn (BUTTON_LEARNING, "Learning", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_LEARNING_X, D_LEARNING_Y, D_LEARNING_W, D_LEARNING_H); + + TextButtonClass suicidebtn (BUTTON_SUICIDE, "Suicide", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_SUICIDE_X, D_SUICIDE_Y, D_SUICIDE_W, D_SUICIDE_H); + + TextButtonClass autocreatebtn (BUTTON_AUTO, "Autocreate", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_AUTOCREATE_X, D_AUTOCREATE_Y, D_AUTOCREATE_W, D_AUTOCREATE_H); + + TextButtonClass mercbtn (BUTTON_MERCENARY, "Mercenary", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MERCENARY_X, D_MERCENARY_Y, D_MERCENARY_W, D_MERCENARY_H); + + TextButtonClass prebuiltbtn (BUTTON_PREBUILT, "Prebuild", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_PREBUILT_X, D_PREBUILT_Y, D_PREBUILT_W, D_PREBUILT_H); + + TextButtonClass reinforcebtn (BUTTON_REINFORCE, "Reinforce", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_REINFORCE_X, D_REINFORCE_Y, D_REINFORCE_W, D_REINFORCE_H); + + ListClass missionlist1 (BUTTON_MISSION1, + D_MISSION1_X, D_MISSION1_Y, D_MISSION1_W, D_MISSION1_H, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + Hires_Retrieve("BTN-UP.SHP"), + Hires_Retrieve("BTN-DN.SHP")); + + ListClass missionlist2 (BUTTON_MISSION2, + D_MISSION2_X, D_MISSION2_Y, D_MISSION2_W, D_MISSION2_H, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + Hires_Retrieve("BTN-UP.SHP"), + Hires_Retrieve("BTN-DN.SHP")); + + TextButtonClass addbtn (BUTTON_ADD, "Add >>", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_ADD_X, D_ADD_Y, D_ADD_W, D_ADD_H); + + TextButtonClass insertbtn (BUTTON_INSERT, "Insert", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_INSERT_X, D_INSERT_Y, D_INSERT_W, D_INSERT_H); + + TextButtonClass delbtn (BUTTON_DEL, "Delete", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_DEL_X, D_DEL_Y, D_DEL_W, D_DEL_H); + + EditClass arg_edt (BUTTON_ARG, arg_buf, 4, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_ARG_X, D_ARG_Y, D_ARG_W, D_ARG_H, EditClass::ALPHANUMERIC); + + TextButtonClass membersbtn (BUTTON_MEMBERS, "Members", + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MEMBERS_X, D_MEMBERS_Y, D_MEMBERS_W, D_MEMBERS_H); + + TextButtonClass okbtn (BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_OK_X, D_OK_Y, D_OK_W, D_OK_H); + + TextButtonClass cancelbtn (BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_CANCEL_X, D_CANCEL_Y, D_CANCEL_W, D_CANCEL_H); + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + + /* + ........................... Copy team's state ............................ + */ + strcpy(name_buf,CurTeam->IniName); + sprintf(recr_buf,"%d",CurTeam->RecruitPriority); + sprintf(maxnum_buf,"%d",CurTeam->MaxAllowed); + sprintf(initnum_buf,"%d",CurTeam->InitNum); + sprintf(fear_buf,"%d",CurTeam->Fear); + roundabout = CurTeam->IsRoundAbout; + learning = CurTeam->IsLearning; + suicide = CurTeam->IsSuicide; + house = CurTeam->House; + autocreate = CurTeam->IsAutocreate; + mercenary = CurTeam->IsMercenary; + prebuilt = CurTeam->IsPrebuilt; + reinforce = CurTeam->IsReinforcable; + + /* + ......................... Fill in mission lists .......................... + */ + for (i = 0; i < TMISSION_COUNT; i++) { + missionlist1.Add_Item(TeamTypeClass::Name_From_Mission((TeamMissionType)i)); + } + + missioncount = CurTeam->MissionCount; + for (i = 0; i < missioncount; i++) { + missions[i] = CurTeam->MissionList[i]; + } + Build_Mission_List(missioncount, missions, missionbuf, &missionlist2); + + curmission = 0; + if (missioncount) { + if (missions[curmission].Mission == TMISSION_MOVE || missions[curmission].Mission == TMISSION_UNLOAD) { + sprintf(arg_buf,"%c",missions[curmission].Argument + 'A'); + } else { + sprintf(arg_buf,"%d",missions[curmission].Argument); + } + } + missionlist2.Set_Tabs(tabs); + + /* + ......................... Init the button states ......................... + */ + name_edt.Set_Text(name_buf,8); + recr_edt.Set_Text(recr_buf,3); + maxnum_edt.Set_Text(maxnum_buf,3); + initnum_edt.Set_Text(initnum_buf,3); + fear_edt.Set_Text(fear_buf,3); + arg_edt.Set_Text(arg_buf,3); + + if (roundabout) { + roundbtn.Turn_On(); + } + if (learning) { + learnbtn.Turn_On(); + } + if (suicide) { + suicidebtn.Turn_On(); + } + if (autocreate) { + autocreatebtn.Turn_On(); + } + if (mercenary) { + mercbtn.Turn_On(); + } + if (reinforce) { + reinforcebtn.Turn_On(); + } + if (prebuilt) { + prebuiltbtn.Turn_On(); + } + + /* + ............................ Create the list ............................. + */ + commands = &okbtn; + cancelbtn.Add_Tail(*commands); + membersbtn.Add_Tail(*commands); + + name_edt.Add_Tail(*commands); + recr_edt.Add_Tail(*commands); + maxnum_edt.Add_Tail(*commands); + initnum_edt.Add_Tail(*commands); + fear_edt.Add_Tail(*commands); + + gdibtn.Add_Tail(*commands); + nodbtn.Add_Tail(*commands); + neubtn.Add_Tail(*commands); + + roundbtn.Add_Tail(*commands); + learnbtn.Add_Tail(*commands); + suicidebtn.Add_Tail(*commands); + autocreatebtn.Add_Tail(*commands); + mercbtn.Add_Tail(*commands); + prebuiltbtn.Add_Tail(*commands); + reinforcebtn.Add_Tail(*commands); + + missionlist1.Add_Tail(*commands); + missionlist2.Add_Tail(*commands); + addbtn.Add_Tail(*commands); + insertbtn.Add_Tail(*commands); + delbtn.Add_Tail(*commands); + arg_edt.Add_Tail(*commands); + + Set_House_Buttons (house, commands, BUTTON_GDI); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + display = REDRAW_ALL; + process = true; + while (process) { + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + + /* + ...................... Display the dialog box ...................... + */ + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + Dialog_Box(D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W, D_DIALOG_H); + + Draw_Caption(TXT_NONE, D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W); + /* + ....................... Draw the captions ....................... + */ + Fancy_Text_Print("Team Edit", D_DIALOG_CX, D_DIALOG_Y + D_MARGIN, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print("Name", D_NAME_X - 5, D_NAME_Y, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print("Priority", D_PRIORITY_X - 5, D_PRIORITY_Y, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print("Max Num", D_MAXNUM_X - 5, D_MAXNUM_Y, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print("Init Num", D_INITNUM_X - 5, D_INITNUM_Y, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print("Fear", D_FEAR_X - 5, D_FEAR_Y, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + /* + ........................ Redraw the buttons ........................ + */ + if (display >= REDRAW_BUTTONS) { + commands->Draw_All(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (BUTTON_NAME | KN_BUTTON): + break; + + case (BUTTON_RECRUIT | KN_BUTTON): + break; + + case (BUTTON_MAXNUM | KN_BUTTON): + break; + + case (BUTTON_INITNUM | KN_BUTTON): + break; + + case (BUTTON_FEAR | KN_BUTTON): + break; + + /*.................................................................. + Toggle RoundAbout + ..................................................................*/ + case (BUTTON_ROUNDABOUT | KN_BUTTON): + if (roundabout) { + roundabout = 0; + roundbtn.Turn_Off(); + } else { + roundabout = 1; + roundbtn.Turn_On(); + } + break; + + /*.................................................................. + Toggle Learning + ..................................................................*/ + case (BUTTON_LEARNING | KN_BUTTON): + if (learning) { + learning = 0; + learnbtn.Turn_Off(); + } else { + learning = 1; + learnbtn.Turn_On(); + } + break; + + /*.................................................................. + Toggle Suicide + ..................................................................*/ + case (BUTTON_SUICIDE | KN_BUTTON): + if (suicide) { + suicide = 0; + suicidebtn.Turn_Off(); + } else { + suicide = 1; + suicidebtn.Turn_On(); + } + break; + + /*.................................................................. + Toggle Spy + ..................................................................*/ + case (BUTTON_AUTO | KN_BUTTON): + if (autocreate) { + autocreate = 0; + autocreatebtn.Turn_Off(); + } else { + autocreate = 1; + autocreatebtn.Turn_On(); + } + break; + + /*.................................................................. + Toggle Mercenary + ..................................................................*/ + case (BUTTON_MERCENARY | KN_BUTTON): + if (mercenary) { + mercenary = 0; + mercbtn.Turn_Off(); + } else { + mercenary = 1; + mercbtn.Turn_On(); + } + break; + + case (BUTTON_PREBUILT | KN_BUTTON): + if (prebuilt) { + prebuilt = 0; + prebuiltbtn.Turn_Off(); + } else { + prebuilt = 1; + prebuiltbtn.Turn_On(); + } + break; + + case (BUTTON_REINFORCE | KN_BUTTON): + if (reinforce) { + reinforce = 0; + reinforcebtn.Turn_Off(); + } else { + reinforce = 1; + reinforcebtn.Turn_On(); + } + break; + + /*.................................................................. + Select a Mission on the left-hand mission list + ..................................................................*/ + case (BUTTON_MISSION1 | KN_BUTTON): + break; + + /*.................................................................. + Select a Mission on the right-hand mission list; update the Argument + field to reflect the current value + ..................................................................*/ + case (BUTTON_MISSION2 | KN_BUTTON): + if (missionlist2.Count() > 0 && + missionlist2.Current_Index() != curmission) { + curmission = missionlist2.Current_Index(); + if (missions[curmission].Mission==TMISSION_MOVE || missions[curmission].Mission == TMISSION_UNLOAD) { + sprintf(arg_buf,"%c",missions[curmission].Argument + 'A'); + } else { + sprintf(arg_buf,"%d",missions[curmission].Argument); + } + arg_edt.Set_Text(arg_buf,3); + } + break; + + /*.................................................................. + Copy mission from left list box to right list box + ..................................................................*/ + case (BUTTON_ADD | KN_BUTTON): + case (BUTTON_INSERT | KN_BUTTON): + if (missioncount < TeamTypeClass::MAX_TEAM_MISSIONS) { + /* + ** Set 'i' to the position we're going to add into; this will + ** be just AFTER the current item if we're adding, and it will + ** be the current item if we're inserting. + */ + if (input == (BUTTON_ADD | KN_BUTTON)) { + i = missionlist2.Current_Index() + 1; + if (i < 0) { + i = 0; + } + if (i > missioncount) { + i = missioncount; + } + } else { + i = missionlist2.Current_Index(); + if (i < 0) { + i = 0; + } + if (i >= missioncount && missioncount > 0) { + i = missioncount - 1; + } + } + + /* + ** Move all other missions forward in the array + */ + for (j = missioncount; j > i; j--) { + missions[j] = missions[j - 1]; + } + + /* + ** Set the Mission value based on 1st list box's index + */ + missions[i].Mission = (TeamMissionType)(TMISSION_FIRST + missionlist1.Current_Index()); + + /* + ** Set the missions argument field + */ + if (missions[i].Mission == TMISSION_MOVE || missions[i].Mission == TMISSION_UNLOAD) { + missions[i].Argument = toupper(arg_buf[0]) - 'A'; + } else { + missions[i].Argument = atoi(arg_buf); + } + missioncount++; + + /* + ** Rebuild the list box from scratch + */ + Build_Mission_List(missioncount, missions, missionbuf, &missionlist2); + + /* + ** Update the list's current item index + */ + missionlist2.Set_Selected_Index(i); + } + break; + + /*.................................................................. + Delete mission from right-hand list box + ..................................................................*/ + case (BUTTON_DEL | KN_BUTTON): + if (missioncount > 0) { + i = missionlist2.Current_Index(); + if (i < 0 || i >= missioncount) { + break; + } + + /* + ** Move all missions back in the array + */ + for (j = i; j < missioncount - 1; j++) { + missions[j] = missions[j + 1]; + } + missioncount--; + + /* + ** Rebuild the list box from scratch + */ + Build_Mission_List(missioncount, missions, missionbuf, &missionlist2); + + /* + ** Update the list's current item index + */ + if (i >= missioncount) { + i--; + if (i < 0) { + i = 0; + } + missionlist2.Set_Selected_Index(i); + } + } + break; + + /*.................................................................. + Set house + ..................................................................*/ + case (BUTTON_GDI | KN_BUTTON): + case (BUTTON_NOD | KN_BUTTON): + case (BUTTON_NEU | KN_BUTTON): + case (BUTTON_MULTI1 | KN_BUTTON): + case (BUTTON_MULTI2 | KN_BUTTON): + case (BUTTON_MULTI3 | KN_BUTTON): + case (BUTTON_MULTI4 | KN_BUTTON): + house = (HousesType)( (input & (~KN_BUTTON)) - BUTTON_GDI); + Set_House_Buttons(house, commands, BUTTON_GDI); + break; + + /*.................................................................. + Invoke the members dialog + ..................................................................*/ + case (BUTTON_MEMBERS | KN_BUTTON): + /* + .................... Take editor focus away ..................... + */ + membersbtn.Turn_Off(); + + /* + ....................... Invoke the dialog ....................... + */ + Team_Members(house); + + /* + ............................ Redraw ............................. + */ + display = REDRAW_ALL; + break; + + /*.................................................................. + OK: return + ..................................................................*/ + case (BUTTON_OK | KN_BUTTON): + cancel = false; + process = false; + break; + + /*.................................................................. + Cancel: return + ..................................................................*/ + case (BUTTON_CANCEL | KN_BUTTON): + cancel = true; + process = false; + break; + + /*.................................................................. + Pass all other events to the currently-active text editor + ..................................................................*/ + default: + break; + } + } + + /* + --------------------------- Redraw the display --------------------------- + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + + /* + ------------------------- If cancel, just return ------------------------- + */ + if (cancel) { + return(-1); + } + + /* + ------------------------ Save selections & return ------------------------ + */ + CurTeam->Set_Name(name_buf); + CurTeam->RecruitPriority = atoi(recr_buf); + CurTeam->MaxAllowed = atoi(maxnum_buf); + CurTeam->InitNum = atoi(initnum_buf); + CurTeam->IsRoundAbout = roundabout; + CurTeam->IsLearning = learning; + CurTeam->IsSuicide = suicide; + CurTeam->IsAutocreate = autocreate; + CurTeam->IsPrebuilt = prebuilt; + CurTeam->IsReinforcable = reinforce; + CurTeam->IsMercenary = mercenary; + CurTeam->House = house; + CurTeam->MissionCount = missioncount; + for (i = 0 ; i < missioncount; i++) { + CurTeam->MissionList[i] = missions[i]; + } + + return(0); +} + + +/*************************************************************************** + * MapEditClass::Team_Members -- user picks makeup of a team * + * * + * Team members are rendered in a 24 x 24 area; the Window coordinates * + * have to be set to this area when the object's 'Display()' routine is * + * called. Thus, the dialog's window coords have to be divisible by * + * 24. The height of the dialog is computed based on how many objects * + * there are in it. * + * * + * 10 pixels are left between rows of objects, so the # of that type of * + * object can be displayed underneath the object. * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Team Members ³ * + * ³ ³ * + * ³ ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ÃÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄ´ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ÃÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄ´ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ÃÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄ´ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ÃÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄ´ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ÃÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄ´ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ³ * + * ³ [OK] [Cancel] ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * house house to display objects for * + * * + * OUTPUT: * + * 0 = OK, -1 = cancel * + * * + * WARNINGS: * + * CurTeam must NOT be NULL when this function is called. * + * This routine uses HIDBUFF for data storage. * + * * + * HISTORY: * + * 12/07/1994 BR : Created. * + *=========================================================================*/ +#define TEENSY_WEENSY +/* +** Dialog & button dimensions +*/ +enum { + D_DIALOG_W = 608, + D_DIALOG_X = ((640 - D_DIALOG_W) / 2), + D_DIALOG_CX = D_DIALOG_X + (D_DIALOG_W / 2), + + D_TXT6_H = 14, + D_MARGIN = 14, + +#ifdef TEENSY_WEENSY + //D_PICTURE_W = 32, + //D_PICTURE_H = 24, + D_PICTURE_W = 64, // 9 pictures / row, 16 pixel margin on each side + D_PICTURE_H = 48, +#else + //D_PICTURE_W = 32, + //D_PICTURE_H = 30, + D_PICTURE_W = 64, + D_PICTURE_H = 60, +#endif + D_ROW_H = (D_PICTURE_H + 6), + + D_OK_W = 100, + D_OK_H = 18, + D_OK_X = D_DIALOG_CX - 10 - D_OK_W, + D_OK_Y = 0, + + D_CANCEL_W = 100, + D_CANCEL_H = 18, + D_CANCEL_X = D_DIALOG_CX + 10, + D_CANCEL_Y = 0, + +}; + +/*************************************************************************** + * MapEditClass::Team_Members -- Team members dialog * + * * + * INPUT: * + * house house to show members for * + * * + * OUTPUT: * + * 0 = OK, -1 = cancel * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/09/1996 BRR : Created. * + *=========================================================================*/ +int MapEditClass::Team_Members(HousesType house) +{ + /* + ** Button enumerations: + */ + enum { + BUTTON_OK = 100, + BUTTON_CANCEL, + }; + + /* + ** Redraw values: in order from "top" to "bottom" layer of the dialog + ** (highest enum is the lowest layer). Each section of the map checks + ** the requested redraw level to see if it's supposed to draw; if it's + ** >= its level, it redraws. + */ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + RedrawType display; // requested redraw level + bool process; // loop while true + + /* + ............................ Dialog variables ............................ + */ + KeyNumType input; // user input + bool cancel = false; // true = user cancels + + /* + ......................... Team display variables ......................... + */ + const TechnoTypeClass **teamclass; // array of team classes + int *teamcount; // array of class counts + int numcols; // # units displayed horizontally + int numrows; // # units displayed vertically +// int col; // horizontal picture index +// int row; // vertical picture index +// int x,y; + + /* + ** Dialog dimensions. + */ + int dlg_y; + int dlg_h; // dialog height + int dlg_picture_top; // coord of top of pictures + int msg_y; // y-coord for object names + + /* + ** Values for parsing the classes. + */ + InfantryType i_id; + AircraftType a_id; + UnitType u_id; + int curclass = -1; // current index into 'teamclass'; can be invalid! + // (is based on current mouse position) + int numclasses; // current # classes in the team (limited to <=5) + int maxclasses; // max # classes available + int i,j; + + /* + ** Values for timing when mouse held down. + */ + int lheld = 0; + int rheld = 0; + long tdelay[3] = {5, 20, 0}; + int tindex = 0; + long heldtime; + + /* + ** Buttons. + */ + ControlClass *commands; + + TextButtonClass okbtn (BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_OK_X, D_OK_Y, D_OK_W, D_OK_H); + + TextButtonClass cancelbtn (BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_CANCEL_X, D_CANCEL_Y, D_CANCEL_W, D_CANCEL_H); + + /* + ** Set up the team data arrays (ObjectTypeClass pointers & count) + */ + teamclass = (const TechnoTypeClass **)(new TechnoTypeClass *[MAX_TEAM_CLASSES]); + teamcount = new int[MAX_TEAM_CLASSES]; + + /* + ** Fill in the ObjectTypeClass array with all available object type ptrs, + ** checking to be sure this house can own the object + */ + i = 0; + for (i_id = INFANTRY_FIRST; i_id < INFANTRY_COUNT; i_id++) { + if (Verify_House(house,&InfantryTypeClass::As_Reference(i_id))) { + teamclass[i] = &InfantryTypeClass::As_Reference(i_id); + i++; + } + } + + for (a_id = AIRCRAFT_FIRST; a_id < AIRCRAFT_COUNT; a_id++) { + if (Verify_House(house,&AircraftTypeClass::As_Reference(a_id))) { + teamclass[i] = &AircraftTypeClass::As_Reference(a_id); + i++; + } + } + + for (u_id = UNIT_FIRST; u_id < UNIT_COUNT; u_id++) { + if (Verify_House(house,&UnitTypeClass::As_Reference(u_id))) { + teamclass[i] = &UnitTypeClass::As_Reference(u_id); + i++; + } + } + + /* + ** Save max # classes. + */ + maxclasses = i; + + /* + ** Fill in the 'count' array with data from the current team: + ** - For every class in the current team, find that class type in the + ** 'teamclass' array & set its count value + */ + for (j = 0; j < maxclasses; j++) { + teamcount[j] = 0; + } + + /* + ** Loop through all classes in the team. + */ + for (i = 0; i < CurTeam->ClassCount; i++) { + + /* + ** Find this class in our array. + */ + for (j = 0; j < maxclasses; j++) { + + /* + ** Set the count; detect a match between the team's class & the + ** 'teamclass' array entry by comparing the actual pointers; typeid + ** won't work because E1 & E2 are the same type class. + */ + if (CurTeam->Class[i] == teamclass[j]) { + teamcount[j] = CurTeam->DesiredNum[i]; + break; + } + } + } + numclasses = CurTeam->ClassCount; + + /* + ** Set up the dialog dimensions based on number of classes we have to draw + ** + ** Compute picture rows & cols. + */ + numcols = (D_DIALOG_W - 16) / D_PICTURE_W; + numrows = (maxclasses + numcols - 1) / numcols; + + // + // Dialog's height = top margin + label + picture rows + + // margin + label + margin + btn + // + dlg_h = (D_MARGIN + D_TXT6_H + D_MARGIN + (numrows * D_ROW_H) + + D_MARGIN + D_TXT6_H + D_MARGIN + D_OK_H + D_MARGIN); + if (dlg_h > 400) { + dlg_h = 400; + } + dlg_y = (400 - dlg_h) / 2; + dlg_picture_top = dlg_y + D_MARGIN + D_TXT6_H + D_MARGIN; + msg_y = dlg_y + D_MARGIN + D_TXT6_H + D_MARGIN + (numrows * D_ROW_H) + D_MARGIN; + + okbtn.Y = dlg_y + dlg_h - D_MARGIN - D_OK_H; + cancelbtn.Y = dlg_y + dlg_h - D_MARGIN - D_CANCEL_H; + + /* + ** Draw to SeenBuff. + */ + Set_Logic_Page(SeenBuff); + + /* + ** Make sure 'house' is valid. + */ + if (house!=HOUSE_GOOD && house!=HOUSE_BAD && house != HOUSE_MULTI1 && + house != HOUSE_MULTI2 && house != HOUSE_MULTI3 && house != HOUSE_MULTI4 ) { + if (ScenPlayer == SCEN_PLAYER_MPLAYER) { + house = HOUSE_MULTI1; + } else { + house = HOUSE_GOOD; + } + } + + /* + ** Create the list. + */ + commands = &okbtn; + cancelbtn.Add_Tail(*commands); + + /* + ** Main Processing Loop. + */ + display = REDRAW_ALL; + process = true; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ** Invoke game callback. + */ + Call_Back(); + + /* + ** Refresh display if needed. + */ + if (display) { + + /* + ** Display the dialog box. + */ + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + + /* + ** Display the constant background of this dialog. + */ + Dialog_Box(D_DIALOG_X, dlg_y, D_DIALOG_W, dlg_h); + Draw_Caption(TXT_NONE, D_DIALOG_X, dlg_y, D_DIALOG_W); + Fancy_Text_Print("Team Members", D_DIALOG_CX, dlg_y + D_MARGIN, CC_GREEN, TBLACK, + TPF_CENTER|TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW); + + // + // Draw the objects. + // + for (i = 0; i < maxclasses; i++) { + // + // Display the object along with any count value for it. + // + Draw_Member(teamclass[i], i, teamcount[i], house, + D_DIALOG_X + 16, dlg_picture_top); + } + + if ((unsigned)curclass < maxclasses) { + Fancy_Text_Print(teamclass[curclass]->Full_Name(), + D_DIALOG_X + D_DIALOG_W / 2, msg_y, CC_TAN, TBLACK, + TPF_CENTER|TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW); + } + } + + /* + ** Redraw the buttons. + */ + if (display >= REDRAW_BUTTONS) { + commands->Draw_All(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ** Get user input. + */ + input = commands->Input(); + + /* + ** Process input. + */ + switch (input) { + + /* + ** Mouse buttons set or clear 'held' values + */ + case (KN_LMOUSE): + if (curclass >= 0 && curclass < maxclasses) { + lheld = 1; + tindex = 2; + heldtime = 0; + } + break; + + case (KN_RMOUSE): + if (curclass >= 0 && curclass < maxclasses) { + rheld = 1; + tindex = 2; + heldtime = 0; + } + break; + + case ((int)KN_LMOUSE | (int)KN_RLSE_BIT): + lheld = 0; + break; + + case ((int)KN_RMOUSE | (int)KN_RLSE_BIT): + rheld = 0; + break; + + /* + ** OK: save values & return. + */ + case (BUTTON_OK | KN_BUTTON): + process = false; + break; + + /* + ** Cancel: abort & return. + */ + case (BUTTON_CANCEL | KN_BUTTON): + cancel = true; + process = false; + break; + + default: + /* + ** Compute new 'curclass' based on mouse position. + */ + i = (Get_Mouse_X() - 16 - D_DIALOG_X) / D_PICTURE_W + + ((Get_Mouse_Y() - dlg_picture_top) / D_ROW_H) * numcols; + + /* + ** If it's changed, update class label. + */ + if (i != curclass) { + + curclass = i; + + /* + ** Clear out the previously printed name of the item. + */ + Hide_Mouse(); + LogicPage->Fill_Rect(D_DIALOG_X + 8, msg_y, D_DIALOG_X + D_DIALOG_W - 9, msg_y + D_TXT6_H, BLACK); + + if ((unsigned)curclass < maxclasses) { + Fancy_Text_Print(teamclass[curclass]->Full_Name(), + D_DIALOG_X + D_DIALOG_W / 2, msg_y, + CC_GREEN, TBLACK, + TPF_CENTER|TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW); + } + + /* + ** Force buttons to not be held. + */ + lheld = 0; + rheld = 0; + Show_Mouse(); + } + break; + } + + /* + ** Check for a 'held' mouse button; if it's down, and the correct + ** amount of time has gone by, increment/decrement the count for the + ** current class. + */ + if (lheld) { + + /* + ** The first time in, TickCount - heldtime will be larger than + ** tdelay[2], so we increment the count immediately; then, we decrement + ** tindex to go to the next time delay, which is longer; then, decr. + ** again to go to the 1st time delay which is the shortest. + */ + if (TickCount.Time() - heldtime > tdelay[tindex]) { + heldtime = TickCount.Time(); + if (tindex) { + tindex--; + } + + /* + ** Detect addition of a new class. + */ + if (teamcount[curclass]==0) { + + /* + ** Don't allow more classes than we can handle. + */ + if (numclasses == TeamTypeClass::MAX_TEAM_CLASSCOUNT) { + continue; + } + numclasses++; + } + teamcount[curclass]++; + + /* + ** Update number label. + */ + Draw_Member(teamclass[curclass], curclass, teamcount[curclass], + house, D_DIALOG_X + 16, dlg_picture_top); + + } + + } else { + + if (rheld) { + + /* + ** The first time in, TickCount - heldtime will be larger than + ** tdelay[2], so we increment the count immediately; then, we decrement + ** tindex to go to the next time delay, which is longer; then, decr. + ** again to go to the 1st time delay which is the shortest. + */ + if (TickCount.Time() - heldtime > tdelay[tindex]) { + if (tindex) { + tindex--; + } + heldtime = TickCount.Time(); + + if (teamcount[curclass] > 0) { + teamcount[curclass]--; + + /* + ** Detect removal of a class. + */ + if (teamcount[curclass] == 0) { + numclasses--; + } + } + + /* + ** Update number label. + */ + Draw_Member(teamclass[curclass], curclass, teamcount[curclass], + house, D_DIALOG_X + 16, dlg_picture_top); + + } + } + } + } + + /* + ** Copy data into team. + */ + if (!cancel) { + CurTeam->ClassCount = numclasses; + i = 0; // current team class index + for (j = 0; j < maxclasses; j++) { + if (teamcount[j] > 0) { + CurTeam->DesiredNum[i] = teamcount[j]; + CurTeam->Class[i] = teamclass[j]; + i++; + } + } + } + + /* + ** Redraw the display. + */ + HiddenPage.Clear(); + Flag_To_Redraw(true); + Render(); + + delete [] teamclass; + delete [] teamcount; + + if (cancel) return(-1); + return(0); +} + + +/*********************************************************************************************** + * MapEditClass::Draw_Member -- Draws a member of the team dialog box. * + * * + * This routine will display the cameo image of the potential team member. In the corner, * + * it will show the current quantity of this member for the current team being edited. * + * * + * INPUT: ptr -- Pointer to the member object type. * + * * + * index -- The index into the team dialog box array of selectable objects. This is * + * used to determine the correct X and Y offsets to draw. * + * * + * quant -- The quantity number to display in the corner of the image. * + * * + * house -- The owner of this object. * + * pic_x, pic_y -- x,y coords of upper-left corner to start drawing at + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/02/1995 JLB : Created. * + *=============================================================================================*/ +void MapEditClass::Draw_Member(TechnoTypeClass const * ptr, int index, + int quant, HousesType house, int pic_x, int pic_y) +{ + int numcols = (D_DIALOG_W - 32) / D_PICTURE_W; + int col = index % numcols; + int row = index / numcols; + int x = pic_x + col * D_PICTURE_W; + int y = pic_y + row * D_ROW_H; + + WindowList[WINDOW_EDITOR][WINDOWX] = 0; + WindowList[WINDOW_EDITOR][WINDOWY] = 0; + WindowList[WINDOW_EDITOR][WINDOWWIDTH] = 640 / 8; + WindowList[WINDOW_EDITOR][WINDOWHEIGHT] = 400; + Change_Window((int)WINDOW_EDITOR); + + Hide_Mouse(); + Draw_Box(x, y, D_PICTURE_W, D_PICTURE_H, BOXSTYLE_GREEN_DOWN, true); + + ptr->Display(x + D_PICTURE_W / 2, y + D_PICTURE_H / 2, WINDOW_EDITOR, house); + + if (quant > 0) { + Fancy_Text_Print("%d", x + 1, y + D_PICTURE_H - 16, + CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_DROPSHADOW, quant); + } + + Show_Mouse(); + +#if 0 + int numcols = (D_DIALOG_W - 16) / D_PICTURE_W; + int col = index % numcols; + int row = index / numcols; + int dlg_y = 0; + int x = D_DIALOG_X + 8 + col * D_PICTURE_W; + int y = dlg_y + 8 + 13 + row * D_ROW_H; + + /* + ** Change the window to this box. + */ + WindowList[WINDOW_EDITOR][WINDOWX] = x >> 3; + WindowList[WINDOW_EDITOR][WINDOWY] = y; + WindowList[WINDOW_EDITOR][WINDOWWIDTH] = D_PICTURE_W >> 3; + WindowList[WINDOW_EDITOR][WINDOWHEIGHT] = D_PICTURE_H; + Change_Window((int)WINDOW_EDITOR); + + Hide_Mouse(); + Draw_Box(x, y, D_PICTURE_W, D_PICTURE_H, BOXSTYLE_GREEN_DOWN, true); + ptr->Display(WinW<<2, WinH>>1, WINDOW_EDITOR, house); + if (quant > 0) { + Fancy_Text_Print("%d", x+1, y+D_PICTURE_H-8, CC_GREEN, TBLACK, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_DROPSHADOW, quant); + } + Show_Mouse(); +#endif +} + + +/*************************************************************************** + * MapEditClass::Build_Mission_list -- fills in mission list box * + * * + * INPUT: * + * missioncount # of missions to add to the list * + * missions array of TeamMissionStruct's * + * missionbuf character arrays to store strings in * + * list list box to add strings to * + * * + * OUTPUT: * + * 0 = OK, -1 = cancel * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/07/1994 BR : Created. * + *=========================================================================*/ +void MapEditClass::Build_Mission_List(int missioncount, TeamMissionStruct *missions, + char missionbuf[TeamTypeClass::MAX_TEAM_MISSIONS][20], ListClass *list) +{ + /* + ** Start with an empty list + */ + while (list->Count()) { + list->Remove_Item(list->Get_Item(0)); + } + + for (int i = 0; i < missioncount; i++) { + /* + ** generate the string for a MOVE mission; the argument is the + ** letter-designation of the cell to move to. + */ + if (missions[i].Mission == TMISSION_MOVE || missions[i].Mission == TMISSION_UNLOAD) { + sprintf(missionbuf[i],"%s\t%c", + TeamTypeClass::Name_From_Mission(missions[i].Mission), + missions[i].Argument + 'A'); + } else { + + /* + ** All other missions take a numeric argument. + */ + sprintf(missionbuf[i],"%s\t%d", + TeamTypeClass::Name_From_Mission(missions[i].Mission), + missions[i].Argument); + } + + /* + ** Add the string to the list box + */ + list->Add_Item(missionbuf[i]); + } +} + +#endif diff --git a/MAPSEL.CPP b/MAPSEL.CPP new file mode 100644 index 0000000..9967606 --- /dev/null +++ b/MAPSEL.CPP @@ -0,0 +1,1276 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\mapsel.cpv 1.8 16 Oct 1995 16:49:48 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MAPSEL.CPP * + * * + * Programmer : Barry W. Green * + * * + * Start Date : April 17, 1995 * + * * + * Last Update : April 27, 1995 [BWG] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Bit_It_In -- Pixel fade graphic copy. * + * Map_Selection -- Starts the whole process of selecting next map to go to * + * Print_Statistics -- Prints statistics on country selected * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "textblit.h" + +#ifndef DEMO + +void Map_Selection(void); +void Fading_Byte_Blit(int srcx, int srcy, int destx, int desty, int w, int h, GraphicBufferClass *src, GraphicBufferClass *dest); +void Print_Statistics(int country, int xpos, int ypos); +void Cycle_Call_Back_Delay(int time, unsigned char *pal); +int LowMedHiStr(int percentage); +extern int ControlQ; + +unsigned char CountryRemap[256]; +#ifdef OBSOLETE +unsigned char const High16Remap[256]={ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF +}; +#endif + +#define SDE SCEN_DIR_EAST +#define SDW SCEN_DIR_WEST +#define SDN SCEN_DIR_NONE +#define SVA SCEN_VAR_A +#define SVB SCEN_VAR_B +#define SVC SCEN_VAR_C +#define SVN SCEN_VAR_NONE +struct countrylist { + int Choices[2]; // # of map choices this time - 0 = no map selection screen + int Start[2]; + int ContAnim[2]; + int CountryColor[2][3]; + int CountryShape[2][3]; // shape in COUNTRYE.SHP + ScenarioDirType CountryDir[2][3]; + ScenarioVarType CountryVariant[2][3]; +} const CountryArray[27] = { +// GDI SCENARIO CHOICES +/* 0 */ {0}, /*E W*/ /* cont */ /* East colors */ /* West color*/ /* E frame W frame */ +/* 1 */ {{1,1}, { 0, 0}, { 3, 3},{{0x95, 0, 0},{0x95, 0, 0}}, {{17, 0, 0},{17, 0, 0}}, {{SDE,SDN,SDN},{SDN,SDN,SDN}},{{SVA,SVN,SVN},{SVN,SVN,SVN}}}, +/* 2 */ {{1,1}, {16,16}, { 19, 19},{{0x80, 0, 0},{0x80, 0, 0}}, {{ 0, 0, 0},{ 0, 0, 0}}, {{SDE,SDN,SDN},{SDN,SDN,SDN}},{{SVA,SVN,SVN},{SVN,SVN,SVN}}}, +/* 3 */ {{3,3}, {32,32}, { 35, 35},{{0x81,0x82,0x83},{0x81,0x82,0x83}}, {{ 3, 3, 1},{ 3, 3, 1}}, {{SDW,SDW,SDE},{SDN,SDN,SDN}},{{SVA,SVB,SVA},{SVN,SVN,SVN}}}, +/* 4 */ {{2,2}, {48,64}, { 51, 67},{{0x84,0x85, 0},{0x86,0x87, 0}}, {{ 4, 4, 0},{ 2, 2, 0}}, {{SDE,SDE,SDN},{SDW,SDW,SDN}},{{SVA,SVA,SVN},{SVA,SVB,SVN}}}, +/* 5 */ {{2,2}, {99,99}, {102,102},{{0x88,0x89, 0},{0x88,0x89, 0}}, {{ 7, 7, 0},{ 7, 7, 0}}, {{SDE,SDE,SDN},{SDE,SDE,SDN}},{{SVA,SVA,SVN},{SVA,SVA,SVN}}}, +/* 6 */ {{2,2}, {80,83}, { 86, 86},{{0x88,0x89, 0},{0x88,0x89, 0}}, {{ 7, 7, 0},{ 7, 7, 0}}, {{SDE,SDE,SDN},{SDE,SDE,SDN}},{{SVA,SVA,SVN},{SVA,SVA,SVN}}}, +/* 7 */ {{2,2}, {115,0}, {118, 0},{{0x8B,0x8A, 0},{0x8B,0x8A, 0}}, {{ 6, 8, 0},{ 6, 8, 0}}, {{SDE,SDE,SDN},{SDN,SDN,SDN}},{{SVA,SVB,SVN},{SVN,SVN,SVN}}}, +/* 8 */ {{1,1}, {131,0}, {134, 0},{{0x8C, 0, 0},{0x8C, 0, 0}}, {{ 9, 0, 0},{ 9, 0, 0}}, {{SDE,SDN,SDN},{SDN,SDN,SDN}},{{SVA,SVN,SVN},{SVN,SVN,SVN}}}, +/* 9 */ {{2,1}, {147,0}, {150, 0},{{0x8D,0x8E, 0},{ 0, 0, 0}}, {{10,13, 0},{ 0, 0, 0}}, {{SDE,SDE,SDN},{SDN,SDN,SDN}},{{SVA,SVB,SVN},{SVN,SVN,SVN}}}, +/* 10 */ {{1,1}, {163,0}, {166, 0},{{0x8F, 0, 0},{ 0, 0, 0}}, {{16, 0, 0},{ 0, 0, 0}}, {{SDE,SDN,SDN},{SDN,SDN,SDN}},{{SVA,SVN,SVN},{SVN,SVN,SVN}}}, +/* 11 */ {{2,1}, {179,0}, {182, 0},{{0x90,0x91, 0},{ 0, 0, 0}}, {{14,15, 0},{ 0, 0, 0}}, {{SDE,SDE,SDN},{SDN,SDN,SDN}},{{SVA,SVB,SVN},{SVN,SVN,SVN}}}, +/* 12 */ {{2,1}, {195,0}, {198, 0},{{0x92,0x93, 0},{ 0, 0, 0}}, {{12,12, 0},{ 0, 0, 0}}, {{SDE,SDE,SDN},{SDN,SDN,SDN}},{{SVA,SVB,SVN},{SVN,SVN,SVN}}}, +/* 13 */ {{1,1}, {211,0}, {214, 0},{{0x93, 0, 0},{ 0, 0, 0}}, {{12, 0, 0},{ 0, 0, 0}}, {{SDE,SDN,SDN},{SDN,SDN,SDN}},{{SVA,SVN,SVN},{SVN,SVN,SVN}}}, +/* 14 */ {{3,1}, { 0,0}, { 3, 0},{{0x81,0x82,0x83},{ 0, 0, 0}}, {{ 0, 0, 0},{ 0, 0, 0}}, {{SDE,SDE,SDE},{SDN,SDN,SDN}},{{SVA,SVB,SVC},{SVN,SVN,SVN}}}, + +// NOD SCENARIO CHOICES +// choices E/W start continue East colors West colors E shape W shape direction variant +/* 1 */ { {2,1}, { 0,0}, { 3, 0},{{0x80,0x81,0x00},{0,0,0}}, {{ 4, 4, 0},{0,0,0}}, {{SDE,SDE,SDN},{SDN,SDN,SDN}},{{SVA,SVB,SVN},{SVN,SVN,SVN}} }, +/* 2 */ { {2,1}, { 16,0}, { 19, 0},{{0x82,0x83,0x00},{0,0,0}}, {{ 6, 6, 0},{0,0,0}}, {{SDE,SDE,SDN},{SDN,SDN,SDN}},{{SVA,SVB,SVN},{SVN,SVN,SVN}} }, +/* 3 */ { {2,1}, { 32,0}, { 35, 0},{{0x84,0x85,0x00},{0,0,0}}, {{ 5, 5, 0},{0,0,0}}, {{SDE,SDE,SDN},{SDN,SDN,SDN}},{{SVA,SVB,SVN},{SVN,SVN,SVN}} }, +/* 4 */ { {1,1}, { 48,0}, { 51, 0},{{0x86,0x00,0x00},{0,0,0}}, {{ 0, 0, 0},{0,0,0}}, {{SDE,SDN,SDN},{SDN,SDN,SDN}},{{SVA,SVN,SVN},{SVN,SVN,SVN}} }, +/* 5 */ { {3,1}, { 64,0}, { 67, 0},{{0x87,0x88,0x89},{0,0,0}}, {{ 1, 2, 3},{0,0,0}}, {{SDE,SDE,SDE},{SDN,SDN,SDN}},{{SVA,SVB,SVC},{SVN,SVN,SVN}} }, +/* 6 */ { {3,1}, { 80,0}, { 83, 0},{{0x8A,0x8B,0x8C},{0,0,0}}, {{ 9, 7, 8},{0,0,0}}, {{SDE,SDE,SDE},{SDN,SDN,SDN}},{{SVA,SVB,SVC},{SVN,SVN,SVN}} }, +/* 7 */ { {2,1}, { 96,0}, { 99, 0},{{0x8D,0x8E,0x00},{0,0,0}}, {{10,10, 0},{0,0,0}}, {{SDE,SDE,SDN},{SDN,SDN,SDN}},{{SVA,SVB,SVN},{SVN,SVN,SVN}} }, +/* 8 */ { {1,1}, {112,0}, {115, 0},{{0xA0,0x00,0x00},{0,0,0}}, {{ 4, 4, 0},{0,0,0}}, {{SDE,SDN,SDN},{SDN,SDN,SDN}},{{SVA,SVN,SVN},{SVN,SVN,SVN}} }, +/* 9 */ { {2,1}, {128,0}, {131, 0},{{0x8F,0x90,0x00},{0,0,0}}, {{11,15, 0},{0,0,0}}, {{SDE,SDE,SDN},{SDN,SDN,SDN}},{{SVA,SVB,SVN},{SVN,SVN,SVN}} }, +/* 10 */ { {2,1}, {144,0}, {147, 0},{{0x91,0x92,0x00},{0,0,0}}, {{12,16, 0},{0,0,0}}, {{SDE,SDE,SDN},{SDN,SDN,SDN}},{{SVA,SVB,SVN},{SVN,SVN,SVN}} }, +/* 11 */ { {1,1}, {160,0}, {163, 0},{{0x93,0x00,0x00},{0,0,0}}, {{13, 0, 0},{0,0,0}}, {{SDE,SDN,SDN},{SDN,SDN,SDN}},{{SVA,SVN,SVN},{SVN,SVN,SVN}} }, +/* 12 */ { {3,1}, { 0,0}, { 3, 0},{{0x81,0x82,0x83},{0,0,0}}, {{14, 0, 0},{0,0,0}}, {{SDE,SDE,SDE},{SDN,SDN,SDN}},{{SVA,SVB,SVC},{SVN,SVN,SVN}} } +}; + +struct gdistats { + int nameindex; + int pop; + int area; + int capital; + int govt; + int gdp; + int conflict; + int military; +} const GDIStats[]={ +// Name Pop Area Capital Government GDP Conflict Military + { 0,TXT_MAP_P01, TXT_MAP_A00,TXT_MAP_C00, 0, TXT_MAP_GDP00, TXT_MAP_PC00, 0}, + { 1,TXT_MAP_P02, TXT_MAP_A01,TXT_MAP_C01, 1, TXT_MAP_GDP01, TXT_MAP_PC01, 3}, + { 1,TXT_MAP_P02, TXT_MAP_A01,TXT_MAP_C01, 1, TXT_MAP_GDP01, TXT_MAP_PC02, 3}, + { 2,TXT_MAP_P03, TXT_MAP_A02,TXT_MAP_C02, 0, TXT_MAP_GDP00, TXT_MAP_PC03, 1}, + { 3,TXT_MAP_P04, TXT_MAP_A03,TXT_MAP_C03, 3, TXT_MAP_GDP02, TXT_MAP_PC04, 1}, + { 3,TXT_MAP_P04, TXT_MAP_A03,TXT_MAP_C03, 3, TXT_MAP_GDP02, TXT_MAP_PC04, 1}, + { 4,TXT_MAP_P05, TXT_MAP_A04,TXT_MAP_C04, 2, TXT_MAP_GDP03, TXT_MAP_PC05, 5}, + { 4,TXT_MAP_P05, TXT_MAP_A04,TXT_MAP_C04, 2, TXT_MAP_GDP03, TXT_MAP_PC06, 5}, + { 5,TXT_MAP_P06, TXT_MAP_A05,TXT_MAP_C05, 0, TXT_MAP_GDP04, TXT_MAP_PC07, 2}, + { 5,TXT_MAP_P06, TXT_MAP_A05,TXT_MAP_C05, 0, TXT_MAP_GDP04, TXT_MAP_PC07, 2}, + { 6,TXT_MAP_P07, TXT_MAP_A06,TXT_MAP_C06, 0, TXT_MAP_GDP00, TXT_MAP_PC08, 0}, + { 7,TXT_MAP_P08, TXT_MAP_A07,TXT_MAP_C07, 4, TXT_MAP_GDP05, TXT_MAP_PC00, 2}, + { 8,TXT_MAP_P09, TXT_MAP_A08,TXT_MAP_C08, 4, TXT_MAP_GDP06, TXT_MAP_PC10, 2}, + { 9,TXT_MAP_P10, TXT_MAP_A09,TXT_MAP_C09, 0, TXT_MAP_GDP07, TXT_MAP_PC11, 1}, + {10,TXT_MAP_P11, TXT_MAP_A10,TXT_MAP_C10, 0, TXT_MAP_GDP08, TXT_MAP_PC12, 2}, + {11,TXT_MAP_P12, TXT_MAP_A11,TXT_MAP_C11, 5, TXT_MAP_GDP09, TXT_MAP_PC13, 3}, + {12,TXT_MAP_P13, TXT_MAP_A12,TXT_MAP_C12, 6, TXT_MAP_GDP10, TXT_MAP_PC14, 2}, + {13,TXT_MAP_P14, TXT_MAP_A13,TXT_MAP_C13, 0, TXT_MAP_GDP11, TXT_MAP_PC15, 2}, + {14,TXT_MAP_P15, TXT_MAP_A14,TXT_MAP_C14, 0, TXT_MAP_GDP12, TXT_MAP_PC16, 3}, + {14,TXT_MAP_P15, TXT_MAP_A14,TXT_MAP_C14, 0, TXT_MAP_GDP12, TXT_MAP_PC17, 3}, + {15,TXT_MAP_P16, TXT_MAP_A15,TXT_MAP_C15, 7, TXT_MAP_GDP13, TXT_MAP_PC18, 4}, +// Hack in a slot for Estonia + {34,TXT_MAP_P17, TXT_MAP_A16,TXT_MAP_C16, 0, TXT_MAP_GDP00, TXT_MAP_PC19, 0} +}; + +struct nodstats { + int nameindex; + int pop; + int expendable; + int capital; + int govt; + int corruptible; + int worth; + int conflict; + int military; + int probability; +} const NodStats[]={ +// Name Pop Expendable Capital Government Corruptible Worth Conflict Military Probability + {16, TXT_MAP_P18, 38, TXT_MAP_C17, 8, 86, TXT_MAP_GDP14, TXT_MAP_PC20, 0, 23}, + {17, TXT_MAP_P19, 75, TXT_MAP_C18, 0, 18, TXT_MAP_GDP15, TXT_MAP_PC21, 1, 82}, + {17, TXT_MAP_P19, 75, TXT_MAP_C18, 0, 18, TXT_MAP_GDP15, TXT_MAP_PC22, 1, 82}, + {18, TXT_MAP_P20, 50, TXT_MAP_C19, 9, 52, TXT_MAP_GDP16, TXT_MAP_PC23, 0, 72}, + {18, TXT_MAP_P20, 50, TXT_MAP_C19, 9, 52, TXT_MAP_GDP16, TXT_MAP_PC24, 0, 72}, + {19, TXT_MAP_P21, 80, TXT_MAP_C20, 0, 85, TXT_MAP_GDP17, TXT_MAP_PC25, 2, 35}, + {19, TXT_MAP_P21, 80, TXT_MAP_C20, 0, 85, TXT_MAP_GDP17, TXT_MAP_PC26, 2, 35}, + {20, TXT_MAP_P22, 50, TXT_MAP_C21, 10, 48, TXT_MAP_GDP17, TXT_MAP_PC27, 2, 24}, + {21, TXT_MAP_P23, 33, TXT_MAP_C22, 0, 28, TXT_MAP_GDP18, TXT_MAP_PC28, 3, 67}, + {22, TXT_MAP_P24, 75, TXT_MAP_C23, 6, 17, TXT_MAP_GDP19, TXT_MAP_PC29, 2, 80}, + {23, TXT_MAP_P25, 60, TXT_MAP_C24, 7, 93, TXT_MAP_GDP20, TXT_MAP_PC30, 3, 50}, + {24, TXT_MAP_P26, 5, TXT_MAP_C25, 0, 84, TXT_MAP_GDP21, TXT_MAP_PC31, 2, 22}, + {25, TXT_MAP_P27, 55, TXT_MAP_C26, 0, 48, TXT_MAP_GDP22, TXT_MAP_PC32, 3, 62}, + {26, TXT_MAP_P28, 65, TXT_MAP_C27, 0, 41, TXT_MAP_GDP23, TXT_MAP_PC33, 2, 49}, + {27, TXT_MAP_P29, 72, TXT_MAP_C28, 0, 74, TXT_MAP_GDP24, TXT_MAP_PC34, 3, 54}, + {27, TXT_MAP_P29, 72, TXT_MAP_C28, 0, 74, TXT_MAP_GDP24, TXT_MAP_PC35, 3, 54}, + {17, TXT_MAP_P30, 45, TXT_MAP_C29, 6, 3, TXT_MAP_GDP15, TXT_MAP_PC36, 3, 100}, + {28, TXT_MAP_P31, 45, TXT_MAP_C30, 0, 63, TXT_MAP_GDP25, TXT_MAP_PC37, 2, 66}, + {29, TXT_MAP_P32, 55, TXT_MAP_C31, 0, 27, TXT_MAP_GDP26, TXT_MAP_PC38, 2, 68}, + {30, TXT_MAP_P33, 5, TXT_MAP_C32, 0, 65, TXT_MAP_GDP27, TXT_MAP_PC39, 4, 74}, + {31, TXT_MAP_P34, 65, TXT_MAP_C33, 0, 52, TXT_MAP_GDP19, TXT_MAP_PC40, 2, 84}, + {32, TXT_MAP_P35, 2, TXT_MAP_C34, 11, 12, TXT_MAP_GDP28, TXT_MAP_PC41, 2, 92}, + {33, TXT_MAP_P36, 10, TXT_MAP_C35, 0, 8, TXT_MAP_GDP29, TXT_MAP_PC42, 1, 100} +}; + + + +/*********************************************************************************************** + * Map_Selection -- Starts the whole process of selecting next map to go to * + * * + * * + * INPUT: * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/17/1995 BWG : Created. * + *=============================================================================================*/ +void Map_Selection(void) +{ + void *anim, *progress, *oldfont, *greyearth, *greyearth2; + unsigned char localpalette[768]; + int scenario, lastscenario; + int house = PlayerPtr->Class->House; + int attackxcoord = 0; + + static int const _countryx[]={195,217,115,167, + 244, 97,130,142, + 171,170,139,158, + 180,207,177,213, + 201,198, + /* Nod countries */ + 69, 82,105,119, + 184,149,187,130, + 153,124,162,144, + 145,164,166,200, + 201}; + static int const _countryy[]={ 35, 57, 82, 75, + 93,111,108, 91, + 100,111,120,136, + 136,117,158,143, + 167,21, + /* Nod countries */ + 45, 80, 75, 76, + 31, 64, 69, 89, + 88,106,115,139, + 168,164,183,123, + 154}; + //static char const _greenpal[]={0,1,0x42,3,0x43,5,0x44,7,0x44,9,10,1,12,13,0x41,15}; + static char const _greenpal[]={0,0x41,0x42,0x43,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44}; + static char const _othergreenpal[]={0,0x21,0x22,0x23,0x24,0x25,0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26}; + static char const _regpal[]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; + GraphicBufferClass backpage(20*6,8); + + unsigned char *grey2palette = new unsigned char[768]; + unsigned char *progresspalette = new unsigned char[768]; + + + Keyboard::Clear(); + oldfont = Set_Font(ScoreFontPtr); + Set_Font_Palette(_regpal); + Set_Palette(BlackPalette); + + scenario = Scenario + ((house == HOUSE_GOOD) ? 0 : 14); + if (house == HOUSE_GOOD) { + lastscenario = (Scenario == 14); + if (Scenario == 15) return; + } else { + lastscenario = (Scenario == 12); + if (Scenario == 13) return; + } + + // Check if they're even entitled to map selection this time + if (CountryArray[scenario].Choices[ScenDir]==0) return; + + Theme.Queue_Song(THEME_MAP1); + PseudoSeenBuff = new GraphicBufferClass(320,200,(void*)NULL); + + /* + ** Extra graphic buffer to draw text into + */ + TextPrintBuffer = new GraphicBufferClass(SeenBuff.Get_Width(), SeenBuff.Get_Height(), (void*)NULL); + TextPrintBuffer->Clear(); + BlitList.Clear(); + + /* + ** Now start the process where we fade the gray earth in. + */ + greyearth = Open_Animation("GREYERTH.WSA", NULL, 0, (WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE), localpalette); + greyearth2 = Open_Animation("E-BWTOCL.WSA", NULL, 0, (WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE), grey2palette); + + /* + ** Load the spinning-globe anim + */ + if (house == HOUSE_GOOD) { + //anim = Open_Animation("EARTH_E.WSA", NULL,0,(WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE),Palette); + anim = Open_Animation("HEARTH_E.WSA", NULL,0,(WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE),Palette); + //progress = Open_Animation(lastscenario ? "BOSNIA.WSA" : "EUROPE.WSA",NULL,0,(WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE),progresspalette); + progress = Open_Animation(lastscenario ? "HBOSNIA.WSA" : "EUROPE.WSA",NULL,0,(WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE),progresspalette); + } else { + //anim = Open_Animation("EARTH_A.WSA", NULL,0,(WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE),Palette); + anim = Open_Animation("HEARTH_A.WSA", NULL,0,(WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE),Palette); + //progress = Open_Animation(lastscenario ? "S_AFRICA.WSA" : "AFRICA.WSA",NULL,0,(WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE),progresspalette); + progress = Open_Animation(lastscenario ? "HSAFRICA.WSA" : "AFRICA.WSA",NULL,0,(WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE),progresspalette); + } + + void const * appear1 = MixFileClass::Retrieve("APPEAR1.AUD"); + void const * sfx4 = MixFileClass::Retrieve("SFX4.AUD"); + void const * text2 = MixFileClass::Retrieve("TEXT2.AUD"); + void const * target1 = MixFileClass::Retrieve("TARGET1.AUD"); + void const * target2 = MixFileClass::Retrieve("TARGET2.AUD"); +// void const * target3 = MixFileClass::Retrieve("TARGET3.AUD"); + void const * newtarg1 = MixFileClass::Retrieve("NEWTARG1.AUD"); + void const * beepy2 = MixFileClass::Retrieve("BEEPY2.AUD"); + void const * beepy3 = MixFileClass::Retrieve("BEEPY3.AUD"); + void const * beepy6 = MixFileClass::Retrieve("BEEPY6.AUD"); + void const * world2 = MixFileClass::Retrieve("WORLD2.AUD"); + void const * country1 = MixFileClass::Retrieve("COUNTRY1.AUD"); + void const * scold1 = MixFileClass::Retrieve("SCOLD1.AUD"); + + SysMemPage.Clear(); + PseudoSeenBuff->Clear(); + WWMouse->Erase_Mouse(&HidPage, TRUE); + HiddenPage.Clear(); + InterpolationPaletteChanged = TRUE; + InterpolationPalette = Palette; + Increase_Palette_Luminance(InterpolationPalette , 30,30,30,63); + Read_Interpolation_Palette("MAP1.PAL"); +// SeenBuff.Blit(HidPage); + Animate_Frame(greyearth, SysMemPage, 0); + + Bit_It_In_Scale(0, 0, 320, 200, &SysMemPage, PseudoSeenBuff , &SeenBuff); + PseudoSeenBuff->Put_Pixel(237,92,TBLACK); + PseudoSeenBuff->Put_Pixel(237,93,TBLACK); + Interpolate_2X_Scale(PseudoSeenBuff, &SeenBuff,"MAP1.PAL"); + + InterpolationPaletteChanged = TRUE; + InterpolationPalette = localpalette; + Increase_Palette_Luminance(InterpolationPalette , 30,30,30,63); + Read_Interpolation_Palette("MAP_LOCL.PAL"); + Play_Sample(appear1, 255, Options.Normalize_Sound(110)); + Fade_Palette_To(localpalette, FADE_PALETTE_MEDIUM, Call_Back); + for (int i = 1; i < Get_Animation_Frame_Count(greyearth); i++) { + Call_Back_Delay(4); + Animate_Frame(greyearth, *PseudoSeenBuff, i); + } + Close_Animation(greyearth); + Write_Interpolation_Palette("MAP_LOCL.PAL"); + + Call_Back_Delay(4); + SysMemPage.Clear(); + Animate_Frame(greyearth2, SysMemPage, 0); + InterpolationPaletteChanged = TRUE; + InterpolationPalette = grey2palette; + Increase_Palette_Luminance(InterpolationPalette , 30,30,30,63); + Read_Interpolation_Palette("MAP_GRY2.PAL"); + Wait_Vert_Blank(); + Set_Palette(grey2palette); + SysMemPage.Blit(*PseudoSeenBuff); + Call_Back_Delay(4); + for (i = 1; i < Get_Animation_Frame_Count(greyearth2); i++) { + Animate_Frame(greyearth2,*PseudoSeenBuff,i); + Call_Back_Delay(4); + } + Close_Animation(greyearth2); + Write_Interpolation_Palette("MAP_GRY2.PAL"); + + /* + ** Copy the first frame up to the seenpage (while screen is black) + */ + SysMemPage.Clear(); + Animate_Frame(anim, SysMemPage, 1);//, 0,0, (WSAType)0,0,0); + SysMemPage.Blit(*PseudoSeenBuff); + Interpolate_2X_Scale(PseudoSeenBuff, &SeenBuff ,NULL); + + Stop_Speaking(); + + while (CountDownTimer.Time() || Is_Speaking()) { + Call_Back(); +// if (Keyboard::Check()) CountDownTimer.Set(0); + } + +// Keyboard::Clear(); + + /* + ** now make the grid appear + */ + SysMemPage.Blit(*PseudoSeenBuff); + Interpolate_2X_Scale(PseudoSeenBuff, &SeenBuff, NULL); + Play_Sample(sfx4, 255, Options.Normalize_Sound(130)); + Play_Sample(text2, 255, Options.Normalize_Sound(90)); + + + int frame = 1; + + while (frame < Get_Animation_Frame_Count(anim)) { + if (frame == 16 || frame == 33 || frame == 44 || frame == 70 || frame == 73) Play_Sample(text2, 255, Options.Normalize_Sound(90)); + if (frame == 21 || frame == 27) Play_Sample(target1, 255, Options.Normalize_Sound(90)); + if (frame == 45 || frame == 47 || frame == 49) Play_Sample(beepy6, 255, Options.Normalize_Sound(90)); + if (frame == 51) Play_Sample(world2, 255, Options.Normalize_Sound(90)); + if (frame == 70 || frame == 72) Play_Sample(beepy2, 255, Options.Normalize_Sound(90)); + if (frame == 74) Play_Sample(target2, 255, Options.Normalize_Sound(110)); + + switch (frame){ + case 1: + Alloc_Object(new MultiStagePrintClass(Text_String (TXT_READING_IMAGE_DATA), 0,10,_othergreenpal)); + break; + + case 16: + TextPrintBuffer->Fill_Rect (0, 2*10, 2*String_Pixel_Width (Text_String (TXT_READING_IMAGE_DATA)), 2*(10 + 12), BLACK); + break; + + case 17: + TextPrintBuffer->Fill_Rect (0, 2*10, 2*String_Pixel_Width (Text_String (TXT_READING_IMAGE_DATA)), 2*(10 + 12), TBLACK); + Alloc_Object(new MultiStagePrintClass("ANALYZING", 0,10,_othergreenpal)); + break; + + case 33: + TextPrintBuffer->Fill_Rect (0, 2*10, 2*String_Pixel_Width (Text_String (TXT_ANALYZING)), 2*(10 + 12), BLACK); + break; + + case 34: + TextPrintBuffer->Fill_Rect (0, 2*10, 2*String_Pixel_Width (Text_String (TXT_ANALYZING)), 2*(10 + 12), TBLACK); + Alloc_Object(new MultiStagePrintClass(Text_String (TXT_ENHANCING_IMAGE_DATA), 0,10,_othergreenpal)); + break; + + case 44: + TextPrintBuffer->Fill_Rect (0, 2*10, 2*String_Pixel_Width (Text_String (TXT_ENHANCING_IMAGE_DATA)), 2*(10 + 12), BLACK); + break; + + case 45: + TextPrintBuffer->Fill_Rect (0, 2*10, 2*String_Pixel_Width (Text_String (TXT_ENHANCING_IMAGE_DATA)), 2*(10 + 12), TBLACK); + Alloc_Object(new MultiStagePrintClass(Text_String(TXT_ISOLATING_OPERATIONAL_THEATER), 0,10,_othergreenpal)); + break; + + case 70: + TextPrintBuffer->Fill_Rect (0, 2*10, 2*String_Pixel_Width (Text_String(TXT_ISOLATING_OPERATIONAL_THEATER)), 2*(10 + 12), BLACK); + break; + + case 71: + TextPrintBuffer->Fill_Rect (0, 2*10, 2*String_Pixel_Width (Text_String(TXT_ISOLATING_OPERATIONAL_THEATER)), 2*(10 + 12), TBLACK); + Alloc_Object(new MultiStagePrintClass(Text_String (TXT_ESTABLISHING_TRADITIONAL_BOUNDARIES), 0,10,_othergreenpal)); + break; + + case 74: + Alloc_Object(new MultiStagePrintClass(Text_String (TXT_FOR_VISUAL_REFERENCE), 0,22,_othergreenpal)); + break; + } + + + Animate_Frame(anim, *PseudoSeenBuff, frame++); + Call_Back_Delay(/*Keyboard::Check() ? 0 :*/ 3); + } + + TextPrintBuffer->Fill_Rect (0, 2*10, 2*String_Pixel_Width (Text_String (TXT_ESTABLISHING_TRADITIONAL_BOUNDARIES)), 2*(10 + 24), BLACK); + Call_Back_Delay (1); + TextPrintBuffer->Fill_Rect (0, 2*10, 2*String_Pixel_Width (Text_String (TXT_ESTABLISHING_TRADITIONAL_BOUNDARIES)), 2*(10 + 24), TBLACK); + Call_Back_Delay (1); + + Close_Animation(anim); + + Keyboard::Clear(); + BlitList.Clear(); + + /* + ** Freeze on the map of Europe or Africa + */ + + SysMemPage.Clear(); + Animate_Frame(progress,SysMemPage,0); + SysMemPage.Blit(*PseudoSeenBuff); + InterpolationPaletteChanged = TRUE; + InterpolationPalette = progresspalette; + Increase_Palette_Luminance(InterpolationPalette , 30,30,30,63); + Read_Interpolation_Palette("MAP_PROG.PAL"); + + GraphicBufferClass *europe = new GraphicBufferClass(SysMemPage.Get_Width(),SysMemPage.Get_Height(),(GBC_Enum)0); + SysMemPage.Blit(*europe); + + /* + ** Now show territories as they existed last scenario + */ + int startframe = CountryArray[scenario].Start[ScenDir]; + if (startframe) { + Animate_Frame(progress,SysMemPage,startframe); + SysMemPage.Blit(*PseudoSeenBuff); + } + Set_Palette(progresspalette); + Call_Back_Delay(45); + //Write_Interpolation_Palette("MAP_PROG.PAL"); + + /* + ** Now dissolve in first advance of territories + */ + int xcoord = (house == HOUSE_GOOD ? 0 : 204); + SysMemPage.Blit(backpage, xcoord,1, 0,0, 20*6,8); + Play_Sample(text2, 255, Options.Normalize_Sound(90)); + if (house == HOUSE_GOOD) { + Alloc_Object(new ScorePrintClass(TXT_MAP_GDI,0,2,_greenpal)); + } else { + Alloc_Object(new ScorePrintClass(TXT_MAP_NOD,xcoord,2,_greenpal)); + } + Call_Back_Delay(60); + + Play_Sample(country1, 255, Options.Normalize_Sound(90)); + Animate_Frame(progress,SysMemPage,startframe+1); + Bit_It_In_Scale(0, 0, 320, 200, &SysMemPage, PseudoSeenBuff,&SeenBuff,1,1); + backpage.Blit(SysMemPage, 0,0, xcoord,1, 20*6,8); + Call_Back_Delay(85); + + /* + ** Now dissolve in second advance of territories + */ +#ifdef FRENCH + PseudoSeenBuff->Fill_Rect(xcoord,0,xcoord + 6*16 + 10,8,BLACK); + TextPrintBuffer->Fill_Rect(xcoord*2,0,2*(xcoord + 6*16 + 10),2*8,BLACK); +#else + PseudoSeenBuff->Fill_Rect(xcoord,0,xcoord + 6*16,8,BLACK); + TextPrintBuffer->Fill_Rect(2*xcoord,0,2*(xcoord + 6*16),2*8,BLACK); +#endif + Interpolate_2X_Scale(PseudoSeenBuff, &SeenBuff,NULL); + SysMemPage.Blit(backpage, xcoord,1, 0,0, 20*6,8); + if (!lastscenario) { + Play_Sample(text2, 255, Options.Normalize_Sound(90)); + if (house == HOUSE_GOOD) { + Alloc_Object(new ScorePrintClass(TXT_MAP_NOD,0,12,_greenpal)); + } else { + Alloc_Object(new ScorePrintClass(TXT_MAP_GDI,xcoord,12,_greenpal)); + } + Call_Back_Delay(65); + } + + Play_Sample(country1, 255, Options.Normalize_Sound(90)); + Animate_Frame(progress,SysMemPage,startframe+2); + Bit_It_In_Scale(0, 0, 320, 200, &SysMemPage, PseudoSeenBuff , &SeenBuff,1,1); + backpage.Blit(SysMemPage, 0,0, xcoord,11, 20*6,8); + if (!lastscenario) Call_Back_Delay(85); +// Set_Font(oldfont); +#ifdef FRENCH + PseudoSeenBuff->Fill_Rect(xcoord,12,xcoord+6*16+10,20,BLACK); + TextPrintBuffer->Fill_Rect(2*xcoord,2*12,2*(xcoord+6*16+10),2*20,BLACK); +#else + PseudoSeenBuff->Fill_Rect(xcoord,12,xcoord+6*16,20,BLACK); + TextPrintBuffer->Fill_Rect(2*xcoord,2*12,2*(xcoord+6*16),2*20,BLACK); +#endif + Interpolate_2X_Scale(PseudoSeenBuff, &SeenBuff,NULL); + + startframe = CountryArray[scenario].ContAnim[ScenDir]; + + /* + ** Now print the text over the page + */ + Play_Sample(text2, 255, Options.Normalize_Sound(90)); + Alloc_Object(new ScorePrintClass(TXT_MAP_LOCATE,0,160,_greenpal)); + Call_Back_Delay(20); + Alloc_Object(new ScorePrintClass(TXT_MAP_NEXT_MISSION, 0,170,_greenpal)); +#if (GERMAN | FRENCH) + Call_Back_Delay(20); + Alloc_Object(new ScorePrintClass(TXT_MAP_NEXT_MISS2, 0,180,_greenpal)); +#endif + Call_Back_Delay(50); + + /* + ** If we're on the last scenario, erase that text before doing the crosshairs + */ + if (lastscenario) { +#if (GERMAN | FRENCH) + SysMemPage.Fill_Rect(0,160, 20*6,186, TBLACK); + PseudoSeenBuff->Fill_Rect(0,160, 20*6,186, TBLACK); + TextPrintBuffer->Fill_Rect(0,2*160, 2*20*6,2*186, BLACK); + SeenBuff.Fill_Rect(0,2*160, 2*20*6,2*186, TBLACK); + HidPage.Fill_Rect(0,2*160, 2*20*6,2*186, TBLACK); +#else + SysMemPage.Fill_Rect(0,160, 20*6,176, TBLACK); + PseudoSeenBuff->Fill_Rect(0,160, 20*6,176, TBLACK); + TextPrintBuffer->Fill_Rect(0,2*160, 2*20*6,2*176, BLACK); + SeenBuff.Fill_Rect(0,2*160, 2*20*6,2*176, TBLACK); + HidPage.Fill_Rect(0,2*160, 2*20*6,2*176, TBLACK); +#endif + BlitList.Clear(); + Bit_It_In_Scale(0, 0, 320, 200, &SysMemPage, PseudoSeenBuff , &SeenBuff); + } + + /* + ** Fix up the palette that seems different for the last scenario + */ + if (lastscenario){ + InterpolationPaletteChanged = TRUE; + InterpolationPalette = CurrentPalette; + if (house == HOUSE_GOOD){ + Read_Interpolation_Palette("LASTSCNG.PAL"); + Interpolate_2X_Scale(PseudoSeenBuff, &SeenBuff, "LASTSCNG.PAL"); + }else{ + Read_Interpolation_Palette("LASTSCNB.PAL"); + Interpolate_2X_Scale(PseudoSeenBuff, &SeenBuff, "LASTSCNB.PAL"); + } + } + + + int q = 0; + for (frame = 0; frame < ((lastscenario) ? Get_Animation_Frame_Count(progress)-4 : 13); frame++) { + if (!frame) Play_Sample(beepy3, 255, Options.Normalize_Sound(90)); + if (frame == 2) Play_Sample(beepy3, 255, Options.Normalize_Sound(90)); + if (frame == 6) Play_Sample(newtarg1, 255, Options.Normalize_Sound(90)); + + + if (lastscenario){ + switch ( frame ){ + + case 23: + if (house == HOUSE_GOOD){ + Alloc_Object(new MultiStagePrintClass (Text_String (TXT_ENHANCING_IMAGE), 0, 10, _othergreenpal)); + }else{ +#if (FRENCH) + Alloc_Object(new MultiStagePrintClass (Text_String (TXT_ENHANCING_IMAGE), 180, 10, _othergreenpal)); +#else + Alloc_Object(new MultiStagePrintClass (Text_String (TXT_ENHANCING_IMAGE), 210, 10, _othergreenpal)); +#endif //(FRENCH) + } + + + case 35: + if (house == HOUSE_GOOD){ + TextPrintBuffer->Fill_Rect (0, 2*10, 2*String_Pixel_Width (Text_String (TXT_ENHANCING_IMAGE)), 2*(10 + 12), BLACK); + }else{ +#if (FRENCH) + TextPrintBuffer->Fill_Rect (2*180, 2*10, 2*(180+String_Pixel_Width (Text_String (TXT_ENHANCING_IMAGE))), 2*(10 + 12), BLACK); +#else + TextPrintBuffer->Fill_Rect (2*210, 2*10, 2*(210+String_Pixel_Width (Text_String (TXT_ENHANCING_IMAGE))), 2*(10 + 12), BLACK); +#endif //(FRENCH) + } + break; + + case 36: + if (house == HOUSE_GOOD){ + TextPrintBuffer->Fill_Rect (0, 2*10, 2*String_Pixel_Width (Text_String (TXT_ENHANCING_IMAGE)), 2*(10 + 12), TBLACK); + }else{ +#if (FRENCH) + TextPrintBuffer->Fill_Rect (2*180, 2*10, 2*(180+String_Pixel_Width (Text_String (TXT_ENHANCING_IMAGE))), 2*(10 + 12), TBLACK); +#else + TextPrintBuffer->Fill_Rect (2*210, 2*10, 2*(210+String_Pixel_Width (Text_String (TXT_ENHANCING_IMAGE))), 2*(10 + 12), TBLACK); +#endif //(FRENCH) + } + break; + } + } + + Animate_Frame(progress,*PseudoSeenBuff,startframe + frame); + Call_Back_Delay(6); + /* Cause it to cycle on the flashing on the country for a little while */ + if (!lastscenario && frame == 4 && q < 4) { + frame = 2; + q++; + } + } + + int selection = 0, color = 0; + // erase the "Locating Coordinates" message... + Play_Sample(beepy6, 255, Options.Normalize_Sound(90)); + if (!lastscenario) { +#if (GERMAN | FRENCH) + SysMemPage.Fill_Rect( 0,160, 20*6,186, TBLACK); + PseudoSeenBuff->Fill_Rect(0,160, 20*6,186, TBLACK); + TextPrintBuffer->Fill_Rect(0,2*160, 2*20*6,2*186, BLACK); +#else + SysMemPage.Fill_Rect( 0,160, 20*6,176, TBLACK); + PseudoSeenBuff->Fill_Rect(0,160, 20*6,176, TBLACK); + TextPrintBuffer->Fill_Rect(0,2*160, 2*20*6,2*176, BLACK); +#endif + } + Interpolate_2X_Scale(PseudoSeenBuff, &SeenBuff, NULL); + + /* + ** Now the crosshairs are over the target countries - loop until a + ** selection is made? + */ + int done = 0; + int framecounter = 0; + + if (house == HOUSE_GOOD) { + Load_Uncompress(CCFileClass(lastscenario ? "CLICK_EB.CPS" : "CLICK_E.CPS"), SysMemPage, SysMemPage); + } else { + Load_Uncompress(CCFileClass(lastscenario ? "CLICK_SA.CPS" : "CLICK_A.CPS"), SysMemPage, SysMemPage); + if (lastscenario) attackxcoord = 200; + } + +// Set_Font(ScoreFontPtr); + Play_Sample(text2, 255, Options.Normalize_Sound(90)); + Alloc_Object(new ScorePrintClass(TXT_MAP_SELECT,attackxcoord,160,_greenpal)); + Cycle_Call_Back_Delay(16,progresspalette); + Alloc_Object(new ScorePrintClass(TXT_MAP_TO_ATTACK,attackxcoord,170,_greenpal)); + Cycle_Call_Back_Delay(24,progresspalette); + while (Get_Mouse_State() > 0) Show_Mouse(); + + Keyboard::Clear(); + while (!done) { + Cycle_Call_Back_Delay(1,progresspalette); + + // Check for the mouse button + if (Keyboard::Check()) { + if ((Keyboard::Get() & 0x10FF) == KN_LMOUSE) { + for (selection = 0; selection < CountryArray[scenario].Choices[ScenDir]; selection++) { + color = SysMemPage.Get_Pixel(Get_Mouse_X()/2,Get_Mouse_Y()/2); + + /* + ** Special hack for Egypt the second time through + */ + if (CountryArray[scenario].CountryColor[ScenDir][selection] == 0xA0) { + if ((color == 0x80) || (color == 0x81)) color = 0xA0; + } + + if (CountryArray[scenario].CountryColor[ScenDir][selection] == color) { + Play_Sample(world2, 255, Options.Normalize_Sound(90)); + done = 1; + break; + } else { + Play_Sample(scold1, 255, Options.Normalize_Sound(90)); + } + } + } + } + } + ScenVar = CountryArray[scenario].CountryVariant[ScenDir][selection]; + ScenDir = CountryArray[scenario].CountryDir[ScenDir][selection]; + + if (!lastscenario) { + Close_Animation(progress); + + /* + ** Now it's time to highlight the country we're going to. + */ + void const * countryshape = MixFileClass::Retrieve(house == HOUSE_GOOD ? "COUNTRYE.SHP" : "COUNTRYA.SHP"); + + Hide_Mouse(); + // erase "Select country to attack" + PseudoSeenBuff->Fill_Rect(attackxcoord,160, attackxcoord+(17*6),178,BLACK); + TextPrintBuffer->Fill_Rect(2*attackxcoord,2*160, 2*(attackxcoord+(17*6)),2*178,BLACK); +#if (GERMAN | FRENCH) + PseudoSeenBuff->Fill_Rect(attackxcoord+(17*6),160, attackxcoord+(21*6),178,BLACK); + TextPrintBuffer->Fill_Rect(2*attackxcoord+(17*6*2),2*160, 2*(attackxcoord+(21*6)),2*178,BLACK); +#endif //GERMAN + Interpolate_2X_Scale(PseudoSeenBuff, &SeenBuff ,NULL); + + /* + ** Draw the country's shape in non-fading colors + */ + Set_Logic_Page(SysMemPage); + europe->Blit(SysMemPage); + + int shape = CountryArray[scenario].CountryShape[ScenDir][selection]; + int xyindex = shape + (house == HOUSE_GOOD ? 0 : 18); + CC_Draw_Shape(countryshape, shape, + _countryx[xyindex],_countryy[xyindex], + WINDOW_MAIN, SHAPE_WIN_REL | SHAPE_CENTER, 0,0); + SysMemPage.Blit(*PseudoSeenBuff); + Interpolate_2X_Scale(PseudoSeenBuff, &SeenBuff ,NULL); + + /* + ** Now clear the palette of all but the country's colors, and fade + ** the palette down + */ + CCFileClass("DARK_E.PAL").Read(localpalette, 768); +// Load_Data("DARK_E.PAL", localpalette, 768); + InterpolationPaletteChanged = TRUE; + InterpolationPalette = localpalette; + Increase_Palette_Luminance(localpalette , 30,30,30,63); + Read_Interpolation_Palette("MAP_LOC2.PAL"); + Interpolate_2X_Scale(PseudoSeenBuff , &SeenBuff , "MAP_LOC2.PAL"); + Fade_Palette_To(localpalette, FADE_PALETTE_MEDIUM, Call_Back); + + countryshape = 0; + + Print_Statistics(color & 0x7F, _countryx[xyindex], _countryy[xyindex]); + } else { + CCFileClass(house == HOUSE_GOOD ? "DARK_B.PAL" : "DARK_SA.PAL").Read(localpalette, 768); + InterpolationPaletteChanged = TRUE; + InterpolationPalette = localpalette; + Increase_Palette_Luminance(localpalette , 30,30,30,63); + Read_Interpolation_Palette("MAP_LOC3.PAL"); + Interpolate_2X_Scale(PseudoSeenBuff , &SeenBuff , "MAP_LOC3.PAL"); + Set_Palette(localpalette); +// Load_Data(house == HOUSE_GOOD ? "DARK_B.PAL" : "DARK_SA.PAL", localpalette, 768); + + Hide_Mouse(); +#if (GERMAN | FRENCH) + PseudoSeenBuff->Fill_Rect(attackxcoord, 160, 319, 178, BLACK); // erase "Select country to attack" + TextPrintBuffer->Fill_Rect(2*attackxcoord, 2*160, 639, 2*178, BLACK); // erase "Select country to attack" +#else + PseudoSeenBuff->Fill_Rect(attackxcoord, 160, attackxcoord + (17*6), 199, BLACK); // erase "Select country to attack" + TextPrintBuffer->Fill_Rect(2*attackxcoord, 2*160, 2*(attackxcoord + (17*6)), 2*199, BLACK); // erase "Select country to attack" +#endif + Interpolate_2X_Scale(PseudoSeenBuff, &SeenBuff, NULL); + Animate_Frame(progress, *PseudoSeenBuff, Get_Animation_Frame_Count(progress)-1); + Set_Palette(localpalette); + Close_Animation(progress); + PseudoSeenBuff->Blit(SysMemPage); + Print_Statistics(20, 160, house == HOUSE_GOOD ? 0 : 160); + } + + Theme.Queue_Song(THEME_NONE); + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, NULL); + delete europe; + delete progresspalette; + delete grey2palette; + delete TextPrintBuffer; + TextPrintBuffer = NULL; + BlitList.Clear(); +} + + +/*************************************************************************** + * Print_Statistics -- Prints statistics on country selected * + * * + * * + * * + * INPUT: shape = country #, x & y = country's on-screen coords * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/27/1995 BWG : Created. * + *=========================================================================*/ +void Print_Statistics(int country, int xpos, int ypos) +{ + int index,newx; + void *oldfont; + static int const _gdistatnames[]={ + TXT_MAP_GDISTAT0, + TXT_MAP_GDISTAT1, + TXT_MAP_GDISTAT2, + TXT_MAP_GDISTAT3, + TXT_MAP_GDISTAT4, + TXT_MAP_GDISTAT5, + TXT_MAP_GDISTAT6 + }; + static int const _nodstatnames[]={ + TXT_MAP_GDISTAT0, + TXT_MAP_NODSTAT0, + TXT_MAP_GDISTAT2, + TXT_MAP_GDISTAT3, + TXT_MAP_NODSTAT1, + TXT_MAP_NODSTAT2, + TXT_MAP_GDISTAT5, + TXT_MAP_NODSTAT3, + TXT_MAP_NODSTAT4 + }; + static int const _countryname[]={ + TXT_MAP_COUNTRYNAME0, + TXT_MAP_COUNTRYNAME1, + TXT_MAP_COUNTRYNAME2, + TXT_MAP_COUNTRYNAME3, + TXT_MAP_COUNTRYNAME4, + TXT_MAP_COUNTRYNAME5, + TXT_MAP_COUNTRYNAME6, + TXT_MAP_COUNTRYNAME7, + TXT_MAP_COUNTRYNAME8, + TXT_MAP_COUNTRYNAME9, + TXT_MAP_COUNTRYNAME10, + TXT_MAP_COUNTRYNAME11, + TXT_MAP_COUNTRYNAME12, + TXT_MAP_COUNTRYNAME13, + TXT_MAP_COUNTRYNAME14, + TXT_MAP_COUNTRYNAME15, + TXT_MAP_COUNTRYNAME16, + TXT_MAP_COUNTRYNAME17, + TXT_MAP_COUNTRYNAME18, + TXT_MAP_COUNTRYNAME19, + TXT_MAP_COUNTRYNAME20, + TXT_MAP_COUNTRYNAME21, + TXT_MAP_COUNTRYNAME22, + TXT_MAP_COUNTRYNAME23, + TXT_MAP_COUNTRYNAME24, + TXT_MAP_COUNTRYNAME25, + TXT_MAP_COUNTRYNAME26, + TXT_MAP_COUNTRYNAME27, + TXT_MAP_COUNTRYNAME28, + TXT_MAP_COUNTRYNAME29, + TXT_MAP_COUNTRYNAME30, + TXT_MAP_COUNTRYNAME31, + TXT_MAP_COUNTRYNAME32, + TXT_MAP_COUNTRYNAME33, + TXT_MAP_COUNTRYNAME34 + }; + + static int const _govtnames[]={ + TXT_MAP_GOVT0, + TXT_MAP_GOVT1, + TXT_MAP_GOVT2, + TXT_MAP_GOVT3, + TXT_MAP_GOVT4, + TXT_MAP_GOVT5, + TXT_MAP_GOVT6, + TXT_MAP_GOVT7, + TXT_MAP_GOVT8, + TXT_MAP_GOVT9, + TXT_MAP_GOVT10, + TXT_MAP_GOVT11 + }; + static int const _armynames[]={ + TXT_MAP_ARMY0, + TXT_MAP_ARMY1, + TXT_MAP_ARMY2, + TXT_MAP_ARMY3, + TXT_MAP_ARMY4, + TXT_MAP_ARMY5 + }; + static int const _military[]={ + TXT_MAP_MILITARY0, + TXT_MAP_MILITARY1, + TXT_MAP_MILITARY2, + TXT_MAP_MILITARY3, + TXT_MAP_MILITARY4 + }; + + static char const _greenpal[]={0,0x41,0x42,0x43,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44}; + //static char const _greenpal[]={0,1,0x42,3,0x43,5,0x44,7,0x44,9,10,1,12,13,0x41,15}; + static char _deststr[16]; + +/* Change to the six-point font for Text_Print */ + oldfont = Set_Font(ScoreFontPtr); + +#ifdef GERMAN + xpos = 8; +#else + xpos = (xpos > 128) ? 8 : 136; +#endif + ypos = (ypos > 100) ? 8 : 104-6; + if (PlayerPtr->Class->House == HOUSE_GOOD) { + Alloc_Object(new ScorePrintClass(_countryname[GDIStats[country].nameindex],xpos,ypos,_greenpal)); + Call_Back_Delay(strlen(Text_String(_countryname[GDIStats[country].nameindex]))*3); + ypos += 16; + for (index = 0; index < 7; index ++) { + Alloc_Object(new ScorePrintClass(_gdistatnames[index],xpos,ypos,_greenpal)); + Call_Back_Delay(strlen(Text_String(_gdistatnames[index]+3))); + newx = xpos + 6*strlen(Text_String(_gdistatnames[index])); + switch (index) { + case 0: + Alloc_Object(new ScorePrintClass(GDIStats[country].pop,newx,ypos,_greenpal)); + break; + case 1: + Alloc_Object(new ScorePrintClass(GDIStats[country].area,newx,ypos,_greenpal)); + break; + case 2: + Alloc_Object(new ScorePrintClass(GDIStats[country].capital,newx,ypos,_greenpal)); + break; + case 3: + Alloc_Object(new ScorePrintClass(_govtnames[GDIStats[country].govt],newx,ypos,_greenpal)); + break; + case 4: + Alloc_Object(new ScorePrintClass(GDIStats[country].gdp,newx,ypos,_greenpal)); + break; + case 5: + Alloc_Object(new ScorePrintClass(GDIStats[country].conflict,newx,ypos,_greenpal)); + break; + case 6: + Alloc_Object(new ScorePrintClass(_armynames[GDIStats[country].military],newx,ypos,_greenpal)); + break; + } + ypos += 8; + } + } else { // Nod statistics + if (country > 30) { + country = 15; // hack for 2nd time in Egypt + } else { + if (country >=15) country++; // hack to account for Egypt + } + country++; + + Alloc_Object(new ScorePrintClass(_countryname[NodStats[country].nameindex],xpos,ypos,_greenpal)); + Call_Back_Delay(strlen(Text_String(_countryname[NodStats[country].nameindex]))*3); + ypos += 16; + for (index = 0; index < 9; index ++) { + Alloc_Object(new ScorePrintClass(_nodstatnames[index],xpos,ypos,_greenpal)); + Call_Back_Delay(strlen(Text_String(_nodstatnames[index]+3))); + newx = xpos + 6*strlen(Text_String(_nodstatnames[index])); + switch (index) { + case 0: + Alloc_Object(new ScorePrintClass(NodStats[country].pop,newx,ypos,_greenpal)); + break; + case 1: + sprintf(_deststr,"%d%%",NodStats[country].expendable); + Alloc_Object(new ScorePrintClass(_deststr,newx,ypos,_greenpal)); + break; + case 2: + Alloc_Object(new ScorePrintClass(NodStats[country].capital,newx,ypos,_greenpal)); + break; + case 3: + Alloc_Object(new ScorePrintClass(_govtnames[NodStats[country].govt],newx,ypos,_greenpal)); + break; + case 4: +#ifdef FIX_ME_LATER + sprintf(_deststr,"%s %d%%",LowMedHiStr(NodStats[country].corruptible),NodStats[country].corruptible); +#endif //FIX_ME_LATER + sprintf(_deststr,"%d%%",NodStats[country].corruptible); + Alloc_Object(new ScorePrintClass(_deststr,newx,ypos,_greenpal)); + break; + case 5: + Alloc_Object(new ScorePrintClass(NodStats[country].worth,newx,ypos,_greenpal)); + break; + case 6: + Alloc_Object(new ScorePrintClass(NodStats[country].conflict,newx,ypos,_greenpal)); + break; + case 7: + Alloc_Object(new ScorePrintClass(_military[NodStats[country].military],newx,ypos,_greenpal)); + break; + case 8: + sprintf(_deststr,"%d%%",NodStats[country].probability); + Alloc_Object(new ScorePrintClass(_deststr,newx,ypos,_greenpal)); + break; + } + ypos += 8; + } + } + +#ifdef FRENCH + Alloc_Object(new ScorePrintClass(TXT_MAP_CLICK2,94,193-6,_greenpal)); +#else + Alloc_Object(new ScorePrintClass(TXT_MAP_CLICK2,160-(17*3),193-6,_greenpal)); +#endif + + int done = 0; + while (!done) { + done = 1; + for (int x = 0; x < MAXSCOREOBJS; x++) if (ScoreObjs[x]) { + done = 0; + Call_Back_Delay(1); + } + } + Keyboard::Clear(); + while (Keyboard::Check()){ + Keyboard::Clear(); + } + while (!Keyboard::Check() && !ControlQ) { + Call_Back_Delay(1); + } + Keyboard::Clear(); + Set_Font(oldfont); +} + +#ifdef NEVER +/*************************************************************************** + * FADING_BYTE_BLIT -- 'Pixelized' incremental byte blit. * + * * + * This routine will perform the same function as Byte_Blit, but will * + * do so in an incremental (one piece at a time) method. This is * + * usefull for graphic 'fades' from one display to another. * + * * + * INPUT: srcx - Source page X byte position of upper left corner. * + * * + * srcy - Source page Y pixel position of upper left corner. * + * * + * destx - Dest page X byte position of upper left corner. * + * * + * desty - Dest page Y pixel position of upper left corner. * + * * + * w - Width of the blit in bytes. * + * * + * h - Height of the blit in pixels. * + * * + * src - PageType of the source page. * + * * + * dest - Page type of the destination. * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine, although functionally equivalent to the * + * normal Byte_Blit, is very slow. Only use this when * + * the 'fading' graphic effect is desired. This means the * + * destination page should probably be the SEENPAGE. * + * * + * HISTORY: * + * 07/17/1991 JLB : Created from Bit_It_In() (LJC code). * + * 04/17/1995 BWG : Adapted to the C++ system. * + *=========================================================================*/ +void Fading_Byte_Blit(int srcx, int srcy, int destx, int desty, int w, int h, GraphicBufferClass *src, GraphicBufferClass *dest) +{ + unsigned int xindex, // Working array index var. + yindex; // Working y index var. + unsigned int x,y; // Extraction position indexes. + unsigned int tempy; // Temporary working Y index var. + int _xindex[40]; // X position array. + int _yindex[200]; // Y position array. + + // Anticipate two pixel rows per blit. + h >>= 1; + + // This routine is byte-aligned + srcx >>=3; + destx >>=3; + w >>=3; + + for (xindex = 0; xindex < w; xindex++) _xindex[xindex] = xindex; /* init the index array */ + for (yindex = 0; yindex < h; yindex++) _yindex[yindex] = yindex; /* init the index array */ + + /* + ** Shuffle the X indexes around a bit. This gives it + ** that 'random' feel while remaining precise. + */ + for (xindex = 0; xindex < w; xindex++) { + int temp; + + x = IRandom(0,w-1); + temp = _xindex[x]; + _xindex[x] = _xindex[xindex]; + _xindex[xindex] = temp; + } + + /* + ** Shuffle the Y indexes around a bit for the same reason that + ** the x indexes were shuffled. + */ + for (yindex = 0; yindex < h; yindex++) { + int temp; + + y = IRandom(0,h-1); + temp = _yindex[y]; + _yindex[y] = _yindex[yindex]; + _yindex[yindex] = temp; + } + + /* + ** Sweep through the indexes and 'construct' the destination display + ** from a series of miniature Byte_Blits. + */ + for (yindex = 0; yindex < h; yindex++) { + tempy = yindex; + Call_Back(); + for (xindex = 0; xindex < w; xindex++) { + x = _xindex[xindex]; + y = _yindex[tempy]; + tempy++; + if (tempy >= h) tempy = 0; + src->Blit(*dest, (srcx+x)<<3, srcy+(y<<1), (destx+x)<<3, desty+(y<<1), 1<<3, 2); + } + } +} +#endif + + + + + +void Cycle_Call_Back_Delay(int time, unsigned char *pal) +{ + static int _counter; + unsigned char r,g,b; + int i; + + while (time--) { + _counter = (++_counter & 3); + + if (!(_counter & 3) ) { + r = pal[249*3+0]; + g = pal[249*3+1]; + b = pal[249*3+2]; + + for (i = 249; i < 254; i++) { + pal[i*3+0] = pal[(i+1)*3+0]; + pal[i*3+1] = pal[(i+1)*3+1]; + pal[i*3+2] = pal[(i+1)*3+2]; + } + pal[254*3+0] = r; + pal[254*3+1] = g; + pal[254*3+2] = b; + + Set_Palette(pal); + } + Call_Back_Delay(1); + } +} + +int LowMedHiStr(int percentage) +{ + if (percentage < 30) return(TXT_MAP_LMH0); + if (percentage < 70) return(TXT_MAP_LMH1); + return(TXT_MAP_LMH2); +} + + + +#endif //DEMO + + +/*************************************************************************** + * Bit_It_In -- Pixel fade graphic copy. * + * * + * Copies a block of graphic memory using a 'random' pixel algorithm. * + * Typcial use would be to 'fade' some graphic display into another. * + * * + * INPUT: x,y - Pixel position of upper left corner of block. * + * * + * w,y - Pixel width and height of block to pixel blit. * + * * + * src - Page number of the source page. * + * * + * dest - Page number of the destination page. * + * * + * delay - # of frames to wait after each line fades in * + * * + * OUTPUT: none * + * * + * WARNINGS: This function uses PIXEL coordinates for the X and width * + * parameters. This is unlike the Byte_Blit() routine that * + * it most closely resembles. * + * * + * HISTORY: * + * 04/16/1991 JLB : Created. * + * 04/17/1995 BWG : Adapted to C++ library. * + *=========================================================================*/ + +void Bit_It_In_Scale(int x, int y, int w, int h, GraphicBufferClass *src, GraphicBufferClass *dest, GraphicViewPortClass * /*seen*/ , int delay, int dagger) +{ + short int *xindex,*yindex; + int n; + unsigned int i,j,k,m,j1; + short ScaleBuffer[320+200]; + + xindex = (short int *) ScaleBuffer; + yindex = xindex + 320; + + for (i=0; iLock() && dest->Lock()){ + for (i=0; i=h) j1=0; + + Buffer_Put_Pixel (dest,k,m,Buffer_Get_Pixel(src,k,m)); + //n=src->Get_Pixel(k,m); + //dest->Put_Pixel(k,m,n); + } + if (dagger) for (int q=j; q>=0; q--) { + Buffer_Put_Pixel(dest,160-(j-q),q,Buffer_Get_Pixel(src,160-(j-q),q)); + Buffer_Put_Pixel(dest,160+(j-q),q,Buffer_Get_Pixel(src,160+(j-q),q)); + //dest->Put_Pixel(160-(j-q),q,src->Get_Pixel(160-(j-q),q)); + //dest->Put_Pixel(160+(j-q),q,src->Get_Pixel(160+(j-q),q)); + } + } + src->Unlock(); + dest->Unlock(); + //if (seen){ + //Interpolate_2X_Scale(dest , seen ,NULL); + //} + } +} + +void Bit_It_In(int x, int y, int w, int h, GraphicBufferClass *src, GraphicBufferClass *dest, int delay, int dagger) +{ + Bit_It_In_Scale (x, y, w, h, src, dest, NULL , delay, dagger); +} + diff --git a/MEMCHECK.H b/MEMCHECK.H new file mode 100644 index 0000000..788d1ce --- /dev/null +++ b/MEMCHECK.H @@ -0,0 +1,2605 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + + +/* #*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*# + + MemCheck 3.0 Professional for DOS + + Copyright (c) 1990-1994, StratosWare Corporation. + All rights reserved. + + 1-800-WE-DEBUG + + Note to Developers: + -------------------- + This file should be #included AFTER any other #includes in + each source file which is to be memory checked, and BEFORE + any code that performs any operations on allocated pointers. + If it isn't, MemCheck will not pick up source file and line + information for intercepted functions. + + The MCCONFIG.EXE utility distributed with MemCheck 3.0 + will do this safely and quickly for you. + + Most specifically, this header file MUST NOT come before + any prototypes of routines that MemCheck intercepts, like + malloc(), free(), strcpy(), and so on. + + The Final Cut: + --------------- + To ENTIRELY remove MemCheck from your code, just #define + the constant "NOMEMCHECK", or equivalently, "NOMC". + + This header file will then automatically 'evaporate' all + MemCheck 3.0 calls. This is MUCH PREFERABLE to placing + #if-#endif's around the header file's inclusion, as in + + #ifdef DEBUG // DON'T DO THIS! + #include + #endif + + Using the "#ifdef DEBUG" as above doesn't allow the + MemCheck header file to evaporate the MemCheck 3.0 API + calls you may have placed in your code, like mc_startcheck() + and mc_endcheck(). You would then have to surround + each MemCheck API call with additional #if-#endif's. + + Modification History + + WHO WHEN WHAT + KWB 07/28/93 Gussy for beta + KWB 09/11/93 Add new placement overload, NEW() C++ stuff + KWB 11/08/93 Final QA/QC for initial release + KWB 12/02/93 LINT -save added + KWB 02/19/94 Fixed function inlining holes, added macros + for underscore func variants under MSC 7+ + KWB 07/08/94 Added defines for BC extender (_CC_POWERPACK_) + KWB 07/09/94 Added special case for STACKTOP, END under PowerPack + KWB 08/04/94 Added cdecl modifier to stklen, atopsp etc. decls + KWB 08/11/94 Associated _CC32_ with _PROTECTED_ in ccdefs section; + Changed method of determining compiler model, + e.g. _CC_MSC6_ from if == to if >= + Associated DOSX286 with _PROTECTED_ for Phar Lap + Added MC_SET_EXCEPTF, mc_set/get_exceptf() + KWB 08/17/94 Added new[] support filtering (_CPP_ANSI20_) + KWB 08/18/94 Changed _MCFARCALL to _MCFARGLUE + KWB 09/13/94 Added erf_printf as alias for erf_stdout + KWB 09/14/94 Added endf_summary + KWB 09/21/94 Added MCCRITF and mc_set_critf() & related + KWB 10/10/94 Added #if !defined(setmem) etc. for BC DPMI32 + KWB 10/11/94 Added _CC_BORLANDx_ comp def, 'x' = major ver + +*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*# */ + + +/* Avoid multiple inclusions */ +#ifndef _MEMCHECK_H_ +#define _MEMCHECK_H_ + +/* Prevent inclusion in Windows(tm) compilations */ +#if defined(_Windows) || defined(WINDOWS) || defined(_WINDOWS) +# if !defined (__DPMI16__) && !defined (__DPMI32__) && !defined (DOSX286) +# error DOS version of MemCheck header file #included! +# endif +#endif + +/* Shorthand equivalence, V2.0 backwards compatibility... */ +#if defined (NOMC) || defined (NOMEMCHK) +# define NOMEMCHECK +#endif + +/* C++ new interception -- see note later and + read the MemCheck 3.0 User's Manual, section + "Integration With C++." Uncommenting the next line + and following simple instructions allows seamless + transmission of the exact file and line location + of new's in your source code. +*/ +/* #define NEW_OVERLOADED */ + + +/* *** Backwards compatibility with V2.0 *** */ + +#define mc_isactive mc_is_active /* standardize naming... */ +#define mc_getmode mc_get_mode +#define mc_errorstatus mc_error_flags +#define mc_verify_memory mc_check_buffers + +#define MC_USEDISK MC_USING_DISK +#define MC_USEMEM MC_USING_MEMORY + + +/* *** Backwards compatibility with V2.1 *** */ + +#define MCLINENO MCSL /* "MemCheck Source Line" */ +#define MFUNC ERF /* error reporting function */ + +#define mc_set_msgfunc mc_set_erf /* "Message funcs" are now */ +#define mc_get_msgfunc mc_get_erf /* universally referred to as */ +#define mc_error_status mc_error_flags /* "error reporting functions"*/ + +/* Maintain source code compatibility with version 2.1. + Buffer registration is simplified to + just calling "mc_register", regardless of model. + Same with buffer checking, e.g. "mc_check_far" + and "mc_check_near" are rolled into "mc_check". +*/ +#define mc_register_near(p,s) mc_register((void _MCFAR *)(p),s) +#define mc_register_far(p,s) mc_register((void _MCFAR *)(p),s) +#define mc_unregister_near(p) mc_unregister((void _MCFAR *)(p)) +#define mc_unregister_far(p) mc_unregister((void _MCFAR *)(p)) +#define mc_check_near(p) mc_check((void _MCFAR *)(p)) +#define mc_check_far(p) mc_check((void _MCFAR *)(p)) + +/* Error Number Definitions + Returned by mc_endcheck() and mc_error_flags(). + In MemCheck 3.0, there's now a global error number much + like the "errno" variable in standard C. +*/ +#define MCE_NO_ERROR 0 /* it's debugging time & all well */ +#define MCE_NULL_SOURCE 1 /* null source ptr on copy */ +#define MCE_NULL_DEST 2 /* null dest ptr on copy */ +#define MCE_UNDERWRITE 3 /* allocated buf underwritten (front) */ +#define MCE_OVERWRITE 4 /* allocated buf overwritten (end) */ +#define MCE_LEAK 5 /* forgot to free alloc'd memory */ +#define MCE_LEAKAGE MCE_LEAK +#define MCE_UNFREED_MEMORY MCE_LEAK +#define MCE_NULL_PTR_ASSIGN 6 /* assigned data through null ptr */ +#define MCE_BAD_STACK_PTR 7 /* bad stack pointer */ +#define MCE_STACK_OVERWRITE 8 /* copy trashes stack frame */ +#define MCE_INTERNAL 9 /* internal error msg */ +#define MCE_OVERLAPPING_COPY 10 /* source overlaps dest on copy */ +#define MCE_INVALID_PTR 11 /* bad ptr on free, realloc */ +#define MCE_DEST_OVERWRITE 12 /* copy too big for dest buffer */ +#define MCE_OUT_OF_MEMORY 13 /* out of memory */ +#define MCE_OOM MCE_OUT_OF_MEMORY +#define MCE_GPF_PTR 14 /* ptr caused GPF */ + + +/* MemCheck Error Flags + + The MemCheck error flag is just an unsigned long + (specifically, a MCEFLAGS typedef) with "sticky" bits + corresponding to the above MCE_... error numbers. + You can use the error flags macro MC_EFLAG(e) to + test the global MC_Errno double flag word. +*/ +/* Returns a long word with the e-th bit set */ +#define MC_EFLAG(e) ( (e) ? ((MCEFLAGS)1 << (e-1)) : (MCEFLAGS)0) + +#define EFLAG_NULL_PTR MC_EFLAG(MCE_NULL_DEST) +#define EFLAG_BAD_PTR MC_EFLAG(MCE_UNALLOCED_PTR) +#define EFLAG_FRONT_MAGIC MC_EFLAG(MCE_UNDERWRITE) +#define EFLAG_BACK_MAGIC MC_EFLAG(MCE_OVERWRITE) +#define EFLAG_PTRS_NOT_FREED MC_EFLAG(MCE_LEAK) +#define EFLAG_TRACK_FILE 0 /*obsolete in 3.0+*/ +#define EFLAG_NULL_ASSIGN MCE_FLAG(MCE_NULL_PTR_ASSIGN) + +/* *** End Compatibility Section *** */ + + +/* *** MemCheck Compiler Constant Definitions *** */ + +/* + Compiler Defines + -------- ------- + Microsoft _CC_MSC_, _CC_MSC_COMPATIBLE_ + V8.x _CC_MSC8_ + V7.x _CC_MSC7_ + V6.x _CC_MSC6_ + V5.x _CC_MSC5_ + Borland* _CC_BORLAND_, _CC_BCC_ + V3.x _CC_BORLAND3_ + V4.x _CC_BORLAND4_ + PowerPack/16 _CC_POWERPACK_, _CC_POWERPACK16_ + PowerPack/32 _CC_POWERPACK_, _CC_POWERPACK32_, _CC32_ + WATCOM _CC_WATCOM_, _CC_MSC_COMPATIBLE_ + WAT/386 _CC_WATCOM32_, _CC32_ + + * Borland has no good way of determining compiler + version. __BORLANDC__ returns some truly funky + hex constant that "will increase in future versions." + + Define Meaning + ------ -------- + _CC32_ * 32-bit compile + _PROTECTED_ 16- or 32-bit protected mode compile + LCODE Defined if large code model + LDATA Defined if large data model + STACKTOP Highest stack address (top) + STACKEND Lowest stack address + STACKLEN Stack length (top - end) + _CPP_ANSI20_ Compiler supports C++ 2.0, e.g. new[] + + * If _CC32_ is defined, _PROTECTED_ is also defined +*/ + + +#ifndef _CCDEFS_H_ +#define _CCDEFS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* NOTE: Microsoft C 5.x users have to "#define _MSC_VER 500" + at the top of this file. +*/ +#if defined(_MSC_VER) +/* + _MSC_VER Microsoft C version; currently defined as 700. + M_I86 _M_I86 Member of the I86 processor family. + M_I86mM _M_I86mM Memory model type: + = T Tiny + S Small (default) + C Compact model + M Medium model + L Large model + H Huge model + Identifiers defined by /AT, /AS, /AC, /AM, + /AL, and /AH, respectively. + _MSDOS MS-DOS operating system. + _QC Microsoft QuickC Compiler. + _WINDLL Windows protected-mode dynamic-link library + is selected with /GD. + _WINDOWS Windows protected-mode is selected with /GA, + /Gn, /GW, /Mq, or /GD. +*/ +# define _CC_MSC_ +# define _CC_MSC_COMPATIBLE_ + +# if (_MSC_VER >= 800) +# define _CC_MSC8_ +# elif (_MSC_VER >= 700) +# define _CC_MSC7_ +# elif (_MSC_VER >= 600) +# define _CC_MSC6_ +# elif (_MSC_VER >= 500) /* see note above */ +# define _CC_MSC5_ +# else +# error MemCheck.h: unrecognized version of Microsoft compiler! +# endif + +#elif defined(__BORLANDC__) +# define _CC_BORLAND_ /* Borland product */ +# define _CC_BCC_ /* Borland C compiler */ + + /* Major compiler rev */ +# if (__BORLANDC__ >= 0x0450) +# define _CC_BORLAND4_ +# elif (__BORLANDC__ >= 0x400) +# define _CC_BORLAND3_ +# else + /* not needed */ +# endif + + /* Borland 4.0 PowerPack BCC.EXE defines (-WX): + __DPMI16__ _Windows + + With -WXD, -WXDE: + __DLL__ __DPMI16__ _Windows + + Borland 4.0 PowerPack BCC21.EXE defines (-WX): + __CONSOLE__ __DPMI32__ __FLAT__ __WIN32__ _Windows + + With -WXD: + __DLL__ __CONSOLE__ __DPMI32__ __FLAT__ __WIN32__ _Windows + */ +# if defined(__DPMI16__) +# define _CC_POWERPACK_ +# define _CC_POWERPACK16_ +# define _PROTECTED_ +# endif +# if defined(__DPMI32__) +# define _CC_POWERPACK_ +# define _CC_POWERPACK32_ +# define _CC32_ /* flat model */ +# endif + +/* Turbo C++ */ +#elif defined(MCTCP) /* homebrew */ +# define _CC_BORLAND_ /* Borland product */ +# define _CC_TCP_ /* Turbo C++ */ + +#elif defined(__TURBOC__) +/* + __TURBOC__ Gives the current Turbo C version + number, a hexadecimal number. Version + 1.0 id 0x1000; version 1.2 is 0x0102, etc. + __TINY__, __SMALL__, __MEDIUM__, + __COMPACT__, __LARGE__, __HUGE__ + Model defintions + __MSDOS__ Signals that we're not yet in the + twenty-first century +*/ +# define _CC_BORLAND_ /* Borland C product */ +# define _CC_TCC_ /* Turbo C/C++ compiler */ + + +#elif defined(_INTELC32_) +/* + _INTELC32_ has the value 1. + _ARCHITECTURE_ is 386 if the nomod486 pragma is ineffect, + 486 otherwise. +*/ +# define _CC_INTEL_ /* Intel Code Builder */ +# define _CC_MSC_COMPATIBLE_ +# define _CC32_ /* flat model */ + +#elif defined(__WATCOMC__) +/* + __WATCOMC__ Used to determine if the WATCOM C + or C/386 compiler is compiling + __386__ identifies the target machine as an + Intel 80386 under the WATCOM C/386 compiler + MSDOS Signals that we're not yet in the + twenty-first century + __FLAT__, __SMALL__, __MEDIUM__, + __COMPACT__, __LARGE__ + Model defintions (flat is default) + Also id's MSC-compatible model defines + + 8.5 and later: + __WINDOWS__ Identifies 16-bit Windows ("zw" or "zW" opts) + __WINDOWS_386__ 32-bit Microsoft Windows "zW" opt + __NETWARE_386__ Novell Netware 386, defined by the + Novell/Watcom C + __OS2__ IBM OS/2-hosted version of Watcom +*/ +# define _CC_WATCOM_ /* Watcom C product */ +# define _CC_MSC_COMPATIBLE_ +# ifdef __386__ +# define _CC32_ /* flat model */ +# define _CC_WATCOM32_ +# endif + + +#elif defined(__STDC__) /* Standard ANSI C */ +# define _CC_ANSI_ +# define _CC32_ /* no segmentation via far/near */ +# define far +# define near +# define huge +# define cdecl + +/* Avoids parameter mismatches from _far, etc. */ +# define _far +# define _near +# define _huge +# define _cdecl + +#else /* UNSUPPORTED / UNRECOGNIZED COMPILER */ + +#error MemCheck 3.0: unrecognized compiler +/* + If you're using Microsoft C5.1, you must + define the constant _MSC_VER, e.g. + + #define _MSC_VER 500 + + Place this statement at the top of this + header file or in your project header file + BEFORE the MemCheck header file is included. + + Microsoft C 5.0 is NOT supported (it's non-ANSI). +*/ + +#endif /* compiler constant definitions */ + +/* END of _CC..._ name setups; ripple & alias */ + +/* Sheer utility & avoidance of !_CC32_ */ +#ifndef _CC32_ +# define _CC16_ +#endif + +/* 32-bit compilers are always protected mode */ +#ifdef _CC32_ +#ifndef _PROTECTED_ +# define _PROTECTED_ +#endif +#endif /* CC32 */ + +/* Phar Lap support */ +#ifdef DOSX286 +#ifndef _PROTECTED_ +# define _PROTECTED_ +#endif +#endif /* DOSX286 */ + +/* C++ 2.0 compliance: _CPP_ANSI20_ */ +#if defined(_CC_BORLAND_) + /* Only BC++ 4.x and later, but Borland has + seriously mangled version info (__BORLANDC__) */ +# if defined (__BCPLUSPLUS__) +# if (__BCPLUSPLUS__ > 0x0310) /* after 3.1 */ +# define _CPP_ANSI20_ +# endif +# elif defined(_CC_POWERPACK_) +# define _CPP_ANSI20_ +# endif +#elif defined (_CC_MSC_) + /* Current release through 8.x doesn't support new[] */ +#elif defined (_CC_WATCOM_) +# if (__WATCOMC__ >= 1000) /* 10.x */ +# define _CPP_ANSI20_ +# endif +#endif + + +/*******************************************************************/ +/*******************************************************************/ + +/* *** Code and Data Size Constants *** + LCODE not used by this header file. + LDATA *is* used, however. + + May be commented out if these constants are already defined. +*/ + +/* #ifndef LCODE */ +#if defined(M_I86MM) || defined(M_I86LM) || defined(M_I86HM) +# define LCODE 1 +#elif defined(__MEDIUM__) || defined(__LARGE__) || defined(__HUGE__) +# define LCODE 1 +#endif +/* #endif */ + +#if defined(__COMPACT__) || defined(__LARGE__) || defined(__HUGE__) +# define LDATA 1 +#elif defined(M_I86CM) || defined(M_I86LM) || defined(M_I86HM) +# define LDATA 1 +#endif + +/* Macro "aliases" */ + +#ifndef _LCODE +# ifdef LCODE +# define _LCODE LCODE +# endif +#endif + +#ifndef _LDATA +# ifdef LDATA +# define _LDATA LDATA +# endif +#endif + +/*******************************************************************/ +/*******************************************************************/ + +/* *** Physical Stack Address *** */ + +#if defined(_CC_BORLAND_) /* -------------------------- */ + + /* + BORLAND RTL Notes: + ;---------------------------------------------------------------------- + ; In large data models, the stack is in its own segment. The stack + ; starts at SS:__stklen and grows down to SS:0. + ; + ; In small data models, the stack is in the DGROUP, and grows down + ; to meet the heap. The end of the heap is marked by ___brklvl. + (KWB: Note that the brklvl is adjusted upwards until it meets + _stklen...) + ;---------------------------------------------------------------------- + */ + +# define STACKSLOP 256 + extern unsigned cdecl _stklen; + +# if defined(_CC_POWERPACK_) +# define STACKTOP (mc_stacktop()) +# define STACKEND (mc_stackend()) +# else /* not P-Pack */ +# ifdef LDATA + /* Compact, Large, Huge Models ... + + The stack starts at SS:_stklen and + grows downward to SS:0 + */ +# define STACKTOP ((unsigned) _stklen) +# define STACKEND ((unsigned) 0 + STACKSLOP) + +# else + /* Small, Medium Models ... + + The stack starts at SS:0xFFFE and grows + downwards _stklen bytes. + */ +# define STACKTOP ((unsigned) 0xFFFE) +# define STACKEND (STACKTOP - _stklen + STACKSLOP) +# endif +# endif /* not PowerPack */ + +#elif defined (_CC_MSC_) /* ------------------------------- */ + + extern char cdecl end; /* end of stack */ + extern unsigned cdecl _atopsp; /* top of stack */ + +# define STACKTOP _atopsp +# define STACKSLOP 256 + +# ifdef LDATA + /* If in far data, stack could be in its own + seg. tho not usually. see /FARSTACK */ +# define STACKEND ((unsigned) ((unsigned long)&end) + STACKSLOP) +# else + /* If near data, must be in DS; use near ptr */ +# define STACKEND ((unsigned)&end + STACKSLOP) +# endif + +#elif defined (_CC_WATCOM_) /* ------------------------------- */ + + extern unsigned _STACKLOW; /* end of stack */ + extern unsigned _STACKTOP; /* top of stack */ + +# define STACKTOP (_STACKTOP) +# define STACKSLOP 256 + +# ifdef LDATA +# define STACKEND ((unsigned) (_STACKLOW + STACKSLOP)) +# else +# define STACKEND ((unsigned) (_STACKLOW + STACKSLOP)) +# endif + +#else /* Unknown compiler ---------------- */ + +#error Unknown compiler at __FILE__(__LINE__) + +#endif /* defining stack top, end */ + +/*******************************************************************/ +/*******************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif /* _ccdefs already #included */ + +/* End CCDEFS */ + + +#if !defined (NULL) /* pull in stdio.h if not already */ +# include +#endif + +/* Backup... sometimes NULL defined in other headers */ +#if !defined (_IOFBF) /* pull in stdio.h if not already */ +# include +#endif + + +/* *** MemCheck Constants *** */ + +/* Standard from MemCheck 3.0 onwards. + Access major version and revision + via mc_version() and mc_revision() macros. +*/ +#define _MC_VERSION 0x0300 /* welcome 3.0 the powerful */ + +#define mc_version() ((int)((_MC_VERSION & 0xFF00) >> 8)) +#define mc_revision() ((int)(_MC_VERSION & 0x00FF)) + +#if defined (_CC32_) /* 32-bit Intel target */ +#define PRT_FP "0x%p" +#else +#define PRT_FP "%Fp" +#endif + +/* *** MCID Macro *** */ + +/* Allows later flexibility in assigning mapping... + Also makes MCIDs formulaic +*/ +#define _MCID(f) (MCID)(__paste(MCID_,f)) + +/* + MemCheck Function ID's (MCID's) + + These are the indices used to retrieve information + about specific runtime library calls. +*/ +/* --- MEMCHECK INTERNAL FUNCTIONS --- */ + +#define _MCID_FIRST_INTERNAL 0 /* index of first internal func */ + +#define MCID_mc_startcheck 0 +#define MCID_mc_endcheck 1 +#define MCID_mc_check_buffers 2 +#define MCID_mc_check 3 +#define MCID_mc_register 4 +#define MCID_mc_unregister 5 +#define MCID_mc_set_alignsize 6 +#define MCID_mc_set_checkbytes 7 +#define MCID_mc_nullcheck 8 +#define MCID_mc_breakpoint 9 +#define MCID_mc_debug 10 +#define MCID_mc_set_location 11 +#define MCID_mc_stack_trace 12 +#define MCID_mc_report 13 + +#define _MCID_LAST_INTERNAL 14 /* Set = to last internal ID */ + + +/* *************** STANDARD C ROUTINES ******************* */ + +#define _MCID_FIRST_ANSI (_MCID_LAST_INTERNAL+1) + +#define MCID_calloc (_MCID_FIRST_ANSI + 0) +#define MCID_free (_MCID_FIRST_ANSI + 1) +#define MCID_malloc (_MCID_FIRST_ANSI + 2) +#define MCID_memcpy (_MCID_FIRST_ANSI + 3) +#define MCID_memmove (_MCID_FIRST_ANSI + 4) +#define MCID_memset (_MCID_FIRST_ANSI + 5) +#define MCID_realloc (_MCID_FIRST_ANSI + 6) +#define MCID_sprintf (_MCID_FIRST_ANSI + 7) +#define MCID_strcat (_MCID_FIRST_ANSI + 8) +#define MCID_strcpy (_MCID_FIRST_ANSI + 9) +#define MCID_strncat (_MCID_FIRST_ANSI + 10) +#define MCID_strncpy (_MCID_FIRST_ANSI + 11) +#define MCID_vsprintf (_MCID_FIRST_ANSI + 12) + +#define _MCID_LAST_ANSI MCID_vsprintf /* define to last ANSI */ + + +/* *************** MICROSOFT C ******************* */ + +#if defined(_CC_MSC_COMPATIBLE_) + +#define _MCID_FIRST_MSC (_MCID_LAST_ANSI + 1) + +# define MCID__expand (_MCID_FIRST_MSC + 0) +# define MCID__ffree (_MCID_FIRST_MSC + 1) +# define MCID__fcalloc (_MCID_FIRST_MSC + 2) +# define MCID__fmalloc (_MCID_FIRST_MSC + 3) +# define MCID__fmsize (_MCID_FIRST_MSC + 4) +# define MCID__frealloc (_MCID_FIRST_MSC + 5) +# define MCID__fexpand (_MCID_FIRST_MSC + 6) +# define MCID__msize (_MCID_FIRST_MSC + 7) + +# define MCID__fmemmove (_MCID_FIRST_MSC + 8) +# define MCID__fmemcpy (_MCID_FIRST_MSC + 9) +# define MCID__fmemset (_MCID_FIRST_MSC + 10) +# define MCID__fmemccpy (_MCID_FIRST_MSC + 11) +# define MCID__fstrcat (_MCID_FIRST_MSC + 12) +# define MCID__fstrncat (_MCID_FIRST_MSC + 13) +# define MCID__fstrcpy (_MCID_FIRST_MSC + 14) +# define MCID__fstrncpy (_MCID_FIRST_MSC + 15) +# define MCID__fstrdup (_MCID_FIRST_MSC + 16) +# define MCID__fstrset (_MCID_FIRST_MSC + 17) +# define MCID__fstrnset (_MCID_FIRST_MSC + 18) + +# define MCID__nfree (_MCID_FIRST_MSC + 19) +# define MCID__nmalloc (_MCID_FIRST_MSC + 20) +# define MCID__ncalloc (_MCID_FIRST_MSC + 21) +# define MCID__nrealloc (_MCID_FIRST_MSC + 22) +# define MCID__nexpand (_MCID_FIRST_MSC + 23) +# define MCID__nmsize (_MCID_FIRST_MSC + 24) +# define MCID__nstrdup (_MCID_FIRST_MSC + 25) + +# define MCID__dos_setvect (_MCID_FIRST_MSC + 26) +# define MCID__getdcwd (_MCID_FIRST_MSC + 27) + +/* Here starts the Great ANSI Divide. + MSC6 and earlier have no underscores; + MSC7 and later *have* underscores to emphasize + departure from ANSI... +*/ +#if defined(_CC_MSC_) && !defined (MSC6) /* not MSC6-compatible */ + +# define MCID__getcwd (_MCID_FIRST_MSC + 28) +# define MCID__cgets (_MCID_FIRST_MSC + 29) +# define MCID__halloc (_MCID_FIRST_MSC + 30) +# define MCID__hfree (_MCID_FIRST_MSC + 31) +# define MCID__memccpy (_MCID_FIRST_MSC + 32) +# define MCID__strdup (_MCID_FIRST_MSC + 33) +# define MCID__strnset (_MCID_FIRST_MSC + 34) +# define MCID__strset (_MCID_FIRST_MSC + 35) +# define MCID__swab (_MCID_FIRST_MSC + 36) +# define MCID__tempnam (_MCID_FIRST_MSC + 37) + +#else /*** MSC6 and before; WATCOM ***/ + +/* No leading underscores */ + +# define MCID_getcwd (_MCID_FIRST_MSC + 28) +# define MCID_cgets (_MCID_FIRST_MSC + 29) +# define MCID_halloc (_MCID_FIRST_MSC + 30) +# define MCID_hfree (_MCID_FIRST_MSC + 31) +# define MCID_memccpy (_MCID_FIRST_MSC + 32) +# define MCID_strdup (_MCID_FIRST_MSC + 33) +# define MCID_strnset (_MCID_FIRST_MSC + 34) +# define MCID_strset (_MCID_FIRST_MSC + 35) +# define MCID_swab (_MCID_FIRST_MSC + 36) +# define MCID_tempnam (_MCID_FIRST_MSC + 37) + +#endif /* MSC6 ANSI calls */ + +# define MCID_new (_MCID_FIRST_MSC + 38) +# define MCID_delete (_MCID_FIRST_MSC + 39) +# define MCID__fullpath (_MCID_FIRST_MSC + 40) + +#endif /* Microsoft C-compatible calls */ + + +/* *************** BORLAND C ******************* */ + +#if defined (_CC_BORLAND_) + +#define _MCID_FIRST_BC (_MCID_LAST_ANSI + 1) + +# define MCID__fmemmove (_MCID_FIRST_BC + 0) +# define MCID__fmemcpy (_MCID_FIRST_BC + 1) +# define MCID__fmemset (_MCID_FIRST_BC + 2) +# define MCID__fmemccpy (_MCID_FIRST_BC + 3) +# define MCID__fstrcat (_MCID_FIRST_BC + 4) +# define MCID__fstrncat (_MCID_FIRST_BC + 5) +# define MCID__fstrcpy (_MCID_FIRST_BC + 6) +# define MCID__fstrncpy (_MCID_FIRST_BC + 7) +# define MCID__fstrdup (_MCID_FIRST_BC + 8) +# define MCID__fstrset (_MCID_FIRST_BC + 9) +# define MCID__fstrnset (_MCID_FIRST_BC + 10) + +# define MCID__dos_setvect (_MCID_FIRST_BC + 11) +# define MCID__getdcwd (_MCID_FIRST_BC + 12) + +# define MCID_getcwd (_MCID_FIRST_BC + 13) +# define MCID_cgets (_MCID_FIRST_BC + 14) +# define MCID_memccpy (_MCID_FIRST_BC + 15) +# define MCID_strdup (_MCID_FIRST_BC + 16) +# define MCID_strnset (_MCID_FIRST_BC + 17) +# define MCID_strset (_MCID_FIRST_BC + 18) +# define MCID_swab (_MCID_FIRST_BC + 19) +# define MCID_tempnam (_MCID_FIRST_BC + 20) + +# define MCID_farmalloc (_MCID_FIRST_BC + 21) +# define MCID_farrealloc (_MCID_FIRST_BC + 22) +# define MCID_farfree (_MCID_FIRST_BC + 23) +# define MCID_farcalloc (_MCID_FIRST_BC + 24) +# define MCID_movmem (_MCID_FIRST_BC + 25) +# define MCID_setmem (_MCID_FIRST_BC + 26) +# define MCID_setvect (_MCID_FIRST_BC + 27) +# define MCID_stpcpy (_MCID_FIRST_BC + 28) +# define MCID__fmovmem (_MCID_FIRST_BC + 29) +# define MCID__fsetmem (_MCID_FIRST_BC + 30) +# define MCID_new (_MCID_FIRST_BC + 31) +# define MCID_delete (_MCID_FIRST_BC + 32) +# define MCID__fullpath (_MCID_FIRST_BC + 33) + +#endif + + +/* + 'TOUCH' macro so high warning levels don't generate + 'unreferenced variable' warnings, especially when + making Production libraries... All MemCheck code + compiles without a whymper. +*/ +#if defined (_CC_WATCOM_) +# define TOUCH(var) var = var +#elif defined (_CC_BORLAND4_) +# define TOUCH(var) var = var +#else +# define TOUCH(var) if (var) +#endif + + +/* Default log name used by stock erf_logfile() and variants... */ +#define MEMCHECK_LOG "MEMCHECK.LOG" + +#define MAX_MEMORY 1000 /* 1000K is more than ever possible */ + +/* User-Modifiable Defaults */ + +#define D_CheckByteCt sizeof(int) /* word size is default */ +#define D_AlignSize sizeof(int) /* align returned memory ptrs */ + +/* Number of bytes to copy from null segment (to determine null + pointer assignments) +*/ +#define D_NULLCHECK_BYTES_FAR 16 /* at 0000:0000 (far NULL) */ +#define D_NULLCHECK_BYTES_NEAR 16 /* at DS:0000 (near NULL) */ +#define MAX_NULLCHECK_BYTES_FAR 1024 /* extent of irupt vect tbl */ +#define MAX_NULLCHECK_BYTES_NEAR 66 /* reserved in DS */ + +/* Unroll the double-negative */ +/* + Debugging code specific to MemCheck can be + conditionally compiled by placing it within + #if-#endif sections: (NOTE that this is NOT + required when just using API functions) + + #ifdef MEMCHECK + + void _MCCALLBACK trackf_special (int op, MEMRECP memrecp) + { + (... your custom callback code ...) + } + + #endif + + instead of the more arcane + + #ifndef NOMEMCHECK + : + #endif + + (Both approaches work equally well, however...) +*/ +#ifndef NOMEMCHECK /* MemCheck active */ +#define MEMCHECK +#endif + + +/* *** Calling Conventions *** */ + +#if !defined (_CC_ANSI_) +#define _MCAPI pascal /* MemCheck API functions */ +#define _FASTAPI pascal /* speed-critical functions */ +#define _MCCDECL cdecl /* MemCheck varargs API */ +#define _MCCALLBACK cdecl /* callback functions */ +#define _MCVAR cdecl /* MemCheck global variable */ +#else +#define _MCAPI /* MemCheck API functions */ +#define _FASTAPI /* speed-critical functions */ +#define _MCCDECL /* MemCheck varargs API */ +#define _MCCALLBACK /* callback functions */ +#define _MCVAR /* MemCheck global variable */ +#endif + +#if !defined(_CC_WATCOM_) +# define _RTL _MCCDECL /* RTL calling conv */ +#else +# define _RTL /* RTL calling conv */ + +/* WATCOM C++ does not currently (2/17/94) + accept "cdecl" as a modifier on variables... +*/ +# undef _MCVAR +# define _MCVAR +#endif /* _CC_WATCOM_ */ + +/* 32-bit compiler-independent stuff */ +#if !defined(_CC32_) +#define _MCFAR far +#define _MCFARCALL far +#define _MCNEAR near +#define _MCNEARCALL near +#define _MCHUGE huge +#else +#define _MCFAR +#define _MCFARCALL +#define _MCNEAR +#define _MCNEARCALL +#define _MCHUGE +#endif /* _CC32_ */ + +/* + MSC declares the following routines as "far"... + So does Borland. WATCOM does not; define glue. + + _fstrset _fstrnset _fstrcpy + _fstrncpy _fstrcat _fstrncat + _fmemset _fmemmove _fmemccpy +*/ +#if !defined(_CC_WATCOM_) +# define _MCFARGLUE far +#else +# define _MCFARGLUE +#endif + + +/* Microsoft C7 and later will not have + have a malloc_mc, only _fmalloc_mc; likewise + with free_mc. + The RTLMALLOC and RTLFREE macros are used + to refer to a generically present back-end malloc + and free. +*/ +#if defined (_CC_MSC_) +# if defined (LDATA) +# define RTLMALLOC RTL(_fmalloc) +# define RTLFREE RTL(_ffree) +# else +# define RTLMALLOC RTL(_nmalloc) +# define RTLFREE RTL(_nfree) +# endif +#else /* non-MSC */ +# define RTLMALLOC RTL(malloc) +# define RTLFREE RTL(free) +#endif + + +/* WATCOM defines its atexit funcs as a "register", + which causes a param type mismatch. + _ATEXITFUNC calling convention smooths this out. +*/ +#if defined (_CC_WATCOM_) +# define _ATEXITFUNC +#else +# define _ATEXITFUNC _MCCDECL +#endif + + +/* MemCheck Tracking Mode + + Returned by mc_get_mode(). + Indicates whether information on each allocation + is being stored in memory or on disk. +*/ +#define MC_USING_MEMORY 1 +#define MC_USING_DISK 2 + + +/* Min, max orders for each node in the B-tree */ + +#define BT_ORDER_MIN 5 +#define BT_ORDER_MAX 255 /* maximum tree order */ +#define BT_ORDER_DEFAULT 19 /* default tree order */ + +/* + Returned by mc_get_speed(). + Pass as params to mc_set_speed(). +*/ +#define MC_RUN_NORMAL 1 +#define MC_RUN_FAST 2 + +/* For mc_report(): + "Flags" field of the MEMREC structure + is set to REPORT_START or REPORT_END + to indicate begin and end of report. + + NOTE: If REPORT_START or REPORT_END conflicts + with defines in your project, just comment + them out and use the MC_... variants instead. +*/ +#define REPORT_START (MRFLAGS)0xFE +#define REPORT_END (MRFLAGS)0xFD + +#define MC_REPORT_START (MRFLAGS)0xFE /* alternates in case of conflict */ +#define MC_REPORT_END (MRFLAGS)0xFD + + +/* + Maximum number of breakpoints that + can be set via mc_breakpoint(). +*/ +#define MC_MAX_BREAKS 50 + + +/* "Optype" parameter on Tracking function callback. */ +#define TRACKF_ADD 1 /* record being added to tree */ +#define TRACKF_DEL 2 /* record being deleted from tree */ + +/* Used for the mcflags field of MEMREC to indicate + whether file & line are exact or approximate +*/ +#define MRFLAG_EXACT_LOC ( (MRFLAGS) 0x01) + +/* + Set if the values for a MEMREC are already converted + to "user" values. +*/ +#define MRFLAG_USER_SPECS ( (MRFLAGS) 0x02) +#define MRFLAG_NO_CHECKBYTES ( (MRFLAGS) 0x04) + +/* Alternate name */ +#define mc_message mc_debug + +/* + Parameter to mc_check_transfer() that + specifies that the given data transfer function cannot + have overlapping source & destination. + (MCID's are unsigned bytes.) +*/ +#define MCF_NO_OVERLAP ((unsigned)0x8000) +#define NO_OVERLAP(mcid) ((mcid) | MCF_NO_OVERLAP) + +/* Parameter to mc_check_transfer indicating that + the found memory record is not needed */ +#define NO_MEMREC ((MEMRECP)NULL) +#define NOMEMREC NO_MEMREC + +/* Parameter to mc_check_transfer indicating that + there is no source pointer operand associated + with the data transfer being checked: e.g. memset. */ +#define NO_SOURCE ((void _MCFAR *)0xFFFFFFFA) + + +/* *** TYPEDEFS *** */ + +typedef char * MCSF; /* MemCheck source file */ +typedef unsigned int MCSL; /* MemCheck source line */ +typedef unsigned char MCID; /* MemCheck function ID */ + +typedef unsigned long MCEFLAGS; /* MemCheck error flags */ +typedef void _MCFAR * MCPTR; /* type of ptr stored in tree */ +typedef unsigned char MRFLAGS; /* flags in MEMRECORD */ +typedef unsigned long MCFLAGS; /* MemCheck settings flags */ + +/* MemCheck Rocket allocator prototypes */ +typedef void _MCFAR * (_MCFAR *ROCKETALLOCF) (size_t); +typedef void (_MCFAR *ROCKETFREEF) (void _MCFAR *); + +#pragma pack(1) +/* + Memory Tracking Structure (MEMREC) + + This is the data structure for buffers being + tracked by MemCheck. +*/ +typedef struct MemRecord + { + MCPTR ptr; /* heap/registered ptr */ + MCID mcid; /* MemCheck function ID */ + MRFLAGS flags; /* internal MC flags */ + unsigned long allocno; /* cardinality of allocation */ + unsigned long size; /* size of block */ + MCSF file; /* source file */ + MCSL line; /* source line */ + + } MEMREC, _MCFAR *MEMRECP; + + +/* *** SETTINGS *** */ +/* These are values that describe the life of a MemCheck run. */ + +typedef struct MCSETTINGS { + /* + Bit Flag What + --- --------------- ----------------------------------- + 0 MCF_ACTIVE MemCheck active or off + 1 MCF_FAST_MODE Fast mode or normal + 2 MCF_PROTECTED_MODE Protected mode or real + 3 MCF_FAR_NULL_CHECK Check for far NULL ptr assigns * + 4 MCF_NEAR_NULL_CHECK Check for far NULL ptr assigns * + 5 MCF_STANDARD_STACK Standard stack frame * + 6 MCF_AUTOINIT Start up automatically * + 7 MCF_CLEAR_ON_FREE Clear buffers when freed + 8 MCF_DISK_ROCKET Use DiskRocket options + 9 MCF_IDX_IN_MEMORY Use memory only for Rocket indexes * + (only if DiskRocket linked) + 10 MCF_SOURCE_ONLY Intercept in source code only + + 11 - 31 Reserved + */ + MCFLAGS Flags; /* Main settings flags */ + + unsigned short MaxMem; /* Max mem for tree usage, in K */ + unsigned short NearNullBytes; /* bytes to check in near null */ + unsigned short FarNullBytes; /* " " " " far " */ + unsigned char CheckByteCt; /* check byte count */ + unsigned char AlignSize; /* alignment boundary size */ + char TrackingDir[36]; /* Rocket stores temp files here */ + + } MCSETTINGS, *MCSETTINGSP; + + +/* Random 32-bit .CFG file sentinel */ +#define MC_CFG_FILE_SENTINEL ( (unsigned long) 0x10F23BC4 ) + +typedef struct MCCfgInfo { + + unsigned long sentinel; /* always MC_CFG_FILE_SENTINEL */ + MCSETTINGS MemCheckSettings; /* saved by user */ + + } MCCFGINFO, *MCCFGINFOP; + + +#ifndef _CC32_ + +/* 16-bit exception stack */ +typedef struct { + + unsigned xRetIP; + unsigned xRetCS; + unsigned xErr; + unsigned xIP; + unsigned xCS; + unsigned xFlags; + unsigned xSP; + unsigned xSS; + + } MCEXCEPTINFO; + +#else + +/* 32-bit exception stack */ +typedef struct { + + unsigned long xRetEIP; + unsigned short xRsvd1; + unsigned short xRetCS; + unsigned long xErr; + unsigned long xEIP; + unsigned short xRsvd2; + unsigned short xCS; + unsigned long xFlags; + unsigned long xESP; + unsigned short xRsvd3; + unsigned short xSS; + + } MCEXCEPTINFO; + +#endif /* _CC32_ */ + +/* Values for MCCRITSECT.action */ +#define MCCS_ENTER_SECTION 0 +#define MCCS_LEAVE_SECTION 1 + +#define MCCS_ACTION(pMCCS) (pMCCS->nAction) +#define MCCS_ENTER(pMCCS) ((*(pMCCS->pLocked))++) /* inc flag */ +#define MCCS_LEAVE(pMCCS) ((*(pMCCS->pLocked))--) /* dec flag */ + +/* + Critical section object - ptr to this passed to crit sect callback + WARNING: access these fields ONLY via the MCCS_...() macros. + To do otherwise subjects you to changes in implementation + of the underlying coordination of critical section locking. +*/ +typedef struct { + int nAction; /* MCCS_ENTER/LEAVE_SECTION */ + int * pLocked; /* # times entered */ + unsigned long ulRsvd; /* internal use */ + } MCCRITSECT; + +#pragma pack() + + +#define MC_FEXIT ( (MCID) 0xFF ) + + +/* Error Reporting Function typedef */ +#ifndef _ERF_DEFINED +#define _ERF_DEFINED +typedef void (_MCCALLBACK *ERF) (char *); +#endif + + +/* *** Callback Functions *** */ + +/* Interception callback (on every interception) */ +typedef void (_MCCALLBACK * GLOBALF) (void); + +/* Called whenever nodes added to or deleted from MC database */ +typedef void (_MCCALLBACK *TRACKF) (int, MEMRECP); + +/* User-definable check function to add to transfer checking */ +typedef void (_MCCALLBACK * CHECKF) ( + int , /* 0 or MCE_... error val for this xfer op */ + void _MCFAR *, /* user ptr dest */ + long /* bytes to copy to dest */ + ); + +/* Funcs called at startup or shutdown */ +typedef void (_MCCALLBACK *STARTF) (void); +typedef void (_MCCALLBACK *ENDF) (void); + +/* Report function type passed to mc_report() */ +typedef void (_MCCALLBACK *REPORTF) (MEMRECP); + +/* Additional heap pointer verification (troubleshoot only) */ +typedef int (_MCCALLBACK *VERIFYF) (void _MCFAR *); + +typedef void (*MCVOIDFP) (void); + +/* Exception handler */ +typedef void (_MCCALLBACK _MCFAR *MCEXCEPTF) (void); + +/* Multitasking enter/exit critical section callback. + Specify as param to mc_set_critf() at beginning of program; + callback function will be called with MCCS_ENTER_SECTION + on entry to MemCheck, or MCCS_LEAVE_SECTION on exit. + + NOT TO BE CONFUSED WITH A MEMCHECK "GLOBAL" FUNCTION. + Global functions (GLOBALF's) are used to perform any + actions on the interception of a runtime function; + CRITF's must be used ONLY to serialize access to MemCheck. +*/ +typedef void (_MCCALLBACK * MCCRITF) (MCCRITSECT *); + + +/* Stack Frame Handler + + You can define your own function to + record, analyze, or inspect each stack frame + when mc_stack_trace() is called. + + You *cannot* modify ANY of the values passed + in, as the "const" typing indicates. If you need to + modify a value, make a copy. See the MemCheck 3.0 + documentation for more details on stack frame handlers. +*/ + +typedef void (_MCFAR _MCCDECL *_SSFRAMEHANDLER ) ( + short const , /* AX: near/far/error flag */ + unsigned short const , /* CX: near (default) rtn CS */ + unsigned short const , /* ES: far rtn CS */ + unsigned const , /* DI: rtn offset from stack */ + short const /* DX: frame count */ + ); + +/* Values for "flag" constant parameter to a + stack frame handler. +*/ +#define TRACE_BAD_FRAME 0x00 /* couldn't recognize frame */ +#define TRACE_FAR_CALL 0x01 /* frame represents a far call */ +#define TRACE_NEAR_CALL 0x02 /* " " " near " */ +#define TRACE_BAD_CHAIN 0x03 /* frame BP chewed up */ +#define TRACE_BEGIN 0x80 /* signals begin walk */ +#define TRACE_END 0x81 /* signals end of walk */ + + +/* MC Settings Structure, "flags" member: */ +#define MCF_ACTIVE (MCFLAGS)(0x01) +#define MCF_FAST_MODE (MCFLAGS)(0x02) +#define MCF_PROTECTED_MODE (MCFLAGS)(0x04) +#define MCF_FAR_NULL_CHECK (MCFLAGS)(0x08) +#define MCF_NEAR_NULL_CHECK (MCFLAGS)(0x10) +#define MCF_STANDARD_STACK (MCFLAGS)(0x20) +#define MCF_AUTOINIT (MCFLAGS)(0x40) +#define MCF_CLEAR_ON_FREE (MCFLAGS)(0x80) +#define MCF_DISK_ROCKET (MCFLAGS)(0x100) +#define MCF_IDX_IN_MEMORY (MCFLAGS)(0x200) +#define MCF_SOURCE_ONLY (MCFLAGS)(0x400) + + +/* *** Conditional Compilation *** */ + +/* -------------------------------------------------------------- + If MEMCHECK is not being `compiled out' (via definition + of the constant NOMEMCHECK), include this section... +-------------------------------------------------------------- */ + +#if !defined(MEMCHECK) + +/* Include Section for `MemCheck Not Active' */ + +/* ***************************** + MemCheck Not Active Section + ***************************** + + This section completely removes or + "evaporates" all MemCheck function references + from your projects when you compile with + NOMEMCHECK #defined. + + There's no need to remove any MemCheck + headers or statements from your code + to produce a full production version + of your application. + + o + ooo + ooooooo + ooooooooo + ooooooooooo + ooo + ooo + ooo + ooo + */ + +#ifndef MEMCHECK_MODULE + +/* Evaporate all MemCheck 3.0 API + statements to do nothing, safely... */ + +# define mc_alloc_count() 0L +# define mc_blocks_allocated() 0L +# define mc_blocks_freed() 0L +# define mc_breakpoint(fi) 0 +# define mc_bytes_allocated() 0L +# define mc_bytes_freed() 0L +# define mc_check(p) 0 +# define mc_check_buffers() 0 +# define mc_cur_file() "No file" +# define mc_cur_line() 0 +# define mc_debug(s) +# define mc_debugf(_args) +# define mc_debug_on() +# define mc_debug_off() +# define mc_endcheck() (MCEFLAGS)0 +# define mc_errno() MCE_NO_ERROR +# define mc_error_flags() (MCEFLAGS)0 +# define mc_error_text(e) "MemCheck not active" +# define mc_except_text(e) "MemCheck not active" +# define mc_file() "No file" +# define mc_find_buffer(p,mr) 0 +# define mc_func() (MCID)0 +# define mc_func_name(mcid) ("") +# define mc_get_erf() (ERF)NULL +# define mc_get_mode() 0 +# define mc_get_speed() 0 +# define mc_in_source() 0 +# define mc_is_active() 0 +# define mc_line() 0 +# define mc_load_debugger() +# define mc_location_text() "MemCheck not active" +# define mc_memory_leaked() 0L +# define mc_memrec() (MEMRECP)NULL +# define mc_memrec_text() "MemCheck not active" +# define mc_msg_continued() 0 +# define mc_nullcheck() 0 +# define mc_null_snapshot() +# define mc_register(p,s) +# define mc_report(_f) +# define mc_set_alignsize(_s) +# define mc_set_breakfile(_f) +# define mc_set_checkbytes(_cb) +# define mc_set_checkf(_f) (CHECKF)NULL +# define mc_set_critf(_f) +# define mc_set_endf(erf) (ENDF)NULL +# define mc_set_erf(erf) (ERF)NULL +# define mc_set_globalf(_f) (GLOBALF)NULL +# define mc_set_globalexitf(_f) (GLOBALF)NULL +# define mc_set_speed(_s) +# define mc_set_location() +# define mc_set_trackf(_f) (TRACKF)NULL +# define mc_set_tracefile(_f) +# define mc_set_tree_order(_q) +# define mc_set_track_dir(_dir) +# define mc_source_ptr() (MCPTR)NULL +# define mc_stack_trace(_memo) 0 +# define mc_startcheck(_erf) +# define mc_unregister(p) +# define mc_set_exceptf(f) +# define mc_get_exceptf() ((MCEXCEPTF)NULL) + +/* *** Stock error reporting functions *** */ +# define erf_default(_msg) +# define erf_standard(_msg) +# define erf_logfile(_msg) +# define erf_log_only(_msg) +# define erf_trace(_msg) + +/* Internal Helpers */ +# define _direct_output(_s) +# define _mcsl(_f,_l) +# define _mcsl_delete(_f,_l) +# define _mcsl_new(_f,_l) +# define _mcslx(_f,_l,_s) +# define _mc_set_delflag() +# define _mc_set_location(_f,_l) +# define _mc_set_newflag() + +/* Link-time compileouts */ +# define MC_SET_AUTOINIT(_toggle) +# define MC_SET_CHECK_FREQ(_freq) +# define MC_SET_CHECKF(_f) +# define MC_SET_CRITF(_f) +# define MC_SET_ENDF(_f) +# define MC_SET_ENV_VAR(_envvarname) +# define MC_SET_ERF(_f) +# define MC_SET_GLOBALF(_f) +# define MC_SET_GLOBALEXITF(_f) +# define MC_SET_LOGFILE(_logfilename) +# define MC_SET_PANIC_BUFFERS(_q) +# define MC_SET_SSFH(_f) +# define MC_SET_STARTF(_f) +# define MC_SET_TRACKF(_f) +# define MC_SET_VERIFYF(_f) + +/* Back-end direct */ +#define RTL(_f) _f + +/* *** C++ *** */ +#ifdef __cplusplus + +#define NEW(_object) new _object +#define DELETE(_object) delete _object +#define DELETE_ARR(_arr) delete[] _arr + +#define cpp_malloc(_s) malloc(_s) +#define cpp_calloc(_n,_s) calloc(_n,_s) +#define cpp_free(_p) free(_p) + +/* Borland C++ */ +#define cpp_farmalloc(_s) farmalloc(_s) +#define cpp_farfree(_fp) farfree(_fp) + +/* Microsoft C++-compatibles */ +#define cpp__fmalloc(_s) _fmalloc(_s) +#define cpp__ffree(_fp) _ffree(_fp) + +#endif /* C++ */ + +#endif /* not MEMCHECK_MODULE */ + + +/* #*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*# */ + +#else /* MEMCHECK is defined */ + +/* #*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*# */ + +#pragma message ("MemCheck V3.0") + +/* + ************************* + MemCheck Active Section + ************************* + + The rest of this header file deals with + MemCheck's being compiled into an application. + + */ + +/* Specify that vars and funcs are straight C.. */ +#ifdef __cplusplus +extern "C" { +#endif + + +/* *** ANSI Location Defines *** */ + +#define _MC_NO_FILE ((MCSF) 0) /* just in case... */ +#define _MC_NO_LINE ((MCSL) 0) + + +/* Allow for the possibility of _MCSF_ being + defined to reference a single, static module + filename. This prevents a multiplicity of + static filenames getting added to the DGROUP, e.g. + + static char *_thisfile = __FILE__; + #define _MCSF_ ((MCSF)thisfile) + #include + + This is only needed under MSC pre-VC++. + Borland has "-d" Merge duplicate strings. + VC++ has "/Gf" Elim duplicate strings. +*/ +#if !defined (_MCSF_) +# ifdef __FILE__ +# define _MCSF_ (MCSF)__FILE__ +# else +# define _MCSF_ _MC_NO_FILE +# endif +#endif + +#ifdef __LINE__ +# define _MCSL_ (MCSL)__LINE__ +#else +# define _MCSL_ _MC_NO_LINE +#endif + + +/* *** Standard ANSI C Includes *** + + For va_list function call args + Inclusion of this header won't change + the behavior of host code. +*/ +#if !defined (va_start) /* avoid multiple inclusion... */ +# include +#endif + + +/* *** Compiler-specific includes *** */ + +/*lint -save -e537 Repeated include files (if necessary) */ +#if defined(_CC_MSC_COMPATIBLE_) + +# if !defined (_INC_MALLOC) /* C7.x and later optimization */ +# include +# endif + +# if !defined (_INC_STRING) /* C7.x and later optimization */ +# include +# endif + +#elif defined(_CC_BORLAND_) + +# if !defined (__ALLOC_H) +# include +# endif + +/* String functions must be proto'd before pragmas */ +# if !defined (__STRING_H) +# include +# endif + +#endif /* Compiler-specific includes */ +/*lint -restore */ + +#if defined (_CC_POWERPACK32_) +extern void cdecl mcinitfp_startcheck (void); +extern void cdecl mcexitfp_endcheck (void); +#pragma startup mcinitfp_startcheck 16 +#pragma exit mcexitfp_endcheck 16 +#endif + +/***************************************/ +/* *** MemCheck 3.0 API Prototypes *** */ +/***************************************/ + +/* Internal helper macro - proto shorthand */ +#define _LOCP MCSF,MCSL + +extern unsigned long _MCAPI mc_alloc_count (void); +extern unsigned long _MCAPI mc_blocks_allocated (void); +extern unsigned long _MCAPI mc_blocks_freed (void); +extern unsigned long _MCAPI mc_bytes_allocated (void); +extern unsigned long _MCAPI mc_bytes_freed (void); +extern int _MCAPI mc_check (void _MCFAR *); +extern int _MCAPI mc_check_buffers (void); +extern MCSF _MCAPI mc_cur_file (void); +extern MCSL _MCAPI mc_cur_line (void); +extern void _MCCDECL mc_debugv (const char *, ...); +extern void _MCAPI mc_debug (const char *); +extern MCEFLAGS _MCAPI mc_endcheck (void); +extern MCEFLAGS _MCAPI mc_error_flags (void); +extern char * _MCAPI mc_error_text (int); +extern int _MCAPI mc_errno (void); +extern char * _MCAPI mc_except_text (unsigned); +extern MCSF _MCAPI mc_file (void); +extern int _MCAPI mc_find_buffer(void _MCFAR *realptr,MEMRECP memrecp); +extern MCID _MCAPI mc_func (void); +extern char * _MCAPI mc_func_name(MCID); +extern ERF _MCAPI mc_get_erf (void); +extern MCEXCEPTF _MCAPI mc_get_exceptf (void); +extern int _MCAPI mc_get_mode (void); +extern int _MCAPI mc_get_speed (void); +extern char * _MCAPI mc_get_tracefile (void); +extern int _MCAPI mc_in_source (void); +extern int _MCAPI mc_is_active (void); +extern MCSL _MCAPI mc_line (void); +extern char * _MCAPI mc_location_text (void); +#define mc_load_debugger() _asm int 3 +extern unsigned long _MCAPI mc_memory_leaked (void); +extern char * _MCAPI mc_memrec_text (MEMRECP); +extern MEMRECP _MCAPI mc_memrec (void); +extern int _MCAPI mc_msg_continued (void); +extern int _MCAPI mc_nullcheck (void); +extern void _MCAPI mc_null_snapshot (void); +extern void _MCAPI mc_register (void _MCFAR *, unsigned long); +extern void _MCAPI mc_report (REPORTF); +extern void _MCAPI mc_set_alignsize (unsigned int); +extern void _MCAPI mc_set_breakfile (char *); +extern void _MCAPI mc_set_checkbytes (unsigned int); +extern CHECKF _MCAPI mc_set_checkf (CHECKF); +extern void _MCAPI mc_set_critf (MCCRITF); +extern ENDF _MCAPI mc_set_endf (ENDF); +extern ERF _MCAPI mc_set_erf (ERF); +extern MCEXCEPTF _MCAPI mc_set_exceptf (MCEXCEPTF); +extern GLOBALF _MCAPI mc_set_globalf (GLOBALF); +extern GLOBALF _MCAPI mc_set_globalexitf (GLOBALF); +#define mc_set_location() _mc_set_location(_MCSF_,_MCSL_) +extern MCPTR _MCAPI mc_source_ptr (void); +extern void _MCAPI mc_set_speed (int); +extern void _MCAPI mc_set_tracefile (char *); +extern void _MCAPI mc_set_track_dir (char *); +extern TRACKF _MCAPI mc_set_trackf (TRACKF); +extern void _MCAPI mc_set_tree_order (int); +extern int _MCAPI mc_stack_trace (char *); +extern void _MCAPI mc_startcheck (_LOCP, ERF); +extern void _ATEXITFUNC mc_endcheck_at_exit (void); +extern void _MCAPI mc_unregister (void _MCFAR *); + +/* Debugging versions of the MemCheck library only */ +extern void _MCAPI mc_debug_on (void); +extern void _MCAPI mc_debug_off (void); +extern int _MCCALLBACK mc_search_heap (void _MCFAR *); + +/* *** INTERNAL API HELPERS *** */ +extern void _MCAPI _mc_set_location (_LOCP); +extern void _FASTAPI _mcsl (MCSF,MCSL); /* location run-ahead */ +extern void _FASTAPI _mcslx (MCSF,MCSL,size_t); /* location run-ahead */ +extern void _FASTAPI _mcsl_new (MCSF,MCSL); /* location run-ahead */ +extern void _FASTAPI _mcsl_delete (MCSF,MCSL); /* location run-ahead */ +extern void _FASTAPI _mc_set_newflag (void); /* new's a'comin' */ +extern void _FASTAPI _mc_set_delflag (void); /* delete's a'comin' */ + +/* Misc - uses INT 9 to output directly to screen */ +#if !defined (_CC_WATCOM32_) +extern void _MCCDECL _MCFAR _direct_output (char _MCFAR *); +#else +#define _direct_output(s) printf ("%s\n", s) /* ALERT */ +#endif + +/* + mc_breakpoint() is now a MemCheck Tracking function (TRACKF). + Tracking functions get called every time + MemCheck adds or deletes from its database. +*/ +#define mc_breakpoint(_f) \ + mc_set_trackf( (mc_set_breakfile (_f), trackf_breakpoint) ) +#define mc_breakpoint_trace(_f) \ + mc_set_trackf( (mc_set_tracefile (_f), trackf_breakpoint_trace) ) + + +/* *** Advanced-user API extenders *** */ + +/* extern int _MCAPI mc_find_buffer(void _MCFAR *, MEMRECP); */ +extern int _MCAPI mc_check_transfer( + void _MCFAR *, + void _MCFAR *, + unsigned long, + unsigned, + unsigned, + MEMRECP); + +/* mc_get_settings + + Write your own "get settings" routine + to override the one shipped with MemCheck. + You can hard-wire any settings you like, e.g. + always ON for versions of your app shipped to + testers/QA stations, etc. +*/ +extern void _MCCALLBACK mc_get_settings (MCSETTINGS *); + + +/* *** Callbacks / Functionality Extenders *** + + Function Type Called... + -------------- ------------------------------ + Error reporting To handle each MemCheck error message + Global Interception On each MemCheck interception + Checking On every data transfer check + Tracking On every allocation/deallocation + Start On mc_startcheck or AutoInit + End At mc_endcheck or MemCheck shutdown + + Refer to your MemCheck 3.0 manual for further details. + + *** STOCK FUNCTIONS *** + These functions are available in the MemCheck + libraries as "ready-made" for your programming + pleasure in the categories above. +*/ + +/* *** Stock error reporting functions *** */ + +extern void _MCCALLBACK erf_default (char *); +extern void _MCCALLBACK erf_standard (char *); +extern void _MCCALLBACK erf_logfile (char *); +extern void _MCCALLBACK erf_log_only (char *); +extern void _MCCALLBACK erf_trace (char *); +extern void _MCCALLBACK erf_trace_all (char *); +extern void _MCCALLBACK erf_trace_obj (char *); +extern void _MCCALLBACK erf_stdout (char *); +extern void _MCCALLBACK erf_stderr (char *); +extern void _MCCALLBACK erf_find_leaks (char *); + +#define erf_printf erf_stdout /* alias*/ + +/* *** Stock Tracking Functions *** */ + +extern void _MCCALLBACK trackf_default (int, MEMRECP); +extern void _MCCALLBACK trackf_all (int, MEMRECP); +extern void _MCCALLBACK trackf_all_2 (int, MEMRECP); +extern void _MCCALLBACK trackf_breakpoint (int, MEMRECP); +extern void _MCCALLBACK trackf_breakpoint_trace (int, MEMRECP); +extern void _MCCALLBACK trackf_big_alloc (int, MEMRECP); + +/* *** Stock End Functions *** */ + +extern void _MCCALLBACK endf_default (void); /* does nothing */ +extern void _MCCALLBACK endf_info (void); /* write run info to log */ +extern void _MCCALLBACK endf_alert (void); /* warn if run errs */ +extern void _MCCALLBACK endf_summary (void); /* warn if run errs */ + +/* *** Stock Start functions *** */ + +extern void _MCCALLBACK startf_default (void); /* does nothing */ +extern void _MCCALLBACK startf_info (void); /* write options to log */ + +/* *** Stock Check Functions *** */ + +extern void _MCCALLBACK checkf_default (int,void _MCFAR *,long); +extern void _MCCALLBACK checkf_dataseg ( + int, /* 0 or MCE_... error val for this xfer op */ + void _MCFAR *, /* user ptr dest */ + long /* bytes to copy to dest */ + ); +extern void _MCCALLBACK checkf_verify_heap (int,void _MCFAR *,long); + +/* *** Stock Global Interception Functions *** */ + +extern void _MCCALLBACK globalf_default (void); /* does nothing */ +extern void _MCCALLBACK globalf_supercheck (void); +extern void _MCCALLBACK globalf_check_buffers (void); +extern void _MCCALLBACK globalf_heapcheck (void); + +/* *** Stock Report Functions *** */ +extern void _MCCALLBACK reportf_default (MEMRECP); + +/* *** Stock Exception Handlers *** */ +extern void _MCCALLBACK _MCFAR exceptf_default (void); + +/* *** Stock Stack Frame Handlers *** */ +extern void _MCFAR _MCCDECL ssfh_info ( + short const, unsigned short const, unsigned short const, unsigned const, short const); + +extern void _MCFAR _MCCDECL ssfh_fast ( + short const, unsigned short const, unsigned short const, unsigned const, short const); + /* int const _flag, */ + +extern void _MCFAR _MCCDECL ssfh_standard ( + short const, unsigned short const, unsigned short const, unsigned const, short const); + +extern void _MCFAR _MCCDECL ssfh_debug ( + short const, unsigned short const, unsigned short const, unsigned const, short const); + +/* */ +extern unsigned int _MCAPI mc_stacktop (void); /* high address */ +extern unsigned int _MCAPI mc_stackend (void); /* low address */ + + +/* Function external variables. + + These are used effectively with MemCheck 3.0's AutoInit + setting. Under AutoInit, MemCheck fires itself up automatically + on its first interception. Under these circumstances, + there's no chance to have changed any defaults (like the + ERF or error reporting function). These variables provide + a link-level method of setting these functions: + + #include + : + // Sets custom erf at link-time + MC_SET_ERF (erf_custom_startup); + : +*/ +/* *** GLOBALS *** */ + +extern ERF _MCVAR MC_ERF; /* error reporting func ptr */ +extern CHECKF _MCVAR MC_CheckF; /* transfer check func */ +extern MCCRITF _MCVAR MC_CritF; /* crit section enter/exit */ +extern GLOBALF _MCVAR MC_GlobalF; /* global interception callback */ +extern GLOBALF _MCVAR MC_GlobalExitF; /* called on exit interception */ +extern TRACKF _MCVAR MC_TrackF; /* alloc/dealloc callback */ +extern STARTF _MCVAR MC_StartF; /* startup callback */ +extern ENDF _MCVAR MC_EndF; /* shutdown callback */ + +extern VERIFYF _MCVAR MC_VerifyF; /* troubleshooting */ + +extern char * _MCVAR MC_LogFile; /* log file name used */ +extern char _MCVAR MC_UserAutoInit; +extern int _MCVAR MC_CheckFreq; /* for globalf_supercheck() et al */ +extern char * _MCVAR MC_EnvVar; /* Env var to detect 'active' */ +extern unsigned short _MCVAR MC_DataSeg; /* DS value */ + +extern int _MCVAR MC_MaxTraceDepth; +extern char * _MCVAR MCST_Desc; /* trace descrip to mc_..trc() */ + +extern MCSETTINGS _MCVAR MC_DefaultSettings; /* default settings */ +extern MCSETTINGS _MCVAR MC_Settings; /* real settings-- + USE WITH CARE!!! */ + +extern MCVOIDFP _MCVAR MC_PMMap1; /* p-mode func in map seg 1 */ + +/* Protected mode exception handling */ +extern unsigned char _MCVAR MC_ExceptList[]; /* exceptions to handle */ +extern MCEXCEPTINFO _MCVAR MC_ExceptInfo; /* in exception */ +extern MCEXCEPTF _MCVAR MC_ExceptF; /* installed hdler */ + +/* Rocket Guidance Systems */ +extern ROCKETALLOCF _MCVAR MC_RocketAllocF; +extern ROCKETFREEF _MCVAR MC_RocketFreeF; +extern unsigned char _MCVAR MC_PanicBufCount; /* anti-tree failure */ + +/* This char is used to fill freed buffers + if the "ClearOnFree" option in effect. + Default buffer clear char is 0. +*/ +extern unsigned char _MCVAR MC_FreedBufferFillChar; + +/* Link-time defaults. + + These macros are "covers" to insulate you, the developer, + from the underlying implementation, as well as to provide + such bennies as compiling clean out of your code when + NOMEMCHECK or NOMC is defined. + + Use instead of accessing vars directly! + + To use, place the following in ONE MODULE e.g. your main module + (any *one* module will work fine) after the MemCheck + header file has been included: + + #include + MC_SET_...(params); + + For example, to change the default log file that MemCheck + uses at runtime to C:\MYDEV\MYPROG.LOG: + + #include + MC_SET_LOGFILE ("C:\\MYDEV\\MPROG.LOG"); + + Most of these macros have runtime function equivalents, + such as mc_set_erf() for MC_SET_ERF(), etc. Notable + exceptions to this are the following values that + must generally have link-time initializations: + + MC_SET_LOGFILE() + MC_SET_AUTOINIT() + MC_SET_STARTF() +*/ +#define MC_SET_AUTOINIT(_toggle) \ + char _MCVAR MC_UserAutoInit = (_toggle); +#define MC_SET_CHECKF(_f) \ + CHECKF _MCVAR MC_CheckF = (_f) +#define MC_SET_CHECK_FREQ(_freq) \ + int _MCVAR MC_CheckFreq = (_freq) +#define MC_SET_CRITF(_f) \ + MCCRITF _MCVAR MC_CritF = (_f) +#define MC_SET_ENDF(_f) \ + ENDF _MCVAR MC_EndF = (_f) +#define MC_SET_ENV_VAR(_envvarname) \ + char * _MCVAR MC_EnvVar = (_envvarname) +#define MC_SET_ERF(_f) \ + ERF _MCVAR MC_ERF = (_f) +#define MC_SET_EXCEPTF(_f) \ + MCEXCEPTF _MCVAR MC_ExceptF = (_f) +#define MC_SET_GLOBALF(_f) \ + GLOBALF _MCVAR MC_GlobalF = (_f) +#define MC_SET_GLOBALEXITF(_f) \ + GLOBALF _MCVAR MC_GlobalExitF = (_f) +#define MC_SET_LOGFILE(_f) \ + char * _MCVAR MC_LogFile = (_f) +#define MC_SET_PANIC_BUFFERS(_q) \ + unsigned char _MCVAR MC_PanicBufCount = (_q) +#define MC_SET_SSFH(_f) \ + _SSFRAMEHANDLER _MCVAR near MC_SFrameHandler = (_f) +#define MC_SET_STARTF(_f) \ + STARTF _MCVAR MC_StartF = (_f) +#define MC_SET_TRACKF(_f) \ + TRACKF _MCVAR MC_TrackF = (_f) +#define MC_SET_VERIFYF(_f) \ + VERIFYF _MCVAR MC_VerifyF = (_f) + +/* Use the MC_BEGIN_EXCEPTLIST, MC_HANDLE_EXCEPTION, + and MC_END_EXCEPTLIST macros to change the exceptions + MemCheck handles in protected mode by default. + + Usage (exactly as typed): + #include + : + MC_BEGIN_EXCEPTLIST + MC_HANDLE_EXCEPTION (0x0) + MC_HANDLE_EXCEPTION (0xD) + MC_END_EXCEPTLIST + + NOTE: + To turn off MemCheck's exception handling completely, use + + MC_SET_EXCEPTF(NULL); + + instead of trying to define an empty EXCEPTLIST... +*/ +#define MC_BEGIN_EXCEPTLIST \ + unsigned char _MCVAR MC_ExceptList[] = { +#define MC_HANDLE_EXCEPTION(e) \ + (unsigned char)(e), +#define MC_END_EXCEPTLIST \ + (unsigned char)0xFF }; /* 0xFF MUST end list */ + +/* ------------- End MemCheck 3.0 Library Calls --------------- */ + +/* Formulaic rogue varargs interceptions; + most host-code-compatible method... + "Are you experienced?" + + "It is better to be mugged than + to live in fear." - Anon. +*/ +#define _VA_DEF(f,r,p) \ + typedef r (_RTL *p_##f) p; \ + extern p_##f _MCAPI _loc_##f (_LOCP); + +/* Declare sprintf helper function */ +_VA_DEF(sprintf,int,(char *, const char *, ...)) + + /* * * * * * * * * * * * * * * * * * * * * * * + ************************* + Back-End RTL + ************************* +*/ + +/* *** Back-end functions *** */ + +/* Macro to access true back-end RTL. + Used internally by the MemCheck API functions. +*/ +#define __paste(x,y) x ## y +#define RTL(func) __paste(func,_mc) + +/* Macro to encapsulate the declaration of + the renamed (zapped) back-end rtl +*/ +#define _RTLDECL(f,rctype,params) \ + extern rctype _RTL RTL(f) params + + +/* For the conversion that MSC underwent + from C 6 to 7, where non-ANSI calls + have underbars +*/ +#if defined (_CC_MSC_) && !defined (MSC6) +#if (_MSC_VER >= 700) +# define _C7A +#endif +#endif + +#ifdef _C7A +#define C7ANSI(func) _##func +#else +#define C7ANSI(func) func +#endif + +#undef _C7A + + +/* ---------------------------------------------- */ +/* These are the renamed ("zapped") RTL functions */ +/* ---------------------------------------------- */ + +/* *** ANSI *** */ + +_RTLDECL(malloc, void *, (size_t)); +_RTLDECL(calloc, void *, (size_t, size_t)); +_RTLDECL(realloc, void *, (void *, size_t)); +_RTLDECL(free, void, (void *)); +_RTLDECL(memcpy, void *, (void *,const void *,size_t)); +_RTLDECL(memmove, void *, (void *,const void *,size_t)); +_RTLDECL(memset, void *, (void *,int,size_t)); +_RTLDECL(strcpy, char *, (char *,const char *)); +_RTLDECL(strncpy, char *, (char *,const char *,size_t)); +_RTLDECL(strcat, char *, (char *,const char *)); +_RTLDECL(strncat, char *, (char *,const char *,size_t)); +_RTLDECL(vsprintf, int, (char *,const char *,va_list)); +_RTLDECL(sprintf, int, (char *,const char *,...)); + +#if !defined (_CC_ANSI_) +/* *** MSC *** */ + +/* WATCOM doesn't support these... */ +#if !defined(_CC32_) +_RTLDECL(_fmalloc, void far *, (size_t)); +_RTLDECL(_fcalloc, void far *, (size_t, size_t)); +_RTLDECL(_ffree, void, (void far *)); +_RTLDECL(_fmsize, size_t, (void far *)); +#endif + +_RTLDECL(_nmalloc, void _MCNEAR *,(size_t)); +_RTLDECL(_nfree, void, (void _MCNEAR *)); + +/* *** Borland *** */ + +#if !defined(_CC_POWERPACK32_) +_RTLDECL(farmalloc, void _MCFAR *, (unsigned long)); +_RTLDECL(farcalloc, void _MCFAR *, (unsigned long, unsigned long)); +_RTLDECL(farfree, void, (void _MCFAR *)); + +/* *** General Porpoise *** */ + +_RTLDECL(_fmemset, void far * _MCFARGLUE, (void far *,int,size_t)); +_RTLDECL(_fmemcpy, void far * _MCFARGLUE, (void far *,const void far *,size_t )); +_RTLDECL(_fstrcpy, char far * _MCFARGLUE, (char far *,const void far *)); +#endif /* not _CC_POWERPACK32_ */ + +#endif /* not STDC/ANSI */ + +/***************************************************************** + * -------- Function Call Interception Definitions --------- * + *****************************************************************/ + +#ifndef MEMCHECK_MODULE + +/* + This section targets user's code only +*/ + +/* Func interceptors... */ +#define _INTERCEPT(_f) (_mcsl(_MCSF_,_MCSL_),_f) +#define _VA_INTERCEPT(_f) (*_loc_##_f(_MCSF_,_MCSL_)) +#define _SETLOC(_f) (mc_set_location(),_f) + +/* NOTE near _mcsl with #if (_MCC_NEAR_INTERCEPT == 0) */ + +/* + MC_NO_TRANSFER_SIZE is used to eliminate errors or warnings + like "sizeof returns 0" or "Not allowed type in sizeof ". + These occur for unsized variables declared like + + extern unsigned char gHelpString[]; + + The optimal solution is to "size" the extern, e.g. + + extern unsigned char gHelpString[80]; + + but where this may not be practical, MC_NO_TRANSFER_SIZE may + be defined on a module-by-module OR project-wide basis. +*/ +#ifdef MC_NO_XFER_SIZE /* beta compat */ +# define MC_NO_TRANSFER_SIZE +#endif +#ifdef NO_TRANSFER_SIZE /* alternate */ +# define MC_NO_TRANSFER_SIZE +#endif + +#if defined (MC_NO_TRANSFER_SIZE) +# define _INTERCEPTX(_f,_d) _INTERCEPT(_f) +#else /* standard; transmit sizeof dest */ +# define _INTERCEPTX(_f,_d) (_mcslx(_MCSF_,_MCSL_,sizeof(_d)),_f) +#endif + + +/* Intrinsic Function Disabling + + It's important to disable function inlining for + all intercepted functions. +*/ + +#if defined(_CC_MSC_) + +/* Intrinsics (== in-line functions) not permissible + since they're implemented as macros... +*/ +#pragma function(strcat) +#pragma function(strcpy) +#pragma function(memcpy) +#pragma function(memset) + +#pragma function(strset) + +#if defined(_MSC_VER) +#if (_MSC_VER >= 700) +#pragma function(_fmemcpy) +#pragma function(_fmemset) +#pragma function(_fstrcat) +#pragma function(_fstrcpy) +#pragma function(_fstrset) +#pragma function(_strset) +#endif +#endif /* defined _MSC_VER */ + +#elif defined(_CC_BORLAND_) + +/* Turbo C not like pragmae */ +#if !defined (_CC_TCC_) + +/* Eliminate duplicate strings. + This can save a bit of space in large + programs particularly, since each call to + MemCheck references an otherwise separate + copy of the current filename. +*/ +#pragma option -d + +/* Intrinsics (== in-line functions) not permissible + since they're implemented as macros, for one... +*/ +#pragma intrinsic -strcat +#pragma intrinsic -strncat +#pragma intrinsic -strcpy +#pragma intrinsic -strncpy +#pragma intrinsic -stpcpy +#pragma intrinsic -strset +#pragma intrinsic -strnset +#pragma intrinsic -memcpy +#pragma intrinsic -memset + +#endif /* not Turbo C */ + +/* end Borland compiler intrinsics */ + +#elif defined (_CC_WATCOM_) + +/* NOTE: unfortunately, WATCOM C/C++ compilers + force inlining of the strcpy() function regardless + of whether you want it inlined or not, all the time. + So this pragma, while it should ensure that + strcpy() is a function call, does not... :{ + + So we take other measures below: see _mcwatcom_strcpy() +*/ +#pragma function(strcpy) + +#pragma function(strcat) +#pragma function(memcpy) +#pragma function(memset) + +#pragma function(_fmemcpy) +#pragma function(_fmemset) +#pragma function(_fstrcat) +#pragma function(_fstrcpy) + +#endif + +/* End disable function inlining */ + + +/*lint -save -e652 Define of symbol declared previously */ +#if defined (MC_NO_INTERCEPT) +#define NO_INTERCEPT +#endif + +#if !defined (NO_INTERCEPT) + +/* *** ANSI Standard C *** */ + +#define calloc(n,_sz) _INTERCEPT(calloc(n,_sz)) +#define malloc(_sz) _INTERCEPT(malloc(_sz)) +#define realloc(p,s) _INTERCEPT(realloc(p,s)) +#define free(p) _INTERCEPT(free(p)) + +#define memcpy(d,s,n) _INTERCEPTX(memcpy(d,s,n),d) +#define memmove(d,s,n) _INTERCEPTX(memmove(d,s,n),d) +#define memset(p,c,n) _INTERCEPTX(memset(p,c,n),p) +#define strcat(s1,s2) _INTERCEPTX(strcat(s1,s2),s1) +#if defined(_CC_WATCOM_) + /* WATCOM forces inlining of strcpy()... see note above */ +# define strcpy(d,s) _INTERCEPTX(_mcwatcom_strcpy(d,s),d) + extern char * _RTL _mcwatcom_strcpy (char *, const char *); +#else +# define strcpy(d,s) _INTERCEPTX(strcpy(d,s),d) +#endif +#define strncat(s1,s2,n) _INTERCEPTX(strncat(s1,s2,n),s1) +#define strncpy(d,s,n) _INTERCEPTX(strncpy(d,s,n),d) +#define vsprintf(s,f,a) _INTERCEPTX(vsprintf(s,f,a),s) + +/* #define sprintf _VA_INTERCEPT(sprintf) */ +#ifndef _lint +#define sprintf _INTERCEPT(sprintf) +#endif + +#if defined(_CC_MSC_COMPATIBLE_) /* *** Microsoft C *** */ + +#define _expand(_p,_s) _INTERCEPT(_expand(_p,_s)) +#define _fcalloc(n,_sz) _INTERCEPT(_fcalloc(n,_sz)) +#define _fexpand(_p,_s) _INTERCEPT(_fexpand(_p,_s)) +#define _ffree(p) _INTERCEPT(_ffree(p)) +#define _fmalloc(_sz) _INTERCEPT(_fmalloc(_sz)) +#define _frealloc(p,s) _INTERCEPT(_frealloc(p,s)) +#define _fmsize(p) _INTERCEPT(_fmsize(p)) +#define _msize(p) _INTERCEPT(_msize(p)) +#define _nfree(p) _INTERCEPT(_nfree(p)) +#define _nmalloc(_sz) _INTERCEPT(_nmalloc(_sz)) +#define _nrealloc(p,s) _INTERCEPT(_nrealloc(p,s)) +#define _ncalloc(n,_sz) _INTERCEPT(_ncalloc(n,_sz)) +#define _nexpand(_p,_s) _INTERCEPT(_nexpand(_p,_s)) +#define _nmsize(p) _INTERCEPT(_nmsize(p)) +#define _nstrdup(s) _INTERCEPT(_nstrdup(s)) +/* #define halloc(n,_sz) _INTERCEPT(halloc(n,_sz)) */ +/* #define _halloc(n,_sz) _INTERCEPT(halloc(n,_sz)) */ +/* #define hfree(p) _INTERCEPT(hfree(p)) */ +/* #define _hfree(p) _INTERCEPT(hfree(p)) */ + +#define cgets(s) _INTERCEPTX(cgets(s),s) +#define _cgets(s) _INTERCEPTX(_cgets(s),s) +#define memccpy(d,s,c,n) _INTERCEPTX(memccpy(d,s,c,n),d) +#define _memccpy(d,s,c,n) _INTERCEPTX(_memccpy(d,s,c,n),d) +#define strdup(s) _INTERCEPT(strdup(s)) +#define _strdup(s) _INTERCEPT(_strdup(s)) +#define _strnset(s,c,n) _INTERCEPTX(_strnset(s,c,n),s) +#define strnset(s,c,n) _INTERCEPTX(strnset(s,c,n),s) +#define strset(s,c) _INTERCEPTX(strset(s,c),s) +#define _strset(s,c) _INTERCEPTX(_strset(s,c),s) +#define swab(s,d,n) _INTERCEPTX(swab(s,d,n),d) +#define _swab(s,d,n) _INTERCEPTX(_swab(s,d,n),d) +#define tempnam(d,pfx) _INTERCEPT(tempnam(d,pfx)) +#define _tempnam(d,pfx) _INTERCEPT(_tempnam(d,pfx)) + +#define _fmemcpy(d,s,n) _INTERCEPTX(_fmemcpy(d,s,n),d) +#define _fmemmove(d,s,n) _INTERCEPTX(_fmemmove(d,s,n),d) +#define _fmemset(d,c,n) _INTERCEPTX(_fmemset(d,c,n),d) +#define _fmemccpy(d,s,c,n) _INTERCEPTX(_fmemccpy(d,s,c,n),d) +#define _fstrcat(s1,s2) _INTERCEPTX(_fstrcat(s1,s2),s1) +#define _fstrcpy(d,s) _INTERCEPTX(_fstrcpy(d,s),d) +#define _fstrncat(s1,s2,n) _INTERCEPTX(_fstrncat(s1,s2,n),s1) +#define _fstrncpy(d,s,n) _INTERCEPTX(_fstrncpy(d,s,n),d) +#define _fstrdup(s) _INTERCEPT(_fstrdup(s)) +#define _fstrnset(d,c,n) _INTERCEPTX(_fstrnset(d,c,n),d) +#define _fstrset(d,c) _INTERCEPTX(_fstrset(d,c),d) + +#define getcwd(d,n) _INTERCEPTX(getcwd(d,n),d) +#define _getcwd(d,n) _INTERCEPTX(getcwd(d,n),d) +#define _getdcwd(r,d,n) _INTERCEPTX(_getdcwd(r,d,n),d) +#define _dos_setvect(_i,_h) _INTERCEPT(_dos_setvect(_i,_h)) +#define _fullpath(b,p,n) _INTERCEPTX(_fullpath(b,p,n),b) + +/* ----- END Microsoft C/C++ interceptions ----- */ + +#elif defined (_CC_BORLAND_) /* *** Borland C/C++ *** */ + +#ifndef _CC_POWERPACK32_ +#define farfree(p) _INTERCEPT(farfree(p)) +#define farmalloc(s) _INTERCEPT(farmalloc(s)) +#define farcalloc(n,s) _INTERCEPT(farcalloc(n,s)) +#define farrealloc(p,s) _INTERCEPT(farrealloc(p,s)) +#endif /* not _CC_POWERPACK32_ */ + +#define cgets(s) _INTERCEPTX(cgets(s),s) +#define memccpy(d,s,c,n) _INTERCEPTX(memccpy(d,s,c,n),d) +#if !defined(movmem) +#define movmem(s,d,l) _INTERCEPTX(movmem(s,d,l),d) +#endif +#if !defined(setmem) +#define setmem(d,c,v) _INTERCEPTX(setmem(d,c,v),d) +#endif +#define setvect(i,v) _INTERCEPT(setvect(i,v)) +#define stpcpy(d,s) _INTERCEPTX(stpcpy(d,s),d) +#define _stpcpy(d,s) _INTERCEPTX(_stpcpy(d,s),d) +#define strdup(s) _INTERCEPT(strdup(s)) +#define strnset(s,c,n) _INTERCEPTX(strnset(s,c,n),s) +#define strset(s,c) _INTERCEPTX(strset(s,c),s) +#define swab(s,d,n) _INTERCEPTX(swab(s,d,n),d) +#define tempnam(d,pfx) _INTERCEPT(tempnam(d,pfx)) + +#define getcwd(d,n) _INTERCEPTX(getcwd(d,n),d) +#define _getdcwd(r,d,n) _INTERCEPTX(_getdcwd(r,d,n),d) +#define _dos_setvect(_i,_h) _INTERCEPT(_dos_setvect(_i,_h)) + +#ifndef _CC_POWERPACK32_ +#define _fmemcpy(d,s,n) _INTERCEPTX(_fmemcpy(d,s,n),d) +#define _fmemmove(d,s,n) _INTERCEPTX(_fmemmove(d,s,n),d) +#define _fmemset(d,c,n) _INTERCEPTX(_fmemset(d,c,n),d) +#define _fmemccpy(d,s,c,n) _INTERCEPTX(_fmemccpy(d,s,c,n),d) +#define _fmovmem(s,d,l) _INTERCEPTX(_fmovmem(s,d,l),s) +#define _fsetmem(d,c,v) _INTERCEPTX(_fsetmem(d,c,v),d) +#define _fstrcat(s1,s2) _INTERCEPTX(_fstrcat(s1,s2),s1) +#define _fstrcpy(d,s) _INTERCEPTX(_fstrcpy(d,s),d) +#define _fstrncat(s1,s2,n) _INTERCEPTX(_fstrncat(s1,s2,n),s1) +#define _fstrncpy(d,s,n) _INTERCEPTX(_fstrncpy(d,s,n),d) +#define _fstrdup(s) _INTERCEPT(_fstrdup(s)) +#define _fstrnset(d,c,n) _INTERCEPTX(_fstrnset(d,c,n),d) +#define _fstrset(d,c) _INTERCEPTX(_fstrset(d,c),d) +#endif /* not _CC_POWERPACK32_ */ + +/* +#define freemem(g) _INTERCEPT(freemem(g)) +#define vsscanf(d,f,a) _INTERCEPTX(vsscanf(d,f,a),d) +*/ + +/* ----- END Borland C/C++ interceptions ----- */ + +#else + +#error Unknown compiler in MemCheck.h + +#endif /* Compiler-specific Function Mapping Section */ + +/* Location Transmitters + + You can add any non-intercepted functions to + this bunch... Just updates MemCheck's file and line + information via mc_set_location(), which is thousands + of times faster than anything that does I/O. + The only time this section could be a problem is + if the header file is included before any other header + files which prototype these routines. + + Borland's TD (Turbo Debugger) also has problems here (see note). +*/ +#ifndef _lint /* LINT not like */ + +/* Borland's Turbo Debugger gets confoosed and executes + a `Run' instead of a `Step' when _SETLOC macro is used... +*/ +#if !defined (_CC_BORLAND_) +#if 1 /* Change this to '0' to omit this section */ + +#define printf _SETLOC(printf) + +#define fopen _SETLOC(fopen) +#define fprintf _SETLOC(fprintf) +#define fread _SETLOC(fread) +#define fwrite _SETLOC(fwrite) +#define fclose _SETLOC(fclose) + +#define system _SETLOC(system) +#define exec _SETLOC(exec) +#define spawnl _SETLOC(spawnl) +#define spawnlp _SETLOC(spawnlp) +#define spawnle _SETLOC(spawnle) +#define spawnlpe _SETLOC(spawnlpe) +#define spawnv _SETLOC(spawnv) +#define spawnvp _SETLOC(spawnvp) +#define spawnve _SETLOC(spawnve) +#define spawnvpe _SETLOC(spawnvpe) + +#endif /* end location transmission section */ +#endif /* not Borland C++ */ +#endif /* not def _lint */ + + +/* **** THIRD-PARTY MAPPINGS **** */ + +/* Vermont Views V3.xx + + The following code will transmit the exact file + and line of any mem_get() and mem_free() calls to + MemCheck, so that it can report on the location where + these functions are called, instead of the location of + the calloc() or free(). + + If you've used MCCONFIG to configure the Vermont Views source + code, you *must* either NOT include the MemCheck header file + in the MEM_GET.C and MEM_FREE.C modules, or, if you do, then + #define NO_INTERCEPT beforehand, e.g. + + Module MEM_GET.C ... + : + #define NO_INTERCEPT + #include + : + + MCCONFIG may be used to configure even the shrouded + Vermont Views source code. + + See also: TechNote "Using MemCheck 3.0 Professional + With Vermont Views", available on the StratosWare + BBS (313) 996-2993 as VIEWS.TXT, or by fax. +*/ +#if defined (VV_SYS) /* should do the trick */ +# define mem_get(s) _INTERCEPT(mem_get(s)) +# define mem_free(p) _INTERCEPT(mem_free(p)) +#endif + + +/* **** APPLICATION-SPECIFIC MAPPINGS **** */ + +/* + If your application uses allocation "cover" routines, + MemCheck will by default report errors and leaks by + the file and line of the malloc or free within the + cover module. To get MemCheck to report by file and + line where the cover function is actually called, follow + the instructtions in MemCheck TechNote "Transmitting File + and Line to MemCheck 3.0 Professional Through Cover Functions." + + This is where you can place the cover function macros. +*/ + + +/* end APPLICATION-SPECIFIC MAPPINGS */ + +#endif /* not NO_INTERCEPT */ + +/* Calls that xmit source file, line number if called in source */ +/* *** MemCheck API file & line transmittal *** */ +#define mc_startcheck(erf) mc_startcheck(_MCSF_,_MCSL_,erf) +#define mc_stack_trace(_memo) _INTERCEPT(mc_stack_trace(_memo)) +#define mc_debug(s) _INTERCEPT(mc_debug(s)) +#define mc_debugf(arglist) mc_debugv arglist +#define mc_debugv _mcsl(_MCSF_,_MCSL_),mc_debugv +#define mc_endcheck() _INTERCEPT(mc_endcheck()) +#define mc_check_buffers() _INTERCEPT(mc_check_buffers()) +#define mc_check(p) _INTERCEPT(mc_check(p)) +#define mc_register(p,s) _INTERCEPT(mc_register(p,s)) +#define mc_unregister(p) _INTERCEPT(mc_unregister(p)) +#define mc_nullcheck() _INTERCEPT(mc_nullcheck()) +#define mc_report(f) _INTERCEPT(mc_report(f)) + +/*lint -restore 652 Define of symbol declared prev */ + +#endif /* not MEMCHECK_MODULE, function interceptions */ + + +/* End "C" call wrapper */ +#ifdef __cplusplus +} + + +/* C++ MemCheck Class + + This class can be used as an alternative to + AutoInit, or to placing the mc_startcheck() and + mc_endcheck() calls in your main() program. + Just declaring an object of class 'MemCheck' + will start MemCheck up; usually you will place + this 'above' any other global or statically declared + C++ objects in your main module. + + Here are some examples of starting MemCheck up + via object mechanics: + + MemCheck On; + MemCheck Active; + MemCheck Rules; + + Use your imagination! Note that if AutoInit is ON, + any calls to mc_startcheck() and mc_endcheck() are + ignored. +*/ +#if !defined (NO_INTERCEPT) /* must not have this def'd */ + +/* This class def causes a warning under MSC if not used */ +#if !defined (_CC_MSC_) + +class MemCheck { +public: + MemCheck () { mc_startcheck (NULL); } + ~MemCheck () { mc_endcheck (); } +}; + +#endif + +#endif /* NO_INTERCEPT */ + + +/* *** For use in new and delete modules only *** */ +/* + Replace 'mallocs' with 'cpp_mallocs', etc. + In new and delete modules, NO_INTERCEPT should be #defined, e.g. + + #define NO_INTERCEPT + #include + : + void * operator new ( size_t size ) + { + if (!size) size = 1; + return (cpp_malloc (size)); + } + etc. +*/ +#define cpp_malloc(_s) (_mc_set_newflag(), malloc(_s)) +#define cpp_calloc(_n,_s) (_mc_set_newflag(), calloc(_n,_s)) +#define cpp_free(_p) (_mc_set_delflag(), free(_p)) + +/* Borland C++ */ +#define cpp_farmalloc(_s) (_mc_set_newflag(), farmalloc(_s)) +#define cpp_farfree(_fp) (_mc_set_delflag(), farfree(_fp)) + +/* Microsoft C++-compatibles */ +#define cpp__fmalloc(_s) (_mc_set_newflag(), _fmalloc(_s)) +#define cpp__ffree(_fp) (_mc_set_delflag(), _ffree(_fp)) + + +/* C++ */ +#if !defined (NO_INTERCEPT) +#if !defined (NO_CPP) + +/* + This method is off by default, because it + requires definition of a new operator like: + + void * new (size_t size, char *file, int lineno); + + Such a new operator is included in your SOURCE\CPP + directory. To have this method used for all modules, + #define NEW_OVERLOADED at the top of this header file + or in your project #include file, BEFORE the MemCheck + header file is #included. + + The substitutions for the new operator + may not work in all situations. To disable + MemCheck's interception of new on a module-by- + module basis by undefining NEW_OVERLOADED. +*/ +#if defined (NEW_OVERLOADED) + +/* Method 1: Placement Operators + + Use placement operators to trap file and line location transparently + on calls to new. + + Thanks for this tip to Dan Saks, + C and C++ writer, author, teacher, and columnist--buy his books. + He came through when no one else had a clue! + + Please consult your manual, MemCheck technotes, + or StratosWare Technical Support (1-800-WE-DEBUG) + for details on how to configure your project for + use with an overloaded new placement operator. +*/ + +/* Declare overloaded new with placement operators */ +void *operator new (size_t _sz, char *file, int lineno); +#if defined (_CPP_ANSI20_) +/* Array version; only under supporting compilers + COMMENT LINE OUT if it causes a compile error. +*/ +void *operator new[] (size_t _sz, char *file, int lineno); +#endif + +#if !defined (_CC_MSC_) +#define new new((char *)__FILE__,(int)__LINE__) +#else +#define new new(__FILE__,__LINE__) +#endif + +/* NOTE: + This placement operator interception syntax has been + known to cause syntax errors (in VC++ 1.0) for no apparent reason + on statements like + + Domain *d = new Domain (); + + Workaround is to change line (sorry!) to equivalent + + Domain *d = new Domain; +*/ + +/* Backwards compatibility with the V2.1 C++ macros */ +#ifndef NEW +#define NEW(_object) new _object +#endif +#ifdef DELETE +#define DELETE(_object) delete _object +#endif +#define DELETE_ARR(_arr) delete[] _arr + +#else /* !NEW_OVERLOADED - end of Placement Operator intercept */ + +/* New and Delete Interception, Method 2: NEW() and DELETE() + + The NEW() and DELETE() macros may be used to transmit file + and line of new and delete. These macros, which require + modification of source code, i.e. "NEW(object)" for "new object", + should probably be used only if the above overloaded new does + not work for your code base. + + Please consult your manual, MemCheck technotes, + or StratosWare Technical Support (1-800-WE-DEBUG) + for details on how to configure your project for + use with NEW() and DELETE(). + + If calling, please have your MemCheck serial number handy. +*/ +#ifndef NEW +#define NEW(_object) (_mcsl_new(_MCSF_,_MCSL_), new _object) +#endif +#ifndef DELETE /* WINNT.H under BC DPMI32 defines DELETE */ +#define DELETE(_object) (_mcsl_delete(_MCSF_,_MCSL_), delete _object) +#endif +#define DELETE_ARR(_arr) (_mcsl_delete(_MCSF_,_MCSL_), delete[] _arr) + +#endif /* !NEW_OVERLOADED */ + +#define delete _mcsl_delete(_MCSF_,_MCSL_), delete + + +/* *** FAILURES *** */ + +/* These macros failed in the purpose of + intercepting new transparently in some + situation or other. +*/ + +/* Failed on " * new expr " (TV) */ +/* #define new (mc_set_location(),0) ? NULL : new */ + +/* Failed on " x = new Object " (TV) */ +/* #define new ((mc_set_location(),0) ? NULL : new) */ +/* #define new new (mc_set_location(),0) ? NULL : */ + +#endif /* !NO_CPP */ +#endif /* NO_INTERCEPT */ +#endif /* cplusplus */ +/******** End C++ ************/ + + +#endif /* End of Section for MEMCHECK Defined */ + +/* -------------------------------------------------------------------------- */ + +#endif /* _MEMCHECK_H_ */ + + +/******************************** + * End of MemCheck 3.0 Header * + ********************************/ + diff --git a/MENUS.CPP b/MENUS.CPP new file mode 100644 index 0000000..873e5c1 --- /dev/null +++ b/MENUS.CPP @@ -0,0 +1,932 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\menus.cpv 2.17 16 Oct 1995 16:50:48 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MENUS.CPP * + * * + * Programmer : Phil W. Gorrow * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : May 17, 1995 [BRR] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Main_Menu -- Menu processing * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "ccdde.h" + +/***************************** +** Function prototypes +******************************/ + +#ifdef SCENARIO_EDITOR + +PRIVATE int Coordinates_In_Region(int x,int y,int inx1,int iny1,int inx2,int iny2); +PRIVATE int Select_To_Entry(int select, unsigned long bitfield, int index); +PRIVATE void Flash_Line(char const *text,int xpix,int ypix,unsigned nfgc,unsigned hfgc,unsigned bgc); + +int UnknownKey; + +PRIVATE int MenuUpdate=1; +PRIVATE int MenuSkip; + + +/*=========================================================================*/ +/* SELECT_TO_ENTRY: */ +/* */ +/* This routine converts a selection to the correct string entry. It */ +/* does this by search through a long bitfield starting at position index */ +/* until it finds the correct conversion to entries. */ +/* */ +/* INPUTS: int selection from menu, long the bit field to search, int */ +/* the starting index within the bit field. */ +/* RETURNS: int the index into the table of entries */ +/*=========================================================================*/ +PRIVATE int Select_To_Entry(int select, unsigned long bitfield, int index) +{ + int placement; + + if (bitfield==0xFFFFFFFFL) /* if all bits are set */ + return(select); /* then it as is */ + + placement=0; /* current pos zero */ + while (select) { /* while still ones */ + if (bitfield & (1L<<(placement+index))) /* if this flagged then */ + select--; /* decrement counter */ + placement++; /* and we moved a place */ + } + while (!(bitfield & (1L<<(placement+index)))) { + placement++; + } + + return(placement); /* return the position */ +} + + +/*=========================================================================*/ +/* FLASH_LINE: */ +/* */ +/* This routine will flash the line at the desired location for the */ +/* menu routine. It is way cool awesome! */ +/* */ +/* INPUTS: char *text, int x position on line, int y position, char */ +/* normal foreground color, char hilight foreground color, char */ +/* background color */ +/* RETURNS: none */ +/*=========================================================================*/ +PRIVATE void Flash_Line(char const *text,int xpix,int ypix,unsigned nfgc,unsigned hfgc,unsigned bgc) +{ + int loop; + + for (loop=0;loop<3;loop++) { + Hide_Mouse(); + Fancy_Text_Print(text,xpix,ypix,hfgc,bgc, TPF_8POINT|TPF_DROPSHADOW); + Delay(2); + Fancy_Text_Print(text,xpix,ypix,nfgc,bgc, TPF_8POINT|TPF_DROPSHADOW); + Show_Mouse(); + Delay(2); + } +} + +/*=========================================================================*/ +/* COORDINATES_IN_REGION: */ +/* */ +/* Test to see if a given pair of coordinates are within the given */ +/* rectangular region. */ +/* */ +/* INPUTS: int x to be tested, int y to be tested, int left x pos, */ +/* int top y pos, int right x pos, int bottom y pos */ +/* RETURNS: none */ +/*=========================================================================*/ +PRIVATE int Coordinates_In_Region(int x,int y,int inx1,int iny1,int inx2,int iny2) +{ + return((x>=inx1)&&(x<=inx2)&&(y>=iny1)&&(y<=iny2)); +} + +#ifdef NEVER +/*=========================================================================*/ +/* FIND_MENU_ITEMS: */ +/* */ +/* This routine finds the real total items in a menu when certain items */ +/* may be disabled by bit fields and the like. This is done by looping */ +/* through the fields, starting at the position passed in index and */ +/* counting the number of bits that are set. */ +/* */ +/* INPUTS: int the maximum number of items possible on the menu, long */ +/* the bit field of enabled and disabled items, char the index */ +/* point to start at within the list. */ +/* RETURNS: int the total number of items in the menu */ +/*=========================================================================*/ + int Find_Menu_Items(int maxitems, unsigned long field, char index) + { + int loop,ctr; + + if (field==0xFFFFFFFFL) /* if all bits are set */ + return(maxitems); /* then maxitems set */ + + for (loop=ctr=0;loop>1; /* adjustment for menus */ + + menuy = WinY+menuptr[MENUY]; /* get the absolute */ + menux = (WinX+menuptr[MENUX])<<3; /* coords of menu */ + normcol = menuptr[NORMCOL]; + litcol = menuptr[HILITE]; + + /* + ** Fetch a pending keystroke from the buffer if there is a keystroke + ** present. If no keystroke is pending then simple mouse tracking will + ** be done. + */ + key = 0; + UnknownKey = 0; + if (Keyboard::Check()) { + key = (Keyboard::Get()&0x18FF); /* mask off all but release bit */ + } + + /* + ** if we are using the mouse and it is installed, then find the mouse + ** coordinates of the menu and if we are not somewhere on the menu get + ** the heck outta here. If we are somewhere on the menu, then figure + ** out the new selected item, and continue forward. + */ + mx1=(WinX<<3)+(menuptr[MENUX]*FontWidth); /* get menu coords */ + my1=(WinY)+(menuptr[MENUY])-halfskip; /* from the menu */ + mx2=mx1+(menuptr[ITEMWIDTH]*FontWidth)-1; /* structure as */ + my2=my1+(menuptr[ITEMSHIGH]*menuskip)-1; /* necessary */ + + tempy=Get_Mouse_Y(); + if (Coordinates_In_Region(Get_Mouse_X(),tempy,mx1,my1,mx2,my2)&& MenuUpdate) { + newitem=(tempy-my1)/menuskip; + } + + switch (key) { + + case KN_UP: /* if the key moves up */ + newitem--; /* new item up one */ + if (newitem<0) /* if invalid new item */ + newitem=maxitem; /* put at list bottom */ + break; + case KN_DOWN: /* if key moves down */ + newitem++; /* new item down one */ + if (newitem>maxitem) /* if new item past */ + newitem=0; /* list end, clear */ + break; + case KN_HOME: /* if top of list key */ + case KN_PGUP: /* is selected then */ + newitem=0; /* new item = top */ + break; + case KN_END: /* if bottom of list is */ + case KN_PGDN: /* selected then */ + newitem=maxitem; /* new item = bottom */ + break; + + /* + ** Handle mouse button press. Set selection and then fall into the + ** normal menu item select logic. + */ + case KN_RMOUSE: + case KN_LMOUSE: + if (Coordinates_In_Region(_Kbd->MouseQX,_Kbd->MouseQY,mx1,my1,mx2,my2)) { + newitem = (_Kbd->MouseQY - my1) / menuskip; + } else { + UnknownKey = key; // Pass the unprocessed button click back. + break; + } + + /* + ** Normal menu item select logic. Will flash line and exit with menu + ** selection number. + */ + case KN_RETURN: /* if a selection is */ + case KN_SPACE: /* made with key */ + case KN_CENTER: + select=newitem; /* flag it made. */ + break; + + case 0: + break; + + /* + ** When no key was pressed or an unknown key was pressed, set the + ** global record of the key and exit normally. + ** EXCEPTION: If the key matches the first letter of any of the + ** menu entries, then presume it as a selection of + ** that entry. + */ + default: + for (idx = 0; idx < menuptr[ITEMSHIGH]; idx++) { + if (toupper(*(text[Select_To_Entry(idx,field,index)])) == toupper(Keyboard::To_ASCII((KeyNumType)(key&0x0FF)))) { + newitem = select = idx; + break; + } + } + UnknownKey = key; + break; + } + + if (newitem!=item) { + Hide_Mouse(); + idx=Select_To_Entry(item,field,index); + drawy=menuy+(item*menuskip); + Fancy_Text_Print(text[idx],menux,drawy,normcol,TBLACK, TPF_8POINT|TPF_DROPSHADOW); + idx=Select_To_Entry(newitem,field,index); + drawy=menuy+(newitem*menuskip); + Fancy_Text_Print(text[idx],menux,drawy,litcol,TBLACK, TPF_8POINT|TPF_DROPSHADOW); + Show_Mouse(); /* resurrect the mouse */ + } + + if (select!=-1) { + idx=Select_To_Entry(select,field,index); + Hide_Mouse(); /* get rid of the mouse */ + drawy=menuy+(newitem*menuskip); + Flash_Line(text[idx], menux, drawy, normcol, litcol, TBLACK); + Show_Mouse(); + select=idx; + } + + menuptr[MSELECTED]=newitem; /* update menu select */ + + return(select); +} + + +/*************************************************************************** + * Do_Menu -- Generic menu processor. * + * * + * This helper function displays a menu of specified entries and waits * + * for the player to make a selection. If a selection is made, then * + * a whole number (starting at 0) is returned matching the entry * + * selected. If ESC is pressed, then -1 is returned. * + * * + * INPUT: strings -- A pointer to an array of pointers to text strings. * + * Each entry in the list will be a menu entry that * + * can be selected. * + * * + * blue -- Should the special blue color be used to display * + * the menu? * + * * + * OUTPUT: Returns with the cardinal number of the selected menu entry. * + * If ESC was pressed, then -1 is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/16/1994 JLB : Created. * + *=========================================================================*/ +int Do_Menu(char const **strings, bool blue) +{ + int count; // Number of entries in this menu. + int length; // The width of the menu (in pixels). + char const **ptr; // Working menu text pointer. + int selection; // Selection from user. + + if (!strings) return(-1); + Set_Logic_Page(SeenBuff); + Keyboard::Clear(); + + /* + ** Determine the number of entries in this string. + */ + ptr = strings; + count = 0; + while (*ptr++) { + count++; + } + MenuList[0][ITEMSHIGH] = count; + + /* + ** Determine the width of the menu by finding the length of the + ** longest menu entry. + */ + Fancy_Text_Print(TXT_NONE, 0, 0, 0, 0, TPF_8POINT|TPF_DROPSHADOW); + length = 0; + ptr = strings; + while (*ptr) { + length = MAX(length, (int)String_Pixel_Width(*ptr)); + ptr++; + } + length += 7; + MenuList[0][ITEMWIDTH] = length >> 3; + + /* + ** Adjust the window values to match the size of the + ** specified menu. + */ + WindowList[WINDOW_MENU][WINDOWWIDTH] = MenuList[0][ITEMWIDTH] + 2; + WindowList[WINDOW_MENU][WINDOWX] = 19 - (length >> 4); + WindowList[WINDOW_MENU][WINDOWY] = 174 - (unsigned)(MenuList[0][ITEMSHIGH] * (FontHeight+FontYSpacing)); + WindowList[WINDOW_MENU][WINDOWHEIGHT] = MenuList[0][ITEMSHIGH] * FontHeight + 5 /*11*/; + + /* + ** Display the menu. + */ + Change_Window((int)WINDOW_MENU); + Show_Mouse(); + Window_Box(WINDOW_MENU, blue ? BOXSTYLE_BLUE_UP : BOXSTYLE_RAISED); + Setup_Menu(0, strings, 0xFFFFL, 0, 0); + + Keyboard::Clear(); + selection = -1; + UnknownKey = 0; + while (selection == -1) { + Call_Back(); + selection = Check_Menu(0, strings, NULL, 0xFFL, 0); + if (UnknownKey != 0 || UnknownKey == KN_ESC || UnknownKey==KN_LMOUSE || UnknownKey==KN_RMOUSE) break; + } + Keyboard::Clear(); + Hide_Mouse(); + + HidPage.Blit(SeenBuff); + Change_Window((int)WINDOW_MAIN); + Map.Flag_To_Redraw(true); + return(selection); +} +#endif + + +/*************************************************************************** + * Main_Menu -- Menu processing * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * index of item selected, -1 if time out * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/17/1995 BRR : Created. * + *=========================================================================*/ +int Main_Menu(unsigned long timeout) +{ + enum { + D_DIALOG_W = 152*2, + D_DIALOG_H = 136*2, + D_DIALOG_X = 85*2, + D_DIALOG_Y = 0, + D_DIALOG_CX = D_DIALOG_X + (D_DIALOG_W / 2), + + D_START_W = 125*2, + D_START_H = 9*2, + D_START_X = 98*2, + D_START_Y = 35*2, + +#ifdef BONUS_MISSIONS + D_BONUS_W = 125*2, + D_BONUS_H = 9*2, + D_BONUS_X = 98*2, + D_BONUS_Y = 0, +#endif //BONUS_MISSIONS + + D_INTERNET_W = 125*2, + D_INTERNET_H = 9*2, + D_INTERNET_X = 98*2, + D_INTERNET_Y = 36*2, + + D_LOAD_W = 125*2, + D_LOAD_H = 9*2, + D_LOAD_X = 98*2, + D_LOAD_Y = 53*2, + + D_MULTI_W = 125*2, + D_MULTI_H = 9*2, + D_MULTI_X = 98*2, + D_MULTI_Y = 71*2, + + D_INTRO_W = 125*2, + D_INTRO_H = 9*2, + D_INTRO_X = 98*2, + D_INTRO_Y = 89*2, +#if (GERMAN | FRENCH) + D_EXIT_W = 83*2, +#else + D_EXIT_W = 63*2, +#endif + D_EXIT_H = 9*2, +#if (GERMAN | FRENCH) + D_EXIT_X = 118*2, +#else + D_EXIT_X = 128*2, +#endif + D_EXIT_Y = 111*2, + + }; + +#ifdef NEWMENU + int starty = 25*2; +#endif + + enum { +#ifdef NEWMENU + BUTTON_EXPAND=100*2, + BUTTON_START, +#ifdef BONUS_MISSIONS + BUTTON_BONUS, +#endif //BONUS_MISSIONS + BUTTON_INTERNET, +#else + BUTTON_START=100*2, +#endif + BUTTON_LOAD, + BUTTON_MULTI, + BUTTON_INTRO, + BUTTON_EXIT, + }; + +#ifdef NEWMENU + bool expansions = Expansion_Present(); +#endif + KeyNumType input; // input from user + int retval; // return value + int curbutton; +#ifdef NEWMENU +#ifdef BONUS_MISSIONS + TextButtonClass *buttons[8]; +#else + TextButtonClass *buttons[7]; +#endif //BONUS_MISSIONS +#else + TextButtonClass *buttons[5]; +#endif + unsigned long starttime; + + ControlClass *commands = NULL; // the button list + +#ifdef NEWMENU +#ifdef BONUS_MISSIONS + int ystep = 13*2; +#else + int ystep = 15*2; +#endif //BONUS_MISSIONS + + if (expansions) ystep -= 2*2; + TextButtonClass expandbtn (BUTTON_EXPAND, TXT_NEW_MISSIONS, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_START_X, starty, D_START_W, D_START_H); + if (expansions) starty += ystep; + + TextButtonClass startbtn (BUTTON_START, TXT_START_NEW_GAME, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_START_X, starty, D_START_W, D_START_H); + starty += ystep; + +#ifdef BONUS_MISSIONS + TextButtonClass bonusbtn (BUTTON_BONUS, TXT_BONUS_MISSIONS, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_BONUS_X, starty, D_BONUS_W, D_BONUS_H); + starty += ystep; +#endif //BONUS_MISSIONS + + TextButtonClass internetbutton(BUTTON_INTERNET, TXT_INTERNET, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_INTERNET_X, starty, D_INTERNET_W, D_INTERNET_H); + starty += ystep; + + TextButtonClass loadbtn (BUTTON_LOAD, TXT_LOAD_MISSION, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_LOAD_X, starty, D_LOAD_W, D_LOAD_H); + starty += ystep; +#else + + TextButtonClass startbtn (BUTTON_START, TXT_START_NEW_GAME, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_START_X, D_START_Y, D_START_W, D_START_H); + + TextButtonClass loadbtn (BUTTON_LOAD, TXT_LOAD_MISSION, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_LOAD_X, D_LOAD_Y, D_LOAD_W, D_LOAD_H); + +#endif + + +#ifdef DEMO + TextButtonClass multibtn (BUTTON_MULTI, TXT_ORDER_INFO, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI_X, D_MULTI_Y, D_MULTI_W, D_MULTI_H); +#else + +#ifdef NEWMENU + TextButtonClass multibtn (BUTTON_MULTI, TXT_MULTIPLAYER_GAME, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI_X, starty, D_MULTI_W, D_MULTI_H); + starty += ystep; + + //TextButtonClass internetbutton(BUTTON_INTERNET, TXT_INTERNET, + // TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + // D_INTERNET_X, starty, D_INTERNET_W, D_INTERNET_H); + //starty += ystep; +#else + TextButtonClass multibtn (BUTTON_MULTI, TXT_MULTIPLAYER_GAME, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_MULTI_X, D_MULTI_Y, D_MULTI_W, D_MULTI_H); +#endif +#endif + +#ifdef NEWMENU +#ifdef DEMO + TextButtonClass introbtn (BUTTON_INTRO, TXT_JUST_INTRO, +#else //DEMO + TextButtonClass introbtn (BUTTON_INTRO, TXT_INTRO, +#endif //DEMO + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_INTRO_X, starty, D_INTRO_W, D_INTRO_H); + starty += ystep; + + TextButtonClass exitbtn (BUTTON_EXIT, TXT_EXIT_GAME, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + //D_EXIT_X, starty); + D_EXIT_X, starty, D_EXIT_W, D_EXIT_H); +#else + D_EXIT_X, starty, D_EXIT_W, D_EXIT_H); +#endif + starty += ystep; + +#else + +#ifdef DEMO + TextButtonClass introbtn (BUTTON_INTRO, TXT_JUST_INTRO, +#else //DEMO + TextButtonClass introbtn (BUTTON_INTRO, TXT_INTRO, +#endif //DEMO + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + D_INTRO_X, D_INTRO_Y, D_INTRO_W, D_INTRO_H); + + TextButtonClass exitbtn (BUTTON_EXIT, TXT_EXIT_GAME, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + //D_EXIT_X, D_EXIT_Y); + D_EXIT_X, D_EXIT_Y, D_EXIT_W, D_EXIT_H); +#else + D_EXIT_X, D_EXIT_Y, D_EXIT_W, D_EXIT_H); +#endif +#endif + + /* + ** Initialize + */ + Set_Logic_Page(SeenBuff); + Keyboard::Clear(); + starttime = TickCount.Time(); + + /* + ** Create the list + */ + commands = &startbtn; +#ifdef NEWMENU + if (expansions) { + expandbtn.Add_Tail(*commands); + } +#endif +#ifdef BONUS_MISSIONS + bonusbtn.Add_Tail(*commands); +#endif //BONUS_MISSIONS + + +#ifndef DEMO + internetbutton.Add_Tail(*commands); +#endif //DEMO + loadbtn.Add_Tail(*commands); + multibtn.Add_Tail(*commands); + introbtn.Add_Tail(*commands); + exitbtn.Add_Tail(*commands); + + /* + ** Fill array of button ptrs + */ +#ifdef NEWMENU + if (expansions) { + curbutton = 0; + } else { + curbutton = 1; + } + int butt = 0; + + buttons[butt++] = &expandbtn; + buttons[butt++] = &startbtn; +#ifdef BONUS_MISSIONS + buttons[butt++] = &bonusbtn; +#endif //BONUS_MISSIONS + buttons[butt++] = &internetbutton; + buttons[butt++] = &loadbtn; + buttons[butt++] = &multibtn; + buttons[butt++] = &introbtn; + buttons[butt++] = &exitbtn; +#else + curbutton = 0; + buttons[0] = &startbtn; + buttons[1] = &loadbtn; + buttons[2] = &multibtn; + buttons[3] = &introbtn; + buttons[4] = &exitbtn; +#endif + buttons[curbutton]->Turn_On(); + + Keyboard::Clear(); + + Fancy_Text_Print(TXT_NONE, 0, 0, CC_GREEN, TBLACK, TPF_CENTER|TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW); + while (Get_Mouse_State() > 0) Show_Mouse(); + + /* + ** Main Processing Loop. + */ + bool display = true; + bool process = true; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=TRUE; + } + + /* + ** If timeout expires, bail + */ + if (timeout && TickCount.Time() - starttime > timeout) { + retval = -1; + process = false; + } + + /* + ** Invoke game callback. + */ + Call_Back(); + + /* + ** Refresh display if needed. + */ + if (display) { + + /* + ** Load the background picture. + */ + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + + /* + ** Display the title and text overlay for the menu. + */ + Set_Logic_Page(HidPage); + Dialog_Box(D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W, D_DIALOG_H); + Draw_Caption (TXT_NONE, D_DIALOG_X, D_DIALOG_Y, D_DIALOG_W); +#ifdef VIRGIN_CHEAT_KEYS +#ifdef DEMO + Version_Number(); + Fancy_Text_Print("Demo%s", D_DIALOG_X+D_DIALOG_W-5*2, D_DIALOG_Y+D_DIALOG_H-10*2, DKGREY, TBLACK, TPF_6POINT|TPF_FULLSHADOW|TPF_RIGHT, VersionText); +#else + Fancy_Text_Print("V.%d%s", D_DIALOG_X+D_DIALOG_W-5*2, D_DIALOG_Y+D_DIALOG_H-10*2, DKGREY, TBLACK, TPF_6POINT|TPF_FULLSHADOW|TPF_RIGHT, Version_Number(), VersionText, FOREIGN_VERSION_NUMBER); +#endif +// Fancy_Text_Print("V.%d%s%02d", D_DIALOG_X+D_DIALOG_W-5, D_DIALOG_Y+D_DIALOG_H-10, DKGREY, TBLACK, TPF_6POINT|TPF_FULLSHADOW|TPF_RIGHT, Version_Number(), VersionText, FOREIGN_VERSION_NUMBER); +#else +#ifdef DEMO + Version_Number(); + Fancy_Text_Print("Demo%s", D_DIALOG_X+D_DIALOG_W-5*2, D_DIALOG_Y+D_DIALOG_H-10*2, DKGREY, TBLACK, TPF_6POINT|TPF_FULLSHADOW|TPF_RIGHT, VersionText); +#else + Fancy_Text_Print("V.%d%s", D_DIALOG_X+D_DIALOG_W-5*2, D_DIALOG_Y+D_DIALOG_H-10*2, DKGREY, TBLACK, TPF_6POINT|TPF_FULLSHADOW|TPF_RIGHT, Version_Number(), VersionText); +#endif +#endif + + /* + ** Copy the menu to the visible page. + */ + Hide_Mouse(); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + Set_Logic_Page(SeenBuff); + startbtn.Draw_All(); + if (ScreenWidth==320){ + ModeX_Blit (SeenBuff.Get_Graphic_Buffer()); + } + display = false; + } + +#ifndef DEMO + /* + ** Check to see if WChat has told us to start playing an internet game + */ + if (DDEServer.Get_MPlayer_Game_Info()){ + retval = BUTTON_INTERNET - BUTTON_EXPAND; + process = false; + } +#endif //DEMO + + /* + ** Get and process player input. + */ + input = commands->Input(); + switch (input) { +#ifdef NEWMENU + case (BUTTON_EXPAND | KN_BUTTON): + retval = (input & 0x7FFF) - BUTTON_EXPAND; + process = false; + break; + + case (BUTTON_INTERNET | KN_BUTTON): + retval = (input & 0x7FFF) - BUTTON_EXPAND; + process = false; + break; + +#else +#define BUTTON_EXPAND BUTTON_START +#endif + + case (BUTTON_START | KN_BUTTON): + retval = (input & 0x7FFF) - BUTTON_EXPAND; + process = false; + break; + +#ifdef BONUS_MISSIONS + case (BUTTON_BONUS | KN_BUTTON): + retval = (input & 0x7FFF) - BUTTON_EXPAND; + process = false; + break; +#endif //BONUS_MISSIONS + + case (BUTTON_LOAD | KN_BUTTON): + retval = (input & 0x7FFF) - BUTTON_EXPAND; +#ifdef DEMO + retval += 1; +#endif //DEMO + process = false; + break; + + case (BUTTON_MULTI | KN_BUTTON): + retval = (input & 0x7FFF) - BUTTON_EXPAND; +#ifdef DEMO + retval += 1; +#endif //DEMO + process = false; + break; + + case (BUTTON_INTRO | KN_BUTTON): + retval = (input & 0x7FFF) - BUTTON_EXPAND; +#ifdef DEMO + retval += 1; +#endif //DEMO + process = false; + break; + + case (BUTTON_EXIT | KN_BUTTON): + retval = (input & 0x7FFF) - BUTTON_EXPAND; +#ifdef DEMO + retval += 1; +#endif //DEMO + process = false; + break; + + case KN_UP: + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + curbutton--; +#ifdef NEWMENU + if (expansions) { + if (curbutton < 0) { + curbutton = 6; + } + } else { + if (curbutton < 1) { + curbutton = 6; + } + } +#else + if (curbutton < 0) { + curbutton = 4; + } +#endif + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + break; + + case KN_DOWN: + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + curbutton++; +#ifdef NEWMENU + if (curbutton > 6) { + if (expansions) { + curbutton = 0; + } else { + curbutton = 1; + } + } +#else + if (curbutton > 4) { + curbutton = 0; + } +#endif + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + break; + + case KN_RETURN: + buttons[curbutton]->IsPressed = true; + buttons[curbutton]->Draw_Me(true); + retval = curbutton; + process = false; + break; + + default: + break; + } + } + return(retval); +} diff --git a/MESSAGE.H b/MESSAGE.H new file mode 100644 index 0000000..25d1a4d --- /dev/null +++ b/MESSAGE.H @@ -0,0 +1,47 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#define MESSAGE_NONE 0 // +#define MESSAGE_BUILD_WINDTRAP 1 // You must build a Windtrap +#define MESSAGE_STRUCT_CONCRETE 2 // Concrete: Use concrete to +#define MESSAGE_STRUCT_PALACE 3 // Palace: This is your +#define MESSAGE_STRUCT_LIGHT 4 // Light Factory: The Light +#define MESSAGE_STRUCT_HEAVY 5 // Heavy Factory: The Heavy +#define MESSAGE_STRUCT_HITECH 6 // Hi-Tech Factory: The +#define MESSAGE_STRUCT_IX 7 // House IX: The IX Research +#define MESSAGE_STRUCT_WOR 8 // WOR: Wor is used to train +#define MESSAGE_STRUCT_CONST 9 // Construction Facility: All +#define MESSAGE_STRUCT_WINDTRAP 10 // Windtrap: The windtrap +#define MESSAGE_STRUCT_BARRACKS 11 // Barracks: The Barracks is +#define MESSAGE_STRUCT_STARPORT 12 // Startport: The Starport is +#define MESSAGE_STRUCT_REFINERY 13 // Spice Refinery: The +#define MESSAGE_STRUCT_REPAIR 14 // Repair Facility: The Repair +#define MESSAGE_STRUCT_WALL 15 // Wall: The wall is used for +#define MESSAGE_STRUCT_TURRET 16 // Gun Turret: The cannon +#define MESSAGE_STRUCT_RTURRET 17 // Rocket Turret: The +#define MESSAGE_STRUCT_SILO 18 // Spice Silo: The Spice silo +#define MESSAGE_STRUCT_OUTPOST 19 // Outpost: The Outpost +#define MESSAGE_NEED_CONCRETE 20 // There isn't enough open +#define MESSAGE_SAND 21 // Sand: This is sand terrain. +#define MESSAGE_DUNE 22 // Sand Dunes: These are an +#define MESSAGE_ROCK 23 // Rock: This is rock terrain. +#define MESSAGE_MOUNT 24 // Mountain: Mountains on +#define MESSAGE_SPICE 25 // Spice Field: This is the +#define MESSAGE_ADJACENT 26 // Structures must be placed +#define MESSAGE_SEARCH4SPICE 27 // Search for spice fields to +#define MESSAGE_SANDWORM 28 // Warning: Sandworms diff --git a/MESSAGE.TXT b/MESSAGE.TXT new file mode 100644 index 0000000..5fc1bb2 --- /dev/null +++ b/MESSAGE.TXT @@ -0,0 +1,57 @@ +# MESSAGE_NONE +You must build a Windtrap to provide power to your base. Without power, your structures will decay. +# MESSAGE_BUILD_WINDTRAP +Concrete: Use concrete to make a sturdy foundation for your structures. +# MESSAGE_STRUCT_CONCRETE +Palace: This is your Palace. +# MESSAGE_STRUCT_PALACE +Light Factory: The Light Factory produces light attack vehicles. +# MESSAGE_STRUCT_LIGHT +Heavy Factory: The Heavy Factory produces tracked vehicles. +# MESSAGE_STRUCT_HEAVY +Hi-Tech Factory: The Hi-Tech Factory produces flying vehicles. +# MESSAGE_STRUCT_HITECH +House IX: The IX Research Facility advances your House's technology. +# MESSAGE_STRUCT_IX +WOR: Wor is used to train your Heavy infantry. +# MESSAGE_STRUCT_WOR +Construction Facility: All structures are built by the construction facility. +# MESSAGE_STRUCT_CONST +Windtrap: The windtrap supplies power to your base. Without power your structures will decay. +# MESSAGE_STRUCT_WINDTRAP +Barracks: The Barracks is used to train your Light infantry. +# MESSAGE_STRUCT_BARRACKS +Startport: The Starport is used to order and receive shipments from C.H.O.A.M. +# MESSAGE_STRUCT_STARPORT +Spice Refinery: The Refinery converts spice into credits. +# MESSAGE_STRUCT_REFINERY +Repair Facility: The Repair Facility is used to repair your vehicles. +# MESSAGE_STRUCT_REPAIR +Wall: The wall is used for passive defense. +# MESSAGE_STRUCT_WALL +Gun Turret: The cannon turret is used for short range active defense. +# MESSAGE_STRUCT_TURRET +Rocket Turret: The rocket/cannon turret is used for both short and medium range active defense. +# MESSAGE_STRUCT_RTURRET +Spice Silo: The Spice silo is used to store refined spice. +# MESSAGE_STRUCT_SILO +Outpost: The Outpost provides radar and aids control of distant vehicles. +# MESSAGE_STRUCT_OUTPOST +There isn't enough open concrete to place this structure. You may proceed, but without enough concrete the building will need repairs. +# MESSAGE_NEED_CONCRETE +Sand: This is sand terrain. Plenty of this stuff on Arrakis, to be sure. +# MESSAGE_SAND +Sand Dunes: These are an ubiquitous feature of Arrakian landscape. +# MESSAGE_DUNE +Rock: This is rock terrain. This valuable terrain is the only place structures can be built. +# MESSAGE_ROCK +Mountain: Mountains on Arrakis are rare (and an inconvenience). +# MESSAGE_MOUNT +Spice Field: This is the Spice, Melange. It is the most precious substance in the universe. +# MESSAGE_SPICE +Structures must be placed on clear rock or concrete and adjacent to another friendly structure. +# MESSAGE_ADJACENT +Search for spice fields to harvest. +# MESSAGE_SEARCH4SPICE +Warning: Sandworms (Shai-Hulud) roam Dune devouring anything on the sand. +# MESSAGE_SANDWORM diff --git a/MISSION.CPP b/MISSION.CPP new file mode 100644 index 0000000..5b1ec6f --- /dev/null +++ b/MISSION.CPP @@ -0,0 +1,477 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\mission.cpv 2.18 16 Oct 1995 16:49:12 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MISSION.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 23, 1994 * + * * + * Last Update : June 25, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * MissionClass::AI -- Processes order script. * + * MissionClass::Assign_Mission -- Give an order to a unit. * + * MissionClass::Commence -- Start script with new order. * + * MissionClass::Debug_Dump -- Dumps status values to mono screen. * + * MissionClass::Get_Mission -- Fetches the mission that this object is acting under. * + * MissionClass::MissionClass -- Default constructor for the mission object type. * + * MissionClass::Mission_From_Name -- Fetch order pointer from its name. * + * MissionClass::Mission_Name -- Converts a mission number into an ASCII string. * + * MissionClass::Overide_Mission -- temporarily overides the units mission * + * MissionClass::Restore_Mission -- Restores overidden mission * + * MissionClass::Set_Mission -- Sets the mission to the specified value. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * MissionClass::MissionClass -- Default constructor for the mission object type. * + * * + * This is the default constructor for the mission class object. It sets the mission * + * handler into a default -- do nothing -- state. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +MissionClass::MissionClass(void) +{ + Status = 0; + Timer = 0; + Mission = MISSION_NONE; + SuspendedMission = MISSION_NONE; + MissionQueue = MISSION_NONE; +} + + +int MissionClass::Mission_Sleep(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Ambush(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Attack(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Capture(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Guard(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Guard_Area(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Harvest(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Hunt(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Timed_Hunt(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Move(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Retreat(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Return(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Stop(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Unload(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Enter(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Construction(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Deconstruction(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Repair(void) {return TICKS_PER_SECOND*30;}; +int MissionClass::Mission_Missile(void) {return TICKS_PER_SECOND*30;}; + + +/*********************************************************************************************** + * MissionClass::Set_Mission -- Sets the mission to the specified value. * + * * + * Use this routine to set the current mission for this object. This routine will blast * + * over the current mission, bypassing the queue method. Call it when the mission needs * + * to be changed immediately. * + * * + * INPUT: mission -- The mission to set to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +void MissionClass::Set_Mission(MissionType mission) +{ + Mission = mission; + MissionQueue = MISSION_NONE; +} + + +/*********************************************************************************************** + * MissionClass::Get_Mission -- Fetches the mission that this object is acting under. * + * * + * Use this routine to fetch the mission that this object is CURRENTLY acting under. The * + * mission queue may be filled with a immanent mission change, but this routine does not * + * consider that. It only returns the CURRENT mission. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the mission that this unit is currently following. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +MissionType MissionClass::Get_Mission(void) const +{ + return(Mission == MISSION_NONE ? MissionQueue : Mission); +} + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * MissionClass::Debug_Dump -- Dumps status values to mono screen. * + * * + * This is a debugging function that dumps this class' status to the monochrome screen * + * for review. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +void MissionClass::Debug_Dump(MonoClass *mono) const +{ + mono->Set_Cursor(21, 1);mono->Printf("%5.5s[%4.4s]", MissionClass::Mission_Name(Mission), MissionClass::Mission_Name(MissionQueue)); +// mono->Text_Print(MissionClass::Mission_Name(Mission), 21, 1); + mono->Set_Cursor(20, 7);mono->Printf("%2d", (long)Timer); + mono->Set_Cursor(74, 1);mono->Printf("%2d", Status); + + ObjectClass::Debug_Dump(mono); +} +#endif + + +/*********************************************************************************************** + * MissionClass::AI -- Processes order script. * + * * + * This routine will process the order script for as much time as * + * possible or until a script delay is detected. This routine should * + * be called for every unit once per game loop (if possible). * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/23/1994 JLB : Created. * + * 06/25/1995 JLB : Added new missions. * + *=============================================================================================*/ +void MissionClass::AI(void) +{ + ObjectClass::AI(); + + /* + ** This is the script AI equivalent processing. + */ + if (Timer.Expired() && Strength > 0) { + switch (Mission) { + default: + case MISSION_STICKY: + case MISSION_SLEEP: + Timer = Mission_Sleep(); + break; + + case MISSION_GUARD: + Timer = Mission_Guard(); + break; + + case MISSION_ENTER: + Timer = Mission_Enter(); + break; + + case MISSION_CONSTRUCTION: + Timer = Mission_Construction(); + break; + + case MISSION_DECONSTRUCTION: + Timer = Mission_Deconstruction(); + break; + + case MISSION_CAPTURE: + case MISSION_SABOTAGE: + Timer = Mission_Capture(); + break; + + case MISSION_MOVE: + Timer = Mission_Move(); + break; + + case MISSION_ATTACK: + Timer = Mission_Attack(); + break; + + case MISSION_RETREAT: + Timer = Mission_Retreat(); + break; + + case MISSION_HARVEST: + Timer = Mission_Harvest(); + break; + + case MISSION_GUARD_AREA: + Timer = Mission_Guard_Area(); + break; + + case MISSION_RETURN: + Timer = Mission_Return(); + break; + + case MISSION_STOP: + Timer = Mission_Stop(); + break; + + case MISSION_AMBUSH: + Timer = Mission_Ambush(); + break; + + case MISSION_HUNT: + case MISSION_RESCUE: + Timer = Mission_Hunt(); + break; + + case MISSION_TIMED_HUNT: + Timer = Mission_Timed_Hunt(); + break; + + case MISSION_UNLOAD: + Timer = Mission_Unload(); + break; + + case MISSION_REPAIR: + Timer = Mission_Repair(); + break; + + case MISSION_MISSILE: + Timer = Mission_Missile(); + break; + } + } +} + + +/*********************************************************************************************** + * MissionClass::Commence -- Start script with new order. * + * * + * This routine will start script processing according to any queued * + * order it may have. If there is no queued order, then this routine * + * does nothing. Call this routine whenever the unit is in a good * + * position to change its order (such as when it is stopped). * + * * + * INPUT: none * + * * + * OUTPUT: Did the mission actually change? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/23/1994 JLB : Created. * + * 07/14/1994 JLB : Simplified. * + * 06/17/1995 JLB : Returns success flag. * + *=============================================================================================*/ +bool MissionClass::Commence(void) +{ + if (MissionQueue != MISSION_NONE) { + Mission = MissionQueue; + MissionQueue = MISSION_NONE; + + /* + ** Force immediate state machine processing at the first state machine state value. + */ + Timer = 0; + Status = 0; + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * MissionClass::Assign_Mission -- Give an order to a unit. * + * * + * This assigns an order to a unit. It does NOT do the AI, but merely * + * flags the unit for normal AI processing. At that time the computer * + * will determine the unit's course of action. * + * * + * INPUT: order -- Mission to give the unit. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/04/1991 JLB : Created. * + * 04/15/1994 JLB : Converted to member function. * + *=============================================================================================*/ +void MissionClass::Assign_Mission(MissionType order) +{ + if (order == MISSION_NONE || Mission == order) return; + +// Status = 0; + MissionQueue = order; +} + + +/*********************************************************************************************** + * MissionClass::Mission_From_Name -- Fetch order pointer from its name. * + * * + * This routine is used to convert an ASCII order name into the actual * + * order number it represents. Typically, this is used when processing * + * a scenario INI file. * + * * + * INPUT: name -- The ASCII order name to process. * + * * + * OUTPUT: Returns with the actual order number that the ASCII name * + * represents. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + * 04/22/1994 JLB : Converted to static member function. * + *=============================================================================================*/ +MissionType MissionClass::Mission_From_Name(char const *name) +{ + MissionType order; + + if (name) { + for (order = MISSION_FIRST; order < MISSION_COUNT; order++) { + if (stricmp(Missions[order], name) == 0) { + return(order); + } + } + } + return(MISSION_NONE); +} + + +/*********************************************************************************************** + * MissionClass::Mission_Name -- Converts a mission number into an ASCII string. * + * * + * Use this routine to convert a mission number into the ASCII string that represents * + * it. Typical use of this is when generating an INI file. * + * * + * INPUT: mission -- The mission number to convert. * + * * + * OUTPUT: Returns with a pointer to the ASCII string that represents the mission type. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +char const * MissionClass::Mission_Name(MissionType mission) +{ + return(mission == MISSION_NONE ? "None" : Missions[mission]); +} + + +/*********************************************************************************************** + * MissionClass::Override_Mission -- temporarily overides the units mission * + * * + * * + * * + * INPUT: MissionType mission - the mission we want to overide * + * TARGET tarcom - the new target we want to overide * + * TARGET navcom - the new navigation point to overide * + * * + * OUTPUT: none * + * * + * WARNINGS: If a mission is already overidden, the current mission is * + * just re-assigned. * + * * + * HISTORY: * + * 04/28/1995 PWG : Created. * + *=============================================================================================*/ +void MissionClass::Override_Mission(MissionType mission, TARGET, TARGET) +{ + if (MissionQueue != MISSION_NONE) { + SuspendedMission = MissionQueue; + } else { + SuspendedMission = Mission; + } + + Assign_Mission(mission); +} + + +/*********************************************************************************************** + * MissionClass::Restore_Mission -- Restores overidden mission * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/28/1995 PWG : Created. * + *=============================================================================================*/ +bool MissionClass::Restore_Mission(void) +{ + if (SuspendedMission != MISSION_NONE) { + Assign_Mission(SuspendedMission); + SuspendedMission= MISSION_NONE; + return(true); + } + return(false); +} + + +/*********************************************************************************************** +** Unit order names. These names correspond to the player selectable orders +** a unit can have. The system initiated orders have no use for the ASCII name +** associated, but they are listed here for completeness sake. +*/ +char const * MissionClass::Missions[MISSION_COUNT] = { + "Sleep", + "Attack", + "Move", + "Retreat", + "Guard", + "Sticky", + "Enter", + "Capture", + "Harvest", + "Area Guard", + "Return", + "Stop", + "Ambush", + "Hunt", + "Timed Hunt", + "Unload", + "Sabotage", + "Construction", + "Selling", + "Repair", + "Rescue", + "Missile", +}; diff --git a/MISSION.H b/MISSION.H new file mode 100644 index 0000000..d19919d --- /dev/null +++ b/MISSION.H @@ -0,0 +1,136 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\mission.h_v 2.16 16 Oct 1995 16:45:46 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MISSION.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 23, 1994 * + * * + * Last Update : April 23, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef MISSION_H +#define MISSION_H + +#include "object.h" +#include "monoc.h" + +/**************************************************************************** +** This handles order assignment and tracking. The order is used to guide +** overall AI processing. +*/ +class MissionClass : public ObjectClass +{ + public: + + /* + ** This the tactical strategy to use. It is used by the unit script. This + ** is a general guide for unit AI processing. + */ + MissionType Mission; + MissionType SuspendedMission; + + /* + ** The order queue is used for orders that should take effect when the vehicle + ** has reached the center point of a cell. The queued order number is +1 when stored here + ** so that 0 will indicated there is no queued order. + */ + MissionType MissionQueue; + + char Status; + + /*--------------------------------------------------------------------- + ** Constructors, Destructors, and overloaded operators. + */ + MissionClass(void); + virtual ~MissionClass(void) {}; + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + #ifdef CHEAT_KEYS + void Debug_Dump(MonoClass *mono) const; + #endif + + virtual MissionType Get_Mission(void) const; + virtual void Assign_Mission(MissionType mission); + virtual bool Commence(void); + virtual void AI(void); + + /* + ** Support functions. + */ + virtual int Mission_Sleep(void); + virtual int Mission_Ambush(void); + virtual int Mission_Attack(void); + virtual int Mission_Capture(void); + virtual int Mission_Guard(void); + virtual int Mission_Guard_Area(void); + virtual int Mission_Harvest(void); + virtual int Mission_Hunt(void); + virtual int Mission_Timed_Hunt(void); + virtual int Mission_Move(void); + virtual int Mission_Retreat(void); + virtual int Mission_Return(void); + virtual int Mission_Stop(void); + virtual int Mission_Unload(void); + virtual int Mission_Enter(void); + virtual int Mission_Construction(void); + virtual int Mission_Deconstruction(void); + virtual int Mission_Repair(void); + virtual int Mission_Missile(void); + virtual void Set_Mission(MissionType mission); + + static char const * Mission_Name(MissionType order); + static MissionType Mission_From_Name(char const *name); + virtual void Override_Mission(MissionType mission, TARGET, TARGET); + virtual bool Restore_Mission(void); + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + private: + + /* + ** This the thread processing timer. When this value counts down to zero, then + ** more script processing may occur. + */ + TCountDownTimerClass Timer; + + /* + ** These are the order names as ASCII strings. + */ + static char const * Missions[MISSION_COUNT]; +}; + + +#endif diff --git a/MIXFILE.CPP b/MIXFILE.CPP new file mode 100644 index 0000000..5954d05 --- /dev/null +++ b/MIXFILE.CPP @@ -0,0 +1,516 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\mixfile.cpv 2.18 16 Oct 1995 16:48:46 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MIXFILE.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : August 8, 1994 * + * * + * Last Update : January 23, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * MixFileClass::Cache -- Caches the named mixfile into RAM. * + * MixFileClass::Cache -- Loads this particular mixfile's data into RAM. * + * MixFileClass::Finder -- Finds the mixfile object that matches the name specified. * + * MixFileClass::Free -- Frees the allocated raw data block (not the index block). * + * MixFileClass::MixFileClass -- Constructor for mixfile object. * + * MixFileClass::Offset -- Determines if the file is in a mixfile and where its sublocation is.* + * MixFileClass::Offset -- Searches in mixfile for matching file and returns offset if found.* + * MixFileClass::Retrieve -- Retrieves a pointer to the specified data file. * + * MixFileClass::~MixFileClass -- Destructor for the mixfile object. * + * MixFileClass::Offset -- Determines the offset of the requested file from the mixfile system.* + * MixFileClass::Free -- Uncaches a cached mixfile. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + +#include "function.h" +#include +#include +#include +#include +#include +#include +#include "mixfile.h" + + +template int Compare(T const *obj1, T const *obj2) { + if (*obj1 < *obj2) return(-1); + if (*obj1 > *obj2) return(1); + return(0); +}; + + +/* +** This is the pointer to the first mixfile in the list of mixfiles registered +** with the mixfile system. +*/ +MixFileClass * MixFileClass::First = 0; + + +/*********************************************************************************************** + * MixFileClass::Free -- Uncaches a cached mixfile. * + * * + * Use this routine to uncache a mixfile that has been cached. * + * * + * INPUT: filename -- Pointer to the filename of the mixfile that is to be uncached. * + * * + * OUTPUT: bool; Was the mixfile found and freed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +bool MixFileClass::Free(char const *filename) +{ + MixFileClass * ptr = Finder(filename); + + if (ptr) { + ptr->Free(); + return(true); + } + return(false); +} + + +#ifndef NOMEMCHECK +void MixFileClass::Free_All(void) +{ + while (First) { + delete First; + } +} +#endif + + +/*********************************************************************************************** + * MixFileClass::~MixFileClass -- Destructor for the mixfile object. * + * * + * This destructor will free all memory allocated by this mixfile and will remove it from * + * the system. A mixfile removed in this fashion must be created anew in order to be * + * subsequent used. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + * 01/06/1995 JLB : Puts mixfile header table into EMS. * + *=============================================================================================*/ +MixFileClass::~MixFileClass(void) +{ + + /* + ** Deallocate any allocated memory. + */ + if (Filename) { + free((char *)Filename); + } + if (Data) { + delete [] Data; + } + if (Buffer) { + delete [] Buffer; + } + + /* + ** Unlink this mixfile object from the chain. + */ + if (this == First) { + First = (MixFileClass *)Get_Next(); + } else { + Remove(); + } + Zap(); +} + + +/*********************************************************************************************** + * MixFileClass::MixFileClass -- Constructor for mixfile object. * + * * + * This is the constructor for the mixfile object. It takes a filename and a memory * + * handler object and registers the mixfile object with the system. The index block is * + * allocated and loaded from disk by this routine. * + * * + * INPUT: filename -- Pointer to the filename of the mixfile object. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + *=============================================================================================*/ +MixFileClass::MixFileClass(char const *filename) +{ + CCFileClass file; // Working file object. + + /* + ** Load in the control block. It always remains resident. + */ + Data = 0; + Count = 0; + Buffer = 0; + file.Set_Name(filename); + Filename = strdup(file.File_Name()); + + if (!Force_CD_Available(RequiredCD)) { + Prog_End(); + exit(EXIT_FAILURE); + } + + if (file.Is_Available(true)) { + FileHeader fileheader; + + file.Open(); + file.Read(&fileheader, sizeof(fileheader)); + Count = fileheader.count; + DataSize = fileheader.size; + + /* + ** Load up the offset control array. This could be located in + ** EMS if possible. + */ + Buffer = new SubBlock [Count]; + if (Buffer) { + file.Read(Buffer, Count * sizeof(SubBlock)); + } + file.Close(); + } else { +// delete this; + return; + } + + /* + ** Raw data block starts uncached. + */ + Data = 0; + + /* + ** Attach to list of mixfiles. + */ + Zap(); + if (!First) { + First = this; + } else { + Add_Tail(*First); + } +} + + +/*********************************************************************************************** + * MixFileClass::Retrieve -- Retrieves a pointer to the specified data file. * + * * + * This routine will return with a pointer to the specified data file if the file resides * + * in memory. Otherwise, this routine returns NULL. Use this routine to access a resident * + * file directly rather than going through the process of pseudo disk access. This will * + * save both time and RAM. * + * * + * INPUT: filename -- Pointer to the filename of the data file to retrieve a pointer to. * + * * + * OUTPUT: Returns with a pointer to the data file's data. If the file is not in RAM, then * + * NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/23/1994 JLB : Created. * + *=============================================================================================*/ +void const * MixFileClass::Retrieve(char const *filename) { + void *ptr = 0; + Offset(filename, &ptr); +// if (!ptr) { +// errno = ENOENT; +// File_Fatal(filename); +// } + return(ptr); +}; + + +/*********************************************************************************************** + * MixFileClass::Finder -- Finds the mixfile object that matches the name specified. * + * * + * This routine will scan through all registered mixfiles and return with a pointer to * + * the matching mixfile. If no mixfile could be found that matches the name specified, * + * then NULL is returned. * + * * + * INPUT: filename -- Pointer to the filename to search for. * + * * + * OUTPUT: Returns with a pointer to the matching mixfile -- if found. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + *=============================================================================================*/ +MixFileClass * MixFileClass::Finder(char const *filename) +{ + MixFileClass * ptr; + + ptr = First; + while (ptr) { + if (stricmp(&ptr->Filename[strlen(ptr->Filename)-strlen(filename)], filename) == 0) { + return(ptr); + } + ptr = (MixFileClass *)ptr->Get_Next(); + } + return(0); +} + + +/*********************************************************************************************** + * MixFileClass::Cache -- Caches the named mixfile into RAM. * + * * + * This routine will cache the mixfile, specified by name, into RAM. * + * * + * INPUT: filename -- The name of the mixfile that should be cached. * + * * + * OUTPUT: bool; Was the cache successful? * + * * + * WARNINGS: This routine could go to disk for a very long time. * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + *=============================================================================================*/ +bool MixFileClass::Cache(char const *filename) +{ + MixFileClass * mixer = Finder(filename); + + if (mixer) { + return(mixer->Cache()); + } + return(false); +} + + +/*********************************************************************************************** + * MixFileClass::Cache -- Loads this particular mixfile's data into RAM. * + * * + * This load the mixfile data into ram for this mixfile object. This is the counterpart * + * to the Free() function. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the file load successful? It could fail if there wasn't enough room * + * to allocate the raw data block. * + * * + * WARNINGS: This routine goes to disk for a potentially very long time. * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + *=============================================================================================*/ +bool MixFileClass::Cache(void) +{ + if (Data) return(true); + + Data = new char [DataSize]; + if (Data) { + CCFileClass file(Filename); + + file.Open(); + file.Seek(sizeof(SubBlock) * Count + sizeof(FileHeader)); + long actual = file.Read(Data, DataSize); + if (actual != DataSize) { +#ifdef GERMAN + Fatal("Korrupte .MIX-Datei \"%s\". Beim Versuch, %ld zu lesen, nur %ld gefunden.", Filename, DataSize, actual); +#else +#ifdef FRENCH + Fatal("Fichier .MIX corrumpu \"%s\". Essai de lecture de %ld, mais %ld obtenu.", Filename, DataSize, actual); +#else + Fatal("Corrupt .MIX file \"%s\". Tried to read %ld, but got %ld.", Filename, DataSize, actual); +#endif +#endif + } + file.Close(); + return(true); + } +#ifdef GERMAN + Fatal("Kann Datei \"%s\" nicht laden.", Filename); +#else +#ifdef FRENCH + Fatal("Impossible de charger \"%s\".", Filename); +#else + Fatal("Unable to load \"%s\".", Filename); +#endif +#endif + return(false); +} + + +/*********************************************************************************************** + * MixFileClass::Free -- Frees the allocated raw data block (not the index block). * + * * + * This routine will free the (presumably large) raw data block, but leave the index * + * block intact. By using this in conjunction with the Cache() function, one can maintain * + * tight control of memory usage. If the index block is desired to be freed, then the * + * mixfile object must be deleted. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + *=============================================================================================*/ +void MixFileClass::Free(void) +{ + if (Data) { + delete [] Data; + Data = 0; + } +} + + +/*********************************************************************************************** + * MixFileClass::Offset -- Determines if the file is in a mixfile and where its sublocation is.* + * * + * This routine will scan through all registers mixfiles in an attempt to locate the file * + * in question. Whether the file is located in RAM or on disk does not restrict this * + * search operation. * + * * + * INPUT: filename -- The filename of the file to search within the mixfile system. * + * * + * realptr -- Pointer to pointer that will be filled with the RAM pointer to * + * the file if it happens to be in RAM. NULL if otherwise. * + * * + * mixfile -- Pointer to the mixfile object that contains the file in question. * + * * + * offset -- The offset to the start of the data of the file. If the file is * + * on disk then this is the offset from the start of the file, if * + * it is located in RAM, then it is the offset from the start of the * + * raw data block. * + * * + * size -- Pointer to where the size of the file will be stored. * + * * + * OUTPUT: bool; Was the file found in one of the mixfiles? The value stored in "realptr" * + * will be NULL if it is on disk, otherwise it is in RAM. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/08/1994 JLB : Created. * + *=============================================================================================*/ +//int _USERENTRY Compare(MixFileClass::SubBlock const *, MixFileClass::SubBlock const *); + +// int _USERENTRY compfunc(void const *ptr1, void const *ptr2) +int compfunc(void const *ptr1, void const *ptr2) +{ +// long diff = *(long const *)ptr1 - *(long const *)ptr2; +// return FP_SEG(diff); + + if (*(long const *)ptr1 < *(long const *)ptr2) return(-1); + if (*(long const *)ptr1 > *(long const *)ptr2) return(1); + return(0); +} + + +/*********************************************************************************************** + * MixFileClass::Offset -- Determines the offset of the requested file from the mixfile system.* + * * + * This routine will take the falename specified and search through the mixfile system * + * looking for it. If the file was found, then the mixfile it was found int, the offset * + * from the start of the mixfile, and the size of the embedded file will be returned. * + * Using this method it is possible for the CCFileClass system to process it as a normal * + * file. * + * * + * INPUT: filename -- The filename to search for. * + * * + * realptr -- Stores a pointer to the start of the file in memory here. If the * + * file is not in memory, then NULL is stored here. * + * * + * mixfile -- The pointer to the corresponding mixfile is placed here. If no * + * mixfile was found that contains the file, then NULL is stored here. * + * * + * offset -- The starting offset from the beginning of the parent mixfile is * + * stored here. * + * * + * size -- The size of the embedded file is stored here. * + * * + * OUTPUT: bool; Was the file found? The file may or may not be resident, but it does exist * + * and can be opened. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +bool MixFileClass::Offset(char const *filename, void ** realptr, MixFileClass ** mixfile, long * offset, long * size) +{ + MixFileClass * ptr; + + if (!filename) return(false); + + /* + ** Create the key block that will be used to binary search for the file. + */ + long crc = Calculate_CRC(strupr((char *)filename), strlen(filename)); + SubBlock key; + key.CRC = crc; + + /* + ** Sweep through all registered mixfiles, trying to find the file in question. + */ + ptr = First; + while (ptr) { + SubBlock * block; + + /* + ** Binary search for the file in this mixfile. If it is found, then extract the + ** appropriate information and store it in the locations provided and then return. + */ + block = (SubBlock *)bsearch(&key, ptr->Buffer, ptr->Count, sizeof(SubBlock), compfunc); + if (block) { + if (mixfile) *mixfile = ptr; + if (size) *size = block->Size; + if (realptr) *realptr = 0; + if (offset) *offset = block->Offset; + if (realptr && ptr->Data) { + *realptr = Add_Long_To_Pointer(ptr->Data, block->Offset); + } + if (!ptr->Data && offset) { + *offset += sizeof(SubBlock) * ptr->Count + sizeof(FileHeader); + } + return(true); + } + + /* + ** Advance to next mixfile. + */ + ptr = (MixFileClass *)ptr->Get_Next(); + } + + /* + ** All the mixfiles have been examined but no match was found. Return with the non success flag. + */ + return(false); +} + diff --git a/MIXFILE.H b/MIXFILE.H new file mode 100644 index 0000000..148314b --- /dev/null +++ b/MIXFILE.H @@ -0,0 +1,87 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\mixfile.h_v 2.18 16 Oct 1995 16:47:22 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MIXFILE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : October 18, 1994 * + * * + * Last Update : October 18, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef MIXFILE_H +#define MIXFILE_H + +#include +#include "link.h" + +class MixFileClass : public LinkClass +{ + public: + char const *Filename; // Filename of mixfile. + + MixFileClass(char const *filename); + ~MixFileClass(void); + + static bool Free(char const *filename); + static void Free_All(void); + void Free(void); + bool Cache(void); + static bool Cache(char const *filename); + static bool Offset(char const *filename, void ** realptr = 0, MixFileClass ** mixfile = 0, long * offset = 0, long * size = 0); + static void const * Retrieve(char const *filename); + + struct SubBlock { + long CRC; // CRC code for embedded file. + long Offset; // Offset from start of data section. + long Size; // Size of data subfile. + + int operator < (SubBlock & two) const {return (CRC < two.CRC);}; + int operator > (SubBlock & two) const {return (CRC > two.CRC);}; + int operator == (SubBlock & two) const {return (CRC == two.CRC);}; + }; + + private: + static MixFileClass * Finder(char const *filename); + long Offset(long crc, long *size = 0); + + typedef struct { + short count; + long size; + } FileHeader; + + int Count; // Number of sub-blocks. + long DataSize; // Size of raw data. + SubBlock * Buffer; // Array of sub blocks (could be in EMS). + void *Data; // Pointer to raw data. + + static MixFileClass * First; +}; + +#endif diff --git a/MMX.ASM b/MMX.ASM new file mode 100644 index 0000000..d4fa7ff --- /dev/null +++ b/MMX.ASM @@ -0,0 +1,325 @@ +; +; Command & Conquer(tm) +; Copyright 2025 Electronic Arts Inc. +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; + +;*************************************************************************** +;** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S I N C ** +;*************************************************************************** +;* * +;* Project Name : Command & Conquer * +;* * +;* File Name : MMX.ASM * +;* * +;* Programmer : Steve Tall * +;* * +;* Start Date : May 19th, 1996 * +;* * +;* Last Update : May 19th 1996 [ST] * +;* * +;*-------------------------------------------------------------------------* +;* Functions: * +;* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * + + + include + + + .model flat + .586 + + + externdef C Detect_MMX_Availability:near + externdef C Single_Line_Trans_Entry:near + externdef C Next_Line:near + externdef C Init_MMX:near + externdef C MMX_Done:near + + externdef EndNewShapeJumpTable:byte + externdef NewShapeJumpTable:dword + externdef Single_Line_Trans:near + externdef MMX_Single_Line_Trans:near + + + .code + +externdef C CPUType:byte + + + +;********************************************************************************************* +;* Detect_MMX_Availability -- Detect the presence of MMX technology. * +;* * +;* * +;* INPUT: Nothing * +;* * +;* OUTPUT: True if MMX technology is available. * +;* * +;* Warnings: * +;* * +;* Note: Based in part on CPUID32.ASM by Intel * +;* * +;* HISTORY: * +;* 05/19/96 ST : Created. * +;*===========================================================================================* + +Detect_MMX_Availability proc C + + local idflag:byte + local cputype:byte + +;assume processor is at least 386 +; +;check whether AC bit in eflags can be toggled. +;If not then processor is 386 + + mov [idflag],0 + + pushfd ;get Eflags in EAX + pop eax + mov ecx,eax ;save eflags + xor eax,40000h ;toggle AC bit in eflags + push eax ;new eflags on stack + popfd ;move new value into eflags + pushfd ;get new eflags back into eax + pop eax + xor eax,ecx ;if AC bit not toggled then CPU=386 + mov [cputype],3 + jz @@end_get_cpu ;cpu is 386 + + push ecx + popfd ;restore AC bit in eflags + + +;processor is at least 486 +; +;Check for ability to set/clear ID flag in EFLAGS +;ID flag indicates ability of processor to execute the CPUID instruction. +;486 not guaranteed to have CPUID inst? +; + mov [cputype],4 + mov eax,ecx ;original EFLAGS + xor eax,200000h ;toggle ID bit + push eax + popfd + pushfd + pop eax + xor eax,ecx ;check if still toggled + jz @@end_get_cpu + + +; Execute CPUID instruction to determine vendor, family, +; model and stepping. +; + + mov [idflag],1 ;flag ID is available + + xor eax,eax + cpuid + + mov dword ptr [VendorID],ebx + mov dword ptr [VendorID+4],edx + mov dword ptr [VendorID+8],ecx + mov dword ptr [VendorID+12]," " + + cmp eax,1 ;check if 1 is valid + jl @@end_get_cpu ;inp for cpuid inst. + + xor eax,eax + inc eax + + cpuid ;get stepping, model and family + + and ax,0f00H + shr ax,08H + + mov [cputype],al + +@@end_get_cpu: mov al,[cputype] + mov [CPUType],al + + +; +; We have the CPU type in al now. +; If we arent on at least a pentium then we can assume there is no MMX +; + cmp al,5 + jl @@no_mmx + + mov eax,1 + cpuid + test edx,00800000h + jz @@no_mmx + +; +; MMX detected - return true +; + mov eax,1 + ret + + +@@no_mmx: xor eax,eax + ret + + +Detect_MMX_Availability endp + + + +;********************************************************************************************* +;* Init_MMX -- Do any special inits required for MMX support * +;* * +;* * +;* INPUT: Nothing * +;* * +;* OUTPUT: None * +;* * +;* Warnings: * +;* * +;* HISTORY: * +;* 05/19/96 ST : Created. * +;*===========================================================================================* + +Init_MMX proc C + + mov edi,offset NewShapeJumpTable + mov ecx,offset EndNewShapeJumpTable + sub ecx,edi + shr ecx,2 + mov eax,offset Single_Line_Trans + mov ebx,offset MMX_Single_Line_Trans + cld + + +@@patch_loop: repnz scasd + jnz @@done + mov [edi-4],ebx + test ecx,ecx + jnz @@patch_loop + +@@done: ret + +Init_MMX endp + + + + + + +;********************************************************************************************* +;* MMX_Done -- Restores floating point capability after MMX usage * +;* * +;* * +;* INPUT: Nothing * +;* * +;* OUTPUT: None * +;* * +;* Warnings: * +;* * +;* HISTORY: * +;* 05/19/96 ST : Created. * +;*===========================================================================================* + +MMX_Done proc C + + emms + ret + +MMX_Done endp + + + + + + + + code segment page public use32 'code' ; Need stricter segment alignment + ; for pentium optimisations + + +;********************************************************************************************* +;* MMX_Single_Line_Trans -- draw a single line of transparent pixels using MMX technology * +;* * +;* * +;* INPUT: Esi - ptr to source data * +;* Edi - ptr to destination data * +;* Ecx - width to draw in bytes * +;* * +;* OUTPUT: None * +;* * +;* Warnings: * +;* * +;* HISTORY: * +;* 05/19/96 ST : Created. * +;*===========================================================================================* + + align 16 + +MMX_Single_Line_Trans proc near + +; +; If we are doing less than 8 bytes then dont use MMX +; + cmp ecx,8 + jge @@mmx_loop + push offset Single_Line_Trans_Entry + ret + +; +; Use MMX instructions to mask 8 bytes at once +; +; Creates a bitmask based on the source bytes equality with zero and then uses this to mask +; out the source bytes in the destination data. The advatage that MMX gives us is that there is +; no 'test for zero then jump' required to mask. +; + align 64 ;MMX instructions like 64 byte alignment! + +@@mmx_loop: + movq mm0,[esi] ; move 8 bytes of source into mm0 + pxor mm1,mm1 ; zero out mm1 + pcmpeqb mm1,mm0 ; compare mm0 with 0. Bits get set in mm1 + lea esi,[esi+8] ; adjust the source data pointer + pand mm1,[edi] ; and in the destination data to throw away the bytes which arent zero in the source + sub ecx,8 ; adjust the byte counter + por mm1,mm0 ; or in the source with the destination data + movq [edi],mm1 ; write back the destination data + lea edi,[edi+8] ; adjust the destination pointer + + cmp ecx,8 + jg @@mmx_loop + +; +; Jump to the approprite code for drawing the end of this line or going to the next one +; + push offset Next_Line + jcxz @@next_line + push offset Single_Line_Trans_Entry +@@next_line: ret + + +MMX_Single_Line_Trans endp + + +code ends + + .data + +CPUType db 0 +VendorID db "Not available",0,0,0,0,0,0 + + +end + diff --git a/MONOC.CPP b/MONOC.CPP new file mode 100644 index 0000000..533f984 --- /dev/null +++ b/MONOC.CPP @@ -0,0 +1,807 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\monoc.cpv 2.13 16 Oct 1995 16:50:36 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MONO.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : July 2, 1994 * + * * + * Last Update : October 17, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * MonoClass::Clear -- Clears the monochrome screen object. * + * MonoClass::Draw_Box -- Draws a box using the IBM linedraw characters. * + * MonoClass::MonoClass -- The default constructor for monochrome screen object. * + * MonoClass::operator = -- Handles making one mono object have the same imagery as another. * + * MonoClass::Print -- Prints the text string at the current cursor coordinates. * + * MonoClass::Printf -- Prints a formatted string to the monochrome screen. * + * MonoClass::Scroll -- Scroll the monochrome screen up by the specified lines. * + * MonoClass::Set_Cursor -- Sets the monochrome cursor to the coordinates specified. * + * MonoClass::Text_Print -- Prints text to the monochrome object at coordinates indicated. * + * MonoClass::View -- Brings the mono object to the main display. * + * MonoClass::~MonoClass -- The default destructor for a monochrome screen object. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +//#pragma inline +#include "function.h" +#include "monoc.h" + +#include +#include +#include +#include +#include +#include + + +//extern void output(short port, short data); +//#pragma aux output parm [dx] [ax] = \ +// "out dx,al" \ +// "inc dx" \ +// "mov al,ah" \ +// "out dx,al" + + + +int MonoClass::Enabled = 0; +MonoClass * MonoClass::PageUsage[MonoClass::MAX_MONO_PAGES] = {0,0,0,0,0,0,0,0}; +//DOSSegmentClass MonoClass::MonoSegment(MonoClass::SEGMENT); +void * MonoClass::MonoSegment = (void*)0x000b0000; + +/* +** These are the IBM linedraw characters. +*/ +MonoClass::BoxDataType const MonoClass::CharData[MonoClass::COUNT] = { + {0xDA,0xC4,0xBF,0xB3,0xD9,0xC4,0xC0,0xB3}, // Single line + {0xD5,0xCD,0xB8,0xB3,0xBE,0xCD,0xD4,0xB3}, // Double horz. + {0xD6,0xC4,0xB7,0xBA,0xBD,0xC4,0xD3,0xBA}, // Double vert. + {0xC9,0xCD,0xBB,0xBA,0xBC,0xCD,0xC8,0xBA} // Double horz and vert. +}; + + +/*********************************************************************************************** + * MonoClass::MonoClass -- The default constructor for monochrome screen object. * + * * + * This is the constructor for monochrome screen objects. It handles allocating a free * + * monochrome page. If there are no more pages available, then this is a big error. The * + * page allocated may not be the visible one. Call the View function in order to bring * + * it to the displayed page. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +MonoClass::MonoClass(void) +{ + int index; + + Attrib = DEFAULT_ATTRIBUTE; // Normal text color. + X = Y = 0; + for (index = 0; index < MAX_MONO_PAGES; index++) { + if (!PageUsage[index]) { + PageUsage[index] = this; + Page = (char)index; + break; + } + } + if (index == MAX_MONO_PAGES) { + // Major error message should pop up here! + delete this; + } +} + + +/*********************************************************************************************** + * MonoClass::~MonoClass -- The default destructor for a monochrome screen object. * + * * + * This is the default destructor for a monochrome screen object. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +MonoClass::~MonoClass(void) +{ + PageUsage[Page] = 0; +} + + +/*********************************************************************************************** + * MonoClass::Draw_Box -- Draws a box using the IBM linedraw characters. * + * * + * Use this routine to draw a box to the monochrome screen. The IBM line draw characters * + * are used to give the it a fancy appearance. There are several line draw modes supported. * + * * + * INPUT: x,y -- The coordinates of the upper left corner of the box. * + * * + * w,y -- The width and height (respectively) to make the box. * + * * + * attrib -- The text attribute to use when drawing the box outline characters. * + * * + * thick -- The thickness style to use. Examine the BoxStyleType enum for * + * elaboration on the supported styles. * + * * + * OUTPUT: none * + * * + * WARNINGS: The interior of the box is NOT cleared by this routine. It is advised that this * + * area be cleared before the box is drawn. * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +void MonoClass::Draw_Box(int x, int y, int w, int h, char attrib, BoxStyleType thick) +{ + CellType cell; + char oldattrib = Attrib; + + if (!Enabled || !w || !h) return; + + cell.Attribute = attrib; + + /* + ** Draw the horizontal lines. + */ + for (int xpos = 0; xpos < w-2; xpos++) { + cell.Character = CharData[thick].TopEdge; + Store_Cell(cell, x+xpos+1, y); +// MonoSegment.Copy_Word_To(*(short*)&cell, Offset(x+xpos+1, y)); + cell.Character = CharData[thick].BottomEdge; + Store_Cell(cell, x+xpos+1, y+h-1); +// MonoSegment.Copy_Word_To(*(short*)&cell, Offset(x+xpos+1, y+h-1)); + } + + /* + ** Draw the vertical lines. + */ + for (int ypos = 0; ypos < h-2; ypos++) { + cell.Character = CharData[thick].LeftEdge; + Store_Cell(cell, x, y+ypos+1); +// MonoSegment.Copy_Word_To(*(short*)&cell, Offset(x, y+ypos+1)); + cell.Character = CharData[thick].RightEdge; + Store_Cell(cell, x+w-1, y+ypos+1); +// MonoSegment.Copy_Word_To(*(short*)&cell, Offset(x+w-1, y+ypos+1)); + } + + /* + ** Draw the four corners. + */ + if (w > 1 && h > 1) { + cell.Character = CharData[thick].UpperLeft; + Store_Cell(cell, x, y); +// MonoSegment.Copy_Word_To(*(short*)&cell, Offset(x, y)); + cell.Character = CharData[thick].UpperRight; + Store_Cell(cell, x+w-1, y); +// MonoSegment.Copy_Word_To(*(short*)&cell, Offset(x+w-1, y)); + cell.Character = CharData[thick].BottomRight; + Store_Cell(cell, x+w-1, y+h-1); +// MonoSegment.Copy_Word_To(*(short*)&cell, Offset(x+w-1, y+h-1)); + cell.Character = CharData[thick].BottomLeft; + Store_Cell(cell, x, y+h-1); +// MonoSegment.Copy_Word_To(*(short*)&cell, Offset(x, y+h-1)); + } + + Attrib = oldattrib; +} + + +/*********************************************************************************************** + * MonoClass::Set_Cursor -- Sets the monochrome cursor to the coordinates specified. * + * * + * Use this routine to set the monochrome's cursor position to the coordinates specified. * + * This is the normal way of controlling where the next Print or Printf will output the * + * text to. * + * * + * INPUT: x,y -- The coordinate to position the monochrome cursor. 0,0 is the upper left * + * corner. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +void MonoClass::Set_Cursor(int x, int y) +{ + #ifdef FIX_ME_LATER + int pos = (y*COLUMNS)+x; + + if (!Enabled) return; + + X = (char)(x%COLUMNS); + Y = (char)(y%LINES); + + if (Page == 0) { + _DX = CONTROL_PORT; + _AX = (short)(0x0E|(pos&0xFF00)); + asm { + out dx,al + inc dx + mov al,ah + out dx,al + } + + _DX = CONTROL_PORT; + _AX = (short)(0x0F|(pos<<8)); + asm { + out dx,al + inc dx + mov al,ah + out dx,al + } + +// outportb(CONTROL_PORT,0x0E|(pos&0xFF00)); +// outportb(CONTROL_PORT,0x0F|(pos<<8)); + } + + #endif //FIX_ME_LATER + +x=y; +y=x; +} + + +/*********************************************************************************************** + * MonoClass::Clear -- Clears the monochrome screen object. * + * * + * This routine will fill the monochrome screen object with spaces. It is clearing the * + * screen of data, making it free for output. The cursor is positioned at the upper left * + * corner of the screen by this routine. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +void MonoClass::Clear(void) +{ + CellType cell; +// int offset; + + if (!Enabled) return; + + Set_Cursor(0, 0); + + cell.Attribute = Attrib; + cell.Character = ' '; + +// offset = Offset(0, 0); + for (int y = 0; y < LINES; y++) { + for (int x = 0; x < COLUMNS; x++) { + Store_Cell(cell, x, y); + } + } +} + + +/*********************************************************************************************** + * MonoClass::Scroll -- Scroll the monochrome screen up by the specified lines. * + * * + * Use this routine to scroll the monochrome screen up by the number of lines specified. * + * This routine is typically called by the printing functions so that the monochrome screen * + * behaves in the expected manner -- printing at the bottom of the screen scrolls it up * + * to make room for new text. * + * * + * INPUT: lines -- The number of lines to scroll the monochrome screen. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +void MonoClass::Scroll(int lines) +{ + CellType cell; + + if (!Enabled || lines <= 0) return; + + memmove( (void*)((long)MonoSegment + Offset(0, 0)), + (void*)((long)MonoSegment + Offset(0, lines)), + (LINES-lines)*COLUMNS*sizeof(CellType)); + +// DOSSegmentClass::Copy(MonoSegment, Offset(0, lines), MonoSegment, Offset(0, 0), (LINES-lines)*COLUMNS*sizeof(CellType)); + + Y--; + cell.Attribute = Attrib; + cell.Character = ' '; + + for (int l = LINES-lines; l < LINES; l++) { + for (int index = 0; index < COLUMNS; index++) { + Store_Cell(cell, index, l); +// MonoSegment.Copy_Word_To(*(short*)&cell, Offset(index, l)); + } + } +} + + +/*********************************************************************************************** + * MonoClass::Printf -- Prints a formatted string to the monochrome screen. * + * * + * Use this routine to output a formatted string, using the standard formatting options, * + * to the monochrome screen object's current cursor position. * + * * + * INPUT: text -- Pointer to the text to print. * + * * + * ... -- Any optional parameters to supply in formatting the text. * + * * + * OUTPUT: none * + * * + * WARNINGS: The total formatted text length must not exceed 255 characters. * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +void MonoClass::Printf(char const *text, ...) +{ + va_list va; + /* + ** The buffer object is placed at the end of the local variable list + ** so that if the sprintf happens to spill past the end, it isn't likely + ** to trash anything (important). The buffer is then manually truncated + ** to maximum allowed size before being printed. + */ + char buffer[256]; + + if (!Enabled) return; + + va_start(va, text); + vsprintf(buffer, text, va); + buffer[sizeof(buffer)-1] = '\0'; + + Print(buffer); + va_end(va); +} + + +#ifdef NEVER +void MonoClass::Printf(int text, ...) +{ + va_list va; + /* + ** The buffer object is placed at the end of the local variable list + ** so that if the sprintf happens to spill past the end, it isn't likely + ** to trash anything (important). The buffer is then manually truncated + ** to maximum allowed size before being printed. + */ + char buffer[256]; + + if (!Enabled) return; + + va_start(va, text); + vsprintf(buffer, Text_String(text), va); + buffer[sizeof(buffer)-1] = '\0'; + + Print(buffer); + va_end(va); +} +#endif + + +/*********************************************************************************************** + * MonoClass::Print -- Prints the text string at the current cursor coordinates. * + * * + * Use this routine to output the specified text string at the monochrome object's current * + * text coordinate position. * + * * + * INPUT: ptr -- Pointer to the string to print. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +void MonoClass::Print(char const *ptr) +{ +// int optr; + char startcol = X; + char const * text; + CellType cell; + + if (!ptr || !Enabled) return; + + text = ptr; + cell.Attribute = Attrib; +// optr = Offset(X, Y); + while (*text) { + + /* + ** Sometimes the character string is used for cursor control instead + ** of plain text output. Check for this case. + */ + switch (*text) { + + /* + ** The "return" code behaves as it did in the old C library + ** mono system. That is, it returns the cursor position to + ** the next line but at the starting column of the print. + */ + case '\r': + X = startcol; + Y++; + Scroll(Y-(LINES-1)); +// optr = Offset(X, Y); + break; + + /* + ** The "newline" code behaves like the console newline character. + ** That is, it moves the cursor down one line and at the first + ** column. + */ + case '\n': + X = 0; + Y++; + Scroll(Y-(LINES-1)); +// optr = Offset(X, Y); + break; + + /* + ** All other characters are output directly and the cursor moves + ** rightward to match. If the cursor wraps past the right + ** edge is it moved to the next now down at left margin. If the + ** cursor goes off the bottom of the display, the display is scrolled + ** upward a line. + */ + default: + cell.Character = *text; + Store_Cell(cell, X, Y); +// MonoSegment.Copy_Word_To(*(short*)&cell, optr); +// optr += sizeof(CellType); + + X++; + if (X >= COLUMNS) { + X = 0; + Y++; + + if (Y > (LINES-1)) { + Scroll(Y-(LINES-1)); +// optr = Offset(X, Y); + } + } + break; + + } + text++; + } + + Set_Cursor(X, Y); +} + + +/*********************************************************************************************** + * MonoClass::Text_Print -- Prints text to the monochrome object at coordinates indicated. * + * * + * Use this routine to output text to the monochrome object at the X and Y coordinates * + * specified. * + * * + * INPUT: text -- Pointer to the text string to display. * + * * + * x,y -- The X and Y character coordinates to start the printing at. * + * * + * attrib-- Optional parameter that specifies what text attribute code to use. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +void MonoClass::Text_Print(char const *text, int x, int y, char attrib) +{ + char oldx = X; + char oldy = Y; + char oldattrib = Attrib; + + X = (char)x; + Y = (char)y; + Attrib = attrib; + Print(text); + Attrib = oldattrib; + Set_Cursor(oldx, oldy); +} + +#ifdef NEVER +void MonoClass::Text_Print(int text, int x, int y, char attrib) +{ + char oldx = X; + char oldy = Y; + char oldattrib = Attrib; + + if (text != TXT_NONE) { + X = (char)x; + Y = (char)y; + Attrib = attrib; + Print(Text_String(text)); + Attrib = oldattrib; + Set_Cursor(oldx, oldy); + } +} + +void MonoClass::Print(int text) +{ + Print(Text_String(text)); +} +#endif + + +/*********************************************************************************************** + * MonoClass::operator = -- Handles making one mono object have the same imagery as another. * + * * + * The assignment operator will handle copying the imagery from one monochrome object to * + * another. Use this routine in to make two monochrome class objects visually identical. * + * * + * INPUT: src -- A reference to the source (right side) monochrome object. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +MonoClass & MonoClass::operator = (MonoClass const & src) +{ + memcpy((void*)((long)MonoSegment + src.Offset(0, 0)), (void*)((long)MonoSegment + Offset(0, 0)), SIZE_OF_PAGE); +// DOSSegmentClass::Copy(MonoSegment, src.Offset(0, 0), MonoSegment, Offset(0,0), SIZE_OF_PAGE); + Set_Cursor(src.X, src.Y); + return(*this); +} + + +/*********************************************************************************************** + * MonoClass::View -- Brings the mono object to the main display. * + * * + * Use this routine to display the mono object on the monochrome screen. It is possible * + * that the mono object exists on some background screen memory. Calling this routine will * + * perform the necessary memory swapping to bring the data to the front. The mono object * + * that was currently being viewed is not destroyed by this function. It is merely moved * + * off to some background page. It can be treated normally, except that is just isn't * + * visible. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +void MonoClass::View(void) +{ + MonoClass *displace; // The page that is being displaced. + + if (Get_Current() == this) return; + + /* + ** If the visible page is already assigned to a real monochrome page + ** object, then it must be swapped with the new one. + */ + displace = Get_Current(); + if (displace) { + char temp[SIZE_OF_PAGE]; + + memcpy(&temp[0], MonoSegment, SIZE_OF_PAGE); + memcpy(MonoSegment, (void*)((long)MonoSegment + Offset(0, 0)), SIZE_OF_PAGE); + memcpy((void*)((long)MonoSegment + Offset(0, 0)), &temp[0], SIZE_OF_PAGE); + +// DOSSegmentClass::Swap(MonoSegment, Offset(0, 0), MonoSegment, 0, SIZE_OF_PAGE); + displace->Page = Page; + + } else { + + /* + ** Just copy the new page over since the display page is not assigned + ** to a real monochrome page object. + */ + memcpy(MonoSegment, (void*)((long)MonoSegment + Offset(0, 0)), SIZE_OF_PAGE); +// DOSSegmentClass::Copy(MonoSegment, Offset(0, 0), MonoSegment, 0, SIZE_OF_PAGE); + } + PageUsage[Page] = displace; + PageUsage[0] = this; + Page = 0; + + Set_Cursor(X, Y); +} + + + +/************************************************************************************ +** This is the set of C wrapper functions that access the MonoClass support routines. +** Since the C interface doesn't have the ability to write to non-visible pages, it +** will just blast the output to whichever mono page is currently visible. If there is +** no mono class object that is visible, then one will be created -- BUT NOT FREED. +** Typically, this is ok, since the C interface will create only one MonoClass object +** and the system supports up to 8. +*/ +void Mono_Set_Cursor(int x, int y) +{ + if (MonoClass::Is_Enabled()) { + MonoClass *mono = MonoClass::Get_Current(); + if (!mono) { + mono = new MonoClass(); + mono->View(); + } + mono->Set_Cursor(x, y); + } +} + +int Mono_Printf(char const *string, ...) +{ + va_list va; + char buffer[256]; + + buffer[0] = '\0'; + if (MonoClass::Is_Enabled()) { + MonoClass *mono = MonoClass::Get_Current(); + if (!mono) { + mono = new MonoClass(); + mono->View(); + } + + va_start(va, string); + vsprintf(buffer, string, va); + + mono->Print(buffer); + + va_end(va); + } + return((short)strlen(buffer)); +} + + +void Mono_Clear_Screen(void) +{ + if (MonoClass::Is_Enabled()) { + MonoClass *mono = MonoClass::Get_Current(); + if (!mono) { + mono = new MonoClass(); + mono->View(); + } + mono->Clear(); + } +} + +void Mono_Text_Print(void const *text, int x, int y, int attrib) +{ + if (MonoClass::Is_Enabled()) { + MonoClass *mono = MonoClass::Get_Current(); + if (!mono) { + mono = new MonoClass(); + mono->View(); + } + mono->Text_Print((const char*)text, x, y, (char)attrib); + } +} + +void Mono_Draw_Rect(int x, int y, int w, int h, int attrib, int thick) +{ + if (MonoClass::Is_Enabled()) { + MonoClass *mono = MonoClass::Get_Current(); + if (!mono) { + mono = new MonoClass(); + mono->View(); + } + mono->Draw_Box(x, y, w, h, (char)attrib, (MonoClass::BoxStyleType)thick); + } +} + +void Mono_Print(void const *text) +{ + if (MonoClass::Is_Enabled()) { + MonoClass *mono = MonoClass::Get_Current(); + if (!mono) { + mono = new MonoClass(); + mono->View(); + } + mono->Print((const char*)text); + } +} + +int Mono_X(void) +{ + if (MonoClass::Is_Enabled()) { + MonoClass *mono = MonoClass::Get_Current(); + if (!mono) { + mono = new MonoClass(); + mono->View(); + } + return(short)mono->Get_X(); + } + return(0); +} + +int Mono_Y(void) +{ + if (MonoClass::Is_Enabled()) { + MonoClass *mono = MonoClass::Get_Current(); + if (!mono) { + mono = new MonoClass(); + mono->View(); + } + return(short)mono->Get_X(); + } + return(0); +} + + +void Mono_Put_Char(char , int ) +{ +} + +void Mono_Scroll(int ) +{ +} + +void Mono_View_Page(int ) +{ +} + + +#ifdef NEVER +int Mono_Printf(int string, ...) +{ + va_list va; + char buffer[256]; + + buffer[0] = '\0'; + if (MonoClass::Is_Enabled()) { + MonoClass *mono = MonoClass::Get_Current(); + if (!mono) { + mono = new MonoClass(); + mono->View(); + } + + va_start(va, string); + vsprintf(buffer, Text_String(string), va); + + mono->Print(buffer); + + va_end(va); + } + return((short)strlen(buffer)); +} +#endif + diff --git a/MONOC.H b/MONOC.H new file mode 100644 index 0000000..d5601c6 --- /dev/null +++ b/MONOC.H @@ -0,0 +1,191 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\monoc.h_v 2.17 16 Oct 1995 16:45:28 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MONO.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : July 2, 1994 * + * * + * Last Update : July 2, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef MONOC_H +#define MONOC_H + +//#include "dpmi.h" +//#include "function.h" + +class MonoClass { + /* + ** This is a private structure that is used to control which characters + ** are used when a box is drawn. Line drawing on the monochrome screen is + ** really made up of characters. This specifies which characters to use. + */ + typedef struct { + char UpperLeft; + char TopEdge; + char UpperRight; + char RightEdge; + char BottomRight; + char BottomEdge; + char BottomLeft; + char LeftEdge; + } BoxDataType; + + /* + ** Each cell is constructed of the actual character that is displayed and the + ** attribute to use. This character pair is located at every position on the + ** display (80 x 25). Since this cell pair can be represented by a "short" + ** integer, certain speed optimizations are taken in the monochrome drawing + ** code. + */ + typedef struct { + char Character; // Character to display. + char Attribute; // Attribute. + } CellType; + + /* + ** These private constants are used in the various monochrome operations. + */ + enum MonoClassPortEnums { + CONTROL_PORT=0x03B4, // CRTC control register. + DATA_PORT=0x03B5, // CRTC data register. + COLUMNS=80, // Number of columns. + LINES=25, // Number of lines. + SIZE_OF_PAGE=LINES*COLUMNS*sizeof(CellType), // Entire page size. + DEFAULT_ATTRIBUTE=0x02 // Normal white on black color attribute. + }; + + public: + enum MonoClassPageEnums { + MAX_MONO_PAGES=16, // Maximum RAM pages on mono card. + SEGMENT=0xB000 // Monochrome screen segment. + }; + + /* + ** These are the various box styles that may be used. + */ + typedef enum BoxStyleType { + SINGLE, // Single thickness. + DOUBLE_HORZ, // Double thick on the horizontal axis. + DOUBLE_VERT, // Double thick on the vertical axis. + DOUBLE, // Double thickness. + + COUNT + } BoxStyleType; + + MonoClass(void); + ~MonoClass(void); + + static void Enable(void) {Enabled = 1;}; + static void Disable(void) {Enabled = 0;}; + static int Is_Enabled(void) {return Enabled;}; + static MonoClass * Get_Current(void) {return PageUsage[0];}; + + void Draw_Box(int x, int y, int w, int h, char attrib=DEFAULT_ATTRIBUTE, BoxStyleType thick=SINGLE); + void Set_Default_Attribute(char attrib) {Attrib = attrib;}; + void Clear(void); + void Set_Cursor(int x, int y); + void Print(char const *text); + void Print(int text); + void Printf(char const *text, ...); + void Printf(int text, ...); + void Text_Print(char const *text, int x, int y, char attrib=DEFAULT_ATTRIBUTE); + void Text_Print(int text, int x, int y, char attrib=DEFAULT_ATTRIBUTE); + void View(void); + int Get_X(void) const {return X;}; + int Get_Y(void) const {return Y;}; + + /* + ** Handles deep copies for the mono class objects. This performs what is essentially + ** a screen copy. + */ + MonoClass & operator = (MonoClass const & ); + + /* + ** This merely makes a duplicate of the mono object into a newly created mono + ** object. + */ + MonoClass (MonoClass const &); + + private: + char X; // Cursor X position. + char Y; // Cursor Y position. + char Attrib; // Normal attribute to use if none specified. + char Page; // The current page to write to. + + /* + ** Helper functions to help with display operations. + */ + int Offset(int x=0, int y=0) const {return (SIZE_OF_PAGE*Page) + sizeof(CellType)*(x + (y*COLUMNS));}; + void Scroll(int lines); + void Store_Cell(CellType &cell, int x, int y) { + *(CellType *)((long)MonoSegment + Offset(x, y)) = cell; + }; + + /* + ** This is the segment/selector of the monochrome screen. + */ +// static DOSSegmentClass MonoSegment; + static void * MonoSegment; + + /* + ** This the the arrays of characters used for drawing boxes. + */ + static BoxDataType const CharData[4]; + + /* + ** This array contains pointers to the monochrome objects that are assigned + ** to each of the monochrome pages. As the monochrome pages are made visible, + ** they can be shuffled around between the actual locations. The first entry + ** in this table is the one that is visible. + */ + static MonoClass * PageUsage[MAX_MONO_PAGES]; + + /* + ** If this is true, then monochrome output is allowed. It defaults to false + ** so that monochrome output must be explicitly enabled. + */ + static int Enabled; +}; + +//extern int cdecl Mono_Printf(int string, ...); + + +void Mono_Set_Cursor(int x, int y); +int Mono_Printf(char const *string, ...); +void Mono_Clear_Screen(void); +void Mono_Text_Print(void const *text, int x, int y, int attrib); +void Mono_Draw_Rect(int x, int y, int w, int h, int attrib, int thick); +void Mono_Print(void const *text); +int Mono_X(void); +int Mono_Y(void); + +#endif + diff --git a/MOUSE.CPP b/MOUSE.CPP new file mode 100644 index 0000000..99687ea --- /dev/null +++ b/MOUSE.CPP @@ -0,0 +1,360 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\mouse.cpv 2.18 16 Oct 1995 16:49:56 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MOUSE.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/15/94 * + * * + * Last Update : June 30, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * MouseClass::AI -- Process player input as it relates to the mouse * + * MouseClass::Override_Mouse_Shape -- Alters the shape of the mouse. * + * MouseClass::Init_Clear -- Sets the mouse system to a known state * + * MouseClass::MouseClass -- Default constructor for the mouse handler class. * + * MouseClass::One_Time -- Performs the one time initialization of the mouse system. * + * MouseClass::Set_Default_Mouse -- Sets the mouse to match the shape specified. * + * MouseClass::Revert_Mouse_Shape -- Reverts the mouse shape to the non overridden shape. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/* +** This points to the loaded mouse shapes. +*/ +void const * MouseClass::MouseShapes; + +/* +** This is the timer that controls the mouse animation. It is always at a fixed +** rate so it uses the constant system timer. +*/ +CountDownTimerClass MouseClass::Timer; + +/* +** This contains the value of the Virtual Function Table Pointer +*/ +void * MouseClass::VTable; + + +/*********************************************************************************************** + * MouseClass::Set_Default_Mouse -- Sets the mouse to match the shape specified. * + * * + * This routine is used to inform the display system as to which mouse shape is desired. * + * * + * INPUT: mouse -- The mouse shape number to set the mouse to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +void MouseClass::Set_Default_Mouse(MouseType mouse, bool size) +{ + NormalMouseShape = mouse; + Override_Mouse_Shape(mouse, size); +} + + +/*********************************************************************************************** + * MouseClass::Revert_Mouse_Shape -- Reverts the mouse shape to the non overridden shape. * + * * + * Use this routine to cancel the effects of Override_Mouse_Shape(). It will revert the * + * mouse back to the original shape. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/27/1995 JLB : Created. * + *=============================================================================================*/ +void MouseClass::Revert_Mouse_Shape(void) +{ + Override_Mouse_Shape(NormalMouseShape, false); +} + + +void MouseClass::Mouse_Small(bool wwsmall) +{ + MouseStruct const * control = &MouseControl[CurrentMouseShape]; + + if (IsSmall == wwsmall) { + return; + } + + IsSmall = wwsmall; + + if (wwsmall) { + if (control->SmallFrame != -1) { + Set_Mouse_Cursor(control->X, control->Y, Extract_Shape(MouseShapes, control->SmallFrame + Frame/4)); + } else { + Set_Mouse_Cursor(MouseControl[MOUSE_NORMAL].X, MouseControl[MOUSE_NORMAL].Y, Extract_Shape(MouseShapes, MOUSE_NORMAL)); + } + } else { + Set_Mouse_Cursor(control->X, control->Y, Extract_Shape(MouseShapes, control->StartFrame + Frame/4)); + } +} + + +/*********************************************************************************************** + * MouseClass::Override_Mouse_Shape -- Alters the shape of the mouse. * + * * + * This routine is used to alter the shape of the mouse as needed. * + * Typical mouse shape change occurs when scrolling the map or * + * selecting targets. * + * * + * INPUT: mouse -- The mouse shape number to use. * + * * + * OUTPUT: bool; Was the mouse shape changed? * + * * + * WARNINGS: This is not intended to be used as a means to hide the * + * mouse. Nor will it work correctly if the mouse shape * + * file hasn't been loaded. * + * * + * HISTORY: * + * 03/10/1994 JLB : Created. * + * 06/03/1994 JLB : Made into member function. * + * 12/24/1994 JLB : Added small control parameter. * + *=============================================================================================*/ +bool MouseClass::Override_Mouse_Shape(MouseType mouse, bool wwsmall) +{ + MouseStruct const * control = &MouseControl[mouse]; + static bool startup = false; + int baseshp; + + /* + ** Only certain mouse shapes have a small counterpart. If the requested mouse + ** shape is not one of these, then force the small size override flag to false. + */ + if (control->SmallFrame == -1) { + wwsmall = false; + } + + /* + ** If the mouse shape is going to change, then inform the mouse driver of the + ** change. + */ + if (!startup || (MouseShapes && ((mouse != CurrentMouseShape) || (wwsmall != IsSmall)))) { + startup = true; + + Timer.Set(control->FrameRate); + Frame = 0; + +#ifdef OBSOLETE + Control.Set_Stage(0); + int rate = Options.Normalize_Delay(control->FrameRate); + Control.Set_Rate(MAX(rate, 1)); +#endif + baseshp = (wwsmall) ? control->SmallFrame : control->StartFrame; + + Set_Mouse_Cursor(control->X, control->Y, Extract_Shape(MouseShapes, baseshp + Frame/4)); + CurrentMouseShape = mouse; + IsSmall = wwsmall; + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * MouseClass::AI -- Process player input as it relates to the mouse * + * * + * This routine will is to be called once per game tick and is passed the player keyboard * + * or mouse input code. It processes this code and updates the mouse shape as appropriate. * + * * + * INPUT: input -- The player input code as returned from Keyboard::Get(). * + * * + * x,y -- The mouse coordinate values to use. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + * 12/31/1994 JLB : Uses mouse coordinate parameters. * + * 03/27/1995 JLB : New animation control. * + * 05/28/1995 JLB : Moderates animation so is more steady regardless of speed. * + * 06/30/1995 JLB : Uses constant timer system. * + *=============================================================================================*/ +void MouseClass::AI(KeyNumType &input, int x, int y) +{ +// bool doit = false; + void *mouse_shape_ptr; + MouseStruct const * control = &MouseControl[CurrentMouseShape]; + + if (control->FrameRate && Timer.Time() == 0) { + + Frame++; + Frame %= control->FrameCount; + Timer.Set(control->FrameRate); + +#ifdef OBSOLETE + Control.Set_Stage(Control.Fetch_Stage() % control->FrameCount); +#endif + + if (!IsSmall || control->SmallFrame != -1) { + int baseframe = (IsSmall) ? control->SmallFrame : control->StartFrame; + mouse_shape_ptr = Extract_Shape(MouseShapes, baseframe + Frame); + if (mouse_shape_ptr){ + Set_Mouse_Cursor(control->X, control->Y, mouse_shape_ptr); + } + } + } + + ScrollClass::AI(input, x, y); +} + + +/*********************************************************************************************** + * MouseClass::MouseClass -- Default constructor for the mouse handler class. * + * * + * This is the default constructor for the mouse handling class. It merely sets up the * + * mouse system to its default state. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +MouseClass::MouseClass(void) +{ + CurrentMouseShape = MOUSE_NORMAL; + NormalMouseShape = MOUSE_NORMAL; + Timer.Start(); +} + + +/*********************************************************************************************** + * MouseClass::One_Time -- Performs the one time initialization of the mouse system. * + * * + * Use this routine to load the mouse data file and perform any other necessary one time * + * preparations for the game. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Only call this routine ONCE. * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +void MouseClass::One_Time(void) +{ + ScrollClass::One_Time(); + + /* + ** Override the mouse shape file with the one in the current directory, but only if there + ** is an override file available. + */ + RawFileClass file("MOUSE.SHP"); + if (file.Is_Available()) { + MouseShapes = Load_Alloc_Data(file); + } else { + MouseShapes = MixFileClass::Retrieve("MOUSE.SHP"); + } + + VTable = ((void **)(((char *)this) + sizeof(VectorClass) - 4))[0]; +} + + +/*********************************************************************************************** + * MouseClass::Init_Clear -- Sets the mouse system to a known state * + * * + * This routine will reset the mouse handling system. Typically, this routine is called * + * when preparing for the beginning of a new scenario. * + * * + * INPUT: theater -- The theater that the scenario will take place. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +void MouseClass::Init_Clear(void) +{ + ScrollClass::Init_Clear(); + IsSmall = false; + NormalMouseShape = MOUSE_NORMAL; +} + + +/* +** This array of structures is used to control the mouse animation +** sequences. +*/ +MouseClass::MouseStruct MouseClass::MouseControl[MOUSE_COUNT] = { + {0, 1, 0, 86, 0, 0}, // MOUSE_NORMAL + {1, 1, 0, -1, 15, 0}, // MOUSE_N + {2, 1, 0, -1, 29, 0}, // MOUSE_NE + {3, 1, 0, -1, 29, 12}, // MOUSE_E + {4, 1, 0, -1, 29, 23}, // MOUSE_SE + {5, 1, 0, -1, 15, 23}, // MOUSE_S + {6, 1, 0, -1, 0, 23}, // MOUSE_SW + {7, 1, 0, -1, 0, 13}, // MOUSE_W + {8, 1, 0, -1, 0, 0}, // MOUSE_NW + + {130, 1, 0, -1, 15, 0}, // MOUSE_NO_N + {131, 1, 0, -1, 29, 0}, // MOUSE_NO_NE + {132, 1, 0, -1, 29, 12}, // MOUSE_NO_E + {133, 1, 0, -1, 29, 23}, // MOUSE_NO_SE + {134, 1, 0, -1, 15, 23}, // MOUSE_NO_S + {135, 1, 0, -1, 0, 23}, // MOUSE_NO_SW + {136, 1, 0, -1, 0, 13}, // MOUSE_NO_W + {137, 1, 0, -1, 0, 0}, // MOUSE_NO_NW + + {11, 1, 0, 27, 15, 12}, // MOUSE_NO_MOVE + {10, 1, 0, 26, 15, 12}, // MOUSE_CAN_MOVE + {119, 3, 4, 148, 15, 12}, // MOUSE_ENTER + {53, 9, 4, -1, 15, 12}, // MOUSE_DEPLOY + {12, 6, 4, -1, 15, 12}, // MOUSE_CAN_SELECT + {18, 8, 4, 140, 15, 12}, // MOUSE_CAN_ATTACK + {62, 24, 2, -1, 15, 12}, // MOUSE_SELL_BACK + {154, 24, 2, -1, 15, 12}, // MOUSE_SELL_UNIT + {29, 24, 2, -1, 15, 12}, // MOUSE_REPAIR + {126, 1, 0, -1, 15, 12}, // MOUSE_NO_REPAIR + {125, 1, 0, -1, 15, 12}, // MOUSE_NO_SELL_BACK + {87, 1, 0, 151, 0, 0}, // MOUSE_RADAR_CURSOR + {103, 16, 2, -1, 15, 12}, // MOUSE_ION_CANNON + {96, 7, 4, -1, 15, 12}, // MOUSE_NUCLEAR_BOMB + {88, 8, 2, -1, 15, 12}, // MOUSE_AIR_STRIKE + {122, 3, 4, 127, 15, 12}, // MOUSE_DEMOLITIONS + {153, 1, 0, 152, 15, 12}, // MOUSE_AREA_GUARD +}; diff --git a/MOUSE.H b/MOUSE.H new file mode 100644 index 0000000..aa7f3f1 --- /dev/null +++ b/MOUSE.H @@ -0,0 +1,130 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\mouse.h_v 2.16 16 Oct 1995 16:45:06 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MOUSE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/15/94 * + * * + * Last Update : December 15, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef MOUSE_H +#define MOUSE_H + +#include "stage.h" +#include "scroll.h" + +class MouseClass: public ScrollClass +{ + public: + MouseClass(void); + + /* + ** Initialization + */ + virtual void One_Time(void); // One-time inits + virtual void Init_Clear(void); // Clears all to known state + + virtual void AI(KeyNumType &input, int x, int y); + virtual bool Override_Mouse_Shape(MouseType mouse, bool wwsmall=false); + virtual void Revert_Mouse_Shape(void); + virtual MouseType Get_Mouse_Shape(void) const {return NormalMouseShape;}; + virtual void Mouse_Small(bool wwsmall); + + /* + ** File I/O. + */ + virtual bool Load(FileClass & file); + virtual bool Save(FileClass & file); + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + virtual void Set_Default_Mouse(MouseType mouse, bool wwsmall = false); + + /* + ** This allows the tactical map input gadget access to change the + ** mouse shapes. + */ + friend class TacticalClass; + + private: + + /* + ** This type is used to control the frames and rates of the mouse + ** pointer. Some mouse pointers are actually looping animations. + */ + typedef struct MouseStruct + { + int StartFrame; // Starting frame number. + int FrameCount; // Number of animation frames. + int FrameRate; // Frame delay between changing frames. + int SmallFrame; // Start frame number for small version (if any). + int X,Y; // Hotspot X and Y offset. + } MouseStruct; + + /* + ** The control frames and rates for the various mouse pointers are stored + ** in this static array. + */ + static MouseStruct MouseControl[MOUSE_COUNT]; + + /* + ** If the small representation of the mouse is active, then this flag is true. + */ + unsigned IsSmall:1; + + /* + ** This points to the loaded mouse shapes. + */ + static void const * MouseShapes; + + /* + ** The mouse shape is controlled by these variables. These + ** hold the current mouse shape (so resetting won't be needlessly performed) and + ** the normal default mouse shape (when arrow shapes are needed). + */ + MouseType CurrentMouseShape; + MouseType NormalMouseShape; + + /* + ** For animating mouse shapes, this controls the frame and animation rate. + */ + static CountDownTimerClass Timer; + int Frame; +// StageClass Control; + + /* + ** This contains the value of the Virtual Function Table Pointer + */ + static void * VTable; +}; + + +#endif diff --git a/MPLAYER.CPP b/MPLAYER.CPP new file mode 100644 index 0000000..b05c557 --- /dev/null +++ b/MPLAYER.CPP @@ -0,0 +1,1408 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\mplayer.cpv 1.9 16 Oct 1995 16:51:08 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MPLAYER.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : April 14, 1995 * + * * + * Last Update : July 5, 1995 [BRR] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Select_MPlayer_Game -- prompts user for NULL-Modem, Modem, or Network game * + * Read_MultiPlayer_Settings -- reads multi-player settings from conquer.ini * + * Write_MultiPlayer_Settings -- writes multi-player settings to conquer.ini * + * Read_Scenario_Descriptions -- reads multi-player scenario #'s # descriptions * + * Free_Scenario_Descriptions -- frees memory for the scenario descriptions * + * Computer_Message -- "sends" a message from the computer * + * Garble_Message -- "garbles" a message * + * Surrender_Dialog -- Prompts user for surrendering * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "tcpip.h" + +static void Garble_Message(char *buf); + +int Choose_Internet_Game(void); +int Get_Internet_Host_Or_Join(void); +int Get_IP_Address(void); +void Show_Internet_Connection_Progress(void); + +/*********************************************************************************************** + * Select_MPlayer_Game -- prompts user for NULL-Modem, Modem, or Network game * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * GAME_NORMAL, GAME_MODEM, etc. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +GameType Select_MPlayer_Game (void) +{ + + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + bool ipx_avail = FALSE; + int number_of_buttons; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 190*factor; + int d_dialog_h = 26*4*factor; + int d_dialog_x = ((320*factor - d_dialog_w) / 2); +// d_dialog_y = ((200 - d_dialog_h) / 2), + int d_dialog_y = ((136*factor - d_dialog_h) / 2); + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); + + int d_txt6_h = 11 * factor; + int d_margin = 7 *factor; + + int d_modemserial_w = 80*factor; + int d_modemserial_h = 9*factor; + int d_modemserial_x = d_dialog_cx - d_modemserial_w / 2; + int d_modemserial_y = d_dialog_y + d_margin + d_txt6_h + d_margin; +#if (0) + int d_internet_w = 80*factor; + int d_internet_h = 9*factor; + int d_internet_x = d_dialog_cx - d_internet_w / 2; + int d_internet_y = d_modemserial_y + d_modemserial_h + 2*factor; +#endif //(0) + int d_ipx_w = 80*factor; + int d_ipx_h = 9*factor; + int d_ipx_x = d_dialog_cx - d_ipx_w / 2; + int d_ipx_y = d_modemserial_y + d_modemserial_h + 2*factor; +// int d_ipx_y = d_internet_y + d_internet_h + 2*factor; + + int d_cancel_w = 60*factor; + int d_cancel_h = 9*factor; + int d_cancel_x = d_dialog_cx - d_cancel_w / 2; + int d_cancel_y = d_ipx_y + d_ipx_h + d_margin; + + CountDownTimerClass delay; + + /*........................................................................ + Button enumerations: + ........................................................................*/ + enum { + BUTTON_MODEMSERIAL = 100, +#if (0) + BUTTON_INTERNET, +#endif //(0) + BUTTON_IPX, + BUTTON_CANCEL, + + NUM_OF_BUTTONS = 3, + }; + number_of_buttons = NUM_OF_BUTTONS; + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, // includes map interior & coord values + REDRAW_BACKGROUND, // includes box, map bord, key, coord labels, btns + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + /*........................................................................ + Dialog variables: + ........................................................................*/ + KeyNumType input; // input from user + bool process; // loop while true + RedrawType display; // true = re-draw everything + GameType retval; // return value + int selection; + bool pressed; + int curbutton; + TextButtonClass *buttons[NUM_OF_BUTTONS]; + + /*........................................................................ + Buttons + ........................................................................*/ + ControlClass *commands = NULL; // the button list + + // + // If neither IPX or winsock are active then do only the modem serial dialog + // + if (Ipx.Is_IPX){ + ipx_avail = TRUE; + } + + + TextButtonClass modemserialbtn (BUTTON_MODEMSERIAL, TXT_MODEM_SERIAL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_modemserial_x, d_modemserial_y, d_modemserial_w, d_modemserial_h); +#if (0) + TextButtonClass internetbtn (BUTTON_INTERNET, TXT_INTERNET, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_internet_x, d_internet_y, d_internet_w, d_internet_h); +#endif //(0) + TextButtonClass ipxbtn (BUTTON_IPX, TXT_NETWORK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_ipx_x, d_ipx_y, d_ipx_w, d_ipx_h); + + TextButtonClass cancelbtn (BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + + /* + ............................ Create the list ............................. + */ + commands = &modemserialbtn; +#if (0) + internetbtn.Add_Tail(*commands); +#endif //(0) + if (ipx_avail){ + ipxbtn.Add_Tail(*commands); + } + cancelbtn.Add_Tail(*commands); + + /* + ......................... Fill array of button ptrs ...................... + */ + curbutton = 0; + buttons[0] = &modemserialbtn; +#if (0) + buttons[1] = &internetbtn; +#endif //(0) + if (ipx_avail){ + buttons[1] = &ipxbtn; + buttons[2] = &cancelbtn; + }else{ + buttons[1] = &cancelbtn; + number_of_buttons--; + } + + buttons[curbutton]->Turn_On(); + + Keyboard::Clear(); + + Fancy_Text_Print(TXT_NONE, 0, 0, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + display = REDRAW_ALL; + process = true; + pressed = false; + while (process) { + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + /* + ..................... Refresh the backdrop ...................... + */ + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + /* + ..................... Draw the background ....................... + */ + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + Draw_Caption (TXT_SELECT_MPLAYER_GAME, d_dialog_x, d_dialog_y, d_dialog_w); + } + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Flag_List_To_Redraw(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (BUTTON_MODEMSERIAL | KN_BUTTON): + selection = BUTTON_MODEMSERIAL; + pressed = true; + break; + +#if (0) + case (BUTTON_INTERNET | KN_BUTTON): + selection = BUTTON_INTERNET; + pressed = true; + break; +#endif //(0) + + case (BUTTON_IPX | KN_BUTTON): + selection = BUTTON_IPX; + pressed = true; + break; + + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + selection = BUTTON_CANCEL; + pressed = true; + break; + + case KN_UP: + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + curbutton--; + if (curbutton < 0) + curbutton = (number_of_buttons - 1); + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + break; + + case KN_DOWN: + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + curbutton++; + if (curbutton > (number_of_buttons - 1) ) + curbutton = 0; + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + break; + + case KN_RETURN: + selection = curbutton + BUTTON_MODEMSERIAL; + if (!ipx_avail) selection--; + pressed = true; + break; + + default: + break; + } + + if (pressed) { + // + // to make sure the selection is correct in case they used the mouse + // + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + curbutton = selection - BUTTON_MODEMSERIAL; + buttons[curbutton]->Turn_On(); +// buttons[curbutton]->Flag_To_Redraw(); + buttons[curbutton]->IsPressed = true; + buttons[curbutton]->Draw_Me(true); + + switch (selection) { + case (BUTTON_MODEMSERIAL): + + // + // Pop up the modem/serial/com port dialog + // + retval = Select_Serial_Dialog(); + + if (retval != GAME_NORMAL) { + process = false; + } else { + buttons[curbutton]->IsPressed = false; + display = REDRAW_ALL; + } + break; + +#if (0) + case (BUTTON_INTERNET): +//#define ONLY_FOR_E3 +#ifdef ONLY_FOR_E3 + Call_Back(); + Show_Internet_Connection_Progress(); //changed to do nothing + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + Call_Back(); + CCMessageBox().Process("Error! - Unable to ping KANE.WESTWOOD.COM"); + + buttons[curbutton]->IsPressed = false; + display = REDRAW_ALL; + + +#endif //ONLY_FOR_E3 + + +#ifdef FORCE_WINSOCK + if (Winsock.Init()){ + Read_MultiPlayer_Settings (); + int result = Choose_Internet_Game(); + + if (!result){ + Winsock.Close(); + buttons[curbutton]->IsPressed = false; + display = REDRAW_ALL; + break; + } + + result = Get_Internet_Host_Or_Join(); + if (result == 1){ + Winsock.Close(); + buttons[curbutton]->IsPressed = false; + display = REDRAW_ALL; + break; + } + Server = !result; + + if (Server){ +#if (0) + ModemGameToPlay = INTERNET_HOST; + Winsock.Start_Server(); +#else + result = Get_IP_Address(); + if (!result){ + Winsock.Close(); + buttons[curbutton]->IsPressed = false; + display = REDRAW_ALL; + break; + } + Winsock.Set_Host_Address(PlanetWestwoodIPAddress); + Winsock.Start_Server(); +#endif + + }else{ + ModemGameToPlay = INTERNET_JOIN; + result = Get_IP_Address(); + if (!result){ + Winsock.Close(); + buttons[curbutton]->IsPressed = false; + display = REDRAW_ALL; + break; + } + Winsock.Set_Host_Address(PlanetWestwoodIPAddress); + Winsock.Start_Client(); + } + + //CountDownTimerClass connect_timeout; + //connect_timeout.Set(15*60); + + ////Show_Internet_Connection_Progress(); + + if (!Winsock.Get_Connected()){ + Winsock.Close(); + return(GAME_NORMAL); + } + + SerialSettingsType *settings; + settings = &SerialDefaults; + Init_Null_Modem(settings); + if (Server){ + if (Com_Scenario_Dialog()){ + return (GAME_INTERNET); + }else{ + Winsock.Close(); + return (GAME_NORMAL); + } + }else{ + if (Com_Show_Scenario_Dialog()){ + return (GAME_INTERNET); + }else{ + Winsock.Close(); + return (GAME_NORMAL); + } + } + } +#endif //FORCE_WINSOCK + break; + +#endif //(0) + + case (BUTTON_IPX): + retval = GAME_IPX; + process = false; + break; + + case (BUTTON_CANCEL): + retval = GAME_NORMAL; + process = false; + break; + } + + pressed = false; + } + } + return(retval); +} + + +/*********************************************************************************************** + * Read_MultiPlayer_Settings -- reads multi-player settings from conquer.ini * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +void Read_MultiPlayer_Settings (void) +{ + char *buffer; // INI staging buffer pointer. + char *tbuffer; // Accumulation buffer of trigger IDs. + int len; // Length of data in buffer. + char *tokenptr; // ptr to token + PhoneEntryClass *phone; // a phone book entry + char *entry; // a phone book entry + char buf[128]; // buffer for parsing INI entry + int i; + CELL cell; + + /*------------------------------------------------------------------------ + Fetch working pointer to the INI staging buffer. Make sure that the buffer + is cleared out before proceeding. (Don't use the HidPage for this, since + the HidPage may be needed for various uncompressions during the INI + parsing.) + ------------------------------------------------------------------------*/ + buffer = (char *)_ShapeBuffer; + memset(buffer, '\0', _ShapeBufferSize); + + /*------------------------------------------------------------------------ + Clear the initstring entries + ------------------------------------------------------------------------*/ + for (i = 0; i < InitStrings.Count(); i++) { + delete[] InitStrings[i]; + } + InitStrings.Clear(); + + /*------------------------------------------------------------------------ + Clear the dialing entries + ------------------------------------------------------------------------*/ + for (i = 0; i < PhoneBook.Count(); i++) { + delete PhoneBook[i]; + } + PhoneBook.Clear(); + + /*------------------------------------------------------------------------ + Create filename and read the file. + ------------------------------------------------------------------------*/ + CCFileClass file ("CONQUER.INI"); + if (!file.Is_Available()) { + return; + } else { + file.Read(buffer, _ShapeBufferSize-1); + } + file.Close(); + + if (!Special.IsFromWChat){ + /*------------------------------------------------------------------------ + Get the player's last-used Handle + ------------------------------------------------------------------------*/ + WWGetPrivateProfileString("MultiPlayer", "Handle", "Noname", MPlayerName, + sizeof(MPlayerName), buffer); + + /*------------------------------------------------------------------------ + Get the player's last-used Color + ------------------------------------------------------------------------*/ + MPlayerPrefColor = WWGetPrivateProfileInt("MultiPlayer", "Color", 0, buffer); + MPlayerHouse = (HousesType)WWGetPrivateProfileInt("MultiPlayer", "Side", + HOUSE_GOOD, buffer); + CurPhoneIdx = WWGetPrivateProfileInt("MultiPlayer", "PhoneIndex", -1, buffer); + }else{ + CurPhoneIdx = -1; + } + +#if 1 + TrapCheckHeap = WWGetPrivateProfileInt( "MultiPlayer", "CheckHeap", 0, buffer); +#endif + + /*------------------------------------------------------------------------ + Read in default serial settings + ------------------------------------------------------------------------*/ + WWGetPrivateProfileString ("SerialDefaults", "ModemName", "NoName", SerialDefaults.ModemName, MODEM_NAME_MAX, buffer); + if (!strcmp ( SerialDefaults.ModemName, "NoName")) { + SerialDefaults.ModemName[0] = 0; + } + WWGetPrivateProfileString ("SerialDefaults", "Port", "0", buf, 5, buffer); + sscanf (buf, "%x", &SerialDefaults.Port); + SerialDefaults.IRQ = WWGetPrivateProfileInt("SerialDefaults", "IRQ", -1, buffer); + SerialDefaults.Baud = WWGetPrivateProfileInt("SerialDefaults", "Baud", -1, buffer); + SerialDefaults.Init = WWGetPrivateProfileInt("SerialDefaults", "Init", 0, buffer); + SerialDefaults.Compression = WWGetPrivateProfileInt ("SerialDefaults", "Compression", 0, buffer); + SerialDefaults.ErrorCorrection = WWGetPrivateProfileInt ("SerialDefaults", "ErrorCorrection", 0, buffer); + SerialDefaults.HardwareFlowControl = WWGetPrivateProfileInt ("SerialDefaults", "HardwareFlowControl", 1, buffer); + WWGetPrivateProfileString ("SerialDefaults", "DialMethod", "T", + buf, 2, buffer); + + + // find dial method + + for (i = 0; i < DIAL_METHODS; i++) { + if ( !strcmpi( buf, DialMethodCheck[ i ]) ) { + SerialDefaults.DialMethod = (DialMethodType)i; + break; + } + } + + // if method not found set to touch tone + + if (i == DIAL_METHODS) { + SerialDefaults.DialMethod = DIAL_TOUCH_TONE; + } + + SerialDefaults.InitStringIndex = + WWGetPrivateProfileInt("SerialDefaults", + "InitStringIndex", 0, buffer); + + SerialDefaults.CallWaitStringIndex = + WWGetPrivateProfileInt("SerialDefaults", + "CallWaitStringIndex", CALL_WAIT_CUSTOM, + buffer); + + WWGetPrivateProfileString ("SerialDefaults", "CallWaitString", "", + SerialDefaults.CallWaitString, CWAITSTRBUF_MAX, buffer); + + if (SerialDefaults.IRQ == 0 || + SerialDefaults.Baud == 0) { + + SerialDefaults.Port = 0; + SerialDefaults.IRQ = -1; + SerialDefaults.Baud = -1; + } + + /*------------------------------------------------------------------------ + Set 'tbuffer' to point past the actual INI data + ------------------------------------------------------------------------*/ + len = strlen(buffer) + 2; + tbuffer = buffer + len; + + /*------------------------------------------------------------------------ + Read all Base-Scenario names into 'tbuffer' + ------------------------------------------------------------------------*/ + WWGetPrivateProfileString("InitStrings", NULL, NULL, tbuffer, + ShapeBufferSize-len, buffer); + + /*------------------------------------------------------------------------ + Read in & store each entry + ------------------------------------------------------------------------*/ + while (*tbuffer != '\0') { + entry = new char[ INITSTRBUF_MAX ]; + + entry[0] = 0; + + WWGetPrivateProfileString("InitStrings", tbuffer, NULL, entry, + INITSTRBUF_MAX, buffer); + + strupr( entry ); + + InitStrings.Add( entry ); + + tbuffer += strlen(tbuffer) + 1; + } + + // if no entries then have at least one + + if ( tbuffer == (buffer + len) ) { + entry = new char[ INITSTRBUF_MAX ]; + strcpy( entry, "ATZ" ); + InitStrings.Add( entry ); + SerialDefaults.InitStringIndex = 0; + } else { + len = strlen(buffer) + 2; + } + + /*------------------------------------------------------------------------ + Repeat the process for the phonebook + ------------------------------------------------------------------------*/ + tbuffer = buffer + len; + + /*------------------------------------------------------------------------ + Read in all phone book listings. + Format: Name=PhoneNum,Port,IRQ,Baud,InitString + ------------------------------------------------------------------------*/ + + /*........................................................................ + Read the entry names in + ........................................................................*/ + WWGetPrivateProfileString("PhoneBook", NULL, NULL, tbuffer, + ShapeBufferSize-len, buffer); + + while (*tbuffer != '\0') { + /*..................................................................... + Create a new phone book entry + .....................................................................*/ + phone = new PhoneEntryClass(); + + /*..................................................................... + Read the entire entry in + .....................................................................*/ + WWGetPrivateProfileString("PhoneBook", tbuffer, NULL, buf, 128, buffer); + + /*..................................................................... + Extract name, phone # & serial port settings + .....................................................................*/ + tokenptr = strtok( buf, "|" ); + if (tokenptr) { + strcpy( phone->Name, tokenptr ); + strupr( phone->Name ); + } else { + phone->Name[0] = 0; + } + + tokenptr = strtok( NULL, "|" ); + if (tokenptr) { + strcpy( phone->Number, tokenptr ); + strupr( phone->Number ); + } else { + phone->Number[0] = 0; + } + + tokenptr = strtok( NULL, "|" ); + if (tokenptr) { + sscanf( tokenptr, "%x", &phone->Settings.Port ); + } else { + phone->Settings.Port = 0; + } + + tokenptr = strtok( NULL, "|" ); + if (tokenptr) { + phone->Settings.IRQ = atoi( tokenptr ); + } else { + phone->Settings.IRQ = -1; + } + + tokenptr = strtok( NULL, "|" ); + if (tokenptr) { + phone->Settings.Baud = atoi( tokenptr ); + } else { + phone->Settings.Baud = -1; + } + + tokenptr = strtok( NULL, "|" ); + if (tokenptr) { + phone->Settings.Compression = atoi( tokenptr ); + } else { + phone->Settings.Compression = 0; + } + + tokenptr = strtok( NULL, "|" ); + if (tokenptr) { + phone->Settings.ErrorCorrection = atoi( tokenptr ); + } else { + phone->Settings.ErrorCorrection = 0; + } + + tokenptr = strtok( NULL, "|" ); + if (tokenptr) { + phone->Settings.HardwareFlowControl = atoi( tokenptr ); + } else { + phone->Settings.HardwareFlowControl = 1; + } + + + tokenptr = strtok( NULL, "|" ); + if (tokenptr) { + strcpy( buf, tokenptr ); + + // find dial method + + for (i = 0; i < DIAL_METHODS; i++) { + if ( !strcmpi( buf, DialMethodCheck[ i ]) ) { + phone->Settings.DialMethod = (DialMethodType)i; + break; + } + } + + // if method not found set to touch tone + + if (i == DIAL_METHODS) { + phone->Settings.DialMethod = DIAL_TOUCH_TONE; + } + } else { + phone->Settings.DialMethod = DIAL_TOUCH_TONE; + } + + tokenptr = strtok( NULL, "|" ); + if (tokenptr) { + phone->Settings.InitStringIndex = atoi( tokenptr ); + } else { + phone->Settings.InitStringIndex = 0; + } + + tokenptr = strtok( NULL, "|" ); + if (tokenptr) { + phone->Settings.CallWaitStringIndex = atoi( tokenptr ); + } else { + phone->Settings.CallWaitStringIndex = CALL_WAIT_CUSTOM; + } + + tokenptr = strtok( NULL, "|" ); + if (tokenptr) { + strcpy (phone->Settings.CallWaitString, tokenptr); + } else { + phone->Settings.CallWaitString[0] = 0; + } + + /*..................................................................... + Add it to our list + .....................................................................*/ + PhoneBook.Add(phone); + + tbuffer += strlen(tbuffer) + 1; + } + + /*------------------------------------------------------------------------ + Read special recording playback values, to help find sync bugs + ------------------------------------------------------------------------*/ + if (PlaybackGame) { + TrapFrame = WWGetPrivateProfileInt ("SyncBug","Frame",0x7fffffff, buffer); + + TrapObjType = (RTTIType)WWGetPrivateProfileInt ("SyncBug","Type",RTTI_NONE, buffer); + WWGetPrivateProfileString ("SyncBug","Type","NONE",buf,80,buffer); + if (!stricmp(buf,"AIRCRAFT")) + TrapObjType = RTTI_AIRCRAFT; + else if (!stricmp(buf,"ANIM")) + TrapObjType = RTTI_ANIM; + else if (!stricmp(buf,"BUILDING")) + TrapObjType = RTTI_BUILDING; + else if (!stricmp(buf,"BULLET")) + TrapObjType = RTTI_BULLET; + else if (!stricmp(buf,"INFANTRY")) + TrapObjType = RTTI_INFANTRY; + else if (!stricmp(buf,"UNIT")) + TrapObjType = RTTI_UNIT; + else { + TrapObjType = RTTI_NONE; + } + + WWGetPrivateProfileString ("SyncBug","Coord","0",buf,80,buffer); + sscanf(buf,"%x",&TrapCoord); + + WWGetPrivateProfileString ("SyncBug","this","0",buf,80,buffer); + sscanf(buf,"%x",&TrapThis); + + WWGetPrivateProfileString ("SyncBug","Cell","0",buf,80,buffer); + cell = atoi(buf); + if (cell) { + TrapCell = &(Map[cell]); + } + } +} + + +/*********************************************************************************************** + * Write_MultiPlayer_Settings -- writes multi-player settings to conquer.ini * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +void Write_MultiPlayer_Settings (void) +{ + char * buffer; // INI staging buffer pointer. + CCFileClass file; + int i; + char entrytext[4]; + char buf[128]; // buffer for parsing INI entry + + /*------------------------------------------------------------------------ + Get a working pointer to the INI staging buffer. Make sure that the buffer + starts cleared out of any data. + ------------------------------------------------------------------------*/ + buffer = (char *)_ShapeBuffer; + memset(buffer, '\0', _ShapeBufferSize); + + file.Set_Name("CONQUER.INI"); + if (file.Is_Available()) { + file.Open(READ); + file.Read(buffer, _ShapeBufferSize-1); + file.Close(); + } + + /*------------------------------------------------------------------------ + Save the player's last-used Handle & Color + ------------------------------------------------------------------------*/ + WWWritePrivateProfileInt("MultiPlayer", "PhoneIndex", CurPhoneIdx, buffer); + WWWritePrivateProfileInt ("MultiPlayer", "Color", MPlayerPrefColor, buffer); + WWWritePrivateProfileInt ("MultiPlayer", "Side", MPlayerHouse, buffer); + WWWritePrivateProfileString("MultiPlayer", "Handle", MPlayerName, buffer); + + /*------------------------------------------------------------------------ + Clear all existing SerialDefault entries. + ------------------------------------------------------------------------*/ + WWWritePrivateProfileString ("SerialDefaults", NULL, NULL, buffer); + + /*------------------------------------------------------------------------ + Save default serial settings in opposite order you want to see them + ------------------------------------------------------------------------*/ + WWWritePrivateProfileString("SerialDefaults", "CallWaitString", + SerialDefaults.CallWaitString, buffer); + WWWritePrivateProfileInt ("SerialDefaults", "CallWaitStringIndex", SerialDefaults.CallWaitStringIndex, buffer); + WWWritePrivateProfileInt ("SerialDefaults", "InitStringIndex", SerialDefaults.InitStringIndex, buffer); + WWWritePrivateProfileInt ("SerialDefaults", "Init", SerialDefaults.Init, buffer); + WWWritePrivateProfileString("SerialDefaults", "DialMethod", + DialMethodCheck[ SerialDefaults.DialMethod ], buffer); + WWWritePrivateProfileInt ("SerialDefaults", "Baud", SerialDefaults.Baud, buffer); + WWWritePrivateProfileInt ("SerialDefaults", "IRQ", SerialDefaults.IRQ, buffer); + sprintf(buf, "%x", SerialDefaults.Port); + WWWritePrivateProfileString("SerialDefaults", "Port", buf, buffer); + WWWritePrivateProfileString("SerialDefaults", "ModemName", SerialDefaults.ModemName, buffer); + WWWritePrivateProfileInt ("SerialDefaults", "Compression", SerialDefaults.Compression , buffer); + WWWritePrivateProfileInt ("SerialDefaults", "ErrorCorrection", SerialDefaults.ErrorCorrection, buffer); + WWWritePrivateProfileInt ("SerialDefaults", "HardwareFlowControl", SerialDefaults.HardwareFlowControl, buffer); + + /*------------------------------------------------------------------------ + Clear all existing InitString entries. + ------------------------------------------------------------------------*/ + WWWritePrivateProfileString ("InitStrings", NULL, NULL, buffer); + + /*------------------------------------------------------------------------ + Save all InitString entries. In descending order so they come out in + ascending order. + ------------------------------------------------------------------------*/ + for (i = (InitStrings.Count() - 1); i >= 0; i--) { + sprintf( buf, "%03d", i); + WWWritePrivateProfileString ("InitStrings", buf, InitStrings[i], buffer); + } + + /*------------------------------------------------------------------------ + Clear all existing Phone Book entries. + ------------------------------------------------------------------------*/ + WWWritePrivateProfileString ("PhoneBook", NULL, NULL, buffer); + + /*------------------------------------------------------------------------ + Save all Phone Book entries. + Format: Entry=Name,PhoneNum,Port,IRQ,Baud,InitString + ------------------------------------------------------------------------*/ + for (i = (PhoneBook.Count() - 1); i >= 0; i--) { + sprintf(buf,"%s|%s|%x|%d|%d|%d|%d|%d|%s|%d|%d|%s", + PhoneBook[i]->Name, + PhoneBook[i]->Number, + PhoneBook[i]->Settings.Port, + PhoneBook[i]->Settings.IRQ, + PhoneBook[i]->Settings.Baud, + PhoneBook[i]->Settings.Compression, + PhoneBook[i]->Settings.ErrorCorrection, + PhoneBook[i]->Settings.HardwareFlowControl, + DialMethodCheck[ PhoneBook[i]->Settings.DialMethod ], + PhoneBook[i]->Settings.InitStringIndex, + PhoneBook[i]->Settings.CallWaitStringIndex, + PhoneBook[i]->Settings.CallWaitString); + sprintf( entrytext, "%03d", i ); + WWWritePrivateProfileString ("PhoneBook", entrytext, buf, buffer); + } + + /*------------------------------------------------------------------------ + Write the INI data out to a file. + ------------------------------------------------------------------------*/ + file.Open(WRITE); + file.Write(buffer,strlen(buffer)); + file.Close(); +} + + +/*********************************************************************************************** + * Read_Scenario_Descriptions -- reads multi-player scenario #'s # descriptions * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +void Read_Scenario_Descriptions (void) +{ + char *buffer; // INI staging buffer pointer. + CCFileClass file; + int i; + char fname[20]; + + /*------------------------------------------------------------------------ + Clear the scenario description lists + ------------------------------------------------------------------------*/ + MPlayerScenarios.Clear(); + MPlayerFilenum.Clear(); + + /*------------------------------------------------------------------------ + Loop through all possible scenario numbers; if a file is available, add + its number to the FileNum list. + ------------------------------------------------------------------------*/ + for (i = 0; i < 100; i++) { + Set_Scenario_Name(ScenarioName, i, SCEN_PLAYER_MPLAYER, + SCEN_DIR_EAST, SCEN_VAR_A); + sprintf(fname,"%s.INI",ScenarioName); + file.Set_Name (fname); + + if (file.Is_Available()) { + MPlayerFilenum.Add(i); + } + } + + /*------------------------------------------------------------------------ + Now, for every file in the FileNum list, read in the INI file, and extract + its description. + ------------------------------------------------------------------------*/ + for (i = 0; i < MPlayerFilenum.Count(); i++) { + /*..................................................................... + Fetch working pointer to the INI staging buffer. Make sure that the + buffer is cleared out before proceeding. + .....................................................................*/ + buffer = (char *)_ShapeBuffer; + memset(buffer, '\0', _ShapeBufferSize); + + /*..................................................................... + Create filename and read the file. + .....................................................................*/ + Set_Scenario_Name(ScenarioName, MPlayerFilenum[i], SCEN_PLAYER_MPLAYER, + SCEN_DIR_EAST, SCEN_VAR_A); + sprintf(fname,"%s.INI",ScenarioName); + file.Set_Name (fname); + file.Read(buffer, _ShapeBufferSize-1); + file.Close(); + + /*..................................................................... + Extract description & add it to the list. + .....................................................................*/ + WWGetPrivateProfileString("Basic", "Name", "Nulls-Ville", + MPlayerDescriptions[i], 40, buffer); + MPlayerScenarios.Add(MPlayerDescriptions[i]); + } +} + + +/*********************************************************************************************** + * Free_Scenario_Descriptions -- frees memory for the scenario descriptions * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/05/1995 BRR : Created. * + *=============================================================================================*/ +void Free_Scenario_Descriptions(void) +{ + int i; + + /*------------------------------------------------------------------------ + Clear the scenario descriptions & filenames + ------------------------------------------------------------------------*/ + MPlayerScenarios.Clear(); + MPlayerFilenum.Clear(); + + /*------------------------------------------------------------------------ + Clear the initstring entries + ------------------------------------------------------------------------*/ + for (i = 0; i < InitStrings.Count(); i++) { + delete InitStrings[i]; + } + InitStrings.Clear(); + + /*------------------------------------------------------------------------ + Clear the dialing entries + ------------------------------------------------------------------------*/ + for (i = 0; i < PhoneBook.Count(); i++) { + delete PhoneBook[i]; + } + PhoneBook.Clear(); +} + + +/*************************************************************************** + * Computer_Message -- "sends" a message from the computer * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/06/1995 BRR : Created. * + *=========================================================================*/ +void Computer_Message(void) +{ + int color; + char txt[160]; + HousesType house; + HouseClass *ptr; + + /*------------------------------------------------------------------------ + Find the computer house that the message will be from + ------------------------------------------------------------------------*/ + for (house = HOUSE_MULTI1; house < (HOUSE_MULTI1 + MPlayerMax); house++) { + ptr = HouseClass::As_Pointer(house); + + if (!ptr || ptr->IsHuman || ptr->IsDefeated) { + continue; + } + + /*..................................................................... + Decode this house's color + .....................................................................*/ + color = MPlayerTColors[ptr->RemapColor]; + + /*..................................................................... + We now have a 1/4 chance of echoing one of the human players' messages + back. + .....................................................................*/ + if (IRandom(0,3) == 2) { + /*.................................................................. + Now we have a 1/3 chance of garbling the human message. + ..................................................................*/ + if (IRandom(0,2) == 1) { + Garble_Message(LastMessage); + } + + /*.................................................................. + Only add the message if there is one to add. + ..................................................................*/ + if (strlen(LastMessage)) { + sprintf(txt,"%s %s",Text_String(TXT_FROM_COMPUTER),LastMessage); + Messages.Add_Message(txt, color, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, 0, 0); + } + } else { + sprintf(txt,"%s %s",Text_String(TXT_FROM_COMPUTER), + Text_String(TXT_COMP_MSG1 + IRandom(0,12))); + Messages.Add_Message(txt, color, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, 0, 0); + } + + return; + } +} + + +/*************************************************************************** + * Garble_Message -- "garbles" a message * + * * + * INPUT: * + * buf buffer to garble; stores output message * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/06/1995 BRR : Created. * + *=========================================================================*/ +static void Garble_Message(char *buf) +{ + char txt[80]; + char punct[20]; // for punctuation + char *p; // working ptr + int numwords; // # words in the phrase + char *words[40]; // ptrs to various words in the phrase + int i,j; + + /*------------------------------------------------------------------------ + Pull off any trailing punctuation + ------------------------------------------------------------------------*/ + p = buf + strlen(buf) - 1; + while (1) { + if (p < buf) + break; + if (p[0]=='!' || p[0]=='.' || p[0]=='?') { + p--; + } else { + p++; + break; + } + if (strlen(p) >= (sizeof(punct) - 1) ) { + break; + } + } + strcpy (punct, p); + p[0] = 0; + + for (i = 0; i < 40; i++) { + words[i] = NULL; + } + + /*------------------------------------------------------------------------ + Copy the original buffer + ------------------------------------------------------------------------*/ + strcpy(txt,buf); + + /*------------------------------------------------------------------------ + Split it up into words + ------------------------------------------------------------------------*/ + p = strtok (txt, " "); + numwords = 0; + while (p) { + words[numwords] = p; + numwords++; + p = strtok (NULL, " "); + } + + /*------------------------------------------------------------------------ + Now randomly put the words back. Don't use the real random-number + generator, since different machines will have different LastMessage's, + and will go out of sync. + ------------------------------------------------------------------------*/ + buf[0] = 0; + for (i = 0; i < numwords; i++) { + j = Sim_IRandom(0,numwords); + if (words[j] == NULL) { // this word has been used already + i--; + continue; + } + strcat(buf,words[j]); + words[j] = NULL; + if (i < numwords-1) + strcat(buf," "); + } + strcat(buf,punct); +} + + +/*************************************************************************** + * Surrender_Dialog -- Prompts user for surrendering * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = user cancels, 1 = user wants to surrender. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/05/1995 BRR : Created. * + *=========================================================================*/ +int Surrender_Dialog(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 170*factor; // dialog width + int d_dialog_h = 53*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // centered x-coord + int d_dialog_y = ((200*factor - d_dialog_h) / 2); // centered y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // coord of x-center + + int d_txt6_h = 6*factor+1; // ht of 6-pt text + int d_margin = 5*factor; // margin width/height + int d_topmargin = 20*factor; // top margin + + int d_ok_w = 45*factor; // ok width + int d_ok_h = 9*factor; // ok height + int d_ok_x = d_dialog_cx - d_ok_w - 5*factor; // ok x + int d_ok_y = d_dialog_y + d_dialog_h - d_ok_h - d_margin; // ok y + + int d_cancel_w = 45*factor; // cancel width + int d_cancel_h = 9*factor; // cancel height + int d_cancel_x = d_dialog_cx + 5*factor; // cancel x + int d_cancel_y = d_dialog_y + d_dialog_h - d_cancel_h - d_margin; // cancel y + + + /*........................................................................ + Button enumerations + ........................................................................*/ + enum { + BUTTON_OK = 100, + BUTTON_CANCEL, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display; // requested redraw level + bool process; // loop while true + KeyNumType input; + int retcode; + + /*........................................................................ + Buttons + ........................................................................*/ + ControlClass *commands = NULL; // the button list + + TextButtonClass okbtn (BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_ok_x, d_ok_y, d_ok_w, d_ok_h); + + TextButtonClass cancelbtn (BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + + /* + ......................... Create the button list ......................... + */ + commands = &okbtn; + cancelbtn.Add_Tail(*commands); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + display = REDRAW_ALL; + process = true; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + if (Main_Loop()) { + retcode = 0; + process = false; + } + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + + /* + ...................... Display the dialog box ...................... + */ + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + Draw_Caption(TXT_NONE, d_dialog_x, d_dialog_y, d_dialog_w); + + /* + ....................... Draw the captions ....................... + */ + Fancy_Text_Print(Text_String(TXT_SURRENDER), + d_dialog_cx, d_dialog_y + d_topmargin, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + + /* + ........................ Redraw the buttons ........................ + */ + if (display >= REDRAW_BUTTONS) { + commands->Flag_List_To_Redraw(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (KN_RETURN): + case (BUTTON_OK | KN_BUTTON): + retcode = 1; + process = false; + break; + + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + retcode = 0; + process = false; + break; + + default: + break; + } + } + + /* + --------------------------- Redraw the display --------------------------- + */ + HiddenPage.Clear(); + Map.Flag_To_Redraw(true); + Map.Render(); + + return (retcode); +} diff --git a/MSGBOX.CPP b/MSGBOX.CPP new file mode 100644 index 0000000..e29dc49 --- /dev/null +++ b/MSGBOX.CPP @@ -0,0 +1,482 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\msgbox.cpv 2.18 16 Oct 1995 16:50:48 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : OPTIONS.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : June 8, 1994 * + * * + * Last Update : August 24, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * CCMessageBox::Process -- Handles all the options graphic interface. * + * CCMessageBox::Process -- Displays message box. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "msgbox.h" +#include "gadget.h" + +#ifdef JAPANESE +CCMessageBox::CCMessageBox(int caption, bool pict) : Caption(caption), IsPicture(pict) +{ +} +#endif + +/*********************************************************************************************** + * CCMessageBox::Process -- pops up a message with yes/no, etc * + * * + * This function displays a dialog box with a one-line message, and * + * up to two buttons. The 2nd button is optional. The buttons default * + * to "OK" and nothing, respectively. The hotkeys for the buttons are * + * RETURN and ESCAPE. * + * * + * INPUT: * + * msg message to display * + * b1txt text for 1st button * + * b2txt text for 2nd button; NULL = no 2nd button * + * * + * OUTPUT: * + * # of button selected (0 = 1st) * + * * + * WARNINGS: * + * 'msg' text must be <= 38 chars * + * 'b1txt' and 'b2txt' must each be <= 18 chars * + * * + * HISTORY: * + * 11/08/1994 BR : Created. * + * 05/18/1995 JLB : Uses new font and dialog style. * + * 08/24/1995 JLB : Handles three buttons. * + *=============================================================================================*/ +#define BUTTON_1 1 +#define BUTTON_2 2 +#define BUTTON_3 3 +#define BUTTON_FLAG 0x8000 +int CCMessageBox::Process(const char *msg, const char *b1txt, const char *b2txt, const char *b3txt, bool preserve) +{ +#define BUFFSIZE (511) +//#define BUFFSIZE (255) + char buffer[BUFFSIZE]; + bool retval; + bool process; // loop while true + KeyNumType input; // user input + int selection; + bool pressed; + int curbutton; + TextButtonClass *buttons[3]; + void *back; + BOOL display; // display level + int realval[5]; + + GraphicBufferClass seen_buff_save(VisiblePage.Get_Width(),VisiblePage.Get_Height(),(void*)NULL); + + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + if (b1txt && *b1txt == '\0') b1txt = 0; + if (b2txt && *b2txt == '\0') b2txt = 0; + if (b3txt && *b3txt == '\0') b3txt = 0; + + /* + ** Examine the optional button parameters. Fetch the width and starting + ** characters for each. + */ + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, TPF_6PT_GRAD|TPF_NOSHADOW); + char b1char, b2char, b3char; // 1st char of each string + int bwidth, bheight; // button width and height + int numbuttons = 0; + if (b1txt) { + b1char = toupper(b1txt[0]); + + /* + ** Build the button list. + */ + bheight = FontHeight + FontYSpacing + (2*factor); + bwidth = MAX((String_Pixel_Width(b1txt) + (8 *factor)), 30 * factor); + + if (b2txt) { + numbuttons = 2; + b2char = toupper(b2txt[0]); + bwidth = MAX((String_Pixel_Width( b2txt ) + (8*factor)), bwidth); + + if (b3txt) { + numbuttons = 3; + b3char = toupper(b3txt[0]); + } + + } else { + numbuttons = 1; + } + } + + /* + ** Determine the dimensions of the text to be used for the dialog box. + ** These dimensions will control how the dialog box looks. + */ + buffer[BUFFSIZE-1] = 0; + strncpy(buffer, msg, BUFFSIZE-2); + int width; + int height; + +#ifdef JAPANESE + if(IsPicture) { + width = 90; + height = 140 - 60; + } else +#endif + Format_Window_String(buffer, 255 * factor, width, height); + +//BG #ifdef JAPANESE +//BG if(!IsPicture) { +//BG #else + width = MAX(width, 50 * factor); + width += 40 * factor; +//BG #endif +//BG #ifdef JAPANESE +//BG } +//BG #endif + height += (numbuttons == 0) ? (30 * factor) : (60 * factor); + + int x = (SeenBuff.Get_Width() - width) / 2; + int y = (SeenBuff.Get_Height() - height) / 2; + + /* + ** Other inits. + */ + Set_Logic_Page(SeenBuff); + VisiblePage.Blit(seen_buff_save); + + /* + ** Initialize the button structures. All are initialized, even though one (or none) may + ** actually be added to the button list. + */ + TextButtonClass button1(BUTTON_1, b1txt, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW, + x + ((numbuttons == 1) ? ((width - bwidth) >> 1) : (10*factor)), y + height - (bheight + (5*factor)), bwidth); + + TextButtonClass button2(BUTTON_2, b2txt, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW, + x + width - (bwidth + (10*factor)), y + height - (bheight + (5*factor)), bwidth); + + TextButtonClass button3(BUTTON_3, b3txt, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW, 0, y + height - (bheight + (5*factor))); + button3.X = x + ((width - button3.Width) >> 1); + + TextButtonClass * buttonlist = 0; + curbutton = 0; + + /* + ** Add and inialize the buttons to the button list. + */ + if (numbuttons) { + buttonlist = &button1; + buttons[0] = &button1; + realval[0] = BUTTON_1; + if (numbuttons > 2) { + button3.Add(*buttonlist); + buttons[1] = &button3; + realval[1] = BUTTON_3; + button2.Add(*buttonlist); + buttons[2] = &button2; + realval[2] = BUTTON_2; + buttons[curbutton]->Turn_On(); + } else if (numbuttons == 2) { + button2.Add(*buttonlist); + buttons[1] = &button2; + realval[1] = BUTTON_2; + buttons[curbutton]->Turn_On(); + } + } + + /* + ** Draw the dialog. + */ + Hide_Mouse(); + if (preserve) { + back = new char[width * height]; + SeenBuff.To_Buffer(x, y, width, height, back, width * height); + } + //display = TRUE; +#ifdef JAPANESE + if(IsPicture) { + Load_Uncompress(CCFileClass(msg), SysMemPage, SysMemPage); + SysMemPage.Blit(SeenBuff, 160, 100); + } else { +#endif + Dialog_Box(x, y, width, height); + Draw_Caption(Caption, x, y, width); + + /* + ** Draw the caption. + */ + Fancy_Text_Print(buffer, x + (20*factor), y + (25*factor), CC_GREEN, TBLACK, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW); +#ifdef JAPANESE + } +#endif + /* + ** Redraw the buttons. + */ + if (buttonlist) { + buttonlist->Draw_All(); + } + Show_Mouse(); + + /* + ** Main Processing Loop. + */ + if (buttonlist) { + process = true; + pressed = false; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + seen_buff_save.Blit(VisiblePage); + display = TRUE; + } + + if (display){ + display = FALSE; + + Hide_Mouse(); + +#ifdef JAPANESE + if(IsPicture) { + Load_Uncompress(CCFileClass(msg), SysMemPage, SysMemPage); + SysMemPage.Blit(SeenBuff, 160, 100); + } else { +#endif + Dialog_Box(x, y, width, height); + Draw_Caption(Caption, x, y, width); + + /* + ** Draw the caption. + */ + Fancy_Text_Print(buffer, x + (20 * factor), y + (25 * factor), CC_GREEN, TBLACK, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW); +#ifdef JAPANESE + } +#endif + /* + ** Redraw the buttons. + */ + if (buttonlist) { + buttonlist->Draw_All(); + } + Show_Mouse(); + } + + /* + ** Invoke game callback. + */ + Call_Back(); + + /* + ** Fetch and process input. + */ + input = buttonlist->Input(); + switch (input) { + case (BUTTON_1|BUTTON_FLAG): + selection = realval[0]; + pressed = true; + break; + + case (KN_ESC): + if (numbuttons > 2) { + selection = realval[1]; + pressed = true; + } else { + selection = realval[2]; + pressed = true; + } + break; + + + case (BUTTON_2|BUTTON_FLAG): + selection = realval[1]; + pressed = true; + break; + + case (BUTTON_3|BUTTON_FLAG): + selection = realval[2]; + pressed = true; + break; + + case (KN_LEFT): + if (numbuttons > 1) { + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + + curbutton--; + if (curbutton < 0) { + curbutton = numbuttons - 1; + } + + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + } + break; + + case (KN_RIGHT): + if (numbuttons > 1) { + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + + curbutton++; + if (curbutton > (numbuttons - 1) ) { + curbutton = 0; + } + + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + } + break; + + case (KN_RETURN): + selection = curbutton + BUTTON_1; + pressed = true; + break; + + /* + ** Check 'input' to see if it's the 1st char of button text + */ + default: +#ifdef NEVER + if (b1char == toupper(Keyboard::To_ASCII((KeyNumType)(input & 0xFF)))) { + selection = BUTTON_1; + pressed = true; + } else if (b2txt!=NULL && + b2char == toupper(Keyboard::To_ASCII((KeyNumType)(input & 0xFF)))) { + selection = BUTTON_2; + pressed = true; + } +#endif + break; + } + + if (pressed) { + switch (selection) { + case (BUTTON_1): + retval = 0; + process = false; + break; + + case (BUTTON_2): + retval = 1; + process = false; + break; + + case BUTTON_3: + retval = 2; + process = false; + break; + } + + pressed = false; + } + } + + } else { + + /* + ** Delay for a brief moment so that the dialog box will be visible long + ** enough to read the text. + */ + CountDownTimerClass timer(BT_SYSTEM, 0); + timer.Start(); + timer.Set(TICKS_PER_SECOND*4); + while (timer.Time() > 0) { + Call_Back(); + } + Keyboard::Clear(); + } + + /* + ** Restore the screen if necessary. + */ + if (preserve) { + Hide_Mouse(); + if (SeenBuff.Lock()){ + Buffer_To_Page(x, y, width, height, back, &SeenBuff); + } + SeenBuff.Unlock(); + delete[] back; + back = NULL; + Show_Mouse(); + } + return(retval); +} + + +/*********************************************************************************************** + * CCMessageBox::Process -- this one takes integer text arguments * + * * + * INPUT: * + * msg message to display * + * b1txt text for 1st button * + * b2txt text for 2nd button; NULL = no 2nd button * + * * + * OUTPUT: * + * # of button selected (0 = 1st) * + * * + * WARNINGS: * + * 'msg' text must be <= 38 chars * + * 'b1txt' and 'b2txt' must each be <= 18 chars * + * * + * HISTORY: * + * 12/12/1994 BR : Created. * + * 06/18/1995 JLB : Simplified. * + *=============================================================================================*/ +int CCMessageBox::Process(int msg, int b1txt, int b2txt, int b3txt, bool preserve) +{ + return(Process(Text_String(msg), b1txt, b2txt, b3txt, preserve)); +} + + +/*********************************************************************************************** + * CCMessageBox::Process -- Displays message box. * + * * + * This routine will display a message box and wait for player input. It varies from the * + * other message box functions only in the type of parameters it takes. * + * * + * INPUT: msg -- Pointer to text string for the message itself. * + * * + * b1txt -- Text number for the "ok" button. * + * * + * b2txt -- Text number for the "cancel" button. * + * * + * OUTPUT: Returns with the button selected. "true" if "OK" was pressed, and "false" if * + * "CANCEL" was pressed. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/18/1995 JLB : Created. * + *=============================================================================================*/ +int CCMessageBox::Process(char const *msg, int b1txt, int b2txt, int b3txt, bool preserve) +{ + return(Process(msg, Text_String(b1txt), Text_String(b2txt), Text_String(b3txt), preserve)); +} diff --git a/MSGBOX.H b/MSGBOX.H new file mode 100644 index 0000000..c4e8e09 --- /dev/null +++ b/MSGBOX.H @@ -0,0 +1,60 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\msgbox.h_v 2.17 16 Oct 1995 16:47:50 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : OPTIONS.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : June 8, 1994 * + * * + * Last Update : June 8, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef MSGBOX_H +#define MSGBOX_H + +#include "jshell.h" + +class CCMessageBox +{ + int Caption; +#ifdef JAPANESE + bool IsPicture; +#endif + public: +#ifdef JAPANESE + CCMessageBox(int caption=TXT_NONE, bool pict=false); +#else + CCMessageBox(int caption=TXT_NONE) {Caption = caption;}; +#endif + int Process(const char *msg, const char *b1txt, const char *b2txt=NULL, const char *b3txt=NULL, bool preserve=false); + int Process(int msg, int b1txt=TXT_OK, int b2txt=TXT_NONE, int b3txt=TXT_NONE, bool preserve=false); + int Process(char const *msg, int b1txt=TXT_OK, int b2txt=TXT_NONE, int b3txt=TXT_NONE, bool preserve=false); +}; + +#endif diff --git a/MSGLIST.CPP b/MSGLIST.CPP new file mode 100644 index 0000000..ebb4ba4 --- /dev/null +++ b/MSGLIST.CPP @@ -0,0 +1,808 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\msglist.cpv 1.4 16 Oct 1995 16:48:20 JOE_BOSTIC $ */ +/*************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MSGLIST.CPP * + * * + * Programmer : Bill R. Randolph * + * * + * Start Date : 05/22/95 * + * * + * Last Update : June 26, 1995 [BRR] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * MessageListClass::Add_Edit -- Adds editable string to message list * + * MessageListClass::Add_Message -- displays the given message * + * MessageListClass::Draw -- Draws the messages * + * MessageListClass::Get_Edit_Buf -- gets edit buffer * + * MessageListClass::Init -- Inits message system, sets options * + * MessageListClass::Input -- Handles input for sending messages * + * MessageListClass::Manage -- Manages multiplayer messages * + * MessageListClass::MessageListClass -- constructor * + * MessageListClass::~MessageListClass -- destructor * + * MessageListClass::Num_Messages -- returns # messages in the list * + * MessageListClass::Set_Width -- sets allowable width of messages * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +char MessageListClass::MessageBuffers[MAX_NUM_MESSAGES][MAX_MESSAGE_LENGTH + 30]; +char MessageListClass::BufferAvail[MAX_NUM_MESSAGES]; + +/*************************************************************************** + * MessageListClass::MessageListClass -- constructor * + * * + * INPUT: * + * x,y coord of upper-left of top message * + * max_msg max messages allowed, including edit message * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/21/1995 BRR : Created. * + *=========================================================================*/ +MessageListClass::MessageListClass(void) +{ + int i; + + MessageList = 0; + MessageX = 0; + MessageY = 0; + MaxMessages = 0; + MaxChars = 0; + Height = 0; + EditLabel = 0; + EditBuf = 0; + EditCurPos = 0; + EditInitPos = 0; + + for (i = 0; i < MAX_NUM_MESSAGES; i++) { + BufferAvail[i] = 1; + } + +} + + +/*************************************************************************** + * MessageListClass::~MessageListClass -- destructor * + * * + * INPUT: * + * x,y coord of upper-left of top message * + * max_msg max messages allowed, including edit message * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/21/1995 BRR : Created. * + *=========================================================================*/ +MessageListClass::~MessageListClass() +{ + Init(0,0,0,0,0); +} + + +/*************************************************************************** + * MessageListClass::Init -- Inits message system, sets options * + * * + * INPUT: * + * x,y coord of upper-left of top message * + * max_msg max messages allowed, including edit message * + * maxchars max # characters allowed per message * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/21/1995 BRR : Created. * + *=========================================================================*/ +void MessageListClass::Init(int x, int y, int max_msg, int maxchars, int height) +{ + TextLabelClass *txtlabel; + int i; + + /*------------------------------------------------------------------------ + Remove every entry in the list + ------------------------------------------------------------------------*/ + txtlabel = MessageList; + while (txtlabel) { + MessageList = (TextLabelClass *)txtlabel->Remove(); + delete txtlabel; + txtlabel = MessageList; + } + + /*------------------------------------------------------------------------ + Mark all buffers as available + ------------------------------------------------------------------------*/ + for (i = 0; i < MAX_NUM_MESSAGES; i++) { + BufferAvail[i] = 1; + } + + /*------------------------------------------------------------------------ + Init variables + ------------------------------------------------------------------------*/ + MessageList = 0; + MessageX = x; + MessageY = y; + + MaxMessages = max_msg; + if (MaxMessages > MAX_NUM_MESSAGES) + MaxMessages = MAX_NUM_MESSAGES; + + MaxChars = maxchars; + if (MaxChars > MAX_MESSAGE_LENGTH) + MaxChars = MAX_MESSAGE_LENGTH; + + Height = height; + EditLabel = 0; + EditBuf = 0; + EditCurPos = 0; + EditInitPos = 0; +} + + +/*************************************************************************** + * MessageListClass::Add_Message -- displays the given message * + * * + * INPUT: * + * txt text to display * + * color color to draw text in * + * style style to use * + * timeout # of ticks the thing is supposed to last (-1 = forever)* + * * + * OUTPUT: * + * ptr to new TextLabelClass object. * + * * + * WARNINGS: * + * The TextLabelClass's text buffer is free'd when the class is free'd, * + * so never pass it a static buffer. * + * * + * HISTORY: * + * 05/05/1995 BRR : Created. * + *=========================================================================*/ +TextLabelClass * MessageListClass::Add_Message(char *txt, int color, + TextPrintType style, int timeout, unsigned short magic_number, unsigned short crc) +{ + int num_msg; + TextLabelClass *txtlabel; + int x,y; + GadgetClass *gadg; + int i,j; + int found; + int position; + char *raw_string; + char *current_string; + char *s1,*s2; + bool same; +#if (0) +#if (GERMAN) + static int from_adjust = -1; +#else +#if (FRENCH) + static int from_adjust = -2; +#else + static int from_adjust = 0; +#endif +#endif +#endif //(0) + + /*------------------------------------------------------------------------ + Prevent a duplicate message. (The IPXManager Global Channel cannot detect + a resend of a packet, so sometimes two identical messages appear in a row.) + ------------------------------------------------------------------------*/ + if (MessageList) { + txtlabel = MessageList; + while (txtlabel) { + /* + ** Dont check for duplicates in multi-segment strings + */ + if (!txtlabel->Segments){ + if (!strcmp (txtlabel->Text,txt) && txtlabel->Color == color && + txtlabel->Style == style) { + return(txtlabel); + } + } + txtlabel = (TextLabelClass *)txtlabel->Get_Next(); + } + } + + + /* + ** If the magic number is a valid message tail then see if we + ** can add this message to the tail of an existing message (crc's must also match) + */ + if (magic_number > MESSAGE_HEAD_MAGIC_NUMBER && + magic_number < MESSAGE_HEAD_MAGIC_NUMBER+MAX_MESSAGE_SEGMENTS){ + + position = magic_number - MESSAGE_HEAD_MAGIC_NUMBER; + txtlabel = MessageList; + + while (txtlabel) { + if (txtlabel->Color == color && txtlabel->Style == style && txtlabel->CRC == crc) { + + same = true; + + s1 = strchr(txtlabel->Text, ':'); + s2 = strchr(txt, ':'); + + if (s1 && s2){ + *s1 = 0; + *s2 = 0; + + same = !strcmp (txtlabel->Text, txt); + + *s1 = ':'; + *s2 = ':'; + } + + if (same){ + + /* + ** If this message segment hasnt already come through then add it to the existing text + */ + if (! (txtlabel->Segments & (1 << position)) ) { + /* + ** Search for the ':' to find the actual message after the players name + */ + raw_string = s2; + current_string = s1; + if (raw_string++ && current_string++){ + memcpy (current_string + (position*(COMPAT_MESSAGE_LENGTH-5))/*+from_adjust*/, raw_string, COMPAT_MESSAGE_LENGTH-4); + /* + ** Flag this string segment as complete + */ + txtlabel->Segments |= 1<Get_Next(); + } + } + + + + /*------------------------------------------------------------------------ + Count the # of messages; if MaxMessages is going to be exceeded, remove + the top-most message. + ------------------------------------------------------------------------*/ + num_msg = 0; + if (MessageList) { + gadg = MessageList; + while (gadg) { + num_msg++; + gadg = gadg->Get_Next(); + } + } + /*........................................................................ + Remove the top-most message, but don't remove the edit message. + ........................................................................*/ + if ( (MaxMessages > 0) && ((num_msg + 1) > MaxMessages)) { + txtlabel = MessageList; + /*..................................................................... + If the top label is the edit label, go to the next one; if there is + no next one, just return. + .....................................................................*/ + if (txtlabel == EditLabel) + txtlabel = (TextLabelClass *)txtlabel->Get_Next(); + if (txtlabel==NULL) + return(NULL); + + /*..................................................................... + Remove this message from the list; mark its buffer as being available. + .....................................................................*/ + MessageList = (TextLabelClass *)txtlabel->Remove(); + for (i = 0; i < MAX_NUM_MESSAGES; i++) { + if (txtlabel->Text == MessageBuffers[i]) + BufferAvail[i] = 1; + } + delete txtlabel; + + /*..................................................................... + Recompute everyone's y-coordinate + .....................................................................*/ + y = MessageY; + if (MessageList) { + gadg = MessageList; + while (gadg) { + gadg->Y = y; + gadg = gadg->Get_Next(); + y += Height; + } + } + } + + /*------------------------------------------------------------------------ + Figure out the message's y-coordinate; put it below the other messages + ------------------------------------------------------------------------*/ + x = MessageX; + y = MessageY; + if (MessageList) { + gadg = MessageList; + while (gadg) { + gadg = gadg->Get_Next(); + y += Height; + } + } + + /*------------------------------------------------------------------------ + Create the message + ------------------------------------------------------------------------*/ + txtlabel = new TextLabelClass (txt, x, y, color, style); + if (timeout==-1) { + txtlabel->UserData = 0; + } else { + txtlabel->UserData = TickCount.Time() + timeout; + } + + /*------------------------------------------------------------------------ + Find a buffer to store our message in; if there are none, don't add the + message. + ------------------------------------------------------------------------*/ + found = 0; + txtlabel->Segments = 0; + txtlabel->CRC = crc; + + for (i = 0; i < MAX_NUM_MESSAGES; i++) { + if (BufferAvail[i]) { + BufferAvail[i] = 0; + memset (MessageBuffers[i],0,MAX_MESSAGE_LENGTH + 30); + strcpy (MessageBuffers[i],txt); + + /* + ** If this is a segment from a larger message then put it in the right place + ** in the buffer and clear out the rest with spaces + */ + if (magic_number >= MESSAGE_HEAD_MAGIC_NUMBER && + magic_number < MESSAGE_HEAD_MAGIC_NUMBER+MAX_MESSAGE_SEGMENTS){ + raw_string = strchr(txt, ':'); + char *dest_str = strchr(MessageBuffers[i], ':'); + if (dest_str){ + dest_str++; + }else{ + dest_str = MessageBuffers[i]; + } + + if (raw_string++){ + for (j=0 ; j<3 ; j++){ + if (! ((magic_number - j) == MESSAGE_HEAD_MAGIC_NUMBER)){ + memset (dest_str + j*(COMPAT_MESSAGE_LENGTH-4)/*+from_adjust*/, 32, COMPAT_MESSAGE_LENGTH-4); + }else{ + strcpy(dest_str + j*(COMPAT_MESSAGE_LENGTH-4)/*+from_adjust*/, raw_string); + } + } + *(dest_str +((COMPAT_MESSAGE_LENGTH-4)*MAX_MESSAGE_SEGMENTS-1)) = 0; + + } + position = magic_number - MESSAGE_HEAD_MAGIC_NUMBER; + txtlabel->Segments = 1<Text = MessageBuffers[i]; + found = 1; + break; + } + } + if (!found) { + delete txtlabel; + return (NULL); + } + + /*------------------------------------------------------------------------ + Attach the message to our list + ------------------------------------------------------------------------*/ + if (MessageList) { + txtlabel->Add_Tail (*MessageList); + } else { + MessageList = txtlabel; + } + + return(txtlabel); +} + + +/*************************************************************************** + * MessageListClass::Add_Edit -- Adds editable string to message list * + * * + * INPUT: * + * color color of edit message * + * style style of edit message * + * to string: who to send to * + * width width of editbox in pixels * + * * + * OUTPUT: * + * ptr to new TextLabelClass * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/22/1995 BRR : Created. * + *=========================================================================*/ +TextLabelClass * MessageListClass::Add_Edit(int color, TextPrintType style, + char *to, int width) +{ + /*------------------------------------------------------------------------ + Do nothing if we're already in "edit" mode + ------------------------------------------------------------------------*/ + if (EditLabel) + return(NULL); + + /*------------------------------------------------------------------------ + Initialize the buffer positions; add a new label to the label list. + ------------------------------------------------------------------------*/ + EditCurPos = EditInitPos = strlen(to); + EditLabel = Add_Message (to, color, style, -1, 0, 0); + Width = width; + + /*------------------------------------------------------------------------ + Save our edit buffer pointer. + ------------------------------------------------------------------------*/ + if (EditLabel) + EditBuf = EditLabel->Text; + else + EditBuf = NULL; + + return(EditLabel); +} + + +/*************************************************************************** + * MessageListClass::Get_Edit_Buf -- gets edit buffer * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * ptr to edit buffer, minus the "To:" header * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/21/1995 BRR : Created. * + *=========================================================================*/ +char * MessageListClass::Get_Edit_Buf(void) +{ + if (!EditBuf) + return(NULL); + + return(EditBuf + EditInitPos); +} + + +/*************************************************************************** + * MessageListClass::Manage -- Manages multiplayer messages * + * * + * If this routine returns TRUE, the caller should update the display. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * 0 = no change has occurred, 1 = changed * + * * + * HISTORY: * + * 05/05/1995 BRR : Created. * + *=========================================================================*/ +int MessageListClass::Manage (void) +{ + TextLabelClass *txtlabel; + TextLabelClass *next; + int changed = 0; + int y; + GadgetClass *gadg; + int i; + + /*------------------------------------------------------------------------ + Loop through all messages + ------------------------------------------------------------------------*/ + txtlabel = MessageList; + while (txtlabel) { + /*..................................................................... + If this message's time is up, remove it from the list + .....................................................................*/ + if (txtlabel->UserData != 0 && TickCount.Time() > txtlabel->UserData) { + /*.................................................................. + If we're about to delete the edit message, clear our edit message + values. + ..................................................................*/ + if (txtlabel == EditLabel) { + EditLabel = 0; + EditBuf = 0; + } + /*.................................................................. + Save the next ptr in the list; remove this entry + ..................................................................*/ + next = (TextLabelClass *)txtlabel->Get_Next(); + MessageList = (TextLabelClass *)txtlabel->Remove(); + for (i = 0; i < MAX_NUM_MESSAGES; i++) { + if (txtlabel->Text == MessageBuffers[i]) + BufferAvail[i] = 1; + } + delete txtlabel; + changed = 1; + txtlabel = next; + } else { + txtlabel = (TextLabelClass *)txtlabel->Get_Next(); + } + } + + /*------------------------------------------------------------------------ + If a changed has been made, recompute the y-coord of all messages + ------------------------------------------------------------------------*/ + if (changed) { + + y = MessageY; + if (MessageList) { + gadg = MessageList; + while (gadg) { + gadg->Y = y; + gadg = gadg->Get_Next(); + y += Height; + } + } + } + + return(changed); +} + + +/*************************************************************************** + * MessageListClass::Input -- Handles input for sending messages * + * * + * INPUT: * + * input key value to process * + * * + * OUTPUT: * + * 1 = caller should redraw the message list (no need to complete * + * refresh, though) * + * 2 = caller should completely refresh the display. * + * 3 = caller should send the edit message. * + * (sets 'input' to 0 if it processes it.) * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/05/1995 BRR : Created. * + *=========================================================================*/ +int MessageListClass::Input(KeyNumType &input) +{ + KeyASCIIType ascii; + int retcode = 0; + + /*------------------------------------------------------------------------ + Do nothing if nothing to do. + ------------------------------------------------------------------------*/ + if (input == KN_NONE) + return(0); + + /*------------------------------------------------------------------------ + Leave mouse events alone. + ------------------------------------------------------------------------*/ + if ( (input & (~KN_RLSE_BIT))==KN_LMOUSE || + (input & (~KN_RLSE_BIT))==KN_RMOUSE) + return(0); + + /*------------------------------------------------------------------------ + If we're in 'edit mode', handle keys + ------------------------------------------------------------------------*/ + if (EditLabel) { + ascii = (KeyASCIIType)(Keyboard::To_ASCII(input) & 0x00ff); + + /* + ** Allow numeric keypad presses to map to ascii numbers + */ + if ((input & WWKEY_VK_BIT) && ascii >='0' && ascii <= '9') { + + input = (KeyNumType)(input & ~WWKEY_VK_BIT); + + } else { + /* + ** Filter out all special keys except return, escape and backspace + */ + if ((!(input & WWKEY_VK_BIT) && !(input & KN_BUTTON) + && ascii >= ' ' && ascii <= 127) + || (input & 0xff)== (KN_RETURN & 0xff) + || (input & 0xff)== (KN_BACKSPACE & 0xff) + || (input & 0xff)== (KN_ESC & 0xff) ) { + + //ascii = (KeyASCIIType)(Keyboard->To_ASCII(input)); + } else { + input = KN_NONE; + return (0); + } + } + + + switch (ascii) { + /*------------------------------------------------------------------ + ESC = abort message + ------------------------------------------------------------------*/ + case KA_ESC & 0xff: + EditLabel->UserData = 1; // force it to be removed + input = KN_NONE; + break; + + /*------------------------------------------------------------------ + RETURN = send the message + ------------------------------------------------------------------*/ + case KA_RETURN & 0xff: + EditLabel->UserData = 1; // force it to be removed + retcode = 3; + input = KN_NONE; + break; + + /*------------------------------------------------------------------ + BACKSPACE = remove a character + ------------------------------------------------------------------*/ + case KA_BACKSPACE & 0xff: + if (EditCurPos > EditInitPos) { + EditCurPos--; + EditBuf[EditCurPos] = 0; + retcode = 2; + } + input = KN_NONE; + break; + + /*------------------------------------------------------------------ + default: add a character. Reserve the last buffer position for null. + (EditCurPos - EditInitPos) is the buffer index # of the next + character, after the "To:" prefix. + ------------------------------------------------------------------*/ + default: + if ( (EditCurPos - EditInitPos) < (MaxChars - 1) ) { + if (!(input & WWKEY_VK_BIT) && ascii >= ' ' && ascii <= 127) { + EditBuf[EditCurPos] = ascii; + EditCurPos++; + retcode = 1; + + /* + ** Verify that the additional character would not overrun the on screen edit box. + */ + Fancy_Text_Print(TXT_NONE, 0, 0, EditLabel->Color, TBLACK, EditLabel->Style); + int width = String_Pixel_Width(EditBuf); + if (width >= Width){ + EditBuf[EditCurPos--] = 0; + retcode = 0; + } + + } + } + input = KN_NONE; + break; + } + } + + return(retcode); +} + + +/*************************************************************************** + * MessageListClass::Draw -- draws messages * + * * + * INPUT: * + * none * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/22/1995 BRR : Created. * + *=========================================================================*/ +void MessageListClass::Draw(void) +{ + if (MessageList) { + Hide_Mouse(); + MessageList->Draw_All(); + Show_Mouse(); + } +} + + +/*************************************************************************** + * MessageListClass::Num_Messages -- returns # messages in the list * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * # of messages * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/26/1995 BRR : Created. * + *=========================================================================*/ +int MessageListClass::Num_Messages(void) +{ + GadgetClass *gadg; + int num; + + num = 0; + + if (MessageList) { + gadg = MessageList; + while (gadg) { + num++; + gadg = gadg->Get_Next(); + } + } + + return (num); +} + + +/*************************************************************************** + * MessageListClass::Set_Width -- sets allowable width of messages * + * * + * INPUT: * + * width pixel width * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/26/1995 BRR : Created. * + *=========================================================================*/ +void MessageListClass::Set_Width(int width) +{ + GadgetClass *gadg; + + if (MessageList) { + gadg = MessageList; + while (gadg) { + ((TextLabelClass *)gadg)->PixWidth = width; + gadg = gadg->Get_Next(); + } + } +} + diff --git a/MSGLIST.H b/MSGLIST.H new file mode 100644 index 0000000..6755765 --- /dev/null +++ b/MSGLIST.H @@ -0,0 +1,109 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : MSGLIST.H * + * * + * Programmer : Bill R. Randolph * + * * + * Start Date : 05/22/95 * + * * + * Last Update : May 22, 1995 [BRR] * + * * + * How the messages work: * + * - MPlayerMessageList is a gadget list of all current messages * + * - MPlayerMessageX & Y are the upper left corner of the 1st message * + * - MPlayerMaxMessages is the max # of messages allowed, including * + * the editable message; 0 = no limit. * + * - EditLabel points to the textmessage gadget for the current editable * + * field. EditBuf points to the char buffer being edited. EditInitPos * + * & EditCurPos define buffer index positions. * + * - EditSendAddress is the IPX Address to send the message to when RETURN * + * is pressed. * + * * + * The UserData field in the TextLabelClass tells what the timeout for * + * each message is (0 = none). * + * When a message's timeout expires, it's deleted. When a new message * + * is added, the top message is deleted if MPlayerMaxMessages is exceeded. * + * * + * The Edit-able message is never deleted until ESC or RETURN is pressed. * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef MSGLIST_H +#define MSGLIST_H + +/* +** Class declaration +*/ +class MessageListClass { + public: + /* + ** Constructor/Destructor + */ + MessageListClass (void); + ~MessageListClass (); + + /* + ** Initialization + */ + void Init (int x, int y, int max_msg, int maxchars, int height); + TextLabelClass * Add_Message (char *txt, int color, TextPrintType style, int timeout, + unsigned short magic_number, unsigned short crc); + + /* + ** Message-editing routines + */ + TextLabelClass * Add_Edit (int color, TextPrintType style, char *to, int width); + char * Get_Edit_Buf (void); + + /* + ** Maintenance routines + */ + int Manage (void); + int Input (KeyNumType &input); + void Draw(void); + int Num_Messages(void); + void Set_Width(int width); + + private: + TextLabelClass * MessageList; // list of messages + int MessageX; // x-coord of upper-left + int MessageY; // y-coord of upper-left + int MaxMessages; // max messages allowed + int MaxChars; // max allowed chars per message + int Height; // height in pixels + TextLabelClass *EditLabel; // ptr to current edit label + char *EditBuf; // ptr to current edit buffer + int EditCurPos; // current edit position + int EditInitPos; // initial edit position + int Width; // Maximum width in pixels of editable string + + /* + ** Static buffers provided for messages. They must be long enough for + ** both the message, and for the "To" prefix on edited messages, or + ** the "From:" prefix on received messages. + */ + static char MessageBuffers[MAX_NUM_MESSAGES][MAX_MESSAGE_LENGTH + 30]; + static char BufferAvail[MAX_NUM_MESSAGES]; +}; + +#endif diff --git a/NETDLG.CPP b/NETDLG.CPP new file mode 100644 index 0000000..d0122c5 --- /dev/null +++ b/NETDLG.CPP @@ -0,0 +1,5431 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\netdlg.cpv 2.17 16 Oct 1995 16:52:26 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : NETDLG.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : January 23, 1995 * + * * + * Last Update : July 8, 1995 [BRR] * + * * + *---------------------------------------------------------------------------------------------* + * * + * These routines establish & maintain peer-to-peer connections between this system * + * and all others in the game. Each system finds out the IPX address of the others, * + * and forms a direct connection (IPXConnectionClass) to that system. Systems are * + * found out via broadcast queries. Every system broadcasts its queries, and every * + * system replies to queries it receives. At the point when the game owner signals * + * 'OK', every system must know about all the other systems in the game. * + * * + * How Bridges are handled: * + * Currently, bridges are handled by specifying the destination IPX address of the * + * "server" (game owner's system) on the command-line. This address is used to * + * derive a broadcast address to that destination network, and this system's queries * + * are broadcast over its network & the server's network; replies to the queries come * + * with each system's IPX address attached, so once we have the address, we can form * + * a connection with any system on the bridged net. * + * * + * The flaw in this plan is that we can only cross one bridge. If there are 3 nets * + * bridged (A, B, & C), and the server is on net B, and we're on net A, our broadcasts * + * will reach nets A & B, but not C. The way to circumvent this (if it becomes a problem) * + * would be to have the server tell us what other systems are in its game, not each * + * individual player's system. Thus, each system would find out about all the other systems * + * by interacting with the game's owner system (this would be more involved than what * + * I'm doing here). * + * * + * Here's a list of all the different packets sent over the Global Channel: * + * * + * NET_QUERY_GAME * + * (no other data) * + * NET_ANSWER_GAME * + * Name: game owner's name * + * GameInfo: game's version & open state * + * NET_QUERY_PLAYER * + * Name: name of game we want players to respond for * + * NET_ANSWER_PLAYER * + * Name: player's name * + * PlayerInfo: info about player * + * NET_QUERY_JOIN * + * Name: name of player wanting to join * + * PlayerInfo: player's requested house & color * + * NET_CONFIRM_JOIN * + * PlayerInfo: approves player's house & color * + * NET_REJECT_JOIN * + * (no other data) * + * NET_GAME_OPTIONS * + * ScenarioInfo: info about scenario * + * NET_SIGN_OFF * + * Name: name of player signing off * + * NET_PING * + * (no other data) * + * NET_GO * + * Delay: value of one-way response time, in frames * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Clear_Game_List -- Clears the game-name listbox & 'Games' Vector * + * Clear_Player_List -- Clears the player-name listbox & Vector * + * Destroy_Connection -- destroys the given connection * + * Get_Join_Responses -- sends queries for the Join Dialog * + * Get_NewGame_Responses -- processes packets for New Game dialog * + * Init_Network -- initializes network stuff * + * Net_Join_Dialog -- lets user join an existing game, or start a new one * + * Net_New_Dialog -- lets user start a new game * + * Process_Global_Packet -- responds to remote queries * + * Remote_Connect -- handles connecting this user to others * + * Request_To_Join -- Sends a JOIN request packet to game owner * + * Send_Join_Queries -- sends queries for the Join Dialog * + * Shutdown_Network -- shuts down network stuff * + * Compute_Name_CRC -- computes CRC from char string * + * Net_Reconnect_Dialog -- Draws/updates the network reconnect dialog * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include +#include "tcpip.h" +#include "ccdde.h" +#define SHOW_MONO 0 + + +#ifndef DEMO + +/*--------------------------------------------------------------------------- +The possible states of the join-game dialog +---------------------------------------------------------------------------*/ +typedef enum { + JOIN_REJECTED = -1, // we've been rejected + JOIN_NOTHING, // we're not trying to join a game + JOIN_WAIT_CONFIRM, // we're asking to join, & waiting for confirmation + JOIN_CONFIRMED, // we've been confirmed + JOIN_GAME_START, // the game we've joined is starting +} JoinStateType; + +/*--------------------------------------------------------------------------- +The possible return codes from Get_Join_Responses() +---------------------------------------------------------------------------*/ +typedef enum { + EV_NONE, // nothing happened + EV_STATE_CHANGE, // Join dialog is in a new state + EV_NEW_GAME, // a new game was detected + EV_NEW_PLAYER, // a new player was detected + EV_PLAYER_SIGNOFF, // a player has signed off + EV_GAME_SIGNOFF, // a gamed owner has signed off + EV_GAME_OPTIONS, // a game options packet was received + EV_MESSAGE, // a message was received +} JoinEventType; + + +/* +******************************** Prototypes ********************************* +*/ +static int Net_Join_Dialog(void); +static void Clear_Game_List (ListClass *gamelist); +static void Clear_Player_List (ListClass *playerlist); +static int Request_To_Join (char *playername, int join_index, ListClass *playerlist, + HousesType house, int color); +static void Send_Join_Queries(int curgame, int gamenow, int playernow); +static JoinEventType Get_Join_Responses(JoinStateType *joinstate, ListClass *gamelist, + ColorListClass *playerlist, int join_index); +static int Net_New_Dialog(void); +static JoinEventType Get_NewGame_Responses(ColorListClass *playerlist); +static int Net_Fake_New_Dialog(void); +static int Net_Fake_Join_Dialog(void); + + +/*********************************************************************************************** + * Init_Network -- initializes network stuff * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = Initialization OK, false = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +bool Init_Network (void) +{ + NetNumType net; + NetNodeType node; + + /*------------------------------------------------------------------------ + This call allocates all necessary queue buffers, allocates Real-mode + memory, and commands IPX to start listening on the Global Channel. + ------------------------------------------------------------------------*/ + if (!Ipx.Init()) + return(false); + + /*------------------------------------------------------------------------ + Allocate our "meta-packet" buffer + ------------------------------------------------------------------------*/ + if (!MetaPacket) { + MetaPacket = new char [sizeof (EventClass) * MAX_EVENTS]; + } + + /*------------------------------------------------------------------------ + Set up the IPX manager to cross a bridge + ------------------------------------------------------------------------*/ + if (!(GameToPlay == GAME_INTERNET)){ + if (IsBridge) { + BridgeNet.Get_Address(net,node); + Ipx.Set_Bridge(net); + } + } + + return(true); + +} /* end of Init_Network */ + + +/*********************************************************************************************** + * Shutdown_Network -- shuts down network stuff * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +void Shutdown_Network (void) +{ + +// +// Note: The thought behind this section of code was that if the program +// terminates early, without an EventClass::EXIT event, it still needs to +// tell the other systems that it's gone, so it would send a SIGN_OFF packet. +// BUT, this causes a sync bug if the systems are running slow and this system +// is running ahead of the others; it will send the NET_SIGN_OFF >>before<< +// the other system execute their EventClass::EXIT event, and the other systems +// will kill the connection at some random Frame # & turn my stuff over to +// the computer possibly at different times. +// BRR, 10/29/96 +// +#if (0) + /*------------------------------------------------------------------------ + Broadcast a sign-off packet, by sending the packet over the Global Channel, + telling the IPX Manager that no ACK is required, and specifying a NULL + destination address. + ------------------------------------------------------------------------*/ + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy (GPacket.Name, MPlayerName); + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), 0, NULL); + + if (IsBridge && !Winsock.Get_Connected()) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, &BridgeNet); + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, &BridgeNet); + } + + /*------------------------------------------------------------------------ + Wait for the packets to finish going out (or the Global Channel times out) + ------------------------------------------------------------------------*/ + for (;;) { + if (Ipx.Global_Num_Send()==0) { + break; + } + Ipx.Service(); + } + +#endif //(0) + + /*------------------------------------------------------------------------ + Delete our "meta-packet" + ------------------------------------------------------------------------*/ + delete [] MetaPacket; + MetaPacket = 0; + + /*------------------------------------------------------------------------ + If I was in a game, I'm not now, so clear the game name + ------------------------------------------------------------------------*/ + MPlayerGameName[0] = 0; +} + + +/*********************************************************************************************** + * Process_Global_Packet -- responds to remote queries * + * * + * The only commands from other systems this routine responds to are NET_QUERY_GAME * + * and NET_QUERY_PLAYER. The other commands are too context-specific to be able * + * to handle here, such as joining the game or signing off; but this routine handles * + * the majority of the program's needs. * + * * + * INPUT: * + * packet ptr to packet to process * + * address source address of sender * + * * + * OUTPUT: * + * true = packet was processed, false = wasn't * + * * + * WARNINGS: * + * MPlayerName & MPlayerGameName must have been filled in before this function * + * can be called. * + * * + * HISTORY: * + * 02/15/1995 BR : Created. * + *=============================================================================================*/ +bool Process_Global_Packet(GlobalPacketType *packet, IPXAddressClass *address) +{ + GlobalPacketType mypacket; + + /* + ---------------- Another system asking what game this is ----------------- + */ + if (packet->Command==NET_QUERY_GAME && NetStealth==0) { + /*..................................................................... + If the game is closed, let every player respond, and let the sender of + the query sort it all out. This way, if the game's host exits the game, + the game still shows up on other players' dialogs. + If the game is open, only the game owner may respond. + .....................................................................*/ + if (strlen(MPlayerName) > 0 && strlen(MPlayerGameName) > 0 && + ((!NetOpen) || (NetOpen && !strcmp(MPlayerName,MPlayerGameName)))) { + memset (packet, 0, sizeof(GlobalPacketType)); + + mypacket.Command = NET_ANSWER_GAME; + strcpy(mypacket.Name, MPlayerGameName); +#ifdef PATCH + if (IsV107) { + mypacket.GameInfo.Version = 1; + } else { + mypacket.GameInfo.Version = 2; + } +#else + mypacket.GameInfo.Version = Version_Number(); +#endif + mypacket.GameInfo.IsOpen = NetOpen; + + Ipx.Send_Global_Message (&mypacket, sizeof(GlobalPacketType), 1, + address); + } + return(true); + } else { + + /* + ----------------- Another system asking what player I am ----------------- + */ + if (packet->Command==NET_QUERY_PLAYER && + !strcmp (packet->Name, MPlayerGameName) && + (strlen(MPlayerGameName) > 0) && NetStealth==0) { + memset (packet, 0, sizeof(GlobalPacketType)); + + mypacket.Command = NET_ANSWER_PLAYER; + strcpy(mypacket.Name, MPlayerName); + mypacket.PlayerInfo.House = MPlayerHouse; + mypacket.PlayerInfo.Color = MPlayerColorIdx; + mypacket.PlayerInfo.NameCRC = Compute_Name_CRC(MPlayerGameName); + + Ipx.Send_Global_Message (&mypacket, sizeof(GlobalPacketType), 1, address); + return(true); + } + } + return(false); +} + + +/*********************************************************************************************** + * Destroy_Connection -- destroys the given connection * + * * + * Call this routine when a connection goes bad, or another player signs off. * + * * + * INPUT: * + * id connection ID to destroy * + * error 0 = user signed off; 1 = connection error; otherwise, no error is shown. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/22/1995 BR : Created. * + *=============================================================================================*/ +void Destroy_Connection(int id, int error) +{ + int i,j; + HousesType house; + HouseClass *housep; + char txt[80]; + + /*------------------------------------------------------------------------ + Create a message to display to the user + ------------------------------------------------------------------------*/ + txt[0] = '\0'; + if (error==1) { + sprintf(txt,Text_String(TXT_CONNECTION_LOST),Ipx.Connection_Name(id)); + } else if (error==0) { + sprintf(txt,Text_String(TXT_LEFT_GAME),Ipx.Connection_Name(id)); + } + + if (strlen(txt)) { + Messages.Add_Message (txt, + MPlayerTColors[MPlayerID_To_ColorIndex((unsigned char)id)], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, 0, 0); + Map.Flag_To_Redraw(false); + } + + /*------------------------------------------------------------------------ + Delete the IPX connection, shift the MPlayerID's & MPlayerHouses' back one. + ------------------------------------------------------------------------*/ + Ipx.Delete_Connection(id); + + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID[i] == (unsigned char)id) { + /*.................................................................. + Turn the player's house over to the computer's AI + ..................................................................*/ + house = MPlayerHouses[i]; + housep = HouseClass::As_Pointer (house); + housep->IsHuman = false; + housep->IsStarted = true; + + /*.................................................................. + Move arrays back by one + ..................................................................*/ + for (j = i; j < MPlayerCount - 1; j++) { + MPlayerID[j] = MPlayerID[j + 1]; + MPlayerHouses[j] = MPlayerHouses[j + 1]; + strcpy (MPlayerNames[j], MPlayerNames[j+1]); + TheirProcessTime[j] = TheirProcessTime[j+1]; + } + } + } + + MPlayerCount--; + + /*------------------------------------------------------------------------ + If we're the last player left, tell the user. + ------------------------------------------------------------------------*/ + if (MPlayerCount == 1) { + sprintf(txt,"%s",Text_String(TXT_JUST_YOU_AND_ME)); + Messages.Add_Message (txt, + MPlayerTColors[MPlayerID_To_ColorIndex((unsigned char)id)], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, 0, 0); + Map.Flag_To_Redraw(false); + } + +} /* end of Destroy_Connection */ + + +/*********************************************************************************************** + * Remote_Connect -- handles connecting this user to others * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = connections established; false = not * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +bool Remote_Connect(void) +{ + int rc; + int stealth; // original state of NetStealth flag + + /*------------------------------------------------------------------------ + Init network timing parameters; these values should work for both a "real" + network, and a simulated modem network (ie Kali) + ------------------------------------------------------------------------*/ + Ipx.Set_Timing ( 30, // retry 2 times per second + -1, // ignore max retries + 600); // give up after 10 seconds + + /*------------------------------------------------------------------------ + Save the original value of the NetStealth flag, so we can turn stealth + off for now (during this portion of the dialogs, we must show ourselves) + ------------------------------------------------------------------------*/ + stealth = NetStealth; + NetStealth = 0; + + /*------------------------------------------------------------------------ + Init my game name to 0-length, since I haven't joined any game yet. + ------------------------------------------------------------------------*/ + MPlayerGameName[0] = 0; + + /*------------------------------------------------------------------------ + The game is now "open" for joining. Close it as soon as we exit this + routine. + ------------------------------------------------------------------------*/ + NetOpen = 1; + + /*------------------------------------------------------------------------ + Read the default values from the INI file + ------------------------------------------------------------------------*/ + Read_MultiPlayer_Settings (); + + /*------------------------------------------------------------------------ + Keep looping until something useful happens. + ------------------------------------------------------------------------*/ + while (1) { + /*--------------------------------------------------------------------- + Pop up the network Join/New dialog + ---------------------------------------------------------------------*/ + rc = Net_Join_Dialog(); + + /*--------------------------------------------------------------------- + -1 = user selected Cancel + ---------------------------------------------------------------------*/ + if (rc==-1) { + NetStealth = stealth; + NetOpen = 0; + return(false); + } else { + + /*--------------------------------------------------------------------- + 0 = user has joined an existing game; save values & return + ---------------------------------------------------------------------*/ + if (rc==0) { + Write_MultiPlayer_Settings (); + NetStealth = stealth; + NetOpen = 0; + + return(true); + } else { + + /*--------------------------------------------------------------------- + 1 = user requests New Network Game + ---------------------------------------------------------------------*/ + if (rc==1) { + /*.................................................................. + Pop up the New Network Game dialog; if user selects OK, return + 'true'; otherwise, return to the Join Dialog. + ..................................................................*/ + if (Net_New_Dialog()) { + Write_MultiPlayer_Settings (); + NetOpen = 0; + NetStealth = stealth; + NetOpen = 0; + + return(true); + } else { + continue; + } + } + } + } + } +} + + + +/*********************************************************************************************** + * Remote_Connect -- handles connecting this host to the server in an internet game * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = connections established; false = not * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +bool Server_Remote_Connect(void) +{ + int stealth; // original state of NetStealth flag + + /*------------------------------------------------------------------------ + Init network timing parameters; these values should work for both a "real" + network, and a simulated modem network (ie Kali) + ------------------------------------------------------------------------*/ + Ipx.Set_Timing ( 30, // retry 2 times per second + -1, // ignore max retries + 600); // give up after 10 seconds + + /*------------------------------------------------------------------------ + Save the original value of the NetStealth flag, so we can turn stealth + off for now (during this portion of the dialogs, we must show ourselves) + ------------------------------------------------------------------------*/ + stealth = NetStealth; + NetStealth = 0; + + /*------------------------------------------------------------------------ + The game is now "open" for joining. Close it as soon as we exit this + routine. + ------------------------------------------------------------------------*/ + NetOpen = 1; + + /*------------------------------------------------------------------------ + Read the default values from the INI file + ------------------------------------------------------------------------*/ + Read_MultiPlayer_Settings (); + + if (!Net_Fake_New_Dialog()){ + Write_MultiPlayer_Settings (); + return (false); + } + + NetOpen = 0; + NetStealth = stealth; + Write_MultiPlayer_Settings (); + return (true); +} + + +/*********************************************************************************************** + * Client_Remote_Connect -- handles connecting this client to the server in an internet game * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = connections established; false = not * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 ST : Created. * + *=============================================================================================*/ +bool Client_Remote_Connect(void) +{ + int rc; + int stealth; // original state of NetStealth flag + + /*------------------------------------------------------------------------ + Init network timing parameters; these values should work for both a "real" + network, and a simulated modem network (ie Kali) + ------------------------------------------------------------------------*/ + Ipx.Set_Timing ( 30, // retry 2 times per second + -1, // ignore max retries + 600); // give up after 10 seconds + + /*------------------------------------------------------------------------ + Save the original value of the NetStealth flag, so we can turn stealth + off for now (during this portion of the dialogs, we must show ourselves) + ------------------------------------------------------------------------*/ + stealth = NetStealth; + NetStealth = 0; + + /*------------------------------------------------------------------------ + The game is now "open" for joining. Close it as soon as we exit this + routine. + ------------------------------------------------------------------------*/ + NetOpen = 1; + + /*------------------------------------------------------------------------ + Read the default values from the INI file + ------------------------------------------------------------------------*/ + Read_MultiPlayer_Settings (); + + /*--------------------------------------------------------------------- + Pop up the network Join/New dialog + ---------------------------------------------------------------------*/ + rc = Net_Fake_Join_Dialog(); + Write_MultiPlayer_Settings (); + + NetStealth = stealth; + NetOpen = 0; + + if (rc == -1) { + return(false); + } else { + return(true); + } +} + + + +/*********************************************************************************************** + * Net_Join_Dialog -- lets user join an existing game or start a new one * + * * + * This dialog displays an edit field for the player's name, and a list of all non-stealth- * + * mode games. Clicking once on a game name displays a list of who's in that game. Clicking * + * "New" takes the user to the Net_New dialog, where he waits for other users to join his * + * game. All other input is done through this dialog. * + * * + * The dialog has several "states": * + * * + * 1) Initially, it waits for the user to fill in his/her name and then to select Join or New; * + * if New is selected, this dialog is exited. * + * * + * 2) If Join is selected, the Join & New buttons are removed, but the Cancel button remains. * + * The join request is transmitted to the game's owner, and the message "Waiting for * + * Confirmation" is displayed, until a confirmation or denial is received from the game's * + * owner. The user may click Cancel at this point to cancel the join request. * + * (Once Join is selected, the name editing field is disabled, and becomes a display-only * + * field. If cancel is selected, it reappears as an edit field.) The user can still click * + * around & see who's in which games. * + * * + * 3) If the join request is denied, the dialog re-initializes to its pre-join state; the * + * Join & New buttons reappear, & the Name field is available again. * + * * + * 4) If join confirmation is obtained, the message just changes to "Confirmed. Waiting for * + * Entry Signal." or some such nonsense. The user can still click around & see who's * + * in which games. * + * * + * Any game running in Stealth mode won't show up on this dialog. * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Network Games ³ * + * ³ ³ * + * ³ Your Name: ____________ ³ * + * ³ House: [GDI] [NOD] ³ * + * ³ Desired Color: [ ][ ][ ][ ] ³ * + * ³ ³ * + * ³ Games Players ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ³ * + * ³ ³(Bill's Game )³³ ³ Peter Parker GDI ³³ ³ * + * ³ ³ Peter Parker's Game ÃÄ´ ³ Mary Jane GDI ÃÄ´ ³ * + * ³ ³(Magnum PI's Game )³ ³ ³ JJ Jameson NOD ³ ³ ³ * + * ³ ³ ÃÄ´ ³ ÃÄ´ ³ * + * ³ ³ ³³ ³ ³³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ³ * + * ³ Scenario: Big Long Description ³ * + * ³ Starting Credits: xxxx ³ * + * ³ Count: --- Level: --- ³ * + * ³ Bases: ON Crates: ON ³ * + * ³ Tiberium: ON AI Players: ON ³ * + * ³ ³ * + * ³ [Join] [Cancel] [New] ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ * + * ³ ³ ³ ³ * + * ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ [Send Message] ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * -1 = cancel, 0 = OK, 1 = New net game requested * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +static int Net_Join_Dialog(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ +/* ###Change collision detected! C:\PROJECTS\CODE\NETDLG.CPP... */ + int d_dialog_w = 287 *factor; // dialog width + int d_dialog_h = 198*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200*factor - d_dialog_h) / 2); // centered y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 6*factor+1; // ht of 6-pt text + int d_margin1 = 5*factor; // large margin + int d_margin2 = 2*factor; // small margin + + int d_name_w = 70*factor; + int d_name_h = 9*factor; + int d_name_x = d_dialog_cx - 10*factor; + int d_name_y = d_dialog_y + d_margin1 + d_txt6_h + d_txt6_h; + + int d_gdi_w = 30*factor; + int d_gdi_h = 9*factor; + int d_gdi_x = d_dialog_cx - 10*factor; + int d_gdi_y = d_name_y + d_name_h + d_margin2; + + int d_nod_w = 30*factor; + int d_nod_h = 9*factor; + int d_nod_x = d_gdi_x + d_gdi_w; + int d_nod_y = d_name_y + d_name_h + d_margin2; + + int d_color_w = 10*factor; + int d_color_h = 9*factor; + int d_color_y = d_nod_y + d_nod_h + d_margin2; + + int d_gamelist_w = 160*factor; + int d_gamelist_h = 27*factor; + int d_gamelist_x = d_dialog_x + d_margin1; + int d_gamelist_y = d_color_y + d_color_h + d_margin1 + d_txt6_h; + + int d_playerlist_w = 106*factor; + int d_playerlist_h = 27*factor; + int d_playerlist_x = d_dialog_x + d_dialog_w - d_margin1 - d_playerlist_w; + int d_playerlist_y = d_color_y + d_color_h + d_margin1 + d_txt6_h; + + int d_msg1_y = d_gamelist_y + d_gamelist_h + d_margin1; + int d_msg2_y = d_msg1_y + d_txt6_h; + int d_msg3_y = d_msg2_y + d_txt6_h; + int d_msg4_y = d_msg3_y + d_txt6_h; + int d_msg5_y = d_msg4_y + d_txt6_h; + + int d_join_w = 40*factor; + int d_join_h = 9*factor; + int d_join_x = d_dialog_x + (d_dialog_w / 6) - (d_join_w / 2); + int d_join_y = d_msg5_y + d_txt6_h + d_margin1; + +#if (GERMAN | FRENCH) + int d_cancel_w = 50*factor; +#else + int d_cancel_w = 40*factor; +#endif + int d_cancel_h = 9*factor; + int d_cancel_x = d_dialog_cx - d_cancel_w / 2; + int d_cancel_y = d_msg5_y + d_txt6_h + d_margin1; + + int d_new_w = 40*factor; + int d_new_h = 9*factor; + int d_new_x = d_dialog_x + ((d_dialog_w * 5) / 6) - (d_new_w / 2); + int d_new_y = d_msg5_y + d_txt6_h + d_margin1; + + int d_message_w = d_dialog_w - (d_margin1 * 2); + int d_message_h = 34*factor; + int d_message_x = d_dialog_x + d_margin1; + int d_message_y = d_cancel_y + d_cancel_h + d_margin1; + + int d_send_w = 80*factor; + int d_send_h = 9*factor; + int d_send_x = d_dialog_cx - (d_send_w / 2); + int d_send_y = d_message_y + d_message_h + d_margin2; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_NAME = 100, + BUTTON_GDI, + BUTTON_NOD, + BUTTON_GAMELIST, + BUTTON_PLAYERLIST, + BUTTON_JOIN, + BUTTON_CANCEL, + BUTTON_NEW, + BUTTON_SEND, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_MESSAGE, + REDRAW_COLORS, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + bool process = true; // process while true + KeyNumType input; + int cbox_x[] = { d_gdi_x, + d_gdi_x + d_color_w, + d_gdi_x + (d_color_w * 2), + d_gdi_x + (d_color_w * 3), + d_gdi_x + (d_color_w * 4), + d_gdi_x + (d_color_w * 5)}; + + JoinStateType joinstate = JOIN_NOTHING; // current "state" of this dialog + char namebuf[MPLAYER_NAME_MAX] = {0}; // buffer for player's name + int tabs[] = {77*factor}; // tabs for player list box + int game_index = -1; // index of currently-selected game + int join_index = -1; // index of game we're joining + int rc = 0; // -1 = user cancelled, 1 = New + JoinEventType event; // event from incoming packet + int i,j; // loop counter + char txt[80]; + char const *p; + int parms_received; // 1 = game options received + int found; + + unsigned char tmp_id[MAX_PLAYERS]; // temp storage for sorting player ID's + int min_index; // for sorting player ID's + unsigned char min_id; // for sorting player ID's + unsigned char id; // connection ID + char * item; + unsigned long starttime; + + NodeNameType *who; + + int message_length; + int sent_so_far; + unsigned short magic_number; + unsigned short crc; + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + EditClass name_edt (BUTTON_NAME, + namebuf, MPLAYER_NAME_MAX, TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_name_x, d_name_y, d_name_w, d_name_h, EditClass::ALPHANUMERIC); + + TextButtonClass gdibtn(BUTTON_GDI, TXT_G_D_I, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_gdi_x, d_gdi_y, d_gdi_w, d_gdi_h); + + TextButtonClass nodbtn(BUTTON_NOD, TXT_N_O_D, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_nod_x, d_nod_y, d_nod_w, d_nod_h); + + ListClass gamelist(BUTTON_GAMELIST, + d_gamelist_x, d_gamelist_y, d_gamelist_w, d_gamelist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + ColorListClass playerlist(BUTTON_PLAYERLIST, + d_playerlist_x, d_playerlist_y, d_playerlist_w, d_playerlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + TextButtonClass joinbtn(BUTTON_JOIN, TXT_JOIN, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#ifdef FRENCH + d_join_x, d_join_y); +#else + d_join_x, d_join_y, d_join_w, d_join_h); +#endif + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_cancel_x, d_cancel_y); +//#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +//#endif + + TextButtonClass newbtn(BUTTON_NEW, TXT_NEW, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_new_x, d_new_y, d_new_w, d_new_h); + + TextButtonClass sendbtn(BUTTON_SEND, TXT_SEND_MESSAGE, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_send_x, d_send_y); +//#else + d_send_x, d_send_y, d_send_w, d_send_h); +//#endif + + playerlist.Set_Tabs(tabs); + + /* + ----------------------------- Various Inits ------------------------------ + */ + MPlayerColorIdx = MPlayerPrefColor; // init my preferred color + strcpy (namebuf, MPlayerName); // set my name + name_edt.Set_Text(namebuf,MPLAYER_NAME_MAX); + name_edt.Set_Color (MPlayerTColors[MPlayerColorIdx]); + + playerlist.Set_Selected_Style(ColorListClass::SELECT_NONE); + + if (MPlayerHouse==HOUSE_GOOD) { + gdibtn.Turn_On(); + } else { + nodbtn.Turn_On(); + } + + Fancy_Text_Print("", 0, 0, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Messages.Init (d_message_x + 2, d_message_y + 2, 4, + MAX_MESSAGE_LENGTH, d_txt6_h); + + /* + --------------------------- Send network query --------------------------- + */ + Send_Join_Queries (game_index, 1, 0); + + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + /* + ---------------------------- Init Mono Output ---------------------------- + */ + #if(SHOW_MONO) + Ipx.Configure_Debug(-1, sizeof (GlobalHeaderType), + sizeof(NetCommandType), GlobalPacketNames, 11); + Ipx.Mono_Debug_Print(-1,1); + #endif + while (Get_Mouse_State() > 0) Show_Mouse(); + + /* + ---------------------------- Processing loop ----------------------------- + */ + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + #if(SHOW_MONO) + Ipx.Mono_Debug_Print(-1,0); + #endif + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_JOIN_NETWORK_GAME, d_dialog_x, d_dialog_y, d_dialog_w); + + Fancy_Text_Print(TXT_YOUR_NAME, + d_name_x - 5, d_name_y + 1, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_SIDE_COLON, + d_gdi_x - 5, d_gdi_y + 1, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_COLOR_COLON, + cbox_x[0] - 5, d_color_y + 1, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_GAMES, + d_gamelist_x + (d_gamelist_w / 2), d_gamelist_y - d_txt6_h, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_PLAYERS, + d_playerlist_x + (d_playerlist_w / 2), d_playerlist_y - d_txt6_h, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................... + Join-state-specific labels: + ...............................................................*/ + if (joinstate > JOIN_NOTHING) { + Fancy_Text_Print(namebuf, d_name_x, d_name_y + 1, CC_GREEN, + TBLACK, TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + if (MPlayerHouse==HOUSE_GOOD) { + Fancy_Text_Print(TXT_G_D_I, d_gdi_x, d_gdi_y + 1, CC_GREEN, + TBLACK, TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } else { + Fancy_Text_Print(TXT_N_O_D, d_gdi_x, d_gdi_y + 1, CC_GREEN, + TBLACK, TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + } + + /* + .................... Rebuild the button list .................... + */ + cancelbtn.Zap(); + gamelist.Zap(); + playerlist.Zap(); + gdibtn.Zap(); + nodbtn.Zap(); + name_edt.Zap(); + joinbtn.Zap(); + newbtn.Zap(); + sendbtn.Zap(); + + commands = &cancelbtn; + gamelist.Add_Tail(*commands); + playerlist.Add_Tail(*commands); + /*............................................................... + Only add the name edit field, the House, Join & New buttons if + we're doing nothing, or we've just been rejected. + ...............................................................*/ + if (joinstate <= JOIN_NOTHING) { + gdibtn.Add_Tail(*commands); + nodbtn.Add_Tail(*commands); + name_edt.Add_Tail(*commands); + joinbtn.Add_Tail(*commands); + newbtn.Add_Tail(*commands); + } + if (joinstate == JOIN_CONFIRMED) + sendbtn.Add_Tail(*commands); + } + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Draw_All(); + } + + /*.................................................................. + Draw the color boxes + ..................................................................*/ + if (display >= REDRAW_COLORS) { + for (i = 0; i < MAX_MPLAYER_COLORS; i++) { + LogicPage->Fill_Rect (cbox_x[i] + 1, d_color_y + 1, + cbox_x[i] + 1 + d_color_w - 2, d_color_y + 1 + d_color_h - 2, + MPlayerGColors[i]); + + if (i == MPlayerColorIdx) { + Draw_Box(cbox_x[i], d_color_y, d_color_w, d_color_h, + BOXSTYLE_GREEN_DOWN, false); + } else { + Draw_Box(cbox_x[i], d_color_y, d_color_w, d_color_h, + BOXSTYLE_GREEN_RAISED, false); + } + } + } + + /*.................................................................. + Draw the message: + - Erase an old message first + - If we're in a game, print the game options (if they've been + received) + - If we've been rejected from a game, print that message + ..................................................................*/ + if (display >= REDRAW_MESSAGE) { + Draw_Box(d_message_x, d_message_y, d_message_w, d_message_h, + BOXSTYLE_GREEN_BORDER, true); + Messages.Draw(); + + LogicPage->Fill_Rect( d_dialog_x + 2, + d_msg1_y, + d_dialog_x + d_dialog_w - 4, + d_msg5_y + d_txt6_h, + BLACK); + + if (joinstate==JOIN_CONFIRMED && parms_received) { + /*............................................................ + Scenario title + ............................................................*/ + p = Text_String(TXT_SCENARIO_COLON); + if (ScenarioIdx != -1) { + sprintf(txt,"%s %s",p, MPlayerScenarios[ScenarioIdx]); + + Fancy_Text_Print (txt, d_dialog_cx, + d_msg1_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW | TPF_CENTER); + } else { + sprintf(txt,"%s %s",p,Text_String(TXT_NOT_FOUND)); + + Fancy_Text_Print (txt, d_dialog_cx, + d_msg1_y, CC_NOD_COLOR, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW | TPF_CENTER); + } + + /*............................................................ + # of credits + ............................................................*/ + p = Text_String(TXT_START_CREDITS_COLON); + sprintf(txt, "%s %d", p, MPlayerCredits); + Fancy_Text_Print (txt, d_dialog_cx, + d_msg2_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW | TPF_CENTER); + + /*............................................................ + Count & Level values + ............................................................*/ + p = Text_String(TXT_COUNT); + sprintf(txt,"%s %d",p,MPlayerUnitCount); + Fancy_Text_Print (txt, + d_dialog_x + (d_dialog_w / 4) - String_Pixel_Width(p), + d_msg3_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + p = Text_String(TXT_LEVEL); + if (BuildLevel <= MPLAYER_BUILD_LEVEL_MAX) { + sprintf(txt, "%s %d", p, BuildLevel); + } else { + sprintf(txt, "%s **", p); + } + Fancy_Text_Print (txt, + d_dialog_x + d_dialog_w - (d_dialog_w / 4) - String_Pixel_Width(p), + d_msg3_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Bases + ............................................................*/ + p = Text_String(TXT_BASES_COLON); + if (MPlayerBases) { + sprintf(txt,"%s %s",p,Text_String(TXT_ON)); + } else { + sprintf(txt,"%s %s",p,Text_String(TXT_OFF)); + } + Fancy_Text_Print (txt, + d_dialog_x + (d_dialog_w / 4) - String_Pixel_Width(p), + d_msg4_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Tiberium + ............................................................*/ + p = Text_String(TXT_TIBERIUM_COLON); + if (MPlayerTiberium) { + sprintf(txt,"%s %s",p,Text_String(TXT_ON)); + } else { + sprintf(txt,"%s %s",p,Text_String(TXT_OFF)); + } + + Fancy_Text_Print (txt, + d_dialog_x + (d_dialog_w / 4) - String_Pixel_Width(p), + d_msg5_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Goody boxes + ............................................................*/ + p = Text_String(TXT_CRATES_COLON); + if (MPlayerGoodies) { + sprintf(txt,"%s %s",p,Text_String(TXT_ON)); + } else { + sprintf(txt,"%s %s",p,Text_String(TXT_OFF)); + } + + Fancy_Text_Print (txt, + d_dialog_x + d_dialog_w - (d_dialog_w / 4) - String_Pixel_Width(p), + d_msg4_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Computer AI players + ............................................................*/ + if (Special.IsCaptureTheFlag) { + p = Text_String(TXT_CAPTURE_THE_FLAG_COLON); + sprintf(txt,"%s %s",p,Text_String(TXT_ON)); + } else { + p = Text_String(TXT_AI_PLAYERS_COLON); + if (MPlayerGhosts) { + sprintf(txt,"%s %s",p,Text_String(TXT_ON)); + } else { + sprintf(txt,"%s %s",p,Text_String(TXT_OFF)); + } + } + Fancy_Text_Print (txt, + d_dialog_x + d_dialog_w - (d_dialog_w / 4) - String_Pixel_Width(p), + d_msg5_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + } else { + + /*............................................................... + Rejection notice + ...............................................................*/ + if (joinstate==JOIN_REJECTED) { + Fancy_Text_Print(TXT_REQUEST_DENIED, + d_dialog_cx, d_msg3_y, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + } + } + + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + /*------------------------------------------------------------------ + User clicks on a color button: + - If we've joined a game, don't allow a new color selection + - otherwise, select that color + ------------------------------------------------------------------*/ + case KN_LMOUSE: + if (joinstate > JOIN_NOTHING) + break; + if (_Kbd->MouseQX > cbox_x[0] && + _Kbd->MouseQX < (cbox_x[MAX_MPLAYER_COLORS - 1] + d_color_w) && + _Kbd->MouseQY > d_color_y && + _Kbd->MouseQY < (d_color_y + d_color_h)) { + MPlayerPrefColor = (_Kbd->MouseQX - cbox_x[0]) / d_color_w; + MPlayerColorIdx = MPlayerPrefColor; + + name_edt.Set_Color (MPlayerTColors[MPlayerColorIdx]); + name_edt.Flag_To_Redraw(); + + display = REDRAW_COLORS; + } + break; + + /*------------------------------------------------------------------ + User clicks on the game list: + - If we've joined a game, don't allow the selected item to change; + otherwise: + - Clear the player list + - Send an immediate player query + ------------------------------------------------------------------*/ + case (BUTTON_GAMELIST | KN_BUTTON): + if (joinstate==JOIN_CONFIRMED) { + gamelist.Set_Selected_Index(game_index); + } else { + if (gamelist.Current_Index() != game_index) { + Clear_Player_List (&playerlist); + game_index = gamelist.Current_Index(); + Send_Join_Queries (game_index, 0, 1); + } + } + break; + + /*------------------------------------------------------------------ + House Buttons: set the player's desired House + ------------------------------------------------------------------*/ + case (BUTTON_GDI | KN_BUTTON): + MPlayerHouse = HOUSE_GOOD; + gdibtn.Turn_On(); + nodbtn.Turn_Off(); + break; + + case (BUTTON_NOD | KN_BUTTON): + MPlayerHouse = HOUSE_BAD; + gdibtn.Turn_Off(); + nodbtn.Turn_On(); + break; + + /*------------------------------------------------------------------ + JOIN: send a join request packet & switch to waiting-for-confirmation + mode. (Request_To_Join fills in MPlayerName with my namebuf.) + ------------------------------------------------------------------*/ + case (BUTTON_JOIN | KN_BUTTON): + name_edt.Clear_Focus(); + name_edt.Flag_To_Redraw(); + + join_index = gamelist.Current_Index(); + parms_received = 0; + if (Request_To_Join (namebuf, join_index, &playerlist, MPlayerHouse, + MPlayerColorIdx)) { + joinstate = JOIN_WAIT_CONFIRM; + } else { + display = REDRAW_ALL; + } + break; + + /*------------------------------------------------------------------ + CANCEL: send a SIGN_OFF + - If we're part of a game, stay in this dialog; otherwise, exit + ------------------------------------------------------------------*/ + case (KN_ESC): + if (Messages.Get_Edit_Buf() != NULL) { + Messages.Input(input); + display = REDRAW_MESSAGE; + break; + } + case (BUTTON_CANCEL | KN_BUTTON): + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy(GPacket.Name,MPlayerName); + + /*............................................................... + If we're joined to a game, make extra sure the other players in + that game know I'm exiting; send my SIGN_OFF as an ack-required + packet. Do not send this packet to myself (index 0). + ...............................................................*/ + if (joinstate == JOIN_CONFIRMED) { + // + // Remove myself from the player list box + // + if (playerlist.Count()) { // added: BRR 6/14/96 + item = (char *)(playerlist.Get_Item(0)); + playerlist.Remove_Item(item); + delete [] item; + playerlist.Flag_To_Redraw(); + } + + // + // Remove myself from the Players list + // + if (Players.Count()) { // added: BRR 6/14/96 + who = Players[0]; + Players.Delete(0); + delete who; + } + + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Players[i]->Address)); + Ipx.Service(); + } + } + + /*............................................................... + Now broadcast my SIGN_OFF so other players looking at this game + know I'm leaving. + ...............................................................*/ + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + + if (IsBridge) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + if (joinstate != JOIN_CONFIRMED) { + process = false; + rc = -1; + } else { + MPlayerGameName[0] = 0; + joinstate = JOIN_NOTHING; + display = REDRAW_ALL; + } + break; + + /*------------------------------------------------------------------ + NEW: bail out with return code 1 + ------------------------------------------------------------------*/ + case (BUTTON_NEW | KN_BUTTON): + /* + .................. Force user to enter a name ................... + */ + if (strlen(namebuf)==0) { + CCMessageBox().Process(TXT_NAME_ERROR); + display = REDRAW_ALL; + break; + } + /* + ..................... Ensure name is unique ..................... + */ + found = 0; + for (i = 0; i < Games.Count(); i++) { + if (!stricmp(Games[i]->Name, namebuf)) { + found = 1; + CCMessageBox().Process (TXT_GAMENAME_MUSTBE_UNIQUE); + display = REDRAW_ALL; + break; + } + } + if (found) + break; + /* + .................... Save player & game name .................... + */ + strcpy(MPlayerName,namebuf); + strcpy(MPlayerGameName,namebuf); + + name_edt.Clear_Focus(); + name_edt.Flag_To_Redraw(); + + rc = 1; + process = false; + break; + + /*------------------------------------------------------------------ + Default: manage the inter-player messages + ------------------------------------------------------------------*/ + default: + /*............................................................... + F4/SEND/'M' = edit a message + ...............................................................*/ + if (Messages.Get_Edit_Buf()==NULL) { + if ( (input == KN_M && joinstate==JOIN_CONFIRMED) || + input==(BUTTON_SEND | KN_BUTTON) || input == KN_F4) { + memset (txt, 0, 80); + + strcpy(txt,Text_String(TXT_TO_ALL)); // "To All:" + + Messages.Add_Edit (MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, txt, d_message_w-70*factor); + + if (joinstate <= JOIN_NOTHING) { + name_edt.Clear_Focus(); + name_edt.Flag_To_Redraw(); + } + + display = REDRAW_MESSAGE; + + break; + } + } else + + /*............................................................... + If we're already editing a message and the user clicks on + 'Send', translate our input to a Return so Messages.Input() will + work properly. + ...............................................................*/ + if (input==(BUTTON_SEND | KN_BUTTON)) { + input = KN_RETURN; + } + + /*............................................................... + Manage the message system (get rid of old messages) + ...............................................................*/ + if (Messages.Manage()) { + display = REDRAW_MESSAGE; + } + + /*............................................................... + Service keyboard input for any message being edited. + ...............................................................*/ + i = Messages.Input(input); + + /*............................................................... + If 'Input' returned 1, it means refresh the message display. + ...............................................................*/ + if (i==1) { + Messages.Draw(); + } else { + + /*............................................................... + If 'Input' returned 2, it means redraw the message display. + ...............................................................*/ + if (i==2) { + display = REDRAW_MESSAGE; + } else { + + /*............................................................... + If 'input' returned 3, it means send the current message. + ...............................................................*/ + if (i==3) { + long actual_message_size; + char *the_string; + + sent_so_far = 0; + magic_number = MESSAGE_HEAD_MAGIC_NUMBER; + message_length = strlen(Messages.Get_Edit_Buf()); + crc = (unsigned short) + (Calculate_CRC(Messages.Get_Edit_Buf(),message_length) &0xffff); + + while ( sent_so_far < message_length ){ + GPacket.Command = NET_MESSAGE; + strcpy (GPacket.Name, namebuf); + memcpy (GPacket.Message.Buf, Messages.Get_Edit_Buf()+sent_so_far, COMPAT_MESSAGE_LENGTH-5); + + /* + ** Steve I's stuff for splitting message on word boundries + */ + actual_message_size = COMPAT_MESSAGE_LENGTH - 5; + + /* Start at the end of the message and find a space with 10 chars. */ + the_string = GPacket.Message.Buf; + while ( (COMPAT_MESSAGE_LENGTH -5) -actual_message_size < 10 && + the_string[actual_message_size] != ' '){ + --actual_message_size; + } + if ( the_string[actual_message_size] == ' ' ){ + + /* Now delete the extra characters after the space (they musnt print) */ + for ( int i=0 ; i< (COMPAT_MESSAGE_LENGTH-5) - actual_message_size; i++ ){ + the_string[i + actual_message_size] = 0xff; + } + }else{ + actual_message_size = COMPAT_MESSAGE_LENGTH - 5; + } + + + *(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-5) = 0; + *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-4)) = magic_number; + *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-2)) = crc; + GPacket.Message.ID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + GPacket.Message.NameCRC = Compute_Name_CRC(MPlayerGameName); + + /*.................................................................. + Send the message to every player in our player list. The local + system will also receive this message, since it's in the Player list. + ..................................................................*/ + if (joinstate == JOIN_CONFIRMED) { + for (i = 1; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, + sizeof(GlobalPacketType), 1, &(Players[i]->Address)); + Ipx.Service(); + } + + sprintf(txt,Text_String (TXT_FROM), MPlayerName, GPacket.Message.Buf); + Messages.Add_Message (txt, + MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, magic_number, crc); + } + else { + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, + sizeof(GlobalPacketType), 1, &(Players[i]->Address)); + Ipx.Service(); + } + } + magic_number++; + sent_so_far += actual_message_size; //COMPAT_MESSAGE_LENGTH-5; + } + } + } + } + break; + } + + /*--------------------------------------------------------------------- + Resend our query packets + ---------------------------------------------------------------------*/ + Send_Join_Queries(game_index, 0, 0); + + /*--------------------------------------------------------------------- + Process incoming packets + ---------------------------------------------------------------------*/ + event = Get_Join_Responses(&joinstate, &gamelist, &playerlist, + join_index); + /*..................................................................... + If we've changed state, redraw everything; if we're starting the game, + break out of the loop. If we've just joined, send out a player query + so I'll get added to the list instantly. + .....................................................................*/ + if (event == EV_STATE_CHANGE) { + display = REDRAW_ALL; + if (joinstate==JOIN_GAME_START) { + rc = 0; + process = false; + } else { + + /*.................................................................. + If we're newly-confirmed, immediately send out a player query + ..................................................................*/ + if (joinstate==JOIN_CONFIRMED) { + + Clear_Player_List(&playerlist); + + item = new char [MPLAYER_NAME_MAX + 4]; + if (MPlayerHouse==HOUSE_GOOD) { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_G_D_I)); + } else { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_N_O_D)); + } + playerlist.Add_Item (item, MPlayerTColors[MPlayerColorIdx]); + + who = new NodeNameType; + strcpy(who->Name, MPlayerName); + who->Address = IPXAddressClass(); + who->Player.House = MPlayerHouse; + who->Player.Color = MPlayerColorIdx; + Players.Add (who); + + Send_Join_Queries (game_index, 0, 1); + } else { + + /*.................................................................. + If we've been rejected, clear any messages we may have been typing. + ..................................................................*/ + if (joinstate==JOIN_REJECTED) { + + // + // Remove myself from the player list box + // + if (playerlist.Count()) { // added: BRR 6/14/96 + item = (char *)(playerlist.Get_Item(0)); + if (item){ + playerlist.Remove_Item(item); + delete [] item; + playerlist.Flag_To_Redraw(); + } + } + + // + // Remove myself from the Players list + // + if (Players.Count()){ + who = Players[0]; + Players.Delete(0); + delete who; + } + + Messages.Init (d_message_x + 2, d_message_y + 2, 4, + MAX_MESSAGE_LENGTH, d_txt6_h); + } + } + } + } else + + /*..................................................................... + If a new game is detected, and it's the first game on our list, + automatically send out a player query for that game. + .....................................................................*/ + if (event == EV_NEW_GAME && gamelist.Count()==1) { + gamelist.Set_Selected_Index(0); + game_index = gamelist.Current_Index(); + Send_Join_Queries (game_index, 0, 1); + } else + + /*..................................................................... + If the game options have changed, print them. + .....................................................................*/ + if (event == EV_GAME_OPTIONS) { + parms_received = 1; + display = REDRAW_MESSAGE; + } else + + /*..................................................................... + Draw an incoming message + .....................................................................*/ + if (event == EV_MESSAGE) { + display = REDRAW_MESSAGE; + } else + + /*..................................................................... + A game before the one I've selected is gone, so we have a new index now. + 'game_index' must be kept set to the currently-selected list item, so + we send out queries for the currently-selected game. It's therefore + imperative that we detect any changes to the game list. + If we're joined in a game, we must decrement our game_index to keep + it aligned with the game we're joined to. + .....................................................................*/ + if (event == EV_GAME_SIGNOFF) { + if (joinstate==JOIN_CONFIRMED) { + game_index--; + join_index--; + gamelist.Set_Selected_Index(join_index); + } else { + gamelist.Flag_To_Redraw(); + Clear_Player_List(&playerlist); + game_index = gamelist.Current_Index(); + Send_Join_Queries (game_index, 0, 1); + } + } + + /*--------------------------------------------------------------------- + Service the Ipx connections + ---------------------------------------------------------------------*/ + Ipx.Service(); + + /*--------------------------------------------------------------------- + Clean out the Game List; if an old entry is found: + - Remove it + - Clear the player list + - Send queries for the new selected game, if there is one + ---------------------------------------------------------------------*/ + for (i = 0; i < Games.Count(); i++) { + if (TickCount.Time() - Games[i]->Game.LastTime > 400) { + Games.Delete(Games[i]); + item = (char *)(gamelist.Get_Item (i)); + gamelist.Remove_Item (item); + delete [] item; + if (i <= game_index) { + gamelist.Flag_To_Redraw(); + Clear_Player_List(&playerlist); + game_index = gamelist.Current_Index(); + Send_Join_Queries (game_index, 0, 1); + } + } + } + + /*--------------------------------------------------------------------- + Service the sounds & score; GameActive must be false at this point, + so Call_Back() doesn't intercept global messages from me! + ---------------------------------------------------------------------*/ + Call_Back(); + } + + /*------------------------------------------------------------------------ + Establish connections with all other players. + ------------------------------------------------------------------------*/ + if (rc == 0) { + /*..................................................................... + If the other guys are playing a scenario I don't have (sniff), I can't + play. Try to bail gracefully. + .....................................................................*/ + if (ScenarioIdx==-1) { + CCMessageBox().Process (TXT_UNABLE_PLAY_WAAUGH); + + // + // Remove myself from the player list box + // + if (playerlist.Count()) { // added: BRR 6/14/96 + item = (char *)(playerlist.Get_Item(0)); + playerlist.Remove_Item(item); + delete [] item; + playerlist.Flag_To_Redraw(); + } + + // + // Remove myself from the Players list + // + if (Players.Count()) { // added: BRR 6/14/96 + who = Players[0]; + Players.Delete(0); + delete who; + } + + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy (GPacket.Name, MPlayerName); + + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Players[i]->Address)); + Ipx.Service(); + } + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + + if (IsBridge) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + rc = -1; + + } else { + + /*.................................................................. + Set the number of players in this game, and my ID + ..................................................................*/ + MPlayerCount = Players.Count(); + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + /*.................................................................. + Get the scenario number + ..................................................................*/ + Scenario = MPlayerFilenum[ScenarioIdx]; + + /*.................................................................. + Form connections with all other players. Form the IPX Connection ID + from the player's Color and House. This will let us extract any + player's color & house at any time. Fill in 'tmp_id' while we're + doing this. + ..................................................................*/ + for (i = 0; i < Players.Count(); i++) { + + /*............................................................... + Only create the connection if it's not myself! + ...............................................................*/ + if (strcmp (MPlayerName, Players[i]->Name)) { + id = Build_MPlayerID(Players[i]->Player.Color, + Players[i]->Player.House); + + tmp_id[i] = id; + + Ipx.Create_Connection((int)id, Players[i]->Name, &(Players[i]->Address) ); + } else { + tmp_id[i] = MPlayerLocalID; + } + } + + /*.................................................................. + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + ..................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + min_index = 0; + min_id = 0xff; + for (j = 0; j < MPlayerCount; j++) { + if (tmp_id[j] < min_id) { + min_id = tmp_id[j]; + min_index = j; + } + } + MPlayerID[i] = tmp_id[min_index]; + tmp_id[min_index] = 0xff; + } + /*.................................................................. + Fill in the array of player names, including my own. + ..................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID[i] == MPlayerLocalID) { + strcpy (MPlayerNames[i], MPlayerName); + } else { + strcpy (MPlayerNames[i], Ipx.Connection_Name(MPlayerID[i])); + } + } + } + /*--------------------------------------------------------------------- + Wait a while, polling the IPX service routines, to give our ACK + a chance to get to the other system. If he doesn't get our ACK, he'll + be waiting the whole time we load MIX files. + ---------------------------------------------------------------------*/ + i = MAX(Ipx.Global_Response_Time() * 2, 60); + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < i) { + Ipx.Service(); + } + } + + /*------------------------------------------------------------------------ + Init network timing values, using previous response times as a measure + of what our retry delta & timeout should be. + ------------------------------------------------------------------------*/ + Ipx.Set_Timing (Ipx.Global_Response_Time() + 2, -1, + Ipx.Global_Response_Time() * 4); + + /*------------------------------------------------------------------------ + Clear all lists + ------------------------------------------------------------------------*/ + Clear_Game_List(&gamelist); + Clear_Player_List(&playerlist); + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + return(rc); +} + + +/*************************************************************************** + * Clear_Game_List -- Clears the game-name listbox & 'Games' Vector * + * * + * Assumes each entry in 'Games' & the list box have been allocated * + * separately. * + * * + * INPUT: * + * gamelist ptr to list box * + * * + * OUTPUT: * + * none * + * * + * WARNINGS: * + * none * + * * + * HISTORY: * + *=========================================================================*/ +static void Clear_Game_List (ListClass *gamelist) +{ + char * item; + int i; + + /*------------------------------------------------------------------------ + Clear the list box + ------------------------------------------------------------------------*/ + while (gamelist->Count()) { + item = (char *)(gamelist->Get_Item (0)); + gamelist->Remove_Item(item); + delete [] item; + } + gamelist->Flag_To_Redraw(); + + /*------------------------------------------------------------------------ + Clear the 'Games' Vector + ------------------------------------------------------------------------*/ + for (i = 0; i < Games.Count(); i++) + delete Games[i]; + + Games.Clear(); + +} /* end of Clear_Game_List */ + + +/*************************************************************************** + * Clear_Player_List -- Clears the player-name listbox & Vector * + * * + * Assumes each entry in 'Players' & the list box have been allocated * + * separately. * + * * + * INPUT: * + * playerlist ptr to list box * + * * + * OUTPUT: * + * none * + * * + * WARNINGS: * + * none * + * * + * HISTORY: * + *=========================================================================*/ +static void Clear_Player_List (ListClass *playerlist) +{ + char * item; + int i; + + /*------------------------------------------------------------------------ + Clear the list box + ------------------------------------------------------------------------*/ + while (playerlist->Count()) { + item = (char *)(playerlist->Get_Item(0)); + playerlist->Remove_Item(item); + delete [] item; + } + playerlist->Flag_To_Redraw(); + + /*------------------------------------------------------------------------ + Clear the 'Players' Vector + ------------------------------------------------------------------------*/ + for (i = 0; i < Players.Count(); i++) + delete Players[i]; + + Players.Clear(); + +} /* end of Clear_Player_List */ + + +/*************************************************************************** + * Request_To_Join -- Sends a JOIN request packet to game owner * + * * + * Regardless of the return code, the Join Dialog will need to be redrawn * + * after calling this routine. * + * * + * INPUT: * + * playername player's name * + * join_index index of game we're joining * + * playerlist listbox containing other players' names * + * house requested house * + * color requested color * + * * + * OUTPUT: * + * 1 = Packet sent, 0 = wasn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + *=========================================================================*/ +static int Request_To_Join (char *playername, int join_index, ListClass *playerlist, + HousesType house, int color) +{ + int i; + + playerlist = playerlist; // shaddup, Mr stupid compiler! + + /* + --------------------------- Validate join_index -------------------------- + */ + if ( (Games.Count()==0) || join_index > Games.Count() || join_index < 0) { + CCMessageBox().Process (TXT_NOTHING_TO_JOIN); + return(false); + } + + /* + ----------------------- Force user to enter a name ----------------------- + */ + if (strlen(playername)==0) { + CCMessageBox().Process (TXT_NAME_ERROR); + return(false); + } + + /* + ------------------------- The game must be open -------------------------- + */ + if (!Games[join_index]->Game.IsOpen) { + CCMessageBox().Process(TXT_GAME_IS_CLOSED); + return (false); + } + + /* + ------------------------ Make sure name is unique ------------------------ + */ + for (i = 0; i < Players.Count(); i++) { + if (!stricmp(playername, Players[i]->Name)) { + CCMessageBox().Process (TXT_NAME_MUSTBE_UNIQUE); + return(false); + } + } + + /* + ----------------------------- Check version #'s -------------------------- + */ + int v; +#ifdef PATCH + if (IsV107) { + v = 1; + } else { + v = 2; + } +#else + v = Version_Number(); +#endif + if (Games[join_index]->Game.Version > v) { + CCMessageBox().Process (TXT_YOURGAME_OUTDATED); + return(false); + } else { + if (Games[join_index]->Game.Version < v) { + CCMessageBox().Process (TXT_DESTGAME_OUTDATED); + return(false); + } + } + + /* + ----------------------------- Save game name ----------------------------- + */ + strcpy (MPlayerName,playername); + + /* + ----------------------- Send packet to game's owner ---------------------- + */ + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_QUERY_JOIN; + strcpy (GPacket.Name, MPlayerName); + GPacket.PlayerInfo.House = house; + GPacket.PlayerInfo.Color = color; + + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Games[join_index]->Address)); + + return(true); +} + + +/*********************************************************************************************** + * Send_Join_Queries -- sends queries for the Join Dialog * + * * + * This routine [re]sends the queries related to the Join Dialog: * + * - NET_QUERY_GAME * + * - NET_QUERY_PLAYER for the game currently selected (if there is one) * + * * + * The queries are "staggered" in time so they aren't all sent at once; otherwise, we'd * + * be inundated with reply packets & we'd miss some (even though the replies will require * + * ACK's). * + * * + * INPUT: * + * curgame index of currently-selected game; -1 = none * + * gamenow if 1, will immediately send the game query * + * playernow if 1, will immediately send the player query for currently-selected game * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + * 04/15/1995 BRR : Created. * + *=============================================================================================*/ +static void Send_Join_Queries(int curgame, int gamenow, int playernow) +{ + static int lasttime1 = 0; // time since last Game query sent out + static int lasttime2 = 0; // time since last Player query sent out + + /*------------------------------------------------------------------------ + Send the game-name query if the time has expired, or we're told to do + it right now + ------------------------------------------------------------------------*/ + if ( (TickCount.Time() - lasttime1 > 120) || gamenow) { + lasttime1 = TickCount.Time(); + + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_QUERY_GAME; + + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, NULL); + + /*..................................................................... + If the user specified a remote server address, broadcast over that + network, too. + .....................................................................*/ + if (IsBridge) + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + + /*------------------------------------------------------------------------ + Send the player query for the game currently clicked on, if the time has + expired and there is a currently-selected game, or we're told to do it + right now + ------------------------------------------------------------------------*/ + if ( (curgame != -1) && curgame < Games.Count() && + ((TickCount.Time() - lasttime2 > 35) || playernow) ) { + lasttime2 = TickCount.Time(); + + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_QUERY_PLAYER; + strcpy (GPacket.Name, Games[curgame]->Name); + + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, NULL); + + /*..................................................................... + If the user specified a remote server address, broadcast over that + network, too. + .....................................................................*/ + if (IsBridge) + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + +} /* end of Send_Join_Queries */ + + +/*********************************************************************************************** + * Get_Join_Responses -- sends queries for the Join Dialog * + * * + * This routine polls the Global Channel to see if there are any incoming packets; * + * if so, it processes them. This routine can change the state of the Join Dialog, or * + * the contents of the list boxes, based on what the packet is. * + * * + * The list boxes are passed in as pointers; they can't be made globals, because they * + * can't be constructed, because they require shape pointers to the arrow buttons, and * + * the mix files won't have been initialized when the global variables' constructors are * + * called. * + * * + * This routine sets the globals * + * MPlayerHouse (from NET_CONFIRM_JOIN) * + * MPlayerColorIdx (from NET_CONFIRM_JOIN) * + * MPlayerBases (from NET_GAME_OPTIONS) * + * MPlayerTiberium (from NET_GAME_OPTIONS) * + * MPlayerGoodies (from NET_GAME_OPTIONS) * + * MPlayerGhosts (from NET_GAME_OPTIONS) * + * ScenarioIdx (from NET_GAME_OPTIONS; -1 = scenario not found) * + * * + * INPUT: * + * joinstate current state of Join Dialog * + * gamelist list box containing game names * + * playerlist list box containing player names for the currently-selected game * + * join_index index of the game we've joined or are asking to join * + * * + * OUTPUT: * + * Event that occurred * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + * 04/15/1995 BRR : Created. * + *=============================================================================================*/ +static JoinEventType Get_Join_Responses(JoinStateType *joinstate, ListClass *gamelist, + ColorListClass *playerlist, int join_index) +{ + int rc; + char * item; // general-purpose string + NodeNameType *who; // node to add to Games or Players + int i; + int found; + JoinEventType retcode = EV_NONE; + char txt[80]; + int color; + unsigned short magic_number; + unsigned short crc; + + /*------------------------------------------------------------------------ + If there is no incoming packet, just return + ------------------------------------------------------------------------*/ + rc = Ipx.Get_Global_Message (&GPacket, &GPacketlen, &GAddress, &GProductID); + if (!rc || GProductID != IPXGlobalConnClass::COMMAND_AND_CONQUER) + return(EV_NONE); + + /*------------------------------------------------------------------------ + If we're joined in a game, handle the packet in a standard way; otherwise, + don't answer standard queries. + ------------------------------------------------------------------------*/ + if ( (*joinstate)==JOIN_CONFIRMED && + Process_Global_Packet(&GPacket,&GAddress)!=0) + return(EV_NONE); + + /*------------------------------------------------------------------------ + NET_ANSWER_GAME: Another system is answering our GAME query, so add that + system to our list box if it's new. + ------------------------------------------------------------------------*/ + if (GPacket.Command==NET_ANSWER_GAME) { + /*..................................................................... + See if this name is unique + .....................................................................*/ + retcode = EV_NONE; + found = 0; + for (i = 0; i < Games.Count(); i++) { + if (!strcmp(Games[i]->Name, GPacket.Name)) { + found = 1; + /*............................................................... + If name was found, update the node's time stamp & IsOpen flag. + ...............................................................*/ + Games[i]->Game.LastTime = TickCount.Time(); + if (Games[i]->Game.IsOpen != GPacket.GameInfo.IsOpen) { + item = (char *)gamelist->Get_Item(i); + if (GPacket.GameInfo.IsOpen) { + sprintf(item,Text_String(TXT_THATGUYS_GAME),GPacket.Name); + } else { + sprintf(item,Text_String(TXT_THATGUYS_GAME_BRACKET),GPacket.Name); + } + Games[i]->Game.IsOpen = GPacket.GameInfo.IsOpen; + gamelist->Flag_To_Redraw(); + /*............................................................ + If this game has gone from closed to open, copy the responder's + address into our Game slot, since the guy responding to this + must be game owner. + ............................................................*/ + if (Games[i]->Game.IsOpen) + Games[i]->Address = GAddress; + } + break; + } + } + /*..................................................................... + name not found (or addresses are different); add it to 'Games' + .....................................................................*/ + if (found==0) { + /*.................................................................. + Create a new node structure, fill it in, add it to 'Games' + ..................................................................*/ + who = new NodeNameType; + strcpy(who->Name, GPacket.Name); + who->Address = GAddress; + who->Game.Version = GPacket.GameInfo.Version; + who->Game.IsOpen = GPacket.GameInfo.IsOpen; + who->Game.LastTime = TickCount.Time(); + Games.Add (who); + + /*.................................................................. + Create a string for "xxx's Game", leaving room for brackets around + the string if it's a closed game + ..................................................................*/ + item = new char [MPLAYER_NAME_MAX + 9]; + if (GPacket.GameInfo.IsOpen) { + sprintf(item,Text_String(TXT_THATGUYS_GAME),GPacket.Name); + } else { + sprintf(item,Text_String(TXT_THATGUYS_GAME_BRACKET),GPacket.Name); + } + gamelist->Add_Item(item); + + retcode = EV_NEW_GAME; + } + } + + /*------------------------------------------------------------------------ + NET_ANSWER_PLAYER: Another system is answering our PLAYER query, so add it + to our player list box & the Player Vector if it's new + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_ANSWER_PLAYER) { + /*..................................................................... + See if this name is unique + .....................................................................*/ + retcode = EV_NONE; + found = 0; + for (i = 0; i < Players.Count(); i++) { + /*.................................................................. + If the address is already present, re-copy their name, color & + house into the existing entry, in case they've changed it without + our knowledge; set the 'found' flag so we won't create a new entry. + ..................................................................*/ + if (Players[i]->Address==GAddress) { + strcpy(Players[i]->Name, GPacket.Name); + Players[i]->Player.House = GPacket.PlayerInfo.House; + Players[i]->Player.Color = GPacket.PlayerInfo.Color; + playerlist->Colors[i] = MPlayerTColors[GPacket.PlayerInfo.Color]; + found = 1; + break; + } + } + /*..................................................................... + Don't add this player if he's not part of the game that's selected. + .....................................................................*/ + i = gamelist->Current_Index(); + if (Games.Count() && GPacket.PlayerInfo.NameCRC != Compute_Name_CRC(Games[i]->Name)) + found = 1; + + /* + ** Dont add this player if its really me! (hack, hack) + */ + if (!strcmp(GPacket.Name, MPlayerName)){ + found = 1; + } + + + /*..................................................................... + name not found (or address didn't match); add to player list box & Vector + .....................................................................*/ + if (found==0) { + /*.................................................................. + Create & add a node to the Vector + ..................................................................*/ + who = new NodeNameType; + strcpy(who->Name, GPacket.Name); + who->Address = GAddress; + who->Player.House = GPacket.PlayerInfo.House; + who->Player.Color = GPacket.PlayerInfo.Color; + Players.Add (who); + + /*.................................................................. + Create & add a string to the list box + ..................................................................*/ + item = new char [MPLAYER_NAME_MAX + 4]; + if (GPacket.PlayerInfo.House==HOUSE_GOOD) { + sprintf(item,"%s\t%s",GPacket.Name,Text_String(TXT_G_D_I)); + } else { + sprintf(item,"%s\t%s",GPacket.Name,Text_String(TXT_N_O_D)); + } + playerlist->Add_Item(item, MPlayerTColors[who->Player.Color]); + + retcode = EV_NEW_PLAYER; + } + } + + /*------------------------------------------------------------------------ + NET_CONFIRM_JOIN: The game owner has confirmed our JOIN query; mark us as + being confirmed, and start answering queries from other systems + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_CONFIRM_JOIN) { + if ( (*joinstate) != JOIN_CONFIRMED) { + strcpy (MPlayerGameName, GPacket.Name); + MPlayerHouse = GPacket.PlayerInfo.House; + MPlayerColorIdx = GPacket.PlayerInfo.Color; + + (*joinstate) = JOIN_CONFIRMED; + retcode = EV_STATE_CHANGE; + } + } + + /*------------------------------------------------------------------------ + NET_REJECT_JOIN: The game owner has turned down our JOIN query; restore + the dialog state to its first pop-up state. Broadcast a sign-off to + tell all other systems that I'm no longer a part of any game; this way, + I'll be properly removed from their dialogs. + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_REJECT_JOIN) { + if ( (*joinstate) != JOIN_REJECTED) { + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy (GPacket.Name,MPlayerName); + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), 0, NULL); + + if (IsBridge) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + MPlayerGameName[0] = 0; + + (*joinstate) = JOIN_REJECTED; + retcode = EV_STATE_CHANGE; + } + } + + /*------------------------------------------------------------------------ + NET_GAME_OPTIONS: The game owner has changed the game options & is sending + us the new values. + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_GAME_OPTIONS) { + if ( (*joinstate)==JOIN_CONFIRMED) { + MPlayerCredits = GPacket.ScenarioInfo.Credits; + MPlayerBases = GPacket.ScenarioInfo.IsBases; + MPlayerTiberium = GPacket.ScenarioInfo.IsTiberium; + MPlayerGoodies = GPacket.ScenarioInfo.IsGoodies; + MPlayerGhosts = GPacket.ScenarioInfo.IsGhosties; + BuildLevel = GPacket.ScenarioInfo.BuildLevel; + MPlayerUnitCount = GPacket.ScenarioInfo.UnitCount; + Seed = GPacket.ScenarioInfo.Seed; + Special = GPacket.ScenarioInfo.Special; + Options.GameSpeed = GPacket.ScenarioInfo.GameSpeed; + + if (MPlayerTiberium) { + Special.IsTGrowth = 1; + Special.IsTSpread = 1; + } else { + Special.IsTGrowth = 0; + Special.IsTSpread = 0; + } + + if (Winsock.Get_Connected()){ + ScenarioIdx = GPacket.ScenarioInfo.Scenario; + }else{ + + ScenarioIdx = -1; + for (i = 0; i < MPlayerFilenum.Count(); i++) { + if (GPacket.ScenarioInfo.Scenario == MPlayerFilenum[i]) + ScenarioIdx = i; + } + } + + retcode = EV_GAME_OPTIONS; + } + } + + /*------------------------------------------------------------------------ + NET_SIGN_OFF: Another system is signing off: search for that system in + both the game list & player list, & remove it if found + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_SIGN_OFF) { + /*..................................................................... + Remove this name from the list of games + .....................................................................*/ + for (i = 0; i < Games.Count(); i++) { + if (!strcmp(Games[i]->Name, GPacket.Name) && + Games[i]->Address==GAddress) { + /*............................................................... + If the system signing off is the currently-selected list + item, clear the player list since that game is no longer + forming. + ...............................................................*/ + if (i==gamelist->Current_Index()) { + Clear_Player_List (playerlist); + } + + /*............................................................... + If the system signing off was the owner of our game, mark + ourselves as rejected + ...............................................................*/ + if ( (*joinstate) > JOIN_NOTHING && i==join_index) { + (*joinstate) = JOIN_REJECTED; + retcode = EV_STATE_CHANGE; + } + + /* + ....................... Set my return code ...................... + */ + if (retcode == EV_NONE) { + if (i <= gamelist->Current_Index()) { + retcode = EV_GAME_SIGNOFF; + } else { + retcode = EV_PLAYER_SIGNOFF; + } + } + + /* + ................. Remove game name from game list ............... + */ + Games.Delete(Games[i]); + item = (char *)(gamelist->Get_Item (i)); + gamelist->Remove_Item (item); + delete [] item; + gamelist->Flag_To_Redraw(); + + } + } + /*..................................................................... + Remove this name from the list of players + .....................................................................*/ + for (i = 0; i < Players.Count(); i++) { + /* + ..................... Name found; remove it ..................... + */ + if (Players[i]->Address==GAddress) { + item = (char *)(playerlist->Get_Item(i)); + playerlist->Remove_Item(item); + delete [] item; + Players.Delete(Players[i]); + playerlist->Flag_To_Redraw(); + + if (retcode == EV_NONE) + retcode = EV_PLAYER_SIGNOFF; + } + } + } + + /*------------------------------------------------------------------------ + NET_GO: The game's owner is signalling us to start playing. + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_GO) { + if ( (*joinstate)==JOIN_CONFIRMED) { + MPlayerMaxAhead = GPacket.ResponseTime.OneWay; + (*joinstate) = JOIN_GAME_START; + retcode = EV_STATE_CHANGE; +CCDebugString ("C&C95 - Received the 'GO' packet\n"); + } + } + + /*------------------------------------------------------------------------ + NET_MESSAGE: Someone is sending us a message + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_MESSAGE) { + sprintf(txt,Text_String (TXT_FROM), GPacket.Name, GPacket.Message.Buf); + magic_number = *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-4)); + crc = *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-2)); + color = MPlayerID_To_ColorIndex(GPacket.Message.ID); + Messages.Add_Message (txt, MPlayerTColors[color], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, magic_number, crc); + retcode = EV_MESSAGE; + } + + /*------------------------------------------------------------------------ + NET_PING: Someone is pinging me to get a response time measure (will only + happen after I've joined a game). Do nothing; the IPX Manager will handle + sending an ACK, and updating the response time measurements. + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_PING) { + retcode = EV_NONE; + } + + /*------------------------------------------------------------------------ + Default case: nothing happened. (This case will be hit every time I + receive my own NET_QUERY_GAME or NET_QUERY_PLAYER packets.) + ------------------------------------------------------------------------*/ + else { + retcode = EV_NONE; + } + + return(retcode); +} + + +/*********************************************************************************************** + * Net_New_Dialog -- lets user start a new game * + * * + * This dialog shows a list of who's requesting to join this game, and lets * + * the game initiator selectively approve each user. * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ New Network Game ³ * + * ³ ³ * + * ³ Players Scenario ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ³ * + * ³ ³ Boffo ³³ ³ Hell's Kitchen ³³ ³ * + * ³ ³ Bozo ÃÄ´ ³ Heaven's Gate ÃÄ´ ³ * + * ³ ³ Bonzo ³ ³ ³ ... ³ ³ ³ * + * ³ ³ ÃÄ´ ³ ÃÄ´ ³ * + * ³ ³ ³³ ³ ³³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ³ * + * ³ [Reject] Count:--- ## ³ * + * ³ Level:--- ## ³ * + * ³ ³ * + * ³ Credits: _____ ³ * + * ³ [ Bases ] [ Crates ] ³ * + * ³ [ Tiberium ] [ AI Players ] ³ * + * ³ ³ * + * ³ [OK] [Cancel] ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ * + * ³ ³ ³ ³ * + * ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ [Send Message] ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = success, false = cancel * + * * + * WARNINGS: * + * MPlayerName & MPlayerGameName must contain this player's name. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +static int Net_New_Dialog(void) +{ +/* ###Change collision detected! C:\PROJECTS\CODE\NETDLG.CPP... */ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + //D_DIALOG_W = 281; // dialog width + int d_dialog_w = 287*factor; // dialog width + int d_dialog_h = 177*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200*factor - d_dialog_h) / 2); // centered y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 6*factor+1; // ht of 6-pt text + int d_margin1 = 5*factor; // margin width/height + int d_margin2 = 2*factor; // margin width/height + + //d_playerlist_w = 100; + int d_playerlist_w = 106*factor; + int d_playerlist_h = 27*factor; + int d_playerlist_x = d_dialog_x + d_margin1; + int d_playerlist_y = d_dialog_y + d_margin1 + (d_txt6_h * 3); + + int d_scenariolist_w = 162*factor; + int d_scenariolist_h = 27*factor; + int d_scenariolist_x = d_dialog_x + d_dialog_w - d_margin1 - d_scenariolist_w; + int d_scenariolist_y = d_dialog_y + d_margin1 + (d_txt6_h * 3); + +#if (GERMAN | FRENCH) + int d_reject_w = 55*factor; +#else + int d_reject_w = 45*factor; +#endif + int d_reject_h = 9*factor; + int d_reject_x = d_playerlist_x + (d_playerlist_w / 2) - (d_reject_w / 2); + int d_reject_y = d_playerlist_y + d_playerlist_h + d_margin2; + + int d_count_w = 25*factor; + int d_count_h = d_txt6_h; + int d_count_x = d_scenariolist_x + (d_scenariolist_w / 2); + int d_count_y = d_scenariolist_y + d_scenariolist_h + d_margin2; + + int d_level_w = 25*factor; + int d_level_h = d_txt6_h; + int d_level_x = d_scenariolist_x + (d_scenariolist_w / 2); + int d_level_y = d_count_y + d_count_h; + + int d_credits_w = ((CREDITSBUF_MAX - 1) * 7*factor) + 4*factor; + //int d_credits_w = ((CREDITSBUF_MAX - 1) * 6*factor) + 3*factor; + int d_credits_h = 9*factor; + int d_credits_x = d_dialog_cx + 2*factor; + int d_credits_y = d_level_y + d_level_h + d_margin1; + +#if (GERMAN | FRENCH) + int d_bases_w = 120*factor;//bga:100; +#else + int d_bases_w = 100*factor; +#endif + int d_bases_h = 9*factor; + int d_bases_x = d_dialog_cx - d_bases_w - d_margin2; + int d_bases_y = d_credits_y + d_credits_h + d_margin2; + +#if (GERMAN | FRENCH) + int d_tiberium_w = 120*factor; +#else + int d_tiberium_w = 100*factor; +#endif + int d_tiberium_h = 9*factor; + int d_tiberium_x = d_dialog_cx - d_bases_w - d_margin2; + int d_tiberium_y = d_bases_y + d_bases_h + d_margin2; + +#if (GERMAN | FRENCH) + int d_goodies_w = 120*factor; +#else + int d_goodies_w = 100*factor; +#endif + int d_goodies_h = 9*factor; + int d_goodies_x = d_dialog_cx + d_margin2; + int d_goodies_y = d_credits_y + d_credits_h + d_margin2; + +#if (GERMAN | FRENCH) + int d_ghosts_w = 120*factor; +#else + int d_ghosts_w = 100*factor; +#endif + int d_ghosts_h = 9*factor; + int d_ghosts_x = d_dialog_cx + d_margin2; + int d_ghosts_y = d_goodies_y + d_goodies_h + d_margin2; + + int d_ok_w = 45*factor; + int d_ok_h = 9*factor; + int d_ok_x = d_dialog_cx - d_margin2 - (d_bases_w / 2) - (d_ok_w / 2); + int d_ok_y = d_ghosts_y + d_ghosts_h + d_margin1; + +#if (GERMAN | FRENCH) + int d_cancel_w = 50*factor; +#else + int d_cancel_w = 45*factor; +#endif + int d_cancel_h = 9*factor; + int d_cancel_x = d_dialog_cx + d_margin2 + (d_goodies_w / 2) - (d_cancel_w / 2); + int d_cancel_y = d_ghosts_y + d_ghosts_h + d_margin1; + + int d_message_w = d_dialog_w - (d_margin1 * 2); + int d_message_h = 34*factor; + int d_message_x = d_dialog_x + d_margin1; + int d_message_y = d_cancel_y + d_cancel_h + d_margin1; + + int d_send_w = 80*factor; + int d_send_h = 9*factor; + int d_send_x = d_dialog_cx - (d_send_w / 2); + int d_send_y = d_message_y + d_message_h + d_margin2; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_PLAYERLIST = 100, + BUTTON_SCENARIOLIST, + BUTTON_REJECT, + BUTTON_COUNT, + BUTTON_LEVEL, + BUTTON_CREDITS, + BUTTON_BASES, + BUTTON_TIBERIUM, + BUTTON_GOODIES, + BUTTON_GHOSTS, + BUTTON_OK, + BUTTON_CANCEL, + BUTTON_SEND, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_UNIT_COUNT, + REDRAW_MESSAGE, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + bool process = true; // process while true + KeyNumType input; + + char credbuf[CREDITSBUF_MAX]; // for credit edit box + int old_cred; // old value in credits buffer + int transmit; // 1 = re-transmit new game options + + long ok_timer = 0; // for timing OK button + int index; // index for rejecting a player + int rc; + int i,j; + char *item; + int tabs[] = {77*factor}; // tabs for player list box + + long ping_timer = 0; // for sending Ping packets + + unsigned char tmp_id[MAX_PLAYERS]; // temp storage for sorting player ID's + int min_index; // for sorting player ID's + unsigned char min_id; // for sorting player ID's + unsigned char id; // connection ID + char txt[80]; + JoinEventType whahoppa; // event generated by received packets + static int first_time = 1; // 1 = 1st time this dialog is run + + int message_length; + int sent_so_far; + unsigned short magic_number; + unsigned short crc; + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + + ColorListClass playerlist(BUTTON_PLAYERLIST, + d_playerlist_x, d_playerlist_y, d_playerlist_w, d_playerlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + ListClass scenariolist(BUTTON_SCENARIOLIST, + d_scenariolist_x, d_scenariolist_y, d_scenariolist_w, d_scenariolist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + EditClass credit_edt (BUTTON_CREDITS, + credbuf, CREDITSBUF_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_credits_x, d_credits_y, d_credits_w, d_credits_h, EditClass::ALPHANUMERIC); + + TextButtonClass rejectbtn(BUTTON_REJECT, TXT_REJECT, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_reject_x, d_reject_y); +//#else + d_reject_x, d_reject_y, d_reject_w, d_reject_h); +//#endif + + GaugeClass countgauge (BUTTON_COUNT, + d_count_x, d_count_y, d_count_w, d_count_h); + + GaugeClass levelgauge (BUTTON_LEVEL, + d_level_x, d_level_y, d_level_w, d_level_h); + + TextButtonClass basesbtn(BUTTON_BASES, TXT_BASES_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_bases_x, d_bases_y, d_bases_w, d_bases_h); + + TextButtonClass tiberiumbtn(BUTTON_TIBERIUM, TXT_TIBERIUM_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_tiberium_x, d_tiberium_y, d_tiberium_w, d_tiberium_h); + + TextButtonClass goodiesbtn(BUTTON_GOODIES, TXT_CRATES_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_goodies_x, d_goodies_y, d_goodies_w, d_goodies_h); + + TextButtonClass ghostsbtn(BUTTON_GHOSTS, TXT_AI_PLAYERS_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_ghosts_x, d_ghosts_y, d_ghosts_w, d_ghosts_h); + + TextButtonClass okbtn(BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_ok_x, d_ok_y, d_ok_w, d_ok_h); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_cancel_x, d_cancel_y); +//#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +//#endif + + TextButtonClass sendbtn(BUTTON_SEND, TXT_SEND_MESSAGE, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_send_x, d_send_y); +//#else + d_send_x, d_send_y, d_send_w, d_send_h); +//#endif + + /* + ------------------------- Build the button list -------------------------- + */ + commands = &playerlist; + scenariolist.Add_Tail(*commands); + credit_edt.Add_Tail(*commands); + rejectbtn.Add_Tail(*commands); + countgauge.Add_Tail(*commands); + levelgauge.Add_Tail(*commands); + basesbtn.Add_Tail(*commands); + tiberiumbtn.Add_Tail(*commands); + goodiesbtn.Add_Tail(*commands); + ghostsbtn.Add_Tail(*commands); + okbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + sendbtn.Add_Tail(*commands); + + playerlist.Set_Tabs(tabs); + + /* + ----------------------------- Various Inits ------------------------------ + */ + /*........................................................................ + Init dialog values, only the first time through + ........................................................................*/ + if (first_time) { + MPlayerCredits = 3000; // init credits & credit buffer + MPlayerBases = 1; // init scenario parameters + MPlayerTiberium = 0; + MPlayerGoodies = 0; + MPlayerGhosts = 0; + Special.IsCaptureTheFlag = 0; + MPlayerUnitCount = (MPlayerCountMax[MPlayerBases] + MPlayerCountMin[MPlayerBases]) / 2; + first_time = 0; + } + + /*........................................................................ + Init button states + ........................................................................*/ + if (MPlayerBases) { + basesbtn.Turn_On(); + basesbtn.Set_Text(TXT_BASES_ON); + } + if (MPlayerTiberium) { + tiberiumbtn.Turn_On(); + tiberiumbtn.Set_Text(TXT_TIBERIUM_ON); + } + if (MPlayerGoodies) { + goodiesbtn.Turn_On(); + goodiesbtn.Set_Text(TXT_CRATES_ON); + } + if (MPlayerGhosts) { + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_AI_PLAYERS_ON); + } + if (Special.IsCaptureTheFlag) { + MPlayerGhosts = 0; + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_CAPTURE_THE_FLAG); + } + + sprintf(credbuf, "%d", MPlayerCredits); + credit_edt.Set_Text(credbuf, CREDITSBUF_MAX); + old_cred = MPlayerCredits; + + levelgauge.Set_Maximum(MPLAYER_BUILD_LEVEL_MAX - 1); + levelgauge.Set_Value(BuildLevel - 1); + + countgauge.Set_Maximum(MPlayerCountMax[MPlayerBases] - MPlayerCountMin[MPlayerBases]); + countgauge.Set_Value(MPlayerUnitCount - MPlayerCountMin[MPlayerBases]); + + /*........................................................................ + Init other scenario parameters + ........................................................................*/ + Special.IsTGrowth = MPlayerTiberium; + Special.IsTSpread = MPlayerTiberium; + transmit = 0; + + /*........................................................................ + Init scenario description list box + ........................................................................*/ + for (i = 0; i < MPlayerScenarios.Count(); i++) { + scenariolist.Add_Item (strupr(MPlayerScenarios[i])); + } + ScenarioIdx = 0; // 1st scenario is selected + + /*........................................................................ + Init player color-used flags + ........................................................................*/ + for (i = 0; i < MAX_MPLAYER_COLORS; i++) { + ColorUsed[i] = 0; // init all colors to available + } + ColorUsed[MPlayerColorIdx] = 1; // set my color to used + playerlist.Set_Selected_Style(ColorListClass::SELECT_BAR, CC_GREEN_SHADOW); + + /*........................................................................ + Init random-number generator, & create a seed to be used for all random + numbers from here on out + ........................................................................*/ + randomize(); + Seed = rand(); + + /*........................................................................ + Init the message display system + ........................................................................*/ + Messages.Init (d_message_x + 2*factor, d_message_y + 2*factor, 4, MAX_MESSAGE_LENGTH, + d_txt6_h); + + /*------------------------------------------------------------------------ + Add myself to the list. Note that since I'm not in the Players Vector, + the Vector & listbox are now 1 out of sync. + ------------------------------------------------------------------------*/ + item = new char [MPLAYER_NAME_MAX + 4]; + if (MPlayerHouse==HOUSE_GOOD) { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_G_D_I)); + } else { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_N_O_D)); + } + playerlist.Add_Item(item, MPlayerTColors[MPlayerColorIdx]); + + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + while (Get_Mouse_State() > 0) Show_Mouse(); + + /* + ---------------------------- Processing loop ----------------------------- + */ + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + #if(SHOW_MONO) + Ipx.Mono_Debug_Print(-1,0); + #endif + /* + ...................... Refresh display if needed ...................... + */ + if (display == REDRAW_UNIT_COUNT){ + /* + ** Wipe the background behind the unit count then reprint it + */ + LogicPage->Fill_Rect(d_count_x + d_count_w + 2*factor, + d_count_y, + d_count_x + d_count_w + 2*factor + 20, + d_count_y + 12, + 0 ); + sprintf(txt,"%d",MPlayerUnitCount); + Fancy_Text_Print (txt, d_count_x + d_count_w + 2*factor, d_count_y, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL); + display = REDRAW_NONE; + } + + + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + + /* + ** Reload and draw the title page + */ + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_NETGAME_SETUP, d_dialog_x, d_dialog_y, d_dialog_w); + + Fancy_Text_Print(TXT_PLAYERS, + d_playerlist_x + (d_playerlist_w / 2), d_playerlist_y - d_txt6_h, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_CENTER); + + Fancy_Text_Print(TXT_SCENARIOS, + d_scenariolist_x + (d_scenariolist_w / 2), + d_scenariolist_y - d_txt6_h, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_CENTER); + + Fancy_Text_Print (TXT_COUNT, d_count_x - 2*factor, d_count_y, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_RIGHT); + + sprintf(txt,"%d",MPlayerUnitCount); + Fancy_Text_Print (txt, d_count_x + d_count_w + 2*factor, d_count_y, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL); + + Fancy_Text_Print (TXT_LEVEL, d_level_x - 2*factor, d_level_y, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_RIGHT); + + if (BuildLevel <= MPLAYER_BUILD_LEVEL_MAX) { + sprintf(txt,"%d",BuildLevel); + } else { + sprintf(txt, "**"); + } + Fancy_Text_Print (txt, d_level_x + d_level_w + 2*factor, d_level_y, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL); + + Fancy_Text_Print (TXT_START_CREDITS_COLON, d_credits_x - 5*factor, + d_credits_y + 1*factor, CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_RIGHT); + + } + + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Draw_All(); + } + + /*.................................................................. + Draw the messages: + - Erase an old message first + - If we're in a game, print the game options (if they've been + received) + - If we've been rejected from a game, print that message + ..................................................................*/ + if (display >= REDRAW_MESSAGE) { + Draw_Box(d_message_x, d_message_y, d_message_w, d_message_h, + BOXSTYLE_GREEN_BORDER, true); + Messages.Draw(); + } + + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + /*------------------------------------------------------------------ + New Scenario selected. + ------------------------------------------------------------------*/ + case (BUTTON_SCENARIOLIST | KN_BUTTON): + if (scenariolist.Current_Index() != ScenarioIdx) { + ScenarioIdx = scenariolist.Current_Index(); + MPlayerCredits = atoi(credbuf); + transmit = 1; + } + break; + + /*------------------------------------------------------------------ + Reject the currently-selected player (don't allow rejecting myself, + who will be the first entry in the list) + ------------------------------------------------------------------*/ + case (BUTTON_REJECT | KN_BUTTON): + index = playerlist.Current_Index(); + if (index == 0) { + CCMessageBox().Process (TXT_CANT_REJECT_SELF, TXT_OOPS); + display = REDRAW_ALL; + break; + } else { + if (index < 0 || index >= playerlist.Count()) { + CCMessageBox().Process (TXT_SELECT_PLAYER_REJECT,TXT_OOPS); + display = REDRAW_ALL; + break; + } + } + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_REJECT_JOIN; + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), 1, + &(Players[index - 1]->Address)); + break; + + /*------------------------------------------------------------------ + User adjusts max # units + ------------------------------------------------------------------*/ + case (BUTTON_COUNT | KN_BUTTON): + MPlayerUnitCount = countgauge.Get_Value() + MPlayerCountMin[MPlayerBases]; + + Hide_Mouse(); + LogicPage->Fill_Rect (d_count_x + d_count_w + 2*factor, d_count_y, + d_count_x + d_count_w + 14*factor, d_count_y + 6*factor, BLACK); + + sprintf(txt,"%d",MPlayerUnitCount); + Fancy_Text_Print (txt, d_count_x + d_count_w + 2*factor, d_count_y, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL); + Show_Mouse(); + + transmit = 1; + break; + + /*------------------------------------------------------------------ + User adjusts build level + ------------------------------------------------------------------*/ + case (BUTTON_LEVEL | KN_BUTTON): + BuildLevel = levelgauge.Get_Value() + 1; + if (BuildLevel > MPLAYER_BUILD_LEVEL_MAX) // if it's pegged, max it out + BuildLevel = MPLAYER_BUILD_LEVEL_MAX; + + Hide_Mouse(); + LogicPage->Fill_Rect (d_level_x + d_level_w + 2*factor, d_level_y, + d_level_x + d_level_w + 14*factor, d_level_y + 6*factor, BLACK); + + if (BuildLevel <= MPLAYER_BUILD_LEVEL_MAX) { + sprintf(txt,"%d",BuildLevel); + } else { + sprintf(txt, "**"); + } + Fancy_Text_Print (txt, d_level_x + d_level_w + 2*factor, d_level_y, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL); + Show_Mouse(); + + transmit = 1; + break; + + /*------------------------------------------------------------------ + User edits the credits value; retransmit new game options + ------------------------------------------------------------------*/ + case (BUTTON_CREDITS | KN_BUTTON): + MPlayerCredits = atoi(credbuf); + transmit = 1; + break; + + /*------------------------------------------------------------------ + Toggle bases: + - Clear scenario list & rebuild it with new names + - toggle bases button, change its text + - adjust the MPlayerUnitCount to reflect the new allowed range, + using the current gauge setting + - Change the unit count gauge limit & value + ------------------------------------------------------------------*/ + case (BUTTON_BASES | KN_BUTTON): + if (MPlayerBases) { + MPlayerBases = 0; + basesbtn.Turn_Off(); + basesbtn.Set_Text(TXT_BASES_OFF); + MPlayerUnitCount = Fixed_To_Cardinal (MPlayerCountMax[0]-MPlayerCountMin[0], + Cardinal_To_Fixed(MPlayerCountMax[1]-MPlayerCountMin[1], + MPlayerUnitCount-MPlayerCountMin[1])) + MPlayerCountMin[0]; + } else { + MPlayerBases = 1; + basesbtn.Turn_On(); + basesbtn.Set_Text(TXT_BASES_ON); + MPlayerUnitCount = Fixed_To_Cardinal (MPlayerCountMax[1]-MPlayerCountMin[1], + Cardinal_To_Fixed(MPlayerCountMax[0]-MPlayerCountMin[0], + MPlayerUnitCount-MPlayerCountMin[0])) + MPlayerCountMin[1]; + } + MPlayerCredits = atoi(credbuf); + countgauge.Set_Maximum(MPlayerCountMax[MPlayerBases] - MPlayerCountMin[MPlayerBases]); + countgauge.Set_Value(MPlayerUnitCount - MPlayerCountMin[MPlayerBases]); + transmit = 1; + countgauge.Flag_To_Redraw(); + display = REDRAW_UNIT_COUNT; + break; + + /*------------------------------------------------------------------ + Toggle tiberium + ------------------------------------------------------------------*/ + case (BUTTON_TIBERIUM | KN_BUTTON): + if (MPlayerTiberium) { + MPlayerTiberium = 0; + Special.IsTGrowth = 0; + Special.IsTSpread = 0; + tiberiumbtn.Turn_Off(); + tiberiumbtn.Set_Text(TXT_TIBERIUM_OFF); + } else { + MPlayerTiberium = 1; + Special.IsTGrowth = 1; + Special.IsTSpread = 1; + tiberiumbtn.Turn_On(); + tiberiumbtn.Set_Text(TXT_TIBERIUM_ON); + } + MPlayerCredits = atoi(credbuf); + transmit = 1; + break; + + /*------------------------------------------------------------------ + Toggle goodies + ------------------------------------------------------------------*/ + case (BUTTON_GOODIES | KN_BUTTON): + if (MPlayerGoodies) { + MPlayerGoodies = 0; + goodiesbtn.Turn_Off(); + goodiesbtn.Set_Text(TXT_CRATES_OFF); + } else { + MPlayerGoodies = 1; + goodiesbtn.Turn_On(); + goodiesbtn.Set_Text(TXT_CRATES_ON); + } + MPlayerCredits = atoi(credbuf); + transmit = 1; + break; + + /*------------------------------------------------------------------ + Toggle ghosts/capture-the-flag + ------------------------------------------------------------------*/ + case (BUTTON_GHOSTS | KN_BUTTON): + if (!MPlayerGhosts && !Special.IsCaptureTheFlag) { // ghosts OFF => ghosts ON + MPlayerGhosts = 1; + Special.IsCaptureTheFlag = 0; + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_AI_PLAYERS_ON); + } else { + if (MPlayerGhosts) { // ghosts ON => capture-flag + MPlayerGhosts = 0; + Special.IsCaptureTheFlag = 1; + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_CAPTURE_THE_FLAG); + } else { + if (Special.IsCaptureTheFlag) { // capture-flag => AI OFF + MPlayerGhosts = 0; + Special.IsCaptureTheFlag = 0; + ghostsbtn.Turn_Off(); + ghostsbtn.Set_Text(TXT_AI_PLAYERS_OFF); + } + } + } + + MPlayerCredits = atoi(credbuf); + transmit = 1; + break; + + /*------------------------------------------------------------------ + OK: exit loop with TRUE status + ------------------------------------------------------------------*/ + case (BUTTON_OK | KN_BUTTON): + /*............................................................... + If a new player has joined in the last second, don't allow + an OK; force a wait longer than 1 second (to give all players + a chance to know about this new guy) + ...............................................................*/ + i = MAX(Ipx.Global_Response_Time() * 2, 60); + while (TickCount.Time() - ok_timer < i) + Ipx.Service(); + + /*............................................................... + If there are at least 2 players, go ahead & play; error otherwise + ...............................................................*/ + if (MPlayerSolo || Players.Count() > 0) { + rc = TRUE; + process = FALSE; + } else { + CCMessageBox().Process (TXT_ONLY_ONE,TXT_OOPS,NULL); + display = REDRAW_ALL; + } + break; + + /*------------------------------------------------------------------ + CANCEL: send a SIGN_OFF, bail out with error code + ------------------------------------------------------------------*/ + case (KN_ESC): + if (Messages.Get_Edit_Buf() != NULL) { + Messages.Input(input); + if (display < REDRAW_MESSAGE) display = REDRAW_MESSAGE; + break; + } + case (BUTTON_CANCEL | KN_BUTTON): + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy (GPacket.Name, MPlayerName); + + /*............................................................... + Broadcast my sign-off over my network + ...............................................................*/ + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + /*............................................................... + Broadcast my sign-off over a bridged network if there is one + ...............................................................*/ + if (IsBridge) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + /*............................................................... + And now, just be absolutely sure, send my sign-off to each + player in my game. (If there's a bridge between us, the other + player will have specified my address, so he can cross the + bridge; but I may not have specified a bridge address, so the + only way I have of crossing the bridge is to send a packet + directly to him.) + ...............................................................*/ + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Players[i]->Address)); + Ipx.Service(); + } + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + MPlayerGameName[0] = 0; + process = false; + rc = false; + break; + + /*------------------------------------------------------------------ + Default: manage the inter-player messages + ------------------------------------------------------------------*/ + default: + /*............................................................... + F4/SEND/'M' = send a message + ...............................................................*/ + if (Messages.Get_Edit_Buf()==NULL) { + if (input == KN_M || input==(BUTTON_SEND | KN_BUTTON) || + input == KN_F4) { + memset (txt, 0, 80); + + strcpy(txt,Text_String(TXT_TO_ALL)); // "To All:" + + Messages.Add_Edit (MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, txt, d_message_w-70*factor); + + credit_edt.Clear_Focus(); + credit_edt.Flag_To_Redraw(); + if (display < REDRAW_MESSAGE) display = REDRAW_MESSAGE; + + break; + } + } else { + + /*............................................................... + If we're already editing a message and the user clicks on + 'Send', translate our input to a Return so Messages.Input() will + work properly. + ...............................................................*/ + if (input==(BUTTON_SEND | KN_BUTTON)) { + input = KN_RETURN; + } + } + + /*............................................................... + Manage the message system (get rid of old messages) + ...............................................................*/ + if (Messages.Manage()) { + if (display < REDRAW_MESSAGE) display = REDRAW_MESSAGE; + } + + /*............................................................... + Re-draw the messages & service keyboard input for any message + being edited. + ...............................................................*/ + i = Messages.Input(input); + + /*............................................................... + If 'Input' returned 1, it means refresh the message display. + ...............................................................*/ + if (i==1) { + Messages.Draw(); + } + + /*............................................................... + If 'Input' returned 2, it means redraw the message display. + ...............................................................*/ + else if (i==2) { + if (display < REDRAW_MESSAGE) display = REDRAW_MESSAGE; + } + + /*............................................................... + If 'input' returned 3, it means send the current message. + ...............................................................*/ + else if (i==3) { + long actual_message_size; + char *the_string; + + sent_so_far = 0; + magic_number = MESSAGE_HEAD_MAGIC_NUMBER; + message_length = strlen(Messages.Get_Edit_Buf()); + crc = (unsigned short) + (Calculate_CRC(Messages.Get_Edit_Buf(), message_length) &0xffff); + while ( sent_so_far < message_length ){ + memset (&GPacket, 0, sizeof(GlobalPacketType)); + GPacket.Command = NET_MESSAGE; + strcpy (GPacket.Name, MPlayerName); + memcpy (GPacket.Message.Buf, Messages.Get_Edit_Buf()+sent_so_far, COMPAT_MESSAGE_LENGTH-5); + + /* + ** Steve I's stuff for splitting message on word boundries + */ + actual_message_size = COMPAT_MESSAGE_LENGTH - 5; + + /* Start at the end of the message and find a space with 10 chars. */ + the_string = GPacket.Message.Buf; + while ( (COMPAT_MESSAGE_LENGTH -5) -actual_message_size < 10 && + the_string[actual_message_size] != ' '){ + --actual_message_size; + } + if ( the_string[actual_message_size] == ' ' ){ + + /* Now delete the extra characters after the space (they musnt print) */ + for ( int i=0 ; i< (COMPAT_MESSAGE_LENGTH-5) - actual_message_size; i++ ){ + the_string[i + actual_message_size] = 0xff; + } + }else{ + actual_message_size = COMPAT_MESSAGE_LENGTH - 5; + } + + *(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-5) = 0; + *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-4)) = magic_number; + *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-2)) = crc; + GPacket.Message.ID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + GPacket.Message.NameCRC = Compute_Name_CRC(MPlayerGameName); + + /*.................................................................. + Send the message to every player in our player list. + ..................................................................*/ + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Players[i]->Address)); + Ipx.Service(); + } + /*.................................................................. + Add the message to our own list, since we're not in the player list + on this dialog. + ..................................................................*/ + sprintf(txt,Text_String (TXT_FROM), MPlayerName, GPacket.Message.Buf); + Messages.Add_Message (txt, MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, magic_number, crc); + + magic_number++; + sent_so_far += actual_message_size; //COMPAT_MESSAGE_LENGTH-5; + + } + if (display < REDRAW_MESSAGE) display = REDRAW_MESSAGE; + } + } + + /*--------------------------------------------------------------------- + Detect editing of the credits buffer, transmit new values to players + ---------------------------------------------------------------------*/ + if (atoi(credbuf) != old_cred) { + old_cred = Bound(atoi(credbuf), 0, 9999); + MPlayerCredits = old_cred; + transmit = 1; + sprintf(credbuf, "%d", MPlayerCredits); + credit_edt.Set_Text(credbuf, CREDITSBUF_MAX); + } + + /*--------------------------------------------------------------------- + Process incoming packets + ---------------------------------------------------------------------*/ + whahoppa = Get_NewGame_Responses(&playerlist); + if (whahoppa == EV_NEW_PLAYER) { + ok_timer = TickCount.Time(); + transmit = 1; + } else { + if (whahoppa == EV_MESSAGE) { + if (display < REDRAW_MESSAGE) display = REDRAW_MESSAGE; + } + } + + /*--------------------------------------------------------------------- + If our Transmit flag is set, we need to send out a game option packet + ---------------------------------------------------------------------*/ + if (transmit) { + for (i = 0; i < Players.Count(); i++) { + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_GAME_OPTIONS; + GPacket.ScenarioInfo.Scenario = MPlayerFilenum[ScenarioIdx]; + GPacket.ScenarioInfo.Credits = MPlayerCredits; + GPacket.ScenarioInfo.IsBases = MPlayerBases; + GPacket.ScenarioInfo.IsTiberium = MPlayerTiberium; + GPacket.ScenarioInfo.IsGoodies = MPlayerGoodies; + GPacket.ScenarioInfo.IsGhosties = MPlayerGhosts; + GPacket.ScenarioInfo.BuildLevel = BuildLevel; + GPacket.ScenarioInfo.UnitCount = MPlayerUnitCount; + GPacket.ScenarioInfo.Seed = Seed; + GPacket.ScenarioInfo.Special = Special; + GPacket.ScenarioInfo.GameSpeed = Options.GameSpeed; + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &(Players[i]->Address) ); + } + transmit = 0; + } + + /*--------------------------------------------------------------------- + Ping every player in my game, to force the Global Channel to measure + the connection response time. + ---------------------------------------------------------------------*/ + if (TickCount.Time() - ping_timer > 15) { + memset (&GPacket, 0, sizeof(GlobalPacketType)); + GPacket.Command = NET_PING; + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &(Players[i]->Address) ); + } + ping_timer = TickCount.Time(); + } + + /*--------------------------------------------------------------------- + Service the Ipx connections + ---------------------------------------------------------------------*/ + Ipx.Service(); + + /*--------------------------------------------------------------------- + Service the sounds & score; GameActive must be false at this point, + so Call_Back() doesn't intercept global messages from me! + ---------------------------------------------------------------------*/ + Call_Back(); + + } /* end of while */ + + /*------------------------------------------------------------------------ + Establish connections with all other players. + ------------------------------------------------------------------------*/ + if (rc) { + /*..................................................................... + Set the number of players in this game, and my ID + .....................................................................*/ + MPlayerCount = Players.Count() + 1; + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + /*..................................................................... + Get the scenario filename + .....................................................................*/ + Scenario = MPlayerFilenum[ScenarioIdx]; + + /*..................................................................... + Compute frame delay value for packet transmissions: + - Divide global channel's response time by 8 (2 to convert to 1-way + value, 4 more to convert from ticks to frames) + .....................................................................*/ + MPlayerMaxAhead = MAX( (Ipx.Global_Response_Time() / 8), 2); + + /*..................................................................... + Send all players the NET_GO packet. Wait until all ACK's have been + received. + .....................................................................*/ + memset (&GPacket, 0, sizeof(GlobalPacketType)); + GPacket.Command = NET_GO; + GPacket.ResponseTime.OneWay = MPlayerMaxAhead; + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &(Players[i]->Address) ); + /*.................................................................. + Wait for all the ACK's to come in. + ..................................................................*/ + while (Ipx.Global_Num_Send() > 0) + Ipx.Service(); + } + + /*..................................................................... + Form connections with all other players. Form the IPX Connection ID + from the player's Color (high byte) and House (low byte). This + will let us extract any player's color & house at any time. + Fill in 'tmp_id' while we're doing this. + .....................................................................*/ + for (i = 0; i < Players.Count(); i++) { + id = Build_MPlayerID (Players[i]->Player.Color, + Players[i]->Player.House); + + tmp_id[i] = id; + + Ipx.Create_Connection(id, Players[i]->Name, &(Players[i]->Address) ); + } + tmp_id[i] = MPlayerLocalID; + + /*..................................................................... + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + .....................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + min_index = 0; + min_id = 0xff; + for (j = 0; j < MPlayerCount; j++) { + if (tmp_id[j] < min_id) { + min_id = tmp_id[j]; + min_index = j; + } + } + MPlayerID[i] = tmp_id[min_index]; + tmp_id[min_index] = 0xff; + } + /*..................................................................... + Fill in the array of player names, including my own. + .....................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID[i] == MPlayerLocalID) { + strcpy (MPlayerNames[i], MPlayerName); + } else { + strcpy (MPlayerNames[i], Ipx.Connection_Name(MPlayerID[i])); + } + } + } + + /*------------------------------------------------------------------------ + Init network timing values, using previous response times as a measure + of what our retry delta & timeout should be. + ------------------------------------------------------------------------*/ + Ipx.Set_Timing (Ipx.Global_Response_Time() + 2, -1, + Ipx.Global_Response_Time() * 4); + + /*------------------------------------------------------------------------ + Clear all lists + ------------------------------------------------------------------------*/ + while (scenariolist.Count()) { + scenariolist.Remove_Item(scenariolist.Get_Item(0)); + } + Clear_Player_List(&playerlist); + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + return(rc); +} + + +/*************************************************************************** + * Get_NewGame_Responses -- processes packets for New Game dialog * + * * + * This routine can modify the contents of the given list box, as well * + * as the contents of the Players Vector global. * + * * + * INPUT: * + * playerlist list of players in this game * + * * + * OUTPUT: * + * EV_NONE = nothing happened * + * EV_NEW_PLAYER = a new player has joined; false otherwise * + * EV_MESSAGE = a message was received * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/18/1995 BRR : Created. * + *=========================================================================*/ +static JoinEventType Get_NewGame_Responses(ColorListClass *playerlist) +{ + int rc; + char * item; // general-purpose string + NodeNameType *who; // node to add to Players Vector + int i; + int found; + JoinEventType retval = EV_NONE; + int resend; + char txt[80]; + int color; + unsigned short magic_number; + unsigned short crc; + + /*------------------------------------------------------------------------ + If there is no incoming packet, just return + ------------------------------------------------------------------------*/ + rc = Ipx.Get_Global_Message (&GPacket, &GPacketlen, &GAddress, &GProductID); + if (!rc || GProductID != IPXGlobalConnClass::COMMAND_AND_CONQUER) { + return(EV_NONE); + } + + /*------------------------------------------------------------------------ + Try to handle the packet in a standard way + ------------------------------------------------------------------------*/ + if (Process_Global_Packet(&GPacket,&GAddress) != 0) { + return(EV_NONE); + } else + + /*------------------------------------------------------------------------ + NET_QUERY_JOIN: + ------------------------------------------------------------------------*/ + if (GPacket.Command==NET_QUERY_JOIN) { + /*..................................................................... + See if this name is unique: + - If the name matches, but the address is different, reject this player + - If the name & address match, this packet must be a re-send of a + prevous request; in this case, do nothing. The other player must have + received my CONFIRM_JOIN packet (since it was sent with an ACK + required), so we can ignore this resend. + .....................................................................*/ + found = 0; + resend = 0; + for (i = 0; i < Players.Count(); i++) { + if (!strcmp(Players[i]->Name,GPacket.Name)) { + if (Players[i]->Address != GAddress) { + found = 1; + } + else { + resend = 1; + } + break; + } + } + if (!strcmp (MPlayerName, GPacket.Name)) { + found = 1; + } + + /*..................................................................... + Reject if name is a duplicate, or if there are too many players: + .....................................................................*/ + if (found || (Players.Count() >= (MPlayerMax - 1) && !resend) ) { + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_REJECT_JOIN; + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &GAddress); + } + + /*..................................................................... + If this packet is NOT a resend, accept the player. Grant him the + requested color if possible. + .....................................................................*/ + else if (!resend) { + /*.................................................................. + Add node to the Vector list + ..................................................................*/ + who = new NodeNameType; + strcpy(who->Name, GPacket.Name); + who->Address = GAddress; + who->Player.House = GPacket.PlayerInfo.House; + Players.Add (who); + + /*.................................................................. + Set player's color; if requested color isn't used, give it to him; + otherwise, give him the 1st available color. Mark the color we + give him as used. + ..................................................................*/ + if (ColorUsed[GPacket.PlayerInfo.Color] == 0) { + who->Player.Color = GPacket.PlayerInfo.Color; + } else { + for (i = 0; i < MAX_MPLAYER_COLORS; i++) { + if (ColorUsed[i]==0) { + who->Player.Color = i; + break; + } + } + } + ColorUsed[who->Player.Color] = 1; + + /*.................................................................. + Add player name to the list box + ..................................................................*/ + item = new char [MPLAYER_NAME_MAX + 4]; + if (GPacket.PlayerInfo.House==HOUSE_GOOD) { + sprintf(item,"%s\t%s",GPacket.Name,Text_String(TXT_G_D_I)); + } else { + sprintf(item,"%s\t%s",GPacket.Name,Text_String(TXT_N_O_D)); + } + playerlist->Add_Item (item, MPlayerTColors[who->Player.Color]); + + /*.................................................................. + Send a confirmation packet + ..................................................................*/ + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_CONFIRM_JOIN; + strcpy(GPacket.Name,MPlayerName); + GPacket.PlayerInfo.House = who->Player.House; + GPacket.PlayerInfo.Color = who->Player.Color; + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &GAddress); + + retval = EV_NEW_PLAYER; + } + } + + /*------------------------------------------------------------------------ + NET_SIGN_OFF: Another system is signing off: search for that system in + the player list, & remove it if found + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_SIGN_OFF) { + for (i = 0; i < Players.Count(); i++) { + /* + ....................... Name found; remove it ...................... + */ + if (!strcmp (Players[i]->Name, GPacket.Name) && + Players[i]->Address==GAddress) { + /*............................................................... + Remove from the list box + ...............................................................*/ + item = (char *)(playerlist->Get_Item(i + 1)); + playerlist->Remove_Item(item); + playerlist->Flag_To_Redraw(); + delete [] item; + /*............................................................... + Mark his color as available + ...............................................................*/ + ColorUsed[Players[i]->Player.Color] = 0; + /*............................................................... + Delete from the Vector list + ...............................................................*/ + Players.Delete(Players[i]); + break; + } + } + } + + /*------------------------------------------------------------------------ + NET_MESSAGE: Someone is sending us a message + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_MESSAGE) { + sprintf(txt,Text_String (TXT_FROM), GPacket.Name, GPacket.Message.Buf); + magic_number = *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-4)); + crc = *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-2)); + color = MPlayerID_To_ColorIndex(GPacket.Message.ID); + Messages.Add_Message (txt, MPlayerTColors[color], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, magic_number, crc); + retval = EV_MESSAGE; + } + + return(retval); +} + + + +/*************************************************************************** + * Compute_Name_CRC -- computes CRC from char string * + * * + * INPUT: * + * name string to create CRC for * + * * + * OUTPUT: * + * CRC * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/29/1995 BRR : Created. * + *=========================================================================*/ +unsigned long Compute_Name_CRC(char *name) +{ + char buf[80]; + unsigned long crc = 0L; + int i; + + strcpy (buf, name); + strupr (buf); + + for (i = 0; i < strlen(buf); i++) { + Add_CRC (&crc, (unsigned long)buf[i]); + } + + return (crc); +} + +/*************************************************************************** + * Net_Reconnect_Dialog -- Draws/updates the network reconnect dialog * + * * + * INPUT: * + * reconn 1 = reconnect, 0 = waiting for first-time connection * + * fresh 1 = draw from scratch, 0 = only update time counter * + * oldest_index IPX connection index of oldest connection * + * (only used for reconnection) * + * timeval value to print in the countdown field * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/08/1995 BRR : Created. * + *=========================================================================*/ +void Net_Reconnect_Dialog(int reconn, int fresh, int oldest_index, + unsigned long timeval) +{ + static int x,y,w,h; + int id; + char buf1[40] = {0}; + char buf2[40] = {0}; + char const *buf3 = ""; + + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + int d_txt6_h = 6*factor+1; + int d_margin = 5*factor; + + + /*------------------------------------------------------------------------ + Draw the dialog from scratch + ------------------------------------------------------------------------*/ + if (fresh) { + Fancy_Text_Print ("", 0, 0, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + if (reconn) { + id = Ipx.Connection_ID(oldest_index); + sprintf(buf1,Text_String(TXT_RECONNECTING_TO), + Ipx.Connection_Name(id)); + } else { + sprintf(buf1,Text_String(TXT_WAITING_FOR_CONNECTIONS)); + } + sprintf(buf2,Text_String(TXT_TIME_ALLOWED), timeval + 1); + buf3 = Text_String(TXT_PRESS_ESC); + + w = MAX(String_Pixel_Width(buf1),String_Pixel_Width(buf2)); + w = MAX(String_Pixel_Width(buf3), w); + w += (d_margin * 4); + h = (d_txt6_h * 3) + (d_margin * 6); + x = 160*factor - (w / 2); + y = 100*factor - (h / 2); + + Hide_Mouse(); + Set_Logic_Page(SeenBuff); + Dialog_Box(x, y, w, h); + + Fancy_Text_Print (buf1, 160*factor, y + (d_margin * 2), CC_GREEN, BLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print (buf2, 160*factor, y + (d_margin * 2) + d_txt6_h + d_margin, + CC_GREEN, BLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print (buf3, 160*factor, y + (d_margin * 2) + (d_txt6_h + d_margin) * 2, + CC_GREEN, BLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Show_Mouse(); + + } else { + + /*------------------------------------------------------------------------ + Just update the timeout value on the dialog + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Set_Logic_Page(SeenBuff); + + sprintf(buf2,Text_String(TXT_TIME_ALLOWED), timeval + 1); + int pixwidth = String_Pixel_Width (buf2); + LogicPage->Fill_Rect (160*factor - (pixwidth/2) - 12, y+(d_margin*2) + d_txt6_h + d_margin, + 160*factor + (pixwidth/2) + 12, y+(d_margin*2) + d_txt6_h*2 + d_margin, + TBLACK); + Fancy_Text_Print (buf2, 160*factor, y + (d_margin * 2) + d_txt6_h + d_margin, + CC_GREEN, BLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Show_Mouse(); + } +} + + + + + + +/*********************************************************************************************** + * Wait_For_Focus -- Wait for game to be in focus before proceeding * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 1/6/97 3:23PM ST : Created * + *=============================================================================================*/ +void Wait_For_Focus (void) +{ + CountDownTimerClass focus_timer; + focus_timer.Set(5*60); + ShowWindow ( MainWindow, SW_SHOWMAXIMIZED ); + + /* + ** Process the message loop until we are in focus. + */ + if (!GameInFocus){ + CCDebugString ("C&C95 - Waiting for game to come into focus."); + do { + CCDebugString ("."); + Keyboard::Check(); + if (!focus_timer.Time()){ + CCDebugString ("C&C95 - Calling SetForgroundWindow.\n"); + SetForegroundWindow ( MainWindow ); + CCDebugString ("C&C95 - Calling ShowWindow.\n"); + ShowWindow ( MainWindow, SW_SHOWMAXIMIZED ); + focus_timer.Set(5*60); + } + + }while (!GameInFocus); + CCDebugString ("\n"); + AllSurfaces.SurfacesRestored=FALSE; + } +} + + + + + +extern bool Spawn_WChat(bool can_launch); + +/*********************************************************************************************** + * Net_Fake_New_Dialog -- Just like Net_New_Dialog but without the Dialog. For internet play * + * * + * This 'dialog' does all the non-dialog game set up stuff that is done in the normal * + * network game set up dialog. The only visible button is 'cancel' * + * * + * INPUT: Nothing * + * * + * OUTPUT: true if successfully connected * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 5/24/96 10:34AM ST : Created * + *=============================================================================================*/ +static int Net_Fake_New_Dialog(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + int d_dialog_w = 120*factor; // dialog width + int d_dialog_h = 80*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200*factor - d_dialog_h) / 2); // centered y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + //d_playerlist_w = 100; + int d_playerlist_w = 106*factor; + int d_playerlist_h = 27*factor; + //int d_playerlist_x = 10 * factor; //off screen + int d_playerlist_x = 500*factor; //10 * factor; //off screen + int d_playerlist_y = d_dialog_y + 20; + +#if (GERMAN | FRENCH) + int d_cancel_w = 50*factor; +#else + int d_cancel_w = 45*factor; +#endif + int d_cancel_h = 9*factor; + int d_cancel_x = d_dialog_cx - (d_cancel_w / 2); + int d_cancel_y = d_dialog_y + d_dialog_h - 20*factor; + +#if (GERMAN | FRENCH) + int width=160*factor; + int height=80*factor; +#else + int width=120*factor; + int height=80*factor; +#endif //GERMAN | FRENCH + + bool player_joined = false; + CountDownTimerClass join_timer; + + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + Format_Window_String((char*)Text_String(TXT_CONNECTING), SeenBuff.Get_Height(), width, height); + +#if (GERMAN | FRENCH) + d_dialog_w = width + 25*factor; + d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + d_cancel_x = d_dialog_cx - (d_cancel_w / 2); +#endif + + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + BUTTON_PLAYERLIST, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_MESSAGE, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + bool process = true; // process while true + KeyNumType input; + + char credbuf[CREDITSBUF_MAX]; // for credit edit box + int old_cred; // old value in credits buffer + int transmit; // 1 = re-transmit new game options + + long ok_timer = 0; // for timing OK button + int rc; + int i,j; + char *item; + int tabs[] = {77*factor}; // tabs for player list box + + long ping_timer = 0; // for sending Ping packets + + unsigned char tmp_id[MAX_PLAYERS]; // temp storage for sorting player ID's + int min_index; // for sorting player ID's + unsigned char min_id; // for sorting player ID's + unsigned char id; // connection ID + JoinEventType whahoppa; // event generated by received packets + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + ColorListClass playerlist(BUTTON_PLAYERLIST, + d_playerlist_x, d_playerlist_y, d_playerlist_w, d_playerlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_cancel_x, d_cancel_y); +//#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +//#endif + + + CCDebugString ("C&C95 - In new game dialog - initialising lists.\n"); + /* + ------------------------- Build the button list -------------------------- + */ + commands = &playerlist; + cancelbtn.Add_Tail(*commands); + + playerlist.Set_Tabs(tabs); + + /* + ----------------------------- Various Inits ------------------------------ + */ + + sprintf(credbuf, "%d", MPlayerCredits); + old_cred = MPlayerCredits; + + /*........................................................................ + Init other scenario parameters + ........................................................................*/ + Special.IsTGrowth = MPlayerTiberium; + Special.IsTSpread = MPlayerTiberium; + transmit = 0; + + /*........................................................................ + Init player color-used flags + ........................................................................*/ + for (i = 0; i < MAX_MPLAYER_COLORS; i++) { + ColorUsed[i] = 0; // init all colors to available + } + ColorUsed[MPlayerColorIdx] = 1; // set my color to used + playerlist.Set_Selected_Style(ColorListClass::SELECT_BAR, CC_GREEN_SHADOW); + + /*........................................................................ + Init random-number generator, & create a seed to be used for all random + numbers from here on out + ........................................................................*/ + randomize(); + Seed = rand(); + + /*------------------------------------------------------------------------ + Add myself to the list. Note that since I'm not in the Players Vector, + the Vector & listbox are now 1 out of sync. + ------------------------------------------------------------------------*/ + item = new char [MPLAYER_NAME_MAX + 4]; + if (MPlayerHouse==HOUSE_GOOD) { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_G_D_I)); + } else { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_N_O_D)); + } + playerlist.Add_Item(item, MPlayerTColors[MPlayerColorIdx]); + + Wait_For_Focus(); + + CCDebugString ("C&C95 - About to uncompress title page.\n"); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + CCDebugString ("C&C95 - About to set the palette.\n"); + Set_Palette(Palette); + CCDebugString ("C&C95 - Palette was set OK.\n"); + + if (LogicPage != &SeenBuff && LogicPage!= &HidPage){ + CCDebugString ("C&C95 - Logic page invalid"); + Set_Logic_Page (SeenBuff); + } + + char a_buffer [128]; + sprintf (a_buffer, "Number of players:%d", Players.Count()); + CCDebugString (a_buffer); + + +#ifdef VIRTUAL_SUBNET_SERVER + /* + ** Send a bogus packet to wake up the VSS + */ + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = (NetCommandType)50; //Invalid command + strcpy (GPacket.Name, MPlayerName); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), 0, NULL); +#endif //VIRTUAL_SUBNET_SERVER + + + CCDebugString ("C&C95 - About to reveal mouse\n"); + while (Get_Mouse_State() > 0) Show_Mouse(); + + /* + ---------------------------- Processing loop ----------------------------- + */ + CCDebugString ("C&C95 - Entering join dialogue loop\n"); + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_NONE, d_dialog_x, d_dialog_y, d_dialog_w); + + Fancy_Text_Print(TXT_CONNECTING, d_dialog_cx-width/2, d_dialog_y + 25*factor, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Draw_All(); + } + + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + + /*------------------------------------------------------------------ + CANCEL: send a SIGN_OFF, bail out with error code + ------------------------------------------------------------------*/ + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy (GPacket.Name, MPlayerName); + + /*............................................................... + Broadcast my sign-off over my network + ...............................................................*/ + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + /*............................................................... + Broadcast my sign-off over a bridged network if there is one + ...............................................................*/ + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + /*............................................................... + And now, just be absolutely sure, send my sign-off to each + player in my game. (If there's a bridge between us, the other + player will have specified my address, so he can cross the + bridge; but I may not have specified a bridge address, so the + only way I have of crossing the bridge is to send a packet + directly to him.) + ...............................................................*/ + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Players[i]->Address)); + Ipx.Service(); + } + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + MPlayerGameName[0] = 0; + process = false; + rc = false; + Send_Data_To_DDE_Server ("Hello", strlen("Hello"), DDEServerClass::DDE_CONNECTION_FAILED); + GameStatisticsPacketSent = false; + Spawn_WChat(false); + break; + + /*------------------------------------------------------------------ + default: exit loop with TRUE status + ------------------------------------------------------------------*/ + default: +#ifdef VIRTUAL_SUBNET_SERVER + if (Players.Count() == InternetMaxPlayers-1){ + + +#else //VIRTUAL_SUBNET_SERVER + if (Players.Count() > 0){ +#endif // VIRTUAL_SUBNET_SERVER + +//char ddkks[128]; +//sprintf (ddkks, "C&C95 - Players.Count() = %d\n", Players.Count()); +//CCDebugString (ddkks); + + /* + ** Wait for several secs after receiving request to join before sending + ** start game packet + */ + if (!player_joined){ + player_joined = true; + join_timer.Set (3*60, true); + break; + }else{ + if (join_timer.Time()) break; + } + +CCDebugString ("C&C95 - Join timer expired\n"); + + /*............................................................... + If a new player has joined in the last second, don't allow + an OK; force a wait longer than 2 seconds (to give all players + a chance to know about this new guy) + ...............................................................*/ + i = MAX(Ipx.Global_Response_Time() * 2, 120); + while (TickCount.Time() - ok_timer < i) + Ipx.Service(); + + /*............................................................... + If there are at least 2 players, go ahead & play; error otherwise + ...............................................................*/ + if (MPlayerSolo || Players.Count() > 0) { + rc = TRUE; + process = FALSE; + } else { + CCMessageBox().Process (TXT_ONLY_ONE,TXT_OOPS,NULL); + display = REDRAW_ALL; + } + } + break; + } + + /*--------------------------------------------------------------------- + Process incoming packets + ---------------------------------------------------------------------*/ + whahoppa = Get_NewGame_Responses(&playerlist); + if (whahoppa == EV_NEW_PLAYER) { + ok_timer = TickCount.Time(); + transmit = 1; + } else { + if (whahoppa == EV_MESSAGE) { + display = REDRAW_MESSAGE; + } + } + + /*--------------------------------------------------------------------- + If our Transmit flag is set, we need to send out a game option packet + ---------------------------------------------------------------------*/ + if (transmit) { + for (i = 0; i < Players.Count(); i++) { + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_GAME_OPTIONS; + GPacket.ScenarioInfo.Scenario = ScenarioIdx; //MPlayerFilenum[ScenarioIdx]; + GPacket.ScenarioInfo.Credits = MPlayerCredits; + GPacket.ScenarioInfo.IsBases = MPlayerBases; + GPacket.ScenarioInfo.IsTiberium = MPlayerTiberium; + GPacket.ScenarioInfo.IsGoodies = MPlayerGoodies; + GPacket.ScenarioInfo.IsGhosties = MPlayerGhosts; + GPacket.ScenarioInfo.BuildLevel = BuildLevel; + GPacket.ScenarioInfo.UnitCount = MPlayerUnitCount; + GPacket.ScenarioInfo.Seed = Seed; + GPacket.ScenarioInfo.Special = Special; + GPacket.ScenarioInfo.GameSpeed = Options.GameSpeed; + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &(Players[i]->Address) ); + } + transmit = 0; + } + + /*--------------------------------------------------------------------- + Ping every player in my game, to force the Global Channel to measure + the connection response time. + ---------------------------------------------------------------------*/ + if (TickCount.Time() - ping_timer > 15) { + memset (&GPacket, 0, sizeof(GlobalPacketType)); + GPacket.Command = NET_PING; + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &(Players[i]->Address) ); + } + ping_timer = TickCount.Time(); + } + + /*--------------------------------------------------------------------- + Service the Ipx connections + ---------------------------------------------------------------------*/ + Ipx.Service(); + + /*--------------------------------------------------------------------- + Service the sounds & score; GameActive must be false at this point, + so Call_Back() doesn't intercept global messages from me! + ---------------------------------------------------------------------*/ + Call_Back(); + + } /* end of while */ + +CCDebugString ("C&C95 - Exited process loop\n"); + + /*------------------------------------------------------------------------ + Establish connections with all other players. + ------------------------------------------------------------------------*/ + if (rc) { + /*..................................................................... + Set the number of players in this game, and my ID + .....................................................................*/ + MPlayerCount = Players.Count() + 1; + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + /*..................................................................... + Get the scenario filename + .....................................................................*/ + Scenario = ScenarioIdx; //PlayerFilenum[ScenarioIdx]; We are passed actual number now from wchat not index from + //Scenario = MPlayerFilenum[ScenarioIdx]; + + /*..................................................................... + Compute frame delay value for packet transmissions: + - Divide global channel's response time by 8 (2 to convert to 1-way + value, 4 more to convert from ticks to frames) + .....................................................................*/ + MPlayerMaxAhead = MAX( (Ipx.Global_Response_Time() / 8), 2); + + /*..................................................................... + Send all players the NET_GO packet. Wait until all ACK's have been + received. + .....................................................................*/ +CCDebugString ("C&C95 - Sending the 'GO' packet\n"); + memset (&GPacket, 0, sizeof(GlobalPacketType)); + GPacket.Command = NET_GO; + GPacket.ResponseTime.OneWay = MPlayerMaxAhead; + for (i = 0; i < Players.Count(); i++) { +char flopbuf [128]; +sprintf (flopbuf, "Sending 'GO' packet to address %d\n", *((unsigned short*)&(Players[i]->Address))); +CCDebugString (flopbuf); + + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &(Players[i]->Address) ); + /*.................................................................. + Wait for all the ACK's to come in. + ..................................................................*/ + while (Ipx.Global_Num_Send() > 0) + Ipx.Service(); + } + + /*..................................................................... + Form connections with all other players. Form the IPX Connection ID + from the player's Color (high byte) and House (low byte). This + will let us extract any player's color & house at any time. + Fill in 'tmp_id' while we're doing this. + .....................................................................*/ + for (i = 0; i < Players.Count(); i++) { + id = Build_MPlayerID (Players[i]->Player.Color, + Players[i]->Player.House); + + tmp_id[i] = id; + + Ipx.Create_Connection(id, Players[i]->Name, &(Players[i]->Address) ); + } + +#ifdef VIRTUAL_SUBNET_SERVER +CCDebugString ("C&C95 - Creating connection to the VSS\n"); + /* + ** Create an additional connection to the VSS + */ + if (UseVirtualSubnetServer){ + IPXAddressClass vss_global_address; + NetNodeType vss_node; + NetNumType vss_net; + memset (vss_net, 1, sizeof (vss_net)); + memset (vss_node, 0, sizeof (vss_node)); + vss_global_address.Set_Address(vss_net, vss_node); + Ipx.Create_Connection( VSS_ID, "VSS", &vss_global_address); + } +#endif //VIRTUAL_SUBNET_SERVER + tmp_id[i] = MPlayerLocalID; + + /*..................................................................... + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + .....................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + min_index = 0; + min_id = 0xff; + for (j = 0; j < MPlayerCount; j++) { + if (tmp_id[j] < min_id) { + min_id = tmp_id[j]; + min_index = j; + } + } + MPlayerID[i] = tmp_id[min_index]; + tmp_id[min_index] = 0xff; + } + /*..................................................................... + Fill in the array of player names, including my own. + .....................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID[i] == MPlayerLocalID) { + strcpy (MPlayerNames[i], MPlayerName); + } else { + strcpy (MPlayerNames[i], Ipx.Connection_Name(MPlayerID[i])); + } + } + } + + /*------------------------------------------------------------------------ + Init network timing values, using previous response times as a measure + of what our retry delta & timeout should be. + ------------------------------------------------------------------------*/ + Ipx.Set_Timing (Ipx.Global_Response_Time() + 2, -1, + Ipx.Global_Response_Time() * 4); + + /*------------------------------------------------------------------------ + Clear all lists + ------------------------------------------------------------------------*/ + Clear_Player_List(&playerlist); + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + if (rc){ + Wait_For_Focus(); + } + + return(rc); +} + + + + + + +/*********************************************************************************************** + * Net_Fake_Join_Dialog -- Like Net_Join_Dialog but with no dialogs. For Internet Play. * + * * + * This 'dialog' does all the non-dialog game set up stuff that is done in the normal * + * network game set up dialog. The only visible button is 'cancel' * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: 0 = good, -1 = bad * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 5/24/96 11:07AM ST : Created * + *=============================================================================================*/ + +static int Net_Fake_Join_Dialog(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ +/* ###Change collision detected! C:\PROJECTS\CODE\NETDLG.CPP... */ + int d_dialog_w = 120 *factor; // dialog width + int d_dialog_h = 80*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200*factor - d_dialog_h) / 2); // centered y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_margin1=10; + int d_txt6_h=15; + + int d_gamelist_w = 160*factor; + int d_gamelist_h = 27*factor; + int d_gamelist_x = 500*factor; //230*factor; //Off screen + //int d_gamelist_x = 230*factor; //Off screen + int d_gamelist_y = d_dialog_y + 20; + + int d_playerlist_w = 106*factor; + int d_playerlist_h = 27*factor; + //int d_playerlist_x = 10 * factor; //Off screen + int d_playerlist_x = 500*factor; //10 * factor; //Off screen + int d_playerlist_y = d_gamelist_y + 20; + +#if (GERMAN | FRENCH) + int d_cancel_w = 50*factor; +#else + int d_cancel_w = 45*factor; +#endif + int d_cancel_h = 9*factor; + int d_cancel_x = d_dialog_cx - d_cancel_w / 2; + int d_cancel_y = d_dialog_y + d_dialog_h - 20*factor; + + bool ready_to_go = false; + +#if (GERMAN | FRENCH) + int width=160*factor; + int height=80*factor; +#else + int width=120*factor; + int height=80*factor; +#endif //GERMAN | FRENCH + + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + Format_Window_String((char*)Text_String(TXT_CONNECTING), SeenBuff.Get_Height(), width, height); + +#if (GERMAN | FRENCH) + d_dialog_w = width + 25*factor; + d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + d_cancel_x = d_dialog_cx - (d_cancel_w / 2); +#endif + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + BUTTON_GAMELIST, + BUTTON_PLAYERLIST, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_MESSAGE, + REDRAW_COLORS, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + bool process = true; // process while true + KeyNumType input; + + JoinStateType joinstate = JOIN_NOTHING; // current "state" of this dialog + char namebuf[MPLAYER_NAME_MAX] = {0}; // buffer for player's name + int game_index = -1; // index of currently-selected game + int join_index = -1; // index of game we're joining + int rc = 0; // -1 = user cancelled, 1 = New + JoinEventType event; // event from incoming packet + int i,j; // loop counter + int parms_received; // 1 = game options received + + unsigned char tmp_id[MAX_PLAYERS]; // temp storage for sorting player ID's + int min_index; // for sorting player ID's + unsigned char min_id; // for sorting player ID's + unsigned char id; // connection ID + char * item; + unsigned long starttime; + + NodeNameType *who; + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + ColorListClass playerlist(BUTTON_PLAYERLIST, + d_playerlist_x, d_playerlist_y, d_playerlist_w, d_playerlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + ListClass gamelist(BUTTON_GAMELIST, + d_gamelist_x, d_gamelist_y, d_gamelist_w, d_gamelist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_cancel_x, d_cancel_y); +//#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +//#endif + + /* + ----------------------------- Various Inits ------------------------------ + */ + //MPlayerColorIdx = MPlayerPrefColor; // init my preferred color + + playerlist.Set_Selected_Style(ColorListClass::SELECT_NONE); + + Fancy_Text_Print("", 0, 0, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /* + --------------------------- Send network query --------------------------- + */ + CCDebugString ("C&C95 - About to call Send_Join_Queries.\n"); + Send_Join_Queries (game_index, 1, 0); + + Wait_For_Focus(); + + CCDebugString ("C&C95 - About to uncompress title page.\n"); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + CCDebugString ("C&C95 - About to set the palette.\n"); + Set_Palette(Palette); + CCDebugString ("C&C95 - Palette was set OK.\n"); + + if (LogicPage != &SeenBuff && LogicPage!= &HidPage){ + CCDebugString ("C&C95 - Logic page invalid\n"); + Set_Logic_Page (SeenBuff); + } + + + char a_buffer [128]; + sprintf (a_buffer, "C&C95 - Number of players:%d\n", Players.Count()); + CCDebugString (a_buffer); + + /* + ---------------------------- Init Mono Output ---------------------------- + */ + CCDebugString ("C&C95 - About to reveal mouse\n"); + while (Get_Mouse_State() > 0) Show_Mouse(); + + /* + ---------------------------- Processing loop ----------------------------- + */ + CCDebugString ("C&C95 - Entering join dialogue loop\n"); + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_NONE, d_dialog_x, d_dialog_y, d_dialog_w); + + Fancy_Text_Print(TXT_CONNECTING, d_dialog_cx-width/2, d_dialog_y + 25*factor, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /* + .................... Rebuild the button list .................... + */ + cancelbtn.Zap(); + gamelist.Zap(); + playerlist.Zap(); + + commands = &cancelbtn; + gamelist.Add_Tail(*commands); + playerlist.Add_Tail(*commands); + } + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Draw_All(); + } + + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + + /*------------------------------------------------------------------ + CANCEL: send a SIGN_OFF + - If we're part of a game, stay in this dialog; otherwise, exit + ------------------------------------------------------------------*/ + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy(GPacket.Name,MPlayerName); + + /*............................................................... + If we're joined to a game, make extra sure the other players in + that game know I'm exiting; send my SIGN_OFF as an ack-required + packet. Do not send this packet to myself (index 0). + ...............................................................*/ + if (joinstate == JOIN_CONFIRMED) { + // + // Remove myself from the player list box + // + item = (char *)(playerlist.Get_Item(0)); + playerlist.Remove_Item(item); + delete [] item; + playerlist.Flag_To_Redraw(); + + // + // Remove myself from the Players list + // + who = Players[0]; + Players.Delete(0); + delete who; + + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Players[i]->Address)); + Ipx.Service(); + } + } + + /*............................................................... + Now broadcast my SIGN_OFF so other players looking at this game + know I'm leaving. + ...............................................................*/ + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + + if (IsBridge) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + Send_Data_To_DDE_Server ("Hello", strlen("Hello"), DDEServerClass::DDE_CONNECTION_FAILED); + GameStatisticsPacketSent = false; + Spawn_WChat(false); + process = false; + rc = -1; +#if (0) + if (joinstate != JOIN_CONFIRMED) { + process = false; + rc = -1; + } else { + MPlayerGameName[0] = 0; + joinstate = JOIN_NOTHING; + display = REDRAW_ALL; + } +#endif //(0) + break; + + /*------------------------------------------------------------------ + JOIN: send a join request packet & switch to waiting-for-confirmation + mode. (Request_To_Join fills in MPlayerName with my namebuf.) + ------------------------------------------------------------------*/ + default: + if (joinstate == JOIN_NOTHING && Games.Count()!=0){ + gamelist.Set_Selected_Index(0); + join_index = gamelist.Current_Index(); + parms_received = 0; + if (Request_To_Join (MPlayerName, join_index, &playerlist, MPlayerHouse, + MPlayerColorIdx)) { + joinstate = JOIN_WAIT_CONFIRM; + } else { + display = REDRAW_ALL; + } + } + break; + + } + + /*--------------------------------------------------------------------- + Resend our query packets + ---------------------------------------------------------------------*/ + Send_Join_Queries(game_index, 0, 0); + + /*--------------------------------------------------------------------- + Process incoming packets + ---------------------------------------------------------------------*/ + event = Get_Join_Responses(&joinstate, &gamelist, &playerlist, + join_index); + /*..................................................................... + If we've changed state, redraw everything; if we're starting the game, + break out of the loop. If we've just joined, send out a player query + so I'll get added to the list instantly. + .....................................................................*/ + if (event == EV_STATE_CHANGE) { + display = REDRAW_ALL; + if (joinstate==JOIN_GAME_START) { +CCDebugString ("C&C95 - Received 'GO' packet\n"); + + ready_to_go = true; + } else { + + /*.................................................................. + If we're newly-confirmed, immediately send out a player query + ..................................................................*/ + if (joinstate==JOIN_CONFIRMED) { + + Clear_Player_List(&playerlist); + + item = new char [MPLAYER_NAME_MAX + 4]; + if (MPlayerHouse==HOUSE_GOOD) { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_G_D_I)); + } else { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_N_O_D)); + } + playerlist.Add_Item (item, MPlayerTColors[MPlayerColorIdx]); + + who = new NodeNameType; + strcpy(who->Name, MPlayerName); + who->Address = IPXAddressClass(); + who->Player.House = MPlayerHouse; + who->Player.Color = MPlayerColorIdx; + Players.Add (who); + + Send_Join_Queries (game_index, 0, 1); + } else { + + /*.................................................................. + If we've been rejected, clear any messages we may have been typing. + ..................................................................*/ + if (joinstate==JOIN_REJECTED) { + + // + // Remove myself from the player list box + // + item = (char *)(playerlist.Get_Item(0)); + if (item){ + playerlist.Remove_Item(item); + delete [] item; + playerlist.Flag_To_Redraw(); + } + + // + // Remove myself from the Players list + // + if (Players.Count()){ + who = Players[0]; + Players.Delete(0); + delete who; + } + } + } + } + } else + + /*..................................................................... + If a new game is detected, and it's the first game on our list, + automatically send out a player query for that game. + .....................................................................*/ + if (event == EV_NEW_GAME && gamelist.Count()==1) { + gamelist.Set_Selected_Index(0); + game_index = gamelist.Current_Index(); + Send_Join_Queries (game_index, 0, 1); + } else + + /*..................................................................... + If the game options have changed, print them. + .....................................................................*/ + if (event == EV_GAME_OPTIONS) { + parms_received = 1; + display = REDRAW_MESSAGE; + } else + + /*..................................................................... + Draw an incoming message + .....................................................................*/ + if (event == EV_MESSAGE) { + display = REDRAW_MESSAGE; + } else + + /*..................................................................... + A game before the one I've selected is gone, so we have a new index now. + 'game_index' must be kept set to the currently-selected list item, so + we send out queries for the currently-selected game. It's therefore + imperative that we detect any changes to the game list. + If we're joined in a game, we must decrement our game_index to keep + it aligned with the game we're joined to. + .....................................................................*/ + if (event == EV_GAME_SIGNOFF) { + if (joinstate==JOIN_CONFIRMED) { + game_index--; + join_index--; + gamelist.Set_Selected_Index(join_index); + } else { + gamelist.Flag_To_Redraw(); + Clear_Player_List(&playerlist); + game_index = gamelist.Current_Index(); + Send_Join_Queries (game_index, 0, 1); + } + } + + /*--------------------------------------------------------------------- + Service the Ipx connections + ---------------------------------------------------------------------*/ + Ipx.Service(); + + /*--------------------------------------------------------------------- + Clean out the Game List; if an old entry is found: + - Remove it + - Clear the player list + - Send queries for the new selected game, if there is one + ---------------------------------------------------------------------*/ + for (i = 0; i < Games.Count(); i++) { + if (TickCount.Time() - Games[i]->Game.LastTime > 400) { + Games.Delete(Games[i]); + item = (char *)(gamelist.Get_Item (i)); + gamelist.Remove_Item (item); + delete [] item; + if (i <= game_index) { + gamelist.Flag_To_Redraw(); + Clear_Player_List(&playerlist); + game_index = gamelist.Current_Index(); + Send_Join_Queries (game_index, 0, 1); + } + } + } + + /* + ** If we were flagged to start the game and we recognise both players then quit the loop + */ + +//char ddkks[128]; +//sprintf (ddkks, "C&C95 - Players.Count() = %d\n", Players.Count()); +//CCDebugString (ddkks); + if (ready_to_go){ // && Players.Count() == InternetMaxPlayers){ + rc = 0; + process = false; + } + + /*--------------------------------------------------------------------- + Service the sounds & score; GameActive must be false at this point, + so Call_Back() doesn't intercept global messages from me! + ---------------------------------------------------------------------*/ + Call_Back(); + } + + /*------------------------------------------------------------------------ + Establish connections with all other players. + ------------------------------------------------------------------------*/ + if (rc == 0) { + /*..................................................................... + If the other guys are playing a scenario I don't have (sniff), I can't + play. Try to bail gracefully. + .....................................................................*/ + if (ScenarioIdx==-1) { + CCMessageBox().Process (TXT_UNABLE_PLAY_WAAUGH); + + // + // Remove myself from the player list box + // + item = (char *)(playerlist.Get_Item(0)); + playerlist.Remove_Item(item); + delete [] item; + playerlist.Flag_To_Redraw(); + + // + // Remove myself from the Players list + // + who = Players[0]; + Players.Delete(0); + delete who; + + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy (GPacket.Name, MPlayerName); + + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Players[i]->Address)); + Ipx.Service(); + } + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + + if (IsBridge) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + rc = -1; + + } else { + + /*.................................................................. + Set the number of players in this game, and my ID + ..................................................................*/ + MPlayerCount = Players.Count(); + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + /*.................................................................. + Get the scenario number + ..................................................................*/ + Scenario = ScenarioIdx; //PlayerFilenum[ScenarioIdx]; We are passed actual number now from wchat not index from + + /*.................................................................. + Form connections with all other players. Form the IPX Connection ID + from the player's Color and House. This will let us extract any + player's color & house at any time. Fill in 'tmp_id' while we're + doing this. + ..................................................................*/ + for (i = 0; i < Players.Count(); i++) { + + /*............................................................... + Only create the connection if it's not myself! + ...............................................................*/ + if (strcmp (MPlayerName, Players[i]->Name)) { + id = Build_MPlayerID(Players[i]->Player.Color, + Players[i]->Player.House); + + tmp_id[i] = id; + + Ipx.Create_Connection((int)id, Players[i]->Name, &(Players[i]->Address) ); + } else { + tmp_id[i] = MPlayerLocalID; + } + } + +#ifdef VIRTUAL_SUBNET_SERVER + /* + ** Create an additional connection to the VSS + */ + if (UseVirtualSubnetServer){ + IPXAddressClass vss_global_address; + NetNodeType vss_node; + NetNumType vss_net; + memset (vss_net, 1, sizeof (vss_net)); + memset (vss_node, 0, sizeof (vss_node)); + vss_global_address.Set_Address(vss_net, vss_node); + Ipx.Create_Connection( VSS_ID, "VSS", &vss_global_address); + } +#endif //VIRTUAL_SUBNET_SERVER + + /*.................................................................. + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + ..................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + min_index = 0; + min_id = 0xff; + for (j = 0; j < MPlayerCount; j++) { + if (tmp_id[j] < min_id) { + min_id = tmp_id[j]; + min_index = j; + } + } + MPlayerID[i] = tmp_id[min_index]; + tmp_id[min_index] = 0xff; + } + /*.................................................................. + Fill in the array of player names, including my own. + ..................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID[i] == MPlayerLocalID) { + strcpy (MPlayerNames[i], MPlayerName); + } else { + strcpy (MPlayerNames[i], Ipx.Connection_Name(MPlayerID[i])); + } + } + } + /*--------------------------------------------------------------------- + Wait a while, polling the IPX service routines, to give our ACK + a chance to get to the other system. If he doesn't get our ACK, he'll + be waiting the whole time we load MIX files. + ---------------------------------------------------------------------*/ + i = MAX(Ipx.Global_Response_Time() * 2, 120); + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < i) { + Ipx.Service(); + } + } + + /*------------------------------------------------------------------------ + Init network timing values, using previous response times as a measure + of what our retry delta & timeout should be. + ------------------------------------------------------------------------*/ + Ipx.Set_Timing (Ipx.Global_Response_Time() + 2, -1, + Ipx.Global_Response_Time() * 4); + + /*------------------------------------------------------------------------ + Clear all lists + ------------------------------------------------------------------------*/ + Clear_Game_List(&gamelist); + Clear_Player_List(&playerlist); + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + if (rc != -1){ + Wait_For_Focus(); + } + + return(rc); +} + + + +#endif diff --git a/NETDLG.CPP.BAK b/NETDLG.CPP.BAK new file mode 100644 index 0000000..9201b4f --- /dev/null +++ b/NETDLG.CPP.BAK @@ -0,0 +1,5295 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\netdlg.cpv 2.17 16 Oct 1995 16:52:26 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : NETDLG.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : January 23, 1995 * + * * + * Last Update : July 8, 1995 [BRR] * + * * + *---------------------------------------------------------------------------------------------* + * * + * These routines establish & maintain peer-to-peer connections between this system * + * and all others in the game. Each system finds out the IPX address of the others, * + * and forms a direct connection (IPXConnectionClass) to that system. Systems are * + * found out via broadcast queries. Every system broadcasts its queries, and every * + * system replies to queries it receives. At the point when the game owner signals * + * 'OK', every system must know about all the other systems in the game. * + * * + * How Bridges are handled: * + * Currently, bridges are handled by specifying the destination IPX address of the * + * "server" (game owner's system) on the command-line. This address is used to * + * derive a broadcast address to that destination network, and this system's queries * + * are broadcast over its network & the server's network; replies to the queries come * + * with each system's IPX address attached, so once we have the address, we can form * + * a connection with any system on the bridged net. * + * * + * The flaw in this plan is that we can only cross one bridge. If there are 3 nets * + * bridged (A, B, & C), and the server is on net B, and we're on net A, our broadcasts * + * will reach nets A & B, but not C. The way to circumvent this (if it becomes a problem) * + * would be to have the server tell us what other systems are in its game, not each * + * individual player's system. Thus, each system would find out about all the other systems * + * by interacting with the game's owner system (this would be more involved than what * + * I'm doing here). * + * * + * Here's a list of all the different packets sent over the Global Channel: * + * * + * NET_QUERY_GAME * + * (no other data) * + * NET_ANSWER_GAME * + * Name: game owner's name * + * GameInfo: game's version & open state * + * NET_QUERY_PLAYER * + * Name: name of game we want players to respond for * + * NET_ANSWER_PLAYER * + * Name: player's name * + * PlayerInfo: info about player * + * NET_QUERY_JOIN * + * Name: name of player wanting to join * + * PlayerInfo: player's requested house & color * + * NET_CONFIRM_JOIN * + * PlayerInfo: approves player's house & color * + * NET_REJECT_JOIN * + * (no other data) * + * NET_GAME_OPTIONS * + * ScenarioInfo: info about scenario * + * NET_SIGN_OFF * + * Name: name of player signing off * + * NET_PING * + * (no other data) * + * NET_GO * + * Delay: value of one-way response time, in frames * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Clear_Game_List -- Clears the game-name listbox & 'Games' Vector * + * Clear_Player_List -- Clears the player-name listbox & Vector * + * Destroy_Connection -- destroys the given connection * + * Get_Join_Responses -- sends queries for the Join Dialog * + * Get_NewGame_Responses -- processes packets for New Game dialog * + * Init_Network -- initializes network stuff * + * Net_Join_Dialog -- lets user join an existing game, or start a new one * + * Net_New_Dialog -- lets user start a new game * + * Process_Global_Packet -- responds to remote queries * + * Remote_Connect -- handles connecting this user to others * + * Request_To_Join -- Sends a JOIN request packet to game owner * + * Send_Join_Queries -- sends queries for the Join Dialog * + * Shutdown_Network -- shuts down network stuff * + * Compute_Name_CRC -- computes CRC from char string * + * Net_Reconnect_Dialog -- Draws/updates the network reconnect dialog * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include +#include "tcpip.h" + +#define SHOW_MONO 0 + + +#ifndef DEMO + +/*--------------------------------------------------------------------------- +The possible states of the join-game dialog +---------------------------------------------------------------------------*/ +typedef enum { + JOIN_REJECTED = -1, // we've been rejected + JOIN_NOTHING, // we're not trying to join a game + JOIN_WAIT_CONFIRM, // we're asking to join, & waiting for confirmation + JOIN_CONFIRMED, // we've been confirmed + JOIN_GAME_START, // the game we've joined is starting +} JoinStateType; + +/*--------------------------------------------------------------------------- +The possible return codes from Get_Join_Responses() +---------------------------------------------------------------------------*/ +typedef enum { + EV_NONE, // nothing happened + EV_STATE_CHANGE, // Join dialog is in a new state + EV_NEW_GAME, // a new game was detected + EV_NEW_PLAYER, // a new player was detected + EV_PLAYER_SIGNOFF, // a player has signed off + EV_GAME_SIGNOFF, // a gamed owner has signed off + EV_GAME_OPTIONS, // a game options packet was received + EV_MESSAGE, // a message was received +} JoinEventType; + + +/* +******************************** Prototypes ********************************* +*/ +static int Net_Join_Dialog(void); +static void Clear_Game_List (ListClass *gamelist); +static void Clear_Player_List (ListClass *playerlist); +static int Request_To_Join (char *playername, int join_index, ListClass *playerlist, + HousesType house, int color); +static void Send_Join_Queries(int curgame, int gamenow, int playernow); +static JoinEventType Get_Join_Responses(JoinStateType *joinstate, ListClass *gamelist, + ColorListClass *playerlist, int join_index); +static int Net_New_Dialog(void); +static JoinEventType Get_NewGame_Responses(ColorListClass *playerlist); +static int Net_Fake_New_Dialog(void); +static int Net_Fake_Join_Dialog(void); + + +/*********************************************************************************************** + * Init_Network -- initializes network stuff * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = Initialization OK, false = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +bool Init_Network (void) +{ + NetNumType net; + NetNodeType node; + + /*------------------------------------------------------------------------ + This call allocates all necessary queue buffers, allocates Real-mode + memory, and commands IPX to start listening on the Global Channel. + ------------------------------------------------------------------------*/ + if (!Ipx.Init()) + return(false); + + /*------------------------------------------------------------------------ + Allocate our "meta-packet" buffer + ------------------------------------------------------------------------*/ + if (!MetaPacket) { + MetaPacket = new char [sizeof (EventClass) * MAX_EVENTS]; + } + + /*------------------------------------------------------------------------ + Set up the IPX manager to cross a bridge + ------------------------------------------------------------------------*/ + if (!(GameToPlay == GAME_INTERNET)){ + if (IsBridge) { + BridgeNet.Get_Address(net,node); + Ipx.Set_Bridge(net); + } + } + + return(true); + +} /* end of Init_Network */ + + +/*********************************************************************************************** + * Shutdown_Network -- shuts down network stuff * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +void Shutdown_Network (void) +{ + /*------------------------------------------------------------------------ + Broadcast a sign-off packet, by sending the packet over the Global Channel, + telling the IPX Manager that no ACK is required, and specifying a NULL + destination address. + ------------------------------------------------------------------------*/ + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy (GPacket.Name, MPlayerName); + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), 0, NULL); + + if (IsBridge && !Winsock.Get_Connected()) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, &BridgeNet); + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, &BridgeNet); + } + + /*------------------------------------------------------------------------ + Wait for the packets to finish going out (or the Global Channel times out) + ------------------------------------------------------------------------*/ + for (;;) { + if (Ipx.Global_Num_Send()==0) { + break; + } + Ipx.Service(); + } + + /*------------------------------------------------------------------------ + Delete our "meta-packet" + ------------------------------------------------------------------------*/ + delete [] MetaPacket; + MetaPacket = 0; + + /*------------------------------------------------------------------------ + If I was in a game, I'm not now, so clear the game name + ------------------------------------------------------------------------*/ + MPlayerGameName[0] = 0; +} + + +/*********************************************************************************************** + * Process_Global_Packet -- responds to remote queries * + * * + * The only commands from other systems this routine responds to are NET_QUERY_GAME * + * and NET_QUERY_PLAYER. The other commands are too context-specific to be able * + * to handle here, such as joining the game or signing off; but this routine handles * + * the majority of the program's needs. * + * * + * INPUT: * + * packet ptr to packet to process * + * address source address of sender * + * * + * OUTPUT: * + * true = packet was processed, false = wasn't * + * * + * WARNINGS: * + * MPlayerName & MPlayerGameName must have been filled in before this function * + * can be called. * + * * + * HISTORY: * + * 02/15/1995 BR : Created. * + *=============================================================================================*/ +bool Process_Global_Packet(GlobalPacketType *packet, IPXAddressClass *address) +{ + GlobalPacketType mypacket; + + /* + ---------------- Another system asking what game this is ----------------- + */ + if (packet->Command==NET_QUERY_GAME && NetStealth==0) { + /*..................................................................... + If the game is closed, let every player respond, and let the sender of + the query sort it all out. This way, if the game's host exits the game, + the game still shows up on other players' dialogs. + If the game is open, only the game owner may respond. + .....................................................................*/ + if (strlen(MPlayerName) > 0 && strlen(MPlayerGameName) > 0 && + ((!NetOpen) || (NetOpen && !strcmp(MPlayerName,MPlayerGameName)))) { + memset (packet, 0, sizeof(GlobalPacketType)); + + mypacket.Command = NET_ANSWER_GAME; + strcpy(mypacket.Name, MPlayerGameName); +#ifdef PATCH + if (IsV107) { + mypacket.GameInfo.Version = 1; + } else { + mypacket.GameInfo.Version = 2; + } +#else + mypacket.GameInfo.Version = Version_Number(); +#endif + mypacket.GameInfo.IsOpen = NetOpen; + + Ipx.Send_Global_Message (&mypacket, sizeof(GlobalPacketType), 1, + address); + } + return(true); + } else { + + /* + ----------------- Another system asking what player I am ----------------- + */ + if (packet->Command==NET_QUERY_PLAYER && + !strcmp (packet->Name, MPlayerGameName) && + (strlen(MPlayerGameName) > 0) && NetStealth==0) { + memset (packet, 0, sizeof(GlobalPacketType)); + + mypacket.Command = NET_ANSWER_PLAYER; + strcpy(mypacket.Name, MPlayerName); + mypacket.PlayerInfo.House = MPlayerHouse; + mypacket.PlayerInfo.Color = MPlayerColorIdx; + mypacket.PlayerInfo.NameCRC = Compute_Name_CRC(MPlayerGameName); + + Ipx.Send_Global_Message (&mypacket, sizeof(GlobalPacketType), 1, address); + return(true); + } + } + return(false); +} + + +/*********************************************************************************************** + * Destroy_Connection -- destroys the given connection * + * * + * Call this routine when a connection goes bad, or another player signs off. * + * * + * INPUT: * + * id connection ID to destroy * + * error 0 = user signed off; 1 = connection error; otherwise, no error is shown. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/22/1995 BR : Created. * + *=============================================================================================*/ +void Destroy_Connection(int id, int error) +{ + int i,j; + HousesType house; + HouseClass *housep; + char txt[80]; + + /*------------------------------------------------------------------------ + Create a message to display to the user + ------------------------------------------------------------------------*/ + txt[0] = '\0'; + if (error==1) { + sprintf(txt,Text_String(TXT_CONNECTION_LOST),Ipx.Connection_Name(id)); + } else if (error==0) { + sprintf(txt,Text_String(TXT_LEFT_GAME),Ipx.Connection_Name(id)); + } + + if (strlen(txt)) { + Messages.Add_Message (txt, + MPlayerTColors[MPlayerID_To_ColorIndex((unsigned char)id)], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, 0, 0); + Map.Flag_To_Redraw(false); + } + + /*------------------------------------------------------------------------ + Delete the IPX connection, shift the MPlayerID's & MPlayerHouses' back one. + ------------------------------------------------------------------------*/ + Ipx.Delete_Connection(id); + + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID[i] == (unsigned char)id) { + /*.................................................................. + Turn the player's house over to the computer's AI + ..................................................................*/ + house = MPlayerHouses[i]; + housep = HouseClass::As_Pointer (house); + housep->IsHuman = false; + housep->IsStarted = true; + + /*.................................................................. + Move arrays back by one + ..................................................................*/ + for (j = i; j < MPlayerCount - 1; j++) { + MPlayerID[j] = MPlayerID[j + 1]; + MPlayerHouses[j] = MPlayerHouses[j + 1]; + strcpy (MPlayerNames[j], MPlayerNames[j+1]); + TheirProcessTime[j] = TheirProcessTime[j+1]; + } + } + } + + MPlayerCount--; + + /*------------------------------------------------------------------------ + If we're the last player left, tell the user. + ------------------------------------------------------------------------*/ + if (MPlayerCount == 1) { + sprintf(txt,"%s",Text_String(TXT_JUST_YOU_AND_ME)); + Messages.Add_Message (txt, + MPlayerTColors[MPlayerID_To_ColorIndex((unsigned char)id)], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, 0, 0); + Map.Flag_To_Redraw(false); + } + +} /* end of Destroy_Connection */ + + +/*********************************************************************************************** + * Remote_Connect -- handles connecting this user to others * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = connections established; false = not * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +bool Remote_Connect(void) +{ + int rc; + int stealth; // original state of NetStealth flag + + /*------------------------------------------------------------------------ + Init network timing parameters; these values should work for both a "real" + network, and a simulated modem network (ie Kali) + ------------------------------------------------------------------------*/ + Ipx.Set_Timing ( 30, // retry 2 times per second + -1, // ignore max retries + 600); // give up after 10 seconds + + /*------------------------------------------------------------------------ + Save the original value of the NetStealth flag, so we can turn stealth + off for now (during this portion of the dialogs, we must show ourselves) + ------------------------------------------------------------------------*/ + stealth = NetStealth; + NetStealth = 0; + + /*------------------------------------------------------------------------ + Init my game name to 0-length, since I haven't joined any game yet. + ------------------------------------------------------------------------*/ + MPlayerGameName[0] = 0; + + /*------------------------------------------------------------------------ + The game is now "open" for joining. Close it as soon as we exit this + routine. + ------------------------------------------------------------------------*/ + NetOpen = 1; + + /*------------------------------------------------------------------------ + Read the default values from the INI file + ------------------------------------------------------------------------*/ + Read_MultiPlayer_Settings (); + + /*------------------------------------------------------------------------ + Keep looping until something useful happens. + ------------------------------------------------------------------------*/ + while (1) { + /*--------------------------------------------------------------------- + Pop up the network Join/New dialog + ---------------------------------------------------------------------*/ + rc = Net_Join_Dialog(); + + /*--------------------------------------------------------------------- + -1 = user selected Cancel + ---------------------------------------------------------------------*/ + if (rc==-1) { + NetStealth = stealth; + NetOpen = 0; + return(false); + } else { + + /*--------------------------------------------------------------------- + 0 = user has joined an existing game; save values & return + ---------------------------------------------------------------------*/ + if (rc==0) { + Write_MultiPlayer_Settings (); + NetStealth = stealth; + NetOpen = 0; + + return(true); + } else { + + /*--------------------------------------------------------------------- + 1 = user requests New Network Game + ---------------------------------------------------------------------*/ + if (rc==1) { + /*.................................................................. + Pop up the New Network Game dialog; if user selects OK, return + 'true'; otherwise, return to the Join Dialog. + ..................................................................*/ + if (Net_New_Dialog()) { + Write_MultiPlayer_Settings (); + NetOpen = 0; + NetStealth = stealth; + NetOpen = 0; + + return(true); + } else { + continue; + } + } + } + } + } +} + + + +/*********************************************************************************************** + * Remote_Connect -- handles connecting this host to the server in an internet game * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = connections established; false = not * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +bool Server_Remote_Connect(void) +{ + int stealth; // original state of NetStealth flag + + /*------------------------------------------------------------------------ + Init network timing parameters; these values should work for both a "real" + network, and a simulated modem network (ie Kali) + ------------------------------------------------------------------------*/ + Ipx.Set_Timing ( 30, // retry 2 times per second + -1, // ignore max retries + 600); // give up after 10 seconds + + /*------------------------------------------------------------------------ + Save the original value of the NetStealth flag, so we can turn stealth + off for now (during this portion of the dialogs, we must show ourselves) + ------------------------------------------------------------------------*/ + stealth = NetStealth; + NetStealth = 0; + + /*------------------------------------------------------------------------ + The game is now "open" for joining. Close it as soon as we exit this + routine. + ------------------------------------------------------------------------*/ + NetOpen = 1; + + /*------------------------------------------------------------------------ + Read the default values from the INI file + ------------------------------------------------------------------------*/ + Read_MultiPlayer_Settings (); + + if (!Net_Fake_New_Dialog()){ + Write_MultiPlayer_Settings (); + return (false); + } + + NetOpen = 0; + NetStealth = stealth; + Write_MultiPlayer_Settings (); + return (true); +} + + +/*********************************************************************************************** + * Client_Remote_Connect -- handles connecting this client to the server in an internet game * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = connections established; false = not * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 ST : Created. * + *=============================================================================================*/ +bool Client_Remote_Connect(void) +{ + int rc; + int stealth; // original state of NetStealth flag + + /*------------------------------------------------------------------------ + Init network timing parameters; these values should work for both a "real" + network, and a simulated modem network (ie Kali) + ------------------------------------------------------------------------*/ + Ipx.Set_Timing ( 30, // retry 2 times per second + -1, // ignore max retries + 600); // give up after 10 seconds + + /*------------------------------------------------------------------------ + Save the original value of the NetStealth flag, so we can turn stealth + off for now (during this portion of the dialogs, we must show ourselves) + ------------------------------------------------------------------------*/ + stealth = NetStealth; + NetStealth = 0; + + /*------------------------------------------------------------------------ + The game is now "open" for joining. Close it as soon as we exit this + routine. + ------------------------------------------------------------------------*/ + NetOpen = 1; + + /*------------------------------------------------------------------------ + Read the default values from the INI file + ------------------------------------------------------------------------*/ + Read_MultiPlayer_Settings (); + + /*--------------------------------------------------------------------- + Pop up the network Join/New dialog + ---------------------------------------------------------------------*/ + rc = Net_Fake_Join_Dialog(); + Write_MultiPlayer_Settings (); + + NetStealth = stealth; + NetOpen = 0; + + if (rc == -1) { + return(false); + } else { + return(true); + } +} + + + +/*********************************************************************************************** + * Net_Join_Dialog -- lets user join an existing game or start a new one * + * * + * This dialog displays an edit field for the player's name, and a list of all non-stealth- * + * mode games. Clicking once on a game name displays a list of who's in that game. Clicking * + * "New" takes the user to the Net_New dialog, where he waits for other users to join his * + * game. All other input is done through this dialog. * + * * + * The dialog has several "states": * + * * + * 1) Initially, it waits for the user to fill in his/her name and then to select Join or New; * + * if New is selected, this dialog is exited. * + * * + * 2) If Join is selected, the Join & New buttons are removed, but the Cancel button remains. * + * The join request is transmitted to the game's owner, and the message "Waiting for * + * Confirmation" is displayed, until a confirmation or denial is received from the game's * + * owner. The user may click Cancel at this point to cancel the join request. * + * (Once Join is selected, the name editing field is disabled, and becomes a display-only * + * field. If cancel is selected, it reappears as an edit field.) The user can still click * + * around & see who's in which games. * + * * + * 3) If the join request is denied, the dialog re-initializes to its pre-join state; the * + * Join & New buttons reappear, & the Name field is available again. * + * * + * 4) If join confirmation is obtained, the message just changes to "Confirmed. Waiting for * + * Entry Signal." or some such nonsense. The user can still click around & see who's * + * in which games. * + * * + * Any game running in Stealth mode won't show up on this dialog. * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Network Games ³ * + * ³ ³ * + * ³ Your Name: ____________ ³ * + * ³ House: [GDI] [NOD] ³ * + * ³ Desired Color: [ ][ ][ ][ ] ³ * + * ³ ³ * + * ³ Games Players ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ³ * + * ³ ³(Bill's Game )³³ ³ Peter Parker GDI ³³ ³ * + * ³ ³ Peter Parker's Game ÃÄ´ ³ Mary Jane GDI ÃÄ´ ³ * + * ³ ³(Magnum PI's Game )³ ³ ³ JJ Jameson NOD ³ ³ ³ * + * ³ ³ ÃÄ´ ³ ÃÄ´ ³ * + * ³ ³ ³³ ³ ³³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ³ * + * ³ Scenario: Big Long Description ³ * + * ³ Starting Credits: xxxx ³ * + * ³ Count: --- Level: --- ³ * + * ³ Bases: ON Crates: ON ³ * + * ³ Tiberium: ON AI Players: ON ³ * + * ³ ³ * + * ³ [Join] [Cancel] [New] ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ * + * ³ ³ ³ ³ * + * ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ [Send Message] ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * -1 = cancel, 0 = OK, 1 = New net game requested * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +static int Net_Join_Dialog(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ +/* ###Change collision detected! C:\PROJECTS\CODE\NETDLG.CPP... */ + int d_dialog_w = 287 *factor; // dialog width + int d_dialog_h = 198*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200*factor - d_dialog_h) / 2); // centered y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 6*factor+1; // ht of 6-pt text + int d_margin1 = 5*factor; // large margin + int d_margin2 = 2*factor; // small margin + + int d_name_w = 70*factor; + int d_name_h = 9*factor; + int d_name_x = d_dialog_cx - 10*factor; + int d_name_y = d_dialog_y + d_margin1 + d_txt6_h + d_txt6_h; + + int d_gdi_w = 30*factor; + int d_gdi_h = 9*factor; + int d_gdi_x = d_dialog_cx - 10*factor; + int d_gdi_y = d_name_y + d_name_h + d_margin2; + + int d_nod_w = 30*factor; + int d_nod_h = 9*factor; + int d_nod_x = d_gdi_x + d_gdi_w; + int d_nod_y = d_name_y + d_name_h + d_margin2; + + int d_color_w = 10*factor; + int d_color_h = 9*factor; + int d_color_y = d_nod_y + d_nod_h + d_margin2; + + int d_gamelist_w = 160*factor; + int d_gamelist_h = 27*factor; + int d_gamelist_x = d_dialog_x + d_margin1; + int d_gamelist_y = d_color_y + d_color_h + d_margin1 + d_txt6_h; + + int d_playerlist_w = 106*factor; + int d_playerlist_h = 27*factor; + int d_playerlist_x = d_dialog_x + d_dialog_w - d_margin1 - d_playerlist_w; + int d_playerlist_y = d_color_y + d_color_h + d_margin1 + d_txt6_h; + + int d_msg1_y = d_gamelist_y + d_gamelist_h + d_margin1; + int d_msg2_y = d_msg1_y + d_txt6_h; + int d_msg3_y = d_msg2_y + d_txt6_h; + int d_msg4_y = d_msg3_y + d_txt6_h; + int d_msg5_y = d_msg4_y + d_txt6_h; + + int d_join_w = 40*factor; + int d_join_h = 9*factor; + int d_join_x = d_dialog_x + (d_dialog_w / 6) - (d_join_w / 2); + int d_join_y = d_msg5_y + d_txt6_h + d_margin1; + +#if (GERMAN | FRENCH) + int d_cancel_w = 50*factor; +#else + int d_cancel_w = 40*factor; +#endif + int d_cancel_h = 9*factor; + int d_cancel_x = d_dialog_cx - d_cancel_w / 2; + int d_cancel_y = d_msg5_y + d_txt6_h + d_margin1; + + int d_new_w = 40*factor; + int d_new_h = 9*factor; + int d_new_x = d_dialog_x + ((d_dialog_w * 5) / 6) - (d_new_w / 2); + int d_new_y = d_msg5_y + d_txt6_h + d_margin1; + + int d_message_w = d_dialog_w - (d_margin1 * 2); + int d_message_h = 34*factor; + int d_message_x = d_dialog_x + d_margin1; + int d_message_y = d_cancel_y + d_cancel_h + d_margin1; + + int d_send_w = 80*factor; + int d_send_h = 9*factor; + int d_send_x = d_dialog_cx - (d_send_w / 2); + int d_send_y = d_message_y + d_message_h + d_margin2; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_NAME = 100, + BUTTON_GDI, + BUTTON_NOD, + BUTTON_GAMELIST, + BUTTON_PLAYERLIST, + BUTTON_JOIN, + BUTTON_CANCEL, + BUTTON_NEW, + BUTTON_SEND, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_MESSAGE, + REDRAW_COLORS, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + bool process = true; // process while true + KeyNumType input; + int cbox_x[] = { d_gdi_x, + d_gdi_x + d_color_w, + d_gdi_x + (d_color_w * 2), + d_gdi_x + (d_color_w * 3), + d_gdi_x + (d_color_w * 4), + d_gdi_x + (d_color_w * 5)}; + + JoinStateType joinstate = JOIN_NOTHING; // current "state" of this dialog + char namebuf[MPLAYER_NAME_MAX] = {0}; // buffer for player's name + int tabs[] = {77*factor}; // tabs for player list box + int game_index = -1; // index of currently-selected game + int join_index = -1; // index of game we're joining + int rc = 0; // -1 = user cancelled, 1 = New + JoinEventType event; // event from incoming packet + int i,j; // loop counter + char txt[80]; + char const *p; + int parms_received; // 1 = game options received + int found; + + unsigned char tmp_id[MAX_PLAYERS]; // temp storage for sorting player ID's + int min_index; // for sorting player ID's + unsigned char min_id; // for sorting player ID's + unsigned char id; // connection ID + char * item; + unsigned long starttime; + + NodeNameType *who; + + int message_length; + int sent_so_far; + unsigned short magic_number; + unsigned short crc; + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + EditClass name_edt (BUTTON_NAME, + namebuf, MPLAYER_NAME_MAX, TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_name_x, d_name_y, d_name_w, d_name_h, EditClass::ALPHANUMERIC); + + TextButtonClass gdibtn(BUTTON_GDI, TXT_G_D_I, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_gdi_x, d_gdi_y, d_gdi_w, d_gdi_h); + + TextButtonClass nodbtn(BUTTON_NOD, TXT_N_O_D, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_nod_x, d_nod_y, d_nod_w, d_nod_h); + + ListClass gamelist(BUTTON_GAMELIST, + d_gamelist_x, d_gamelist_y, d_gamelist_w, d_gamelist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + ColorListClass playerlist(BUTTON_PLAYERLIST, + d_playerlist_x, d_playerlist_y, d_playerlist_w, d_playerlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + TextButtonClass joinbtn(BUTTON_JOIN, TXT_JOIN, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#ifdef FRENCH + d_join_x, d_join_y); +#else + d_join_x, d_join_y, d_join_w, d_join_h); +#endif + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_cancel_x, d_cancel_y); +#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +#endif + + TextButtonClass newbtn(BUTTON_NEW, TXT_NEW, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_new_x, d_new_y, d_new_w, d_new_h); + + TextButtonClass sendbtn(BUTTON_SEND, TXT_SEND_MESSAGE, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_send_x, d_send_y); +#else + d_send_x, d_send_y, d_send_w, d_send_h); +#endif + + playerlist.Set_Tabs(tabs); + + /* + ----------------------------- Various Inits ------------------------------ + */ + MPlayerColorIdx = MPlayerPrefColor; // init my preferred color + strcpy (namebuf, MPlayerName); // set my name + name_edt.Set_Text(namebuf,MPLAYER_NAME_MAX); + name_edt.Set_Color (MPlayerTColors[MPlayerColorIdx]); + + playerlist.Set_Selected_Style(ColorListClass::SELECT_NONE); + + if (MPlayerHouse==HOUSE_GOOD) { + gdibtn.Turn_On(); + } else { + nodbtn.Turn_On(); + } + + Fancy_Text_Print("", 0, 0, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Messages.Init (d_message_x + 2, d_message_y + 2, 4, + MAX_MESSAGE_LENGTH, d_txt6_h); + + /* + --------------------------- Send network query --------------------------- + */ + Send_Join_Queries (game_index, 1, 0); + + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + /* + ---------------------------- Init Mono Output ---------------------------- + */ + #if(SHOW_MONO) + Ipx.Configure_Debug(-1, sizeof (GlobalHeaderType), + sizeof(NetCommandType), GlobalPacketNames, 11); + Ipx.Mono_Debug_Print(-1,1); + #endif + while (Get_Mouse_State() > 0) Show_Mouse(); + + /* + ---------------------------- Processing loop ----------------------------- + */ + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + #if(SHOW_MONO) + Ipx.Mono_Debug_Print(-1,0); + #endif + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_JOIN_NETWORK_GAME, d_dialog_x, d_dialog_y, d_dialog_w); + + Fancy_Text_Print(TXT_YOUR_NAME, + d_name_x - 5, d_name_y + 1, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_SIDE_COLON, + d_gdi_x - 5, d_gdi_y + 1, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_COLOR_COLON, + cbox_x[0] - 5, d_color_y + 1, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_GAMES, + d_gamelist_x + (d_gamelist_w / 2), d_gamelist_y - d_txt6_h, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_PLAYERS, + d_playerlist_x + (d_playerlist_w / 2), d_playerlist_y - d_txt6_h, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................... + Join-state-specific labels: + ...............................................................*/ + if (joinstate > JOIN_NOTHING) { + Fancy_Text_Print(namebuf, d_name_x, d_name_y + 1, CC_GREEN, + TBLACK, TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + if (MPlayerHouse==HOUSE_GOOD) { + Fancy_Text_Print(TXT_G_D_I, d_gdi_x, d_gdi_y + 1, CC_GREEN, + TBLACK, TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } else { + Fancy_Text_Print(TXT_N_O_D, d_gdi_x, d_gdi_y + 1, CC_GREEN, + TBLACK, TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + } + + /* + .................... Rebuild the button list .................... + */ + cancelbtn.Zap(); + gamelist.Zap(); + playerlist.Zap(); + gdibtn.Zap(); + nodbtn.Zap(); + name_edt.Zap(); + joinbtn.Zap(); + newbtn.Zap(); + sendbtn.Zap(); + + commands = &cancelbtn; + gamelist.Add_Tail(*commands); + playerlist.Add_Tail(*commands); + /*............................................................... + Only add the name edit field, the House, Join & New buttons if + we're doing nothing, or we've just been rejected. + ...............................................................*/ + if (joinstate <= JOIN_NOTHING) { + gdibtn.Add_Tail(*commands); + nodbtn.Add_Tail(*commands); + name_edt.Add_Tail(*commands); + joinbtn.Add_Tail(*commands); + newbtn.Add_Tail(*commands); + } + if (joinstate == JOIN_CONFIRMED) + sendbtn.Add_Tail(*commands); + } + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Draw_All(); + } + + /*.................................................................. + Draw the color boxes + ..................................................................*/ + if (display >= REDRAW_COLORS) { + for (i = 0; i < MAX_MPLAYER_COLORS; i++) { + LogicPage->Fill_Rect (cbox_x[i] + 1, d_color_y + 1, + cbox_x[i] + 1 + d_color_w - 2, d_color_y + 1 + d_color_h - 2, + MPlayerGColors[i]); + + if (i == MPlayerColorIdx) { + Draw_Box(cbox_x[i], d_color_y, d_color_w, d_color_h, + BOXSTYLE_GREEN_DOWN, false); + } else { + Draw_Box(cbox_x[i], d_color_y, d_color_w, d_color_h, + BOXSTYLE_GREEN_RAISED, false); + } + } + } + + /*.................................................................. + Draw the message: + - Erase an old message first + - If we're in a game, print the game options (if they've been + received) + - If we've been rejected from a game, print that message + ..................................................................*/ + if (display >= REDRAW_MESSAGE) { + Draw_Box(d_message_x, d_message_y, d_message_w, d_message_h, + BOXSTYLE_GREEN_BORDER, true); + Messages.Draw(); + + LogicPage->Fill_Rect( d_dialog_x + 2, + d_msg1_y, + d_dialog_x + d_dialog_w - 4, + d_msg5_y + d_txt6_h, + BLACK); + + if (joinstate==JOIN_CONFIRMED && parms_received) { + /*............................................................ + Scenario title + ............................................................*/ + p = Text_String(TXT_SCENARIO_COLON); + if (ScenarioIdx != -1) { + sprintf(txt,"%s %s",p, MPlayerScenarios[ScenarioIdx]); + + Fancy_Text_Print (txt, d_dialog_cx, + d_msg1_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW | TPF_CENTER); + } else { + sprintf(txt,"%s %s",p,Text_String(TXT_NOT_FOUND)); + + Fancy_Text_Print (txt, d_dialog_cx, + d_msg1_y, CC_NOD_COLOR, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW | TPF_CENTER); + } + + /*............................................................ + # of credits + ............................................................*/ + p = Text_String(TXT_START_CREDITS_COLON); + sprintf(txt, "%s %d", p, MPlayerCredits); + Fancy_Text_Print (txt, d_dialog_cx, + d_msg2_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW | TPF_CENTER); + + /*............................................................ + Count & Level values + ............................................................*/ + p = Text_String(TXT_COUNT); + sprintf(txt,"%s %d",p,MPlayerUnitCount); + Fancy_Text_Print (txt, + d_dialog_x + (d_dialog_w / 4) - String_Pixel_Width(p), + d_msg3_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + p = Text_String(TXT_LEVEL); + if (BuildLevel <= MPLAYER_BUILD_LEVEL_MAX) { + sprintf(txt, "%s %d", p, BuildLevel); + } else { + sprintf(txt, "%s **", p); + } + Fancy_Text_Print (txt, + d_dialog_x + d_dialog_w - (d_dialog_w / 4) - String_Pixel_Width(p), + d_msg3_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Bases + ............................................................*/ + p = Text_String(TXT_BASES_COLON); + if (MPlayerBases) { + sprintf(txt,"%s %s",p,Text_String(TXT_ON)); + } else { + sprintf(txt,"%s %s",p,Text_String(TXT_OFF)); + } + Fancy_Text_Print (txt, + d_dialog_x + (d_dialog_w / 4) - String_Pixel_Width(p), + d_msg4_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Tiberium + ............................................................*/ + p = Text_String(TXT_TIBERIUM_COLON); + if (MPlayerTiberium) { + sprintf(txt,"%s %s",p,Text_String(TXT_ON)); + } else { + sprintf(txt,"%s %s",p,Text_String(TXT_OFF)); + } + + Fancy_Text_Print (txt, + d_dialog_x + (d_dialog_w / 4) - String_Pixel_Width(p), + d_msg5_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Goody boxes + ............................................................*/ + p = Text_String(TXT_CRATES_COLON); + if (MPlayerGoodies) { + sprintf(txt,"%s %s",p,Text_String(TXT_ON)); + } else { + sprintf(txt,"%s %s",p,Text_String(TXT_OFF)); + } + + Fancy_Text_Print (txt, + d_dialog_x + d_dialog_w - (d_dialog_w / 4) - String_Pixel_Width(p), + d_msg4_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Computer AI players + ............................................................*/ + if (Special.IsCaptureTheFlag) { + p = Text_String(TXT_CAPTURE_THE_FLAG_COLON); + sprintf(txt,"%s %s",p,Text_String(TXT_ON)); + } else { + p = Text_String(TXT_AI_PLAYERS_COLON); + if (MPlayerGhosts) { + sprintf(txt,"%s %s",p,Text_String(TXT_ON)); + } else { + sprintf(txt,"%s %s",p,Text_String(TXT_OFF)); + } + } + Fancy_Text_Print (txt, + d_dialog_x + d_dialog_w - (d_dialog_w / 4) - String_Pixel_Width(p), + d_msg5_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + } else { + + /*............................................................... + Rejection notice + ...............................................................*/ + if (joinstate==JOIN_REJECTED) { + Fancy_Text_Print(TXT_REQUEST_DENIED, + d_dialog_cx, d_msg3_y, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + } + } + + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + /*------------------------------------------------------------------ + User clicks on a color button: + - If we've joined a game, don't allow a new color selection + - otherwise, select that color + ------------------------------------------------------------------*/ + case KN_LMOUSE: + if (joinstate > JOIN_NOTHING) + break; + if (_Kbd->MouseQX > cbox_x[0] && + _Kbd->MouseQX < (cbox_x[MAX_MPLAYER_COLORS - 1] + d_color_w) && + _Kbd->MouseQY > d_color_y && + _Kbd->MouseQY < (d_color_y + d_color_h)) { + MPlayerPrefColor = (_Kbd->MouseQX - cbox_x[0]) / d_color_w; + MPlayerColorIdx = MPlayerPrefColor; + + name_edt.Set_Color (MPlayerTColors[MPlayerColorIdx]); + name_edt.Flag_To_Redraw(); + + display = REDRAW_COLORS; + } + break; + + /*------------------------------------------------------------------ + User clicks on the game list: + - If we've joined a game, don't allow the selected item to change; + otherwise: + - Clear the player list + - Send an immediate player query + ------------------------------------------------------------------*/ + case (BUTTON_GAMELIST | KN_BUTTON): + if (joinstate==JOIN_CONFIRMED) { + gamelist.Set_Selected_Index(game_index); + } else { + if (gamelist.Current_Index() != game_index) { + Clear_Player_List (&playerlist); + game_index = gamelist.Current_Index(); + Send_Join_Queries (game_index, 0, 1); + } + } + break; + + /*------------------------------------------------------------------ + House Buttons: set the player's desired House + ------------------------------------------------------------------*/ + case (BUTTON_GDI | KN_BUTTON): + MPlayerHouse = HOUSE_GOOD; + gdibtn.Turn_On(); + nodbtn.Turn_Off(); + break; + + case (BUTTON_NOD | KN_BUTTON): + MPlayerHouse = HOUSE_BAD; + gdibtn.Turn_Off(); + nodbtn.Turn_On(); + break; + + /*------------------------------------------------------------------ + JOIN: send a join request packet & switch to waiting-for-confirmation + mode. (Request_To_Join fills in MPlayerName with my namebuf.) + ------------------------------------------------------------------*/ + case (BUTTON_JOIN | KN_BUTTON): + name_edt.Clear_Focus(); + name_edt.Flag_To_Redraw(); + + join_index = gamelist.Current_Index(); + parms_received = 0; + if (Request_To_Join (namebuf, join_index, &playerlist, MPlayerHouse, + MPlayerColorIdx)) { + joinstate = JOIN_WAIT_CONFIRM; + } else { + display = REDRAW_ALL; + } + break; + + /*------------------------------------------------------------------ + CANCEL: send a SIGN_OFF + - If we're part of a game, stay in this dialog; otherwise, exit + ------------------------------------------------------------------*/ + case (KN_ESC): + if (Messages.Get_Edit_Buf() != NULL) { + Messages.Input(input); + display = REDRAW_MESSAGE; + break; + } + case (BUTTON_CANCEL | KN_BUTTON): + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy(GPacket.Name,MPlayerName); + + /*............................................................... + If we're joined to a game, make extra sure the other players in + that game know I'm exiting; send my SIGN_OFF as an ack-required + packet. Do not send this packet to myself (index 0). + ...............................................................*/ + if (joinstate == JOIN_CONFIRMED) { + // + // Remove myself from the player list box + // + if (playerlist.Count()) { // added: BRR 6/14/96 + item = (char *)(playerlist.Get_Item(0)); + playerlist.Remove_Item(item); + delete [] item; + playerlist.Flag_To_Redraw(); + } + + // + // Remove myself from the Players list + // + if (Players.Count()) { // added: BRR 6/14/96 + who = Players[0]; + Players.Delete(0); + delete who; + } + + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Players[i]->Address)); + Ipx.Service(); + } + } + + /*............................................................... + Now broadcast my SIGN_OFF so other players looking at this game + know I'm leaving. + ...............................................................*/ + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + + if (IsBridge) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + if (joinstate != JOIN_CONFIRMED) { + process = false; + rc = -1; + } else { + MPlayerGameName[0] = 0; + joinstate = JOIN_NOTHING; + display = REDRAW_ALL; + } + break; + + /*------------------------------------------------------------------ + NEW: bail out with return code 1 + ------------------------------------------------------------------*/ + case (BUTTON_NEW | KN_BUTTON): + /* + .................. Force user to enter a name ................... + */ + if (strlen(namebuf)==0) { + CCMessageBox().Process(TXT_NAME_ERROR); + display = REDRAW_ALL; + break; + } + /* + ..................... Ensure name is unique ..................... + */ + found = 0; + for (i = 0; i < Games.Count(); i++) { + if (!stricmp(Games[i]->Name, namebuf)) { + found = 1; + CCMessageBox().Process (TXT_GAMENAME_MUSTBE_UNIQUE); + display = REDRAW_ALL; + break; + } + } + if (found) + break; + /* + .................... Save player & game name .................... + */ + strcpy(MPlayerName,namebuf); + strcpy(MPlayerGameName,namebuf); + + name_edt.Clear_Focus(); + name_edt.Flag_To_Redraw(); + + rc = 1; + process = false; + break; + + /*------------------------------------------------------------------ + Default: manage the inter-player messages + ------------------------------------------------------------------*/ + default: + /*............................................................... + F4/SEND/'M' = edit a message + ...............................................................*/ + if (Messages.Get_Edit_Buf()==NULL) { + if ( (input == KN_M && joinstate==JOIN_CONFIRMED) || + input==(BUTTON_SEND | KN_BUTTON) || input == KN_F4) { + memset (txt, 0, 80); + + strcpy(txt,Text_String(TXT_TO_ALL)); // "To All:" + + Messages.Add_Edit (MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, txt, d_message_w-70*factor); + + if (joinstate <= JOIN_NOTHING) { + name_edt.Clear_Focus(); + name_edt.Flag_To_Redraw(); + } + + display = REDRAW_MESSAGE; + + break; + } + } else + + /*............................................................... + If we're already editing a message and the user clicks on + 'Send', translate our input to a Return so Messages.Input() will + work properly. + ...............................................................*/ + if (input==(BUTTON_SEND | KN_BUTTON)) { + input = KN_RETURN; + } + + /*............................................................... + Manage the message system (get rid of old messages) + ...............................................................*/ + if (Messages.Manage()) { + display = REDRAW_MESSAGE; + } + + /*............................................................... + Service keyboard input for any message being edited. + ...............................................................*/ + i = Messages.Input(input); + + /*............................................................... + If 'Input' returned 1, it means refresh the message display. + ...............................................................*/ + if (i==1) { + Messages.Draw(); + } else { + + /*............................................................... + If 'Input' returned 2, it means redraw the message display. + ...............................................................*/ + if (i==2) { + display = REDRAW_MESSAGE; + } else { + + /*............................................................... + If 'input' returned 3, it means send the current message. + ...............................................................*/ + if (i==3) { + sent_so_far = 0; + magic_number = MESSAGE_HEAD_MAGIC_NUMBER; + message_length = strlen(Messages.Get_Edit_Buf()); + crc = (unsigned short) + (Calculate_CRC(Messages.Get_Edit_Buf(),message_length) &0xffff); + + while ( sent_so_far < message_length ){ + GPacket.Command = NET_MESSAGE; + strcpy (GPacket.Name, namebuf); + memcpy (GPacket.Message.Buf, Messages.Get_Edit_Buf()+sent_so_far, COMPAT_MESSAGE_LENGTH-5); + *(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-5) = 0; + *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-4)) = magic_number; + *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-2)) = crc; + GPacket.Message.ID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + GPacket.Message.NameCRC = Compute_Name_CRC(MPlayerGameName); + + /*.................................................................. + Send the message to every player in our player list. The local + system will also receive this message, since it's in the Player list. + ..................................................................*/ + if (joinstate == JOIN_CONFIRMED) { + for (i = 1; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, + sizeof(GlobalPacketType), 1, &(Players[i]->Address)); + Ipx.Service(); + } + + sprintf(txt,Text_String (TXT_FROM), MPlayerName, GPacket.Message.Buf); + Messages.Add_Message (txt, + MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, magic_number, crc); + } + else { + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, + sizeof(GlobalPacketType), 1, &(Players[i]->Address)); + Ipx.Service(); + } + } + magic_number++; + sent_so_far += COMPAT_MESSAGE_LENGTH-5; + } + } + } + } + break; + } + + /*--------------------------------------------------------------------- + Resend our query packets + ---------------------------------------------------------------------*/ + Send_Join_Queries(game_index, 0, 0); + + /*--------------------------------------------------------------------- + Process incoming packets + ---------------------------------------------------------------------*/ + event = Get_Join_Responses(&joinstate, &gamelist, &playerlist, + join_index); + /*..................................................................... + If we've changed state, redraw everything; if we're starting the game, + break out of the loop. If we've just joined, send out a player query + so I'll get added to the list instantly. + .....................................................................*/ + if (event == EV_STATE_CHANGE) { + display = REDRAW_ALL; + if (joinstate==JOIN_GAME_START) { + rc = 0; + process = false; + } else { + + /*.................................................................. + If we're newly-confirmed, immediately send out a player query + ..................................................................*/ + if (joinstate==JOIN_CONFIRMED) { + + Clear_Player_List(&playerlist); + + item = new char [MPLAYER_NAME_MAX + 4]; + if (MPlayerHouse==HOUSE_GOOD) { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_G_D_I)); + } else { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_N_O_D)); + } + playerlist.Add_Item (item, MPlayerTColors[MPlayerColorIdx]); + + who = new NodeNameType; + strcpy(who->Name, MPlayerName); + who->Address = IPXAddressClass(); + who->Player.House = MPlayerHouse; + who->Player.Color = MPlayerColorIdx; + Players.Add (who); + + Send_Join_Queries (game_index, 0, 1); + } else { + + /*.................................................................. + If we've been rejected, clear any messages we may have been typing. + ..................................................................*/ + if (joinstate==JOIN_REJECTED) { + + // + // Remove myself from the player list box + // + if (playerlist.Count()) { // added: BRR 6/14/96 + item = (char *)(playerlist.Get_Item(0)); + if (item){ + playerlist.Remove_Item(item); + delete [] item; + playerlist.Flag_To_Redraw(); + } + } + + // + // Remove myself from the Players list + // + if (Players.Count()){ + who = Players[0]; + Players.Delete(0); + delete who; + } + + Messages.Init (d_message_x + 2, d_message_y + 2, 4, + MAX_MESSAGE_LENGTH, d_txt6_h); + } + } + } + } else + + /*..................................................................... + If a new game is detected, and it's the first game on our list, + automatically send out a player query for that game. + .....................................................................*/ + if (event == EV_NEW_GAME && gamelist.Count()==1) { + gamelist.Set_Selected_Index(0); + game_index = gamelist.Current_Index(); + Send_Join_Queries (game_index, 0, 1); + } else + + /*..................................................................... + If the game options have changed, print them. + .....................................................................*/ + if (event == EV_GAME_OPTIONS) { + parms_received = 1; + display = REDRAW_MESSAGE; + } else + + /*..................................................................... + Draw an incoming message + .....................................................................*/ + if (event == EV_MESSAGE) { + display = REDRAW_MESSAGE; + } else + + /*..................................................................... + A game before the one I've selected is gone, so we have a new index now. + 'game_index' must be kept set to the currently-selected list item, so + we send out queries for the currently-selected game. It's therefore + imperative that we detect any changes to the game list. + If we're joined in a game, we must decrement our game_index to keep + it aligned with the game we're joined to. + .....................................................................*/ + if (event == EV_GAME_SIGNOFF) { + if (joinstate==JOIN_CONFIRMED) { + game_index--; + join_index--; + gamelist.Set_Selected_Index(join_index); + } else { + gamelist.Flag_To_Redraw(); + Clear_Player_List(&playerlist); + game_index = gamelist.Current_Index(); + Send_Join_Queries (game_index, 0, 1); + } + } + + /*--------------------------------------------------------------------- + Service the Ipx connections + ---------------------------------------------------------------------*/ + Ipx.Service(); + + /*--------------------------------------------------------------------- + Clean out the Game List; if an old entry is found: + - Remove it + - Clear the player list + - Send queries for the new selected game, if there is one + ---------------------------------------------------------------------*/ + for (i = 0; i < Games.Count(); i++) { + if (TickCount.Time() - Games[i]->Game.LastTime > 400) { + Games.Delete(Games[i]); + item = (char *)(gamelist.Get_Item (i)); + gamelist.Remove_Item (item); + delete [] item; + if (i <= game_index) { + gamelist.Flag_To_Redraw(); + Clear_Player_List(&playerlist); + game_index = gamelist.Current_Index(); + Send_Join_Queries (game_index, 0, 1); + } + } + } + + /*--------------------------------------------------------------------- + Service the sounds & score; GameActive must be false at this point, + so Call_Back() doesn't intercept global messages from me! + ---------------------------------------------------------------------*/ + Call_Back(); + } + + /*------------------------------------------------------------------------ + Establish connections with all other players. + ------------------------------------------------------------------------*/ + if (rc == 0) { + /*..................................................................... + If the other guys are playing a scenario I don't have (sniff), I can't + play. Try to bail gracefully. + .....................................................................*/ + if (ScenarioIdx==-1) { + CCMessageBox().Process (TXT_UNABLE_PLAY_WAAUGH); + + // + // Remove myself from the player list box + // + if (playerlist.Count()) { // added: BRR 6/14/96 + item = (char *)(playerlist.Get_Item(0)); + playerlist.Remove_Item(item); + delete [] item; + playerlist.Flag_To_Redraw(); + } + + // + // Remove myself from the Players list + // + if (Players.Count()) { // added: BRR 6/14/96 + who = Players[0]; + Players.Delete(0); + delete who; + } + + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy (GPacket.Name, MPlayerName); + + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Players[i]->Address)); + Ipx.Service(); + } + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + + if (IsBridge) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + rc = -1; + + } else { + + /*.................................................................. + Set the number of players in this game, and my ID + ..................................................................*/ + MPlayerCount = Players.Count(); + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + /*.................................................................. + Get the scenario number + ..................................................................*/ + Scenario = MPlayerFilenum[ScenarioIdx]; + + /*.................................................................. + Form connections with all other players. Form the IPX Connection ID + from the player's Color and House. This will let us extract any + player's color & house at any time. Fill in 'tmp_id' while we're + doing this. + ..................................................................*/ + for (i = 0; i < Players.Count(); i++) { + + /*............................................................... + Only create the connection if it's not myself! + ...............................................................*/ + if (strcmp (MPlayerName, Players[i]->Name)) { + id = Build_MPlayerID(Players[i]->Player.Color, + Players[i]->Player.House); + + tmp_id[i] = id; + + Ipx.Create_Connection((int)id, Players[i]->Name, &(Players[i]->Address) ); + } else { + tmp_id[i] = MPlayerLocalID; + } + } + + /*.................................................................. + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + ..................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + min_index = 0; + min_id = 0xff; + for (j = 0; j < MPlayerCount; j++) { + if (tmp_id[j] < min_id) { + min_id = tmp_id[j]; + min_index = j; + } + } + MPlayerID[i] = tmp_id[min_index]; + tmp_id[min_index] = 0xff; + } + /*.................................................................. + Fill in the array of player names, including my own. + ..................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID[i] == MPlayerLocalID) { + strcpy (MPlayerNames[i], MPlayerName); + } else { + strcpy (MPlayerNames[i], Ipx.Connection_Name(MPlayerID[i])); + } + } + } + /*--------------------------------------------------------------------- + Wait a while, polling the IPX service routines, to give our ACK + a chance to get to the other system. If he doesn't get our ACK, he'll + be waiting the whole time we load MIX files. + ---------------------------------------------------------------------*/ + i = MAX(Ipx.Global_Response_Time() * 2, 60); + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < i) { + Ipx.Service(); + } + } + + /*------------------------------------------------------------------------ + Init network timing values, using previous response times as a measure + of what our retry delta & timeout should be. + ------------------------------------------------------------------------*/ + Ipx.Set_Timing (Ipx.Global_Response_Time() + 2, -1, + Ipx.Global_Response_Time() * 4); + + /*------------------------------------------------------------------------ + Clear all lists + ------------------------------------------------------------------------*/ + Clear_Game_List(&gamelist); + Clear_Player_List(&playerlist); + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + return(rc); +} + + +/*************************************************************************** + * Clear_Game_List -- Clears the game-name listbox & 'Games' Vector * + * * + * Assumes each entry in 'Games' & the list box have been allocated * + * separately. * + * * + * INPUT: * + * gamelist ptr to list box * + * * + * OUTPUT: * + * none * + * * + * WARNINGS: * + * none * + * * + * HISTORY: * + *=========================================================================*/ +static void Clear_Game_List (ListClass *gamelist) +{ + char * item; + int i; + + /*------------------------------------------------------------------------ + Clear the list box + ------------------------------------------------------------------------*/ + while (gamelist->Count()) { + item = (char *)(gamelist->Get_Item (0)); + gamelist->Remove_Item(item); + delete [] item; + } + gamelist->Flag_To_Redraw(); + + /*------------------------------------------------------------------------ + Clear the 'Games' Vector + ------------------------------------------------------------------------*/ + for (i = 0; i < Games.Count(); i++) + delete Games[i]; + + Games.Clear(); + +} /* end of Clear_Game_List */ + + +/*************************************************************************** + * Clear_Player_List -- Clears the player-name listbox & Vector * + * * + * Assumes each entry in 'Players' & the list box have been allocated * + * separately. * + * * + * INPUT: * + * playerlist ptr to list box * + * * + * OUTPUT: * + * none * + * * + * WARNINGS: * + * none * + * * + * HISTORY: * + *=========================================================================*/ +static void Clear_Player_List (ListClass *playerlist) +{ + char * item; + int i; + + /*------------------------------------------------------------------------ + Clear the list box + ------------------------------------------------------------------------*/ + while (playerlist->Count()) { + item = (char *)(playerlist->Get_Item(0)); + playerlist->Remove_Item(item); + delete [] item; + } + playerlist->Flag_To_Redraw(); + + /*------------------------------------------------------------------------ + Clear the 'Players' Vector + ------------------------------------------------------------------------*/ + for (i = 0; i < Players.Count(); i++) + delete Players[i]; + + Players.Clear(); + +} /* end of Clear_Player_List */ + + +/*************************************************************************** + * Request_To_Join -- Sends a JOIN request packet to game owner * + * * + * Regardless of the return code, the Join Dialog will need to be redrawn * + * after calling this routine. * + * * + * INPUT: * + * playername player's name * + * join_index index of game we're joining * + * playerlist listbox containing other players' names * + * house requested house * + * color requested color * + * * + * OUTPUT: * + * 1 = Packet sent, 0 = wasn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + *=========================================================================*/ +static int Request_To_Join (char *playername, int join_index, ListClass *playerlist, + HousesType house, int color) +{ + int i; + + playerlist = playerlist; // shaddup, Mr stupid compiler! + + /* + --------------------------- Validate join_index -------------------------- + */ + if ( (Games.Count()==0) || join_index > Games.Count() || join_index < 0) { + CCMessageBox().Process (TXT_NOTHING_TO_JOIN); + return(false); + } + + /* + ----------------------- Force user to enter a name ----------------------- + */ + if (strlen(playername)==0) { + CCMessageBox().Process (TXT_NAME_ERROR); + return(false); + } + + /* + ------------------------- The game must be open -------------------------- + */ + if (!Games[join_index]->Game.IsOpen) { + CCMessageBox().Process(TXT_GAME_IS_CLOSED); + return (false); + } + + /* + ------------------------ Make sure name is unique ------------------------ + */ + for (i = 0; i < Players.Count(); i++) { + if (!stricmp(playername, Players[i]->Name)) { + CCMessageBox().Process (TXT_NAME_MUSTBE_UNIQUE); + return(false); + } + } + + /* + ----------------------------- Check version #'s -------------------------- + */ + int v; +#ifdef PATCH + if (IsV107) { + v = 1; + } else { + v = 2; + } +#else + v = Version_Number(); +#endif + if (Games[join_index]->Game.Version > v) { + CCMessageBox().Process (TXT_YOURGAME_OUTDATED); + return(false); + } else { + if (Games[join_index]->Game.Version < v) { + CCMessageBox().Process (TXT_DESTGAME_OUTDATED); + return(false); + } + } + + /* + ----------------------------- Save game name ----------------------------- + */ + strcpy (MPlayerName,playername); + + /* + ----------------------- Send packet to game's owner ---------------------- + */ + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_QUERY_JOIN; + strcpy (GPacket.Name, MPlayerName); + GPacket.PlayerInfo.House = house; + GPacket.PlayerInfo.Color = color; + + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Games[join_index]->Address)); + + return(true); +} + + +/*********************************************************************************************** + * Send_Join_Queries -- sends queries for the Join Dialog * + * * + * This routine [re]sends the queries related to the Join Dialog: * + * - NET_QUERY_GAME * + * - NET_QUERY_PLAYER for the game currently selected (if there is one) * + * * + * The queries are "staggered" in time so they aren't all sent at once; otherwise, we'd * + * be inundated with reply packets & we'd miss some (even though the replies will require * + * ACK's). * + * * + * INPUT: * + * curgame index of currently-selected game; -1 = none * + * gamenow if 1, will immediately send the game query * + * playernow if 1, will immediately send the player query for currently-selected game * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + * 04/15/1995 BRR : Created. * + *=============================================================================================*/ +static void Send_Join_Queries(int curgame, int gamenow, int playernow) +{ + static int lasttime1 = 0; // time since last Game query sent out + static int lasttime2 = 0; // time since last Player query sent out + + /*------------------------------------------------------------------------ + Send the game-name query if the time has expired, or we're told to do + it right now + ------------------------------------------------------------------------*/ + if ( (TickCount.Time() - lasttime1 > 120) || gamenow) { + lasttime1 = TickCount.Time(); + + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_QUERY_GAME; + + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, NULL); + + /*..................................................................... + If the user specified a remote server address, broadcast over that + network, too. + .....................................................................*/ + if (IsBridge) + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + + /*------------------------------------------------------------------------ + Send the player query for the game currently clicked on, if the time has + expired and there is a currently-selected game, or we're told to do it + right now + ------------------------------------------------------------------------*/ + if ( (curgame != -1) && curgame < Games.Count() && + ((TickCount.Time() - lasttime2 > 35) || playernow) ) { + lasttime2 = TickCount.Time(); + + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_QUERY_PLAYER; + strcpy (GPacket.Name, Games[curgame]->Name); + + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, NULL); + + /*..................................................................... + If the user specified a remote server address, broadcast over that + network, too. + .....................................................................*/ + if (IsBridge) + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + +} /* end of Send_Join_Queries */ + + +/*********************************************************************************************** + * Get_Join_Responses -- sends queries for the Join Dialog * + * * + * This routine polls the Global Channel to see if there are any incoming packets; * + * if so, it processes them. This routine can change the state of the Join Dialog, or * + * the contents of the list boxes, based on what the packet is. * + * * + * The list boxes are passed in as pointers; they can't be made globals, because they * + * can't be constructed, because they require shape pointers to the arrow buttons, and * + * the mix files won't have been initialized when the global variables' constructors are * + * called. * + * * + * This routine sets the globals * + * MPlayerHouse (from NET_CONFIRM_JOIN) * + * MPlayerColorIdx (from NET_CONFIRM_JOIN) * + * MPlayerBases (from NET_GAME_OPTIONS) * + * MPlayerTiberium (from NET_GAME_OPTIONS) * + * MPlayerGoodies (from NET_GAME_OPTIONS) * + * MPlayerGhosts (from NET_GAME_OPTIONS) * + * ScenarioIdx (from NET_GAME_OPTIONS; -1 = scenario not found) * + * * + * INPUT: * + * joinstate current state of Join Dialog * + * gamelist list box containing game names * + * playerlist list box containing player names for the currently-selected game * + * join_index index of the game we've joined or are asking to join * + * * + * OUTPUT: * + * Event that occurred * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + * 04/15/1995 BRR : Created. * + *=============================================================================================*/ +static JoinEventType Get_Join_Responses(JoinStateType *joinstate, ListClass *gamelist, + ColorListClass *playerlist, int join_index) +{ + int rc; + char * item; // general-purpose string + NodeNameType *who; // node to add to Games or Players + int i; + int found; + JoinEventType retcode = EV_NONE; + char txt[80]; + int color; + unsigned short magic_number; + unsigned short crc; + + /*------------------------------------------------------------------------ + If there is no incoming packet, just return + ------------------------------------------------------------------------*/ + rc = Ipx.Get_Global_Message (&GPacket, &GPacketlen, &GAddress, &GProductID); + if (!rc || GProductID != IPXGlobalConnClass::COMMAND_AND_CONQUER) + return(EV_NONE); + + /*------------------------------------------------------------------------ + If we're joined in a game, handle the packet in a standard way; otherwise, + don't answer standard queries. + ------------------------------------------------------------------------*/ + if ( (*joinstate)==JOIN_CONFIRMED && + Process_Global_Packet(&GPacket,&GAddress)!=0) + return(EV_NONE); + + /*------------------------------------------------------------------------ + NET_ANSWER_GAME: Another system is answering our GAME query, so add that + system to our list box if it's new. + ------------------------------------------------------------------------*/ + if (GPacket.Command==NET_ANSWER_GAME) { + /*..................................................................... + See if this name is unique + .....................................................................*/ + retcode = EV_NONE; + found = 0; + for (i = 0; i < Games.Count(); i++) { + if (!strcmp(Games[i]->Name, GPacket.Name)) { + found = 1; + /*............................................................... + If name was found, update the node's time stamp & IsOpen flag. + ...............................................................*/ + Games[i]->Game.LastTime = TickCount.Time(); + if (Games[i]->Game.IsOpen != GPacket.GameInfo.IsOpen) { + item = (char *)gamelist->Get_Item(i); + if (GPacket.GameInfo.IsOpen) { + sprintf(item,Text_String(TXT_THATGUYS_GAME),GPacket.Name); + } else { + sprintf(item,Text_String(TXT_THATGUYS_GAME_BRACKET),GPacket.Name); + } + Games[i]->Game.IsOpen = GPacket.GameInfo.IsOpen; + gamelist->Flag_To_Redraw(); + /*............................................................ + If this game has gone from closed to open, copy the responder's + address into our Game slot, since the guy responding to this + must be game owner. + ............................................................*/ + if (Games[i]->Game.IsOpen) + Games[i]->Address = GAddress; + } + break; + } + } + /*..................................................................... + name not found (or addresses are different); add it to 'Games' + .....................................................................*/ + if (found==0) { + /*.................................................................. + Create a new node structure, fill it in, add it to 'Games' + ..................................................................*/ + who = new NodeNameType; + strcpy(who->Name, GPacket.Name); + who->Address = GAddress; + who->Game.Version = GPacket.GameInfo.Version; + who->Game.IsOpen = GPacket.GameInfo.IsOpen; + who->Game.LastTime = TickCount.Time(); + Games.Add (who); + + /*.................................................................. + Create a string for "xxx's Game", leaving room for brackets around + the string if it's a closed game + ..................................................................*/ + item = new char [MPLAYER_NAME_MAX + 9]; + if (GPacket.GameInfo.IsOpen) { + sprintf(item,Text_String(TXT_THATGUYS_GAME),GPacket.Name); + } else { + sprintf(item,Text_String(TXT_THATGUYS_GAME_BRACKET),GPacket.Name); + } + gamelist->Add_Item(item); + + retcode = EV_NEW_GAME; + } + } + + /*------------------------------------------------------------------------ + NET_ANSWER_PLAYER: Another system is answering our PLAYER query, so add it + to our player list box & the Player Vector if it's new + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_ANSWER_PLAYER) { + /*..................................................................... + See if this name is unique + .....................................................................*/ + retcode = EV_NONE; + found = 0; + for (i = 0; i < Players.Count(); i++) { + /*.................................................................. + If the address is already present, re-copy their name, color & + house into the existing entry, in case they've changed it without + our knowledge; set the 'found' flag so we won't create a new entry. + ..................................................................*/ + if (Players[i]->Address==GAddress) { + strcpy(Players[i]->Name, GPacket.Name); + Players[i]->Player.House = GPacket.PlayerInfo.House; + Players[i]->Player.Color = GPacket.PlayerInfo.Color; + playerlist->Colors[i] = MPlayerTColors[GPacket.PlayerInfo.Color]; + found = 1; + break; + } + } + /*..................................................................... + Don't add this player if he's not part of the game that's selected. + .....................................................................*/ + i = gamelist->Current_Index(); + if (Games.Count() && GPacket.PlayerInfo.NameCRC != Compute_Name_CRC(Games[i]->Name)) + found = 1; + + /* + ** Dont add this player if its really me! (hack, hack) + */ + if (!strcmp(GPacket.Name, MPlayerName)){ + found = 1; + } + + + /*..................................................................... + name not found (or address didn't match); add to player list box & Vector + .....................................................................*/ + if (found==0) { + /*.................................................................. + Create & add a node to the Vector + ..................................................................*/ + who = new NodeNameType; + strcpy(who->Name, GPacket.Name); + who->Address = GAddress; + who->Player.House = GPacket.PlayerInfo.House; + who->Player.Color = GPacket.PlayerInfo.Color; + Players.Add (who); + + /*.................................................................. + Create & add a string to the list box + ..................................................................*/ + item = new char [MPLAYER_NAME_MAX + 4]; + if (GPacket.PlayerInfo.House==HOUSE_GOOD) { + sprintf(item,"%s\t%s",GPacket.Name,Text_String(TXT_G_D_I)); + } else { + sprintf(item,"%s\t%s",GPacket.Name,Text_String(TXT_N_O_D)); + } + playerlist->Add_Item(item, MPlayerTColors[who->Player.Color]); + + retcode = EV_NEW_PLAYER; + } + } + + /*------------------------------------------------------------------------ + NET_CONFIRM_JOIN: The game owner has confirmed our JOIN query; mark us as + being confirmed, and start answering queries from other systems + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_CONFIRM_JOIN) { + if ( (*joinstate) != JOIN_CONFIRMED) { + strcpy (MPlayerGameName, GPacket.Name); + MPlayerHouse = GPacket.PlayerInfo.House; + MPlayerColorIdx = GPacket.PlayerInfo.Color; + + (*joinstate) = JOIN_CONFIRMED; + retcode = EV_STATE_CHANGE; + } + } + + /*------------------------------------------------------------------------ + NET_REJECT_JOIN: The game owner has turned down our JOIN query; restore + the dialog state to its first pop-up state. Broadcast a sign-off to + tell all other systems that I'm no longer a part of any game; this way, + I'll be properly removed from their dialogs. + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_REJECT_JOIN) { + if ( (*joinstate) != JOIN_REJECTED) { + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy (GPacket.Name,MPlayerName); + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), 0, NULL); + + if (IsBridge) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + MPlayerGameName[0] = 0; + + (*joinstate) = JOIN_REJECTED; + retcode = EV_STATE_CHANGE; + } + } + + /*------------------------------------------------------------------------ + NET_GAME_OPTIONS: The game owner has changed the game options & is sending + us the new values. + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_GAME_OPTIONS) { + if ( (*joinstate)==JOIN_CONFIRMED) { + MPlayerCredits = GPacket.ScenarioInfo.Credits; + MPlayerBases = GPacket.ScenarioInfo.IsBases; + MPlayerTiberium = GPacket.ScenarioInfo.IsTiberium; + MPlayerGoodies = GPacket.ScenarioInfo.IsGoodies; + MPlayerGhosts = GPacket.ScenarioInfo.IsGhosties; + BuildLevel = GPacket.ScenarioInfo.BuildLevel; + MPlayerUnitCount = GPacket.ScenarioInfo.UnitCount; + Seed = GPacket.ScenarioInfo.Seed; + Special = GPacket.ScenarioInfo.Special; + Options.GameSpeed = GPacket.ScenarioInfo.GameSpeed; + + if (MPlayerTiberium) { + Special.IsTGrowth = 1; + Special.IsTSpread = 1; + } else { + Special.IsTGrowth = 0; + Special.IsTSpread = 0; + } + + if (Winsock.Get_Connected()){ + ScenarioIdx = GPacket.ScenarioInfo.Scenario; + }else{ + + ScenarioIdx = -1; + for (i = 0; i < MPlayerFilenum.Count(); i++) { + if (GPacket.ScenarioInfo.Scenario == MPlayerFilenum[i]) + ScenarioIdx = i; + } + } + + retcode = EV_GAME_OPTIONS; + } + } + + /*------------------------------------------------------------------------ + NET_SIGN_OFF: Another system is signing off: search for that system in + both the game list & player list, & remove it if found + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_SIGN_OFF) { + /*..................................................................... + Remove this name from the list of games + .....................................................................*/ + for (i = 0; i < Games.Count(); i++) { + if (!strcmp(Games[i]->Name, GPacket.Name) && + Games[i]->Address==GAddress) { + /*............................................................... + If the system signing off is the currently-selected list + item, clear the player list since that game is no longer + forming. + ...............................................................*/ + if (i==gamelist->Current_Index()) { + Clear_Player_List (playerlist); + } + + /*............................................................... + If the system signing off was the owner of our game, mark + ourselves as rejected + ...............................................................*/ + if ( (*joinstate) > JOIN_NOTHING && i==join_index) { + (*joinstate) = JOIN_REJECTED; + retcode = EV_STATE_CHANGE; + } + + /* + ....................... Set my return code ...................... + */ + if (retcode == EV_NONE) { + if (i <= gamelist->Current_Index()) { + retcode = EV_GAME_SIGNOFF; + } else { + retcode = EV_PLAYER_SIGNOFF; + } + } + + /* + ................. Remove game name from game list ............... + */ + Games.Delete(Games[i]); + item = (char *)(gamelist->Get_Item (i)); + gamelist->Remove_Item (item); + delete [] item; + gamelist->Flag_To_Redraw(); + + } + } + /*..................................................................... + Remove this name from the list of players + .....................................................................*/ + for (i = 0; i < Players.Count(); i++) { + /* + ..................... Name found; remove it ..................... + */ + if (Players[i]->Address==GAddress) { + item = (char *)(playerlist->Get_Item(i)); + playerlist->Remove_Item(item); + delete [] item; + Players.Delete(Players[i]); + playerlist->Flag_To_Redraw(); + + if (retcode == EV_NONE) + retcode = EV_PLAYER_SIGNOFF; + } + } + } + + /*------------------------------------------------------------------------ + NET_GO: The game's owner is signalling us to start playing. + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_GO) { + if ( (*joinstate)==JOIN_CONFIRMED) { + MPlayerMaxAhead = GPacket.ResponseTime.OneWay; + (*joinstate) = JOIN_GAME_START; + retcode = EV_STATE_CHANGE; +CCDebugString ("C&C95 - Received the 'GO' packet\n"); + } + } + + /*------------------------------------------------------------------------ + NET_MESSAGE: Someone is sending us a message + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_MESSAGE) { + sprintf(txt,Text_String (TXT_FROM), GPacket.Name, GPacket.Message.Buf); + magic_number = *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-4)); + crc = *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-2)); + color = MPlayerID_To_ColorIndex(GPacket.Message.ID); + Messages.Add_Message (txt, MPlayerTColors[color], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, magic_number, crc); + retcode = EV_MESSAGE; + } + + /*------------------------------------------------------------------------ + NET_PING: Someone is pinging me to get a response time measure (will only + happen after I've joined a game). Do nothing; the IPX Manager will handle + sending an ACK, and updating the response time measurements. + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_PING) { + retcode = EV_NONE; + } + + /*------------------------------------------------------------------------ + Default case: nothing happened. (This case will be hit every time I + receive my own NET_QUERY_GAME or NET_QUERY_PLAYER packets.) + ------------------------------------------------------------------------*/ + else { + retcode = EV_NONE; + } + + return(retcode); +} + + +/*********************************************************************************************** + * Net_New_Dialog -- lets user start a new game * + * * + * This dialog shows a list of who's requesting to join this game, and lets * + * the game initiator selectively approve each user. * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ New Network Game ³ * + * ³ ³ * + * ³ Players Scenario ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ³ * + * ³ ³ Boffo ³³ ³ Hell's Kitchen ³³ ³ * + * ³ ³ Bozo ÃÄ´ ³ Heaven's Gate ÃÄ´ ³ * + * ³ ³ Bonzo ³ ³ ³ ... ³ ³ ³ * + * ³ ³ ÃÄ´ ³ ÃÄ´ ³ * + * ³ ³ ³³ ³ ³³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ³ * + * ³ [Reject] Count:--- ## ³ * + * ³ Level:--- ## ³ * + * ³ ³ * + * ³ Credits: _____ ³ * + * ³ [ Bases ] [ Crates ] ³ * + * ³ [ Tiberium ] [ AI Players ] ³ * + * ³ ³ * + * ³ [OK] [Cancel] ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ * + * ³ ³ ³ ³ * + * ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ [Send Message] ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = success, false = cancel * + * * + * WARNINGS: * + * MPlayerName & MPlayerGameName must contain this player's name. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +static int Net_New_Dialog(void) +{ +/* ###Change collision detected! C:\PROJECTS\CODE\NETDLG.CPP... */ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + //D_DIALOG_W = 281; // dialog width + int d_dialog_w = 287*factor; // dialog width + int d_dialog_h = 177*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200*factor - d_dialog_h) / 2); // centered y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 6*factor+1; // ht of 6-pt text + int d_margin1 = 5*factor; // margin width/height + int d_margin2 = 2*factor; // margin width/height + + //d_playerlist_w = 100; + int d_playerlist_w = 106*factor; + int d_playerlist_h = 27*factor; + int d_playerlist_x = d_dialog_x + d_margin1; + int d_playerlist_y = d_dialog_y + d_margin1 + (d_txt6_h * 3); + + int d_scenariolist_w = 162*factor; + int d_scenariolist_h = 27*factor; + int d_scenariolist_x = d_dialog_x + d_dialog_w - d_margin1 - d_scenariolist_w; + int d_scenariolist_y = d_dialog_y + d_margin1 + (d_txt6_h * 3); + +#if (GERMAN | FRENCH) + int d_reject_w = 55*factor; +#else + int d_reject_w = 45*factor; +#endif + int d_reject_h = 9*factor; + int d_reject_x = d_playerlist_x + (d_playerlist_w / 2) - (d_reject_w / 2); + int d_reject_y = d_playerlist_y + d_playerlist_h + d_margin2; + + int d_count_w = 25*factor; + int d_count_h = d_txt6_h; + int d_count_x = d_scenariolist_x + (d_scenariolist_w / 2); + int d_count_y = d_scenariolist_y + d_scenariolist_h + d_margin2; + + int d_level_w = 25*factor; + int d_level_h = d_txt6_h; + int d_level_x = d_scenariolist_x + (d_scenariolist_w / 2); + int d_level_y = d_count_y + d_count_h; + + int d_credits_w = ((CREDITSBUF_MAX - 1) * 7*factor) + 4*factor; + //int d_credits_w = ((CREDITSBUF_MAX - 1) * 6*factor) + 3*factor; + int d_credits_h = 9*factor; + int d_credits_x = d_dialog_cx + 2*factor; + int d_credits_y = d_level_y + d_level_h + d_margin1; + +#if (GERMAN | FRENCH) + int d_bases_w = 120*factor;//bga:100; +#else + int d_bases_w = 100*factor; +#endif + int d_bases_h = 9*factor; + int d_bases_x = d_dialog_cx - d_bases_w - d_margin2; + int d_bases_y = d_credits_y + d_credits_h + d_margin2; + +#if (GERMAN | FRENCH) + int d_tiberium_w = 120*factor; +#else + int d_tiberium_w = 100*factor; +#endif + int d_tiberium_h = 9*factor; + int d_tiberium_x = d_dialog_cx - d_bases_w - d_margin2; + int d_tiberium_y = d_bases_y + d_bases_h + d_margin2; + +#if (GERMAN | FRENCH) + int d_goodies_w = 120*factor; +#else + int d_goodies_w = 100*factor; +#endif + int d_goodies_h = 9*factor; + int d_goodies_x = d_dialog_cx + d_margin2; + int d_goodies_y = d_credits_y + d_credits_h + d_margin2; + +#if (GERMAN | FRENCH) + int d_ghosts_w = 120*factor; +#else + int d_ghosts_w = 100*factor; +#endif + int d_ghosts_h = 9*factor; + int d_ghosts_x = d_dialog_cx + d_margin2; + int d_ghosts_y = d_goodies_y + d_goodies_h + d_margin2; + + int d_ok_w = 45*factor; + int d_ok_h = 9*factor; + int d_ok_x = d_dialog_cx - d_margin2 - (d_bases_w / 2) - (d_ok_w / 2); + int d_ok_y = d_ghosts_y + d_ghosts_h + d_margin1; + +#if (GERMAN | FRENCH) + int d_cancel_w = 50*factor; +#else + int d_cancel_w = 45*factor; +#endif + int d_cancel_h = 9*factor; + int d_cancel_x = d_dialog_cx + d_margin2 + (d_goodies_w / 2) - (d_cancel_w / 2); + int d_cancel_y = d_ghosts_y + d_ghosts_h + d_margin1; + + int d_message_w = d_dialog_w - (d_margin1 * 2); + int d_message_h = 34*factor; + int d_message_x = d_dialog_x + d_margin1; + int d_message_y = d_cancel_y + d_cancel_h + d_margin1; + + int d_send_w = 80*factor; + int d_send_h = 9*factor; + int d_send_x = d_dialog_cx - (d_send_w / 2); + int d_send_y = d_message_y + d_message_h + d_margin2; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_PLAYERLIST = 100, + BUTTON_SCENARIOLIST, + BUTTON_REJECT, + BUTTON_COUNT, + BUTTON_LEVEL, + BUTTON_CREDITS, + BUTTON_BASES, + BUTTON_TIBERIUM, + BUTTON_GOODIES, + BUTTON_GHOSTS, + BUTTON_OK, + BUTTON_CANCEL, + BUTTON_SEND, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_UNIT_COUNT, + REDRAW_MESSAGE, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + bool process = true; // process while true + KeyNumType input; + + char credbuf[CREDITSBUF_MAX]; // for credit edit box + int old_cred; // old value in credits buffer + int transmit; // 1 = re-transmit new game options + + long ok_timer = 0; // for timing OK button + int index; // index for rejecting a player + int rc; + int i,j; + char *item; + int tabs[] = {77*factor}; // tabs for player list box + + long ping_timer = 0; // for sending Ping packets + + unsigned char tmp_id[MAX_PLAYERS]; // temp storage for sorting player ID's + int min_index; // for sorting player ID's + unsigned char min_id; // for sorting player ID's + unsigned char id; // connection ID + char txt[80]; + JoinEventType whahoppa; // event generated by received packets + static int first_time = 1; // 1 = 1st time this dialog is run + + int message_length; + int sent_so_far; + unsigned short magic_number; + unsigned short crc; + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + + ColorListClass playerlist(BUTTON_PLAYERLIST, + d_playerlist_x, d_playerlist_y, d_playerlist_w, d_playerlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + ListClass scenariolist(BUTTON_SCENARIOLIST, + d_scenariolist_x, d_scenariolist_y, d_scenariolist_w, d_scenariolist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + EditClass credit_edt (BUTTON_CREDITS, + credbuf, CREDITSBUF_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_credits_x, d_credits_y, d_credits_w, d_credits_h, EditClass::ALPHANUMERIC); + + TextButtonClass rejectbtn(BUTTON_REJECT, TXT_REJECT, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_reject_x, d_reject_y); +#else + d_reject_x, d_reject_y, d_reject_w, d_reject_h); +#endif + + GaugeClass countgauge (BUTTON_COUNT, + d_count_x, d_count_y, d_count_w, d_count_h); + + GaugeClass levelgauge (BUTTON_LEVEL, + d_level_x, d_level_y, d_level_w, d_level_h); + + TextButtonClass basesbtn(BUTTON_BASES, TXT_BASES_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_bases_x, d_bases_y, d_bases_w, d_bases_h); + + TextButtonClass tiberiumbtn(BUTTON_TIBERIUM, TXT_TIBERIUM_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_tiberium_x, d_tiberium_y, d_tiberium_w, d_tiberium_h); + + TextButtonClass goodiesbtn(BUTTON_GOODIES, TXT_CRATES_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_goodies_x, d_goodies_y, d_goodies_w, d_goodies_h); + + TextButtonClass ghostsbtn(BUTTON_GHOSTS, TXT_AI_PLAYERS_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_ghosts_x, d_ghosts_y, d_ghosts_w, d_ghosts_h); + + TextButtonClass okbtn(BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_ok_x, d_ok_y, d_ok_w, d_ok_h); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_cancel_x, d_cancel_y); +#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +#endif + + TextButtonClass sendbtn(BUTTON_SEND, TXT_SEND_MESSAGE, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_send_x, d_send_y); +#else + d_send_x, d_send_y, d_send_w, d_send_h); +#endif + + /* + ------------------------- Build the button list -------------------------- + */ + commands = &playerlist; + scenariolist.Add_Tail(*commands); + credit_edt.Add_Tail(*commands); + rejectbtn.Add_Tail(*commands); + countgauge.Add_Tail(*commands); + levelgauge.Add_Tail(*commands); + basesbtn.Add_Tail(*commands); + tiberiumbtn.Add_Tail(*commands); + goodiesbtn.Add_Tail(*commands); + ghostsbtn.Add_Tail(*commands); + okbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + sendbtn.Add_Tail(*commands); + + playerlist.Set_Tabs(tabs); + + /* + ----------------------------- Various Inits ------------------------------ + */ + /*........................................................................ + Init dialog values, only the first time through + ........................................................................*/ + if (first_time) { + MPlayerCredits = 3000; // init credits & credit buffer + MPlayerBases = 1; // init scenario parameters + MPlayerTiberium = 0; + MPlayerGoodies = 0; + MPlayerGhosts = 0; + Special.IsCaptureTheFlag = 0; + MPlayerUnitCount = (MPlayerCountMax[MPlayerBases] + MPlayerCountMin[MPlayerBases]) / 2; + first_time = 0; + } + + /*........................................................................ + Init button states + ........................................................................*/ + if (MPlayerBases) { + basesbtn.Turn_On(); + basesbtn.Set_Text(TXT_BASES_ON); + } + if (MPlayerTiberium) { + tiberiumbtn.Turn_On(); + tiberiumbtn.Set_Text(TXT_TIBERIUM_ON); + } + if (MPlayerGoodies) { + goodiesbtn.Turn_On(); + goodiesbtn.Set_Text(TXT_CRATES_ON); + } + if (MPlayerGhosts) { + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_AI_PLAYERS_ON); + } + if (Special.IsCaptureTheFlag) { + MPlayerGhosts = 0; + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_CAPTURE_THE_FLAG); + } + + sprintf(credbuf, "%d", MPlayerCredits); + credit_edt.Set_Text(credbuf, CREDITSBUF_MAX); + old_cred = MPlayerCredits; + + levelgauge.Set_Maximum(MPLAYER_BUILD_LEVEL_MAX - 1); + levelgauge.Set_Value(BuildLevel - 1); + + countgauge.Set_Maximum(MPlayerCountMax[MPlayerBases] - MPlayerCountMin[MPlayerBases]); + countgauge.Set_Value(MPlayerUnitCount - MPlayerCountMin[MPlayerBases]); + + /*........................................................................ + Init other scenario parameters + ........................................................................*/ + Special.IsTGrowth = MPlayerTiberium; + Special.IsTSpread = MPlayerTiberium; + transmit = 0; + + /*........................................................................ + Init scenario description list box + ........................................................................*/ + for (i = 0; i < MPlayerScenarios.Count(); i++) { + scenariolist.Add_Item (strupr(MPlayerScenarios[i])); + } + ScenarioIdx = 0; // 1st scenario is selected + + /*........................................................................ + Init player color-used flags + ........................................................................*/ + for (i = 0; i < MAX_MPLAYER_COLORS; i++) { + ColorUsed[i] = 0; // init all colors to available + } + ColorUsed[MPlayerColorIdx] = 1; // set my color to used + playerlist.Set_Selected_Style(ColorListClass::SELECT_BAR, CC_GREEN_SHADOW); + + /*........................................................................ + Init random-number generator, & create a seed to be used for all random + numbers from here on out + ........................................................................*/ + randomize(); + Seed = rand(); + + /*........................................................................ + Init the message display system + ........................................................................*/ + Messages.Init (d_message_x + 2*factor, d_message_y + 2*factor, 4, MAX_MESSAGE_LENGTH, + d_txt6_h); + + /*------------------------------------------------------------------------ + Add myself to the list. Note that since I'm not in the Players Vector, + the Vector & listbox are now 1 out of sync. + ------------------------------------------------------------------------*/ + item = new char [MPLAYER_NAME_MAX + 4]; + if (MPlayerHouse==HOUSE_GOOD) { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_G_D_I)); + } else { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_N_O_D)); + } + playerlist.Add_Item(item, MPlayerTColors[MPlayerColorIdx]); + + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + while (Get_Mouse_State() > 0) Show_Mouse(); + + /* + ---------------------------- Processing loop ----------------------------- + */ + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + #if(SHOW_MONO) + Ipx.Mono_Debug_Print(-1,0); + #endif + /* + ...................... Refresh display if needed ...................... + */ + if (display == REDRAW_UNIT_COUNT){ + /* + ** Wipe the background behind the unit count then reprint it + */ + LogicPage->Fill_Rect(d_count_x + d_count_w + 2*factor, + d_count_y, + d_count_x + d_count_w + 2*factor + 20, + d_count_y + 12, + 0 ); + sprintf(txt,"%d",MPlayerUnitCount); + Fancy_Text_Print (txt, d_count_x + d_count_w + 2*factor, d_count_y, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL); + display = REDRAW_NONE; + } + + + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + + /* + ** Reload and draw the title page + */ + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_NETGAME_SETUP, d_dialog_x, d_dialog_y, d_dialog_w); + + Fancy_Text_Print(TXT_PLAYERS, + d_playerlist_x + (d_playerlist_w / 2), d_playerlist_y - d_txt6_h, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_CENTER); + + Fancy_Text_Print(TXT_SCENARIOS, + d_scenariolist_x + (d_scenariolist_w / 2), + d_scenariolist_y - d_txt6_h, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_CENTER); + + Fancy_Text_Print (TXT_COUNT, d_count_x - 2*factor, d_count_y, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_RIGHT); + + sprintf(txt,"%d",MPlayerUnitCount); + Fancy_Text_Print (txt, d_count_x + d_count_w + 2*factor, d_count_y, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL); + + Fancy_Text_Print (TXT_LEVEL, d_level_x - 2*factor, d_level_y, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_RIGHT); + + if (BuildLevel <= MPLAYER_BUILD_LEVEL_MAX) { + sprintf(txt,"%d",BuildLevel); + } else { + sprintf(txt, "**"); + } + Fancy_Text_Print (txt, d_level_x + d_level_w + 2*factor, d_level_y, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL); + + Fancy_Text_Print (TXT_START_CREDITS_COLON, d_credits_x - 5*factor, + d_credits_y + 1*factor, CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_RIGHT); + + } + + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Draw_All(); + } + + /*.................................................................. + Draw the messages: + - Erase an old message first + - If we're in a game, print the game options (if they've been + received) + - If we've been rejected from a game, print that message + ..................................................................*/ + if (display >= REDRAW_MESSAGE) { + Draw_Box(d_message_x, d_message_y, d_message_w, d_message_h, + BOXSTYLE_GREEN_BORDER, true); + Messages.Draw(); + } + + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + /*------------------------------------------------------------------ + New Scenario selected. + ------------------------------------------------------------------*/ + case (BUTTON_SCENARIOLIST | KN_BUTTON): + if (scenariolist.Current_Index() != ScenarioIdx) { + ScenarioIdx = scenariolist.Current_Index(); + MPlayerCredits = atoi(credbuf); + transmit = 1; + } + break; + + /*------------------------------------------------------------------ + Reject the currently-selected player (don't allow rejecting myself, + who will be the first entry in the list) + ------------------------------------------------------------------*/ + case (BUTTON_REJECT | KN_BUTTON): + index = playerlist.Current_Index(); + if (index == 0) { + CCMessageBox().Process (TXT_CANT_REJECT_SELF, TXT_OOPS); + display = REDRAW_ALL; + break; + } else { + if (index < 0 || index >= playerlist.Count()) { + CCMessageBox().Process (TXT_SELECT_PLAYER_REJECT,TXT_OOPS); + display = REDRAW_ALL; + break; + } + } + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_REJECT_JOIN; + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), 1, + &(Players[index - 1]->Address)); + break; + + /*------------------------------------------------------------------ + User adjusts max # units + ------------------------------------------------------------------*/ + case (BUTTON_COUNT | KN_BUTTON): + MPlayerUnitCount = countgauge.Get_Value() + MPlayerCountMin[MPlayerBases]; + + Hide_Mouse(); + LogicPage->Fill_Rect (d_count_x + d_count_w + 2*factor, d_count_y, + d_count_x + d_count_w + 14*factor, d_count_y + 6*factor, BLACK); + + sprintf(txt,"%d",MPlayerUnitCount); + Fancy_Text_Print (txt, d_count_x + d_count_w + 2*factor, d_count_y, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL); + Show_Mouse(); + + transmit = 1; + break; + + /*------------------------------------------------------------------ + User adjusts build level + ------------------------------------------------------------------*/ + case (BUTTON_LEVEL | KN_BUTTON): + BuildLevel = levelgauge.Get_Value() + 1; + if (BuildLevel > MPLAYER_BUILD_LEVEL_MAX) // if it's pegged, max it out + BuildLevel = MPLAYER_BUILD_LEVEL_MAX; + + Hide_Mouse(); + LogicPage->Fill_Rect (d_level_x + d_level_w + 2*factor, d_level_y, + d_level_x + d_level_w + 14*factor, d_level_y + 6*factor, BLACK); + + if (BuildLevel <= MPLAYER_BUILD_LEVEL_MAX) { + sprintf(txt,"%d",BuildLevel); + } else { + sprintf(txt, "**"); + } + Fancy_Text_Print (txt, d_level_x + d_level_w + 2*factor, d_level_y, + CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL); + Show_Mouse(); + + transmit = 1; + break; + + /*------------------------------------------------------------------ + User edits the credits value; retransmit new game options + ------------------------------------------------------------------*/ + case (BUTTON_CREDITS | KN_BUTTON): + MPlayerCredits = atoi(credbuf); + transmit = 1; + break; + + /*------------------------------------------------------------------ + Toggle bases: + - Clear scenario list & rebuild it with new names + - toggle bases button, change its text + - adjust the MPlayerUnitCount to reflect the new allowed range, + using the current gauge setting + - Change the unit count gauge limit & value + ------------------------------------------------------------------*/ + case (BUTTON_BASES | KN_BUTTON): + if (MPlayerBases) { + MPlayerBases = 0; + basesbtn.Turn_Off(); + basesbtn.Set_Text(TXT_BASES_OFF); + MPlayerUnitCount = Fixed_To_Cardinal (MPlayerCountMax[0]-MPlayerCountMin[0], + Cardinal_To_Fixed(MPlayerCountMax[1]-MPlayerCountMin[1], + MPlayerUnitCount-MPlayerCountMin[1])) + MPlayerCountMin[0]; + } else { + MPlayerBases = 1; + basesbtn.Turn_On(); + basesbtn.Set_Text(TXT_BASES_ON); + MPlayerUnitCount = Fixed_To_Cardinal (MPlayerCountMax[1]-MPlayerCountMin[1], + Cardinal_To_Fixed(MPlayerCountMax[0]-MPlayerCountMin[0], + MPlayerUnitCount-MPlayerCountMin[0])) + MPlayerCountMin[1]; + } + MPlayerCredits = atoi(credbuf); + countgauge.Set_Maximum(MPlayerCountMax[MPlayerBases] - MPlayerCountMin[MPlayerBases]); + countgauge.Set_Value(MPlayerUnitCount - MPlayerCountMin[MPlayerBases]); + transmit = 1; + countgauge.Flag_To_Redraw(); + display = REDRAW_UNIT_COUNT; + break; + + /*------------------------------------------------------------------ + Toggle tiberium + ------------------------------------------------------------------*/ + case (BUTTON_TIBERIUM | KN_BUTTON): + if (MPlayerTiberium) { + MPlayerTiberium = 0; + Special.IsTGrowth = 0; + Special.IsTSpread = 0; + tiberiumbtn.Turn_Off(); + tiberiumbtn.Set_Text(TXT_TIBERIUM_OFF); + } else { + MPlayerTiberium = 1; + Special.IsTGrowth = 1; + Special.IsTSpread = 1; + tiberiumbtn.Turn_On(); + tiberiumbtn.Set_Text(TXT_TIBERIUM_ON); + } + MPlayerCredits = atoi(credbuf); + transmit = 1; + break; + + /*------------------------------------------------------------------ + Toggle goodies + ------------------------------------------------------------------*/ + case (BUTTON_GOODIES | KN_BUTTON): + if (MPlayerGoodies) { + MPlayerGoodies = 0; + goodiesbtn.Turn_Off(); + goodiesbtn.Set_Text(TXT_CRATES_OFF); + } else { + MPlayerGoodies = 1; + goodiesbtn.Turn_On(); + goodiesbtn.Set_Text(TXT_CRATES_ON); + } + MPlayerCredits = atoi(credbuf); + transmit = 1; + break; + + /*------------------------------------------------------------------ + Toggle ghosts/capture-the-flag + ------------------------------------------------------------------*/ + case (BUTTON_GHOSTS | KN_BUTTON): + if (!MPlayerGhosts && !Special.IsCaptureTheFlag) { // ghosts OFF => ghosts ON + MPlayerGhosts = 1; + Special.IsCaptureTheFlag = 0; + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_AI_PLAYERS_ON); + } else { + if (MPlayerGhosts) { // ghosts ON => capture-flag + MPlayerGhosts = 0; + Special.IsCaptureTheFlag = 1; + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_CAPTURE_THE_FLAG); + } else { + if (Special.IsCaptureTheFlag) { // capture-flag => AI OFF + MPlayerGhosts = 0; + Special.IsCaptureTheFlag = 0; + ghostsbtn.Turn_Off(); + ghostsbtn.Set_Text(TXT_AI_PLAYERS_OFF); + } + } + } + + MPlayerCredits = atoi(credbuf); + transmit = 1; + break; + + /*------------------------------------------------------------------ + OK: exit loop with TRUE status + ------------------------------------------------------------------*/ + case (BUTTON_OK | KN_BUTTON): + /*............................................................... + If a new player has joined in the last second, don't allow + an OK; force a wait longer than 1 second (to give all players + a chance to know about this new guy) + ...............................................................*/ + i = MAX(Ipx.Global_Response_Time() * 2, 60); + while (TickCount.Time() - ok_timer < i) + Ipx.Service(); + + /*............................................................... + If there are at least 2 players, go ahead & play; error otherwise + ...............................................................*/ + if (MPlayerSolo || Players.Count() > 0) { + rc = TRUE; + process = FALSE; + } else { + CCMessageBox().Process (TXT_ONLY_ONE,TXT_OOPS,NULL); + display = REDRAW_ALL; + } + break; + + /*------------------------------------------------------------------ + CANCEL: send a SIGN_OFF, bail out with error code + ------------------------------------------------------------------*/ + case (KN_ESC): + if (Messages.Get_Edit_Buf() != NULL) { + Messages.Input(input); + display = REDRAW_MESSAGE; + break; + } + case (BUTTON_CANCEL | KN_BUTTON): + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy (GPacket.Name, MPlayerName); + + /*............................................................... + Broadcast my sign-off over my network + ...............................................................*/ + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + /*............................................................... + Broadcast my sign-off over a bridged network if there is one + ...............................................................*/ + if (IsBridge) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + /*............................................................... + And now, just be absolutely sure, send my sign-off to each + player in my game. (If there's a bridge between us, the other + player will have specified my address, so he can cross the + bridge; but I may not have specified a bridge address, so the + only way I have of crossing the bridge is to send a packet + directly to him.) + ...............................................................*/ + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Players[i]->Address)); + Ipx.Service(); + } + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + MPlayerGameName[0] = 0; + process = false; + rc = false; + break; + + /*------------------------------------------------------------------ + Default: manage the inter-player messages + ------------------------------------------------------------------*/ + default: + /*............................................................... + F4/SEND/'M' = send a message + ...............................................................*/ + if (Messages.Get_Edit_Buf()==NULL) { + if (input == KN_M || input==(BUTTON_SEND | KN_BUTTON) || + input == KN_F4) { + memset (txt, 0, 80); + + strcpy(txt,Text_String(TXT_TO_ALL)); // "To All:" + + Messages.Add_Edit (MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, txt, d_message_w-70*factor); + + credit_edt.Clear_Focus(); + credit_edt.Flag_To_Redraw(); + display = REDRAW_MESSAGE; + + break; + } + } else { + + /*............................................................... + If we're already editing a message and the user clicks on + 'Send', translate our input to a Return so Messages.Input() will + work properly. + ...............................................................*/ + if (input==(BUTTON_SEND | KN_BUTTON)) { + input = KN_RETURN; + } + } + + /*............................................................... + Manage the message system (get rid of old messages) + ...............................................................*/ + if (Messages.Manage()) { + display = REDRAW_MESSAGE; + } + + /*............................................................... + Re-draw the messages & service keyboard input for any message + being edited. + ...............................................................*/ + i = Messages.Input(input); + + /*............................................................... + If 'Input' returned 1, it means refresh the message display. + ...............................................................*/ + if (i==1) { + Messages.Draw(); + } + + /*............................................................... + If 'Input' returned 2, it means redraw the message display. + ...............................................................*/ + else if (i==2) { + display = REDRAW_MESSAGE; + } + + /*............................................................... + If 'input' returned 3, it means send the current message. + ...............................................................*/ + else if (i==3) { + + sent_so_far = 0; + magic_number = MESSAGE_HEAD_MAGIC_NUMBER; + message_length = strlen(Messages.Get_Edit_Buf()); + crc = (unsigned short) + (Calculate_CRC(Messages.Get_Edit_Buf(), message_length) &0xffff); + while ( sent_so_far < message_length ){ + memset (&GPacket, 0, sizeof(GlobalPacketType)); + GPacket.Command = NET_MESSAGE; + strcpy (GPacket.Name, MPlayerName); + memcpy (GPacket.Message.Buf, Messages.Get_Edit_Buf()+sent_so_far, COMPAT_MESSAGE_LENGTH-5); + *(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-5) = 0; + *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-4)) = magic_number; + *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-2)) = crc; + GPacket.Message.ID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + GPacket.Message.NameCRC = Compute_Name_CRC(MPlayerGameName); + + /*.................................................................. + Send the message to every player in our player list. + ..................................................................*/ + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Players[i]->Address)); + Ipx.Service(); + } + /*.................................................................. + Add the message to our own list, since we're not in the player list + on this dialog. + ..................................................................*/ + sprintf(txt,Text_String (TXT_FROM), MPlayerName, GPacket.Message.Buf); + Messages.Add_Message (txt, MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, magic_number, crc); + + magic_number++; + sent_so_far += COMPAT_MESSAGE_LENGTH-5; + + } + display = REDRAW_MESSAGE; + } + } + + /*--------------------------------------------------------------------- + Detect editing of the credits buffer, transmit new values to players + ---------------------------------------------------------------------*/ + if (atoi(credbuf) != old_cred) { + old_cred = Bound(atoi(credbuf), 0, 9999); + MPlayerCredits = old_cred; + transmit = 1; + sprintf(credbuf, "%d", MPlayerCredits); + credit_edt.Set_Text(credbuf, CREDITSBUF_MAX); + } + + /*--------------------------------------------------------------------- + Process incoming packets + ---------------------------------------------------------------------*/ + whahoppa = Get_NewGame_Responses(&playerlist); + if (whahoppa == EV_NEW_PLAYER) { + ok_timer = TickCount.Time(); + transmit = 1; + } else { + if (whahoppa == EV_MESSAGE) { + display = REDRAW_MESSAGE; + } + } + + /*--------------------------------------------------------------------- + If our Transmit flag is set, we need to send out a game option packet + ---------------------------------------------------------------------*/ + if (transmit) { + for (i = 0; i < Players.Count(); i++) { + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_GAME_OPTIONS; + GPacket.ScenarioInfo.Scenario = MPlayerFilenum[ScenarioIdx]; + GPacket.ScenarioInfo.Credits = MPlayerCredits; + GPacket.ScenarioInfo.IsBases = MPlayerBases; + GPacket.ScenarioInfo.IsTiberium = MPlayerTiberium; + GPacket.ScenarioInfo.IsGoodies = MPlayerGoodies; + GPacket.ScenarioInfo.IsGhosties = MPlayerGhosts; + GPacket.ScenarioInfo.BuildLevel = BuildLevel; + GPacket.ScenarioInfo.UnitCount = MPlayerUnitCount; + GPacket.ScenarioInfo.Seed = Seed; + GPacket.ScenarioInfo.Special = Special; + GPacket.ScenarioInfo.GameSpeed = Options.GameSpeed; + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &(Players[i]->Address) ); + } + transmit = 0; + } + + /*--------------------------------------------------------------------- + Ping every player in my game, to force the Global Channel to measure + the connection response time. + ---------------------------------------------------------------------*/ + if (TickCount.Time() - ping_timer > 15) { + memset (&GPacket, 0, sizeof(GlobalPacketType)); + GPacket.Command = NET_PING; + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &(Players[i]->Address) ); + } + ping_timer = TickCount.Time(); + } + + /*--------------------------------------------------------------------- + Service the Ipx connections + ---------------------------------------------------------------------*/ + Ipx.Service(); + + /*--------------------------------------------------------------------- + Service the sounds & score; GameActive must be false at this point, + so Call_Back() doesn't intercept global messages from me! + ---------------------------------------------------------------------*/ + Call_Back(); + + } /* end of while */ + + /*------------------------------------------------------------------------ + Establish connections with all other players. + ------------------------------------------------------------------------*/ + if (rc) { + /*..................................................................... + Set the number of players in this game, and my ID + .....................................................................*/ + MPlayerCount = Players.Count() + 1; + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + /*..................................................................... + Get the scenario filename + .....................................................................*/ + Scenario = MPlayerFilenum[ScenarioIdx]; + + /*..................................................................... + Compute frame delay value for packet transmissions: + - Divide global channel's response time by 8 (2 to convert to 1-way + value, 4 more to convert from ticks to frames) + .....................................................................*/ + MPlayerMaxAhead = MAX( (Ipx.Global_Response_Time() / 8), 2); + + /*..................................................................... + Send all players the NET_GO packet. Wait until all ACK's have been + received. + .....................................................................*/ + memset (&GPacket, 0, sizeof(GlobalPacketType)); + GPacket.Command = NET_GO; + GPacket.ResponseTime.OneWay = MPlayerMaxAhead; + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &(Players[i]->Address) ); + /*.................................................................. + Wait for all the ACK's to come in. + ..................................................................*/ + while (Ipx.Global_Num_Send() > 0) + Ipx.Service(); + } + + /*..................................................................... + Form connections with all other players. Form the IPX Connection ID + from the player's Color (high byte) and House (low byte). This + will let us extract any player's color & house at any time. + Fill in 'tmp_id' while we're doing this. + .....................................................................*/ + for (i = 0; i < Players.Count(); i++) { + id = Build_MPlayerID (Players[i]->Player.Color, + Players[i]->Player.House); + + tmp_id[i] = id; + + Ipx.Create_Connection(id, Players[i]->Name, &(Players[i]->Address) ); + } + tmp_id[i] = MPlayerLocalID; + + /*..................................................................... + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + .....................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + min_index = 0; + min_id = 0xff; + for (j = 0; j < MPlayerCount; j++) { + if (tmp_id[j] < min_id) { + min_id = tmp_id[j]; + min_index = j; + } + } + MPlayerID[i] = tmp_id[min_index]; + tmp_id[min_index] = 0xff; + } + /*..................................................................... + Fill in the array of player names, including my own. + .....................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID[i] == MPlayerLocalID) { + strcpy (MPlayerNames[i], MPlayerName); + } else { + strcpy (MPlayerNames[i], Ipx.Connection_Name(MPlayerID[i])); + } + } + } + + /*------------------------------------------------------------------------ + Init network timing values, using previous response times as a measure + of what our retry delta & timeout should be. + ------------------------------------------------------------------------*/ + Ipx.Set_Timing (Ipx.Global_Response_Time() + 2, -1, + Ipx.Global_Response_Time() * 4); + + /*------------------------------------------------------------------------ + Clear all lists + ------------------------------------------------------------------------*/ + while (scenariolist.Count()) { + scenariolist.Remove_Item(scenariolist.Get_Item(0)); + } + Clear_Player_List(&playerlist); + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + return(rc); +} + + +/*************************************************************************** + * Get_NewGame_Responses -- processes packets for New Game dialog * + * * + * This routine can modify the contents of the given list box, as well * + * as the contents of the Players Vector global. * + * * + * INPUT: * + * playerlist list of players in this game * + * * + * OUTPUT: * + * EV_NONE = nothing happened * + * EV_NEW_PLAYER = a new player has joined; false otherwise * + * EV_MESSAGE = a message was received * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/18/1995 BRR : Created. * + *=========================================================================*/ +static JoinEventType Get_NewGame_Responses(ColorListClass *playerlist) +{ + int rc; + char * item; // general-purpose string + NodeNameType *who; // node to add to Players Vector + int i; + int found; + JoinEventType retval = EV_NONE; + int resend; + char txt[80]; + int color; + unsigned short magic_number; + unsigned short crc; + + /*------------------------------------------------------------------------ + If there is no incoming packet, just return + ------------------------------------------------------------------------*/ + rc = Ipx.Get_Global_Message (&GPacket, &GPacketlen, &GAddress, &GProductID); + if (!rc || GProductID != IPXGlobalConnClass::COMMAND_AND_CONQUER) { + return(EV_NONE); + } + + /*------------------------------------------------------------------------ + Try to handle the packet in a standard way + ------------------------------------------------------------------------*/ + if (Process_Global_Packet(&GPacket,&GAddress) != 0) { + return(EV_NONE); + } else + + /*------------------------------------------------------------------------ + NET_QUERY_JOIN: + ------------------------------------------------------------------------*/ + if (GPacket.Command==NET_QUERY_JOIN) { + /*..................................................................... + See if this name is unique: + - If the name matches, but the address is different, reject this player + - If the name & address match, this packet must be a re-send of a + prevous request; in this case, do nothing. The other player must have + received my CONFIRM_JOIN packet (since it was sent with an ACK + required), so we can ignore this resend. + .....................................................................*/ + found = 0; + resend = 0; + for (i = 0; i < Players.Count(); i++) { + if (!strcmp(Players[i]->Name,GPacket.Name)) { + if (Players[i]->Address != GAddress) { + found = 1; + } + else { + resend = 1; + } + break; + } + } + if (!strcmp (MPlayerName, GPacket.Name)) { + found = 1; + } + + /*..................................................................... + Reject if name is a duplicate, or if there are too many players: + .....................................................................*/ + if (found || (Players.Count() >= (MPlayerMax - 1) && !resend) ) { + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_REJECT_JOIN; + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &GAddress); + } + + /*..................................................................... + If this packet is NOT a resend, accept the player. Grant him the + requested color if possible. + .....................................................................*/ + else if (!resend) { + /*.................................................................. + Add node to the Vector list + ..................................................................*/ + who = new NodeNameType; + strcpy(who->Name, GPacket.Name); + who->Address = GAddress; + who->Player.House = GPacket.PlayerInfo.House; + Players.Add (who); + + /*.................................................................. + Set player's color; if requested color isn't used, give it to him; + otherwise, give him the 1st available color. Mark the color we + give him as used. + ..................................................................*/ + if (ColorUsed[GPacket.PlayerInfo.Color] == 0) { + who->Player.Color = GPacket.PlayerInfo.Color; + } else { + for (i = 0; i < MAX_MPLAYER_COLORS; i++) { + if (ColorUsed[i]==0) { + who->Player.Color = i; + break; + } + } + } + ColorUsed[who->Player.Color] = 1; + + /*.................................................................. + Add player name to the list box + ..................................................................*/ + item = new char [MPLAYER_NAME_MAX + 4]; + if (GPacket.PlayerInfo.House==HOUSE_GOOD) { + sprintf(item,"%s\t%s",GPacket.Name,Text_String(TXT_G_D_I)); + } else { + sprintf(item,"%s\t%s",GPacket.Name,Text_String(TXT_N_O_D)); + } + playerlist->Add_Item (item, MPlayerTColors[who->Player.Color]); + + /*.................................................................. + Send a confirmation packet + ..................................................................*/ + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_CONFIRM_JOIN; + strcpy(GPacket.Name,MPlayerName); + GPacket.PlayerInfo.House = who->Player.House; + GPacket.PlayerInfo.Color = who->Player.Color; + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &GAddress); + + retval = EV_NEW_PLAYER; + } + } + + /*------------------------------------------------------------------------ + NET_SIGN_OFF: Another system is signing off: search for that system in + the player list, & remove it if found + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_SIGN_OFF) { + for (i = 0; i < Players.Count(); i++) { + /* + ....................... Name found; remove it ...................... + */ + if (!strcmp (Players[i]->Name, GPacket.Name) && + Players[i]->Address==GAddress) { + /*............................................................... + Remove from the list box + ...............................................................*/ + item = (char *)(playerlist->Get_Item(i + 1)); + playerlist->Remove_Item(item); + playerlist->Flag_To_Redraw(); + delete [] item; + /*............................................................... + Mark his color as available + ...............................................................*/ + ColorUsed[Players[i]->Player.Color] = 0; + /*............................................................... + Delete from the Vector list + ...............................................................*/ + Players.Delete(Players[i]); + break; + } + } + } + + /*------------------------------------------------------------------------ + NET_MESSAGE: Someone is sending us a message + ------------------------------------------------------------------------*/ + else if (GPacket.Command==NET_MESSAGE) { + sprintf(txt,Text_String (TXT_FROM), GPacket.Name, GPacket.Message.Buf); + magic_number = *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-4)); + crc = *((unsigned short*)(GPacket.Message.Buf + COMPAT_MESSAGE_LENGTH-2)); + color = MPlayerID_To_ColorIndex(GPacket.Message.ID); + Messages.Add_Message (txt, MPlayerTColors[color], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, magic_number, crc); + retval = EV_MESSAGE; + } + + return(retval); +} + + + +/*************************************************************************** + * Compute_Name_CRC -- computes CRC from char string * + * * + * INPUT: * + * name string to create CRC for * + * * + * OUTPUT: * + * CRC * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/29/1995 BRR : Created. * + *=========================================================================*/ +unsigned long Compute_Name_CRC(char *name) +{ + char buf[80]; + unsigned long crc = 0L; + int i; + + strcpy (buf, name); + strupr (buf); + + for (i = 0; i < strlen(buf); i++) { + Add_CRC (&crc, (unsigned long)buf[i]); + } + + return (crc); +} + +/*************************************************************************** + * Net_Reconnect_Dialog -- Draws/updates the network reconnect dialog * + * * + * INPUT: * + * reconn 1 = reconnect, 0 = waiting for first-time connection * + * fresh 1 = draw from scratch, 0 = only update time counter * + * oldest_index IPX connection index of oldest connection * + * (only used for reconnection) * + * timeval value to print in the countdown field * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/08/1995 BRR : Created. * + *=========================================================================*/ +void Net_Reconnect_Dialog(int reconn, int fresh, int oldest_index, + unsigned long timeval) +{ + static int x,y,w,h; + int id; + char buf1[40] = {0}; + char buf2[40] = {0}; + char const *buf3 = ""; + + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + int d_txt6_h = 6*factor+1; + int d_margin = 5*factor; + + + /*------------------------------------------------------------------------ + Draw the dialog from scratch + ------------------------------------------------------------------------*/ + if (fresh) { + Fancy_Text_Print ("", 0, 0, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + if (reconn) { + id = Ipx.Connection_ID(oldest_index); + sprintf(buf1,Text_String(TXT_RECONNECTING_TO), + Ipx.Connection_Name(id)); + } else { + sprintf(buf1,Text_String(TXT_WAITING_FOR_CONNECTIONS)); + } + sprintf(buf2,Text_String(TXT_TIME_ALLOWED), timeval + 1); + buf3 = Text_String(TXT_PRESS_ESC); + + w = MAX(String_Pixel_Width(buf1),String_Pixel_Width(buf2)); + w = MAX(String_Pixel_Width(buf3), w); + w += (d_margin * 2); + h = (d_txt6_h * 3) + (d_margin * 6); + x = 160*factor - (w / 2); + y = 100*factor - (h / 2); + + Hide_Mouse(); + Set_Logic_Page(SeenBuff); + Dialog_Box(x, y, w, h); + + Fancy_Text_Print (buf1, 160*factor, y + (d_margin * 2), CC_GREEN, BLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print (buf2, 160*factor, y + (d_margin * 2) + d_txt6_h + d_margin, + CC_GREEN, BLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print (buf3, 160*factor, y + (d_margin * 2) + (d_txt6_h + d_margin) * 2, + CC_GREEN, BLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Show_Mouse(); + + } else { + + /*------------------------------------------------------------------------ + Just update the timeout value on the dialog + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Set_Logic_Page(SeenBuff); + + sprintf(buf2,Text_String(TXT_TIME_ALLOWED), timeval + 1); + Fancy_Text_Print (buf2, 160*factor, y + (d_margin * 2) + d_txt6_h + d_margin, + CC_GREEN, BLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Show_Mouse(); + } +} + + + + + + + + + + + +/*********************************************************************************************** + * Net_Fake_New_Dialog -- Just like Net_New_Dialog but without the Dialog. For internet play * + * * + * This 'dialog' does all the non-dialog game set up stuff that is done in the normal * + * network game set up dialog. The only visible button is 'cancel' * + * * + * INPUT: Nothing * + * * + * OUTPUT: true if successfully connected * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 5/24/96 10:34AM ST : Created * + *=============================================================================================*/ +static int Net_Fake_New_Dialog(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + int d_dialog_w = 120*factor; // dialog width + int d_dialog_h = 80*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200*factor - d_dialog_h) / 2); // centered y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + //d_playerlist_w = 100; + int d_playerlist_w = 106*factor; + int d_playerlist_h = 27*factor; + int d_playerlist_x = 10 * factor; //off screen + //int d_playerlist_x = 500*factor; //10 * factor; //off screen + int d_playerlist_y = d_dialog_y + 20; + +#if (GERMAN | FRENCH) + int d_cancel_w = 50*factor; +#else + int d_cancel_w = 45*factor; +#endif + int d_cancel_h = 9*factor; + int d_cancel_x = d_dialog_cx - (d_cancel_w / 2); + int d_cancel_y = d_dialog_y + d_dialog_h - 20*factor; + + char text_buffer[64]; + int width; + int height; + + bool player_joined = false; + CountDownTimerClass join_timer; + + strcpy(text_buffer, "Connecting...."); + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + Format_Window_String(text_buffer, SeenBuff.Get_Height(), width, height); + + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + BUTTON_PLAYERLIST, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_MESSAGE, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + bool process = true; // process while true + KeyNumType input; + + char credbuf[CREDITSBUF_MAX]; // for credit edit box + int old_cred; // old value in credits buffer + int transmit; // 1 = re-transmit new game options + + long ok_timer = 0; // for timing OK button + int rc; + int i,j; + char *item; + int tabs[] = {77*factor}; // tabs for player list box + + long ping_timer = 0; // for sending Ping packets + + unsigned char tmp_id[MAX_PLAYERS]; // temp storage for sorting player ID's + int min_index; // for sorting player ID's + unsigned char min_id; // for sorting player ID's + unsigned char id; // connection ID + JoinEventType whahoppa; // event generated by received packets + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + ColorListClass playerlist(BUTTON_PLAYERLIST, + d_playerlist_x, d_playerlist_y, d_playerlist_w, d_playerlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_cancel_x, d_cancel_y); +#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +#endif + + + CCDebugString ("C&C95 - In new game dialog - initialising lists.\n"); + /* + ------------------------- Build the button list -------------------------- + */ + commands = &playerlist; + cancelbtn.Add_Tail(*commands); + + playerlist.Set_Tabs(tabs); + + /* + ----------------------------- Various Inits ------------------------------ + */ + + sprintf(credbuf, "%d", MPlayerCredits); + old_cred = MPlayerCredits; + + /*........................................................................ + Init other scenario parameters + ........................................................................*/ + Special.IsTGrowth = MPlayerTiberium; + Special.IsTSpread = MPlayerTiberium; + transmit = 0; + + /*........................................................................ + Init player color-used flags + ........................................................................*/ + for (i = 0; i < MAX_MPLAYER_COLORS; i++) { + ColorUsed[i] = 0; // init all colors to available + } + ColorUsed[MPlayerColorIdx] = 1; // set my color to used + playerlist.Set_Selected_Style(ColorListClass::SELECT_BAR, CC_GREEN_SHADOW); + + /*........................................................................ + Init random-number generator, & create a seed to be used for all random + numbers from here on out + ........................................................................*/ + randomize(); + Seed = rand(); + + /*------------------------------------------------------------------------ + Add myself to the list. Note that since I'm not in the Players Vector, + the Vector & listbox are now 1 out of sync. + ------------------------------------------------------------------------*/ + item = new char [MPLAYER_NAME_MAX + 4]; + if (MPlayerHouse==HOUSE_GOOD) { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_G_D_I)); + } else { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_N_O_D)); + } + playerlist.Add_Item(item, MPlayerTColors[MPlayerColorIdx]); + + /* + ** Process the message loop until we are in focus. + */ + if (!GameInFocus){ + CCDebugString ("C&C95 - Waiting for game to come into focus."); + do { + OutputDebugString ("."); + Keyboard::Check(); + }while (!GameInFocus); + CCDebugString ("\n"); + AllSurfaces.SurfacesRestored=FALSE; + } + + + CCDebugString ("C&C95 - About to uncompress title page.\n"); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + CCDebugString ("C&C95 - About to set the palette.\n"); + Set_Palette(Palette); + CCDebugString ("C&C95 - Palette was set OK.\n"); + + if (LogicPage != &SeenBuff && LogicPage!= &HidPage){ + CCDebugString ("C&C95 - Logic page invalid"); + Set_Logic_Page (SeenBuff); + } + + char a_buffer [128]; + sprintf (a_buffer, "Number of players:%d", Players.Count()); + CCDebugString (a_buffer); + + + /* + ** Send a bogus packet to wake up the VSS + */ + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = (NetCommandType)50; //Invalid command + strcpy (GPacket.Name, MPlayerName); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), 0, NULL); + + + CCDebugString ("C&C95 - About to reveal mouse\n"); + while (Get_Mouse_State() > 0) Show_Mouse(); + + /* + ---------------------------- Processing loop ----------------------------- + */ + CCDebugString ("C&C95 - Entering join dialogue loop\n"); + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_NONE, d_dialog_x, d_dialog_y, d_dialog_w); + + Fancy_Text_Print(text_buffer, d_dialog_cx-width/2, d_dialog_y + 25*factor, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Draw_All(); + } + + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + + /*------------------------------------------------------------------ + CANCEL: send a SIGN_OFF, bail out with error code + ------------------------------------------------------------------*/ + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy (GPacket.Name, MPlayerName); + + /*............................................................... + Broadcast my sign-off over my network + ...............................................................*/ + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + /*............................................................... + Broadcast my sign-off over a bridged network if there is one + ...............................................................*/ + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + /*............................................................... + And now, just be absolutely sure, send my sign-off to each + player in my game. (If there's a bridge between us, the other + player will have specified my address, so he can cross the + bridge; but I may not have specified a bridge address, so the + only way I have of crossing the bridge is to send a packet + directly to him.) + ...............................................................*/ + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Players[i]->Address)); + Ipx.Service(); + } + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + MPlayerGameName[0] = 0; + process = false; + rc = false; + break; + + /*------------------------------------------------------------------ + default: exit loop with TRUE status + ------------------------------------------------------------------*/ + default: +char ddkks[128]; + if (Players.Count() == InternetMaxPlayers-1){ + +sprintf (ddkks, "C&C95 - Players.Count() = %d\n", Players.Count()); +CCDebugString (ddkks); + + + /* + ** Wait for several secs after receiving request to join before sending + ** start game packet + */ + if (!player_joined){ + player_joined = true; + join_timer.Set (3*60, true); + break; + }else{ + if (join_timer.Time()) break; + } + +CCDebugString ("C&C95 - Join timer expired\n"); + + /*............................................................... + If a new player has joined in the last second, don't allow + an OK; force a wait longer than 2 seconds (to give all players + a chance to know about this new guy) + ...............................................................*/ + i = MAX(Ipx.Global_Response_Time() * 2, 120); + while (TickCount.Time() - ok_timer < i) + Ipx.Service(); + + /*............................................................... + If there are at least 2 players, go ahead & play; error otherwise + ...............................................................*/ + if (MPlayerSolo || Players.Count() > 0) { + rc = TRUE; + process = FALSE; + } else { + CCMessageBox().Process (TXT_ONLY_ONE,TXT_OOPS,NULL); + display = REDRAW_ALL; + } + } + break; + } + + /*--------------------------------------------------------------------- + Process incoming packets + ---------------------------------------------------------------------*/ + whahoppa = Get_NewGame_Responses(&playerlist); + if (whahoppa == EV_NEW_PLAYER) { + ok_timer = TickCount.Time(); + transmit = 1; + } else { + if (whahoppa == EV_MESSAGE) { + display = REDRAW_MESSAGE; + } + } + + /*--------------------------------------------------------------------- + If our Transmit flag is set, we need to send out a game option packet + ---------------------------------------------------------------------*/ + if (transmit) { + for (i = 0; i < Players.Count(); i++) { + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_GAME_OPTIONS; + GPacket.ScenarioInfo.Scenario = ScenarioIdx; //MPlayerFilenum[ScenarioIdx]; + GPacket.ScenarioInfo.Credits = MPlayerCredits; + GPacket.ScenarioInfo.IsBases = MPlayerBases; + GPacket.ScenarioInfo.IsTiberium = MPlayerTiberium; + GPacket.ScenarioInfo.IsGoodies = MPlayerGoodies; + GPacket.ScenarioInfo.IsGhosties = MPlayerGhosts; + GPacket.ScenarioInfo.BuildLevel = BuildLevel; + GPacket.ScenarioInfo.UnitCount = MPlayerUnitCount; + GPacket.ScenarioInfo.Seed = Seed; + GPacket.ScenarioInfo.Special = Special; + GPacket.ScenarioInfo.GameSpeed = Options.GameSpeed; + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &(Players[i]->Address) ); + } + transmit = 0; + } + + /*--------------------------------------------------------------------- + Ping every player in my game, to force the Global Channel to measure + the connection response time. + ---------------------------------------------------------------------*/ + if (TickCount.Time() - ping_timer > 15) { + memset (&GPacket, 0, sizeof(GlobalPacketType)); + GPacket.Command = NET_PING; + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &(Players[i]->Address) ); + } + ping_timer = TickCount.Time(); + } + + /*--------------------------------------------------------------------- + Service the Ipx connections + ---------------------------------------------------------------------*/ + Ipx.Service(); + + /*--------------------------------------------------------------------- + Service the sounds & score; GameActive must be false at this point, + so Call_Back() doesn't intercept global messages from me! + ---------------------------------------------------------------------*/ + Call_Back(); + + } /* end of while */ + +CCDebugString ("C&C95 - Exited process loop\n"); + + /*------------------------------------------------------------------------ + Establish connections with all other players. + ------------------------------------------------------------------------*/ + if (rc) { + /*..................................................................... + Set the number of players in this game, and my ID + .....................................................................*/ + MPlayerCount = Players.Count() + 1; + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + /*..................................................................... + Get the scenario filename + .....................................................................*/ + Scenario = ScenarioIdx; //PlayerFilenum[ScenarioIdx]; We are passed actual number now from wchat not index from + //Scenario = MPlayerFilenum[ScenarioIdx]; + + /*..................................................................... + Compute frame delay value for packet transmissions: + - Divide global channel's response time by 8 (2 to convert to 1-way + value, 4 more to convert from ticks to frames) + .....................................................................*/ + MPlayerMaxAhead = MAX( (Ipx.Global_Response_Time() / 8), 2); + + /*..................................................................... + Send all players the NET_GO packet. Wait until all ACK's have been + received. + .....................................................................*/ +CCDebugString ("C&C95 - Sending the 'GO' packet\n"); + memset (&GPacket, 0, sizeof(GlobalPacketType)); + GPacket.Command = NET_GO; + GPacket.ResponseTime.OneWay = MPlayerMaxAhead; + for (i = 0; i < Players.Count(); i++) { +char flopbuf [128]; +sprintf (flopbuf, "Sending 'GO' packet to address %d\n", *((unsigned short*)&(Players[i]->Address))); +CCDebugString (flopbuf); + + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 1, &(Players[i]->Address) ); + /*.................................................................. + Wait for all the ACK's to come in. + ..................................................................*/ + while (Ipx.Global_Num_Send() > 0) + Ipx.Service(); + } + + /*..................................................................... + Form connections with all other players. Form the IPX Connection ID + from the player's Color (high byte) and House (low byte). This + will let us extract any player's color & house at any time. + Fill in 'tmp_id' while we're doing this. + .....................................................................*/ + for (i = 0; i < Players.Count(); i++) { + id = Build_MPlayerID (Players[i]->Player.Color, + Players[i]->Player.House); + + tmp_id[i] = id; + + Ipx.Create_Connection(id, Players[i]->Name, &(Players[i]->Address) ); + } + +CCDebugString ("C&C95 - Creating connection to the VSS\n"); + /* + ** Create an additional connection to the VSS + */ + if (UseVirtualSubnetServer){ + IPXAddressClass vss_global_address; + NetNodeType vss_node; + NetNumType vss_net; + memset (vss_net, 1, sizeof (vss_net)); + memset (vss_node, 0, sizeof (vss_node)); + vss_global_address.Set_Address(vss_net, vss_node); + Ipx.Create_Connection( VSS_ID, "VSS", &vss_global_address); + } + + tmp_id[i] = MPlayerLocalID; + + /*..................................................................... + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + .....................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + min_index = 0; + min_id = 0xff; + for (j = 0; j < MPlayerCount; j++) { + if (tmp_id[j] < min_id) { + min_id = tmp_id[j]; + min_index = j; + } + } + MPlayerID[i] = tmp_id[min_index]; + tmp_id[min_index] = 0xff; + } + /*..................................................................... + Fill in the array of player names, including my own. + .....................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID[i] == MPlayerLocalID) { + strcpy (MPlayerNames[i], MPlayerName); + } else { + strcpy (MPlayerNames[i], Ipx.Connection_Name(MPlayerID[i])); + } + } + } + + /*------------------------------------------------------------------------ + Init network timing values, using previous response times as a measure + of what our retry delta & timeout should be. + ------------------------------------------------------------------------*/ + Ipx.Set_Timing (Ipx.Global_Response_Time() + 2, -1, + Ipx.Global_Response_Time() * 4); + + /*------------------------------------------------------------------------ + Clear all lists + ------------------------------------------------------------------------*/ + Clear_Player_List(&playerlist); + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + return(rc); +} + + + + + + +/*********************************************************************************************** + * Net_Fake_Join_Dialog -- Like Net_Join_Dialog but with no dialogs. For Internet Play. * + * * + * This 'dialog' does all the non-dialog game set up stuff that is done in the normal * + * network game set up dialog. The only visible button is 'cancel' * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: 0 = good, -1 = bad * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 5/24/96 11:07AM ST : Created * + *=============================================================================================*/ + +static int Net_Fake_Join_Dialog(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ +/* ###Change collision detected! C:\PROJECTS\CODE\NETDLG.CPP... */ + int d_dialog_w = 120 *factor; // dialog width + int d_dialog_h = 80*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200*factor - d_dialog_h) / 2); // centered y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_margin1=10; + int d_txt6_h=15; + + int d_gamelist_w = 160*factor; + int d_gamelist_h = 27*factor; + //int d_gamelist_x = 500*factor; //230*factor; //Off screen + int d_gamelist_x = 230*factor; //Off screen + int d_gamelist_y = d_dialog_y + 20; + + int d_playerlist_w = 106*factor; + int d_playerlist_h = 27*factor; + int d_playerlist_x = 10 * factor; //Off screen + //int d_playerlist_x = 500*factor; //10 * factor; //Off screen + int d_playerlist_y = d_gamelist_y + 20; + +#if (GERMAN | FRENCH) + int d_cancel_w = 50*factor; +#else + int d_cancel_w = 40*factor; +#endif + int d_cancel_h = 9*factor; + int d_cancel_x = d_dialog_cx - d_cancel_w / 2; + int d_cancel_y = d_dialog_y + d_dialog_h - 20*factor; + + bool ready_to_go = false; + + char text_buffer[64]; + int width; + int height; + strcpy(text_buffer, "Connecting...."); + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + Format_Window_String(text_buffer, SeenBuff.Get_Height(), width, height); + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + BUTTON_GAMELIST, + BUTTON_PLAYERLIST, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_MESSAGE, + REDRAW_COLORS, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + bool process = true; // process while true + KeyNumType input; + + JoinStateType joinstate = JOIN_NOTHING; // current "state" of this dialog + char namebuf[MPLAYER_NAME_MAX] = {0}; // buffer for player's name + int game_index = -1; // index of currently-selected game + int join_index = -1; // index of game we're joining + int rc = 0; // -1 = user cancelled, 1 = New + JoinEventType event; // event from incoming packet + int i,j; // loop counter + int parms_received; // 1 = game options received + + unsigned char tmp_id[MAX_PLAYERS]; // temp storage for sorting player ID's + int min_index; // for sorting player ID's + unsigned char min_id; // for sorting player ID's + unsigned char id; // connection ID + char * item; + unsigned long starttime; + + NodeNameType *who; + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + ColorListClass playerlist(BUTTON_PLAYERLIST, + d_playerlist_x, d_playerlist_y, d_playerlist_w, d_playerlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + ListClass gamelist(BUTTON_GAMELIST, + d_gamelist_x, d_gamelist_y, d_gamelist_w, d_gamelist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_cancel_x, d_cancel_y); +#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +#endif + + /* + ----------------------------- Various Inits ------------------------------ + */ + //MPlayerColorIdx = MPlayerPrefColor; // init my preferred color + + playerlist.Set_Selected_Style(ColorListClass::SELECT_NONE); + + Fancy_Text_Print("", 0, 0, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /* + --------------------------- Send network query --------------------------- + */ + CCDebugString ("C&C95 - About to call Send_Join_Queries.\n"); + Send_Join_Queries (game_index, 1, 0); + + /* + ** Process the message loop until we are in focus. + */ + if (!GameInFocus){ + CCDebugString ("C&C95 - Waiting for game to come into focus."); + do { + CCDebugString ("."); + Keyboard::Check(); + }while (!GameInFocus); + CCDebugString ("\n"); + AllSurfaces.SurfacesRestored=FALSE; + } + + CCDebugString ("C&C95 - About to uncompress title page.\n"); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + CCDebugString ("C&C95 - About to set the palette.\n"); + Set_Palette(Palette); + CCDebugString ("C&C95 - Palette was set OK.\n"); + + if (LogicPage != &SeenBuff && LogicPage!= &HidPage){ + CCDebugString ("C&C95 - Logic page invalid\n"); + Set_Logic_Page (SeenBuff); + } + + + char a_buffer [128]; + sprintf (a_buffer, "C&C95 - Number of players:%d\n", Players.Count()); + CCDebugString (a_buffer); + + /* + ---------------------------- Init Mono Output ---------------------------- + */ + CCDebugString ("C&C95 - About to reveal mouse\n"); + while (Get_Mouse_State() > 0) Show_Mouse(); + + /* + ---------------------------- Processing loop ----------------------------- + */ + CCDebugString ("C&C95 - Entering join dialogue loop\n"); + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_NONE, d_dialog_x, d_dialog_y, d_dialog_w); + + Fancy_Text_Print(text_buffer, d_dialog_cx-width/2, d_dialog_y + 25*factor, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /* + .................... Rebuild the button list .................... + */ + cancelbtn.Zap(); + gamelist.Zap(); + playerlist.Zap(); + + commands = &cancelbtn; + gamelist.Add_Tail(*commands); + playerlist.Add_Tail(*commands); + } + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Draw_All(); + } + + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + + /*------------------------------------------------------------------ + CANCEL: send a SIGN_OFF + - If we're part of a game, stay in this dialog; otherwise, exit + ------------------------------------------------------------------*/ + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy(GPacket.Name,MPlayerName); + + /*............................................................... + If we're joined to a game, make extra sure the other players in + that game know I'm exiting; send my SIGN_OFF as an ack-required + packet. Do not send this packet to myself (index 0). + ...............................................................*/ + if (joinstate == JOIN_CONFIRMED) { + // + // Remove myself from the player list box + // + item = (char *)(playerlist.Get_Item(0)); + playerlist.Remove_Item(item); + delete [] item; + playerlist.Flag_To_Redraw(); + + // + // Remove myself from the Players list + // + who = Players[0]; + Players.Delete(0); + delete who; + + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Players[i]->Address)); + Ipx.Service(); + } + } + + /*............................................................... + Now broadcast my SIGN_OFF so other players looking at this game + know I'm leaving. + ...............................................................*/ + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + + if (IsBridge) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + if (joinstate != JOIN_CONFIRMED) { + process = false; + rc = -1; + } else { + MPlayerGameName[0] = 0; + joinstate = JOIN_NOTHING; + display = REDRAW_ALL; + } + break; + + /*------------------------------------------------------------------ + JOIN: send a join request packet & switch to waiting-for-confirmation + mode. (Request_To_Join fills in MPlayerName with my namebuf.) + ------------------------------------------------------------------*/ + default: + if (joinstate == JOIN_NOTHING && Games.Count()!=0){ + gamelist.Set_Selected_Index(0); + join_index = gamelist.Current_Index(); + parms_received = 0; + if (Request_To_Join (MPlayerName, join_index, &playerlist, MPlayerHouse, + MPlayerColorIdx)) { + joinstate = JOIN_WAIT_CONFIRM; + } else { + display = REDRAW_ALL; + } + } + break; + + } + + /*--------------------------------------------------------------------- + Resend our query packets + ---------------------------------------------------------------------*/ + Send_Join_Queries(game_index, 0, 0); + + /*--------------------------------------------------------------------- + Process incoming packets + ---------------------------------------------------------------------*/ + event = Get_Join_Responses(&joinstate, &gamelist, &playerlist, + join_index); + /*..................................................................... + If we've changed state, redraw everything; if we're starting the game, + break out of the loop. If we've just joined, send out a player query + so I'll get added to the list instantly. + .....................................................................*/ + if (event == EV_STATE_CHANGE) { + display = REDRAW_ALL; + if (joinstate==JOIN_GAME_START) { +CCDebugString ("C&C95 - Received 'GO' packet\n"); + + ready_to_go = true; + } else { + + /*.................................................................. + If we're newly-confirmed, immediately send out a player query + ..................................................................*/ + if (joinstate==JOIN_CONFIRMED) { + + Clear_Player_List(&playerlist); + + item = new char [MPLAYER_NAME_MAX + 4]; + if (MPlayerHouse==HOUSE_GOOD) { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_G_D_I)); + } else { + sprintf(item,"%s\t%s",MPlayerName,Text_String(TXT_N_O_D)); + } + playerlist.Add_Item (item, MPlayerTColors[MPlayerColorIdx]); + + who = new NodeNameType; + strcpy(who->Name, MPlayerName); + who->Address = IPXAddressClass(); + who->Player.House = MPlayerHouse; + who->Player.Color = MPlayerColorIdx; + Players.Add (who); + + Send_Join_Queries (game_index, 0, 1); + } else { + + /*.................................................................. + If we've been rejected, clear any messages we may have been typing. + ..................................................................*/ + if (joinstate==JOIN_REJECTED) { + + // + // Remove myself from the player list box + // + item = (char *)(playerlist.Get_Item(0)); + if (item){ + playerlist.Remove_Item(item); + delete [] item; + playerlist.Flag_To_Redraw(); + } + + // + // Remove myself from the Players list + // + if (Players.Count()){ + who = Players[0]; + Players.Delete(0); + delete who; + } + } + } + } + } else + + /*..................................................................... + If a new game is detected, and it's the first game on our list, + automatically send out a player query for that game. + .....................................................................*/ + if (event == EV_NEW_GAME && gamelist.Count()==1) { + gamelist.Set_Selected_Index(0); + game_index = gamelist.Current_Index(); + Send_Join_Queries (game_index, 0, 1); + } else + + /*..................................................................... + If the game options have changed, print them. + .....................................................................*/ + if (event == EV_GAME_OPTIONS) { + parms_received = 1; + display = REDRAW_MESSAGE; + } else + + /*..................................................................... + Draw an incoming message + .....................................................................*/ + if (event == EV_MESSAGE) { + display = REDRAW_MESSAGE; + } else + + /*..................................................................... + A game before the one I've selected is gone, so we have a new index now. + 'game_index' must be kept set to the currently-selected list item, so + we send out queries for the currently-selected game. It's therefore + imperative that we detect any changes to the game list. + If we're joined in a game, we must decrement our game_index to keep + it aligned with the game we're joined to. + .....................................................................*/ + if (event == EV_GAME_SIGNOFF) { + if (joinstate==JOIN_CONFIRMED) { + game_index--; + join_index--; + gamelist.Set_Selected_Index(join_index); + } else { + gamelist.Flag_To_Redraw(); + Clear_Player_List(&playerlist); + game_index = gamelist.Current_Index(); + Send_Join_Queries (game_index, 0, 1); + } + } + + /*--------------------------------------------------------------------- + Service the Ipx connections + ---------------------------------------------------------------------*/ + Ipx.Service(); + + /*--------------------------------------------------------------------- + Clean out the Game List; if an old entry is found: + - Remove it + - Clear the player list + - Send queries for the new selected game, if there is one + ---------------------------------------------------------------------*/ + for (i = 0; i < Games.Count(); i++) { + if (TickCount.Time() - Games[i]->Game.LastTime > 400) { + Games.Delete(Games[i]); + item = (char *)(gamelist.Get_Item (i)); + gamelist.Remove_Item (item); + delete [] item; + if (i <= game_index) { + gamelist.Flag_To_Redraw(); + Clear_Player_List(&playerlist); + game_index = gamelist.Current_Index(); + Send_Join_Queries (game_index, 0, 1); + } + } + } + + /* + ** If we were flagged to start the game and we recognise both players then quit the loop + */ + +char ddkks[128]; +sprintf (ddkks, "C&C95 - Players.Count() = %d\n", Players.Count()); +CCDebugString (ddkks); + if (ready_to_go){ // && Players.Count() == InternetMaxPlayers){ + rc = 0; + process = false; + } + + /*--------------------------------------------------------------------- + Service the sounds & score; GameActive must be false at this point, + so Call_Back() doesn't intercept global messages from me! + ---------------------------------------------------------------------*/ + Call_Back(); + } + + /*------------------------------------------------------------------------ + Establish connections with all other players. + ------------------------------------------------------------------------*/ + if (rc == 0) { + /*..................................................................... + If the other guys are playing a scenario I don't have (sniff), I can't + play. Try to bail gracefully. + .....................................................................*/ + if (ScenarioIdx==-1) { + CCMessageBox().Process (TXT_UNABLE_PLAY_WAAUGH); + + // + // Remove myself from the player list box + // + item = (char *)(playerlist.Get_Item(0)); + playerlist.Remove_Item(item); + delete [] item; + playerlist.Flag_To_Redraw(); + + // + // Remove myself from the Players list + // + who = Players[0]; + Players.Delete(0); + delete who; + + memset (&GPacket, 0, sizeof(GlobalPacketType)); + + GPacket.Command = NET_SIGN_OFF; + strcpy (GPacket.Name, MPlayerName); + + for (i = 0; i < Players.Count(); i++) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 1, + &(Players[i]->Address)); + Ipx.Service(); + } + + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + Ipx.Send_Global_Message (&GPacket, sizeof (GlobalPacketType), + 0, NULL); + + if (IsBridge) { + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + Ipx.Send_Global_Message (&GPacket, sizeof(GlobalPacketType), 0, + &BridgeNet); + } + + while (Ipx.Global_Num_Send() > 0 && Ipx.Service() != 0) ; + + rc = -1; + + } else { + + /*.................................................................. + Set the number of players in this game, and my ID + ..................................................................*/ + MPlayerCount = Players.Count(); + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + /*.................................................................. + Get the scenario number + ..................................................................*/ + Scenario = ScenarioIdx; //PlayerFilenum[ScenarioIdx]; We are passed actual number now from wchat not index from + + /*.................................................................. + Form connections with all other players. Form the IPX Connection ID + from the player's Color and House. This will let us extract any + player's color & house at any time. Fill in 'tmp_id' while we're + doing this. + ..................................................................*/ + for (i = 0; i < Players.Count(); i++) { + + /*............................................................... + Only create the connection if it's not myself! + ...............................................................*/ + if (strcmp (MPlayerName, Players[i]->Name)) { + id = Build_MPlayerID(Players[i]->Player.Color, + Players[i]->Player.House); + + tmp_id[i] = id; + + Ipx.Create_Connection((int)id, Players[i]->Name, &(Players[i]->Address) ); + } else { + tmp_id[i] = MPlayerLocalID; + } + } + + /* + ** Create an additional connection to the VSS + */ + if (UseVirtualSubnetServer){ + IPXAddressClass vss_global_address; + NetNodeType vss_node; + NetNumType vss_net; + memset (vss_net, 1, sizeof (vss_net)); + memset (vss_node, 0, sizeof (vss_node)); + vss_global_address.Set_Address(vss_net, vss_node); + Ipx.Create_Connection( VSS_ID, "VSS", &vss_global_address); + } + + /*.................................................................. + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + ..................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + min_index = 0; + min_id = 0xff; + for (j = 0; j < MPlayerCount; j++) { + if (tmp_id[j] < min_id) { + min_id = tmp_id[j]; + min_index = j; + } + } + MPlayerID[i] = tmp_id[min_index]; + tmp_id[min_index] = 0xff; + } + /*.................................................................. + Fill in the array of player names, including my own. + ..................................................................*/ + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID[i] == MPlayerLocalID) { + strcpy (MPlayerNames[i], MPlayerName); + } else { + strcpy (MPlayerNames[i], Ipx.Connection_Name(MPlayerID[i])); + } + } + } + /*--------------------------------------------------------------------- + Wait a while, polling the IPX service routines, to give our ACK + a chance to get to the other system. If he doesn't get our ACK, he'll + be waiting the whole time we load MIX files. + ---------------------------------------------------------------------*/ + i = MAX(Ipx.Global_Response_Time() * 2, 120); + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < i) { + Ipx.Service(); + } + } + + /*------------------------------------------------------------------------ + Init network timing values, using previous response times as a measure + of what our retry delta & timeout should be. + ------------------------------------------------------------------------*/ + Ipx.Set_Timing (Ipx.Global_Response_Time() + 2, -1, + Ipx.Global_Response_Time() * 4); + + /*------------------------------------------------------------------------ + Clear all lists + ------------------------------------------------------------------------*/ + Clear_Game_List(&gamelist); + Clear_Player_List(&playerlist); + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + return(rc); +} + + + +#endif diff --git a/NOD.BAT b/NOD.BAT new file mode 100644 index 0000000..e8c6d9b --- /dev/null +++ b/NOD.BAT @@ -0,0 +1,5 @@ +@echo off +pushd +cd ..\run +conquer -CD..\cd2\aud1;..\cd2;..\cd2\install %1 %2 %3 %4 %5 +popd diff --git a/NOSEQCON.CPP b/NOSEQCON.CPP new file mode 100644 index 0000000..8da01b8 --- /dev/null +++ b/NOSEQCON.CPP @@ -0,0 +1,690 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\noseqcon.cpv 1.9 16 Oct 1995 16:49:38 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SEQCONN.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 20, 1994 * + * * + * Last Update : April 9, 1995 [BRR] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * NonSequencedConnClass::NonSequencedConnClass -- class constructor * + * NonSequencedConnClass::~NonSequencedConnClass -- class destructor * + * NonSequencedConnClass::Init -- Initializes connection queue to empty * + * NonSequencedConnClass::Send_Packet -- adds a packet to the send queue * + * NonSequencedConnClass::Receive_Packet -- adds packet to receive queue * + * NonSequencedConnClass::Get_Packet -- gets a packet from receive queue * + * NonSequencedConnClass::Service_Send_Queue -- services the send queue * + * NonSequencedConnClass::Service_Receive_Queue -- services recieve queue* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*************************************************************************** + * NonSequencedConnClass::NonSequencedConnClass -- class constructor * + * * + * INPUT: * + * numsend desired # of entries for the send queue * + * numreceive desired # of entries for the recieve queue * + * maxlen max length of an application packet * + * magicnum the packet "magic number" for this connection * + * retry_delta the time to wait between sends * + * max_retries the max # of retries allowed for a packet * + * (-1 means retry forever, based on this parameter) * + * timeout the max amount of time before we give up on a packet * + * (-1 means retry forever, based on this parameter) * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +NonSequencedConnClass::NonSequencedConnClass (int numsend, int numreceive, + int maxlen, unsigned short magicnum, unsigned long retry_delta, + unsigned long max_retries, unsigned long timeout) : + ConnectionClass (maxlen, magicnum, retry_delta, max_retries, timeout) +{ + /*------------------------------------------------------------------------ + Allocate the packet Queue. This will store incoming packets (which will + be placed there by the Connection Manager), and outgoing packets (which + are placed there by this class when it "sends" a packet). + ------------------------------------------------------------------------*/ + Queue = new CommBufferClass (numsend, numreceive, MaxPacketLen); +} + + +/*************************************************************************** + * NonSequencedConnClass::~NonSequencedConnClass -- class destructor * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +NonSequencedConnClass::~NonSequencedConnClass () +{ + delete Queue; +} + + +/*************************************************************************** + * NonSequencedConnClass::Init -- Initializes connection queue to empty * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +void NonSequencedConnClass::Init (void) +{ + NumRecNoAck = 0; + NumRecAck = 0; + NumSendNoAck = 0; + NumSendAck = 0; + + LastSeqID = 0xffffffff; + LastReadID = 0xffffffff; + + Queue->Init(); +} + + +/*************************************************************************** + * NonSequencedConnClass::Send_Packet -- adds a packet to the send queue * + * * + * This routine prefixes the given buffer with a CommHeaderType and * + * queues the resulting packet into the Send Queue. (It's actually the * + * Service() routine that handles the hardware-dependent Send of the data).* + * The packet's MagicNumber, Code, and PacketID are set here. * + * * + * INPUT: * + * buf buffer to send * + * buflen length of buffer * + * ack_req true = ACK is required for this packet; false = isn't * + * * + * OUTPUT: * + * 1 = packet was queue'd OK, 0 = wasn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int NonSequencedConnClass::Send_Packet (void * buf, int buflen, int ack_req) +{ + /*........................................................................ + Set the magic # for the packet + ........................................................................*/ + ((CommHeaderType *)PacketBuf)->MagicNumber = MagicNum; + + /*........................................................................ + Set the packet Code: DATA_ACK if it requires an ACK, NOACK if it doesn't + Set the packet ID to the appropriate counter value. + ........................................................................*/ + if (ack_req) { + ((CommHeaderType *)PacketBuf)->Code = PACKET_DATA_ACK; + ((CommHeaderType *)PacketBuf)->PacketID = NumSendAck; + } else { + ((CommHeaderType *)PacketBuf)->Code = PACKET_DATA_NOACK; + ((CommHeaderType *)PacketBuf)->PacketID = NumSendNoAck; + } + + /*........................................................................ + Now build the packet + ........................................................................*/ + memcpy(PacketBuf + sizeof(CommHeaderType), buf, buflen); + + /*........................................................................ + Add it to the queue. + ........................................................................*/ + if (Queue->Queue_Send(PacketBuf,buflen + sizeof(CommHeaderType))) { + if (ack_req) { +// Smart_Printf( "Packet ack Queued ID %d \n", ((CommHeaderType *)PacketBuf)->PacketID ); + NumSendAck++; + } else { +// Smart_Printf( "Packet noack Queued ID %d \n", ((CommHeaderType *)PacketBuf)->PacketID ); + NumSendNoAck++; + } + return(true); + } else { +// Smart_Printf( "Packet not Queued ID %d \n", ((CommHeaderType *)PacketBuf)->PacketID ); + return(false); + } +} + + +/*************************************************************************** + * NonSequencedConnClass::Receive_Packet -- adds packet to the receive queue * + * * + * INPUT: * + * buf buffer to process (already includes CommHeaderType) * + * buflen length of buffer to process * + * * + * OUTPUT: * + * 1 = packet was processed OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int NonSequencedConnClass::Receive_Packet (void * buf, int buflen) +{ + CommHeaderType *packet; // ptr to packet header + SendQueueType *send_entry; // ptr to send entry header + ReceiveQueueType *rec_entry; // ptr to recv entry header + CommHeaderType *entry_data; // ptr to queue entry data + CommHeaderType ackpacket; // ACK packet to send + int i; + int save_packet = 1; // 0 = this is a resend + int found; + + /* + --------------------------- Check the magic # ---------------------------- + */ + packet = (CommHeaderType *)buf; + if (packet->MagicNumber != MagicNum) { +// Smart_Printf( "Bad Magic Number\n" ); + return(false); + } + + /*------------------------------------------------------------------------ + Handle an incoming ACK + ------------------------------------------------------------------------*/ + if (packet->Code == PACKET_ACK) { + + for (i = 0; i < Queue->Num_Send(); i++) { + /* + ....................... Get queue entry ptr ........................ + */ + send_entry = Queue->Get_Send(i); + /* + ............... If ptr is valid, get ptr to its data ............... + */ + if (send_entry != NULL) { + entry_data = (CommHeaderType *)send_entry->Buffer; + /* + .............. If ACK is for this entry, mark it ................ + */ + if (packet->PacketID==entry_data->PacketID && + entry_data->Code == PACKET_DATA_ACK) { +// Smart_Printf( "Received ACK for %d \n", packet->PacketID ); + send_entry->IsACK = 1; + break; + } + } + } + +//{ +// if (i == Queue->Num_Send() ) { +// Smart_Printf( "Received bad ACK for %d \n", packet->PacketID ); +// } +//} + + return(true); + } + + /*------------------------------------------------------------------------ + Handle an incoming PACKET_DATA_NOACK packet + ------------------------------------------------------------------------*/ + else if (packet->Code == PACKET_DATA_NOACK) { + /*--------------------------------------------------------------------- + If there's only one slot left, don't tie up the queue with this packet + ---------------------------------------------------------------------*/ + if (Queue->Max_Receive() - Queue->Num_Receive() <= 1) { +// Smart_Printf( "Only one slot left don't tie up with DATA NOACK packet %d \n", packet->PacketID ); + return(false); + } + + /*--------------------------------------------------------------------- + Error if we can't queue the packet + ---------------------------------------------------------------------*/ + if (!Queue->Queue_Receive (buf, buflen)) { +// Smart_Printf( "Can't Queue the packet %d \n", packet->PacketID ); + return(false); + } + +// Smart_Printf( "Queued DATA NOACK for %d \n", packet->PacketID ); + NumRecNoAck++; + + return(true); + } + + /*------------------------------------------------------------------------ + Handle an incoming PACKET_DATA_ACK packet + ------------------------------------------------------------------------*/ + else if (packet->Code == PACKET_DATA_ACK) { +// Smart_Printf( "Looking at ID %d, LastSeqID=%d \n", packet->PacketID, LastSeqID ); + /*.................................................................... + If this is a packet requires an ACK, and it's ID is older than our + "oldest" ID, we know it's a resend; send an ACK, but don't queue it + ....................................................................*/ + if (packet->PacketID <= LastSeqID && LastSeqID != 0xffffffff) { +// Smart_Printf( "Older than oldest\n" ); + save_packet = 0; + } + /*.................................................................... + Otherwise, scan the queue for this entry; if it's found, it's a + resend, so don't save it. + ....................................................................*/ + else { + save_packet = 1; + for (i = 0; i < Queue->Num_Receive(); i++) { + rec_entry = Queue->Get_Receive(i); + if (rec_entry) { + entry_data = (CommHeaderType *)rec_entry->Buffer; + /*........................................................... + Packet is found; it's a resend + ...........................................................*/ + if (entry_data->Code == PACKET_DATA_ACK && + entry_data->PacketID == packet->PacketID) { +// Smart_Printf( "It's a resend\n" ); + save_packet = 0; + break; + } + } + } + } /* end of scan for resend */ + + /*--------------------------------------------------------------------- + Queue the packet & update our LastSeqID value. + ---------------------------------------------------------------------*/ + if (save_packet) { + /*------------------------------------------------------------------ + If there's only one slot left, make sure we only put a packet in it if + this packet will let us increment our LastSeqID; otherwise, we'll get + stuck, forever unable to increment LastSeqID. + ------------------------------------------------------------------*/ + if (Queue->Max_Receive() - Queue->Num_Receive() <= 1) { + if (packet->PacketID != (LastSeqID + 1) ) { +// Smart_Printf( "One slot left not what we looking for max=%d,num=%d \n", +// Queue->Max_Receive(), Queue->Num_Receive() ); + return(0); + } + } + + /*------------------------------------------------------------------ + If we can't queue the packet, return; don't send an ACK. + ------------------------------------------------------------------*/ + if (!Queue->Queue_Receive (buf, buflen)) { +// Smart_Printf( "unable to queue packet\n" ); + return(0); + } + + NumRecAck++; + + /*------------------------------------------------------------------ + Update our LastSeqID value if we can. Anything less than LastSeqID + we'll know is a resend. + ------------------------------------------------------------------*/ + if (packet->PacketID == (LastSeqID + 1)) { + LastSeqID = packet->PacketID; + /*............................................................ + Now that we have a new 'LastSeqID', search our Queue to see if + the next ID is there; if so, keep checking for the next one; + break only when the next one isn't found. This forces + LastSeqID to be the largest possible value. + ............................................................*/ + do { + found = 0; + for (i = 0; i < Queue->Num_Receive(); i++) { + + rec_entry = Queue->Get_Receive(i); + + if (rec_entry) { + entry_data = (CommHeaderType *)rec_entry->Buffer; + + /*...................................................... + Entry is found + ......................................................*/ + if (entry_data->Code == PACKET_DATA_ACK && + entry_data->PacketID == (LastSeqID + 1)) { + + LastSeqID = entry_data->PacketID; + found = 1; + break; + } + } + } + } while (found); + } + } /* end of save packet */ + + /*--------------------------------------------------------------------- + Send an ACK, regardless of whether this was a resend or not. + ---------------------------------------------------------------------*/ + ackpacket.MagicNumber = Magic_Num(); + ackpacket.Code = PACKET_ACK; + ackpacket.PacketID = packet->PacketID; +// Smart_Printf( "Sending ACK for %d \n", packet->PacketID ); + Send ((char *)&ackpacket, sizeof(CommHeaderType)); + + return(true); + + } else { +// Smart_Printf( "invalid packet type %d \n", packet->Code ); + } + + return(false); +} + + +/*************************************************************************** + * NonSequencedConnClass::Get_Packet -- gets a packet from receive queue * + * * + * INPUT: * + * buf location to store buffer * + * buflen filled in with length of 'buf' * + * * + * OUTPUT: * + * 1 = packet was read, 0 = wasn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int NonSequencedConnClass::Get_Packet (void * buf, int *buflen) +{ + ReceiveQueueType *rec_entry; // ptr to receive entry header + int packetlen; // size of received packet + CommHeaderType *entry_data; + int i; + + /*------------------------------------------------------------------------ + Ensure that we read the packets in order. LastReadID is the ID of the + last PACKET_DATA_ACK packet we read. + ------------------------------------------------------------------------*/ + for (i = 0; i < Queue->Num_Receive(); i++) { + + rec_entry = Queue->Get_Receive(i); + + /*..................................................................... + Only read this entry if it hasn't been yet + .....................................................................*/ + if (rec_entry && rec_entry->IsRead==0) { + + entry_data = (CommHeaderType *)rec_entry->Buffer; + + /*.................................................................. + If this is a DATA_ACK packet, its ID must be one greater than + the last one we read. + ..................................................................*/ + if ( (entry_data->Code == PACKET_DATA_ACK) && + (entry_data->PacketID == (LastReadID + 1))) { + + LastReadID = entry_data->PacketID; + rec_entry->IsRead = 1; + + packetlen = rec_entry->BufLen - sizeof(CommHeaderType); + if (packetlen > 0) { + memcpy(buf, rec_entry->Buffer + sizeof(CommHeaderType), packetlen); + } + (*buflen) = packetlen; + return(true); + } + /*.................................................................. + If this is a DATA_NOACK packet, who cares what the ID is? + ..................................................................*/ + else if (entry_data->Code == PACKET_DATA_NOACK) { + + rec_entry->IsRead = 1; + + packetlen = rec_entry->BufLen - sizeof(CommHeaderType); + if (packetlen > 0) { + memcpy(buf, rec_entry->Buffer + sizeof(CommHeaderType), packetlen); + } + (*buflen) = packetlen; + return(true); + } + } + } + + return(false); +} + + +/*************************************************************************** + * NonSequencedConnClass::Service_Send_Queue -- services the send queue * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int NonSequencedConnClass::Service_Send_Queue (void) +{ + int i; + int num_entries; + SendQueueType *send_entry; // ptr to send queue entry + CommHeaderType *packet_hdr; // packet header + unsigned long curtime; // current time + int bad_conn = 0; + + /*------------------------------------------------------------------------ + Remove any ACK'd packets from the queue + ------------------------------------------------------------------------*/ + for (i = 0; i < Queue->Num_Send(); i++) { + /* + ------------------------- Get this queue entry ------------------------ + */ + send_entry = Queue->Get_Send(i); + /* + ---------------- If ACK has been received, unqueue it ----------------- + */ + if (send_entry->IsACK) { + /* + ................ Update this queue's response time ................. + */ + packet_hdr = (CommHeaderType *)send_entry->Buffer; + if (packet_hdr->Code == PACKET_DATA_ACK) { + Queue->Add_Delay(Time() - send_entry->FirstTime); + } + /* + ....................... unqueue the packet ......................... + */ + Queue->UnQueue_Send(NULL,NULL,i); + i--; + } + } + + /*------------------------------------------------------------------------ + Loop through all entries in the Send queue. [Re]Send any entries that + need it. + ------------------------------------------------------------------------*/ + num_entries = Queue->Num_Send(); + + for (i = 0; i < num_entries; i++) { + send_entry = Queue->Get_Send(i); + + if (send_entry->IsACK) { + continue; + } + + /*..................................................................... + Only send the message if time has elapsed. (The message's Time + fields are init'd to 0 when a message is queue'd or unqueue'd, so the + first time through, the delta time will appear large.) + .....................................................................*/ + curtime = Time(); + if (curtime - send_entry->LastTime > RetryDelta) { + /* + ......................... Send the message ......................... + */ +#if(0) +{ + packet_hdr = (CommHeaderType *)send_entry->Buffer; + if (send_entry->SendCount) { + if (packet_hdr->Code == PACKET_DATA_NOACK) { +// Smart_Printf( "Resending DATA NOACK for %d \n", packet_hdr->PacketID ); + } else { + if (packet_hdr->Code == PACKET_DATA_ACK) { +// Smart_Printf( "Resending DATA ACK for %d \n", packet_hdr->PacketID ); + } + } + } else { + if (packet_hdr->Code == PACKET_DATA_NOACK) { +// Smart_Printf( "Sending DATA NOACK for %d \n", packet_hdr->PacketID ); + } else { + if (packet_hdr->Code == PACKET_DATA_ACK) { +// Smart_Printf( "Sending DATA ACK for %d \n", packet_hdr->PacketID ); + } + } + } +} +#endif + Send (send_entry->Buffer, send_entry->BufLen); + + /* + ....................... Fill in Time fields ........................ + */ + send_entry->LastTime = curtime; + if (send_entry->SendCount==0) { + send_entry->FirstTime = curtime; + /*............................................................... + If this is the 1st time we're sending this packet, and it doesn't + require an ACK, mark it as ACK'd; then, the next time through, + it will just be removed from the queue. + ...............................................................*/ + packet_hdr = (CommHeaderType *)send_entry->Buffer; + if (packet_hdr->Code == PACKET_DATA_NOACK) { + send_entry->IsACK = 1; + } + } + /* + ......................... Update SendCount ......................... + */ + send_entry->SendCount++; + /*.................................................................. + Perform error detection, based on either MaxRetries or Timeout + ..................................................................*/ + if (MaxRetries != -1 && send_entry->SendCount > MaxRetries) { +// Smart_Printf( "Max Retries!!! %d !!! \n", MaxRetries ); + bad_conn = 1; + } + + if (Timeout != -1 && + (send_entry->LastTime - send_entry->FirstTime) > Timeout) { +// Smart_Printf( "Timed out!!! Time %d, Timeout %d, buflen %d !!! \n", +// (send_entry->LastTime - send_entry->FirstTime), Timeout, +// send_entry->BufLen ); + bad_conn = 1; + } + } + } + + /*------------------------------------------------------------------------ + If the connection is going bad, return an error + ------------------------------------------------------------------------*/ + if (bad_conn) { +// Smart_Printf( "Connection going bad!!! \n" ); + return(false); + } else { + return(true); + } +} + + +/*************************************************************************** + * NonSequencedConnClass::Service_Receive_Queue -- services recieve queue * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int NonSequencedConnClass::Service_Receive_Queue (void) +{ + ReceiveQueueType *rec_entry; // ptr to receive entry header + CommHeaderType *packet_hdr; // packet header + int i; + + /*------------------------------------------------------------------------ + Remove all dead packets. + PACKET_DATA_NOACK: if it's been read, throw it away. + PACKET_DATA_ACK: if it's been read, and its ID is older than LastSeqID, + throw it away. + ------------------------------------------------------------------------*/ + for (i = 0; i < Queue->Num_Receive(); i++) { + rec_entry = Queue->Get_Receive(i); + + if (rec_entry->IsRead) { + packet_hdr = (CommHeaderType *)(rec_entry->Buffer); + + if (packet_hdr->Code == PACKET_DATA_NOACK) { + Queue->UnQueue_Receive(NULL,NULL,i); + i--; + } else { + if (packet_hdr->PacketID < LastSeqID) { + Queue->UnQueue_Receive(NULL,NULL,i); + i--; + } + } + } + } + + return(true); +} + + diff --git a/NOSEQCON.H b/NOSEQCON.H new file mode 100644 index 0000000..9be0fe6 --- /dev/null +++ b/NOSEQCON.H @@ -0,0 +1,129 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\noseqcon.h_v 1.12 16 Oct 1995 16:46:22 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : NOSEQCON.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : April 9, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * * + * This class provides a "Non-Sequenced" ACK/Retry approach to packet * + * transmission. It sends out as many packets as are in the queue, whose * + * resend delta times have expired; and it ACK's any packets its received * + * who haven't been ACK'd yet. Thus, order of delivery is NOT guaranteed; * + * however, the performance is better than the Sequenced approach. * + * * + * A derived class must provide: * + * - Init: Initialization of any hardware-specific values. * + * - Send: a hardware-dependent send routine. * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef NONSEQCONN_H +#define NONSEQCONN_H + + +/* +********************************* Includes ********************************** +*/ +#include "connect.h" +#include "combuf.h" + + +/* +***************************** Class Declaration ***************************** +*/ +class NonSequencedConnClass : public ConnectionClass +{ + /* + ---------------------------- Public Interface ---------------------------- + */ + public: + /*..................................................................... + Constructor/destructor. + .....................................................................*/ + NonSequencedConnClass (int numsend, int numrecieve, int maxlen, + unsigned short magicnum, unsigned long retry_delta, + unsigned long max_retries, unsigned long timeout); + virtual ~NonSequencedConnClass (); + + /*..................................................................... + Initialization. + .....................................................................*/ + virtual void Init (void); + + /*..................................................................... + Send/Receive routines. + .....................................................................*/ + virtual int Send_Packet (void * buf, int buflen, int ack_req); + virtual int Receive_Packet (void * buf, int buflen); + virtual int Get_Packet (void * buf, int *buflen); + + /*..................................................................... + The packet "queue"; this non-sequenced version isn't really much of + a queue, but more of a repository. + .....................................................................*/ + CommBufferClass *Queue; + + /* + -------------------------- Protected Interface --------------------------- + */ + protected: + /*..................................................................... + Routines to service the Send & Receive queues. + .....................................................................*/ + virtual int Service_Send_Queue (void); + virtual int Service_Receive_Queue (void); + + /*..................................................................... + Running totals of # of packets we send & receive which require an ACK, + and those that don't. + .....................................................................*/ + unsigned long NumRecNoAck; + unsigned long NumRecAck; + unsigned long NumSendNoAck; + unsigned long NumSendAck; + + /*..................................................................... + This is the ID of the last consecutively-received packet; anything older + than this, we know is a resend. Anything newer than this MUST be lying + around in the Queue for us to detect it as a resend. + .....................................................................*/ + unsigned long LastSeqID; + + /*..................................................................... + This is the ID of the PACKET_DATA_ACK packet we read last; it ensures + that the application reads that type of packet in order. + .....................................................................*/ + unsigned long LastReadID; +}; + +#endif + + diff --git a/NULLCONN.CPP b/NULLCONN.CPP new file mode 100644 index 0000000..bd75959 --- /dev/null +++ b/NULLCONN.CPP @@ -0,0 +1,265 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\nullconn.cpv 1.10 16 Oct 1995 16:51:36 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : NULLCONN.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : April 5, 1995 * + * * + * Last Update : April 20, 1995 [DRD] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * NullModemConnClass::NullModemConnClass -- class constructor * + * NullModemConnClass::~NullModemConnClass -- class destructor * + * NullModemConnClass::Init -- hardware-dependent initialization * + * NullModemConnClass::Send -- hardware-dependent packet sending * + * NullModemConnClass::Compute_CRC -- computes CRC for given buffer * + * NullModemConnClass::Packet_Overhead_Size -- number of extra bytes * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "wincomm.h" +#include "tcpip.h" + +/*************************************************************************** + * NullModemConnClass::NullModemConnClass -- class constructor * + * * + * INPUT: * + * numsend desired # send queue entries * + * numreceive desired # send receive entries * + * maxlen max length of application's packets * + * magicnum application-defined magic # for the packets * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +NullModemConnClass::NullModemConnClass (int numsend, int numreceive, + int maxlen, unsigned short magicnum) : + NonSequencedConnClass (numsend, numreceive, maxlen, magicnum, + 60, // Retry Delta Time + -1, // Max Retries (-1 means ignore this timeout parameter) + 1200) // Timeout: 20 seconds +{ + /*------------------------------------------------------------------------ + Pre-set the port value to NULL, so Send won't send until we've been Init'd + ------------------------------------------------------------------------*/ + PortHandle = NULL; + + /*------------------------------------------------------------------------ + Allocate the Send Buffer; the parent constructor has set MaxPacketLen, + so we can use it in our computation. + ------------------------------------------------------------------------*/ +// SendBuf = new char [MaxPacketLen + sizeof(int) * 3]; + SendBuf = new char [ Actual_Max_Packet() ]; + +} /* end of NullModemConnClass */ + + +/*************************************************************************** + * NullModemConnClass::~NullModemConnClass -- class destructor * + * * + * INPUT: * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +NullModemConnClass::~NullModemConnClass () +{ + delete [] SendBuf; + +} /* end of ~NullModemConnClass */ + + +/*************************************************************************** + * NullModemConnClass::Init -- hardware-dependent initialization * + * * + * INPUT: * + * port GreenLeaf port handle * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +void NullModemConnClass::Init (HANDLE port_handle) +{ + NonSequencedConnClass::Init(); + PortHandle = port_handle; + +} /* end of Init */ + + +/*************************************************************************** + * NullModemConnClass::Send -- hardware-dependent packet sending * + * * + * INPUT: * + * port GreenLeaf port handle * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * 1 = OK, 0 = error * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int NullModemConnClass::Send (char *buf, int buflen) +{ + //int status; + int *ibuf; + SerialHeaderType *header; + unsigned long sendlen; + + + /*------------------------------------------------------------------------ + Error if we haven't been properly initialized + ------------------------------------------------------------------------*/ + if ( PortHandle == NULL ) + return(false); + + /*------------------------------------------------------------------------ + Package the data into the Send Buffer + ------------------------------------------------------------------------*/ + header = (SerialHeaderType *) SendBuf; + header->MagicNumber = PACKET_SERIAL_START; + header->Length = (short) buflen; + header->MagicNumber2 = PACKET_SERIAL_VERIFY; + + sendlen = sizeof( SerialHeaderType ); + memcpy (SendBuf + sendlen, buf, buflen); + sendlen += buflen; + ibuf = (int *)(SendBuf + sendlen); + *ibuf = Compute_CRC( buf, buflen ); + sendlen += sizeof( int ); + + *(SendBuf + sendlen) = '\r'; + sendlen += 1; + + /*------------------------------------------------------------------------ + Send the data + ------------------------------------------------------------------------*/ + //status = +#ifdef FORCE_WINSOCK + if (Winsock.Get_Connected() || GameToPlay == GAME_INTERNET){ + Winsock.Write(SendBuf, (int)sendlen); + }else{ + SerialPort->Write_To_Serial_Port((unsigned char *)SendBuf, (int)sendlen ); + } +#else + SerialPort->Write_To_Serial_Port((unsigned char *)SendBuf, (int)sendlen ); +#endif //WINSOCK + + //if ( status == ASSUCCESS ) { + return(true); + //} else { +// Smart_Printf( "Write Buffer status %d, Port->status %d, sendlen %d \n", status, Port->status, sendlen ); + // return(false); + //} +} + + +/*************************************************************************** + * NullModemConnClass::Compute_CRC -- computes CRC for given buffer * + * * + * INPUT: * + * buf buffer to compute CRC for * + * buflen length of buffer in bytes * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int NullModemConnClass::Compute_CRC (char *buf, int buflen) +{ + unsigned int sum, hibit; + + sum = 0; + for (int i = 0; i < buflen; i++) { + if ( sum & 0x80000000 ) { // check hi bit to rotate into low bit + hibit = 1; + } else { + hibit = 0; + } + + sum <<= 1; + sum += (hibit + (unsigned char)buf[i]); + } + + return((int)sum); +} + + +/*************************************************************************** + * NullModemConnClass::Packet_Overhead_Size -- number of extra bytes * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * number of bytes used for communications only. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/20/1995 DRD : Created. * + *=========================================================================*/ +int NullModemConnClass::Packet_Overhead_Size ( void ) +{ + // + // short for Null Modem Magic Number + // short for Null Modem length of packet + // int for Null Modem CRC check + // CommHeaderType for Queued packets + // + + return( (PACKET_SERIAL_OVERHEAD_SIZE + sizeof(CommHeaderType)) ); + +} /* end of Packet_Overhead_Size */ + diff --git a/NULLCONN.H b/NULLCONN.H new file mode 100644 index 0000000..5e342f4 --- /dev/null +++ b/NULLCONN.H @@ -0,0 +1,140 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\nullconn.h_v 1.12 16 Oct 1995 16:45:54 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : NULLCONN.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : April 3, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * * + * This is the Connection Class for a NULL-Modem connection. It inherits * + * a Queue, PacketBuf, timeout variables from ConnectionClass. It * + * inherits its Send_/Receive_/Get_Packet functions, and the non-sequenced * + * ACK/Retry logic in Service_Send_Queue & Service_Recieve_Queue from * + * NonSequencedConnClass. * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef NULLCONN_H +#define NULLCONN_H + + +/* +********************************* Includes ********************************** +*/ +#include "noseqcon.h" +#include "commlib.h" + +/* +********************************** Defines ********************************** +*/ +#define PACKET_SERIAL_START 0xDABD +#define PACKET_SERIAL_VERIFY 0xDEAF + +#define PACKET_SERIAL_OVERHEAD_SIZE (sizeof( SerialHeaderType ) + sizeof( SerialCRCType )) + +typedef struct { + unsigned short MagicNumber; + unsigned short Length; + unsigned short MagicNumber2; +} SerialHeaderType; + +typedef struct { + int SerialCRC; +} SerialCRCType; + + +/* +***************************** Class Declaration ***************************** +*/ +class NullModemConnClass : public NonSequencedConnClass +{ + /* + ---------------------------- Public Interface ---------------------------- + */ + public: + /*..................................................................... + Constructor/destructor. + .....................................................................*/ + NullModemConnClass (int numsend, int numrecieve, int maxlen, + unsigned short magicnum); + virtual ~NullModemConnClass (); + + /*..................................................................... + Initialization. + .....................................................................*/ + void Init (HANDLE port_handle); + + /*..................................................................... + Utility routines. + .....................................................................*/ + unsigned long Actual_Max_Packet (void) { return (MaxPacketLen + (sizeof(SerialHeaderType)) + sizeof(int) + sizeof (char)); } + + /*..................................................................... + This routine computes a CRC value for the given buffer. + .....................................................................*/ + static int Compute_CRC(char *buf, int buflen); + + /*..................................................................... + This routine returns the number of bytes extra added the packet + for communication. + .....................................................................*/ + static int Packet_Overhead_Size( void ); + + /* + --------------------------- Private Interface ---------------------------- + */ + protected: + /*..................................................................... + This routine actually performs a hardware-dependent data send. + .....................................................................*/ + int Send (char *buf, int buflen); + + /*..................................................................... + This is the PORT value used by the GreenLeaf calls. + .....................................................................*/ + HANDLE PortHandle; + PORT *Port; + + /*..................................................................... + This buffer is a staging area for data sent out; it includes the + packet sent by the parent class (which includes the application's + packet, plus the CommHeaderType header), plus: + - 2-byte buffer start ID + - 2-byte length + - 4-byte CRC value (at the end of the buffer) + This is the actual packet that gets sent across the serial line. + .....................................................................*/ + char *SendBuf; +}; + +#endif + +/************************** end of nullconn.h ******************************/ + diff --git a/NULLDLG.CPP b/NULLDLG.CPP new file mode 100644 index 0000000..637b4b0 --- /dev/null +++ b/NULLDLG.CPP @@ -0,0 +1,8178 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\nulldlg.cpv 1.9 16 Oct 1995 16:52:12 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : NULLDLG.CPP * + * * + * Programmer : Bill R. Randolph * + * * + * Start Date : 04/29/95 * + * * + * Last Update : April 29, 1995 [BRR] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * Init_Null_Modem -- Initializes Null Modem communications * + * Shutdown_Modem -- Shuts down modem/null-modem communications * + * Test_Null_Modem -- Null-Modem test routine * + * Reconnect_Null_Modem -- allows user to reconnect * + * Destroy_Null_Connection -- destroys the given connection * + * Select_Serial_Dialog -- Serial Communications menu dialog * + * Com_Settings_Dialog -- Lets user select serial port settings * + * Com_Scenario_Dialog -- Serial game scenario selection dialog * + * Phone_Dialog -- Lets user edit phone directory & dial * + * Build_InitString_Listbox -- [re]builds the initstring entry listbox * + * Init_String_Compare -- for qsort * + * Build_Phone_Listbox -- [re]builds the phone entry listbox * + * Phone_Compare -- for qsort * + * Edit_Phone_Dialog -- lets user edit a phone book entry * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#include "function.h" +#include "wincomm.h" +#include "modemreg.h" +#include "tcpip.h" + +ModemRegistryEntryClass *ModemRegistry = NULL; //Ptr to modem registry data + +// +// how much time (ticks) to go by before thinking other system +// is not responding. +// +#define PACKET_SENDING_TIMEOUT 1800 +#define PACKET_CANCEL_TIMEOUT 900 + +// +// how much time (ticks) to go by before sending another packet +// of game options or serial connect. +// +#define PACKET_RETRANS_TIME 30 +#define PACKET_REDRAW_TIME 60 + + +static int Reconnect_Null_Modem( void ); +static int Com_Settings_Dialog( SerialSettingsType *settings ); +static int Phone_Dialog (void); +static void Build_Init_String_Listbox (ListClass *list, EditClass *edit, char *buf, int *index); +static int Init_String_Compare (const void *p1, const void *p2); +static void Build_Phone_Listbox (ListClass *list, EditClass *edit, char *buf); +static int Phone_Compare (const void *p1, const void *p2); +static int Edit_Phone_Dialog (PhoneEntryClass *phone); +static bool Dial_Modem( SerialSettingsType *settings, bool reconnect ); +static bool Answer_Modem( SerialSettingsType *settings, bool reconnect ); +static void Modem_Echo( char c ); + +void Smart_Printf( char *format, ... ); +void Hex_Dump_Data( char *buffer, int length ); +void itoh( int i, char *s); + + +static SerialPacketType SendPacket; +static SerialPacketType ReceivePacket; +char TheirName[MPLAYER_NAME_MAX]; +unsigned char TheirColor; +HousesType TheirHouse; +unsigned char TheirID; +static char DialString[ CWAITSTRBUF_MAX + PhoneEntryClass::PHONE_MAX_NUM - 1 ]; +static SerialSettingsType *DialSettings; + +#define SHOW_MONO 0 + +/*************************************************************************** + * Init_Null_Modem -- Initializes Null Modem communications * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = OK, false = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +int Init_Null_Modem( SerialSettingsType *settings ) +{ + + if ( NullModem.Init( settings->Port, settings->IRQ, + settings->ModemName, + settings->Baud, 0, 8, 1, + settings->HardwareFlowControl ) ) { + + + return(true); + } else { + return(false); + } +} + + +/*************************************************************************** + * Shutdown_Modem -- Shuts down modem/null-modem communications * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +void Shutdown_Modem( void ) +{ + if (!PlaybackGame) { + if (GameToPlay == GAME_MODEM) { + NullModem.Hangup_Modem(); + } + } + + // + // close port + // + NullModem.Shutdown(); +} + + +/*************************************************************************** + * Modem_Signoff -- sends EXIT event * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/03/1995 DRD : Created. * + *=========================================================================*/ +void Modem_Signoff( void ) +{ + unsigned long starttime; + EventClass event; + + if (!PlaybackGame) { + /*------------------------------------------------------------------------ + Send a sign-off packet + ------------------------------------------------------------------------*/ + event.Type = EventClass::EXIT; + NullModem.Send_Message (&event,sizeof(EventClass),0); + NullModem.Send_Message (&event,sizeof(EventClass),0); + + starttime = TickCount.Time(); + while( (TickCount.Time() - starttime) < 30) { + NullModem.Service(); + } + } +} + + +/*************************************************************************** + * Test_Null_Modem -- Null-Modem test routine * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = failure to connect; 1 = I'm the game owner, 2 = I'm not * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +int Test_Null_Modem( void ) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + }; + + /*........................................................................ + Dialog variables + ........................................................................*/ + bool process = true; // process while true + KeyNumType input; + + int retval; + unsigned long starttime; + int packetlen; + + int x,y,width,height; // dialog dimensions + char buffer[80*3]; + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + /* + ** Determine the dimensions of the text to be used for the dialog box. + ** These dimensions will control how the dialog box looks. + */ + strcpy( buffer, Text_String( TXT_WAITING_CONNECT ) ); + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + Format_Window_String(buffer, 200*factor, width, height); + + width = MAX(width, 50*factor); + width += 40*factor; + height += 60*factor; + + x = (320*factor - width) / 2; + y = (200*factor - height) / 2; + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + x + ((width - (String_Pixel_Width( Text_String( TXT_CANCEL ) ) + 8 *factor)) >> 1), + y + height - (FontHeight + FontYSpacing + 2*factor) - 5*factor); + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + process = true; + + /* + ............................ Create the list ............................. + */ + commands = &cancelbtn; + + commands->Flag_List_To_Redraw(); + + /* + ............................ Draw the dialog ............................. + */ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + + Dialog_Box(x, y, width, height); + Draw_Caption(TXT_NONE, x, y, width); + + Fancy_Text_Print(buffer, x + 20*factor, y + 25*factor, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + commands->Draw_All(); + while (Get_Mouse_State() > 0) Show_Mouse(); + + /* + ** This is supposed to be a direct connection so hang up any modem on this port + ** just to annoy British Telecom + */ + /* + ** Go into break mode + */ + SetCommBreak(SerialPort->Get_Port_Handle()); + + /* + ** Send hangup command + */ + SerialPort->Write_To_Serial_Port ((unsigned char*)"ATH\r", strlen("ATH\r")); + CountDownTimerClass time; + time.Set(2*60); + while (time.Time()){} + + /* + ** Back out of break mode + */ + ClearCommBreak(SerialPort->Get_Port_Handle()); + + /* + ** Drop DTR as well - just in case the modem still hasnt got the message + */ + EscapeCommFunction(SerialPort->Get_Port_Handle(), CLRDTR); + + + /*------------------------------------------------------------------------ + Check for a packet. If we detect one, the other system has already been + started. Wait 1/2 sec for him to receive my ACK, then exit with success. + Note: The initial time must be a little longer than the resend delay. + Just in case we just missed the packet. + ------------------------------------------------------------------------*/ + starttime = TickCount.Time(); + while ( TickCount.Time() - starttime < 80) { + NullModem.Service(); + if ( NullModem.Get_Message( &ReceivePacket, &packetlen ) > 0) { + if (ReceivePacket.Command == SERIAL_CONNECT) { + //Smart_Printf( "Received SERIAL_CONNECT %d, ID %d \n", ReceivePacket.Seed, ReceivePacket.ID ); + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < 30) + NullModem.Service(); + process = false; + retval = 2; + break; + } + } + } + + /*------------------------------------------------------------------------ + Send a packet across. As long as Num_Send() is non-zero, the other system + hasn't received it yet. + ------------------------------------------------------------------------*/ + if (process) { + memset (&SendPacket, 0, sizeof(SerialPacketType)); + SendPacket.Command = SERIAL_CONNECT; + // + // put time from start of game for determining the host in case of tie. + // + SendPacket.Seed = TickCount.Time(); + SendPacket.ID = (int) buffer; // address of buffer for more uniqueness. + + //Smart_Printf( "Sending SERIAL_CONNECT %d, ID %d \n", SendPacket.Seed, SendPacket.ID ); + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < 80) { + NullModem.Service(); + if (NullModem.Get_Message (&ReceivePacket, &packetlen) > 0) { + if (ReceivePacket.Command == SERIAL_CONNECT) { + //Smart_Printf( "Received2 SERIAL_CONNECT %d, ID %d \n", ReceivePacket.Seed, ReceivePacket.ID ); + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < 30) + NullModem.Service(); + + // + // whoever has the highest time is the host + // + if (ReceivePacket.Seed > SendPacket.Seed) { + process = false; + retval = 2; + } else { + if (ReceivePacket.Seed == SendPacket.Seed) { + if (ReceivePacket.ID > SendPacket.ID) { + process = false; + retval = 2; + } else + // + // if they are equal then it's a loopback cable or a modem + // + if (ReceivePacket.ID == SendPacket.ID) { + process = false; + retval = 3; + } + } + } + + break; + } + } + } + } + + starttime = TickCount.Time(); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + commands->Draw_All(); + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + //Smart_Printf( "Canceled waiting for SERIAL_CONNECT\n" ); + retval = 0; + process = false; + break; + + default: + break; + } + /*..................................................................... + Service the connection. + .....................................................................*/ + NullModem.Service(); + if (NullModem.Num_Send() == 0) { + //Smart_Printf( "No more messages to send.\n" ); + if (NullModem.Get_Message (&ReceivePacket, &packetlen) > 0) { + if (ReceivePacket.Command == SERIAL_CONNECT) { + //Smart_Printf( "Received3 SERIAL_CONNECT %d, ID %d \n", ReceivePacket.Seed, ReceivePacket.ID ); + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < 30) + NullModem.Service(); + + // + // whoever has the highest time is the host + // + if (ReceivePacket.Seed > SendPacket.Seed) { + process = false; + retval = 2; + } else { + + if (ReceivePacket.Seed == SendPacket.Seed) { + if (ReceivePacket.ID > SendPacket.ID) { + process = false; + retval = 2; + } else { + + // + // if they are equal then it's a loopback cable or a modem + // + if (ReceivePacket.ID == SendPacket.ID) { + process = false; + retval = 3; + } + } + } + } + + } else { + retval = 0; + process = false; + } + } else { + retval = 1; + process = false; + } + } + + if (TickCount.Time() - starttime > 3600) { // only wait 1 minute + retval = 0; + process = false; + } + } /* end of while */ + + return( retval ); + +} + + +/*************************************************************************** + * Reconnect_Modem -- allows user to reconnect * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = failure to connect; 1 = connect OK * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +int Reconnect_Modem( void ) +{ + int status; + int modemstatus; + + + switch (ModemGameToPlay) { + case (MODEM_NULL_HOST): + case (MODEM_NULL_JOIN): + status = Reconnect_Null_Modem(); + break; + + case (MODEM_DIALER): + modemstatus = NullModem.Get_Modem_Status(); + if ( (modemstatus & CD_SET) ) { + //Smart_Printf( "Dial Modem connection error! Attempting reconnect....\n" ); + status = Reconnect_Null_Modem(); + } else { + status = Dial_Modem( DialSettings, true ); + } + break; + + case (MODEM_ANSWERER): + modemstatus = NullModem.Get_Modem_Status(); + if ( (modemstatus & CD_SET) ) { + //Smart_Printf( "Answer Modem connection error! Attempting reconnect....\n" ); + status = Reconnect_Null_Modem(); + } else { + status = Answer_Modem( DialSettings, true ); + } + break; + } + + return( status ); +} + + +/*************************************************************************** + * Reconnect_Null_Modem -- allows user to reconnect * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = failure to connect; 1 = connect OK * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +static int Reconnect_Null_Modem( void ) +{ + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + }; + + /*........................................................................ + Dialog variables + ........................................................................*/ + bool process = true; // process while true + KeyNumType input; + + int retval; + unsigned long starttime; + unsigned long lastmsgtime; + int packetlen; + + int x,y,width,height; // dialog dimensions + char buffer[80*3]; + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + + /* + ** Determine the dimensions of the text to be used for the dialog box. + ** These dimensions will control how the dialog box looks. + */ + strcpy( buffer, Text_String( TXT_NULL_CONNERR_CHECK_CABLES ) ); + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + Format_Window_String(buffer, 200, width, height); + + width = MAX(width, 50); + width += 40; + height += 60; + + x = (320 - width) / 2; + y = (200 - height) / 2; + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + x + ((width - (String_Pixel_Width( Text_String( TXT_CANCEL ) ) + 8)) >> 1), + y + height - (FontHeight + FontYSpacing + 2) - 5); + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + process = true; + + /* + ............................ Create the list ............................. + */ + commands = &cancelbtn; + + commands->Flag_List_To_Redraw(); + + /* + ............................ Draw the dialog ............................. + */ + Hide_Mouse(); + + Dialog_Box(x, y, width, height); + Draw_Caption(TXT_NONE, x, y, width); + + Fancy_Text_Print(buffer, x + 20, y + 25, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + commands->Draw_All(); + Show_Mouse(); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + starttime = lastmsgtime = TickCount.Time(); + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + commands->Draw_All(); + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + retval = false; + process = false; + break; + + default: + break; + } + /*..................................................................... + Service the connection. + .....................................................................*/ + NullModem.Service(); + + /*..................................................................... + Resend our message if it's time + .....................................................................*/ + if (TickCount.Time() - starttime > PACKET_RETRANS_TIME) { + starttime = TickCount.Time(); + SendPacket.Command = SERIAL_CONNECT; + SendPacket.ID = MPlayerLocalID; + //Smart_Printf( "Sending a SERIAL_CONNECT packet !!!!!!!!\n" ); + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 0); + } + + /*..................................................................... + Check for an incoming message + .....................................................................*/ + if (NullModem.Get_Message (&ReceivePacket, &packetlen) > 0) { + + lastmsgtime = TickCount.Time(); + + if (ReceivePacket.Command == SERIAL_CONNECT) { + //Smart_Printf( "Received a SERIAL_CONNECT packet !!!!!!!!\n" ); + + // are we getting our own packets back?? + + if (ReceivePacket.ID == MPlayerLocalID) { + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + retval = false; + process = false; + break; + } + + /*............................................................... + OK, we got our message; now we have to make certain the other + guy gets his, so send him one with an ACK required. + ...............................................................*/ + SendPacket.Command = SERIAL_CONNECT; + SendPacket.ID = MPlayerLocalID; + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < 60) + NullModem.Service(); + retval = true; + process = false; + } + } + + // + // timeout if we do not get any packets + // + if (TickCount.Time() - lastmsgtime > PACKET_CANCEL_TIMEOUT) { + retval = false; + process = false; + } + + } /* end of while */ + + return( retval ); + +} + + +/*********************************************************************************************** + * Destroy_Null_Connection -- destroys the given connection * + * * + * Call this routine when a connection goes bad, or another player signs off. * + * * + * INPUT: * + * id connection ID to destroy * + * error 0 = user signed off; 1 = connection error * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/31/1995 DRD : Created. * + *=============================================================================================*/ +void Destroy_Null_Connection(int id, int error) +{ + int i,j,idx; + HousesType house; + HouseClass *housep; + char txt[80]; + + + if ( MPlayerCount == 1 ) { + return; + } + + // find index for id + + idx = -1; + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID[i] == (unsigned char)id) { + idx = i; + break; + } + } + + if (idx == -1) { + return; + } + + /*------------------------------------------------------------------------ + Create a message to display to the user + ------------------------------------------------------------------------*/ + txt[0] = '\0'; + if (error == 1) { + sprintf(txt,Text_String(TXT_CONNECTION_LOST), MPlayerNames[idx] ); + } + else if (error == 0) { + sprintf(txt,Text_String(TXT_LEFT_GAME), MPlayerNames[idx] ); + } + else if (error == -1) { + NullModem.Delete_Connection(); + } + + if (strlen(txt)) { + Messages.Add_Message (txt, + MPlayerTColors[MPlayerID_To_ColorIndex((unsigned char)id)], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, 0, 0); + Map.Flag_To_Redraw(false); + } + + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID[i] == (unsigned char)id) { + /*.................................................................. + Turn the player's house over to the computer's AI + ..................................................................*/ + house = MPlayerHouses[i]; + housep = HouseClass::As_Pointer (house); + housep->IsHuman = false; + + /*.................................................................. + Move arrays back by one + ..................................................................*/ + for (j = i; j < MPlayerCount - 1; j++) { + MPlayerID[j] = MPlayerID[j + 1]; + MPlayerHouses[j] = MPlayerHouses[j + 1]; + strcpy (MPlayerNames[j], MPlayerNames[j+1]); + TheirProcessTime[j] = TheirProcessTime[j+1]; + } + } + } + + MPlayerCount--; + + /*------------------------------------------------------------------------ + If we're the last player left, tell the user. + ------------------------------------------------------------------------*/ + if (MPlayerCount == 1) { + sprintf(txt,"%s",Text_String(TXT_JUST_YOU_AND_ME)); + Messages.Add_Message (txt, + MPlayerTColors[MPlayerID_To_ColorIndex((unsigned char)id)], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, 0, 0); + Map.Flag_To_Redraw(false); + } + +} /* end of Destroy_Null_Connection */ + + +/*************************************************************************** + * Select_Serial_Dialog -- Serial Communications menu dialog * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * GAME_MODEM user wants to play a modem game * + * GAME_NULL_MODEM user wants to play a null-modem game * + * GAME_NORMAL user hit Cancel * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +GameType Select_Serial_Dialog( void ) +{ + int rc; +// int value, i; + int com = -1, baud = -1; + int error = 0; + + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 160 *factor; // dialog width + int d_dialog_h = 94 *factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord +// D_DIALOG_Y = ((200 - D_DIALOG_H) / 2), // dialog y-coord + int d_dialog_y = ((136*factor - d_dialog_h) / 2); // dialog y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 11*factor; // ht of 6-pt text + int d_margin = 7; // margin width/height + + int d_dial_w = 90 *factor; + int d_dial_h = 9 *factor; + int d_dial_x = d_dialog_cx - d_dial_w / 2; + int d_dial_y = d_dialog_y + d_margin + d_txt6_h + d_margin; + + int d_answer_w = 90 *factor; + int d_answer_h = 9 *factor; + int d_answer_x = d_dialog_cx - d_answer_w / 2; + int d_answer_y = d_dial_y + d_dial_h + 2; + + int d_nullmodem_w = 90 *factor; + int d_nullmodem_h = 9 *factor; + int d_nullmodem_x = d_dialog_cx - d_nullmodem_w / 2; + int d_nullmodem_y = d_answer_y + d_answer_h + 2; + + int d_settings_w = 90 *factor; + int d_settings_h = 9 *factor; + int d_settings_x = d_dialog_cx - d_settings_w / 2; + int d_settings_y = d_nullmodem_y + d_nullmodem_h + 2; + +#if (GERMAN | FRENCH) + int d_cancel_w = 50 *factor; +#else + int d_cancel_w = 40 *factor; +#endif + int d_cancel_h = 9 *factor; + int d_cancel_x = d_dialog_cx - d_cancel_w / 2; + int d_cancel_y = d_settings_y + d_settings_h + d_margin; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_DIAL = 100, + BUTTON_ANSWER, + BUTTON_NULLMODEM, + BUTTON_SETTINGS, + BUTTON_CANCEL, + + NUM_OF_BUTTONS = 5, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + bool process = true; // process while true + KeyNumType input; + char namebuf[MPLAYER_NAME_MAX] = {0}; // buffer for player's name + int tabs[] = {77 * factor}; // tabs for player list box + GameType retval; // return value + + int selection; + bool pressed; + int curbutton; + TextButtonClass *buttons[NUM_OF_BUTTONS]; + + SerialSettingsType *settings; + bool selectsettings = false; + + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + TextButtonClass dialbtn(BUTTON_DIAL, TXT_DIAL_MODEM, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_dial_x, d_dial_y, d_dial_w, d_dial_h); + + TextButtonClass answerbtn(BUTTON_ANSWER, TXT_ANSWER_MODEM, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_answer_x, d_answer_y, d_answer_w, d_answer_h); + + TextButtonClass nullmodembtn(BUTTON_NULLMODEM, TXT_NULL_MODEM, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_nullmodem_x, d_nullmodem_y, d_nullmodem_w, d_nullmodem_h); + + TextButtonClass settingsbtn(BUTTON_SETTINGS, TXT_SETTINGS, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_settings_x, d_settings_y, d_settings_w, d_settings_h); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_cancel_x, d_cancel_y); +//#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +//#endif + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + + /*........................................................................ + Read the CC.INI file to extract default serial settings, scenario numbers + & descriptions, and the phone list. + ........................................................................*/ + Read_MultiPlayer_Settings (); + + if (SerialDefaults.Port == 0 || + SerialDefaults.IRQ == -1 || + SerialDefaults.Baud == -1) { + selectsettings = true; + } else { + if ( NullModem.Detect_Port( &SerialDefaults ) != PORT_VALID ) { + selectsettings = true; + } + } + + /* + ............................ Create the list ............................. + */ + commands = &dialbtn; + answerbtn.Add_Tail(*commands); + nullmodembtn.Add_Tail(*commands); + settingsbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + + /* + ......................... Fill array of button ptrs ...................... + */ + curbutton = 0; + buttons[0] = &dialbtn; + buttons[1] = &answerbtn; + buttons[2] = &nullmodembtn; + buttons[3] = &settingsbtn; + buttons[4] = &cancelbtn; + buttons[curbutton]->Turn_On(); + + Keyboard::Clear(); + + Fancy_Text_Print(TXT_NONE, 0, 0, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + +Debug_Smart_Print = true; + + MPlayerLocalID = 0xff; // set to invalid value + + /* + -------------------------- Main Processing Loop -------------------------- + */ + display = REDRAW_ALL; + process = true; + pressed = false; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + /* + ..................... Refresh the backdrop ...................... + */ + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + /* + ..................... Draw the background ....................... + */ + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + /* + ..................... Redraw the buttons ....................... + */ + commands->Draw_All(); + /* + ....................... Draw the labels ......................... + */ + Draw_Caption (TXT_SELECT_SERIAL_GAME, d_dialog_x, d_dialog_y, d_dialog_w); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (BUTTON_DIAL | KN_BUTTON): + selection = BUTTON_DIAL; + pressed = true; + break; + + case (BUTTON_ANSWER | KN_BUTTON): + selection = BUTTON_ANSWER; + pressed = true; + break; + + case (BUTTON_NULLMODEM | KN_BUTTON): + selection = BUTTON_NULLMODEM; + pressed = true; + break; + + case (BUTTON_SETTINGS | KN_BUTTON): + selection = BUTTON_SETTINGS; + pressed = true; + break; + + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + selection = BUTTON_CANCEL; + pressed = true; + break; + + case (KN_UP): + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + curbutton--; + if (curbutton < 0) + curbutton = (NUM_OF_BUTTONS - 1); + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + break; + + case (KN_DOWN): + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + curbutton++; + if (curbutton > (NUM_OF_BUTTONS - 1) ) + curbutton = 0; + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + break; + + case (KN_RETURN): + selection = curbutton + BUTTON_DIAL; + pressed = true; + break; + + default: + break; + } + + if (pressed) { + // + // to make sure the selection is correct in case they used the mouse + // + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + curbutton = selection - BUTTON_DIAL; + buttons[curbutton]->Turn_On(); + buttons[curbutton]->IsPressed = true; + buttons[curbutton]->Draw_Me(true); + + switch (selection) { + case (BUTTON_DIAL): + + if (selectsettings) { + CCMessageBox().Process(TXT_SELECT_SETTINGS); + } + + /* + ** Remote-connect + */ + else if ( Phone_Dialog() ) { + if (PhoneBook[CurPhoneIdx]->Settings.Port == 0) { + settings = &SerialDefaults; + } else { + settings = &(PhoneBook[CurPhoneIdx]->Settings); + } + + if (SerialPort){ + delete SerialPort; + } + SerialPort = new WinModemClass; + + if ( Init_Null_Modem( settings ) ) { + + if (settings->CallWaitStringIndex == CALL_WAIT_CUSTOM) { + strcpy( DialString, settings->CallWaitString ); + } else { + strcpy( DialString, + CallWaitStrings[ settings->CallWaitStringIndex ] ); + } + strcat( DialString, PhoneBook[ CurPhoneIdx ]->Number ); + + if ( Dial_Modem( settings, false ) ) { + ModemGameToPlay = MODEM_DIALER; + if ( Com_Scenario_Dialog() ) { + retval = GAME_MODEM; + process = false; + } + } + + if (process) { // restore to default + NullModem.Change_IRQ_Priority( 0 ); + } + } else { + CCMessageBox().Process(TXT_SELECT_SETTINGS); + } + } + + if (process) { + buttons[curbutton]->IsPressed = false; + buttons[curbutton]->Flag_To_Redraw(); + } + + display = REDRAW_ALL; + break; + + case (BUTTON_ANSWER): + + if (selectsettings) { + CCMessageBox().Process(TXT_SELECT_SETTINGS); + } else { + /* + ** Remote-connect + */ + settings = &SerialDefaults; + + if (SerialPort){ + delete SerialPort; + } + SerialPort = new WinModemClass; + + if ( Init_Null_Modem( settings ) ) { + if ( Answer_Modem( settings, false ) ) { + ModemGameToPlay = MODEM_ANSWERER; + if ( Com_Show_Scenario_Dialog() ) { + retval = GAME_MODEM; + process = false; + } + } + + if (process) { // restore to default + NullModem.Change_IRQ_Priority( 0 ); + } + } else { + CCMessageBox().Process(TXT_SELECT_SETTINGS); + } + } + + if (process) { + buttons[curbutton]->IsPressed = false; + buttons[curbutton]->Flag_To_Redraw(); + } + + display = REDRAW_ALL; + break; + + case (BUTTON_NULLMODEM): + + if (selectsettings) { + CCMessageBox().Process(TXT_SELECT_SETTINGS); + } else { + /* + ** Otherwise, remote-connect; save values if we're recording + */ + + if (SerialPort){ + delete SerialPort; + } + SerialPort = new WinNullModemClass; + + if ( Init_Null_Modem( &SerialDefaults ) ) { + rc = Test_Null_Modem(); + switch (rc) { + case (1): + ModemGameToPlay = MODEM_NULL_HOST; + if ( Com_Scenario_Dialog() ) { + retval = GAME_NULL_MODEM; + process = false; + } + break; + + case (2): + ModemGameToPlay = MODEM_NULL_JOIN; + if ( Com_Show_Scenario_Dialog() ) { + retval = GAME_NULL_MODEM; + process = false; + } + break; + + case (3): + CCMessageBox().Process( TXT_MODEM_OR_LOOPBACK ); + break; + } + + if (process) { // restore to default + NullModem.Change_IRQ_Priority( 0 ); + } + } else { + CCMessageBox().Process(TXT_SELECT_SETTINGS); + } + } + + if (process) { + buttons[curbutton]->IsPressed = false; + buttons[curbutton]->Flag_To_Redraw(); + } + + display = REDRAW_ALL; + break; + + case (BUTTON_SETTINGS): + if ( Com_Settings_Dialog( &SerialDefaults ) ) { + Write_MultiPlayer_Settings (); + + selectsettings = true; + + if (SerialDefaults.Port != 0 && + SerialDefaults.IRQ != -1 && + SerialDefaults.Baud != -1) { + if ( NullModem.Detect_Port( &SerialDefaults ) == PORT_VALID) { + selectsettings = false; + } + } + } + + buttons[curbutton]->IsPressed = false; + buttons[curbutton]->Flag_To_Redraw(); + display = REDRAW_ALL; + break; + + case (BUTTON_CANCEL): + retval = GAME_NORMAL; + process = false; + break; + } + + pressed = false; + } + } /* end of while */ + +#if 0 + if (retval == GAME_NORMAL) { + Debug_Smart_Print = false; + } +#endif + +Debug_Smart_Print = false; + + return( retval ); +} + + + + +/*********************************************************************************************** + * Advanced_Modem_Settings -- Allows to user to set additional modem settings * + * * + * * + * * + * INPUT: current settings * + * * + * OUTPUT: modified settings * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 12/16/96 2:29PM ST : Created * + *=============================================================================================*/ +Advanced_Modem_Settings (SerialSettingsType *settings) +{ + + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 340; // dialog width + int d_dialog_h = 170; // dialog height + int d_dialog_x = 320 - d_dialog_w/2; // dialog x-coord + int d_dialog_y = 200 - d_dialog_h/ 2; // dialog y-coord + + + int d_compression_w = 50; + int d_compression_h = 18; + int d_compression_x = d_dialog_x + d_dialog_w/2 +40; + int d_compression_y = d_dialog_y + 30; + + int d_errorcorrection_w = 50; + int d_errorcorrection_h = 18; + int d_errorcorrection_x = d_dialog_x + d_dialog_w/2 +40; + int d_errorcorrection_y = d_dialog_y + 52; + + int d_hardwareflowcontrol_w = 50; + int d_hardwareflowcontrol_h = 18; + int d_hardwareflowcontrol_x = d_dialog_x + d_dialog_w/2 +40; + int d_hardwareflowcontrol_y = d_dialog_y + 74; + + int d_default_w = 100; + int d_default_h = 18; + int d_default_x = d_dialog_x + d_dialog_w / 2 - d_default_w / 2; + int d_default_y = d_dialog_y + 110; + + int d_ok_w = 100; + int d_ok_h = 18; + int d_ok_x = d_dialog_x + d_dialog_w/2 - d_ok_w / 2; + int d_ok_y = d_dialog_y + d_dialog_h - 24; + + enum { + BUTTON_COMPRESSION = 100, + BUTTON_ERROR_CORRECTION, + BUTTON_HARDWARE_FLOW_CONTROL, + BUTTON_DEFAULT, + BUTTON_OK, + }; + + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND, + } RedrawType; + + /* + ** Yes/No strings + */ + char compress_text [16]; + char correction_text [16]; + char flowcontrol_text[16]; + + + /* + ** Initialise the button text + */ + strcpy (compress_text, settings->Compression ? Text_String (TXT_ON) : Text_String (TXT_OFF) ); + strcpy (correction_text, settings->ErrorCorrection ? + Text_String (TXT_ON) : Text_String (TXT_OFF) ); + strcpy (flowcontrol_text, settings->HardwareFlowControl ? + Text_String (TXT_ON) : Text_String (TXT_OFF) ); + + + /* + ** Create the buttons + */ + TextButtonClass compressionbutton(BUTTON_COMPRESSION, compress_text, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_compression_x, d_compression_y, d_compression_w, d_compression_h); + + TextButtonClass errorcorrectionbutton(BUTTON_ERROR_CORRECTION, correction_text, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_errorcorrection_x, d_errorcorrection_y, d_errorcorrection_w, d_errorcorrection_h); + + TextButtonClass hardwareflowcontrolbutton(BUTTON_HARDWARE_FLOW_CONTROL, flowcontrol_text, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_hardwareflowcontrol_x, d_hardwareflowcontrol_y, d_hardwareflowcontrol_w, d_hardwareflowcontrol_h); + + TextButtonClass defaultbutton(BUTTON_DEFAULT, TXT_DEFAULT, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_default_x, d_default_y, d_default_w, d_default_h); + + TextButtonClass okbutton(BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_ok_x, d_ok_y, d_ok_w, d_ok_h); + + + /* + ** Misc. variables. + */ + RedrawType display = REDRAW_ALL; // redraw level + BOOL process = true; // process while true + KeyNumType input; + GadgetClass *commands; // button list + + + commands = &okbutton; + defaultbutton.Add_Tail(*commands); + compressionbutton.Add_Tail(*commands); + errorcorrectionbutton.Add_Tail(*commands); + hardwareflowcontrolbutton.Add_Tail(*commands); + + + /* + ** Main process loop + */ + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + // init font variables + + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_MODEM_INITIALISATION, d_dialog_x, d_dialog_y, d_dialog_w); + + Fancy_Text_Print( TXT_DATA_COMPRESSION, + d_compression_x - 26, d_compression_y + 2, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print( TXT_ERROR_CORRECTION, + d_errorcorrection_x - 26, d_errorcorrection_y +2, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print( TXT_HARDWARE_FLOW_CONTROL, + d_hardwareflowcontrol_x -26, d_hardwareflowcontrol_y +2, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + compressionbutton.Flag_To_Redraw(); + errorcorrectionbutton.Flag_To_Redraw(); + hardwareflowcontrolbutton.Flag_To_Redraw(); + } + + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + case (BUTTON_COMPRESSION | KN_BUTTON): + settings->Compression = settings->Compression ^ 1; + strcpy (compress_text, settings->Compression ? + Text_String (TXT_ON) : Text_String (TXT_OFF) ); + break; + + case (BUTTON_ERROR_CORRECTION | KN_BUTTON): + settings->ErrorCorrection = settings->ErrorCorrection ^ 1; + strcpy (correction_text, settings->ErrorCorrection ? + Text_String (TXT_ON) : Text_String (TXT_OFF) ); + break; + + case (BUTTON_HARDWARE_FLOW_CONTROL | KN_BUTTON): + settings->HardwareFlowControl = settings->HardwareFlowControl ^ 1; + strcpy (flowcontrol_text, settings->HardwareFlowControl ? + Text_String (TXT_ON) : Text_String (TXT_OFF) ); + break; + + case (BUTTON_DEFAULT | KN_BUTTON): + settings->Compression = false; + settings->ErrorCorrection = false; + settings->HardwareFlowControl = true; + + strcpy (compress_text, settings->Compression ? + Text_String (TXT_ON) : Text_String (TXT_OFF) ); + + strcpy (correction_text, settings->ErrorCorrection ? + Text_String (TXT_ON) : Text_String (TXT_OFF) ); + + strcpy (flowcontrol_text, settings->HardwareFlowControl ? + Text_String (TXT_ON) : Text_String (TXT_OFF) ); + + if (display < REDRAW_BUTTONS) display = REDRAW_BUTTONS; + break; + + case (BUTTON_OK | KN_BUTTON): + process = false; + break; + } + } +} + + + + +/*************************************************************************** + * Com_Settings_Dialog -- Lets user select serial port settings * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Settings ³ * + * ³ ³ * + * ³ Port:____ IRQ:__ Baud:______ ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ ³ * + * ³ Initialization: [Add] [Delete] ³ * + * ³ _____________________________ ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ * + * ³ ³ ³ ³ * + * ³ ³ ³ ³ * + * ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ ³ * + * ³ Call Waiting: ³ * + * ³ _______________ ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ [Tone Dialing] ³ * + * ³ ³ ³ ³ * + * ³ ³ ³ [Pulse Dialing] ³ * + * ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ ³ * + * ³ [OK] [Cancel] ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * settings ptr to SerialSettingsType structure * + * * + * OUTPUT: * + * true = OK, false = Cancel * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +static int Com_Settings_Dialog( SerialSettingsType *settings ) +{ +/* ###Change collision detected! C:\PROJECTS\CODE\NULLDLG.CPP... */ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 301 *factor; // dialog width + int d_dialog_h = 200 *factor; // dialog height + int d_dialog_x = ((320 *factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200 *factor - d_dialog_h) / 2); // dialog y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 6 *factor +1; // ht of 6-pt text + int d_margin = 5 *factor; // margin width/height + +#ifdef EDIT_IRQ + int d_portlist_w = 80 *factor; + int d_portlist_h = 35 *factor; + int d_portlist_x = d_dialog_x + (d_dialog_w / 6) - (d_portlist_w / 2); + int d_portlist_y = d_dialog_y + ((d_margin + d_txt6_h) * 2) + d_margin + 10 *factor; + + int d_port_w = ((PORTBUF_MAX - 1) * 6 *factor) + 4 *factor; + int d_port_h = 9 *factor; + int d_port_x = d_portlist_x + 31 *factor; + int d_port_y = d_portlist_y - d_margin - d_txt6_h; + + + int d_irqlist_w = 80 *factor; + int d_irqlist_h = 35 *factor; + int d_irqlist_x = d_dialog_x + (d_dialog_w / 2) - (d_irqlist_w / 2); + int d_irqlist_y = d_portlist_y; + + int d_irq_w = ((IRQBUF_MAX - 1) * 6 *factor) + 3 *factor; + int d_irq_h = 9 *factor; + int d_irq_x = d_irqlist_x + 25 *factor; + int d_irq_y = d_irqlist_y - d_margin - d_txt6_h; + + int d_baudlist_w = 80 *factor; + int d_baudlist_h = 35 *factor; + int d_baudlist_x = d_dialog_x + ((d_dialog_w * 5) / 6) - (d_baudlist_w / 2); + int d_baudlist_y = d_portlist_y; + + int d_baud_w = ((BAUDBUF_MAX - 1) * 6 *factor) + 3 *factor; + int d_baud_h = 9 *factor; + int d_baud_x = d_baudlist_x + 31 *factor; + int d_baud_y = d_baudlist_y - d_margin - d_txt6_h; + +#endif //EDIT_IRQ + int d_initstrlist_w = ((INITSTRBUF_MAX - 1) * 6 *factor) + 8 + 3 *factor; + int d_initstrlist_h = 21 *factor; + int d_initstrlist_x = d_dialog_cx - (d_initstrlist_w / 2); + int d_initstrlist_y = d_dialog_y + ((d_margin + d_txt6_h) * 2) + d_margin + 10 *factor + + 35*factor + + ((d_margin + d_txt6_h) * 2) + d_margin + 4 *factor; + + int d_initstr_w = ((INITSTRBUF_MAX - 1) * 6 *factor) + 3 *factor; + int d_initstr_h = 9 *factor; + int d_initstr_x = d_initstrlist_x; + int d_initstr_y = d_initstrlist_y - d_margin - d_txt6_h; + +#ifndef EDIT_IRQ + int d_portlist_w = 80 *factor + 80; + int d_portlist_h = 35 *factor; + int d_portlist_x = d_initstrlist_x; + int d_portlist_y = d_dialog_y + ((d_margin + d_txt6_h) * 2) + d_margin + 10 *factor; + + int d_port_w = d_portlist_w; + int d_port_h = 9 *factor; + int d_port_x = d_portlist_x; // + 31 *factor; + int d_port_y = d_portlist_y - d_margin - d_txt6_h; + + int d_baudlist_w = 80 *factor; + int d_baudlist_h = 35 *factor; + int d_baudlist_x = d_dialog_x + ((d_dialog_w * 5) / 6) - (d_baudlist_w / 2); + d_baudlist_x -= 32; + //int d_baudlist_x = d_portlist_x + d_portlist_w + 20 * factor; + int d_baudlist_y = d_portlist_y; + + int d_baud_w = ((BAUDBUF_MAX - 1) * 6 *factor) + 3 *factor; + int d_baud_h = 9 *factor; + int d_baud_x = d_baudlist_x + 31 *factor; + int d_baud_y = d_baudlist_y - d_margin - d_txt6_h; + + int d_inittype_w = 30*factor; + int d_inittype_h = 9*factor; + int d_inittype_x = d_dialog_x + ((d_dialog_w * 5) / 6) - (d_inittype_w / 2); + int d_inittype_y = d_baud_y + 20*factor; + +#endif //EDIT_IRQ + + int d_add_w = 45 *factor; + int d_add_h = 9 *factor; +#ifdef FRENCH + int d_add_x = (d_dialog_cx - (d_add_w / 2))+34*factor; +#else + int d_add_x = d_dialog_cx - (d_add_w / 2); +#endif + int d_add_y = d_initstr_y - d_add_h - 3*factor; + + int d_delete_w = 45 *factor; + int d_delete_h = 9 *factor; + +#ifdef FRENCH + int d_delete_x = 14*factor + d_dialog_x + ((d_dialog_w * 3) / 4) - (d_delete_w / 2); +#else + int d_delete_x = d_dialog_x + ((d_dialog_w * 3) / 4) - (d_delete_w / 2); +#endif + int d_delete_y = d_initstr_y - d_add_h - 3 *factor; + + int d_cwaitstrlist_w = (((CWAITSTRBUF_MAX - 1) + 9) * 6 *factor) + 3 *factor; + int d_cwaitstrlist_h = 27 *factor; + int d_cwaitstrlist_x = d_initstrlist_x; + int d_cwaitstrlist_y = d_initstrlist_y + d_initstrlist_h + ((d_margin + d_txt6_h) * 2) + 2 *factor; + + int d_cwaitstr_w = ((CWAITSTRBUF_MAX - 1) * 6 *factor) + 3 *factor; + int d_cwaitstr_h = 9 *factor; + int d_cwaitstr_x = d_cwaitstrlist_x; + int d_cwaitstr_y = d_cwaitstrlist_y - d_margin - d_txt6_h; + + int d_tone_w = 80 *factor; + int d_tone_h = 9 *factor; + int d_tone_x = d_dialog_x + ((d_dialog_w * 3) / 4) - (d_tone_w / 2); + int d_tone_y = d_cwaitstrlist_y; + + int d_pulse_w = 80 *factor; + int d_pulse_h = 9 *factor; + int d_pulse_x = d_dialog_x + ((d_dialog_w * 3) / 4) - (d_pulse_w / 2); + int d_pulse_y = d_tone_y + d_tone_h + d_margin; + + int d_save_w = 40 *factor; + int d_save_h = 9 *factor; + int d_save_x = d_dialog_x + (d_dialog_w / 5) - (d_save_w / 2); + int d_save_y = d_dialog_y + d_dialog_h - d_save_h - d_margin - 2 *factor; + +#if (GERMAN | FRENCH) + int d_cancel_w = 50 *factor; +#else + int d_cancel_w = 40 *factor; +#endif + int d_cancel_h = 9 *factor; + int d_cancel_x = d_dialog_x + ((d_dialog_w * 4) / 5) - (d_cancel_w / 2); + int d_cancel_y = d_dialog_y + d_dialog_h - d_cancel_h - d_margin - 2 *factor; + +#if (GERMAN | FRENCH) + int d_advanced_w = 50*factor; +#else + int d_advanced_w = 40*factor; +#endif + int d_advanced_h = 9*factor; + int d_advanced_x = d_dialog_x + ((d_dialog_w) / 2) - (d_advanced_w / 2); + int d_advanced_y = d_dialog_y + d_dialog_h - d_advanced_h - d_margin - 2 *factor; + + + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_PORT = 100, + BUTTON_PORTLIST, + BUTTON_IRQ, + BUTTON_IRQLIST, + BUTTON_BAUD, + BUTTON_BAUDLIST, + BUTTON_INITSTR, + BUTTON_INITSTRLIST, + BUTTON_ADD, + BUTTON_DELETE, + BUTTON_CWAITSTR, + BUTTON_CWAITSTRLIST, + BUTTON_TONE, + BUTTON_PULSE, + BUTTON_SAVE, + BUTTON_ADVANCED, + BUTTON_INITTYPE, + BUTTON_CANCEL, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + static char *portname[4] = { + "COM1 - 3F8", + "COM2 - 2F8", + "COM3 - 3E8", + "COM4 - 2E8" + }; + + static char custom_port[10 + MODEM_NAME_MAX] = {"CUSTOM - ????"}; + +#ifdef EDIT_IRQ + static char *irqname[5] = { + "2 / 9", + "3 - [COM2 & 4]", + "4 - [COM1 & 3]", + "5", + "CUSTOM - ??" + }; + + static int _irqidx[4] = { + 2, + 1, + 2, + 1 + }; +#endif // EDIT_IRQ + + static char modemnames[10][MODEM_NAME_MAX]; + + static char *baudname[5] = { + "14400", + "19200", + "28800", + "38400", + "57600", + }; + + static char *init_types[2] = { + "Normal", + "Full", + }; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + BOOL process = true; // process while true + KeyNumType input; + char * item; // general-purpose string + char * temp; // general-purpose string + + char portbuf[ PORTBUF_MAX ] = {0}; // buffer for port +#ifdef EDIT_IRQ + char irqbuf[ IRQBUF_MAX ] = {0}; // buffer for irq +#endif //EDIT_IRQ + char baudbuf[ BAUDBUF_MAX ] = {0}; // buffer for baud + char initstrbuf[ INITSTRBUF_MAX ] = {0}; // buffer for init string + char cwaitstrbuf[ CWAITSTRBUF_MAX ] = {0}; // buffer for call waiting string + + int port_index = 1; // index of currently-selected port (default = com2) + int port_custom_index = 4; //index of custom entry in port list +#ifdef EDIT_IRQ + int irq_index = 1; // index of currently-selected irq (default = 3) +#endif //EDIT_IRQ + int baud_index = 1; // index of currently-selected baud (default = 19200) + int initstr_index = 0; // index of currently-selected modem init (default = "ATZ") + int cwaitstr_index = CALL_WAIT_CUSTOM; // index of currently-selected call waiting (default = "") + int rc = 0; // -1 = user cancelled, 1 = New + int i; // loop counter + int pos; + int len; + int firsttime = 1; + SerialSettingsType tempsettings; + DetectPortType dpstatus; + char init_text[32]; + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + EditClass port_edt (BUTTON_PORT, + portbuf, PORTBUF_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_port_x, d_port_y, d_port_w, d_port_h, EditClass::ALPHANUMERIC); + + ListClass portlist(BUTTON_PORTLIST, + d_portlist_x, d_portlist_y, d_portlist_w, d_portlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + +#ifdef EDIT_IRQ + EditClass irq_edt (BUTTON_IRQ, + irqbuf, IRQBUF_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_irq_x, d_irq_y, d_irq_w, d_irq_h, EditClass::NUMERIC); + + ListClass irqlist(BUTTON_IRQLIST, + d_irqlist_x, d_irqlist_y, d_irqlist_w, d_irqlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); +#endif //EDIT_IRQ + + EditClass baud_edt (BUTTON_BAUD, + baudbuf, BAUDBUF_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_baud_x, d_baud_y, d_baud_w, d_baud_h, EditClass::NUMERIC); + + ListClass baudlist(BUTTON_BAUDLIST, + d_baudlist_x, d_baudlist_y, d_baudlist_w, d_baudlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + EditClass initstr_edt (BUTTON_INITSTR, + initstrbuf, INITSTRBUF_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_initstr_x, d_initstr_y, d_initstr_w, d_initstr_h, EditClass::ALPHANUMERIC); + + ListClass initstrlist(BUTTON_INITSTRLIST, + d_initstrlist_x, d_initstrlist_y, d_initstrlist_w, d_initstrlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + TextButtonClass addbtn(BUTTON_ADD, TXT_ADD, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#ifdef FRENCH +// d_add_x, d_add_y); +//#else + d_add_x, d_add_y, d_add_w, d_add_h); +//#endif + + TextButtonClass deletebtn(BUTTON_DELETE, TXT_DELETE_BUTTON, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#ifdef FRENCH +// d_delete_x, d_delete_y); +//#else + d_delete_x, d_delete_y, d_delete_w, d_delete_h); +//#endif + + EditClass cwaitstr_edt (BUTTON_CWAITSTR, + cwaitstrbuf, CWAITSTRBUF_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_cwaitstr_x, d_cwaitstr_y, d_cwaitstr_w, d_cwaitstr_h, EditClass::ALPHANUMERIC); + + ListClass cwaitstrlist(BUTTON_CWAITSTRLIST, + d_cwaitstrlist_x, d_cwaitstrlist_y, d_cwaitstrlist_w, d_cwaitstrlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + TextButtonClass tonebtn(BUTTON_TONE, TXT_TONE_BUTTON, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_tone_x, d_tone_y, d_tone_w, d_tone_h); + + TextButtonClass pulsebtn(BUTTON_PULSE, TXT_PULSE_BUTTON, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_pulse_x, d_pulse_y, d_pulse_w, d_pulse_h); + + TextButtonClass savebtn(BUTTON_SAVE, TXT_SAVE_BUTTON, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_save_x, d_save_y); +//#else + d_save_x, d_save_y, d_save_w, d_save_h); +//#endif + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_cancel_x, d_cancel_y); +//#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +//#endif +#if (0) + TextButtonClass inittypebutton(BUTTON_INITTYPE, init_text, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_inittype_x, d_inittype_y, d_inittype_w, d_inittype_h); +#endif //(0) + + TextButtonClass advancedbutton(BUTTON_ADVANCED, TXT_ADVANCED, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_advanced_x, d_advanced_y, d_advanced_w, d_advanced_h); + + /* + ----------------------------- Various Inits ------------------------------ + */ + memcpy( &tempsettings, settings, sizeof(SerialSettingsType) ); + + strcpy (init_text, init_types[tempsettings.Init]); + + if (tempsettings.Port == 0) { + tempsettings.Port = 0x2f8; + } + + if (tempsettings.IRQ == -1) { + tempsettings.IRQ = 3; + } + + if (tempsettings.Baud == -1) { + tempsettings.Baud = 19200; + } + + /*........................................................................ + Set the current indices + ........................................................................*/ + +#ifdef EDIT_IRQ + switch ( tempsettings.IRQ ) { + case ( 2 ): + irq_index = 0; + strcpy (irqbuf, "2"); + break; + + case ( 3 ): + irq_index = 1; + strcpy (irqbuf, "3"); + break; + + case ( 4 ): + irq_index = 2; + strcpy (irqbuf, "4"); + break; + + case ( 5 ): + irq_index = 3; + strcpy (irqbuf, "5"); + break; + + default: + irq_index = 4; + sprintf (irqbuf, "%d", tempsettings.IRQ); + temp = strchr( irqname[4], '-' ); + if ( temp ) { + pos = (int)(temp - irqname[4]) + 2; + len = strlen( irqbuf ); + strncpy( irqname[4] + pos, irqbuf, len ); + *(irqname[4] + pos + len) = 0; + } + break; + } +#endif //EDIT_IRQ + + if (tempsettings.Baud == 14400) { + baud_index = 0; + } else { + if (tempsettings.Baud == 19200) { + baud_index = 1; + } else { + if (tempsettings.Baud == 28800) { + baud_index = 2; + } else { + if (tempsettings.Baud == 38400) { + baud_index = 3; + } else { + baud_index = 4; + } + } + } + } + + sprintf (baudbuf, "%d", tempsettings.Baud); + + /*........................................................................ + Set up the port list box & edit box + ........................................................................*/ + for (i = 0; i < 4; i++) { + portlist.Add_Item( portname[ i ] ); + } + + /* + ** Loop through the first 10 possible modem entries in the registry. Frankly, its just + ** tough luck if the user has more than 10 modems attached! + */ + if (ModemRegistry) { + delete ModemRegistry; + } + int modems_found = 0; + for (i=0 ; i<10 ; i++) { + ModemRegistry = new ModemRegistryEntryClass (i); + if (ModemRegistry->Get_Modem_Name()) { + strncpy (modemnames[modems_found], ModemRegistry->Get_Modem_Name(), MODEM_NAME_MAX); + portlist.Add_Item( modemnames [modems_found++] ); + port_custom_index ++; + } + delete ModemRegistry; + } + ModemRegistry = NULL; + + portlist.Add_Item ( custom_port ); + + + /* + ** Work out the current port index + */ + port_index = -1; + + if (tempsettings.ModemName[0]) { + for ( i=0 ; i= REDRAW_BACKGROUND) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + // init font variables + + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_SETTINGS, d_dialog_x, d_dialog_y, d_dialog_w); + + Fancy_Text_Print( TXT_PORT_COLON, + d_port_x - 3, d_port_y + 1 *factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + +#ifdef EDIT_IRQ + Fancy_Text_Print( TXT_IRQ_COLON, + d_irq_x - 3, d_irq_y + 1 *factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); +#endif //EDIT_IRQ + + Fancy_Text_Print( TXT_BAUD_COLON, + d_baud_x - 3, d_baud_y + 1 *factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print( TXT_INIT_STRING, + d_initstr_x, d_initstr_y - d_txt6_h - 3 *factor, + CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print( TXT_CWAIT_STRING, + d_cwaitstr_x, d_cwaitstr_y - d_txt6_h - 3 *factor, + CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); +#if (0) + Fancy_Text_Print ( "Modem Init", + d_inittype_x, d_inittype_y - d_txt6_h - d_margin, + CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); +#endif //(0) + } + + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + cancelbtn.Flag_To_Redraw(); + port_edt.Flag_To_Redraw(); + portlist.Flag_To_Redraw(); +#ifdef EDIT_IRQ + irq_edt.Flag_To_Redraw(); + irqlist.Flag_To_Redraw(); +#endif // EDIT_IRQ + baud_edt.Flag_To_Redraw(); + baudlist.Flag_To_Redraw(); + //inittypebutton.Flag_To_Redraw(); + advancedbutton.Flag_To_Redraw(); + initstr_edt.Flag_To_Redraw(); + initstrlist.Flag_To_Redraw(); + addbtn.Flag_To_Redraw(); + deletebtn.Flag_To_Redraw(); + cwaitstr_edt.Flag_To_Redraw(); + cwaitstrlist.Flag_To_Redraw(); + tonebtn.Flag_To_Redraw(); + pulsebtn.Flag_To_Redraw(); + savebtn.Flag_To_Redraw(); + } + + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + if ( firsttime ) { + port_edt.Set_Focus(); + port_edt.Flag_To_Redraw(); + input = commands->Input(); + firsttime = 0; + } + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { +#if (0) + case (BUTTON_INITTYPE | KN_BUTTON): + tempsettings.Init = !tempsettings.Init; + strcpy (init_text, init_types[tempsettings.Init]); + inittypebutton.Flag_To_Redraw(); + break; + + +#endif //(0) + + case (BUTTON_ADVANCED | KN_BUTTON): + Advanced_Modem_Settings (&tempsettings); + display = REDRAW_ALL; + break; + + + case (BUTTON_PORT | KN_BUTTON): + item = (char *)portlist.Current_Item(); + if (port_index < 4) { + temp = strchr( item, ' ' ); + if ( !temp ) { + strncpy( portbuf, item, PORTBUF_MAX ); + } else { + pos = (int)(temp - item); + strncpy( portbuf, item, pos ); + portbuf[pos] = 0; + } + port_edt.Set_Text( portbuf, PORTBUF_MAX ); + port_edt.Flag_To_Redraw(); +#ifdef EDIT_IRQ + irq_edt.Set_Focus(); + irq_edt.Flag_To_Redraw(); +#endif //EDIT_IRQ + } else { + strupr( portbuf ); + if ( stricmp(portbuf, "3F8") == 0 ) { + port_index = 0; + portlist.Set_Selected_Index( port_index ); + strcpy (portbuf, "COM1"); + display = REDRAW_BUTTONS; + } + else if ( stricmp(portbuf, "2F8") == 0 ) { + port_index = 1; + portlist.Set_Selected_Index( port_index ); + strcpy (portbuf, "COM2"); + display = REDRAW_BUTTONS; + } + else if ( stricmp(portbuf, "3E8") == 0 ) { + port_index = 2; + portlist.Set_Selected_Index( port_index ); + strcpy (portbuf, "COM3"); + display = REDRAW_BUTTONS; + } + else if ( stricmp(portbuf, "2E8") == 0 ) { + port_index = 3; + portlist.Set_Selected_Index( port_index ); + strcpy (portbuf, "COM4"); + display = REDRAW_BUTTONS; + } + else if ( strncmp(portbuf, "COM", 3) == 0 ) { + display = REDRAW_BUTTONS; + + switch ( (portbuf[3] - '0') ) { + case 1: + port_index = 0; + break; + + case 2: + port_index = 1; + break; + + case 3: + port_index = 2; + break; + + case 4: + port_index = 3; + break; + + default: + if (portbuf[3] <= '9' && portbuf[3] >'0') { + portbuf[4] = 0; + port_index = port_custom_index; + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + len = strlen( portbuf ); + strncpy( item + pos, portbuf, len ); + *(item + pos + len) = 0; + display = REDRAW_BUTTONS; + } + break; + } + CCMessageBox().Process( TXT_INVALID_PORT_ADDRESS ); + port_edt.Set_Focus(); + display = REDRAW_ALL; + break; + } + + portlist.Set_Selected_Index( port_index ); + } else { + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + len = strlen( portbuf ); + strncpy( item + pos, portbuf, len ); + *(item + pos + len) = 0; + display = REDRAW_BUTTONS; + } + } + +#ifdef EDIT_IRQ + if (display == REDRAW_BUTTONS) { + irq_edt.Set_Focus(); + irq_edt.Flag_To_Redraw(); + } +#endif //EDIT_IRQ + } + break; + + case (BUTTON_PORTLIST | KN_BUTTON): + if (portlist.Current_Index() != port_index) { + port_index = portlist.Current_Index(); + item = (char *)portlist.Current_Item(); + if (port_index < 4) { + temp = strchr( item, ' ' ); + if ( !temp ) { + strncpy( portbuf, item, PORTBUF_MAX ); + } else { + pos = (int)(temp - item); + strncpy( portbuf, item, pos ); + portbuf[pos] = 0; + } + port_edt.Clear_Focus(); + + // auto select the irq for port + +#ifdef EDIT_IRQ + irq_index = _irqidx[ port_index ]; + irqlist.Set_Selected_Index( irq_index ); + item = (char *)irqlist.Current_Item(); + temp = strchr( item, ' ' ); + if ( !temp ) { + strncpy( irqbuf, item, 2 ); + } else { + pos = (int)(temp - item); + strncpy( irqbuf, item, pos ); + irqbuf[pos] = 0; + } + irq_edt.Clear_Focus(); +#endif //EDIT_IRQ + } else { + if (port_index == port_custom_index) { + /* + ** This is the custom entry + */ + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + if ( *(item + pos) == '?' ) { + portbuf[0] = 0; + } else { + strncpy( portbuf, item + pos, PORTBUF_MAX ); + } + } + port_edt.Set_Focus(); + }else{ + /* + ** Must be a modem name entry so just copy iy + */ + strncpy (portbuf, item, PORTBUF_MAX); + } + + } + port_edt.Set_Text( portbuf, PORTBUF_MAX ); + display = REDRAW_BUTTONS; + } else { + if (port_index < port_custom_index) { + port_edt.Clear_Focus(); + } else { + port_edt.Set_Focus(); + } + display = REDRAW_BUTTONS; + } + break; + +#ifdef EDIT_IRQ + case (BUTTON_IRQ | KN_BUTTON): + item = (char *)irqlist.Current_Item(); + if (irq_index < 4) { + temp = strchr( item, ' ' ); + if ( !temp ) { + strncpy( irqbuf, item, IRQBUF_MAX ); + } else { + pos = (int)(temp - item); + strncpy( irqbuf, item, pos ); + irqbuf[pos] = 0; + } + irq_edt.Set_Text( irqbuf, IRQBUF_MAX ); + irq_edt.Flag_To_Redraw(); + } else { + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + len = strlen( irqbuf ); + strncpy( item + pos, irqbuf, len ); + *(item + pos + len) = 0; + display = REDRAW_BUTTONS; + } + } + baud_edt.Set_Focus(); + baud_edt.Flag_To_Redraw(); + break; + + case (BUTTON_IRQLIST | KN_BUTTON): + if (irqlist.Current_Index() != irq_index) { + irq_index = irqlist.Current_Index(); + item = (char *)irqlist.Current_Item(); + if (irq_index < 4) { + temp = strchr( item, ' ' ); + if ( !temp ) { + strncpy( irqbuf, item, IRQBUF_MAX ); + } else { + pos = (int)(temp - item); + strncpy( irqbuf, item, pos ); + irqbuf[pos] = 0; + } + irq_edt.Clear_Focus(); + } else { + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + if ( *(item + pos) == '?' ) { + irqbuf[0] = 0; + } else { + strncpy( irqbuf, item + pos, IRQBUF_MAX ); + } + } + irq_edt.Set_Focus(); + } + irq_edt.Set_Text( irqbuf, IRQBUF_MAX ); + } else { + if (irq_index < 4) { + irq_edt.Clear_Focus(); + } else { + irq_edt.Set_Focus(); + } + } + display = REDRAW_BUTTONS; + break; +#endif //EDIT_IRQ + + case (BUTTON_BAUD | KN_BUTTON): + item = (char *)baudlist.Current_Item(); + strncpy( baudbuf, item, BAUDBUF_MAX ); + baud_edt.Set_Text( baudbuf, BAUDBUF_MAX ); + initstr_edt.Set_Focus(); + initstr_edt.Flag_To_Redraw(); + display = REDRAW_BUTTONS; + break; + + case (BUTTON_BAUDLIST | KN_BUTTON): + if (baudlist.Current_Index() != baud_index) { + baud_index = baudlist.Current_Index(); + item = (char *)baudlist.Current_Item(); + strncpy( baudbuf, item, BAUDBUF_MAX ); + baud_edt.Set_Text( baudbuf, BAUDBUF_MAX ); + baud_edt.Clear_Focus(); + display = REDRAW_BUTTONS; + } + break; + +#if 0 + case (BUTTON_INITSTR | KN_BUTTON): + strupr( initstrbuf ); + strncpy( InitStrings[ initstr_index ], initstrbuf, INITSTRBUF_MAX ); + Build_Init_String_Listbox(&initstrlist, &initstr_edt, initstrbuf, + &initstr_index); + cwaitstr_edt.Set_Focus(); + cwaitstr_edt.Flag_To_Redraw(); + display = REDRAW_BUTTONS; + break; +#endif + + case (BUTTON_INITSTRLIST | KN_BUTTON): + if (initstrlist.Current_Index() != initstr_index) { + initstr_index = initstrlist.Current_Index(); + item = (char *)initstrlist.Current_Item(); + strncpy( initstrbuf, item, INITSTRBUF_MAX ); + initstr_edt.Set_Text( initstrbuf, INITSTRBUF_MAX ); + } + initstr_edt.Set_Focus(); + initstr_edt.Flag_To_Redraw(); + display = REDRAW_BUTTONS; + break; + + /*------------------------------------------------------------------ + Add a new InitString entry + ------------------------------------------------------------------*/ + case (BUTTON_ADD | KN_BUTTON): + + item = new char[ INITSTRBUF_MAX ]; + memset (item, 0, INITSTRBUF_MAX); + + strupr ( initstrbuf ); + strncpy ( item, initstrbuf, INITSTRBUF_MAX-1 ); + + InitStrings.Add ( item ); + Build_Init_String_Listbox (&initstrlist, &initstr_edt, initstrbuf, + &initstr_index); + /*............................................................ + Set the current listbox index to the newly-added item. + ............................................................*/ + for (i = 0; i < InitStrings.Count(); i++) { + if (item == InitStrings[i]) { + initstr_index = i; + strcpy( initstrbuf, InitStrings[ initstr_index ] ); + initstr_edt.Set_Text( initstrbuf, INITSTRBUF_MAX ); + initstrlist.Set_Selected_Index( initstr_index ); + } + } + initstr_edt.Set_Focus(); + initstr_edt.Flag_To_Redraw(); + display = REDRAW_BUTTONS; + break; + + /*------------------------------------------------------------------ + Delete the current InitString entry + ------------------------------------------------------------------*/ + case (BUTTON_DELETE | KN_BUTTON): + + if ( InitStrings.Count() && initstr_index != -1) { + InitStrings.Delete( initstr_index ); + Build_Init_String_Listbox(&initstrlist, &initstr_edt, initstrbuf, + &initstr_index); + } + break; + + case (BUTTON_CWAITSTR | KN_BUTTON): + item = (char *)cwaitstrlist.Current_Item(); + if (cwaitstr_index < 3) { + } else { + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + len = strlen( cwaitstrbuf ); + strncpy( item + pos, cwaitstrbuf, len ); + *(item + pos + len) = 0; + display = REDRAW_BUTTONS; + } + } + break; + + case (BUTTON_CWAITSTRLIST | KN_BUTTON): + if (cwaitstrlist.Current_Index() != cwaitstr_index) { + cwaitstr_index = cwaitstrlist.Current_Index(); + item = (char *)cwaitstrlist.Current_Item(); + if (cwaitstr_index < 3) { + strncpy( cwaitstrbuf, item, CWAITSTRBUF_MAX ); + cwaitstr_edt.Clear_Focus(); + } else { + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + strncpy( cwaitstrbuf, item + pos, CWAITSTRBUF_MAX ); + } + cwaitstr_edt.Set_Focus(); + } + cwaitstr_edt.Set_Text( cwaitstrbuf, CWAITSTRBUF_MAX ); + } else { + if (cwaitstr_index < 3) { + cwaitstr_edt.Clear_Focus(); + } else { + cwaitstr_edt.Set_Focus(); + } + } + display = REDRAW_BUTTONS; + break; + + case (BUTTON_TONE | KN_BUTTON): + tempsettings.DialMethod = DIAL_TOUCH_TONE; + tonebtn.Turn_On(); + pulsebtn.Turn_Off(); + break; + + case (BUTTON_PULSE | KN_BUTTON): + tempsettings.DialMethod = DIAL_PULSE; + tonebtn.Turn_Off(); + pulsebtn.Turn_On(); + break; + + /*------------------------------------------------------------------ + SAVE: save the com settings + ------------------------------------------------------------------*/ + case (KN_RETURN): + case (BUTTON_SAVE | KN_BUTTON): + switch (port_index) { + case ( 0 ): + tempsettings.Port = 0x3f8; + tempsettings.ModemName[0] = 0; + break; + + case ( 1 ): + tempsettings.Port = 0x2f8; + tempsettings.ModemName[0] = 0; + break; + + case ( 2 ): + tempsettings.Port = 0x3e8; + tempsettings.ModemName[0] = 0; + break; + + case ( 3 ): + tempsettings.Port = 0x2e8; + tempsettings.ModemName[0] = 0; + break; + + default: + if (port_index == port_custom_index) { + strncpy ( tempsettings.ModemName, portbuf, MODEM_NAME_MAX ); + tempsettings.Port = 1; + } else { + /* + ** Must be a modem name index + */ + strcpy (tempsettings.ModemName, portlist.Current_Item()); + tempsettings.Port = 1; + } + break; + } + +#ifdef EDIT_IRQ + switch (irq_index) { + case ( 0 ): + tempsettings.IRQ = 2; + break; + + case ( 1 ): + tempsettings.IRQ = 3; + break; + + case ( 2 ): + tempsettings.IRQ = 4; + break; + + case ( 3 ): + tempsettings.IRQ = 5; + break; + + default: + sscanf( irqbuf, "%d", &tempsettings.IRQ ); + break; + } +#endif //EDIT_IRQ + + sscanf( baudbuf, "%d", &tempsettings.Baud ); + + tempsettings.InitStringIndex = initstr_index; + tempsettings.CallWaitStringIndex = cwaitstr_index; + + item = CallWaitStrings[ CALL_WAIT_CUSTOM ]; + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + strncpy( cwaitstrbuf, item + pos, CWAITSTRBUF_MAX ); + } else { + cwaitstrbuf[ 0 ] = 0; + } + + strncpy( tempsettings.CallWaitString, cwaitstrbuf, CWAITSTRBUF_MAX ); + + dpstatus = NullModem.Detect_Port( &tempsettings ); + + if (dpstatus == PORT_VALID) { + process = false; + rc = true; + } + else if (dpstatus == PORT_INVALID) { + CCMessageBox().Process( TXT_INVALID_SETTINGS ); + firsttime = 1; + display = REDRAW_ALL; + } + else if (dpstatus == PORT_IRQ_INUSE) { + CCMessageBox().Process( TXT_IRQ_ALREADY_IN_USE ); + firsttime = 1; + display = REDRAW_ALL; + } + break; + + /*------------------------------------------------------------------ + CANCEL: send a SIGN_OFF, bail out with error code + ------------------------------------------------------------------*/ + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + process = false; + rc = false; + break; + } + + } /* end of while */ + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + /*------------------------------------------------------------------------ + Save values into the Settings structure + ------------------------------------------------------------------------*/ + if (rc) { + memcpy( settings, &tempsettings, sizeof(SerialSettingsType) ); + } + + return(rc); + +} /* end of Com_Settings_Dialog */ + + +/*************************************************************************** + * Build_Init_String_Listbox -- [re]builds the initstring listbox * + * * + * This routine rebuilds the initstring list box from scratch; it also * + * updates the contents of the initstring edit field. * + * * + * INPUT: * + * list ptr to list box * + * edit ptr to edit box * + * buf ptr to buffer for initstring * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/08/1995 DRD : Created. * + *=========================================================================*/ +static void Build_Init_String_Listbox (ListClass *list, EditClass *edit, char *buf, int *index) +{ + int i, curidx; + char *item; + + + curidx = *index; + + /*........................................................................ + Clear the list + ........................................................................*/ + while (list->Count()) { + item = (char *)(list->Get_Item(0)); + list->Remove_Item(item); + delete [] item; + } + + /* + ** Now sort the init string list by name then number + */ + qsort ((void *)(&InitStrings[0]), InitStrings.Count(), sizeof(char *), Init_String_Compare); + + /*........................................................................ + Build the list + ........................................................................*/ + for (i = 0; i < InitStrings.Count(); i++) { + item = new char[ INITSTRBUF_MAX ]; + strcpy( item, InitStrings[i] ); + list->Add_Item(item); + } + list->Flag_To_Redraw(); + + /*........................................................................ + Init the current phone book index + ........................................................................*/ + if (list->Count() == 0 || curidx < -1) { + curidx = -1; + } else { + if (curidx >= list->Count() ) { + curidx = 0; + } + } + + /*........................................................................ + Fill in initstring edit buffer + ........................................................................*/ + if (curidx > -1) { + strcpy (buf, InitStrings[ curidx ]); + edit->Set_Text (buf, INITSTRBUF_MAX ); + list->Set_Selected_Index( curidx ); + } + + *index = curidx; +} + + +/*************************************************************************** + * Init_String_Compare -- for qsort * + * * + * INPUT: * + * p1,p2 ptrs to elements to compare * + * * + * OUTPUT: * + * 0 = same, -1 = (*p1) goes BEFORE (*p2), 1 = (*p1) goes AFTER (*p2) * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/08/1995 DRD : Created. * + *=========================================================================*/ +static int Init_String_Compare (const void *p1, const void *p2) +{ + return( strcmp( *((char **)p1), *((char **)p2) ) ); +} + + +/*********************************************************************************************** + * Com_Scenario_Dialog -- Serial game scenario selection dialog * + * * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Serial Game ³ * + * ³ ³ * + * ³ Your Name: __________ House: [GDI] [NOD] ³ * + * ³ Credits: ______ Desired Color: [ ][ ][ ][ ] ³ * + * ³ Opponent: Name ³ * + * ³ ³ * + * ³ Scenario ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ³ * + * ³ ³ Hell's Kitchen ³³ ³ * + * ³ ³ Heaven's Gate ÃÄ´ ³ * + * ³ ³ ... ³ ³ ³ * + * ³ ³ ÃÄ´ ³ * + * ³ ³ ³³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ³ * + * ³ [ Bases ] [ Crates ] ³ * + * ³ [ Tiberium ] [ AI Players ] ³ * + * ³ ³ * + * ³ [OK] [Cancel] ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ * + * ³ ³ ³ ³ * + * ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ [Send Message] ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = success, false = cancel * + * * + * WARNINGS: * + * MPlayerName & MPlayerGameName must contain this player's name. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +#define TXT_HOST_INTERNET_GAME 4567+1 +#define TXT_JOIN_INTERNET_GAME 4567+2 +int Com_Scenario_Dialog(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 290*factor; // dialog width + int d_dialog_h = 190*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200*factor - d_dialog_h) / 2); // dialog y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 6*factor+1; // ht of 6-pt text + int d_margin1 = 5*factor; // margin width/height + int d_margin2 = 2*factor; // margin width/height + + int d_name_w = 70*factor; + int d_name_h = 9*factor; + int d_name_x = d_dialog_x + 108*factor; + int d_name_y = d_dialog_y + d_margin1 + d_txt6_h + d_txt6_h + d_margin1; + + int d_credits_w = ((CREDITSBUF_MAX - 1) * 6*factor) + 3*factor; + int d_credits_h = 9*factor; + int d_credits_x = d_name_x; + int d_credits_y = d_name_y + d_name_h + d_margin2; + + int d_gdi_w = 30*factor; + int d_gdi_h = 9*factor; + int d_gdi_x = d_dialog_cx + (d_dialog_w / 4); + int d_gdi_y = d_dialog_y + d_margin1 + d_txt6_h + d_txt6_h + d_margin1; + + int d_nod_w = 30*factor; + int d_nod_h = 9*factor; + int d_nod_x = d_gdi_x + d_gdi_w + (d_margin1 / 2); + int d_nod_y = d_gdi_y; + + int d_color_w = 10*factor; + int d_color_h = 9*factor; + int d_color_y = d_gdi_y + d_gdi_h + d_margin2; + + int d_opponent_x = d_name_x; + int d_opponent_y = d_color_y + d_color_h + d_margin2; + + int d_scenariolist_w = 182*factor; + int d_scenariolist_h = 27*factor; + int d_scenariolist_x = d_dialog_cx - (d_scenariolist_w / 2); + int d_scenariolist_y = d_opponent_y + d_txt6_h + 3*factor + d_txt6_h; + + // d_count_x is calculated below after other enums + int d_count_w = 25*factor; + int d_count_h = 7*factor; + int d_count_y = d_scenariolist_y + d_scenariolist_h + d_margin2; + + // d_level_x is calculated below after other enums + int d_level_w = 25*factor; + int d_level_h = 7*factor; + int d_level_y = d_count_y; + +#if (GERMAN | FRENCH) + int d_bases_w = 120*factor;//BGA:100; +#else + int d_bases_w = 110*factor; +#endif + int d_bases_h = 9*factor; + int d_bases_x = d_dialog_cx - d_bases_w - d_margin2; + int d_bases_y = d_count_y + d_count_h + d_margin2; + +#if (GERMAN | FRENCH) + int d_goodies_w = 120*factor; +#else + int d_goodies_w = 110*factor; +#endif + int d_goodies_h = 9*factor; + int d_goodies_x = d_dialog_cx + d_margin2; + int d_goodies_y = d_bases_y; + + int d_count_x = d_dialog_cx - d_count_w - ((2 * 6*factor) + 3*factor) + - ((d_bases_w - ((13 * 6*factor) + 3*factor + d_count_w)) / 2) - d_margin2; + + int d_level_x = d_dialog_cx + (11 * 6*factor) + + ((d_goodies_w - ((13 * 6*factor) + 3*factor + d_level_w)) / 2) + d_margin2; + +#if (GERMAN | FRENCH) + int d_tiberium_w = 120*factor; +#else + int d_tiberium_w = 110*factor; +#endif + int d_tiberium_h = 9*factor; + int d_tiberium_x = d_dialog_cx - d_bases_w - d_margin2; + int d_tiberium_y = d_bases_y + d_bases_h + d_margin2; + +#if (GERMAN | FRENCH) + int d_ghosts_w = 120*factor; +#else + int d_ghosts_w = 110*factor; +#endif + int d_ghosts_h = 9*factor; + int d_ghosts_x = d_dialog_cx + d_margin2; + int d_ghosts_y = d_tiberium_y; + + int d_ok_w = 45*factor; + int d_ok_h = 9*factor; + int d_ok_x = d_tiberium_x + (d_tiberium_w / 2) - (d_ok_w / 2); + int d_ok_y = d_tiberium_y + d_tiberium_h + d_margin1; + + int d_cancel_w = 45*factor; + int d_cancel_h = 9*factor; + int d_cancel_x = d_ghosts_x + (d_ghosts_w / 2) - (d_cancel_w / 2); + int d_cancel_y = d_tiberium_y + d_tiberium_h + d_margin1; + + int d_message_w = d_dialog_w - (d_margin1 * 2); + int d_message_h = 34*factor; + int d_message_x = d_dialog_x + d_margin1; + int d_message_y = d_cancel_y + d_cancel_h + d_margin1; + + int d_send_w = 80*factor; + int d_send_h = 9*factor; + int d_send_x = d_dialog_cx - (d_send_w / 2); + int d_send_y = d_message_y + d_message_h + d_margin2; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_NAME = 100, + BUTTON_GDI, + BUTTON_NOD, + BUTTON_CREDITS, + BUTTON_SCENARIOLIST, + BUTTON_COUNT, + BUTTON_LEVEL, + BUTTON_BASES, + BUTTON_TIBERIUM, + BUTTON_GOODIES, + BUTTON_GHOSTS, + BUTTON_OK, + BUTTON_CANCEL, + BUTTON_SEND, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_MESSAGE, + REDRAW_COLORS, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + bool process = true; // process while true + KeyNumType input; + + char namebuf[MPLAYER_NAME_MAX] = {0}; // buffer for player's name + char credbuf[CREDITSBUF_MAX]; // for credit edit box + int old_cred; // old value in credits buffer + int transmit; // 1 = re-transmit new game options + int cbox_x[] = { d_gdi_x, + d_gdi_x + d_color_w, + d_gdi_x + (d_color_w * 2), + d_gdi_x + (d_color_w * 3), + d_gdi_x + (d_color_w * 4), + d_gdi_x + (d_color_w * 5)}; + int parms_received = 0; // 1 = game options received + int changed = 0; // 1 = user has changed an option + + int rc; + int recsignedoff = false; + int i; + int version; + char txt[80]; + unsigned long starttime; + unsigned long timingtime; + unsigned long lastmsgtime; + unsigned long lastredrawtime; + unsigned long transmittime = 0; + unsigned long theirresponsetime; + int packetlen; + static int first_time = 1; + bool oppscorescreen = false; + bool gameoptions = false; + EventClass *event; // event ptr + unsigned long msg_timeout = 1200; // init to 20 seconds + + int message_length; + int sent_so_far; + unsigned short magic_number; + unsigned short crc; + bool ready_to_go = false; + CountDownTimerClass ready_time; + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + EditClass name_edt (BUTTON_NAME, + namebuf, MPLAYER_NAME_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_name_x, d_name_y, d_name_w, d_name_h, EditClass::ALPHANUMERIC); + + TextButtonClass gdibtn(BUTTON_GDI, TXT_G_D_I, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_gdi_x, d_gdi_y, d_gdi_w, d_gdi_h); + + TextButtonClass nodbtn(BUTTON_NOD, TXT_N_O_D, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_nod_x, d_nod_y, d_nod_w, d_nod_h); + + EditClass credit_edt (BUTTON_CREDITS, + credbuf, CREDITSBUF_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_credits_x, d_credits_y, d_credits_w, d_credits_h, EditClass::ALPHANUMERIC); + + ListClass scenariolist(BUTTON_SCENARIOLIST, + d_scenariolist_x, d_scenariolist_y, d_scenariolist_w, d_scenariolist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + GaugeClass countgauge (BUTTON_COUNT, + d_count_x, d_count_y, d_count_w, d_count_h); + + GaugeClass levelgauge (BUTTON_LEVEL, + d_level_x, d_level_y, d_level_w, d_level_h); + + TextButtonClass basesbtn(BUTTON_BASES, TXT_BASES_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_bases_x, d_bases_y, d_bases_w, d_bases_h); + + TextButtonClass tiberiumbtn(BUTTON_TIBERIUM, TXT_TIBERIUM_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_tiberium_x, d_tiberium_y, d_tiberium_w, d_tiberium_h); + + TextButtonClass goodiesbtn(BUTTON_GOODIES, TXT_CRATES_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_goodies_x, d_goodies_y, d_goodies_w, d_goodies_h); + + TextButtonClass ghostsbtn(BUTTON_GHOSTS, TXT_AI_PLAYERS_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_ghosts_x, d_ghosts_y, d_ghosts_w, d_ghosts_h); + + TextButtonClass okbtn(BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_ok_x, d_ok_y, d_ok_w, d_ok_h); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_cancel_x, d_cancel_y); +//#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +//#endif + + TextButtonClass sendbtn(BUTTON_SEND, TXT_SEND_MESSAGE, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_send_x, d_send_y); +//#else + d_send_x, d_send_y, d_send_w, d_send_h); +//#endif + + /* + ------------------------- Build the button list -------------------------- + */ + commands = &name_edt; + gdibtn.Add_Tail(*commands); + nodbtn.Add_Tail(*commands); + credit_edt.Add_Tail(*commands); + scenariolist.Add_Tail(*commands); + countgauge.Add_Tail(*commands); + levelgauge.Add_Tail(*commands); + basesbtn.Add_Tail(*commands); + tiberiumbtn.Add_Tail(*commands); + goodiesbtn.Add_Tail(*commands); + ghostsbtn.Add_Tail(*commands); + okbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + sendbtn.Add_Tail(*commands); + + /* + ----------------------------- Various Inits ------------------------------ + */ + /*........................................................................ + Init player name & house + ........................................................................*/ + MPlayerColorIdx = MPlayerPrefColor; // init my preferred color + strcpy (namebuf, MPlayerName); // set my name + name_edt.Set_Text(namebuf,MPLAYER_NAME_MAX); + name_edt.Set_Color(MPlayerTColors[MPlayerColorIdx]); + + if (MPlayerHouse==HOUSE_GOOD) { + gdibtn.Turn_On(); + } else { + nodbtn.Turn_On(); + } + + /*........................................................................ + Init scenario values, only the first time through + ........................................................................*/ + if (first_time) { + MPlayerCredits = 3000; // init credits & credit buffer + MPlayerBases = 1; // init scenario parameters + MPlayerTiberium = 0; + MPlayerGoodies = 0; + MPlayerGhosts = 0; + Special.IsCaptureTheFlag = 0; + MPlayerUnitCount = (MPlayerCountMax[MPlayerBases] + MPlayerCountMin[MPlayerBases]) / 2; + first_time = 0; + } + + /*........................................................................ + Init button states + ........................................................................*/ + if (MPlayerBases) { + basesbtn.Turn_On(); + basesbtn.Set_Text(TXT_BASES_ON); + } + if (MPlayerTiberium) { + tiberiumbtn.Turn_On(); + tiberiumbtn.Set_Text(TXT_TIBERIUM_ON); + } + if (MPlayerGoodies) { + goodiesbtn.Turn_On(); + goodiesbtn.Set_Text(TXT_CRATES_ON); + } + if (MPlayerGhosts) { + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_AI_PLAYERS_ON); + } + if (Special.IsCaptureTheFlag) { + MPlayerGhosts = 0; + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_CAPTURE_THE_FLAG); + } + + sprintf(credbuf, "%d", MPlayerCredits); + credit_edt.Set_Text(credbuf, CREDITSBUF_MAX); + old_cred = MPlayerCredits; + + levelgauge.Set_Maximum(MPLAYER_BUILD_LEVEL_MAX - 1); + levelgauge.Set_Value(BuildLevel - 1); + + countgauge.Set_Maximum(MPlayerCountMax[MPlayerBases] - MPlayerCountMin[MPlayerBases]); + countgauge.Set_Value(MPlayerUnitCount - MPlayerCountMin[MPlayerBases]); + + /*........................................................................ + Init other scenario parameters + ........................................................................*/ + Special.IsTGrowth = MPlayerTiberium; + Special.IsTSpread = MPlayerTiberium; + transmit = 1; + + /*........................................................................ + Init scenario description list box + ........................................................................*/ + for (i = 0; i < MPlayerScenarios.Count(); i++) { + scenariolist.Add_Item (strupr(MPlayerScenarios[i])); + } + ScenarioIdx = 0; // 1st scenario is selected + + /*........................................................................ + Init random-number generator, & create a seed to be used for all random + numbers from here on out + ........................................................................*/ + randomize(); + Seed = rand(); + + /*........................................................................ + Init the message display system + ........................................................................*/ + Messages.Init (d_message_x + 2*factor, d_message_y + 2*factor, 4, MAX_MESSAGE_LENGTH, d_txt6_h); + + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + extern char ModemRXString[]; + + if (strlen(ModemRXString) > 36) + ModemRXString[36] = 0; + + if (strlen(ModemRXString) > 0) + Messages.Add_Message (ModemRXString, CC_TAN, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, 0, 0); + + ModemRXString[0] = '\0'; + + /* + ---------------------------- Init Mono Output ---------------------------- + */ + #if(SHOW_MONO) + NullModem.Configure_Debug(sizeof (CommHeaderType),sizeof (SerialCommandType), + SerialPacketNames, 106); + NullModem.Mono_Debug_Print(1); + #endif + + /* + ---------------------------- Processing loop ----------------------------- + */ + NullModem.Reset_Response_Time(); // clear response time + theirresponsetime = 10000; // initialize to an invalid value + timingtime = lastmsgtime = lastredrawtime = TickCount.Time(); + while (Get_Mouse_State() > 0) Show_Mouse(); + + + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + // init font variables + + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ +#ifdef FORCE_WINSOCK + if (Winsock.Get_Connected()){ + Draw_Caption (TXT_HOST_INTERNET_GAME, d_dialog_x, d_dialog_y, d_dialog_w); + }else{ + Draw_Caption (TXT_HOST_SERIAL_GAME, d_dialog_x, d_dialog_y, d_dialog_w); + } +#else + Draw_Caption (TXT_HOST_SERIAL_GAME, d_dialog_x, d_dialog_y, d_dialog_w); +#endif //FORCE_WINSOCK + + Fancy_Text_Print(TXT_YOUR_NAME, + d_name_x - 5*factor, d_name_y + 1*factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_SIDE_COLON, + d_gdi_x - 5*factor, d_gdi_y + 1*factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print (TXT_START_CREDITS_COLON, d_credits_x - 5*factor, d_credits_y + 1*factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_COLOR_COLON, + cbox_x[0] - 5*factor, d_color_y + 1*factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_SCENARIOS, + d_scenariolist_x + (d_scenariolist_w / 2), + d_scenariolist_y - d_txt6_h, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print (TXT_COUNT, d_count_x - 3*factor, d_count_y, CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_RIGHT); + + Fancy_Text_Print (TXT_LEVEL, d_level_x - 3*factor, d_level_y, CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_RIGHT); + } + + /*.................................................................. + Draw the color boxes + ..................................................................*/ + if (display >= REDRAW_COLORS) { + for (i = 0; i < MAX_MPLAYER_COLORS; i++) { + LogicPage->Fill_Rect (cbox_x[i] + 1*factor, d_color_y + 1*factor, + cbox_x[i] + 1*factor + d_color_w - 2*factor, d_color_y + 1*factor + d_color_h - 2*factor, + MPlayerGColors[i]); + + if (i == MPlayerColorIdx) { + Draw_Box(cbox_x[i], d_color_y, d_color_w, d_color_h, + BOXSTYLE_GREEN_DOWN, false); + } else { + Draw_Box(cbox_x[i], d_color_y, d_color_w, d_color_h, + BOXSTYLE_GREEN_RAISED, false); + } + } + } + + /*.................................................................. + Draw the message: + - Erase an old message first + ..................................................................*/ + if (display >= REDRAW_MESSAGE) { + Draw_Box(d_message_x, d_message_y, d_message_w, d_message_h, + BOXSTYLE_GREEN_BORDER, true); + Messages.Draw(); + + LogicPage->Fill_Rect (d_dialog_x + 2*factor, + d_opponent_y, + d_dialog_x + d_dialog_w - 4*factor, + d_opponent_y + d_txt6_h, + BLACK); + + if (parms_received) { + if (oppscorescreen) { + sprintf(txt,"%s",Text_String(TXT_WAITING_FOR_OPPONENT)); + + int txtwidth = String_Pixel_Width( txt ); + + Fancy_Text_Print (txt, d_dialog_cx - (txtwidth / 2), + d_opponent_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } else { + Fancy_Text_Print (TXT_OPPONENT_COLON, d_opponent_x - 3*factor, + d_opponent_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (TheirHouse == HOUSE_GOOD) { + sprintf(txt,"%s %s",TheirName,Text_String(TXT_G_D_I)); + } else { + sprintf(txt,"%s %s",TheirName,Text_String(TXT_N_O_D)); + } + + Fancy_Text_Print (txt, d_opponent_x, + d_opponent_y, MPlayerTColors[TheirColor], TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + } + + sprintf( txt, "%d ", MPlayerUnitCount ); + Fancy_Text_Print (txt, d_count_x + d_count_w + 3*factor, + d_count_y, CC_GREEN, BLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (BuildLevel <= MPLAYER_BUILD_LEVEL_MAX) { + sprintf(txt, "%d ", BuildLevel); + } else { + sprintf(txt, "**"); + } + Fancy_Text_Print (txt, d_level_x + d_level_w + 3*factor, + d_level_y, CC_GREEN, BLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Flag_List_To_Redraw(); + } + + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + /*------------------------------------------------------------------ + User clicks on a color button + ------------------------------------------------------------------*/ + case KN_LMOUSE: + if (_Kbd->MouseQX > cbox_x[0] && + _Kbd->MouseQX < (cbox_x[MAX_MPLAYER_COLORS - 1] + d_color_w) && + _Kbd->MouseQY > d_color_y && + _Kbd->MouseQY < (d_color_y + d_color_h)) { + if (!ready_to_go){ + MPlayerPrefColor = (_Kbd->MouseQX - cbox_x[0]) / d_color_w; + MPlayerColorIdx = MPlayerPrefColor; + display = REDRAW_COLORS; + + name_edt.Set_Color (MPlayerTColors[MPlayerColorIdx]); + name_edt.Flag_To_Redraw(); + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + changed = 1; + } + } + break; + + /*------------------------------------------------------------------ + User edits the name field; retransmit new game options + ------------------------------------------------------------------*/ + case (BUTTON_NAME | KN_BUTTON): + if (!ready_to_go){ + credit_edt.Clear_Focus(); + credit_edt.Flag_To_Redraw(); + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + changed = 1; + } + break; + + /*------------------------------------------------------------------ + House Buttons: set the player's desired House + ------------------------------------------------------------------*/ + case (BUTTON_GDI | KN_BUTTON): + if (!ready_to_go){ + MPlayerHouse = HOUSE_GOOD; + gdibtn.Turn_On(); + nodbtn.Turn_Off(); + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + } + break; + + case (BUTTON_NOD | KN_BUTTON): + if (!ready_to_go){ + MPlayerHouse = HOUSE_BAD; + gdibtn.Turn_Off(); + nodbtn.Turn_On(); + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + } + break; + + /*------------------------------------------------------------------ + User edits the credits value; retransmit new game options + ------------------------------------------------------------------*/ + case (BUTTON_CREDITS | KN_BUTTON): + if (!ready_to_go){ + name_edt.Clear_Focus(); + name_edt.Flag_To_Redraw(); + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + } + break; + + /*------------------------------------------------------------------ + New Scenario selected. + ------------------------------------------------------------------*/ + case (BUTTON_SCENARIOLIST | KN_BUTTON): + if (scenariolist.Current_Index() != ScenarioIdx && !ready_to_go) { + ScenarioIdx = scenariolist.Current_Index(); + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + } + break; + + /*------------------------------------------------------------------ + User adjusts max # units + ------------------------------------------------------------------*/ + case (BUTTON_COUNT | KN_BUTTON): + if (!ready_to_go){ + MPlayerUnitCount = countgauge.Get_Value() + MPlayerCountMin[MPlayerBases]; + if (display < REDRAW_MESSAGE) display = REDRAW_MESSAGE; + transmit = 1; + } + break; + + /*------------------------------------------------------------------ + User adjusts build level + ------------------------------------------------------------------*/ + case (BUTTON_LEVEL | KN_BUTTON): + if (!ready_to_go){ + BuildLevel = levelgauge.Get_Value() + 1; + if (BuildLevel > MPLAYER_BUILD_LEVEL_MAX) // if it's pegged, max it out + BuildLevel = MPLAYER_BUILD_LEVEL_MAX; + if (display < REDRAW_MESSAGE) display = REDRAW_MESSAGE; + transmit = 1; + } + break; + + /*------------------------------------------------------------------ + Toggle bases + ------------------------------------------------------------------*/ + case (BUTTON_BASES | KN_BUTTON): + if (!ready_to_go){ + if (MPlayerBases) { + MPlayerBases = 0; + basesbtn.Turn_Off(); + basesbtn.Set_Text(TXT_BASES_OFF); + MPlayerUnitCount = Fixed_To_Cardinal (MPlayerCountMax[0]-MPlayerCountMin[0], + Cardinal_To_Fixed(MPlayerCountMax[1]-MPlayerCountMin[1], + MPlayerUnitCount-MPlayerCountMin[1])) + MPlayerCountMin[0]; + } else { + MPlayerBases = 1; + basesbtn.Turn_On(); + basesbtn.Set_Text(TXT_BASES_ON); + MPlayerUnitCount = Fixed_To_Cardinal (MPlayerCountMax[1]-MPlayerCountMin[1], + Cardinal_To_Fixed(MPlayerCountMax[0]-MPlayerCountMin[0], + MPlayerUnitCount-MPlayerCountMin[0])) + MPlayerCountMin[1]; + } + MPlayerCredits = atoi(credbuf); + countgauge.Set_Maximum(MPlayerCountMax[MPlayerBases] - MPlayerCountMin[MPlayerBases]); + countgauge.Set_Value(MPlayerUnitCount - MPlayerCountMin[MPlayerBases]); + strcpy (MPlayerName, namebuf); + transmit = 1; + display = REDRAW_ALL; + } + break; + + /*------------------------------------------------------------------ + Toggle tiberium + ------------------------------------------------------------------*/ + case (BUTTON_TIBERIUM | KN_BUTTON): + if (!ready_to_go){ + if (MPlayerTiberium) { + MPlayerTiberium = 0; + Special.IsTGrowth = 0; + Special.IsTSpread = 0; + tiberiumbtn.Turn_Off(); + tiberiumbtn.Set_Text(TXT_TIBERIUM_OFF); + } else { + MPlayerTiberium = 1; + Special.IsTGrowth = 1; + Special.IsTSpread = 1; + tiberiumbtn.Turn_On(); + tiberiumbtn.Set_Text(TXT_TIBERIUM_ON); + } + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + } + break; + + /*------------------------------------------------------------------ + Toggle goodies + ------------------------------------------------------------------*/ + case (BUTTON_GOODIES | KN_BUTTON): + if (!ready_to_go){ + if (MPlayerGoodies) { + MPlayerGoodies = 0; + goodiesbtn.Turn_Off(); + goodiesbtn.Set_Text(TXT_CRATES_OFF); + } else { + MPlayerGoodies = 1; + goodiesbtn.Turn_On(); + goodiesbtn.Set_Text(TXT_CRATES_ON); + } + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + } + break; + + /*------------------------------------------------------------------ + Toggle ghosts + ------------------------------------------------------------------*/ + case (BUTTON_GHOSTS | KN_BUTTON): + if (!ready_to_go){ + if (!MPlayerGhosts && !Special.IsCaptureTheFlag) { // ghosts OFF => ghosts ON + MPlayerGhosts = 1; + Special.IsCaptureTheFlag = 0; + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_AI_PLAYERS_ON); + } + else if (MPlayerGhosts) { // ghosts ON => capture-flag + MPlayerGhosts = 0; + Special.IsCaptureTheFlag = 1; + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_CAPTURE_THE_FLAG); + } + else if (Special.IsCaptureTheFlag) { // capture-flag => AI OFF + MPlayerGhosts = 0; + Special.IsCaptureTheFlag = 0; + ghostsbtn.Turn_Off(); + ghostsbtn.Set_Text(TXT_AI_PLAYERS_OFF); + } + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + } + break; + + /*------------------------------------------------------------------ + OK: exit loop with true status + ------------------------------------------------------------------*/ + case (BUTTON_OK | KN_BUTTON): + if (!ready_to_go){ + // + // make sure we got a game options packet from the other player + // + if (gameoptions) { + //rc = true; + //process = false; + + // force transmitting of game options packet one last time + + + + SendPacket.Command = SERIAL_READY_TO_GO; + SendPacket.ID = ModemGameToPlay; + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + + while ( ( NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_SENDING_TIMEOUT) )){ + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + NullModem.Service(); + Keyboard::Check(); //Make sure the message loop gets called + } + + ready_to_go = true; + ready_time.Set(120, true); + + transmit = 1; + transmittime = 0; + + } else { + CCMessageBox().Process (TXT_ONLY_ONE,TXT_OOPS,NULL); + display = REDRAW_ALL; + } + } + break; + + /*------------------------------------------------------------------ + CANCEL: send a SIGN_OFF, bail out with error code + ------------------------------------------------------------------*/ + case (KN_ESC): + if (!ready_to_go){ + if (Messages.Get_Edit_Buf() != NULL) { + Messages.Input(input); + if (display < REDRAW_MESSAGE) display = REDRAW_MESSAGE; + break; + } + } + case (BUTTON_CANCEL | KN_BUTTON): + if (!ready_to_go){ + process = false; + rc = false; + } + break; + + /*------------------------------------------------------------------ + Default: manage the inter-player messages + ------------------------------------------------------------------*/ + default: + if (ready_to_go) break; + + /*............................................................... + F4/SEND/'M' = send a message + ...............................................................*/ + if (Messages.Get_Edit_Buf()==NULL) { + if (input == KN_M || input==(BUTTON_SEND | KN_BUTTON) || + input == KN_F4) { + memset (txt, 0, 80); + + strcpy(txt,Text_String(TXT_MESSAGE)); // "Message:" + + Messages.Add_Edit (MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, txt, d_message_w-70*factor); + if (display < REDRAW_MESSAGE) display = REDRAW_MESSAGE; + + credit_edt.Clear_Focus(); + credit_edt.Flag_To_Redraw(); + name_edt.Clear_Focus(); + name_edt.Flag_To_Redraw(); + + break; + } + } else { + if ( input == (BUTTON_SEND | KN_BUTTON) ) { + input = KN_RETURN; + } + } + + /*............................................................... + Manage the message system (get rid of old messages) + ...............................................................*/ + if (Messages.Manage()) { + if (display < REDRAW_MESSAGE) display = REDRAW_MESSAGE; + } + + /*............................................................... + Service keyboard input for any message being edited. + ...............................................................*/ + i = Messages.Input(input); + + /*............................................................... + If 'Input' returned 1, it means refresh the message display. + ...............................................................*/ + if (i==1) { + Messages.Draw(); + } + + /*............................................................... + If 'Input' returned 2, it means redraw the message display. + ...............................................................*/ + else if (i==2) { + if (display < REDRAW_MESSAGE) display = REDRAW_MESSAGE; + } + + /*............................................................... + If 'input' returned 3, it means send the current message. + ...............................................................*/ + else if (i==3) { + long actual_message_size; + char *the_string; + + sent_so_far = 0; + magic_number = MESSAGE_HEAD_MAGIC_NUMBER; + message_length = strlen(Messages.Get_Edit_Buf()); + crc = (unsigned short) + (Calculate_CRC(Messages.Get_Edit_Buf(), message_length) &0xffff); + + while (sent_so_far < message_length){ + + SendPacket.Command = SERIAL_MESSAGE; + strcpy (SendPacket.Name, MPlayerName); + SendPacket.ID = Build_MPlayerID(MPlayerColorIdx, MPlayerHouse); + memcpy (SendPacket.Message, Messages.Get_Edit_Buf()+sent_so_far, COMPAT_MESSAGE_LENGTH-5); + + /* + ** Steve I's stuff for splitting message on word boundries + */ + actual_message_size = COMPAT_MESSAGE_LENGTH - 5; + + /* Start at the end of the message and find a space with 10 chars. */ + the_string = SendPacket.Message; + while ( (COMPAT_MESSAGE_LENGTH -5) -actual_message_size < 10 && + the_string[actual_message_size] != ' '){ + --actual_message_size; + } + if ( the_string[actual_message_size] == ' ' ){ + + /* Now delete the extra characters after the space (they musnt print) */ + for ( int i=0 ; i< (COMPAT_MESSAGE_LENGTH-5) - actual_message_size; i++ ){ + the_string[i + actual_message_size] = 0xff; + } + }else{ + actual_message_size = COMPAT_MESSAGE_LENGTH - 5; + } + + *(SendPacket.Message + COMPAT_MESSAGE_LENGTH-5) = 0; + *((unsigned short*)(SendPacket.Message + COMPAT_MESSAGE_LENGTH-4)) = magic_number; + *((unsigned short*)(SendPacket.Message + COMPAT_MESSAGE_LENGTH-2)) = crc; + + /*.................................................................. + Send the message + ..................................................................*/ + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + NullModem.Service(); + + /*.................................................................. + Add the message to our own screen + ..................................................................*/ + sprintf(txt, Text_String (TXT_FROM), MPlayerName, SendPacket.Message); + Messages.Add_Message (txt, MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, magic_number, crc); + + magic_number++; + sent_so_far += actual_message_size; //COMPAT_MESSAGE_LENGTH-5; + } + + if (display < REDRAW_MESSAGE) display = REDRAW_MESSAGE; + } /* end of send message */ + + } /* end of input processing */ + + /*--------------------------------------------------------------------- + Detect editing of the credits buffer, transmit new values to players + ---------------------------------------------------------------------*/ + if (atoi(credbuf) != old_cred) { + old_cred = Bound(atoi(credbuf), 0, 9999); + MPlayerCredits = old_cred; + transmit = 1; + sprintf(credbuf, "%d", MPlayerCredits); + credit_edt.Set_Text(credbuf, CREDITSBUF_MAX); + } + + /*--------------------------------------------------------------------- + Detect editing of the name buffer, transmit new values to players + ---------------------------------------------------------------------*/ + if (strcmp (namebuf, MPlayerName)) { + strcpy (MPlayerName, namebuf); + transmit = 1; + changed = 1; + } + + /*--------------------------------------------------------------------- + If our Transmit flag is set, we need to send out a game option packet. + This message requires an ACK. The first time through the loop, transmit + should be set, so we send out our default options; we'll then send + any changes we make to the defaults. + ---------------------------------------------------------------------*/ + if (transmit && (TickCount.Time() - transmittime) > PACKET_RETRANS_TIME) { + SendPacket.Command = SERIAL_GAME_OPTIONS; + strcpy (SendPacket.Name, MPlayerName); +#ifdef PATCH + if (IsV107) { + SendPacket.Version = 1; + } else { + SendPacket.Version = 2; + } +#else + SendPacket.Version = Version_Number(); +#endif + SendPacket.House = MPlayerHouse; + SendPacket.Color = MPlayerColorIdx; + + SendPacket.Scenario = MPlayerFilenum[ScenarioIdx]; + + SendPacket.Credits = MPlayerCredits; + SendPacket.IsBases = MPlayerBases; + SendPacket.IsTiberium = MPlayerTiberium; + SendPacket.IsGoodies = MPlayerGoodies; + SendPacket.IsGhosties = MPlayerGhosts; + SendPacket.BuildLevel = BuildLevel; + SendPacket.UnitCount = MPlayerUnitCount; + SendPacket.Seed = Seed; + SendPacket.Special = Special; + SendPacket.GameSpeed = Options.GameSpeed; + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + transmittime = TickCount.Time(); + transmit = 0; + + starttime = TickCount.Time(); + while ( ( NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_SENDING_TIMEOUT) )){ + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + NullModem.Service(); + Keyboard::Check(); //Make sure the message loop gets called + } + } + + // + // send a timing packet if enough time has gone by. + // + if ( (TickCount.Time() - timingtime) > PACKET_TIMING_TIMEOUT) { + SendPacket.Command = SERIAL_TIMING; + SendPacket.ResponseTime = NullModem.Response_Time(); + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 0); + timingtime = TickCount.Time(); + } + + /*--------------------------------------------------------------------- + Check for an incoming message + ---------------------------------------------------------------------*/ + while (NullModem.Get_Message (&ReceivePacket, &packetlen) > 0) { +// Smart_Printf( "received packet of length %d\n", packetlen ); + + lastmsgtime = TickCount.Time(); + msg_timeout = 600; // reset timeout value to 10 seconds + // (only the 1st time through is 20 seconds) + + // are we getting our own packets back?? + + if (ReceivePacket.Command >= SERIAL_CONNECT && + ReceivePacket.Command < SERIAL_LAST_COMMAND && + ReceivePacket.Command != SERIAL_MESSAGE && + ReceivePacket.ID == ModemGameToPlay) { + + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + break; + } + + event = (EventClass *)&ReceivePacket; + if (event->Type <= EventClass::FRAMEINFO) { + if ( (TickCount.Time() - lastredrawtime) > PACKET_REDRAW_TIME) { + lastredrawtime = TickCount.Time(); + oppscorescreen = true; + +if (display != REDRAW_ALL) { + display = REDRAW_MESSAGE; +} + +// display = REDRAW_MESSAGE; + parms_received = 1; + } + } else { + switch ( ReceivePacket.Command ) { + /*.................................................................. + Sign-off: Give the other machine time to receive my ACK, display a + message, and exit. + ..................................................................*/ + case (SERIAL_SIGN_OFF): +// Smart_Printf( "received sign off\n" ); + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < 60) + NullModem.Service(); + CCMessageBox().Process(TXT_USER_SIGNED_OFF); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + recsignedoff = true; + break; + + /*.................................................................. + Game Options: Store the other machine's name, color & house; + If they've picked the same color as myself, re-transmit my settings + to force him to choose a different color. (Com_Show_Scenario_Dialog + is responsible for ensuring the colors are different.) + ..................................................................*/ + case (SERIAL_GAME_OPTIONS): +// Smart_Printf( "received game options\n" ); + oppscorescreen = false; + gameoptions = true; + strcpy (TheirName, ReceivePacket.Name); + TheirColor = ReceivePacket.Color; + TheirHouse = ReceivePacket.House; + transmit = 1; + + parms_received = 1; +if (display != REDRAW_ALL) { + display = REDRAW_MESSAGE; +} +// display = REDRAW_MESSAGE; + + /*............................................................... + Check the version number of the other system. + ...............................................................*/ +#ifdef PATCH + if (IsV107) { + version = 1; + } else { + version = 2; + } +#else + version = Version_Number(); +#endif + if (ReceivePacket.Version > version) { + CCMessageBox().Process (TXT_YOURGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } else { + if (ReceivePacket.Version < version) { + CCMessageBox().Process (TXT_DESTGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } + } + break; + + /*.................................................................. + Incoming message: add to our list + ..................................................................*/ + case (SERIAL_MESSAGE): +// Smart_Printf( "received serial message\n" ); + oppscorescreen = false; + sprintf(txt, Text_String (TXT_FROM), ReceivePacket.Name, + ReceivePacket.Message); + magic_number = *((unsigned short*)(ReceivePacket.Message + COMPAT_MESSAGE_LENGTH-4)); + crc = *((unsigned short*)(ReceivePacket.Message + COMPAT_MESSAGE_LENGTH-2)); + Messages.Add_Message (txt, + MPlayerTColors[MPlayerID_To_ColorIndex(ReceivePacket.ID)], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, magic_number, crc); +if (display != REDRAW_ALL) { + display = REDRAW_MESSAGE; +} +// display = REDRAW_MESSAGE; + break; + + // + // get their response time + // + case (SERIAL_TIMING): +// Smart_Printf( "received timing\n" ); + oppscorescreen = false; + theirresponsetime = ReceivePacket.ResponseTime; + + if ( !gameoptions ) { + + // retransmit of game options packet again + transmit = 1; + } + break; + + // + // print msg waiting for opponent + // + case (SERIAL_SCORE_SCREEN): +// Smart_Printf( "received score screen\n" ); + oppscorescreen = true; +if (display != REDRAW_ALL) { + display = REDRAW_MESSAGE; +} +// display = REDRAW_MESSAGE; + parms_received = 1; + break; + + default: +// Smart_Printf( "received unknown command %X\n", ReceivePacket.Command ); + break; + } + } + } + + // if we haven't received a msg for 10 seconds exit + + if ( (TickCount.Time() - lastmsgtime) > msg_timeout) { + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + } + + /*--------------------------------------------------------------------- + Service the connection + ---------------------------------------------------------------------*/ + NullModem.Service(); + + /* + ** If user has clicked 'GO' and the timeout has elapsed then quit the loop + */ + if ( ready_to_go && ready_time.Time() == 0 ){ + rc = 1; + process = false; + } + + } /* end of while */ + + /*------------------------------------------------------------------------ + Sort player ID's, so we can execute commands in the same order on both + machines. + ------------------------------------------------------------------------*/ + if (rc) { + /*..................................................................... + Set the number of players in this game, and my ID + .....................................................................*/ + MPlayerCount = 2; + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + TheirID = Build_MPlayerID (TheirColor,TheirHouse); + + /*..................................................................... + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + .....................................................................*/ + if (TheirID < MPlayerLocalID) { + MPlayerID[0] = TheirID; + MPlayerID[1] = MPlayerLocalID; + strcpy (MPlayerNames[0], TheirName); + strcpy (MPlayerNames[1], MPlayerName); + } else { + MPlayerID[0] = MPlayerLocalID; + MPlayerID[1] = TheirID; + strcpy (MPlayerNames[0], MPlayerName); + strcpy (MPlayerNames[1], TheirName); + } + + /*..................................................................... + Get the scenario filename + .....................................................................*/ + Scenario = MPlayerFilenum[ScenarioIdx]; + + /*..................................................................... + Send all players the GO packet. + .....................................................................*/ + SendPacket.Command = SERIAL_GO; + SendPacket.ResponseTime = NullModem.Response_Time(); + if ( theirresponsetime == 10000 ) { +// Mono_Clear_Screen(); +// Smart_Printf( "Did not receive their response time!!!!!!!\n" ); +// Get_Key(); + } else { + if (SendPacket.ResponseTime < theirresponsetime) { + SendPacket.ResponseTime = theirresponsetime; + } + } + + // + // calculated one way delay for a packet and overall delay to execute + // a packet + // + MPlayerMaxAhead = MAX( (SendPacket.ResponseTime / 8), 2); +char flip[128]; +sprintf (flip, "C&C95 - MaxAhead set to %d frames\n", MPlayerMaxAhead); +CCDebugString (flip); + + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + while ( ( NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_SENDING_TIMEOUT) )){ + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + NullModem.Service(); + Keyboard::Check(); //Make sure the message loop gets called + } + + // clear queue to keep from doing any resends + NullModem.Init_Send_Queue(); + + } else { + if ( !recsignedoff ) { + /*..................................................................... + Broadcast my sign-off over my network + .....................................................................*/ + SendPacket.Command = SERIAL_SIGN_OFF; + SendPacket.Color = MPlayerLocalID; // use Color for ID + SendPacket.ID = ModemGameToPlay; + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + while ( (NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_CANCEL_TIMEOUT) )){ + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + if ( NullModem.Get_Message( &ReceivePacket, &packetlen ) > 0) { + + // are we getting our own packets back?? + + if (ReceivePacket.Command == SERIAL_SIGN_OFF + && ReceivePacket.ID == ModemGameToPlay) { + + // exit while + break; + } + } + + NullModem.Service(); + } + } + + Shutdown_Modem(); + } + + /*------------------------------------------------------------------------ + Clear all lists + ------------------------------------------------------------------------*/ + while (scenariolist.Count()) { + scenariolist.Remove_Item(scenariolist.Get_Item(0)); + } + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + /*------------------------------------------------------------------------ + Save any changes made to our options + ------------------------------------------------------------------------*/ + if (changed) { + Write_MultiPlayer_Settings (); + } + + return(rc); + +} /* end of Com_Scenario_Dialog */ + + +/*********************************************************************************************** + * Com_Show_Scenario_Dialog -- Serial game scenario selection dialog * + * * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Serial Game ³ * + * ³ ³ * + * ³ Your Name: __________ ³ * + * ³ House: [GDI] [NOD] ³ * + * ³ Desired Color: [ ][ ][ ][ ] ³ * + * ³ ³ * + * ³ Opponent: Name ³ * + * ³ Scenario: Description ³ * + * ³ Credits: xxxx ³ * + * ³ Bases: ON ³ * + * ³ Crates: ON ³ * + * ³ Tiberium: ON ³ * + * ³ Ghosts: ON ³ * + * ³ ³ * + * ³ [Cancel] ³ * + * ³ ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ * + * ³ ³ ³ ³ * + * ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ [Send Message] ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = success, false = cancel * + * * + * WARNINGS: * + * MPlayerName & MPlayerGameName must contain this player's name. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +int Com_Show_Scenario_Dialog(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 306*factor; // dialog width + int d_dialog_h = 187*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200*factor - d_dialog_h) / 2); // dialog y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 6*factor+1; // ht of 6-pt text + int d_margin1 = 5*factor; // margin width/height + int d_margin2 = 2*factor; // margin width/height + + int d_name_w = 70*factor; + int d_name_h = 9*factor; + int d_name_x = d_dialog_cx; + int d_name_y = d_dialog_y + d_margin1 + d_txt6_h + d_txt6_h; + + int d_gdi_w = 30*factor; + int d_gdi_h = 9*factor; + int d_gdi_x = d_dialog_cx; + int d_gdi_y = d_name_y + d_name_h + d_margin2; + + int d_nod_w = 30*factor; + int d_nod_h = 9*factor; + int d_nod_x = d_gdi_x + d_gdi_w + d_margin2; + int d_nod_y = d_gdi_y; + + int d_color_w = 10*factor; + int d_color_h = 9*factor; + int d_color_y = d_gdi_y + d_gdi_h + d_margin2; + + int d_opponent_y = d_color_y + d_color_h + d_margin1; + int d_scenario_y = d_opponent_y + d_txt6_h; + int d_credits_y = d_scenario_y + d_txt6_h; + int d_count_y = d_credits_y + d_txt6_h; + int d_level_y = d_count_y + d_txt6_h; + int d_bases_y = d_level_y + d_txt6_h; + int d_goodies_y = d_bases_y + d_txt6_h; + int d_tiberium_y = d_goodies_y + d_txt6_h; + int d_ghosts_y = d_tiberium_y + d_txt6_h; + + int d_cancel_w = 45*factor; + int d_cancel_h = 9*factor; + int d_cancel_x = d_dialog_cx - (d_cancel_w / 2); + int d_cancel_y = d_ghosts_y + d_txt6_h + d_margin1; + + int d_message_w = d_dialog_w - (d_margin1 * 2); + int d_message_h = 34*factor; + int d_message_x = d_dialog_x + d_margin1; + int d_message_y = d_cancel_y + d_cancel_h + d_margin1; + + int d_send_w = 80*factor; + int d_send_h = 9*factor; + int d_send_x = d_dialog_cx - (d_send_w / 2); + int d_send_y = d_message_y + d_message_h + d_margin2; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_NAME = 100, + BUTTON_GDI, + BUTTON_NOD, + BUTTON_CANCEL, + BUTTON_SEND, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_MESSAGE, + REDRAW_COLORS, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + BOOL process = true; // process while true + KeyNumType input; + + char namebuf[MPLAYER_NAME_MAX] = {0}; // buffer for player's name + int transmit; // 1 = re-transmit new game options + int first; // 1 = no packets received yet + int cbox_x[] = { d_dialog_cx, + d_dialog_cx + d_color_w, + d_dialog_cx + (d_color_w * 2), + d_dialog_cx + (d_color_w * 3), + d_dialog_cx + (d_color_w * 4), + d_dialog_cx + (d_color_w * 5)}; + int parms_received = 0; // 1 = game options received + int changed = 0; // 1 = user has changed an option + + int rc; + int recsignedoff = 0; + int i; + int version; + char txt[80]; + unsigned long starttime; + unsigned long timingtime; + unsigned long lastmsgtime; + unsigned long lastredrawtime; + unsigned long transmittime = 0; + int packetlen; + bool oppscorescreen = false; + bool gameoptions = false; + EventClass *event; // event ptr + unsigned long msg_timeout = 1200; // init to 20 seconds + + int message_length; + int sent_so_far; + unsigned short magic_number; + unsigned short crc; + bool ready_to_go = false; + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + EditClass name_edt (BUTTON_NAME, + namebuf, MPLAYER_NAME_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_name_x, d_name_y, d_name_w, d_name_h, EditClass::ALPHANUMERIC); + + TextButtonClass gdibtn(BUTTON_GDI, TXT_G_D_I, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_gdi_x, d_gdi_y, d_gdi_w, d_gdi_h); + + TextButtonClass nodbtn(BUTTON_NOD, TXT_N_O_D, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_nod_x, d_nod_y, d_nod_w, d_nod_h); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_cancel_x, d_cancel_y); +//#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +//#endif + + TextButtonClass sendbtn(BUTTON_SEND, TXT_SEND_MESSAGE, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_send_x, d_send_y); +//#else + d_send_x, d_send_y, d_send_w, d_send_h); +//#endif + + /* + ------------------------- Build the button list -------------------------- + */ + commands = &name_edt; + gdibtn.Add_Tail(*commands); + nodbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + sendbtn.Add_Tail(*commands); + + /* + ----------------------------- Various Inits ------------------------------ + */ + /*........................................................................ + Init player name & house + ........................................................................*/ + MPlayerColorIdx = MPlayerPrefColor; // init my preferred color + strcpy (namebuf, MPlayerName); // set my name + name_edt.Set_Text(namebuf,MPLAYER_NAME_MAX); + name_edt.Set_Color(MPlayerTColors[MPlayerColorIdx]); + + if (MPlayerHouse==HOUSE_GOOD) { + gdibtn.Turn_On(); + } else { + nodbtn.Turn_On(); + } + + Fancy_Text_Print("", 0, 0, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + transmit = 1; + first = 1; + + /*........................................................................ + Init the message display system + ........................................................................*/ + Messages.Init (d_message_x + 2*factor, d_message_y + 2*factor, 4, MAX_MESSAGE_LENGTH, d_txt6_h); + + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + extern char ModemRXString[]; + + if (strlen(ModemRXString) > 36) + ModemRXString[36] = 0; + + if (strlen(ModemRXString) > 0) + Messages.Add_Message (ModemRXString, CC_TAN, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, 0, 0); + + ModemRXString[0] = '\0'; + + /* + ---------------------------- Init Mono Output ---------------------------- + */ + #if(SHOW_MONO) + NullModem.Configure_Debug(sizeof (CommHeaderType),sizeof (SerialCommandType), + SerialPacketNames, 106); + NullModem.Mono_Debug_Print(1); + #endif + + /* + ---------------------------- Processing loop ----------------------------- + */ + NullModem.Reset_Response_Time(); // clear response time + timingtime = lastmsgtime = lastredrawtime = TickCount.Time(); + while (Get_Mouse_State() > 0) Show_Mouse(); + + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ +#ifdef FORCE_WINSOCK + if (Winsock.Get_Connected()){ + Draw_Caption (TXT_JOIN_INTERNET_GAME, d_dialog_x, d_dialog_y, d_dialog_w); + }else{ + Draw_Caption (TXT_JOIN_SERIAL_GAME, d_dialog_x, d_dialog_y, d_dialog_w); + } +#else + Draw_Caption (TXT_JOIN_SERIAL_GAME, d_dialog_x, d_dialog_y, d_dialog_w); +#endif //FORCE_WINSOCK + + Fancy_Text_Print(TXT_YOUR_NAME, + d_name_x - 5*factor, d_name_y + 1*factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_SIDE_COLON, + d_gdi_x - 5*factor, d_gdi_y + 1*factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_COLOR_COLON, + cbox_x[0] - 5*factor, d_color_y + 1*factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + } + + /*.................................................................. + Draw the color boxes + ..................................................................*/ + if (display >= REDRAW_COLORS) { + for (i = 0; i < MAX_MPLAYER_COLORS; i++) { + LogicPage->Fill_Rect (cbox_x[i] + 1*factor, d_color_y + 1*factor, + cbox_x[i] + 1*factor + d_color_w - 2*factor, d_color_y + 1*factor + d_color_h - 2*factor, + MPlayerGColors[i]); + + if (i == MPlayerColorIdx) { + Draw_Box(cbox_x[i], d_color_y, d_color_w, d_color_h, + BOXSTYLE_GREEN_DOWN, false); + } else { + Draw_Box(cbox_x[i], d_color_y, d_color_w, d_color_h, + BOXSTYLE_GREEN_RAISED, false); + } + } + } + + /*.................................................................. + Draw the message: + - Erase an old message first + ..................................................................*/ + if (display >= REDRAW_MESSAGE) { + Draw_Box(d_message_x, d_message_y, d_message_w, d_message_h, + BOXSTYLE_GREEN_BORDER, true); + Messages.Draw(); + + LogicPage->Fill_Rect( d_dialog_x + 2*factor, + d_opponent_y, + d_dialog_x + d_dialog_w - 4*factor, + d_ghosts_y + d_txt6_h, + BLACK); + + if (parms_received) { + if (oppscorescreen) { + sprintf(txt,"%s",Text_String(TXT_WAITING_FOR_OPPONENT)); + + int txtwidth = String_Pixel_Width( txt ); + + Fancy_Text_Print (txt, d_dialog_cx - (txtwidth / 2), + d_opponent_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } else { + /*............................................................ + Opponent's name + ............................................................*/ + Fancy_Text_Print (TXT_OPPONENT_COLON, d_dialog_cx - 3*factor, + d_opponent_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (TheirHouse == HOUSE_GOOD) { + sprintf(txt,"%s %s",TheirName,Text_String(TXT_G_D_I)); + } else { + sprintf(txt,"%s %s",TheirName,Text_String(TXT_N_O_D)); + } + + Fancy_Text_Print (txt, d_dialog_cx, + d_opponent_y, MPlayerTColors[TheirColor], TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Scenario description + ............................................................*/ + Fancy_Text_Print (TXT_SCENARIO_COLON, d_dialog_cx - 3*factor, + d_scenario_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (ScenarioIdx != -1) { + sprintf(txt,"%s", MPlayerScenarios[ScenarioIdx]); + + Fancy_Text_Print (txt, d_dialog_cx, + d_scenario_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } else { + strcpy(txt,Text_String(TXT_NOT_FOUND)); + + Fancy_Text_Print (txt, d_dialog_cx, + d_scenario_y, RED, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + + /*............................................................ + Credits + ............................................................*/ + Fancy_Text_Print (TXT_START_CREDITS_COLON, d_dialog_cx - 3*factor, + d_credits_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + sprintf(txt,"%d",MPlayerCredits); + Fancy_Text_Print (txt, d_dialog_cx, + d_credits_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Count + ............................................................*/ + + Fancy_Text_Print (TXT_COUNT, d_dialog_cx - 3*factor, + d_count_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + sprintf( txt, "%d ", MPlayerUnitCount ); + Fancy_Text_Print (txt, d_dialog_cx, + d_count_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Level + ............................................................*/ + + Fancy_Text_Print (TXT_LEVEL, d_dialog_cx - 3*factor, + d_level_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (BuildLevel <= MPLAYER_BUILD_LEVEL_MAX) { + sprintf(txt, "%d ", BuildLevel); + } else { + sprintf(txt, "**"); + } + Fancy_Text_Print (txt, d_dialog_cx, + d_level_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Bases status + ............................................................*/ + Fancy_Text_Print (TXT_BASES_COLON, d_dialog_cx - 3*factor, + d_bases_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (MPlayerBases) { + strcpy(txt,Text_String(TXT_ON)); + } else { + strcpy(txt,Text_String(TXT_OFF)); + } + Fancy_Text_Print (txt, d_dialog_cx, + d_bases_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Tiberium status + ............................................................*/ + Fancy_Text_Print (TXT_TIBERIUM_COLON, d_dialog_cx - 3*factor, + d_tiberium_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (MPlayerTiberium) { + strcpy(txt,Text_String(TXT_ON)); + } else { + strcpy(txt,Text_String(TXT_OFF)); + } + Fancy_Text_Print (txt, d_dialog_cx, + d_tiberium_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Goodies status + ............................................................*/ + Fancy_Text_Print (TXT_CRATES_COLON, d_dialog_cx - 3*factor, + d_goodies_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (MPlayerGoodies) { + strcpy(txt,Text_String(TXT_ON)); + } else { + strcpy(txt,Text_String(TXT_OFF)); + } + Fancy_Text_Print (txt, d_dialog_cx, + d_goodies_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Capture the flag or AI player ON/OFF + ............................................................*/ + if ( Special.IsCaptureTheFlag ) { + strcpy( txt, Text_String( TXT_CAPTURE_THE_FLAG ) ); + strcat( txt, ":" ); + Fancy_Text_Print (txt, d_dialog_cx - 3*factor, + d_ghosts_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + strcpy(txt,Text_String(TXT_ON)); + Fancy_Text_Print (txt, d_dialog_cx, + d_ghosts_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } else { + /*............................................................ + Ghost player status + ............................................................*/ + Fancy_Text_Print (TXT_AI_PLAYERS_COLON, d_dialog_cx - 3*factor, + d_ghosts_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (MPlayerGhosts) { + strcpy(txt,Text_String(TXT_ON)); + } else { + strcpy(txt,Text_String(TXT_OFF)); + } + Fancy_Text_Print (txt, d_dialog_cx, + d_ghosts_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + } + } + } + + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Flag_List_To_Redraw(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + /*------------------------------------------------------------------ + User clicks on a color button + ------------------------------------------------------------------*/ + case KN_LMOUSE: + if (_Kbd->MouseQX > cbox_x[0] && + _Kbd->MouseQX < (cbox_x[MAX_MPLAYER_COLORS - 1] + d_color_w) && + _Kbd->MouseQY > d_color_y && + _Kbd->MouseQY < (d_color_y + d_color_h)) { + if (!ready_to_go){ + /*......................................................... + Compute my preferred color as the one I clicked on. + .........................................................*/ + MPlayerPrefColor = (_Kbd->MouseQX - cbox_x[0]) / d_color_w; + changed = 1; + /*......................................................... + If 'TheirColor' is set to the other player's color, make + sure we can't pick that color. + .........................................................*/ + if (parms_received) { + if (MPlayerPrefColor == TheirColor) + break; + } + MPlayerColorIdx = MPlayerPrefColor; + + name_edt.Set_Color(MPlayerTColors[MPlayerColorIdx]); + name_edt.Flag_To_Redraw(); + display = REDRAW_COLORS; + strcpy (MPlayerName, namebuf); + transmit = 1; + } + } + break; + + /*------------------------------------------------------------------ + House Buttons: set the player's desired House + ------------------------------------------------------------------*/ + case (BUTTON_GDI | KN_BUTTON): + if (!ready_to_go){ + MPlayerHouse = HOUSE_GOOD; + gdibtn.Turn_On(); + nodbtn.Turn_Off(); + strcpy (MPlayerName, namebuf); + transmit = 1; + } + break; + + case (BUTTON_NOD | KN_BUTTON): + if (!ready_to_go){ + MPlayerHouse = HOUSE_BAD; + gdibtn.Turn_Off(); + nodbtn.Turn_On(); + strcpy (MPlayerName, namebuf); + transmit = 1; + } + break; + + /*------------------------------------------------------------------ + User edits the name value; retransmit + ------------------------------------------------------------------*/ + case (BUTTON_NAME | KN_BUTTON): + if (!ready_to_go){ + strcpy (MPlayerName, namebuf); + transmit = 1; + changed = 1; + } + break; + + /*------------------------------------------------------------------ + CANCEL: send a SIGN_OFF, bail out with error code + ------------------------------------------------------------------*/ + case (KN_ESC): + if (!ready_to_go){ + if (Messages.Get_Edit_Buf() != NULL) { + Messages.Input(input); + display = REDRAW_MESSAGE; + break; + } + } + case (BUTTON_CANCEL | KN_BUTTON): + if (!ready_to_go){ + process = false; + rc = false; + } + break; + + /*------------------------------------------------------------------ + Default: manage the inter-player messages + ------------------------------------------------------------------*/ + default: + if (!ready_to_go){ + + /*............................................................... + F4/SEND/'M' = send a message + ...............................................................*/ + if (Messages.Get_Edit_Buf()==NULL) { + if (input == KN_M || input==(BUTTON_SEND | KN_BUTTON) || + input == KN_F4) { + memset (txt, 0, 80); + + strcpy(txt,Text_String(TXT_MESSAGE)); // "Message:" + + Messages.Add_Edit (MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, txt, d_message_w-70*factor); + display = REDRAW_MESSAGE; + + name_edt.Clear_Focus(); + name_edt.Flag_To_Redraw(); + + break; + } + } else { + if ( input == (BUTTON_SEND | KN_BUTTON) ) { + input = KN_RETURN; + } + } + + /*............................................................... + Manage the message system (get rid of old messages) + ...............................................................*/ + if (Messages.Manage()) { + display = REDRAW_MESSAGE; + } + + /*............................................................... + Re-draw the messages & service keyboard input for any message + being edited. + ...............................................................*/ + i = Messages.Input(input); + + /*............................................................... + If 'Input' returned 1, it means refresh the message display. + ...............................................................*/ + if (i==1) { + Messages.Draw(); + } else { + + /*............................................................... + If 'Input' returned 2, it means redraw the message display. + ...............................................................*/ + if (i==2) { + display = REDRAW_MESSAGE; + } else { + + /*............................................................... + If 'input' returned 3, it means send the current message. + ...............................................................*/ + if (i==3) { + long actual_message_size; + char *the_string; + + sent_so_far = 0; + magic_number = MESSAGE_HEAD_MAGIC_NUMBER; + message_length = strlen(Messages.Get_Edit_Buf()); + crc = (unsigned short) + (Calculate_CRC(Messages.Get_Edit_Buf(), message_length) &0xffff); + + while (sent_so_far < message_length){ + SendPacket.Command = SERIAL_MESSAGE; + strcpy (SendPacket.Name, MPlayerName); + SendPacket.ID = Build_MPlayerID(MPlayerColorIdx, MPlayerHouse); + memcpy (SendPacket.Message, Messages.Get_Edit_Buf()+sent_so_far, COMPAT_MESSAGE_LENGTH-5); + + /* + ** Steve I's stuff for splitting message on word boundries + */ + actual_message_size = COMPAT_MESSAGE_LENGTH - 5; + + /* Start at the end of the message and find a space with 10 chars. */ + the_string = GPacket.Message.Buf; + while ( (COMPAT_MESSAGE_LENGTH -5) -actual_message_size < 10 && + the_string[actual_message_size] != ' '){ + --actual_message_size; + } + if ( the_string[actual_message_size] == ' ' ){ + + /* Now delete the extra characters after the space (they musnt print) */ + for ( int i=0 ; i< (COMPAT_MESSAGE_LENGTH-5) - actual_message_size; i++ ){ + the_string[i + actual_message_size] = 0xff; + } + }else{ + actual_message_size = COMPAT_MESSAGE_LENGTH - 5; + } + + *(SendPacket.Message + COMPAT_MESSAGE_LENGTH-5) = 0; + *((unsigned short*)(SendPacket.Message + COMPAT_MESSAGE_LENGTH-4)) = magic_number; + *((unsigned short*)(SendPacket.Message + COMPAT_MESSAGE_LENGTH-2)) = crc; + + /*.................................................................. + Send the message + ..................................................................*/ + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + NullModem.Service(); + + /*.................................................................. + Add the message to our own screen + ..................................................................*/ + sprintf(txt, Text_String (TXT_FROM), MPlayerName, SendPacket.Message); + Messages.Add_Message (txt, MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, magic_number, crc); + + magic_number++; + sent_so_far += actual_message_size; //COMPAT_MESSAGE_LENGTH-5; + } + display = REDRAW_MESSAGE; + } + } + } + } + break; + } + + /*--------------------------------------------------------------------- + Detect editing of the name buffer, transmit new values to players + ---------------------------------------------------------------------*/ + if (strcmp (namebuf, MPlayerName)) { + strcpy (MPlayerName, namebuf); + transmit = 1; + changed = 1; + } + + /*--------------------------------------------------------------------- + If our Transmit flag is set, we need to send out a game option packet + ---------------------------------------------------------------------*/ + if (transmit && (TickCount.Time() - transmittime) > PACKET_RETRANS_TIME) { + SendPacket.Command = SERIAL_GAME_OPTIONS; + strcpy (SendPacket.Name, MPlayerName); +#ifdef PATCH + if (IsV107) { + SendPacket.Version = 1; + } else { + SendPacket.Version = 2; + } +#else + SendPacket.Version = Version_Number(); +#endif + SendPacket.House = MPlayerHouse; + SendPacket.Color = MPlayerColorIdx; + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + transmittime = TickCount.Time(); + transmit = 0; + } + + // + // send a timing packet if enough time has gone by. + // + if ( (TickCount.Time() - timingtime) > PACKET_TIMING_TIMEOUT) { + SendPacket.Command = SERIAL_TIMING; + SendPacket.ResponseTime = NullModem.Response_Time(); + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 0); + timingtime = TickCount.Time(); + } + + /*--------------------------------------------------------------------- + Check for an incoming message + ---------------------------------------------------------------------*/ + if (NullModem.Get_Message (&ReceivePacket, &packetlen) > 0) { +// Smart_Printf( "received packet of length %d\n", packetlen ); + + lastmsgtime = TickCount.Time(); + + msg_timeout = 600; + + // are we getting our own packets back?? + + if (ReceivePacket.Command >= SERIAL_CONNECT && + ReceivePacket.Command < SERIAL_LAST_COMMAND && + ReceivePacket.Command != SERIAL_MESSAGE && + ReceivePacket.ID == ModemGameToPlay) { + + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + break; + } + + event = (EventClass *)&ReceivePacket; + if (event->Type <= EventClass::FRAMEINFO) { + if ( (TickCount.Time() - lastredrawtime) > PACKET_REDRAW_TIME) { + lastredrawtime = TickCount.Time(); + oppscorescreen = true; + display = REDRAW_MESSAGE; + parms_received = 1; + } + } else { + switch ( ReceivePacket.Command ) { + + /* + ** Once the host is ready to go, we can no longer change game options. + */ + case SERIAL_READY_TO_GO: + ready_to_go = true; + break; + + /*.................................................................. + Other system signs off: Give it time to receive my ACK, then show + a message. + ..................................................................*/ + case (SERIAL_SIGN_OFF): + starttime = TickCount.Time(); + while ( (TickCount.Time() - starttime) < 60) + NullModem.Service(); + CCMessageBox().Process(TXT_USER_SIGNED_OFF); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + recsignedoff = true; + break; + + /*.................................................................. + Game Options: Store all options; check my color & game version. + ..................................................................*/ + case (SERIAL_GAME_OPTIONS): + oppscorescreen = false; + gameoptions = true; + display = REDRAW_MESSAGE; + parms_received = 1; + + strcpy (TheirName, ReceivePacket.Name); + TheirColor = ReceivePacket.Color; + TheirHouse = ReceivePacket.House; + + /*............................................................... + Make sure I don't have the same color as the other guy. + ...............................................................*/ + if (MPlayerColorIdx == TheirColor) { + + // force transmitting of game options packet + + transmit = 1; + transmittime = 0; + + MPlayerColorIdx = TheirColor + 1; + if (MPlayerColorIdx >= 6) + MPlayerColorIdx = 0; + name_edt.Set_Color(MPlayerTColors[MPlayerColorIdx]); + name_edt.Flag_To_Redraw(); + display = REDRAW_COLORS; + } + + /*............................................................... + Save scenario settings. + ...............................................................*/ + MPlayerCredits = ReceivePacket.Credits; + MPlayerBases = ReceivePacket.IsBases; + MPlayerTiberium = ReceivePacket.IsTiberium; + MPlayerGoodies = ReceivePacket.IsGoodies; + MPlayerGhosts = ReceivePacket.IsGhosties; + BuildLevel = ReceivePacket.BuildLevel; + MPlayerUnitCount = ReceivePacket.UnitCount; + Seed = ReceivePacket.Seed; + Special = ReceivePacket.Special; + Options.GameSpeed = ReceivePacket.GameSpeed; + + if (MPlayerTiberium) { + Special.IsTGrowth = 1; + Special.IsTSpread = 1; + } else { + Special.IsTGrowth = 0; + Special.IsTSpread = 0; + } + + /*............................................................... + Find the index of the scenario number; if it's not found, leave + it at -1. + ...............................................................*/ + ScenarioIdx = -1; + for (i = 0; i < MPlayerFilenum.Count(); i++) { + if (ReceivePacket.Scenario == MPlayerFilenum[i]) + ScenarioIdx = i; + } + + /*............................................................... + Check our version numbers; if they're incompatible, sign off. + ...............................................................*/ +#ifdef PATCH + if (IsV107) { + version = 1; + } else { + version = 2; + } +#else + version = Version_Number(); +#endif + if (ReceivePacket.Version > version) { + CCMessageBox().Process (TXT_YOURGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } else { + if (ReceivePacket.Version < version) { + CCMessageBox().Process (TXT_DESTGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } + } + + /*............................................................... + If this is the first game-options packet we've received, transmit + our options to him. + ...............................................................*/ + if (first) { + first = 0; + + // force transmitting of game options packet + + transmit = 1; + transmittime = 0; + } + break; + + /*.................................................................. + GO: Exit this routine with a success code. + ..................................................................*/ + case (SERIAL_GO): + // + // calculated one way delay for a packet and overall delay + // to execute a packet + // + MPlayerMaxAhead = MAX( (ReceivePacket.ResponseTime / 8), 2); +char flip[128]; +sprintf (flip, "C&C95 - MaxAhead set to %d frames\n", MPlayerMaxAhead); +CCDebugString (flip); + + process = false; + rc = true; + break; + + /*.................................................................. + Incoming message: add to our list + ..................................................................*/ + case (SERIAL_MESSAGE): + oppscorescreen = false; + sprintf(txt, Text_String (TXT_FROM), ReceivePacket.Name, + ReceivePacket.Message); + magic_number = *((unsigned short*)(ReceivePacket.Message + COMPAT_MESSAGE_LENGTH-4)); + crc = *((unsigned short*)(ReceivePacket.Message + COMPAT_MESSAGE_LENGTH-2)); + Messages.Add_Message (txt, MPlayerTColors[MPlayerID_To_ColorIndex(ReceivePacket.ID)], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, magic_number, crc); + display = REDRAW_MESSAGE; + break; + + // + // throw away timing packet + // + case (SERIAL_TIMING): + oppscorescreen = false; + break; + + // + // print msg waiting for opponent + // + case (SERIAL_SCORE_SCREEN): +// Smart_Printf( "received score screen\n" ); + oppscorescreen = true; + display = REDRAW_MESSAGE; + parms_received = 1; + break; + + default: + break; + } + } + } + + // if we haven't received a msg for 10 seconds exit + + //if ( ((TickCount.Time() - lastmsgtime) > msg_timeout) || + //(Winsock.Get_Connected() && Winsock.Get_Connection_Status == TcpipManagerClass::CONNECTION_LOST)) { + + if ( (TickCount.Time() - lastmsgtime) > msg_timeout) { + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + } + + + + + /*--------------------------------------------------------------------- + Service the connection + ---------------------------------------------------------------------*/ + NullModem.Service(); + + } /* end of while */ + + /*------------------------------------------------------------------------ + Sort player ID's, so we can execute commands in the same order on both + machines. + ------------------------------------------------------------------------*/ + if (rc) { + /*..................................................................... + Set the number of players in this game, and my ID + .....................................................................*/ + MPlayerCount = 2; + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + TheirID = Build_MPlayerID (TheirColor,TheirHouse); + + /*..................................................................... + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + .....................................................................*/ + if (TheirID < MPlayerLocalID) { + MPlayerID[0] = TheirID; + MPlayerID[1] = MPlayerLocalID; + strcpy (MPlayerNames[0], TheirName); + strcpy (MPlayerNames[1], MPlayerName); + } else { + MPlayerID[0] = MPlayerLocalID; + MPlayerID[1] = TheirID; + strcpy (MPlayerNames[0], MPlayerName); + strcpy (MPlayerNames[1], TheirName); + } + + /*..................................................................... + Get the scenario filename + .....................................................................*/ + Scenario = MPlayerFilenum[ScenarioIdx]; + + starttime = TickCount.Time(); + while ( ( NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_SENDING_TIMEOUT) )){ + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + NullModem.Service(); + Keyboard::Check(); //Make sure the message loop gets called + } + + // clear queue to keep from doing any resends + NullModem.Init_Send_Queue(); + + } else { + if ( !recsignedoff ) { + /*..................................................................... + Broadcast my sign-off over my network + .....................................................................*/ + SendPacket.Command = SERIAL_SIGN_OFF; + SendPacket.Color = MPlayerLocalID; // use Color for ID + SendPacket.ID = ModemGameToPlay; + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + while ( (NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_CANCEL_TIMEOUT) )){ + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + if ( NullModem.Get_Message( &ReceivePacket, &packetlen ) > 0) { + + // are we getting our own packets back?? + + if (ReceivePacket.Command == SERIAL_SIGN_OFF + && ReceivePacket.ID == ModemGameToPlay) { + + // exit while + break; + } + } + + NullModem.Service(); + } + } + + Shutdown_Modem(); + } + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + /*------------------------------------------------------------------------ + Save any changes made to our options + ------------------------------------------------------------------------*/ + if (changed) { + Write_MultiPlayer_Settings (); + } + + return(rc); + +} /* end of Com_Show_Scenario_Dialog */ + + + +/*************************************************************************** + * Phone_Dialog -- Lets user edit phone directory & dial * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = dial the current phone book entry, false = cancel. * + * * + * WARNINGS: * + * Serial options must have been read from CC.INI. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +static int Phone_Dialog (void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 280 *factor; // dialog width + int d_dialog_h = 150 *factor; // dialog height + int d_dialog_x = ((320 *factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200 *factor- d_dialog_h) / 2); // dialog y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 11 *factor; // ht of 6-pt text + int d_margin = 7 *factor; // margin width/height + + int d_phonelist_w = 268 *factor; + int d_phonelist_h = 87 *factor; + int d_phonelist_x = d_dialog_cx - (d_phonelist_w / 2); + int d_phonelist_y = d_dialog_y + d_margin + d_txt6_h + 11*factor; + + int d_add_w = 45*factor; + int d_add_h = 9*factor; + int d_add_x = d_dialog_cx - (d_add_w / 2) - d_margin - d_add_w; + int d_add_y = d_phonelist_y + d_phonelist_h + d_margin; + + int d_edit_w = 45*factor; + int d_edit_h = 9*factor; + int d_edit_x = d_dialog_cx - (d_edit_w / 2); + int d_edit_y = d_phonelist_y + d_phonelist_h + d_margin; + + int d_delete_w = 45*factor; + int d_delete_h = 9*factor; + int d_delete_x = d_dialog_cx + (d_delete_w / 2) + d_margin; + int d_delete_y = d_phonelist_y + d_phonelist_h + d_margin; + + int d_numedit_w = ( (PhoneEntryClass::PHONE_MAX_NUM - 1) * 6*factor) + 3*factor; + int d_numedit_h = 9*factor; + int d_numedit_x = d_dialog_cx - (d_numedit_w / 2); + int d_numedit_y = d_add_y + d_add_h + d_margin; + + int d_dial_w = 45*factor; + int d_dial_h = 9*factor; + int d_dial_x = d_dialog_cx - (d_numedit_w / 2) - d_margin - d_dial_w; + int d_dial_y = d_add_y + d_add_h + d_margin; + + int d_cancel_w = 45*factor; + int d_cancel_h = 9*factor; + int d_cancel_x = d_dialog_cx + (d_numedit_w / 2) + d_margin; + int d_cancel_y = d_add_y + d_add_h + d_margin; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_PHONELIST = 100, + BUTTON_ADD, + BUTTON_EDIT, + BUTTON_DELETE, + BUTTON_DIAL, + BUTTON_CANCEL, + BUTTON_NUMEDIT, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + BOOL process = true; // process while true + KeyNumType input; + + char phone_num[ PhoneEntryClass::PHONE_MAX_NUM ] = { 0 }; // buffer for editing phone # + int rc; + int i; + int tabs[] = {123*factor, 207*factor}; // tabs for list box + char *item; // for removing items from list box + PhoneEntryClass *p_entry; // for creating / editing phonebook entries + int changed = 0; // 1 = save changes to INI file + int firsttime = 0; + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + + ListClass phonelist(BUTTON_PHONELIST, + d_phonelist_x, d_phonelist_y, d_phonelist_w, d_phonelist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + TextButtonClass addbtn(BUTTON_ADD, TXT_ADD, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#ifdef FRENCH +// d_add_x-4, d_add_y); +//#else + d_add_x, d_add_y, d_add_w, d_add_h); +//#endif + + TextButtonClass editbtn(BUTTON_EDIT, TXT_EDIT, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_edit_x, d_edit_y, d_edit_w, d_edit_h); + + TextButtonClass deletebtn(BUTTON_DELETE, TXT_DELETE_BUTTON, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#ifdef FRENCH +// d_delete_x, d_delete_y); +//#else + d_delete_x, d_delete_y, d_delete_w, d_delete_h); +//#endif + + TextButtonClass dialbtn(BUTTON_DIAL, TXT_DIAL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +/* ###Change collision detected! C:\PROJECTS\CODE\NULLDLG.CPP... */ + d_dial_x, d_dial_y, d_dial_w, d_dial_h); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +//#if (GERMAN | FRENCH) +// d_cancel_x, d_cancel_y); +//#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +//#endif + + EditClass numedit (BUTTON_NUMEDIT, + phone_num, PhoneEntryClass::PHONE_MAX_NUM, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_numedit_x, d_numedit_y, d_numedit_w, d_numedit_h, EditClass::ALPHANUMERIC); + + /* + ------------------------- Build the button list -------------------------- + */ + commands = &phonelist; + addbtn.Add_Tail(*commands); + editbtn.Add_Tail(*commands); + deletebtn.Add_Tail(*commands); + dialbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + numedit.Add_Tail(*commands); + dialbtn.Turn_On(); + + /* + ----------------------------- Various Inits ------------------------------ + */ + /*........................................................................ + Fill in the phone directory list box + ........................................................................*/ + phonelist.Set_Tabs(tabs); + Build_Phone_Listbox(&phonelist, &numedit, phone_num); + + if (CurPhoneIdx == -1) { + firsttime = 1; + } + + /* + ---------------------------- Processing loop ----------------------------- + */ + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + // init font variables + + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_PHONE_LIST, d_dialog_x, d_dialog_y, d_dialog_w); + } + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + phonelist.Flag_To_Redraw(); + addbtn.Flag_To_Redraw(); + editbtn.Flag_To_Redraw(); + deletebtn.Flag_To_Redraw(); + dialbtn.Flag_To_Redraw(); + cancelbtn.Flag_To_Redraw(); + numedit.Flag_To_Redraw(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + if ( firsttime ) { + numedit.Set_Focus(); + numedit.Flag_To_Redraw(); + input = commands->Input(); + firsttime = 0; + } + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + /*------------------------------------------------------------------ + New phone listing selected. + ------------------------------------------------------------------*/ + case (BUTTON_PHONELIST | KN_BUTTON): + /*............................................................... + Detect a change in the selected item; update CurPhoneIdx, and + the edit box buffer. + ...............................................................*/ + if (phonelist.Current_Index() != CurPhoneIdx) { + CurPhoneIdx = phonelist.Current_Index(); + strcpy (phone_num, PhoneBook[CurPhoneIdx]->Number); + numedit.Set_Text (phone_num, PhoneEntryClass::PHONE_MAX_NUM ); + changed = 1; + } + break; + + /*------------------------------------------------------------------ + Add a new entry + ------------------------------------------------------------------*/ + case (BUTTON_ADD | KN_BUTTON): + + /*............................................................... + Allocate a new phone book entry + ...............................................................*/ + p_entry = new PhoneEntryClass(); + p_entry->Name[0] = 0; + p_entry->Number[0] = 0; + p_entry->Settings.Port = 0; + p_entry->Settings.IRQ = -1; + p_entry->Settings.Baud = -1; + p_entry->Settings.DialMethod = DIAL_TOUCH_TONE; + p_entry->Settings.InitStringIndex = 0; + p_entry->Settings.CallWaitStringIndex = CALL_WAIT_CUSTOM; + p_entry->Settings.CallWaitString[0] = 0; + + /*............................................................... + Invoke the entry editor; if user clicks Save, add the new entry + to the list, and rebuild the list box. + ...............................................................*/ + if ( Edit_Phone_Dialog( p_entry ) ) { + PhoneBook.Add (p_entry); + Build_Phone_Listbox( &phonelist, &numedit, phone_num ); + /*............................................................ + Set the current listbox index to the newly-added item. + ............................................................*/ + for (i = 0; i < PhoneBook.Count(); i++) { + if (p_entry == PhoneBook[i]) { + CurPhoneIdx = i; + strcpy (phone_num, PhoneBook[CurPhoneIdx]->Number); + numedit.Set_Text (phone_num, PhoneEntryClass::PHONE_MAX_NUM ); + phonelist.Set_Selected_Index( CurPhoneIdx ); + } + } + changed = 1; + } else { + + /*............................................................... + If the user clicked Cancel, delete the entry & keep looping. + ...............................................................*/ + delete p_entry; + } + display = REDRAW_ALL; + break; + + /*------------------------------------------------------------------ + Edit the current entry + ------------------------------------------------------------------*/ + case (BUTTON_EDIT | KN_BUTTON): + + /*............................................................... + Do nothing if no entry is selected. + ...............................................................*/ + if (CurPhoneIdx==-1) + break; + + /*............................................................... + Allocate a new entry & copy the currently-selected entry into it + ...............................................................*/ + p_entry = new PhoneEntryClass(); + (*p_entry) = (*PhoneBook[CurPhoneIdx]); + + /*............................................................... + Pass the new entry to the entry editor; if the user selects OK, + copy the data back into our phone book. Rebuild the list so + the changes show up in the list box. + ...............................................................*/ + if ( Edit_Phone_Dialog( p_entry ) ) { + (*PhoneBook[CurPhoneIdx]) = (*p_entry); + Build_Phone_Listbox(&phonelist, &numedit, phone_num); + /*............................................................ + Set the current listbox index to the newly-added item. + ............................................................*/ + for (i = 0; i < PhoneBook.Count(); i++) { + if (PhoneBook[CurPhoneIdx] == PhoneBook[i]) { + CurPhoneIdx = i; + strcpy (phone_num, PhoneBook[CurPhoneIdx]->Number); + numedit.Set_Text (phone_num, PhoneEntryClass::PHONE_MAX_NUM ); + phonelist.Set_Selected_Index( CurPhoneIdx ); + } + } + changed = 1; + } + delete p_entry; + display = REDRAW_ALL; + break; + + /*------------------------------------------------------------------ + Delete the current entry + ------------------------------------------------------------------*/ + case (BUTTON_DELETE | KN_BUTTON): + + /*............................................................... + Do nothing if no entry is selected. + ...............................................................*/ + if (CurPhoneIdx == -1) + break; + + /*............................................................... + Delete the current item & rebuild the phone listbox + ...............................................................*/ + PhoneBook.Delete (CurPhoneIdx); + Build_Phone_Listbox(&phonelist, &numedit, phone_num); + + if (CurPhoneIdx == -1) { + *phone_num = 0; + numedit.Set_Text (phone_num, PhoneEntryClass::PHONE_MAX_NUM ); + } + changed = 1; + break; + + /*------------------------------------------------------------------ + Dial the current number + ------------------------------------------------------------------*/ + case (KN_RETURN): + dialbtn.IsPressed = true; + dialbtn.Draw_Me(true); + // fall thru + + case (BUTTON_DIAL | KN_BUTTON): + + /*............................................................... + If no item is selected, just dial the number in the phone # + edit box: + - Create a new phone entry + - Copy the phone number into it + - Set settings to defaults + ...............................................................*/ + if (CurPhoneIdx == -1 || + strcmp( PhoneBook[CurPhoneIdx]->Number, phone_num) ) { + + if ( strlen(phone_num) == 0) { // do not dial + dialbtn.IsPressed = false; + dialbtn.Flag_To_Redraw(); + break; + } + + p_entry = new PhoneEntryClass(); + strcpy( p_entry->Name, "NONAME" ); + strcpy( p_entry->Number, phone_num); + p_entry->Settings.Port = 0; + p_entry->Settings.IRQ = -1; + p_entry->Settings.Baud = -1; + p_entry->Settings.DialMethod = DIAL_TOUCH_TONE; + p_entry->Settings.InitStringIndex = 0; + p_entry->Settings.CallWaitStringIndex = CALL_WAIT_CUSTOM; + p_entry->Settings.CallWaitString[0] = 0; + + PhoneBook.Add (p_entry); + Build_Phone_Listbox(&phonelist, &numedit, phone_num); + /*............................................................ + Set the current listbox index to the newly-added item. + ............................................................*/ + for (i = 0; i < PhoneBook.Count(); i++) { + if (p_entry == PhoneBook[i]) { + CurPhoneIdx = i; + } + } + changed = 1; + } + + process = false; + rc = true; + break; + + /*------------------------------------------------------------------ + CANCEL: bail out + ------------------------------------------------------------------*/ + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + process = false; + rc = false; + break; + } + + } /* end of while */ + + /*------------------------------------------------------------------------ + Save any changes we've made to the phone list or settings + ------------------------------------------------------------------------*/ + if (changed) { + Write_MultiPlayer_Settings (); + } + + /*------------------------------------------------------------------------ + Clear the list box + ------------------------------------------------------------------------*/ + while (phonelist.Count()) { + item = (char *)phonelist.Get_Item(0); + phonelist.Remove_Item(item); + delete [] item; + } + + return(rc); + +} /* end of Phone_Dialog */ + + +/*************************************************************************** + * Build_Phone_Listbox -- [re]builds the phone entry listbox * + * * + * This routine rebuilds the phone list box from scratch; it also updates * + * the contents of the phone # edit field. * + * * + * INPUT: * + * list ptr to list box * + * edit ptr to edit box * + * buf ptr to buffer for phone # * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +static void Build_Phone_Listbox (ListClass *list, EditClass *edit, char *buf) +{ + int i; + char *item; + char phonename[21]; + char phonenum[15]; + + /*........................................................................ + Clear the list + ........................................................................*/ + while (list->Count()) { + item = (char *)(list->Get_Item(0)); + list->Remove_Item(item); + delete [] item; + } + + /* + ** Now sort the phone list by name then number + */ + qsort ((void *)(&PhoneBook[0]), PhoneBook.Count(), sizeof(class PhoneEntryClass *), Phone_Compare); + + /*........................................................................ + Build the list + ........................................................................*/ + for (i = 0; i < PhoneBook.Count(); i++) { + item = new char[80]; + if ( !(strlen( PhoneBook[i]->Name )) ) { + strcpy( phonename, " " ); + } else { + strncpy( phonename, PhoneBook[i]->Name, 20 ); + phonename[21] = 0; + } + + if ( !(strlen( PhoneBook[i]->Number )) ) { + strcpy( phonenum, " " ); + } else { + if ( strlen( PhoneBook[i]->Number ) < 14) { + strcpy( phonenum, PhoneBook[i]->Number ); + } else { + strncpy( phonenum, PhoneBook[i]->Number, 12 ); + phonenum[12] = 0; + strcat( phonenum, "..." ); + } + } + + if (PhoneBook[i]->Settings.Baud != -1) { + sprintf(item,"%s\t%s\t%d", phonename, phonenum, + PhoneBook[i]->Settings.Baud); + } else { + sprintf(item,"%s\t%s\t[%s]", phonename, phonenum, + Text_String(TXT_DEFAULT)); + } + list->Add_Item(item); + } + list->Flag_To_Redraw(); + + /*........................................................................ + Init the current phone book index + ........................................................................*/ + if (list->Count() == 0 || CurPhoneIdx < -1) { + CurPhoneIdx = -1; + } else { + if (CurPhoneIdx >= list->Count() ) { + CurPhoneIdx = 0; + } + } + + /*........................................................................ + Fill in phone number edit buffer + ........................................................................*/ + if (CurPhoneIdx > -1) { + strcpy (buf, PhoneBook[CurPhoneIdx]->Number); + edit->Set_Text (buf, PhoneEntryClass::PHONE_MAX_NUM ); + list->Set_Selected_Index( CurPhoneIdx ); + } +} + + +/*************************************************************************** + * Phone_Compare -- for qsort * + * * + * INPUT: * + * p1,p2 ptrs to elements to compare * + * * + * OUTPUT: * + * 0 = same, -1 = (*p1) goes BEFORE (*p2), 1 = (*p1) goes AFTER (*p2) * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=========================================================================*/ +static int Phone_Compare (const void *p1, const void *p2) +{ + class PhoneEntryClass *pe1,*pe2; + int result; + + pe1 = *((class PhoneEntryClass **)p1); + pe2 = *((class PhoneEntryClass **)p2); + + result = strcmp( pe1->Name, pe2->Name ); + + // if strings are equal then check the phone number + + if ( !result ) { + result = strcmp( pe1->Number, pe2->Number ); + } + + return(result); +} + + +/*************************************************************************** + * Edit_Phone_Dialog -- lets user edit a phone book entry * + * * + * INPUT: * + * phone entry to edit * + * * + * OUTPUT: * + * true = OK, false = cancel * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +static int Edit_Phone_Dialog (PhoneEntryClass *phone) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 230 *factor; // dialog width + int d_dialog_h = 105*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord +// D_DIALOG_Y = ((200 - D_DIALOG_H) / 2); // dialog y-coord + int d_dialog_y = ((136*factor - d_dialog_h) / 2); // dialog y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 11 *factor; // ht of 6-pt text + int d_margin = 7 *factor; // margin width/height + + int d_name_w = ( (PhoneEntryClass::PHONE_MAX_NAME - 1) * 6) + 3 *factor; + int d_name_h = 9 *factor; + int d_name_x = d_dialog_x + (((d_dialog_w - d_name_w) * 3) / 4) - 5 *factor; + int d_name_y = d_dialog_y + 25 *factor; + + int d_number_w = ( (PhoneEntryClass::PHONE_MAX_NUM - 1) * 6) + 3 *factor; + int d_number_h = 9 *factor; + int d_number_x = d_dialog_x + (((d_dialog_w - d_number_w) * 3) / 4) - 5 *factor; + int d_number_y = d_name_y + d_name_h + d_margin; + +#if (GERMAN | FRENCH) + int d_default_w = 130 *factor; +#else + int d_default_w = 104 *factor; +#endif + int d_default_h = 9 *factor; + int d_default_x = d_dialog_cx - (d_default_w / 2); + int d_default_y = d_number_y + d_number_h + d_margin; + +#if (GERMAN | FRENCH) + int d_custom_w = 130 *factor; +#else + int d_custom_w = 100 *factor; +#endif + int d_custom_h = 9 *factor; + int d_custom_x = d_dialog_cx - (d_default_w / 2); + int d_custom_y = d_default_y + d_default_h + d_margin; + +#if (GERMAN | FRENCH) + int d_save_w = 55 *factor; +#else + int d_save_w = 45 *factor; +#endif + int d_save_h = 9 *factor; + int d_save_x = d_dialog_cx - d_margin - d_save_w; + int d_save_y = d_dialog_y + d_dialog_h - d_margin - d_save_h; + +#if (GERMAN | FRENCH) + int d_cancel_w = 55 *factor; +#else + int d_cancel_w = 45 *factor; +#endif + int d_cancel_h = 9 *factor; + int d_cancel_x = d_dialog_cx + d_margin; + int d_cancel_y = d_dialog_y + d_dialog_h - d_margin - d_cancel_h; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_NAME = 100, + BUTTON_NUMBER, + BUTTON_DEFAULT, + BUTTON_CUSTOM, + BUTTON_SAVE, + BUTTON_CANCEL, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + BOOL process = true; // process while true + KeyNumType input; + + char namebuf[PhoneEntryClass::PHONE_MAX_NAME] = { 0 }; // buffer for editing name + char numbuf[PhoneEntryClass::PHONE_MAX_NUM] = { 0 }; // buffer for editing phone # + int rc; + SerialSettingsType settings; + int custom = 0; + int firsttime = 1; + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + EditClass nameedit (BUTTON_NAME, + namebuf, PhoneEntryClass::PHONE_MAX_NAME, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_name_x, d_name_y, d_name_w, d_name_h, EditClass::ALPHANUMERIC); + + EditClass numedit (BUTTON_NUMBER, + numbuf, PhoneEntryClass::PHONE_MAX_NUM, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_number_x, d_number_y, d_number_w, d_number_h, EditClass::ALPHANUMERIC); + + TextButtonClass defaultbtn(BUTTON_DEFAULT, TXT_DEFAULT_SETTINGS, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_default_x, d_default_y, d_default_w, d_default_h); + + TextButtonClass custombtn(BUTTON_CUSTOM, TXT_CUSTOM_SETTINGS, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_custom_x, d_custom_y, d_custom_w, d_custom_h); + + TextButtonClass savebtn(BUTTON_SAVE, TXT_SAVE_BUTTON, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_save_x, d_save_y, d_save_w, d_save_h); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); + + /* + ------------------------- Build the button list -------------------------- + */ + commands = &nameedit; + numedit.Add_Tail(*commands); + defaultbtn.Add_Tail(*commands); + custombtn.Add_Tail(*commands); + savebtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + + /* + ----------------------------- Various Inits ------------------------------ + */ + /*........................................................................ + Init the settings; if the phone entry is set to use defaults, init our + settings to sensible values (in case we invoke the setting editor); + otherwise, copy the entry's settings. + ........................................................................*/ + if (phone->Settings.Port == 0 || phone->Settings.IRQ == -1 || + phone->Settings.Baud == -1) { + settings = SerialDefaults; + defaultbtn.Turn_On(); + custom = false; + } else { + settings = phone->Settings; + custombtn.Turn_On(); + custom = true; + } + + strcpy (namebuf, phone->Name); + nameedit.Set_Text (namebuf, PhoneEntryClass::PHONE_MAX_NAME); + + strcpy (numbuf, phone->Number); + numedit.Set_Text (numbuf, PhoneEntryClass::PHONE_MAX_NUM); + + /* + ---------------------------- Processing loop ----------------------------- + */ + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + // init font variables + + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_PHONE_LISTING, d_dialog_x, d_dialog_y, d_dialog_w); + + Fancy_Text_Print (TXT_NAME_COLON, + d_name_x - 5, d_name_y + 1, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print (TXT_NUMBER_COLON, + d_number_x - 5, d_number_y + 1, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Flag_List_To_Redraw(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + if ( firsttime ) { + nameedit.Set_Focus(); + nameedit.Flag_To_Redraw(); + input = commands->Input(); + firsttime = 0; + } + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + case (BUTTON_NAME | KN_BUTTON): + numedit.Set_Focus(); + numedit.Flag_To_Redraw(); + break; + +// case (BUTTON_NUMBER | KN_BUTTON): +// nameedit.Clear_Focus(); +// nameedit.Flag_To_Redraw(); +// break; + + /*------------------------------------------------------------------ + Use Default Serial Settings + ------------------------------------------------------------------*/ + case (BUTTON_DEFAULT | KN_BUTTON): + custombtn.Turn_Off(); + defaultbtn.Turn_On(); + custom = 0; + break; + + /*------------------------------------------------------------------ + Use Custom Serial Settings + ------------------------------------------------------------------*/ + case (BUTTON_CUSTOM | KN_BUTTON): + if (Com_Settings_Dialog (&settings)) { + custombtn.Turn_On(); + defaultbtn.Turn_Off(); + } + custom = 1; + display = REDRAW_ALL; + break; + + /*------------------------------------------------------------------ + CANCEL: bail out + ------------------------------------------------------------------*/ + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + process = false; + rc = false; + break; + + /*------------------------------------------------------------------ + Save: save changes + ------------------------------------------------------------------*/ + case (KN_RETURN): + case (BUTTON_SAVE | KN_BUTTON): + process = false; + rc = true; + break; + } + + } /* end of while */ + + /*------------------------------------------------------------------------ + If 'Save', save all current settings + ------------------------------------------------------------------------*/ + if (rc) { + strcpy( phone->Name, strupr( namebuf ) ); + + // if nothing was entered then make if NONAME + + if ( !(phone->Name[0]) ) { + strcpy( phone->Name, "NONAME" ); + } + + strcpy( phone->Number, strupr( numbuf ) ); + + if (custom) { + phone->Settings = settings; + } else { + phone->Settings.Port = 0; + phone->Settings.IRQ = -1; + phone->Settings.Baud = -1; + phone->Settings.DialMethod = DIAL_TOUCH_TONE; + phone->Settings.InitStringIndex = 0; + phone->Settings.CallWaitStringIndex = CALL_WAIT_CUSTOM; + phone->Settings.CallWaitString[0] = 0; + } + } + + return(rc); + + +} /* end of Edit_Phone_Dialog */ + + +static bool Dial_Modem( SerialSettingsType *settings, bool reconnect ) +{ + bool connected = false; + DialStatusType dialstatus; + int modemstatus; + + /* + ** Turn modem servicing off in the callback routine. + */ + ModemService = false; + + // save for later to reconnect + + DialSettings = settings; + + modemstatus = NullModem.Get_Modem_Status(); + if (reconnect) { + if ( (modemstatus & CD_SET) ) { + connected = true; + ModemService = true; + return( connected ); + } + } else { + if ( (modemstatus & CD_SET) ) { + NullModem.Hangup_Modem(); + ModemService = false; + } + } + + NullModem.Setup_Modem_Echo( Modem_Echo ); + + modemstatus = NullModem.Detect_Modem( settings, reconnect ); + if ( !modemstatus ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + + /* + ** If our first attempt to detect the modem failed, and we're at + ** 14400 or 28800, bump up to the next baud rate & try again. + */ + switch (settings->Baud) { + + case 14400: + settings->Baud = 19200; + Shutdown_Modem(); + Init_Null_Modem(settings); + NullModem.Setup_Modem_Echo( Modem_Echo ); + modemstatus = NullModem.Detect_Modem( settings, reconnect ); + if ( !modemstatus ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + CCMessageBox().Process( TXT_UNABLE_FIND_MODEM ); + ModemService = true; + return( connected ); + } + break; + + case 28800: + settings->Baud = 38400; + Shutdown_Modem(); + Init_Null_Modem(settings); + NullModem.Setup_Modem_Echo( Modem_Echo ); + modemstatus = NullModem.Detect_Modem( settings, reconnect ); + if ( !modemstatus ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + CCMessageBox().Process( TXT_UNABLE_FIND_MODEM ); + ModemService = true; + return( connected ); + } + break; + + default: + CCMessageBox().Process( TXT_UNABLE_FIND_MODEM ); + ModemService = true; + return( connected ); + + } + } + else if ( modemstatus == -1 ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + CCMessageBox().Process( TXT_ERROR_IN_INITSTRING ); + ModemService = true; + return( connected ); + } + + + /* + ** Completely disable audio. This is required for MWave devices + */ + ThemeType old_theme = THEME_NONE; + if (SoundOn){ + old_theme = Theme.What_Is_Playing(); + Theme.Stop(); + CountDownTimerClass wait; + Call_Back(); + wait.Set (60,true); + while ( wait.Time() ) { + Call_Back(); + } + Sound_End(); + Call_Back(); + wait.Set (60,true); + while ( wait.Time() ) { + Call_Back(); + } + SoundOn = 0; + } + + dialstatus = NullModem.Dial_Modem( DialString, settings->DialMethod, reconnect ); + + if (reconnect) { + /* + --------------------------- Redraw the display --------------------------- + */ + HiddenPage.Clear(); + Map.Flag_To_Redraw(true); + Map.Render(); + } + + switch ( dialstatus ) { + case DIAL_CONNECTED: + connected = true; + break; + + case DIAL_NO_CARRIER: + CCMessageBox().Process(TXT_NO_CARRIER); + connected = false; + break; + + case DIAL_BUSY: + CCMessageBox().Process(TXT_LINE_BUSY); + connected = false; + break; + + case DIAL_ERROR: + CCMessageBox().Process(TXT_NUMBER_INVALID); + connected = false; + break; + + case DIAL_NO_DIAL_TONE: + CCMessageBox().Process(TXT_NO_DIAL_TONE); + connected = false; + break; + + case DIAL_CANCELED: + NullModem.Hangup_Modem(); + ModemService = false; + CCMessageBox().Process(TXT_DIALING_CANCELED); + connected = false; + break; + } + + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + + /* + ** Restore audio capability + */ + SoundOn = Audio_Init ( MainWindow , 16 , false , 11025*2 , 0 ); + if (SoundOn){ + Theme.Play_Song (old_theme); + } + + ModemService = true; + return( connected ); + +} /* end of Dial_Modem */ + + +static bool Answer_Modem( SerialSettingsType *settings, bool reconnect ) +{ + bool connected = false; + DialStatusType dialstatus; + int modemstatus; + +/* ###Change collision detected! C:\PROJECTS\CODE\NULLDLG.CPP... */ + /* + ** Turn modem servicing off in the callback routine. + */ + ModemService = false; + + // save for later to reconnect + + DialSettings = settings; + + modemstatus = NullModem.Get_Modem_Status(); + if (reconnect) { + if ( (modemstatus & CD_SET) ) { + connected = true; + ModemService = true; + return( connected ); + } + } else { + if ( (modemstatus & CD_SET) ) { + NullModem.Hangup_Modem(); + ModemService = false; + } + } + + NullModem.Setup_Modem_Echo( Modem_Echo ); + + modemstatus = NullModem.Detect_Modem( settings, reconnect ); + if ( !modemstatus ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + + /* + ** If our first attempt to detect the modem failed, and we're at + ** 14400 or 28800, bump up to the next baud rate & try again. + */ + switch (settings->Baud) { + + case 14400: + settings->Baud = 19200; + Shutdown_Modem(); + Init_Null_Modem(settings); + NullModem.Setup_Modem_Echo( Modem_Echo ); + modemstatus = NullModem.Detect_Modem( settings, reconnect ); + if ( !modemstatus ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + CCMessageBox().Process( TXT_UNABLE_FIND_MODEM ); + ModemService = true; + return( connected ); + } + break; + + case 28800: + settings->Baud = 38400; + Shutdown_Modem(); + Init_Null_Modem(settings); + NullModem.Setup_Modem_Echo( Modem_Echo ); + modemstatus = NullModem.Detect_Modem( settings, reconnect ); + if ( !modemstatus ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + CCMessageBox().Process( TXT_UNABLE_FIND_MODEM ); + ModemService = true; + return( connected ); + } + break; + + default: + CCMessageBox().Process( TXT_UNABLE_FIND_MODEM ); + ModemService = true; + return( connected ); + } + + } + else if ( modemstatus == -1 ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + CCMessageBox().Process( TXT_ERROR_IN_INITSTRING ); + ModemService = true; + return( connected ); + } + + /* + ** Completely disable audio. This is required for MWave devices + */ + ThemeType old_theme = THEME_NONE; + if (SoundOn){ + old_theme = Theme.What_Is_Playing(); + Theme.Stop(); + CountDownTimerClass wait; + Call_Back(); + wait.Set (60,true); + while ( wait.Time() ) { + Call_Back(); + } + Sound_End(); + Call_Back(); + wait.Set (60,true); + while ( wait.Time() ) { + Call_Back(); + } + SoundOn = 0; + } + + dialstatus = NullModem.Answer_Modem( reconnect ); + + switch ( dialstatus ) { + case DIAL_CONNECTED: + connected = true; + break; + + case DIAL_NO_CARRIER: + CCMessageBox().Process(TXT_NO_CARRIER); + connected = false; + break; + + case DIAL_BUSY: + CCMessageBox().Process(TXT_LINE_BUSY); + connected = false; + break; + + case DIAL_ERROR: + CCMessageBox().Process(TXT_NUMBER_INVALID); + connected = false; + break; + + case DIAL_NO_DIAL_TONE: + CCMessageBox().Process(TXT_NO_DIAL_TONE); + connected = false; + break; + + case DIAL_CANCELED: + CCMessageBox().Process(TXT_ANSWERING_CANCELED); + connected = false; + break; + } + + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + + /* + ** Restore audio capability + */ + SoundOn = Audio_Init ( MainWindow , 16 , false , 11025*2 , 0 ); + if (SoundOn){ + Theme.Play_Song (old_theme); + } + + ModemService = true; + return( connected ); + +} /* end of Answer_Modem */ + + +static void Modem_Echo( char c ) +{ + if (NullModem.EchoCount < (NullModem.EchoSize - 1) ) { + *(NullModem.EchoBuf + NullModem.EchoCount) = c; + *(NullModem.EchoBuf + NullModem.EchoCount + 1) = 0; + NullModem.EchoCount++; + } else { + //Smart_Printf( "Echo buffer full!!!\n" ); + } + +} /* end of Modem_Echo */ + + +void Smart_Printf( char *format, ... ) +{ + va_list arglist; + char buf[501]; + + + va_start(arglist,format); + vsprintf(buf,format,arglist); + va_end(arglist); + + if (Debug_Smart_Print) { + if (Special.IsMonoEnabled) { +// Mono_Set_Cursor(0,0); + Mono_Printf("%s",buf); + } else { +// Mono_Printf("%s",buf); + printf("%s",buf); + } + } else { + if (Debug_Heap_Dump) { + printf("%s",buf); + } + } +} + + +void Hex_Dump_Data( char *buffer, int length ) +{ + int i; + int offset = 0; + char buff[10]; + char ptr[16]; + char c; + + + while (length >= 16) { + memcpy( ptr, (buffer + offset), 16); + + Smart_Printf("%05lX ", offset); + + for (i = 0; i < 16; i++) { + + c = ptr[i]; + itoh(c, buff); + + if (!(i & 0x3) && i) { + Smart_Printf("³ "); + } + + Smart_Printf("%s ", buff); + } + + Smart_Printf(" "); + + for (i = 0; i < 16; i++) { + c = ptr[i]; + + if (c && ((c < 7) || (c > 11)) && (c != 13)) { + Smart_Printf("%c", c); + } else { + Smart_Printf("."); + } + } + + Smart_Printf("\n"); + + offset += 16; + length -= 16; + } + + if (length) { + memcpy( ptr, (buffer + offset), 16); + + Smart_Printf("%05lX ", offset); + + for (i = 0; i < 16; i++) { + if (i < length) { + c = ptr[i]; + itoh(c, buff); + if (!(i & 0x3) && i) { + Smart_Printf("³ "); + } + Smart_Printf("%s ", buff); + } else { + if (!(i & 0x3) && i) { + Smart_Printf(" "); + } + Smart_Printf(" "); + } + } + + Smart_Printf(" "); + + for (i = 0; i < length; i++) { + + c = ptr[i]; + + if (c && ((c < 7) || (c > 11)) && (c != 13)) { + Smart_Printf("%c", c); + } else { + Smart_Printf("."); + } + } + + Smart_Printf("\n"); + } + +} /* end of Hex_Dump_Data */ + + +void itoh( int i, char *s) +{ + + int nibble, loop; + +// *s++ = '0'; +// *s++ = 'x'; + + if (i == 0) { + *s++ = '0'; + *s++ = '0'; + } else { + for (loop = 1; loop >= 0; loop--) { + nibble = (i >> (loop << 2)) & 0x000F; + + /* decimal range */ + if (nibble < 10) { + *s++ = '0' + nibble; + } else { + *s++ = 'A' + (nibble - 10); + } + } + } + *s = 0; /* null terminate it */ +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#if (0) + +int Com_Fake_Scenario_Dialog(void) +{ + bool display = true; // redraw level + bool process = true; // process while true + + char namebuf[MPLAYER_NAME_MAX] = {0}; // buffer for player's name + int transmit; // 1 = re-transmit new game options + int parms_received = 1; // 1 = game options received + int changed = 0; // 1 = user has changed an option + + int rc; + int recsignedoff = false; + int version; + unsigned long starttime; + unsigned long timingtime; + unsigned long lastmsgtime; + unsigned long lastredrawtime; + unsigned long transmittime = 0; + unsigned long theirresponsetime; + int packetlen; + bool oppscorescreen = false; + bool gameoptions = false; + EventClass *event; // event ptr + unsigned long msg_timeout = 60*60; // init to 60 seconds + + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + }; + + /*........................................................................ + Dialog variables + ........................................................................*/ + TextButtonClass * buttons = 0; + + KeyNumType input; + + int x,y; + int width=0; + int height=0; + char text_buffer[80*3]; + + const char *current_status_string = Text_String(TXT_WINSOCK_CONNECTING); + strcpy(text_buffer, current_status_string); + + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + + Format_Window_String(text_buffer, SeenBuff.Get_Height(), width, height); + + width = MAX(width, 50*factor); + width += 40*factor; + height += 60*factor; + + x = (SeenBuff.Get_Width() - width) / 2; + y = (SeenBuff.Get_Height() - height) / 2; + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + x + ((width - (String_Pixel_Width( Text_String( TXT_CANCEL ) ) + 8*factor)) >> 1), + y + height - (FontHeight + FontYSpacing + 2*factor) - 5*factor); + + Set_Logic_Page(SeenBuff); + buttons = &cancelbtn; + + buttons->Flag_List_To_Redraw(); + process = true; + + /*........................................................................ + Init other scenario parameters + ........................................................................*/ + Special.IsTGrowth = MPlayerTiberium; + Special.IsTSpread = MPlayerTiberium; + transmit = 1; + + /*........................................................................ + Init random-number generator, & create a seed to be used for all random + numbers from here on out + ........................................................................*/ + randomize(); + //Seed = rand(); + + /* + ---------------------------- Processing loop ----------------------------- + */ + NullModem.Reset_Response_Time(); // clear response time + theirresponsetime = 10000; // initialize to an invalid value + timingtime = lastmsgtime = lastredrawtime = TickCount.Time(); + + + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=true; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + /* + ..................... Draw the background ....................... + */ + Dialog_Box(x, y, width, height); + /* + ....................... Draw the labels ......................... + */ + Draw_Caption(TXT_NONE, x, y, width); + + Fancy_Text_Print(text_buffer, x + 20*factor, y + 25*factor, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + buttons->Draw_All(); + Show_Mouse(); + display = false; + } + + /* + ............................ Process input ............................ + */ + input = buttons->Input(); + switch (input) { + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + process = false; + rc = false; + break; + + default: + break; + } + + + /*--------------------------------------------------------------------- + If our Transmit flag is set, we need to send out a game option packet. + This message requires an ACK. The first time through the loop, transmit + should be set, so we send out our default options; we'll then send + any changes we make to the defaults. + ---------------------------------------------------------------------*/ + if (transmit && (TickCount.Time() - transmittime) > PACKET_RETRANS_TIME) { + SendPacket.Command = SERIAL_GAME_OPTIONS; + strcpy (SendPacket.Name, MPlayerName); +#ifdef PATCH + if (IsV107) { + SendPacket.Version = 1; + } else { + SendPacket.Version = 2; + } +#else + SendPacket.Version = Version_Number(); +#endif + SendPacket.House = MPlayerHouse; + SendPacket.Color = MPlayerColorIdx; + + SendPacket.Scenario = MPlayerFilenum[ScenarioIdx]; + + SendPacket.Credits = MPlayerCredits; + SendPacket.IsBases = MPlayerBases; + SendPacket.IsTiberium = MPlayerTiberium; + SendPacket.IsGoodies = MPlayerGoodies; + SendPacket.IsGhosties = MPlayerGhosts; + SendPacket.BuildLevel = BuildLevel; + SendPacket.UnitCount = MPlayerUnitCount; + SendPacket.Seed = Seed; + SendPacket.Special = Special; + SendPacket.GameSpeed = Options.GameSpeed; + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + transmittime = TickCount.Time(); + transmit = 0; + } + + // + // send a timing packet if enough time has gone by. + // + if ( (TickCount.Time() - timingtime) > PACKET_TIMING_TIMEOUT) { + SendPacket.Command = SERIAL_TIMING; + SendPacket.ResponseTime = NullModem.Response_Time(); + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 0); + timingtime = TickCount.Time(); + } + + /*--------------------------------------------------------------------- + Check for an incoming message + ---------------------------------------------------------------------*/ + if (NullModem.Get_Message (&ReceivePacket, &packetlen) > 0) { + + lastmsgtime = TickCount.Time(); + msg_timeout = 600; // reset timeout value to 10 seconds + // (only the 1st time through is 20 seconds) + + + if (ReceivePacket.Command >= SERIAL_CONNECT && + ReceivePacket.Command < SERIAL_LAST_COMMAND && + ReceivePacket.Command != SERIAL_MESSAGE && + ReceivePacket.ID == ModemGameToPlay) { + + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + break; + } + + event = (EventClass *)&ReceivePacket; + if (event->Type <= EventClass::FRAMEINFO) { + if ( (TickCount.Time() - lastredrawtime) > PACKET_REDRAW_TIME) { + lastredrawtime = TickCount.Time(); + oppscorescreen = true; + parms_received = 1; + } + } else { + switch ( ReceivePacket.Command ) { + /*.................................................................. + Sign-off: Give the other machine time to receive my ACK, display a + message, and exit. + ..................................................................*/ + case (SERIAL_SIGN_OFF): + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < 60) + NullModem.Service(); + CCMessageBox().Process(TXT_USER_SIGNED_OFF); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + recsignedoff = true; + break; + + /*.................................................................. + Game Options: Store the other machine's name, color & house; + If they've picked the same color as myself, re-transmit my settings + to force him to choose a different color. (Com_Show_Scenario_Dialog + is responsible for ensuring the colors are different.) + ..................................................................*/ + case (SERIAL_GAME_OPTIONS): + oppscorescreen = false; + gameoptions = true; + strcpy (TheirName, ReceivePacket.Name); + TheirColor = ReceivePacket.Color; + TheirHouse = ReceivePacket.House; + transmit = 1; + + parms_received = 1; + + /*............................................................... + Check the version number of the other system. + ...............................................................*/ +#ifdef PATCH + if (IsV107) { + version = 1; + } else { + version = 2; + } +#else + version = Version_Number(); +#endif + if (ReceivePacket.Version > version) { + CCMessageBox().Process (TXT_YOURGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } else { + if (ReceivePacket.Version < version) { + CCMessageBox().Process (TXT_DESTGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } + } + break; + + /*.................................................................. + Incoming message: add to our list + ..................................................................*/ + + // + // get their response time + // + case (SERIAL_TIMING): + oppscorescreen = false; + theirresponsetime = ReceivePacket.ResponseTime; + + if ( !gameoptions ) { + transmit = 1; + }else{ + process = false; + rc = true; + } + break; + + // + // print msg waiting for opponent + // + case (SERIAL_SCORE_SCREEN): + oppscorescreen = true; + parms_received = 1; + break; + + default: + break; + } + } + } + + // if we haven't received a msg for 10 seconds exit + + if ( (TickCount.Time() - lastmsgtime) > msg_timeout) { + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + } + + /*--------------------------------------------------------------------- + Service the connection + ---------------------------------------------------------------------*/ + NullModem.Service(); + + } /* end of while */ + + /*------------------------------------------------------------------------ + Sort player ID's, so we can execute commands in the same order on both + machines. + ------------------------------------------------------------------------*/ + if (rc) { + /*..................................................................... + Set the number of players in this game, and my ID + .....................................................................*/ + MPlayerCount = 2; + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + TheirID = Build_MPlayerID (TheirColor,TheirHouse); + + /*..................................................................... + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + .....................................................................*/ + if (TheirID < MPlayerLocalID) { + MPlayerID[0] = TheirID; + MPlayerID[1] = MPlayerLocalID; + strcpy (MPlayerNames[0], TheirName); + strcpy (MPlayerNames[1], MPlayerName); + } else { + MPlayerID[0] = MPlayerLocalID; + MPlayerID[1] = TheirID; + strcpy (MPlayerNames[0], MPlayerName); + strcpy (MPlayerNames[1], TheirName); + } + + /*..................................................................... + Get the scenario filename + .....................................................................*/ + Scenario = MPlayerFilenum[ScenarioIdx]; + + /*..................................................................... + Send all players the GO packet. + .....................................................................*/ + SendPacket.Command = SERIAL_GO; + SendPacket.ResponseTime = NullModem.Response_Time(); + if ( theirresponsetime == 10000 ) { +// Mono_Clear_Screen(); +// Smart_Printf( "Did not receive their response time!!!!!!!\n" ); +// Get_Key(); + } else { + if (SendPacket.ResponseTime < theirresponsetime) { + SendPacket.ResponseTime = theirresponsetime; + } + } + + // + // calculated one way delay for a packet and overall delay to execute + // a packet + // + ////////MPlayerMaxAhead = MAX( (SendPacket.ResponseTime / 8), 2); + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + while ( ( NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_SENDING_TIMEOUT) ) + || ((TickCount.Time() - starttime) < 60) ) { + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + NullModem.Service(); + Keyboard::Check(); //Make sure the message loop gets called + } + + // clear queue to keep from doing any resends + NullModem.Init_Send_Queue(); + + } else { + if ( !recsignedoff ) { + /*..................................................................... + Broadcast my sign-off over my network + .....................................................................*/ + SendPacket.Command = SERIAL_SIGN_OFF; + SendPacket.Color = MPlayerLocalID; // use Color for ID + SendPacket.ID = ModemGameToPlay; + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + while ( (NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_CANCEL_TIMEOUT) ) + || ((TickCount.Time() - starttime) < 60) ) { + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + if ( NullModem.Get_Message( &ReceivePacket, &packetlen ) > 0) { + + // are we getting our own packets back?? + + if (ReceivePacket.Command == SERIAL_SIGN_OFF + && ReceivePacket.ID == ModemGameToPlay) { + + // exit while + break; + } + } + + NullModem.Service(); + } + } + + Shutdown_Modem(); + } + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + /*------------------------------------------------------------------------ + Save any changes made to our options + ------------------------------------------------------------------------*/ + if (changed) { + Write_MultiPlayer_Settings (); + } + + return(rc); + +} /* end of Com_Scenario_Dialog */ + + + + + + + + + + + + + + + + + + + + + + + + + + +int Com_Show_Fake_Scenario_Dialog(void) +{ + /*........................................................................ + Dialog variables + ........................................................................*/ + bool display = true; // redraw level + BOOL process = true; // process while true + + char namebuf[MPLAYER_NAME_MAX] = {0}; // buffer for player's name + int transmit; // 1 = re-transmit new game options + int first; // 1 = no packets received yet + int parms_received = 0; // 1 = game options received + int changed = 0; // 1 = user has changed an option + + int rc; + int recsignedoff = 0; + int i; + int version; + unsigned long starttime; + unsigned long timingtime; + unsigned long lastmsgtime; + unsigned long lastredrawtime; + unsigned long transmittime = 0; + int packetlen; + bool oppscorescreen = false; + bool gameoptions = false; + EventClass *event; // event ptr + unsigned long msg_timeout = 60*60; // init to 60 seconds + + + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + }; + + /*........................................................................ + Dialog variables + ........................................................................*/ + TextButtonClass * buttons = 0; + + KeyNumType input; + + int x,y; + int width=0; + int height=0; + char text_buffer[80*3]; + + const char *current_status_string = Text_String(TXT_WINSOCK_CONNECTING); + strcpy(text_buffer, current_status_string); + + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + + Format_Window_String(text_buffer, SeenBuff.Get_Height(), width, height); + + width = MAX(width, 50*factor); + width += 40*factor; + height += 60*factor; + + x = (SeenBuff.Get_Width() - width) / 2; + y = (SeenBuff.Get_Height() - height) / 2; + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + x + ((width - (String_Pixel_Width( Text_String( TXT_CANCEL ) ) + 8*factor)) >> 1), + y + height - (FontHeight + FontYSpacing + 2*factor) - 5*factor); + + Set_Logic_Page(SeenBuff); + buttons = &cancelbtn; + + buttons->Flag_List_To_Redraw(); + + + transmit = 1; + first = 1; + + + /* + ---------------------------- Processing loop ----------------------------- + */ + NullModem.Reset_Response_Time(); // clear response time + timingtime = lastmsgtime = lastredrawtime = TickCount.Time(); + + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=true; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + /* + ..................... Draw the background ....................... + */ + Dialog_Box(x, y, width, height); + /* + ....................... Draw the labels ......................... + */ + Draw_Caption(TXT_NONE, x, y, width); + + Fancy_Text_Print(text_buffer, x + 20*factor, y + 25*factor, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + buttons->Draw_All(); + Show_Mouse(); + display = false; + } + + + /* + ............................ Process input ............................ + */ + input = buttons->Input(); + switch (input) { + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + process = false; + rc = false; + break; + + default: + break; + } + + + + /*--------------------------------------------------------------------- + If our Transmit flag is set, we need to send out a game option packet + ---------------------------------------------------------------------*/ + if (transmit && (TickCount.Time() - transmittime) > PACKET_RETRANS_TIME) { + SendPacket.Command = SERIAL_GAME_OPTIONS; + strcpy (SendPacket.Name, MPlayerName); +#ifdef PATCH + if (IsV107) { + SendPacket.Version = 1; + } else { + SendPacket.Version = 2; + } +#else + SendPacket.Version = Version_Number(); +#endif + SendPacket.House = MPlayerHouse; + SendPacket.Color = MPlayerColorIdx; + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + transmittime = TickCount.Time(); + transmit = 0; + } + + // + // send a timing packet if enough time has gone by. + // + if ( (TickCount.Time() - timingtime) > PACKET_TIMING_TIMEOUT) { + SendPacket.Command = SERIAL_TIMING; + SendPacket.ResponseTime = NullModem.Response_Time(); + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 0); + timingtime = TickCount.Time(); + } + + /*--------------------------------------------------------------------- + Check for an incoming message + ---------------------------------------------------------------------*/ + if (NullModem.Get_Message (&ReceivePacket, &packetlen) > 0) { + + lastmsgtime = TickCount.Time(); + + msg_timeout = 600; + + // are we getting our own packets back?? + + if (ReceivePacket.Command >= SERIAL_CONNECT && + ReceivePacket.Command < SERIAL_LAST_COMMAND && + ReceivePacket.Command != SERIAL_MESSAGE && + ReceivePacket.ID == ModemGameToPlay) { + + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + break; + } + + event = (EventClass *)&ReceivePacket; + if (event->Type <= EventClass::FRAMEINFO) { + if ( (TickCount.Time() - lastredrawtime) > PACKET_REDRAW_TIME) { + lastredrawtime = TickCount.Time(); + oppscorescreen = true; + display = false; + parms_received = 1; + } + } else { + switch ( ReceivePacket.Command ) { + /*.................................................................. + Other system signs off: Give it time to receive my ACK, then show + a message. + ..................................................................*/ + case (SERIAL_SIGN_OFF): + starttime = TickCount.Time(); + while ( (TickCount.Time() - starttime) < 60) + NullModem.Service(); + CCMessageBox().Process(TXT_USER_SIGNED_OFF); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + recsignedoff = true; + break; + + /*.................................................................. + Game Options: Store all options; check my color & game version. + ..................................................................*/ + case (SERIAL_GAME_OPTIONS): + oppscorescreen = false; + gameoptions = true; + display = false; + parms_received = 1; + + strcpy (TheirName, ReceivePacket.Name); + TheirColor = ReceivePacket.Color; + TheirHouse = ReceivePacket.House; + + /*............................................................... + Save scenario settings. + ...............................................................*/ + MPlayerCredits = ReceivePacket.Credits; + MPlayerBases = ReceivePacket.IsBases; + MPlayerTiberium = ReceivePacket.IsTiberium; + MPlayerGoodies = ReceivePacket.IsGoodies; + MPlayerGhosts = ReceivePacket.IsGhosties; + BuildLevel = ReceivePacket.BuildLevel; + MPlayerUnitCount = ReceivePacket.UnitCount; + Seed = ReceivePacket.Seed; + Special = ReceivePacket.Special; + Options.GameSpeed = ReceivePacket.GameSpeed; + + if (MPlayerTiberium) { + Special.IsTGrowth = 1; + Special.IsTSpread = 1; + } else { + Special.IsTGrowth = 0; + Special.IsTSpread = 0; + } + + /*............................................................... + Find the index of the scenario number; if it's not found, leave + it at -1. + ...............................................................*/ + ScenarioIdx = -1; + for (i = 0; i < MPlayerFilenum.Count(); i++) { + if (ReceivePacket.Scenario == MPlayerFilenum[i]) + ScenarioIdx = i; + } + + /*............................................................... + Check our version numbers; if they're incompatible, sign off. + ...............................................................*/ +#ifdef PATCH + if (IsV107) { + version = 1; + } else { + version = 2; + } +#else + version = Version_Number(); +#endif + if (ReceivePacket.Version > version) { + CCMessageBox().Process (TXT_YOURGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } else { + if (ReceivePacket.Version < version) { + CCMessageBox().Process (TXT_DESTGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } + } + + /*............................................................... + If this is the first game-options packet we've received, transmit + our options to him. + ...............................................................*/ + if (first) { + first = 0; + + // force transmitting of game options packet + + transmit = 1; + transmittime = 0; + } + break; + + /*.................................................................. + GO: Exit this routine with a success code. + ..................................................................*/ + case (SERIAL_GO): + + // + // calculated one way delay for a packet and overall delay + // to execute a packet + // + ////////MPlayerMaxAhead = MAX( (ReceivePacket.ResponseTime / 8), 2); + + process = false; + rc = true; + break; + + // + // throw away timing packet + // + case (SERIAL_TIMING): + oppscorescreen = false; + break; + + // + // print msg waiting for opponent + // + case (SERIAL_SCORE_SCREEN): +// Smart_Printf( "received score screen\n" ); + oppscorescreen = true; + display = false; + parms_received = 1; + break; + + default: + break; + } + } + } + + // if we haven't received a msg for 10 seconds exit + + if ( (TickCount.Time() - lastmsgtime) > msg_timeout) { + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + } + + + /*--------------------------------------------------------------------- + Service the connection + ---------------------------------------------------------------------*/ + NullModem.Service(); + + } /* end of while */ + + /*------------------------------------------------------------------------ + Sort player ID's, so we can execute commands in the same order on both + machines. + ------------------------------------------------------------------------*/ + if (rc) { + /*..................................................................... + Set the number of players in this game, and my ID + .....................................................................*/ + MPlayerCount = 2; + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + TheirID = Build_MPlayerID (TheirColor,TheirHouse); + + /*..................................................................... + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + .....................................................................*/ + if (TheirID < MPlayerLocalID) { + MPlayerID[0] = TheirID; + MPlayerID[1] = MPlayerLocalID; + strcpy (MPlayerNames[0], TheirName); + strcpy (MPlayerNames[1], MPlayerName); + } else { + MPlayerID[0] = MPlayerLocalID; + MPlayerID[1] = TheirID; + strcpy (MPlayerNames[0], MPlayerName); + strcpy (MPlayerNames[1], TheirName); + } + + /*..................................................................... + Get the scenario filename + .....................................................................*/ + Scenario = MPlayerFilenum[ScenarioIdx]; + + starttime = TickCount.Time(); + while ( ( NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_SENDING_TIMEOUT) ) + || ((TickCount.Time() - starttime) < 60) ) { + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + NullModem.Service(); + Keyboard::Check(); //Make sure the message loop gets called + } + + // clear queue to keep from doing any resends + NullModem.Init_Send_Queue(); + + } else { + if ( !recsignedoff ) { + /*..................................................................... + Broadcast my sign-off over my network + .....................................................................*/ + SendPacket.Command = SERIAL_SIGN_OFF; + SendPacket.Color = MPlayerLocalID; // use Color for ID + SendPacket.ID = ModemGameToPlay; + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + while ( (NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_CANCEL_TIMEOUT) ) + || ((TickCount.Time() - starttime) < 60) ) { + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + if ( NullModem.Get_Message( &ReceivePacket, &packetlen ) > 0) { + + // are we getting our own packets back?? + + if (ReceivePacket.Command == SERIAL_SIGN_OFF + && ReceivePacket.ID == ModemGameToPlay) { + + // exit while + break; + } + } + + NullModem.Service(); + } + } + + Shutdown_Modem(); + } + + /*------------------------------------------------------------------------ + Save any changes made to our options + ------------------------------------------------------------------------*/ + if (changed) { + Write_MultiPlayer_Settings (); + } + + return(rc); + +} /* end of Com_Show_Scenario_Dialog */ + + +#endif //(0) + + + + + + + + + + + diff --git a/NULLDLG.CPP.BAK b/NULLDLG.CPP.BAK new file mode 100644 index 0000000..e2aff58 --- /dev/null +++ b/NULLDLG.CPP.BAK @@ -0,0 +1,7580 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\nulldlg.cpv 1.9 16 Oct 1995 16:52:12 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : NULLDLG.CPP * + * * + * Programmer : Bill R. Randolph * + * * + * Start Date : 04/29/95 * + * * + * Last Update : April 29, 1995 [BRR] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * Init_Null_Modem -- Initializes Null Modem communications * + * Shutdown_Modem -- Shuts down modem/null-modem communications * + * Test_Null_Modem -- Null-Modem test routine * + * Reconnect_Null_Modem -- allows user to reconnect * + * Destroy_Null_Connection -- destroys the given connection * + * Select_Serial_Dialog -- Serial Communications menu dialog * + * Com_Settings_Dialog -- Lets user select serial port settings * + * Com_Scenario_Dialog -- Serial game scenario selection dialog * + * Phone_Dialog -- Lets user edit phone directory & dial * + * Build_InitString_Listbox -- [re]builds the initstring entry listbox * + * Init_String_Compare -- for qsort * + * Build_Phone_Listbox -- [re]builds the phone entry listbox * + * Phone_Compare -- for qsort * + * Edit_Phone_Dialog -- lets user edit a phone book entry * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#include "function.h" +#include "wincomm.h" +#include "tcpip.h" + +// +// how much time (ticks) to go by before thinking other system +// is not responding. +// +#define PACKET_SENDING_TIMEOUT 1800 +#define PACKET_CANCEL_TIMEOUT 900 + +// +// how much time (ticks) to go by before sending another packet +// of game options or serial connect. +// +#define PACKET_RETRANS_TIME 30 +#define PACKET_REDRAW_TIME 60 + + +static int Reconnect_Null_Modem( void ); +static int Com_Settings_Dialog( SerialSettingsType *settings ); +static int Phone_Dialog (void); +static void Build_Init_String_Listbox (ListClass *list, EditClass *edit, char *buf, int *index); +static int Init_String_Compare (const void *p1, const void *p2); +static void Build_Phone_Listbox (ListClass *list, EditClass *edit, char *buf); +static int Phone_Compare (const void *p1, const void *p2); +static int Edit_Phone_Dialog (PhoneEntryClass *phone); +static bool Dial_Modem( SerialSettingsType *settings, bool reconnect ); +static bool Answer_Modem( SerialSettingsType *settings, bool reconnect ); +static void Modem_Echo( char c ); + +void Smart_Printf( char *format, ... ); +void Hex_Dump_Data( char *buffer, int length ); +void itoh( int i, char *s); + + +static SerialPacketType SendPacket; +static SerialPacketType ReceivePacket; +char TheirName[MPLAYER_NAME_MAX]; +unsigned char TheirColor; +HousesType TheirHouse; +unsigned char TheirID; +static char DialString[ CWAITSTRBUF_MAX + PhoneEntryClass::PHONE_MAX_NUM - 1 ]; +static SerialSettingsType *DialSettings; + +#define SHOW_MONO 0 + +/*************************************************************************** + * Init_Null_Modem -- Initializes Null Modem communications * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = OK, false = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +int Init_Null_Modem( SerialSettingsType *settings ) +{ + + if ( NullModem.Init( settings->Port, settings->IRQ, + settings->Baud, 0, 8, 1 ) ) { +// settings->Baud, 'N', 8, 1 ) ) { + +// NullModem.Change_IRQ_Priority( settings->IRQ ); + + return(true); + } else { + return(false); + } +} + + +/*************************************************************************** + * Shutdown_Modem -- Shuts down modem/null-modem communications * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +void Shutdown_Modem( void ) +{ + if (!PlaybackGame) { + if (GameToPlay == GAME_MODEM) { + NullModem.Hangup_Modem(); + } + } + + //NullModem.Change_IRQ_Priority( 0 ); // reset priority of interrupts + + // + // close port + // + NullModem.Shutdown(); +} + + +/*************************************************************************** + * Modem_Signoff -- sends EXIT event * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/03/1995 DRD : Created. * + *=========================================================================*/ +void Modem_Signoff( void ) +{ + unsigned long starttime; + EventClass event; + + if (!PlaybackGame) { + /*------------------------------------------------------------------------ + Send a sign-off packet + ------------------------------------------------------------------------*/ + event.Type = EventClass::EXIT; + NullModem.Send_Message (&event,sizeof(EventClass),0); + NullModem.Send_Message (&event,sizeof(EventClass),0); + + starttime = TickCount.Time(); + while( (TickCount.Time() - starttime) > 30) { + NullModem.Service(); + } + } +} + + +/*************************************************************************** + * Test_Null_Modem -- Null-Modem test routine * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = failure to connect; 1 = I'm the game owner, 2 = I'm not * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +int Test_Null_Modem( void ) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + }; + + /*........................................................................ + Dialog variables + ........................................................................*/ + bool process = true; // process while true + KeyNumType input; + + int retval; + unsigned long starttime; + int packetlen; + + int x,y,width,height; // dialog dimensions + char buffer[80*3]; + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + /* + ** Determine the dimensions of the text to be used for the dialog box. + ** These dimensions will control how the dialog box looks. + */ + strcpy( buffer, Text_String( TXT_WAITING_CONNECT ) ); + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + Format_Window_String(buffer, 200*factor, width, height); + + width = MAX(width, 50*factor); + width += 40*factor; + height += 60*factor; + + x = (320*factor - width) / 2; + y = (200*factor - height) / 2; + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + x + ((width - (String_Pixel_Width( Text_String( TXT_CANCEL ) ) + 8 *factor)) >> 1), + y + height - (FontHeight + FontYSpacing + 2*factor) - 5*factor); + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + process = true; + + /* + ............................ Create the list ............................. + */ + commands = &cancelbtn; + + commands->Flag_List_To_Redraw(); + + /* + ............................ Draw the dialog ............................. + */ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + + Dialog_Box(x, y, width, height); + Draw_Caption(TXT_NONE, x, y, width); + + Fancy_Text_Print(buffer, x + 20*factor, y + 25*factor, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + commands->Draw_All(); + while (Get_Mouse_State() > 0) Show_Mouse(); + + /* + ** This is supposed to be a direct connection so hang up any modem on this port + ** just to annoy British Telecom + */ + /* + ** Go into break mode + */ + SetCommBreak(SerialPort->Get_Port_Handle()); + + /* + ** Send hangup command + */ + SerialPort->Write_To_Serial_Port ((unsigned char*)"ATH\r", strlen("ATH\r")); + CountDownTimerClass time; + time.Set(2*60); + while (time.Time()){} + + /* + ** Back out of break mode + */ + ClearCommBreak(SerialPort->Get_Port_Handle()); + + /* + ** Drop DTR as well - just in case the modem still hasnt got the message + */ + EscapeCommFunction(SerialPort->Get_Port_Handle(), CLRDTR); + + + /*------------------------------------------------------------------------ + Check for a packet. If we detect one, the other system has already been + started. Wait 1/2 sec for him to receive my ACK, then exit with success. + Note: The initial time must be a little longer than the resend delay. + Just in case we just missed the packet. + ------------------------------------------------------------------------*/ + starttime = TickCount.Time(); + while ( TickCount.Time() - starttime < 80) { + NullModem.Service(); + if ( NullModem.Get_Message( &ReceivePacket, &packetlen ) > 0) { + if (ReceivePacket.Command == SERIAL_CONNECT) { + //Smart_Printf( "Received SERIAL_CONNECT %d, ID %d \n", ReceivePacket.Seed, ReceivePacket.ID ); + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < 30) + NullModem.Service(); + process = false; + retval = 2; + break; + } + } + } + + /*------------------------------------------------------------------------ + Send a packet across. As long as Num_Send() is non-zero, the other system + hasn't received it yet. + ------------------------------------------------------------------------*/ + if (process) { + memset (&SendPacket, 0, sizeof(SerialPacketType)); + SendPacket.Command = SERIAL_CONNECT; + // + // put time from start of game for determining the host in case of tie. + // + SendPacket.Seed = TickCount.Time(); + SendPacket.ID = (int) buffer; // address of buffer for more uniqueness. + + //Smart_Printf( "Sending SERIAL_CONNECT %d, ID %d \n", SendPacket.Seed, SendPacket.ID ); + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < 80) { + NullModem.Service(); + if (NullModem.Get_Message (&ReceivePacket, &packetlen) > 0) { + if (ReceivePacket.Command == SERIAL_CONNECT) { + //Smart_Printf( "Received2 SERIAL_CONNECT %d, ID %d \n", ReceivePacket.Seed, ReceivePacket.ID ); + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < 30) + NullModem.Service(); + + // + // whoever has the highest time is the host + // + if (ReceivePacket.Seed > SendPacket.Seed) { + process = false; + retval = 2; + } else { + if (ReceivePacket.Seed == SendPacket.Seed) { + if (ReceivePacket.ID > SendPacket.ID) { + process = false; + retval = 2; + } else + // + // if they are equal then it's a loopback cable or a modem + // + if (ReceivePacket.ID == SendPacket.ID) { + process = false; + retval = 3; + } + } + } + + break; + } + } + } + } + + starttime = TickCount.Time(); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + commands->Draw_All(); + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + //Smart_Printf( "Canceled waiting for SERIAL_CONNECT\n" ); + retval = 0; + process = false; + break; + + default: + break; + } + /*..................................................................... + Service the connection. + .....................................................................*/ + NullModem.Service(); + if (NullModem.Num_Send() == 0) { + //Smart_Printf( "No more messages to send.\n" ); + if (NullModem.Get_Message (&ReceivePacket, &packetlen) > 0) { + if (ReceivePacket.Command == SERIAL_CONNECT) { + //Smart_Printf( "Received3 SERIAL_CONNECT %d, ID %d \n", ReceivePacket.Seed, ReceivePacket.ID ); + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < 30) + NullModem.Service(); + + // + // whoever has the highest time is the host + // + if (ReceivePacket.Seed > SendPacket.Seed) { + process = false; + retval = 2; + } else { + + if (ReceivePacket.Seed == SendPacket.Seed) { + if (ReceivePacket.ID > SendPacket.ID) { + process = false; + retval = 2; + } else { + + // + // if they are equal then it's a loopback cable or a modem + // + if (ReceivePacket.ID == SendPacket.ID) { + process = false; + retval = 3; + } + } + } + } + + } else { + retval = 0; + process = false; + } + } else { + retval = 1; + process = false; + } + } + + if (TickCount.Time() - starttime > 3600) { // only wait 1 minute + retval = 0; + process = false; + } + } /* end of while */ + + return( retval ); + +} + + +/*************************************************************************** + * Reconnect_Modem -- allows user to reconnect * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = failure to connect; 1 = connect OK * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +int Reconnect_Modem( void ) +{ + int status; + int modemstatus; + + + switch (ModemGameToPlay) { + case (MODEM_NULL_HOST): + case (MODEM_NULL_JOIN): + status = Reconnect_Null_Modem(); + break; + + case (MODEM_DIALER): + modemstatus = NullModem.Get_Modem_Status(); + if ( (modemstatus & CD_SET) ) { + //Smart_Printf( "Dial Modem connection error! Attempting reconnect....\n" ); + status = Reconnect_Null_Modem(); + } else { + status = Dial_Modem( DialSettings, true ); + } + break; + + case (MODEM_ANSWERER): + modemstatus = NullModem.Get_Modem_Status(); + if ( (modemstatus & CD_SET) ) { + //Smart_Printf( "Answer Modem connection error! Attempting reconnect....\n" ); + status = Reconnect_Null_Modem(); + } else { + status = Answer_Modem( DialSettings, true ); + } + break; + } + + return( status ); +} + + +/*************************************************************************** + * Reconnect_Null_Modem -- allows user to reconnect * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 = failure to connect; 1 = connect OK * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +static int Reconnect_Null_Modem( void ) +{ + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + }; + + /*........................................................................ + Dialog variables + ........................................................................*/ + bool process = true; // process while true + KeyNumType input; + + int retval; + unsigned long starttime; + unsigned long lastmsgtime; + int packetlen; + + int x,y,width,height; // dialog dimensions + char buffer[80*3]; + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + + /* + ** Determine the dimensions of the text to be used for the dialog box. + ** These dimensions will control how the dialog box looks. + */ + strcpy( buffer, Text_String( TXT_NULL_CONNERR_CHECK_CABLES ) ); + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + Format_Window_String(buffer, 200, width, height); + + width = MAX(width, 50); + width += 40; + height += 60; + + x = (320 - width) / 2; + y = (200 - height) / 2; + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + x + ((width - (String_Pixel_Width( Text_String( TXT_CANCEL ) ) + 8)) >> 1), + y + height - (FontHeight + FontYSpacing + 2) - 5); + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + process = true; + + /* + ............................ Create the list ............................. + */ + commands = &cancelbtn; + + commands->Flag_List_To_Redraw(); + + /* + ............................ Draw the dialog ............................. + */ + Hide_Mouse(); + + Dialog_Box(x, y, width, height); + Draw_Caption(TXT_NONE, x, y, width); + + Fancy_Text_Print(buffer, x + 20, y + 25, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + commands->Draw_All(); + Show_Mouse(); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + starttime = lastmsgtime = TickCount.Time(); + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + commands->Draw_All(); + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + retval = false; + process = false; + break; + + default: + break; + } + /*..................................................................... + Service the connection. + .....................................................................*/ + NullModem.Service(); + + /*..................................................................... + Resend our message if it's time + .....................................................................*/ + if (TickCount.Time() - starttime > PACKET_RETRANS_TIME) { + starttime = TickCount.Time(); + SendPacket.Command = SERIAL_CONNECT; + SendPacket.ID = MPlayerLocalID; + //Smart_Printf( "Sending a SERIAL_CONNECT packet !!!!!!!!\n" ); + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 0); + } + + /*..................................................................... + Check for an incoming message + .....................................................................*/ + if (NullModem.Get_Message (&ReceivePacket, &packetlen) > 0) { + + lastmsgtime = TickCount.Time(); + + if (ReceivePacket.Command == SERIAL_CONNECT) { + //Smart_Printf( "Received a SERIAL_CONNECT packet !!!!!!!!\n" ); + + // are we getting our own packets back?? + + if (ReceivePacket.ID == MPlayerLocalID) { + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + retval = false; + process = false; + break; + } + + /*............................................................... + OK, we got our message; now we have to make certain the other + guy gets his, so send him one with an ACK required. + ...............................................................*/ + SendPacket.Command = SERIAL_CONNECT; + SendPacket.ID = MPlayerLocalID; + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < 60) + NullModem.Service(); + retval = true; + process = false; + } + } + + // + // timeout if we do not get any packets + // + if (TickCount.Time() - lastmsgtime > PACKET_CANCEL_TIMEOUT) { + retval = false; + process = false; + } + + } /* end of while */ + + return( retval ); + +} + + +/*********************************************************************************************** + * Destroy_Null_Connection -- destroys the given connection * + * * + * Call this routine when a connection goes bad, or another player signs off. * + * * + * INPUT: * + * id connection ID to destroy * + * error 0 = user signed off; 1 = connection error * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/31/1995 DRD : Created. * + *=============================================================================================*/ +void Destroy_Null_Connection(int id, int error) +{ + int i,j,idx; + HousesType house; + HouseClass *housep; + char txt[80]; + + + if ( MPlayerCount == 1 ) { + return; + } + + // find index for id + + idx = -1; + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID[i] == (unsigned char)id) { + idx = i; + break; + } + } + + if (idx == -1) { + return; + } + + /*------------------------------------------------------------------------ + Create a message to display to the user + ------------------------------------------------------------------------*/ + txt[0] = '\0'; + if (error == 1) { + sprintf(txt,Text_String(TXT_CONNECTION_LOST), MPlayerNames[idx] ); + } + else if (error == 0) { + sprintf(txt,Text_String(TXT_LEFT_GAME), MPlayerNames[idx] ); + } + else if (error == -1) { + NullModem.Delete_Connection(); + } + + if (strlen(txt)) { + Messages.Add_Message (txt, + MPlayerTColors[MPlayerID_To_ColorIndex((unsigned char)id)], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, 0, 0); + Map.Flag_To_Redraw(false); + } + + for (i = 0; i < MPlayerCount; i++) { + if (MPlayerID[i] == (unsigned char)id) { + /*.................................................................. + Turn the player's house over to the computer's AI + ..................................................................*/ + house = MPlayerHouses[i]; + housep = HouseClass::As_Pointer (house); + housep->IsHuman = false; + + /*.................................................................. + Move arrays back by one + ..................................................................*/ + for (j = i; j < MPlayerCount - 1; j++) { + MPlayerID[j] = MPlayerID[j + 1]; + MPlayerHouses[j] = MPlayerHouses[j + 1]; + strcpy (MPlayerNames[j], MPlayerNames[j+1]); + TheirProcessTime[j] = TheirProcessTime[j+1]; + } + } + } + + MPlayerCount--; + + /*------------------------------------------------------------------------ + If we're the last player left, tell the user. + ------------------------------------------------------------------------*/ + if (MPlayerCount == 1) { + sprintf(txt,"%s",Text_String(TXT_JUST_YOU_AND_ME)); + Messages.Add_Message (txt, + MPlayerTColors[MPlayerID_To_ColorIndex((unsigned char)id)], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, 0, 0); + Map.Flag_To_Redraw(false); + } + +} /* end of Destroy_Null_Connection */ + + +/*************************************************************************** + * Select_Serial_Dialog -- Serial Communications menu dialog * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * GAME_MODEM user wants to play a modem game * + * GAME_NULL_MODEM user wants to play a null-modem game * + * GAME_NORMAL user hit Cancel * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +GameType Select_Serial_Dialog( void ) +{ + int rc; +// int value, i; + int com = -1, baud = -1; + int error = 0; + + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 160 *factor; // dialog width + int d_dialog_h = 94 *factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord +// D_DIALOG_Y = ((200 - D_DIALOG_H) / 2), // dialog y-coord + int d_dialog_y = ((136*factor - d_dialog_h) / 2); // dialog y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 11*factor; // ht of 6-pt text + int d_margin = 7; // margin width/height + + int d_dial_w = 90 *factor; + int d_dial_h = 9 *factor; + int d_dial_x = d_dialog_cx - d_dial_w / 2; + int d_dial_y = d_dialog_y + d_margin + d_txt6_h + d_margin; + + int d_answer_w = 90 *factor; + int d_answer_h = 9 *factor; + int d_answer_x = d_dialog_cx - d_answer_w / 2; + int d_answer_y = d_dial_y + d_dial_h + 2; + + int d_nullmodem_w = 90 *factor; + int d_nullmodem_h = 9 *factor; + int d_nullmodem_x = d_dialog_cx - d_nullmodem_w / 2; + int d_nullmodem_y = d_answer_y + d_answer_h + 2; + + int d_settings_w = 90 *factor; + int d_settings_h = 9 *factor; + int d_settings_x = d_dialog_cx - d_settings_w / 2; + int d_settings_y = d_nullmodem_y + d_nullmodem_h + 2; + +#if (GERMAN | FRENCH) + int d_cancel_w = 50 *factor; +#else + int d_cancel_w = 40 *factor; +#endif + int d_cancel_h = 9 *factor; + int d_cancel_x = d_dialog_cx - d_cancel_w / 2; + int d_cancel_y = d_settings_y + d_settings_h + d_margin; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_DIAL = 100, + BUTTON_ANSWER, + BUTTON_NULLMODEM, + BUTTON_SETTINGS, + BUTTON_CANCEL, + + NUM_OF_BUTTONS = 5, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + bool process = true; // process while true + KeyNumType input; + char namebuf[MPLAYER_NAME_MAX] = {0}; // buffer for player's name + int tabs[] = {77}; // tabs for player list box + GameType retval; // return value + + int selection; + bool pressed; + int curbutton; + TextButtonClass *buttons[NUM_OF_BUTTONS]; + + SerialSettingsType *settings; + bool selectsettings = false; + + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + TextButtonClass dialbtn(BUTTON_DIAL, TXT_DIAL_MODEM, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_dial_x, d_dial_y, d_dial_w, d_dial_h); + + TextButtonClass answerbtn(BUTTON_ANSWER, TXT_ANSWER_MODEM, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_answer_x, d_answer_y, d_answer_w, d_answer_h); + + TextButtonClass nullmodembtn(BUTTON_NULLMODEM, TXT_NULL_MODEM, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_nullmodem_x, d_nullmodem_y, d_nullmodem_w, d_nullmodem_h); + + TextButtonClass settingsbtn(BUTTON_SETTINGS, TXT_SETTINGS, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_settings_x, d_settings_y, d_settings_w, d_settings_h); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_cancel_x, d_cancel_y); +#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +#endif + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + + /*........................................................................ + Read the CC.INI file to extract default serial settings, scenario numbers + & descriptions, and the phone list. + ........................................................................*/ + Read_MultiPlayer_Settings (); + + if (SerialDefaults.Port == 0 || + SerialDefaults.IRQ == -1 || + SerialDefaults.Baud == -1) { + selectsettings = true; + } else { + if ( NullModem.Detect_Port( &SerialDefaults ) != PORT_VALID ) { + selectsettings = true; + } + } + + /* + ............................ Create the list ............................. + */ + commands = &dialbtn; + answerbtn.Add_Tail(*commands); + nullmodembtn.Add_Tail(*commands); + settingsbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + + /* + ......................... Fill array of button ptrs ...................... + */ + curbutton = 0; + buttons[0] = &dialbtn; + buttons[1] = &answerbtn; + buttons[2] = &nullmodembtn; + buttons[3] = &settingsbtn; + buttons[4] = &cancelbtn; + buttons[curbutton]->Turn_On(); + + Keyboard::Clear(); + + Fancy_Text_Print(TXT_NONE, 0, 0, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + +Debug_Smart_Print = true; + + MPlayerLocalID = 0xff; // set to invalid value + + /* + -------------------------- Main Processing Loop -------------------------- + */ + display = REDRAW_ALL; + process = true; + pressed = false; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + /* + ..................... Refresh the backdrop ...................... + */ + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + /* + ..................... Draw the background ....................... + */ + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + /* + ..................... Redraw the buttons ....................... + */ + commands->Draw_All(); + /* + ....................... Draw the labels ......................... + */ + Draw_Caption (TXT_SELECT_SERIAL_GAME, d_dialog_x, d_dialog_y, d_dialog_w); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ............................ Process input ............................ + */ + switch (input) { + case (BUTTON_DIAL | KN_BUTTON): + selection = BUTTON_DIAL; + pressed = true; + break; + + case (BUTTON_ANSWER | KN_BUTTON): + selection = BUTTON_ANSWER; + pressed = true; + break; + + case (BUTTON_NULLMODEM | KN_BUTTON): + selection = BUTTON_NULLMODEM; + pressed = true; + break; + + case (BUTTON_SETTINGS | KN_BUTTON): + selection = BUTTON_SETTINGS; + pressed = true; + break; + + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + selection = BUTTON_CANCEL; + pressed = true; + break; + + case (KN_UP): + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + curbutton--; + if (curbutton < 0) + curbutton = (NUM_OF_BUTTONS - 1); + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + break; + + case (KN_DOWN): + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + curbutton++; + if (curbutton > (NUM_OF_BUTTONS - 1) ) + curbutton = 0; + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + break; + + case (KN_RETURN): + selection = curbutton + BUTTON_DIAL; + pressed = true; + break; + + default: + break; + } + + if (pressed) { + // + // to make sure the selection is correct in case they used the mouse + // + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + curbutton = selection - BUTTON_DIAL; + buttons[curbutton]->Turn_On(); + buttons[curbutton]->IsPressed = true; + buttons[curbutton]->Draw_Me(true); + + switch (selection) { + case (BUTTON_DIAL): + + if (selectsettings) { + CCMessageBox().Process(TXT_SELECT_SETTINGS); + } + + /* + ** Remote-connect + */ + else if ( Phone_Dialog() ) { + if (PhoneBook[CurPhoneIdx]->Settings.Port == 0) { + settings = &SerialDefaults; + } else { + settings = &(PhoneBook[CurPhoneIdx]->Settings); + } + + if (SerialPort){ + delete SerialPort; + } + SerialPort = new WinModemClass; + + if ( Init_Null_Modem( settings ) ) { + + if (settings->CallWaitStringIndex == CALL_WAIT_CUSTOM) { + strcpy( DialString, settings->CallWaitString ); + } else { + strcpy( DialString, + CallWaitStrings[ settings->CallWaitStringIndex ] ); + } + strcat( DialString, PhoneBook[ CurPhoneIdx ]->Number ); + + if ( Dial_Modem( settings, false ) ) { + ModemGameToPlay = MODEM_DIALER; + if ( Com_Scenario_Dialog() ) { + retval = GAME_MODEM; + process = false; + } + } + + if (process) { // restore to default + NullModem.Change_IRQ_Priority( 0 ); + } + } else { + CCMessageBox().Process(TXT_SELECT_SETTINGS); + } + } + + if (process) { + buttons[curbutton]->IsPressed = false; + buttons[curbutton]->Flag_To_Redraw(); + } + + display = REDRAW_ALL; + break; + + case (BUTTON_ANSWER): + + if (selectsettings) { + CCMessageBox().Process(TXT_SELECT_SETTINGS); + } else { + /* + ** Remote-connect + */ + settings = &SerialDefaults; + + if (SerialPort){ + delete SerialPort; + } + SerialPort = new WinModemClass; + + if ( Init_Null_Modem( settings ) ) { + if ( Answer_Modem( settings, false ) ) { + ModemGameToPlay = MODEM_ANSWERER; + if ( Com_Show_Scenario_Dialog() ) { + retval = GAME_MODEM; + process = false; + } + } + + if (process) { // restore to default + NullModem.Change_IRQ_Priority( 0 ); + } + } else { + CCMessageBox().Process(TXT_SELECT_SETTINGS); + } + } + + if (process) { + buttons[curbutton]->IsPressed = false; + buttons[curbutton]->Flag_To_Redraw(); + } + + display = REDRAW_ALL; + break; + + case (BUTTON_NULLMODEM): + + if (selectsettings) { + CCMessageBox().Process(TXT_SELECT_SETTINGS); + } else { + /* + ** Otherwise, remote-connect; save values if we're recording + */ + + if (SerialPort){ + delete SerialPort; + } + SerialPort = new WinNullModemClass; + + if ( Init_Null_Modem( &SerialDefaults ) ) { + rc = Test_Null_Modem(); + switch (rc) { + case (1): + ModemGameToPlay = MODEM_NULL_HOST; + if ( Com_Scenario_Dialog() ) { + retval = GAME_NULL_MODEM; + process = false; + } + break; + + case (2): + ModemGameToPlay = MODEM_NULL_JOIN; + if ( Com_Show_Scenario_Dialog() ) { + retval = GAME_NULL_MODEM; + process = false; + } + break; + + case (3): + CCMessageBox().Process( TXT_MODEM_OR_LOOPBACK ); + break; + } + + if (process) { // restore to default + NullModem.Change_IRQ_Priority( 0 ); + } + } else { + CCMessageBox().Process(TXT_SELECT_SETTINGS); + } + } + + if (process) { + buttons[curbutton]->IsPressed = false; + buttons[curbutton]->Flag_To_Redraw(); + } + + display = REDRAW_ALL; + break; + + case (BUTTON_SETTINGS): + if ( Com_Settings_Dialog( &SerialDefaults ) ) { + Write_MultiPlayer_Settings (); + + selectsettings = true; + + if (SerialDefaults.Port != 0 && + SerialDefaults.IRQ != -1 && + SerialDefaults.Baud != -1) { + if ( NullModem.Detect_Port( &SerialDefaults ) == PORT_VALID) { + selectsettings = false; + } + } + } + + buttons[curbutton]->IsPressed = false; + buttons[curbutton]->Flag_To_Redraw(); + display = REDRAW_ALL; + break; + + case (BUTTON_CANCEL): + retval = GAME_NORMAL; + process = false; + break; + } + + pressed = false; + } + } /* end of while */ + +#if 0 + if (retval == GAME_NORMAL) { + Debug_Smart_Print = false; + } +#endif + +Debug_Smart_Print = false; + + return( retval ); +} + + +/*************************************************************************** + * Com_Settings_Dialog -- Lets user select serial port settings * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Settings ³ * + * ³ ³ * + * ³ Port:____ IRQ:__ Baud:______ ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ³ ³ ³ ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ ³ * + * ³ Initialization: [Add] [Delete] ³ * + * ³ _____________________________ ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ * + * ³ ³ ³ ³ * + * ³ ³ ³ ³ * + * ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ ³ * + * ³ Call Waiting: ³ * + * ³ _______________ ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ [Tone Dialing] ³ * + * ³ ³ ³ ³ * + * ³ ³ ³ [Pulse Dialing] ³ * + * ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ ³ * + * ³ [OK] [Cancel] ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * settings ptr to SerialSettingsType structure * + * * + * OUTPUT: * + * true = OK, false = Cancel * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +static int Com_Settings_Dialog( SerialSettingsType *settings ) +{ +/* ###Change collision detected! C:\PROJECTS\CODE\NULLDLG.CPP... */ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 301 *factor; // dialog width + int d_dialog_h = 200 *factor; // dialog height + int d_dialog_x = ((320 *factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200 *factor - d_dialog_h) / 2); // dialog y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 6 *factor +1; // ht of 6-pt text + int d_margin = 5 *factor; // margin width/height + +#ifdef EDIT_IRQ + int d_portlist_w = 80 *factor; + int d_portlist_h = 35 *factor; + int d_portlist_x = d_dialog_x + (d_dialog_w / 6) - (d_portlist_w / 2); + int d_portlist_y = d_dialog_y + ((d_margin + d_txt6_h) * 2) + d_margin + 10 *factor; + + int d_port_w = ((PORTBUF_MAX - 1) * 6 *factor) + 4 *factor; + int d_port_h = 9 *factor; + int d_port_x = d_portlist_x + 31 *factor; + int d_port_y = d_portlist_y - d_margin - d_txt6_h; + + + int d_irqlist_w = 80 *factor; + int d_irqlist_h = 35 *factor; + int d_irqlist_x = d_dialog_x + (d_dialog_w / 2) - (d_irqlist_w / 2); + int d_irqlist_y = d_portlist_y; + + int d_irq_w = ((IRQBUF_MAX - 1) * 6 *factor) + 3 *factor; + int d_irq_h = 9 *factor; + int d_irq_x = d_irqlist_x + 25 *factor; + int d_irq_y = d_irqlist_y - d_margin - d_txt6_h; + + int d_baudlist_w = 80 *factor; + int d_baudlist_h = 35 *factor; + int d_baudlist_x = d_dialog_x + ((d_dialog_w * 5) / 6) - (d_baudlist_w / 2); + int d_baudlist_y = d_portlist_y; + + int d_baud_w = ((BAUDBUF_MAX - 1) * 6 *factor) + 3 *factor; + int d_baud_h = 9 *factor; + int d_baud_x = d_baudlist_x + 31 *factor; + int d_baud_y = d_baudlist_y - d_margin - d_txt6_h; + +#endif //EDIT_IRQ + int d_initstrlist_w = ((INITSTRBUF_MAX - 1) * 6 *factor) + 8 + 3 *factor; + int d_initstrlist_h = 21 *factor; + int d_initstrlist_x = d_dialog_cx - (d_initstrlist_w / 2); + int d_initstrlist_y = d_dialog_y + ((d_margin + d_txt6_h) * 2) + d_margin + 10 *factor + + 35*factor + + ((d_margin + d_txt6_h) * 2) + d_margin + 4 *factor; + + int d_initstr_w = ((INITSTRBUF_MAX - 1) * 6 *factor) + 3 *factor; + int d_initstr_h = 9 *factor; + int d_initstr_x = d_initstrlist_x; + int d_initstr_y = d_initstrlist_y - d_margin - d_txt6_h; + +#ifndef EDIT_IRQ + int d_portlist_w = 80 *factor; + int d_portlist_h = 35 *factor; + int d_portlist_x = d_initstrlist_x; + int d_portlist_y = d_dialog_y + ((d_margin + d_txt6_h) * 2) + d_margin + 10 *factor; + + int d_port_w = ((PORTBUF_MAX - 1) * 6 *factor) + 4 *factor; + int d_port_h = 9 *factor; + int d_port_x = d_portlist_x + 31 *factor; + int d_port_y = d_portlist_y - d_margin - d_txt6_h; + + int d_baudlist_w = 80 *factor; + int d_baudlist_h = 35 *factor; + int d_baudlist_x = d_portlist_x + d_portlist_w + 20 * factor; + int d_baudlist_y = d_portlist_y; + + int d_baud_w = ((BAUDBUF_MAX - 1) * 6 *factor) + 3 *factor; + int d_baud_h = 9 *factor; + int d_baud_x = d_baudlist_x + 31 *factor; + int d_baud_y = d_baudlist_y - d_margin - d_txt6_h; + + int d_inittype_w = 30*factor; + int d_inittype_h = 9*factor; + int d_inittype_x = d_dialog_x + ((d_dialog_w * 5) / 6) - (d_inittype_w / 2); + int d_inittype_y = d_baud_y + 20*factor; + +#endif //EDIT_IRQ + + int d_add_w = 45 *factor; + int d_add_h = 9 *factor; +#ifdef FRENCH + int d_add_x = (d_dialog_cx - (d_add_w / 2))+34*factor; +#else + int d_add_x = d_dialog_cx - (d_add_w / 2); +#endif + int d_add_y = d_initstr_y - d_add_h - 3*factor; + + int d_delete_w = 45 *factor; + int d_delete_h = 9 *factor; + +#ifdef FRENCH + int d_delete_x = 14*factor + d_dialog_x + ((d_dialog_w * 3) / 4) - (d_delete_w / 2); +#else + int d_delete_x = d_dialog_x + ((d_dialog_w * 3) / 4) - (d_delete_w / 2); +#endif + int d_delete_y = d_initstr_y - d_add_h - 3 *factor; + + int d_cwaitstrlist_w = (((CWAITSTRBUF_MAX - 1) + 9) * 6 *factor) + 3 *factor; + int d_cwaitstrlist_h = 27 *factor; + int d_cwaitstrlist_x = d_initstrlist_x; + int d_cwaitstrlist_y = d_initstrlist_y + d_initstrlist_h + ((d_margin + d_txt6_h) * 2) + 2 *factor; + + int d_cwaitstr_w = ((CWAITSTRBUF_MAX - 1) * 6 *factor) + 3 *factor; + int d_cwaitstr_h = 9 *factor; + int d_cwaitstr_x = d_cwaitstrlist_x; + int d_cwaitstr_y = d_cwaitstrlist_y - d_margin - d_txt6_h; + + int d_tone_w = 80 *factor; + int d_tone_h = 9 *factor; + int d_tone_x = d_dialog_x + ((d_dialog_w * 3) / 4) - (d_tone_w / 2); + int d_tone_y = d_cwaitstrlist_y; + + int d_pulse_w = 80 *factor; + int d_pulse_h = 9 *factor; + int d_pulse_x = d_dialog_x + ((d_dialog_w * 3) / 4) - (d_pulse_w / 2); + int d_pulse_y = d_tone_y + d_tone_h + d_margin; + + int d_save_w = 40 *factor; + int d_save_h = 9 *factor; + int d_save_x = d_dialog_x + (d_dialog_w / 3) - (d_save_w / 2); + int d_save_y = d_dialog_y + d_dialog_h - d_save_h - d_margin - 2 *factor; + +#if (GERMAN | FRENCH) + int d_cancel_w = 50 *factor; +#else + int d_cancel_w = 40 *factor; +#endif + int d_cancel_h = 9 *factor; + int d_cancel_x = d_dialog_x + ((d_dialog_w * 2) / 3) - (d_cancel_w / 2); + int d_cancel_y = d_dialog_y + d_dialog_h - d_cancel_h - d_margin - 2 *factor; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_PORT = 100, + BUTTON_PORTLIST, + BUTTON_IRQ, + BUTTON_IRQLIST, + BUTTON_BAUD, + BUTTON_BAUDLIST, + BUTTON_INITSTR, + BUTTON_INITSTRLIST, + BUTTON_ADD, + BUTTON_DELETE, + BUTTON_CWAITSTR, + BUTTON_CWAITSTRLIST, + BUTTON_TONE, + BUTTON_PULSE, + BUTTON_SAVE, + BUTTON_INITTYPE, + BUTTON_CANCEL, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + static char *portname[5] = { + "COM1 - 3F8", + "COM2 - 2F8", + "COM3 - 3E8", + "COM4 - 2E8", + "CUSTOM - ????" + }; + +#ifdef EDIT_IRQ + static char *irqname[5] = { + "2 / 9", + "3 - [COM2 & 4]", + "4 - [COM1 & 3]", + "5", + "CUSTOM - ??" + }; + + static int _irqidx[4] = { + 2, + 1, + 2, + 1 + }; +#endif // EDIT_IRQ + + static char *baudname[5] = { + "14400", + "19200", + "28800", + "38400", + "57600", + }; + + static char *init_types[2] = { + "Normal", + "Full", + }; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + BOOL process = true; // process while true + KeyNumType input; + char * item; // general-purpose string + char * temp; // general-purpose string + + char portbuf[ PORTBUF_MAX ] = {0}; // buffer for port +#ifdef EDIT_IRQ + char irqbuf[ IRQBUF_MAX ] = {0}; // buffer for irq +#endif //EDIT_IRQ + char baudbuf[ BAUDBUF_MAX ] = {0}; // buffer for baud + char initstrbuf[ INITSTRBUF_MAX ] = {0}; // buffer for init string + char cwaitstrbuf[ CWAITSTRBUF_MAX ] = {0}; // buffer for call waiting string + + int port_index = 1; // index of currently-selected port (default = com2) +#ifdef EDIT_IRQ + int irq_index = 1; // index of currently-selected irq (default = 3) +#endif //EDIT_IRQ + int baud_index = 1; // index of currently-selected baud (default = 19200) + int initstr_index = 0; // index of currently-selected modem init (default = "ATZ") + int cwaitstr_index = CALL_WAIT_CUSTOM; // index of currently-selected call waiting (default = "") + int rc = 0; // -1 = user cancelled, 1 = New + int i; // loop counter + int pos; + int len; + int firsttime = 1; + SerialSettingsType tempsettings; + DetectPortType dpstatus; + char init_text[32]; + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + EditClass port_edt (BUTTON_PORT, + portbuf, PORTBUF_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_port_x, d_port_y, d_port_w, d_port_h, EditClass::ALPHANUMERIC); + + ListClass portlist(BUTTON_PORTLIST, + d_portlist_x, d_portlist_y, d_portlist_w, d_portlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + +#ifdef EDIT_IRQ + EditClass irq_edt (BUTTON_IRQ, + irqbuf, IRQBUF_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_irq_x, d_irq_y, d_irq_w, d_irq_h, EditClass::NUMERIC); + + ListClass irqlist(BUTTON_IRQLIST, + d_irqlist_x, d_irqlist_y, d_irqlist_w, d_irqlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); +#endif //EDIT_IRQ + + EditClass baud_edt (BUTTON_BAUD, + baudbuf, BAUDBUF_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_baud_x, d_baud_y, d_baud_w, d_baud_h, EditClass::NUMERIC); + + ListClass baudlist(BUTTON_BAUDLIST, + d_baudlist_x, d_baudlist_y, d_baudlist_w, d_baudlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + EditClass initstr_edt (BUTTON_INITSTR, + initstrbuf, INITSTRBUF_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_initstr_x, d_initstr_y, d_initstr_w, d_initstr_h, EditClass::ALPHANUMERIC); + + ListClass initstrlist(BUTTON_INITSTRLIST, + d_initstrlist_x, d_initstrlist_y, d_initstrlist_w, d_initstrlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + TextButtonClass addbtn(BUTTON_ADD, TXT_ADD, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#ifdef FRENCH + d_add_x, d_add_y); +#else + d_add_x, d_add_y, d_add_w, d_add_h); +#endif + + TextButtonClass deletebtn(BUTTON_DELETE, TXT_DELETE_BUTTON, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#ifdef FRENCH + d_delete_x, d_delete_y); +#else + d_delete_x, d_delete_y, d_delete_w, d_delete_h); +#endif + + EditClass cwaitstr_edt (BUTTON_CWAITSTR, + cwaitstrbuf, CWAITSTRBUF_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_cwaitstr_x, d_cwaitstr_y, d_cwaitstr_w, d_cwaitstr_h, EditClass::ALPHANUMERIC); + + ListClass cwaitstrlist(BUTTON_CWAITSTRLIST, + d_cwaitstrlist_x, d_cwaitstrlist_y, d_cwaitstrlist_w, d_cwaitstrlist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + TextButtonClass tonebtn(BUTTON_TONE, TXT_TONE_BUTTON, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_tone_x, d_tone_y, d_tone_w, d_tone_h); + + TextButtonClass pulsebtn(BUTTON_PULSE, TXT_PULSE_BUTTON, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_pulse_x, d_pulse_y, d_pulse_w, d_pulse_h); + + TextButtonClass savebtn(BUTTON_SAVE, TXT_SAVE_BUTTON, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_save_x, d_save_y); +#else + d_save_x, d_save_y, d_save_w, d_save_h); +#endif + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_cancel_x, d_cancel_y); +#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +#endif +#if (0) + TextButtonClass inittypebutton(BUTTON_INITTYPE, init_text, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_inittype_x, d_inittype_y, d_inittype_w, d_inittype_h); +#endif //(0) + + /* + ----------------------------- Various Inits ------------------------------ + */ + memcpy( &tempsettings, settings, sizeof(SerialSettingsType) ); + + strcpy (init_text, init_types[tempsettings.Init]); + + if (tempsettings.Port == 0) { + tempsettings.Port = 0x2f8; + } + + if (tempsettings.IRQ == -1) { + tempsettings.IRQ = 3; + } + + if (tempsettings.Baud == -1) { + tempsettings.Baud = 19200; + } + + /*........................................................................ + Set the current indices + ........................................................................*/ + switch ( tempsettings.Port ) { + case ( 0x3f8 ): + port_index = 0; + strcpy (portbuf, "COM1"); + break; + + case ( 0x2f8 ): + port_index = 1; + strcpy (portbuf, "COM2"); + break; + + case ( 0x3e8 ): + port_index = 2; + strcpy (portbuf, "COM3"); + break; + + case ( 0x2e8 ): + port_index = 3; + strcpy (portbuf, "COM4"); + break; + + default: + port_index = 4; + sprintf (portbuf, "%x", tempsettings.Port); + temp = strchr( portname[4], '-' ); + if ( temp ) { + pos = (int)(temp - portname[4]) + 2; + len = strlen( portbuf ); + strncpy( portname[4] + pos, portbuf, len ); + *(portname[4] + pos + len) = 0; + } + break; + } + +#ifdef EDIT_IRQ + switch ( tempsettings.IRQ ) { + case ( 2 ): + irq_index = 0; + strcpy (irqbuf, "2"); + break; + + case ( 3 ): + irq_index = 1; + strcpy (irqbuf, "3"); + break; + + case ( 4 ): + irq_index = 2; + strcpy (irqbuf, "4"); + break; + + case ( 5 ): + irq_index = 3; + strcpy (irqbuf, "5"); + break; + + default: + irq_index = 4; + sprintf (irqbuf, "%d", tempsettings.IRQ); + temp = strchr( irqname[4], '-' ); + if ( temp ) { + pos = (int)(temp - irqname[4]) + 2; + len = strlen( irqbuf ); + strncpy( irqname[4] + pos, irqbuf, len ); + *(irqname[4] + pos + len) = 0; + } + break; + } +#endif //EDIT_IRQ + + if (tempsettings.Baud == 14400) { + baud_index = 0; + } else { + if (tempsettings.Baud == 19200) { + baud_index = 1; + } else { + if (tempsettings.Baud == 28800) { + baud_index = 2; + } else { + if (tempsettings.Baud == 38400) { + baud_index = 3; + } else { + baud_index = 4; + } + } + } + } + + sprintf (baudbuf, "%d", tempsettings.Baud); + + /*........................................................................ + Set up the port list box & edit box + ........................................................................*/ + for (i = 0; i < 5; i++) { + portlist.Add_Item( portname[ i ] ); + } + + portlist.Set_Selected_Index( port_index ); + port_edt.Set_Text( portbuf, PORTBUF_MAX ); + + /*........................................................................ + Set up the IRQ list box & edit box + ........................................................................*/ +#ifdef EDIT_IRQ + for (i = 0; i < 5; i++) { + irqlist.Add_Item( irqname[ i ] ); + } + + irqlist.Set_Selected_Index( irq_index ); + irq_edt.Set_Text( irqbuf, IRQBUF_MAX ); +#endif //EDIT_IRQ + + /*........................................................................ + Set up the baud rate list box & edit box + ........................................................................*/ + for (i = 0; i < 5; i++) { + baudlist.Add_Item( baudname[ i ] ); + } + + baudlist.Set_Selected_Index( baud_index ); + baud_edt.Set_Text( baudbuf, BAUDBUF_MAX ); + + initstr_index = tempsettings.InitStringIndex; + Build_Init_String_Listbox(&initstrlist, &initstr_edt, initstrbuf, &initstr_index); + + /*........................................................................ + Set up the cwait rate list box & edit box + ........................................................................*/ + + cwaitstr_index = tempsettings.CallWaitStringIndex; + for (i = 0; i < CALL_WAIT_STRINGS_NUM; i++) { + if ( i == CALL_WAIT_CUSTOM ) { + item = CallWaitStrings[ i ]; + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + len = strlen( tempsettings.CallWaitString ); + strncpy( item + pos, tempsettings.CallWaitString, len ); + *(item + pos + len) = 0; + if (i == cwaitstr_index) { + strncpy( cwaitstrbuf, item + pos, CWAITSTRBUF_MAX ); + } + } + } else { + if (i == cwaitstr_index) { + strncpy( cwaitstrbuf, CallWaitStrings[ i ], CWAITSTRBUF_MAX ); + } + } + cwaitstrlist.Add_Item( CallWaitStrings[ i ] ); + } + + cwaitstrlist.Set_Selected_Index( cwaitstr_index ); + cwaitstr_edt.Set_Text( cwaitstrbuf, CWAITSTRBUF_MAX ); + + /*........................................................................ + Build the button list + ........................................................................*/ + commands = &cancelbtn; + port_edt.Add_Tail(*commands); + portlist.Add_Tail(*commands); +#ifdef EDIT_IRQ + irq_edt.Add_Tail(*commands); + irqlist.Add_Tail(*commands); +#endif // EDIT_IRQ + baud_edt.Add_Tail(*commands); + baudlist.Add_Tail(*commands); + //inittypebutton.Add_Tail(*commands); + initstr_edt.Add_Tail(*commands); + initstrlist.Add_Tail(*commands); + addbtn.Add_Tail(*commands); + deletebtn.Add_Tail(*commands); + cwaitstr_edt.Add_Tail(*commands); + cwaitstrlist.Add_Tail(*commands); + tonebtn.Add_Tail(*commands); + pulsebtn.Add_Tail(*commands); + savebtn.Add_Tail(*commands); + + if (tempsettings.DialMethod == DIAL_TOUCH_TONE) { + tonebtn.Turn_On(); + } else { + pulsebtn.Turn_On(); + } + /* + ---------------------------- Processing loop ----------------------------- + */ + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + // init font variables + + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_SETTINGS, d_dialog_x, d_dialog_y, d_dialog_w); + + Fancy_Text_Print( TXT_PORT_COLON, + d_port_x - 3, d_port_y + 1 *factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + +#ifdef EDIT_IRQ + Fancy_Text_Print( TXT_IRQ_COLON, + d_irq_x - 3, d_irq_y + 1 *factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); +#endif //EDIT_IRQ + + Fancy_Text_Print( TXT_BAUD_COLON, + d_baud_x - 3, d_baud_y + 1 *factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print( TXT_INIT_STRING, + d_initstr_x, d_initstr_y - d_txt6_h - 3 *factor, + CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print( TXT_CWAIT_STRING, + d_cwaitstr_x, d_cwaitstr_y - d_txt6_h - 3 *factor, + CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); +#if (0) + Fancy_Text_Print ( "Modem Init", + d_inittype_x, d_inittype_y - d_txt6_h - d_margin, + CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); +#endif //(0) + } + + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + cancelbtn.Flag_To_Redraw(); + port_edt.Flag_To_Redraw(); + portlist.Flag_To_Redraw(); +#ifdef EDIT_IRQ + irq_edt.Flag_To_Redraw(); + irqlist.Flag_To_Redraw(); +#endif // EDIT_IRQ + baud_edt.Flag_To_Redraw(); + baudlist.Flag_To_Redraw(); + //inittypebutton.Flag_To_Redraw(); + initstr_edt.Flag_To_Redraw(); + initstrlist.Flag_To_Redraw(); + addbtn.Flag_To_Redraw(); + deletebtn.Flag_To_Redraw(); + cwaitstr_edt.Flag_To_Redraw(); + cwaitstrlist.Flag_To_Redraw(); + tonebtn.Flag_To_Redraw(); + pulsebtn.Flag_To_Redraw(); + savebtn.Flag_To_Redraw(); + } + + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + if ( firsttime ) { + port_edt.Set_Focus(); + port_edt.Flag_To_Redraw(); + input = commands->Input(); + firsttime = 0; + } + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { +#if (0) + case (BUTTON_INITTYPE | KN_BUTTON): + tempsettings.Init = !tempsettings.Init; + strcpy (init_text, init_types[tempsettings.Init]); + inittypebutton.Flag_To_Redraw(); + break; +#endif //(0) + case (BUTTON_PORT | KN_BUTTON): + item = (char *)portlist.Current_Item(); + if (port_index < 4) { + temp = strchr( item, ' ' ); + if ( !temp ) { + strncpy( portbuf, item, PORTBUF_MAX ); + } else { + pos = (int)(temp - item); + strncpy( portbuf, item, pos ); + portbuf[pos] = 0; + } + port_edt.Set_Text( portbuf, PORTBUF_MAX ); + port_edt.Flag_To_Redraw(); +#ifdef EDIT_IRQ + irq_edt.Set_Focus(); + irq_edt.Flag_To_Redraw(); +#endif //EDIT_IRQ + } else { + strupr( portbuf ); + if ( stricmp(portbuf, "3F8") == 0 ) { + port_index = 0; + portlist.Set_Selected_Index( port_index ); + strcpy (portbuf, "COM1"); + display = REDRAW_BUTTONS; + } + else if ( stricmp(portbuf, "2F8") == 0 ) { + port_index = 1; + portlist.Set_Selected_Index( port_index ); + strcpy (portbuf, "COM2"); + display = REDRAW_BUTTONS; + } + else if ( stricmp(portbuf, "3E8") == 0 ) { + port_index = 2; + portlist.Set_Selected_Index( port_index ); + strcpy (portbuf, "COM3"); + display = REDRAW_BUTTONS; + } + else if ( stricmp(portbuf, "2E8") == 0 ) { + port_index = 3; + portlist.Set_Selected_Index( port_index ); + strcpy (portbuf, "COM4"); + display = REDRAW_BUTTONS; + } + else if ( strncmp(portbuf, "COM", 3) == 0 ) { + display = REDRAW_BUTTONS; + + switch ( (portbuf[3] - '0') ) { + case 1: + port_index = 0; + break; + + case 2: + port_index = 1; + break; + + case 3: + port_index = 2; + break; + + case 4: + port_index = 3; + break; + + default: + CCMessageBox().Process( TXT_INVALID_PORT_ADDRESS ); + port_edt.Set_Focus(); + display = REDRAW_ALL; + break; + } + + portlist.Set_Selected_Index( port_index ); + } else { + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + len = strlen( portbuf ); + strncpy( item + pos, portbuf, len ); + *(item + pos + len) = 0; + display = REDRAW_BUTTONS; + } + } + +#ifdef EDIT_IRQ + if (display == REDRAW_BUTTONS) { + irq_edt.Set_Focus(); + irq_edt.Flag_To_Redraw(); + } +#endif //EDIT_IRQ + } + break; + + case (BUTTON_PORTLIST | KN_BUTTON): + if (portlist.Current_Index() != port_index) { + port_index = portlist.Current_Index(); + item = (char *)portlist.Current_Item(); + if (port_index < 4) { + temp = strchr( item, ' ' ); + if ( !temp ) { + strncpy( portbuf, item, PORTBUF_MAX ); + } else { + pos = (int)(temp - item); + strncpy( portbuf, item, pos ); + portbuf[pos] = 0; + } + port_edt.Clear_Focus(); + + // auto select the irq for port + +#ifdef EDIT_IRQ + irq_index = _irqidx[ port_index ]; + irqlist.Set_Selected_Index( irq_index ); + item = (char *)irqlist.Current_Item(); + temp = strchr( item, ' ' ); + if ( !temp ) { + strncpy( irqbuf, item, 2 ); + } else { + pos = (int)(temp - item); + strncpy( irqbuf, item, pos ); + irqbuf[pos] = 0; + } + irq_edt.Clear_Focus(); +#endif //EDIT_IRQ + } else { + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + if ( *(item + pos) == '?' ) { + portbuf[0] = 0; + } else { + strncpy( portbuf, item + pos, PORTBUF_MAX ); + } + } + port_edt.Set_Focus(); + } + port_edt.Set_Text( portbuf, PORTBUF_MAX ); + display = REDRAW_BUTTONS; + } else { + if (port_index < 4) { + port_edt.Clear_Focus(); + } else { + port_edt.Set_Focus(); + } + display = REDRAW_BUTTONS; + } + break; + +#ifdef EDIT_IRQ + case (BUTTON_IRQ | KN_BUTTON): + item = (char *)irqlist.Current_Item(); + if (irq_index < 4) { + temp = strchr( item, ' ' ); + if ( !temp ) { + strncpy( irqbuf, item, IRQBUF_MAX ); + } else { + pos = (int)(temp - item); + strncpy( irqbuf, item, pos ); + irqbuf[pos] = 0; + } + irq_edt.Set_Text( irqbuf, IRQBUF_MAX ); + irq_edt.Flag_To_Redraw(); + } else { + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + len = strlen( irqbuf ); + strncpy( item + pos, irqbuf, len ); + *(item + pos + len) = 0; + display = REDRAW_BUTTONS; + } + } + baud_edt.Set_Focus(); + baud_edt.Flag_To_Redraw(); + break; + + case (BUTTON_IRQLIST | KN_BUTTON): + if (irqlist.Current_Index() != irq_index) { + irq_index = irqlist.Current_Index(); + item = (char *)irqlist.Current_Item(); + if (irq_index < 4) { + temp = strchr( item, ' ' ); + if ( !temp ) { + strncpy( irqbuf, item, IRQBUF_MAX ); + } else { + pos = (int)(temp - item); + strncpy( irqbuf, item, pos ); + irqbuf[pos] = 0; + } + irq_edt.Clear_Focus(); + } else { + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + if ( *(item + pos) == '?' ) { + irqbuf[0] = 0; + } else { + strncpy( irqbuf, item + pos, IRQBUF_MAX ); + } + } + irq_edt.Set_Focus(); + } + irq_edt.Set_Text( irqbuf, IRQBUF_MAX ); + } else { + if (irq_index < 4) { + irq_edt.Clear_Focus(); + } else { + irq_edt.Set_Focus(); + } + } + display = REDRAW_BUTTONS; + break; +#endif //EDIT_IRQ + + case (BUTTON_BAUD | KN_BUTTON): + item = (char *)baudlist.Current_Item(); + strncpy( baudbuf, item, BAUDBUF_MAX ); + baud_edt.Set_Text( baudbuf, BAUDBUF_MAX ); + initstr_edt.Set_Focus(); + initstr_edt.Flag_To_Redraw(); + display = REDRAW_BUTTONS; + break; + + case (BUTTON_BAUDLIST | KN_BUTTON): + if (baudlist.Current_Index() != baud_index) { + baud_index = baudlist.Current_Index(); + item = (char *)baudlist.Current_Item(); + strncpy( baudbuf, item, BAUDBUF_MAX ); + baud_edt.Set_Text( baudbuf, BAUDBUF_MAX ); + baud_edt.Clear_Focus(); + display = REDRAW_BUTTONS; + } + break; + +#if 0 + case (BUTTON_INITSTR | KN_BUTTON): + strupr( initstrbuf ); + strncpy( InitStrings[ initstr_index ], initstrbuf, INITSTRBUF_MAX ); + Build_Init_String_Listbox(&initstrlist, &initstr_edt, initstrbuf, + &initstr_index); + cwaitstr_edt.Set_Focus(); + cwaitstr_edt.Flag_To_Redraw(); + display = REDRAW_BUTTONS; + break; +#endif + + case (BUTTON_INITSTRLIST | KN_BUTTON): + if (initstrlist.Current_Index() != initstr_index) { + initstr_index = initstrlist.Current_Index(); + item = (char *)initstrlist.Current_Item(); + strncpy( initstrbuf, item, INITSTRBUF_MAX ); + initstr_edt.Set_Text( initstrbuf, INITSTRBUF_MAX ); + } + initstr_edt.Set_Focus(); + initstr_edt.Flag_To_Redraw(); + display = REDRAW_BUTTONS; + break; + + /*------------------------------------------------------------------ + Add a new InitString entry + ------------------------------------------------------------------*/ + case (BUTTON_ADD | KN_BUTTON): + + item = new char[ INITSTRBUF_MAX ]; + memset (item, 0, INITSTRBUF_MAX); + + strupr ( initstrbuf ); + strncpy ( item, initstrbuf, INITSTRBUF_MAX-1 ); + + InitStrings.Add ( item ); + Build_Init_String_Listbox (&initstrlist, &initstr_edt, initstrbuf, + &initstr_index); + /*............................................................ + Set the current listbox index to the newly-added item. + ............................................................*/ + for (i = 0; i < InitStrings.Count(); i++) { + if (item == InitStrings[i]) { + initstr_index = i; + strcpy( initstrbuf, InitStrings[ initstr_index ] ); + initstr_edt.Set_Text( initstrbuf, INITSTRBUF_MAX ); + initstrlist.Set_Selected_Index( initstr_index ); + } + } + initstr_edt.Set_Focus(); + initstr_edt.Flag_To_Redraw(); + display = REDRAW_BUTTONS; + break; + + /*------------------------------------------------------------------ + Delete the current InitString entry + ------------------------------------------------------------------*/ + case (BUTTON_DELETE | KN_BUTTON): + + if ( InitStrings.Count() && initstr_index != -1) { + InitStrings.Delete( initstr_index ); + Build_Init_String_Listbox(&initstrlist, &initstr_edt, initstrbuf, + &initstr_index); + } + break; + + case (BUTTON_CWAITSTR | KN_BUTTON): + item = (char *)cwaitstrlist.Current_Item(); + if (cwaitstr_index < 3) { + } else { + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + len = strlen( cwaitstrbuf ); + strncpy( item + pos, cwaitstrbuf, len ); + *(item + pos + len) = 0; + display = REDRAW_BUTTONS; + } + } + break; + + case (BUTTON_CWAITSTRLIST | KN_BUTTON): + if (cwaitstrlist.Current_Index() != cwaitstr_index) { + cwaitstr_index = cwaitstrlist.Current_Index(); + item = (char *)cwaitstrlist.Current_Item(); + if (cwaitstr_index < 3) { + strncpy( cwaitstrbuf, item, CWAITSTRBUF_MAX ); + cwaitstr_edt.Clear_Focus(); + } else { + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + strncpy( cwaitstrbuf, item + pos, CWAITSTRBUF_MAX ); + } + cwaitstr_edt.Set_Focus(); + } + cwaitstr_edt.Set_Text( cwaitstrbuf, CWAITSTRBUF_MAX ); + } else { + if (cwaitstr_index < 3) { + cwaitstr_edt.Clear_Focus(); + } else { + cwaitstr_edt.Set_Focus(); + } + } + display = REDRAW_BUTTONS; + break; + + case (BUTTON_TONE | KN_BUTTON): + tempsettings.DialMethod = DIAL_TOUCH_TONE; + tonebtn.Turn_On(); + pulsebtn.Turn_Off(); + break; + + case (BUTTON_PULSE | KN_BUTTON): + tempsettings.DialMethod = DIAL_PULSE; + tonebtn.Turn_Off(); + pulsebtn.Turn_On(); + break; + + /*------------------------------------------------------------------ + SAVE: save the com settings + ------------------------------------------------------------------*/ + case (KN_RETURN): + case (BUTTON_SAVE | KN_BUTTON): + switch (port_index) { + case ( 0 ): + tempsettings.Port = 0x3f8; + break; + + case ( 1 ): + tempsettings.Port = 0x2f8; + break; + + case ( 2 ): + tempsettings.Port = 0x3e8; + break; + + case ( 3 ): + tempsettings.Port = 0x2e8; + break; + + default: + sscanf( portbuf, "%x", &tempsettings.Port ); + break; + } + +#ifdef EDIT_IRQ + switch (irq_index) { + case ( 0 ): + tempsettings.IRQ = 2; + break; + + case ( 1 ): + tempsettings.IRQ = 3; + break; + + case ( 2 ): + tempsettings.IRQ = 4; + break; + + case ( 3 ): + tempsettings.IRQ = 5; + break; + + default: + sscanf( irqbuf, "%d", &tempsettings.IRQ ); + break; + } +#endif //EDIT_IRQ + + sscanf( baudbuf, "%d", &tempsettings.Baud ); + + tempsettings.InitStringIndex = initstr_index; + tempsettings.CallWaitStringIndex = cwaitstr_index; + + item = CallWaitStrings[ CALL_WAIT_CUSTOM ]; + temp = strchr( item, '-' ); + if ( temp ) { + pos = (int)(temp - item) + 2; + strncpy( cwaitstrbuf, item + pos, CWAITSTRBUF_MAX ); + } else { + cwaitstrbuf[ 0 ] = 0; + } + + strncpy( tempsettings.CallWaitString, cwaitstrbuf, CWAITSTRBUF_MAX ); + + dpstatus = NullModem.Detect_Port( &tempsettings ); + + if (dpstatus == PORT_VALID) { + process = false; + rc = true; + } + else if (dpstatus == PORT_INVALID) { + CCMessageBox().Process( TXT_INVALID_SETTINGS ); + firsttime = 1; + display = REDRAW_ALL; + } + else if (dpstatus == PORT_IRQ_INUSE) { + CCMessageBox().Process( TXT_IRQ_ALREADY_IN_USE ); + firsttime = 1; + display = REDRAW_ALL; + } + break; + + /*------------------------------------------------------------------ + CANCEL: send a SIGN_OFF, bail out with error code + ------------------------------------------------------------------*/ + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + process = false; + rc = false; + break; + } + + } /* end of while */ + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + /*------------------------------------------------------------------------ + Save values into the Settings structure + ------------------------------------------------------------------------*/ + if (rc) { + memcpy( settings, &tempsettings, sizeof(SerialSettingsType) ); + } + + return(rc); + +} /* end of Com_Settings_Dialog */ + + +/*************************************************************************** + * Build_Init_String_Listbox -- [re]builds the initstring listbox * + * * + * This routine rebuilds the initstring list box from scratch; it also * + * updates the contents of the initstring edit field. * + * * + * INPUT: * + * list ptr to list box * + * edit ptr to edit box * + * buf ptr to buffer for initstring * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/08/1995 DRD : Created. * + *=========================================================================*/ +static void Build_Init_String_Listbox (ListClass *list, EditClass *edit, char *buf, int *index) +{ + int i, curidx; + char *item; + + + curidx = *index; + + /*........................................................................ + Clear the list + ........................................................................*/ + while (list->Count()) { + item = (char *)(list->Get_Item(0)); + list->Remove_Item(item); + delete [] item; + } + + /* + ** Now sort the init string list by name then number + */ + qsort ((void *)(&InitStrings[0]), InitStrings.Count(), sizeof(char *), Init_String_Compare); + + /*........................................................................ + Build the list + ........................................................................*/ + for (i = 0; i < InitStrings.Count(); i++) { + item = new char[ INITSTRBUF_MAX ]; + strcpy( item, InitStrings[i] ); + list->Add_Item(item); + } + list->Flag_To_Redraw(); + + /*........................................................................ + Init the current phone book index + ........................................................................*/ + if (list->Count() == 0 || curidx < -1) { + curidx = -1; + } else { + if (curidx >= list->Count() ) { + curidx = 0; + } + } + + /*........................................................................ + Fill in initstring edit buffer + ........................................................................*/ + if (curidx > -1) { + strcpy (buf, InitStrings[ curidx ]); + edit->Set_Text (buf, INITSTRBUF_MAX ); + list->Set_Selected_Index( curidx ); + } + + *index = curidx; +} + + +/*************************************************************************** + * Init_String_Compare -- for qsort * + * * + * INPUT: * + * p1,p2 ptrs to elements to compare * + * * + * OUTPUT: * + * 0 = same, -1 = (*p1) goes BEFORE (*p2), 1 = (*p1) goes AFTER (*p2) * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/08/1995 DRD : Created. * + *=========================================================================*/ +static int Init_String_Compare (const void *p1, const void *p2) +{ + return( strcmp( *((char **)p1), *((char **)p2) ) ); +} + + +/*********************************************************************************************** + * Com_Scenario_Dialog -- Serial game scenario selection dialog * + * * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Serial Game ³ * + * ³ ³ * + * ³ Your Name: __________ House: [GDI] [NOD] ³ * + * ³ Credits: ______ Desired Color: [ ][ ][ ][ ] ³ * + * ³ Opponent: Name ³ * + * ³ ³ * + * ³ Scenario ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄ¿ ³ * + * ³ ³ Hell's Kitchen ³³ ³ * + * ³ ³ Heaven's Gate ÃÄ´ ³ * + * ³ ³ ... ³ ³ ³ * + * ³ ³ ÃÄ´ ³ * + * ³ ³ ³³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÙ ³ * + * ³ [ Bases ] [ Crates ] ³ * + * ³ [ Tiberium ] [ AI Players ] ³ * + * ³ ³ * + * ³ [OK] [Cancel] ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ * + * ³ ³ ³ ³ * + * ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ [Send Message] ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = success, false = cancel * + * * + * WARNINGS: * + * MPlayerName & MPlayerGameName must contain this player's name. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +#define TXT_HOST_INTERNET_GAME 4567+1 +#define TXT_JOIN_INTERNET_GAME 4567+2 +int Com_Scenario_Dialog(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 290*factor; // dialog width + int d_dialog_h = 190*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200*factor - d_dialog_h) / 2); // dialog y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 6*factor+1; // ht of 6-pt text + int d_margin1 = 5*factor; // margin width/height + int d_margin2 = 2*factor; // margin width/height + + int d_name_w = 70*factor; + int d_name_h = 9*factor; + int d_name_x = d_dialog_x + 108*factor; + int d_name_y = d_dialog_y + d_margin1 + d_txt6_h + d_txt6_h + d_margin1; + + int d_credits_w = ((CREDITSBUF_MAX - 1) * 6*factor) + 3*factor; + int d_credits_h = 9*factor; + int d_credits_x = d_name_x; + int d_credits_y = d_name_y + d_name_h + d_margin2; + + int d_gdi_w = 30*factor; + int d_gdi_h = 9*factor; + int d_gdi_x = d_dialog_cx + (d_dialog_w / 4); + int d_gdi_y = d_dialog_y + d_margin1 + d_txt6_h + d_txt6_h + d_margin1; + + int d_nod_w = 30*factor; + int d_nod_h = 9*factor; + int d_nod_x = d_gdi_x + d_gdi_w + (d_margin1 / 2); + int d_nod_y = d_gdi_y; + + int d_color_w = 10*factor; + int d_color_h = 9*factor; + int d_color_y = d_gdi_y + d_gdi_h + d_margin2; + + int d_opponent_x = d_name_x; + int d_opponent_y = d_color_y + d_color_h + d_margin2; + + int d_scenariolist_w = 182*factor; + int d_scenariolist_h = 27*factor; + int d_scenariolist_x = d_dialog_cx - (d_scenariolist_w / 2); + int d_scenariolist_y = d_opponent_y + d_txt6_h + 3*factor + d_txt6_h; + + // d_count_x is calculated below after other enums + int d_count_w = 25*factor; + int d_count_h = 7*factor; + int d_count_y = d_scenariolist_y + d_scenariolist_h + d_margin2; + + // d_level_x is calculated below after other enums + int d_level_w = 25*factor; + int d_level_h = 7*factor; + int d_level_y = d_count_y; + +#if (GERMAN | FRENCH) + int d_bases_w = 120*factor;//BGA:100; +#else + int d_bases_w = 110*factor; +#endif + int d_bases_h = 9*factor; + int d_bases_x = d_dialog_cx - d_bases_w - d_margin2; + int d_bases_y = d_count_y + d_count_h + d_margin2; + +#if (GERMAN | FRENCH) + int d_goodies_w = 120*factor; +#else + int d_goodies_w = 110*factor; +#endif + int d_goodies_h = 9*factor; + int d_goodies_x = d_dialog_cx + d_margin2; + int d_goodies_y = d_bases_y; + + int d_count_x = d_dialog_cx - d_count_w - ((2 * 6*factor) + 3*factor) + - ((d_bases_w - ((13 * 6*factor) + 3*factor + d_count_w)) / 2) - d_margin2; + + int d_level_x = d_dialog_cx + (11 * 6*factor) + + ((d_goodies_w - ((13 * 6*factor) + 3*factor + d_level_w)) / 2) + d_margin2; + +#if (GERMAN | FRENCH) + int d_tiberium_w = 120*factor; +#else + int d_tiberium_w = 110*factor; +#endif + int d_tiberium_h = 9*factor; + int d_tiberium_x = d_dialog_cx - d_bases_w - d_margin2; + int d_tiberium_y = d_bases_y + d_bases_h + d_margin2; + +#if (GERMAN | FRENCH) + int d_ghosts_w = 120*factor; +#else + int d_ghosts_w = 110*factor; +#endif + int d_ghosts_h = 9*factor; + int d_ghosts_x = d_dialog_cx + d_margin2; + int d_ghosts_y = d_tiberium_y; + + int d_ok_w = 45*factor; + int d_ok_h = 9*factor; + int d_ok_x = d_tiberium_x + (d_tiberium_w / 2) - (d_ok_w / 2); + int d_ok_y = d_tiberium_y + d_tiberium_h + d_margin1; + + int d_cancel_w = 45*factor; + int d_cancel_h = 9*factor; + int d_cancel_x = d_ghosts_x + (d_ghosts_w / 2) - (d_cancel_w / 2); + int d_cancel_y = d_tiberium_y + d_tiberium_h + d_margin1; + + int d_message_w = d_dialog_w - (d_margin1 * 2); + int d_message_h = 34*factor; + int d_message_x = d_dialog_x + d_margin1; + int d_message_y = d_cancel_y + d_cancel_h + d_margin1; + + int d_send_w = 80*factor; + int d_send_h = 9*factor; + int d_send_x = d_dialog_cx - (d_send_w / 2); + int d_send_y = d_message_y + d_message_h + d_margin2; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_NAME = 100, + BUTTON_GDI, + BUTTON_NOD, + BUTTON_CREDITS, + BUTTON_SCENARIOLIST, + BUTTON_COUNT, + BUTTON_LEVEL, + BUTTON_BASES, + BUTTON_TIBERIUM, + BUTTON_GOODIES, + BUTTON_GHOSTS, + BUTTON_OK, + BUTTON_CANCEL, + BUTTON_SEND, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_MESSAGE, + REDRAW_COLORS, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + bool process = true; // process while true + KeyNumType input; + + char namebuf[MPLAYER_NAME_MAX] = {0}; // buffer for player's name + char credbuf[CREDITSBUF_MAX]; // for credit edit box + int old_cred; // old value in credits buffer + int transmit; // 1 = re-transmit new game options + int cbox_x[] = { d_gdi_x, + d_gdi_x + d_color_w, + d_gdi_x + (d_color_w * 2), + d_gdi_x + (d_color_w * 3), + d_gdi_x + (d_color_w * 4), + d_gdi_x + (d_color_w * 5)}; + int parms_received = 0; // 1 = game options received + int changed = 0; // 1 = user has changed an option + + int rc; + int recsignedoff = false; + int i; + int version; + char txt[80]; + unsigned long starttime; + unsigned long timingtime; + unsigned long lastmsgtime; + unsigned long lastredrawtime; + unsigned long transmittime = 0; + unsigned long theirresponsetime; + int packetlen; + static int first_time = 1; + bool oppscorescreen = false; + bool gameoptions = false; + EventClass *event; // event ptr + unsigned long msg_timeout = 1200; // init to 20 seconds + + int message_length; + int sent_so_far; + unsigned short magic_number; + unsigned short crc; + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + EditClass name_edt (BUTTON_NAME, + namebuf, MPLAYER_NAME_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_name_x, d_name_y, d_name_w, d_name_h, EditClass::ALPHANUMERIC); + + TextButtonClass gdibtn(BUTTON_GDI, TXT_G_D_I, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_gdi_x, d_gdi_y, d_gdi_w, d_gdi_h); + + TextButtonClass nodbtn(BUTTON_NOD, TXT_N_O_D, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_nod_x, d_nod_y, d_nod_w, d_nod_h); + + EditClass credit_edt (BUTTON_CREDITS, + credbuf, CREDITSBUF_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_credits_x, d_credits_y, d_credits_w, d_credits_h, EditClass::ALPHANUMERIC); + + ListClass scenariolist(BUTTON_SCENARIOLIST, + d_scenariolist_x, d_scenariolist_y, d_scenariolist_w, d_scenariolist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + GaugeClass countgauge (BUTTON_COUNT, + d_count_x, d_count_y, d_count_w, d_count_h); + + GaugeClass levelgauge (BUTTON_LEVEL, + d_level_x, d_level_y, d_level_w, d_level_h); + + TextButtonClass basesbtn(BUTTON_BASES, TXT_BASES_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_bases_x, d_bases_y, d_bases_w, d_bases_h); + + TextButtonClass tiberiumbtn(BUTTON_TIBERIUM, TXT_TIBERIUM_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_tiberium_x, d_tiberium_y, d_tiberium_w, d_tiberium_h); + + TextButtonClass goodiesbtn(BUTTON_GOODIES, TXT_CRATES_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_goodies_x, d_goodies_y, d_goodies_w, d_goodies_h); + + TextButtonClass ghostsbtn(BUTTON_GHOSTS, TXT_AI_PLAYERS_OFF, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_ghosts_x, d_ghosts_y, d_ghosts_w, d_ghosts_h); + + TextButtonClass okbtn(BUTTON_OK, TXT_OK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_ok_x, d_ok_y, d_ok_w, d_ok_h); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_cancel_x, d_cancel_y); +#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +#endif + + TextButtonClass sendbtn(BUTTON_SEND, TXT_SEND_MESSAGE, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_send_x, d_send_y); +#else + d_send_x, d_send_y, d_send_w, d_send_h); +#endif + + /* + ------------------------- Build the button list -------------------------- + */ + commands = &name_edt; + gdibtn.Add_Tail(*commands); + nodbtn.Add_Tail(*commands); + credit_edt.Add_Tail(*commands); + scenariolist.Add_Tail(*commands); + countgauge.Add_Tail(*commands); + levelgauge.Add_Tail(*commands); + basesbtn.Add_Tail(*commands); + tiberiumbtn.Add_Tail(*commands); + goodiesbtn.Add_Tail(*commands); + ghostsbtn.Add_Tail(*commands); + okbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + sendbtn.Add_Tail(*commands); + + /* + ----------------------------- Various Inits ------------------------------ + */ + /*........................................................................ + Init player name & house + ........................................................................*/ + MPlayerColorIdx = MPlayerPrefColor; // init my preferred color + strcpy (namebuf, MPlayerName); // set my name + name_edt.Set_Text(namebuf,MPLAYER_NAME_MAX); + name_edt.Set_Color(MPlayerTColors[MPlayerColorIdx]); + + if (MPlayerHouse==HOUSE_GOOD) { + gdibtn.Turn_On(); + } else { + nodbtn.Turn_On(); + } + + /*........................................................................ + Init scenario values, only the first time through + ........................................................................*/ + if (first_time) { + MPlayerCredits = 3000; // init credits & credit buffer + MPlayerBases = 1; // init scenario parameters + MPlayerTiberium = 0; + MPlayerGoodies = 0; + MPlayerGhosts = 0; + Special.IsCaptureTheFlag = 0; + MPlayerUnitCount = (MPlayerCountMax[MPlayerBases] + MPlayerCountMin[MPlayerBases]) / 2; + first_time = 0; + } + + /*........................................................................ + Init button states + ........................................................................*/ + if (MPlayerBases) { + basesbtn.Turn_On(); + basesbtn.Set_Text(TXT_BASES_ON); + } + if (MPlayerTiberium) { + tiberiumbtn.Turn_On(); + tiberiumbtn.Set_Text(TXT_TIBERIUM_ON); + } + if (MPlayerGoodies) { + goodiesbtn.Turn_On(); + goodiesbtn.Set_Text(TXT_CRATES_ON); + } + if (MPlayerGhosts) { + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_AI_PLAYERS_ON); + } + if (Special.IsCaptureTheFlag) { + MPlayerGhosts = 0; + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_CAPTURE_THE_FLAG); + } + + sprintf(credbuf, "%d", MPlayerCredits); + credit_edt.Set_Text(credbuf, CREDITSBUF_MAX); + old_cred = MPlayerCredits; + + levelgauge.Set_Maximum(MPLAYER_BUILD_LEVEL_MAX - 1); + levelgauge.Set_Value(BuildLevel - 1); + + countgauge.Set_Maximum(MPlayerCountMax[MPlayerBases] - MPlayerCountMin[MPlayerBases]); + countgauge.Set_Value(MPlayerUnitCount - MPlayerCountMin[MPlayerBases]); + + /*........................................................................ + Init other scenario parameters + ........................................................................*/ + Special.IsTGrowth = MPlayerTiberium; + Special.IsTSpread = MPlayerTiberium; + transmit = 1; + + /*........................................................................ + Init scenario description list box + ........................................................................*/ + for (i = 0; i < MPlayerScenarios.Count(); i++) { + scenariolist.Add_Item (strupr(MPlayerScenarios[i])); + } + ScenarioIdx = 0; // 1st scenario is selected + + /*........................................................................ + Init random-number generator, & create a seed to be used for all random + numbers from here on out + ........................................................................*/ + randomize(); + Seed = rand(); + + /*........................................................................ + Init the message display system + ........................................................................*/ + Messages.Init (d_message_x + 2*factor, d_message_y + 2*factor, 4, MAX_MESSAGE_LENGTH, d_txt6_h); + + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + extern char ModemRXString[]; + + if (strlen(ModemRXString) > 36) + ModemRXString[36] = 0; + + if (strlen(ModemRXString) > 0) + Messages.Add_Message (ModemRXString, CC_TAN, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, 0, 0); + + ModemRXString[0] = '\0'; + + /* + ---------------------------- Init Mono Output ---------------------------- + */ + #if(SHOW_MONO) + NullModem.Configure_Debug(sizeof (CommHeaderType),sizeof (SerialCommandType), + SerialPacketNames, 106); + NullModem.Mono_Debug_Print(1); + #endif + + /* + ---------------------------- Processing loop ----------------------------- + */ + NullModem.Reset_Response_Time(); // clear response time + theirresponsetime = 10000; // initialize to an invalid value + timingtime = lastmsgtime = lastredrawtime = TickCount.Time(); + while (Get_Mouse_State() > 0) Show_Mouse(); + + + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + // init font variables + + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ +#ifdef FORCE_WINSOCK + if (Winsock.Get_Connected()){ + Draw_Caption (TXT_HOST_INTERNET_GAME, d_dialog_x, d_dialog_y, d_dialog_w); + }else{ + Draw_Caption (TXT_HOST_SERIAL_GAME, d_dialog_x, d_dialog_y, d_dialog_w); + } +#else + Draw_Caption (TXT_HOST_SERIAL_GAME, d_dialog_x, d_dialog_y, d_dialog_w); +#endif //FORCE_WINSOCK + + Fancy_Text_Print(TXT_YOUR_NAME, + d_name_x - 5*factor, d_name_y + 1*factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_SIDE_COLON, + d_gdi_x - 5*factor, d_gdi_y + 1*factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print (TXT_START_CREDITS_COLON, d_credits_x - 5*factor, d_credits_y + 1*factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_COLOR_COLON, + cbox_x[0] - 5*factor, d_color_y + 1*factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_SCENARIOS, + d_scenariolist_x + (d_scenariolist_w / 2), + d_scenariolist_y - d_txt6_h, + CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print (TXT_COUNT, d_count_x - 3*factor, d_count_y, CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_RIGHT); + + Fancy_Text_Print (TXT_LEVEL, d_level_x - 3*factor, d_level_y, CC_GREEN, TBLACK, + TPF_NOSHADOW | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_RIGHT); + } + + /*.................................................................. + Draw the color boxes + ..................................................................*/ + if (display >= REDRAW_COLORS) { + for (i = 0; i < MAX_MPLAYER_COLORS; i++) { + LogicPage->Fill_Rect (cbox_x[i] + 1*factor, d_color_y + 1*factor, + cbox_x[i] + 1*factor + d_color_w - 2*factor, d_color_y + 1*factor + d_color_h - 2*factor, + MPlayerGColors[i]); + + if (i == MPlayerColorIdx) { + Draw_Box(cbox_x[i], d_color_y, d_color_w, d_color_h, + BOXSTYLE_GREEN_DOWN, false); + } else { + Draw_Box(cbox_x[i], d_color_y, d_color_w, d_color_h, + BOXSTYLE_GREEN_RAISED, false); + } + } + } + + /*.................................................................. + Draw the message: + - Erase an old message first + ..................................................................*/ + if (display >= REDRAW_MESSAGE) { + Draw_Box(d_message_x, d_message_y, d_message_w, d_message_h, + BOXSTYLE_GREEN_BORDER, true); + Messages.Draw(); + + LogicPage->Fill_Rect (d_dialog_x + 2*factor, + d_opponent_y, + d_dialog_x + d_dialog_w - 4*factor, + d_opponent_y + d_txt6_h, + BLACK); + + if (parms_received) { + if (oppscorescreen) { + sprintf(txt,"%s",Text_String(TXT_WAITING_FOR_OPPONENT)); + + int txtwidth = String_Pixel_Width( txt ); + + Fancy_Text_Print (txt, d_dialog_cx - (txtwidth / 2), + d_opponent_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } else { + Fancy_Text_Print (TXT_OPPONENT_COLON, d_opponent_x - 3*factor, + d_opponent_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (TheirHouse == HOUSE_GOOD) { + sprintf(txt,"%s %s",TheirName,Text_String(TXT_G_D_I)); + } else { + sprintf(txt,"%s %s",TheirName,Text_String(TXT_N_O_D)); + } + + Fancy_Text_Print (txt, d_opponent_x, + d_opponent_y, MPlayerTColors[TheirColor], TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + } + + sprintf( txt, "%d ", MPlayerUnitCount ); + Fancy_Text_Print (txt, d_count_x + d_count_w + 3*factor, + d_count_y, CC_GREEN, BLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (BuildLevel <= MPLAYER_BUILD_LEVEL_MAX) { + sprintf(txt, "%d ", BuildLevel); + } else { + sprintf(txt, "**"); + } + Fancy_Text_Print (txt, d_level_x + d_level_w + 3*factor, + d_level_y, CC_GREEN, BLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Flag_List_To_Redraw(); + } + + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + /*------------------------------------------------------------------ + User clicks on a color button + ------------------------------------------------------------------*/ + case KN_LMOUSE: + if (_Kbd->MouseQX > cbox_x[0] && + _Kbd->MouseQX < (cbox_x[MAX_MPLAYER_COLORS - 1] + d_color_w) && + _Kbd->MouseQY > d_color_y && + _Kbd->MouseQY < (d_color_y + d_color_h)) { + MPlayerPrefColor = (_Kbd->MouseQX - cbox_x[0]) / d_color_w; + MPlayerColorIdx = MPlayerPrefColor; + display = REDRAW_COLORS; + + name_edt.Set_Color (MPlayerTColors[MPlayerColorIdx]); + name_edt.Flag_To_Redraw(); + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + changed = 1; + } + break; + + /*------------------------------------------------------------------ + User edits the name field; retransmit new game options + ------------------------------------------------------------------*/ + case (BUTTON_NAME | KN_BUTTON): + credit_edt.Clear_Focus(); + credit_edt.Flag_To_Redraw(); + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + changed = 1; + break; + + /*------------------------------------------------------------------ + House Buttons: set the player's desired House + ------------------------------------------------------------------*/ + case (BUTTON_GDI | KN_BUTTON): + MPlayerHouse = HOUSE_GOOD; + gdibtn.Turn_On(); + nodbtn.Turn_Off(); + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + break; + + case (BUTTON_NOD | KN_BUTTON): + MPlayerHouse = HOUSE_BAD; + gdibtn.Turn_Off(); + nodbtn.Turn_On(); + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + break; + + /*------------------------------------------------------------------ + User edits the credits value; retransmit new game options + ------------------------------------------------------------------*/ + case (BUTTON_CREDITS | KN_BUTTON): + name_edt.Clear_Focus(); + name_edt.Flag_To_Redraw(); + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + break; + + /*------------------------------------------------------------------ + New Scenario selected. + ------------------------------------------------------------------*/ + case (BUTTON_SCENARIOLIST | KN_BUTTON): + if (scenariolist.Current_Index() != ScenarioIdx) { + ScenarioIdx = scenariolist.Current_Index(); + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + } + break; + + /*------------------------------------------------------------------ + User adjusts max # units + ------------------------------------------------------------------*/ + case (BUTTON_COUNT | KN_BUTTON): + MPlayerUnitCount = countgauge.Get_Value() + MPlayerCountMin[MPlayerBases]; + display = REDRAW_MESSAGE; + transmit = 1; + break; + + /*------------------------------------------------------------------ + User adjusts build level + ------------------------------------------------------------------*/ + case (BUTTON_LEVEL | KN_BUTTON): + BuildLevel = levelgauge.Get_Value() + 1; + if (BuildLevel > MPLAYER_BUILD_LEVEL_MAX) // if it's pegged, max it out + BuildLevel = MPLAYER_BUILD_LEVEL_MAX; + display = REDRAW_MESSAGE; + transmit = 1; + break; + + /*------------------------------------------------------------------ + Toggle bases + ------------------------------------------------------------------*/ + case (BUTTON_BASES | KN_BUTTON): + if (MPlayerBases) { + MPlayerBases = 0; + basesbtn.Turn_Off(); + basesbtn.Set_Text(TXT_BASES_OFF); + MPlayerUnitCount = Fixed_To_Cardinal (MPlayerCountMax[0]-MPlayerCountMin[0], + Cardinal_To_Fixed(MPlayerCountMax[1]-MPlayerCountMin[1], + MPlayerUnitCount-MPlayerCountMin[1])) + MPlayerCountMin[0]; + } else { + MPlayerBases = 1; + basesbtn.Turn_On(); + basesbtn.Set_Text(TXT_BASES_ON); + MPlayerUnitCount = Fixed_To_Cardinal (MPlayerCountMax[1]-MPlayerCountMin[1], + Cardinal_To_Fixed(MPlayerCountMax[0]-MPlayerCountMin[0], + MPlayerUnitCount-MPlayerCountMin[0])) + MPlayerCountMin[1]; + } + MPlayerCredits = atoi(credbuf); + countgauge.Set_Maximum(MPlayerCountMax[MPlayerBases] - MPlayerCountMin[MPlayerBases]); + countgauge.Set_Value(MPlayerUnitCount - MPlayerCountMin[MPlayerBases]); + strcpy (MPlayerName, namebuf); + transmit = 1; + display = REDRAW_ALL; + break; + + /*------------------------------------------------------------------ + Toggle tiberium + ------------------------------------------------------------------*/ + case (BUTTON_TIBERIUM | KN_BUTTON): + if (MPlayerTiberium) { + MPlayerTiberium = 0; + Special.IsTGrowth = 0; + Special.IsTSpread = 0; + tiberiumbtn.Turn_Off(); + tiberiumbtn.Set_Text(TXT_TIBERIUM_OFF); + } else { + MPlayerTiberium = 1; + Special.IsTGrowth = 1; + Special.IsTSpread = 1; + tiberiumbtn.Turn_On(); + tiberiumbtn.Set_Text(TXT_TIBERIUM_ON); + } + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + break; + + /*------------------------------------------------------------------ + Toggle goodies + ------------------------------------------------------------------*/ + case (BUTTON_GOODIES | KN_BUTTON): + if (MPlayerGoodies) { + MPlayerGoodies = 0; + goodiesbtn.Turn_Off(); + goodiesbtn.Set_Text(TXT_CRATES_OFF); + } else { + MPlayerGoodies = 1; + goodiesbtn.Turn_On(); + goodiesbtn.Set_Text(TXT_CRATES_ON); + } + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + break; + + /*------------------------------------------------------------------ + Toggle ghosts + ------------------------------------------------------------------*/ + case (BUTTON_GHOSTS | KN_BUTTON): + if (!MPlayerGhosts && !Special.IsCaptureTheFlag) { // ghosts OFF => ghosts ON + MPlayerGhosts = 1; + Special.IsCaptureTheFlag = 0; + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_AI_PLAYERS_ON); + } + else if (MPlayerGhosts) { // ghosts ON => capture-flag + MPlayerGhosts = 0; + Special.IsCaptureTheFlag = 1; + ghostsbtn.Turn_On(); + ghostsbtn.Set_Text(TXT_CAPTURE_THE_FLAG); + } + else if (Special.IsCaptureTheFlag) { // capture-flag => AI OFF + MPlayerGhosts = 0; + Special.IsCaptureTheFlag = 0; + ghostsbtn.Turn_Off(); + ghostsbtn.Set_Text(TXT_AI_PLAYERS_OFF); + } + MPlayerCredits = atoi(credbuf); + strcpy (MPlayerName, namebuf); + transmit = 1; + break; + + /*------------------------------------------------------------------ + OK: exit loop with true status + ------------------------------------------------------------------*/ + case (BUTTON_OK | KN_BUTTON): + // + // make sure we got a game options packet from the other player + // + if (gameoptions) { + rc = true; + process = false; + + // force transmitting of game options packet one last time + + transmit = 1; + transmittime = 0; + } else { + CCMessageBox().Process (TXT_ONLY_ONE,TXT_OOPS,NULL); + display = REDRAW_ALL; + } + break; + + /*------------------------------------------------------------------ + CANCEL: send a SIGN_OFF, bail out with error code + ------------------------------------------------------------------*/ + case (KN_ESC): + if (Messages.Get_Edit_Buf() != NULL) { + Messages.Input(input); + display = REDRAW_MESSAGE; + break; + } + case (BUTTON_CANCEL | KN_BUTTON): + process = false; + rc = false; + break; + + /*------------------------------------------------------------------ + Default: manage the inter-player messages + ------------------------------------------------------------------*/ + default: + /*............................................................... + F4/SEND/'M' = send a message + ...............................................................*/ + if (Messages.Get_Edit_Buf()==NULL) { + if (input == KN_M || input==(BUTTON_SEND | KN_BUTTON) || + input == KN_F4) { + memset (txt, 0, 80); + + strcpy(txt,Text_String(TXT_MESSAGE)); // "Message:" + + Messages.Add_Edit (MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, txt, d_message_w-70*factor); + display = REDRAW_MESSAGE; + + credit_edt.Clear_Focus(); + credit_edt.Flag_To_Redraw(); + name_edt.Clear_Focus(); + name_edt.Flag_To_Redraw(); + + break; + } + } else { + if ( input == (BUTTON_SEND | KN_BUTTON) ) { + input = KN_RETURN; + } + } + + /*............................................................... + Manage the message system (get rid of old messages) + ...............................................................*/ + if (Messages.Manage()) { + display = REDRAW_MESSAGE; + } + + /*............................................................... + Service keyboard input for any message being edited. + ...............................................................*/ + i = Messages.Input(input); + + /*............................................................... + If 'Input' returned 1, it means refresh the message display. + ...............................................................*/ + if (i==1) { + Messages.Draw(); + } + + /*............................................................... + If 'Input' returned 2, it means redraw the message display. + ...............................................................*/ + else if (i==2) { + display = REDRAW_MESSAGE; + } + + /*............................................................... + If 'input' returned 3, it means send the current message. + ...............................................................*/ + else if (i==3) { + + sent_so_far = 0; + magic_number = MESSAGE_HEAD_MAGIC_NUMBER; + message_length = strlen(Messages.Get_Edit_Buf()); + crc = (unsigned short) + (Calculate_CRC(Messages.Get_Edit_Buf(), message_length) &0xffff); + + while (sent_so_far < message_length){ + + SendPacket.Command = SERIAL_MESSAGE; + strcpy (SendPacket.Name, MPlayerName); + SendPacket.ID = Build_MPlayerID(MPlayerColorIdx, MPlayerHouse); + memcpy (SendPacket.Message, Messages.Get_Edit_Buf()+sent_so_far, COMPAT_MESSAGE_LENGTH-5); + *(SendPacket.Message + COMPAT_MESSAGE_LENGTH-5) = 0; + *((unsigned short*)(SendPacket.Message + COMPAT_MESSAGE_LENGTH-4)) = magic_number; + *((unsigned short*)(SendPacket.Message + COMPAT_MESSAGE_LENGTH-2)) = crc; + + /*.................................................................. + Send the message + ..................................................................*/ + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + NullModem.Service(); + + /*.................................................................. + Add the message to our own screen + ..................................................................*/ + sprintf(txt, Text_String (TXT_FROM), MPlayerName, SendPacket.Message); + Messages.Add_Message (txt, MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, magic_number, crc); + + magic_number++; + sent_so_far += COMPAT_MESSAGE_LENGTH-5; + } + + display = REDRAW_MESSAGE; + } /* end of send message */ + + } /* end of input processing */ + + /*--------------------------------------------------------------------- + Detect editing of the credits buffer, transmit new values to players + ---------------------------------------------------------------------*/ + if (atoi(credbuf) != old_cred) { + old_cred = Bound(atoi(credbuf), 0, 9999); + MPlayerCredits = old_cred; + transmit = 1; + sprintf(credbuf, "%d", MPlayerCredits); + credit_edt.Set_Text(credbuf, CREDITSBUF_MAX); + } + + /*--------------------------------------------------------------------- + Detect editing of the name buffer, transmit new values to players + ---------------------------------------------------------------------*/ + if (strcmp (namebuf, MPlayerName)) { + strcpy (MPlayerName, namebuf); + transmit = 1; + changed = 1; + } + + /*--------------------------------------------------------------------- + If our Transmit flag is set, we need to send out a game option packet. + This message requires an ACK. The first time through the loop, transmit + should be set, so we send out our default options; we'll then send + any changes we make to the defaults. + ---------------------------------------------------------------------*/ + if (transmit && (TickCount.Time() - transmittime) > PACKET_RETRANS_TIME) { + SendPacket.Command = SERIAL_GAME_OPTIONS; + strcpy (SendPacket.Name, MPlayerName); +#ifdef PATCH + if (IsV107) { + SendPacket.Version = 1; + } else { + SendPacket.Version = 2; + } +#else + SendPacket.Version = Version_Number(); +#endif + SendPacket.House = MPlayerHouse; + SendPacket.Color = MPlayerColorIdx; + + SendPacket.Scenario = MPlayerFilenum[ScenarioIdx]; + + SendPacket.Credits = MPlayerCredits; + SendPacket.IsBases = MPlayerBases; + SendPacket.IsTiberium = MPlayerTiberium; + SendPacket.IsGoodies = MPlayerGoodies; + SendPacket.IsGhosties = MPlayerGhosts; + SendPacket.BuildLevel = BuildLevel; + SendPacket.UnitCount = MPlayerUnitCount; + SendPacket.Seed = Seed; + SendPacket.Special = Special; + SendPacket.GameSpeed = Options.GameSpeed; + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + transmittime = TickCount.Time(); + transmit = 0; + } + + // + // send a timing packet if enough time has gone by. + // + if ( (TickCount.Time() - timingtime) > PACKET_TIMING_TIMEOUT) { + SendPacket.Command = SERIAL_TIMING; + SendPacket.ResponseTime = NullModem.Response_Time(); + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 0); + timingtime = TickCount.Time(); + } + + /*--------------------------------------------------------------------- + Check for an incoming message + ---------------------------------------------------------------------*/ + if (NullModem.Get_Message (&ReceivePacket, &packetlen) > 0) { +// Smart_Printf( "received packet of length %d\n", packetlen ); + + lastmsgtime = TickCount.Time(); + msg_timeout = 600; // reset timeout value to 10 seconds + // (only the 1st time through is 20 seconds) + + // are we getting our own packets back?? + + if (ReceivePacket.Command >= SERIAL_CONNECT && + ReceivePacket.Command < SERIAL_LAST_COMMAND && + ReceivePacket.Command != SERIAL_MESSAGE && + ReceivePacket.ID == ModemGameToPlay) { + + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + break; + } + + event = (EventClass *)&ReceivePacket; + if (event->Type <= EventClass::FRAMEINFO) { + if ( (TickCount.Time() - lastredrawtime) > PACKET_REDRAW_TIME) { + lastredrawtime = TickCount.Time(); + oppscorescreen = true; + +if (display != REDRAW_ALL) { + display = REDRAW_MESSAGE; +} + +// display = REDRAW_MESSAGE; + parms_received = 1; + } + } else { + switch ( ReceivePacket.Command ) { + /*.................................................................. + Sign-off: Give the other machine time to receive my ACK, display a + message, and exit. + ..................................................................*/ + case (SERIAL_SIGN_OFF): +// Smart_Printf( "received sign off\n" ); + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < 60) + NullModem.Service(); + CCMessageBox().Process(TXT_USER_SIGNED_OFF); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + recsignedoff = true; + break; + + /*.................................................................. + Game Options: Store the other machine's name, color & house; + If they've picked the same color as myself, re-transmit my settings + to force him to choose a different color. (Com_Show_Scenario_Dialog + is responsible for ensuring the colors are different.) + ..................................................................*/ + case (SERIAL_GAME_OPTIONS): +// Smart_Printf( "received game options\n" ); + oppscorescreen = false; + gameoptions = true; + strcpy (TheirName, ReceivePacket.Name); + TheirColor = ReceivePacket.Color; + TheirHouse = ReceivePacket.House; + transmit = 1; + + parms_received = 1; +if (display != REDRAW_ALL) { + display = REDRAW_MESSAGE; +} +// display = REDRAW_MESSAGE; + + /*............................................................... + Check the version number of the other system. + ...............................................................*/ +#ifdef PATCH + if (IsV107) { + version = 1; + } else { + version = 2; + } +#else + version = Version_Number(); +#endif + if (ReceivePacket.Version > version) { + CCMessageBox().Process (TXT_YOURGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } else { + if (ReceivePacket.Version < version) { + CCMessageBox().Process (TXT_DESTGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } + } + break; + + /*.................................................................. + Incoming message: add to our list + ..................................................................*/ + case (SERIAL_MESSAGE): +// Smart_Printf( "received serial message\n" ); + oppscorescreen = false; + sprintf(txt, Text_String (TXT_FROM), ReceivePacket.Name, + ReceivePacket.Message); + magic_number = *((unsigned short*)(ReceivePacket.Message + COMPAT_MESSAGE_LENGTH-4)); + crc = *((unsigned short*)(ReceivePacket.Message + COMPAT_MESSAGE_LENGTH-2)); + Messages.Add_Message (txt, + MPlayerTColors[MPlayerID_To_ColorIndex(ReceivePacket.ID)], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, magic_number, crc); +if (display != REDRAW_ALL) { + display = REDRAW_MESSAGE; +} +// display = REDRAW_MESSAGE; + break; + + // + // get their response time + // + case (SERIAL_TIMING): +// Smart_Printf( "received timing\n" ); + oppscorescreen = false; + theirresponsetime = ReceivePacket.ResponseTime; + + if ( !gameoptions ) { + + // retransmit of game options packet again + transmit = 1; + } + break; + + // + // print msg waiting for opponent + // + case (SERIAL_SCORE_SCREEN): +// Smart_Printf( "received score screen\n" ); + oppscorescreen = true; +if (display != REDRAW_ALL) { + display = REDRAW_MESSAGE; +} +// display = REDRAW_MESSAGE; + parms_received = 1; + break; + + default: +// Smart_Printf( "received unknown command %X\n", ReceivePacket.Command ); + break; + } + } + } + + // if we haven't received a msg for 10 seconds exit + + if ( (TickCount.Time() - lastmsgtime) > msg_timeout) { + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + } + + /*--------------------------------------------------------------------- + Service the connection + ---------------------------------------------------------------------*/ + NullModem.Service(); + + } /* end of while */ + + /*------------------------------------------------------------------------ + Sort player ID's, so we can execute commands in the same order on both + machines. + ------------------------------------------------------------------------*/ + if (rc) { + /*..................................................................... + Set the number of players in this game, and my ID + .....................................................................*/ + MPlayerCount = 2; + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + TheirID = Build_MPlayerID (TheirColor,TheirHouse); + + /*..................................................................... + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + .....................................................................*/ + if (TheirID < MPlayerLocalID) { + MPlayerID[0] = TheirID; + MPlayerID[1] = MPlayerLocalID; + strcpy (MPlayerNames[0], TheirName); + strcpy (MPlayerNames[1], MPlayerName); + } else { + MPlayerID[0] = MPlayerLocalID; + MPlayerID[1] = TheirID; + strcpy (MPlayerNames[0], MPlayerName); + strcpy (MPlayerNames[1], TheirName); + } + + /*..................................................................... + Get the scenario filename + .....................................................................*/ + Scenario = MPlayerFilenum[ScenarioIdx]; + + /*..................................................................... + Send all players the GO packet. + .....................................................................*/ + SendPacket.Command = SERIAL_GO; + SendPacket.ResponseTime = NullModem.Response_Time(); + if ( theirresponsetime == 10000 ) { +// Mono_Clear_Screen(); +// Smart_Printf( "Did not receive their response time!!!!!!!\n" ); +// Get_Key(); + } else { + if (SendPacket.ResponseTime < theirresponsetime) { + SendPacket.ResponseTime = theirresponsetime; + } + } + + // + // calculated one way delay for a packet and overall delay to execute + // a packet + // + MPlayerMaxAhead = MAX( (SendPacket.ResponseTime / 8), 2); + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + while ( ( NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_SENDING_TIMEOUT) ) + || ((TickCount.Time() - starttime) < 60) ) { + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + NullModem.Service(); + Keyboard::Check(); //Make sure the message loop gets called + } + + // clear queue to keep from doing any resends + NullModem.Init_Send_Queue(); + + } else { + if ( !recsignedoff ) { + /*..................................................................... + Broadcast my sign-off over my network + .....................................................................*/ + SendPacket.Command = SERIAL_SIGN_OFF; + SendPacket.Color = MPlayerLocalID; // use Color for ID + SendPacket.ID = ModemGameToPlay; + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + while ( (NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_CANCEL_TIMEOUT) ) + || ((TickCount.Time() - starttime) < 60) ) { + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + if ( NullModem.Get_Message( &ReceivePacket, &packetlen ) > 0) { + + // are we getting our own packets back?? + + if (ReceivePacket.Command == SERIAL_SIGN_OFF + && ReceivePacket.ID == ModemGameToPlay) { + + // exit while + break; + } + } + + NullModem.Service(); + } + } + + Shutdown_Modem(); + } + + /*------------------------------------------------------------------------ + Clear all lists + ------------------------------------------------------------------------*/ + while (scenariolist.Count()) { + scenariolist.Remove_Item(scenariolist.Get_Item(0)); + } + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + /*------------------------------------------------------------------------ + Save any changes made to our options + ------------------------------------------------------------------------*/ + if (changed) { + Write_MultiPlayer_Settings (); + } + + return(rc); + +} /* end of Com_Scenario_Dialog */ + + +/*********************************************************************************************** + * Com_Show_Scenario_Dialog -- Serial game scenario selection dialog * + * * + * * + * ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ * + * ³ Serial Game ³ * + * ³ ³ * + * ³ Your Name: __________ ³ * + * ³ House: [GDI] [NOD] ³ * + * ³ Desired Color: [ ][ ][ ][ ] ³ * + * ³ ³ * + * ³ Opponent: Name ³ * + * ³ Scenario: Description ³ * + * ³ Credits: xxxx ³ * + * ³ Bases: ON ³ * + * ³ Crates: ON ³ * + * ³ Tiberium: ON ³ * + * ³ Ghosts: ON ³ * + * ³ ³ * + * ³ [Cancel] ³ * + * ³ ³ * + * ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ * + * ³ ³ ³ ³ * + * ³ ³ ³ ³ * + * ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ * + * ³ [Send Message] ³ * + * ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = success, false = cancel * + * * + * WARNINGS: * + * MPlayerName & MPlayerGameName must contain this player's name. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +int Com_Show_Scenario_Dialog(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 306*factor; // dialog width + int d_dialog_h = 187*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200*factor - d_dialog_h) / 2); // dialog y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 6*factor+1; // ht of 6-pt text + int d_margin1 = 5*factor; // margin width/height + int d_margin2 = 2*factor; // margin width/height + + int d_name_w = 70*factor; + int d_name_h = 9*factor; + int d_name_x = d_dialog_cx; + int d_name_y = d_dialog_y + d_margin1 + d_txt6_h + d_txt6_h; + + int d_gdi_w = 30*factor; + int d_gdi_h = 9*factor; + int d_gdi_x = d_dialog_cx; + int d_gdi_y = d_name_y + d_name_h + d_margin2; + + int d_nod_w = 30*factor; + int d_nod_h = 9*factor; + int d_nod_x = d_gdi_x + d_gdi_w + d_margin2; + int d_nod_y = d_gdi_y; + + int d_color_w = 10*factor; + int d_color_h = 9*factor; + int d_color_y = d_gdi_y + d_gdi_h + d_margin2; + + int d_opponent_y = d_color_y + d_color_h + d_margin1; + int d_scenario_y = d_opponent_y + d_txt6_h; + int d_credits_y = d_scenario_y + d_txt6_h; + int d_count_y = d_credits_y + d_txt6_h; + int d_level_y = d_count_y + d_txt6_h; + int d_bases_y = d_level_y + d_txt6_h; + int d_goodies_y = d_bases_y + d_txt6_h; + int d_tiberium_y = d_goodies_y + d_txt6_h; + int d_ghosts_y = d_tiberium_y + d_txt6_h; + + int d_cancel_w = 45*factor; + int d_cancel_h = 9*factor; + int d_cancel_x = d_dialog_cx - (d_cancel_w / 2); + int d_cancel_y = d_ghosts_y + d_txt6_h + d_margin1; + + int d_message_w = d_dialog_w - (d_margin1 * 2); + int d_message_h = 34*factor; + int d_message_x = d_dialog_x + d_margin1; + int d_message_y = d_cancel_y + d_cancel_h + d_margin1; + + int d_send_w = 80*factor; + int d_send_h = 9*factor; + int d_send_x = d_dialog_cx - (d_send_w / 2); + int d_send_y = d_message_y + d_message_h + d_margin2; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_NAME = 100, + BUTTON_GDI, + BUTTON_NOD, + BUTTON_CANCEL, + BUTTON_SEND, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_MESSAGE, + REDRAW_COLORS, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + BOOL process = true; // process while true + KeyNumType input; + + char namebuf[MPLAYER_NAME_MAX] = {0}; // buffer for player's name + int transmit; // 1 = re-transmit new game options + int first; // 1 = no packets received yet + int cbox_x[] = { d_dialog_cx, + d_dialog_cx + d_color_w, + d_dialog_cx + (d_color_w * 2), + d_dialog_cx + (d_color_w * 3), + d_dialog_cx + (d_color_w * 4), + d_dialog_cx + (d_color_w * 5)}; + int parms_received = 0; // 1 = game options received + int changed = 0; // 1 = user has changed an option + + int rc; + int recsignedoff = 0; + int i; + int version; + char txt[80]; + unsigned long starttime; + unsigned long timingtime; + unsigned long lastmsgtime; + unsigned long lastredrawtime; + unsigned long transmittime = 0; + int packetlen; + bool oppscorescreen = false; + bool gameoptions = false; + EventClass *event; // event ptr + unsigned long msg_timeout = 1200; // init to 20 seconds + + int message_length; + int sent_so_far; + unsigned short magic_number; + unsigned short crc; + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + EditClass name_edt (BUTTON_NAME, + namebuf, MPLAYER_NAME_MAX, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_name_x, d_name_y, d_name_w, d_name_h, EditClass::ALPHANUMERIC); + + TextButtonClass gdibtn(BUTTON_GDI, TXT_G_D_I, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_gdi_x, d_gdi_y, d_gdi_w, d_gdi_h); + + TextButtonClass nodbtn(BUTTON_NOD, TXT_N_O_D, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_nod_x, d_nod_y, d_nod_w, d_nod_h); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_cancel_x, d_cancel_y); +#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +#endif + + TextButtonClass sendbtn(BUTTON_SEND, TXT_SEND_MESSAGE, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_send_x, d_send_y); +#else + d_send_x, d_send_y, d_send_w, d_send_h); +#endif + + /* + ------------------------- Build the button list -------------------------- + */ + commands = &name_edt; + gdibtn.Add_Tail(*commands); + nodbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + sendbtn.Add_Tail(*commands); + + /* + ----------------------------- Various Inits ------------------------------ + */ + /*........................................................................ + Init player name & house + ........................................................................*/ + MPlayerColorIdx = MPlayerPrefColor; // init my preferred color + strcpy (namebuf, MPlayerName); // set my name + name_edt.Set_Text(namebuf,MPLAYER_NAME_MAX); + name_edt.Set_Color(MPlayerTColors[MPlayerColorIdx]); + + if (MPlayerHouse==HOUSE_GOOD) { + gdibtn.Turn_On(); + } else { + nodbtn.Turn_On(); + } + + Fancy_Text_Print("", 0, 0, CC_GREEN, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + transmit = 1; + first = 1; + + /*........................................................................ + Init the message display system + ........................................................................*/ + Messages.Init (d_message_x + 2*factor, d_message_y + 2*factor, 4, MAX_MESSAGE_LENGTH, d_txt6_h); + + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + extern char ModemRXString[]; + + if (strlen(ModemRXString) > 36) + ModemRXString[36] = 0; + + if (strlen(ModemRXString) > 0) + Messages.Add_Message (ModemRXString, CC_TAN, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, 0, 0); + + ModemRXString[0] = '\0'; + + /* + ---------------------------- Init Mono Output ---------------------------- + */ + #if(SHOW_MONO) + NullModem.Configure_Debug(sizeof (CommHeaderType),sizeof (SerialCommandType), + SerialPacketNames, 106); + NullModem.Mono_Debug_Print(1); + #endif + + /* + ---------------------------- Processing loop ----------------------------- + */ + NullModem.Reset_Response_Time(); // clear response time + timingtime = lastmsgtime = lastredrawtime = TickCount.Time(); + while (Get_Mouse_State() > 0) Show_Mouse(); + + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ +#ifdef FORCE_WINSOCK + if (Winsock.Get_Connected()){ + Draw_Caption (TXT_JOIN_INTERNET_GAME, d_dialog_x, d_dialog_y, d_dialog_w); + }else{ + Draw_Caption (TXT_JOIN_SERIAL_GAME, d_dialog_x, d_dialog_y, d_dialog_w); + } +#else + Draw_Caption (TXT_JOIN_SERIAL_GAME, d_dialog_x, d_dialog_y, d_dialog_w); +#endif //FORCE_WINSOCK + + Fancy_Text_Print(TXT_YOUR_NAME, + d_name_x - 5*factor, d_name_y + 1*factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_SIDE_COLON, + d_gdi_x - 5*factor, d_gdi_y + 1*factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print(TXT_COLOR_COLON, + cbox_x[0] - 5*factor, d_color_y + 1*factor, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + } + + /*.................................................................. + Draw the color boxes + ..................................................................*/ + if (display >= REDRAW_COLORS) { + for (i = 0; i < MAX_MPLAYER_COLORS; i++) { + LogicPage->Fill_Rect (cbox_x[i] + 1*factor, d_color_y + 1*factor, + cbox_x[i] + 1*factor + d_color_w - 2*factor, d_color_y + 1*factor + d_color_h - 2*factor, + MPlayerGColors[i]); + + if (i == MPlayerColorIdx) { + Draw_Box(cbox_x[i], d_color_y, d_color_w, d_color_h, + BOXSTYLE_GREEN_DOWN, false); + } else { + Draw_Box(cbox_x[i], d_color_y, d_color_w, d_color_h, + BOXSTYLE_GREEN_RAISED, false); + } + } + } + + /*.................................................................. + Draw the message: + - Erase an old message first + ..................................................................*/ + if (display >= REDRAW_MESSAGE) { + Draw_Box(d_message_x, d_message_y, d_message_w, d_message_h, + BOXSTYLE_GREEN_BORDER, true); + Messages.Draw(); + + LogicPage->Fill_Rect( d_dialog_x + 2*factor, + d_opponent_y, + d_dialog_x + d_dialog_w - 4*factor, + d_ghosts_y + d_txt6_h, + BLACK); + + if (parms_received) { + if (oppscorescreen) { + sprintf(txt,"%s",Text_String(TXT_WAITING_FOR_OPPONENT)); + + int txtwidth = String_Pixel_Width( txt ); + + Fancy_Text_Print (txt, d_dialog_cx - (txtwidth / 2), + d_opponent_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } else { + /*............................................................ + Opponent's name + ............................................................*/ + Fancy_Text_Print (TXT_OPPONENT_COLON, d_dialog_cx - 3*factor, + d_opponent_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (TheirHouse == HOUSE_GOOD) { + sprintf(txt,"%s %s",TheirName,Text_String(TXT_G_D_I)); + } else { + sprintf(txt,"%s %s",TheirName,Text_String(TXT_N_O_D)); + } + + Fancy_Text_Print (txt, d_dialog_cx, + d_opponent_y, MPlayerTColors[TheirColor], TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Scenario description + ............................................................*/ + Fancy_Text_Print (TXT_SCENARIO_COLON, d_dialog_cx - 3*factor, + d_scenario_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (ScenarioIdx != -1) { + sprintf(txt,"%s", MPlayerScenarios[ScenarioIdx]); + + Fancy_Text_Print (txt, d_dialog_cx, + d_scenario_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } else { + strcpy(txt,Text_String(TXT_NOT_FOUND)); + + Fancy_Text_Print (txt, d_dialog_cx, + d_scenario_y, RED, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + + /*............................................................ + Credits + ............................................................*/ + Fancy_Text_Print (TXT_START_CREDITS_COLON, d_dialog_cx - 3*factor, + d_credits_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + sprintf(txt,"%d",MPlayerCredits); + Fancy_Text_Print (txt, d_dialog_cx, + d_credits_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Count + ............................................................*/ + + Fancy_Text_Print (TXT_COUNT, d_dialog_cx - 3*factor, + d_count_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + sprintf( txt, "%d ", MPlayerUnitCount ); + Fancy_Text_Print (txt, d_dialog_cx, + d_count_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Level + ............................................................*/ + + Fancy_Text_Print (TXT_LEVEL, d_dialog_cx - 3*factor, + d_level_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (BuildLevel <= MPLAYER_BUILD_LEVEL_MAX) { + sprintf(txt, "%d ", BuildLevel); + } else { + sprintf(txt, "**"); + } + Fancy_Text_Print (txt, d_dialog_cx, + d_level_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Bases status + ............................................................*/ + Fancy_Text_Print (TXT_BASES_COLON, d_dialog_cx - 3*factor, + d_bases_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (MPlayerBases) { + strcpy(txt,Text_String(TXT_ON)); + } else { + strcpy(txt,Text_String(TXT_OFF)); + } + Fancy_Text_Print (txt, d_dialog_cx, + d_bases_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Tiberium status + ............................................................*/ + Fancy_Text_Print (TXT_TIBERIUM_COLON, d_dialog_cx - 3*factor, + d_tiberium_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (MPlayerTiberium) { + strcpy(txt,Text_String(TXT_ON)); + } else { + strcpy(txt,Text_String(TXT_OFF)); + } + Fancy_Text_Print (txt, d_dialog_cx, + d_tiberium_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Goodies status + ............................................................*/ + Fancy_Text_Print (TXT_CRATES_COLON, d_dialog_cx - 3*factor, + d_goodies_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (MPlayerGoodies) { + strcpy(txt,Text_String(TXT_ON)); + } else { + strcpy(txt,Text_String(TXT_OFF)); + } + Fancy_Text_Print (txt, d_dialog_cx, + d_goodies_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................ + Capture the flag or AI player ON/OFF + ............................................................*/ + if ( Special.IsCaptureTheFlag ) { + strcpy( txt, Text_String( TXT_CAPTURE_THE_FLAG ) ); + strcat( txt, ":" ); + Fancy_Text_Print (txt, d_dialog_cx - 3*factor, + d_ghosts_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + strcpy(txt,Text_String(TXT_ON)); + Fancy_Text_Print (txt, d_dialog_cx, + d_ghosts_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } else { + /*............................................................ + Ghost player status + ............................................................*/ + Fancy_Text_Print (TXT_AI_PLAYERS_COLON, d_dialog_cx - 3*factor, + d_ghosts_y, CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + if (MPlayerGhosts) { + strcpy(txt,Text_String(TXT_ON)); + } else { + strcpy(txt,Text_String(TXT_OFF)); + } + Fancy_Text_Print (txt, d_dialog_cx, + d_ghosts_y, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + } + } + } + + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Flag_List_To_Redraw(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + /*------------------------------------------------------------------ + User clicks on a color button + ------------------------------------------------------------------*/ + case KN_LMOUSE: + if (_Kbd->MouseQX > cbox_x[0] && + _Kbd->MouseQX < (cbox_x[MAX_MPLAYER_COLORS - 1] + d_color_w) && + _Kbd->MouseQY > d_color_y && + _Kbd->MouseQY < (d_color_y + d_color_h)) { + /*......................................................... + Compute my preferred color as the one I clicked on. + .........................................................*/ + MPlayerPrefColor = (_Kbd->MouseQX - cbox_x[0]) / d_color_w; + changed = 1; + /*......................................................... + If 'TheirColor' is set to the other player's color, make + sure we can't pick that color. + .........................................................*/ + if (parms_received) { + if (MPlayerPrefColor == TheirColor) + break; + } + MPlayerColorIdx = MPlayerPrefColor; + + name_edt.Set_Color(MPlayerTColors[MPlayerColorIdx]); + name_edt.Flag_To_Redraw(); + display = REDRAW_COLORS; + strcpy (MPlayerName, namebuf); + transmit = 1; + } + break; + + /*------------------------------------------------------------------ + House Buttons: set the player's desired House + ------------------------------------------------------------------*/ + case (BUTTON_GDI | KN_BUTTON): + MPlayerHouse = HOUSE_GOOD; + gdibtn.Turn_On(); + nodbtn.Turn_Off(); + strcpy (MPlayerName, namebuf); + transmit = 1; + break; + + case (BUTTON_NOD | KN_BUTTON): + MPlayerHouse = HOUSE_BAD; + gdibtn.Turn_Off(); + nodbtn.Turn_On(); + strcpy (MPlayerName, namebuf); + transmit = 1; + break; + + /*------------------------------------------------------------------ + User edits the name value; retransmit + ------------------------------------------------------------------*/ + case (BUTTON_NAME | KN_BUTTON): + strcpy (MPlayerName, namebuf); + transmit = 1; + changed = 1; + break; + + /*------------------------------------------------------------------ + CANCEL: send a SIGN_OFF, bail out with error code + ------------------------------------------------------------------*/ + case (KN_ESC): + if (Messages.Get_Edit_Buf() != NULL) { + Messages.Input(input); + display = REDRAW_MESSAGE; + break; + } + case (BUTTON_CANCEL | KN_BUTTON): + process = false; + rc = false; + break; + + /*------------------------------------------------------------------ + Default: manage the inter-player messages + ------------------------------------------------------------------*/ + default: + /*............................................................... + F4/SEND/'M' = send a message + ...............................................................*/ + if (Messages.Get_Edit_Buf()==NULL) { + if (input == KN_M || input==(BUTTON_SEND | KN_BUTTON) || + input == KN_F4) { + memset (txt, 0, 80); + + strcpy(txt,Text_String(TXT_MESSAGE)); // "Message:" + + Messages.Add_Edit (MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, txt, d_message_w-70*factor); + display = REDRAW_MESSAGE; + + name_edt.Clear_Focus(); + name_edt.Flag_To_Redraw(); + + break; + } + } else { + if ( input == (BUTTON_SEND | KN_BUTTON) ) { + input = KN_RETURN; + } + } + + /*............................................................... + Manage the message system (get rid of old messages) + ...............................................................*/ + if (Messages.Manage()) { + display = REDRAW_MESSAGE; + } + + /*............................................................... + Re-draw the messages & service keyboard input for any message + being edited. + ...............................................................*/ + i = Messages.Input(input); + + /*............................................................... + If 'Input' returned 1, it means refresh the message display. + ...............................................................*/ + if (i==1) { + Messages.Draw(); + } else { + + /*............................................................... + If 'Input' returned 2, it means redraw the message display. + ...............................................................*/ + if (i==2) { + display = REDRAW_MESSAGE; + } else { + + /*............................................................... + If 'input' returned 3, it means send the current message. + ...............................................................*/ + if (i==3) { + + sent_so_far = 0; + magic_number = MESSAGE_HEAD_MAGIC_NUMBER; + message_length = strlen(Messages.Get_Edit_Buf()); + crc = (unsigned short) + (Calculate_CRC(Messages.Get_Edit_Buf(), message_length) &0xffff); + + while (sent_so_far < message_length){ + SendPacket.Command = SERIAL_MESSAGE; + strcpy (SendPacket.Name, MPlayerName); + SendPacket.ID = Build_MPlayerID(MPlayerColorIdx, MPlayerHouse); + memcpy (SendPacket.Message, Messages.Get_Edit_Buf()+sent_so_far, COMPAT_MESSAGE_LENGTH-5); + *(SendPacket.Message + COMPAT_MESSAGE_LENGTH-5) = 0; + *((unsigned short*)(SendPacket.Message + COMPAT_MESSAGE_LENGTH-4)) = magic_number; + *((unsigned short*)(SendPacket.Message + COMPAT_MESSAGE_LENGTH-2)) = crc; + + /*.................................................................. + Send the message + ..................................................................*/ + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + NullModem.Service(); + + /*.................................................................. + Add the message to our own screen + ..................................................................*/ + sprintf(txt, Text_String (TXT_FROM), MPlayerName, SendPacket.Message); + Messages.Add_Message (txt, MPlayerTColors[MPlayerColorIdx], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, magic_number, crc); + + magic_number++; + sent_so_far += COMPAT_MESSAGE_LENGTH-5; + } + display = REDRAW_MESSAGE; + } + } + } + break; + } + + /*--------------------------------------------------------------------- + Detect editing of the name buffer, transmit new values to players + ---------------------------------------------------------------------*/ + if (strcmp (namebuf, MPlayerName)) { + strcpy (MPlayerName, namebuf); + transmit = 1; + changed = 1; + } + + /*--------------------------------------------------------------------- + If our Transmit flag is set, we need to send out a game option packet + ---------------------------------------------------------------------*/ + if (transmit && (TickCount.Time() - transmittime) > PACKET_RETRANS_TIME) { + SendPacket.Command = SERIAL_GAME_OPTIONS; + strcpy (SendPacket.Name, MPlayerName); +#ifdef PATCH + if (IsV107) { + SendPacket.Version = 1; + } else { + SendPacket.Version = 2; + } +#else + SendPacket.Version = Version_Number(); +#endif + SendPacket.House = MPlayerHouse; + SendPacket.Color = MPlayerColorIdx; + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + transmittime = TickCount.Time(); + transmit = 0; + } + + // + // send a timing packet if enough time has gone by. + // + if ( (TickCount.Time() - timingtime) > PACKET_TIMING_TIMEOUT) { + SendPacket.Command = SERIAL_TIMING; + SendPacket.ResponseTime = NullModem.Response_Time(); + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 0); + timingtime = TickCount.Time(); + } + + /*--------------------------------------------------------------------- + Check for an incoming message + ---------------------------------------------------------------------*/ + if (NullModem.Get_Message (&ReceivePacket, &packetlen) > 0) { +// Smart_Printf( "received packet of length %d\n", packetlen ); + + lastmsgtime = TickCount.Time(); + + msg_timeout = 600; + + // are we getting our own packets back?? + + if (ReceivePacket.Command >= SERIAL_CONNECT && + ReceivePacket.Command < SERIAL_LAST_COMMAND && + ReceivePacket.Command != SERIAL_MESSAGE && + ReceivePacket.ID == ModemGameToPlay) { + + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + break; + } + + event = (EventClass *)&ReceivePacket; + if (event->Type <= EventClass::FRAMEINFO) { + if ( (TickCount.Time() - lastredrawtime) > PACKET_REDRAW_TIME) { + lastredrawtime = TickCount.Time(); + oppscorescreen = true; + display = REDRAW_MESSAGE; + parms_received = 1; + } + } else { + switch ( ReceivePacket.Command ) { + /*.................................................................. + Other system signs off: Give it time to receive my ACK, then show + a message. + ..................................................................*/ + case (SERIAL_SIGN_OFF): + starttime = TickCount.Time(); + while ( (TickCount.Time() - starttime) < 60) + NullModem.Service(); + CCMessageBox().Process(TXT_USER_SIGNED_OFF); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + recsignedoff = true; + break; + + /*.................................................................. + Game Options: Store all options; check my color & game version. + ..................................................................*/ + case (SERIAL_GAME_OPTIONS): + oppscorescreen = false; + gameoptions = true; + display = REDRAW_MESSAGE; + parms_received = 1; + + strcpy (TheirName, ReceivePacket.Name); + TheirColor = ReceivePacket.Color; + TheirHouse = ReceivePacket.House; + + /*............................................................... + Make sure I don't have the same color as the other guy. + ...............................................................*/ + if (MPlayerColorIdx == TheirColor) { + + // force transmitting of game options packet + + transmit = 1; + transmittime = 0; + + MPlayerColorIdx = TheirColor + 1; + if (MPlayerColorIdx >= 6) + MPlayerColorIdx = 0; + name_edt.Set_Color(MPlayerTColors[MPlayerColorIdx]); + name_edt.Flag_To_Redraw(); + display = REDRAW_COLORS; + } + + /*............................................................... + Save scenario settings. + ...............................................................*/ + MPlayerCredits = ReceivePacket.Credits; + MPlayerBases = ReceivePacket.IsBases; + MPlayerTiberium = ReceivePacket.IsTiberium; + MPlayerGoodies = ReceivePacket.IsGoodies; + MPlayerGhosts = ReceivePacket.IsGhosties; + BuildLevel = ReceivePacket.BuildLevel; + MPlayerUnitCount = ReceivePacket.UnitCount; + Seed = ReceivePacket.Seed; + Special = ReceivePacket.Special; + Options.GameSpeed = ReceivePacket.GameSpeed; + + if (MPlayerTiberium) { + Special.IsTGrowth = 1; + Special.IsTSpread = 1; + } else { + Special.IsTGrowth = 0; + Special.IsTSpread = 0; + } + + /*............................................................... + Find the index of the scenario number; if it's not found, leave + it at -1. + ...............................................................*/ + ScenarioIdx = -1; + for (i = 0; i < MPlayerFilenum.Count(); i++) { + if (ReceivePacket.Scenario == MPlayerFilenum[i]) + ScenarioIdx = i; + } + + /*............................................................... + Check our version numbers; if they're incompatible, sign off. + ...............................................................*/ +#ifdef PATCH + if (IsV107) { + version = 1; + } else { + version = 2; + } +#else + version = Version_Number(); +#endif + if (ReceivePacket.Version > version) { + CCMessageBox().Process (TXT_YOURGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } else { + if (ReceivePacket.Version < version) { + CCMessageBox().Process (TXT_DESTGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } + } + + /*............................................................... + If this is the first game-options packet we've received, transmit + our options to him. + ...............................................................*/ + if (first) { + first = 0; + + // force transmitting of game options packet + + transmit = 1; + transmittime = 0; + } + break; + + /*.................................................................. + GO: Exit this routine with a success code. + ..................................................................*/ + case (SERIAL_GO): + + // + // calculated one way delay for a packet and overall delay + // to execute a packet + // + MPlayerMaxAhead = MAX( (ReceivePacket.ResponseTime / 8), 2); + + process = false; + rc = true; + break; + + /*.................................................................. + Incoming message: add to our list + ..................................................................*/ + case (SERIAL_MESSAGE): + oppscorescreen = false; + sprintf(txt, Text_String (TXT_FROM), ReceivePacket.Name, + ReceivePacket.Message); + magic_number = *((unsigned short*)(ReceivePacket.Message + COMPAT_MESSAGE_LENGTH-4)); + crc = *((unsigned short*)(ReceivePacket.Message + COMPAT_MESSAGE_LENGTH-2)); + Messages.Add_Message (txt, MPlayerTColors[MPlayerID_To_ColorIndex(ReceivePacket.ID)], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 1200, magic_number, crc); + display = REDRAW_MESSAGE; + break; + + // + // throw away timing packet + // + case (SERIAL_TIMING): + oppscorescreen = false; + break; + + // + // print msg waiting for opponent + // + case (SERIAL_SCORE_SCREEN): +// Smart_Printf( "received score screen\n" ); + oppscorescreen = true; + display = REDRAW_MESSAGE; + parms_received = 1; + break; + + default: + break; + } + } + } + + // if we haven't received a msg for 10 seconds exit + + //if ( ((TickCount.Time() - lastmsgtime) > msg_timeout) || + //(Winsock.Get_Connected() && Winsock.Get_Connection_Status == TcpipManagerClass::CONNECTION_LOST)) { + + if ( (TickCount.Time() - lastmsgtime) > msg_timeout) { + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + } + + + + + /*--------------------------------------------------------------------- + Service the connection + ---------------------------------------------------------------------*/ + NullModem.Service(); + + } /* end of while */ + + /*------------------------------------------------------------------------ + Sort player ID's, so we can execute commands in the same order on both + machines. + ------------------------------------------------------------------------*/ + if (rc) { + /*..................................................................... + Set the number of players in this game, and my ID + .....................................................................*/ + MPlayerCount = 2; + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + TheirID = Build_MPlayerID (TheirColor,TheirHouse); + + /*..................................................................... + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + .....................................................................*/ + if (TheirID < MPlayerLocalID) { + MPlayerID[0] = TheirID; + MPlayerID[1] = MPlayerLocalID; + strcpy (MPlayerNames[0], TheirName); + strcpy (MPlayerNames[1], MPlayerName); + } else { + MPlayerID[0] = MPlayerLocalID; + MPlayerID[1] = TheirID; + strcpy (MPlayerNames[0], MPlayerName); + strcpy (MPlayerNames[1], TheirName); + } + + /*..................................................................... + Get the scenario filename + .....................................................................*/ + Scenario = MPlayerFilenum[ScenarioIdx]; + + starttime = TickCount.Time(); + while ( ( NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_SENDING_TIMEOUT) ) + || ((TickCount.Time() - starttime) < 60) ) { + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + NullModem.Service(); + Keyboard::Check(); //Make sure the message loop gets called + } + + // clear queue to keep from doing any resends + NullModem.Init_Send_Queue(); + + } else { + if ( !recsignedoff ) { + /*..................................................................... + Broadcast my sign-off over my network + .....................................................................*/ + SendPacket.Command = SERIAL_SIGN_OFF; + SendPacket.Color = MPlayerLocalID; // use Color for ID + SendPacket.ID = ModemGameToPlay; + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + while ( (NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_CANCEL_TIMEOUT) ) + || ((TickCount.Time() - starttime) < 60) ) { + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + if ( NullModem.Get_Message( &ReceivePacket, &packetlen ) > 0) { + + // are we getting our own packets back?? + + if (ReceivePacket.Command == SERIAL_SIGN_OFF + && ReceivePacket.ID == ModemGameToPlay) { + + // exit while + break; + } + } + + NullModem.Service(); + } + } + + Shutdown_Modem(); + } + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + /*------------------------------------------------------------------------ + Save any changes made to our options + ------------------------------------------------------------------------*/ + if (changed) { + Write_MultiPlayer_Settings (); + } + + return(rc); + +} /* end of Com_Show_Scenario_Dialog */ + + + +/*************************************************************************** + * Phone_Dialog -- Lets user edit phone directory & dial * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * true = dial the current phone book entry, false = cancel. * + * * + * WARNINGS: * + * Serial options must have been read from CC.INI. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +static int Phone_Dialog (void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 280 *factor; // dialog width + int d_dialog_h = 150 *factor; // dialog height + int d_dialog_x = ((320 *factor - d_dialog_w) / 2); // dialog x-coord + int d_dialog_y = ((200 *factor- d_dialog_h) / 2); // dialog y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 11 *factor; // ht of 6-pt text + int d_margin = 7 *factor; // margin width/height + + int d_phonelist_w = 268 *factor; + int d_phonelist_h = 87 *factor; + int d_phonelist_x = d_dialog_cx - (d_phonelist_w / 2); + int d_phonelist_y = d_dialog_y + d_margin + d_txt6_h + 11*factor; + + int d_add_w = 45*factor; + int d_add_h = 9*factor; + int d_add_x = d_dialog_cx - (d_add_w / 2) - d_margin - d_add_w; + int d_add_y = d_phonelist_y + d_phonelist_h + d_margin; + + int d_edit_w = 45*factor; + int d_edit_h = 9*factor; + int d_edit_x = d_dialog_cx - (d_edit_w / 2); + int d_edit_y = d_phonelist_y + d_phonelist_h + d_margin; + + int d_delete_w = 45*factor; + int d_delete_h = 9*factor; + int d_delete_x = d_dialog_cx + (d_delete_w / 2) + d_margin; + int d_delete_y = d_phonelist_y + d_phonelist_h + d_margin; + + int d_numedit_w = ( (PhoneEntryClass::PHONE_MAX_NUM - 1) * 6*factor) + 3*factor; + int d_numedit_h = 9*factor; + int d_numedit_x = d_dialog_cx - (d_numedit_w / 2); + int d_numedit_y = d_add_y + d_add_h + d_margin; + + int d_dial_w = 45*factor; + int d_dial_h = 9*factor; + int d_dial_x = d_dialog_cx - (d_numedit_w / 2) - d_margin - d_dial_w; + int d_dial_y = d_add_y + d_add_h + d_margin; + + int d_cancel_w = 45*factor; + int d_cancel_h = 9*factor; + int d_cancel_x = d_dialog_cx + (d_numedit_w / 2) + d_margin; + int d_cancel_y = d_add_y + d_add_h + d_margin; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_PHONELIST = 100, + BUTTON_ADD, + BUTTON_EDIT, + BUTTON_DELETE, + BUTTON_DIAL, + BUTTON_CANCEL, + BUTTON_NUMEDIT, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + BOOL process = true; // process while true + KeyNumType input; + + char phone_num[ PhoneEntryClass::PHONE_MAX_NUM ] = { 0 }; // buffer for editing phone # + int rc; + int i; + int tabs[] = {123*factor, 207*factor}; // tabs for list box + char *item; // for removing items from list box + PhoneEntryClass *p_entry; // for creating / editing phonebook entries + int changed = 0; // 1 = save changes to INI file + int firsttime = 0; + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + void const *up_button; + void const *down_button; + + if (InMainLoop){ + up_button = Hires_Retrieve("BTN-UP.SHP"); + down_button = Hires_Retrieve("BTN-DN.SHP"); + }else{ + up_button = Hires_Retrieve("BTN-UP2.SHP"); + down_button = Hires_Retrieve("BTN-DN2.SHP"); + } + + + ListClass phonelist(BUTTON_PHONELIST, + d_phonelist_x, d_phonelist_y, d_phonelist_w, d_phonelist_h, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + up_button, + down_button); + + TextButtonClass addbtn(BUTTON_ADD, TXT_ADD, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#ifdef FRENCH + d_add_x-4, d_add_y); +#else + d_add_x, d_add_y, d_add_w, d_add_h); +#endif + + TextButtonClass editbtn(BUTTON_EDIT, TXT_EDIT, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_edit_x, d_edit_y, d_edit_w, d_edit_h); + + TextButtonClass deletebtn(BUTTON_DELETE, TXT_DELETE_BUTTON, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#ifdef FRENCH + d_delete_x, d_delete_y); +#else + d_delete_x, d_delete_y, d_delete_w, d_delete_h); +#endif + + TextButtonClass dialbtn(BUTTON_DIAL, TXT_DIAL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +/* ###Change collision detected! C:\PROJECTS\CODE\NULLDLG.CPP... */ + d_dial_x, d_dial_y, d_dial_w, d_dial_h); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, +#if (GERMAN | FRENCH) + d_cancel_x, d_cancel_y); +#else + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); +#endif + + EditClass numedit (BUTTON_NUMEDIT, + phone_num, PhoneEntryClass::PHONE_MAX_NUM, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_numedit_x, d_numedit_y, d_numedit_w, d_numedit_h, EditClass::ALPHANUMERIC); + + /* + ------------------------- Build the button list -------------------------- + */ + commands = &phonelist; + addbtn.Add_Tail(*commands); + editbtn.Add_Tail(*commands); + deletebtn.Add_Tail(*commands); + dialbtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + numedit.Add_Tail(*commands); + dialbtn.Turn_On(); + + /* + ----------------------------- Various Inits ------------------------------ + */ + /*........................................................................ + Fill in the phone directory list box + ........................................................................*/ + phonelist.Set_Tabs(tabs); + Build_Phone_Listbox(&phonelist, &numedit, phone_num); + + if (CurPhoneIdx == -1) { + firsttime = 1; + } + + /* + ---------------------------- Processing loop ----------------------------- + */ + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + // init font variables + + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_PHONE_LIST, d_dialog_x, d_dialog_y, d_dialog_w); + } + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + phonelist.Flag_To_Redraw(); + addbtn.Flag_To_Redraw(); + editbtn.Flag_To_Redraw(); + deletebtn.Flag_To_Redraw(); + dialbtn.Flag_To_Redraw(); + cancelbtn.Flag_To_Redraw(); + numedit.Flag_To_Redraw(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + if ( firsttime ) { + numedit.Set_Focus(); + numedit.Flag_To_Redraw(); + input = commands->Input(); + firsttime = 0; + } + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + /*------------------------------------------------------------------ + New phone listing selected. + ------------------------------------------------------------------*/ + case (BUTTON_PHONELIST | KN_BUTTON): + /*............................................................... + Detect a change in the selected item; update CurPhoneIdx, and + the edit box buffer. + ...............................................................*/ + if (phonelist.Current_Index() != CurPhoneIdx) { + CurPhoneIdx = phonelist.Current_Index(); + strcpy (phone_num, PhoneBook[CurPhoneIdx]->Number); + numedit.Set_Text (phone_num, PhoneEntryClass::PHONE_MAX_NUM ); + changed = 1; + } + break; + + /*------------------------------------------------------------------ + Add a new entry + ------------------------------------------------------------------*/ + case (BUTTON_ADD | KN_BUTTON): + + /*............................................................... + Allocate a new phone book entry + ...............................................................*/ + p_entry = new PhoneEntryClass(); + p_entry->Name[0] = 0; + p_entry->Number[0] = 0; + p_entry->Settings.Port = 0; + p_entry->Settings.IRQ = -1; + p_entry->Settings.Baud = -1; + p_entry->Settings.DialMethod = DIAL_TOUCH_TONE; + p_entry->Settings.InitStringIndex = 0; + p_entry->Settings.CallWaitStringIndex = CALL_WAIT_CUSTOM; + p_entry->Settings.CallWaitString[0] = 0; + + /*............................................................... + Invoke the entry editor; if user clicks Save, add the new entry + to the list, and rebuild the list box. + ...............................................................*/ + if ( Edit_Phone_Dialog( p_entry ) ) { + PhoneBook.Add (p_entry); + Build_Phone_Listbox( &phonelist, &numedit, phone_num ); + /*............................................................ + Set the current listbox index to the newly-added item. + ............................................................*/ + for (i = 0; i < PhoneBook.Count(); i++) { + if (p_entry == PhoneBook[i]) { + CurPhoneIdx = i; + strcpy (phone_num, PhoneBook[CurPhoneIdx]->Number); + numedit.Set_Text (phone_num, PhoneEntryClass::PHONE_MAX_NUM ); + phonelist.Set_Selected_Index( CurPhoneIdx ); + } + } + changed = 1; + } else { + + /*............................................................... + If the user clicked Cancel, delete the entry & keep looping. + ...............................................................*/ + delete p_entry; + } + display = REDRAW_ALL; + break; + + /*------------------------------------------------------------------ + Edit the current entry + ------------------------------------------------------------------*/ + case (BUTTON_EDIT | KN_BUTTON): + + /*............................................................... + Do nothing if no entry is selected. + ...............................................................*/ + if (CurPhoneIdx==-1) + break; + + /*............................................................... + Allocate a new entry & copy the currently-selected entry into it + ...............................................................*/ + p_entry = new PhoneEntryClass(); + (*p_entry) = (*PhoneBook[CurPhoneIdx]); + + /*............................................................... + Pass the new entry to the entry editor; if the user selects OK, + copy the data back into our phone book. Rebuild the list so + the changes show up in the list box. + ...............................................................*/ + if ( Edit_Phone_Dialog( p_entry ) ) { + (*PhoneBook[CurPhoneIdx]) = (*p_entry); + Build_Phone_Listbox(&phonelist, &numedit, phone_num); + /*............................................................ + Set the current listbox index to the newly-added item. + ............................................................*/ + for (i = 0; i < PhoneBook.Count(); i++) { + if (PhoneBook[CurPhoneIdx] == PhoneBook[i]) { + CurPhoneIdx = i; + strcpy (phone_num, PhoneBook[CurPhoneIdx]->Number); + numedit.Set_Text (phone_num, PhoneEntryClass::PHONE_MAX_NUM ); + phonelist.Set_Selected_Index( CurPhoneIdx ); + } + } + changed = 1; + } + delete p_entry; + display = REDRAW_ALL; + break; + + /*------------------------------------------------------------------ + Delete the current entry + ------------------------------------------------------------------*/ + case (BUTTON_DELETE | KN_BUTTON): + + /*............................................................... + Do nothing if no entry is selected. + ...............................................................*/ + if (CurPhoneIdx == -1) + break; + + /*............................................................... + Delete the current item & rebuild the phone listbox + ...............................................................*/ + PhoneBook.Delete (CurPhoneIdx); + Build_Phone_Listbox(&phonelist, &numedit, phone_num); + + if (CurPhoneIdx == -1) { + *phone_num = 0; + numedit.Set_Text (phone_num, PhoneEntryClass::PHONE_MAX_NUM ); + } + changed = 1; + break; + + /*------------------------------------------------------------------ + Dial the current number + ------------------------------------------------------------------*/ + case (KN_RETURN): + dialbtn.IsPressed = true; + dialbtn.Draw_Me(true); + // fall thru + + case (BUTTON_DIAL | KN_BUTTON): + + /*............................................................... + If no item is selected, just dial the number in the phone # + edit box: + - Create a new phone entry + - Copy the phone number into it + - Set settings to defaults + ...............................................................*/ + if (CurPhoneIdx == -1 || + strcmp( PhoneBook[CurPhoneIdx]->Number, phone_num) ) { + + if ( strlen(phone_num) == 0) { // do not dial + dialbtn.IsPressed = false; + dialbtn.Flag_To_Redraw(); + break; + } + + p_entry = new PhoneEntryClass(); + strcpy( p_entry->Name, "NONAME" ); + strcpy( p_entry->Number, phone_num); + p_entry->Settings.Port = 0; + p_entry->Settings.IRQ = -1; + p_entry->Settings.Baud = -1; + p_entry->Settings.DialMethod = DIAL_TOUCH_TONE; + p_entry->Settings.InitStringIndex = 0; + p_entry->Settings.CallWaitStringIndex = CALL_WAIT_CUSTOM; + p_entry->Settings.CallWaitString[0] = 0; + + PhoneBook.Add (p_entry); + Build_Phone_Listbox(&phonelist, &numedit, phone_num); + /*............................................................ + Set the current listbox index to the newly-added item. + ............................................................*/ + for (i = 0; i < PhoneBook.Count(); i++) { + if (p_entry == PhoneBook[i]) { + CurPhoneIdx = i; + } + } + changed = 1; + } + + process = false; + rc = true; + break; + + /*------------------------------------------------------------------ + CANCEL: bail out + ------------------------------------------------------------------*/ + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + process = false; + rc = false; + break; + } + + } /* end of while */ + + /*------------------------------------------------------------------------ + Save any changes we've made to the phone list or settings + ------------------------------------------------------------------------*/ + if (changed) { + Write_MultiPlayer_Settings (); + } + + /*------------------------------------------------------------------------ + Clear the list box + ------------------------------------------------------------------------*/ + while (phonelist.Count()) { + item = (char *)phonelist.Get_Item(0); + phonelist.Remove_Item(item); + delete [] item; + } + + return(rc); + +} /* end of Phone_Dialog */ + + +/*************************************************************************** + * Build_Phone_Listbox -- [re]builds the phone entry listbox * + * * + * This routine rebuilds the phone list box from scratch; it also updates * + * the contents of the phone # edit field. * + * * + * INPUT: * + * list ptr to list box * + * edit ptr to edit box * + * buf ptr to buffer for phone # * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +static void Build_Phone_Listbox (ListClass *list, EditClass *edit, char *buf) +{ + int i; + char *item; + char phonename[21]; + char phonenum[15]; + + /*........................................................................ + Clear the list + ........................................................................*/ + while (list->Count()) { + item = (char *)(list->Get_Item(0)); + list->Remove_Item(item); + delete [] item; + } + + /* + ** Now sort the phone list by name then number + */ + qsort ((void *)(&PhoneBook[0]), PhoneBook.Count(), sizeof(class PhoneEntryClass *), Phone_Compare); + + /*........................................................................ + Build the list + ........................................................................*/ + for (i = 0; i < PhoneBook.Count(); i++) { + item = new char[80]; + if ( !(strlen( PhoneBook[i]->Name )) ) { + strcpy( phonename, " " ); + } else { + strncpy( phonename, PhoneBook[i]->Name, 20 ); + phonename[21] = 0; + } + + if ( !(strlen( PhoneBook[i]->Number )) ) { + strcpy( phonenum, " " ); + } else { + if ( strlen( PhoneBook[i]->Number ) < 14) { + strcpy( phonenum, PhoneBook[i]->Number ); + } else { + strncpy( phonenum, PhoneBook[i]->Number, 12 ); + phonenum[12] = 0; + strcat( phonenum, "..." ); + } + } + + if (PhoneBook[i]->Settings.Baud != -1) { + sprintf(item,"%s\t%s\t%d", phonename, phonenum, + PhoneBook[i]->Settings.Baud); + } else { + sprintf(item,"%s\t%s\t[%s]", phonename, phonenum, + Text_String(TXT_DEFAULT)); + } + list->Add_Item(item); + } + list->Flag_To_Redraw(); + + /*........................................................................ + Init the current phone book index + ........................................................................*/ + if (list->Count() == 0 || CurPhoneIdx < -1) { + CurPhoneIdx = -1; + } else { + if (CurPhoneIdx >= list->Count() ) { + CurPhoneIdx = 0; + } + } + + /*........................................................................ + Fill in phone number edit buffer + ........................................................................*/ + if (CurPhoneIdx > -1) { + strcpy (buf, PhoneBook[CurPhoneIdx]->Number); + edit->Set_Text (buf, PhoneEntryClass::PHONE_MAX_NUM ); + list->Set_Selected_Index( CurPhoneIdx ); + } +} + + +/*************************************************************************** + * Phone_Compare -- for qsort * + * * + * INPUT: * + * p1,p2 ptrs to elements to compare * + * * + * OUTPUT: * + * 0 = same, -1 = (*p1) goes BEFORE (*p2), 1 = (*p1) goes AFTER (*p2) * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=========================================================================*/ +static int Phone_Compare (const void *p1, const void *p2) +{ + class PhoneEntryClass *pe1,*pe2; + int result; + + pe1 = *((class PhoneEntryClass **)p1); + pe2 = *((class PhoneEntryClass **)p2); + + result = strcmp( pe1->Name, pe2->Name ); + + // if strings are equal then check the phone number + + if ( !result ) { + result = strcmp( pe1->Number, pe2->Number ); + } + + return(result); +} + + +/*************************************************************************** + * Edit_Phone_Dialog -- lets user edit a phone book entry * + * * + * INPUT: * + * phone entry to edit * + * * + * OUTPUT: * + * true = OK, false = cancel * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/29/1995 BRR : Created. * + *=========================================================================*/ +static int Edit_Phone_Dialog (PhoneEntryClass *phone) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + /*........................................................................ + Dialog & button dimensions + ........................................................................*/ + int d_dialog_w = 230 *factor; // dialog width + int d_dialog_h = 105*factor; // dialog height + int d_dialog_x = ((320*factor - d_dialog_w) / 2); // dialog x-coord +// D_DIALOG_Y = ((200 - D_DIALOG_H) / 2); // dialog y-coord + int d_dialog_y = ((136*factor - d_dialog_h) / 2); // dialog y-coord + int d_dialog_cx = d_dialog_x + (d_dialog_w / 2); // center x-coord + + int d_txt6_h = 11 *factor; // ht of 6-pt text + int d_margin = 7 *factor; // margin width/height + + int d_name_w = ( (PhoneEntryClass::PHONE_MAX_NAME - 1) * 6) + 3 *factor; + int d_name_h = 9 *factor; + int d_name_x = d_dialog_x + (((d_dialog_w - d_name_w) * 3) / 4) - 5 *factor; + int d_name_y = d_dialog_y + 25 *factor; + + int d_number_w = ( (PhoneEntryClass::PHONE_MAX_NUM - 1) * 6) + 3 *factor; + int d_number_h = 9 *factor; + int d_number_x = d_dialog_x + (((d_dialog_w - d_number_w) * 3) / 4) - 5 *factor; + int d_number_y = d_name_y + d_name_h + d_margin; + +#if (GERMAN | FRENCH) + int d_default_w = 130 *factor; +#else + int d_default_w = 104 *factor; +#endif + int d_default_h = 9 *factor; + int d_default_x = d_dialog_cx - (d_default_w / 2); + int d_default_y = d_number_y + d_number_h + d_margin; + +#if (GERMAN | FRENCH) + int d_custom_w = 130 *factor; +#else + int d_custom_w = 100 *factor; +#endif + int d_custom_h = 9 *factor; + int d_custom_x = d_dialog_cx - (d_default_w / 2); + int d_custom_y = d_default_y + d_default_h + d_margin; + +#if (GERMAN | FRENCH) + int d_save_w = 55 *factor; +#else + int d_save_w = 45 *factor; +#endif + int d_save_h = 9 *factor; + int d_save_x = d_dialog_cx - d_margin - d_save_w; + int d_save_y = d_dialog_y + d_dialog_h - d_margin - d_save_h; + +#if (GERMAN | FRENCH) + int d_cancel_w = 55 *factor; +#else + int d_cancel_w = 45 *factor; +#endif + int d_cancel_h = 9 *factor; + int d_cancel_x = d_dialog_cx + d_margin; + int d_cancel_y = d_dialog_y + d_dialog_h - d_margin - d_cancel_h; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_NAME = 100, + BUTTON_NUMBER, + BUTTON_DEFAULT, + BUTTON_CUSTOM, + BUTTON_SAVE, + BUTTON_CANCEL, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + BOOL process = true; // process while true + KeyNumType input; + + char namebuf[PhoneEntryClass::PHONE_MAX_NAME] = { 0 }; // buffer for editing name + char numbuf[PhoneEntryClass::PHONE_MAX_NUM] = { 0 }; // buffer for editing phone # + int rc; + SerialSettingsType settings; + int custom = 0; + int firsttime = 1; + + /*........................................................................ + Buttons + ........................................................................*/ + GadgetClass *commands; // button list + + EditClass nameedit (BUTTON_NAME, + namebuf, PhoneEntryClass::PHONE_MAX_NAME, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_name_x, d_name_y, d_name_w, d_name_h, EditClass::ALPHANUMERIC); + + EditClass numedit (BUTTON_NUMBER, + numbuf, PhoneEntryClass::PHONE_MAX_NUM, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_number_x, d_number_y, d_number_w, d_number_h, EditClass::ALPHANUMERIC); + + TextButtonClass defaultbtn(BUTTON_DEFAULT, TXT_DEFAULT_SETTINGS, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_default_x, d_default_y, d_default_w, d_default_h); + + TextButtonClass custombtn(BUTTON_CUSTOM, TXT_CUSTOM_SETTINGS, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_custom_x, d_custom_y, d_custom_w, d_custom_h); + + TextButtonClass savebtn(BUTTON_SAVE, TXT_SAVE_BUTTON, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_save_x, d_save_y, d_save_w, d_save_h); + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + d_cancel_x, d_cancel_y, d_cancel_w, d_cancel_h); + + /* + ------------------------- Build the button list -------------------------- + */ + commands = &nameedit; + numedit.Add_Tail(*commands); + defaultbtn.Add_Tail(*commands); + custombtn.Add_Tail(*commands); + savebtn.Add_Tail(*commands); + cancelbtn.Add_Tail(*commands); + + /* + ----------------------------- Various Inits ------------------------------ + */ + /*........................................................................ + Init the settings; if the phone entry is set to use defaults, init our + settings to sensible values (in case we invoke the setting editor); + otherwise, copy the entry's settings. + ........................................................................*/ + if (phone->Settings.Port == 0 || phone->Settings.IRQ == -1 || + phone->Settings.Baud == -1) { + settings = SerialDefaults; + defaultbtn.Turn_On(); + custom = false; + } else { + settings = phone->Settings; + custombtn.Turn_On(); + custom = true; + } + + strcpy (namebuf, phone->Name); + nameedit.Set_Text (namebuf, PhoneEntryClass::PHONE_MAX_NAME); + + strcpy (numbuf, phone->Number); + numedit.Set_Text (numbuf, PhoneEntryClass::PHONE_MAX_NUM); + + /* + ---------------------------- Processing loop ----------------------------- + */ + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + if (display >= REDRAW_BACKGROUND) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + + Dialog_Box(d_dialog_x, d_dialog_y, d_dialog_w, d_dialog_h); + + // init font variables + + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + /*............................................................... + Dialog & Field labels + ...............................................................*/ + Draw_Caption (TXT_PHONE_LISTING, d_dialog_x, d_dialog_y, d_dialog_w); + + Fancy_Text_Print (TXT_NAME_COLON, + d_name_x - 5, d_name_y + 1, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Fancy_Text_Print (TXT_NUMBER_COLON, + d_number_x - 5, d_number_y + 1, + CC_GREEN, TBLACK, + TPF_RIGHT | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + } + /* + .......................... Redraw buttons .......................... + */ + if (display >= REDRAW_BUTTONS) { + commands->Flag_List_To_Redraw(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + /* + ........................... Get user input ............................ + */ + input = commands->Input(); + + if ( firsttime ) { + nameedit.Set_Focus(); + nameedit.Flag_To_Redraw(); + input = commands->Input(); + firsttime = 0; + } + + /* + ---------------------------- Process input ---------------------------- + */ + switch (input) { + case (BUTTON_NAME | KN_BUTTON): + numedit.Set_Focus(); + numedit.Flag_To_Redraw(); + break; + +// case (BUTTON_NUMBER | KN_BUTTON): +// nameedit.Clear_Focus(); +// nameedit.Flag_To_Redraw(); +// break; + + /*------------------------------------------------------------------ + Use Default Serial Settings + ------------------------------------------------------------------*/ + case (BUTTON_DEFAULT | KN_BUTTON): + custombtn.Turn_Off(); + defaultbtn.Turn_On(); + custom = 0; + break; + + /*------------------------------------------------------------------ + Use Custom Serial Settings + ------------------------------------------------------------------*/ + case (BUTTON_CUSTOM | KN_BUTTON): + if (Com_Settings_Dialog (&settings)) { + custombtn.Turn_On(); + defaultbtn.Turn_Off(); + } + custom = 1; + display = REDRAW_ALL; + break; + + /*------------------------------------------------------------------ + CANCEL: bail out + ------------------------------------------------------------------*/ + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + process = false; + rc = false; + break; + + /*------------------------------------------------------------------ + Save: save changes + ------------------------------------------------------------------*/ + case (KN_RETURN): + case (BUTTON_SAVE | KN_BUTTON): + process = false; + rc = true; + break; + } + + } /* end of while */ + + /*------------------------------------------------------------------------ + If 'Save', save all current settings + ------------------------------------------------------------------------*/ + if (rc) { + strcpy( phone->Name, strupr( namebuf ) ); + + // if nothing was entered then make if NONAME + + if ( !(phone->Name[0]) ) { + strcpy( phone->Name, "NONAME" ); + } + + strcpy( phone->Number, strupr( numbuf ) ); + + if (custom) { + phone->Settings = settings; + } else { + phone->Settings.Port = 0; + phone->Settings.IRQ = -1; + phone->Settings.Baud = -1; + phone->Settings.DialMethod = DIAL_TOUCH_TONE; + phone->Settings.InitStringIndex = 0; + phone->Settings.CallWaitStringIndex = CALL_WAIT_CUSTOM; + phone->Settings.CallWaitString[0] = 0; + } + } + + return(rc); + + +} /* end of Edit_Phone_Dialog */ + + +static bool Dial_Modem( SerialSettingsType *settings, bool reconnect ) +{ + bool connected = false; + DialStatusType dialstatus; + int modemstatus; + + /* + ** Turn modem servicing off in the callback routine. + */ + ModemService = false; + + // save for later to reconnect + + DialSettings = settings; + + modemstatus = NullModem.Get_Modem_Status(); + if (reconnect) { + if ( (modemstatus & CD_SET) ) { + connected = true; + ModemService = true; + return( connected ); + } + } else { + if ( (modemstatus & CD_SET) ) { + NullModem.Hangup_Modem(); + ModemService = false; + } + } + + NullModem.Setup_Modem_Echo( Modem_Echo ); + + modemstatus = NullModem.Detect_Modem( settings, reconnect ); + if ( !modemstatus ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); +#if (0) + /* + ** If our first attempt to detect the modem failed, and we're at + ** 14400 or 28800, bump up to the next baud rate & try again. + */ + switch (settings->Baud) { + case 9600: + case 19200: + case 38400: + //CCMessageBox().Process( TXT_UNABLE_FIND_MODEM ); + ModemService = true; + return( connected ); + + case 14400: + settings->Baud = 19200; + Shutdown_Modem(); + Init_Null_Modem(settings); + NullModem.Setup_Modem_Echo( Modem_Echo ); + modemstatus = NullModem.Detect_Modem( settings, reconnect ); + if ( !modemstatus ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + //CCMessageBox().Process( TXT_UNABLE_FIND_MODEM ); + ModemService = true; + return( connected ); + } + break; + + case 28800: + settings->Baud = 38400; + Shutdown_Modem(); + Init_Null_Modem(settings); + NullModem.Setup_Modem_Echo( Modem_Echo ); + modemstatus = NullModem.Detect_Modem( settings, reconnect ); + if ( !modemstatus ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + //CCMessageBox().Process( TXT_UNABLE_FIND_MODEM ); + ModemService = true; + return( connected ); + } + break; + } +#else //(0) + ModemService = true; + return( connected ); + +#endif //(0) + } + else if ( modemstatus == -1 ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + CCMessageBox().Process( TXT_ERROR_IN_INITSTRING ); +// CCMessageBox().Process( "Error in the InitString." ); + ModemService = true; + return( connected ); + } + + dialstatus = NullModem.Dial_Modem( DialString, settings->DialMethod, reconnect ); + + if (reconnect) { + /* + --------------------------- Redraw the display --------------------------- + */ + HiddenPage.Clear(); + Map.Flag_To_Redraw(true); + Map.Render(); + } + + switch ( dialstatus ) { + case DIAL_CONNECTED: + connected = true; + break; + + case DIAL_NO_CARRIER: + CCMessageBox().Process(TXT_NO_CARRIER); + connected = false; + break; + + case DIAL_BUSY: + CCMessageBox().Process(TXT_LINE_BUSY); + connected = false; + break; + + case DIAL_ERROR: + CCMessageBox().Process(TXT_NUMBER_INVALID); + connected = false; + break; + + case DIAL_CANCELED: + NullModem.Hangup_Modem(); + ModemService = false; + CCMessageBox().Process(TXT_DIALING_CANCELED); + connected = false; + break; + } + + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + + ModemService = true; + return( connected ); + +} /* end of Dial_Modem */ + + +static bool Answer_Modem( SerialSettingsType *settings, bool reconnect ) +{ + bool connected = false; + DialStatusType dialstatus; + int modemstatus; + +/* ###Change collision detected! C:\PROJECTS\CODE\NULLDLG.CPP... */ + /* + ** Turn modem servicing off in the callback routine. + */ + ModemService = false; + + // save for later to reconnect + + DialSettings = settings; + + modemstatus = NullModem.Get_Modem_Status(); + if (reconnect) { + if ( (modemstatus & CD_SET) ) { + connected = true; + ModemService = true; + return( connected ); + } + } else { + if ( (modemstatus & CD_SET) ) { + NullModem.Hangup_Modem(); + ModemService = false; + } + } + + NullModem.Setup_Modem_Echo( Modem_Echo ); + + modemstatus = NullModem.Detect_Modem( settings, reconnect ); + if ( !modemstatus ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + +#if (0) + /* + ** If our first attempt to detect the modem failed, and we're at + ** 14400 or 28800, bump up to the next baud rate & try again. + */ + switch (settings->Baud) { + case 9600: + case 19200: + case 38400: + //CCMessageBox().Process( TXT_UNABLE_FIND_MODEM ); + ModemService = true; + return( connected ); + + case 14400: + settings->Baud = 19200; + Shutdown_Modem(); + Init_Null_Modem(settings); + NullModem.Setup_Modem_Echo( Modem_Echo ); + modemstatus = NullModem.Detect_Modem( settings, reconnect ); + if ( !modemstatus ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + //CCMessageBox().Process( TXT_UNABLE_FIND_MODEM ); + ModemService = true; + return( connected ); + } + break; + + case 28800: + settings->Baud = 38400; + Shutdown_Modem(); + Init_Null_Modem(settings); + NullModem.Setup_Modem_Echo( Modem_Echo ); + modemstatus = NullModem.Detect_Modem( settings, reconnect ); + if ( !modemstatus ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + //CCMessageBox().Process( TXT_UNABLE_FIND_MODEM ); + ModemService = true; + return( connected ); + } + break; + } +#else //(0) + + ModemService = true; + return( connected ); +#endif //(0) + + } + else if ( modemstatus == -1 ) { + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + CCMessageBox().Process( TXT_ERROR_IN_INITSTRING ); + ModemService = true; + return( connected ); + } + + dialstatus = NullModem.Answer_Modem( reconnect ); + + switch ( dialstatus ) { + case DIAL_CONNECTED: + connected = true; + break; + + case DIAL_NO_CARRIER: + CCMessageBox().Process(TXT_NO_CARRIER); + connected = false; + break; + +// case DIAL_BUSY: +// CCMessageBox().Process(TXT_LINE_BUSY); +// connected = false; +// break; + + case DIAL_ERROR: + // Error occurred but not neccessarily an invalid number + //CCMessageBox().Process(TXT_NUMBER_INVALID); + connected = false; + break; + + case DIAL_CANCELED: + CCMessageBox().Process(TXT_ANSWERING_CANCELED); + connected = false; + break; + } + + NullModem.Remove_Modem_Echo(); + NullModem.Print_EchoBuf(); + NullModem.Reset_EchoBuf(); + + ModemService = true; + return( connected ); + +} /* end of Answer_Modem */ + + +static void Modem_Echo( char c ) +{ + if (NullModem.EchoCount < (NullModem.EchoSize - 1) ) { + *(NullModem.EchoBuf + NullModem.EchoCount) = c; + *(NullModem.EchoBuf + NullModem.EchoCount + 1) = 0; + NullModem.EchoCount++; + } else { + //Smart_Printf( "Echo buffer full!!!\n" ); + } + +} /* end of Modem_Echo */ + + +void Smart_Printf( char *format, ... ) +{ + va_list arglist; + char buf[501]; + + + va_start(arglist,format); + vsprintf(buf,format,arglist); + va_end(arglist); + + if (Debug_Smart_Print) { + if (Special.IsMonoEnabled) { +// Mono_Set_Cursor(0,0); + Mono_Printf("%s",buf); + } else { +// Mono_Printf("%s",buf); + printf("%s",buf); + } + } else { + if (Debug_Heap_Dump) { + printf("%s",buf); + } + } +} + + +void Hex_Dump_Data( char *buffer, int length ) +{ + int i; + int offset = 0; + char buff[10]; + char ptr[16]; + char c; + + + while (length >= 16) { + memcpy( ptr, (buffer + offset), 16); + + Smart_Printf("%05lX ", offset); + + for (i = 0; i < 16; i++) { + + c = ptr[i]; + itoh(c, buff); + + if (!(i & 0x3) && i) { + Smart_Printf("?"); + } + + Smart_Printf("%s ", buff); + } + + Smart_Printf(" "); + + for (i = 0; i < 16; i++) { + c = ptr[i]; + + if (c && ((c < 7) || (c > 11)) && (c != 13)) { + Smart_Printf("%c", c); + } else { + Smart_Printf("."); + } + } + + Smart_Printf("\n"); + + offset += 16; + length -= 16; + } + + if (length) { + memcpy( ptr, (buffer + offset), 16); + + Smart_Printf("%05lX ", offset); + + for (i = 0; i < 16; i++) { + if (i < length) { + c = ptr[i]; + itoh(c, buff); + if (!(i & 0x3) && i) { + Smart_Printf("?"); + } + Smart_Printf("%s ", buff); + } else { + if (!(i & 0x3) && i) { + Smart_Printf(" "); + } + Smart_Printf(" "); + } + } + + Smart_Printf(" "); + + for (i = 0; i < length; i++) { + + c = ptr[i]; + + if (c && ((c < 7) || (c > 11)) && (c != 13)) { + Smart_Printf("%c", c); + } else { + Smart_Printf("."); + } + } + + Smart_Printf("\n"); + } + +} /* end of Hex_Dump_Data */ + + +void itoh( int i, char *s) +{ + + int nibble, loop; + +// *s++ = '0'; +// *s++ = 'x'; + + if (i == 0) { + *s++ = '0'; + *s++ = '0'; + } else { + for (loop = 1; loop >= 0; loop--) { + nibble = (i >> (loop << 2)) & 0x000F; + + /* decimal range */ + if (nibble < 10) { + *s++ = '0' + nibble; + } else { + *s++ = 'A' + (nibble - 10); + } + } + } + *s = 0; /* null terminate it */ +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#if (0) + +int Com_Fake_Scenario_Dialog(void) +{ + bool display = true; // redraw level + bool process = true; // process while true + + char namebuf[MPLAYER_NAME_MAX] = {0}; // buffer for player's name + int transmit; // 1 = re-transmit new game options + int parms_received = 1; // 1 = game options received + int changed = 0; // 1 = user has changed an option + + int rc; + int recsignedoff = false; + int version; + unsigned long starttime; + unsigned long timingtime; + unsigned long lastmsgtime; + unsigned long lastredrawtime; + unsigned long transmittime = 0; + unsigned long theirresponsetime; + int packetlen; + bool oppscorescreen = false; + bool gameoptions = false; + EventClass *event; // event ptr + unsigned long msg_timeout = 60*60; // init to 60 seconds + + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + }; + + /*........................................................................ + Dialog variables + ........................................................................*/ + TextButtonClass * buttons = 0; + + KeyNumType input; + + int x,y; + int width=0; + int height=0; + char text_buffer[80*3]; + + const char *current_status_string = Text_String(TXT_WINSOCK_CONNECTING); + strcpy(text_buffer, current_status_string); + + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + + Format_Window_String(text_buffer, SeenBuff.Get_Height(), width, height); + + width = MAX(width, 50*factor); + width += 40*factor; + height += 60*factor; + + x = (SeenBuff.Get_Width() - width) / 2; + y = (SeenBuff.Get_Height() - height) / 2; + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + x + ((width - (String_Pixel_Width( Text_String( TXT_CANCEL ) ) + 8*factor)) >> 1), + y + height - (FontHeight + FontYSpacing + 2*factor) - 5*factor); + + Set_Logic_Page(SeenBuff); + buttons = &cancelbtn; + + buttons->Flag_List_To_Redraw(); + process = true; + + /*........................................................................ + Init other scenario parameters + ........................................................................*/ + Special.IsTGrowth = MPlayerTiberium; + Special.IsTSpread = MPlayerTiberium; + transmit = 1; + + /*........................................................................ + Init random-number generator, & create a seed to be used for all random + numbers from here on out + ........................................................................*/ + randomize(); + //Seed = rand(); + + /* + ---------------------------- Processing loop ----------------------------- + */ + NullModem.Reset_Response_Time(); // clear response time + theirresponsetime = 10000; // initialize to an invalid value + timingtime = lastmsgtime = lastredrawtime = TickCount.Time(); + + + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=true; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + /* + ..................... Draw the background ....................... + */ + Dialog_Box(x, y, width, height); + /* + ....................... Draw the labels ......................... + */ + Draw_Caption(TXT_NONE, x, y, width); + + Fancy_Text_Print(text_buffer, x + 20*factor, y + 25*factor, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + buttons->Draw_All(); + Show_Mouse(); + display = false; + } + + /* + ............................ Process input ............................ + */ + input = buttons->Input(); + switch (input) { + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + process = false; + rc = false; + break; + + default: + break; + } + + + /*--------------------------------------------------------------------- + If our Transmit flag is set, we need to send out a game option packet. + This message requires an ACK. The first time through the loop, transmit + should be set, so we send out our default options; we'll then send + any changes we make to the defaults. + ---------------------------------------------------------------------*/ + if (transmit && (TickCount.Time() - transmittime) > PACKET_RETRANS_TIME) { + SendPacket.Command = SERIAL_GAME_OPTIONS; + strcpy (SendPacket.Name, MPlayerName); +#ifdef PATCH + if (IsV107) { + SendPacket.Version = 1; + } else { + SendPacket.Version = 2; + } +#else + SendPacket.Version = Version_Number(); +#endif + SendPacket.House = MPlayerHouse; + SendPacket.Color = MPlayerColorIdx; + + SendPacket.Scenario = MPlayerFilenum[ScenarioIdx]; + + SendPacket.Credits = MPlayerCredits; + SendPacket.IsBases = MPlayerBases; + SendPacket.IsTiberium = MPlayerTiberium; + SendPacket.IsGoodies = MPlayerGoodies; + SendPacket.IsGhosties = MPlayerGhosts; + SendPacket.BuildLevel = BuildLevel; + SendPacket.UnitCount = MPlayerUnitCount; + SendPacket.Seed = Seed; + SendPacket.Special = Special; + SendPacket.GameSpeed = Options.GameSpeed; + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + transmittime = TickCount.Time(); + transmit = 0; + } + + // + // send a timing packet if enough time has gone by. + // + if ( (TickCount.Time() - timingtime) > PACKET_TIMING_TIMEOUT) { + SendPacket.Command = SERIAL_TIMING; + SendPacket.ResponseTime = NullModem.Response_Time(); + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 0); + timingtime = TickCount.Time(); + } + + /*--------------------------------------------------------------------- + Check for an incoming message + ---------------------------------------------------------------------*/ + if (NullModem.Get_Message (&ReceivePacket, &packetlen) > 0) { + + lastmsgtime = TickCount.Time(); + msg_timeout = 600; // reset timeout value to 10 seconds + // (only the 1st time through is 20 seconds) + + + if (ReceivePacket.Command >= SERIAL_CONNECT && + ReceivePacket.Command < SERIAL_LAST_COMMAND && + ReceivePacket.Command != SERIAL_MESSAGE && + ReceivePacket.ID == ModemGameToPlay) { + + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + break; + } + + event = (EventClass *)&ReceivePacket; + if (event->Type <= EventClass::FRAMEINFO) { + if ( (TickCount.Time() - lastredrawtime) > PACKET_REDRAW_TIME) { + lastredrawtime = TickCount.Time(); + oppscorescreen = true; + parms_received = 1; + } + } else { + switch ( ReceivePacket.Command ) { + /*.................................................................. + Sign-off: Give the other machine time to receive my ACK, display a + message, and exit. + ..................................................................*/ + case (SERIAL_SIGN_OFF): + starttime = TickCount.Time(); + while (TickCount.Time() - starttime < 60) + NullModem.Service(); + CCMessageBox().Process(TXT_USER_SIGNED_OFF); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + recsignedoff = true; + break; + + /*.................................................................. + Game Options: Store the other machine's name, color & house; + If they've picked the same color as myself, re-transmit my settings + to force him to choose a different color. (Com_Show_Scenario_Dialog + is responsible for ensuring the colors are different.) + ..................................................................*/ + case (SERIAL_GAME_OPTIONS): + oppscorescreen = false; + gameoptions = true; + strcpy (TheirName, ReceivePacket.Name); + TheirColor = ReceivePacket.Color; + TheirHouse = ReceivePacket.House; + transmit = 1; + + parms_received = 1; + + /*............................................................... + Check the version number of the other system. + ...............................................................*/ +#ifdef PATCH + if (IsV107) { + version = 1; + } else { + version = 2; + } +#else + version = Version_Number(); +#endif + if (ReceivePacket.Version > version) { + CCMessageBox().Process (TXT_YOURGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } else { + if (ReceivePacket.Version < version) { + CCMessageBox().Process (TXT_DESTGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } + } + break; + + /*.................................................................. + Incoming message: add to our list + ..................................................................*/ + + // + // get their response time + // + case (SERIAL_TIMING): + oppscorescreen = false; + theirresponsetime = ReceivePacket.ResponseTime; + + if ( !gameoptions ) { + transmit = 1; + }else{ + process = false; + rc = true; + } + break; + + // + // print msg waiting for opponent + // + case (SERIAL_SCORE_SCREEN): + oppscorescreen = true; + parms_received = 1; + break; + + default: + break; + } + } + } + + // if we haven't received a msg for 10 seconds exit + + if ( (TickCount.Time() - lastmsgtime) > msg_timeout) { + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + } + + /*--------------------------------------------------------------------- + Service the connection + ---------------------------------------------------------------------*/ + NullModem.Service(); + + } /* end of while */ + + /*------------------------------------------------------------------------ + Sort player ID's, so we can execute commands in the same order on both + machines. + ------------------------------------------------------------------------*/ + if (rc) { + /*..................................................................... + Set the number of players in this game, and my ID + .....................................................................*/ + MPlayerCount = 2; + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + TheirID = Build_MPlayerID (TheirColor,TheirHouse); + + /*..................................................................... + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + .....................................................................*/ + if (TheirID < MPlayerLocalID) { + MPlayerID[0] = TheirID; + MPlayerID[1] = MPlayerLocalID; + strcpy (MPlayerNames[0], TheirName); + strcpy (MPlayerNames[1], MPlayerName); + } else { + MPlayerID[0] = MPlayerLocalID; + MPlayerID[1] = TheirID; + strcpy (MPlayerNames[0], MPlayerName); + strcpy (MPlayerNames[1], TheirName); + } + + /*..................................................................... + Get the scenario filename + .....................................................................*/ + Scenario = MPlayerFilenum[ScenarioIdx]; + + /*..................................................................... + Send all players the GO packet. + .....................................................................*/ + SendPacket.Command = SERIAL_GO; + SendPacket.ResponseTime = NullModem.Response_Time(); + if ( theirresponsetime == 10000 ) { +// Mono_Clear_Screen(); +// Smart_Printf( "Did not receive their response time!!!!!!!\n" ); +// Get_Key(); + } else { + if (SendPacket.ResponseTime < theirresponsetime) { + SendPacket.ResponseTime = theirresponsetime; + } + } + + // + // calculated one way delay for a packet and overall delay to execute + // a packet + // + ////////MPlayerMaxAhead = MAX( (SendPacket.ResponseTime / 8), 2); + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + while ( ( NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_SENDING_TIMEOUT) ) + || ((TickCount.Time() - starttime) < 60) ) { + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + NullModem.Service(); + Keyboard::Check(); //Make sure the message loop gets called + } + + // clear queue to keep from doing any resends + NullModem.Init_Send_Queue(); + + } else { + if ( !recsignedoff ) { + /*..................................................................... + Broadcast my sign-off over my network + .....................................................................*/ + SendPacket.Command = SERIAL_SIGN_OFF; + SendPacket.Color = MPlayerLocalID; // use Color for ID + SendPacket.ID = ModemGameToPlay; + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + while ( (NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_CANCEL_TIMEOUT) ) + || ((TickCount.Time() - starttime) < 60) ) { + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + if ( NullModem.Get_Message( &ReceivePacket, &packetlen ) > 0) { + + // are we getting our own packets back?? + + if (ReceivePacket.Command == SERIAL_SIGN_OFF + && ReceivePacket.ID == ModemGameToPlay) { + + // exit while + break; + } + } + + NullModem.Service(); + } + } + + Shutdown_Modem(); + } + + /*------------------------------------------------------------------------ + Restore screen + ------------------------------------------------------------------------*/ + Hide_Mouse(); + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Show_Mouse(); + + /*------------------------------------------------------------------------ + Save any changes made to our options + ------------------------------------------------------------------------*/ + if (changed) { + Write_MultiPlayer_Settings (); + } + + return(rc); + +} /* end of Com_Scenario_Dialog */ + + + + + + + + + + + + + + + + + + + + + + + + + + +int Com_Show_Fake_Scenario_Dialog(void) +{ + /*........................................................................ + Dialog variables + ........................................................................*/ + bool display = true; // redraw level + BOOL process = true; // process while true + + char namebuf[MPLAYER_NAME_MAX] = {0}; // buffer for player's name + int transmit; // 1 = re-transmit new game options + int first; // 1 = no packets received yet + int parms_received = 0; // 1 = game options received + int changed = 0; // 1 = user has changed an option + + int rc; + int recsignedoff = 0; + int i; + int version; + unsigned long starttime; + unsigned long timingtime; + unsigned long lastmsgtime; + unsigned long lastredrawtime; + unsigned long transmittime = 0; + int packetlen; + bool oppscorescreen = false; + bool gameoptions = false; + EventClass *event; // event ptr + unsigned long msg_timeout = 60*60; // init to 60 seconds + + + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + }; + + /*........................................................................ + Dialog variables + ........................................................................*/ + TextButtonClass * buttons = 0; + + KeyNumType input; + + int x,y; + int width=0; + int height=0; + char text_buffer[80*3]; + + const char *current_status_string = Text_String(TXT_WINSOCK_CONNECTING); + strcpy(text_buffer, current_status_string); + + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + + Format_Window_String(text_buffer, SeenBuff.Get_Height(), width, height); + + width = MAX(width, 50*factor); + width += 40*factor; + height += 60*factor; + + x = (SeenBuff.Get_Width() - width) / 2; + y = (SeenBuff.Get_Height() - height) / 2; + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + x + ((width - (String_Pixel_Width( Text_String( TXT_CANCEL ) ) + 8*factor)) >> 1), + y + height - (FontHeight + FontYSpacing + 2*factor) - 5*factor); + + Set_Logic_Page(SeenBuff); + buttons = &cancelbtn; + + buttons->Flag_List_To_Redraw(); + + + transmit = 1; + first = 1; + + + /* + ---------------------------- Processing loop ----------------------------- + */ + NullModem.Reset_Response_Time(); // clear response time + timingtime = lastmsgtime = lastredrawtime = TickCount.Time(); + + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=true; + } + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + /* + .................. Redraw backgound & dialog box ................... + */ + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + Set_Palette(Palette); + /* + ..................... Draw the background ....................... + */ + Dialog_Box(x, y, width, height); + /* + ....................... Draw the labels ......................... + */ + Draw_Caption(TXT_NONE, x, y, width); + + Fancy_Text_Print(text_buffer, x + 20*factor, y + 25*factor, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + buttons->Draw_All(); + Show_Mouse(); + display = false; + } + + + /* + ............................ Process input ............................ + */ + input = buttons->Input(); + switch (input) { + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + process = false; + rc = false; + break; + + default: + break; + } + + + + /*--------------------------------------------------------------------- + If our Transmit flag is set, we need to send out a game option packet + ---------------------------------------------------------------------*/ + if (transmit && (TickCount.Time() - transmittime) > PACKET_RETRANS_TIME) { + SendPacket.Command = SERIAL_GAME_OPTIONS; + strcpy (SendPacket.Name, MPlayerName); +#ifdef PATCH + if (IsV107) { + SendPacket.Version = 1; + } else { + SendPacket.Version = 2; + } +#else + SendPacket.Version = Version_Number(); +#endif + SendPacket.House = MPlayerHouse; + SendPacket.Color = MPlayerColorIdx; + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + transmittime = TickCount.Time(); + transmit = 0; + } + + // + // send a timing packet if enough time has gone by. + // + if ( (TickCount.Time() - timingtime) > PACKET_TIMING_TIMEOUT) { + SendPacket.Command = SERIAL_TIMING; + SendPacket.ResponseTime = NullModem.Response_Time(); + SendPacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 0); + timingtime = TickCount.Time(); + } + + /*--------------------------------------------------------------------- + Check for an incoming message + ---------------------------------------------------------------------*/ + if (NullModem.Get_Message (&ReceivePacket, &packetlen) > 0) { + + lastmsgtime = TickCount.Time(); + + msg_timeout = 600; + + // are we getting our own packets back?? + + if (ReceivePacket.Command >= SERIAL_CONNECT && + ReceivePacket.Command < SERIAL_LAST_COMMAND && + ReceivePacket.Command != SERIAL_MESSAGE && + ReceivePacket.ID == ModemGameToPlay) { + + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + break; + } + + event = (EventClass *)&ReceivePacket; + if (event->Type <= EventClass::FRAMEINFO) { + if ( (TickCount.Time() - lastredrawtime) > PACKET_REDRAW_TIME) { + lastredrawtime = TickCount.Time(); + oppscorescreen = true; + display = false; + parms_received = 1; + } + } else { + switch ( ReceivePacket.Command ) { + /*.................................................................. + Other system signs off: Give it time to receive my ACK, then show + a message. + ..................................................................*/ + case (SERIAL_SIGN_OFF): + starttime = TickCount.Time(); + while ( (TickCount.Time() - starttime) < 60) + NullModem.Service(); + CCMessageBox().Process(TXT_USER_SIGNED_OFF); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + recsignedoff = true; + break; + + /*.................................................................. + Game Options: Store all options; check my color & game version. + ..................................................................*/ + case (SERIAL_GAME_OPTIONS): + oppscorescreen = false; + gameoptions = true; + display = false; + parms_received = 1; + + strcpy (TheirName, ReceivePacket.Name); + TheirColor = ReceivePacket.Color; + TheirHouse = ReceivePacket.House; + + /*............................................................... + Save scenario settings. + ...............................................................*/ + MPlayerCredits = ReceivePacket.Credits; + MPlayerBases = ReceivePacket.IsBases; + MPlayerTiberium = ReceivePacket.IsTiberium; + MPlayerGoodies = ReceivePacket.IsGoodies; + MPlayerGhosts = ReceivePacket.IsGhosties; + BuildLevel = ReceivePacket.BuildLevel; + MPlayerUnitCount = ReceivePacket.UnitCount; + Seed = ReceivePacket.Seed; + Special = ReceivePacket.Special; + Options.GameSpeed = ReceivePacket.GameSpeed; + + if (MPlayerTiberium) { + Special.IsTGrowth = 1; + Special.IsTSpread = 1; + } else { + Special.IsTGrowth = 0; + Special.IsTSpread = 0; + } + + /*............................................................... + Find the index of the scenario number; if it's not found, leave + it at -1. + ...............................................................*/ + ScenarioIdx = -1; + for (i = 0; i < MPlayerFilenum.Count(); i++) { + if (ReceivePacket.Scenario == MPlayerFilenum[i]) + ScenarioIdx = i; + } + + /*............................................................... + Check our version numbers; if they're incompatible, sign off. + ...............................................................*/ +#ifdef PATCH + if (IsV107) { + version = 1; + } else { + version = 2; + } +#else + version = Version_Number(); +#endif + if (ReceivePacket.Version > version) { + CCMessageBox().Process (TXT_YOURGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } else { + if (ReceivePacket.Version < version) { + CCMessageBox().Process (TXT_DESTGAME_OUTDATED); + + // to skip the other system not responding msg + lastmsgtime = TickCount.Time(); + + process = false; + rc = false; + } + } + + /*............................................................... + If this is the first game-options packet we've received, transmit + our options to him. + ...............................................................*/ + if (first) { + first = 0; + + // force transmitting of game options packet + + transmit = 1; + transmittime = 0; + } + break; + + /*.................................................................. + GO: Exit this routine with a success code. + ..................................................................*/ + case (SERIAL_GO): + + // + // calculated one way delay for a packet and overall delay + // to execute a packet + // + ////////MPlayerMaxAhead = MAX( (ReceivePacket.ResponseTime / 8), 2); + + process = false; + rc = true; + break; + + // + // throw away timing packet + // + case (SERIAL_TIMING): + oppscorescreen = false; + break; + + // + // print msg waiting for opponent + // + case (SERIAL_SCORE_SCREEN): +// Smart_Printf( "received score screen\n" ); + oppscorescreen = true; + display = false; + parms_received = 1; + break; + + default: + break; + } + } + } + + // if we haven't received a msg for 10 seconds exit + + if ( (TickCount.Time() - lastmsgtime) > msg_timeout) { + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + process = false; + rc = false; + + // say we did receive sign off to keep from sending one + recsignedoff = true; + } + + + /*--------------------------------------------------------------------- + Service the connection + ---------------------------------------------------------------------*/ + NullModem.Service(); + + } /* end of while */ + + /*------------------------------------------------------------------------ + Sort player ID's, so we can execute commands in the same order on both + machines. + ------------------------------------------------------------------------*/ + if (rc) { + /*..................................................................... + Set the number of players in this game, and my ID + .....................................................................*/ + MPlayerCount = 2; + MPlayerLocalID = Build_MPlayerID (MPlayerColorIdx, MPlayerHouse); + + TheirID = Build_MPlayerID (TheirColor,TheirHouse); + + /*..................................................................... + Store every player's ID in the MPlayerID[] array. This array will + determine the order of event execution, so the ID's must be stored + in the same order on all systems. + .....................................................................*/ + if (TheirID < MPlayerLocalID) { + MPlayerID[0] = TheirID; + MPlayerID[1] = MPlayerLocalID; + strcpy (MPlayerNames[0], TheirName); + strcpy (MPlayerNames[1], MPlayerName); + } else { + MPlayerID[0] = MPlayerLocalID; + MPlayerID[1] = TheirID; + strcpy (MPlayerNames[0], MPlayerName); + strcpy (MPlayerNames[1], TheirName); + } + + /*..................................................................... + Get the scenario filename + .....................................................................*/ + Scenario = MPlayerFilenum[ScenarioIdx]; + + starttime = TickCount.Time(); + while ( ( NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_SENDING_TIMEOUT) ) + || ((TickCount.Time() - starttime) < 60) ) { + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + NullModem.Service(); + Keyboard::Check(); //Make sure the message loop gets called + } + + // clear queue to keep from doing any resends + NullModem.Init_Send_Queue(); + + } else { + if ( !recsignedoff ) { + /*..................................................................... + Broadcast my sign-off over my network + .....................................................................*/ + SendPacket.Command = SERIAL_SIGN_OFF; + SendPacket.Color = MPlayerLocalID; // use Color for ID + SendPacket.ID = ModemGameToPlay; + NullModem.Send_Message (&SendPacket, sizeof(SendPacket), 1); + + starttime = TickCount.Time(); + while ( (NullModem.Num_Send() + && ((TickCount.Time() - starttime) < PACKET_CANCEL_TIMEOUT) ) + || ((TickCount.Time() - starttime) < 60) ) { + #if(SHOW_MONO) + NullModem.Mono_Debug_Print(0); + #endif + + if ( NullModem.Get_Message( &ReceivePacket, &packetlen ) > 0) { + + // are we getting our own packets back?? + + if (ReceivePacket.Command == SERIAL_SIGN_OFF + && ReceivePacket.ID == ModemGameToPlay) { + + // exit while + break; + } + } + + NullModem.Service(); + } + } + + Shutdown_Modem(); + } + + /*------------------------------------------------------------------------ + Save any changes made to our options + ------------------------------------------------------------------------*/ + if (changed) { + Write_MultiPlayer_Settings (); + } + + return(rc); + +} /* end of Com_Show_Scenario_Dialog */ + + +#endif //(0) + + + + + + + + + + + diff --git a/NULLMGR.CPP b/NULLMGR.CPP new file mode 100644 index 0000000..ec9b137 --- /dev/null +++ b/NULLMGR.CPP @@ -0,0 +1,2350 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\nullmgr.cpv 1.10 16 Oct 1995 16:51:52 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : NULLMGR.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : April 5, 1995 * + * * + * Last Update : May 1, 1995 [BRR] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * NullModemClass::NullModemClass -- class constructor * + * NullModemClass::~NullModemClass -- class destructor * + * NullModemClass::Init -- initialization * + * NullModemClass::Send_Message -- sends a message * + * NullModemClass::Get_Message -- polls the Queue for a message * + * NullModemClass::Service -- main polling routine * + * NullModemClass::Num_Send -- Returns # of unACK'd send entries * + * NullModemClass::Num_Receive -- Returns # entries in the receive queue * + * NullModemClass::Response_Time -- Returns Queue's avg response time * + * NullModemClass::Reset_Response_Time -- Resets response time computatio* + * NullModemClass::Oldest_Send -- Returns ptr to oldest unACK'd send buf * + * NullModemClass::Detect_Modem -- Detects and initializes the modem * + * NullModemClass::Dial_Modem -- dials a number passed * + * NullModemClass::Answer_Modem -- waits for call and answers * + * NullModemClass::Hangup_Modem -- hangs up the modem * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "wincomm.h" +#include "modemreg.h" +#include "i86.h" +#include "tcpip.h" + + +extern ModemRegistryEntryClass *ModemRegistry; + +// the following line was taken from Greenleaf's +// because of other define conflicts + +#define ESC 27 +#define NOKEY 0xffff +#define INIT_COMMAND_RETRIES 2 + +// this time is in milliseconds + +#define DEFAULT_TIMEOUT 2000 + +// +// the following is for a fix around a greenleaf bug +// where they do not check for the value of abortkey +// to determine whether or not they call the abort modem function. +// +extern "C" { + extern void (*_AbortModemFunctionPtr)(int); +} + +static void (*NullModemClass::OrigAbortModemFunc)(int); + +static KeyNumType NullModemClass::Input; +static GadgetClass *NullModemClass::Commands; // button list + +/* +** Ugly hack: this string stores the string received from the modem +*/ +char ModemRXString[80]; + +/*************************************************************************** + * NullModemClass::NullModemClass -- class constructor * + * * + * INPUT: * + * numsend # desired entries for the send queue * + * numreceive # desired entries for the receive queue * + * maxlen application's max packet length * + * magicnum application-specific magic # (so we don't * + * accidentally end up talking to another one of our own * + * products using the same protocol) * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +NullModemClass::NullModemClass (int numsend, int numreceive, int maxlen, + unsigned short magicnum) : ConnManClass() +{ + /*------------------------------------------------------------------------ + Init Port to NULL; we haven't opened Greenleaf yet. + ------------------------------------------------------------------------*/ + PortHandle = NULL; + Connection = NULL; + + NumSend = numsend; + NumReceive = numreceive; + MaxLen = maxlen; + MagicNum = magicnum; + + RXBuf = 0; + BuildBuf = 0; + + EchoSize = 500; + EchoBuf = 0; + + OldIRQPri = -1; + + ModemVerboseOn = false; // default true + ModemEchoOn = false; // default true + ModemWaitCarrier = 50000; // default 50 * 1000ms = 50 secs + ModemCarrierDetect = 600; // default 6 * 100ms = .6 secs + ModemCarrierLoss = 1400; // default 14 * 100ms = 1.4 secs + ModemHangupDelay = 20000; // default 20 * 1000ms = 20 secs + ModemGuardTime = 1000; // default 50 * 20ms = 1 sec + ModemEscapeCode = '+'; // default ASCII 43 + + SendOverflows = 0; + ReceiveOverflows = 0; + CRCErrors = 0; + + NumConnections = 0; + + /*........................................................................ + Init timing parameters + ........................................................................*/ + RetryDelta = 60; // 60 ticks between retries + MaxRetries = -1; // disregard # retries + Timeout = 1200; // report bad connection after 20 seconds + +} /* end of NullModemClass */ + + +/*************************************************************************** + * NullModemClass::~NullModemClass -- class destructor * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +NullModemClass::~NullModemClass () +{ + Delete_Connection(); +} /* end of ~NullModemClass */ + + +/*************************************************************************** + * NullModemClass::Init -- initialization * + * * + * INPUT: * + * port address * + * irq 2-15 * + * baud 300, 1200, 9600, etc * + * parity 'O' (odd), 'E' (even), 'N' (none), 'S' (space), * + * 'M' (mark) * + * wordlength 5, 6, 7, or 8 * + * stopbits 1 or 2 * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +//int NullModemClass::Init (int port, int irq, int baud, char parity, int wordlen, int stopbits) +#pragma off (unreferenced) +int NullModemClass::Init (int port, int ,char *dev_name, int baud, char parity, int wordlen, int stopbits, int flowcontrol) +{ + int com; + //int irqnum; + //int address; + //int status; + + + if (PortHandle) { + CloseHandle(PortHandle); + PortHandle = NULL; + } + + if (!Connection){ + /*------------------------------------------------------------------------ + Init our Connection + ------------------------------------------------------------------------*/ + Connection = new NullModemConnClass (NumSend, NumReceive, MaxLen, + MagicNum); + + Connection->Set_Retry_Delta (RetryDelta); + Connection->Set_Max_Retries (MaxRetries); + Connection->Set_TimeOut (Timeout); + + /*------------------------------------------------------------------------ + Allocate our packet parsing buffer; make it the same # of packets as the + # of receive queue entries the application has requested. Use the + "Actual" maximum packet size, given from the connection; this allows for + both headers that get added to the packet. + ------------------------------------------------------------------------*/ + RXSize = Connection->Actual_Max_Packet() * NumReceive; + RXBuf = new char [RXSize]; + + BuildBuf = new char [MaxLen]; + + EchoBuf = new char [ EchoSize ]; + } + + RXCount = 0; + EchoCount = 0; + + + /*------------------------------------------------------------------------ + This call allocates all necessary queue buffers + ------------------------------------------------------------------------*/ + switch (port) { + case 0x3f8: + com = COM1; + break; + + case 0x2f8: + com = COM2; + break; + + case 0x3e8: + com = COM3; + break; + + case 0x2e8: + com = COM4; + break; + + default: + com = COM5; + break; + } + + int i; + + /* + ** Create a new modem class for our com port + */ + if (!SerialPort) { + SerialPort = new WinModemClass; + } + + /* + ** Shift up the baud rate to sensible values + */ +// if (baud == 14400) baud = 19200; +// if (baud == 28800) baud = 38400; + + static char com_ids[9][5]={ + "COM1", + "COM2", + "COM3", + "COM4", + "COM5", + "COM6", + "COM7", + "COM8", + "COM9" + }; + + char *device; + + switch ( port ) { + + case 0x3f8: + device = com_ids[0]; + break; + + case 0x2f8: + device = com_ids[1]; + break; + + case 0x3e8: + device = com_ids[2]; + break; + + case 0x2e8: + device = com_ids[3]; + break; + + case 1: + /* + ** 1 is a special value. It means use the device name not the port address. + */ + device = dev_name; + + /* + ** If we can match a registry entry with the device name then use that, otherwise use + ** the device name directly to open the port with. + */ + if (ModemRegistry){ + delete ModemRegistry; + ModemRegistry = NULL; + } + for ( i=0 ; i<10 ; i++ ){ + ModemRegistry = new ModemRegistryEntryClass (i); + if (ModemRegistry->Get_Modem_Name()){ + if (!strcmp (dev_name, ModemRegistry->Get_Modem_Name() )){ + device = ModemRegistry->Get_Modem_Device_Name(); + break; + } + } + delete ModemRegistry; + ModemRegistry = NULL; + } + break; + + default: + device = NULL; + } + + /* + ** Open the com port + */ + PortHandle = SerialPort->Serial_Port_Open (device, baud, parity, wordlen, stopbits, flowcontrol); + if (PortHandle == INVALID_HANDLE_VALUE) { + Shutdown(); + return(false); + } + + /*------------------------------------------------------------------------ + Init the Connection + ------------------------------------------------------------------------*/ + Connection->Init(PortHandle); + + NumConnections = 1; + + return(true); +} + + +/*********************************************************************************************** + * NMC::Num_Connections -- returns NumConnections member * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: NumConnections * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/2/96 11:44AM ST : Documented / Win32 support * + *=============================================================================================*/ +int NullModemClass::Num_Connections( void ) +{ + return(NumConnections); +} + + + +/*********************************************************************************************** + * NMC::Delete_Connection -- removes the connection * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: true * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/2/96 11:44AM ST : Documented / Win32 support * + *=============================================================================================*/ +int NullModemClass::Delete_Connection( void ) +{ + if (Connection) { + delete Connection; + Connection = NULL; + } + + if (RXBuf) { + delete [] RXBuf; + RXBuf = NULL; + } + + if (BuildBuf) { + delete [] BuildBuf; + BuildBuf = NULL; + } + + if (EchoBuf) { + delete [] EchoBuf; + EchoBuf = NULL; + } + + NumConnections = 0; + + return( true ); +} /* end of Delete_Connection */ + + +/*********************************************************************************************** + * NMC::Init_Send_Queue -- Initialises the connections send queue * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: true * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/2/96 11:46AM ST : Documented / Win32 support * + *=============================================================================================*/ +int NullModemClass::Init_Send_Queue( void ) +{ + + /*--------------------------------------------------------------- + Init the send queue + -----------------------------------------------------------------*/ + if ( Connection ) { + Connection->Queue->Init_Send_Queue(); + } + + return(true); +} + + +//DetectPortType NullModemClass::Detect_Port( SerialSettingsType *settings ) +/*********************************************************************************************** + * NMC::Detect_Port -- Checks that the specified com port exists * + * * + * * + * * + * INPUT: ptr to SerialSettingsType * + * * + * OUTPUT: true if port is valid * + * * + * HISTORY: * + * 8/2/96 11:47AM ST : Documented / Win32 support * + *=============================================================================================*/ +DetectPortType NullModemClass::Detect_Port( SerialSettingsType *settings) +{ + + static char com_ids[9][5]={ + "COM1", + "COM2", + "COM3", + "COM4", + "COM5", + "COM6", + "COM7", + "COM8", + "COM9" + }; + + int i; + + /* + ** Create a new modem class for our com port + */ + if (!SerialPort) { + SerialPort = new WinModemClass; + }else{ + SerialPort->Serial_Port_Close(); + } + + /* + ** Shift up the baud rate to sensible values + */ + int baud = settings->Baud; +// if (baud == 14400) baud = 19200; +// if (baud == 28800) baud = 38400; + + + /* + ** Translate the port address into a usable device name + */ + char *device; + + switch ( settings->Port ) { + + case 0x3f8: + device = com_ids[0]; + break; + + case 0x2f8: + device = com_ids[1]; + break; + + case 0x3e8: + device = com_ids[2]; + break; + + case 0x2e8: + device = com_ids[3]; + break; + + case 1: + /* + ** 1 is a special value. It means use the device name not the port address. + */ + device = settings->ModemName; + + /* + ** If we can match a registry entry with the device name then use that, otherwise use + ** the device name directly to open the port with. + */ + if (ModemRegistry){ + delete ModemRegistry; + ModemRegistry = NULL; + } + for ( i=0 ; i<10 ; i++){ + ModemRegistry = new ModemRegistryEntryClass (i); + if (ModemRegistry->Get_Modem_Name()){ + if (!strcmp (device, ModemRegistry->Get_Modem_Name() )){ + /* + ** Got a match. Break out leaving the registry info intact. + */ + device = ModemRegistry->Get_Modem_Device_Name(); + break; + } + } + delete ModemRegistry; + ModemRegistry = NULL; + } + break; + + default: + return (PORT_INVALID); + } + + /* + ** Open the com port + */ + HANDLE porthandle = SerialPort->Serial_Port_Open (device, baud, 0, 8, 1, settings->HardwareFlowControl); + + if (porthandle == INVALID_HANDLE_VALUE){ + return (PORT_INVALID); + } + + SerialPort->Serial_Port_Close(); + return (PORT_VALID); + +} + + + + +/*********************************************************************************************** + * NullModemClass::ShutDown -- Closes serial port and removes the connection * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/2/96 11:43AM ST : Documented / Win32 support * + *=============================================================================================*/ +void NullModemClass::Shutdown ( void ) +{ + if (PortHandle && SerialPort) { + SerialPort->Serial_Port_Close(); + delete SerialPort; + SerialPort = NULL; + PortHandle = NULL; + Delete_Connection(); + } + +#ifdef FORCE_WINSOCK + if (Winsock.Get_Connected()){ + Delete_Connection(); + } +#endif + +} /* end of Shutdown */ + + +/*************************************************************************** + * NullModemClass::Set_Timing -- sets timing for all connections * + * * + * This will set the timing parameters. This allows an application to * + * measure the Response_Time while running, and adjust timing accordingly. * + * * + * INPUT: * + * retrydelta value to set for retry delta * + * maxretries value to set for max # retries * + * timeout value to set for connection timeout * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/07/1995 DRD : Created. * + *=========================================================================*/ +void NullModemClass::Set_Timing (unsigned long retrydelta, + unsigned long maxretries, unsigned long timeout) +{ + RetryDelta = retrydelta; + MaxRetries = maxretries; + Timeout = timeout; + + Connection->Set_Retry_Delta (RetryDelta); + Connection->Set_Max_Retries (MaxRetries); + Connection->Set_TimeOut (Timeout); + +} /* end of Set_Timing */ + + +/*************************************************************************** + * NullModemClass::Send_Message -- sends a message * + * * + * For clarity's sake, here's what happens to the buffer passed in: * + * - It gets passed to the Connection's Send_Packet() routine * + * - The CommHeaderType header gets tacked onto it * + * - The resulting buffer gets added to the Connection's Send Queue * + * - When Service() determines that it needs to send the data, it * + * copies the entire packet (CommHeaderType and all) into its local * + * SendBuf, adds the packet start ID, length, and CRC, then sends it out.* + * * + * The ack_req argument will almost always be '1' (the default). The only * + * reason to use 0 is if you don't know whether the other system is * + * ready or not, so you have to periodically send out a query packet, * + * and wait for a response. (Using the connection's built-in retry * + * system would just blast out useless data if the other system isn't * + * even there.) * + * * + * INPUT: * + * buf buffer to send * + * buflen length of buffer * + * ack_req 1 = ACK is required; 0 = not * + * * + * OUTPUT: * + * 1 = OK; 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int NullModemClass::Send_Message (void *buf, int buflen, int ack_req) +{ + int rc; + + if (NumConnections == 0) { + return( false ); + } + + rc = Connection->Send_Packet(buf,buflen,ack_req); + if (!rc) + SendOverflows++; + + return(rc); + +} /* end of Send_Message */ + + +/*************************************************************************** + * NullModemClass::Get_Message -- polls the Queue for a message * + * * + * INPUT: * + * buf buffer to store message in * + * buflen ptr filled in with length of message * + * * + * OUTPUT: * + * 1 = message was received; 0 = wasn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int NullModemClass::Get_Message (void *buf, int *buflen) +{ + if (NumConnections == 0) { + return( false ); + } + return( Connection->Get_Packet( buf, buflen ) ); +} + + +/*************************************************************************** + * NullModemClass::Service -- main polling routine * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = OK, 0 = connection has gone bad * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int NullModemClass::Service (void) +{ + int pos; // current position in RXBuf + int i; // loop counter + //int status; + unsigned short length; + SerialHeaderType *header; // decoded packet start, length + SerialCRCType *crc; // decoded packet CRC + char moredata = 0; + + if (NumConnections == 0) { + return( false ); + } + + /*------------------------------------------------------------------------ + First, copy all the bytes we can from the Greenleaf RX buffer to our + own buffer. + ------------------------------------------------------------------------*/ + RXCount += SerialPort->Read_From_Serial_Port((unsigned char*)(RXBuf + RXCount), int(RXSize - RXCount) ); + +// if (RXCount){ +//char port[128]; +//sprintf (port, "C&C95 - RXCount = %d bytes.\n", RXCount); +//CCDebugString (port); +// } + +BOOL enabled = FALSE; + +#if (0) +if (SerialPort->FramingErrors || + SerialPort->IOErrors || + SerialPort->InBufferOverflows || + SerialPort->BufferOverruns || + SerialPort->InBufferOverflows || + SerialPort->OutBufferOverflows){ + + +if (!MonoClass::Is_Enabled()) { +MonoClass::Enable(); +enabled = TRUE; +} +Special.IsMonoEnabled = TRUE; +Debug_Smart_Print = TRUE; +Mono_Set_Cursor(0,0); +Smart_Printf( " In Queue: %5d \n", SerialPort->InQueue); +Smart_Printf( " Out Queue: %5d \n", SerialPort->OutQueue); +Smart_Printf( " Framing errors: %5d \n", SerialPort->FramingErrors); +Smart_Printf( " IO errors: %5d \n", SerialPort->IOErrors); +Smart_Printf( " Parity Errors: %5d \n", SerialPort->InBufferOverflows); +Smart_Printf( " Buffer overruns: %5d \n", SerialPort->BufferOverruns); +Smart_Printf( " In buffer overflows: %5d \n", SerialPort->InBufferOverflows); +Smart_Printf( "Out buffer overflows: %5d \n", SerialPort->OutBufferOverflows); + +MonoClass::Disable(); +Debug_Smart_Print = FALSE; +} +#endif //(0) + + // minimum packet size + + if ( RXCount < (PACKET_SERIAL_OVERHEAD_SIZE + 1) ) { + return( Connection->Service() ); + } + + /*------------------------------------------------------------------------ + Now scan the buffer for the start of a packet. + ------------------------------------------------------------------------*/ + pos = -1; + for (i = 0; i <= RXCount - sizeof( short ); i++) { + if ( *((unsigned short *)(RXBuf + i)) == PACKET_SERIAL_START ) { + pos = i; + break; + } + } + + /*------------------------------------------------------------------------ + No start code was found; throw away all bytes except the last few, and + return. + ------------------------------------------------------------------------*/ + if (pos==-1) { +// Smart_Printf( "No magic number found \n" ); + /*..................................................................... + move the remaining, un-checked bytes to the start of the buffer + .....................................................................*/ + memmove (RXBuf, RXBuf + i, sizeof( short ) - 1); + RXCount = sizeof( short ) - 1; + return( Connection->Service() ); + } + + /*........................................................................ + Check to see if there are enough bytes for the header to be decoded + ........................................................................*/ + if ( (RXCount - pos) < sizeof( SerialHeaderType ) ) { + memmove (RXBuf, RXBuf + pos, RXCount - pos); + RXCount -= pos; + return(Connection->Service()); + } + + /*------------------------------------------------------------------------ + A start code was found; check the packet's length & CRC + ------------------------------------------------------------------------*/ + header = (SerialHeaderType *)(RXBuf + pos); + + /*........................................................................ + If we lost a byte in the length, we may end up waiting a very long time + for the buffer to get to the right length; check the verify value to + make sure this didn't happen. + ........................................................................*/ + if ( header->MagicNumber2 != PACKET_SERIAL_VERIFY ) { +// Smart_Printf( "Verify failed\n"); +// Hex_Dump_Data( (RXBuf + pos), PACKET_SERIAL_OVERHEAD_SIZE ); + + pos += sizeof ( short ); // throw away the bogus start code + memmove (RXBuf, RXBuf + pos, RXCount - pos); + RXCount -= pos; + return(Connection->Service()); + } + + length = header->Length; + + /*........................................................................ + Special case: if the length comes out too long for us to process: + - Assume the packet is bad + - Throw away the bogus packet-start code + - Return; we'll search for another packet-start code next time. + ........................................................................*/ + if (length > MaxLen) { +#if (CONN_DEBUG) + printf( "length too lonnng\n" ); +#endif +// Smart_Printf( "length too lonnng %d, max %d \n", length, MaxLen ); + + pos += sizeof ( short ); // throw away the bogus start code + memmove (RXBuf, RXBuf + pos, RXCount - pos); + RXCount -= pos; + return(Connection->Service()); + } + + /*........................................................................ + If the entire packet isn't stored in our buffer, copy the remaining bytes + to the front of the buffer & return. + ........................................................................*/ + if ( (pos + length + PACKET_SERIAL_OVERHEAD_SIZE) > RXCount) { + + if ( moredata ) { +// Smart_Printf( "waiting for more data %d, pos = %d \n", ((length + PACKET_SERIAL_OVERHEAD_SIZE) - (RXCount - pos)), pos ); + } + + if (pos) { + memmove (RXBuf, RXBuf + pos, RXCount - pos); + RXCount -= pos; + } + return(Connection->Service()); + } + + /*........................................................................ + Now grab the CRC value in the packet, & compare it to the CRC value + computed from the actual data. If they don't match, throw away the bogus + start-code, move the rest to the front of the buffer, & return. + We'll continue parsing this data when we're called next time. + ........................................................................*/ + crc = (SerialCRCType *)(RXBuf + pos + sizeof( SerialHeaderType ) + length); + if (NullModemConnClass::Compute_CRC(RXBuf + pos + + sizeof( SerialHeaderType ), length) != crc->SerialCRC) { + + CRCErrors++; + +#if (CONN_DEBUG) + printf( "CRC check failed\n" ); +#endif +// Smart_Printf( "CRC check failed for packet of length %d \n", length ); + +// if (length < 100) { +// Hex_Dump_Data( (RXBuf + pos), (PACKET_SERIAL_OVERHEAD_SIZE + length) ); +// } + + pos += sizeof ( short ); // throw away the bogus start code + memmove (RXBuf, RXBuf + pos, RXCount - pos); + RXCount -= pos; + return(Connection->Service()); + } + + /*------------------------------------------------------------------------ + Give the new packet to the Connection to process. + ------------------------------------------------------------------------*/ + if (!Connection->Receive_Packet(RXBuf + pos + sizeof( SerialHeaderType ), length)) { + ReceiveOverflows++; +// Smart_Printf( "Received overflows %d \n", ReceiveOverflows ); + } + +#if (0) + Hex_Dump_Data( (RXBuf + pos), (PACKET_SERIAL_OVERHEAD_SIZE + length) ); +#endif + + /*------------------------------------------------------------------------ + Move all data past this packet to the front of the buffer. + ------------------------------------------------------------------------*/ + pos += (PACKET_SERIAL_OVERHEAD_SIZE + length); + memmove (RXBuf, RXBuf + pos, RXCount - pos); + RXCount -= pos; + + /*------------------------------------------------------------------------ + Now, service the connection's Queue's; this will handle ACK & Retries. + ------------------------------------------------------------------------*/ + return(Connection->Service()); + +} /* end of Service */ + + +/*************************************************************************** + * NullModemClass::Num_Send -- Returns # of unACK'd send entries * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 05/01/1995 BRR : Created. * + *=========================================================================*/ +int NullModemClass::Num_Send(void) +{ + if (Connection) + return( Connection->Queue->Num_Send() ); + else + return (0); + +} /* end of Num_Send */ + + +/*************************************************************************** + * NullModemClass::Num_Receive -- Returns # entries in the receive queue * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 05/01/1995 BRR : Created. * + *=========================================================================*/ +int NullModemClass::Num_Receive(void) +{ + if (Connection) + return( Connection->Queue->Num_Receive() ); + else + return (0); + +} /* end of Num_Receive */ + + +/*************************************************************************** + * NullModemClass::Response_Time -- Returns Queue's avg response time * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 05/01/1995 BRR : Created. * + *=========================================================================*/ +unsigned long NullModemClass::Response_Time(void) +{ + if (Connection) + return( Connection->Queue->Avg_Response_Time() ); + else + return (0); + +} /* end of Response_Time */ + + +/*************************************************************************** + * NullModemClass::Reset_Response_Time -- Resets response time computation * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 05/01/1995 BRR : Created. * + *=========================================================================*/ +void NullModemClass::Reset_Response_Time(void) +{ + if (Connection) + Connection->Queue->Reset_Response_Time(); + +} /* end of Reset_Response_Time */ + + +/*************************************************************************** + * Oldest_Send -- Returns ptr to oldest unACK'd send buffer * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 05/01/1995 BRR : Created. * + *=========================================================================*/ +void * NullModemClass::Oldest_Send(void) +{ + int i; + SendQueueType *send_entry; // ptr to send entry header + CommHeaderType *packet; + void *buf = NULL; + + for (i = 0; i < Connection->Queue->Num_Send(); i++) { + send_entry = Connection->Queue->Get_Send(i); + if (send_entry) { + packet = (CommHeaderType *)send_entry->Buffer; + if (packet->Code == ConnectionClass::PACKET_DATA_ACK && send_entry->IsACK == 0) { + buf = send_entry->Buffer; + break; + } + } + } + + return(buf); + +} /* end of Oldest_Send */ + + +/*************************************************************************** + * NullModemClass::Configure_Debug -- sets up special debug values * + * * + * Mono_Debug_Print2() can look into a packet to pull out a particular * + * ID, and can print both that ID and a string corresponding to * + * that ID. This routine configures these values so it can find * + * and decode the ID. This ID is used in addition to the normal * + * CommHeaderType values. * + * * + * INPUT: * + * index connection index to configure (-1 = Global Channel) * + * offset ID's byte offset into packet * + * size size of ID, in bytes; 0 if none * + * names ptr to array of names; use ID as an index into this * + * maxnames max # in the names array; 0 if none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * Names shouldn't be longer than 12 characters. * + * * + * HISTORY: * + * 05/31/1995 BRR : Created. * + *=========================================================================*/ +void NullModemClass::Configure_Debug(int , int offset, int size, + char **names, int maxnames) +{ + if (Connection) + Connection->Queue->Configure_Debug (offset, size, names, maxnames); +} + + +/*************************************************************************** + * Mono_Debug_Print -- Debug output routine * + * * + * INPUT: * + * refresh 1 = clear screen & completely refresh * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/02/1995 BRR : Created. * + *=========================================================================*/ +void NullModemClass::Mono_Debug_Print(int, int refresh) +{ + if (!Connection) + return; + + Connection->Queue->Mono_Debug_Print (refresh); + + if (refresh) { + Mono_Set_Cursor (31,1); + Mono_Printf ("Serial Port Queues"); + + Mono_Set_Cursor (9,2); + Mono_Printf ("Average Response Time:"); + + Mono_Set_Cursor (20,3); + Mono_Printf ("CRC Errors:"); + + Mono_Set_Cursor (43,2); + Mono_Printf ("Send Overflows:"); + + Mono_Set_Cursor (40,3); + Mono_Printf ("Receive Overflows:"); + } + + Mono_Set_Cursor (32,2); + Mono_Printf ("%d ", Connection->Queue->Avg_Response_Time()); + + Mono_Set_Cursor (32,3); + Mono_Printf ("%d ", CRCErrors); + + Mono_Set_Cursor (59,2); + Mono_Printf ("%d ", SendOverflows); + + Mono_Set_Cursor (59,3); + Mono_Printf ("%d ", ReceiveOverflows); + + Mono_Set_Cursor (2,5); + Mono_Printf ("%d ", Num_Send()); + + Mono_Set_Cursor (41,5); + Mono_Printf ("%d ", Num_Receive()); + +} /* end of Mono_Debug_Print */ + + +void Timer_Test (int line, char *file) +{ + + char abuffer [128]; + + sprintf (abuffer, "Testing timer at line %d in file %s", line, file); + CCDebugString (abuffer); + + CountDownTimerClass timer; + + timer.Set (1); + + while (timer.Time()){ + CCDebugString ("."); + } + + CCDebugString ("OK\n"); +} + + + + +/*************************************************************************** + * NullModemClass::Detect_Modem -- Detects and initializes the modem * + * * + * INPUT: * + * settings ptr to SerialSettings structure * + * * + * OUTPUT: * + * -1 init string invalid * + * 0 no modem found * + * 1 modem found * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/02/1995 DRD : Created. * + *=========================================================================*/ +int NullModemClass::Detect_Modem( SerialSettingsType *settings, bool reconnect ) +{ + /*........................................................................ + Button Enumerations + ........................................................................*/ +// enum { +// BUTTON_CANCEL = 100, +// }; + + int status; +// int modemstatus; + int value; + int error_count = 0; + + int x,y,width,height; // dialog dimensions + char buffer[80*3]; + + int factor = SeenBuff.Get_Width()/320; + + + +//Timer_Test(__LINE__, __FILE__); + + + /* + ** Determine the dimensions of the text to be used for the dialog box. + ** These dimensions will control how the dialog box looks. + */ + strcpy( buffer, Text_String( TXT_INITIALIZING_MODEM ) ); + + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + Format_Window_String(buffer, SeenBuff.Get_Height(), width, height); + + width = MAX(width, 50*factor); + width += 40*factor; + height += 60*factor; + + x = (SeenBuff.Get_Width() - width) / 2; + y = (SeenBuff.Get_Height() - height) / 2; + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + + /* + ............................ Draw the dialog ............................. + */ + Hide_Mouse(); + if ( !reconnect ) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + } + + Dialog_Box(x, y, width, height); + Draw_Caption(TXT_NONE, x, y, width); + + Fancy_Text_Print(buffer, x + 20*factor, y + 25*factor, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Show_Mouse(); + + HMWaitForOK( 0, NULL ); + + + + + /* + ** OK, lets not mess about any more. Just turn on echo, verbose, and result codes + ** before we even begin. At least this way when we get an error later on we have already + ** removed all the steps we use to try and recover. + ** The timeouts need to be quite small in case the modem is turned off. + */ + + /* + ** Turn on result codes. + */ + Send_Modem_Command ( "ATQ0", '\r', buffer, 81, DEFAULT_TIMEOUT / 2, 2); + + /* + ** Make result codes verbose. + */ + Send_Modem_Command ( "ATV1", '\r', buffer, 81, DEFAULT_TIMEOUT / 2, 2); + + /* + ** Turn on echo. + */ + Send_Modem_Command ( "ATE1", '\r', buffer, 81, DEFAULT_TIMEOUT / 2, 2); + + ModemVerboseOn = true; + ModemEchoOn = true; + + + + /* + ** Try sending a plain old AT command to the modem. Now that we have theoretically + ** turned on verbose result codes we should get an 'OK' back. + ** + */ + status = Send_Modem_Command( "AT", '\r', buffer, 81, DEFAULT_TIMEOUT, 2 ); + + if (status < ASSUCCESS) { + /* + ** No 'OK' came back so return failure. We cant print an error message here because + ** the calling function may want to upshift the baud rate (eg from 14400 to 19200) and + ** try again. + */ + return (false); + } + + /* + ** Send the user supplied modem init string + */ + if (settings->InitStringIndex == -1) { + status = Send_Modem_Command( "", '\r', buffer, 81, 300, 1 ); + } else { + /* + ** Split up the init string into seperate strings if it contains one or more '|' characters. + ** This character acts as a carriage return/pause. + */ + char *istr = new char [2 + strlen( InitStrings [settings->InitStringIndex] )]; + char *tokenptr; + strcpy (istr, InitStrings [settings->InitStringIndex] ); + + /* + ** Tokenise the string and send it in chunks + */ + tokenptr = strtok ( istr, "|" ); + while ( tokenptr ) { + + status = Send_Modem_Command( tokenptr, '\r', buffer, 81, DEFAULT_TIMEOUT, 1 ); + /* + ** Handle error case. + */ + if (status < ASSUCCESS) { + if (CCMessageBox().Process(TXT_ERROR_NO_INIT, TXT_IGNORE, TXT_CANCEL)) { + delete istr; + return( false ); + } + error_count++; + break; + } + + tokenptr = strtok ( NULL, "|"); + + } + } + /* + ** Use the settings from the registry to further initialise the modem + */ + if (settings->Port == 1 && ModemRegistry) { + /* + ** Send the init strings from the registry if available + */ + char send_string[256] = {"AT"}; + + if (settings->HardwareFlowControl){ + /* + ** Send the init string for hardware flow control + */ + if (ModemRegistry->Get_Modem_Hardware_Flow_Control()) { + strcpy (&send_string[2], ModemRegistry->Get_Modem_Hardware_Flow_Control()); + status = Send_Modem_Command (send_string, '\r', buffer, 81, DEFAULT_TIMEOUT, 1); + if (status != MODEM_CMD_OK && status != MODEM_CMD_0) { + if (CCMessageBox().Process(TXT_NO_FLOW_CONTROL_RESPONSE, TXT_IGNORE, TXT_CANCEL)) return (false); + } + } + }else{ + /* + ** Send the init string for no flow control + */ + if (ModemRegistry->Get_Modem_No_Flow_Control()) { + strcpy (&send_string[2], ModemRegistry->Get_Modem_No_Flow_Control()); + status = Send_Modem_Command (send_string, '\r', buffer, 81, DEFAULT_TIMEOUT, 1); + if (status != MODEM_CMD_OK && status != MODEM_CMD_0) { + if (CCMessageBox().Process(TXT_NO_FLOW_CONTROL_RESPONSE, TXT_IGNORE, TXT_CANCEL)) return (false); + } + } + } + + + /* + ** Send the string for data compresseion + */ + if (settings->Compression){ + + if (ModemRegistry->Get_Modem_Compression_Enable()) { + strcpy (&send_string[2], ModemRegistry->Get_Modem_Compression_Enable()); + Send_Modem_Command (send_string, '\r', buffer, 81, DEFAULT_TIMEOUT, 1); + if (status != MODEM_CMD_OK && status != MODEM_CMD_0) { + if (CCMessageBox().Process(TXT_NO_COMPRESSION_RESPONSE, TXT_IGNORE, TXT_CANCEL)) return (false); + } + } + }else{ + + if (ModemRegistry->Get_Modem_Compression_Disable()) { + strcpy (&send_string[2], ModemRegistry->Get_Modem_Compression_Disable()); + Send_Modem_Command (send_string, '\r', buffer, 81, DEFAULT_TIMEOUT, 1); + if (status != MODEM_CMD_OK && status != MODEM_CMD_0) { + if (CCMessageBox().Process(TXT_NO_COMPRESSION_RESPONSE, TXT_IGNORE, TXT_CANCEL)) return (false); + } + } + } + + + /* + ** Send the string for error correction + */ + if (settings->ErrorCorrection){ + + if (ModemRegistry->Get_Modem_Error_Correction_Enable()) { + strcpy (&send_string[2], ModemRegistry->Get_Modem_Error_Correction_Enable()); + Send_Modem_Command (send_string, '\r', buffer, 81, DEFAULT_TIMEOUT, 1); + if (status != MODEM_CMD_OK && status != MODEM_CMD_0) { + if (CCMessageBox().Process(TXT_NO_ERROR_CORRECTION_RESPONSE, TXT_IGNORE, TXT_CANCEL)) return (false); + } + } + }else{ + if (ModemRegistry->Get_Modem_Error_Correction_Disable()) { + strcpy (&send_string[2], ModemRegistry->Get_Modem_Error_Correction_Disable()); + Send_Modem_Command (send_string, '\r', buffer, 81, DEFAULT_TIMEOUT, 1); + if (status != MODEM_CMD_OK && status != MODEM_CMD_0) { + if (CCMessageBox().Process(TXT_NO_ERROR_CORRECTION_RESPONSE, TXT_IGNORE, TXT_CANCEL)) return (false); + } + } + } + + + } + /* + ** We require that auto-answer be disabled so turn it off now. + */ + status = Send_Modem_Command( "ATS0=0", '\r', buffer, 81, DEFAULT_TIMEOUT, INIT_COMMAND_RETRIES ); + if (status != MODEM_CMD_OK) { + if (CCMessageBox().Process(TXT_ERROR_NO_DISABLE, TXT_IGNORE, TXT_CANCEL)) return( false ); + error_count++; + } + + /* + ** If we had an unreasonable number of ignored errors then return failure + */ + if (error_count >= 3) { + CCMessageBox().Process(TXT_ERROR_TOO_MANY, TXT_OK); + return (false); + } + + return( true ); +} + + +/*************************************************************************** + * NullModemClass::Dial_Modem -- dials a number passed * + * * + * INPUT: * + * settings ptr to SerialSettings structure * + * * + * OUTPUT: * + * status DialStatus * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/02/1995 DRD : Created. * + *=========================================================================*/ +DialStatusType NullModemClass::Dial_Modem( char *string, DialMethodType method, bool reconnect ) +{ + +//Timer_Test(__LINE__, __FILE__); + + + int factor = SeenBuff.Get_Width()/320; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + }; + + /*........................................................................ + Dialog variables + ........................................................................*/ + bool process = true; // process while true + + //int status; + int delay; + DialStatusType dialstatus; + + int x,y,width,height; // dialog dimensions + char buffer[80*3]; + + Input = 0; + + +//Timer_Test(__LINE__, __FILE__); + + + /* + ** Determine the dimensions of the text to be used for the dialog box. + ** These dimensions will control how the dialog box looks. + */ + if (reconnect) { + strcpy( buffer, Text_String( TXT_MODEM_CONNERR_REDIALING ) ); + } else { + strcpy( buffer, Text_String( TXT_DIALING ) ); + } + + +//Timer_Test(__LINE__, __FILE__); + + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + Format_Window_String(buffer, SeenBuff.Get_Height(), width, height); + + int text_width = width; + width = MAX(width, 50*factor); + width += 40*factor; + height += 60*factor; + + x = (SeenBuff.Get_Width() - width) / 2; + y = (SeenBuff.Get_Height() - height) / 2; + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + x + ((width - (String_Pixel_Width( Text_String( TXT_CANCEL ) ) + 8*factor)) >> 1), + y + height - (FontHeight + FontYSpacing + 2*factor) - 5*factor); + + +//Timer_Test(__LINE__, __FILE__); + + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + + /* + ............................ Create the list ............................. + */ + Commands = &cancelbtn; + + +//Timer_Test(__LINE__, __FILE__); + + Commands->Flag_List_To_Redraw(); + + +//Timer_Test(__LINE__, __FILE__); + + + /* + ............................ Draw the dialog ............................. + */ + Hide_Mouse(); + if ( !reconnect ) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + } + + +//Timer_Test(__LINE__, __FILE__); + + + + Dialog_Box(x, y, width, height); + Draw_Caption(TXT_NONE, x, y, width); + + +//Timer_Test(__LINE__, __FILE__); + + + Fancy_Text_Print(buffer, SeenBuff.Get_Width()/2 - text_width/2, y + 25*factor, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + +//Timer_Test(__LINE__, __FILE__); + + + CCDebugString ("C&C95 - About to draw buttons.\n"); + Commands->Draw_All(); + CCDebugString ("C&C95 - About to show mouse.\n"); + Show_Mouse(); + + +//Timer_Test(__LINE__, __FILE__); + + // start waiting + + //CCDebugString ("C&C95 - About to delay for 2 seconds.\n"); + //Delay(120); + //HMSetDialingMethod( Port, (int)method ); + CCDebugString ("C&C95 - About to set modem dial type.\n"); + SerialPort->Set_Modem_Dial_Type((WinCommDialMethodType) method); + + +//Timer_Test(__LINE__, __FILE__); + + //Clear out any old modem result codes + CCDebugString ("C&C95 - About to call 'Get_Modem_Result'.\n"); + SerialPort->Get_Modem_Result(60, buffer, 81); + //status = HMDial( Port, string ); + CCDebugString ("C&C95 - About to call 'SerialPort->Dial_Modem'.\n"); + SerialPort->Dial_Modem(string); + + +//Timer_Test(__LINE__, __FILE__); + + + // + // Sets up the ability to abort modem commands when any input is in the + // Keyboard buffer. This also calls the game CallBack(). + // + CCDebugString ("C&C95 - About to call 'Setup_Abort_Modem'.\n"); + Setup_Abort_Modem(); + + +//Timer_Test(__LINE__, __FILE__); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + process = true; + delay = ModemWaitCarrier; + CCDebugString ("C&C95 - About to enter main process loop.\n"); + while (process) { + + +//Timer_Test(__LINE__, __FILE__); + + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + CCDebugString ("C&C95 - About to restore video surfaces.\n"); + AllSurfaces.SurfacesRestored=FALSE; + Commands->Draw_All(); + } + +//Timer_Test(__LINE__, __FILE__); + + + //delay = HMInputLine( Port, delay, buffer, 81 ); + CCDebugString ("C&C95 - About to call 'Get_Modem_Result 2'.\n"); + + +//Timer_Test(__LINE__, __FILE__); + + + char abuffer [128]; + sprintf (abuffer, "C&C95 - ModemWaitCarrier delay = %d\n", delay); + CCDebugString (abuffer); + delay = SerialPort->Get_Modem_Result(delay, buffer, 81); + + + /* + ............................ Process input ............................ + */ + CCDebugString ("C&C95 - About to check for keyboard input.\n"); + if (!Input) Input = Commands->Input(); + + + switch (Input) { + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + dialstatus = DIAL_CANCELED; + process = false; + break; + + default: + break; + } + + if (process) { + if ( strncmp( buffer, "CON", 3 ) == 0 ) { + memset (ModemRXString, 0, 80); + strncpy (ModemRXString, buffer, 79); + dialstatus = DIAL_CONNECTED; + process = false; + } + else if ( strncmp( buffer, "BUSY", 4 ) == 0 ) { + dialstatus = DIAL_BUSY; + process = false; + } + else if ( strncmp( buffer, "NO C", 4 ) == 0 ) { + dialstatus = DIAL_NO_CARRIER; + process = false; + } + else if ( strncmp( buffer, "NO D", 4 ) == 0 ) { + dialstatus = DIAL_NO_DIAL_TONE; + process = false; + } + else if ( strncmp( buffer, "ERRO", 4 ) == 0 ) { + dialstatus = DIAL_ERROR; + process = false; + } + } + + if (delay <= 0) { + if (delay < 0) { + } + process = false; + } + } + + Remove_Abort_Modem(); + + return( dialstatus ); + +} /* end of Dial_Modem */ + + +/*************************************************************************** + * NullModemClass::Answer_Modem -- waits for call and answers * + * * + * INPUT: * + * reconnect whether this is to reconnect * + * * + * OUTPUT: * + * status DialStatus * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/02/1995 DRD : Created. * + *=========================================================================*/ +DialStatusType NullModemClass::Answer_Modem( bool reconnect ) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + }; + + /*........................................................................ + Redraw values: in order from "top" to "bottom" layer of the dialog + ........................................................................*/ + typedef enum { + REDRAW_NONE = 0, + REDRAW_BUTTONS, + REDRAW_BACKGROUND, + REDRAW_ALL = REDRAW_BACKGROUND + } RedrawType; + + /*........................................................................ + Dialog variables + ........................................................................*/ + RedrawType display = REDRAW_ALL; // redraw level + bool process = true; // process while true + + //int status; + int delay; + DialStatusType dialstatus; + bool ring = false; + + int x,y,width,height; // dialog dimensions + char text_buffer[80*3]; + char comm_buffer[80*3]; + + int text_width; + + /* + ** Determine the dimensions of the text to be used for the dialog box. + ** These dimensions will control how the dialog box looks. + */ + if (reconnect) { + strcpy( text_buffer, Text_String( TXT_MODEM_CONNERR_WAITING ) ); + } else { + strcpy( text_buffer, Text_String( TXT_WAITING_FOR_CALL ) ); + } + + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + Format_Window_String(text_buffer, SeenBuff.Get_Height(), width, height); + + text_width = width; + width = MAX(width, 50*factor); + width += 40*factor; + height += 60*factor; + + x = (SeenBuff.Get_Width() - width) / 2; + y = (SeenBuff.Get_Height() - height) / 2; + + TextButtonClass cancelbtn(BUTTON_CANCEL, TXT_CANCEL, + TPF_CENTER | TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW, + x + ((width - (String_Pixel_Width( Text_String( TXT_CANCEL ) ) + 8*factor)) >> 1), + y + height - (FontHeight + FontYSpacing + 2*factor) - 5*factor); + + /* + ------------------------------- Initialize ------------------------------- + */ + Set_Logic_Page(SeenBuff); + + //Load_Picture("TITLE.CPS", HidPage, HidPage, Palette, BM_DEFAULT); + + Input = 0; + + /* + ............................ Create the list ............................. + */ + Commands = &cancelbtn; + + Commands->Flag_List_To_Redraw(); + + // start waiting + + +// HMWaitForOK( 1000, NULL ); +// status = HMSendString( Port, "ATS0=1" ); + + + // + // Sets up the ability to abort modem commands when any input is in the + // Keyboard buffer. This also calls the game CallBack() and Input(). + // + Setup_Abort_Modem(); + + /* + -------------------------- Main Processing Loop -------------------------- + */ + process = true; + delay = 60000; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=REDRAW_ALL; + } + + /* + ...................... Refresh display if needed ...................... + */ + if (display) { + Hide_Mouse(); + if (display >= REDRAW_BACKGROUND) { + /* + ..................... Refresh the backdrop ...................... + */ + if ( !reconnect ) { + Load_Title_Screen("HTITLE.PCX", &HidPage, Palette); + HidPage.Blit(SeenBuff); + } + /* + ..................... Draw the background ....................... + */ + Dialog_Box(x, y, width, height); + /* + ....................... Draw the labels ......................... + */ + Draw_Caption(TXT_NONE, x, y, width); + + Fancy_Text_Print(text_buffer, SeenBuff.Get_Width()/2 - text_width/2, y + 25*factor, CC_GREEN, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + Commands->Draw_All(); + } + Show_Mouse(); + display = REDRAW_NONE; + } + + //delay = HMInputLine( Port, delay, buffer, 81 ); + delay = SerialPort->Get_Modem_Result(delay, comm_buffer, 81); + + /* + ............................ Process input ............................ + */ + if (!Input) Input = Commands->Input(); + switch (Input) { + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): +// Sound_Effect(VOC_BUTTON,255); + dialstatus = DIAL_CANCELED; + process = false; + break; + + default: + break; + } + + if (process) { + if ( strncmp( comm_buffer, "RING", 4 ) == 0 ) { + + strcpy( text_buffer, Text_String( TXT_ANSWERING ) ); + + Fancy_Text_Print(TXT_NONE,0,0,TBLACK,TBLACK,TPF_6PT_GRAD | TPF_NOSHADOW); + Format_Window_String(text_buffer, SeenBuff.Get_Height(), width, height); + + text_width = width; + width = MAX(width, 50*factor); + width += 40*factor; + height += 60*factor; + + x = (SeenBuff.Get_Width() - width) / 2; + y = (SeenBuff.Get_Height() - height) / 2; + + //PortKillTime( Port, 100 ); + + //HMWaitForOK( 0, NULL ); + //status = HMAnswer( Port ); + SerialPort->Write_To_Serial_Port ((unsigned char*)"ATA\r", strlen("ATA\r")); + + ring = true; + delay = ModemWaitCarrier; + display = REDRAW_ALL; + } + else if ( strncmp( comm_buffer, "CON", 3 ) == 0 ) { + memset (ModemRXString, 0, 80); + strncpy (ModemRXString, comm_buffer, 79); + dialstatus = DIAL_CONNECTED; + process = false; + } + else if ( strncmp( comm_buffer, "BUSY", 4 ) == 0 ) { + dialstatus = DIAL_BUSY; + process = false; + } + else if ( strncmp( comm_buffer, "NO C", 4 ) == 0 ) { + dialstatus = DIAL_NO_CARRIER; + process = false; + } + else if ( strncmp( comm_buffer, "NO D", 4 ) == 0 ) { + dialstatus = DIAL_NO_DIAL_TONE; + process = false; + } + else if ( strncmp( comm_buffer, "ERRO", 4 ) == 0 ) { + dialstatus = DIAL_ERROR; + CCMessageBox().Process ("Error - Modem returned error status.", TXT_OK); + process = false; + } + } + + if (delay <= 0) { + if (ring) { + if (SerialPort->Get_Modem_Status() & CD_SET){ + sprintf(ModemRXString, "%s", "Connected"); + dialstatus = DIAL_CONNECTED; + }else{ + dialstatus = DIAL_ERROR; + CCMessageBox().Process ("Error - TIme out waiting for connect.", TXT_OK); + } + process = false; + } else { + delay = 60000; + } + } + } + + Remove_Abort_Modem(); + + return( dialstatus ); + +} /* end of Answer_Modem */ + + +/*************************************************************************** + * NullModemClass::Hangup_Modem -- hangs up the modem * + * * + * INPUT: * + * none * + * * + * OUTPUT: * + * status successful or not * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/02/1995 DRD : Created. * + *=========================================================================*/ +bool NullModemClass::Hangup_Modem( void ) +{ + int status; + int delay; + char buffer[81]; + char escape[4]; + + /* + ** Turn modem servicing off in the callback routine. + */ + ModemService = false; + + + status = Send_Modem_Command( "AT", '\r', buffer, 81, DEFAULT_TIMEOUT, 1 ); + + if (status == MODEM_CMD_OK) { + ModemService = true; + return( true ); + } + + SerialPort->Set_Serial_DTR(FALSE); + Delay(3200/60); + SerialPort->Set_Serial_DTR(TRUE); + + //SetDtr( Port, 0 ); + //PortKillTime( Port, 3200 ); + //SetDtr( Port, 1 ); + + status = Send_Modem_Command( "AT", '\r', buffer, 81, DEFAULT_TIMEOUT, 1 ); + + if (status == MODEM_CMD_OK) { + ModemService = true; + return( true ); + } + + delay = ModemGuardTime; + while ( delay > 0 ) { + //delay = HMInputLine( Port, delay, buffer, 81 ); + delay = SerialPort->Get_Modem_Result(delay, buffer, 81); + + } + + escape[0] = ModemEscapeCode; + escape[1] = ModemEscapeCode; + escape[2] = ModemEscapeCode; + escape[3] = 0; + + //status = HMSendStringNoWait( Port, escape, -1 ); + SerialPort->Write_To_Serial_Port((unsigned char*)escape, 3); + + delay = ModemGuardTime; + while ( delay > 0 ) { + delay = SerialPort->Get_Modem_Result(delay, buffer, 81); + //delay = HMInputLine( Port, delay, buffer, 81 ); + + if ( strncmp( buffer, "OK", 2 ) == 0 ) { + break; + } + } + + status = Send_Modem_Command( "ATH", '\r', buffer, 81, ModemHangupDelay, 1 ); + + if (status == MODEM_CMD_OK) { + } else { + ModemService = true; + return( false ); + } + + status = Send_Modem_Command( "ATZ", '\r', buffer, 81, DEFAULT_TIMEOUT, 1 ); + + if (status == MODEM_CMD_OK) { + } else { + ModemService = true; + return( false ); + } + + ModemService = true; + return( true ); + +} /* end of Hangup_Modem */ + + +/*********************************************************************************************** + * NMC::Setup_Modem_Echo -- Sets the echo callback function pointer * + * * + * * + * * + * INPUT: Ptr to callback function * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/2/96 12:48PM ST : Documented and added WIn32 support * + *=============================================================================================*/ +void NullModemClass::Setup_Modem_Echo( void ( *func )( char c) ) +{ + SerialPort->Set_Echo_Function(func); + //HMSetUpEchoRoutine( func ); + +} /* end of Setup_Modem_Echo */ + + +/*********************************************************************************************** + * NMC::Remove_Modem_Echo -- Set the echo function callback pointer to null * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/2/96 12:50PM ST : Documented / Win32 support added * + *=============================================================================================*/ +void NullModemClass::Remove_Modem_Echo( void ) +{ + SerialPort->Set_Echo_Function(NULL); + //HMSetUpEchoRoutine( NULL ); + +} /* end of Remove_Modem_Echo */ + + +/*********************************************************************************************** + * NMC::Print_EchoBuf -- Print out the contents of the echo buffer * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/2/96 12:51PM ST : Documented * + *=============================================================================================*/ +void NullModemClass::Print_EchoBuf( void ) +{ + for (int i = 0; i < strlen(NullModem.EchoBuf); i++) { + if (NullModem.EchoBuf[i] == '\r') { + NullModem.EchoBuf[i] = 1; + } else { + if (NullModem.EchoBuf[i] == '\n') { + NullModem.EchoBuf[i] = 2; + } + } + } +} + + +/*********************************************************************************************** + * NMC::Reset_EchoBuf -- Empties the echo buffer * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/2/96 12:51PM ST : Documented * + *=============================================================================================*/ +void NullModemClass::Reset_EchoBuf( void ) +{ + *EchoBuf = 0; + EchoCount = 0; +} + + +/*********************************************************************************************** + * NMC::Abort_Modem -- Checks for user input so that modem operations can be aborted * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: ASUSERABORT if abort key pressed. ASSUCESS otherwise. * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/2/96 12:52PM ST : Documented * + *=============================================================================================*/ +int NullModemClass::Abort_Modem(void) +//int NullModemClass::Abort_Modem( PORT * ) +{ + /*........................................................................ + Button Enumerations + ........................................................................*/ + enum { + BUTTON_CANCEL = 100, + }; + + /* + ........................ Invoke game callback ......................... + */ + Call_Back(); + + /* + ........................... Get user input ............................ + */ + Input = Commands->Input(); + + switch ( Input ) { + case (KN_ESC): + case (BUTTON_CANCEL | KN_BUTTON): + return( ASUSERABORT ); + } + + return( ASSUCCESS ); + +} /* end of Abort_Modem */ + + +/*********************************************************************************************** + * NMC::Setup_Abort_Modem -- sets the modem abort function pointer * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/2/96 2:59PM ST : Documented / Win32 support added * + *=============================================================================================*/ +void NullModemClass::Setup_Abort_Modem( void ) +{ + SerialPort->Set_Abort_Function((int(*)(void))Abort_Modem); +} /* end of Setup_Abort_Modem */ + + +/*********************************************************************************************** + * NMC::Remove_Abort_Modem -- Removes the modem abort function pointer * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/2/96 3:01PM ST : Documented / Win32 support added * + *=============================================================================================*/ +void NullModemClass::Remove_Abort_Modem( void ) +{ + SerialPort->Set_Abort_Function(NULL); +} /* end of Remove_Abort_Modem */ + + + +/*********************************************************************************************** + * NMC::Change_IRQ_Priority -- Increases the priority of the serial interrupt * + * * + * * + * * + * INPUT: Interrupt request number * + * * + * OUTPUT: ASSUCCESS if changed * + * * + * WARNINGS: The Win32 version of this function does nothing. * + * Priorities are controlled by windoze * + * * + * HISTORY: * + * 8/2/96 3:03PM ST : Documented / Win32 support added * + *=============================================================================================*/ +int NullModemClass::Change_IRQ_Priority( int ) +{ + return (ASSUCCESS); +} /* end of Change_IRQ_Priority */ + + +/*********************************************************************************************** + * NMC::Get_Modem_Status -- returns status of modem control bits * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Modem status * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/2/96 3:06PM ST : Documented / Win32 support added * + *=============================================================================================*/ +int NullModemClass::Get_Modem_Status( void ) +{ + int modemstatus; + int status; + char buffer[81]; + + //modemstatus = GetModemStatus( Port ); + modemstatus = SerialPort->Get_Modem_Status(); + + status = Send_Modem_Command( "AT", '\r', buffer, 81, DEFAULT_TIMEOUT, 1 ); + + if (status == MODEM_CMD_OK) { + modemstatus &= (~CD_SET); + } + + return( modemstatus ); + +} /* end of Get_Modem_Status */ + + +/*********************************************************************************************** + * NMC::Send_Modem_Command -- Sends an 'AT' command to the modem and gets the response * + * * + * * + * * + * INPUT: command to send to modem. e.g. 'ATZ' * + * terminator byte for command string * + * buffer to put modem response into * + * length of above buffer * + * delay to wait for response * + * number of times to retry when modem doesnt respond * + * * + * OUTPUT: input delay less the time it took the modem to respond * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/2/96 3:09PM ST : Documented / Win32 support added * + *=============================================================================================*/ +int NullModemClass::Send_Modem_Command( char *command, char terminator, char *buffer, int buflen, int delay, int retries ) +{ + return (SerialPort->Send_Command_To_Modem(command, terminator, buffer, buflen, delay, retries)); +} + + +/*********************************************************************************************** + * NMC::Verify_And_Convert_To_Int -- converts a text string of numbers to an int * + * * + * * + * * + * INPUT: ptr to buffer * + * * + * OUTPUT: value of text number in buffer * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 8/2/96 3:13PM ST : Documented * + *=============================================================================================*/ +int NullModemClass::Verify_And_Convert_To_Int( char *buffer ) +{ + int value = 0; + int len = strlen(buffer); + + + for (int i = 0; i < len; i++) { + if ( !isdigit( *(buffer + i) ) ) { + value = -1; + break; + } + } + + if (value == 0) { + value = atoi( buffer ); + } + + return( value ); + +} /* end of Verify_And_Convert_To_Int */ + +/*************************** end of nullmgr.cpp ****************************/ diff --git a/NULLMGR.H b/NULLMGR.H new file mode 100644 index 0000000..8e3c8a6 --- /dev/null +++ b/NULLMGR.H @@ -0,0 +1,217 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\nullmgr.h_v 1.14 16 Oct 1995 16:45:26 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : CONNECT.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : April 3, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * * + * This is the Connection Manager for a NULL-Modem connection. * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef NULLMODEM_H +#define NULLMODEM_H + + +/* +********************************* Includes ********************************** +*/ +#include "nullconn.h" +#include "connmgr.h" +#include "commlib.h" + +/* +***************************** Class Declaration ***************************** +*/ +class NullModemClass : public ConnManClass +{ + /* + ---------------------------- Public Interface ---------------------------- + */ + public: + enum SendModemEnum { + MODEM_CMD_TIMEOUT = 0, + MODEM_CMD_OK, + MODEM_CMD_0, + MODEM_CMD_ERROR + }; + + char *BuildBuf; + int MaxLen; + + char *EchoBuf; + int EchoSize; + int EchoCount; + + int OldIRQPri; + + bool ModemVerboseOn; + bool ModemEchoOn; + int ModemWaitCarrier; + int ModemCarrierDetect; + int ModemCarrierLoss; + int ModemHangupDelay; + int ModemGuardTime; + char ModemEscapeCode; + + static void (*OrigAbortModemFunc)(int); + static KeyNumType Input; + static GadgetClass *Commands; // button list + + /* + ** Constructor/destructor. + */ + NullModemClass (int numsend, int numreceive, int maxlen, unsigned short magicnum); + virtual ~NullModemClass (); + + /* + ** This is the main initialization routine. + */ + int Init( int port, int irq, char *dev_name, int baud, char parity, int wordlength, int stopbits, int flowcontrol ); + int Delete_Connection( void ); + virtual int Num_Connections(void); + virtual int Connection_ID(int ) {return (0);} + virtual int Connection_Index(int ) {return (0);} + int Init_Send_Queue( void ); + void Shutdown( void ); + + virtual void Set_Timing (unsigned long retrydelta, + unsigned long maxretries, unsigned long timeout); + + /* + ** This is how the application sends & receives messages. + */ + int Send_Message (void *buf, int buflen, int ack_req = 1); + int Get_Message (void *buf, int *buflen); + + /* + ** These are for compatibility + */ + virtual int Send_Private_Message (void *buf, int buflen, + int ack_req = 1, int = CONNECTION_NONE) + {return (Send_Message(buf,buflen,ack_req));} + virtual int Get_Private_Message (void *buf, int *buflen, int *) + {return (Get_Message(buf,buflen));} + + /* + ** The main polling routine; should be called as often as possible. + */ + virtual int Service (void); + + /* + ** Queue utility routines. The application can determine how many + ** messages are in the send/receive queues, and the queue's average + ** response time (in clock ticks). + */ + int Num_Send(void); + int Num_Receive(void); + virtual unsigned long Response_Time(void); + virtual void Reset_Response_Time(void); + void * Oldest_Send(void); + virtual void Configure_Debug(int index, int offset, int size, + char **names, int maxnames); + virtual void Mono_Debug_Print(int index, int refresh = 0); + + /* + ** These are for compatibility + */ + virtual int Global_Num_Send(void) {return (Num_Send());} + virtual int Global_Num_Receive(void) {return (Num_Receive());} + virtual int Private_Num_Send(int = CONNECTION_NONE) + {return (Num_Send());} + virtual int Private_Num_Receive(int = CONNECTION_NONE) + {return (Num_Receive());} + + DetectPortType Detect_Port( SerialSettingsType *settings ); + int Detect_Modem( SerialSettingsType *settings, bool reconnect = false ); + DialStatusType Dial_Modem(char *string, DialMethodType method, bool reconnect = false); + DialStatusType Answer_Modem(bool reconnect = false); + bool Hangup_Modem(void); + void Setup_Modem_Echo(void (*func)(char c)); + void Remove_Modem_Echo(void); + void Print_EchoBuf(void); + void Reset_EchoBuf(void); + //static int Abort_Modem(PORT *); + static int Abort_Modem(void); + void Setup_Abort_Modem(void); + void Remove_Abort_Modem(void); + + int Change_IRQ_Priority(int irq); + int Get_Modem_Status(void); + int Send_Modem_Command( char *command, char terminator, char *buffer, int buflen, int delay, int retries ); + int Verify_And_Convert_To_Int( char *buffer ); + + /* + ** Private Interface. + */ + private: + + /* + ** This is a pointer to the NULL-Modem Connection object. + */ + NullModemConnClass *Connection; + int NumConnections; // # connection objects in use + + /* + ** This is the Greenleaf port handle. + */ + PORT *Port; + HANDLE PortHandle; + + int NumSend; + int NumReceive; + unsigned short MagicNum; + + /* + ** This is the staging buffer for parsing incoming packets. + ** RXSize is the allocated size of the RX buffer. + ** RXCount is the # of characters we currently have in our buffer. + */ + char *RXBuf; + int RXSize; + int RXCount; + + /*..................................................................... + Timing parameters for all connections + .....................................................................*/ + unsigned long RetryDelta; + unsigned long MaxRetries; + unsigned long Timeout; + + /* + ** Various Statistics + */ + int SendOverflows; + int ReceiveOverflows; + int CRCErrors; +}; + +#endif diff --git a/OBJECT.CPP b/OBJECT.CPP new file mode 100644 index 0000000..f26cfb9 --- /dev/null +++ b/OBJECT.CPP @@ -0,0 +1,1507 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\object.cpv 2.17 16 Oct 1995 16:49:22 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : OBJECT.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 29, 1994 * + * * + * Last Update : August 13, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * ObjectClass::Debug_Dump -- Displays status of the object class to the mono monitor. * + * ObjectClass::Detach_All -- Removes the object from all tracking systems. * + * ObjectClass::Detach_This_From_All -- Detatches this object from all others. * + * ObjectClass::Fire_Out -- Informs object that attached animation has finished. * + * ObjectClass::Get_Mission -- Fetches the current mission of this object. * + * ObjectClass::Init -- Initializes the basic object system. * + * ObjectClass::Limbo -- Brings the object into a state of limbo. * + * ObjectClass::Mark -- Handles basic marking logic. * + * ObjectClass::Mark_For_Redraw -- Marks object and system for redraw. * + * ObjectClass::Move -- Moves (by force) the object in the desired direction. * + * ObjectClass::ObjectClass -- Default constructor for objects. * + * ObjectClass::Passive_Click_With -- Right mouse button click process. * + * ObjectClass::Receive_Message -- Processes an incoming radio message. * + * ObjectClass::Render -- Displays the object onto the map. * + * ObjectClass::Repair -- Handles object repair control. * + * ObjectClass::Revealed -- Reveals this object to the house specified. * + * ObjectClass::Select -- Try to make this object the "selected" object. * + * ObjectClass::Sell_Back -- Sells the object -- if possible. * + * ObjectClass::Take_Damage -- Applies damage to the object. * + * ObjectClass::Unlimbo -- Brings the object into the game system. * + * ObjectClass::Unselect -- This will un-select the object if it was selected. * + * ObjectClass::Value -- Fetches the target value of this object. * + * ObjectClass::What_Action -- Deteremines what action to perform on specified object. * + * ObjectClass::What_Am_I -- RTTI query of this object type. * + * ObjectTypeClass::Cost_Of -- Returns the cost to buy this unit. * + * ObjectTypeClass::Dimensions -- Gets the dimensions of the object in pixels. * + * ObjectTypeClass::Get_Cameo_Data -- Fetches pointer to cameo data for this object type. * + * ObjectTypeClass::Max_Pips -- Fetches the maximum pips allowed for this object. * + * ObjectTypeClass::ObjectTypeClass -- Normal constructor for object type class objects. * + * ObjectTypeClass::Occupy_List -- Returns with simple occupation list for object. * + * ObjectTypeClass::One_Time -- Handles one time processing for object types. * + * ObjectTypeClass::Overlap_List -- Returns a pointer to a simple overlap list. * + * ObjectTypeClass::Time_To_Build -- Fetches the time to construct this object. * + * ObjectTypeClass::Who_Can_Build_Me -- Finds the factory building that can build this object* + * ObjectClass::What_Action -- Returns with the action to perform for this object. * + * ObjectClass::In_Which_Layer -- Fetches what layer this object is located in. * + * ObjectClass::Is_Techno -- Checks to see if this object is a techno type. * + * ObjectClass::Get_Ownable -- Fetches the house owner legality options for this object. * + * ObjectClass::Can_Repair -- Queries whether this object can be repaired. * + * ObjectClass::Can_Demolish -- Queries whether this object can be sold back. * + * ObjectClass::Can_Player_Fire -- Can the player give this object an attack mission? * + * ObjectClass::Can_Player_Move -- Can the player give this object a movement mission? * + * ObjectClass::Target_Coord -- Fetches the coordinate if this object is a target. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/* +** Selected objects have a special marking box around them. This is the shapes that are +** used for this purpose. +*/ +void const * ObjectTypeClass::SelectShapes = 0; + +void const * ObjectTypeClass::PipShapes = 0; + + +bool ObjectClass::Is_Infantry(void) const +{ + return(false); +} + + + +/*********************************************************************************************** + * ObjectTypeClass::ObjectTypeClass -- Normal constructor for object type class objects. * + * * + * This is the base constructor that is used when constructing the object type classes. * + * Every tangible game piece type calls this constructor for the ObjectTypeClass. This * + * class holds static information that is common to objects in general. * + * * + * INPUT: see below... * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/23/1995 JLB : Created. * + *=============================================================================================*/ +ObjectTypeClass::ObjectTypeClass( + bool is_sentient, + bool is_flammable, + bool is_crushable, + bool is_stealthy, + bool is_selectable, + bool is_legal_target, + bool is_insignificant, + bool is_immune, + int name, + char const *ini, + ArmorType armor, + unsigned short strength) : + AbstractTypeClass(name, ini) +{ + IsSentient = is_sentient; + IsFlammable = is_flammable; + IsCrushable = is_crushable; + IsStealthy = is_stealthy; + IsSelectable = is_selectable; + IsLegalTarget = is_legal_target; + IsInsignificant = is_insignificant; + IsImmune = is_immune; + Armor = armor; + MaxStrength = strength; + ImageData = NULL; + //RadarIcon = NULL; +} + + +/*********************************************************************************************** + * ObjectTypeClass::Max_Pips -- Fetches the maximum pips allowed for this object. * + * * + * This routine will return the maximum number of pips that can be displayed for this * + * object. When dealing with generic objects, this value is always zero. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of pip boxes (empty or otherwise) to display. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1995 JLB : Created. * + *=============================================================================================*/ +int ObjectTypeClass::Max_Pips(void) const +{ + return(0); +} + + +/*********************************************************************************************** + * ObjectTypeClass::Dimensions -- Gets the dimensions of the object in pixels. * + * * + * This routine will fetch the dimensions of this object expressed as pixels width and * + * pixels height. This information can be used to intelligently update the clipping * + * rectangles. * + * * + * INPUT: width -- Reference to the width variable that will be filled in. * + * * + * height -- Reference to the height variable that will be filled in. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1995 JLB : Created. * + *=============================================================================================*/ +void ObjectTypeClass::Dimensions(int &width, int &height) const +{ + width = 10; + height = 10; +} + + +/*********************************************************************************************** + * ObjectTypeClass::Cost_Of -- Returns the cost to buy this unit. * + * * + * This routine will return the cost to purchase this unit. This routine is expected to be * + * overridden by the objects that can actually be purchased. All other object types can * + * simply return zero since this value won't be used. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the cost of the object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1995 JLB : Created. * + *=============================================================================================*/ +int ObjectTypeClass::Cost_Of(void) const +{ + return(0); +} + + +/*********************************************************************************************** + * ObjectTypeClass::Time_To_Build -- Fetches the time to construct this object. * + * * + * This routine will fetch the time in takes to construct this object. Objects that can * + * be constructed will override this routine in order to return a useful value. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the time units (arbitrary) that it takes to construct this object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1995 JLB : Created. * + *=============================================================================================*/ +int ObjectTypeClass::Time_To_Build(HousesType ) const +{ + return(0); +} + + +/*********************************************************************************************** + * ObjectTypeClass::Who_Can_Build_Me -- Finds the factory building that can build this object. * + * * + * This routine will search for a factory building that can build this object type. * + * * + * INPUT: this routine is just here to be overridden by other classes. * + * * + * OUTPUT: Returns with a pointer to the building that can construct the object specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1995 JLB : Created. * + *=============================================================================================*/ +BuildingClass * ObjectTypeClass::Who_Can_Build_Me(bool, bool, HousesType) const +{ + return(NULL); +} + + +/*********************************************************************************************** + * ObjectTypeClass::Get_Cameo_Data -- Fetches pointer to cameo data for this object type. * + * * + * This routine will return with the cameo data pointer for this object type. It is * + * expected that objects that can appear on the sidebar will override this routine in order * + * to provide proper cameo data pointer. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the cameo shape data. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1995 JLB : Created. * + *=============================================================================================*/ +void const * ObjectTypeClass::Get_Cameo_Data(void) const +{ + return(NULL); +} + + + +/*********************************************************************************************** + * ObjectClass::ObjectClass -- Default constructor for objects. * + * * + * This is the default constructor for objects. It is called as an inherent part of the * + * construction process for all the normal game objects instantiated. It serves merely to * + * initialize the object values to a common (default) state. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Objects always start in a state of limbo. They must be Unlimbo()ed before they * + * can be used. * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +ObjectClass::ObjectClass(void) +{ + Coord = 0xFFFFFFFFL; // Some bogus illegal value. + Next = 0; // Not part of any object list. + Trigger = 0; // No associated trigger. + IsToDamage = false; + IsToDisplay = false; // Redraw is presumed unnecessary. + IsInLimbo = true; // Always presumed to start in limbo state. + IsSelected = false; // Limboed units cannot be selected. + IsDown = false; // Limboed units cannot be on the map. + IsAnimAttached = false; // Anim is not attached. + Strength = 255; // nominal strength value +} + + +/*********************************************************************************************** + * ObjectClass::What_Am_I -- RTTI query of this object type. * + * * + * This routine will never be called, but is here for completeness. Every object that * + * is derived from object class must overload this function and return their own proper * + * object RTTI value. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the RTTI value that coresponds to the object's type. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1995 JLB : Created. * + *=============================================================================================*/ +RTTIType ObjectClass::What_Am_I(void) const +{ + return(RTTI_OBJECT); +} + + +/*********************************************************************************************** + * ObjectClass::What_Action -- Deteremines what action to perform on specified object. * + * * + * This routine will return that action that this object could perform if the mouse were * + * clicked over the object specified. * + * * + * INPUT: object -- Pointer to the object to check this object against when determining * + * the action to perform. * + * * + * OUTPUT: It returns that action that will be performed if the mouse were clicked over the * + * object. Since non-derived objects cannot do anything, and cannot even be * + * instantiated, this routine will always return ACTION_NONE. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1995 JLB : Created. * + *=============================================================================================*/ +ActionType ObjectClass::What_Action(ObjectClass *) const +{ + return(ACTION_NONE); +} + + +/*********************************************************************************************** + * ObjectClass::What_Action -- Returns with the action to perform for this object. * + * * + * This routine is called when information on a potential action if the mouse were clicked * + * on the cell specified. This routine merely serves as a virtual placeholder so that * + * object types that can actually perform some action will override this routine to provide * + * true functionality. * + * * + * INPUT: cell -- The cell that the mouse is over and might be clicked on. * + * * + * OUTPUT: Returns with the action that this object would try to perform if the mouse were * + * clicked. Since objects at this level have no ability to do anything, this routine * + * will always returns ACTION_NONE unless it is overridden. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +ActionType ObjectClass::What_Action(CELL) const +{ + return(ACTION_NONE); +} + + +/*********************************************************************************************** + * ObjectClass::In_Which_Layer -- Fetches what layer this object is located in. * + * * + * The default layer for object location is the LAYER_GROUND. Aircraft will override this * + * routine and make adjustments as necessary according to the aircraft's altitude. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the layer that this object is located in. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +LayerType ObjectClass::In_Which_Layer(void) const +{ + return(LAYER_GROUND); +} + + +/*********************************************************************************************** + * ObjectClass::Is_Techno -- Checks to see if this object is a techno type. * + * * + * Most active objects in the game are of the techno type. This routine will return true * + * if called on an object that is derived from TechnoClass. The RTTI interface is * + * insufficient for this purpose -- hence the existence of this routine. * + * * + * INPUT: none * + * * + * OUTPUT: Is this object derived from the TechnoClass object? This is true for units, * + * infantry, aircraft, and buildings. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +bool ObjectClass::Is_Techno(void) const +{ + return(false); +} + + +/*********************************************************************************************** + * ObjectClass::Get_Ownable -- Fetches the house owner legality options for this object. * + * * + * This routine will return the ownable bits for this object. Objects at this level can't * + * really be owned by anyone, but return the full spectrum of legality just to be safe. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the ownable flags (as a combined bitfield) for this object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +unsigned char ObjectClass::Get_Ownable(void) const +{ + return(0xff); +} + + +/*********************************************************************************************** + * ObjectClass::Can_Repair -- Queries whether this object can be repaired. * + * * + * Most objects cannot be repaired. This routine defaults to returning "false", but is * + * overridden by derived functions defined by object types that can support repair. * + * * + * INPUT: none * + * * + * OUTPUT: Can this object be repaired? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +bool ObjectClass::Can_Repair(void) const +{ + return(false); +} + + +/*********************************************************************************************** + * ObjectClass::Can_Demolish -- Queries whether this object can be sold back. * + * * + * This routine is used to determine if this object can be sold. Most objects cannot be * + * but for those objects that can, this routine will be overridden as necessary. * + * * + * INPUT: none * + * * + * OUTPUT: Can this object be sold back? Typically, the answer is no. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +bool ObjectClass::Can_Demolish(void) const +{ + return(false); +} + + +/*********************************************************************************************** + * ObjectClass::Can_Player_Fire -- Can the player give this object an attack mission? * + * * + * This routine is used to determine if attacking is an option under player control with * + * respect to this unit. This routine will be overridden as necessary for those objects * + * that have the ability to attack. * + * * + * INPUT: none * + * * + * OUTPUT: Can this object be given an attack order by the player? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +bool ObjectClass::Can_Player_Fire(void) const +{ + return(false); +} + + +/*********************************************************************************************** + * ObjectClass::Can_Player_Move -- Can the player give this object a movement mission? * + * * + * This routine is used to determine if the player has the ability to command this object * + * with a movement mission. This routine will be overridden as necessary to support this * + * ability. * + * * + * INPUT: none * + * * + * OUTPUT: Can this object be given a movement mission by the player? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +bool ObjectClass::Can_Player_Move(void) const +{ + return(false); +} + + +/*********************************************************************************************** + * ObjectClass::Target_Coord -- Fetches the coordinate if this object is a target. * + * * + * When the coordinate to use when firing at this object is needed, this routine will * + * provide it. Normal objects just use the center of the object for this, but there are * + * some more sophisticated objects that are not fired upon the center. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the coordinate to fire at if this object is a target. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +COORDINATE ObjectClass::Target_Coord(void) const +{ + return(Center_Coord()); +} + +COORDINATE ObjectClass::Center_Coord(void) const {return Coord;}; +COORDINATE ObjectClass::Render_Coord(void) const {return(Center_Coord());} +COORDINATE ObjectClass::Docking_Coord(void) const {return(Center_Coord());} +COORDINATE ObjectClass::Sort_Y(void) const {return Coord;}; +COORDINATE ObjectClass::Fire_Coord(int ) const {return Coord;}; +void ObjectClass::Record_The_Kill(TechnoClass * ) {}; +void ObjectClass::Do_Shimmer(void) {}; +int ObjectClass::Exit_Object(TechnoClass *) {return 0;}; +void ObjectClass::Hidden(void) {}; +void ObjectClass::Look(bool ) {}; +void ObjectClass::Active_Click_With(ActionType , ObjectClass *) {}; +void ObjectClass::Active_Click_With(ActionType , CELL ) {}; +void ObjectClass::Clicked_As_Target(int) {}; +bool ObjectClass::In_Range(COORDINATE , int) const {return false;}; +int ObjectClass::Weapon_Range(int) const {return 0x0000;}; +TARGET ObjectClass::As_Target(void) const {return TARGET_NONE;}; +void ObjectClass::Scatter(COORDINATE , bool) {}; +bool ObjectClass::Catch_Fire(void) {return false;}; + + +/*********************************************************************************************** + * ObjectClass::Fire_Out -- Informs object that attached animation has finished. * + * * + * This routine is called if there is an attached animation on this object and that * + * animation has finished. Typically, this is necessary for when trees are on fire. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/24/1995 JLB : Created. * + *=============================================================================================*/ +void ObjectClass::Fire_Out(void) +{ +} + + +/*********************************************************************************************** + * ObjectClass::Value -- Fetches the target value of this object. * + * * + * This routine will return the target value of this object. The higher the number, the * + * better the object will be as a target. This routine is called when searching for * + * targets. Generic objects have no target potential, and this routine returns zero to * + * reflect that. Other object types will override this routine to return the appropriate * + * target value. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the value of this object as a target. Higher values mean better * + * target. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/24/1995 JLB : Created. * + *=============================================================================================*/ +int ObjectClass::Value(void) const +{ + return(0); +} + + +/*********************************************************************************************** + * ObjectClass::Get_Mission -- Fetches the current mission of this object. * + * * + * Generic objects don't have a mission, so this routine will just return MISSION_NONE. * + * However, techno objects do have a mission and this routine is overloaded to handle * + * those objects in order to return the correct mission value. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the current mission being followed by this object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/24/1995 JLB : Created. * + *=============================================================================================*/ +MissionType ObjectClass::Get_Mission(void) const +{ + return(MISSION_NONE); +} + + +/*********************************************************************************************** + * ObjectClass::Repair -- Handles object repair control. * + * * + * This routine will control object repair mode. At the object level, no repair is * + * possible, so it is expected that any object that can repair will override this function * + * as necessary. * + * * + * INPUT: control -- The repair control parameter. * + * 0 = turn repair off * + * 1 = turn repair on * + * -1 = toggle repair state * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/24/1995 JLB : Created. * + *=============================================================================================*/ +void ObjectClass::Repair(int ) +{ +} + + +/*********************************************************************************************** + * ObjectClass::Sell_Back -- Sells the object -- if possible. * + * * + * This routine is called to sell back the object. Override this routine for the more * + * sophisticated objects that can actually be sold back. Normal objects can't be sold and * + * this routine does nothing as a consequence. * + * * + * INPUT: control -- How to control the sell state of this object. * + * 0 = stop selling. * + * 1 = start selling. * + * -1 = toggle selling state. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1995 JLB : Created. * + *=============================================================================================*/ +void ObjectClass::Sell_Back(int ) +{ +} + + +/*********************************************************************************************** + * ObjectClass::Move -- Moves (by force) the object in the desired direction. * + * * + * This routine will instantly move the object one cell in the specified direction. It * + * moves the object by force. This is typically ONLY used by the scenario editor * + * process. * + * * + * INPUT: facing -- The direction to move the object. * + * * + * OUTPUT: none * + * * + * WARNINGS: Naturally, this can cause illegal placement situations -- use with caution. * + * * + * HISTORY: * + * 06/19/1994 JLB : Created. * + *=============================================================================================*/ +void ObjectClass::Move(FacingType facing) +{ + COORDINATE coord; + + Mark(MARK_UP); + coord = Adjacent_Cell(Coord, facing); + if (Can_Enter_Cell(Coord_Cell(coord)) == MOVE_OK) { + Coord = coord; + } + Mark(MARK_DOWN); +} + + +/*********************************************************************************************** + * ObjectClass::Unselect -- This will un-select the object if it was selected. * + * * + * This routine brings a currently selected object into an unselected state. This is * + * needed when another object becomes selected as well as if the object is destroyed. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/19/1994 JLB : Created. * + *=============================================================================================*/ +void ObjectClass::Unselect(void) +{ + if (IsSelected) { + CurrentObject.Delete(this); + + if (In_Which_Layer()==LAYER_GROUND){ + Mark(MARK_OVERLAP_UP); + } + + IsSelected = false; + + if (In_Which_Layer()==LAYER_GROUND){ + Mark(MARK_OVERLAP_DOWN); + } + } +} + + +/*********************************************************************************************** + * ObjectClass::Select -- Try to make this object the "selected" object. * + * * + * This routine is used to make this object into the one that is "selected". A selected * + * object usually displays a floating bar graph and is available to be given orders from * + * the player's I/O. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/19/1994 JLB : Created. * + * 06/12/1995 JLB : Cannot select a loaner object. * + * 07/23/1995 JLB : Adds to head or tail depending on leader type flag. * + *=============================================================================================*/ +bool ObjectClass::Select(void) +{ + if (!Debug_Map && (IsSelected || !Class_Of().IsSelectable)) return(false); + + if (Can_Player_Move() && Is_Techno() && ((TechnoClass *)this)->IsALoaner) return(false); + + /* + ** Don't allow selection of object when in building placement mode. + */ + if (Map.PendingObject) return(false); + + /* + ** If selecting an object of a different house than the player's, make sure that + ** the entire selection list is cleared. + */ + if (CurrentObject.Count() > 0) { + if (Owner() != CurrentObject[0]->Owner() || CurrentObject[0]->Owner() != PlayerPtr->Class->House) { + Unselect_All(); + } + } + if (((TechnoTypeClass const &)Class_Of()).IsLeader) { + CurrentObject.Add_Head(this); + } else { + CurrentObject.Add(this); + } + + if (In_Which_Layer()==LAYER_GROUND){ + Mark(MARK_OVERLAP_UP); + } + + IsSelected = true; + + if (In_Which_Layer()==LAYER_GROUND){ + Mark(MARK_OVERLAP_DOWN); + } + return(true); +} + + +/*********************************************************************************************** + * ObjectClass::Render -- Displays the object onto the map. * + * * + * This routine will determine the location of the object and if it is roughly on the * + * visible screen, it will display it. Not displaying objects that are not on the screen * + * will save valuable time. * + * * + * INPUT: bool; Should the render be forced regardless of whether the object is flagged to * + * be redrawn? * + * * + * OUTPUT: bool; Was the draw code called for this object? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/19/1994 JLB : Created. * + *=============================================================================================*/ +bool ObjectClass::Render(bool forced) +{ + int x,y; + COORDINATE coord = Render_Coord(); + CELL cell = Coord_Cell(coord); + + if (Debug_Map || Debug_Unshroud || ((forced || IsToDisplay) && IsDown && !IsInLimbo)) { + IsToDisplay = false; + + /* + ** Draw the path as lines on the map if so directed and the object is one that + ** contains a path. + */ + if (Special.IsShowPath && IsSelected) { + switch (What_Am_I()) { + case RTTI_INFANTRY: + case RTTI_UNIT: + FootClass * foot = (FootClass *)this; + CELL cell; + int oldx, oldy; + + if (foot->Head_To_Coord() && foot->Path[0] != FACING_NONE) { + cell = Adjacent_Cell(Coord_Cell(foot->Head_To_Coord()), (FacingType)((foot->Path[0] + FACING_S) & FACING_NW)); + Map.Coord_To_Pixel(Cell_Coord(cell), oldx, oldy); + for (int index = 0; index < PATH_MAX; index++) { + if (foot->Path[index] == FACING_NONE) break; + cell = Adjacent_Cell(cell, foot->Path[index]); + if (Map.Coord_To_Pixel(Cell_Coord(cell), x, y)) { + LogicPage->Draw_Line(oldx, 8+oldy, x, 8+y, BLACK); + } + oldx = x; + oldy = y; + } + } + break; + } + } + + if (Map.Coord_To_Pixel(coord, x, y)) { + + /* + ** Draw the object itself + */ + Draw_It(x, y, WINDOW_TACTICAL); + +#ifdef SCENARIO_EDITOR + /* + ** Draw the trigger attached to the object. Draw_It is window- + ** relative, so add the window's x-coord to 'x'. + */ + if (Debug_Map && Trigger) { + Fancy_Text_Print(Trigger->Get_Name(), x + (WinX<<3), y, PINK, TBLACK, TPF_CENTER | TPF_NOSHADOW | TPF_6POINT); + } +#endif + + return(true); + } + } + return(false); +} + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * ObjectClass::Debug_Dump -- Displays status of the object class to the mono monitor. * + * * + * This routine is used to display the current status of the object class to the mono * + * monitor. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/02/1994 JLB : Created. * + *=============================================================================================*/ +void ObjectClass::Debug_Dump(MonoClass *mono) const +{ + mono->Text_Print("X", 16 + (IsToDisplay?2:0), 18); + mono->Text_Print("X", 16 + (IsActive?2:0), 3); + mono->Text_Print("X", 16 + (IsInLimbo?2:0), 4); + mono->Text_Print("X", 16 + (IsSelected?2:0), 7); + mono->Set_Cursor(56, 1); + mono->Printf("%08lX", Coord); + mono->Set_Cursor(14, 1);mono->Printf("[%04X]", As_Target()); + mono->Set_Cursor(20, 3);mono->Printf("%2d[%d]", Strength, Class_Of().MaxStrength); +} +#endif + + +/*********************************************************************************************** + * ObjectTypeClass::Occupy_List -- Returns with simple occupation list for object. * + * * + * This routine returns a pointer to a simple occupation list for this object. Since at * + * this tier of the object class chain, the exact shape of the object is indeterminate, * + * this function merely returns a single cell occupation list. This actually works for * + * most vehicles. * + * * + * INPUT: none * + * * + * OUTPUT: Returns a pointer to a simple occupation list. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +short const * ObjectTypeClass::Occupy_List(bool) const +{ + static short const _list[] = {0, REFRESH_EOL}; + return(_list); +} + + +/*********************************************************************************************** + * ObjectTypeClass::Overlap_List -- Returns a pointer to a simple overlap list. * + * * + * This function returns a pointer to an overlap list for the object. An overlap list is * + * the offsets from the object's cell to get the cells the imagery overlaps, but is object * + * is not considered to occupy. Since at this stage, the overlap information is not * + * available, this function merely returns a pointer to an empty list. * + * * + * INPUT: none * + * * + * OUTPUT: Returns a pointer to the generic overlap list. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +short const * ObjectTypeClass::Overlap_List(void) const +{ + static short const _list[] = {REFRESH_EOL}; + return(_list); +} + + +/*********************************************************************************************** + * ObjectTypeClass::One_Time -- Handles one time processing for object types. * + * * + * This routine is used to handle the once per game processing required for object types. * + * This consists of loading any data and initializing any data tables the game requires. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine goes to disk. * + * * + * HISTORY: * + * 11/01/1994 JLB : Created. * + *=============================================================================================*/ +void ObjectTypeClass::One_Time(void) +{ + SelectShapes = MixFileClass::Retrieve("SELECT.SHP"); +#if (FRENCH) + PipShapes = Hires_Retrieve("PIPS_F.SHP"); +#else +#if (GERMAN) + PipShapes = Hires_Retrieve("PIPS_G.SHP"); +#else + PipShapes = Hires_Retrieve("PIPS.SHP"); +#endif +#endif +} + + +/*********************************************************************************************** + * ObjectClass::Mark_For_Redraw -- Marks object and system for redraw. * + * * + * This routine will mark the object and inform the display system * + * that appropriate rendering is needed. Whenever it is determined * + * that an object needs to be redrawn, call this routine. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This is a subordinate function to the function Mark(). If an object needs to * + * be redrawn it is probably better to call the function Mark(MARK_CHANGE) rather * + * than this function. This function does not inform the map system that * + * overlapping objects are to be redrawn and thus unless you are really sure that * + * this routine should be called, don't. * + * * + * HISTORY: * + * 05/08/1994 JLB : Created. * + * 12/23/1994 JLB : Flags map and flags unit only. * + *=============================================================================================*/ +void ObjectClass::Mark_For_Redraw(void) +{ + if (!IsToDisplay) { + IsToDisplay = true; + + /* + ** This tells the map rendering logic to "go through the motions" and call the + ** rendering function. In the rendering function, it will sort out what gets + ** rendered and what doesn't. + */ + Map.Flag_To_Redraw(false); + } +} + + +/*********************************************************************************************** + * ObjectClass::Limbo -- Brings the object into a state of limbo. * + * * + * An object brought into a state of limbo by this routine can be safely deleted. This * + * routine will remove the object from all game lists and tracking systems. It is called * + * prior to deleting the object or placing the object "on ice". * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the object successfully placed in limbo? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +bool ObjectClass::Limbo(void) +{ + if (GameActive && !IsInLimbo) { + + Unselect(); + Detach_All(); + Mark(MARK_UP); + + /* + ** Remove the object from the appropriate display list. + */ + Map.Remove(this, In_Which_Layer()); + + /* + ** Remove the object from the logic processing list. + */ + if (Class_Of().IsSentient) { + Logic.Delete(this); + } + + Hidden(); + IsInLimbo = true; + IsToDisplay = false; + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * ObjectClass::Unlimbo -- Brings the object into the game system. * + * * + * This routine will place the object into the game tracking and display systems. It is * + * called as a consequence of creating the object. Every game object must be unlimboed at * + * some point. * + * * + * INPUT: coord -- The coordinate to place the object into the game system. * + * * + * dir (optional) -- initial facing direction for this object * + * * + * OUTPUT: bool; Was the game object successfully unlimboed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + * 12/23/1994 JLB : Sets object strength. * + *=============================================================================================*/ +bool ObjectClass::Unlimbo(COORDINATE coord, DirType ) +{ + if (GameActive && IsInLimbo && !IsDown) { + if (ScenarioInit || Can_Enter_Cell(Coord_Cell(coord), FACING_NONE) == MOVE_OK) { + IsInLimbo = false; + IsToDisplay = false; + Coord = Class_Of().Coord_Fixup(coord); + + if (Mark(MARK_DOWN)) { + if (IsActive) { + + /* + ** Add the object to the appropriate map layer. This layer is used + ** for rendering purposes. + */ + if (In_Which_Layer() != LAYER_NONE) { + Map.Submit(this, In_Which_Layer()); + } + + if (Class_Of().IsSentient) { + Logic.Submit(this); + } + } + return(true); + } + } + } + return(false); +} + + +/*********************************************************************************************** + * ObjectClass::Detach_All -- Removes the object from all tracking systems. * + * * + * This routine will take the object and see that it is removed from all miscellaneous * + * tracking systems in the game. This operation is vital when deleting an object. It is * + * necessary so that when the object is removed from the game, existing game objects won't * + * be referencing a now invalid game object. This typically affects the targeting * + * and navigation computers of other game objects. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +void ObjectClass::Detach_All(bool all) +{ + /* + ** Unselect this object if it was selected. + */ + if (all || Owner() != PlayerPtr->Class->House) { + Unselect(); + } + + Map.Detach(this); + + /* + ** Remove from targeting computers. + */ + Detach_This_From_All(As_Target(), all); +} + + +/*********************************************************************************************** + * ObjectClass::Detach_This_From_All -- Detatches this object from all others. * + * * + * This routine sweeps through all game objects and makes sure that it is no longer * + * referenced by them. Typically, this is called in preparation for the object's death * + * or limbo state. * + * * + * INPUT: target -- This object expressed as a target number. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +void ObjectClass::Detach_This_From_All(TARGET target, bool all) +{ + int index; + if (Target_Legal(target)) { + + for (index = 0; index < Houses.Count(); index++) { + Houses.Ptr(index)->Detach(target, all); + } + for (index = 0; index < Teams.Count(); index++) { + Teams.Ptr(index)->Detach(target, all); + } + for (index = 0; index < Units.Count(); index++) { + Units.Ptr(index)->Detach(target, all); + } + for (index = 0; index < Infantry.Count(); index++) { + Infantry.Ptr(index)->Detach(target, all); + } + for (index = 0; index < Aircraft.Count(); index++) { + Aircraft.Ptr(index)->Detach(target, all); + } + for (index = 0; index < Buildings.Count(); index++) { + Buildings.Ptr(index)->Detach(target, all); + } + for (index = 0; index < Bullets.Count(); index++) { + Bullets.Ptr(index)->Detach(target, all); + } + for (index = 0; index < Anims.Count(); index++) { + Anims.Ptr(index)->Detach(target, all); + } + } +} + + +/*********************************************************************************************** + * ObjectClass::Receive_Message -- Processes an incoming radio message. * + * * + * Any radio message received that applies to objects in general are handled by this * + * routine. Typically, this is the "redraw" message, which occurs when another object is * + * loading or unloading and thus overlapping. * + * * + * INPUT: message -- The message received. * + * * + * OUTPUT: Returns with the appropriate radio response. If the message was recognized, then * + * RADIO_ROGER is returned, otherwise, just RADIO_STATIC is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +RadioMessageType ObjectClass::Receive_Message(RadioClass *, RadioMessageType message, long & ) +{ + switch (message) { + + /* + ** This message serves as a rendering convenience. It lets the system + ** know that there might be a visual conflict and the unit in radio + ** contact should be redrawn. This typically occurs when a vehicle + ** is being unloaded from a hover lander. + */ + case RADIO_REDRAW: + Mark(MARK_CHANGE); + return(RADIO_ROGER); + + default: + break; + } + return(RADIO_STATIC); +} + + +/*********************************************************************************************** + * ObjectClass::Take_Damage -- Applies damage to the object. * + * * + * This routine applies damage to the object according to the damage parameters. It handles * + * reducing the strength of the object and also returns the result of that damage. The * + * result value can be examined to determine if the object was destroyed, greatly damaged, * + * or other results. * + * * + * INPUT: damage -- Reference to the damage number to apply. This number will be adjusted * + * according to defensive armor and distance. Examine this value after * + * the call to determine the actual amount of damage applied. * + * * + * distance -- The distance (in leptons) from the center of the damage causing * + * explosion to the object itself. * + * * + * warhead -- The warhead type that is causing the damage. * + * * + * OUTPUT: Returns the ResultType that indicates what the affect of the damage was. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/29/1994 JLB : Created. * + * 12/27/1994 JLB : Trigger event processing for attacked or destroyed. * + * 01/01/1995 JLB : Reduces damage greatly depending on range. * + *=============================================================================================*/ +ResultType ObjectClass::Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source) +{ + ResultType result = RESULT_NONE; + int oldstrength = Strength; + + if (oldstrength && damage && !Class_Of().IsImmune) { + int maxstrength = Class_Of().MaxStrength; + + /* + ** Modify damage based on the warhead type and the armor of the object. This results + ** in a reduced damage value, but never below 1 damage point. + */ + damage = Modify_Damage(damage, warhead, Class_Of().Armor, distance); + if (!damage) return(RESULT_NONE); + + /* + ** At this point, we KNOW that at least light damage has occurred. + */ + result = RESULT_LIGHT; + + /* + ** A non-fatal blow has occurred. Check to see if the object transitioned to below + ** half strength or if it is now down to one hit point. + */ + if (oldstrength > damage) { + + if (oldstrength >= (maxstrength >> 1) && (oldstrength-damage) < (maxstrength >> 1)) { + result = RESULT_HALF; + } + } else { + + /* + ** When an object is damaged to destruction, it will instead stop at one + ** damage point. This will prolong the damage state as well as + ** give greater satisfaction when it is finally destroyed. + */ + damage = oldstrength; + } + + /* + ** Apply the damage to the object. + */ + Strength = oldstrength - damage; + + /* + ** Check to see if the object is majorly damaged or destroyed. + */ + switch (Strength) { + case 0: + Record_The_Kill(source); + result = RESULT_DESTROYED; + Detach_All(); + break; + + case 1: + result = RESULT_MAJOR; + break; + + default: + break; + } + + /* + ** Handle any trigger event associated with this object. + */ + if (source && Trigger && result != RESULT_DESTROYED) { + Trigger->Spring(EVENT_ATTACKED, this); + } + + /* + ** If any damage was assessed and this object is selected, then flag + ** the object to be redrawn so that the health bar will be updated. + */ + if (result != RESULT_NONE && IsSelected) { + Mark(MARK_CHANGE); + } + } + + /* + ** Return with the result of the damage taken. + */ + return(result); +} + + +/*********************************************************************************************** + * ObjectClass::Mark -- Handles basic marking logic. * + * * + * This routine handles the base logic for marking an object up or down on the map. It * + * manages the IsDown flag as well as flagging the object to be redrawn if necessary. * + * Whenever an object is to be marked, it should call this base class function first. If * + * this function returns true, then the higher level function should proceed with its own * + * logic. * + * * + * INPUT: mark -- The marking method to use for this object. It can be either MARK_DOWN, * + * MARK_UP, or MARK_CHANGE. * + * * + * OUTPUT: bool; Was the object marked successfully? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +bool ObjectClass::Mark(MarkType mark) +{ + TechnoClass *tech; + CELL cell; + int threat; + HousesType house; + + if (!IsInLimbo && IsActive) { + + /* + ** A mark for change is always successful UNLESS the object + ** is not placed down or has already been flagged as changed + ** this game frame. + */ + if (mark == MARK_CHANGE) { + if (IsToDisplay) return(false); + if (IsDown == true) { + Mark_For_Redraw(); + return(true); + } + return(false); + } + + /* + ** Handle adding or removing the object in the cells' overlap lists + */ + if (mark == MARK_OVERLAP_UP) { + if (IsDown == true) { + Map.Overlap_Up(Coord_Cell(Coord),this); + Mark_For_Redraw(); + return(true); + } + } + if (mark == MARK_OVERLAP_DOWN) { + if (IsDown == true) { + Map.Overlap_Down(Coord_Cell(Coord),this); + Mark_For_Redraw(); + return(true); + } + } + + /* + ** It is important to know whether the object is a techno class + ** or not to see if we have to adjust the regional threat ratings + */ + if (Is_Techno()) { + tech = (TechnoClass *)this; + threat = tech->Risk(); + house = tech->Owner(); + cell = Coord_Cell(Coord); + } else + tech = NULL; + + /* + ** Marking down is only successful if the object isn't already + ** placed down. + */ + if (mark == MARK_DOWN && !IsDown) { + if (tech && GameToPlay == GAME_NORMAL) { + Map[cell].Adjust_Threat(house, threat); + } + IsDown = true; + Mark_For_Redraw(); + return(true); + } + + /* + ** Lifting up is only successful if the object isn't already + ** lifted up from the map. + */ + if (mark == MARK_UP && IsDown) { + if (tech && GameToPlay == GAME_NORMAL) { + Map[cell].Adjust_Threat(house, -threat); + } + IsDown = false; + return(true); + } + } + return(false); +} + + +/*********************************************************************************************** + * ObjectClass::Init -- Initializes the basic object system. * + * * + * This routine should be called when the basic object system needs to be initialized. This * + * occurs when the scenario is about to be loaded. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +void ObjectClass::Init(void) +{ + CurrentObject.Clear(); +} + + +/*********************************************************************************************** + * ObjectClass::Revealed -- Reveals this object to the house specified. * + * * + * This routine is called when this object gets revealed to the house specified. * + * * + * INPUT: house -- Pointer to the house that this object is being revealed to. * + * * + * OUTPUT: Was this object revealed for the first time to this house? Generic objects always * + * return true unless an invalid house pointer was specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1995 JLB : Created. * + *=============================================================================================*/ +bool ObjectClass::Revealed(HouseClass * house) +{ + return(house != NULL); +} + +// These can't be made inline (for various reasons). +short const * ObjectClass::Occupy_List(bool placement) const {return(Class_Of().Occupy_List(placement));}; +short const * ObjectClass::Overlap_List(void) const {return(Class_Of().Overlap_List());}; +BuildingClass * ObjectClass::Who_Can_Build_Me(bool intheory, bool legal) const {return(Class_Of().Who_Can_Build_Me(intheory, legal, Owner()));}; +unsigned ObjectClass::Health_Ratio(void) const {return(Cardinal_To_Fixed(Class_Of().MaxStrength, Strength));}; +int ObjectClass::Full_Name(void) const {return Class_Of().Full_Name();}; + diff --git a/OBJECT.H b/OBJECT.H new file mode 100644 index 0000000..2d35cb7 --- /dev/null +++ b/OBJECT.H @@ -0,0 +1,245 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\object.h_v 2.15 16 Oct 1995 16:46:16 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : OBJECT.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 29, 1994 * + * * + * Last Update : April 29, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef OBJECT_H +#define OBJECT_H + +#include "abstract.h" + +class ObjectClass; +class TechnoClass; +class ObjectTypeClass; +class HouseClass; +class TriggerClass; +class BuildingClass; +class RadioClass; + +//extern "C" { +//unsigned Cardinal_To_Fixed(unsigned base, unsigned cardinal); +//} + +/********************************************************************** +** Every game object (that can exist on the map) is ultimately derived from this object +** class. It holds the common information between all objects. This is primarily the +** object unique ID number and its location in the world. All common operations between +** game objects are represented by virtual functions in this class. +*/ +class ObjectClass : public AbstractClass +{ + public: + /* + ** The object can be in one of two states -- placed down on the map, or not. If the + ** object is placed down on the map, then this flag will be true. + */ + unsigned IsDown:1; + + /* + ** This is a support flag that is only used while building a list of objects to + ** be damaged by a proximity affect (explosion). When this flag is set, this object + ** will not be added to the list of units to damage. When damage is applied to the + ** object, this flag is cleared again. This process ensures that an object is never + ** subject to "double jeapordy". + */ + unsigned IsToDamage:1; + +// private: + /* + ** Is this object flagged to be displayed during the next rendering process? This + ** flag could be set by many different circumstances. It is automatically cleared + ** when the object is rerendered. + */ + unsigned IsToDisplay:1; + + + public: + /* + ** An object in the game may be valid yet held in a state of "limbo". Units are in such + ** a state if they are being transported or are otherwise "inside" another unit. They can + ** also be in limbo if they have been created but are being held until the proper time + ** for delivery. + */ + unsigned IsInLimbo:1; + + /* + ** When an object is "selected" it is given a floating bar graph or other graphic imagery + ** to display this fact. When the player performs I/O, the actions may have a direct + ** bearing on the actions of the currently selected object. For quick checking purposes, + ** if this object is the one that is "selected", this flag will be true. + */ + unsigned IsSelected:1; + + /* + ** If an animation is attached to this object, then this flag will be true. + */ + unsigned IsAnimAttached:1; + + /* + ** Several objects could exist in the same cell list. This is a pointer to the + ** next object in the cell list. The objects in this list are not in any + ** significant order. + */ + ObjectClass * Next; + + /* + ** Every object can be assigned a trigger; the same trigger can be assigned + ** to multiple objects. + */ + TriggerClass * Trigger; + + /* + ** This is the current strength of this object. + */ + short Strength; + + /*----------------------------------------------------------------------------------- + ** Constructor & destructors. + */ + ObjectClass(void); + virtual ~ObjectClass(void) {}; + virtual RTTIType What_Am_I(void) const; + int operator < (ObjectClass const & object) const {return Sort_Y() < object.Sort_Y();}; + int operator > (ObjectClass const & object) const {return Sort_Y() > object.Sort_Y();}; + + /* + ** Object selection control. + */ + static void Init(void); + + /* + ** Query functions. + */ + virtual ActionType What_Action(ObjectClass *) const; + virtual ActionType What_Action(CELL) const; + virtual LayerType In_Which_Layer(void) const; + virtual bool Is_Infantry(void) const; + virtual bool Is_Techno(void) const; + virtual unsigned char Get_Ownable(void) const; + virtual ObjectTypeClass const & Class_Of(void) const = 0; + virtual int Full_Name(void) const; + virtual bool Can_Repair(void) const; + virtual bool Can_Demolish(void) const; + virtual bool Can_Player_Fire(void) const; + virtual bool Can_Player_Move(void) const; + + /* + ** Coordinate inquiry functions. These are used for both display and + ** combat purposes. + */ + virtual COORDINATE Docking_Coord(void) const; + virtual COORDINATE Target_Coord(void) const; + virtual COORDINATE Center_Coord(void) const; + virtual COORDINATE Render_Coord(void) const; + virtual COORDINATE Sort_Y(void) const; + virtual COORDINATE Fire_Coord(int ) const; + + /* + ** Object entry and exit from the game system. + */ + virtual bool Limbo(void); + virtual bool Unlimbo(COORDINATE , DirType facing = DIR_N); + virtual void Detach(TARGET, bool) {}; + virtual void Detach_All(bool all=true); + static void Detach_This_From_All(TARGET target, bool all=true); + virtual void Record_The_Kill(TechnoClass * ); + + /* + ** Display and rendering support functionality. Supports imagery and how + ** object interacts with the map and thus indirectly controls rendering. + */ + virtual void Do_Shimmer(void); + virtual int Exit_Object(TechnoClass *); + virtual bool Render(bool forced); + virtual short const * Occupy_List(bool placement=false) const; + virtual short const * Overlap_List(void) const; + virtual unsigned Health_Ratio(void) const; + virtual void Draw_It(int x, int y, WindowNumberType ) = 0; + virtual void Hidden(void); + virtual void Look(bool =false); + virtual bool Mark(MarkType); + + private: + virtual void Mark_For_Redraw(void); + + public: + + /* + ** User I/O. + */ + virtual void Active_Click_With(ActionType , ObjectClass *); + virtual void Active_Click_With(ActionType , CELL ); + virtual void Clicked_As_Target(int = 7); + virtual bool Select(void); + virtual void Unselect(void); + + /* + ** Combat related. + */ + virtual bool In_Range(COORDINATE , int=0) const; + virtual int Weapon_Range(int =0) const; + virtual ResultType Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source=0); + virtual TARGET As_Target(void) const; + virtual void Scatter(COORDINATE , bool=false); + virtual bool Catch_Fire(void); + virtual void Fire_Out(void); + virtual int Value(void) const; + virtual MissionType Get_Mission(void) const; + + /* + ** AI. + */ + virtual BuildingClass * Who_Can_Build_Me(bool intheory, bool legal) const; + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, long & param); + virtual bool Revealed(HouseClass * house); + virtual void Repair(int ); + virtual void Sell_Back(int ); + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /* + ** Scenario and debug support. + */ + #ifdef CHEAT_KEYS + virtual void Debug_Dump(MonoClass *mono) const; + #endif + virtual void Move(FacingType); +}; + +#endif diff --git a/ODATA.CPP b/ODATA.CPP new file mode 100644 index 0000000..1d34cc4 --- /dev/null +++ b/ODATA.CPP @@ -0,0 +1,925 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\odata.cpv 2.16 16 Oct 1995 16:50:36 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : ODATA.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 16, 1994 * + * * + * Last Update : April 19, 1995 [PWG] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * OverlayTypeClass::Display -- Displays a generic representation of overlay. * + * OverlayTypeClass::From_Name -- Determine overlay from ASCII name. * + * OverlayTypeClass::Init -- Loads graphic data for overlays. * + * OverlayTypeClass::Occupy_List -- Determines occupation list. * + * OverlayTypeClass::Prep_For_Add -- Prepares to add overlay to scenario. * + * OverlayTypeClass::Create_And_Place -- Creates and places a overlay object on the map. * + * OverlayTypeClass::Create_One_Of -- Creates an object of this overlay type. * + * OverlayTypeClass::OverlayTypeClass -- Constructor for overlay type objects. * + * OverlayTypeClass::Draw_It -- Draws the overlay image at location specified. * + * OverlayTypeClass::One_Time -- Loads all the necessary general overlay shape data. * + * OverlayTypeClass::Init -- Initialize the overlay graphic data per theater. * + * OverlayTypeClass::Radar_Icon -- Gets a pointer to the radar icons * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "type.h" + + +static OverlayTypeClass const Road( + OVERLAY_ROAD, // Overlay type number. + "ROAD", // INI name of overlay. + TXT_CONCRETE, // Full name of overlay. + LAND_ROAD, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + false, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Concrete( + OVERLAY_CONCRETE, // Overlay type number. + "CONC", // INI name of overlay. + TXT_CONCRETE, // Full name of overlay. + LAND_ROAD, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + false, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Sandbag( + OVERLAY_SANDBAG_WALL, // Overlay type number. + "SBAG", // INI name of overlay. + TXT_SANDBAG_WALL, // Full name of overlay. + LAND_WALL, // What kind of ground is it? + 1, // If this is a wall, how many damage levels? + 20, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + true, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + false, // Theater specific art? + true, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Cyclone( + OVERLAY_CYCLONE_WALL, // Overlay type number. + "CYCL", // INI name of overlay. + TXT_CYCLONE_WALL, // Full name of overlay. + LAND_WALL, // What kind of ground is it? + 2, // If this is a wall, how many damage levels? + 10, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + true, // Targetable as a destroyable overlay? + true, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + false, // Theater specific art? + true, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Brick( + OVERLAY_BRICK_WALL, // Overlay type number. + "BRIK", // INI name of overlay. + TXT_BRICK_WALL, // Full name of overlay. + LAND_WALL, // What kind of ground is it? + 3, // If this is a wall, how many damage levels? + 70, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + true, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + true, // Stops low level bullets in flight? + false, // Theater specific art? + true, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Barbwire( + OVERLAY_BARBWIRE_WALL, // Overlay type number. + "BARB", // INI name of overlay. + TXT_BARBWIRE_WALL, // Full name of overlay. + LAND_WALL, // What kind of ground is it? + 1, // If this is a wall, how many damage levels? + 2, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + true, // Targetable as a destroyable overlay? + true, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + false, // Theater specific art? + true, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Wood( + OVERLAY_WOOD_WALL, // Overlay type number. + "WOOD", // INI name of overlay. + TXT_WOOD_WALL, // Full name of overlay. + LAND_WALL, // What kind of ground is it? + 1, // If this is a wall, how many damage levels? + 2, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + true, // Is it a wooden overlay (affected by fire)? + true, // Targetable as a destroyable overlay? + true, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + false, // Theater specific art? + true, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Tiberium1( + OVERLAY_TIBERIUM1, // Overlay type number. + "TI1", // INI name of overlay. + TXT_TIBERIUM, // Full name of overlay. + LAND_TIBERIUM, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + true, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Tiberium2( + OVERLAY_TIBERIUM2, // Overlay type number. + "TI2", // INI name of overlay. + TXT_TIBERIUM, // Full name of overlay. + LAND_TIBERIUM, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + true, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Tiberium3( + OVERLAY_TIBERIUM3, // Overlay type number. + "TI3", // INI name of overlay. + TXT_TIBERIUM, // Full name of overlay. + LAND_TIBERIUM, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + true, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Tiberium4( + OVERLAY_TIBERIUM4, // Overlay type number. + "TI4", // INI name of overlay. + TXT_TIBERIUM, // Full name of overlay. + LAND_TIBERIUM, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + true, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Tiberium5( + OVERLAY_TIBERIUM5, // Overlay type number. + "TI5", // INI name of overlay. + TXT_TIBERIUM, // Full name of overlay. + LAND_TIBERIUM, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + true, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Tiberium6( + OVERLAY_TIBERIUM6, // Overlay type number. + "TI6", // INI name of overlay. + TXT_TIBERIUM, // Full name of overlay. + LAND_TIBERIUM, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + true, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Tiberium7( + OVERLAY_TIBERIUM7, // Overlay type number. + "TI7", // INI name of overlay. + TXT_TIBERIUM, // Full name of overlay. + LAND_TIBERIUM, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + true, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Tiberium8( + OVERLAY_TIBERIUM8, // Overlay type number. + "TI8", // INI name of overlay. + TXT_TIBERIUM, // Full name of overlay. + LAND_TIBERIUM, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + true, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Tiberium9( + OVERLAY_TIBERIUM9, // Overlay type number. + "TI9", // INI name of overlay. + TXT_TIBERIUM, // Full name of overlay. + LAND_TIBERIUM, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + true, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Tiberium10( + OVERLAY_TIBERIUM10, // Overlay type number. + "TI10", // INI name of overlay. + TXT_TIBERIUM, // Full name of overlay. + LAND_TIBERIUM, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + true, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Tiberium11( + OVERLAY_TIBERIUM11, // Overlay type number. + "TI11", // INI name of overlay. + TXT_TIBERIUM, // Full name of overlay. + LAND_TIBERIUM, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + true, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Tiberium12( + OVERLAY_TIBERIUM12, // Overlay type number. + "TI12", // INI name of overlay. + TXT_TIBERIUM, // Full name of overlay. + LAND_TIBERIUM, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + true, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const Squish( + OVERLAY_SQUISH, // Overlay type number. + "SQUISH", // INI name of overlay. + TXT_SQUISH, // Full name of overlay. + LAND_CLEAR, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + false, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + false, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); + +static OverlayTypeClass const V12( + OVERLAY_V12, // Overlay type number. + "V12", // INI name of overlay. + TXT_CIV12, // Full name of overlay. + LAND_ROCK, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + false, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + true, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const V13( + OVERLAY_V13, // Overlay type number. + "V13", // INI name of overlay. + TXT_CIV13, // Full name of overlay. + LAND_ROCK, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + false, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + true, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const V14( + OVERLAY_V14, // Overlay type number. + "V14", // INI name of overlay. + TXT_CIV14, // Full name of overlay. + LAND_ROCK, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + false, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + true, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const V15( + OVERLAY_V15, // Overlay type number. + "V15", // INI name of overlay. + TXT_CIV15, // Full name of overlay. + LAND_ROCK, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + false, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + true, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const V16( + OVERLAY_V16, // Overlay type number. + "V16", // INI name of overlay. + TXT_CIV16, // Full name of overlay. + LAND_ROCK, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + false, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + true, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const V17( + OVERLAY_V17, // Overlay type number. + "V17", // INI name of overlay. + TXT_CIV17, // Full name of overlay. + LAND_ROCK, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + false, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + true, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const V18( + OVERLAY_V18, // Overlay type number. + "V18", // INI name of overlay. + TXT_CIV18, // Full name of overlay. + LAND_ROCK, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + false, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + true, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + true, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const FlagSpot( + OVERLAY_FLAG_SPOT, // Overlay type number. + "FPLS", // INI name of overlay. + TXT_FLAG_SPOT, // Full name of overlay. + LAND_CLEAR, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + true, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + false, // Theater specific art? + false, // Is this a wall type? + false // Is this a crate? +); +static OverlayTypeClass const WoodCrate( + OVERLAY_WOOD_CRATE, // Overlay type number. + "WCRATE", // INI name of overlay. + TXT_WOOD_CRATE, // Full name of overlay. + LAND_CLEAR, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + false, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + false, // Theater specific art? + false, // Is this a wall type? + true // Is this a crate? +); +static OverlayTypeClass const SteelCrate( + OVERLAY_STEEL_CRATE, // Overlay type number. + "SCRATE", // INI name of overlay. + TXT_STEEL_CRATE, // Full name of overlay. + LAND_CLEAR, // What kind of ground is it? + 0, // If this is a wall, how many damage levels? + 0, // If this is a wall, how many damage points can it take per level? + false, // Visible on the radar map? + false, // Is it a wooden overlay (affected by fire)? + false, // Targetable as a destroyable overlay? + false, // Crushable by tracked vehicle? + false, // Is this harvestable Tiberium? + false, // Stops low level bullets in flight? + false, // Theater specific art? + false, // Is this a wall type? + true // Is this a crate? +); + + +OverlayTypeClass const * const OverlayTypeClass::Pointers[OVERLAY_COUNT] = { + &Concrete, // OVERLAY_CONCRETE + &Sandbag, // OVERLAY_SANDBAG_WALL + &Cyclone, // OVERLAY_CYCLONE_WALL + &Brick, // OVERLAY_BRICK_WALL + &Barbwire, // OVERLAY_BARBWIRE_WALL + &Wood, // OVERLAY_WOOD_WALL + &Tiberium1, // OVERLAY_TIBERIUM1 + &Tiberium2, // OVERLAY_TIBERIUM2 + &Tiberium3, // OVERLAY_TIBERIUM3 + &Tiberium4, // OVERLAY_TIBERIUM4 + &Tiberium5, // OVERLAY_TIBERIUM5 + &Tiberium6, // OVERLAY_TIBERIUM6 + &Tiberium7, // OVERLAY_TIBERIUM7 + &Tiberium8, // OVERLAY_TIBERIUM8 + &Tiberium9, // OVERLAY_TIBERIUM9 + &Tiberium10, // OVERLAY_TIBERIUM10 + &Tiberium11, // OVERLAY_TIBERIUM11 + &Tiberium12, // OVERLAY_TIBERIUM12 + &Road, // OVERLAY_ROAD + &Squish, // OVERLAY_SQUISH + &V12, // OVERLAY_V12 + &V13, // OVERLAY_V13 + &V14, // OVERLAY_V14 + &V15, // OVERLAY_V15 + &V16, // OVERLAY_V16 + &V17, // OVERLAY_V17 + &V18, // OVERLAY_V18 + &FlagSpot, // OVERLAY_FLAG_SPOT + &WoodCrate, // OVERLAY_WOOD_CRATE + &SteelCrate, // OVERLAY_STEEL_CRATE +}; + + +/*********************************************************************************************** + * OverlayTypeClass::OverlayTypeClass -- Constructor for overlay type objects. * + * * + * This is the constructor for the overlay types. * + * * + * INPUT: see below... * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1994 JLB : Created. * + *=============================================================================================*/ +OverlayTypeClass::OverlayTypeClass( + OverlayType iconset, + char const *ininame, + int fullname, + LandType ground, + int damagelevels, + int damagepoints, + bool isradarvisible, + bool iswooden, + bool istarget, + bool iscrushable, + bool istiberium, + bool high, + bool theater, + bool walltype, + bool iscrate) : + ObjectTypeClass(false, + false, + iscrushable, + true, + false, + istarget, + true, + false, + fullname, + ininame, + ARMOR_NONE, + 0) +{ + IsRadarVisible = isradarvisible; + IsCrate = iscrate; + IsWooden = iswooden; + IsHigh = high; + IsTheater = theater; + IsTiberium = istiberium; + Type = iconset; + Land = ground; + IsWall = walltype; + DamageLevels = damagelevels; + DamagePoints = damagepoints; +} + + +/*********************************************************************************************** + * OverlayTypeClass::One_Time -- Loads all the necessary general overlay shape data. * + * * + * This routine should be called once when the game first starts. It will establish * + * pointers to the graphic data of the overlay objects. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/12/1994 JLB : Created. * + *=============================================================================================*/ +void OverlayTypeClass::One_Time(void) +{ +} + + +/*********************************************************************************************** + * OverlayTypeClass::From_Name -- Determine overlay from ASCII name. * + * * + * This routine is used to determine the overlay number given only * + * an ASCII representation. The scenario loader uses this routine * + * to construct the map from the INI control file. * + * * + * INPUT: name -- Pointer to the ASCII name of the overlay. * + * * + * OUTPUT: Returns with the overlay number. If the name had no match, * + * then returns with OVERLAY_NONE. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1994 JLB : Created. * + *=============================================================================================*/ +OverlayType OverlayTypeClass::From_Name(char const *name) +{ + if (name) { + for (OverlayType index = OVERLAY_FIRST; index < OVERLAY_COUNT; index++) { + if (stricmp(As_Reference(index).IniName, name) == 0) { + return(index); + } + } + } + return(OVERLAY_NONE); +} + + +/*********************************************************************************************** + * OverlayTypeClass::Occupy_List -- Determines occupation list. * + * * + * This routine is used to examine the overlay map and build an * + * occupation list. This list is used to render a overlay cursor as * + * well as placement of icon numbers. * + * * + * INPUT: placement -- Is this for placement legality checking only? The normal condition * + * is for marking occupation flags. * + * * + * OUTPUT: Returns with a pointer to the overlay occupation list. * + * * + * WARNINGS: The return pointer is valid only until the next time that * + * this routine is called. * + * * + * HISTORY: * + * 05/23/1994 JLB : Created. * + *=============================================================================================*/ +short const * OverlayTypeClass::Occupy_List(bool) const +{ + static short _simple[] = {0, REFRESH_EOL}; + + return(_simple); +} + + +/*************************************************************************** + * OverlayTypeClass::Radar_Icon -- Gets a pointer to the radar icons * + * * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/19/1995 PWG : Created. * + *=========================================================================*/ +unsigned char * OverlayTypeClass::Radar_Icon(int data) const +{ + unsigned char *icon = (unsigned char *)Get_Radar_Data(); // Get pointer to radar icons + icon += (data * 9) + 2; // move icon ptr to correct icon + return(icon); // Return the correct icon +} + + +#ifdef SCENARIO_EDITOR +/*********************************************************************************************** + * OverlayTypeClass::Display -- Displays a generic representation of overlay. * + * * + * This routine is used to display a generic view of the overlay * + * object. This is necessary for selection in the scenario editor. * + * * + * INPUT: x,y -- The coordinates to center the display about. * + * * + * window-- The window to base the coordinates upon. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1994 JLB : Created. * + *=============================================================================================*/ +void OverlayTypeClass::Display(int x, int y, WindowNumberType window, HousesType ) const +{ + /* + ---------------------------- Draw the shape ------------------------------ + */ + if (Get_Image_Data()) { + int frame = 0; + + if (IsTiberium) { + frame = 7; + } + + CC_Draw_Shape(Get_Image_Data(), frame, x, y, window, SHAPE_NORMAL|SHAPE_CENTER|SHAPE_WIN_REL); + } +} + + +/*********************************************************************************************** + * OverlayTypeClass::Prep_For_Add -- Prepares to add overlay to scenario. * + * * + * This routine prepares a list of overlay objects so that the * + * scenario editor can use this list to display a dialog box. The * + * selection of a overlay object will allow its placement upon the * + * map. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/06/1994 JLB : Created * + *=============================================================================================*/ +void OverlayTypeClass::Prep_For_Add(void) +{ + for (OverlayType index = OVERLAY_FIRST; index < OVERLAY_COUNT; index++) { + OverlayTypeClass const & overlay = As_Reference(index); + if (overlay.Get_Image_Data() && !overlay.IsWall && (!overlay.IsTiberium || index == OVERLAY_TIBERIUM1)) { + Map.Add_To_List(&overlay); + } + } +} +#endif + + +/*********************************************************************************************** + * OverlayTypeClass::Create_And_Place -- Creates and places a overlay object on the map. * + * * + * This support routine is used by the scenario editor to add a overlay object to the map * + * and to the game. * + * * + * INPUT: cell -- The cell to place the overlay object. * + * * + * OUTPUT: bool; Was the overlay object placed successfully? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +bool OverlayTypeClass::Create_And_Place(CELL cell, HousesType ) const +{ + if (new OverlayClass(Type, cell)) { + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * OverlayTypeClass::Create_One_Of -- Creates an object of this overlay type. * + * * + * This routine will create an object of this type. For certain overlay objects, such * + * as walls, it is actually created as a building. The "building" wall is converted into * + * a overlay at the moment of placing down on the map. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the appropriate object for this overlay type. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/18/1994 JLB : Created. * + *=============================================================================================*/ +ObjectClass * OverlayTypeClass::Create_One_Of(HouseClass *) const +{ + return(new OverlayClass(Type, -1)); +} + + +/*********************************************************************************************** + * OverlayTypeClass::Draw_It -- Draws the overlay image at location specified. * + * * + * This routine will draw the overlay shape at the coordinates specified. It is presumed * + * that all the underlying layers have already been rendered by the time this routine is * + * called. * + * * + * INPUT: x, y -- Coordinate (upper left) of cell where overlay image is to be drawn. * + * * + * data -- Cell specific data that controls the imagery of the overlay. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/12/1994 JLB : Created. * + *=============================================================================================*/ +void OverlayTypeClass::Draw_It(int x, int y, int data) const +{ + CC_Draw_Shape(Get_Image_Data(), data, Map.TacPixelX+x+(CELL_PIXEL_W>>1), Map.TacPixelY+y+(CELL_PIXEL_H>>1), WINDOW_MAIN, SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_GHOST, NULL, Map.UnitShadow); +} + + +/*********************************************************************************************** + * OverlayTypeClass::Init -- Initialize the overlay graphic data per theater. * + * * + * This routine will update the overlay graphic data according to the theater specified. * + * It is typically called when the scenario is first loaded (theater change). * + * * + * INPUT: theater -- The theater to load specific data for. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + * 4/25/1996 ST : Modified to load theater specific sidebar icons if available * + *=============================================================================================*/ +void OverlayTypeClass::Init(TheaterType theater) +{ + if (theater != LastTheater){ + for (OverlayType index = OVERLAY_FIRST; index < OVERLAY_COUNT; index++) { + OverlayTypeClass const & overlay = As_Reference(index); + char fullname[_MAX_FNAME+_MAX_EXT]; // Fully constructed iconset name. + + if (overlay.IsTheater) { + _makepath(fullname, NULL, NULL, overlay.IniName, Theaters[theater].Suffix); + } else { + _makepath(fullname, NULL, NULL, overlay.IniName, ".SHP"); + } + ((void const *&)overlay.ImageData) = MixFileClass::Retrieve(fullname); + + IsTheaterShape = overlay.IsTheater; + if (overlay.RadarIcon) delete[] (char *)overlay.RadarIcon; + ((void const *&)overlay.RadarIcon) = Get_Radar_Icon(overlay.Get_Image_Data(), 0, -1, 3); + IsTheaterShape = false; + } + } +} diff --git a/OPTIONS.CPP b/OPTIONS.CPP new file mode 100644 index 0000000..7e59193 --- /dev/null +++ b/OPTIONS.CPP @@ -0,0 +1,797 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\options.cpv 2.17 16 Oct 1995 16:51:28 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : OPTIONS.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : June 8, 1994 * + * * + * Last Update : June 30, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * OptionsClass::Adjust_Palette -- Adjusts the palette according to the settings specified. * + * OptionsClass::Get_Brightness -- Fetches the current brightness setting. * + * OptionsClass::Get_Color -- Fetches the current color setting. * + * OptionsClass::Get_Contrast -- Gets the current contrast setting. * + * OptionsClass::Get_Game_Speed -- Fetches the current game speed setting. * + * OptionsClass::Get_Scroll_Rate -- Fetches the current scroll rate setting. * + * OptionsClass::Get_Tint -- Fetches the current tint setting. * + * OptionsClass::Load_Settings -- reads options settings from the INI file * + * OptionsClass::Normalize_Delay -- Normalizes delay factor to keep rate constant. * + * OptionsClass::One_Time -- This performs any one time initialization for the options class.* + * OptionsClass::OptionsClass -- The default constructor for the options class. * + * OptionsClass::Process -- Handles all the options graphic interface. * + * OptionsClass::Save_Settings -- writes options settings to the INI file * + * OptionsClass::Set -- Sets options based on current settings * + * OptionsClass::Set_Brightness -- Sets the brightness level to that specified. * + * OptionsClass::Set_Color -- Sets the color to the value specified. * + * OptionsClass::Set_Contrast -- Sets the contrast to the value specified. * + * OptionsClass::Set_Game_Speed -- Sets the game speed as specified. * + * OptionsClass::Set_Repeat -- Controls the score repeat option. * + * OptionsClass::Set_Score_Volume -- Sets the global score volume to that specified. * + * OptionsClass::Set_Scroll_Rate -- Sets the scroll rate as specified. * + * OptionsClass::Set_Shuffle -- Controls the play shuffle setting. * + * OptionsClass::Set_Sound_Volume -- Sets the sound effects volume level. * + * OptionsClass::Set_Tint -- Sets the tint setting. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "options.h" + + +/*********************************************************************************************** + * OptionsClass::OptionsClass -- The default constructor for the options class. * + * * + * This is the constructor for the options class. It handles setting up all the globals * + * necessary for the options. This includes setting them to their default state. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/21/1994 JLB : Created. * + *=============================================================================================*/ +OptionsClass::OptionsClass(void) +{ + GameSpeed = TIMER_SECOND / TICKS_PER_SECOND; + ScrollRate = TIMER_SECOND / TICKS_PER_SECOND; + Volume = 0xE0; + ScoreVolume = 0x90; + Contrast = 0x80; + Color = 0x80; + Contrast = 0x80; + Tint = 0x80; + Brightness = 0x80; + AutoScroll = true; +#if (GERMAN | FRENCH) + IsDeathAnnounce = true; +#else + IsDeathAnnounce = false; +#endif + IsScoreRepeat = false; + IsScoreShuffle = false; + IsFreeScroll = false; +} + + +/*********************************************************************************************** + * OptionsClass::One_Time -- This performs any one time initialization for the options class. * + * * + * This routine should be called only once and it will perform any inializations for the * + * options class that is needed. This may include things like file loading and memory * + * allocation. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Only call this routine once. * + * * + * HISTORY: * + * 07/21/1994 JLB : Created. * + *=============================================================================================*/ +void OptionsClass::One_Time(void) +{ + Set_Score_Vol(ScoreVolume); +} + + +/*********************************************************************************************** + * OptionsClass::Process -- Handles all the options graphic interface. * + * * + * This routine is the main control for the visual representation of the options * + * screen. It handles the visual overlay and the player input. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/21/1994 JLB : Created. * + *=============================================================================================*/ +void OptionsClass::Process(void) +{ +} + + +/*********************************************************************************************** + * OptionsClass::Set_Shuffle -- Controls the play shuffle setting. * + * * + * This routine will control the score shuffle flag. The setting to use is provided as * + * a parameter. When shuffling is on, the score play order is scrambled. * + * * + * INPUT: on -- Should the shuffle option be activated? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void OptionsClass::Set_Shuffle(int on) +{ + IsScoreShuffle = on; +} + + +/*********************************************************************************************** + * OptionsClass::Set_Repeat -- Controls the score repeat option. * + * * + * This routine is used to control whether scores repeat or not. The setting to use for * + * the repeat flag is provided as a parameter. * + * * + * INPUT: on -- Should the scores repeat? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void OptionsClass::Set_Repeat(int on) +{ + IsScoreRepeat = on; +} + + +/*********************************************************************************************** + * OptionsClass::Set_Score_Volume -- Sets the global score volume to that specified. * + * * + * This routine will set the global score volume to the value specified. The value ranges * + * from zero to 255. * + * * + * INPUT: volume -- The new volume setting to use for scores. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void OptionsClass::Set_Score_Volume(int volume) +{ + volume = Bound(volume, 0, 255); + ScoreVolume = volume; + Set_Score_Vol(ScoreVolume); +} + + +/*********************************************************************************************** + * OptionsClass::Set_Sound_Volume -- Sets the sound effects volume level. * + * * + * This routine will set the sound effect volume level as indicated. It can generate a * + * sound effect for feedback purposes if desired. The volume setting can range from zero * + * to 255. The value of 255 is the loudest. * + * * + * INPUT: volume -- The volume setting to use for the new value. 0 to 255. * + * * + * feedback -- Should a feedback sound effect be generated? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void OptionsClass::Set_Sound_Volume(int volume, int feedback) +{ + volume = Bound(volume, 0, 255); + Volume = volume; + if (feedback) { + Sound_Effect(VOC_BLEEPY3, NULL); + } +} + + +/*********************************************************************************************** + * OptionsClass::Set_Brightness -- Sets the brightness level to that specified. * + * * + * This routine will set the current brightness level to the value specified. This value * + * can range from zero to 255, with 128 being the normal (default) brightness level. * + * * + * INPUT: brightness -- The brightness level to set as current. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void OptionsClass::Set_Brightness(int brightness) +{ + Brightness = 0x40 + Fixed_To_Cardinal(0x80, brightness); + Adjust_Palette(OriginalPalette, GamePalette, Brightness, Color, Tint, Contrast); + if (InMainLoop){ + Set_Palette(GamePalette); + } +} + + +/*********************************************************************************************** + * OptionsClass::Get_Brightness -- Fetches the current brightness setting. * + * * + * This routine will fetch the current setting for the brightness level. The value ranges * + * from zero to 255, with 128 being the normal (default) value. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the current brightness setting. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +int OptionsClass::Get_Brightness(void) const +{ + return(Cardinal_To_Fixed(0x80, Brightness-0x40)); +} + + +/*********************************************************************************************** + * OptionsClass::Set_Color -- Sets the color to the value specified. * + * * + * This routine will set the color value to that specified. The value specified can range * + * from zero to 255. The value of 128 is the normal default color setting. * + * * + * INPUT: color -- The new color value to set as current. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void OptionsClass::Set_Color(int color) +{ + Color = color; + Adjust_Palette(OriginalPalette, GamePalette, Brightness, Color, Tint, Contrast); + if (InMainLoop){ + Set_Palette(GamePalette); + } +} + + +/*********************************************************************************************** + * OptionsClass::Get_Color -- Fetches the current color setting. * + * * + * This routine will fetch the current color setting. This value ranges from zero to * + * 255. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the current color setting. The value of 128 is the normal (default) * + * color setting. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +int OptionsClass::Get_Color(void) const +{ + return(Color); +} + + +/*********************************************************************************************** + * OptionsClass::Set_Contrast -- Sets the contrast to the value specified. * + * * + * This routine will set the constrast to the setting specified. This setting ranges from * + * zero to 255. The value o 128 is the normal default value. * + * * + * INPUT: contrast -- The constrast setting to make as the current setting. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void OptionsClass::Set_Contrast(int contrast) +{ + Contrast = 0x40 + Fixed_To_Cardinal(0x80, contrast); + Adjust_Palette(OriginalPalette, GamePalette, Brightness, Color, Tint, Contrast); + if (InMainLoop){ + Set_Palette(GamePalette); + } +} + + +/*********************************************************************************************** + * OptionsClass::Get_Contrast -- Gets the current contrast setting. * + * * + * This routine will get the current contrast setting. The value returned is in the range * + * of zero to 255. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the current contrast setting. A setting of 128 is the normal default value.* + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +int OptionsClass::Get_Contrast(void) const +{ + return(Cardinal_To_Fixed(0x80, Contrast-0x40)); +} + + +/*********************************************************************************************** + * OptionsClass::Set_Tint -- Sets the tint setting. * + * * + * This routine will change the current tint setting according to the value specified. * + * * + * INPUT: tint -- The desired tint setting. This value ranges from zero to 255. * + * * + * OUTPUT: none * + * * + * WARNINGS: The value of 128 is the default (normal) tint setting. * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void OptionsClass::Set_Tint(int tint) +{ + Tint = tint; + Adjust_Palette(OriginalPalette, GamePalette, Brightness, Color, Tint, Contrast); + if (InMainLoop){ + Set_Palette(GamePalette); + } +} + + +/*********************************************************************************************** + * OptionsClass::Get_Tint -- Fetches the current tint setting. * + * * + * This fetches the current tint setting. The value is returned as a number between * + * zero and 255. This has been adjusted for the valid range allowed. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the current tint setting. Normal tint setting is 128. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +int OptionsClass::Get_Tint(void) const +{ + return(Tint); +} + + +/*********************************************************************************************** + * OptionsClass::Adjust_Palette -- Adjusts the palette according to the settings specified. * + * * + * This routine is used to adjust the palette according to the settings provided. It is * + * used by the options class to monkey with the palette. * + * * + * INPUT: oldpal -- Pointer to the original (unmodified) palette. * + * * + * newpal -- The new palette to create according to the settings provided. * + * * + * brightness -- The brightness level (0..255). * + * * + * color -- The color level (0..255). * + * * + * tint -- The tint (hue) level (0..255). * + * * + * contrast -- The contrast level (0..255). * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/21/1994 JLB : Created. * + *=============================================================================================*/ +void OptionsClass::Adjust_Palette(void *oldpal, void *newpal, unsigned char brightness, unsigned char color, unsigned char tint, unsigned char contrast) const +{ + int index; + unsigned h,s,v; + unsigned r,g,b; + + if (!oldpal || !newpal) return; + + /* + ** Adjust for palette. + */ + for (index = 0; index < 256; index++) { + if (/*index == LTGREEN ||*/ index == 255) { + memcpy(&((char*)newpal)[index*3], &((char*)oldpal)[index*3], 3); + } else { + r = ((char*)oldpal)[(index*3)+0]; + g = ((char*)oldpal)[(index*3)+1]; + b = ((char*)oldpal)[(index*3)+2]; + Convert_RGB_To_HSV(r, g, b, &h, &s, &v); + + /* + ** Adjust contrast by moving the value toward the center according to the + ** percentage indicated. + */ + int temp; + + temp = (v * brightness) / 0x80; // Brightness + temp = Bound(temp, 0, 0xFF); + v = temp; + temp = (((((int)v) - 0x80) * contrast) / 0x80) + 0x80; // Contrast + temp = Bound(temp, 0, 0xFF); + v = temp; + temp = (s * color) / 0x80; // Color + temp = Bound(temp, 0, 0xFF); + s = temp; + temp = (h * tint) / 0x80; // Tint + temp = Bound(temp, 0, 0xFF); + h = temp; + Convert_HSV_To_RGB(h, s, v, &r, &g, &b); + ((char*)newpal)[(index*3)+0] = r; + ((char*)newpal)[(index*3)+1] = g; + ((char*)newpal)[(index*3)+2] = b; + } + } +} + + +/*********************************************************************************************** + * OptionsClass::Load_Settings -- reads options settings from the INI file * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +void OptionsClass::Load_Settings (void) +{ + char *buffer; // INI staging buffer pointer. + + /* + ** Fetch working pointer to the INI staging buffer. Make sure that the buffer + ** is cleared out before proceeding. (Don't use the HidPage for this, since + ** the HidPage may be needed for various uncompressions during the INI + ** parsing.) + */ + buffer = (char *)_ShapeBuffer; + memset(buffer, '\0', _ShapeBufferSize); + + /* + ** Create filename and read the file. + */ + CCFileClass file ("CONQUER.INI"); + if (!file.Is_Available()) { + return; + } else { + file.Read(buffer, _ShapeBufferSize-1); + } + file.Close(); + + /* + ** Read in the Options values + */ + GameSpeed = WWGetPrivateProfileInt("Options", "GameSpeed", 4, buffer); + ScrollRate = WWGetPrivateProfileInt("Options", "ScrollRate", 4, buffer); + Set_Brightness(WWGetPrivateProfileInt("Options", "Brightness", 0x80, buffer)); + Set_Sound_Volume(WWGetPrivateProfileInt("Options", "Volume", 0xA0, buffer),false); + Set_Score_Volume(WWGetPrivateProfileInt("Options", "ScoreVolume", 0xFF, buffer)); + Set_Contrast(WWGetPrivateProfileInt("Options", "Contrast", 0x80, buffer)); + Set_Color(WWGetPrivateProfileInt("Options", "Color", 0x80, buffer)); + Set_Tint(WWGetPrivateProfileInt("Options", "Tint", 0x80, buffer)); + AutoScroll = WWGetPrivateProfileInt("Options", "AutoScroll", 1, buffer); + Set_Repeat(WWGetPrivateProfileInt("Options", "IsScoreRepeat", 0, buffer)); + Set_Shuffle(WWGetPrivateProfileInt("Options", "IsScoreShuffle", 0, buffer)); + IsDeathAnnounce = WWGetPrivateProfileInt("Options", "DeathAnnounce", 0, buffer); + IsFreeScroll = WWGetPrivateProfileInt("Options", "FreeScrolling", 0, buffer); + SlowPalette = WWGetPrivateProfileInt("Options", "SlowPalette", 1, buffer); + + char workbuf[128]; + + /* + ** Check for and possible enable true object names. + */ + WWGetPrivateProfileString("Options", "TrueNames", "", workbuf, sizeof(workbuf), buffer); + if (Obfuscate(workbuf) == PARM_TRUENAME) { + Special.IsNamed = true; + } + + /* + ** Enable 6 player games if special flag is detected. + */ + WWGetPrivateProfileString("Options", "Players", "", workbuf, sizeof(workbuf), buffer); + if (Obfuscate(workbuf) == PARM_6PLAYER) { + MPlayerMax = 6; + } + + /* + ** Enable three point turning logic as indicated. + */ + WWGetPrivateProfileString("Options", "Rotation", "", workbuf, sizeof(workbuf), buffer); + if (Obfuscate(workbuf) == PARM_3POINT) { + Special.IsThreePoint = true; + } + + /* + ** Allow purchase of the helipad separately from the helicopter. + */ + WWGetPrivateProfileString("Options", "Helipad", "", workbuf, sizeof(workbuf), buffer); + if (Obfuscate(workbuf) == PARM_HELIPAD) { + Special.IsSeparate = true; + } + + /* + ** Allow the MCV to undeploy rather than sell. + */ + WWGetPrivateProfileString("Options", "MCV", "", workbuf, sizeof(workbuf), buffer); + if (Obfuscate(workbuf) == PARM_MCV) { + Special.IsMCVDeploy = true; + } + + /* + ** Allow disabling of building bibs so that tigher building packing can occur. + */ + WWGetPrivateProfileString("Options", "Bibs", "", workbuf, sizeof(workbuf), buffer); + if (Obfuscate(workbuf) == PARM_BIB) { + Special.IsRoad = true; + } + + /* + ** Allow targeting of trees without having to hold down the shift key. + */ + WWGetPrivateProfileString("Options", "TreeTarget", "", workbuf, sizeof(workbuf), buffer); + if (Obfuscate(workbuf) == PARM_TREETARGET) { + Special.IsTreeTarget = true; + } + + /* + ** Allow infantry to fire while moving. Attacker gets advantage with this flag. + */ + WWGetPrivateProfileString("Options", "Combat", "", workbuf, sizeof(workbuf), buffer); + if (Obfuscate(workbuf) == PARM_COMBAT) { + Special.IsDefenderAdvantage = false; + } + + /* + ** Allow custom scores. + */ + WWGetPrivateProfileString("Options", "Scores", "", workbuf, sizeof(workbuf), buffer); + if (Obfuscate(workbuf) == PARM_SCORE) { + Special.IsVariation = true; + } + + /* + ** Smarter self defense logic. Tanks will try to run over adjacent infantry. Buildings + ** will automatically return fire if they are fired upon. Infantry will run from an + ** incoming explosive (grenade or napalm) or damage that can't be directly addressed. + */ + WWGetPrivateProfileString("Options", "CombatIQ", "", workbuf, sizeof(workbuf), buffer); + if (Obfuscate(workbuf) == PARM_IQ) { + Special.IsSmartDefense = true; + Special.IsScatter = true; + } + + /* + ** Enable the infantry squish marks when run over by a vehicle. + */ + WWGetPrivateProfileString("Options", "Overrun", "", workbuf, sizeof(workbuf), buffer); + if (Obfuscate(workbuf) == PARM_SQUISH) { + Special.IsGross = true; + } + + /* + ** Enable the human generated sound effects. + */ + WWGetPrivateProfileString("Options", "Sounds", "", workbuf, sizeof(workbuf), buffer); + if (Obfuscate(workbuf) == PARM_HUMAN) { + Special.IsJuvenile = true; + } + + /* + ** Scrolling is disabled over the tabs with this option. + */ + WWGetPrivateProfileString("Options", "Scrolling", "", workbuf, sizeof(workbuf), buffer); + if (Obfuscate(workbuf) == PARM_SCROLLING) { + Special.IsScrollMod = true; + } +} + + +/*********************************************************************************************** + * OptionsClass::Save_Settings -- writes options settings to the INI file * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ +void OptionsClass::Save_Settings (void) +{ + char * buffer; // INI staging buffer pointer. + CCFileClass file; + + /* + ** Get a working pointer to the INI staging buffer. Make sure that the buffer + ** starts cleared out of any data. + */ + buffer = (char *)_ShapeBuffer; + memset(buffer, '\0', _ShapeBufferSize); + + file.Set_Name("CONQUER.INI"); + if (file.Is_Available()) { + file.Read(buffer, _ShapeBufferSize-1); + } + + /* + ** Save Options settings + */ + WWWritePrivateProfileInt("Options", "GameSpeed", GameSpeed, buffer); + WWWritePrivateProfileInt("Options", "ScrollRate", ScrollRate, buffer); + WWWritePrivateProfileInt("Options", "Brightness", Brightness, buffer); + WWWritePrivateProfileInt("Options", "Volume", Volume, buffer); + WWWritePrivateProfileInt("Options", "ScoreVolume", ScoreVolume, buffer); + WWWritePrivateProfileInt("Options", "Contrast", Contrast, buffer); + WWWritePrivateProfileInt("Options", "Color", Color, buffer); + WWWritePrivateProfileInt("Options", "Tint", Tint, buffer); + WWWritePrivateProfileInt("Options", "AutoScroll", AutoScroll, buffer); + WWWritePrivateProfileInt("Options", "IsScoreRepeat", IsScoreRepeat, buffer); + WWWritePrivateProfileInt("Options", "IsScoreShuffle", IsScoreShuffle, buffer); + WWWritePrivateProfileInt("Options", "DeathAnnounce", IsDeathAnnounce, buffer); + WWWritePrivateProfileInt("Options", "FreeScrolling", IsFreeScroll, buffer); + + /* + ** Write the INI data out to a file. + */ + file.Write(buffer,strlen(buffer)); +} + + +/*********************************************************************************************** + * OptionsClass::Set -- Sets options based on current settings * + * * + * Use this routine to adjust the palette or sound settings after a fresh scenario load. * + * It assumes the values needed are already loaded into OptionsClass. * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 06/24/1995 BRR : Created. * + *=============================================================================================*/ +void OptionsClass::Set(void) +{ + Set_Brightness(Brightness); + Set_Contrast(Contrast); + Set_Color(Color); + Set_Tint(Tint); + Set_Sound_Volume(Volume,false); + Set_Score_Volume(ScoreVolume); + Set_Repeat(IsScoreRepeat); + Set_Shuffle(IsScoreShuffle); +} + + +/*********************************************************************************************** + * OptionsClass::Normalize_Delay -- Normalizes delay factor to keep rate constant. * + * * + * This routine is used to adjust delay factors that MUST be synchronized on all machines * + * but should maintain a speed as close to constant as possible. Building animations are * + * a good example of this. * + * * + * INPUT: delay -- The normal delay factor. * + * * + * OUTPUT: Returns with the delay to use that has been modified so that a reasonably constant * + * rate will result. * + * * + * WARNINGS: This calculation is crude due to the coarse resolution that a 1/15 second timer * + * allows. * + * * + * Use of this routine ASSUMES that the GameSpeed is synchronized on all machines. * + * * + * HISTORY: * + * 06/18/1995 JLB : Created. * + * 06/30/1995 JLB : Handles low values in a more consistent manner. * + *=============================================================================================*/ +int OptionsClass::Normalize_Delay(int delay) const +{ + static int _adjust[][8] = { + {2,2,1,1,1,1,1,1}, + {3,3,3,2,2,2,1,1}, + {5,4,4,3,3,2,2,1}, + {7,6,5,4,4,4,3,2} + }; + if (delay) { + if (delay < 5) { + delay = _adjust[delay-1][GameSpeed]; + } else { + delay = ((delay * 8) / (GameSpeed+1)); + } + } + return(delay); +} + + + +void OptionsClass::Fixup_Palette(void) const +{ + Adjust_Palette(OriginalPalette, GamePalette, Brightness, Color, Tint, Contrast); +} + + +int OptionsClass::Normalize_Sound(int volume) const +{ + return(Fixed_To_Cardinal(volume, Volume)); +} diff --git a/OPTIONS.H b/OPTIONS.H new file mode 100644 index 0000000..b363965 --- /dev/null +++ b/OPTIONS.H @@ -0,0 +1,104 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\options.h_v 2.18 16 Oct 1995 16:46:20 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : OPTIONS.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : June 8, 1994 * + * * + * Last Update : June 8, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef OPTIONS_H +#define OPTIONS_H + +class OptionsClass { + public: + enum { + MAX_SCROLL_SETTING=7, + MAX_SPEED_SETTING=7 + }; + + OptionsClass(void); + + void One_Time(void); + void Process(void); + + void Fixup_Palette(void) const; + void Set_Shuffle(int on); + void Set_Repeat(int on); + void Set_Score_Volume(int volume); + void Set_Sound_Volume(int volume, int feedback); + void Set_Brightness(int brightness); + int Get_Brightness(void) const; + void Set_Color(int color); + int Get_Color(void) const; + void Set_Contrast(int contrast); + int Get_Contrast(void) const; + void Set_Tint(int tint); + int Get_Tint(void) const; + int Normalize_Delay(int delay) const; + int Normalize_Sound(int volume) const; + + /* + ** File I/O routines + */ + void Load_Settings(void); + void Save_Settings(void); + + void Set(void); + + /* + ** This is actually the delay between game frames expressed as 1/60 of + ** a second. The default value is 4 (1/15 second). + */ + unsigned int GameSpeed; + + int ScrollRate; // Distance to scroll. + unsigned char Brightness; + unsigned char Volume; // Volume for sound effects. + unsigned char ScoreVolume; // Volume for scores. + unsigned char Contrast; // Value + unsigned char Color; // Saturation + unsigned char Tint; // Hue + unsigned AutoScroll:1; // Does map autoscroll? + unsigned IsScoreRepeat:1; // Score should repeat? + unsigned IsScoreShuffle:1; // Score list should shuffle? + unsigned IsDeathAnnounce:1;// Announce enemy deaths? + unsigned IsFreeScroll:1; // Allow free direction scrolling? + + protected: + + void Adjust_Palette(void *oldpal, void *newpal, unsigned char brightness, unsigned char color, unsigned char tint, unsigned char contrast) const; + + private: +}; + + +#endif diff --git a/OPTIONS.LNT b/OPTIONS.LNT new file mode 100644 index 0000000..46e6057 --- /dev/null +++ b/OPTIONS.LNT @@ -0,0 +1,26 @@ +// Please note -- this is a representative set of error suppression +// options. Please adjust to suit your own policies +// See PC-lint for C/C++ manual (chapter LIVING WITH LINT) +// for further details. + +-e502 -e713 -e737 -eau // don't report on signed/unsigned mismatches +-e734 // allow sub-integer loss of information +-e701 -e703 // shifting int left is OK +-e537 // don't care about repeated include file +-e641 // converting enum to int is ok +-e1042 // operator ++/-- don't need class parameters +-e963 -e763 // redundant declarations are ok +-e1712 // no default constructor defined is ok +-e1704 // private constructors are ok +-e534 // ignoring return value is ok +-e732 // going from signed to unsigned parameter is ok +-e1411 // functions hiding base functions is ok +-e788 // switch with default doesn't need all values specified +-e655 -e656 // compatable enum bit and arithmetic operations are ok +-e1542 // members possibly not initialized isn't a valid warning +-e522 // calling 'new' without assignment isn't always an error + + +-e1401 // uninitialized by constructor warning disabled. + + diff --git a/OVERLAY.CPP b/OVERLAY.CPP new file mode 100644 index 0000000..5d5dc30 --- /dev/null +++ b/OVERLAY.CPP @@ -0,0 +1,429 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\overlay.cpv 2.17 16 Oct 1995 16:50:44 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : OVERLAY.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 17, 1994 * + * * + * Last Update : July 24, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * OverlayClass::Read_INI -- Reads the overlay data from an INI file. * + * OverlayClass::Write_INI -- Writes the overlay data to an INI file. * + * OverlayClass::delete -- Returns a overlay object to the pool. * + * OverlayClass::Init -- Resets the overlay object system. * + * OverlayClass::new -- Allocates a overlay object from pool * + * OverlayClass::OverlayClass -- Overlay object constructor. * + * OverlayClass::Mark -- Marks the overlay down on the map. * + * OverlayClass::Validate -- validates overlay * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "overlay.h" + +/* +** This contains the value of the Virtual Function Table Pointer +*/ +void * OverlayClass::VTable; + +HousesType OverlayClass::ToOwn = HOUSE_NONE; + +OverlayClass::OverlayClass(void) : Class(0) {ToOwn = HOUSE_NONE;}; + + +/*********************************************************************************************** + * OverlayClass::Validate -- validates overlay * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int OverlayClass::Validate(void) const +{ + int num; + + num = Overlays.ID(this); + if (num < 0 || num >= OVERLAY_MAX) { + Validate_Error("OVERLAY"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*********************************************************************************************** + * OverlayClass::Init -- Resets the overlay object system. * + * * + * This routine resets the overlay object system. It is called * + * prior to loading a new scenario. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/24/1994 JLB : Created. * + *=============================================================================================*/ +void OverlayClass::Init(void) +{ + OverlayClass *ptr; + + Overlays.Free_All(); + + ptr = new OverlayClass(); + VTable = ((void **)(((char *)ptr) + sizeof(AbstractClass) - 4))[0]; + delete ptr; +} + + +/*********************************************************************************************** + * OverlayClass::new -- Allocates a overlay object from pool * + * * + * This routine is used to allocate a overlay object from the * + * overlay object pool. * + * * + * INPUT: size -- The size of a overlay object (not used). * + * * + * OUTPUT: Returns with a pointer to an available overlay object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/17/1994 JLB : Created. * + *=============================================================================================*/ +void * OverlayClass::operator new(size_t ) +{ + void * ptr = Overlays.Allocate(); + if (ptr) { + ((OverlayClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +/*********************************************************************************************** + * OverlayClass::delete -- Returns a overlay object to the pool. * + * * + * This routine will return a overlay object to the overlay object * + * pool. A overlay so returned is available for allocation again. * + * * + * INPUT: ptr -- Pointer to the object to be returned. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/17/1994 JLB : Created. * + *=============================================================================================*/ +void OverlayClass::operator delete(void *ptr) +{ + if (ptr) { + ((OverlayClass *)ptr)->IsActive = false; + } + Overlays.Free((OverlayClass *)ptr); +} + + +/*********************************************************************************************** + * OverlayClass::OverlayClass -- Overlay object constructor. * + * * + * This is the constructor for a overlay object. * + * * + * INPUT: type -- The overlay object this is to become. * + * * + * pos -- The position on the map to place the object. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/17/1994 JLB : Created. * + *=============================================================================================*/ +OverlayClass::OverlayClass(OverlayType type, CELL pos, HousesType house) : + Class(&OverlayTypeClass::As_Reference(type)) +{ + if (pos != -1) { + ToOwn = house; + Unlimbo(Cell_Coord(pos)); + ToOwn = HOUSE_NONE; + } +} + + +/*********************************************************************************************** + * OverlayClass::Mark -- Marks the overlay down on the map. * + * * + * This routine will place the overlay onto the map. The overlay object is deleted by this * + * operation. The map is updated to reflect the presence of the overlay. * + * * + * INPUT: mark -- The type of marking to perform. Only MARK_DOWN is supported. * + * * + * OUTPUT: bool; Was the overlay successfully marked? Failure occurs if it is not being * + * marked down. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + * 12/23/1994 JLB : Checks low level legality before proceeding. * + *=============================================================================================*/ +bool OverlayClass::Mark(MarkType mark) +{ + Validate(); + if (ObjectClass::Mark(mark)) { + if (mark == MARK_DOWN) { + CELL cell = Coord_Cell(Coord); + CellClass * cellptr = &Map[cell]; + + /* + ** Road placement occurs in two steps. First the foundation is placed, but only + ** on buildable terrain. Second, the road is completed, but only if the foundation + ** was previously placed. + */ + if (*this == OVERLAY_ROAD) { + if ((cellptr->Overlay == OVERLAY_ROAD && cellptr->OverlayData == 0) || + (cellptr->Overlay == OVERLAY_NONE && cellptr->Is_Generally_Clear())) { + + if (cellptr->Overlay == OVERLAY_ROAD) { + cellptr->OverlayData = 1; + } else { + cellptr->OverlayData = 0; + } + cellptr->Overlay = Class->Type; + cellptr->Redraw_Objects(); + } + } else { + + /* + ** Walls have special logic when they are marked down. + */ + if (Class->IsWall) { + if (cellptr->Is_Generally_Clear() && cellptr->Overlay != OVERLAY_FLAG_SPOT) { + cellptr->Overlay = Class->Type; + cellptr->OverlayData = 0; + cellptr->Redraw_Objects(); + cellptr->Wall_Update(); + + /* + ** Flag ownership of the cell if the 'global' ownership flag indicates that this + ** is necessary for the overlay. + */ + if (ToOwn != HOUSE_NONE) { + cellptr->Owner = ToOwn; + } + + } else { + delete this; + return(false); + } + } else { + if ((cellptr->Overlay == OVERLAY_NONE || cellptr->Overlay == OVERLAY_SQUISH) && !cellptr->Cell_Terrain() && Ground[cellptr->Land_Type()].Build) { + + /* + ** Increment the global crate counter. This is used to regulate + ** the crate generation. + */ + if (Class->IsCrate) CrateCount++; + + /* + ** Don't show the squish unless the gross flag is active. + */ + if (!Special.IsGross && Class->Type != OVERLAY_SQUISH) { + cellptr->Overlay = Class->Type; + cellptr->OverlayData = 0; + } + cellptr->Redraw_Objects(); + if (Class->Land == LAND_TIBERIUM) { + cellptr->OverlayData = 1; + cellptr->Tiberium_Adjust(); + } else { + if (*this == OVERLAY_CONCRETE) { + CELL newcell; + + /* + ** Smudges go away when concrete is laid down. + */ + cellptr->Smudge = SMUDGE_NONE; + cellptr->SmudgeData = 0; + cellptr->Concrete_Calc(); + + /* + ** Possibly add concrete to adjacent cells depending on whether this + ** concrete is in an odd or even row. + */ + if (Cell_X(cell) & 0x01) { + newcell = Adjacent_Cell((CELL)(cellptr->Cell_Number()), FACING_W); + } else { + newcell = Adjacent_Cell((CELL)(cellptr->Cell_Number()), FACING_E); + } + if (Map[newcell].Overlay != OVERLAY_CONCRETE) { + Class->Create_And_Place(newcell); + } + + /* + ** The display attributes must be recalculated for all adjacent + ** cells since their shape can be altered by the presence of + ** concrete at this location. + */ + static FacingType _face[4] = {FACING_N, FACING_E, FACING_S, FACING_W}; + + for (int index = 0; index < (sizeof(_face)/sizeof(_face[0])); index++) { + cellptr->Adjacent_Cell(_face[index]).Concrete_Calc(); + } + } + } + } + } + + /* + ** ***** Is this really needed? + */ + cellptr->Recalc_Attributes(); + } + delete this; + return(true); + } + } + return(false); +} + + +/*********************************************************************************************** + * OverlayClass::Read_INI -- Reads the overlay data from an INI file. * + * * + * This routine is used to load a scenario's overlay data. The overlay objects are read * + * from the INI file and then created on the map. * + * * + * INPUT: buffer -- Pointer to the INI file staging buffer. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + * 07/24/1995 JLB : Specifically forbid manual crates in multiplayer scenarios. * + *=============================================================================================*/ +void OverlayClass::Read_INI(char *buffer) +{ + char *tbuffer; + int len; // Length of data in buffer. + char buf[128]; + + len = strlen(buffer) + 2; + tbuffer = buffer + len; + + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-len, buffer); + while (*tbuffer != '\0') { + CELL cell; + OverlayType classid; + + cell = atoi(tbuffer); + WWGetPrivateProfileString(INI_Name(), tbuffer, NULL, buf, sizeof(buf)-1, buffer); + classid = OverlayTypeClass::From_Name(strtok(buf, ",\n\r")); + + /* + ** Don't allow placement of crates in the multiplayer scenarios. + */ + if (classid != OVERLAY_NONE && (GameToPlay == GAME_NORMAL || !OverlayTypeClass::As_Reference(classid).IsCrate)) { + + /* + ** Don't allow placement of overlays on the top or bottom rows of + ** the map. + */ + if (cell >= MAP_CELL_W && cell <= MAP_CELL_TOTAL - MAP_CELL_W) { + new OverlayClass(classid, cell); + } + } + tbuffer += strlen(tbuffer)+1; + } +} + + +/*********************************************************************************************** + * OverlayClass::Write_INI -- Writes the overlay data to an INI file. * + * * + * This is used to output the overlay data to a scenario INI file. Typically, this is * + * only used by the scenario editor. * + * * + * INPUT: buffer -- Pointer to the INI file staging buffer. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + *=============================================================================================*/ +void OverlayClass::Write_INI(char *buffer) +{ + int index; + char uname[10]; + char buf[128]; + char *tbuffer; // Accumulation buffer of unit IDs. + + /* + ** First, clear out all existing unit data from the ini file. + */ + tbuffer = buffer + strlen(buffer) + 2; + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-strlen(buffer), buffer); + while (*tbuffer != '\0') { + WWWritePrivateProfileString(INI_Name(), tbuffer, NULL, buffer); + tbuffer += strlen(tbuffer)+1; + } + + /* + ** Write the unit data out. + */ + for (index = 0; index < MAP_CELL_TOTAL; index++) { + CellClass * cellptr = &Map[index]; + + if (cellptr->Overlay != OVERLAY_NONE) { + sprintf(uname, "%03d", index); + sprintf(buf, "%s", OverlayTypeClass::As_Reference(cellptr->Overlay).IniName); + WWWritePrivateProfileString(INI_Name(), uname, buf, buffer); + } + } +} diff --git a/OVERLAY.H b/OVERLAY.H new file mode 100644 index 0000000..657b536 --- /dev/null +++ b/OVERLAY.H @@ -0,0 +1,107 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\overlay.h_v 2.16 16 Oct 1995 16:44:50 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : OVERLAY.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 17, 1994 * + * * + * Last Update : May 17, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef OVERLAY_H +#define OVERLAY_H + +#include "object.h" +#include "type.h" + +/****************************************************************************** +** This class controls the overlay object. Overlay objects function congruously +** to carpet on a floor. They have no depth, but merely control the icon to be rendered +** as the cell's bottom most layer. +*/ +class OverlayClass : public ObjectClass +{ + public: + /*------------------------------------------------------------------- + ** Constructors and destructors. + */ + static void * operator new(size_t size); + static void operator delete(void *ptr); + OverlayClass(void); + OverlayClass(OverlayType type, CELL pos=-1, HousesType = HOUSE_NONE); + virtual ~OverlayClass(void) {if (GameActive) OverlayClass::Limbo();}; + operator OverlayType(void) const {return Class->Type;}; + virtual RTTIType What_Am_I(void) const {return RTTI_OVERLAY;}; + + static void Init(void); + + /* + ** File I/O. + */ + static void Read_INI(char *); + static void Write_INI(char *); + static char *INI_Name(void) {return "OVERLAY";}; + bool Load(FileClass & file); + bool Save(FileClass & file); + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /* + ** Virtual support functionality. + */ + virtual bool Mark(MarkType); + virtual ObjectTypeClass const & Class_Of(void) const {return *Class;}; + virtual void Draw_It(int , int , WindowNumberType ) {}; + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + private: + /* + ** This is used to control the marking process of the overlay. If this is + ** set to a valid house number, then the cell that the overlay is marked down + ** upon will be flagged as being owned by the specified house. + */ + static HousesType ToOwn; + + /* + ** This is a pointer to the overlay object's class. + */ + OverlayTypeClass const * const Class; + + /* + ** This contains the value of the Virtual Function Table Pointer + */ + static void * VTable; +}; + +#endif diff --git a/PACKET.CPP b/PACKET.CPP new file mode 100644 index 0000000..001e85d --- /dev/null +++ b/PACKET.CPP @@ -0,0 +1,463 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*************************************************************************** + * * + * Project Name : Westwood Auto Registration App * + * * + * File Name : PACKET.CPP * + * * + * Programmer : Philip W. Gorrow * + * * + * Start Date : 04/22/96 * + * * + * Last Update : April 24, 1996 [PWG] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * *PacketClass::Find_Field -- Finds a field if it exists in the packets * + * Get_Field -- Find specified name and returns data * + * PacketClass::~PacketClass -- destroys a packet class be freeing list * + * PacketClass::Add_Field -- Adds a FieldClass entry to head of packet li* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#include +#include +#include + +enum {false=0,true=1}; +typedef int bool; + +#include "packet.h" + + +/************************************************************************** + * PACKETCLASS::~PACKETCLASS -- destroys a packet class be freeing list * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * HISTORY: * + * 04/24/1996 PWG : Created. * + *========================================================================*/ +PacketClass::~PacketClass(void) +{ + FieldClass *current; + FieldClass *next; + // + // Loop through the entire field list and delete each entry. + // + for (current = Head; current; current = next) { + next = current->Next; + delete current; + } +} + + +/************************************************************************** + * PACKETCLASS::ADD_FIELD -- Adds a FieldClass entry to head of packet li * + * * + * INPUT: FieldClass * - a properly constructed field class entry. * + * * + * OUTPUT: none * + * * + * HISTORY: * + * 04/24/1996 PWG : Created. * + *========================================================================*/ +void PacketClass::Add_Field(FieldClass *field) +{ + field->Next = Head; + Head = field; +} + +/************************************************************************** + * PACKETCLASS::PACKETCLASS -- Creates a Packet object from a COMMS packe * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/22/1996 PWG : Created. * + *========================================================================*/ +PacketClass::PacketClass(char *curbuf) +{ + int remaining_size; + // + // Pull the size and packet ID out of the linear packet stream. + // + Size = *(unsigned short *)curbuf; + curbuf += sizeof (unsigned short); + Size = ntohs(Size); + ID = *(short *)curbuf; + curbuf += sizeof (short); + ID = ntohs(ID); + Head = NULL; + + // + // Calculate the remaining size so that we can loop through the + // packets and extract them. + // + remaining_size = Size - 4; + + // + // Loop through the linear packet until we run out of room and + // create a field for each. + // + while (remaining_size > 0) { + FieldClass *field = new FieldClass; + + // + // Copy the adjusted header into the buffer and then advance the buffer + // + memcpy(field, curbuf, FIELD_HEADER_SIZE); + curbuf += FIELD_HEADER_SIZE; + remaining_size -= FIELD_HEADER_SIZE; + + // + // Copy the data into the buffer + // + int size = ntohs(field->Size); + field->Data = new char[size]; + memcpy(field->Data, curbuf, size); + curbuf += size; + remaining_size -= size; + // + // Make sure we allow for the pad bytes. + // + int pad = (4 - (ntohs(field->Size) & 3)) & 3; + curbuf += pad; + remaining_size -= pad; + + // + // Convert the field back to the host format + // + field->Net_To_Host(); + + // + // Finally add the field to the field list in the packet + // structure. + // + Add_Field(field); + } +} + +/************************************************************************** + * CREATE_COMMS_PACKET -- Walks field list creating a packet * + * * + * INPUT: short - the id of the packet so the server can identify it * + * unsigned short & - the size of the packet returned here * + * * + * OUTPUT: void * pointer to the linear packet data * + * * + * WARNINGS: This routine allocates memory that the user is responsible * + * for freeing. * + * * + * HISTORY: * + * 04/22/1996 PWG : Created. * + *========================================================================*/ +char *PacketClass::Create_Comms_Packet(int &size) +{ + FieldClass *current; + + // + // Size starts at four because that is the size of the packet header. + // + size = 4; + + // + // Take a quick spin through and calculate the size of the packet we + // are building. + // + for (current = Head; current; current=current->Next) { + size += (unsigned short)FIELD_HEADER_SIZE; // add in packet header size + size += current->Size; // add in data size + size += (4 - (size & 3)) & 3; // add in pad value to dword align next packet + } + + // + // Now that we know the size allocate a buffer big enough to hold the + // packet. + // + char *retval = new char[size]; + char *curbuf = retval; + + // + // write the size into the packet header + // + *(unsigned short *)curbuf = (unsigned short)htons((unsigned short)size); + curbuf += sizeof (unsigned short); + *(short *)curbuf = htons(ID); + curbuf += sizeof (short); + + // + // Ok now that the actual header information has been written we need to write out + // field information. + // + for (current = Head; current; current = current->Next) { + // + // Temporarily convert the packet to net format (this saves alot of + // effort, and seems safe...) + // + current->Host_To_Net(); + + // + // Copy the adjusted header into the buffer and then advance the buffer + // + memcpy(curbuf, current, FIELD_HEADER_SIZE); + curbuf += FIELD_HEADER_SIZE; + + // + // Copy the data into the buffer and then advance the buffer + // + memcpy(curbuf, current->Data, ntohs(current->Size)); + curbuf += ntohs(current->Size); + + // + // Finally take care of any pad bytes by setting them to 0 + // + int pad = (4 - (ntohs(current->Size) & 3)) & 3; + + // + // If there is any pad left over, make sure you memset it + // to zeros, so it looks like a pad. + // + if (pad) { + memset(curbuf, 0, pad); + curbuf += pad; + } + + current->Net_To_Host(); + } + return(retval); +} + + +/************************************************************************** + * PACKETCLASS::FIND_FIELD -- Finds a field if it exists in the packets * + * * + * INPUT: char * - the id of the field we are looking for. * + * * + * OUTPUT: FieldClass * pointer to the field class * + * * + * HISTORY: * + * 04/23/1996 PWG : Created. * + *========================================================================*/ +FieldClass *PacketClass::Find_Field(char *id) +{ + for (FieldClass *current = Head; current; current = current->Next) { + if ( strncmp(id, current->ID, 4) == 0) + return current; + } + return NULL; +} + + +/************************************************************************** + * GET_FIELD -- Find specified name and returns data * + * * + * INPUT: char * - the id of the field that holds the data. * + * char & - the reference to store the data into * + * * + * OUTPUT: true if the field was found, false if it was not. * + * * + * WARNINGS: The data reference is not changed if the field is not * + * found. * + * * + * HISTORY: * + * 04/23/1996 PWG : Created. * + *========================================================================*/ +bool PacketClass::Get_Field(char *id, char &data) +{ + FieldClass *field = Find_Field(id); + if (field) { + data = *((char *)field->Data); + } + return((field) ? true : false); +} + + +/************************************************************************** + * GET_FIELD -- Find specified name and returns data * + * * + * INPUT: char * - the id of the field that holds the data. * + * unsigned char & - the reference to store the data into * + * * + * OUTPUT: true if the field was found, false if it was not. * + * * + * WARNINGS: The data reference is not changed if the field is not * + * found. * + * * + * HISTORY: * + * 04/23/1996 PWG : Created. * + *========================================================================*/ +bool PacketClass::Get_Field(char *id, unsigned char &data) +{ + FieldClass *field = Find_Field(id); + if (field) { + data = *((unsigned char *)field->Data); + } + return((field) ? true : false); +} + + +/************************************************************************** + * GET_FIELD -- Find specified name and returns data * + * * + * INPUT: char * - the id of the field that holds the data. * + * short & - the reference to store the data into * + * * + * OUTPUT: true if the field was found, false if it was not. * + * * + * WARNINGS: The data reference is not changed if the field is not * + * found. * + * * + * HISTORY: * + * 04/23/1996 PWG : Created. * + *========================================================================*/ +bool PacketClass::Get_Field(char *id, short &data) +{ + FieldClass *field = Find_Field(id); + if (field) { + data = *((short *)field->Data); + } + return((field) ? true : false); +} + + +/************************************************************************** + * GET_FIELD -- Find specified name and returns data * + * * + * INPUT: char * - the id of the field that holds the data. * + * unsigned short & - the reference to store the data into * + * * + * OUTPUT: true if the field was found, false if it was not. * + * * + * WARNINGS: The data reference is not changed if the field is not * + * found. * + * * + * HISTORY: * + * 04/23/1996 PWG : Created. * + *========================================================================*/ +bool PacketClass::Get_Field(char *id, unsigned short &data) +{ + FieldClass *field = Find_Field(id); + if (field) { + data = *((unsigned short *)field->Data); + } + return((field) ? true : false); +} + + +/************************************************************************** + * GET_FIELD -- Find specified name and returns data * + * * + * INPUT: char * - the id of the field that holds the data. * + * long & - the reference to store the data into * + * * + * OUTPUT: true if the field was found, false if it was not. * + * * + * WARNINGS: The data reference is not changed if the field is not * + * found. * + * * + * HISTORY: * + * 04/23/1996 PWG : Created. * + *========================================================================*/ +bool PacketClass::Get_Field(char *id, long &data) +{ + FieldClass *field = Find_Field(id); + if (field) { + data = *((long *)field->Data); + } + return((field) ? true : false); +} + +/************************************************************************** + * GET_FIELD -- Find specified name and returns data as a string * + * * + * INPUT: char * - the id of the field that holds the data. * + * char * - the string to store the data into * + * * + * OUTPUT: true if the field was found, false if it was not. * + * * + * WARNINGS: The string is not changed if the field is not found. It * + * is assumed that the string variabled specified by the * + * pointer is large enough to hold the data. * + * * + * HISTORY: * + * 04/23/1996 PWG : Created. * + *========================================================================*/ +bool PacketClass::Get_Field(char *id, char *data) +{ + FieldClass *field = Find_Field(id); + if (field) { + strcpy(data, (char *)field->Data); + } + return((field) ? true : false); +} + +/************************************************************************** + * GET_FIELD -- Find specified name and returns data * + * * + * INPUT: char * - the id of the field that holds the data. * + * unsigned long & - the reference to store the data into * + * * + * OUTPUT: true if the field was found, false if it was not. * + * * + * WARNINGS: The data reference is not changed if the field is not * + * found. * + * * + * HISTORY: * + * 04/23/1996 PWG : Created. * + *========================================================================*/ +bool PacketClass::Get_Field(char *id, unsigned long &data) +{ + FieldClass *field = Find_Field(id); + if (field) { + data = *((unsigned long *)field->Data); + } + return((field) ? true : false); +} + + +/************************************************************************** + * GET_FIELD -- Find specified name and returns data * + * * + * INPUT: char * - the id of the field that holds the data. * + * void * - the reference to store the data into * + * int - the length of the buffer passed in * + * * + * OUTPUT: true if the field was found, false if it was not. * + * * + * WARNINGS: The data reference is not changed if the field is not * + * found. * + * * + * HISTORY: * + * 6/4/96 4:46PM ST : Created * + *========================================================================*/ +bool PacketClass::Get_Field(char *id, void *data, int &length) +{ + FieldClass *field = Find_Field(id); + if (field) { + memcpy (data, field->Data, min(field->Size, length)); + length = (int) field->Size; + } + return((field) ? true : false); +} diff --git a/PACKET.H b/PACKET.H new file mode 100644 index 0000000..83870de --- /dev/null +++ b/PACKET.H @@ -0,0 +1,101 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*************************************************************************** + * * + * Project Name : Westwood Auto Registration App * + * * + * File Name : PACKET.H * + * * + * Programmer : Philip W. Gorrow * + * * + * Start Date : 04/19/96 * + * * + * Last Update : April 19, 1996 [PWG] * + * * + * This header defines the functions for the PacketClass. The packet * + * class is used to create a linked list of field entries which can be * + * converted to a linear packet in a COMMS API compatible format. * + * * + * Packets can be created empty and then have fields added to them or can * + * be created from an existing linear packet. * + * * + *-------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#ifndef __PACKET_H +#define __PACKET_H + + +#include "field.h" + +class PacketClass { + public: + PacketClass(short id = 0) + { + Size = 0; + ID = id; + Head = 0; + } + PacketClass(char *cur_buf); + ~PacketClass(void); + + // + // This function allows us to add a field to the start of the list. As the field is just + // a big linked list it makes no difference which end we add a member to. + // + void Add_Field(FieldClass *field); + + // + // These conveniance functions allow us to add a field directly to the list without + // having to worry about newing one first. + // + void Add_Field(char *field, char data) {Add_Field(new FieldClass(field, data));}; + void Add_Field(char *field, unsigned char data) {Add_Field(new FieldClass(field, data));}; + void Add_Field(char *field, short data) {Add_Field(new FieldClass(field, data));}; + void Add_Field(char *field, unsigned short data) {Add_Field(new FieldClass(field, data));}; + void Add_Field(char *field, long data) {Add_Field(new FieldClass(field, data));}; + void Add_Field(char *field, unsigned long data) {Add_Field(new FieldClass(field, data));}; + void Add_Field(char *field, char *data) {Add_Field(new FieldClass(field, data));}; + void Add_Field(char *field, void *data, int length) {Add_Field(new FieldClass(field, data, length));}; + + // + // These functions search for a field of a given name in the list and + // return the data via a reference value. + // + FieldClass *Find_Field(char *id); + bool Get_Field(char *id, char &data); + bool Get_Field(char *id, unsigned char &data); + bool Get_Field(char *id, short &data); + bool Get_Field(char *id, unsigned short &data); + bool Get_Field(char *id, long &data); + bool Get_Field(char *id, unsigned long &data); + bool Get_Field(char *id, char *data); + bool Get_Field(char *id, void *data, int &length); + + char *Create_Comms_Packet(int &size); + + private: + unsigned short Size; + short ID; + FieldClass *Head; + FieldClass *Current; +}; + + +#endif diff --git a/PAGFAULT.ASM b/PAGFAULT.ASM new file mode 100644 index 0000000..444c434 --- /dev/null +++ b/PAGFAULT.ASM @@ -0,0 +1,225 @@ +; +; Command & Conquer(tm) +; Copyright 2025 Electronic Arts Inc. +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; + +;*************************************************************************** +;** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S ** +;*************************************************************************** +;* * +;* Project Name : Library * +;* * +;* File Name : PAGFAULT.ASM * +;* * +;* Programmer : Julio R Jerez * +;* * +;* Date : April 25,1995 * +;* * +;*-------------------------------------------------------------------------* +;* Functions: * +; +; Here are prototypes for the routines defined within this module: +; VOID Install_Page_Fault_Handle (void) ; +; +; ---------------------------------------------------------------- + +IDEAL ; the product runs in ideal mode +P386 ; use 386 real mode instructions +MODEL USE32 FLAT +LOCALS ?? ; ?? is the symbol for a local +WARN ; generate all warnings we can +JUMPS ; optimize jumps if possible + +;--------------------------------------------------------------------------- +; Make some general equates for easy compatability +;--------------------------------------------------------------------------- +DPMI_INTR EQU 31h +PAGE_FAULT equ 0eh +DIVIDE_ERROR equ 00h +RESET_VIDEO_MODE equ -1 + +global C Install_Page_Fault_Handle : NEAR +global C Set_Video_Mode : NEAR +global C Remove_Mouse : NEAR +global C Remove_Keyboard_Interrupt : NEAR +global C Remove_Timer_Interrupt : NEAR + +DATASEG + Old_Page_Fault_handle DF ? + Page_Fault_SS DD ? + Page_Fault_ESP DD ? + + Old_Div_Error_handle DF ? + Div_Error_SS DD ? + Div_Error_ESP DD ? + + +CODESEG +;*************************************************************************** +;* INSTALL_PAGE_FAULT_HANDLE -- Installs new page fault and div Error * +;* handles. * +;* This function will install a new page fault handle and Div Error * +;* so in the event that we have a program crash these handles will * +;* remove all interrupts and then will chain to the default Exception * +;* Handle * +;* * +;* INPUT: none * +;* * +;* * +;* OUTPUT: none * +;* * +;* PROTO: VOID Install_Page_Fault_Handle( void); * +;* * +;* HISTORY: 04/25/96 Created * +;*=========================================================================* + PROC Install_Page_Fault_Handle C NEAR + USES eax,ebx,ecx,edx,esi,edi + + ; Install_Page_Fault_Handle + mov eax,0202h ; get address of exception handle + mov bl,PAGE_FAULT + int DPMI_INTR + jc ??exit ; not action is taken + + ; save addrees of default handle + mov [dword ptr Old_Page_Fault_handle],edx + mov [word ptr Old_Page_Fault_handle+4],cx + + ; redirect default handle to a new Page Fault Handle + mov eax,0203h + mov bl,PAGE_FAULT + mov cx,cs + lea edx,[Page_Fault_Handle] + int DPMI_INTR + + + ; Install_Divide_Error_Handle + mov eax,0202h ; get address of exception handle + mov bl,DIVIDE_ERROR + int DPMI_INTR + jc ??exit ; not action is taken + + ; save addrees of default fault handle + mov [dword ptr Old_Div_Error_handle],edx + mov [word ptr Old_Div_Error_handle+4],cx + + ; redirect default handle to a new Divede Handle + mov eax,0203h + mov bl,DIVIDE_ERROR + mov cx,cs + lea edx,[Divide_Error_Handle] + int DPMI_INTR + + ??exit: + + + ret + ENDP Install_Page_Fault_Handle + + +;*************************************************************************** +;* PAGE_FAULT_HANDLE -- This * +;* * +;* * +;* * +;* HISTORY: 04/25/96 Created * +;*=========================================================================* +PROC Page_Fault_Handle far + + ; preserve used registers + push eax + push ebx + ; save Page Fault satck frame + mov ax,ss + mov [Page_Fault_SS],eax + mov [Page_Fault_ESP],esp + + ; retrieve application original stack frame + mov eax , [ esp + ( 6 + 2 ) * 4 ] + mov ebx , [ esp + ( 7 + 2 ) * 4 ] + mov ss , bx + mov esp , eax + + ; set video mode to standard text mode + push RESET_VIDEO_MODE + call Set_Video_Mode + pop eax + call Remove_Mouse + call Remove_Keyboard_Interrupt + call Remove_Timer_Interrupt + + ; restore Page Fault stack frame + mov eax,[Page_Fault_SS] + mov ss , ax + mov esp, [Page_Fault_ESP] + + ; restore used registers and chain to default Page Fault Handle + pop ebx + pop eax + jmp [fword Old_Page_Fault_handle] + + ENDP Page_Fault_Handle + + + +;*************************************************************************** +;* Divide_Error -- * +;* * +;* * +;* * +;* HISTORY: 04/25/96 Created * +;*=========================================================================* +PROC Divide_Error_Handle far + + ; preserve used registers + push eax + push ebx + ; save Page Fault satck frame + mov ax,ss + mov [Div_Error_SS],eax + mov [Div_Error_ESP],esp + + ; retrieve application original stack frame + mov eax , [ esp + ( 6 + 2 ) * 4 ] + mov ebx , [ esp + ( 7 + 2 ) * 4 ] + mov ss , bx + mov esp , eax + + ; set video mode to standard text mode + push RESET_VIDEO_MODE + call Set_Video_Mode + pop eax + call Remove_Mouse + call Remove_Keyboard_Interrupt + call Remove_Timer_Interrupt + + ; restore Fault stack frame + mov eax,[Div_Error_SS] + mov ss , ax + mov esp, [Div_Error_ESP] + + ; restore used registers and chain to default Page Fault Handle + pop ebx + pop eax + jmp [fword Old_Div_Error_handle] + + ENDP Divide_Error_Handle + + +;*************************************************************************** +;* End of File. * +;*************************************************************************** +END diff --git a/PHONE.H b/PHONE.H new file mode 100644 index 0000000..c5cf5ed --- /dev/null +++ b/PHONE.H @@ -0,0 +1,69 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\phone.h_v 1.9 16 Oct 1995 16:47:58 JOE_BOSTIC $ */ +/*************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : PHONE.H * + * * + * Programmer : Bill R. Randolph * + * * + * Start Date : 04/28/95 * + * * + * Last Update : April 28, 1995 [BRR] * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef PHONE_H +#define PHONE_H + +/* +***************************** Class Declaration ***************************** +*/ +class PhoneEntryClass +{ + public: + enum PhoneEntryEnum { + PHONE_MAX_NAME = 21, + PHONE_MAX_NUM = 21 + }; + + PhoneEntryClass(void) {}; + ~PhoneEntryClass() {}; + + operator == (PhoneEntryClass &obj) + { return (memcmp (Name,obj.Name,strlen(Name))==0);} + operator != (PhoneEntryClass &obj) + { return (memcmp (Name,obj.Name,strlen(Name))!=0);} + operator > (PhoneEntryClass &obj) + { return (memcmp(Name, obj.Name, strlen(Name)) > 0);} + operator < (PhoneEntryClass &obj) + { return (memcmp(Name, obj.Name, strlen(Name)) < 0);} + operator >= (PhoneEntryClass &obj) + { return (memcmp(Name, obj.Name, strlen(Name)) >= 0);} + operator <= (PhoneEntryClass &obj) + { return (memcmp(Name, obj.Name, strlen(Name)) <= 0);} + + SerialSettingsType Settings; + char Name[ PHONE_MAX_NAME ]; // destination person's name + char Number[ PHONE_MAX_NUM ]; // phone # +}; + +#endif diff --git a/POWER.CPP b/POWER.CPP new file mode 100644 index 0000000..3b19570 --- /dev/null +++ b/POWER.CPP @@ -0,0 +1,462 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\power.cpv 2.18 16 Oct 1995 16:52:10 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : POWER.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/15/94 * + * * + * Last Update : August 7, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * PowerClass::AI -- Process the power bar logic. * + * PowerClass::Draw_It -- Renders the power bar graphic. * + * PowerClass::Init_Clear -- Clears all the power bar variables. * + * PowerClass::One_Time -- One time processing for the power bar. * + * PowerClass::PowerButtonClass::Action -- Handles the mouse over the power bar area. * + * PowerClass::PowerClass -- Default constructor for the power bar class. * + * PowerClass::Refresh_Cells -- Intercepts the redraw logic to see if sidebar to redraw too. * + * Power_Height -- Given a value figure where it falls on bar * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/* +** Points to the shape to use for the "desired" power level indicator. +*/ +void const * PowerClass::PowerShape; +void const * PowerClass::PowerBarShape; + +PowerClass::PowerButtonClass PowerClass::PowerButton; + + +/*********************************************************************************************** + * PowerClass::PowerClass -- Default constructor for the power bar class. * + * * + * This is the default constructor for the power bar class. It doesn't really do anything. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/20/1994 JLB : Created. * + *=============================================================================================*/ +PowerClass::PowerClass(void) +{ + IsToRedraw = false; + RecordedDrain = -1; + RecordedPower = -1; + DesiredDrainHeight = 0; + DesiredPowerHeight = 0; + DrainHeight = 0; + PowerHeight = 0; + PowerBounce = 0; + DrainBounce = 0; + DrainDir = 0; + PowerDir = 0; +} + + +/*********************************************************************************************** + * PowerClass::Init_Clear -- Clears all the power bar variables. * + * * + * This routine is called in preparation for the start of a scenario. The power bar is * + * initialized into the null state by this routine. As soon as the scenario starts, the * + * power bar will rise to reflect the actual power output and drain. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/07/1995 JLB : Created. * + *=============================================================================================*/ +void PowerClass::Init_Clear(void) +{ + RadarClass::Init_Clear(); + RecordedDrain = -1; + RecordedPower = -1; + DesiredDrainHeight = 0; + DesiredPowerHeight = 0; + DrainHeight = 0; + PowerHeight = 0; + PowerBounce = 0; + DrainBounce = 0; + DrainDir = 0; + PowerDir = 0; +} + + +/*********************************************************************************************** + * PowerClass::One_Time -- One time processing for the power bar. * + * * + * This routine is for code that truly only needs to be done once per game run. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +void PowerClass::One_Time(void) +{ + RadarClass::One_Time(); + + int factor = Get_Resolution_Factor(); + PowX = SeenBuff.Get_Width() - Map.RadWidth; + PowY = Map.RadY+Map.RadHeight + (13 << factor); + PowWidth = 8 << factor; + PowHeight = SeenBuff.Get_Height() - PowY; + PowLineSpace = 5 << factor; + PowLineWidth = PowWidth - 4; + + PowerButton.X = PowX; + PowerButton.Y = PowY; + PowerButton.Width = PowWidth-1; + PowerButton.Height = PowHeight; + + PowerShape = MixFileClass::Retrieve((factor)? "HPOWER.SHP" :"POWER.SHP"); + PowerBarShape = Hires_Retrieve("PWRBAR.SHP"); +} + + +/*********************************************************************************************** + * PowerClass::Draw_It -- Renders the power bar graphic. * + * * + * This routine will draw the power bar graphic to the LogicPage. * + * * + * INPUT: complete -- Should the power bar be redrawn even if it isn't specifically flagged * + * to do so? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/20/1994 JLB : Created. * + * 12/27/1994 JLB : Changes power bar color depending on amount of power. * + *=============================================================================================*/ +void PowerClass::Draw_It(bool complete) +{ + static int _modtable[]={ + 0, -1, 0, 1, 0, -1, -2, -1, 0, 1, 2, 1 ,0 + }; + int power_color; + + if (complete || IsToRedraw) { +// PowX = TacPixelX + TacWidth*ICON_PIXEL_W; // X position of upper left corner of power bar. + + if (LogicPage->Lock()){ + + if (Map.IsSidebarActive) { + IsToRedraw = false; + + /* + ** 1st get the height of the filled section of the power bar + */ + int bottom = PowY + PowHeight - 1; + int power_height = (PowerHeight == DesiredPowerHeight) ? PowerHeight + (_modtable[PowerBounce] * PowerDir) : PowerHeight; + int drain_height = (DrainHeight == DesiredDrainHeight) ? DrainHeight + (_modtable[DrainBounce] * DrainDir) : DrainHeight; + power_height = Bound(power_height, 0, PowHeight - 2); + drain_height = Bound(drain_height, 0, PowHeight - 2); + + /* + ** Create a clip region to draw the unfilled section of the bar + */ + WindowList[WINDOW_CUSTOM][WINDOWX] = 0; + WindowList[WINDOW_CUSTOM][WINDOWY] = 0; + WindowList[WINDOW_CUSTOM][WINDOWWIDTH] = SeenBuff.Get_Width(); + WindowList[WINDOW_CUSTOM][WINDOWHEIGHT] = bottom-power_height; + + /* + ** Draw the unfilled section + */ + CC_Draw_Shape(PowerBarShape, 0, PowX, PowY, WINDOW_CUSTOM, SHAPE_WIN_REL); + CC_Draw_Shape(PowerBarShape, 1 ,PowX, PowY+100, WINDOW_CUSTOM, SHAPE_WIN_REL); + + + /* + ** Set up the clip region for the filled section + */ + WindowList[WINDOW_CUSTOM][WINDOWY] = bottom-power_height; + WindowList[WINDOW_CUSTOM][WINDOWHEIGHT] = SeenBuff.Get_Height() - WindowList[WINDOW_CUSTOM][WINDOWY]; + + /* + ** What color is the filled section? + */ + if (power_height) { + power_color = 0; //green + + if (PlayerPtr->Drain > PlayerPtr->Power) { + power_color = 2; + } + if (PlayerPtr->Drain > (PlayerPtr->Power * 2)) { + power_color = 4; + } + + /* + ** Draw the filled section + */ + CC_Draw_Shape(PowerBarShape, 2+power_color, + PowX, + PowY - WindowList[WINDOW_CUSTOM][WINDOWY], + WINDOW_CUSTOM, + SHAPE_WIN_REL); + + CC_Draw_Shape(PowerBarShape, 3+power_color, + PowX, + PowY - WindowList[WINDOW_CUSTOM][WINDOWY] + 100, + WINDOW_CUSTOM, + SHAPE_WIN_REL); + } + + /* + ** Draw the power drain threshold marker. + */ + CC_Draw_Shape(PowerShape, 0, PowX, bottom - drain_height + 1, WINDOW_MAIN, SHAPE_NORMAL); + + } + LogicPage->Unlock(); + } + } + RadarClass::Draw_It(complete); +} + + +/*********************************************************************************************** + * PowerClass::AI -- Process the power bar logic. * + * * + * Use this routine to process the power bar logic. This consists of animation effects. * + * * + * INPUT: input -- The player input value to be consumed or ignored as appropriate. * + * * + * x,y -- Mouse coordinate parameters to use. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/20/1994 JLB : Created. * + * 12/31/1994 JLB : Uses mouse coordinate parameters. * + *=============================================================================================*/ +void PowerClass::AI(KeyNumType &input, int x, int y) +{ +// if (!IsActive) { +// IsActive = true; +// IsToRedraw = true; +// Flag_To_Redraw(false); +// } + + if (Map.IsSidebarActive /*IsActive*/) { + int olddrain = DrainHeight; + int oldpower = PowerHeight; + + + /* + ** If the recorded power value has changed we need to adjust for + ** it. + */ + if (PlayerPtr->Power != RecordedPower) { + DesiredPowerHeight = Power_Height(PlayerPtr->Power); + RecordedPower = PlayerPtr->Power; + PowerBounce = 12; + if (PowerHeight > DesiredPowerHeight) { + PowerDir = -1; + } else if (PowerHeight < DesiredPowerHeight) { + PowerDir = 1; + } else { + PowerBounce = 0; + } + } + + /* + ** If the recorded drain value has changed we need to adjust for + ** it. + */ + if (PlayerPtr->Drain != RecordedDrain) { + DesiredDrainHeight = Power_Height(PlayerPtr->Drain); + RecordedDrain = PlayerPtr->Drain; + DrainBounce = 12; + if (DrainHeight > DesiredDrainHeight) { + DrainDir = -1; + } else if (DrainHeight < DesiredDrainHeight) { + DrainDir = 1; + } else { + DrainBounce = 0; + } + } + + if (DrainBounce && DrainHeight == DesiredDrainHeight) { + IsToRedraw = true; + Flag_To_Redraw(false); + DrainBounce--; + } else { + /* + ** If we need to move the drain height then do so. + */ + if (DrainHeight != DesiredDrainHeight) { + DrainHeight += DrainDir; + } + } + + if (PowerBounce && PowerHeight == DesiredPowerHeight) { + IsToRedraw = true; + Flag_To_Redraw(false); + PowerBounce--; + } else { + /* + ** If we need to move the power height then do so. + */ + if (PowerHeight != DesiredPowerHeight) { + PowerHeight += PowerDir; + } + } + + if (olddrain != DrainHeight || oldpower != PowerHeight) { + IsToRedraw = true; + Flag_To_Redraw(false); + } + } + RadarClass::AI(input, x, y); +} + + +/*********************************************************************************************** + * PowerClass::Refresh_Cells -- Intercepts the redraw logic to see if sidebar to redraw too. * + * * + * This routine will examine a refresh list request and determine if the sidebar would be * + * affect. If so, it will flag the sidebar to be redrawn. * + * * + * INPUT: cell -- The cell that the offset list is base on. * + * * + * list -- The list of cell offset used to flag for redraw. If the special sidebar * + * affecting cell magic offset number is detected, the sidebar is flagged * + * for redraw and the magic offset is removed. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/01/1995 JLB : Created. * + *=============================================================================================*/ +void PowerClass::Refresh_Cells(CELL cell, short const *list) +{ + if (*list == REFRESH_SIDEBAR) { + IsToRedraw = true; + Flag_To_Redraw(false); + } + RadarClass::Refresh_Cells(cell, list); +} + + +/*************************************************************************** + * PowHeight -- Given a value figure where it falls on bar * + * * + * INPUT: int value - the value we are testing * + * * + * OUTPUT: int the height of the point that this value is on graph * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/14/1995 PWG : Created. * + *=========================================================================*/ +int PowerClass::Power_Height(int value) +{ + int num = value/ POWER_STEP_LEVEL; // figure out the initial num of DRAIN_VALUE's + int retval = 0; // currently there is no power + + /* + ** Loop through the diffrent hundreds figuring out the fractional piece + ** of each. + */ + for (int lp = 0; lp < num; lp ++) { + retval = retval + (((PowHeight - 2) - retval) / POWER_STEP_FACTOR); + value -= POWER_STEP_LEVEL; + } + + /* + ** Adjust the retval to factor in the remainder + */ + if (value) { + retval = retval + (((((PowHeight - 2) - retval) / POWER_STEP_FACTOR) * value) / POWER_STEP_LEVEL); + } + + retval = Bound(retval, 0, PowHeight -2); + return(retval); +} + + +/*********************************************************************************************** + * PowerClass::PowerButtonClass::Action -- Handles the mouse over the power bar area. * + * * + * This routine handles input on the power bar area. Since no input is used for the power * + * bar, this routine just pops up appropriate help text for the power bar. * + * * + * INPUT: flags -- The event flags that triggered this action call. * + * * + * key -- The key code (if any) associated with the trigger event. * + * * + * OUTPUT: Should further button processing be stopped? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/07/1995 JLB : Created. * + *=============================================================================================*/ +int PowerClass::PowerButtonClass::Action(unsigned flags, KeyNumType & key) +{ + if (!Map.IsSidebarActive) { + return(false); + } + + /* + ** Force any help label to disappear when the mouse is held over the + ** radar map. + */ + Map.Override_Mouse_Shape(MOUSE_NORMAL); + if (PlayerPtr->Power_Fraction() < 0x0100 && PlayerPtr->Power > 0) { + Map.Help_Text(TXT_POWER_OUTPUT_LOW, -1, -1, CC_GREEN); + } else { + Map.Help_Text(TXT_POWER_OUTPUT, -1, -1, CC_GREEN); + } + GadgetClass::Action(flags, key); + return(true); +} + + diff --git a/POWER.H b/POWER.H new file mode 100644 index 0000000..fcdf49f --- /dev/null +++ b/POWER.H @@ -0,0 +1,126 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\power.h_v 2.16 16 Oct 1995 16:48:06 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : POWER.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/15/94 * + * * + * Last Update : December 15, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef POWER_H +#define POWER_H + +#include "radar.h" + +class PowerClass : public RadarClass +{ + public: + int PowX; + int PowY; + int PowWidth; + int PowHeight; + int PowLineSpace; + int PowLineWidth; + + + PowerClass(); + + /* + ** Initialization + */ + virtual void One_Time(void); // One-time inits + + virtual void Init_Clear(void); // Clears all to known state + virtual void Draw_It(bool complete=false); + virtual void AI(KeyNumType &input, int x, int y); + virtual void Refresh_Cells(CELL cell, short const *list); +// virtual void Must_Redraw_Sidebar(void); + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + unsigned IsToRedraw:1; + + protected: + /* + ** This gadget is used to capture mouse input on the power bar. + */ + class PowerButtonClass : public GadgetClass { + public: + PowerButtonClass(void) : GadgetClass(0,0,0,0,LEFTPRESS|LEFTRELEASE|LEFTHELD|LEFTUP|RIGHTPRESS,true) {}; + + protected: + virtual int Action(unsigned flags, KeyNumType & key); + friend class PowerClass; + }; + + /* + ** This is the "button" that tracks all input to the tactical map. + ** It must be available to derived classes, for Save/Load purposes. + */ + static PowerButtonClass PowerButton; + + enum PowerEnums { + POWER_STEP_LEVEL=100, + POWER_STEP_FACTOR=6, + }; + + private: + int Power_Height(int value); + + unsigned IsActive:1; + + int RecordedDrain; + int RecordedPower; + int DesiredDrainHeight; + int DesiredPowerHeight; + int DrainHeight; + int PowerHeight; + int DrainBounce; + int PowerBounce; + short PowerDir; + short DrainDir; + + /* + ** Points to the shape to use for the "desired" power level indicator. + */ + static void const * PowerShape; + + /* + ** Points to the shapes to be used for drawing the power bar + */ + static void const * PowerBarShape; +}; + +#endif diff --git a/PROFILE.CPP b/PROFILE.CPP new file mode 100644 index 0000000..4a4c069 --- /dev/null +++ b/PROFILE.CPP @@ -0,0 +1,656 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\profile.cpv 2.18 16 Oct 1995 16:51:14 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : PROFILE.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : September 10, 1993 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * WWGetPrivateProfileInt -- Fetches integer value from INI. * + * WWWritePrivateProfileInt -- Write a profile int to the profile data block. * + * WWGetPrivateProfileString -- Fetch string from INI. * + * WWWritePrivateProfileString -- Write a string to the profile data block. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*************************************************************************** + * Read_Private_Config_Struct -- Fetches override integer value. * + * * + * INPUT: * + * OUTPUT: * + * WARNINGS: * + * HISTORY: * + * 08/05/1992 JLB : Created. * + *=========================================================================*/ +bool Read_Private_Config_Struct (char *profile, NewConfigType *config) +{ + config->DigitCard = WWGetPrivateProfileHex ("Sound", "Card", profile); + config->IRQ = WWGetPrivateProfileInt ("Sound", "IRQ", 0,profile); + config->DMA = WWGetPrivateProfileInt ("Sound", "DMA", 0,profile); + config->Port = WWGetPrivateProfileHex ("Sound", "Port", profile); + config->BitsPerSample= WWGetPrivateProfileInt ("Sound", "BitsPerSample",0,profile); + config->Channels = WWGetPrivateProfileInt ("Sound", "Channels", 0,profile); + config->Reverse = WWGetPrivateProfileInt ("Sound", "Reverse", 0,profile); + config->Speed = WWGetPrivateProfileInt ("Sound", "Speed", 0,profile); + WWGetPrivateProfileString ("Language", "Language", NULL, config->Language, 3, profile); + + return((config->DigitCard == 0) && (config->IRQ == 0) && (config->DMA == 0)); +} + + +/*************************************************************************** + * Get_Private_Profile_Hex -- Fetches override integer value. * + * * + * INPUT: * + * OUTPUT: * + * WARNINGS: * + * HISTORY: * + * 08/05/1992 JLB : Created. * + *=========================================================================*/ +unsigned WWGetPrivateProfileHex (char const *section, char const *entry, char *profile) +{ + char buffer[MAX_ENTRY_SIZE]; // Integer staging buffer. + unsigned card; + + memset (buffer, '0', MAX_ENTRY_SIZE); // MAX_ENTRY_SIZE = 15 + buffer[MAX_ENTRY_SIZE] = '\0'; + + WWGetPrivateProfileString(section, entry, "0", buffer, MAX_ENTRY_SIZE, profile); + + if (strlen (buffer) > 0) { + sscanf (buffer, "%x", &card); + } else { + card = 0; + } + + return(card); +} + + +/*********************************************************************************************** + * WWGetPrivateProfileInt -- Fetches integer value. * + * * + * INPUT: * + * section section to read from * + * * + * entry name of entry to read * + * * + * def default value, if entry isn't found * + * * + * profile buffer containing INI data * + * * + * OUTPUT: * + * integer requested * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/05/1992 JLB : Created. * + *=============================================================================================*/ +int WWGetPrivateProfileInt(char const *section, char const *entry, int def, char *profile) +{ + char buffer[16]; // Integer staging buffer. + + /* + ** Store the default in the buffer. + */ + sprintf(buffer, "%d", def); + + /* + ** Get the buffer; use itself as the default. + */ + WWGetPrivateProfileString(section, entry, buffer, buffer, 15, profile); + + /* + ** Convert to int & return. + */ + return(atoi(buffer)); +} + + +/*********************************************************************************************** + * WWWritePrivateProfileInt -- Write a profile int to the profile data block. * + * * + * INPUT: * + * section section name to write to * + * * + * entry name of entry to write; if NULL, the entire section is deleted * + * * + * value value to write * + * * + * profile INI buffer * + * * + * OUTPUT: * + * true = success, false = failure * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + *=============================================================================================*/ +bool WWWritePrivateProfileInt(char const *section, char const *entry, int value, char *profile) +{ + char buffer[250]; // Working section buffer. + + /* + ** Just return if nothing to do. + */ + if (!profile || !section) { + return(true); + } + + /* + ** Generate string to save. + */ + sprintf(buffer, "%d", value); + + /* + ** Save the string. + */ + return(WWWritePrivateProfileString(section, entry, buffer, profile)); +} + + +/*********************************************************************************************** + * WWGetPrivateProfileString -- Fetch game override string. * + * * + * INPUT: * + * section section name to read from * + * * + * entry name of entry to read; if NULL, all entry names are returned * + * * + * def default string to use if not found; can be NULL * + * * + * retbuffer buffer to store result in * + * * + * retlen max length of return buffer * + * * + * profile INI buffer * + * * + * OUTPUT: * + * ptr to entry found in INI buf; NULL if not found * + * * + * WARNINGS: * + * On the PC, the "\n" (10) is translated to "\r\n" (13,10) when it's written * + * to disk. This routine must take this into consideration, by searching * + * for \n when scanning backward, and for \r when scanning forward. * + * * + * HISTORY: * + * 08/05/1992 JLB : Created. * + *=============================================================================================*/ +char * WWGetPrivateProfileString(char const *section, char const *entry, char const *def, char *retbuffer, int retlen, char *profile) +{ + char * workptr, // Working pointer into profile block. + * altworkptr; // Alternate work pointer. + char sec[50]; // Working section buffer. + char *retval; // Start of section or entry pointer. + char * next; // Pointer to start of next section (or EOF). + char c,c2; // Working character values. + int len; // Working substring length value. + int entrylen; // Byte length of specified entry. + char *orig_retbuf; // original retbuffer ptr + + /* + ** Fill in the default value just in case the entry could not be found. + */ + if (retbuffer) { + if (def) { + strncpy(retbuffer, def, retlen); + } + retbuffer[retlen-1] = '\0'; + orig_retbuf = retbuffer; + } + + /* + ** Make sure a profile string was passed in + */ + if (!profile || !section) { + return(retbuffer); + } + + /* + ** Build section string to match file image. + */ + sprintf(sec, "[%s]", section); // sec = section name including []'s + strupr(sec); + len = strlen(sec); // len = section name length, incl []'s + + /* + ** Scan for a matching section + */ + retval = profile; + workptr = profile; + for (;;) { + + /* + ** 'workptr' = start of next section + */ + workptr = strchr(workptr, '['); + + /* + ** If the end has been reached without finding the desired section + ** then abort with a failure flag. + */ + if (!workptr) { + return(NULL); + } + + /* + ** 'c' = character just before the '[' + */ + if (workptr==profile) { + c = '\n'; + } else { + c = *(workptr-1); + } + + /* + ** If this is the section name & the character before is a newline, + ** process this section + */ + if (memicmp(workptr, sec, len) == 0 && (c == '\n')) { + + /* + ** Skip work pointer to start of first valid entry. + */ + workptr += len; + while (isspace(*workptr)) { + workptr++; + } + + /* + ** If the section name is empty, we will have stepped onto the start + ** of the next section name; inserting new entries here will leave + ** a blank line between this section's name & 1st entry. So, check + ** for 2 newlines in a row & step backward. + */ + if (workptr - profile > 4) { + if ( *(workptr-1)=='\n' && *(workptr-3)=='\n') + workptr -= 2; + } + + /* + ** 'next = end of section or end of file. + */ + next = strchr(workptr, '['); + for (;;) { + if (next) { + + c = *(next-1); + + /* + ** If character before '[' is newline, this is the start of the + ** next section + */ + if (c == '\n') { + if (*(next-1)=='\n' && *(next-3)=='\n') { + next -= 2; + } + break; + } + + /* + ** This bracket was in the section; keep looking + */ + next = strchr(next+1, '['); + } else { + + /* + ** No bracket found; set 'next' to the end of the file + */ + next = workptr + strlen(workptr)-1; + break; + } + } + + /* + ** If a specific entry was specified then return with the associated + ** string. + */ + if (entry) { + retval = workptr; + entrylen = strlen(entry); + + for (;;) { + /* + ** Search for the 1st character of the entry + */ + workptr = strchr(workptr, *entry); + + /* + ** If the end of the file has been reached or we have spilled + ** into the next section, then abort + */ + if (!workptr || workptr >= next) { + return(NULL); + } + + /* + ** 'c' = character before possible entry; must be a newline + ** 'c2' = character after possible entry; must be '=' or space + */ + c = *(workptr-1); + c2 = *(workptr+entrylen); + + /* + ** Entry found; extract it + */ + if (memicmp(workptr, entry, entrylen) == 0 && (c == '\n') && + (c2 == '=' || isspace(c2))) { + retval = workptr; + workptr += entrylen; // skip entry name + workptr = strchr(workptr, '='); // find '=' + + /* + ** 'altworkptr' = next newline; \r is used here since we're + ** scanning forward! + */ + if (workptr) { + altworkptr = strchr(workptr, '\r'); // find next newline + } + + /* + ** Return if there was no '=', or if the newline is before + ** the next '=' + */ + if (workptr == NULL || altworkptr < workptr) { + return(retval); + } + + /* + ** Skip any white space after the '=' and before the first + ** valid character of the parameter. + */ + workptr++; // Skip the '='. + while (isspace(*workptr)) { + + /* + ** Just return if there's no entry past the '='. + */ + if (workptr >= altworkptr) + return(retval); + + workptr++; // Skip the whitespace + } + + /* + ** Copy the entry into the return buffer. + */ + len = (int)(altworkptr - workptr); + if (len > retlen-1) { + len = retlen-1; + } + + if (retbuffer) { + memcpy(retbuffer, workptr, len); + *(retbuffer + len) = '\0'; // Insert trailing null. + strtrim(retbuffer); + } + return(retval); + } + + /* + ** Entry was not found; go to the next one + */ + workptr++; + } + } else { + + /* + ** No entry was specified, so build a list of all entries. + ** 'workptr' is at 1st entry after section name + ** 'next' is next bracket, or end of file + */ + retval = workptr; + + if (retbuffer) { + + /* + ** Keep accumulating the identifier strings in the retbuffer. + */ + while (workptr && workptr < next) { + altworkptr = strchr(workptr, '='); // find '=' + + if (altworkptr && altworkptr < next) { + int length; // Length of ID string. + + length = (int)(altworkptr - workptr); + + /* + ** Make sure we don't write past the end of the retbuffer; + ** add '3' for the 3 NULL's at the end + */ + if (retbuffer - orig_retbuf + length + 3 < retlen) { + memcpy(retbuffer, workptr, length); // copy entry name + *(retbuffer+length) = '\0'; // NULL-terminate it + strtrim(retbuffer); // trim spaces + retbuffer += strlen(retbuffer)+1; // next pos in dest buf + } else { + break; + } + + /* + ** Advance the work pointer to the start of the next line + ** by skipping the end of line character. + */ + workptr = strchr(altworkptr, '\n'); + if (!workptr) { + break; + } + workptr++; + } else { + + /* + ** If no '=', break out of loop + */ + break; + } + } + + /* + ** Final trailing terminator. Make double sure the double + ** trailing null is added. + */ + *retbuffer++ = '\0'; + *retbuffer++ = '\0'; + } + break; + } + } else { + + /* + ** Section name not found; go to the next bracket & try again + ** Advance past '[' and keep scanning. + */ + workptr++; + } + } + + return(retval); +} + + +/*********************************************************************************************** + * WWWritePrivateProfileString -- Write a string to the profile data block. * + * * + * INPUT: * + * section section name to write to * + * entry name of entry to write; if NULL, the section is deleted * + * string string to write; if NULL, the entry is deleted * + * profile INI buffer * + * * + * OUTPUT: * + * true = success, false = failure * + * * + * WARNINGS: * + * This function has to translate newlines into \r\n sequences. * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + *=============================================================================================*/ +bool WWWritePrivateProfileString(char const *section, char const *entry, char const *string, char *profile) +{ + char buffer[250]; // Working section buffer + char *offset; + char *next; // ptr to next section + char c; // Working character value + + /* + ** Just return if nothing to do. + */ + if (!profile || !section) { + return(true); + } + + /* + ** Try to find the section. WWGetPrivateProfileString with NULL entry name + ** will return all entry names in the given buffer, truncated to the given + ** buffer length. 'offset' will point to 1st entry in the section, NULL if + ** section not found. + */ + offset = WWGetPrivateProfileString(section, NULL, NULL, NULL, 0, profile); + + /* + ** If the section could not be found, then add it to the end. Don't add + ** anything if a removal of an entry is requested (it is obviously already + ** non-existent). Make sure two newlines precede the section name. + */ + if (!offset && entry) { + sprintf(buffer, "\r\n[%s]\r\n", section); + strcat(profile, buffer); + } + + /* + ** If the section is there and 'entry' is NULL, remove the entire section + */ + if (offset && !entry) { + + /* + ** 'next = end of section or end of file. + */ + next = strchr(offset, '['); + for (;;) { + if (next) { + c = *(next-1); + + /* + ** If character before '[' is newline, this is the start of the + ** next section + */ + if (c == '\n') { + if ( *(next-1)=='\n' && *(next-3)=='\n') { + next -= 2; + } + break; + } + + /* + ** This bracket was in the section; keep looking + */ + next = strchr(next+1, '['); + } else { + + /* + ** No bracket found; set 'next' to the end of the file + */ + next = offset + strlen(offset); + break; + } + } + + /* + ** Remove the section + */ + strcpy(offset,next); + + return(true); + } + + /* + ** Find the matching entry within the desired section. A NULL return buffer + ** with 0 length will just return the offset of the found entry, NULL if + ** entry not found. + */ + offset = WWGetPrivateProfileString(section, entry, NULL, NULL, 0, profile); + + /* + ** Remove any existing entry + */ + if (offset) { + int eol; // Working EOL offset. + + /* + ** Get # characters up to newline; \n is used since we're after the end + ** of this line + */ + eol = strcspn(offset, "\n"); + + /* + ** Erase the entry by strcpy'ing the entire INI file over this entry + */ + if (eol) { + strcpy(offset, offset + eol + 1); + } + } else { + + /* + ** Entry doesn't exist, so point 'offset' to the 1st entry position in + ** the section. + */ + offset = WWGetPrivateProfileString(section, NULL, NULL, NULL, 0, profile); + } + + /* + ** Add the desired entry. + */ + if (entry && string) { + + /* + ** Generate entry string. + */ + sprintf(buffer, "%s=%s\r\n", entry, string); + + /* + ** Make room for new entry. + */ + memmove(offset+strlen(buffer), offset, strlen(offset)+1); + + /* + ** Copy the entry into the INI buffer. + */ + memcpy(offset, buffer, strlen(buffer)); + } + + return(true); +} diff --git a/QUEUE.CPP b/QUEUE.CPP new file mode 100644 index 0000000..f3d318d --- /dev/null +++ b/QUEUE.CPP @@ -0,0 +1,4204 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c0\vcs\code\queue.cpv 2.24 11 Oct 1995 13:47:40 JOE_BOSTIC $ */ +/*************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : QUEUE.CPP * + * * + * Programmer : Bill R. Randolph * + * * + * Start Date : 11/28/95 * + * * + * Last Update : November 28, 1995 [BRR] * + * * + *-------------------------------------------------------------------------* + * Functions for Queueing Events: * + * Queue_Mission -- Queue a mega mission event. * + * Queue_Options -- Queue the options event. * + * Queue_Exit -- Add the exit game event to the queue. * + * * + * Functions for processing Queued Events: * + * Queue_AI -- Process all queued events. * + * Queue_AI_Normal -- Process all queued events. * + * Queue_AI_Multiplayer -- Process all queued events. * + * * + * Main Multiplayer Queue Logic: * + * Wait_For_Players -- Waits for other systems to come on-line * + * Generate_Timing_Event -- computes & queues a RESPONSE_TIME event * + * Process_Send_Period -- timing for sending packets every 'n' frames * + * Send_Packets -- sends out events from the OutList * + * Send_FrameSync -- Sends a FRAMESYNC packet * + * Process_Receive_Packet -- processes an incoming packet * + * Process_Serial_Packet -- Handles an incoming serial packet * + * Can_Advance -- determines if it's OK to advance to the next frame * + * Process_Reconnect_Dialog -- processes the reconnection dialog * + * Handle_Timeout -- attempts to reconnect; if fails, bails. * + * Stop_Game -- stops the game * + * * + * Packet Compression / Decompression: * + * Build_Send_Packet -- Builds a big packet from a bunch of little ones. * + * Add_Uncompressed_Events -- adds uncompressed events to a packet * + * Add_Compressed_Events -- adds compressed events to a packet * + * Breakup_Receive_Packet -- Splits a big packet into little ones. * + * Extract_Uncompressed_Events -- extracts events from a packet * + * Extract_Compressed_Events -- extracts events from a packet * + * * + * DoList Management: * + * Execute_DoList -- Executes commands from the DoList * + * Clean_DoList -- Cleans out old events from the DoList * + * Queue_Record -- Records the DoList to disk * + * Queue_Playback -- plays back queue entries from a record file * + * * + * Debugging: * + * Compute_Game_CRC -- Computes a CRC value of the entire game. * + * Add_CRC -- Adds a value to a CRC * + * Print_CRCs -- Prints a data file for finding Sync Bugs * + * Init_Queue_Mono -- inits mono display * + * Update_Queue_Mono -- updates mono display * + * Print_Framesync_Values -- displays frame-sync variables * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#include "function.h" +#include "tcpip.h" + +/********************************** Defines *********************************/ +#define SHOW_MONO 1 + +int tmp_flag = 0; + +/********************************** Globals *********************************/ +//--------------------------------------------------------------------------- +// GameCRC is the current computed CRC value for this frame. +// CRC[] is a record of our last 32 game CRC's. +// ColorNames is for debug output in Print_CRCs +//--------------------------------------------------------------------------- +#ifndef DEMO +static unsigned long GameCRC; +static unsigned long CRC[32] = + {0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0}; + +static char *ColorNames[6] = { + "Yellow", + "Red", + "BlueGreen", + "Orange", + "Green", + "Blue", +}; +#endif //DEMO + +//........................................................................... +// Mono debugging variables: +// NetMonoMode: 0 = show connection output, 1 = flowcount output +// NewMonoMode: set by anything that toggles NetMonoMode; re-inits screen +// IsMono: used for taking control of Mono screen away from the engine +//........................................................................... +#ifndef DEMO +int NetMonoMode = 1; +int NewMonoMode = 1; +static int IsMono = 0; +#endif //DEMO + +//--------------------------------------------------------------------------- +// Several routines return various codes; here's an enum for all of them. +//--------------------------------------------------------------------------- +typedef enum RetcodeEnum { + RC_NORMAL, // no news is good news + RC_PLAYER_READY, // a new player has been heard from + RC_SCENARIO_MISMATCH, // scenario mismatch + RC_DOLIST_FULL, // DoList is full + RC_SERIAL_PROCESSED, // modem: SERIAL packet was processed + RC_PLAYER_LEFT, // modem: other player left the game + RC_HUNG_UP, // modem has hung up + RC_NOT_RESPONDING, // other player not responding (timeout/hung up) + RC_CANCEL, // user cancelled +} RetcodeType; + + +/********************************* Prototypes *******************************/ +//........................................................................... +// Main multiplayer queue logic +//........................................................................... +static void Queue_AI_Normal(void); +#ifndef DEMO +static void Queue_AI_Multiplayer(void); +static RetcodeType Wait_For_Players(int first_time, ConnManClass *net, + int resend_delta, int dialog_time, int timeout, char *multi_packet_buf, + int my_sent, long *their_frame, unsigned short *their_sent, + unsigned short *their_recv); +static void Generate_Timing_Event(ConnManClass *net, int my_sent); +static void Generate_Real_Timing_Event(ConnManClass *net, int my_sent); +static void Generate_Process_Time_Event(ConnManClass *net); +static int Process_Send_Period(ConnManClass *net); +static int Send_Packets(ConnManClass *net, char *multi_packet_buf, + int multi_packet_max, int max_ahead, int my_sent); +static void Send_FrameSync(ConnManClass *net, int cmd_count); +static RetcodeType Process_Receive_Packet(ConnManClass *net, + char *multi_packet_buf, int id, int packetlen, long *their_frame, + unsigned short *their_sent, unsigned short *their_recv); +static RetcodeType Process_Serial_Packet(char *multi_packet_buf, + int first_time); +static int Can_Advance(ConnManClass *net, int max_ahead, long *their_frame, + unsigned short *their_sent, unsigned short *their_recv); +static int Process_Reconnect_Dialog(CountDownTimerClass *timeout_timer, + long *their_frame, int num_conn, int reconn, int fresh); +static int Handle_Timeout(ConnManClass *net, long *their_frame, + unsigned short *their_sent, unsigned short *their_recv); +static void Stop_Game(void); +#endif //DEMO + +//........................................................................... +// Packet compression/decompression: +//........................................................................... +#ifndef DEMO +static int Build_Send_Packet(void *buf, int bufsize, int frame_delay, + int num_cmds, int cap); +static int Breakup_Receive_Packet(void *buf, int bufsize ); +#endif //DEMO +int Add_Uncompressed_Events(void *buf, int bufsize, int frame_delay, int size, + int cap); +int Add_Compressed_Events(void *buf, int bufsize, int frame_delay, int size, + int cap); +int Extract_Uncompressed_Events(void *buf, int bufsize); +int Extract_Compressed_Events(void *buf, int bufsize); + +//........................................................................... +// DoList management: +//........................................................................... +static int Execute_DoList(int max_houses, HousesType base_house, + ConnManClass *net, TCountDownTimerClass *skip_crc, + long *their_frame, unsigned short *their_sent, unsigned short *their_recv); +static void Clean_DoList(ConnManClass *net); +#ifndef DEMO +static void Queue_Record(void); +static void Queue_Playback(void); +#endif //DEMO + +//........................................................................... +// Debugging: +//........................................................................... +#ifndef DEMO +static void Compute_Game_CRC(void); +static void Init_Queue_Mono(ConnManClass *net); +static void Update_Queue_Mono(ConnManClass *net, int flow_index); +static void Print_Framesync_Values(long curframe, unsigned long max_ahead, + int num_connections, unsigned short *their_recv, + unsigned short *their_sent, unsigned short my_sent); +#endif //DEMO +void Add_CRC(unsigned long *crc, unsigned long val); +void Print_CRCs(EventClass *); + +extern void Keyboard_Process(KeyNumType &input); +void Dump_Packet_Too_Late_Stuff(EventClass *event); + +extern void Register_Game_End_Time(void); +extern void Send_Statistics_Packet(void); + +/*************************************************************************** + * Queue_Mission -- Queue a mega mission event. * + * * + * This routine is called when the player causes a change to a game unit. * + * The event that initiates the change is queued to as a result of a call * + * to this routine. * + * * + * INPUT: * + * whom Whom this mission request applies to (a friendly unit). * + * mission The mission to assign to this object. * + * target The target of this mission (if any). * + * dest The movement destination for this mission (if any). * + * * + * OUTPUT: * + * Was the mission request queued successfully? * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 09/21/1995 JLB : Created. * + *=========================================================================*/ +bool Queue_Mission(TARGET whom, MissionType mission, TARGET target, + TARGET destination) +{ + if (! OutList.Add(EventClass(whom, mission, target, destination))) { + return(false); + } + else { + return(true); + } + +} /* end of Queue_Mission */ + + +/*************************************************************************** + * Queue_Options -- Queue the options event. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * Was the options screen event queued successfully? * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 09/21/1995 JLB : Created. * + *=========================================================================*/ +bool Queue_Options(void) +{ + if (! OutList.Add(EventClass(EventClass::OPTIONS))) { + return(false); + } + else { + return(true); + } + +} /* end of Queue_Options */ + + +/*************************************************************************** + * Queue_Exit -- Add the exit game event to the queue. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * Was the exit event queued successfully? * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 09/21/1995 JLB : Created. * + *=========================================================================*/ +bool Queue_Exit(void) +{ + if (! OutList.Add(EventClass(EventClass::EXIT))) { + return(false); + } + else { + return(true); + } + +} /* end of Queue_Exit */ + + +/*************************************************************************** + * Queue_AI -- Process all queued events. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 09/21/1995 JLB : Created. * + *=========================================================================*/ +void Queue_AI(void) +{ +#ifdef DEMO + Queue_AI_Normal(); +#else //DEMO + + if (PlaybackGame) { + Queue_Playback(); + } + + else { + + switch (GameToPlay) { + + case GAME_NORMAL: + Queue_AI_Normal(); + break; + + case GAME_MODEM: + case GAME_NULL_MODEM: + case GAME_IPX: + case GAME_INTERNET: + Queue_AI_Multiplayer(); + break; + } + } +#endif //DEMO + +} /* end of Queue_AI */ + + +/*************************************************************************** + * Queue_AI_Normal -- Process all queued events. * + * * + * This is the "normal" version of the queue management routine. It does * + * the following: * + * - Transfers items in the OutList to the DoList * + * - Executes any commands in the DoList that are supposed to be done on * + * this frame # * + * - Cleans out the DoList * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 09/21/1995 JLB : Created. * + *=========================================================================*/ +static void Queue_AI_Normal(void) +{ + //------------------------------------------------------------------------ + // Move events from the OutList (events generated by this player) into the + // DoList (the list of events to execute). + //------------------------------------------------------------------------ + while (OutList.Count) { + OutList.First().IsExecuted = false; + if (!DoList.Add(OutList.First())) { + ; + } + OutList.Next(); + } + + //------------------------------------------------------------------------ + // Save the DoList to disk, if we're in "Record" mode + //------------------------------------------------------------------------ +#ifndef DEMO + if (RecordGame) { + Queue_Record(); + } +#endif //DEMO + + //------------------------------------------------------------------------ + // Execute the DoList + //------------------------------------------------------------------------ + if (!Execute_DoList(1,PlayerPtr->Class->House, NULL, NULL, NULL, + NULL, NULL)) { + GameActive = 0; + return; + } + //------------------------------------------------------------------------ + // Clean out the DoList + //------------------------------------------------------------------------ + Clean_DoList(NULL); + +} /* end of Queue_AI_Normal */ + +#ifndef DEMO + +/*************************************************************************** + * Queue_AI_Multiplayer -- Process all queued events. * + * * + * This is the network version of the queue management routine. It does * + * the following: * + * - If this is the 1st frame, waits for other systems to signal ready * + * - Generates a timing event, to allow the connection time to be dynamic * + * - Handles timing related to sending packets every 'n' frames * + * - Sends outgoing events * + * - Frame-syncs to the other systems (see below) * + * - Executes & cleans out the DoList * + * * + * The Frame-Sync'ing logic is the heart & soul of network play. It works * + * by ensuring that any system won't out-run the other system by more than * + * 'MaxAhead' frames; this in turn ensures that a packet's * + * execution frame # won't have been passed by the time that packet is * + * received by all systems. * + * * + * To achieve this, the system must keep track of all other system's * + * current frame #'s; these are stored in an array called 'their_frame[]'. * + * However, because current frame #'s are sent in FRAMEINFO packets, which * + * don't require an ACK, and command packets are sent in packets requiring * + * an ACK, it's possible for a command packet to get lost, and the next * + * frame's FRAMEINFO packet to not get lost; the other system may then * + * advance past the frame # the command is to execute on! So, to prevent * + * this, all FRAMEINFO packets include a CommandCount field. This value * + * tells the other system how many events it should have received by this * + * time. This system can therefore keep track of how many commands it's * + * actually received, and compare it to the CommandCount field, to see if * + * it's missed an event packet. The # of events we've received from each * + * system is stored in 'their_recv[]', and the # events they say they've * + * sent is stored in 'their_sent[]'. * + * * + * Thus, two conditions must be met in order to advance to the next frame: * + * - Our current frame # must be <= their_frame + MaxAhead * + * - their_recv[i] must be >= their_sent[i] * + * * + * 'their_frame[] is updated by Process_Receive_Packet() * + * 'their_recv[] is updated by Process_Receive_Packet() * + * 'their_sent[] is updated by Process_Receive_Packet() * + * 'my_sent' is updated by this routine. * + * * + * The only routines allowed to pop up dialogs are: * + * Wait_For_Players() (only pops up the reconnect dialog) * + * Execute_DoList() (tells if out of sync, or packet recv'd too late) * + * * + * Sign-off's are detected by: * + * - Timing out while waiting for a packet * + * - Detecting that the other player is now at the score screen or * + * connection dialog (serial) * + * - If we see an EventClass::EXIT event on the private channel * + * * + * The current communications protocol, COMM_PROTOCOL_MULTI_E_COMP, has * + * the following properites: * + * - It compresses packets, so that the minimum number of bytes are * + * transmitted. Packets are compressed by extracting all info common to * + * the events into the packet header, and then sending only the bytes * + * relevant to each type of event. For instance, if 100 infantry guys * + * are told to move to the same location, the command itself & the * + * location will be included in the 1st movement command only; after * + * that, there will be a rep count then 99 infantry TARGET numbers, * + * identifying all the infantry told to move. * + * - The protocol also only sends packets out every 'n' frames. This cuts * + * the data rate dramatically. It means that 'MaxAhead' must be * + * divisible by 'n'; also, the minimum value for 'MaxAhead' is * + * 'n * 2', to give both sides some "breathing" room in case a FRAMEINFO * + * packet gets missed. * + * * + * Note: For synchronization-waiting loops (like waiting to hear from all * + * other players, waiting to advance to the next frame, etc), use * + * Net.Num_Connections() rather than NumPlayers; this reflects the * + * actual # of connections, and can be "faked" into playing even when * + * there aren't any other players actually there. A typical example of * + * this is playing back a recorded game. For command-execution loops, use * + * NumPlayers. This ensures all commands get executed, even if * + * there isn't a human generating those commands. * + * * + * The modem works a little differently from the network in this respect: * + * - The connection has to stay "alive" even if the other player exits to * + * the join dialog. This prevents each system from timing out & hanging * + * the modem up. Thus, packets are sent back & forth & just thrown away,* + * but each system knows the other is still there. Messages may be sent * + * between systems, though. * + * - Destroy_Null_Connection doesn't hang up the modem, so * + * Num_Connections() still reports a value of 1 even though the other * + * player has left. * + * - Any waits on Num_Connections() must also check for * + * NumPlayers > 1, to keep from waiting forever if the other * + * guy has left * + * - Packets sent to a player who's left require no ACK * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static void Queue_AI_Multiplayer(void) +{ + //........................................................................ + // Enums: + //........................................................................ + enum { + MIXFILE_RESEND_DELTA = 120, // ticks b/w resends + MIXFILE_TIMEOUT = 3600, // timeout waiting for mixfiles + FRAMESYNC_DLG_TIME = (3*60), // time until displaying reconnect dialog + FRAMESYNC_TIMEOUT = (25*60), // timeout waiting for frame sync packet + }; + + int timeout_factor = (GameToPlay == GAME_INTERNET) ? 6 : 1; + + //........................................................................ + // Variables for sending, receiving & parsing packets: + //........................................................................ + ConnManClass *net; // ptr to access all multiplayer functions + EventClass packet; // for sending single frame-sync's + char *multi_packet_buf; // buffer for sending/receiving + int multi_packet_max; // max length of multi_packet_buf + + //........................................................................ + // Frame-sync'ing variables. Values in these arrays are stored in the + // order in which the connections are created. + // (ie net->Connection_Index(id)) + //........................................................................ + static long + their_frame[MAX_PLAYERS - 1]; // other players' frame #'s + static unsigned short + their_sent[MAX_PLAYERS - 1]; // # cmds other player claims to have sent + static unsigned short + their_recv[MAX_PLAYERS - 1]; // # cmds actually received from others + static unsigned short + my_sent; // # cmds I've sent out + + //........................................................................ + // Other misc variables + //........................................................................ + int i; + RetcodeType rc; + int reconnect_dlg = 0; // 1 = the reconnect dialog is displayed + + //------------------------------------------------------------------------ + // Initialize the packet buffer pointer & its max size + //------------------------------------------------------------------------ + if (GameToPlay == GAME_MODEM + || GameToPlay == GAME_NULL_MODEM){ + multi_packet_buf = NullModem.BuildBuf; + multi_packet_max = NullModem.MaxLen - sizeof (CommHeaderType); + net = &NullModem; + } + else if (GameToPlay == GAME_IPX || GameToPlay == GAME_INTERNET) { + multi_packet_buf = MetaPacket; + multi_packet_max = MetaSize; + net = &Ipx; + } + + //------------------------------------------------------------------------ + // Debug stuff + //------------------------------------------------------------------------ + Init_Queue_Mono(net); + Update_Queue_Mono (net, 0); + + //------------------------------------------------------------------------ + // If we've just started a game, or loaded a multiplayer game, we must + // wait for all other systems to signal ready. + //------------------------------------------------------------------------ + if (Frame==0) { + //..................................................................... + // Initialize static locals + //..................................................................... + for (i = 0; i < MAX_PLAYERS - 1; i++) { + their_frame[i] = -1; + their_sent[i] = 0; + their_recv[i] = 0; + TheirProcessTime[i] = -1; + } + my_sent = 0; + for (i = 0; i < 32; i++) { + CRC[i] = 0; + } + + //..................................................................... + // Send our initial FRAMESYNC packet + //..................................................................... + Send_FrameSync(net, my_sent); + + //..................................................................... + // Wait for the other guys + //..................................................................... + rc = Wait_For_Players (1, net, MIXFILE_RESEND_DELTA, FRAMESYNC_DLG_TIME*timeout_factor, + MIXFILE_TIMEOUT, multi_packet_buf, my_sent, their_frame, + their_sent, their_recv); + + if (rc != RC_NORMAL) { + if (rc == RC_NOT_RESPONDING) { + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + } + else if (rc == RC_SCENARIO_MISMATCH) { + CCMessageBox().Process (TXT_SCENARIOS_DO_NOT_MATCH); + } + else if (rc == RC_DOLIST_FULL) { + CCMessageBox().Process(TXT_QUEUE_FULL); + } + Stop_Game(); + return; + } + + //..................................................................... + // Re-initialize frame numbers (in case somebody signed off while I was + // waiting for MIX files to load; we would have fallen through, but + // their frame # would still be -1). + //..................................................................... + for (i = 0; i < MAX_PLAYERS - 1; i++) + their_frame[i] = 0; + + //..................................................................... + // Reset the network response time computation, now that we're both + // sending data again (loading MIX files will have introduced + // deceptively large values). + //..................................................................... + net->Reset_Response_Time(); + + //..................................................................... + // Initialize the frame timers + //..................................................................... + if (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { + Process_Send_Period(net); + } + + } // end of Frame 0 wait + + //------------------------------------------------------------------------ + // Adjust connection timing parameters every 128 frames. + //------------------------------------------------------------------------ + else if ( (Frame & 0x007f) == 0) { + // + // If we're using the new spiffy protocol, do proper timing handling. + // If we're the net "master", compute our desired frame rate & new + // 'MaxAhead' value. + // + if (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { + + // + // All systems will transmit their required process time. + // + Generate_Process_Time_Event(net); + + // + // The game "host" will transmit timing adjustment events. + // + if (MPlayerLocalID == MPlayerID[0]) { + Generate_Real_Timing_Event(net, my_sent); + } + } else { + // + // For the older protocols, do the old broken timing handling. + // + Generate_Timing_Event(net, my_sent); + } + } + + //------------------------------------------------------------------------ + // Compute the Game's CRC + //------------------------------------------------------------------------ + Compute_Game_CRC(); + CRC[Frame & 0x001f] = GameCRC; + //unsigned long save_crc = GameCRC; + //Print_CRCs((EventClass *)NULL); + //GameCRC = save_crc; + + //------------------------------------------------------------------------ + // Only process every 'FrameSendRate' frames + //------------------------------------------------------------------------ + if (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { + if (!Process_Send_Period(net)) { + if (IsMono) { + MonoClass::Disable(); + } + return; + } + } + + //------------------------------------------------------------------------ + // Send our data packet(s); update my command-sent counter + //------------------------------------------------------------------------ + my_sent += Send_Packets(net, multi_packet_buf, multi_packet_max, + MPlayerMaxAhead, my_sent); + + //------------------------------------------------------------------------ + // If this is our first time through, we're done. + //------------------------------------------------------------------------ + if (Frame==0) { + if (IsMono) { + MonoClass::Disable(); + } + return; + } + + //------------------------------------------------------------------------ + // Frame-sync'ing: wait until it's OK to advance to the next frame. + //------------------------------------------------------------------------ + rc = Wait_For_Players (0, net, + (MPlayerMaxAhead << 3), + MAX ( net->Response_Time() * 3, FRAMESYNC_DLG_TIME*timeout_factor ), + FRAMESYNC_TIMEOUT * (timeout_factor*2), + multi_packet_buf, my_sent, their_frame, + their_sent, their_recv); + + if (rc != RC_NORMAL) { + if (rc == RC_NOT_RESPONDING) { + CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); + } + else if (rc == RC_SCENARIO_MISMATCH) { + CCMessageBox().Process (TXT_SCENARIOS_DO_NOT_MATCH); + } + else if (rc == RC_DOLIST_FULL) { + CCMessageBox().Process(TXT_QUEUE_FULL); + } + Stop_Game(); + return; + } + + //------------------------------------------------------------------------ + // Save the DoList to disk, if we're in "Record" mode + //------------------------------------------------------------------------ + if (RecordGame) { + Queue_Record(); + } + + //------------------------------------------------------------------------ + // Execute the DoList; if an error occurs, bail out. + //------------------------------------------------------------------------ + if (!Execute_DoList(MPlayerMax, HOUSE_MULTI1, net, NULL, + their_frame, their_sent, their_recv)) { + Stop_Game(); + return; + } + + //------------------------------------------------------------------------ + // Clean out the DoList + //------------------------------------------------------------------------ + Clean_DoList(net); + + if (IsMono) { + MonoClass::Disable(); + } + +} // end of Queue_AI_Multiplayer + + +/*************************************************************************** + * Wait_For_Players -- Waits for other systems to come on-line * + * * + * This routine performs the most critical logic in multiplayer; that of * + * synchronizing my frame number with those of the other systems. * + * * + * INPUT: * + * first_time 1 = 1st time this routine is called * + * net ptr to connection manager * + * resend_delta time (ticks) between FRAMESYNC resends * + * dialog_time time (ticks) until pop up a reconnect dialog * + * timeout time (ticks) until we give up the ghost * + * multi_packet_buf buffer to store packets in * + * my_sent # commands I've sent so far * + * their_frame array of their frame #'s * + * their_sent array of their CommandCount values * + * their_recv array of # cmds I've received from them * + * * + * OUTPUT: * + * RC_NORMAL OK to advance to the next frame * + * RC_CANCEL user hit 'Cancel' at the timeout countdown dlg * + * RC_NOT_RESPONDING other player(s) not responding * + * RC_SCENARIO_MISMATCH scenario's don't match (first_time only) * + * RC_DOLIST_FULL DoList was full * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static RetcodeType Wait_For_Players(int first_time, ConnManClass *net, + int resend_delta, int dialog_time, int timeout, char *multi_packet_buf, + int my_sent, long *their_frame, unsigned short *their_sent, + unsigned short *their_recv) +{ + //........................................................................ + // Variables for sending, receiving & parsing packets: + //........................................................................ + EventClass *event; // event ptr for parsing incoming packets + int packetlen; // size of meta-packet sent, & received + int id; // id of other player + int messages_this_loop; // to limit # messages processed each loop + + //........................................................................ + // Variables used only if 'first_time': + //........................................................................ + int num_ready; // # players signalling ready + + //........................................................................ + // Timing variables + //........................................................................ + CountDownTimerClass retry_timer; // time between FRAMESYNC packet resends + CountDownTimerClass dialog_timer; // time to pop up a dialog + CountDownTimerClass timeout_timer; // general-purpose timeout + + //........................................................................ + // Dialog variables + //........................................................................ + int reconnect_dlg = 0; // 1 = the reconnect dialog is displayed + + //........................................................................ + // Other misc variables + //........................................................................ + KeyNumType input; // for user input + int x,y; // for map input + RetcodeType rc; + + //------------------------------------------------------------------------ + // Wait to hear from all other players + //------------------------------------------------------------------------ + num_ready = 0; + retry_timer.Set (resend_delta, true); // time to retry + dialog_timer.Set (dialog_time, true); // time to show dlg + timeout_timer.Set (timeout, true); // time to bail out + + while (1) { + + Update_Queue_Mono (net, 2); + + //--------------------------------------------------------------------- + // Resend a frame-sync packet if longer than one propogation delay goes + // by; this prevents a "deadlock". If he's waiting for me to advance, + // but has missed my last few FRAMEINFO packets, I may be waiting for + // him to advance. Resending a FRAMESYNC ensures he knows what frame + // number I'm on. + //--------------------------------------------------------------------- + if (!retry_timer.Time()) { + retry_timer.Set (resend_delta, true); // time to retry + Update_Queue_Mono (net, 3); + Send_FrameSync(net, my_sent); + } + + //--------------------------------------------------------------------- + // Service the connections + //--------------------------------------------------------------------- + net->Service(); + + //--------------------------------------------------------------------- + // Pop up a reconnect dialog if enough time goes by + //--------------------------------------------------------------------- + if (!dialog_timer.Time() && SpecialDialog==SDLG_NONE) { + if (Process_Reconnect_Dialog(&timeout_timer, their_frame, + net->Num_Connections(), (first_time==0), (reconnect_dlg==0))) { + return (RC_CANCEL); + } + reconnect_dlg = 1; + } + + //--------------------------------------------------------------------- + // Exit if too much time goes by (the other system has crashed or + // bailed) + //--------------------------------------------------------------------- + if (!timeout_timer.Time()) { + //.................................................................. + // For the first-time run, just give up; something's wrong. + //.................................................................. + if (first_time) { + return (RC_NOT_RESPONDING); + } + //.................................................................. + // Otherwise, we're in the middle of a game; so, the modem & + // network must deal with a timeout differently. + //.................................................................. + else { + Update_Queue_Mono (net, 4); + + if (Handle_Timeout(net, their_frame, their_sent, their_recv)) { + Map.Flag_To_Redraw(true); // erase modem reconnect dialog + Map.Render(); + retry_timer.Set (resend_delta, true); + dialog_timer.Set (dialog_time, true); + timeout_timer.Set (timeout, true); + } + else { + return (RC_NOT_RESPONDING); + } + } + } + + //--------------------------------------------------------------------- + // Check for an incoming message. We must still process commands + // even if 'first_time' is set, in case the other system got my 1st + // FRAMESYNC, but I didn't get his; he'll be at the next frame, and + // may be sending commands. + // We have to limit the number of incoming messages we handle; it's + // possible to go into an infinite loop processing modem messages. + //--------------------------------------------------------------------- + messages_this_loop = 0; + while ( (messages_this_loop++ < 5) && + net->Get_Private_Message (multi_packet_buf, &packetlen, &id) ) { + Update_Queue_Mono (net, 5); + + /*.................................................................. + Get an event ptr to the incoming message + ..................................................................*/ + event = (EventClass *)multi_packet_buf; + + //------------------------------------------------------------------ + // Special processing for a modem game: process SERIAL packets + //------------------------------------------------------------------ + if (GameToPlay == GAME_MODEM + || GameToPlay == GAME_NULL_MODEM){ + //|| GameToPlay == GAME_INTERNET) { + rc = Process_Serial_Packet(multi_packet_buf, first_time); + //............................................................... + // SERIAL packet received & processed + //............................................................... + if (rc == RC_SERIAL_PROCESSED) { + net->Service(); + retry_timer.Set (resend_delta, true); + dialog_timer.Set (dialog_time, true); + timeout_timer.Set (timeout, true); + continue; + } + //............................................................... + // other player has left the game + //............................................................... + else if (rc == RC_PLAYER_LEFT) { + if (first_time) { + num_ready++; + } + break; + } + //............................................................... + // Connection was lost + //............................................................... + else if (rc == RC_HUNG_UP) { + return (RC_NOT_RESPONDING); + } + //............................................................... + // If it was any other type of serial packet, break + //............................................................... + else if (rc != RC_NORMAL) { + break; + } + } + + //------------------------------------------------------------------ + // Process the incoming packet + //------------------------------------------------------------------ + rc = Process_Receive_Packet(net, multi_packet_buf, id, packetlen, + their_frame, their_sent, their_recv); + //.................................................................. + // New player heard from + //.................................................................. + if (rc == RC_PLAYER_READY) { + num_ready++; + } + //.................................................................. + // Scenario's don't match + //.................................................................. + else if (rc == RC_SCENARIO_MISMATCH) { + return (RC_SCENARIO_MISMATCH); + } + //.................................................................. + // DoList was full + //.................................................................. + else if (rc == RC_DOLIST_FULL) { + return (RC_DOLIST_FULL); + } + + //.................................................................. + // Service the connection, to clean out the receive queues + //.................................................................. + net->Service(); + } + + //--------------------------------------------------------------------- + // Debug output + //--------------------------------------------------------------------- + Print_Framesync_Values(Frame, MPlayerMaxAhead, net->Num_Connections(), + their_recv, their_sent, my_sent); + + //--------------------------------------------------------------------- + // Attempt to advance to the next frame. + //--------------------------------------------------------------------- + //..................................................................... + // For the first-time run, just check to see if we've heard from + // everyone. + //..................................................................... + if (first_time) { + if (num_ready >= net->Num_Connections()) break; + } + //..................................................................... + // For in-game processing, we have to check their_sent, their_recv, + // their_frame, etc. + //..................................................................... + else { + if (Can_Advance(net, MPlayerMaxAhead, their_frame, their_sent, + their_recv)) { + break; + } + } + + //--------------------------------------------------------------------- + // Service game stuff. Servicing the map's input, and rendering the + // map, allows the map to scroll even though we're hung up waiting for + // packets. Don't do this if 'first_time' is set, since users could be + // waiting a very long time for all systems to load the scenario, and + // it gets frustrating being able to scroll around without doing + // anything. + //--------------------------------------------------------------------- + Call_Back(); + if (!first_time && SpecialDialog == SDLG_NONE && reconnect_dlg==0) { + WWMouse->Erase_Mouse(&HidPage, TRUE); + Map.Input(input, x, y); + if (input) + Keyboard_Process(input); + Map.Render(); + } + + } /* end of while */ + + //------------------------------------------------------------------------ + // If the reconnect dialog was shown, force the map to redraw. + //------------------------------------------------------------------------ + if (reconnect_dlg) { + Map.Flag_To_Redraw(true); + Map.Render(); + } + + return (RC_NORMAL); + +} // end of Wait_For_Players + + +/*************************************************************************** + * Generate_Timing_Event -- computes & queues a RESPONSE_TIME event * + * * + * This routine adjusts the connection timing on the local system; it also * + * optionally generates a RESPONSE_TIME event, to tell all systems to * + * dynamically adjust the current MaxAhead value. This allows both the * + * MaxAhead & the connection retry logic to have dynamic timing, to adjust * + * to varying line conditions. * + * * + * INPUT: * + * net ptr to connection manager * + * my_sent # commands I've sent out so far * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static void Generate_Timing_Event(ConnManClass *net, int my_sent) +{ + unsigned long resp_time; // connection response time, in ticks + EventClass ev; + + //------------------------------------------------------------------------ + // Measure the current connection response time. This time will be in + // 60ths of a second, and represents full round-trip time of a packet. + // To convert to one-way packet time, divide by 2; to convert to game + // frames, divide again by 4, assuming a game rate of 15 fps. + //------------------------------------------------------------------------ + resp_time = net->Response_Time(); + + //------------------------------------------------------------------------ + // Adjust my connection retry timing; only do this if I've sent out more + // than 5 commands, so I know I have a measure of the response time. + //------------------------------------------------------------------------ + if (my_sent > 5) { + + net->Set_Timing (resp_time + 10, -1, (resp_time * 4)+15); + + //..................................................................... + // If I'm the network "master", I'm also responsible for updating the + // MaxAhead value on all systems, so do that here too. + //..................................................................... + if (MPlayerLocalID == MPlayerID[0]) { + ev.Type = EventClass::RESPONSE_TIME; + //.................................................................. + // For multi-frame compressed events, the MaxAhead must be an even + // multiple of the FrameSendRate. + //.................................................................. + if (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { + ev.Data.FrameInfo.Delay = MAX( ((((resp_time / 8) + + (FrameSendRate - 1)) / FrameSendRate) * + FrameSendRate), (FrameSendRate * 2) ); +char flip[128]; +sprintf (flip, "C&C95 - Generating timing packet - MaxAhead = %d frames\n", ev.Data.FrameInfo.Delay); +CCDebugString (flip); + + } + //.................................................................. + // For sending packets every frame, just use the 1-way connection + // response time. + //.................................................................. + else { + if (GameToPlay == GAME_MODEM + || GameToPlay == GAME_NULL_MODEM){ + //|| GameToPlay == GAME_INTERNET) { + ev.Data.FrameInfo.Delay = MAX( (resp_time / 8), + MODEM_MIN_MAX_AHEAD ); + } + else if (GameToPlay == GAME_IPX || GameToPlay == GAME_INTERNET) { + ev.Data.FrameInfo.Delay = MAX( (resp_time / 8), + NETWORK_MIN_MAX_AHEAD ); + } + } + OutList.Add(ev); + } + } + +} // end of Generate_Timing_Event + + +/*************************************************************************** + * Generate_Real_Timing_Event -- Generates a TIMING event * + * * + * INPUT: * + * net ptr to connection manager * + * my_sent # commands I've sent out so far * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/02/1996 BRR : Created. * + *=========================================================================*/ +static void Generate_Real_Timing_Event(ConnManClass *net, int my_sent) +{ + unsigned long resp_time; // connection response time, in ticks + EventClass ev; + int highest_ticks; + int i; + int specified_frame_rate; + int maxahead; + + // + // If we haven't sent out at least 5 guaranteed-delivery packets, don't + // bother trying to measure our connection response time; just return. + // + if (my_sent < 5) { + return; + } + + // + // Find the highest processing time we have stored + // + highest_ticks = 0; + for (i = 0; i < MPlayerCount; i++) { + // + // If we haven't heard from all systems yet, bail out. + // + if (TheirProcessTime[i] == -1) { + return; + } + if (TheirProcessTime[i] > highest_ticks) { + highest_ticks = TheirProcessTime[i]; + } + } + + // + // Compute our "desired" frame rate as the lower of: + // - What the user has dialed into the options screen + // - What we're really able to run at + // + if (highest_ticks == 0) { + DesiredFrameRate = 60; + } else { + DesiredFrameRate = 60 / highest_ticks; + } + + if (Options.GameSpeed == 0) { + specified_frame_rate = 60; + } else { + specified_frame_rate = 60 / Options.GameSpeed; + } + + DesiredFrameRate = MIN (DesiredFrameRate, specified_frame_rate); + + // + // Measure the current connection response time. This time will be in + // 60ths of a second, and represents full round-trip time of a packet. + // To convert to one-way packet time, divide by 2; to convert to game + // frames, ....uh.... + // + resp_time = net->Response_Time(); + + // + // Compute our new 'MaxAhead' value, based upon the response time of our + // connection and our desired frame rate. + // 'MaxAhead' in frames is: + // + // (resp_time / 2 ticks) * (1 sec/60 ticks) * (n Frames / sec) + // + // resp_time is divided by 2 because, as reported, it represents a round- + // trip, and we only want to use a one-way trip. + // + maxahead = (resp_time * DesiredFrameRate) / (2 * 60); + + // + // Now, we have to round 'maxahead' so it's an even multiple of our + // send rate. It also must be at least thrice the FrameSendRate. + // (Isn't "thrice" a cool word?) + // + maxahead = ((maxahead + FrameSendRate - 1) / FrameSendRate) * FrameSendRate; + maxahead = MAX (maxahead, FrameSendRate * 3); + + ev.Type = EventClass::TIMING; + ev.Data.Timing.DesiredFrameRate = DesiredFrameRate; + ev.Data.Timing.MaxAhead = maxahead; + + OutList.Add(ev); + + // + // Adjust my connection retry timing. These values set the retry timeout + // to just over one round-trip time, the 'maxretries' to -1, and the + // connection timeout to allow for about 4 retries. + // + net->Set_Timing (resp_time + 10, -1, (resp_time * 4)+15); + +} + + +/*************************************************************************** + * Generate_Process_Time_Event -- Generates a PROCESS_TIME event * + * * + * INPUT: * + * net ptr to connection manager * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 07/02/1996 BRR : Created. * + *=========================================================================*/ +static void Generate_Process_Time_Event(ConnManClass *net) +{ + EventClass ev; + int avgticks; + unsigned long resp_time; // connection response time, in ticks + + // + // Measure the current connection response time. This time will be in + // 60ths of a second, and represents full round-trip time of a packet. + // To convert to one-way packet time, divide by 2; to convert to game + // frames, ....uh.... + // + resp_time = net->Response_Time(); + + // + // Adjust my connection retry timing. These values set the retry timeout + // to just over one round-trip time, the 'maxretries' to -1, and the + // connection timeout to allow for about 4 retries. + // + net->Set_Timing (resp_time + 10, -1, (resp_time * 4)+15); + + if (IsMono) { + MonoClass::Enable(); + Mono_Set_Cursor(0,23); + Mono_Printf("Processing Ticks:%03d Frames:%03d\n", ProcessTicks,ProcessFrames); + MonoClass::Disable(); + } + + avgticks = ProcessTicks / ProcessFrames; + + ev.Type = EventClass::PROCESS_TIME; + ev.Data.ProcessTime.AverageTicks = avgticks; +char flip[128]; +sprintf (flip, "C&C95 - Sending PROCESS_TIME packet of %04x ticks\n", ev.Data.ProcessTime.AverageTicks); +CCDebugString (flip); + + OutList.Add(ev); + + ProcessTicks = 0; + ProcessFrames = 0; +} + + +/*************************************************************************** + * Process_Send_Period -- timing for sending packets every 'n' frames * + * * + * This function is for a CommProtocol of COMM_PROTOCOL_MULTI_E_COMP only. * + * It determines if it's time to send a packet or not. * + * * + * INPUT: * + * net ptr to connection manager * + * * + * OUTPUT: * + * 1 = it's time to send a packet; 0 = don't send a packet this frame. * + * * + * WARNINGS: * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static int Process_Send_Period(ConnManClass *net) +{ + //------------------------------------------------------------------------ + // If the current frame # is not an even multiple of 'FrameSendRate', then + // it's not time to send a packet; just return. + //------------------------------------------------------------------------ + if (Frame != (((Frame + (FrameSendRate - 1)) / + FrameSendRate) * FrameSendRate) ) { + + net->Service(); + + if (IsMono) { + MonoClass::Disable(); + } + + return (0); + } + + return (1); + +} // end of Process_Send_Period + + +/*************************************************************************** + * Send_Packets -- sends out events from the OutList * + * * + * This routine computes how many events can be sent this frame, and then * + * builds the "meta-packet" & sends it. * + * * + * The 'cap' value is the max # of events we can send. Ideally, it should * + * be based upon the bandwidth of our connection. Currently, it's just * + * hardcoded to prevent the modem from having to resend "too much" data, * + * which is about 200 bytes per frame. * + * * + * INPUT: * + * net ptr to connection manager * + * multi_packet_buf buffer to store packets in * + * multi_packet_max max size of multi_packet_buf * + * max_ahead current game MaxAhead value * + * my_sent # commands I've sent this game * + * * + * OUTPUT: * + * # events sent, NOT including the FRAMEINFO event * + * * + * WARNINGS: * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static int Send_Packets(ConnManClass *net, char *multi_packet_buf, + int multi_packet_max, int max_ahead, int my_sent) +{ + int cap; // max # events to send, NOT including FRAMEINFO event + int do_once; // true: only go through packet loop once + int ack_req; // 0 = no ack required on outgoing packet + int packetlen; // size of meta-packet sent + + //------------------------------------------------------------------------ + // Determine how many events it's OK to send this frame. + //------------------------------------------------------------------------ + //........................................................................ + // If we have 4 or more packets queue'd for sending, don't add any more + // this frame. + //........................................................................ + if (net->Private_Num_Send() >= 4) { + cap = 0; + do_once = 1; + } + //........................................................................ + // If there are 2 or more packets queued, the entire packet we send must + // fit within a single ComQueue buffer, so limit # events to 5. + // (The Modem connection manager has a max buffer size of 200 bytes, which + // is large enough for 6 uncompressed events, which leaves room for 5 + // events plus a FRAMEINFO.) + //........................................................................ + else if (net->Private_Num_Send() >= 2) { + cap = 5; + do_once = 1; + + } + //........................................................................ + // Otherwise, just send all events in the OutList + //........................................................................ + else { + cap = OutList.Count; + do_once = 0; + } + //........................................................................ + // Make sure we aren't sending more events than are in the OutList + //........................................................................ + if (cap > OutList.Count) { + cap = OutList.Count; + } + + //........................................................................ + // Make sure we don't send so many events that our DoList fills up + //........................................................................ + if (cap > (MAX_EVENTS * 8) - DoList.Count) { + cap = (MAX_EVENTS * 8) - DoList.Count; + } + + /* + ** No cap for internet game + ** + ** Or for serial games for that matter ST - 5/31/96 4:00PM + */ + if (GameToPlay == GAME_INTERNET || GameToPlay == GAME_MODEM || GameToPlay == GAME_NULL_MODEM){ + cap = OutList.Count; + do_once = 0; + } + + //------------------------------------------------------------------------ + // Build our meta-packet & transmit it. + //------------------------------------------------------------------------ + while (1) { + + Update_Queue_Mono (net, 1); + + //..................................................................... + // If there are no commands this frame, we'll just be sending a FRAMEINFO + // packet; no ack is required. For the modem's sake, check + // MPlayerCount; no ACK is needed if we're just sending to someone + // who's left the game. + //..................................................................... + if (cap == 0 || OutList.Count == 0 || MPlayerCount == 1) { + ack_req = 0; + } + else { + ack_req = 1; + } + + //..................................................................... + // Build & send out our message + //..................................................................... + packetlen = Build_Send_Packet (multi_packet_buf, multi_packet_max, + max_ahead, my_sent, cap); + net->Send_Private_Message (multi_packet_buf, packetlen, ack_req); + + //..................................................................... + // Call Service() to actually send the packet + //..................................................................... + net->Service(); + + //..................................................................... + // Stop if there's no more data to send, or if our send queue is + // filling up. + //..................................................................... + if (OutList.Count == 0 || do_once) { + break; + } + } + + return (cap); + +} // end of Send_Packets + + +/*************************************************************************** + * Send_FrameSync -- Sends a FRAMESYNC packet * + * * + * This routine is used to periodically remind the other systems that * + * we're still here, and to tell them what frame # we're on, in case * + * they've missed my FRAMEINFO packets. * + * * + * INPUT: * + * net ptr to connection manager * + * cmd_count # commands I've sent so far * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static void Send_FrameSync(ConnManClass *net, int cmd_count) +{ + EventClass packet; + + //------------------------------------------------------------------------ + // Build a frame-sync event to send. FRAMESYNC packets contain a + // scenario-based CRC rather than a game-state-based CRC, to let the + // games compare scenario CRC's on startup. + //------------------------------------------------------------------------ + memset (&packet, 0, sizeof(EventClass)); + packet.Type = EventClass::FRAMESYNC; + if (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { + packet.Frame = ((Frame + MPlayerMaxAhead + (FrameSendRate - 1)) / + FrameSendRate) * FrameSendRate; + } + else { + packet.Frame = Frame + MPlayerMaxAhead; + } + packet.ID = Houses.ID(PlayerPtr); + packet.MPlayerID = MPlayerLocalID; + packet.Data.FrameInfo.CRC = ScenarioCRC; + packet.Data.FrameInfo.CommandCount = cmd_count; + packet.Data.FrameInfo.Delay = MPlayerMaxAhead; + + //------------------------------------------------------------------------ + // Send the event. For modem, this just sends to the other player; + // for network, it sends to everyone we're connected to. + //------------------------------------------------------------------------ + net->Send_Private_Message (&packet, (offsetof(EventClass, Data) + + size_of(EventClass, Data.FrameInfo)), 0 ); + + return; + +} // end of Send_FrameSync + + +/*************************************************************************** + * Process_Receive_Packet -- processes an incoming packet * + * * + * This routine receives a packet from another system, adds it to our * + * execution queue (the DoList), and updates my arrays of their frame #, * + * their commands-sent, and their commands-received. * + * * + * INPUT: * + * net ptr to connection manager * + * multi_packet_buf buffer containing packet(s) to parse * + * id id of sender * + * their_frame array containing frame #'s of other players * + * their_sent array containing command count of other players * + * their_recv array containing # recv'd cmds from other players * + * * + * OUTPUT: * + * RC_NORMAL: nothing unusual happened, although * + * their_sent or their_recv may have been * + * altered * + * RC_PLAYER_READY: player has been heard from for the 1st time; * + * this presumes that his original * + * 'their_frame[]' value was -1 when this * + * routine was called * + * RC_SCENARIO_MISMATCH: FRAMEINFO scenario CRC doesn't match; * + * normally only applies after loading a new * + * scenario or save-game * + * RC_DOLIST_FULL: fatal error; unable to add events to DoList * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static RetcodeType Process_Receive_Packet(ConnManClass *net, + char *multi_packet_buf, int id, int packetlen, long *their_frame, + unsigned short *their_sent, unsigned short *their_recv) +{ + EventClass *event; + int index; + RetcodeType retcode = RC_NORMAL; + int i; + + //------------------------------------------------------------------------ + // Get an event ptr to the incoming message + //------------------------------------------------------------------------ + event = (EventClass *)multi_packet_buf; + + //------------------------------------------------------------------------ + // Get the index of the sender + //------------------------------------------------------------------------ + index = net->Connection_Index(id); + + //------------------------------------------------------------------------ + // Compute the other player's frame # (at the time this packet was sent) + //------------------------------------------------------------------------ + if (their_frame[index] < + (int)(event->Frame - event->Data.FrameInfo.Delay)) { + + //..................................................................... + // If the original frame # for this player is -1, it means we've heard + // from this player for the 1st time; return the appropriate value. + //..................................................................... + if (their_frame[index]==-1) { + retcode = RC_PLAYER_READY; + } + + their_frame[index] = event->Frame - event->Data.FrameInfo.Delay; + } + + //------------------------------------------------------------------------ + // Extract the other player's CommandCount. This count will include + // the commands in this packet, if there are any. + //------------------------------------------------------------------------ + if (event->Data.FrameInfo.CommandCount > their_sent[index]) { + their_sent[index] = event->Data.FrameInfo.CommandCount; + } + + //------------------------------------------------------------------------ + // If this packet was not a FRAMESYNC packet: + // - Add the events in it to our DoList + // - Increment our commands-received counter by the number of non- + // FRAMEINFO packets received + //------------------------------------------------------------------------ + if (event->Type != EventClass::FRAMESYNC) { + //..................................................................... + // Break up the packet into its component events. A returned packet + // count of -1 indicates a fatal queue-full error. + //..................................................................... + i = Breakup_Receive_Packet( multi_packet_buf, packetlen); + if (i==-1) { + return (RC_DOLIST_FULL); + } + //..................................................................... + // Compute the actual # commands in the packet by subtracting off the + // FRAMEINFO event + //..................................................................... + if ( (event->Type==EventClass::FRAMEINFO) && (i > 0)) { + i--; + } + + their_recv[index] += i; + } + + //------------------------------------------------------------------------ + // If the event was a FRAMESYNC packet, there will be no commands to add, + // but we must check the ScenarioCRC value. + //------------------------------------------------------------------------ + else if (event->Data.FrameInfo.CRC != ScenarioCRC) { + return (RC_SCENARIO_MISMATCH); + } + + return (retcode); + +} // end of Process_Receive_Packet + + +/*************************************************************************** + * Process_Serial_Packet -- Handles an incoming serial packet * + * * + * This routine is needed because the modem classes don't support a * + * "global channel" like the network classes do, but that functionality is * + * still needed for modem communications. Specifically, the modem dialogs * + * transmit their own special packets back & forth, and messages are sent * + * using a special packet type. Thus, we have to call this routine when * + * we receive a modem packet, to allow it to process messages & dialog * + * packets. * + * * + * INPUT: * + * multi_packet_buf packet buffer to process * + * first_time 1 = this is the 1st game frame * + * * + * OUTPUT: * + * RC_NORMAL: this wasn't a SERIAL-type packet * + * RC_SERIAL_PROCESSED: this was a SERIAL-type packet, and was * + * processed; the other player is still connected, * + * even if he's not in the game. * + * RC_PLAYER_LEFT: other player has left the game * + * RC_HUNG_UP: we're getting our own packets back; thus, the * + * modem is mirroring our packets, which means the * + * modem hung up! * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static RetcodeType Process_Serial_Packet(char *multi_packet_buf, + int first_time) +{ + SerialPacketType *serial_packet; // for parsing serial packets + int player_gone; + EventClass *event; + char txt[MAX_MESSAGE_LENGTH+80]; + unsigned short magic_number; + unsigned short crc; + + //------------------------------------------------------------------------ + // Determine if this packet means that the other player has left the game + //------------------------------------------------------------------------ + serial_packet = (SerialPacketType *)multi_packet_buf; + player_gone = 0; + //........................................................................ + // On Frame 0, only a SIGN_OFF means the other player left; the other + // packet types may be left over from a previous session. + //........................................................................ + if (first_time) { + if (serial_packet->Command == SERIAL_SIGN_OFF) { + player_gone = 1; + } + } + //........................................................................ + // On subsequent frames, any of SIGN_OFF, TIMING, or SCORE_SCREEN means + // the other player is gone. + //........................................................................ + else { + if (serial_packet->Command == SERIAL_SIGN_OFF || + serial_packet->Command == SERIAL_TIMING || + serial_packet->Command == SERIAL_SCORE_SCREEN ) { + player_gone = 1; + } + } + if (player_gone) { + Destroy_Null_Connection(serial_packet->Color, 0); + return (RC_PLAYER_LEFT); + } + + //------------------------------------------------------------------------ + // Process an incoming message + //------------------------------------------------------------------------ + if (serial_packet->Command == SERIAL_MESSAGE) { + sprintf(txt, Text_String(TXT_FROM), serial_packet->Name, + serial_packet->Message); + + magic_number = *((unsigned short*)(serial_packet->Message + COMPAT_MESSAGE_LENGTH-4)); + crc = *((unsigned short*)(serial_packet->Message + COMPAT_MESSAGE_LENGTH-2)); + + Messages.Add_Message (txt, + MPlayerTColors[MPlayerID_To_ColorIndex(serial_packet->ID)], + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, magic_number, crc); + + //..................................................................... + // Save this message in our last-message buffer + //..................................................................... + if (strlen (serial_packet->Message)) { + strcpy (LastMessage, serial_packet->Message); + } + + //..................................................................... + // Tell the map to do a partial update (just to force the + // messages to redraw). + //..................................................................... + Map.Flag_To_Redraw(false); + return (RC_SERIAL_PROCESSED); + } + + //------------------------------------------------------------------------ + // Any other SERIAL-type packet means the other player is still there; + // throw them away, but let the caller know the connection is OK. + //------------------------------------------------------------------------ + if ( (serial_packet->Command >= SERIAL_CONNECT && + serial_packet->Command < SERIAL_LAST_COMMAND) || + MPlayerCount == 1) { + return (RC_SERIAL_PROCESSED); + } + + //........................................................................ + // are we getting our own packets back?? + //........................................................................ + event = (EventClass *)multi_packet_buf; + if (event->ID == Houses.ID(PlayerPtr)) { + return (RC_HUNG_UP); + } + + return (RC_NORMAL); + +} // end of Process_Serial_Packet + + +/*************************************************************************** + * Can_Advance -- determines if it's OK to advance to the next frame * + * * + * This routine uses the current values stored in their_frame[], * + * their_send[], and their_recv[] to see if it's OK to advance to the next * + * game frame. We must not advance if: * + * - If our frame # would be too far ahead of the slowest player (the * + * lowest their_frame[] value). "Too far" means * + * (Frame > their_frame + MaxAhead). * + * - our current command count doesn't match the sent command count of one * + * other player (meaning that we've missed a command packet from that * + * player, and thus the frame # we're receiving from him may be due to a * + * FRAMEINFO packet sent later than the command, so we shouldn't use * + * this frame # to see if we should advance; we should wait until we * + * have all the commands before we advance. * + * * + * Of course, this routine assumes the values in their_frame[] etc are * + * kept current by the caller. * + * * + * INPUT: * + * net ptr to connection manager * + * max_ahead max frames ahead * + * their_frame array of their frame #'s * + * their_sent array of their sent command count * + * their_recv array of their # received commands * + * * + * OUTPUT: * + * 1 = OK to advance; 0 = not OK * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static int Can_Advance(ConnManClass *net, int max_ahead, long *their_frame, + unsigned short *their_sent, unsigned short *their_recv) +{ + long their_oldest_frame; // other players' oldest frame # + int count_ok; // true = my cmd count matches theirs + int i; + + //------------------------------------------------------------------------ + // Special case for modem: if the other player has left, go ahead and + // advance to the next frame; don't wait on him. + //------------------------------------------------------------------------ + if (MPlayerCount == 1) { + return (1); + } + //------------------------------------------------------------------------ + // Find the oldest frame # in 'their_frame' + //------------------------------------------------------------------------ + their_oldest_frame = Frame + 1000; + for (i = 0; i < net->Num_Connections(); i++) { + if (their_frame[i] < their_oldest_frame) + their_oldest_frame = their_frame[i]; + } + + //------------------------------------------------------------------------ + // I can advance to the next frame IF: + // 1) I'm less than a one-way propogation delay ahead of the other + // players' frame numbers, AND + // 2) their_recv[i] >= their_sent[i] (ie I've received all the commands + // the other players have sent so far). + //------------------------------------------------------------------------ + count_ok = 1; + for (i = 0; i < net->Num_Connections(); i++) { + if (their_recv[i] < their_sent[i]) { + count_ok = 0; + break; + } + } + if (count_ok && (Frame < (their_oldest_frame + max_ahead))) { + return (1); + } + + return (0); + +} // end of Can_Advance + + +/*************************************************************************** + * Process_Reconnect_Dialog -- processes the reconnection dialog * + * * + * This routine [re]draws the reconnection dialog; if 'reconn' is set, * + * it tells the user who we're trying to reconnect to; otherwise, is just * + * says something generic like "Waiting for connections". * + * * + * INPUT: * + * timeout_timer ptr to count down timer, showing time remaining * + * their_frame array of other players' frame #'s * + * num_conn # connections in 'their_frame' * + * reconn 1 = reconnect, 0 = waiting for first-time connection * + * fresh 1 = draw from scratch, 0 = only update time counter * + * * + * OUTPUT: * + * 1 = user wants to cancel, 0 = not * + * * + * WARNINGS: * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static int Process_Reconnect_Dialog(CountDownTimerClass *timeout_timer, + long *their_frame, int num_conn, int reconn, int fresh) +{ + static int displayed_time = 0; // time value currently displayed + int new_time; + int oldest_index; // index of person requiring a reconnect + int i,j; + + //------------------------------------------------------------------------ + // Convert the timer to seconds + //------------------------------------------------------------------------ + new_time = timeout_timer->Time() / 60; + + //-------------------------------------------------------------------------------- + // If we have just received input focus again after running in the background then + // we need to redraw the whole dialog. + //-------------------------------------------------------------------------------- + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + fresh = true; + } + + //------------------------------------------------------------------------ + // If the timer has changed, or 'fresh' is set, redraw the dialog + //------------------------------------------------------------------------ + if (fresh || (new_time != displayed_time)) { + //..................................................................... + // Find the index of the person we're trying to reconnect to + //..................................................................... + if (reconn) { + j = 0x7fffffff; + oldest_index = 0; + for (i = 0; i < num_conn; i++) { + if (their_frame[i] < j) { + j = their_frame[i]; + oldest_index = i; + } + } + } + Net_Reconnect_Dialog(reconn, fresh, oldest_index, new_time); + } + displayed_time = new_time; + + //........................................................................ + // If user hits ESC, bail out + //........................................................................ + if (Check_Key()) { + if (Get_Key_Num()==KN_ESC) { + return (1); + } + } + + return (0); + +} // end of Process_Reconnect_Dialog + + +/*************************************************************************** + * Handle_Timeout -- handles a timeout in the wait-for-players loop * + * * + * This routine "gracefully" handles a timeout in the frame-sync loop. * + * The timeout must be handled differently by a modem game or network * + * game. * + * * + * The modem game must detect if the other player is still connected * + * physically, even if he's not playing the game any more; if so, this * + * routine returns an OK status. If the other player isn't even * + * physically connected, an error is returned. * + * * + * The network game must find the connection that's causing the timeout, * + * and destroy it. The game continues, even if there are no more human * + * players left. * + * * + * INPUT: * + * net ptr to connection manager * + * their_frame array containing frame #'s of other players * + * their_sent array containing command count of other players * + * their_recv array containing # recv'd cmds from other players * + * * + * OUTPUT: * + * 1 = it's OK; reset timeout timers & keep processing * + * 0 = game over, man * + * * + * WARNINGS: * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static int Handle_Timeout(ConnManClass *net, long *their_frame, + unsigned short *their_sent, unsigned short *their_recv) +{ + int oldest_index; // index of person requiring a reconnect + int i,j; + int id; + + //------------------------------------------------------------------------ + // For modem, attempt to reconnect; if that fails, save the game & bail. + //------------------------------------------------------------------------ + if (GameToPlay == GAME_MODEM || GameToPlay == GAME_NULL_MODEM) { + if ( net->Num_Connections() ) { + if (!Reconnect_Modem()) { + return (0); + } + else { + return (1); + } + } + } + + //------------------------------------------------------------------------ + // For network, destroy the oldest connection + //------------------------------------------------------------------------ + else if (GameToPlay == GAME_IPX || GameToPlay == GAME_INTERNET) { + j = 0x7fffffff; + oldest_index = 0; + for (i = 0; i < net->Num_Connections(); i++) { + if (their_frame[i] < j) { + j = their_frame[i]; + oldest_index = i; + } + } + + id = net->Connection_ID(oldest_index); + + /* + ** Send the game statistics packet now if the game is effectivly over + */ + if (MPlayerCount == 2 && + GameToPlay == GAME_INTERNET && + !GameStatisticsPacketSent){ + Register_Game_End_Time(); + ConnectionLost = true; + Send_Statistics_Packet(); + } + + if (id != ConnManClass::CONNECTION_NONE) { + for (i = oldest_index; i < net->Num_Connections() - 1; i++) { + their_frame[i] = their_frame[i+1]; + their_sent[i] = their_sent[i+1]; + their_recv[i] = their_recv[i+1]; + } + CCDebugString ("C&C95 = Destroying connection due to time out\n"); + Destroy_Connection(id,1); + } + } + + return (1); + +} // end of Handle_Timeout + + +/*************************************************************************** + * Stop_Game -- stops the game * + * * + * This routine clears any global flags that need it, in preparation for * + * halting the game prematurely. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/22/1995 BRR : Created. * + *=========================================================================*/ +static void Stop_Game(void) +{ + CCDebugString ("C&C95 - In Stop_Game.\n"); + GameActive = 0; + if (IsMono) { + MonoClass::Disable(); + } + + if (GameToPlay == GAME_INTERNET){ + ConnectionLost = true; + CCDebugString ("C&C95 - About to send statistics packet.\n"); + Register_Game_End_Time(); + Send_Statistics_Packet(); + CCDebugString ("C&C95 - Returned from sending stats packet.\n"); + } + + return; + +} // end of Stop_Game + + +/*************************************************************************** + * Build_Send_Packet -- Builds a big packet from a bunch of little ones. * + * * + * This routine takes events from the OutList, and puts them into a * + * "meta-packet", which is transmitted to all systems we're connected to. * + * Also, these events are added to our own DoList. * + * * + * Every Meta-Packet we send uses a FRAMEINFO packet as a header; this * + * tells the other systems what frame we're on, as well as serving as a * + * standard packet header. * + * * + * INPUT: * + * buf buffer to store packet in * + * bufsize max size of buffer * + * frame_delay desired frame delay to attach to all outgoing packets * + * num_cmds value to use for the CommandCount field * + * cap max # events to send * + * * + * OUTPUT: * + * new size of packet * + * * + * WARNINGS: * + * 'num_cmds' should be the total of of commands, including all those sent * + * this frame! * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static int Build_Send_Packet(void *buf, int bufsize, int frame_delay, + int num_cmds, int cap) +{ + int size = 0; + EventClass *finfo; + + //------------------------------------------------------------------------ + // All events start with a FRAMEINFO event; fill this part in. + //------------------------------------------------------------------------ + //........................................................................ + // Set the event type + //........................................................................ + finfo = (EventClass *)buf; + finfo->Type = EventClass::FRAMEINFO; + //........................................................................ + // Set the frame to execute this event on; this is protocol-specific + //........................................................................ + if (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { + finfo->Frame = ((Frame + frame_delay + (FrameSendRate - 1)) / + FrameSendRate) * FrameSendRate; + } + else { + finfo->Frame = Frame + frame_delay; + } + //........................................................................ + // Fill in the rest of the event + //........................................................................ + finfo->ID = Houses.ID(PlayerPtr); + finfo->MPlayerID = MPlayerLocalID; + finfo->Data.FrameInfo.CRC = GameCRC; + finfo->Data.FrameInfo.CommandCount = num_cmds; + finfo->Data.FrameInfo.Delay = frame_delay; + + //------------------------------------------------------------------------ + // Initialize the # of bytes processed; this is protocol-specific + //------------------------------------------------------------------------ + if (CommProtocol==COMM_PROTOCOL_SINGLE_NO_COMP) { + size += sizeof(EventClass); + } + else { + size += (offsetof(EventClass, Data) + + size_of(EventClass, Data.FrameInfo)); + } + + //------------------------------------------------------------------------ + // Transfer all events from the OutList into the DoList, building our + // packet while we go. + //------------------------------------------------------------------------ + switch (CommProtocol) { + //..................................................................... + // COMM_PROTOCOL_SINGLE_NO_COMP: + // We'll send at least a FRAMEINFO every single frame, no compression + //..................................................................... + case (COMM_PROTOCOL_SINGLE_NO_COMP): + size = Add_Uncompressed_Events(buf, bufsize, frame_delay, size, cap); + break; + + //..................................................................... + // COMM_PROTOCOL_SINGLE_E_COMP: + // Compress a group of packets into our send buffer; send out + // compressed packets every frame. + // COMM_PROTOCOL_MULTI_E_COMP: + // Compress a group of packets into our send buffer; send out + // compressed packets every 'n' frames. + //..................................................................... + case (COMM_PROTOCOL_SINGLE_E_COMP): + case (COMM_PROTOCOL_MULTI_E_COMP): + size = Add_Compressed_Events(buf, bufsize, frame_delay, size, cap); + break; + + //..................................................................... + // Default: We have no idea what to do, so do nothing. + //..................................................................... + default: + size = 0; + break; + } + + return( size ); + +} /* end of Build_Send_Packet */ + + +/*************************************************************************** + * Add_Uncompressed_Events -- adds uncompressed events to a packet * + * * + * INPUT: * + * buf buffer to store packet in * + * bufsize max size of buffer * + * frame_delay desired frame delay to attach to all outgoing packets * + * size current packet size * + * cap max # events to process * + * * + * OUTPUT: * + * new size value * + * * + * WARNINGS: * + * This routine MUST check to be sure it doesn't overflow the buffer. * + * * + * HISTORY: * + * 11/21/1995 DRD : Created. * + *=========================================================================*/ +static int Add_Uncompressed_Events(void *buf, int bufsize, int frame_delay, + int size, int cap) +{ + int num = 0; // # of events processed + int ev_size; // size of event we're adding + + //------------------------------------------------------------------------ + // Loop until there are no more events, or we've processed our max # of + // events, or the buffer is full. + //------------------------------------------------------------------------ + while (OutList.Count && (num < cap)) { + ev_size = sizeof(EventClass); + //..................................................................... + // Will the next event exceed the size of the buffer? If so, break. + //..................................................................... + if ( (size + ev_size) > bufsize ) { + return (size); + } + + //..................................................................... + // Set the event's frame delay + //..................................................................... + OutList.First().Frame = Frame + frame_delay; + + //..................................................................... + // Set the event's ID + //..................................................................... + OutList.First().ID = Houses.ID(PlayerPtr); + OutList.First().MPlayerID = MPlayerLocalID; + + //..................................................................... + // Transfer the event in OutList to DoList, un-queue the OutList + // event. If the DoList is full, stop transferring immediately. + //..................................................................... + OutList.First().IsExecuted = 0; + if (!DoList.Add(OutList.First())) { + return (size); + } + + //..................................................................... + // Add event to the send packet + //..................................................................... + memcpy ( ((char *)buf) + size, &OutList.First(), sizeof(EventClass) ); + size += sizeof(EventClass); + + //..................................................................... + // Increment our event counter; delete the last event from the queue + //..................................................................... + num++; + OutList.Next(); + } + + return (size); + +} // end of Add_Uncompressed_Events + + +/*************************************************************************** + * Add_Compressed_Events -- adds an compressed events to a packet * + * * + * INPUT: * + * buf buffer to store packet in * + * bufsize max size of buffer * + * frame_delay desired frame delay to attach to all outgoing packets * + * size reference to current packet size * + * cap max # events to process * + * * + * OUTPUT: * + * new size value * + * * + * WARNINGS: * + * This routine MUST check to be sure it doesn't overflow the buffer. * + * * + * HISTORY: * + * 11/21/1995 DRD : Created. * + *=========================================================================*/ +static int Add_Compressed_Events(void *buf, int bufsize, int frame_delay, + int size, int cap) +{ + int num = 0; // # of events processed + EventClass::EventType eventtype; // type of event being compressed + EventClass prevevent; // last event processed + int datasize; // size of element plucked from event union + int storedsize; // actual # bytes stored from event + unsigned char *unitsptr = NULL; // ptr to buffer pos to store mega. rep count + unsigned char numunits = 0; // megamission rep count value + bool missiondup = false; // flag: is this event a megamission repeat? + + //------------------------------------------------------------------------ + // clear previous event + //------------------------------------------------------------------------ + memset (&prevevent, 0, sizeof(EventClass)); + + //------------------------------------------------------------------------ + // Loop until there are no more events, we've processed our max # of + // events, or the buffer is full. + //------------------------------------------------------------------------ + while (OutList.Count && (num < cap)) { + eventtype = OutList.First().Type; + datasize = EventClass::EventLength[ eventtype ]; + //..................................................................... + // For a variable-sized event, pull the size from the event; otherwise, + // the size will be the data element size plus the event type value. + //..................................................................... + storedsize = datasize + sizeof (EventClass::EventType); + + //..................................................................... + // MegaMission compression: MegaMissions are stored as: + // EventType + // Rep Count + // MegaMission structure (event # 1 only) + // Whom #2 + // Whom #3 + // Whom #4 + // ... + // Whom #n + //..................................................................... + if (prevevent.Type == EventClass::MEGAMISSION) { + //.................................................................. + // If previous & current events are both MegaMissions: + //.................................................................. + if (eventtype == EventClass::MEGAMISSION) { + //............................................................... + // If the Mission, Target, & Destination are the same, compress + // the events into one: + // - Change datasize to the size of the 'Whom' field only + // - set total # bytes to store to the size of the 'Whom' only + // - increment the MegaMission rep count + // - set the MegaMission rep flag + //............................................................... + if (OutList.First().Data.MegaMission.Mission == + prevevent.Data.MegaMission.Mission && + OutList.First().Data.MegaMission.Target == + prevevent.Data.MegaMission.Target && + OutList.First().Data.MegaMission.Destination == + prevevent.Data.MegaMission.Destination) { + + datasize = sizeof(prevevent.Data.MegaMission.Whom); + storedsize = datasize; + numunits++; + missiondup = true; + } + //............................................................... + // Data doesn't match; start a new run of MegaMissions: + // - Store previous MegaMission rep count + // - Init 'unitsptr' to buffer pos after next EventType + // - set total # bytes to store to 'datasize' + sizeof(EventType) + + // sizeof (numunits) + // - init the MegaMission rep count to 1 + // - clear the MegaMission rep flag + //............................................................... + else { + *unitsptr = numunits; + unitsptr = ((unsigned char *)buf) + size + + sizeof(EventClass::EventType); + storedsize += sizeof(numunits); + numunits = 1; + missiondup = false; + } + } + //.................................................................. + // Previous event was a MegaMission, but this one isn't: end the + // run of MegaMissions: + // - Store previous MegaMission rep count + // - Clear variables + //.................................................................. + else { + *unitsptr = numunits; // save # events in our run + unitsptr = NULL; // init other values + numunits = 0; + missiondup = false; + } + } + + //..................................................................... + // The previous event is not a MEGAMISSION but the current event is: + // Set up a new run of MegaMissions: + // - Init 'unitsptr' to buffer pos after next EventType + // - set total # bytes to store to 'datasize' + sizeof(EventType) + + // sizeof (numunits) + // - init the MegaMission rep count to 1 + // - clear the MegaMission rep flag + //..................................................................... + else if (eventtype == EventClass::MEGAMISSION) { + unitsptr = ((unsigned char *)buf) + size + + sizeof(EventClass::EventType); + storedsize += sizeof(numunits); + numunits = 1; + missiondup = false; + } + + //..................................................................... + // Will the next event exceed the size of the buffer? If so, + // stop compressing. + //..................................................................... + if ( (size + storedsize) > bufsize ) + break; + + //..................................................................... + // Set the event's frame delay (this is protocol-dependent) + //..................................................................... + if (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { + OutList.First().Frame = ((Frame + frame_delay + + (FrameSendRate - 1)) / FrameSendRate) * + FrameSendRate; + } + else { + OutList.First().Frame = Frame + frame_delay; + } + + //..................................................................... + // Set the event's ID + //..................................................................... + OutList.First().ID = Houses.ID(PlayerPtr); + OutList.First().MPlayerID = MPlayerLocalID; + + //..................................................................... + // Transfer the event in OutList to DoList, un-queue the OutList event. + // If the DoList is full, stop transferring immediately. + //..................................................................... + OutList.First().IsExecuted = 0; + if ( !DoList.Add( OutList.First() ) ) { + break; + } + + //--------------------------------------------------------------------- + // Compress the event into the send packet buffer + //--------------------------------------------------------------------- + switch ( eventtype ) { + //.................................................................. + // RESPONSE_TIME: just use the Delay field of the FrameInfo union + //.................................................................. + case (EventClass::RESPONSE_TIME): + + *(EventClass::EventType *)( ((char *)buf) + size) = eventtype; + + memcpy ( ((char *)buf) + size + sizeof(EventClass::EventType), + &OutList.First().Data.FrameInfo.Delay, datasize ); + + size += (datasize + sizeof(EventClass::EventType)); + break; + + //.................................................................. + // MEGAMISSION: + //.................................................................. + case (EventClass::MEGAMISSION): + //............................................................... + // Repeated mission in a run: + // - Update the rep count (in case we break out) + // - Copy the Whom field only + //............................................................... + if (missiondup) { + *unitsptr = numunits; + + memcpy ( ((char *)buf) + size, + &OutList.First().Data.MegaMission.Whom, datasize ); + + size += datasize; + } + //............................................................... + // 1st mission in a run: + // - Init the rep count (in case we break out) + // - Set the EventType + // - Copy the MegaMission structure, leaving room for 'numunits' + //............................................................... + else { + *unitsptr = numunits; + + *(EventClass::EventType *)( ((char *)buf) + size) = eventtype; + + memcpy ( ((char *)buf) + size + + sizeof(EventClass::EventType) + sizeof(numunits), + &OutList.First().Data.MegaMission, datasize ); + + size += (datasize + sizeof(EventClass::EventType) + sizeof(numunits)); + } + break; + + //.................................................................. + // Default case: Just copy over the data field from the union + //.................................................................. + default: + *(EventClass::EventType *)( ((char *)buf) + size) = eventtype; + + memcpy ( ((char *)buf) + size + sizeof(EventClass::EventType), + &OutList.First().Data, datasize ); + + size += (datasize + sizeof(EventClass::EventType)); + + break; + } + + //--------------------------------------------------------------------- + // update # events processed + //--------------------------------------------------------------------- + num++; + + //--------------------------------------------------------------------- + // Update 'prevevent' + //--------------------------------------------------------------------- + memcpy ( &prevevent, &OutList.First(), sizeof(EventClass) ); + + //--------------------------------------------------------------------- + // Go to the next event to process + //--------------------------------------------------------------------- + OutList.Next(); + } + + return (size); + +} // end of Add_Compressed_Events + + +/*************************************************************************** + * Breakup_Receive_Packet -- Splits a big packet into little ones. * + * * + * INPUT: * + * buf buffer to break up * + * bufsize length of buffer * + * * + * OUTPUT: * + * # events added to queue, -1 if fatal error (queue is full) * + * (return value includes any FRAMEINFO packets encountered; * + * FRAMESYNC's are ignored) * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static int Breakup_Receive_Packet(void *buf, int bufsize ) +{ + int count = 0; + + /* + ** is there enough leftover for another record + */ + switch (CommProtocol) { + case (COMM_PROTOCOL_SINGLE_NO_COMP): + count = Extract_Uncompressed_Events(buf, bufsize); + break; + + default: + count = Extract_Compressed_Events(buf, bufsize); + break; + } + + return (count); + +} /* end of Breakup_Receive_Packet */ + + +/*************************************************************************** + * Extract_Uncompressed_Events -- extracts events from a packet * + * * + * INPUT: * + * buf buffer containing events to extract * + * bufsize length of 'buf' * + * * + * OUTPUT: * + * # events extracted * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1995 DRD : Created. * + *=========================================================================*/ +static int Extract_Uncompressed_Events(void *buf, int bufsize) +{ + int count = 0; + int pos = 0; + int leftover = bufsize; + EventClass *event; + + //------------------------------------------------------------------------ + // Loop until there are no more events in the packet + //------------------------------------------------------------------------ + while (leftover >= sizeof(EventClass) ) { + event = (EventClass *)(((char *)buf) + pos); + + //..................................................................... + // add event to the DoList, only if it's not a FRAMESYNC + // (but FRAMEINFO's do get added.) + //..................................................................... + if (event->Type != EventClass::FRAMESYNC) { + event->IsExecuted = 0; + + if (!DoList.Add( *event )) { + return (-1); + } + + //.................................................................. + // Keep count of how many events we add to the queue + //.................................................................. + count++; + } + + //..................................................................... + // Point to the next position in the buffer; decrement our 'leftover' + //..................................................................... + pos += sizeof(EventClass); + leftover -= sizeof(EventClass); + } + + return (count); + +} // end of Extract_Uncompressed_Events + + +/*************************************************************************** + * Extract_Compressed_Events -- extracts events from a packet * + * * + * INPUT: * + * buf buffer containing events to extract * + * bufsize length of 'buf' * + * * + * OUTPUT: * + * # events extracted * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1995 DRD : Created. * + *=========================================================================*/ +static int Extract_Compressed_Events(void *buf, int bufsize) +{ + int pos = 0; // current buffer parsing position + int leftover = bufsize; // # bytes left to process + EventClass *event; // event ptr for parsing buffer + int count = 0; // # events processed + int datasize = 0; // size of data to copy + EventClass eventdata; // stores Frame, ID, etc + unsigned char numunits = 0; // # units stored in compressed MegaMissions +//int lasteventtype=0; + //------------------------------------------------------------------------ + // Clear work event structure + //------------------------------------------------------------------------ + memset (&eventdata, 0, sizeof(EventClass)); + + //------------------------------------------------------------------------ + // Assume the first event is a FRAMEINFO event + // Init 'datasize' to the amount of data to copy, minus the EventType value + // For the 1st packet only, this will include all info before the Data + // union, plus the size of the FrameInfo structure, minus the EventType size. + //------------------------------------------------------------------------ + datasize = (offsetof(EventClass, Data) + + size_of(EventClass, Data.FrameInfo)) - sizeof(EventClass::EventType); + event = (EventClass *)(((char *)buf) + pos); + + while (leftover >= (datasize + sizeof(EventClass::EventType)) ) { + //..................................................................... + // add event to the DoList, only if it's not a FRAMESYNC + // (but FRAMEINFO's do get added.) + //..................................................................... + if (event->Type != EventClass::FRAMESYNC) { + //.................................................................. + // initialize the common data from the FRAMEINFO event + // keeping IsExecuted 0 + //.................................................................. + if (event->Type == EventClass::FRAMEINFO) { + eventdata.Frame = event->Frame; + eventdata.ID = event->ID; + eventdata.MPlayerID = event->MPlayerID; + + //............................................................... + // Adjust position past the common data + //............................................................... + pos += (offsetof(EventClass, Data) - + sizeof(EventClass::EventType)); + leftover -= (offsetof(EventClass, Data) - + sizeof(EventClass::EventType)); + } + //.................................................................. + // if MEGAMISSION event get the number of units (events to generate) + //.................................................................. + else if (event->Type == EventClass::MEGAMISSION) { + numunits = *(((unsigned char *)buf) + pos + sizeof(eventdata.Type)); + pos += sizeof(numunits); + leftover -= sizeof(numunits); + } + + //.................................................................. + // clear the union data portion of the event + //.................................................................. + memset (&eventdata.Data, 0, sizeof(eventdata.Data)); + eventdata.Type = event->Type; + datasize = EventClass::EventLength[ eventdata.Type ]; + + switch (eventdata.Type) { + case (EventClass::RESPONSE_TIME): + memcpy ( &eventdata.Data.FrameInfo.Delay, + ((char *)buf) + pos + sizeof(EventClass::EventType), + datasize ); + break; + + case (EventClass::MEGAMISSION): + memcpy ( &eventdata.Data.MegaMission, + ((char *)buf) + pos + sizeof(EventClass::EventType), + datasize ); + + if (numunits > 1) { + pos += (datasize + sizeof(EventClass::EventType)); + leftover -= (datasize + sizeof(EventClass::EventType)); + datasize = sizeof(eventdata.Data.MegaMission.Whom); + + while (numunits) { + if ( !DoList.Add( eventdata ) ) { + return (-1); + } + + //...................................................... + // Keep count of how many events we add to the queue + //...................................................... + count++; + numunits--; + memcpy ( &eventdata.Data.MegaMission.Whom, + ((char *)buf) + pos, datasize ); + + //...................................................... + // if one unit left fall thru to normal code + //...................................................... + if (numunits == 1) { + datasize -= sizeof(EventClass::EventType); + break; + } + else { + pos += datasize; + leftover -= datasize; + } + } + } + break; + + default: + memcpy ( &eventdata.Data, + ((char *)buf) + pos + sizeof(EventClass::EventType), + datasize ); + break; + } + +char flip[128]; +sprintf (flip, "C&C95 - Adding event type %d to queue\n", eventdata.Type); +CCDebugString (flip); + +//if (lasteventtype == 11){ +// break; +//} + +//lasteventtype = (int) eventdata.Type; + + + if ( !DoList.Add( eventdata ) ) { + return (-1); + } + + //.................................................................. + // Keep count of how many events we add to the queue + //.................................................................. + count++; + + pos += (datasize + sizeof(EventClass::EventType)); + leftover -= (datasize + sizeof(EventClass::EventType)); + + if (leftover) { + event = (EventClass *)(((char *)buf) + pos); + datasize = EventClass::EventLength[ event->Type ]; + if (event->Type == EventClass::MEGAMISSION) { + datasize += sizeof(numunits); + } + } + } + //..................................................................... + // FRAMESYNC event: This >should< be the only event in the buffer, + // and it will be uncompressed. + //..................................................................... + else { + pos += (datasize + sizeof(EventClass::EventType)); + leftover -= (datasize + sizeof(EventClass::EventType)); + event = (EventClass *)(((char *)buf) + pos); + + //.................................................................. + // size of FRAMESYNC event - EventType size + //.................................................................. + datasize = (offsetof(EventClass, Data) + + size_of(EventClass, Data.FrameInfo)) - + sizeof(EventClass::EventType); + } + } + + return (count); + +} // end of Extract_Compressed_Events + +#endif //DEMO + +/*************************************************************************** + * Execute_DoList -- Executes commands from the DoList * + * * + * This routine executes any events in the DoList that need to be executed * + * on the current game frame. The events must be executed in a special * + * order, so that all systems execute all events in exactly the same * + * order. * + * * + * This routine also handles checking the Game CRC sent by other systems * + * against my own, to be sure we're still in sync. * + * * + * INPUT: * + * max_houses # houses to execute commands for * + * base_house HousesType to start with * + * net ptr to connection manager; NULL if none * + * skip_crc a frame-based countdown timer; if it's non-zero, the * + * CRC check will be skipped. Ignored if NULL. * + * their_frame array of their frame #'s * + * their_sent array of # commands they've sent * + * their_recv array of # commands I've received from them * + * * + * (their_xxx are ignored if 'net' is NULL.) * + * * + * OUTPUT: * + * 1 = OK, 0 = some error occurred (CRC error, packet rcv'd too late.) * + * * + * WARNINGS: * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static int Execute_DoList(int , HousesType , + ConnManClass *net, TCountDownTimerClass *, + long *their_frame, unsigned short *their_sent, unsigned short *their_recv) +{ + int i,j,k,wibble; + int index; + + //------------------------------------------------------------------------ + // For a single-player game, just execute all events in the queue. + //------------------------------------------------------------------------ + if (GameToPlay == GAME_NORMAL) { + for (i = 0; i < DoList.Count; i++) { + if (Frame >= DoList[i].Frame && !DoList[i].IsExecuted) { + DoList[i].Execute(); // execute it + DoList[i].IsExecuted = true; // mark as having been executed + } + } + return (1); + } + +//#if(TIMING_FIX) + // + // If MPlayerMaxAhead is recomputed such that it increases, the systems + // may try to free-run to the new MaxAhead value. If so, they may miss + // an event that was generated after the TIMING event was created, but + // before it executed; this event will be scheduled with the older, + // shorter MaxAhead value. If a system doesn't receive this event, it + // may execute past the frame it's scheduled to execute on, creating + // a Packet-Recieved-Too-Late error. To prevent this, find any events + // that are scheduled to execute during this "period of vulnerability", + // and re-schedule for the end of that period. + // + if (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { + for (j = 0; j < DoList.Count; j++) { + if (DoList[j].Type != EventClass::FRAMEINFO && + DoList[j].Frame > NewMaxAheadFrame1 && + DoList[j].Frame < NewMaxAheadFrame2) { + DoList[j].Frame = NewMaxAheadFrame2; + } + } + } +//#endif + + //------------------------------------------------------------------------ + // Execute the DoList. Events must be executed in the same order on all + // systems; so, execute them in the order of the MPlayerID array. This + // array is stored in the same order on all systems. + //------------------------------------------------------------------------ + for (i = 0; i < MPlayerCount; i++) { + + HousesType house; + HouseClass *housep; + + house = MPlayerHouses [i]; + housep= HouseClass::As_Pointer (house); + + //..................................................................... + // If for some reason this house doesn't exist, skip it. + // Also, if this house has exited the game, skip it. (The user can + // generate events after he exits, because the exit event is scheduled + // at least FrameSendRate*3 frames ahead. If one system gets these + // packets & another system doesn't, they'll go out of sync because + // they aren't checking the CommandCount for that house, since that + // house isn't connected any more.) + //..................................................................... + if (!housep){ + continue; + } + + if (!housep->IsHuman){ + continue; + } + + //..................................................................... + // Loop through all events + //..................................................................... + for (j = 0; j < DoList.Count; j++) { + +#ifndef DEMO + if (net) + Update_Queue_Mono (net, 6); +#endif //DEMO + + //.................................................................. + // If this event was from the currently-executing player ID, and it's + // time to execute it, execute it. + //.................................................................. + if (DoList[j].MPlayerID == MPlayerID[i] && Frame >= DoList[j].Frame && + !DoList[j].IsExecuted) { + + //............................................................... + // Error if it's too late to execute this packet! + //............................................................... + if (Frame > DoList[j].Frame && DoList[j].Type != + EventClass::FRAMEINFO) { +#ifndef DEMO + Dump_Packet_Too_Late_Stuff(&DoList[j]); +#endif //DEMO + CCMessageBox().Process (TXT_PACKET_TOO_LATE); + return (0); + } + + //............................................................... + // Only execute EXIT & OPTIONS commands if they're from myself. + //............................................................... + if (DoList[j].Type==EventClass::EXIT || + DoList[j].Type==EventClass::OPTIONS) { + + + if (DoList[j].Type==EventClass::EXIT){ +CCDebugString ("C&C95 - Received EXIT packet\n"); + + /* + ** Flag that this house lost because it quit. ST - 6/5/96 0:29AM + */ + for (wibble = 0; wibble < MPlayerCount; wibble++) { + if (MPlayerID[wibble] == DoList[j].MPlayerID) { + house = MPlayerHouses[wibble]; + housep = HouseClass::As_Pointer (house); + housep->IGaveUp = true; + break; + } + } + + /* + ** Send the game statistics packet now since the game is effectivly over + */ + if (MPlayerCount == 2 && + GameToPlay == GAME_INTERNET && + !GameStatisticsPacketSent){ + Register_Game_End_Time(); + Send_Statistics_Packet(); + } + } + + + + if (DoList[j].ID == Houses.ID(PlayerPtr)) { + DoList[j].Execute(); + } + + //............................................................ + // If this EXIT event isn't from myself, destroy the connection + // for that player. The HousesType for this event is the + // connection ID. + //............................................................ + else if (DoList[j].Type==EventClass::EXIT) { + if (GameToPlay == GAME_MODEM + || GameToPlay == GAME_NULL_MODEM){ + //|| GameToPlay == GAME_INTERNET) { + Destroy_Null_Connection( DoList[j].MPlayerID, 0 ); + } + + else if ((GameToPlay == GAME_IPX || GameToPlay == GAME_INTERNET) && net) { + index = net->Connection_Index (DoList[j].MPlayerID); + if (index != -1) { + for (k = index; k < net->Num_Connections() - 1; k++) { + their_frame[k] = their_frame[k+1]; + their_sent[k] = their_sent[k+1]; + their_recv[k] = their_recv[k+1]; + } + CCDebugString ("C&C95 = Destroying connection due to exit event\n"); +#ifndef DEMO + Destroy_Connection(DoList[j].MPlayerID,0); +#endif //DEMO + } + } + } + } + + //............................................................... + // For a FRAMEINFO event, check the CRC value. + // This could be an old FRAMEINFO packet that was floating around + // for awhile and just arrived; if so, its Frame value will be + // old. Ignore these packets. (This created bogus sync bugs on + // the Internet, when packets that were 35 frames old arrived.) + //............................................................... +#ifndef DEMO + else if (DoList[j].Type == EventClass::FRAMEINFO) { + if (DoList[j].Frame == Frame && + DoList[j].Data.FrameInfo.Delay < 32) { + index = ((DoList[j].Frame - DoList[j].Data.FrameInfo.Delay) & + 0x001f); + if (CRC[index] != DoList[j].Data.FrameInfo.CRC) { + Print_CRCs(&DoList[j]); + if (CCMessageBox().Process (TXT_OUT_OF_SYNC, + TXT_CONTINUE, TXT_STOP) == 0) { + if (GameToPlay == GAME_MODEM || + GameToPlay == GAME_NULL_MODEM){ + Destroy_Null_Connection( DoList[j].MPlayerID, -1 ); + Shutdown_Modem(); + GameToPlay = GAME_NORMAL; + } + else if ((GameToPlay == GAME_IPX || GameToPlay == GAME_INTERNET) && net) { + CCDebugString ("C&C95 = Destroying connections due to bad frame info packet\n"); + while (net->Num_Connections()) { + Destroy_Connection (net->Connection_ID(0), -1); + } + } + Map.Flag_To_Redraw(true); + } + else { + return (0); + } + return (1); + } + } + } +#endif //DEMO + //............................................................... + // Execute other commands + //............................................................... + else { + DoList[j].Execute(); + } + + //............................................................... + // Mark this event as executed. + //............................................................... + DoList[j].IsExecuted = 1; + } + } + } + + return (1); + +} // end of Execute_DoList + + +/*************************************************************************** + * Clean_DoList -- Cleans out old events from the DoList * + * * + * Currently, an event can only be removed from the DoList if it's at the * + * head of the list; and event can't be removed from the middle. So, * + * this routine loops as long as the next event in the DoList has been * + * executed, it's removed. * + * * + * INPUT: * + * net ptr to connection manager; ignored if NULL * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static void Clean_DoList(ConnManClass *net) +{ + while (DoList.Count) { + +#ifndef DEMO + if (net) + Update_Queue_Mono (net, 7); +#else + net = net; +#endif //DEMO + + //..................................................................... + // Discard events that have been executed, OR it's too late to execute. + // (This happens if another player exits the game; he'll leave FRAMEINFO + // events lying around in my queue. They won't have been "executed", + // because his IPX connection was destroyed.) + //..................................................................... + if ( (DoList.First().IsExecuted) || (Frame > DoList.First().Frame) ) { + DoList.Next(); + } + else { + break; + } + } + +} // end of Clean_DoList + +#ifndef DEMO + +/*************************************************************************** + * Queue_Record -- Records the DoList to disk * + * * + * This routine just saves any events in the DoList to disk; we can later * + * "play back" the recording just be pulling events from disk rather than * + * from the network! * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/14/1995 BRR : Created. * + *=========================================================================*/ +static void Queue_Record(void) +{ + int i,j; + + //------------------------------------------------------------------------ + // Compute # of events to save this frame + //------------------------------------------------------------------------ + j = 0; + for (i = 0; i < DoList.Count; i++) { + if (Frame == DoList[i].Frame && !DoList[i].IsExecuted) { + j++; + } + } + + //------------------------------------------------------------------------ + // Save the # of events, then all events. + //------------------------------------------------------------------------ + RecordFile.Write (&j,sizeof(j)); + for (i = 0; i < DoList.Count; i++) { + if (Frame == DoList[i].Frame && !DoList[i].IsExecuted) { + RecordFile.Write (&DoList[i],sizeof (EventClass)); + j--; + } + } + +} /* end of Queue_Record */ + + +/*************************************************************************** + * Queue_Playback -- plays back queue entries from a record file * + * * + * This routine reads events from disk, putting them into the DoList; * + * it then executes the DoList just like the network version does. The * + * result is that the game "plays back" like a recording. * + * * + * This routine detects mouse motion and stops playback, so it can work * + * like an "attract" mode, showing a demo of the game itself. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/15/1995 BRR : Created. * + *=========================================================================*/ +static void Queue_Playback(void) +{ + int numevents; + EventClass event; + int i; + int ok; + static int mx,my; + int max_houses; + HousesType base_house; + int key; + int testframe; + + //------------------------------------------------------------------------ + // If the user hits ESC, stop the playback + //------------------------------------------------------------------------ + if (Check_Key_Num()) { + key = Get_Key(); + // + // If the user hit ESC, end the recording. If this is an Attract-mode + // recording, end it no matter what the user does (any key or mouse). + // + if (key == KA_ESC || AllowAttract) { + GameActive = 0; + return; + } + } + + //------------------------------------------------------------------------ + // If we're in "Attact" mode, and the user moves the mouse, stop the + // playback. + //------------------------------------------------------------------------ + if (AllowAttract && Frame > 0 && + (mx != Get_Mouse_X() || my != Get_Mouse_Y())) { + GameActive = 0; + return; + } + mx = Get_Mouse_X(); + my = Get_Mouse_Y(); + + //------------------------------------------------------------------------ + // Compute the Game's CRC + //------------------------------------------------------------------------ + Compute_Game_CRC(); + CRC[Frame & 0x001f] = GameCRC; + + //------------------------------------------------------------------------ + // Don't read anything the first time through (since the Queue_AI_Network + // routine didn't write anything the first time through); do this after the + // CRC is computed, since we'll still need a CRC for Frame 0. + //------------------------------------------------------------------------ + if (Frame==0 && GameToPlay!=GAME_NORMAL) { + return; + } + + //------------------------------------------------------------------------ + // Only process every 'FrameSendRate' frames + //------------------------------------------------------------------------ + testframe = ((Frame + (FrameSendRate - 1)) / FrameSendRate) * FrameSendRate; + + if (GameToPlay != GAME_NORMAL && + CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { + if (Frame != testframe) { + return; + } + } + + //------------------------------------------------------------------------ + // Read the DoList from disk + //------------------------------------------------------------------------ + ok = 1; + if (RecordFile.Read (&numevents, sizeof(numevents)) == + sizeof(numevents)) { + for (i = 0; i < numevents; i++) { + if (RecordFile.Read (&event, sizeof(EventClass)) == + sizeof(EventClass)) { + event.IsExecuted = 0; + DoList.Add (event); + } + else { + ok = 0; + break; + } + } + } + else { + ok = 0; + } + + if (!ok) { + GameActive = 0; + return; + } + + + //------------------------------------------------------------------------ + // Execute the DoList; if an error occurs, bail out. + //------------------------------------------------------------------------ + if (GameToPlay == GAME_NORMAL) { + max_houses = 1; + base_house = PlayerPtr->Class->House; + } + else { + max_houses = MPlayerMax; + base_house = HOUSE_MULTI1; + } + if (!Execute_DoList(max_houses, base_house, NULL, NULL, NULL, NULL, NULL)) { + GameActive = 0; + return; + } + + //------------------------------------------------------------------------ + // Clean out the DoList + //------------------------------------------------------------------------ + Clean_DoList(NULL); + +} /* end of Queue_Playback */ + + +/*************************************************************************** + * Compute_Game_CRC -- Computes a CRC value of the entire game. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 05/09/1995 BRR : Created. * + *=========================================================================*/ +static void Compute_Game_CRC(void) +{ + int i; + InfantryClass *infp; + UnitClass *unitp; + BuildingClass *bldgp; + //ObjectClass *objp; + + GameCRC = 0; + + //------------------------------------------------------------------------ + // Infantry + //------------------------------------------------------------------------ + for (i = 0; i < Infantry.Count(); i++) { + infp = (InfantryClass *)Infantry.Active_Ptr(i); + Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); + } + + //------------------------------------------------------------------------ + // Units + //------------------------------------------------------------------------ + for (i = 0; i < Units.Count(); i++) { + unitp = (UnitClass *)Units.Active_Ptr(i); + Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing + + (int)unitp->SecondaryFacing); + } + + //------------------------------------------------------------------------ + // Buildings + //------------------------------------------------------------------------ + for (i = 0; i < Buildings.Count(); i++) { + bldgp = (BuildingClass *)Buildings.Active_Ptr(i); + Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); + } + +#if(0) + //------------------------------------------------------------------------ + // Map Layers + //------------------------------------------------------------------------ + for (i = 0; i < LAYER_COUNT; i++) { + for (j = 0; j < Map.Layer[i].Count(); j++) { + objp = Map.Layer[i][j]; + Add_CRC (&GameCRC, (int)objp->Coord + (int)objp->What_Am_I()); + } + } + + //------------------------------------------------------------------------ + // Logic Layers + //------------------------------------------------------------------------ + for (i = 0; i < Logic.Count(); i++) { + objp = Logic[i]; + Add_CRC (&GameCRC, (int)objp->Coord + (int)objp->What_Am_I()); + } +#endif + + //------------------------------------------------------------------------ + // A random # + //------------------------------------------------------------------------ + Add_CRC(&GameCRC, rand()); + +} /* end of Compute_Game_CRC */ + + +/*************************************************************************** + * Add_CRC -- Adds a value to a CRC * + * * + * INPUT: * + * crc ptr to crc * + * val value to add * + * * + * OUTPUT: * + * none * + * * + * WARNINGS: * + * none * + * * + * HISTORY: * + * 05/09/1995 BRR : Created. * + *=========================================================================*/ +void Add_CRC(unsigned long *crc, unsigned long val) +{ + int hibit; + + if ( (*crc) & 0x80000000) { + hibit = 1; + } + else { + hibit = 0; + } + + (*crc) <<= 1; + (*crc) += val; + (*crc) += hibit; + +} /* end of Add_CRC */ + + +/*************************************************************************** + * Print_CRCs -- Prints a data file for finding Sync Bugs * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none * + * * + * WARNINGS: * + * none * + * * + * HISTORY: * + * 05/09/1995 BRR : Created. * + *=========================================================================*/ +void Print_CRCs(EventClass *ev) +{ + ev=ev; + + int i; //,j; + InfantryClass *infp; + UnitClass *unitp; + BuildingClass *bldgp; + //ObjectClass *objp; + FILE *fp; + int rnd; + HouseClass *housep; + //HousesType house; + int color; + + Mono_Clear_Screen(); + Mono_Set_Cursor (0,0); + + char filename[80]; + sprintf (filename, "CRC%02d.TXT", Frame & 0x1f); + + fp = fopen(filename, "wt"); //"OUT.TXT","wt"); + if (fp==NULL) { + return; + } + + for (i = 0; i < 32; i++) { + fprintf(fp,"CRC[%d]=%x\n",i,CRC[i]); + } + + + housep = HouseClass::As_Pointer (HOUSE_MULTI1); + if (housep) { + color = housep->RemapColor; + fprintf(fp,"Multi1: IsHuman:%d Color:%s\n",housep->IsHuman, + ColorNames[color]); + } + + housep = HouseClass::As_Pointer (HOUSE_MULTI2); + if (housep) { + color = housep->RemapColor; + fprintf(fp,"Multi2: IsHuman:%d Color:%s\n",housep->IsHuman, + ColorNames[color]); + } + + housep = HouseClass::As_Pointer (HOUSE_MULTI3); + if (housep) { + color = housep->RemapColor; + fprintf(fp,"Multi3: IsHuman:%d Color:%s\n",housep->IsHuman, + ColorNames[color]); + } + + housep = HouseClass::As_Pointer (HOUSE_MULTI4); + if (housep) { + color = housep->RemapColor; + fprintf(fp,"Multi4: IsHuman:%d Color:%s\n",housep->IsHuman, + ColorNames[color]); + } + + housep = HouseClass::As_Pointer (HOUSE_MULTI5); + if (housep) { + color = housep->RemapColor; + fprintf(fp,"Multi5: IsHuman:%d Color:%s\n",housep->IsHuman, + ColorNames[color]); + } + + housep = HouseClass::As_Pointer (HOUSE_MULTI6); + if (housep) { + color = housep->RemapColor; + fprintf(fp,"Multi6: IsHuman:%d Color:%s\n",housep->IsHuman, + ColorNames[color]); + } + + + //------------------------------------------------------------------------ + // Multi1 Infantry + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI1)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI1 INFANTRY -------------------\n"); + for (i = 0; i < Infantry.Count(); i++) { + infp = (InfantryClass *)Infantry.Active_Ptr(i); + if (infp->Owner()==HOUSE_MULTI1) { + Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); + fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", + infp->Coord,(int)infp->PrimaryFacing,infp->Get_Mission(), + infp->Class->Type); + } + } + Mono_Printf("Multi1 Infantry:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi2 Infantry + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI2)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI2 INFANTRY -------------------\n"); + for (i = 0; i < Infantry.Count(); i++) { + infp = (InfantryClass *)Infantry.Active_Ptr(i); + if (infp->Owner()==HOUSE_MULTI2) { + Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); + fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", + infp->Coord,(int)infp->PrimaryFacing,infp->Get_Mission(), + infp->Class->Type); + } + } + Mono_Printf("Multi2 Infantry:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi3 Infantry + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI3)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI3 INFANTRY -------------------\n"); + for (i = 0; i < Infantry.Count(); i++) { + infp = (InfantryClass *)Infantry.Active_Ptr(i); + if (infp->Owner()==HOUSE_MULTI3) { + Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); + fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", + infp->Coord,(int)infp->PrimaryFacing,infp->Get_Mission(), + infp->Class->Type); + } + } + Mono_Printf("Multi3 Infantry:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi4 Infantry + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI4)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI4 INFANTRY -------------------\n"); + for (i = 0; i < Infantry.Count(); i++) { + infp = (InfantryClass *)Infantry.Active_Ptr(i); + if (infp->Owner()==HOUSE_MULTI4) { + Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); + fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", + infp->Coord,(int)infp->PrimaryFacing,infp->Get_Mission(), + infp->Class->Type); + } + } + Mono_Printf("Multi4 Infantry:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi5 Infantry + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI5)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI5 INFANTRY -------------------\n"); + for (i = 0; i < Infantry.Count(); i++) { + infp = (InfantryClass *)Infantry.Active_Ptr(i); + if (infp->Owner()==HOUSE_MULTI5) { + Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); + fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", + infp->Coord,(int)infp->PrimaryFacing,infp->Get_Mission(), + infp->Class->Type); + } + } + Mono_Printf("Multi5 Infantry:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi6 Infantry + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI6)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI6 INFANTRY -------------------\n"); + for (i = 0; i < Infantry.Count(); i++) { + infp = (InfantryClass *)Infantry.Active_Ptr(i); + if (infp->Owner()==HOUSE_MULTI6) { + Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); + fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", + infp->Coord,(int)infp->PrimaryFacing,infp->Get_Mission(), + infp->Class->Type); + } + } + Mono_Printf("Multi6 Infantry:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi1 Units + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI1)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI1 UNITS -------------------\n"); + for (i = 0; i < Units.Count(); i++) { + unitp = (UnitClass *)Units.Active_Ptr(i); + if (unitp->Owner()==HOUSE_MULTI1) { + Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing); + fprintf(fp, + "COORD:%x Facing:%d Facing2:%d Mission:%d Type:%d\n", + unitp->Coord,(int)unitp->PrimaryFacing, + (int)unitp->SecondaryFacing,unitp->Get_Mission(), + unitp->Class->Type); + } + } + Mono_Printf("Multi1 Units:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi2 Units + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI2)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI2 UNITS -------------------\n"); + for (i = 0; i < Units.Count(); i++) { + unitp = (UnitClass *)Units.Active_Ptr(i); + if (unitp->Owner()==HOUSE_MULTI2) { + Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing); + fprintf(fp, + "COORD:%x Facing:%d Facing2:%d Mission:%d Type:%d\n", + unitp->Coord,(int)unitp->PrimaryFacing, + (int)unitp->SecondaryFacing,unitp->Get_Mission(), + unitp->Class->Type); + } + } + Mono_Printf("Multi2 Units:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi3 Units + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI3)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI3 UNITS -------------------\n"); + for (i = 0; i < Units.Count(); i++) { + unitp = (UnitClass *)Units.Active_Ptr(i); + if (unitp->Owner()==HOUSE_MULTI3) { + Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing); + fprintf(fp, + "COORD:%x Facing:%d Facing2:%d Mission:%d Type:%d\n", + unitp->Coord,(int)unitp->PrimaryFacing, + (int)unitp->SecondaryFacing,unitp->Get_Mission(), + unitp->Class->Type); + } + } + Mono_Printf("Multi3 Units:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi4 Units + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI4)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI4 UNITS -------------------\n"); + for (i = 0; i < Units.Count(); i++) { + unitp = (UnitClass *)Units.Active_Ptr(i); + if (unitp->Owner()==HOUSE_MULTI4) { + Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing); + fprintf(fp, + "COORD:%x Facing:%d Facing2:%d Mission:%d Type:%d\n", + unitp->Coord,(int)unitp->PrimaryFacing, + (int)unitp->SecondaryFacing,unitp->Get_Mission(), + unitp->Class->Type); + } + } + Mono_Printf("Multi4 Units:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi5 Units + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI5)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI5 UNITS -------------------\n"); + for (i = 0; i < Units.Count(); i++) { + unitp = (UnitClass *)Units.Active_Ptr(i); + if (unitp->Owner()==HOUSE_MULTI5) { + Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing); + fprintf(fp, + "COORD:%x Facing:%d Facing2:%d Mission:%d Type:%d\n", + unitp->Coord,(int)unitp->PrimaryFacing, + (int)unitp->SecondaryFacing,unitp->Get_Mission(), + unitp->Class->Type); + } + } + Mono_Printf("Multi5 Units:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi6 Units + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI6)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI6 UNITS -------------------\n"); + for (i = 0; i < Units.Count(); i++) { + unitp = (UnitClass *)Units.Active_Ptr(i); + if (unitp->Owner()==HOUSE_MULTI6) { + Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing); + fprintf(fp, + "COORD:%x Facing:%d Facing2:%d Mission:%d Type:%d\n", + unitp->Coord,(int)unitp->PrimaryFacing, + (int)unitp->SecondaryFacing,unitp->Get_Mission(), + unitp->Class->Type); + } + } + Mono_Printf("Multi6 Units:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi1 Buildings + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI1)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI1 BUILDINGS -------------------\n"); + for (i = 0; i < Buildings.Count(); i++) { + bldgp = (BuildingClass *)Buildings.Active_Ptr(i); + if (bldgp->Owner()==HOUSE_MULTI1) { + Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); + fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", + bldgp->Coord,(int)bldgp->PrimaryFacing,bldgp->Get_Mission(), + bldgp->Class->Type); + } + } + Mono_Printf("Multi1 Buildings:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi2 Buildings + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI2)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI2 BUILDINGS -------------------\n"); + for (i = 0; i < Buildings.Count(); i++) { + bldgp = (BuildingClass *)Buildings.Active_Ptr(i); + if (bldgp->Owner()==HOUSE_MULTI2) { + Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); + fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", + bldgp->Coord,(int)bldgp->PrimaryFacing,bldgp->Get_Mission(), + bldgp->Class->Type); + } + } + Mono_Printf("Multi2 Buildings:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi3 Buildings + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI3)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI3 BUILDINGS -------------------\n"); + for (i = 0; i < Buildings.Count(); i++) { + bldgp = (BuildingClass *)Buildings.Active_Ptr(i); + if (bldgp->Owner()==HOUSE_MULTI3) { + Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); + fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", + bldgp->Coord,(int)bldgp->PrimaryFacing,bldgp->Get_Mission(), + bldgp->Class->Type); + } + } + Mono_Printf("Multi3 Buildings:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi4 Buildings + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI4)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI4 BUILDINGS -------------------\n"); + for (i = 0; i < Buildings.Count(); i++) { + bldgp = (BuildingClass *)Buildings.Active_Ptr(i); + if (bldgp->Owner()==HOUSE_MULTI4) { + Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); + fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", + bldgp->Coord,(int)bldgp->PrimaryFacing,bldgp->Get_Mission(), + bldgp->Class->Type); + } + } + Mono_Printf("Multi4 Buildings:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi5 Buildings + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI5)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI5 BUILDINGS -------------------\n"); + for (i = 0; i < Buildings.Count(); i++) { + bldgp = (BuildingClass *)Buildings.Active_Ptr(i); + if (bldgp->Owner()==HOUSE_MULTI5) { + Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); + fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", + bldgp->Coord,(int)bldgp->PrimaryFacing,bldgp->Get_Mission(), + bldgp->Class->Type); + } + } + Mono_Printf("Multi5 Buildings:%d\n",GameCRC); + } + + //------------------------------------------------------------------------ + // Multi6 Buildings + //------------------------------------------------------------------------ + if (HouseClass::As_Pointer(HOUSE_MULTI6)) { + GameCRC = 0; + fprintf(fp,"-------------------- MULTI6 BUILDINGS -------------------\n"); + for (i = 0; i < Buildings.Count(); i++) { + bldgp = (BuildingClass *)Buildings.Active_Ptr(i); + if (bldgp->Owner()==HOUSE_MULTI6) { + Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); + fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", + bldgp->Coord,(int)bldgp->PrimaryFacing,bldgp->Get_Mission(), + bldgp->Class->Type); + } + } + Mono_Printf("Multi6 Buildings:%d\n",GameCRC); + } +#if (0) + //------------------------------------------------------------------------ + // Map Layers + //------------------------------------------------------------------------ + GameCRC = 0; + for (i = 0; i < LAYER_COUNT; i++) { + fprintf(fp,">>>> MAP LAYER %d <<<<\n",i); + for (j = 0; j < Map.Layer[i].Count(); j++) { + objp = Map.Layer[i][j]; + Add_CRC (&GameCRC, (int)objp->Coord + (int)objp->What_Am_I()); + fprintf(fp,"Object %d: %x ",j,objp->Coord); + + if (objp->What_Am_I() == RTTI_AIRCRAFT) + fprintf(fp,"Aircraft (Type:%d) ", + (AircraftType)(*((AircraftClass *)objp))); + else if (objp->What_Am_I() == RTTI_ANIM) + fprintf(fp,"Anim (Type:%d) ", + (AnimType)(*((AnimClass *)objp))); + else if (objp->What_Am_I() == RTTI_BUILDING) + fprintf(fp,"Building (Type:%d) ", + (StructType)(*((BuildingClass *)objp))); + else if (objp->What_Am_I() == RTTI_BULLET) + fprintf(fp,"Bullet (Type:%d) ", + (BulletType)(*((BulletClass *)objp))); + else if (objp->What_Am_I() == RTTI_INFANTRY) + fprintf(fp,"Infantry (Type:%d) ", + (InfantryType)(*((InfantryClass *)objp))); + else if (objp->What_Am_I() == RTTI_OVERLAY) + fprintf(fp,"Overlay (Type:%d) ", + (OverlayType)(*((OverlayClass *)objp))); + else if (objp->What_Am_I() == RTTI_SMUDGE) + fprintf(fp,"Smudge (Type:%d) ", + (SmudgeType)(*((SmudgeClass *)objp))); + else if (objp->What_Am_I() == RTTI_TEMPLATE) + fprintf(fp,"Template (Type:%d) ", + (TemplateType)(*((TemplateClass *)objp))); + else if (objp->What_Am_I() == RTTI_TERRAIN) + fprintf(fp,"Terrain (Type:%d) ", + (TerrainType)(*((TerrainClass *)objp))); + else if (objp->What_Am_I() == RTTI_UNIT) + fprintf(fp,"Unit (Type:%d) ", + (UnitType)(*((UnitClass *)objp))); + + house = objp->Owner(); + if (house!=HOUSE_NONE) { + housep = HouseClass::As_Pointer (house); + fprintf(fp,"Owner: %s\n",housep->Class->IniName); + } + else { + fprintf(fp,"Owner: NONE\n"); + } + } + } + Mono_Printf("Map Layers:%d\n",GameCRC); + + //------------------------------------------------------------------------ + // Logic Layers + //------------------------------------------------------------------------ + GameCRC = 0; + fprintf(fp,">>>> LOGIC LAYER <<<<\n"); + for (i = 0; i < Logic.Count(); i++) { + objp = Logic[i]; + Add_CRC (&GameCRC, (int)objp->Coord + (int)objp->What_Am_I()); + fprintf(fp,"Object %d: %x ",i,objp->Coord); + + if (objp->What_Am_I() == RTTI_AIRCRAFT) + fprintf(fp,"Aircraft (Type:%d) ", + (AircraftType)(*((AircraftClass *)objp))); + else if (objp->What_Am_I() == RTTI_ANIM) + fprintf(fp,"Anim (Type:%d) ", + (AnimType)(*((AnimClass *)objp))); + else if (objp->What_Am_I() == RTTI_BUILDING) + fprintf(fp,"Building (Type:%d) ", + (StructType)(*((BuildingClass *)objp))); + else if (objp->What_Am_I() == RTTI_BULLET) + fprintf(fp,"Bullet (Type:%d) ", + (BulletType)(*((BulletClass *)objp))); + else if (objp->What_Am_I() == RTTI_INFANTRY) + fprintf(fp,"Infantry (Type:%d) ", + (InfantryType)(*((InfantryClass *)objp))); + else if (objp->What_Am_I() == RTTI_OVERLAY) + fprintf(fp,"Overlay (Type:%d) ", + (OverlayType)(*((OverlayClass *)objp))); + else if (objp->What_Am_I() == RTTI_SMUDGE) + fprintf(fp,"Smudge (Type:%d) ", + (SmudgeType)(*((SmudgeClass *)objp))); + else if (objp->What_Am_I() == RTTI_TEMPLATE) + fprintf(fp,"Template (Type:%d) ", + (TemplateType)(*((TemplateClass *)objp))); + else if (objp->What_Am_I() == RTTI_TERRAIN) + fprintf(fp,"Terrain (Type:%d) ", + (TerrainType)(*((TerrainClass *)objp))); + else if (objp->What_Am_I() == RTTI_UNIT) + fprintf(fp,"Unit (Type:%d) ", + (UnitType)(*((UnitClass *)objp))); + + house = objp->Owner(); + if (house!=HOUSE_NONE) { + housep = HouseClass::As_Pointer (house); + fprintf(fp,"Owner: %s\n",housep->Class->IniName); + } + else { + fprintf(fp,"Owner: NONE\n"); + } + } + Mono_Printf("Logic:%d\n",GameCRC); +#endif + + //------------------------------------------------------------------------ + // Random # generator, frame # + //------------------------------------------------------------------------ + rnd = rand(); + + Mono_Printf("Random Number:%d\n",rnd); + fprintf(fp,"\nRandom Number:%d\n",rnd); + + Mono_Printf("My Frame:%d\n",Frame); + fprintf(fp,"My Frame:%d\n",Frame); +#if (0) + fprintf(fp,"-------------- Offending event: ----------------\n"); + fprintf(fp,"Type: %d\n",ev->Type); + fprintf(fp,"Frame: %d\n",ev->Frame); + fprintf(fp,"MPlayerID: %04x\n",ev->MPlayerID); + if (ev->Type == EventClass::FRAMEINFO) { + fprintf(fp,"CRC: %x\n",ev->Data.FrameInfo.CRC); + fprintf(fp,"CommandCount: %x\n",ev->Data.FrameInfo.CommandCount); + fprintf(fp,"Delay: %x\n",ev->Data.FrameInfo.Delay); + } +#endif + fclose(fp); + +} /* end of Print_CRCs */ + + +/*************************************************************************** + * Init_Queue_Mono -- inits mono display * + * * + * This routine steals control of the mono screen away from the rest of * + * the engine, by setting the global IsMono; if IsMono is set, the other * + * routines in this module turn off the Mono display when they're done * + * with it, so the rest of the engine won't over-write what we're writing. * + * * + * INPUT: * + * net ptr to connection manager * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static void Init_Queue_Mono(ConnManClass *net) +{ +#if(SHOW_MONO) + //------------------------------------------------------------------------ + // Set 'IsMono' so we can steal the mono screen from the engine + //------------------------------------------------------------------------ + if (Frame==0 && MonoClass::Is_Enabled()) { + IsMono = true; + } + + //------------------------------------------------------------------------ + // Enable mono output for our stuff; we must Disable it before we return + // control to the engine. + //------------------------------------------------------------------------ + if (IsMono) + MonoClass::Enable(); + + if (net->Num_Connections() > 0) { + //..................................................................... + // Network mono debugging screen + //..................................................................... + if (NetMonoMode==0) { + if (Frame==0 || NewMonoMode) { + net->Configure_Debug (0, sizeof (CommHeaderType), + sizeof(EventClass::EventType), EventClass::EventNames, 0); + net->Mono_Debug_Print (0,1); + NewMonoMode = 0; + } + else { + net->Mono_Debug_Print (0,0); + } + } + //..................................................................... + // Flow control debugging output + //..................................................................... + else { + if (NewMonoMode) { + Mono_Clear_Screen(); + Mono_Printf(" Queue AI:\n"); // flowcount[0] + Mono_Printf(" Build Packet Loop:\n"); // flowcount[1] + Mono_Printf(" Frame Sync:\n"); // flowcount[2] + Mono_Printf(" Frame Sync Resend:\n"); // flowcount[3] + Mono_Printf(" Frame Sync Timeout:\n"); // flowcount[4] + Mono_Printf(" Frame Sync New Message:\n"); // flowcount[5] + Mono_Printf(" DoList Execution:\n"); // flowcount[6] + Mono_Printf(" DoList Cleaning:\n"); // flowcount[7] + Mono_Printf("\n"); + Mono_Printf(" Frame:\n"); + Mono_Printf(" MPlayerMaxAhead:\n"); + Mono_Printf(" their_recv:\n"); + Mono_Printf(" their_sent:\n"); + Mono_Printf(" my_sent:\n"); + Mono_Printf(" DesiredFrameRate:\n"); + NewMonoMode = 0; + } + } + } +#else + net = net; +#endif +} // end of Init_Queue_Mono + + +/*************************************************************************** + * Update_Queue_Mono -- updates mono display * + * * + * INPUT: * + * net ptr to connection manager * + * flow_index index # for flow-count updates * + * -1: display * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static void Update_Queue_Mono(ConnManClass *net, int flow_index) +{ +#if(SHOW_MONO) + static int flowcount[20] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + + //------------------------------------------------------------------------ + // If 'NetMonoMode' is 1, display flowcount info + //------------------------------------------------------------------------ + if (NetMonoMode==1) { + if (flow_index >= 0 && flow_index < 20) { + Mono_Set_Cursor(35,flow_index); + flowcount[flow_index]++; + Mono_Printf("%d",flowcount[flow_index]); + } + } + //------------------------------------------------------------------------ + // Otherwise, display the connection debug screen + //------------------------------------------------------------------------ + else { + net->Mono_Debug_Print (0,0); + } + +#else + flow_index = flow_index; + net = net; +#endif + +} // end of Update_Queue_Mono + + +/*************************************************************************** + * Print_Framesync_Values -- displays frame-sync variables * + * * + * INPUT: * + * curframe current game Frame # * + * max_ahead max-ahead value * + * num_connections # connections * + * their_recv # commands I've received from my connections * + * their_sent # commands each connection claims to have sent * + * my_sent # commands I've sent * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/21/1995 BRR : Created. * + *=========================================================================*/ +static void Print_Framesync_Values(long curframe, unsigned long max_ahead, + int num_connections, unsigned short *their_recv, + unsigned short *their_sent, unsigned short my_sent) +{ +#if(SHOW_MONO) + int i; + + if (NetMonoMode==1) { + Mono_Set_Cursor(35,9); + Mono_Printf("%d",curframe); + + Mono_Set_Cursor(35,10); + Mono_Printf("%d",max_ahead); + + for (i = 0; i < num_connections; i++) { + Mono_Set_Cursor(35 + i*5,11); + Mono_Printf("%4d",(int)their_recv[i]); + } + + for (i = 0; i < num_connections; i++) { + Mono_Set_Cursor(35 + i*5,12); + Mono_Printf("%4d",(int)their_sent[i]); + } + + Mono_Set_Cursor(35,13); + Mono_Printf("%4d",(int)my_sent); + + Mono_Set_Cursor(35,14); + Mono_Printf("%2d",(int)DesiredFrameRate); + } +#else + curframe = curframe; + max_ahead = max_ahead; + num_connections = num_connections; + their_recv = their_recv; + their_sent = their_sent; + my_sent = my_sent; +#endif +} // end of Print_Framesync_Values + + +/*************************************************************************** + * Dump_Packet_Too_Late_Stuff -- Dumps a debug file to disk * + * * + * INPUT: * + * event ptr to event to print * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/28/1996 BRR : Created. * + *=========================================================================*/ +void Dump_Packet_Too_Late_Stuff(EventClass *event) +{ + FILE *fp; + int i; + + fp = fopen("toolate.txt", "wt"); + if (!fp) { + return; + } + fprintf(fp,"--------- Event data: -------------------\n"); + fprintf(fp,"Type: %s\n",EventClass::EventNames[event->Type]); + fprintf(fp,"Frame: %d\n",event->Frame); + fprintf(fp,"ID: %d\n",event->ID); + fprintf(fp,"MPlayerID: %04x\n",event->MPlayerID); + + for (i = 0; i < MPlayerCount; i++) { + if (event->MPlayerID == MPlayerID[i]) { + fprintf(fp,"Player's Name: %s",MPlayerNames[i]); + } + } + + fprintf(fp,"----------- My data: ------------------\n"); + fprintf(fp,"Frame:%d\n",Frame); + fprintf(fp,"MaxAhead:%d\n",MPlayerMaxAhead); + + fclose(fp); +} + +#endif //DEMO + +/*************************** end of queue.cpp ******************************/ diff --git a/QUEUE.H b/QUEUE.H new file mode 100644 index 0000000..7d6f615 --- /dev/null +++ b/QUEUE.H @@ -0,0 +1,275 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\queue.h_v 2.16 16 Oct 1995 16:45:50 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : QUEUE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/08/94 * + * * + * Last Update : December 9, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * QueueClass::Add -- Add object to queue. * + * QueueClass::First -- Fetches reference to first object in list. * + * QueueClass::Init -- Initializes queue to empty state. * + * QueueClass::Next -- Throws out the head of the line. * + * QueueClass::operator[] -- Fetches reference to sub object in queue. * + * QueueClass::QueueClass -- Default constructor for QueueClass objects. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef QUEUE_H +#define QUEUE_H + +#include "mission.h" +#include "target.h" +#include "defines.h" + +#pragma warn -inl + +/* +** This class implements a classic FIFO queue (also known as - standing in line). Objects +** are added to the end (tail) of the line. Objects are removed from the start (first) of +** the line. A keyboard buffer is a good example of a common computer use of a queue. There +** is no provision for "taking cuts" or leaving the line once an object has been added. +** +** The implementation uses a circular list of objects. This allows adding and deleting of +** elements without any maintenance moves of remaining objects that would otherwise be +** necessary in a simple array storage method. A side effect of this means that accessing the +** internal array directly is not allowed. Supporting functions are provided for this purpose. +** +** WARNING WARNING WARNING WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +** The size parameter MUST be an exact power of two (2, 4, 8, 16, etc.) otherwise the internal +** indexing algorithm will fail. +*/ +template +class QueueClass +{ + public: + /* + ** This is the count of the number of objects in the queue. If this count is zero, + ** then the operator[], First(), and Next() functions are undefined. Check this + ** value BEFORE calling these functions. + */ + const int Count; + + //-------------- Functions -------------------- + QueueClass(void); // Default constructor. + + /* + ** The bracket subscript operator functions similarly to the way a normal subscript + ** operator works except that entry [0] matches the first-in-line and entry + ** [Count-1] matches the last-in-line. This is ensured regardless of the actual position + ** of the object in the circular internal list. + */ + T & operator[](int); + + /* + ** This function will return a reference to the "head of the line" object. + */ + T & First(void); + + /* + ** This function clears the list of objects. + */ + void Init(void); + + /* + ** This function discards the head-of-the-line object and advances all the remaining + ** objects up by one. Mnemonic: Imagine a broadway audition and the director yells + ** "NEXT!" + */ + int Next(void); + + /* + ** This will add an object to the tail of the line. If there is no more room to add + ** the object, then false will be returned. + */ + int Add(T const &); + + private: + int Head; // Index of element in list the longest. + int Tail; // Index where next new addition will go. + + T Array[size]; // Raw array of objects. +}; + + +/*********************************************************************************************** + * QueueClass::QueueClass -- Default constructor for QueueClass objects. * + * * + * This default constructor for QueueClass objects initializes the queue to an empty * + * state. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/09/1994 JLB : Created. * + *=============================================================================================*/ +template +inline QueueClass::QueueClass(void) : Count(0) +{ + Init(); +} + + +/*********************************************************************************************** + * QueueClass::Init -- Initializes queue to empty state. * + * * + * This function resets the queue to an empty state. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/09/1994 JLB : Created. * + *=============================================================================================*/ +template +inline void QueueClass::Init(void) +{ + ((int &)Count) = 0; + Head = 0; + Tail = 0; +} + + +/*********************************************************************************************** + * QueueClass::Add -- Add object to queue. * + * * + * This function is used to add an object to the tail of the line. If the queue cannot * + * accept any more entries, then the object won't be added and false will be returned. * + * * + * INPUT: object -- The object that is to be added to the queue. * + * * + * OUTPUT: bool; Was the object added successfully? * + * * + * WARNINGS: If the queue is full, then the object won't be added. Be sure to check for this.* + * * + * HISTORY: * + * 12/09/1994 JLB : Created. * + *=============================================================================================*/ +template +inline int QueueClass::Add(T const &q) +{ + if (Count < size) { + Array[Tail] = q; + Tail = (Tail + 1) & (size-1); + ((int &)Count) = Count + 1; + return(true); + } + Mono_Printf( "Queue Add failed Count %d size %d tail %d head %d \n", + Count, size, Tail, Head ); + return (false); +} + + +/*********************************************************************************************** + * QueueClass::Next -- Throws out the head of the line. * + * * + * This routine is used to discard the object at the head of the line. All remaining * + * objects "move up" one. No actual movement occurs, merely the index is adjusted, but * + * the affect is that the next oldest object in the queue will now be returned with the * + * next call to the First() function. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the number of object remaining in the queue. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/09/1994 JLB : Created. * + *=============================================================================================*/ +template +inline int QueueClass::Next(void) +{ + if (Count) { + Head = (Head + 1) & (size-1); + ((int &)Count) = Count - 1; + } + return (Count); +} + + +/*********************************************************************************************** + * QueueClass::operator[] -- Fetches reference to sub object in queue. * + * * + * Use this routine to examine individual objects within the queue. The oldest object in * + * the queue is referenced by an index value of zero. The newest object in the queue is * + * referenced by a value of Count-1. If there are no objects in the queue, then this * + * operator is undefined. Although this operator allows examination of the queue, there is * + * no corresponding ability to insert or delete objects from the middle of the queue. * + * * + * INPUT: index -- The index into the queue of objects. Valid values range from zero to * + * Count-1. All other values return an undefined reference! * + * * + * OUTPUT: Returns with a reference to the object indicated by the index. * + * * + * WARNINGS: Check to make sure that Count is not zero before using this operator. Failure * + * to do so will return a reference to an undefined object. * + * * + * HISTORY: * + * 12/09/1994 JLB : Created. * + *=============================================================================================*/ +template +inline T & QueueClass::operator[](int index) +{ + return Array[(Head + index) & (size-1)]; +} + + +/*********************************************************************************************** + * QueueClass::First -- Fetches reference to first object in list. * + * * + * This routine is used to fetch the first object in the list (head of the line). This * + * object is the oldest in the list. Typical use of this function is to get and process * + * the first object so that it may be discarded with the Next() function in order to bring * + * subsequent objects to the first position. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a reference to the oldest object in the queue. * + * * + * WARNINGS: If there are no objects in the queue, then this function returns an undefined * + * reference. Be sure to check Count against zero before calling this function. * + * * + * HISTORY: * + * 12/09/1994 JLB : Created. * + *=============================================================================================*/ +template +inline T & QueueClass::First(void) +{ + return Array[Head]; +} + +#endif diff --git a/RADAR.CPP b/RADAR.CPP new file mode 100644 index 0000000..0e20ac7 --- /dev/null +++ b/RADAR.CPP @@ -0,0 +1,1960 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\radar.cpv 2.17 16 Oct 1995 16:49:28 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : RADAR.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/15/94 * + * * + * Last Update : November 17, 1995 [PWG] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Get_Multi_Color -- Get the multi color offset number * + * RadarClass::AI -- Processes radar input (non-tactical). * + * RadarClass::Cell_On_Radar -- Determines if a cell is currently visible on radar. * + * RadarClass::Click_Cell_Calc -- Determines what cell the pixel coordinate is over. * + * RadarClass::Click_In_Radar -- Check to see if a click is in radar map * + * RadarClass::Click_In_Radar -- Converts a radar click into cell X and Y coordinate. * + * RadarClass::Draw_It -- Displays the radar map of the terrain. * + * RadarClass::Draw_Names -- draws players' names on the radar map * + * RadarClass::Init_Clear -- Sets the radar map to a known state * + * RadarClass::Map_Cell -- Updates radar map when a cell becomes mapped. * + * RadarClass::One_Time -- Handles one time processing for the radar map. * + * RadarClass::Player_Names -- toggles the Player-Names mode of the radar map * + * RadarClass::Plot_Radar_Pixel -- Updates the radar map with a terrain pixel. * + * RadarClass::RadarClass -- Default constructor for RadarClass object. * + * RadarClass::Radar_Activate -- Controls radar activation. * + * RadarClass::Radar_Anim -- Renders current frame of radar animation * + * RadarClass::Radar_Cursor -- Adjust the position of the radar map cursor. * + * RadarClass::Radar_Pixel -- Mark a cell to be rerendered on the radar map. * + * RadarClass::Radar_Position -- Returns with the current position of the radar map. * + * RadarClass::Refresh_Cells -- Intercepts refresh request and updates radar if needed * + * RadarClass::Render_Infantry -- Displays objects on the radar map. * + * RadarClass::Render_Overlay -- Renders an icon for given overlay * + * RadarClass::Render_Terrain -- Render the terrain over the given cell * + * RadarClass::Set_Map_Dimensions -- Sets the tactical map dimensions. * + * RadarClass::Set_Radar_Position -- Sets the radar map coordinates. * + * RadarClass::Set_Radar_Position -- Sets the radar position to center around specified cell.* + * RadarClass::Set_Tactical_Position -- Called when setting the tactical display position. * + * RadarClass::Set_Tactical_Position -- Called when setting the tactical display position. * + * RadarClass::TacticalClass::Action -- I/O function for the radar map. * + * RadarClass::Zoom_Mode(void) -- Handles toggling zoom on the map * + * RadarClass::Set_Tactical_Position -- Sets the map's tactical position and adjusts radar to* + * RadarClass::Coord_To_Radar_Pixel -- Converts a coordinate to a radar pixel position * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include + +//void const * RadarClass::CoverShape; +RadarClass::TacticalClass RadarClass::RadarButton; + +void const * RadarClass::RadarAnim = NULL; + +static bool FullRedraw = false; + +#define _MAX_NAME 13 + +static GraphicBufferClass _IconStage(3,3); +static GraphicBufferClass _TileStage(24,24); + +/*********************************************************************************************** + * RadarClass::RadarClass -- Default constructor for RadarClass object. * + * * + * This default constructor merely sets the radar specific values to default settings. The * + * radar must be deliberately activated in order for it to be displayed. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/16/1994 JLB : Created. * + *=============================================================================================*/ +RadarClass::RadarClass(void) +{ + IsZoomed = true; + IsRadarActive = false; + IsToRedraw = false; + RadarCursorRedraw = false; + PixelPtr = 0; + SpecialRadarFrame = 0; + IsPlayerNames = false; +} + + +/*********************************************************************************************** + * RadarClass::One_Time -- Handles one time processing for the radar map. * + * * + * This routine handles any one time processing required in order for the radar map to * + * function. This actually only requires an allocation of the radar staging buffer. This * + * buffer is needed for those cases where the radar area of the page is being destroyed * + * and it needs to be destroyed. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Be sure to call this routine only ONCE. * + * * + * HISTORY: * + * 12/22/1994 JLB : Created. * + *=============================================================================================*/ +void RadarClass::One_Time(void) +{ + int factor = Get_Resolution_Factor(); + RadWidth = 80 << factor; + RadHeight = 70 << factor; + RadX = SeenBuff.Get_Width() - RadWidth; + RadY = Map.Get_Tab_Height() - (1 << factor); + RadPWidth = 64 << factor; + RadPHeight = 64 << factor; + if ( factor ) { + RadOffX = 16; + RadOffY = 7; + RadIWidth = 128; + RadIHeight = 128; + } else { + RadOffX = 4 << factor; + RadOffY = 1 << factor; + RadIWidth = 72 << factor; + RadIHeight = 69 << factor; + } + + DisplayClass::One_Time(); + RadarButton.X = RadX+RadOffX; + RadarButton.Y = RadY+RadOffY; + RadarButton.Width = RadIWidth; + RadarButton.Height = RadIHeight; +} + + +/*********************************************************************************************** + * RadarClass::Init_Clear -- Sets the radar map to a known state. * + * * + * This routine is used to initialize the radar map at the start of the scenario. It * + * sets the radar map position and starts it in the disabled state. * + * * + * INPUT: theater -- The theater that the scenario is starting (unused by this routine). * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/22/1994 JLB : Created. * + *=============================================================================================*/ +void RadarClass::Init_Clear(void) +{ + DisplayClass::Init_Clear(); + IsRadarActive = false; + IsToRedraw = true; + RadarCursorRedraw = true; + IsRadarActivating = false; + IsRadarDeactivating = false; + DoesRadarExist = false; + PixelPtr = 0; + IsPlayerNames = false; + + /* + ** If we have a valid map lets make sure that we set it correctly + */ + if (MapCellWidth || MapCellHeight) { + IsZoomed = false; + Zoom_Mode(Coord_Cell(Map.TacticalCoord)); + } +} + + +/*********************************************************************************************** + * RadarClass::Radar_Activate -- Controls radar activation. * + * * + * Use this routine to turn the radar map on or off. * + * * + * INPUT: control -- What to do with the radar map: * + * 0 = Turn radar off. * + * 1 = Turn radar on. * + * 2 = Remove Radar Gadgets * + * 3 = Add Radar Gadgets * + * 4 = Remove radar. * + * -1= Toggle radar on or off. * + * * + * OUTPUT: bool; Was the radar map already on? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/11/1994 JLB : Created. * + *=============================================================================================*/ +bool RadarClass::Radar_Activate(int control) +{ + bool old = IsRadarActive; + + switch (control) { + + /* + ** Toggle the state of the radar map on or off. + */ + case -1: + { + int temp = (IsRadarActive == false); + if (temp) { + Radar_Activate(1); + } else { + Radar_Activate(0); + } + } + break; + + /* + ** Turn the radar map off properly. + */ + case 0: + if (Map.IsSidebarActive) { + if (IsRadarActive && !IsRadarDeactivating) { + Sound_Effect(VOC_RADAR_OFF); + IsRadarDeactivating = true; + IsRadarActive = false; + if (IsRadarActivating == true) { + IsRadarActivating = false; + } else { + RadarAnimFrame = RADAR_ACTIVATED_FRAME; + } + } + } else { + Radar_Activate(2); + } + return(old); + + case 1: + if (Map.IsSidebarActive) { + if (!IsRadarActivating && !IsRadarActive) { + Sound_Effect(VOC_RADAR_ON); + IsRadarActivating = true; + if (IsRadarDeactivating == true) { + IsRadarDeactivating = false; + } else { + if (DoesRadarExist) { + RadarAnimFrame = MAX_RADAR_FRAMES; + } else { + RadarAnimFrame = 0; + } + } + } + } else { + Radar_Activate(3); + } + return(old); + + case 2: + if (GameToPlay==GAME_NORMAL) { + Map.Zoom.Disable(); + } + IsRadarActive = false; + IsRadarActivating = false; + IsRadarDeactivating = false; + break; + + case 3: + if (GameToPlay==GAME_NORMAL) { + Map.Zoom.Enable(); + } + IsRadarActive = true; + IsRadarActivating = false; + IsRadarDeactivating = false; + break; + + case 4: + IsRadarActive = false; + IsRadarActivating = false; + IsRadarDeactivating = false; + DoesRadarExist = false; + Flag_To_Redraw(false); + IsToRedraw = true; + break; + } + + if (IsRadarActive != old) { + IsToRedraw = true; + Flag_To_Redraw(false); + } + FullRedraw = IsRadarActive; + return(old); +} + + +/*********************************************************************************************** + * RadarClass::Draw_It -- Displays the radar map of the terrain. * + * * + * This is used to display the radar map that appears in the lower * + * right corner. The main changes to this map are the vehicles and * + * structure pixels. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/24/1991 JLB : Created. * + * 05/08/1994 JLB : Converted to member function. * + *=============================================================================================*/ +void RadarClass::Draw_It(bool forced) +{ + DisplayClass::Draw_It(forced); +// if (!In_Debugger) while (!HidPage.Lock()) {} + + /* + ** Don't perform any rendering if none is requested. + */ + if (!forced && !IsToRedraw && !FullRedraw) return; + + static HousesType _house = HOUSE_NONE; + if (PlayerPtr->ActLike != _house) { + char name[_MAX_NAME + _MAX_EXT]; + + if (Special.IsJurassic && AreThingiesEnabled) { + strcpy(name, "RADAR.JP"); + } else { + _makepath(name, NULL, NULL, "RADAR", HouseTypeClass::As_Reference(PlayerPtr->ActLike).Suffix); + } + RadarAnim = Hires_Retrieve(name); + _house = PlayerPtr->ActLike; + } + + /* + ** If in player name mode, just draw player names + */ + if (IsPlayerNames) { + Draw_Names(); + IsToRedraw = false; + return; + } + + if (IsRadarActivating || IsRadarDeactivating) { + Radar_Anim(); + IsToRedraw = false; + return; + } + + if (Map.IsSidebarActive) { + if (IsRadarActive) { + + //HidPage.Lock(); +// ST 8/13/96 2:24PM +//forced = true; + /* + ** If only a few of the radar pixels need to be redrawn, then find and redraw + ** only these. + */ + if (!forced && IsToRedraw && !FullRedraw) { + IsToRedraw = false; + + if (PixelPtr) { + + /* + ** Render all pixels in the "to redraw" stack. + */ + for (int index = 0; index < PixelPtr; index++) { + CELL cell = PixelStack[index]; + if (Cell_On_Radar(cell)) { + (*this)[cell].IsPlot = false; + Plot_Radar_Pixel(cell); + RadarCursorRedraw |= (*this)[cell].IsRadarCursor; + } + } + + + /* + ** Refill the stack if there is pending pixels yet to be plotted. + ** This should only process in sections for speed reasons + */ + + if (PixelPtr == PIXELSTACK) { + PixelPtr = 0; + + for (int y = 0; y < MapCellHeight; y++) { + for (int x = 0; x < MapCellWidth; x++) { + CELL cell = XY_Cell(MapCellX + x, MapCellY + y); + if (Cell_On_Radar(cell)) { + + if ((*this)[cell].IsPlot) { + PixelStack[PixelPtr++] = cell; + IsToRedraw = true; + if (PixelPtr == PIXELSTACK) break; + } + } + } + if (PixelPtr == PIXELSTACK) break; + } + } else { + PixelPtr = 0; + } + } + + Radar_Cursor(RadarCursorRedraw); + + } else { + GraphicViewPortClass *oldpage = Set_Logic_Page(HidPage); +// if (LogicPage->Lock()) { + CC_Draw_Shape(RadarAnim, RADAR_ACTIVATED_FRAME, RadX, RadY+1, WINDOW_MAIN, SHAPE_NORMAL); + if (BaseX || BaseY) { + LogicPage->Fill_Rect( RadX + RadOffX, + RadY + RadOffY, + RadX + RadOffX + RadIWidth - 1, + RadY + RadOffY + RadIHeight - 1, + DKGREY); + } else { + LogicPage->Fill_Rect( RadX + RadOffX, + RadY + RadOffY, + RadX + RadOffX + RadIWidth - 1, + RadY + RadOffY + RadIHeight - 1, + BLACK); + } + + /* + ** Draw the entire radar map. + */ + for (int index = 0; index < MAP_CELL_TOTAL; index++) { + Plot_Radar_Pixel(index); + } + Radar_Cursor(true); + FullRedraw = false; + IsToRedraw = false; + LogicPage->Unlock(); + if (oldpage == &SeenBuff) { + Hide_Mouse(); + LogicPage->Blit(SeenBuff, RadX, RadY, RadX, RadY, RadWidth, RadHeight); + Show_Mouse(); + } + +// Set_Logic_Page(oldpage); + +// } + + } + + } else { + + /* + ** If the radar is not active, then only draw the cover plate if forced to do so. + */ +// if (forced) { + int val = (DoesRadarExist) ? MAX_RADAR_FRAMES : 0; + CC_Draw_Shape(RadarAnim, val, RadX, RadY + 1, WINDOW_MAIN, SHAPE_NORMAL); + FullRedraw = false; + IsToRedraw = false; +// } + } + + //HidPage.Unlock(); +// Map.Activator.Draw_Me(true); + } +} + +/*************************************************************************** + * RadarClass::Render_Terrain -- Render the terrain over the given cell * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/12/1995 PWG : Created. * + *=========================================================================*/ +void RadarClass::Render_Terrain(CELL cell, int x, int y, int size) +{ + TerrainClass *list[4]; + int listidx = 0; + int lp,lp2; + + + ObjectClass *obj = Map[cell].Cell_Occupier(); + + /* + ** If the cell is occupied by a terrain type, add it to the sortable + ** list. + */ + if (obj && obj->What_Am_I() == RTTI_TERRAIN) + list[listidx++] = (TerrainClass *)obj; + + /* + ** Now loop through all the occupiers and add them to the list if they + ** are terrain type. + */ + for (lp = 0; lp < 3; lp ++) { + obj = Map[cell].Overlapper[lp]; + if (obj && obj->What_Am_I() == RTTI_TERRAIN) + list[listidx++] = (TerrainClass *)obj; + } + + /* + ** If there are no entrys in our list then just get out. + */ + if (!listidx) return; + + /* + ** If there is terrain in this cell then draw a dark pixel to + ** represent it. + */ + if (size == 1) { + LogicPage->Put_Pixel(x, y, 60); + return; + } + + /* + ** Sort the list by its sort Y value so that we can render in the proper + ** order. + */ + for (lp = 0; lp < listidx - 1; lp ++) { + for (lp2 = lp + 1; lp2 < listidx; lp2++) { + if (list[lp]->Sort_Y() > list[lp2]->Sort_Y()) { + TerrainClass *terrain = list[lp]; + list[lp] = list[lp2]; + list[lp2] = terrain; + } + } + } + + /* + ** loop through the list and take care of rendering the correct icon. + */ + for (lp = 0; lp < listidx; lp ++) { + unsigned char *icon = list[lp]->Radar_Icon(cell); + if (!icon) continue; + + Buffer_To_Page(0, 0, 3, 3, icon, _IconStage); + _IconStage.Scale(*LogicPage, 0, 0, x, y, 3, 3, ZoomFactor, ZoomFactor, TRUE, (char *)&FadingBrighten[0]); + } +} + + +/*********************************************************************************************** + * RadarClass::Render_Infantry -- Displays objects on the radar map. * + * * + * This routine will display an object imagery at the location specified according to the * + * condition of the specified cell. * + * * + * INPUT: cell -- The cell to use as reference when drawing the radar pixel. * + * * + * x,y -- The pixel coordinate to render the radar "pixel" at. * + * * + * size -- The size of the "pixel". When zoomed in, this value will be "3". * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/17/1995 JLB : Created. * + *=============================================================================================*/ +void RadarClass::Render_Infantry(CELL cell, int x, int y, int size) +{ + ObjectClass *obj; + int xoff,yoff; + + obj = (ObjectClass *)Map[cell].Cell_Occupier(); + while (obj) { + if (obj->Is_Techno() && (((TechnoClass *)obj)->Cloak != CLOAKED || ((TechnoClass *)obj)->House->Is_Ally(PlayerPtr))) { + switch (obj->What_Am_I()) { + case RTTI_INFANTRY: + { + //int divisor = 255 / ZoomFactor; + int divisor = 86; + if ( ZoomFactor >= 3 ) { + xoff = Coord_XLepton(obj->Coord) / divisor; + yoff = Coord_YLepton(obj->Coord) / divisor; + if ( ZoomFactor >= 6 ) { + xoff<<=1; + yoff<<=1; + } + } else { + xoff = 0; + yoff = 0; + } + LogicPage->Put_Pixel(x+xoff, y+yoff, ((InfantryClass *)obj)->House->Class->BrightColor); + } + break; + + case RTTI_UNIT: + case RTTI_AIRCRAFT: + // PWG: Slowdown? + //if (LogicPage->Lock()){ + Fat_Put_Pixel(x, y, ((UnitClass *)obj)->House->Class->BrightColor, size, *LogicPage); + //LogicPage->Unlock(); + //} + break; + } + } + obj = obj->Next; + } +} + + +/*************************************************************************** + * RadarClass::Render_Overlay -- Renders an icon for given overlay * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/18/1995 PWG : Created. * + *=========================================================================*/ +void RadarClass::Render_Overlay(CELL cell, int x, int y, int size) +{ + OverlayType overlay = (*this)[cell].Overlay; + if (overlay != OVERLAY_NONE) { + OverlayTypeClass const * otype = &OverlayTypeClass::As_Reference(overlay); + + if (otype->IsRadarVisible) { + unsigned char *icon = otype->Radar_Icon((*this)[cell].OverlayData); + if (!icon) return; + Buffer_To_Page(0, 0, 3, 3, icon, _IconStage); + if (otype->IsTiberium) { + _IconStage.Scale(*LogicPage, 0, 0, x, y, 3, 3, size, size, TRUE, (char *)&FadingGreen[0]); + } else { + _IconStage.Scale(*LogicPage, 0, 0, x, y, 3, 3, size, size, TRUE, (char *)&FadingBrighten[0]); + } + } + } +} + + +/*************************************************************************** + * RadarClass::Zoom_Mode -- Handles toggling zoom on the map * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * HISTORY: * + * 05/29/1995 PWG : Created. * + *=========================================================================*/ +void RadarClass::Zoom_Mode(CELL cell) +{ + int map_c_width; + int map_c_height; + /* + ** Set all of the initial zoom mode variables to the correct + ** setting. + */ + IsZoomed = !IsZoomed; + BaseX = 0; + BaseY = 0; + + /* + ** Figure out exactly what size we need to zoom the map to. + */ + if ( !IsZoomed ) { + int xfactor = RadIWidth / MapCellWidth; + int yfactor = RadIHeight / MapCellHeight; + ZoomFactor = MIN(xfactor,yfactor); + map_c_width = MapCellWidth; + map_c_height = MapCellHeight; + } else { + ZoomFactor = 6; + map_c_width = RadIWidth / ZoomFactor; + map_c_height = RadIHeight / ZoomFactor; + } + + /* + ** Make sure we do not show more cell then are on the map. + */ + map_c_width = MIN(map_c_width, 62); + map_c_height = MIN(map_c_height, 62); + + /* + ** Find the amount of remainder because this will let us calculate + ** how to center the thing. + */ + int rem_x = RadIWidth - (map_c_width * ZoomFactor); + int rem_y = RadIHeight - (map_c_height * ZoomFactor); + + /* + ** Finally mark the map so it shows just as much as it is supposed + ** to. + */ + BaseX = rem_x / 2; + BaseY = rem_y / 2; + RadarCellWidth = map_c_width; + RadarCellHeight = map_c_height; + RadarWidth = RadIWidth - rem_x; + RadarHeight = RadIWidth - rem_y; + + /* + ** Set the radar position to the current cell. + */ + Set_Radar_Position(cell); + + /* + ** When zoom mode changes then we need to redraw the radar + ** area. + */ + IsToRedraw = true; + + /* + ** Notify the map that we need to redraw a portion + */ + Flag_To_Redraw(false); + + /* + ** Since we have made a vast change we must redraw everything + */ + FullRedraw = true; +} + + +/*********************************************************************************************** + * RadarClass::Plot_Radar_Pixel -- Updates the radar map with a terrain pixel. * + * * + * This will update the radar map with a pixel. It is used to display * + * vehicle positions on the radar map. * + * * + * INPUT: unit -- Pointer to unit to render at the given position. If * + * NULL is passed in, then the underlying terrain is * + * displayed instead. * + * * + * pos -- Position on the map to update. * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine does NOT hide the mouse. It is up to you to * + * do so. * + * * + * HISTORY: * + * 06/04/1991 JLB : Created. * + * 06/21/1991 JLB : Large blips for units & buildings. * + * 02/14/1994 JLB : Revamped. * + * 04/17/1995 PWG : Created. * + * 04/18/1995 PWG : Created. * + *=============================================================================================*/ +void RadarClass::Plot_Radar_Pixel(CELL cell) +{ + if (cell == -1) cell = 1; + + int x,y; // Coordinate of cell location. + + /* + ** Perform any clipping on the cell coordinate. + */ + if (!IsRadarActive || (unsigned)cell > MAP_CELL_TOTAL) return; + + if (!In_Radar(cell) || !Cell_On_Radar(cell)) { + return; + } + + /* + ** If we are zoomed in then calculate the pixel based off of the portion + ** of the map the radar is viewing. + */ + x = Cell_X(cell) - RadarX; + y = Cell_Y(cell) - RadarY; + + if (LogicPage->Lock()) { + CellClass * cellptr = &(*this)[cell]; + x = RadX + RadOffX + BaseX + (x * ZoomFactor); + y = RadY + RadOffY + BaseY + (y * ZoomFactor); + + /* + ** Determine what (if any) vehicle or unit should be rendered in this blip. + */ + int color=TBLACK; // Color of the pixel to plot. + if ((*this)[cell].IsVisible || Debug_Unshroud) { + color = cellptr->Cell_Color(true); + } else { + color = BLACK; + } + +// ST 8/13/96 2:24PM +//if (cellptr->IsRadarCursor){ +// color = WHITE; +//} + + /* + ** If no color override occurs for this cell, then render the underlying + ** terrain. + */ + if (color == TBLACK) { + if (ZoomFactor > 1) { + void const *ptr; + long offset; + int icon; + + if (cellptr->TType != TEMPLATE_NONE) { + ptr = TemplateTypeClass::As_Reference(cellptr->TType).Get_Image_Data(); + icon = cellptr->TIcon; + } else { + ptr = TemplateTypeClass::As_Reference(TEMPLATE_CLEAR1).Get_Image_Data(); + icon = cellptr->Clear_Icon(); + } + + /* + ** Convert the logical icon number into the actual icon number. + */ + Mem_Copy(Add_Long_To_Pointer((void *)ptr, 28), &offset, sizeof(offset)); + Mem_Copy(Add_Long_To_Pointer((void *)ptr, offset+icon), &icon, sizeof(char)); + icon &= 0x00FF; + + Mem_Copy(Add_Long_To_Pointer((void *)ptr, 12), &offset, sizeof(offset)); + ptr = Add_Long_To_Pointer((void *)ptr, offset + icon*(24*24)); + + unsigned char * data = (unsigned char *)ptr; + Buffer_To_Page(0, 0, 24, 24, data, _TileStage); + _TileStage.Scale(*LogicPage, 0, 0, x, y, 24, 24, ZoomFactor, ZoomFactor, TRUE); + + } else { + if (LogicPage->Lock()){ + Fat_Put_Pixel(x, y, cellptr->Cell_Color(false), ZoomFactor, *LogicPage); + LogicPage->Unlock(); + } + } + } else { + if (LogicPage->Lock()){ + Fat_Put_Pixel(x, y, color, ZoomFactor, *LogicPage); + LogicPage->Unlock(); + } + } + if (color != BLACK) { + Render_Overlay(cell, x, y, ZoomFactor); + Render_Terrain(cell, x, y, ZoomFactor); + Render_Infantry(cell, x, y, ZoomFactor); + } + LogicPage->Unlock(); + } +} + + +/*********************************************************************************************** + * RadarClass::Radar_Pixel -- Mark a cell to be rerendered on the radar map. * + * * + * This routine is used to inform the system that a pixel needs to be * + * rerendered on the radar map. The pixel(s) will be rendered the * + * next time the map is refreshed. * + * * + * INPUT: cell -- The map cell to be rerendered. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/12/1992 JLB : Created. * + * 05/08/1994 JLB : Converted to member function. * + *=============================================================================================*/ +void RadarClass::Radar_Pixel(CELL cell) +{ + if (IsRadarActive && Map.IsSidebarActive && Cell_On_Radar(cell)) { + IsToRedraw = true; + (*this)[cell].IsPlot = true; + if (PixelPtr < PIXELSTACK) { + PixelStack[PixelPtr++] = cell; + } + } +} + + +/*********************************************************************************************** + * RadarClass::Click_In_Radar -- Converts a radar click into cell X and Y coordinate. * + * * + * This routine will examine the X and Y coordinate and convert them into the X and Y * + * cell coordinate value that cooresponds to the location. * + * * + * INPUT: x,y -- The X and Y moouse coordinate already normalized to the radar upper left * + * corner. * + * * + * OUTPUT: Returns with success rating in addition, the X and Y values will now hold the * + * cell coordinates of the cell the pixel offsets indicated. * + * Result 1 = click was in radar region * + * Result 0 = click was outside radar region completly * + * Result-1 = click in radar area but not on clickable reagion of radar. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/30/1995 PWG : Created. * + * 07/16/1995 JLB : Recognizes when sidebar is closed now. * + *=============================================================================================*/ +int RadarClass::Click_In_Radar(int &ptr_x, int &ptr_y, bool change) +{ + int x = ptr_x; + int y = ptr_y; + + /* + ** If radar is not active the click could have been on a radar point + */ + if (!IsRadarActive || !Map.IsSidebarActive) return(0); + + x -= (RadX + RadOffX); + y -= (RadY + RadOffY); + if ((unsigned)x < RadIWidth && (unsigned)y < RadIHeight) { + x -= BaseX; + y -= BaseY; + + if ((unsigned)x < RadarWidth && (unsigned)y < RadarHeight) { + x = RadarX + (x / ZoomFactor); + y = RadarY + (y / ZoomFactor); + if (change) { + ptr_x = x; + ptr_y = y; + } + return(1); + } + return(-1); + } + return(0); +} + + +/*********************************************************************************************** + * RadarClass::Click_Cell_Calc -- Determines what cell the pixel coordinate is over. * + * * + * This routine will examine the pixel coordinate provided and determine what cell it * + * represents. If the radar map is not active or the coordinates are not positioned over * + * the radar map, then it will fall into the base class corresponding routine. * + * * + * INPUT: x,y -- The pixel coordinate to convert into a cell number. * + * * + * OUTPUT: Returns with the cell number that the coordinate is over or -1 if not over any * + * cell. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/22/1994 JLB : Created. * + *=============================================================================================*/ +CELL RadarClass::Click_Cell_Calc(int x, int y) +{ + int result = Click_In_Radar(x, y, true); + switch (result) { + case 1: + return(XY_Cell(x, y)); + + case -1: + return(-1); + } + return(DisplayClass::Click_Cell_Calc(x, y)); +} + + +/*********************************************************************************************** + * RadarClass::Map_Cell -- Updates radar map when a cell becomes mapped. * + * * + * This routine will update the radar map if a cell becomes mapped. * + * * + * INPUT: cell -- The cell that is being mapped. * + * * + * house -- The house that is doing the mapping. * + * * + * OUTPUT: bool; Was the cell mapped (for the first time) by this routine? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/22/1994 JLB : Created. * + *=============================================================================================*/ +bool RadarClass::Map_Cell(CELL cell, HouseClass * house) +{ + if (DisplayClass::Map_Cell(cell, house)) { + Radar_Pixel(cell); + return(true); + } + return(false); +} + +void RadarClass::Cursor_Cell(CELL cell, int value) +{ + int temp = (*this)[cell].IsRadarCursor; + + /* + ** If this cell is not on the radar don't botther doing anything. + */ + if (In_Radar(cell) && temp != value) { + /* + ** Record the new state of this cell. + */ + (*this)[cell].IsRadarCursor = value; + + /* + ** If we are erasing then erase the cell. + */ +////// ST 8/13/96 2:23PM + if (value == FALSE) { + Plot_Radar_Pixel(cell); +////// + } + } +} + +void RadarClass::Mark_Radar(int x1, int y1, int x2, int y2, int value, int barlen) +{ + int x, y; + /* + ** First step is to convert pixel coordinates back to a CellX and CellY. + */ + x1 = RadarX + (x1 / ZoomFactor); + y1 = RadarY + (y1 / ZoomFactor); + x2 = RadarX + (x2 / ZoomFactor); + y2 = RadarY + (y2 / ZoomFactor); + + /* + ** Now we need to convert the Pixel length to a cell length. + */ + barlen = (barlen / ZoomFactor)+1; + + /* + ** Now lets loop through and mark the map with the proper value. + */ + for (int lp = 0; lp <= barlen; lp++) { + /* + ** Do Horizontal action to upper and lower left corners. + */ + x = x1 + lp; + Cursor_Cell(XY_Cell(x,y1), value); + Cursor_Cell(XY_Cell(x,y2), value); + /* + ** Do Horizontal Action to upper and lower right corners + */ + x = x2 - lp; + Cursor_Cell(XY_Cell(x,y1), value); + Cursor_Cell(XY_Cell(x,y2), value); + /* + ** Do Vertical Action to left and right upper corners + */ + y = y1 + lp; + Cursor_Cell(XY_Cell(x1,y), value); + Cursor_Cell(XY_Cell(x2,y), value); + + /* + ** Do Vertical action to left and right lower corners. + */ + y = y2 - lp; + Cursor_Cell(XY_Cell(x1,y), value); + Cursor_Cell(XY_Cell(x2,y), value); + } +} + + + +/*********************************************************************************************** + * RadarClass::Cell_XY_To_Radar_Pixel-- Adjust the position of the radar map cursor. * + * * + * This routine will adjust the location (and visibility) of the radar * + * map cursor. It handles all restoration, drawing, and flashing. * + * * + * INPUT: pos - Cell position for the cursor. If the value is -1 then * + * the cursor will be hidden. If the value is equal to * + * the last value passed in then cursor flashing will * + * be maintained. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1991 JLB : Created. * + * 11/17/1995 PWG : Created. * + *=============================================================================================*/ +void RadarClass::Cell_XY_To_Radar_Pixel(int cellx, int celly, int &x, int &y) +{ + x = (cellx - RadarX) * ZoomFactor; + y = (celly - RadarY) * ZoomFactor; +} + +/*********************************************************************************************** + * RadarClass::Radar_Cursor -- Adjust the position of the radar map cursor. * + * * + * This routine will adjust the location (and visibility) of the radar * + * map cursor. It handles all restoration, drawing, and flashing. * + * * + * INPUT: pos - Cell position for the cursor. If the value is -1 then * + * the cursor will be hidden. If the value is equal to * + * the last value passed in then cursor flashing will * + * be maintained. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1991 JLB : Created. * + * 11/17/1995 PWG : Created. * + *=============================================================================================*/ +#pragma argsused +void RadarClass::Radar_Cursor(int forced) +{ + static _last_pos = -1; + static _last_frame = -1; + GraphicViewPortClass *oldpage; + int x1, y1, x2, y2; + /* + ** figure out these function calls as we will need to call them multiple times. + */ + int tac_cell = Coord_Cell(TacticalCoord); + int tac_cell_x = Cell_X(tac_cell); + int tac_cell_y = Cell_Y(tac_cell); + int barlen = 6; + /* + ** If the current tactical cell is invalid or we haven't moved and we are not forced to redraw then + ** just skip the redraw process. + */ + if (tac_cell != -1 && _last_pos == tac_cell && _last_frame == SpecialRadarFrame && !forced) return; + + if ( _last_pos != -1 ) { + /* + ** The first thing we need to do is take care of erasing the last radar cell position. We do this + ** by converting to pixel coordinates, then adjusting for the pixel coords for the current frame and + ** finally taking care of calling the erase procedure which will convert the pixel coordinates back + ** to the cells that need to be redraw. + **/ + int last_cell_x = Cell_X(_last_pos); + int last_cell_y = Cell_Y(_last_pos); + + Cell_XY_To_Radar_Pixel(last_cell_x, last_cell_y, x1, y1); + Cell_XY_To_Radar_Pixel(last_cell_x + Lepton_To_Cell(TacLeptonWidth), last_cell_y + Lepton_To_Cell(TacLeptonHeight), x2, y2); + x2--; + y2--; + + /* + ** Adjust the current coordinates based on the last animation frame. + */ + x1-= _last_frame; + y1-= _last_frame; + x2+= _last_frame; + y2+= _last_frame; + /* + ** Finally mark the map (actually remove the marks that indicate the radar cursor was there + */ + Mark_Radar(x1, y1, x2, y2, FALSE, barlen); + } + + + /* + ** find the upper left and lower right corners of the radar cursor. Remember to adjust x2 and y2 back + ** by one pixel as they will not be pointing to the right value otherwise. They point one cell ahead + ** of where they should. + */ + Cell_XY_To_Radar_Pixel(tac_cell_x, tac_cell_y, x1, y1); + Cell_XY_To_Radar_Pixel(tac_cell_x + Lepton_To_Cell(TacLeptonWidth), tac_cell_y + Lepton_To_Cell(TacLeptonHeight), x2, y2); + x2--; + y2--; + + /* + ** Adjust the coordinates based on the current frame of radar animation. + */ + x1-= SpecialRadarFrame; + y1-= SpecialRadarFrame; + x2+= SpecialRadarFrame; + y2+= SpecialRadarFrame; + + Mark_Radar(x1, y1, x2, y2, TRUE, barlen); + + /* + ** setup a graphic view port class so we can write all the pixels relative + ** to 0,0 rather than relative to full screen coordinates. + */ + oldpage = Set_Logic_Page(HidPage); + GraphicViewPortClass draw_window(LogicPage->Get_Graphic_Buffer(), + RadX + RadOffX + BaseX + LogicPage->Get_XPos(), + RadY + RadOffY + BaseY + LogicPage->Get_YPos(), + RadarWidth, + RadarHeight); + + draw_window.Draw_Line(x1, y1, x1 + barlen, y1, LTGREEN); + draw_window.Draw_Line(x1, y1, x1, y1 + barlen, LTGREEN); + + // Draw upper right hand corner + draw_window.Draw_Line(x2 - barlen, y1, x2, y1, LTGREEN); + draw_window.Draw_Line(x2, y1, x2, y1 + barlen, LTGREEN); + + // Draw lower left hand corner + draw_window.Draw_Line(x1, y2 - barlen, x1, y2, LTGREEN); + draw_window.Draw_Line(x1, y2, x1 + barlen, y2, LTGREEN); + + // Draw lower right hand corner + draw_window.Draw_Line(x2, y2 - barlen, x2, y2, LTGREEN); + draw_window.Draw_Line(x2 - barlen, y2, x2, y2, LTGREEN); +#if(0) + draw_window.Draw_Rect(x1, y1, x2, y2, WHITE); +#endif + +#if(FALSE) + if (oldpage == &SeenBuff) { + Hide_Mouse(); + HidPage.Blit( SeenBuff, + (int)(RadX + RadOffX + BaseX), + (int)(RadY + RadOffY + BaseY), + (int)(RadX + RadOffX + BaseX), + (int)(RadY + RadOffY + BaseY), + (int)draw_window.Get_Width(), + (int)draw_window.Get_Height(), + (BOOL)FALSE); + + Show_Mouse(); + } +#endif + Set_Logic_Page(oldpage); + _last_pos = tac_cell; + _last_frame = SpecialRadarFrame; + RadarCursorRedraw = FALSE; +} + + +/*************************************************************************** + * RadarClass::Radar_Anim -- Renders current frame of radar animation * + * * + * * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/19/1995 PWG : Created. * + *=========================================================================*/ +void RadarClass::Radar_Anim(void) +{ + /* + ** Do nothing if we're in player-name mode + */ + if (IsPlayerNames) + return; + + if (!Map.IsSidebarActive) return; + + GraphicViewPortClass *oldpage= Set_Logic_Page(HidPage); + GraphicViewPortClass draw_window(LogicPage->Get_Graphic_Buffer(), + RadX + RadOffX + LogicPage->Get_XPos(), + RadY + RadOffY + LogicPage->Get_YPos(), + RadIWidth, + RadIHeight); + + Draw_Box(RadX+RadOffX-1, RadY+RadOffY-1, RadIWidth+2, RadIHeight+2, BOXSTYLE_RAISED, true); + draw_window.Clear(); + CC_Draw_Shape(RadarAnim, RadarAnimFrame, RadX, RadY+1, WINDOW_MAIN, SHAPE_NORMAL); + + Flag_To_Redraw(false); + Set_Logic_Page(oldpage); +} + + +/*********************************************************************************************** + * RadarClass::AI -- Processes radar input (non-tactical). * + * * + * This routine intercepts any player input that concerns the radar map, but not those * + * areas that represent the tactical map. These are handled by the tactical map AI * + * processor. Primarily, this routine handles the little buttons that border the radar * + * map. * + * * + * INPUT: input -- The player input code. * + * * + * x,y -- Mouse coordinate parameters to use. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/23/1994 JLB : Created. * + * 12/26/1994 JLB : Moves tactical map with click or drag. * + * 12/31/1994 JLB : Uses mouse coordinate parameters. * + *=============================================================================================*/ +void RadarClass::AI(KeyNumType & input, int x, int y) +{ + /* + ** Check to see if we need to animate the radar cursor + */ + if (IsRadarActive && Map.IsSidebarActive && SpecialRadarFrame) { + SpecialRadarFrame--; + RadarCursorRedraw = TRUE; + IsToRedraw = TRUE; + Flag_To_Redraw(FALSE); + } + + /* + ** Check goes here to see if there is enough power to run the radar + */ + if (IsRadarActivating) { + if (!DoesRadarExist) { + RadarAnimFrame++; + if (RadarAnimFrame < RADAR_ACTIVATED_FRAME) { + IsToRedraw = true; + Flag_To_Redraw(false); + } else { + DoesRadarExist = true; + Radar_Activate(3); + } + } else { + RadarAnimFrame--; + if (RadarAnimFrame > RADAR_ACTIVATED_FRAME) { + IsToRedraw = true; + Flag_To_Redraw(false); + } else { + Radar_Activate(3); + } + } + } + if (IsRadarDeactivating) { + RadarAnimFrame++; + if (RadarAnimFrame == MAX_RADAR_FRAMES) { + IsRadarDeactivating = false; + } else { + IsToRedraw = true; + Flag_To_Redraw(false); + } + } + + DisplayClass::AI(input, x, y); +} + + +/*********************************************************************************************** + * RadarClass::TacticalClass::Action -- I/O function for the radar map. * + * * + * This is the main action function for handling player I/O on the radar map. It processes * + * mouse clicks as well as mouse moves. * + * * + * INPUT: flags -- The event flags that trigger this function call. * + * * + * key -- Reference the keyboard event that applies to the trigger event. * + * * + * OUTPUT: Should further processing of the input list be aborted? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +int RadarClass::TacticalClass::Action(unsigned flags, KeyNumType & key) +{ + CELL cell; // cell num click happened over + int x,y; // Sub cell pixel coordinates. + int cellx,celly; // Sub cell pixel coordinates. + bool shadow; // is the cell in shadow or not + ObjectClass *object = 0; // what object is in the cell + ActionType action = ACTION_NONE; // Action possible with currently selected object. + + /* + ** Force any help label to disappear when the mouse is held over the + ** radar map. + */ + if (Map.IsSidebarActive) { + Map.Help_Text(TXT_NONE); + } + + if (!Map.IsRadarActive) { + if (Map.IsSidebarActive) { + Map.Override_Mouse_Shape(MOUSE_NORMAL, true); + } + return(false); + } + + /* + ** Disable processing if the player names are up + */ + if (Map.Is_Player_Names()) { + GadgetClass::Action(0, key); + return(true); + } + + /* + ** Set some working variables that depend on the mouse position. For the press + ** or release event, special mouse queuing storage variables are used. Other + ** events must use the current mouse position globals. + */ + if (flags & (LEFTPRESS|LEFTRELEASE|RIGHTPRESS|RIGHTRELEASE)) { + x = _Kbd->MouseQX; + y = _Kbd->MouseQY; + } else { + x = Get_Mouse_X(); + y = Get_Mouse_Y(); + } + + int result = Map.RadarClass::Click_In_Radar(x, y, false); + + if (result == 1) { + cell = Map.RadarClass::Click_Cell_Calc(x, y); + if (cell != -1) { + shadow = (!Map[cell].IsVisible && !Debug_Unshroud); + cellx = 12; + celly = 12; + + /* + ** Determine the object that the mouse is currently over. + */ + if (!shadow) { + object = Map.Cell_Object(cell, cellx, celly); + } + + /* + ** If there is a currently selected object, then the action to perform if + ** the left mouse button were clicked must be determined. + */ + if (CurrentObject.Count()) { + if (object) { + action = CurrentObject[0]->What_Action(object); + } else { + action = CurrentObject[0]->What_Action(cell); + } + + /* + ** If this is not a valid radar map action then we are not going to do + ** anything. + */ + switch (action) { + case ACTION_MOVE: + case ACTION_NOMOVE: + case ACTION_ATTACK: + case ACTION_ENTER: + case ACTION_CAPTURE: + case ACTION_SABOTAGE: + break; + + default: + action = ACTION_NONE; + object = NULL; + break; + } + + /* + ** On the radar map the only reason we would want the normal cursor to + ** appear is if we were over one of our own selected units. Otherwise + ** we can't move there. + **/ + if (action == ACTION_NONE) { + if (object && object->IsSelected) { + object = NULL; + } else { + action = ACTION_NOMOVE; + } + } + + /* + ** A right mouse button press toggles the zoom mode. + */ + if (flags & RIGHTPRESS) { + Map.Mouse_Right_Press(); + } + + /* + ** When the mouse buttons aren't pressed, only the mouse cursor shape is processed. + ** The shape changes depending on what object the mouse is currently over and what + ** object is currently selected. + */ + if (flags & LEFTUP) { + Map.Mouse_Left_Up(shadow, object, action, true); + } + + /* + ** Normal actions occur when the mouse button is released. The press event is + ** intercepted and possible rubber-band mode is flagged. + */ + if (flags & LEFTPRESS) { + Map.Mouse_Left_Release(cell, cellx, celly, object, action, true); + } + + } else { + + Map.Set_Default_Mouse(MOUSE_RADAR_CURSOR, !Map.IsZoomed); + + if (flags & LEFTPRESS) { + + cell = Map.RadarClass::Click_Cell_Calc(x, y); + if (cell != -1) { + int cellx = Cell_X(cell); + int celly = Cell_Y(cell); + cellx -= Lepton_To_Cell(Map.TacLeptonWidth) / 2; + cellx = MAX(cellx, Map.MapCellX); + celly -= Lepton_To_Cell(Map.TacLeptonHeight) / 2; + celly = MAX(celly, Map.MapCellY); + cell = XY_Cell(cellx, celly); + shadow = (!Map[cell].IsVisible && !Debug_Unshroud); + Map.Set_Tactical_Position(Cell_Coord(cell)); + cell = Coord_Cell(Map.DesiredTacticalCoord); + Map.DisplayClass::IsToRedraw = true; + //Map.Flag_To_Redraw(false); + Map.Flag_To_Redraw(true); + Map.SpecialRadarFrame = 4; + } + } + + /* + ** A right mouse button press toggles the zoom mode. + */ + if (flags & RIGHTPRESS) { + Map.Zoom_Mode(cell); + } + } + } + } + if (result == -1) { + Map.Override_Mouse_Shape(MOUSE_NORMAL, true); + } + GadgetClass::Action(0, key); + return(true); +} + + +/*********************************************************************************************** + * RadarClass::Refresh_Cells -- Intercepts refresh request and updates radar if needed * + * * + * This routine intercepts the refresh cells request and if it detects that the sidebar * + * should be rerendered, it flags the radar map to redraw during the next draw operation. * + * * + * INPUT: cell -- The origin cell that the refresh cell offset list is based upon. * + * * + * list -- Pointer to the list of offsets from the origin cell that specifies the * + * cells to be flagged for redraw. If the list starts with the special * + * code to refresh the sidebar, then this routine recognizes it and flags * + * the radar map to be redrawn accordingly. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/01/1995 JLB : Created. * + *=============================================================================================*/ +void RadarClass::Refresh_Cells(CELL cell, short const *list) +{ + if (*list == REFRESH_SIDEBAR) { + IsToRedraw = true; + Flag_To_Redraw(false); + } + DisplayClass::Refresh_Cells(cell, list); +} + + +/*********************************************************************************************** + * RadarClass::Set_Radar_Position -- Sets the radar position to center around specified cell. * + * * + * This routine will try to center the radar map around the cell position specified. * + * * + * INPUT: cell -- The cell to try and position the radar map around. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +void RadarClass::Set_Radar_Position(CELL cell) +{ + int oldx, oldy; + int newx, newy; + int newcell; + + if (ZoomFactor != 1) { +#if(FALSE) + oldx = (Cell_X(cell) - MapCellX) - (RadarCellWidth / 2); + oldy = (Cell_Y(cell) - MapCellY) - (RadarCellHeight / 2); +#else + oldx = (Cell_X(cell) - MapCellX); + oldy = (Cell_Y(cell) - MapCellY); +#endif + } else { + oldx = 0; + oldy = 0; + } + + Confine_Rect(&oldx, &oldy, RadarCellWidth, RadarCellHeight, MapCellWidth, MapCellHeight); + + newx = oldx + MapCellX; + newy = oldy + MapCellY; + newcell = XY_Cell(newx, newy); + + if (RadarCell != newcell) { + int forced = FALSE; + int xmod = newx; + int ymod = newy; + + int radx = (Cell_X(RadarCell)) - xmod; + int rady = (Cell_Y(RadarCell)) - ymod; + + RadarX = newx; + RadarY = newy; + RadarCell = newcell; + + if (Map.IsSidebarActive&& Map.IsRadarActive) { + int radw = RadarCellWidth-ABS(radx); // Replicable width. + int radh = RadarCellHeight-ABS(rady); // Replicable height. + + if (radw < 1) forced = true; + if (radh < 1) forced = true; + + if (!forced && (radw != RadarWidth || radh != RadarHeight)) { + /* + ** Blit the section that is actually overlapping. + */ + if (OverlappedVideoBlits || !HidPage.Get_IsDirectDraw()){ + HidPage.Blit(HidPage, + (((radx < 0) ? -radx : 0) * ZoomFactor) + RadX + RadOffX + BaseX, + (((rady < 0) ? -rady : 0) * ZoomFactor) + RadY + RadOffY + BaseY, + (((radx < 0) ? 0 : radx) * ZoomFactor) + RadX+ RadOffX + BaseX, + (((rady < 0) ? 0 : rady) * ZoomFactor) + RadY + RadOffY + BaseY, + radw * ZoomFactor, + radh * ZoomFactor); + }else{ + /* + ** System does not support overlapped blitting of video surfaces. + ** Blit it in 2 stages using an intermediate buffer. + */ + GraphicBufferClass temp_surface; + temp_surface.Init((RadarWidth + 16) & 0xfffffff0, + (RadarHeight + 16) & 0xfffffff0, + NULL, 0, (GBC_Enum) GBC_VIDEOMEM); + + HidPage.Blit(temp_surface, (((radx < 0) ? -radx : 0) * ZoomFactor) + RadX + RadOffX + BaseX, + (((rady < 0) ? -rady : 0) * ZoomFactor) + RadY + RadOffY + BaseY, + 0, + 0, + RadarWidth, + RadarHeight); + + temp_surface.Blit (HidPage,0, + 0, + (((radx < 0) ? 0 : radx) * ZoomFactor) + RadX + RadOffX + BaseX, + (((rady < 0) ? 0 : rady) * ZoomFactor) + RadY + RadOffY + BaseY, + radw * ZoomFactor, + radh * ZoomFactor); + } + + /* + ** Now we need to flag the section of the map that is going to redraw. + */ + if ( radx != 0 ) { + int min; + int max; + if ( radx < 0 ) { // this mean regen the right edge + min = radw; + max = radw+ABS(radx); + } else { // this mean regen the left edge + min = 0; + max = radx; + } + for (int x = min; x < max; x++ ) { + for (int y = 0; y < RadarCellHeight; y++ ) { + Radar_Pixel(XY_Cell(newx + x, newy + y)); + } + } + } + if ( newy != 0 ) { + int min; + int max; + if ( rady < 0 ) { // this mean regen the bottom edge + min = radh; + max = radh+ABS(rady); + } else { // this mean regen the top edge + min = 0; + max = rady; + } + for (int y = min; y < max; y++ ) { + for ( int x = 0; x < RadarCellWidth; x++ ) { + Radar_Pixel(XY_Cell(newx + x, newy + y)); + } + } + } + } + } + RadarCursorRedraw = IsRadarActive; + IsToRedraw = IsRadarActive; + Flag_To_Redraw(false); + if (ZoomFactor > 4) { + FullRedraw = forced; + } + } else { + RadarCursorRedraw = IsRadarActive; + IsToRedraw = IsRadarActive; + Flag_To_Redraw(false); + } +} + + +/*********************************************************************************************** + * RadarClass::Radar_Position -- Returns with the current position of the radar map. * + * * + * This returns the cell number of the upper left corner of the radar map. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the radar map upper left corner cell position. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +CELL RadarClass::Radar_Position(void) +{ + return(RadarCell); +} + + +/*********************************************************************************************** + * RadarClass::Set_Map_Dimensions -- Sets the tactical map dimensions. * + * * + * This routine is called when the tactical map changes its dimensions. This occurs when * + * the tactical map moves and when the sidebar pops on or off. * + * * + * INPUT: x,y -- The cell coordinate of the upper left corner of the tactical map. * + * * + * w,y -- The cell width and height of the tactical map. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +void RadarClass::Set_Map_Dimensions(int x, int y, int w, int h) +{ + Set_Radar_Position(XY_Cell(x, y)); + DisplayClass::Set_Map_Dimensions(x, y, w, h); +} + + +/*********************************************************************************************** + * RadarClass::Set_Tactical_Position -- Sets the map's tactical position and adjusts radar to * + * * + * This routine is called when the tactical map is to change position. The radar map might * + * be adjusted as well by this routine. * + * * + * INPUT: coord -- The new coordinate to use for the upper left corner of the tactical * + * map. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/17/1995 JLB : Created. * + *=============================================================================================*/ +void RadarClass::Set_Tactical_Position(COORDINATE coord) +{ + DisplayClass::Set_Tactical_Position(coord); + Set_Radar_Position(Coord_Cell(DesiredTacticalCoord)); +} + + +/*********************************************************************************************** + * RadarClass::Cell_On_Radar -- Determines if a cell is currently visible on radar. * + * * + * This routine will examine the specified cell number and return whether it is visible * + * on the radar map. This depends on the radar map position. * + * * + * INPUT: cell -- The cell number to check. * + * * + * OUTPUT: Is the specified cell visible on the radar map currently? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/03/1995 JLB : Created. * + *=============================================================================================*/ +bool RadarClass::Cell_On_Radar(CELL cell) +{ + if ((unsigned)cell > MAP_CELL_TOTAL) + return(false); + + int x = Cell_X(cell) - RadarX; + int y = Cell_Y(cell) - RadarY; + return (!((unsigned)x >= RadarCellWidth || (unsigned)y >= RadarCellHeight)); + +// if (!IsZoomed) { +// return(true); +// } +// return(!(((Cell_X(cell) - RadarX) > RadarCellWidth) || ((Cell_Y(cell) - RadarY) > RadarCellHeight))); +} + +/*********************************************************************************************** + * RadarClass::Player_Names -- toggles the Player-Names mode of the radar map * + * * + * INPUT: * + * on true = turn on; false = turn off * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/07/1995 BRR : Created. * + *=============================================================================================*/ +void RadarClass::Player_Names(bool on) +{ + IsPlayerNames = on; + IsToRedraw = true; + if (on) { + Flag_To_Redraw(true); +// Flag_To_Redraw(false); + } else { + Flag_To_Redraw(true); // force drawing of the plate + } +} + + +/*********************************************************************************************** + * Draw_Names -- draws players' names on the radar map * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 06/07/1995 BRR : Created. * + *=============================================================================================*/ +void RadarClass::Draw_Names(void) +{ + int c_idx; + HousesType house; + HouseClass *ptr; + int y; + char txt[40]; + unsigned char id; + int i; + HousesType h; + int kills; + int color; + TextPrintType style; + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + /* + ** Do nothing if the sidebar isn't there + */ + if (!Map.IsSidebarActive) { + return; + } + + CC_Draw_Shape(RadarAnim, RADAR_ACTIVATED_FRAME, RadX, RadY+1, WINDOW_MAIN, SHAPE_NORMAL); + LogicPage->Fill_Rect( RadX + RadOffX, + RadY + RadOffY, + RadX + RadOffX + RadIWidth - 1, + RadY + RadOffY + RadIHeight - 1, + BLACK); + + y = RadY + RadOffY; + + Fancy_Text_Print (TXT_NAME_COLON, RadX + RadOffX, y, LTGREY, TBLACK, + TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW); + + + Fancy_Text_Print (TXT_KILLS_COLON, RadX + RadOffX + RadIWidth - 2, y, + LTGREY, TBLACK, TPF_RIGHT | TPF_6PT_GRAD | TPF_NOSHADOW | TPF_USE_GRAD_PAL); + + y += 6*factor+1; + + LogicPage->Draw_Line(RadX + RadOffX, y, + RadX + RadOffX + RadIWidth - 1, y, LTGREY); + + y += 2*factor; + + for (house = HOUSE_MULTI1; house < (HOUSE_MULTI1 + MPlayerMax); house++) { + ptr = HouseClass::As_Pointer(house); + + if (!ptr) + continue; + + /* + ** Decode this house's color + */ + c_idx = ptr->RemapColor; + + if (ptr->IsDefeated) { + color = GREY; + style = TPF_6PT_GRAD | TPF_NOSHADOW | TPF_USE_GRAD_PAL; + } else { + color = MPlayerTColors[c_idx]; + style = TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_NOSHADOW; + } + + /* + ** Initialize our message + */ + txt[0] = 0; + + /* + ** If the house is non-human, generate the message + */ + if (!ptr->IsHuman) { + sprintf(txt,"%s", Text_String(TXT_COMPUTER)); + } else { + + /* + ** For a human house: + ** - Compute the multiplayer ID for this house + ** - find the name for this player + */ + id = Build_MPlayerID (c_idx,ptr->ActLike); + for (i = 0; i < MPlayerCount; i++) { + if (id == MPlayerID[i]) { + sprintf(txt,"%s",MPlayerNames[i]); + break; + } + } + } + + /* + ** Print the player name, and the # of kills + */ + if (strlen(txt)) { + if (strlen(txt) > 9) { + txt[9] = '.'; + txt[10] = '\0'; + } + Fancy_Text_Print (txt, RadX + RadOffX, y, color, BLACK, style); + + kills = 0; + for (h = HOUSE_FIRST; h < HOUSE_COUNT; h++) { + kills += ptr->UnitsKilled[h]; + kills += ptr->BuildingsKilled[h]; + } + sprintf(txt, "%2d", kills); + Fancy_Text_Print (txt, RadX + RadOffX + RadIWidth - 2, y, + color, BLACK, style | TPF_RIGHT); + + y += 6*factor+1; + + } + } +} diff --git a/RADAR.H b/RADAR.H new file mode 100644 index 0000000..ed0cc02 --- /dev/null +++ b/RADAR.H @@ -0,0 +1,213 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\radar.h_v 2.17 16 Oct 1995 16:48:04 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : RADAR.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/15/94 * + * * + * Last Update : December 15, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef RADAR_H +#define RADAR_H + +#include "display.h" + +class RadarClass: public DisplayClass +{ + public: + int RadX; + int RadOffX; + int RadY; + int RadOffY; + int RadWidth; + int RadHeight; + int RadIWidth; + int RadIHeight; + int RadPWidth; + int RadPHeight; + + RadarClass(void); + + /* + ** Initialization + */ + virtual void One_Time(void); // One-time inits + virtual void Init_Clear(void); // Clears all to known state + + virtual bool Map_Cell(CELL cell, HouseClass * house); + virtual CELL Click_Cell_Calc(int x, int y); + virtual void AI(KeyNumType &input, int x, int y); + virtual void Draw_It(bool complete=false); + virtual void Refresh_Cells(CELL cell, short const *list); + virtual void Set_Map_Dimensions(int x, int y, int w, int h); +// virtual void Set_Tactical_Position(int x, int y, int leptonx=0, int leptony=0); +// virtual void Set_Tactical_Position(CELL cell); + virtual void Set_Tactical_Position(COORDINATE coord); + void Zoom_Mode(CELL cell); + int Click_In_Radar(int &x, int &y, bool change=false); + void Cell_XY_To_Radar_Pixel(int cellx, int celly, int &x, int &y); + + void Set_Radar_Position(CELL cell); + CELL Radar_Position(void); + bool Radar_Activate(int control); + void Plot_Radar_Pixel(CELL cell); + void Radar_Pixel(CELL cell); + void Coord_To_Radar_Pixel(COORDINATE coord, int &x, int &y); + void Cursor_Cell(CELL cell, int value); + void RadarClass::Mark_Radar(int x1, int y1, int x2, int y2, int value, int barlen); + void Radar_Cursor(int forced = false); + void Render_Terrain(CELL cell, int x, int y, int size); + bool Cell_On_Radar(CELL cell); + void Render_Infantry(CELL cell, int x, int y, int size); + void Render_Overlay(CELL cell, int x, int y, int size); + void Radar_Anim(void); + bool Is_Radar_Active(void) {return IsRadarActive;}; + bool Is_Radar_Existing(void) {return(DoesRadarExist);}; + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /* + ** Toggles player names on & off + */ + void Player_Names(bool on); + int Is_Player_Names(void) {return IsPlayerNames;} + void Draw_Names(void); + int Is_Zoomed(void) {return IsZoomed;} + + protected: + + /* + ** Radar map constant values. + */ + enum RadarClassEnums { + RADAR_ACTIVATED_FRAME=22, + MAX_RADAR_FRAMES = 41 + }; + + /* + ** If the radar map must be completely redrawn, then this flag will be true. + ** Typical causes of this would be when the radar first appears, or when the + ** screen has been damaged. + */ + unsigned IsToRedraw:1; + unsigned RadarCursorRedraw:1; + + /* + ** If the radar map is visible then this flag is true. + */ + unsigned DoesRadarExist:1; + unsigned IsRadarActive:1; + unsigned IsRadarActivating:1; + unsigned IsRadarDeactivating:1; + + /* + ** Special radar frame is set when a new location is selected on the + ** radar map. It counts down through the special radar cursors until + ** either the radar cursor becomes normal or the radar cursor is moved + ** again. + */ + unsigned SpecialRadarFrame:3; + unsigned RadarAnimFrame:6; + + static void const * RadarAnim; + + /* + ** This gadget class is used for capturing input to the tactical map. All mouse input + ** will be routed through this gadget. + */ + class TacticalClass : public GadgetClass { + public: + TacticalClass(void) : GadgetClass(0,0,0,0,LEFTPRESS|LEFTRELEASE|LEFTHELD|LEFTUP|RIGHTPRESS,true) {}; + + protected: + virtual int Action(unsigned flags, KeyNumType & key); + friend class RadarClass; + }; + friend class TacticalClass; + + /* + ** This is the "button" that tracks all input to the tactical map. + ** It must be available to derived classes, for Save/Load purposes. + */ + static TacticalClass RadarButton; + + private: + + /* + ** The current radar position as the upper left corner cell for the + ** radar map display. The width and height is controlled by the + ** actual dimensions of the radar map display box (in pixels). + */ + unsigned RadarX; + unsigned RadarY; + unsigned RadarCell; + + /* + ** This is the origin (pixel offsets) for the upper left corner + ** of the radar map within the full radar map area of the screen. + ** This is biased so that the radar map, when smaller than full + ** size will appear centered. + */ + unsigned BaseX; + unsigned BaseY; + + unsigned RadarWidth; + unsigned RadarCellWidth; + unsigned RadarHeight; + unsigned RadarCellHeight; + + /* + ** If the radar map is in zoom mode, then this value will be true. + */ + unsigned IsZoomed:1; + + /* + ** This flag is true if the radar map is in its special show-the-player + ** names mode. + */ + unsigned IsPlayerNames:1; + + /* + ** This is the list of radar pixels that need to be updated. Only a partial + ** list is maintained for maximum speed. + */ + unsigned PixelPtr; + int ZoomFactor; + enum PixelStackEnums {PIXELSTACK=200}; + CELL PixelStack[PIXELSTACK]; +}; + + +#endif diff --git a/RADIO.CPP b/RADIO.CPP new file mode 100644 index 0000000..8aa7abc --- /dev/null +++ b/RADIO.CPP @@ -0,0 +1,252 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\radio.cpv 2.19 16 Oct 1995 16:50:12 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : RADIO.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : June 25, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * RadioClass::Debug_Dump -- Displays the current status of the radio to the mono monitor. * + * RadioClass::Receive_Message -- Handles receipt of a radio message. * + * RadioClass::Transmit_Message -- Transmit message from one object to another. * + * RadioClass::Limbo -- When limboing a unit will always break radio contact. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/* +** These are the text representations of the radio messages that can be transmitted. +*/ +char const * RadioClass::Messages[RADIO_COUNT] = { + "hisssss", + "Roger.", + "Come in.", + "Over and out.", + "Requesting transport.", + "Attach to transport.", + "I've got a delivery for you.", + "I'm performing load/unload maneuver. Be careful.", + "I'm clear.", + "You are clear to unload. Driving away now.", + "Am unable to comply.", + "I'm starting construction now... act busy.", + "I've finished construction. You are free.", + "We bumped, redraw yourself please.", + "I'm trying to load up now.", + "May I become a passenger?", + "Are you ready to receive shipment?", + "Are you trying to become a passenger?", + "Move to location X.", + "Do you need to move?", + "All right already. Now what?", + "I'm a passenger now.", + "Backup into refinery now.", + "Run away!", + "Tether established.", + "Tether broken.", + "Repair one step.", + "Are you prepared to fight?", + "Attack this target please.", + "Reload one step.", + "Take this kick! You... You...", + "Take this punch! You... You...", + "Fancy a little fisticuffs, eh?" +}; + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * RadioClass::Debug_Dump -- Displays the current status of the radio to the mono monitor. * + * * + * This displays the radio connection value to the monochrome monitor. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/02/1994 JLB : Created. * + *=============================================================================================*/ +void RadioClass::Debug_Dump(MonoClass *mono) const +{ + mono->Set_Cursor(34, 5);mono->Printf(Messages[LastMessage]); + if (Radio) { + mono->Set_Cursor(50, 1);mono->Printf("%04X", Radio->As_Target()); + } + MissionClass::Debug_Dump(mono); +} +#endif + + +/*********************************************************************************************** + * RadioClass::Receive_Message -- Handles receipt of a radio message. * + * * + * This is the base version of what should happen when a radio message is received. It * + * turns the radio off when the "OVER_OUT" message is received. All other messages are * + * merely acknowledged with a "ROGER". * + * * + * INPUT: from -- The object that is initiating this radio message (always valid). * + * * + * message -- The radio message received. * + * * + * param -- Reference to optional value that might be used to return more * + * information than can be conveyed in the simple radio response * + * messages. * + * * + * OUTPUT: Returns with the response radio message. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + * 09/24/1994 JLB : Streamlined to be only a communications carrier. * + * 05/22/1995 JLB : Recognized who is sending the message * + *=============================================================================================*/ +RadioMessageType RadioClass::Receive_Message(RadioClass * from, RadioMessageType message, long & param) +{ + /* + ** Keep a record of the last message received by this radio. + */ + LastMessage = message; + + /* + ** When this message is received, it means that the other object + ** has already turned its radio off. Turn this radio off as well. + ** This only applies if the message is coming from the object that + ** has an established conversation with this object. + */ + if (from == Radio && message == RADIO_OVER_OUT) { + MissionClass::Receive_Message(from, message, param); + Radio_Off(); + return(RADIO_ROGER); + } + + /* + ** The "hello" message is an attempt to establish contact. If this radio + ** is already in an established conversation with another object, then + ** return with "negative". If all is well, return with "roger". + */ + if (message == RADIO_HELLO && Strength) { + if (Radio == from || !Radio) { + Radio = from; + return(RADIO_ROGER); + } + return(RADIO_NEGATIVE); + } + + return(MissionClass::Receive_Message(from, message, param)); +} + + +/*********************************************************************************************** + * RadioClass::Transmit_Message -- Transmit message from one object to another. * + * * + * This routine is used to transmit a radio message from this object to another. Most * + * inter object coordination is handled through this mechanism. * + * * + * INPUT: to -- Pointer to the object that will receive the radio message. * + * * + * message -- The message itself (see RadioType). * + * * + * param -- Optional reference to parameter that might be used to pass or * + * receive additional information. * + * * + * OUTPUT: Returns with the response radio message from the receiving object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1995 JLB : Created. * + *=============================================================================================*/ +RadioMessageType RadioClass::Transmit_Message(RadioMessageType message, long & param, RadioClass * to) +{ + if (!to) { + to = Contact_With_Whom(); + } + + /* + ** If there is no target for the radio message, then always return static. + */ + if (!to) return(RADIO_STATIC); + + /* + ** Handle some special case processing that occurs when certain messages + ** are transmitted. + */ + if (to == Radio && message == RADIO_OVER_OUT) { + Radio = 0; + } + + /* + ** If this object is not in radio contact but the message + ** indicates that radio contact should be established, then + ** try to do so. If the other party agrees then contact + ** is established. + */ + if (/*!Radio &&*/ message == RADIO_HELLO) { + Transmit_Message(RADIO_OVER_OUT); + if (to->Receive_Message(this, message, param) == RADIO_ROGER) { + Radio = to; + return(RADIO_ROGER); + } + return(RADIO_NEGATIVE); + } + + return(to->Receive_Message(this, message, param)); +} + + +/*********************************************************************************************** + * RadioClass::Limbo -- When limboing a unit will always break radio contact. * + * * + * This routine will break radio contact as the object is entering limbo state. * + * * + * INPUT: none * + * * + * OUTPUT: Was the object successfully limboed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +bool RadioClass::Limbo(void) +{ + if (!IsInLimbo) { + Transmit_Message(RADIO_OVER_OUT); + } + return(MissionClass::Limbo()); +} + + +RadioMessageType RadioClass::Transmit_Message(RadioMessageType message, RadioClass * to) {return(Transmit_Message(message, LParam, to));}; diff --git a/RADIO.H b/RADIO.H new file mode 100644 index 0000000..cf6023f --- /dev/null +++ b/RADIO.H @@ -0,0 +1,108 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\radio.h_v 2.15 16 Oct 1995 16:45:32 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : RADIO.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 23, 1994 * + * * + * Last Update : April 23, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef RADIO_H +#define RADIO_H + +//#include "object.h" +#include "mission.h" +//#include "flasher.h" + +class ObjectClass; +class TechnoClass; + + +/**************************************************************************** +** Radio contact is controlled by this class. It handles the mundane chore +** of keeping the radio contact alive as well as broadcasting messages +** to the receiving radio. Radio contact is primarily used when one object +** is in "command" of another. +*/ +class RadioClass : public MissionClass { + private: + + /* + ** This is a record of the last message received by this receiver. + */ + RadioMessageType LastMessage; + + /* + ** This is the object that radio communication has been established + ** with. Although is is only a one-way reference, it is required that + ** the receiving radio is also tuned to the object that contains this + ** radio set. + */ + RadioClass * Radio; + + /* + ** This is a text representation of all the possible radio messages. This + ** text is used for monochrome debug printing. + */ + static char const * Messages[RADIO_COUNT]; + + public: + + /*--------------------------------------------------------------------- + ** Constructors, Destructors, and overloaded operators. + */ + RadioClass(void) {Radio = 0;LastMessage = RADIO_STATIC;}; + virtual ~RadioClass(void) {}; + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + bool In_Radio_Contact(void) const {return (Radio != 0);}; + void Radio_Off(void) {Radio = 0;}; + TechnoClass * Contact_With_Whom(void) const {return (TechnoClass*)Radio;}; + + // Inherited from base class(es). + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, long & param); + virtual RadioMessageType Transmit_Message(RadioMessageType message, long & param=LParam, RadioClass * to=NULL); + virtual RadioMessageType Transmit_Message(RadioMessageType message, RadioClass * to); + #ifdef CHEAT_KEYS + virtual void Debug_Dump(MonoClass *mono) const; + #endif + virtual bool Limbo(void); + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); +}; + +#endif diff --git a/RAND.CPP b/RAND.CPP new file mode 100644 index 0000000..ffb0e87 --- /dev/null +++ b/RAND.CPP @@ -0,0 +1,173 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\rand.cpv 1.6 16 Oct 1995 16:51:10 JOE_BOSTIC $ */ +/*************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : RAND.CPP * + * * + * Programmer : Bill R. Randolph * + * * + * Start Date : 04/25/95 * + * * + * Last Update : June 17, 1995 [JLB] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * Sim_Random -- Returns 0 - 255 * + * Sim_IRandom -- Returns minval to maxval, inclusive * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +int SimRandIndex = 0; + +/*************************************************************************** + * Sim_Random -- Returns 0 - 255 * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 0 - 255, at random * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/25/1995 BRR : Created. * + * 06/17/1995 JLB : Takes advantage of character math wrap. * + *=========================================================================*/ +int Sim_Random(void) +{ + static unsigned char _randvals[] = { + 0x47, 0xce, 0xc6, 0x6e, 0xd7, 0x9f, 0x98, 0x29, 0x92, 0x0c, 0x74, 0xa2, + 0x65, 0x20, 0x4b, 0x4f, 0x1e, 0xed, 0x3a, 0xdf, 0xa5, 0x7d, 0xb5, 0xc8, + 0x86, 0x01, 0x81, 0xca, 0xf1, 0x17, 0xd6, 0x23, 0xe1, 0xbd, 0x0e, 0xe4, + 0x62, 0xfa, 0xd9, 0x5c, 0x68, 0xf5, 0x7f, 0xdc, 0xe7, 0xb9, 0xc4, 0xb3, + 0x7a, 0xd8, 0x06, 0x3e, 0xeb, 0x09, 0x1a, 0x31, 0x3f, 0x46, 0x28, 0x12, + 0xf0, 0x10, 0x84, 0x76, 0x3b, 0xc5, 0x53, 0x18, 0x14, 0x73, 0x7e, 0x59, + 0x48, 0x93, 0xaa, 0x1d, 0x5d, 0x79, 0x24, 0x61, 0x1b, 0xfd, 0x2b, 0xa8, + 0xc2, 0xdb, 0xe8, 0x2a, 0xb0, 0x25, 0x95, 0xab, 0x96, 0x83, 0xfc, 0x5f, + 0x9c, 0x32, 0x78, 0x9a, 0x9e, 0xe2, 0x8e, 0x35, 0x4c, 0x41, 0xa1, 0x69, + 0x5a, 0xfe, 0xa7, 0xa4, 0xf6, 0x6d, 0xc1, 0x58, 0x0a, 0xcf, 0xea, 0xc3, + 0xba, 0x85, 0x99, 0x8d, 0x36, 0xb6, 0xdd, 0xd3, 0x04, 0xe6, 0x45, 0x0d, + 0x60, 0xae, 0xa3, 0x22, 0x4d, 0xe9, 0xc9, 0x9b, 0xb7, 0x0f, 0x02, 0x42, + 0xf9, 0x0b, 0x8f, 0x43, 0x44, 0x87, 0x70, 0xbe, 0xe3, 0xf8, 0xee, 0xa9, + 0xbc, 0xc0, 0x67, 0x33, 0x16, 0x37, 0x57, 0xad, 0x5e, 0x9d, 0x64, 0x40, + 0x54, 0x05, 0x2c, 0xe0, 0xb2, 0x97, 0x08, 0xaf, 0x75, 0x8a, 0x5b, 0xfb, + 0x4e, 0xbf, 0x91, 0xf3, 0xcb, 0x7c, 0x63, 0xef, 0x89, 0x52, 0x6c, 0x2f, + 0x21, 0x4a, 0xf7, 0xcd, 0x2e, 0xf4, 0xc7, 0x6f, 0x19, 0xb1, 0x66, 0xcc, + 0x90, 0x8c, 0x50, 0x51, 0x26, 0x7b, 0xda, 0x49, 0x80, 0x30, 0x55, 0x1f, + 0xd2, 0xb4, 0xd1, 0xd5, 0x6b, 0xf2, 0x72, 0xbb, 0x13, 0x3d, 0xff, 0x15, + 0x38, 0xe5, 0xd4, 0xde, 0x2d, 0x27, 0x94, 0xa0, 0xd0, 0x39, 0x82, 0x8b, + 0x03, 0xac, 0x3c, 0x34, 0x77, 0xb8, 0xec, 0x00, 0x07, 0x1c, 0x88, 0xa6, + 0x56, 0x11, 0x71, 0x6a, + }; + + ((unsigned char&)SimRandIndex)++; +// SimRandIndex &= 0xff; + return(_randvals[SimRandIndex]); +} + + +/*************************************************************************** + * Sim_IRandom -- returns minval to maxval, inclusive * + * * + * INPUT: * + * minval,maxval range limits * + * * + * OUTPUT: * + * random value * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 04/25/1995 BRR : Created. * + * 06/17/1995 JLB : Uses fixed point math helper routine. * + *=========================================================================*/ +int Sim_IRandom(int minval, int maxval) +{ + return(Fixed_To_Cardinal((maxval-minval), Sim_Random()) + minval); +} + + +/*--------------------------------------------------------------------------- +This routine can be used to create the _RandVals[] table. It will be +different every time it's run. +---------------------------------------------------------------------------*/ +#if 0 +#include +#include +#include +#include + +void main(void); + +void main(void) +{ + int i; + FILE *fp; + int r; + char is_used[256]; + char rand_val[256]; + + srand ( time ( NULL ) ) ; + + for (i = 0; i < 256; i++) { + is_used[i] = 0; + } + + /* + ** Store the digits 0 to 255 in a random order within the 'rand_val' array + ** This ensures our random number sequence represents every value. + */ + for (i = 0; i < 256; i++) { + if (kbhit()!=0) { + printf("ABORTED!\n"); + break; + } + r = (rand() * 256) / RAND_MAX; // r is the index into array + if (is_used[r]==0) { + is_used[r] = 1; + rand_val[r] = i; + } else { + i--; + } + } + + fp = fopen("rand.cpp","wt"); + if (fp==NULL) { + printf("File error\n"); + exit(0); + } + + fprintf(fp, "unsigned char _RandVals[] = {\n"); + for (i = 0; i < 256; i++) { + fprintf(fp,"0x%02x,\n",rand_val[i]); + } + fprintf(fp,"};\n"); + fclose(fp); + +} + +#endif + diff --git a/RAWFILE.CPP b/RAWFILE.CPP new file mode 100644 index 0000000..61551d1 --- /dev/null +++ b/RAWFILE.CPP @@ -0,0 +1,1089 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\rawfile.cpv 2.18 16 Oct 1995 16:50:54 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Westwood Library * + * * + * File Name : RAWFILE.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : August 8, 1994 * + * * + * Last Update : October 18, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * RawFileClass::Close -- Perform a closure of the file. * + * RawFileClass::Create -- Creates an empty file. * + * RawFileClass::Delete -- Deletes the file object from the disk. * + * RawFileClass::Error -- Handles displaying a file error message. * + * RawFileClass::Is_Available -- Checks to see if the specified file is available to open. * + * RawFileClass::Open -- Assigns name and opens file in one operation. * + * RawFileClass::Open -- Opens the file object with the rights specified. * + * RawFileClass::RawFileClass -- Simple constructor for a file object. * + * RawFileClass::Read -- Reads the specified number of bytes into a memory buffer. * + * RawFileClass::Seek -- Reposition the file pointer as indicated. * + * RawFileClass::Set_Name -- Manually sets the name for a file object. * + * RawFileClass::Size -- Determines size of file (in bytes). * + * RawFileClass::Write -- Writes the specified data to the buffer specified. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + +#include "function.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wwlib32.h" +#include "compat.h" +#include "rawfile.h" + +extern GraphicBufferClass SeenBuff; +extern GraphicBufferClass HidPage; + +/*********************************************************************************************** + * RawFileClass::Error -- Handles displaying a file error message. * + * * + * Display an error message as indicated. If it is allowed to retry, then pressing a key * + * will return from this function. Otherwise, it will exit the program with "exit()". * + * * + * INPUT: error -- The error number (same as the DOSERR.H error numbers). * + * * + * canretry -- Can this routine exit normally so that retrying can occur? If this is * + * false, then the program WILL exit in this routine. * + * * + * filename -- Optional filename to report with this error. If no filename is * + * supplied, then no filename is listed in the error message. * + * * + * OUTPUT: none, but this routine might not return at all if the "canretry" parameter is * + * false or the player pressed ESC. * + * * + * WARNINGS: This routine may not return at all. It handles being in text mode as well as * + * if in a graphic mode. * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +void RawFileClass::Error(int error, int canretry, char const * filename) +{ + char message[256]; // Staging buffer for error message string. + + /* + ** Build the complete text of the error message. This text is used in either the graphic + ** mode or the text mode version. + */ +#ifdef GERMAN + strcpy(message, "DATEIFEHLER"); +#else +#ifdef FRENCH + strcpy(message, "ERREUR DE FICHIER"); +#else + strcpy(message, "FILE ERROR"); +#endif +#endif + if (filename) { + strcat(message, "("); + strcat(message, filename); + strcat(message, ")"); + } + strcat(message, ": "); +//BG: Borland only strcat(message, _sys_errlist[error]); + strcat(message, strerror(error) ); + strcat(message, ". "); + + /* + ** If it can't properly handle displaying the error message in the + ** current graphic mode, then this forces the error to become non + ** recoverable. Go into text mode and proceed. + */ + if (!FontPtr && GraphicMode != TXT_MODE) { + Set_Video_Mode(RESET_MODE); + canretry = false; + GraphicMode = TXT_MODE; + } + + /* + ** Add the text explaining the valid actions to take. + */ + if (canretry) { + if (GraphicMode == TXT_MODE) strcat(message, "\n"); +#ifdef GERMAN + strcat(message, " Beliebige Taste drcken fr erneuten Versuch."); + if (GraphicMode == TXT_MODE) strcat(message, "\n"); + strcat(message, " drcken, um das Programm zu verlassen."); +#else +#ifdef FRENCH + strcat(message, " Appuyez sur une touche pour recommencer."); + if (GraphicMode == TXT_MODE) strcat(message, "\n"); + strcat(message, " Appuyez sur Echap pour quitter le programme."); +#else + strcat(message, " Press any key to retry."); + if (GraphicMode == TXT_MODE) strcat(message, "\n"); + strcat(message, " Press to exit program."); +#endif +#endif + if (GraphicMode == TXT_MODE) strcat(message, "\n"); + } else { + if (GraphicMode == TXT_MODE) strcat(message, "\n"); +#ifdef GERMAN + strcat(message, " Beliebige Taste drcken, um das Programm zu verlassen."); +#else +#ifdef FRENCH + strcat(message, " Appuyez sur une touche pour quitter le programme."); +#else + strcat(message, " Press any key to exit program."); +#endif +#endif + if (GraphicMode == TXT_MODE) strcat(message, "\n"); + } + + /* + ** In text mode, the error handler is very simple. It just prints the error message + ** to the screen and waits for a response. + */ + if (GraphicMode == TXT_MODE) { + int input; + + /* + ** Display the error message and wait for a response. + */ + printf(message); + Keyboard::Clear(); + input = Keyboard::Get(); + + /* + ** Check for input. If the ESC key was pressed or if retrying is not allowed, + ** then exit the program. Otherwise, return from this routine for a retry + ** attempt. + */ + if (input == KN_ESC || !canretry) { + Prog_End(); + exit(EXIT_FAILURE); + } + + } else { + + /* + ** The graphic mode version of the error handler will display a simple message + ** box on the screen. If the palette is black at this point, then the error will + ** be invisible. For more thorough and pleasing results, you should replace this + ** virtual function with one of your own, that is more aware of the environment + ** in which is exists. + */ + void *background; // Pointer to background saving buffer. + GraphicBufferClass * oldpage; // Copy of old logic page. + int oldwindow; // Copy of old window number. + void const *oldfont; // Copy of old font pointer. + int oldspacing; // Old font X spacing. + + /* + ** Setup display in preparation for printing the error message. + */ + oldpage = Set_Logic_Page(SeenBuff); + oldwindow = Change_Window(ErrorWindow); + oldfont = Set_Font(FontPtr); + oldspacing = FontXSpacing; FontXSpacing = 0; + Hide_Mouse(); + + /* + ** Try to allocate a storage buffer for the background to the + ** error window. + */ + background = new char [Size_Of_Region(WinW<<3, WinH)]; + + /* + ** If there is memory for the background storage, then save the + ** screen image area to that buffer. + */ + if (background) { + SeenBuff.To_Buffer(WinX<<3, WinY, WinW<<3, WinH, background, Size_Of_Region(WinW<<3, WinH)); + } + + /* + ** Draw a rudimentary box. + */ + New_Window(); + LogicPage->Draw_Rect(WinX<<3, WinY, (WinX+WinW)<<3, WinY+WinH, WinC); + + /* + ** shrinks window down one byte in all directions. + */ + WindowList[Window][WINDOWX] += 1; + WindowList[Window][WINDOWY] += 1<<3; + WindowList[Window][WINDOWWIDTH] -= 1<<1; + WindowList[Window][WINDOWHEIGHT] -= 1<<4; + Change_Window(Window); + WinCx = WinCy = 0; + + Window_Print(message); + Keyboard::Clear(); + + /* + ** Check for input. If the ESC key was pressed or if retrying is not allowed, + ** then exit the program. Otherwise, return from this routine for a retry + ** attempt. + */ + int input = Keyboard::Get(); + if (input == KN_ESC || !canretry) { + Prog_End(); + exit(EXIT_FAILURE); + } + + /* + ** Restore the window back to its original size. + */ + WindowList[Window][WINDOWX] -= 1; + WindowList[Window][WINDOWY] -= 1<<3; + WindowList[Window][WINDOWWIDTH] += 1<<1; + WindowList[Window][WINDOWHEIGHT] += 1<<4; + Change_Window(Window); + WinCx = WinCy = 0; + + /* + ** If the background was saved off, then restore it. + */ + if (background) { + Buffer_To_Page(WinX<<3, WinY, WinW<<3, WinH, background, SeenBuff); + delete [] background; + } + + /* + ** Restore the system global settings to original values. + */ + Show_Mouse(); + Change_Window(oldwindow); + Set_Font(oldfont); + Set_Logic_Page(oldpage); + FontXSpacing = oldspacing; + } +} + + +/*********************************************************************************************** + * RawFileClass::RawFileClass -- Simple constructor for a file object. * + * * + * This constructor is called when a file object is created with a supplied filename, but * + * not opened at the same time. In this case, an assumption is made that the supplied * + * filename is a constant string. A duplicate of the filename string is not created since * + * it would be wasteful in that case. * + * * + * INPUT: filename -- The filename to assign to this file object. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +RawFileClass::RawFileClass(char const *filename) : + Handle(-1), + Filename(filename), + Allocated(false) +{ +} + + +RawFileClass::~RawFileClass(void) +{ + if (Allocated && Filename) { + free((char *)Filename); + } + Allocated = false; + Filename = 0; +} + + +/*********************************************************************************************** + * RawFileClass::Set_Name -- Manually sets the name for a file object. * + * * + * This routine will set the name for the file object to the name specified. This name is * + * duplicated in free store. This allows the supplied name to be a temporarily constructed * + * text string. Setting the name in this fashion doesn't affect the closed or opened state * + * of the file. * + * * + * INPUT: filename -- The filename to assign to this file object. * + * * + * OUTPUT: Returns with a pointer to the allocated copy of this filename. This pointer is * + * guaranteed to remain valid for the duration of this file object or until the name * + * is changed -- whichever is sooner. * + * * + * WARNINGS: Because of the allocation this routine must perform, memory could become * + * fragmented. * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +char const * RawFileClass::Set_Name(char const *filename) +{ + if (Filename && Allocated) { + free((char*)Filename); + Filename = 0; + Allocated = false; + } + + if (!filename) return(NULL); + + Filename = strdup(filename); + if (!Filename) { + Error(ENOMEM, false, filename); + } + Allocated = true; + return(Filename); +} + + +/*********************************************************************************************** + * RawFileClass::Open -- Assigns name and opens file in one operation. * + * * + * This routine will assign the specified filename to the file object and open it at the * + * same time. If the file object was already open, then it will be closed first. If the * + * file object was previously assigned a filename, then it will be replaced with the new * + * name. Typically, this routine is used when an anonymous file object has been crated and * + * now it needs to be assigned a name and opened. * + * * + * INPUT: filename -- The filename to assign to this file object. * + * * + * rights -- The open file access rights to use. * + * * + * OUTPUT: bool; Was the file opened? The return value of this is moot, since the open file * + * is designed to never return unless it succeeded. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +int RawFileClass::Open(char const *filename, int rights) +{ + Set_Name(filename); + return(Open(rights)); +} + + +/*********************************************************************************************** + * RawFileClass::Open -- Opens the file object with the rights specified. * + * * + * This routine is used to open the specified file object with the access rights indicated. * + * This only works if the file has already been assigned a filename. It is guaranteed, by * + * the error handler, that this routine will always return with success. * + * * + * INPUT: rights -- The file access rights to use when opening this file. This is a * + * combination of READ and/or WRITE bit flags. * + * * + * OUTPUT: bool; Was the file opened successfully? This will always return true by reason of * + * the error handler. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +int RawFileClass::Open(int rights) +{ + Close(); + + /* + ** Verify that there is a filename associated with this file object. If not, then this is a + ** big error condition. + */ + if (!Filename) { + Error(ENOENT, false); + } + + /* + ** Record the access rights used for this open call. These rights will be used if the + ** file object is duplicated. + */ + Rights = rights; + + /* + ** Repetatively try to open the file. Abort if a fatal error condition occurs. + */ + for (;;) { + + /* + ** Try to open the file according to the access rights specified. + */ + Hard_Error_Occured = 0; + switch (rights) { + + /* + ** If the access rights are not recognized, then report this as + ** an invalid access code. + */ + default: + errno = EINVAL; + break; + + case READ: + _dos_open(Filename, O_RDONLY|SH_DENYNO, &Handle); + break; + + case WRITE: + _dos_creat(Filename, 0, &Handle); + break; + + case READ|WRITE: + _dos_open(Filename, O_RDWR|O_CREAT|SH_DENYWR, &Handle); + break; + } + + /* + ** If the handle indicates the file is not open, then this is an error condition. + ** For the case of the file cannot be found, then allow a retry. All other cases + ** are fatal. + */ + if (Handle < 0) { + + /* + ** If this flag is set, then some hard error occurred. Just assume that the error + ** is probably a removed CD-ROM and allow a retry. + */ + if (Hard_Error_Occured) { + Error(Hard_Error_Occured, true, Filename); + } else { + if (errno == ENOENT) { + Error(ENOENT, true, Filename); + } else { + Error(errno, false, Filename); + } + } + continue; + } + break; + } + return(true); +} + + +/*********************************************************************************************** + * RawFileClass::Is_Available -- Checks to see if the specified file is available to open. * + * * + * This routine will examine the disk system to see if the specified file can be opened * + * or not. Use this routine before opening a file in order to make sure that is available * + * or to perform other necessary actions. * + * * + * INPUT: force -- Should this routine keep retrying until the file becomes available? If * + * in this case it doesn't become available, then the program will abort. * + * * + * OUTPUT: bool; Is the file available to be opened? * + * * + * WARNINGS: Depending on the parameter passed in, this routine may never return. * + * * + * HISTORY: * + * 10/18/1994 JLB : Created. * + *=============================================================================================*/ +int RawFileClass::Is_Available(int forced) +{ + int file; // Working file handle. + int open_failed; + + /* + ** If the file is already open, then is must have already passed the availability check. + ** Return true in this case. + */ + if (Is_Open()) { + return(true); + } + + /* + ** If this is a forced check, then go through the normal open channels, since those + ** channels ensure that the file must exist. + */ + if (forced) { + RawFileClass::Open(READ); + RawFileClass::Close(); + return(true); + } + + /* + ** Perform a raw open of the file. If this open fails for ANY REASON, including a missing + ** CD-ROM, this routine will return a failure condition. In all but the missing file + ** condition, go through the normal error recover channels. + */ + for (;;) { + + Hard_Error_Occured = 0; + open_failed = _dos_open(Filename, O_RDONLY|SH_DENYNO, &file); + + /* + ** If DOS reports that everything is just fine except that the file is not present, + ** then return with this fact. Any other case will fall through to the error handler + ** routine. + */ + if (open_failed && errno == ENOENT) { + return(false); + } + + /* + ** If we got an access error it could be because there is no cd in + ** the drive. Call the error handler but allow a continue if it + ** returns. + */ + if (open_failed && errno == EACCES) { +// Error(errno, true, Filename); +// continue; + return(false); + } + + /* + ** If the file could not be found, then return with this information. If a more + ** serious error occurred, then display the error message and abort. + */ + if (Hard_Error_Occured) { + return(false); +// Error(Hard_Error_Occured, true, Filename); +// continue; + } else { + if (open_failed) { + /* + ** An unhandled error condition is fatal. Display the error message and then + ** abort. + */ +// Error(errno, false, Filename); + return(false); + } + } + break; + } + + /* + ** Since the file could be opened, then close it and return that the file exists. + */ + if (_dos_close(file)) { + Error(errno, false, Filename); + } + return(true); +} + + +/*********************************************************************************************** + * RawFileClass::Close -- Perform a closure of the file. * + * * + * Close the file object. In the rare case of an error, handle it as appropriate. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Some rare error conditions may cause this routine to abort the program. * + * * + * HISTORY: * + * 10/18/1994 JLB : Created. * + *=============================================================================================*/ +void RawFileClass::Close(void) +{ + + /* + ** If the file is open, then close it. If the file is already closed, then just return. This + ** isn't considered an error condition. + */ + if (Is_Open()) { + + for (;;) { + /* + ** Close the file. If there was an error in the close operation -- abort. + */ + Hard_Error_Occured = 0; + if (_dos_close(Handle)) { + + /* + ** By definition, this error can only be a bad file handle. This a fatal condition + ** of course, so abort with an error message. + */ + Error(errno, false, Filename); + } + + /* + ** In the condition (if it is even possible) of a hard error occurring, then + ** assume it is the case of missing media. Display an error message and try + ** again if indicated. + */ + if (Hard_Error_Occured) { + Error(Hard_Error_Occured, true, Filename); + continue; + } + break; + } + + /* + ** At this point the file must have been closed. Mark the file as empty and return. + */ + Handle = -1; + } +} + + +/*********************************************************************************************** + * RawFileClass::Read -- Reads the specified number of bytes into a memory buffer. * + * * + * This routine will read the specified number of bytes and place the data into the buffer * + * indicated. It is legal to call this routine with a request for more bytes than are in * + * the file. This condition can result in fewer bytes being read than requested. Determine * + * this by examining the return value. * + * * + * INPUT: buffer -- Pointer to the buffer to read data into. If NULL is passed, no read * + * is performed. * + * * + * size -- The number of bytes to read. If NULL is passed, then no read is * + * performed. * + * * + * OUTPUT: Returns with the number of bytes read into the buffer. If this number is less * + * than requested, it indicates that the file has been exhausted. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/18/1994 JLB : Created. * + *=============================================================================================*/ +long RawFileClass::Read(void *buffer, long size) +{ + long bytesread = 0; // Running count of the number of bytes read into the buffer. + int opened = false; // Was the file opened by this routine? + int readresult; + + /* + ** If the file isn't opened, open it. This serves as a convenience + ** for the programmer. + */ + if (!Is_Open()) { + + /* + ** The error check here is moot. Open will never return unless it succeeded. + */ + if (!Open(READ)) { + return(0); + } + opened = true; + } + + /* + ** Read the file in convenient chunk sizes. When the actual number + ** of bytes read does not match the desired, then assume that the file + ** is exhausted and bail. This loop was adjusted to take into + ** consideration the fact that "read" returns a SIGNED value whereas + ** it takes an UNSIGNED value as the byte count. + */ + while (size) { + unsigned desired; // Bytes desired to be read this pass. + unsigned actual; // Actual number of bytes read. + + /* + ** Break the read request into chunks no bigger than the low level DOS read + ** can handle. + */ + desired = size; + + Hard_Error_Occured = 0; + readresult = _dos_read(Handle, buffer, desired, &actual); + + /* + ** If a hard error occurred, then assume that it is the case of the CD-ROM or + ** floppy media having been removed. Display the error and retry as directed. + */ + if (Hard_Error_Occured) { + Error(Hard_Error_Occured, true, Filename); + continue; // Not technically needed, but to be consistent... + } else { + + /* + ** If negative one is returned from the read operation, then this indicates + ** either a bad file number or invalid access. These are fatal conditions, so + ** display the error and then abort. + */ + if (readresult != 0) { + Error(errno, false, Filename); + } else { + + /* + ** No error occurred during the read. Adjust the pointers and size counters and + ** loop again if more data is needed to be read. + */ + buffer = Add_Long_To_Pointer(buffer, actual); + bytesread += actual; + size -= actual; + if (actual != desired) break; // No more data? + } + } + } + + /* + ** Close the file if it was opened by this routine and return + ** the actual number of bytes read into the buffer. + */ + if (opened) Close(); + return(bytesread); +} + + +/*********************************************************************************************** + * RawFileClass::Write -- Writes the specified data to the buffer specified. * + * * + * This routine will write the data specified to the file. * + * * + * INPUT: buffer -- The buffer that holds the data to write. * + * * + * size -- The number of bytes to write to the file. * + * * + * OUTPUT: Returns with the number of bytes written to the file. This routine catches the * + * case of a disk full condition, so this routine will always return with the number * + * matching the size request. * + * * + * WARNINGS: A fatal file condition could cause this routine to never return. * + * * + * HISTORY: * + * 10/18/1994 JLB : Created. * + *=============================================================================================*/ +long RawFileClass::Write(void const *buffer, long size) +{ + long bytesread = 0; + int opened = false; // Was the file manually opened? + int writeresult; + + /* + ** Check to open status of the file. If the file is open, then merely write to + ** it. Otherwise, open the file for writing and then close the file when the + ** output is finished. + */ + if (!Is_Open()) { + if (!Open(WRITE)) { + return(0); + } + opened = true; + } + + /* + ** Write the data to the file in chunks no bigger than what the low level DOS write + ** can handle. + */ + while (size) { + unsigned desired; // Bytes desired to be write this pass. + unsigned actual; // Actual number of bytes written. + + Hard_Error_Occured = 0; +// desired = (unsigned)MIN(size, Transfer_Block_Size()); + desired = size; + writeresult = _dos_write(Handle, buffer, desired, &actual); + + /* + ** If a hard error occurred, then assume it is the case of the media being + ** removed. Print the error message an retry as directed. + */ + if (Hard_Error_Occured) { + Error(Hard_Error_Occured, true, Filename); + continue; // Not technically needed, but to be consistent... + } else { + + /* + ** If negative one is returned by the DOS read, then this indicates a bad file + ** handle or invalid access. Either condition is fatal -- display error condition + ** and abort. + */ + if (writeresult != 0) { + Error(errno, false, Filename); + } else { + + /* + ** A successful write occurred. Update pointers and byte counter as appropriate. + */ + buffer = Add_Long_To_Pointer((void *)buffer, actual); + bytesread += actual; + size -= actual; + + /* + ** If the actual bytes written is less than requested, assume this is a case of + ** the disk being full. Consider this a fatal error condition. + */ + if (actual != desired) { + Error(ENOSPC, false, Filename); + } + } + } + } + + /* + ** If this routine had to open the file, then close it before returning. + */ + if (opened) { + Close(); + } + + /* + ** Return with the number of bytes written. This will always be the number of bytes + ** requested, since the case of the disk being full is caught by this routine. + */ + return(bytesread); +} + + +/*********************************************************************************************** + * RawFileClass::Seek -- Reposition the file pointer as indicated. * + * * + * Use this routine to move the filepointer to the position indicated. It can move either * + * relative to current position or absolute from the beginning or ending of the file. This * + * routine will only return if it successfully performed the seek. * + * * + * INPUT: pos -- The position to seek to. This is interpreted as relative to the position * + * indicated by the "dir" parameter. * + * * + * dir -- The relative position to relate the seek to. This can be either SEEK_SET * + * for the beginning of the file, SEEK_CUR for the current position, or * + * SEEK_END for the end of the file. * + * * + * OUTPUT: This routine returns the position that the seek ended up at. * + * * + * WARNINGS: If there was a file error, then this routine might never return. * + * * + * HISTORY: * + * 10/18/1994 JLB : Created. * + *=============================================================================================*/ +long RawFileClass::Seek(long pos, int dir) +{ + /* + ** If the file isn't opened, then this is a fatal error condition. + */ + if (!Is_Open()) { + Error(EBADF, false, Filename); + } + + /* + ** Keep trying to seek until a non-retry condition occurs. + */ + for (;;) { + + /* + ** Perform the low level seek on the file. + */ + Hard_Error_Occured = 0; + pos = lseek(Handle, pos, dir); + + /* + ** If a hard error occurred, then assume that it is the case of removed media. Display + ** error message and retry. + */ + if (Hard_Error_Occured) { + Error(Hard_Error_Occured, true, Filename); + continue; + } else { + + /* + ** A negative one indicates a fatal error with the seek operation. Display error + ** condition and then abort. + */ + if (pos == -1) { + Error(errno, false, Filename); + } + } + break; + } + + /* + ** Return with the new position of the file. This will range between zero and the number of + ** bytes the file contains. + */ + return(pos); +} + + +/*********************************************************************************************** + * RawFileClass::Size -- Determines size of file (in bytes). * + * * + * Use this routine to determine the size of the file. The file must exist or this is an * + * error condition. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of bytes in the file. * + * * + * WARNINGS: This routine handles error conditions and will not return unless the file * + * exists and can successfully be queried for file length. * + * * + * HISTORY: * + * 10/18/1994 JLB : Created. * + *=============================================================================================*/ +long RawFileClass::Size(void) +{ + long size = 0; + + /* + ** If the file is open, then proceed normally. + */ + if (Is_Open()) { + + /* + ** Repetitively try to determine the file size until a fatal error condition or success + ** is achieved. + */ + for (;;) { + Hard_Error_Occured = 0; + size = filelength(Handle); + + /* + ** If a hard error occurred, then assume it is the case of removed media. Display an + ** error condition and allow retry. + */ + if (Hard_Error_Occured) { + Error(Hard_Error_Occured, true, Filename); + continue; + } else { + if (size == -1) { + Error(errno, false, Filename); + } + } + break; + } + } else { + + /* + ** If the file wasn't open, then open the file and call this routine again. Count on + ** the fact that the open function must succeed. + */ + if (Open()) { + size = Size(); + + /* + ** Since we needed to open the file we must remember to close the file when the + ** size has been determined. + */ + Close(); + } + } + return(size); +} + + +/*********************************************************************************************** + * RawFileClass::Create -- Creates an empty file. * + * * + * This routine will create an empty file from the file object. The file object's filename * + * must already have been assigned before this routine will function. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the file successfully created? This routine will always return true. * + * * + * WARNINGS: A fatal error condition could occur with this routine. Especially if the disk * + * is full or a read-only media was selected. * + * * + * HISTORY: * + * 10/18/1994 JLB : Created. * + *=============================================================================================*/ +int RawFileClass::Create(void) +{ + Close(); + if (Open(WRITE)) { + Close(); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * RawFileClass::Delete -- Deletes the file object from the disk. * + * * + * This routine will delete the file object from the disk. If the file object doesn't * + * exist, then this routine will return as if it had succeeded (since the effect is the * + * same). * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the file deleted? If the file was already missing, the this value will * + * be false. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/18/1994 JLB : Created. * + *=============================================================================================*/ +int RawFileClass::Delete(void) +{ + /* + ** If the file was open, then it must be closed first. + */ + Close(); + + /* + ** If there is no filename associated with this object, then this indicates a fatal error + ** condition. Report this and abort. + */ + if (!Filename) { + Error(ENOENT, false); + } + + /* + ** Repetitively try to delete the file if possible. Either return with success, or + ** abort the program with an error. + */ + for (;;) { + + /* + ** If the file is already missing, then return with this fact. No action is necessary. + ** This can occur as this section loops if the file exists on a floppy and the floppy + ** was removed, the file deleted on another machine, and then the floppy was + ** reinserted. Admittedly, this is a rare case, but is handled here. + */ + if (!Is_Available()) { + return(false); + } + + Hard_Error_Occured = 0; + if (remove(Filename) == -1) { + + /* + ** If a hard error occurred, then assume that the media has been removed. Display + ** error message and retry as directed. + */ + if (Hard_Error_Occured) { + Error(Hard_Error_Occured, true, Filename); + continue; + } + + /* + ** If at this point, DOS says the file doesn't exist, then just exit with this + ** fact. It should have been caught earlier, but in any case, this is a legal + ** condition. + */ + if (errno == ENOENT) break; + + /* + ** The only way it can reach this point is if DOS indicates that access is denied + ** on the file. This occurs when trying to delete a file on a read-only media such + ** as a CD-ROM. Report this as a fatal error and then abort. + */ + Error(errno, false, Filename); + } + break; + } + + /* + ** DOS reports that the file was successfully deleted. Return with this fact. + */ + return(true); +} diff --git a/RAWFILE.H b/RAWFILE.H new file mode 100644 index 0000000..46476e9 --- /dev/null +++ b/RAWFILE.H @@ -0,0 +1,243 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\rawfile.h_v 2.16 16 Oct 1995 16:45:46 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Westwood Library * + * * + * File Name : RAWFILE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : August 8, 1994 * + * * + * Last Update : October 18, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * RawFileClass::File_Name -- Returns with the filename associate with the file object. * + * RawFileClass::RawFileClass -- Default constructor for a file object. * + * RawFileClass::Is_Open -- Checks to see if the file is open or not. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef RAWFILE_H +#define RAWFILE_H + +#include +#include +#include +#include + +#ifdef NEVER + /* + ** This is a duplicate of the error numbers. The error handler for the RawFileClass handles + ** these errors. If the error routine is overridden and additional errors are defined, then + ** use numbers starting with 100. Note that these errors here are listed in numerical order. + ** These errors are defined in the standard header file "ERRNO.H". + */ + EZERO, // Non-error. + EINVFNC, // Invalid function number. + ENOFILE, // File not found. + ENOENT=ENOFILE, // No such file or directory. + ENOPATH, // Path not found. + EMFILE, // Too many open files. + EACCES, // Permission denied. + EBADF, // Bad file number. + ECONTR, // Memory blocks destroyed. + ENOMEM, // Not enough core memory. + EINVMEM, // Invalid memory block address. + EINVENV, // Invalid environment. + EINVFMT, // Invalid format. + EINVACC, // Invalid access code. + EINVDAT, // Invalid data. + EFAULT, // Unknown error. + EINVDRV, // Invalid drive specified. + ENODEV=EINVDRV, // No such device. + ECURDIR, // Attempt to remove CurDir. + ENOTSAM, // Not same device. + ENMFILE, // No more files. + EINVAL, // Invalid argument. + E2BIG, // Argument list too long. + ENOEXEC, // exec format error. + EXDEV, // Cross-device link. + ENFILE, // Too many open files. + ECHILD, // No child process. + ENOTTY, // not used + ETXTBSY, // not used + EFBIG, // not used + ENOSPC, // No space left on device. + ESPIPE, // Illegal seek. + EROFS, // Read-only file system. + EMLINK, // not used + EPIPE, // Broken pipe. + EDOM, // Math argument. + ERANGE, // Result too large. + EEXIST, // File already exists. + EDEADLOCK, // Locking violation. + EPERM, // Operation not permitted. + ESRCH, // not used + EINTR, // Interrupted function call. + EIO, // Input/output error. + ENXIO, // No such device or address. + EAGAIN, // Resource temporarily unavailable. + ENOTBLK, // not used + EBUSY, // Resource busy. + ENOTDIR, // not used + EISDIR, // not used + EUCLEAN, // not used +#endif + +/* +** This is the definition of the raw file class. It is derived from the abstract base FileClass +** and handles the interface to the low level DOS routines. This is the first class in the +** chain of derived file classes that actually performs a useful function. With this class, +** I/O is possible. More sophisticated features, such as packed files, CD-ROM support, +** file caching, and XMS/EMS memory support, are handled by derived classes. +** +** Of particular importance is the need to override the error routine if more sophisticated +** error handling is required. This is more than likely if greater functionality is derived +** from this base class. +*/ +class RawFileClass : public FileClass +{ + public: + + /* + ** This is a record of the access rights used to open the file. These rights are + ** used if the file object is duplicated. + */ + int Rights; + + RawFileClass(char const *filename); + RawFileClass(void); + RawFileClass(RawFileClass const & f); + RawFileClass & operator = (RawFileClass const & f); + virtual ~RawFileClass(void); + + virtual char const * File_Name(void) const; + virtual char const * Set_Name(char const *filename); + virtual int Create(void); + virtual int Delete(void); + virtual int Is_Available(int forced=false); + virtual int Is_Open(void) const; + virtual int Open(char const *filename, int rights=READ); + virtual int Open(int rights=READ); + virtual long Read(void *buffer, long size); + virtual long Seek(long pos, int dir=SEEK_CUR); + virtual long Size(void); + virtual long Write(void const *buffer, long size); + virtual void Close(void); + virtual void Error(int error, int canretry = false, char const * filename=NULL); + + protected: + + /* + ** This function returns the largest size a low level DOS read or write may + ** perform. Larger file transfers are performed in chunks of this size or less. + */ + long Transfer_Block_Size(void) {return (long)((unsigned)UINT_MAX)-16L;}; + + private: + + /* + ** This is the low level DOS handle. A -1 indicates an empty condition. + */ + int Handle; + + /* + ** This points to the filename as a NULL terminated string. It may point to either a + ** constant or an allocated string as indicated by the "Allocated" flag. + */ + char const * Filename; + + /* + ** Filenames that were assigned as part of the construction process + ** are not allocated. It is assumed that the filename string is a + ** constant in that case and thus making duplication unnecessary. + ** This value will be non-zero if the filename has be allocated + ** (using strdup()). + */ + unsigned Allocated:1; +}; + + +/*********************************************************************************************** + * RawFileClass::File_Name -- Returns with the filename associate with the file object. * + * * + * Use this routine to determine what filename is associated with this file object. If no * + * filename has yet been assigned, then this routing will return NULL. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the file name associated with this file object or NULL * + * if one doesn't exist. * + * * + * WARNINGS: none * + * * + * HISTORY: * +;* 10/18/1994 JLB : Created. * + *=============================================================================================*/ +inline char const * RawFileClass::File_Name(void) const +{ + return(Filename); +} + +/*********************************************************************************************** + * RawFileClass::RawFileClass -- Default constructor for a file object. * + * * + * This constructs a null file object. A null file object has no file handle or filename * + * associated with it. In order to use a file object created in this fashion it must be * + * assigned a name and then opened. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * +;* 10/18/1994 JLB : Created. * + *=============================================================================================*/ +inline RawFileClass::RawFileClass(void) : Handle(-1), Filename(0), Allocated(false) +{ +} + +/*********************************************************************************************** + * RawFileClass::Is_Open -- Checks to see if the file is open or not. * + * * + * Use this routine to determine if the file is open. It returns true if it is. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Is the file open? * + * * + * * + * WARNINGS: none * + * * + * HISTORY: * +;* 10/18/1994 JLB : Created. * + *=============================================================================================*/ +inline int RawFileClass::Is_Open(void) const +{ + return (Handle >= 0); +} + +#endif diff --git a/REAL.H b/REAL.H new file mode 100644 index 0000000..23411bd --- /dev/null +++ b/REAL.H @@ -0,0 +1,934 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\function.h_v 2.21 16 Oct 1995 16:46:44 JOE_BOSTIC $*/ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : FUNCTION.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 27, 1994 * + * * + * Last Update : May 27, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef FUNCTION_H +#define FUNCTION_H + +#ifdef NEVER +Map (screen) class heirarchy. + + MapeditClass (most derived class) -- scenario editor + ³ + MouseClass -- handles mouse animation and display control + ³ + ScrollClass -- map scroll handler + ³ + HelpClass -- pop-up help text handler + ³ + TabClass -- file folder tab screen mode control dispatcher + ³ + SidebarClass -- displays and controls construction list sidebar + ³ + PowerClass -- display power production/consumption bargraph + ³ + RadarClass -- displays and controls radar map + ³ + DisplayClass -- general tactical map display handler + ³ + MapClass -- general tactical map data handler + ³ + GScreenClass (pure virtual base class) -- generic screen control + + AbstractClass + ³ + ³ + ³ + ³ + ObjectClass + ³ + ÚÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄ¿ + AnimClass ³ TemplateClass ³ ÃÄ FuseClass ³ TerrainClass + ³ ³ ÃÄ FlyClass ³ + ³ ³ BulletClass ³ + OverlayClass MissionClass SmudgeClass + ³ + RadioClass + ³ + ÃÄ CrewClass + ÃÄ FlasherClass + ÃÄ StageClass + ÃÄ CargoClass + TechnoClass + ³ + ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ + FootClass BuildingClass + ³ + ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ + DriveClass InfantryClass ÃÄ FlyClass + ³ AircraftClass + TurretClass + ³ + TarComClass + ³ + UnitClass + + + AbstractTypeClass + ³ + ObjectTypeClass + ³ + ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ + ³ ³ ³ ³ + TechnoTypeClass ³ ³ ³ + ³ BulletTypeClass ³ ³ + ³ TemplateTypeClass ³ + ÚÄÄÄÄÄÄÄÄÁÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ TerrainTypeClass + ³ ³ ³ ³ +UnitTypeClass ³ BuildingTypeClass ³ + ³ InfantryTypeClass + AircraftTypeClass +#endif + +/* +** The "bool" integral type was defined by the C++ comittee in +** November of '94. Until the compiler supports this, use the following +** definition. +*/ +enum {false=0,true=1}; +typedef int bool; + + + +#define _WIN32 +#define WIN32 //_LEAN_AND_MEAN +#include + + +/********************************************************************** +** If the following define is enabled, then the memory checking code +** will be disabled. +*/ +#define NOMEMCHECK + +#include "watcom.h" +#define FILE_H +#define WWMEM_H +#include "compat.h" +#include +#include "jshell.h" + +#ifdef +#undef +#endif +#ifdef _pascal +#undef _pascal +#endif +#ifdef pascal +#undef pascal +#endif + +#define pascal +#define _pascal +#define + + + + +// Should be part of WWLIB.H. This is used in JSHELL.CPP. +typedef struct { + unsigned char SourceColor; + unsigned char DestColor; + unsigned char Fading; + unsigned char reserved; +} TLucentType; + + +// Don't complain if these headers aren't referenced. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* +** VQ player specific includes. +*/ +#include +#include + +extern bool GameActive; +extern long LParam; + +#include "vector.h" +#include "heap.h" +#include "ccfile.h" +#include "monoc.h" +#include "conquer.h" +//#include "debug.h" +#include "special.h" +#include "defines.h" + + +/* +** Greenleaf specific includes. +*/ +#include +#include + + +extern long Frame; +inline CELL Coord_XCell(COORDINATE coord) {return (CELL)(*(((unsigned char*)&coord)+1));} +inline CELL Coord_YCell(COORDINATE coord) {return (CELL)(*(((unsigned char*)&coord)+3));} +//inline CELL Coord_Cell(COORD coord){return (CELL)(((*(((unsigned short *)&coord)+1) & 0xFF00) >> 2) | *(((unsigned char *)&coord)+1));} +CELL Coord_Cell(COORDINATE coord); +#pragma aux Coord_Cell parm [eax] \ + modify [ebx] \ + value [ax] = \ + "mov ebx,eax" \ + "shr eax,010h" \ + "xor al,al" \ + "shr eax,2" \ + "or al,bh" + + +#include "facing.h" +#include "ftimer.h" +#include "theme.h" +#include "link.h" +#include "gadget.h" +#include "control.h" +#include "toggle.h" +#include "checkbox.h" +#include "shapebtn.h" +#include "textbtn.h" +#include "slider.h" +#include "list.h" +#include "cheklist.h" +#include "colrlist.h" +#include "edit.h" +#include "gauge.h" +#include "msgbox.h" +#include "dial8.h" +#include "txtlabel.h" +#include "super.h" +#include "house.h" +#include "gscreen.h" +#include "map.h" +#include "display.h" +#include "radar.h" +#include "power.h" +#include "sidebar.h" +#include "tab.h" +#include "help.h" +#include "mouse.h" +//#include "mapedit.h" +#include "help.h" +#include "target.h" +#include "theme.h" +#include "team.h" // Team objects. +#include "teamtype.h" // Team type objects. +#include "trigger.h" // Trigger event objects. +#include "mapedit.h" // ??? +#include "abstract.h" +#include "object.h" +#include "mission.h" +#include "door.h" +#include "bullet.h" // Bullet objects. +#include "terrain.h" // Terrain objects. +#include "anim.h" // Animation objects. +#include "template.h" // Icon template objects. +#include "overlay.h" // Overlay objects. +#include "smudge.h" // Stains on the terrain objects. +#include "aircraft.h" // Aircraft objects. +#include "unit.h" // Ground unit objects. +#include "infantry.h" // Infantry objects. +#include "credits.h" // Credit counter class. +#include "score.h" // Scoring system class. +#include "factory.h" // Production manager class. +#include "intro.h" +#include "ending.h" +#include "logic.h" +#include "queue.h" +#include "event.h" +#include "base.h" // defines the AI's pre-built base +#include "ipxmgr.h" +#include "combuf.h" +#include "connect.h" +#include "connmgr.h" +#include "noseqcon.h" +#include "msglist.h" +#include "nullconn.h" +#include "nullmgr.h" +#include "phone.h" +#include "loaddlg.h" +#include "ipxaddr.h" + +/**************************************************************************** +** This is a "node", used for the lists of available games & players. The +** 'Game' structure is used for games; the 'Player' structure for players. +*/ +typedef struct NodeNameTag { + char Name[MPLAYER_NAME_MAX]; + IPXAddressClass Address; + union { + struct { + int Version; + unsigned char IsOpen; + unsigned long LastTime; + } Game; + struct { + HousesType House; + unsigned char Color; + } Player; + }; +} NodeNameType; + + +#include "externs.h" + + +extern int Get_CD_Drive(void); +extern void Fatal(char const *message, ...); + + +/* +** ANIM.CPP +*/ +void Shorten_Attached_Anims(ObjectClass * obj); + +/* +** AUDIO.CPP +*/ +int Sound_Effect(VocType voc, VolType volume, int variation=1, signed short panvalue=0); +void Speak(VoxType voice); +void Speak_AI(void); +void Stop_Speaking(void); +void Sound_Effect(VocType voc, COORDINATE coord=NULL, int variation=1); +bool Is_Speaking(void); + +/* +** COMBAT.CPP +*/ +int Modify_Damage(int damage, WarheadType warhead, ArmorType armor, int distance); +void Explosion_Damage(COORDINATE coord, unsigned strength, TechnoClass * source, WarheadType warhead); + +/* +** CONQUER.CPP +*/ +void Center_About_Objects(void); +bool Force_CD_Available(int cd); +void Handle_View(int view, int action=0); +void Handle_Team(int team, int action=0); +TechnoTypeClass const * Fetch_Techno_Type(RTTIType type, int id); +char const * Fading_Table_Name(char const * base, TheaterType theater); +void Unselect_All(void); +void Play_Movie(char const * name, ThemeType theme=THEME_NONE, bool clrscrn=true); +bool Main_Loop(); +TheaterType Theater_From_Name(char const *name); +//DirType Rotation_Calc(DirType current, DirType desired, int rate); +void Main_Game(int argc, char *argv[]); +long VQ_Call_Back(unsigned char * buffer=NULL, long frame=0); +void Call_Back(void); +char const *Language_Name(char const *basename); +SourceType Source_From_Name(char const *name); +char const *Name_From_Source(SourceType source); +FacingType KN_To_Facing(int input); +void const *Get_Radar_Icon(void const *shapefile, int shapenum, int frames, int zoomfactor); +void CC_Draw_Shape(void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata=0, void const * ghostdata=0); +void Go_Editor(bool flag); +long MixFileHandler(VQAHandle *vqa, long action, void *buffer, long nbytes); + +char *CC_Get_Shape_Filename(void const *shapeptr ); +void CC_Add_Shape_To_Global(void const *shapeptr, char *filename, char code ); + +void Bubba_Print(char *format,...); + +void Heap_Dump_Check( char *string ); +void Dump_Heap_Pointers( void ); +unsigned long Disk_Space_Available(void); + +void Validate_Error(char *name); +void const * Hires_Retrieve(char *name); +int Get_Resolution_Factor(void); + + +/* +** INTERPAL.CPP +*/ +#define SIZE_OF_PALETTE 256 +extern "C" unsigned char *InterpolationPalette; +extern BOOL InterpolationPaletteChanged; +extern void Interpolate_2X_Scale( GraphicBufferClass *source, GraphicBufferClass *dest ,char const *palette_file_name); +void Read_Interpolation_Palette (char const *palette_file_name); +void Write_Interpolation_Palette (char const *palette_file_name); +void Increase_Palette_Luminance(unsigned char *InterpolationPalette , int RedPercentage ,int GreenPercentage ,int BluePercentage ,int cap); +extern "C"{ + extern unsigned char PaletteInterpolationTable[SIZE_OF_PALETTE][SIZE_OF_PALETTE]; + extern unsigned char *InterpolationPalette; + void __cdecl Asm_Create_Palette_Interpolation_Table(void); +} + + +/* +** COORD.CPP +*/ +void Move_Point(short &x, short &y, register DirType dir, unsigned short distance); +COORDINATE Adjacent_Cell(COORDINATE coord, FacingType dir); +COORDINATE Coord_Move(COORDINATE start, DirType facing, unsigned short distance); +COORDINATE Coord_Scatter(COORDINATE coord, unsigned distance, bool lock=false); +DirType Direction(CELL cell1, CELL cell2); +DirType Direction(COORDINATE coord1, COORDINATE coord2); +DirType Direction256(COORDINATE coord1, COORDINATE coord2); +DirType Direction8(COORDINATE coord1, COORDINATE coord2); +int Distance(CELL coord1, CELL coord2); +int Distance(COORDINATE coord1, COORDINATE coord2); +short const * Coord_Spillage_List(COORDINATE coord, int maxsize); +//void Move_Point(unsigned short &x, unsigned short &y, DirType dir, unsigned short distance); + +/* +** COORDA.CPP +*/ +//extern "C" { +//unsigned Cardinal_To_Fixed(unsigned base, unsigned cardinal); +//unsigned Fixed_To_Cardinal(unsigned base, unsigned fixed); +//} + +/* +** DEBUG.CPP +*/ +void Log_Event(char const *text, ...); +void Debug_Key(unsigned input); +void Self_Regulate(void); + +/* +** DIALOG.CPP +*/ +int Format_Window_String(char * string, int maxlinelen, int & width, int & height); +extern void Dialog_Box(int x, int y, int w, int h); +void Conquer_Clip_Text_Print(char const *, unsigned x, unsigned y, unsigned fore, unsigned back=(unsigned)TBLACK, TextPrintType flag=TPF_8POINT|TPF_DROPSHADOW, unsigned width=-1, int const * tabs=0); +void Draw_Box(int x, int y, int w, int h, BoxStyleEnum up, bool filled); +int __cdecl Dialog_Message(char *errormsg, ...); +void Window_Box(WindowNumberType window, BoxStyleEnum style); +void Fancy_Text_Print(char const *text, unsigned x, unsigned y, unsigned fore, unsigned back, TextPrintType flag, ...); +void Fancy_Text_Print(int text, unsigned x, unsigned y, unsigned fore, unsigned back, TextPrintType flag, ...); +void Simple_Text_Print(char const *text, unsigned x, unsigned y, unsigned fore, unsigned back, TextPrintType flag); + +/* +** DISPLAY.CPP +*/ + +/* +** ENDING.CPP +*/ +void GDI_Ending(void); +void Nod_Ending(void); + +/* +** EXPAND.CPP +*/ +bool Expansion_Present(void); +bool Expansion_Dialog(void); + +/* +** FINDPATH.CPP +*/ +//PathType * Find_Path(CELL source, CELL dest, FacingType *final_moves, int maxlen, int (*callback)(CELL, FacingType), int threshhold); +int Optimize_Moves(PathType *path, int (*callback)(CELL, FacingType), int threshhold); + +/* +** GOPTIONS.CPP +*/ +void Draw_Caption(int text, int x, int y, int w); + +/* +** INI.CPP +*/ +void Set_Scenario_Name(char *buf, int scenario, ScenarioPlayerType player, ScenarioDirType dir = SCEN_DIR_NONE, ScenarioVarType var = SCEN_VAR_NONE); +void Write_Scenario_Ini(char *root); +bool Read_Scenario_Ini(char *root, bool fresh=true); +int Scan_Place_Object(ObjectClass *obj, CELL cell); + +/* +** INIT.CPP +*/ +void Uninit_Game(void); +long Obfuscate(char const * string); +void Anim_Init(void); +bool Init_Game(int argc, char *argv[]); +bool Select_Game(bool fade = false); +bool Parse_Command_Line(int argc, char *argv[]); +void Parse_INI_File(void); +int Version_Number(void); +void Save_Recording_Values(void); +void Load_Recording_Values(void); + +/* +** JSHELL.CPP +*/ +void * Small_Icon(void const * iconptr, int iconnum); +void Set_Window(int window, int x, int y, int w, int h); +void * Load_Alloc_Data(FileClass &file); +long Load_Uncompress(FileClass &file, BuffType &uncomp_buff, BuffType &dest_buff, void *reserved_data); +long Translucent_Table_Size(int count); +void *Build_Translucent_Table(void const *palette, TLucentType const *control, int count, void *buffer); +void *Conquer_Build_Translucent_Table(void const *palette, TLucentType const *control, int count, void *buffer); + +/* +** KEYFBUFF.ASM +*/ +#ifdef __cplusplus +extern "C" { +#endif +long __cdecl Buffer_Frame_To_Page(int x, int y, int w, int h, void *Buffer, GraphicViewPortClass &view, int flags, ...); +#ifdef __cplusplus +} +#endif + +/* +** KEYFRAME.CPP +*/ +int Get_Last_Frame_Length(void); +unsigned long Build_Frame(void const *dataptr, unsigned short framenumber, void *buffptr); +unsigned short Get_Build_Frame_Count(void const *dataptr); +unsigned short Get_Build_Frame_X(void const *dataptr); +unsigned short Get_Build_Frame_Y(void const *dataptr); +unsigned short Get_Build_Frame_Width(void const *dataptr); +unsigned short Get_Build_Frame_Height(void const *dataptr); +bool Get_Build_Frame_Palette(void const *dataptr, void *palette); + +/* +** MAP.CPP +*/ +int Terrain_Cost(CELL cell, FacingType facing); +int Coord_Spillage_Number(COORDINATE coord, int maxsize); + +/* +** MENUS.CPP +*/ +void Setup_Menu(int menu, char const *text[], unsigned long field, int index, int skip); +int Check_Menu(int menu, char const *text[], char *selection, long field, int index); +int Do_Menu(char const **strings, bool blue); +extern int UnknownKey; +int Main_Menu(unsigned long timeout); + +/* +** MPLAYER.CPP +*/ +GameType Select_MPlayer_Game (void); +void Read_MultiPlayer_Settings (void); +void Write_MultiPlayer_Settings (void); +void Read_Scenario_Descriptions (void); +void Free_Scenario_Descriptions(void); +void Computer_Message(void); +int Surrender_Dialog(void); + +/* +** NETDLG.CPP +*/ +bool Init_Network (void); +void Shutdown_Network (void); +bool Remote_Connect (void); +void Destroy_Connection(int id, int error); +bool Process_Global_Packet(GlobalPacketType *packet, IPXAddressClass *address); +unsigned long Compute_Name_CRC(char *name); +void Net_Reconnect_Dialog(int reconn, int fresh, int oldest_index, unsigned long timeval); + +/* +** NULLDLG.CPP +*/ +int Init_Null_Modem( SerialSettingsType *settings ); +void Shutdown_Modem( void ); +void Modem_Signoff( void ); +int Test_Null_Modem( void ); +int Reconnect_Modem( void ); +void Destroy_Null_Connection(int id, int error); +GameType Select_Serial_Dialog( void ); +int Com_Scenario_Dialog(void); +int Com_Show_Scenario_Dialog(void); + +void Smart_Printf( char *format, ... ); +void Hex_Dump_Data( char *buffer, int length ); +void itoh( int i, char *s); + +/* +** PROFILE.CPP +*/ +int WWGetPrivateProfileInt(char const *section, char const *entry, int def, char *profile); +bool WWWritePrivateProfileInt(char const *section, char const *entry, int value, char *profile); +bool WWWritePrivateProfileString(char const *section, char const *entry, char const *string, char *profile); +char * WWGetPrivateProfileString(char const *section, char const *entry, char const *def, char *retbuffer, int retlen, char *profile); +unsigned WWGetPrivateProfileHex (char const *section, char const *entry, char *profile); + +/* +** QUEUE.CPP +*/ +bool Queue_Target(TARGET whom, TARGET target); +bool Queue_Destination(TARGET whom, TARGET target); +bool Queue_Mission(TARGET whom, MissionType mission); +bool Queue_Mission(TARGET whom, MissionType mission, TARGET target, TARGET destination); +bool Queue_Options(void); +bool Queue_Exit(void); +void Queue_AI(void); +void Add_CRC(unsigned long *crc, unsigned long val); + +/* +** RAND.CPP +*/ +int Sim_IRandom(int minval, int maxval); +int Sim_Random(void); + +/* +** REINF.CPP +*/ +bool Do_Reinforcements(TeamTypeClass *team); +bool Create_Special_Reinforcement(HouseClass * house, TechnoTypeClass const * type, TechnoTypeClass const * another, TeamMissionType mission = TMISSION_NONE, int argument =0); +int Create_Air_Reinforcement(HouseClass *house, AircraftType air, int number, MissionType mission, TARGET tarcom, TARGET navcom); + +/* +** SAVELOAD.CPP +*/ +bool Load_Misc_Values(FileClass &file); +bool Save_Misc_Values(FileClass &file); +bool Get_Savefile_Info(int id, char *buf, unsigned *scenp, HousesType *housep); +bool Load_Game(int id); +bool Read_Object (void *ptr, int base_size, int class_size, FileClass & file, void * vtable); +bool Save_Game(int id,char *descr); +bool Write_Object (void *ptr, int class_size, FileClass & file); +TARGET TechnoType_To_Target(TechnoTypeClass const * ptr); +TechnoTypeClass const * Target_To_TechnoType(TARGET target); +void * Get_VTable(void *ptr, int base_size); +void Code_All_Pointers(void); +void Decode_All_Pointers(void); +void Dump(void); +void Set_VTable(void *ptr, int base_size, void *vtable); + +/* +** SCENARIO.CPP +*/ +bool End_Game(void); +bool Read_Scenario(char *root); +bool Start_Scenario(char *root, bool briefing=true); +HousesType Select_House(void); +void Clear_Scenario(void); +void Do_Briefing(char const * text); +void Do_Lose(void); +void Do_Win(void); +void Do_Restart(void); +void Fill_In_Data(void); +bool Restate_Mission(char const * name, int button1, int button2); + +/* +** SCORE.CPP +*/ +void Map_Selection(void); +void Bit_It_In_Scale(int x, int y, int w, int h, GraphicBufferClass *src, GraphicBufferClass *dest, GraphicBufferClass *seen , int delay=0, int dagger=0); +void Bit_It_In(int x, int y, int w, int h, GraphicBufferClass *src, GraphicBufferClass *dest, int delay=0, int dagger=0); +void Call_Back_Delay(int time); +int Alloc_Object(ScoreAnimClass *obj); +extern GraphicBufferClass *PseudoSeenBuff; + +void Window_Dialog_Box(HANDLE hinst, LPCTSTR lpszTemplate, HWND hwndOwner, DLGPROC dlgprc); + +/* +** SPECIAL.CPP +*/ +void Special_Dialog(void); + +/* +** SUPPORT.ASM +*/ +#ifdef __cplusplus +extern "C" { +#endif +void __cdecl Remove_From_List(void **list, int *index, void * ptr); +void * __cdecl Conquer_Build_Fading_Table(void const *palette, void *dest, int color, int frac); +void __cdecl Fat_Put_Pixel(int x, int y, int color, int size, GraphicViewPortClass &); +void __cdecl strtrim(char *buffer); +long __cdecl Get_EAX( void ); +#ifdef __cplusplus +} +#endif + +/* +** TARCOM.CPP +*/ + +/* +** TARGET.CPP +*/ +COORDINATE As_Movement_Coord(TARGET target); +AircraftClass * As_Aircraft(TARGET target); +AnimClass * As_Animation(TARGET target); +BuildingClass * As_Building(TARGET target); +BulletClass * As_Bullet(TARGET target); +CELL As_Cell(TARGET target); +COORDINATE As_Coord(TARGET target); +InfantryClass * As_Infantry(TARGET target); +TeamClass * As_Team(TARGET target); +TeamTypeClass * As_TeamType(TARGET target); +TechnoClass * As_Techno(TARGET target); +//TerrainClass * As_Terrain(TARGET target); +TriggerClass * As_Trigger(TARGET target); +UnitClass * As_Unit(TARGET target); +inline bool Target_Legal(TARGET target) {return(target != TARGET_NONE);}; +ObjectClass * As_Object(TARGET target); + +/* +** ULOGIC.CPP +*/ +int Terrain_Cost(CELL cell, FacingType facing); + +/* +** Inline miscellaneous functions. +*/ +#define XYP_COORD(x,y) (((x)*ICON_LEPTON_W)/CELL_PIXEL_W + ((((y)*ICON_LEPTON_H)/CELL_PIXEL_H)<<16)) +inline FacingType Dir_Facing(DirType facing) {return (FacingType)(((unsigned char)(facing+0x10)&0xFF)>>5);} +inline DirType Facing_Dir(FacingType facing) {return (DirType)((int)facing << 5);} +inline int Cell_To_Lepton(int cell) {return cell<<8;} +inline int Lepton_To_Cell(int lepton) {return ((unsigned)(lepton + 0x0080))>>8;} +inline CELL XY_Cell(int x, int y) {return ((CELL)(((y)<<6)|(x)));} +inline COORDINATE XY_Coord(int x, int y) {return ((COORDINATE)MAKE_LONG(y, x));} +inline int Coord_X(COORDINATE coord) {return (short)(LOW_WORD(coord));} +inline int Coord_Y(COORDINATE coord) {return (short)(HIGH_WORD(coord));} +inline int Cell_X(CELL cell) {return (int)(((unsigned)cell) & 0x3F);} +inline int Cell_Y(CELL cell) {return (int)(((unsigned)cell) >> 6);} +inline int Dir_Diff(DirType dir1, DirType dir2) {return (int)(*((signed char*)&dir2) - *((signed char*)&dir1));} +inline CELL Coord_XLepton(COORDINATE coord) {return (CELL)(*((unsigned char*)&coord));} +inline CELL Coord_YLepton(COORDINATE coord) {return (CELL)(*(((unsigned char*)&coord)+2));} +//inline COORD CellXY_Coord(unsigned x, unsigned y) {return (COORD)(MAKE_LONG(y<<8, x<<8));} +inline COORDINATE Coord_Add(COORDINATE coord1, COORDINATE coord2) {return (COORDINATE)MAKE_LONG((*((short*)(&coord1)+1) + *((short*)(&coord2)+1)), (*((short*)(&coord1)) + *((short*)(&coord2))));} +inline COORDINATE Coord_Sub(COORDINATE coord1, COORDINATE coord2) {return (COORDINATE)MAKE_LONG((*((short*)(&coord1)+1) - *((short*)(&coord2)+1)), (*((short*)(&coord1)) - *((short*)(&coord2))));} +inline COORDINATE Coord_Snap(COORDINATE coord) {return (COORDINATE)MAKE_LONG((((*(((unsigned short *)&coord)+1))&0xFF00)|0x80), (((*((unsigned short *)&coord))&0xFF00)|0x80));} +inline COORDINATE Coord_Mid(COORDINATE coord1, COORDINATE coord2) {return (COORDINATE)MAKE_LONG((*((unsigned short *)(&coord1)+1) + *((unsigned short *)(&coord2)+1))>>1, (*((unsigned short *)(&coord1)) + *((unsigned short *)(&coord2)))>>1);} +inline COORDINATE Cell_Coord(CELL cell) {return (COORDINATE) MAKE_LONG( (((cell & 0x0FC0)<<2)|0x80), ((((cell & 0x003F)<<1)+1)<<7) );} +inline COORDINATE XYPixel_Coord(int x, int y) {return ((COORDINATE)MAKE_LONG((int)(((long)y*(long)ICON_LEPTON_H)/(long)ICON_PIXEL_H)/*+LEPTON_OFFSET_Y*/, (int)(((long)x*(long)ICON_LEPTON_W)/(long)ICON_PIXEL_W)/*+LEPTON_OFFSET_X*/));} +//inline int Facing_To_16(int facing) {return Facing16[facing];} +inline int Facing_To_32(DirType facing) {return Facing32[facing];} +inline DirType Direction256(COORDINATE coord1, COORDINATE coord2) {return ((DirType)Desired_Facing256(Coord_X(coord1), Coord_Y(coord1), Coord_X(coord2), Coord_Y(coord2)));} +inline DirType Direction(COORDINATE coord1, COORDINATE coord2) {return ((DirType)Desired_Facing256(Coord_X(coord1), Coord_Y(coord1), Coord_X(coord2), Coord_Y(coord2)));} +inline DirType Direction8(COORDINATE coord1, COORDINATE coord2) {return ((DirType)Desired_Facing8(Coord_X(coord1), Coord_Y(coord1), Coord_X(coord2), Coord_Y(coord2)));} +//inline int Direction16(COORDINATE coord1, COORD coord2) {return (Desired_Facing16(Coord_X(coord1), Coord_Y(coord1), Coord_X(coord2), Coord_Y(coord2)));} +inline DirType Direction(CELL cell1, CELL cell2) {return (DirType)(Desired_Facing8(Cell_X(cell1), Cell_Y(cell1), Cell_X(cell2), Cell_Y(cell2)));} +inline COORDINATE Adjacent_Cell(COORDINATE coord, FacingType dir) {return (Coord_Snap(Coord_Add(AdjacentCoord[dir & 0x07], coord)));} +inline COORDINATE Adjacent_Cell(COORDINATE coord, DirType dir) {return Adjacent_Cell(coord, Dir_Facing(dir));} +inline CELL Adjacent_Cell(CELL cell, FacingType dir) {return (CELL)(cell + AdjacentCell[dir]);} +inline CELL Adjacent_Cell(CELL cell, DirType dir) {return (CELL)(cell + AdjacentCell[Dir_Facing(dir)]);} +inline int Lepton_To_Pixel(int lepton) {return ((lepton * ICON_PIXEL_W) + (ICON_LEPTON_W / 2)) / ICON_LEPTON_W;} +inline int Pixel_To_Lepton(int pixel) {return ((pixel * ICON_LEPTON_W) + (ICON_PIXEL_W / 2)) / ICON_PIXEL_W;} +//inline FacingType Facing_To_8(DirType facing) {return (FacingType)(((unsigned char)(facing|0x10))>>5);} +inline COORDINATE XYP_Coord(int x,int y) {return XY_Coord(Pixel_To_Lepton(x), Pixel_To_Lepton(y));}; +inline char const * Text_String(int string) {return(Extract_String(SystemStrings, string));}; + + +template inline T Random_Pick(T a, T b) +{ + return (T)IRandom((int)a, (int)b); +}; + +template inline T Sim_Random_Pick(T a, T b) +{ + return (T)Sim_IRandom((int)a, (int)b); +}; + + +#ifdef CHEAT_KEYS +#define Check_Ptr(ptr,file,line) \ +{ \ + if (!ptr) { \ + Mono_Clear_Screen(); \ + Mono_Printf("NULL Pointer, Module:%s, line:%d!\n",file,line); \ + Prog_End(); \ + exit(EXIT_SUCCESS); \ + } \ +} +#else +#define Check_Ptr(ptr,file,line) +#endif + +/* +** These routines are for coding & decoding multiplayer ID's +*/ +inline PlayerColorType MPlayerID_To_ColorIndex(unsigned short id) {return (PlayerColorType)(id >> 4);} +inline HousesType MPlayerID_To_HousesType(unsigned short id) {return ((HousesType)(id & 0x000f)); } +inline unsigned short Build_MPlayerID(int c_idx, HousesType htype) { return ((c_idx << 4) | htype); } + + + + +// +// True if we are the currently in focus windows app +// +extern bool GameInFocus; + +extern int ScreenWidth; +extern int ScreenHeight; +extern "C" void ModeX_Blit (GraphicBufferClass *source); +extern void Colour_Debug (int call_number); + + +extern unsigned char *InterpolatedPalettes[50]; +extern BOOL PalettesRead; +extern unsigned PaletteCounter; + +extern "C"{ + extern unsigned char PaletteInterpolationTable[SIZE_OF_PALETTE][SIZE_OF_PALETTE]; + extern unsigned char *InterpolationPalette; +} + +extern void Free_Interpolated_Palettes(void); +extern int Load_Interpolated_Palettes(char const *filename, BOOL add=FALSE); + + +#define CELL_BLIT_ONLY 1 +#define CELL_DRAW_ONLY 2 + +/*********************************************************************************************** + * Distance -- Determines the lepton distance between two coordinates. * + * * + * This routine is used to determine the distance between two coordinates. It uses the * + * Dragon Strike method of distance determination and thus it is very fast. * + * * + * INPUT: coord1 -- First coordinate. * + * * + * coord2 -- Second coordinate. * + * * + * OUTPUT: Returns the lepton distance between the two coordinates. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/27/1994 JLB : Created. * + *=============================================================================================*/ +int Distance_Coord(COORDINATE coord1, COORDINATE coord2); +#pragma aux Distance_Coord parm [eax] [ebx] \ + modify [edx ebx] \ + value [eax] = \ + "mov dx,ax" \ + "sub dx,bx" \ + "jg okx" \ + "neg dx" \ + "okx:" \ + "shr eax,16" \ + "shr ebx,16" \ + "sub ax,bx" \ + "jg oky" \ + "neg ax" \ + "oky:" \ + "cmp ax,dx" \ + "jg ok" \ + "xchg ax,dx" \ + "ok:" \ + "shr dx,1" \ + "add ax,dx" + +inline int Distance(COORDINATE coord1, COORDINATE coord2) +{ +#ifdef NEVER + int diff1, diff2; + + diff1 = Coord_Y(coord1) - Coord_Y(coord2); + if (diff1 < 0) diff1 = -diff1; + diff2 = Coord_X(coord1) - Coord_X(coord2); + if (diff2 < 0) diff2 = -diff2; + if (diff1 > diff2) { + return(diff1 + (diff2>>1)); + } + return(diff2 + (diff1>>1)); +#else + return(Distance_Coord(coord1, coord2)); +#endif +} + + + +/*********************************************************************************************** + * Distance -- Determines the cell distance between two cells. * + * * + * Use this routine to determine the distance between the two cells specified. The distance * + * is returned in cells. * + * * + * INPUT: cell1, cell2 -- The two cells to determine the distance between. * + * * + * OUTPUT: Returns with the distance between the two cells in units of cell size. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/23/1994 JLB : Created. * + *=============================================================================================*/ +inline int Distance(CELL coord1, CELL coord2) +{ + int diff1, diff2; + + diff1 = Cell_Y(coord1) - Cell_Y(coord2); + if (diff1 < 0) diff1 = -diff1; + diff2 = Cell_X(coord1) - Cell_X(coord2); + if (diff2 < 0) diff2 = -diff2; + if (diff1 > diff2) { + return(diff1 + (diff2>>1)); + } + return(diff2 + (diff1>>1)); +} + + +/*********************************************************************************************** + * CellClass::Cell_Number -- Returns the cell ID number for this cell object. * + * * + * Call this routine if you wish to determine what the cell number ID is for the currrent * + * cell object. This ID number is the index number into the cell array. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the cell number for this cell object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/19/1995 JLB : Created. * + *=============================================================================================*/ +inline CELL CellClass::Cell_Number(void) const +{ + return(Map.ID(this)); +} + +#ifndef NOMEMCHECK +#define NO_INTERCEPT +#include "memcheck.h" +#endif + + +void WWDOS_Shutdown(void); + +#endif + diff --git a/REGION.H b/REGION.H new file mode 100644 index 0000000..dee57d8 --- /dev/null +++ b/REGION.H @@ -0,0 +1,59 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\region.h_v 2.13 16 Oct 1995 16:45:10 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : REGION.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 03/09/95 * + * * + * Last Update : March 9, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef REGION_H +#define REGION_H + + +class RegionClass { + public: + RegionClass(void) {Threat = 0;}; + ~RegionClass(void) {}; + int operator != (RegionClass const & region) {return memcmp(this, ®ion, sizeof(RegionClass));}; + int operator == (RegionClass const & region) {return !memcmp(this, ®ion, sizeof(RegionClass));}; + int operator > (RegionClass const & region) {return memcmp(this, ®ion, sizeof(RegionClass)) > 0;}; + int operator < (RegionClass const & region) {return memcmp(this, ®ion, sizeof(RegionClass)) < 0;}; + + void Reset_Threat(void) {Threat = 0;}; + void Adjust_Threat(int threat, int neg) {if (neg) Threat -= threat; else Threat+= threat;}; + int Threat_Value(void) const {return Threat;}; + + protected: + long Threat; +}; + +#endif diff --git a/REG_ICON.ICO b/REG_ICON.ICO new file mode 100644 index 0000000000000000000000000000000000000000..32ce0b944d1e3adb69f0ff31569fca8010426cf0 GIT binary patch literal 766 zcmbV~u};G<5QhK6ND0Y6yC5+&%7mCIWkJdSj})ddAY#$26KjS_QKqV0s16{c{P1oXEc38gD4rV+|1FLeIJ8pbN;b%7Pjeo>CHkQ z-aL$L%shLRVU~uj6_p8N-?h!fJfFuK%GyNe=1a?JB!YRKe_K(bD;$>nMpt. +*/ + +/* $Header: F:\projects\c&c\vcs\code\reinf.cpv 2.17 16 Oct 1995 16:48:58 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : REINF.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 24, 1994 * + * * + * Last Update : July 4, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Create_Air_Reinforcement -- Creates air strike reinforcement * + * Create_Special_Reinforcement -- Ad hoc reinforcement handler. * + * Do_Reinforcements -- Create and place a reinforcement team. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * Do_Reinforcements -- Create and place a reinforcement team. * + * * + * This routine is called when a reinforcement team must be created and placed on the map. * + * It will create all members of the team and place them at the location determined from * + * the team composition. The reinforcement team should follow team orders until overriden * + * by AI or player intervention. * + * * + * INPUT: teamtype -- Pointer to the team type to create as a reinforcement. * + * * + * OUTPUT: Was the reinforcement successfully created and placed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + * 05/18/1995 JLB : Returns success or failure condition. * + * 06/19/1995 JLB : Announces reinforcements. * + *=============================================================================================*/ +bool Do_Reinforcements(TeamTypeClass *teamtype) +{ + /* + ** preform some preliminary checks for validity. + */ + if (!teamtype || !teamtype->ClassCount) return(false); + + /* + ** Create the controlling team. All the objects are grouped + ** under this team control. If there are no missions for this team + ** then don't actually create the team -- it won't serve a purpose. + */ + TeamClass * team = NULL; + if (teamtype->MissionCount) { + team = new TeamClass(teamtype, HouseClass::As_Pointer(teamtype->House)); + if (!team) return(false); + team->Force_Active(); + } + + /* + ** Determine if this team contains its own transport. In such a case, the + ** transport is used as a loaner. This is only true, if there is other + ** objects to be transport. Without such cargo objects, then the transport + ** is presumed to be the reinforcement itself and thus should not be a + ** loaner. + */ + bool okvoice = true; // Presume ok to announce reinforcement? + bool airtransport = false; // Transport can fly in? + bool watertransport = false; // Transport needs a beach to land at? + bool onlytransport = true; // Just transport is in reinforcement? + bool hastransport = false; // Group comes with transport? + for (int index=0; index < teamtype->ClassCount; index++) { + if (teamtype->Class[index]->IsTransporter || teamtype->Class[index]->What_Am_I() == RTTI_AIRCRAFTTYPE) { + hastransport = true; + if (teamtype->Class[index]->What_Am_I() == RTTI_AIRCRAFTTYPE) { + airtransport = true; + } else { + watertransport = (((UnitTypeClass const *)teamtype->Class[index])->Type == UNIT_HOVER); + } + } else { + onlytransport = false; + } + } + + /* + ** Now determine how the reinforcement should be delivered. This is largely determined + ** by whether there is a transport with the reinforcements. + */ + SourceType source = SOURCE_NONE; + if (airtransport) { + source = SOURCE_AIR; + } else { + + if (watertransport) { + source = SOURCE_BEACH; + } else { + + /* + ** Special case for the gunboat. It always arrives according to the shipping source. + */ + if (teamtype->Class[0]->What_Am_I() == RTTI_UNITTYPE && ((UnitTypeClass const *)teamtype->Class[0])->Type == UNIT_GUNBOAT) { + source = SOURCE_SHIPPING; + } else { + source = HouseClass::As_Pointer(teamtype->House)->Edge; + } + } + } + + /* + ** If we can't determine where the reinforcement should come from, then delete it + ** and return a failure condition. + */ + if (source == SOURCE_NONE) { + if (team) delete team; + return(false); + } + + /* + ** Now that the official source for the reinforcement has been determined, the + ** objects themselves must be created. + */ + TechnoClass * transport = NULL; + TechnoClass * object = NULL; + for (index = 0; index < teamtype->ClassCount; index++) { + TechnoTypeClass const * tclass = teamtype->Class[index]; + + for (int sub = 0; sub < teamtype->DesiredNum[index]; sub++) { + ScenarioInit++; + FootClass * temp = (FootClass *)tclass->Create_One_Of(HouseClass::As_Pointer(teamtype->House)); + ScenarioInit--; + + if (temp) { + + /* + ** Add the object to the team. This is true even for the transports. The one + ** exception is for the hover lander which never becomes part of the team. + */ + if (team && (temp->What_Am_I() != RTTI_UNIT || *((UnitClass*)temp) != UNIT_HOVER)) { + ScenarioInit++; + team->Add(temp); + ScenarioInit--; + } + + /* + ** Build the list of transporters and passengers. + */ + if (tclass->IsTransporter && (!airtransport || tclass->What_Am_I() == RTTI_AIRCRAFTTYPE)) { + + /* + ** Transports are considered loaners if they are transporting + ** something. They are presumed to only serve as a delivery + ** agent. + */ + if (!onlytransport && temp->What_Am_I() != RTTI_UNIT) { + temp->IsALoaner = true; + } + + /* + ** Link to the list of transports. + */ + temp->Next = transport; + transport = temp; + } else { + + /* + ** A-10s are always considered loaners since the player should + ** never be allowed to control them. + */ + if (temp->What_Am_I() == RTTI_AIRCRAFT && *((AircraftClass *)temp) == AIRCRAFT_A10) { + temp->IsALoaner = true; + } + + /* + ** Link to the list of normal objects. + */ + temp->Next = object; + object = temp; + } + } + } + } + + /* + ** Bail on this reinforcement if no reinforcements could be created. + ** This is probably because the object maximum was reached. + */ + if (!object && !transport) { + if (team) delete team; + return(false); + } + + /* + ** Now it is time to place the objects on the map. If there is a transport, then the + ** transported objects must be placed inside the transport at this time as well. + */ + if (transport) { + if (object) { + + /* + ** For cargo planes that carry reinforcements, don't announce arrival + ** when the transport is created. The announcement will occur when the + ** transport unloads. + */ + if (transport->What_Am_I() == RTTI_AIRCRAFT && *((AircraftClass *)transport) == AIRCRAFT_CARGO) { + okvoice = false; + } + + transport->Attach((FootClass *)object); + } + object = transport; + } + + /* + ** Pick the location where the reinforcements appear and then place + ** them there. + */ + bool placed = false; + CELL cell; + FacingType eface; + switch (source) { + + case SOURCE_SHIPPING: + cell = Map.Calculated_Cell(source, teamtype->House); + object->IsALoaner = true; + if (object->Unlimbo(Cell_Coord(cell), DIR_W)) { + object->Assign_Mission(MISSION_GUARD); + object->Assign_Destination(::As_Target(XY_Cell(Map.MapCellX-1, Cell_Y(Coord_Cell(object->Coord)) ))); + } else { + if (team) delete team; + delete object; + return(false); + } + break; + + case SOURCE_NORTH: + case SOURCE_SOUTH: + case SOURCE_EAST: + case SOURCE_WEST: { + eface = (FacingType)(source << 1); // Facing to enter map. + + if (airtransport) ScenarioInit++; + cell = Map.Calculated_Cell(source, teamtype->House); + if (airtransport) ScenarioInit--; + CELL newcell = cell; + + FootClass * o = (FootClass *)object->Next; + object->Next = 0; + bool ok = true; + while (newcell > 0 && object) { + DirType desiredfacing = Facing_Dir(eface); + if (object->What_Am_I() == RTTI_AIRCRAFT) { + desiredfacing = Random_Pick(DIR_N, DIR_MAX); + } + + if (ok && object->Unlimbo(Cell_Coord(newcell), desiredfacing)) { + + /* + ** If this object is part of a team, then the mission for this + ** object will be guard. The team handler will assign the proper + ** mission that it should follow. + */ + if (object->What_Am_I() == RTTI_AIRCRAFT) { + ok = false; + } else { + if (team) { + object->Assign_Mission(MISSION_GUARD); + } else { + object->Assign_Mission(MISSION_MOVE); + object->Assign_Destination(Adjacent_Cell(newcell, eface)); + } + object->Commence(); + } + + } else { + ok = true; + + /* + ** Could not unlimbo at location specified so find an adjacent location that it can + ** be unlimboed at. If this fails, then abort the whole placement process. + */ + for (FacingType adj = FACING_N; adj < FACING_COUNT; adj++) { + CELL trycell = Adjacent_Cell(newcell, adj); + if (!Map.In_Radar(trycell) && object->Can_Enter_Cell(trycell, adj) == MOVE_OK) { + newcell = trycell; + break; + } + } + if (adj < FACING_COUNT) continue; + newcell = -1; + } + + object = o; + if (object) { + o = (FootClass *)object->Next; + object->Next = 0; + } + } + + /* + ** If there are still objects that could not be placed, then delete them. + */ + if (o) { + while (o) { + FootClass * old = o; + o = (FootClass *)o->Next; + old->Next = 0; + + delete old; + } + + } + } + break; + + /* + ** Bring out the aircraft as separate "groups" of one. + */ + case SOURCE_AIR: { + AircraftClass * thisone = (AircraftClass *)object; + while (thisone) { + AircraftClass * next = (AircraftClass *)thisone->Next; + + /* + ** Find a suitable map entry location. Cargo planes will try to find a cell that + ** exactly lines up with the airfield they will unload at. + */ + CELL newcell; + ScenarioInit++; + newcell = Map.Calculated_Cell(HouseClass::As_Pointer(teamtype->House)->Edge, teamtype->House); + ScenarioInit--; + if (*thisone == AIRCRAFT_CARGO) { + BuildingClass const * building = thisone->Find_Docking_Bay(STRUCT_AIRSTRIP, false); + if (building) { + newcell = XY_Cell(Map.MapCellX+Map.MapCellWidth, Coord_YCell(building->Docking_Coord()+2)); + } + } + thisone->Next = 0; + + ScenarioInit++; + placed = thisone->Unlimbo(Cell_Coord(newcell), DIR_W); + ScenarioInit--; + if (placed) { + if (!team) { + if (thisone->Class->IsFixedWing) { + thisone->Assign_Mission(MISSION_HUNT); + } else { + if (thisone->Class->IsTransporter && thisone->Is_Something_Attached()) { + thisone->Assign_Mission(MISSION_UNLOAD); + } else { + thisone->Assign_Mission(MISSION_MOVE); + } + thisone->Assign_Destination(::As_Target(Map.Calculated_Cell(source, teamtype->House))); + } + thisone->Commence(); + } + } else { + delete thisone; + } + + thisone = next; + } + if (!placed && team) delete team; + + + /* + ** Fixes bug that can happen if the reinforcement cannot be created. + ** This prevent "phantom" teams and team types from being left around. + */ + if (GameToPlay == GAME_NORMAL && !placed) return(false); + + } + break; + + case SOURCE_OCEAN: + case SOURCE_BEACH: + cell = Map.Calculated_Cell(SOURCE_BEACH, teamtype->House); + if (cell) { + CELL edge = XY_Cell(Cell_X(cell), Map.MapCellY+Map.MapCellHeight); + + placed = object->Unlimbo(Cell_Coord(edge), DIR_N); + if (placed) { + if (!team) { + object->IsLocked = false; + object->Assign_Mission(MISSION_UNLOAD); + object->Commence(); + object->Assign_Destination(::As_Target(cell)); + } + } else { + if (team) delete team; + delete object; + return(false); + } + } + break; + + default: + break; + } + + /* + ** Announce when the reinforcements have arrived. + */ + if (okvoice && teamtype->House == PlayerPtr->Class->House) { + Speak(VOX_REINFORCEMENTS); + } + + return(true); +} + + +/*********************************************************************************************** + * Create_Special_Reinforcement -- Ad hoc reinforcement handler. * + * * + * Use this routine to bring on a reinforcement that hasn't been anticipated by the trigger * + * system. An example of this would be replacement harvesters or airfield ordered units. * + * The appropriate transport is created (if necessary) and a mission is assigned such that * + * the object will legally bring itself onto the playing field. * + * * + * INPUT: house -- The owner of this reinforcement. * + * * + * type -- The object to bring on. * + * * + * another -- This is reserved for the transport class in those cases where the * + * transport MUST be forced to a specific type. * + * * + * mission -- The mission to assign this reinforcement team. * + * * + * argument -- Optional team mission argument (usually a waypoint). * + * * + * OUTPUT: Was the special reinforcement created without error? * + * * + * WARNINGS: This routine will fail if a team type cannot be created. * + * * + * HISTORY: * + * 07/04/1995 JLB : Created. * + *=============================================================================================*/ +bool Create_Special_Reinforcement(HouseClass * house, TechnoTypeClass const * type, TechnoTypeClass const * another, TeamMissionType mission, int argument) +{ + if (house && type) { + TeamTypeClass * team = new TeamTypeClass(); + + if (team) { + + /* + ** If a ground based reinforcement is desired, but the edge of the map that the + ** reinforcement will arrive at is completely covered with water, then add + ** a hover lander for transport. + */ + if (!another && (type->What_Am_I() == RTTI_UNITTYPE || type->What_Am_I() == RTTI_INFANTRYTYPE)) { + + /* + ** Hover lander reinforcements can only arrive from the south. Yes, this is an + ** arbitrary limitation, but that's the way it is (for now). + */ + if (house->Edge == SOURCE_SOUTH) { + bool found = false; + for (int index = Map.MapCellX; index < Map.MapCellX+Map.MapCellWidth-1; index++) { + CELL cell = XY_Cell(index, Map.MapCellY+Map.MapCellHeight); + if (Map[cell].Is_Generally_Clear() && Map[cell-MAP_CELL_W].Is_Generally_Clear()) { + found = true; + break; + } + } + + /* + ** No land route was found for the reinforcement to drive itself onto the + ** map. Assign it a hover lander. This presumes that if the south edge is + ** non drivable, it will have water there instead. Risky, but is not a + ** problem with the current C&C maps. + */ + if (!found) { + mission = TMISSION_NONE; + another = &UnitTypeClass::As_Reference(UNIT_HOVER); + } + } + + if (!another) { + mission = TMISSION_MOVECELL; + argument = Map.Calculated_Cell(SOURCE_AIR, house->Class->House); + } + } + + /* + ** If there is no overridden mission assign to this special reinforcement, then + ** we must assign something. If not, the reinforcement will just sit at the edge + ** of the map. + */ + if (!another && mission == TMISSION_NONE) { + mission = TMISSION_MOVECELL; + argument = Map.Calculated_Cell(SOURCE_AIR, house->Class->House); + } + + /* + ** Fill in the team characteristics. + */ + strcpy((char *)&team->IniName[0], "TEMP"); + team->IsReinforcable = false; + team->IsTransient = true; + team->ClassCount = 1; + team->Class[0] = type; + team->DesiredNum[0] = 1; + team->MissionCount = 1; + if (mission == TMISSION_NONE) { + if (another && (another->What_Am_I() != RTTI_UNITTYPE || ((UnitTypeClass const *)another)->Type != UNIT_HOVER)) { + team->MissionList[0].Mission = TMISSION_UNLOAD; + team->MissionList[0].Argument = WAYPT_REINF; + } + } else { + team->MissionList[0].Mission = mission; + team->MissionList[0].Argument = argument; + } + team->House = house->Class->House; + if (another) { + team->ClassCount++; + team->Class[1] = another; + team->DesiredNum[1] = 1; + } + + bool ok = Do_Reinforcements(team); + if (!ok && GameToPlay == GAME_NORMAL) { + delete team; + } + return(ok); + } + } + return(false); +} + + +/*********************************************************************************************** + * Create_Air_Reinforcement -- Creates air strike reinforcement * + * * + * This routine is used to launch an airstrike. It will create the necessary aircraft and * + * assign them to attack the target specified. This routine bypasses the normal * + * reinforcement logic since it doesn't need the sophistication of unloading and following * + * team mission lists. * + * * + * INPUT: house -- The purpetrator of this air strike. * + * * + * air -- The type of aircraft to make up this airstrike. * + * * + * number -- The number of aircraft in this airstrike. * + * * + * mission -- The mission to assign the aircraft. * + * * + * tarcom -- The target to assign these aircraft. * + * * + * navcom -- The navigation target to assign (if necessary). * + * * + * OUTPUT: Returns the number of aircraft created for this airstrike. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/04/1995 JLB : Commented. * + *=============================================================================================*/ +int Create_Air_Reinforcement(HouseClass *house, AircraftType air, int number, MissionType mission, TARGET tarcom, TARGET navcom) +{ + /* + ** Get a pointer to the class of the object that we are going to create. + */ + TechnoTypeClass const * type = (TechnoTypeClass *)&AircraftTypeClass::As_Reference(air); + + /* + ** Loop through the number of objects we are supposed to create and + ** create and place them on the map. + */ + for (int sub = 0; sub < number; sub++) { + + /* + ** Create one of the required objects. If this fails we could have + ** a real problem. + */ + ScenarioInit++; + TechnoClass * obj = (TechnoClass *)type->Create_One_Of(house); + ScenarioInit--; + if (!obj) return(sub); + + /* + ** Flying objects always have the IsALoaner bit set. + */ + obj->IsALoaner = true; + + /* + ** Find a cell for the object to come in on. This is stolen from the + ** the code that handles a SOURCE_AIR in the normal logic. + */ + SourceType source = house->Edge; + switch (source) { + case SOURCE_NORTH: + case SOURCE_EAST: + case SOURCE_SOUTH: + case SOURCE_WEST: + break; + + default: + source = SOURCE_NORTH; + break; + } + CELL newcell = Map.Calculated_Cell(source, house->Class->House); + + /* + ** Try and place the object onto the map. + */ + ScenarioInit++; + int placed = obj->Unlimbo(Cell_Coord(newcell), DIR_N); + ScenarioInit--; + if (placed) { + + /* + ** If we suceeded in placing the obj onto the map then + ** now we need to give it a mission and destination. + */ + obj->Assign_Mission(mission); + + /* + ** If a navcom was specified then set it. + */ + if (navcom != TARGET_NONE) { + obj->Assign_Destination(navcom); + } + + /* + ** If a tarcom was specified then set it. + */ + if (tarcom != TARGET_NONE) { + obj->Assign_Target(tarcom); + } + + /* + ** Start the object into action. + */ + obj->Commence(); + } else { + delete obj; + sub--; + return(sub); + } + } + return(sub); +} diff --git a/RULES.MAK b/RULES.MAK new file mode 100644 index 0000000..b9d753a --- /dev/null +++ b/RULES.MAK @@ -0,0 +1,423 @@ +# +# Command & Conquer(tm) +# Copyright 2025 Electronic Arts Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# Size of the program shape buffer (passed to MAKESHPS.EXE) +SHAPEBUFFSIZE = 20000 + +# Assign the paths +# +!if$d(DAVID) +ROOTDIR = f:\projects\c&c\\ +MAPDIR = f:\projects\c&c\\ +ROOTCODEDIR = f:\projects\c&c\code\david +ROOTCDDIR = f:\projects\c&c\cddavid\\ +ROOTCDSDIR = f:\projects\c&c\cddavid\\ +ABSROOT = ..\\ +WAVDIR = f:\projects\c&c\audio\ingame\\ +#WAVDIR = r:\\ +LANGDIR = +INGAMEDIR =ingame +VQADIR = f:\projects\c&c\art\\ +!else +!if$d(BARRY) +ROOTDIR = f:\projects\c&c\\ +MAPDIR = c:\projects\c&c\\ +ROOTCODEDIR = f:\projects\c&c\code\barry +ROOTCDDIR = s:\\ +ROOTCDSDIR = s:\\ +ABSROOT = ..\\ +#WAVDIR = f:\projects\c&c\audio\ingame\\ +WAVDIR = r:\\ +LANGDIR=\german +INGAMEDIR=german +VQADIR = f:\projects\c&c\art\\ +!else +ROOTDIR = ..\\ +MAPDIR = ..\\ +ROOTCODEDIR = .\\ +#ROOTCDDIR = f:\projects\c&c95\cd\\ +#ROOTCDSDIR = f:\projects\c&c95\cd\\ +#ROOTCDDIR = f:\projects\c&c95\cd\japanese\\ +#ROOTCDSDIR = f:\projects\c&c95\cd\japanese\\ +#ROOTCDSDIR = v:\projects\c&c95\cdjapan\\ +ROOTCDDIR = ..\cd\\ +ROOTCDSDIR = ..\\ +!if$d(SLAVE) +ABSROOT = $(SLAVE) +WAVDIR = $(SLAVE) +VQADIR = $(SLAVE) +!else +ABSROOT = ..\\ +WAVDIR = ..\audio\ingame\\ #..\\ +VQADIR = f:\projects\c&c\art\\ +VQADIR = f:\projects\c&c95\art\\ +#VQADIR = d:\\ +!endif +LANGDIR = \\japan\\ +#LANGDIR = +INGAMEDIR =ingame +!endif +!endif + +.path.a6 = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a60 = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a61 = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a62 = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a63 = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a64 = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a6a = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a6j = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a8 = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a80 = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a81 = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a82 = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a83 = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a84 = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a8a = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.a8j = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.anm = $(ROOTDIR)art\$(INGAMEDIR)\\ # Raw animation file +.path.ash = .\\ # Autogenerated assembly header file +.path.asm = .\\ # Raw assembly file +.path.aud = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.bin = $(MAPDIR)maps\\ # Scenario map data files. +.path.c = .\\ # Raw C file +.path.dmo = $(ROOTCDSDIR)DEMO\\ # CD for Demo Version +.path.dm2 = $(ROOTCDSDIR)DEMO2\\ # CD for Demo Version +.path.exp = $(ROOTCDSDIR)COVERT\\ # CD #1 expansion missions +.path.cpp = .\\ # Raw C++ file +.path.cps = $(ROOTCODEDIR)cps\\ # Compressed art +.path.dat = $(ROOTCODEDIR)cps\\ # Generic data +.path.des = $(ROOTCODEDIR)cps\\ # Desert icons +.path.dip = $(ROOTCODEDIR)cps\\ # Generic compressed text (obsolete) +.path.eng = $(ROOTCODEDIR)cps\\ # English compressed text +.path.exe = $(ROOTDIR)run\\ # Executable (relative path from OBJ directory) +.path.fin = $(ROOTDIR)art\$(INGAMEDIR)\fin\\ # Finale source animation +.path.fnt = $(ROOTCODEDIR)cps\\ # Font (compressed) +.path.foc = $(ROOTDIR)audio\french\\ # French digitized voices +.path.fre = $(ROOTCODEDIR)cps\\ # French compressed text +.path.gdi = $(ROOTCODEDIR)cps\\ # Good guy compressed files +.path.ger = $(ROOTCODEDIR)cps\\ # German compressed text +.path.goc = $(ROOTDIR)audio\german\\ # German digitized voices +.path.h = .\\ # C header file +.path.i = .\\ # Assembly header +.path.icn = $(ROOTCODEDIR)cps\\ # Icon file (compressed) +.path.ini = $(MAPDIR)maps\\ # INI control files +.path.jp = $(ROOTCODEDIR)cps\\ # Dinosaur compressed files +.path.jun = $(ROOTCODEDIR)cps\\ # Jungle icons +.path.juv = $(ABSROOT)code\cps\\ # Juvenile sound effects. +.path.lbm = $(ROOTDIR)art\$(INGAMEDIR)\\ # Source art (uncompressed) +.path.mak = .\\ # Shape making control file +.path.map = $(ROOTCODEDIR)cps\\ # Icon map data file +.path.mid = $(ROOTDIR)audio\mid\\ # Midi file +.path.cd1 = $(ROOTCDSDIR)CD1\\ # CD #1 (GDI) +.path.cd2 = $(ROOTCDSDIR)CD2\\ # CD #2 (NOD) +.path.mix = $(ROOTCDDIR) # Mix-file location. +.path.mrf = $(ROOTCODEDIR)cps\\ # Palette morph data files +.path.nod = $(ROOTCODEDIR)cps\\ # Bad guy compressed files +.path.o = obj\\ # Overlay object file +.path.obj = obj\\ # Object file +.path.out = .\\ # Temporary parser file. +.path.pak = $(ROOTDIR)run\\ # Packed file +.path.pal = $(ROOTCODEDIR)cps\\ # Palette file (processed) +.path.shp = $(ROOTCODEDIR)cps\\ # Shape file (processed) +.path.sym = .\\ # Precompiled header symbol file. +.path.tbl = $(ROOTCODEDIR)cps\\ # Remap table file. +.path.tem = $(ROOTCODEDIR)cps\\ # Temperate icons +.path.txt = $(ROOTDIR)eng\\ # Default location for text files. +.path.v00 = $(ABSROOT)code\cps\\ # Infantry response. +.path.v01 = $(ABSROOT)code\cps\\ # Infantry response. +.path.v02 = $(ABSROOT)code\cps\\ # Infantry response. +.path.v03 = $(ABSROOT)code\cps\\ # Infantry response. +.path.v04 = $(ABSROOT)code\cps\\ # Infantry response. +.path.v16 = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.v8 = $(ABSROOT)code\cps\\ # Game formatted audio data +.path.vqa = $(VQADIR)movies$(LANGDIR)\\ # VQA movie files location. +.path.vqp = $(ABSROOT)code\cps\\ # VQA Movie Interpolated palette files +.path.wav = $(WAVDIR) # Digitized sample file (generic) +.path.win = $(ROOTCODEDIR)cps\\ # Winter icons +.path.wsa = $(ROOTCODEDIR)cps\\ # Processed animation file +.path.wv = $(ROOTDIR)audio\wv\\ # Digitized sample file (generic) +.path.xmi = $(ROOTDIR)audio\xmi\\ # MT-32 midi scores +.path.pcx = $(ROOTCODEDIR)cps\\ #.pcx files + + +########################################################################## +# Rules + +# +# Rule for converting anim files. +# +.anm.wsa: + utils\animate $*.anm $(.path.anm)$&.lbm $(.path.wsa)$&.wsa -l -f -p + +# +# Rule for converting art files. +# +.lbm.cps: + utils\wwcomp $*.lbm $(.path.cps)$&.cps -ex -i + +# +# Rule for converting outtake first frame art files. +# +{$(.path.lbm)outtake}.lbm.cps: + utils\wwcomp $*.lbm $(.path.cps)$&.cps -ex + +{$(.path.anm)outtake}.anm.wsa: + utils\animate $*.anm $(.path.anm)outtake\$&.lbm $(.path.wsa)$&.wsa -l -f -p + +{$(.path.anm)score}.anm.wsa: + utils\animate $*.anm $(.path.wsa)$&.wsa -p + +{$(.path.anm)score\\$(LANGDIR)\\}.anm.wsa: + utils\animate $*.anm $(.path.wsa)$&.wsa -p + +######################################################### +# Rule for creating palette files. +# +{$(.path.lbm)palettes}.lbm.pal: + utils\wwcomp $*.lbm -ppal + copy $&.pal $(.path.pal)$&.pal + del $&.pal + +{$(.path.lbm)score}.lbm.pal: + utils\wwcomp $*.lbm -ppal + copy $&.pal $(.path.pal)$&.pal + del $&.pal + +######################################################### +# Rule for creating vehicles. +{$(.path.anm)units}.anm.shp: + utils\keyframe $*.anm $(.path.shp)$&.shp -l + +######################################################### +# Rule for creating buildings. +{$(.path.anm)building}.anm.shp: + utils\keyframe $*.anm $(.path.shp)$&.shp -l + +######################################################### +# Rule for creating misc shape animations. +{$(.path.anm)anim}.anm.shp: + utils\keyframe $*.anm $(.path.shp)$&.shp -l + +######################################################### +# Rule for converting font files. +{$(.path.lbm)fonts}.lbm.fnt: + echo Creating font file "$&.fnt". + utils\fontmake $*.lbm -o$(.path.fnt)$&.fnt + +######################################################### +# Rules for creating overlay files. +{$(.path.lbm)overlay}.lbm.cps: + utils\wwcomp $*.lbm $(.path.cps)$&.cps + +{$(.path.anm)overlay}.anm.shp: + utils\keyframe $*.anm $(.path.shp)$&.shp -l + +######################################################### +# Rules for creating temperate files. +{$(.path.lbm)temperat\\$(LANGDIR)\\}.lbm.tem: + utils\iconmap -r -w3 -o$(.path.tem)$&.tem $*.lbm + +{$(.path.anm)temperat\\$(LANGDIR)\\}.anm.tem: + utils\keyframe $*.anm $(.path.tem)$&.tem -l + +######################################################### +# Rules for creating winter files. +{$(.path.lbm)winter\\$(LANGDIR)\\}.lbm.win: + utils\iconmap -r -w3 -o$(.path.win)$&.win $*.lbm + +{$(.path.anm)winter\\$(LANGDIR)\\}.anm.win: + utils\keyframe $*.anm $(.path.win)$&.win -l + +######################################################### +# Rules for creating desert files. +{$(.path.lbm)desert\\$(LANGDIR)\\}.lbm.des: + utils\iconmap -r -w3 -o$(.path.des)$&.des $*.lbm + +{$(.path.anm)desert\\$(LANGDIR)\\}.anm.des: + utils\keyframe $*.anm $(.path.des)$&.des -l + +######################################################### +# Rules for creating jungle files. +{$(.path.lbm)jungle}.lbm.jun: + utils\iconmap -r -w3 -o$(.path.jun)$&.jun $*.lbm + +{$(.path.anm)jungle}.anm.jun: + utils\keyframe $*.anm $(.path.jun)$&.jun -l + + +######################################################### +# Generic icon file creation. +.lbm.icn: + utils\iconmap -r -w3 -o$(.path.icn)$&.icn $*.lbm + + +######################################################### +# Text files. +{eng}.txt.eng: + utils\textmake $*.txt $(.path.eng)$&.eng $&.h + +{ger}.txt.ger: + utils\textmake $*.txt $(.path.ger)$&.ger $&.h + +{fre}.txt.fre: + utils\textmake $*.txt $(.path.fre)$&.fre $&.h + +######################################################### +# Score/mapsel files. +{$(.path.anm)score}.anm.shp: + utils\keyframe $*.anm $(.path.shp)$&.shp -l + +{$(.path.lbm)score}.lbm.cps: + utils\wwcomp $*.lbm $(.path.cps)$&.cps -ex -i + + +######################################################### +# Generic shape files. +#.lbm.shp: +# utils\makeshps -q $*.lbm $&.mak $(.path.cps)$&.shp $(SHAPEBUFFSIZE) + +{$(.path.anm)generic}.anm.shp: + utils\keyframe $*.anm $(.path.shp)$&.shp -l + +.c.lob: + utils\LINT -ml -si2 -sl4 +v $(WWLIB)WWLIB.LNT $*.c >$&.err + +# Generic shape file creation. +{$(.path.anm)}.anm.shp: + utils\keyframe $*.anm $(.path.shp)$&.shp -l + +{$(.path.lbm)units}.lbm.cps: + utils\wwcomp $*.lbm $(.path.cps)$&.cps -ex -i + + +########################################################## +# Hi-res shape files +# +{$(.path.anm)hires}.anm.shp: + utils\keyframe $*.anm $(.path.shp)$&.shp -l +# Hi-res font files +{$(.path.lbm)hires}.lbm.fnt: + echo Creating font file "$&.fnt". + utils\fontmake $*.lbm -o$(.path.fnt)$&.fnt + + +################################### +# NOD data file creation rules. +{$(.path.anm)nod}.anm.nod: + utils\keyframe $*.anm $(.path.nod)$&.nod -l + +{$(.path.lbm)nod}.lbm.nod: + utils\wwcomp $*.lbm $(.path.nod)$&.nod -ex -i + +################################### +# GDI data file creation rules. +{$(.path.anm)gdi}.anm.gdi: + utils\keyframe $*.anm $(.path.gdi)$&.gdi -l + +{$(.path.lbm)gdi}.lbm.gdi: + utils\wwcomp $*.lbm $(.path.gdi)$&.gdi -ex -i + +################################### +# Dino data file creation rules. +{$(.path.anm)jp}.anm.jp: + utils\keyframe $*.anm $(.path.jp)$&.jp -l + +{$(.path.lbm)jp}.lbm.jp: + utils\wwcomp $*.lbm $(.path.jp)$&.jp -ex -i + +####################################### +# Rules to convert 8 bit audio files. +{$(.path.wav)audio8\scores\mono}.wav.a8: + utils\audiomak $(.path.wav)audio8\scores\mono\$&.wav $(.path.aud)$&.a8 + +{$(.path.wav)audio8\scores\mono\olf}.wav.v8: + utils\audiomak $(.path.wav)audio8\scores\mono\old\$&.wav $(.path.aud)$&.v8 + +{$(.path.wav)audio8\speech}.wav.a8: + utils\audiomak $(.path.wav)audio8\speech\$&.wav $(.path.aud)$&.a8 + +{$(.path.wav)audio8\sfx\juvenile}.wav.a8j: + utils\audiomak $(.path.wav)audio8\sfx\juvenile\$&.wav $(.path.juv)$&.a8j + +{$(.path.wav)audio8\sfx\adult}.wav.a8a: + utils\audiomak $(.path.wav)audio8\sfx\adult\$&.wav $(.path.aud)$&.a8a + +{$(.path.wav)audio8\sfx\generic}.wav.a8: + utils\audiomak $(.path.wav)audio8\sfx\generic\$&.wav $(.path.aud)$&.a8 + +{$(.path.wav)audio8\sfx\b&b}.wav.a8: + utils\audiomak $(.path.wav)audio8\sfx\b&b\$&.wav $(.path.aud)$&.a8 + +{$(.path.wav)audio8\sfx\v00}.wav.a80: + utils\audiomak $(.path.wav)audio8\sfx\v00\$&.wav $(.path.aud)$&.a80 + +{$(.path.wav)audio8\sfx\v01}.wav.a81: + utils\audiomak $(.path.wav)audio8\sfx\v01\$&.wav $(.path.aud)$&.a81 + +{$(.path.wav)audio8\sfx\v02}.wav.a82: + utils\audiomak $(.path.wav)audio8\sfx\v02\$&.wav $(.path.aud)$&.a82 + +{$(.path.wav)audio8\sfx\v03}.wav.a83: + utils\audiomak $(.path.wav)audio8\sfx\v03\$&.wav $(.path.aud)$&.a83 + +{$(.path.wav)audio8\sfx\v04}.wav.a84: + utils\audiomak $(.path.wav)audio8\sfx\v04\$&.wav $(.path.aud)$&.a84 + + +####################################### +# Rules to convert 16 bit audio files. +{$(.path.wav)audio16\sfx\mobius$(LANGDIR)}.wav.a6: + utils\audiomak $(.path.wav)audio16\sfx\mobius$(LANGDIR)\$&.wav $(.path.aud)$&.a6 + +{$(.path.wav)audio16\scores\mono}.wav.a6: + utils\audiomak $(.path.wav)audio16\scores\mono\$&.wav $(.path.aud)$&.a6 + +{$(.path.wav)audio16\scores\mono\old}.wav.v16: + utils\audiomak $(.path.wav)audio16\scores\mono\old\$&.wav $(.path.aud)$&.v16 + +{$(.path.wav)audio16\speech$(LANGDIR)}.wav.a6: + utils\audiomak $(.path.wav)audio16\speech$(LANGDIR)\$&.wav $(.path.aud)$&.a6 + +{$(.path.wav)audio16\sfx\juvenile}.wav.a6j: + utils\audiomak $(.path.wav)audio16\sfx\juvenile\$&.wav $(.path.juv)$&.a6j + +{$(.path.wav)audio16\sfx\adult$(LANGDIR)}.wav.a6a: + utils\audiomak $(.path.wav)audio16\sfx\adult$(LANGDIR)\$&.wav $(.path.aud)$&.a6a + +{$(.path.wav)audio16\sfx\generic$(LANGDIR)}.wav.a6: + utils\audiomak $(.path.wav)audio16\sfx\generic$(LANGDIR)\$&.wav $(.path.aud)$&.a6 + +{$(.path.wav)audio16\sfx\b&b$(LANGDIR)}.wav.a6: + utils\audiomak $(.path.wav)audio16\sfx\b&b$(LANGDIR)\$&.wav $(.path.aud)$&.a6 + +{$(.path.wav)audio16\sfx\v00$(LANGDIR)}.wav.a60: + utils\audiomak $(.path.wav)audio16\sfx\v00$(LANGDIR)\$&.wav $(.path.aud)$&.a60 + +{$(.path.wav)audio16\sfx\v01$(LANGDIR)}.wav.a61: + utils\audiomak $(.path.wav)audio16\sfx\v01$(LANGDIR)\$&.wav $(.path.aud)$&.a61 + +{$(.path.wav)audio16\sfx\v02$(LANGDIR)}.wav.a62: + utils\audiomak $(.path.wav)audio16\sfx\v02$(LANGDIR)\$&.wav $(.path.aud)$&.a62 + +{$(.path.wav)audio16\sfx\v03$(LANGDIR)}.wav.a63: + utils\audiomak $(.path.wav)audio16\sfx\v03$(LANGDIR)\$&.wav $(.path.aud)$&.a63 + +{$(.path.wav)audio16\sfx\v04}.wav.a64: + utils\audiomak $(.path.wav)audio16\sfx\v04\$&.wav $(.path.aud)$&.a64 + diff --git a/SAVEDLG.H b/SAVEDLG.H new file mode 100644 index 0000000..7e7adac --- /dev/null +++ b/SAVEDLG.H @@ -0,0 +1,71 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\savedlg.h_v 2.14 16 Oct 1995 16:45:12 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SAVEDLG.H * + * * + * Programmer : Maria del Mar McCready Legg, Joe L. Bostic * + * * + * Start Date : Jan 8, 1995 * + * * + * Last Update : Jan 18, 1995 [MML] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef SAVEDLG_H +#define SAVEDLG_H + +#include "gadget.h" + +class SaveOptionsClass +{ + private: + + enum SaveOptionsClassEnums { + BUTTON_CANCEL=200, + BUTTON_SAVE, + OPTION_WIDTH=216, + OPTION_HEIGHT=122, + OPTION_X=((320 - OPTION_WIDTH) / 2) & ~7, + OPTION_Y=(200 - OPTION_HEIGHT) / 2, + NUMBER_OF_BUTTONS=2, + CAPTION_Y_POS=5, + BORDER1_LEN=49, + BUTTON_CANCEL_X=90, + BUTTON_CANCEL_Y=103, + LISTBOX_X=40, + LISTBOX_Y=24, + LISTBOX_W=136, + LISTBOX_H=72 + }; + + public: + SaveOptionsClass (void) { }; + void Process (void); +}; + + +#endif diff --git a/SAVELOAD.CPP b/SAVELOAD.CPP new file mode 100644 index 0000000..d9cc57d --- /dev/null +++ b/SAVELOAD.CPP @@ -0,0 +1,1392 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\saveload.cpv 2.18 16 Oct 1995 16:48:44 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SAVELOAD.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : August 23, 1994 * + * * + * Last Update : June 24, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Code_All_Pointers -- Code all pointers. * + * Decode_All_Pointers -- Decodes all pointers. * + * Get_Savefile_Info -- gets description, scenario #, house * + * Load_Game -- loads a saved game * + * Load_Misc_Values -- Loads miscellaneous variables. * + * Load_Misc_Values -- loads miscellaneous variables * + * Read_Object -- reads an object from disk, in a safe way * + * Save_Game -- saves a game to disk * + * Save_Misc_Values -- saves miscellaneous variables * + * Target_To_TechnoType -- converts TARGET to TechnoTypeClass * + * TechnoType_To_Target -- converts TechnoTypeClass to TARGET * + * Write_Object -- reads an object from disk, in a safe way * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +/* +********************************** Defines ********************************** +*/ +#define SAVEGAME_VERSION (DESCRIP_MAX + \ + 0x01000003 + ( \ + sizeof(AircraftClass) + \ + sizeof(AircraftTypeClass) + \ + sizeof(AnimClass) + \ + sizeof(AnimTypeClass) + \ + sizeof(BuildingClass) + \ + sizeof(BuildingTypeClass) + \ + sizeof(BulletClass) + \ + sizeof(BulletTypeClass) + \ + sizeof(HouseClass) + \ + sizeof(HouseTypeClass) + \ + sizeof(InfantryClass) + \ + sizeof(InfantryTypeClass) + \ + sizeof(OverlayClass) + \ + sizeof(OverlayTypeClass) + \ + sizeof(SmudgeClass) + \ + sizeof(SmudgeTypeClass) + \ + sizeof(TeamClass) + \ + sizeof(TeamTypeClass) + \ + sizeof(TemplateClass) + \ + sizeof(TemplateTypeClass) + \ + sizeof(TerrainClass) + \ + sizeof(TerrainTypeClass) + \ + sizeof(UnitClass) + \ + sizeof(UnitTypeClass) + \ + sizeof(MouseClass) + \ + sizeof(CellClass) + \ + sizeof(FactoryClass) + \ + sizeof(BaseClass) + \ + sizeof(LayerClass) + \ + sizeof(BriefingText) + \ + sizeof(Waypoint))) + + +/*************************************************************************** + * Save_Game -- saves a game to disk * + * * + * Saving the Map: * + * DisplayClass::Save() invokes CellClass's Write() for every cell * + * that needs to be saved. A cell needs to be saved if it contains * + * any special data at all, such as a TIcon, or an Occupier. * + * The cell saves its own CellTrigger pointer, converted to a TARGET. * + * * + * Saving game objects: * + * - Any object stored in an ArrayOf class needs to be saved. The ArrayOf* + * Save() routine invokes each object's Write() routine, if that * + * object's IsActive is set. * + * * + * Saving the layers: * + * The Map's Layers (Ground, Air, etc) of things that are on the map, * + * and the Logic's Layer of things to process both need to be saved. * + * LayerClass::Save() writes the entire layer array to disk * + * * + * Saving the houses: * + * Each house needs to be saved, to record its Credits, Power, etc. * + * * + * Saving miscellaneous data: * + * There are a lot of miscellaneous variables to save, such as the * + * map's dimensions, the player's house, etc. * + * * + * INPUT: * + * id numerical ID, for the file extension * + * * + * OUTPUT: * + * true = OK, false = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/28/1994 BR : Created. * + *=========================================================================*/ +bool Save_Game(int id,char *descr) +{ + RawFileClass file; + char name[_MAX_FNAME+_MAX_EXT]; + int i; + unsigned long version; + unsigned scenario; + HousesType house; + char descr_buf[DESCRIP_MAX]; + + scenario = Scenario; // get current scenario # + house = PlayerPtr->Class->House; // get current house + + /* + ** Generate the filename to save + */ + sprintf(name, "SAVEGAME.%03d", id); + + /* + ** Code everybody's pointers + */ + Code_All_Pointers(); + + /* + ** Open the file + */ + if (!file.Open(name, WRITE)) { + Decode_All_Pointers(); + return(false); + } + + /* + ** Save the description, scenario #, and house + ** (scenario # & house are saved separately from the actual Scenario & + ** PlayerPtr globals for convenience; we can quickly find out which + ** house & scenario this save-game file is for by reading these values. + ** Also, PlayerPtr is stored in a coded form in Save_Misc_Values(), + ** which may or may not be a HousesType number; so, saving 'house' + ** here ensures we can always pull out the house for this file.) + */ + sprintf(descr_buf, "%s\r\n",descr); // put CR-LF after text + descr_buf[strlen(descr_buf) + 1] = 26; // put CTRL-Z after NULL + + if (file.Write(descr_buf, DESCRIP_MAX) != DESCRIP_MAX) { + file.Close(); + return(false); + } + + if (file.Write(&scenario, sizeof(scenario)) != sizeof(scenario)) { + file.Close(); + return(false); + } + + if (file.Write(&house, sizeof(house)) != sizeof(house)) { + file.Close(); + return(false); + } + + /* + ** Save the save-game version, for loading verification + */ + version = SAVEGAME_VERSION; + + if (file.Write(&version, sizeof(version)) != sizeof(version)) { + file.Close(); + return(false); + } + + Call_Back(); + /* + ** Save the map. The map must be saved first, since it saves the Theater. + */ + Map.Save(file); + + Call_Back(); + /* + ** Save all game objects. This code saves every object that's stored in a + ** TFixedIHeap class. + */ + if (!Houses.Save(file) || + !TeamTypes.Save(file) || + !Teams.Save(file) || + !Triggers.Save(file) || + !Aircraft.Save(file) || + !Anims.Save(file) || + !Buildings.Save(file) || + !Bullets.Save(file) || + !Infantry.Save(file) || + !Overlays.Save(file) || + !Smudges.Save(file) || + !Templates.Save(file) || + !Terrains.Save(file) || + !Units.Save(file) || + !Factories.Save(file)) { + file.Close(); + + Decode_All_Pointers(); + + return(false); + } + + Call_Back(); + /* + ** Save the Logic & Map layers + */ + if (!Logic.Save(file)) { + file.Close(); + Decode_All_Pointers(); + return(false); + } + + for (i = 0; i < LAYER_COUNT; i++) { + if (!Map.Layer[i].Save(file)) { + file.Close(); + Decode_All_Pointers(); + return(false); + } + } + + /* + ** Save the Score + */ + if (!Score.Save(file)) { + file.Close(); + Decode_All_Pointers(); + return(false); + } + + /* + ** Save the AI Base + */ + if (!Base.Save(file)) { + file.Close(); + Decode_All_Pointers(); + return(false); + } + + /* + ** Save miscellaneous variables. + */ + if (!Save_Misc_Values(file)) { + file.Close(); + Decode_All_Pointers(); + return(false); + } + + Call_Back(); + /* + ** Close the file; we're done + */ + file.Close(); + Decode_All_Pointers(); + + return(true); +} + + +/*************************************************************************** + * Load_Game -- loads a saved game * + * * + * This routine loads the data in the same way it was saved out. * + * * + * Loading the Map: * + * - DisplayClass::Load() invokes CellClass's Load() for every cell * + * that was saved. * + * - The cell loads its own CellTrigger pointer. * + * * + * Loading game objects: * + * - IHeap's Load() routine loads the # of objects stored, and loads * + * each object. * + * - Triggers: Add themselves to the HouseTriggers if they're associated * + * with a house * + * * + * Loading the layers: * + * LayerClass::Load() reads the entire layer array to disk * + * * + * Loading the houses: * + * Each house is loaded in its entirety. * + * * + * Loading miscellaneous data: * + * There are a lot of miscellaneous variables to load, such as the * + * map's dimensions, the player's house, etc. * + * * + * INPUT: * + * id numerical ID, for the file extension * + * * + * OUTPUT: * + * true = OK, false = error * + * * + * WARNINGS: * + * If this routine returns false, the entire game will be in an * + * unknown state, so the scenario will have to be re-initialized. * + * * + * HISTORY: * + * 12/28/1994 BR : Created. * + *=========================================================================*/ +bool Load_Game(int id) +{ + RawFileClass file; + char name[_MAX_FNAME+_MAX_EXT]; + int i; + unsigned long version; + unsigned scenario; + HousesType house; + char descr_buf[DESCRIP_MAX]; + + /* + ** Generate the filename to load + */ + sprintf(name, "SAVEGAME.%03d", id); + + /* + ** Open the file + */ + if (!file.Open(name, READ)) { + return(false); + } + + /* + ** Read & discard the save-game's header info + */ + if (file.Read(descr_buf, DESCRIP_MAX) != DESCRIP_MAX) { + file.Close(); + return(false); + } + + if (file.Read(&scenario, sizeof(scenario)) != sizeof(scenario)) { + file.Close(); + return(false); + } + + if (file.Read(&house, sizeof(house)) != sizeof(house)) { + file.Close(); + return(false); + } + + Call_Back(); + /* + ** Clear the scenario so we start fresh; this calls the Init_Clear() routine + ** for the Map, and all object arrays. It has the following important + ** effects: + ** - Every cell is cleared to 0's, via MapClass::Init_Clear() + ** - All heap elements' are cleared + ** - The Houses are Initialized, which also clears their HouseTriggers + ** array + ** - The map's Layers & Logic Layer are cleared to empty + ** - The list of currently-selected objects is cleared + */ + Clear_Scenario(); + + /* + ** Read in & verify the save-game ID code + */ + if (file.Read(&version,sizeof(version)) != sizeof(version)) { + file.Close(); + return(false); + } + + if (version != SAVEGAME_VERSION) { + file.Close(); + return(false); + } + + Call_Back(); + /* + ** Set the required CD to be in the drive according to the scenario + ** loaded. + */ + if (RequiredCD != -2) { + if (scenario >= 20 && scenario <60 && GameToPlay == GAME_NORMAL) { + RequiredCD = 2; + } else { + if (scenario >= 60){ + /* + ** This is a gateway bonus scenario + */ + RequiredCD = -1; + }else{ + if (house == HOUSE_GOOD) { + RequiredCD = 0; + } else { + RequiredCD = 1; + } + } + } + } + if(!Force_CD_Available(RequiredCD)) { + Prog_End(); + exit(EXIT_FAILURE); + } + + Call_Back(); + + /* + ** Load the map. The map comes first, since it loads the Theater & init's + ** mixfiles. The map calls all the type-class's Init routines, telling them + ** what the Theater is; this must be done before any objects are created, so + ** they'll be properly created. + */ + Map.Load(file); + + Call_Back(); + /* + ** Load the object data. + */ + if (!Houses.Load(file) || + !TeamTypes.Load(file) || + !Teams.Load(file) || + !Triggers.Load(file) || + !Aircraft.Load(file) || + !Anims.Load(file) || + !Buildings.Load(file) || + !Bullets.Load(file) || + !Infantry.Load(file) || + !Overlays.Load(file) || + !Smudges.Load(file) || + !Templates.Load(file) || + !Terrains.Load(file) || + !Units.Load(file) || + !Factories.Load(file)) { + file.Close(); + return(false); + } + + Call_Back(); + /* + ** Load the Logic & Map Layers + */ + if (!Logic.Load(file)) { + file.Close(); + return(false); + } + for (i = 0; i < LAYER_COUNT; i++) { + if (!Map.Layer[i].Load(file)) { + file.Close(); + return(false); + } + } + + Call_Back(); + /* + ** Load the Score + */ + if (!Score.Load(file)) { + file.Close(); + return(false); + } + + /* + ** Load the AI Base + */ + if (!Base.Load(file)) { + file.Close(); + return(false); + } + + /* + ** Load miscellaneous variables, including the map size & the Theater + */ + if (!Load_Misc_Values(file)) { + file.Close(); + return(false); + } + + file.Close(); + Decode_All_Pointers(); + Map.Init_IO(); + Map.Flag_To_Redraw(true); + + ScenarioInit = 0; + +#ifdef DEMO + if (Scenario != 10 && Scenario != 1 && Scenario != 6) { + Clear_Scenario(); + return(false); + } +#endif + + Call_Back(); + return(true); +} + + +/*************************************************************************** + * Save_Misc_Values -- saves miscellaneous variables * + * * + * INPUT: * + * file file to use for writing * + * * + * OUTPUT: * + * true = success, false = failure * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/29/1994 BR : Created. * + *=========================================================================*/ +bool Save_Misc_Values(FileClass &file) +{ + int i; + int count; // # ptrs in 'CurrentObject' + ObjectClass * ptr; // for saving 'CurrentObject' ptrs + + /* + ** Player's House. + */ + if (file.Write(&PlayerPtr, sizeof(PlayerPtr)) != sizeof(PlayerPtr)) { + return(false); + } + + /* + ** Save this scenario number. + */ + if (file.Write(&Scenario, sizeof(Scenario)) != sizeof(Scenario)) { + return(false); + } + + /* + ** Save frame #. + */ + if (file.Write(&Frame, sizeof(Frame)) != sizeof(Frame)) { + return(false); + } + + /* + ** Save VQ Movie names. + */ + if (file.Write(WinMovie, sizeof(WinMovie)) != sizeof(WinMovie)) { + return(false); + } + + if (file.Write(LoseMovie, sizeof(LoseMovie)) != sizeof(LoseMovie)) { + return(false); + } + + /* + ** Save currently-selected objects list. + ** Save the # of ptrs in the list. + */ + count = CurrentObject.Count(); + if (file.Write(&count, sizeof(count)) != sizeof(count)) { + return(false); + } + + /* + ** Save the pointers. + */ + for (i = 0; i < count; i++) { + ptr = CurrentObject[i]; + if (file.Write(&ptr, sizeof(ptr)) != sizeof(ptr)) { + return(false); + } + } + + /* + ** Save the list of waypoints. + */ + if (file.Write(Waypoint, sizeof(Waypoint)) != sizeof(Waypoint)) { + return(false); + } + + file.Write(&ScenDir, sizeof(ScenDir)); + file.Write(&ScenVar, sizeof(ScenVar)); + file.Write(&CarryOverMoney, sizeof(CarryOverMoney)); + file.Write(&CarryOverPercent, sizeof(CarryOverPercent)); + file.Write(&BuildLevel, sizeof(BuildLevel)); + file.Write(BriefMovie, sizeof(BriefMovie)); + file.Write(Views, sizeof(Views)); + file.Write(&EndCountDown, sizeof(EndCountDown)); + file.Write(BriefingText, sizeof(BriefingText)); + + // This is new... + file.Write(ActionMovie, sizeof(ActionMovie)); + + return(true); +} + + +/*********************************************************************************************** + * Load_Misc_Values -- Loads miscellaneous variables. * + * * + * INPUT: file -- The file to load the misc values from. * + * * + * OUTPUT: Was the misc load process successful? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/24/1995 BRR : Created. * + *=============================================================================================*/ +bool Load_Misc_Values(FileClass &file) +{ + int i; + int count; // # ptrs in 'CurrentObject' + ObjectClass * ptr; // for loading 'CurrentObject' ptrs + + /* + ** Player's House. + */ + if (file.Read(&PlayerPtr, sizeof(PlayerPtr)) != sizeof(PlayerPtr)) { + return(false); + } + + /* + ** Read this scenario number. + */ + if (file.Read(&Scenario,sizeof(Scenario)) != sizeof(Scenario)) { + return(false); + } + + /* + ** Load frame #. + */ + if (file.Read(&Frame, sizeof(Frame)) != sizeof(Frame)) { + return(false); + } + + /* + ** Load VQ Movie names. + */ + if (file.Read(WinMovie, sizeof(WinMovie)) != sizeof(WinMovie)) { + return(false); + } + + if (file.Read(LoseMovie, sizeof(LoseMovie)) != sizeof(LoseMovie)) { + return(false); + } + + /* + ** Load currently-selected objects list. + ** Load the # of ptrs in the list. + */ + if (file.Read(&count, sizeof(count)) != sizeof(count)) { + return(false); + } + + /* + ** Load the pointers. + */ + for (i = 0; i < count; i++) { + if (file.Read(&ptr, sizeof(ptr)) != sizeof(ptr)) { + return(false); + } + CurrentObject.Add(ptr); // add to the list + } + + /* + ** Save the list of waypoints. + */ + if (file.Read(Waypoint, sizeof(Waypoint)) != sizeof(Waypoint)) { + return(false); + } + + file.Read(&ScenDir, sizeof(ScenDir)); + file.Read(&ScenVar, sizeof(ScenVar)); + file.Read(&CarryOverMoney, sizeof(CarryOverMoney)); + file.Read(&CarryOverPercent, sizeof(CarryOverPercent)); + file.Read(&BuildLevel, sizeof(BuildLevel)); + file.Read(BriefMovie, sizeof(BriefMovie)); + file.Read(Views, sizeof(Views)); + file.Read(&EndCountDown, sizeof(EndCountDown)); + file.Read(BriefingText, sizeof(BriefingText)); + + if (file.Seek(0, SEEK_CUR) < file.Size()) { + file.Read(ActionMovie, sizeof(ActionMovie)); + } + + return(true); +} + + +/*********************************************************************************************** + * Code_All_Pointers -- Code all pointers. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/24/1995 BRR : Created. * + *=============================================================================================*/ +void Code_All_Pointers(void) +{ + int i; + + /* + ** The Map. + */ + Map.Code_Pointers(); + + /* + ** The ArrayOf's. + */ + TeamTypes.Code_Pointers(); + Teams.Code_Pointers(); + Triggers.Code_Pointers(); + Aircraft.Code_Pointers(); + Anims.Code_Pointers(); + Buildings.Code_Pointers(); + Bullets.Code_Pointers(); + Infantry.Code_Pointers(); + Overlays.Code_Pointers(); + Smudges.Code_Pointers(); + Templates.Code_Pointers(); + Terrains.Code_Pointers(); + Units.Code_Pointers(); + Factories.Code_Pointers(); + + /* + ** The Layers. + */ + Logic.Code_Pointers(); + for (i = 0; i < LAYER_COUNT; i++) { + Map.Layer[i].Code_Pointers(); + } + + /* + ** The Score. + */ + Score.Code_Pointers(); + + /* + ** The Base. + */ + Base.Code_Pointers(); + + /* + ** PlayerPtr. + */ + PlayerPtr = (HouseClass *)(PlayerPtr->Class->House); + + /* + ** Currently-selected objects. + */ + for (i = 0; i < CurrentObject.Count(); i++) { + CurrentObject[i] = (ObjectClass *)CurrentObject[i]->As_Target(); + } + + /* + ** Houses must be coded last, because the Class->House member of the HouseClass + ** is used to code HouseClass pointers for all other objects, and if Class is + ** coded, it will point to a meaningless value. + */ + Houses.Code_Pointers(); +} + + +/*********************************************************************************************** + * Decode_All_Pointers -- Decodes all pointers. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/24/1995 BRR : Created. * + *=============================================================================================*/ +void Decode_All_Pointers(void) +{ + int i; + + /* + ** The Map. + */ + Map.Decode_Pointers(); + + /* + ** Decode houses first, so we can properly decode all other objects' + ** House pointers + */ + Houses.Decode_Pointers(); + + /* + ** The ArrayOf's. + */ + TeamTypes.Decode_Pointers(); + Teams.Decode_Pointers(); + Triggers.Decode_Pointers(); + Aircraft.Decode_Pointers(); + Anims.Decode_Pointers(); + Buildings.Decode_Pointers(); + Bullets.Decode_Pointers(); + Infantry.Decode_Pointers(); + Overlays.Decode_Pointers(); + Smudges.Decode_Pointers(); + Templates.Decode_Pointers(); + Terrains.Decode_Pointers(); + Units.Decode_Pointers(); + Factories.Decode_Pointers(); + + /* + ** The Layers. + */ + Logic.Decode_Pointers(); + for (i = 0; i < LAYER_COUNT; i++) { + Map.Layer[i].Decode_Pointers(); + } + + /* + ** The Score. + */ + Score.Decode_Pointers(); + + /* + ** The Base. + */ + Base.Decode_Pointers(); + + /* + ** PlayerPtr. + */ + PlayerPtr = HouseClass::As_Pointer((HousesType)PlayerPtr); + Whom = PlayerPtr->Class->House; + switch (PlayerPtr->Class->House) { + case HOUSE_GOOD: + ScenPlayer = SCEN_PLAYER_GDI; + break; + + case HOUSE_BAD: + ScenPlayer = SCEN_PLAYER_NOD; + break; + + case HOUSE_JP: + ScenPlayer = SCEN_PLAYER_JP; + break; + } + Check_Ptr(PlayerPtr,__FILE__,__LINE__); + + Set_Scenario_Name(ScenarioName, Scenario, ScenPlayer, ScenDir, ScenVar); + + /* + ** Currently-selected objects. + */ + for (i = 0; i < CurrentObject.Count(); i++) { + CurrentObject[i] = As_Object((TARGET)CurrentObject[i]); + Check_Ptr(CurrentObject[i],__FILE__,__LINE__); + } + + /* + ** Last-Minute Fixups; to resolve these pointers properly requires all other + ** pointers to be loaded & decoded. + */ + if (Map.PendingObjectPtr) { + Map.PendingObject = &Map.PendingObjectPtr->Class_Of(); + Check_Ptr((void *)Map.PendingObject, __FILE__, __LINE__); + Map.Set_Cursor_Shape(Map.PendingObject->Occupy_List(true)); + } else { + Map.PendingObject = 0; + Map.Set_Cursor_Shape(0); + } +} + + +/*********************************************************************************************** + * Read_Object -- reads an object from disk * + * * + * This routine reads in an object and fills in the virtual function table pointer. * + * * + * INPUT: * + * ptr pointer to object to read * + * base_size size of object's absolute base class * + * class_size size of the class itself * + * file file to use for I/O * + * vtable virtual function table pointer value, NULL if none * + * * + * OUTPUT: * + * true = OK, false = error * + * * + * WARNINGS: * + * This routine ASSUMES the program modules are compiled with: * + * -Vb- Always make the virtual function table ptr 2 bytes long * + * -Vt Put the virtual function table after the 1st class's data * + * * + * ALSO, the class used to compute 'base_size' must come first in a multiple-inheritence * + * hierarchy. AND, if your class multiply-inherits from other classes, only ONE of those * + * classes can contain virtual functions! If you include virtual functions in the other * + * classes, the compiler will generate multiple virtual function tables, and this load/save * + * technique will fail. * + * * + * Each class hierarchy is stored in memory as a chain: first the data for the base-est * + * class, then the virtual function table pointer for this hierarchy, then the data for * + * all derived classes. If any of these derived classes multiply-inherit, the base class * + * for the multiple inheritance is stored as a separate chain following this chain. The * + * new chain will contain its own virtual function table pointer, if the multiply- * + * inherited hierarchy contains any virtual functions. Thus, the declaration * + * class A * + * class B: public A * + * class C: public B, X * + * is stored as: * + * A data * + * A's Virtual Table Pointer * + * B data * + * X data * + * [X's Virtual Table Pointer] * + * C data * + * * + * and * + * class A * + * class B: public A * + * class C: public X, B * + * is stored in memory as: * + * X data * + * [X's Virtual Table Pointer] * + * A data * + * A's Virtual Table Pointer * + * B data * + * C data * + * * + * * + * HISTORY: * + * 01/10/1995 BR : Created. * + *=============================================================================================*/ +bool Read_Object(void *ptr, int base_size, int class_size, FileClass & file, void * vtable) +{ + int size; // object size in bytes + + /* + ** Read size of this chunk. + */ + if (file.Read(&size,sizeof(size)) != sizeof(size)) { + return(false); + } + + /* + ** Error if incorrect size. + */ + if (size != class_size) { + return(false); + } + + /* + ** Read object data. + */ + if (file.Read(ptr, class_size) != (class_size)) { + return(false); + } + + /* + ** Fill in VTable. + */ + if (vtable) { + ((void **)(((char *)ptr) + base_size - 4))[0] = vtable; + } + + return(true); +} + + +/*********************************************************************************************** + * Write_Object -- reads an object from disk, in a safe way * + * * + * This routine writes an object in 2 pieces, skipping the embedded * + * virtual function table pointer. * + * * + * INPUT: * + * ptr pointer to object to write * + * class_size size of the class itself * + * file file to use for I/O * + * * + * OUTPUT: * + * true = OK, false = error * + * * + * WARNINGS: * + * This routine ASSUMES the program modules are compiled with: * + * -Vb- Always make the virtual function table ptr 2 bytes long * + * -Vt Put the virtual function table after the 1st class's data * + * * + * Also see warnings for Read_Object(). * + * * + * HISTORY: * + * 01/10/1995 BR : Created. * + *=============================================================================================*/ +bool Write_Object(void *ptr, int class_size, FileClass & file) +{ + /* + ** Save size of this chunk. + */ + if (file.Write(&class_size,sizeof(class_size)) != sizeof(class_size)) { + return(false); + } + + /* + ** Save object data. + */ + if (file.Write(ptr, class_size) != (class_size)) { + return(false); + } + + return(true); +} + + +/*************************************************************************** + * Get_Savefile_Info -- gets description, scenario #, house * + * * + * INPUT: * + * id numerical ID, for the file extension * + * buf buffer to store description in * + * scenp ptr to variable to hold scenario * + * housep ptr to variable to hold house * + * * + * OUTPUT: * + * true = OK, false = error (save-game file invalid) * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 01/12/1995 BR : Created. * + *=========================================================================*/ +bool Get_Savefile_Info(int id, char *buf, unsigned *scenp, HousesType *housep) +{ + RawFileClass file; + char name[_MAX_FNAME+_MAX_EXT]; + unsigned long version; + char descr_buf[DESCRIP_MAX]; + + /* + ** Generate the filename to load + */ + sprintf(name, "SAVEGAME.%03d", id); + + /* + ** If the file opens OK, read the file + */ + if (file.Open(name, READ)) { + + /* + ** Read in the description, scenario #, and the house + */ + if (file.Read(descr_buf, DESCRIP_MAX) != DESCRIP_MAX) { + file.Close(); + return(false); + } + + descr_buf[strlen(descr_buf) - 2] = '\0'; // trim off CR/LF + strcpy(buf, descr_buf); + + if (file.Read(scenp, sizeof(unsigned)) != sizeof(unsigned)) { + file.Close(); + return(false); + } + + if (file.Read(housep, sizeof(HousesType)) != sizeof(HousesType)) { + file.Close(); + return(false); + } + + /* + ** Read & verify the save-game version # + */ + if (file.Read(&version,sizeof(version)) != sizeof(version)) { + file.Close(); + return(false); + } + + if (version!=SAVEGAME_VERSION) { + file.Close(); + return(false); + } + + file.Close(); + + return(true); + } + return(false); +} + + +/*************************************************************************** + * TechnoType_To_Target -- converts TechnoTypeClass to TARGET * + * * + * INPUT: * + * ptr pointer to convert * + * * + * OUTPUT: * + * target value * + * * + * WARNINGS: * + * Be certain that you only use the returned target value by passing * + * it to Target_To_TechnoType; do NOT call As_Techno, or you'll get * + * a totally invalid pointer. * + * * + * HISTORY: * + * 01/12/1995 BR : Created. * + *=========================================================================*/ +TARGET TechnoType_To_Target(TechnoTypeClass const * ptr) +{ + TARGET target; + + switch (ptr->What_Am_I()) { + case RTTI_INFANTRYTYPE: + target = Build_Target(KIND_INFANTRY, ((InfantryTypeClass const *)ptr)->Type); + break; + + case RTTI_UNITTYPE: + target = Build_Target(KIND_UNIT, ((UnitTypeClass const *)ptr)->Type); + break; + + case RTTI_AIRCRAFTTYPE: + target = Build_Target(KIND_AIRCRAFT, ((AircraftTypeClass const *)ptr)->Type); + break; + + case RTTI_BUILDINGTYPE: + target = Build_Target(KIND_BUILDING, ((BuildingTypeClass const *)ptr)->Type); + break; + + default: + target = 0; + break; + } + + return(target); +} + + +/*************************************************************************** + * Target_To_TechnoType -- converts TARGET to TechnoTypeClass * + * * + * INPUT: * + * target TARGET value to convert * + * * + * OUTPUT: * + * pointer to the TechnoTypeClass for this target value * + * * + * WARNINGS: * + * The TARGET value MUST have been generated with TechnoType_To_Target;* + * If you give this routine a target generated by an As_Target() * + * routine, it will return a bogus pointer. * + * * + * HISTORY: * + * 01/12/1995 BR : Created. * + *=========================================================================*/ +TechnoTypeClass const * Target_To_TechnoType(TARGET target) +{ + switch (Target_Kind(target)) { + case KIND_INFANTRY: + return(&InfantryTypeClass::As_Reference((InfantryType)Target_Value(target))); + + case KIND_UNIT: + return(&UnitTypeClass::As_Reference((UnitType)Target_Value(target))); + + case KIND_AIRCRAFT: + return(&AircraftTypeClass::As_Reference((AircraftType)Target_Value(target))); + + case KIND_BUILDING: + return(&BuildingTypeClass::As_Reference((StructType)Target_Value(target))); + } + return(NULL); +} + + +/*************************************************************************** + * Get_VTable -- gets the VTable pointer for the given object * + * * + * INPUT: * + * ptr pointer to check * + * * + * OUTPUT: * + * none * + * * + * WARNINGS: * + * none * + * * + * HISTORY: * + * 01/12/1995 BR : Created. * + *=========================================================================*/ +void * Get_VTable(void *ptr, int base_size) +{ + return(((void **)(((char *)ptr) + base_size - 4))[0]); +} + + +/*************************************************************************** + * Set_VTable -- sets the VTable pointer for the given object * + * * + * INPUT: * + * ptr pointer to check * + * base_size size of base class * + * vtable value of VTable to plug in * + * * + * OUTPUT: * + * none * + * * + * WARNINGS: * + * none * + * * + * HISTORY: * + * 01/12/1995 BR : Created. * + *=========================================================================*/ +void Set_VTable(void *ptr, int base_size, void *vtable) +{ + ((void **)(((char *)ptr) + base_size - 4))[0] = vtable; +} + + +#if 0 +/**************************************************************************** +Dump routine: prints everything about everything related to the Save/Load +process (OK, not exactly everything, but lots of stuff) +****************************************************************************/ +void Dump(void) +{ + int i,j; + FILE *fp; + char *layername[] = { + "Ground", + "Air", + "Top" + }; + + /* + ------------------------------- Open file -------------------------------- + */ + fp = fopen("dump.txt","wt"); + + /* + ------------------------------ Logic Layer ------------------------------- + */ + fprintf(fp,"--------------------- Logic Layer ---------------------\n"); + fprintf(fp,"Count: %d\n",Logic.Count()); + for (j = 0; j < Logic.Count(); j++) { + fprintf(fp, "Entry %d: %x \n",j,Logic[j]); + } + fprintf(fp,"\n"); + + /* + ------------------------------- Map Layers ------------------------------- + */ + for (i = 0; i < LAYER_COUNT; i++) { + fprintf(fp,"----------------- Map Layer %s ---------------------\n", + layername[i]); + fprintf(fp,"Count: %d\n",Map.Layer[i].Count()); + for (j = 0; j < Map.Layer[i].Count(); j++) { + fprintf(fp, "Entry %d: %x \n",j,Map.Layer[i][j]); + } + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ TeamTypes --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",TeamTypes.ActiveCount); + for (i = 0; i < TEAMTYPE_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d Name:%s\n",i,TeamTypes[i].IsActive, + TeamTypes[i].Get_Name()); + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ Teams --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",Teams.ActiveCount); + for (i = 0; i < TEAM_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d Name:%s\n",i,Teams[i].IsActive, + Teams[i].Class->Get_Name()); + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ Triggers --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",Triggers.ActiveCount); + for (i = 0; i < TRIGGER_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d Name:%s\n",i,Triggers[i].IsActive, + Triggers[i].Get_Name()); + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ Aircraft --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",Aircraft.ActiveCount); + for (i = 0; i < AIRCRAFT_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d \n",i,Aircraft[i].IsActive); + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ Anims --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",Anims.ActiveCount); + for (i = 0; i < ANIM_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d \n",i,Anims[i].IsActive); + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ Buildings --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",Buildings.ActiveCount); + for (i = 0; i < BUILDING_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d \n",i,Buildings[i].IsActive); + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ Bullets --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",Bullets.ActiveCount); + for (i = 0; i < BULLET_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d \n",i,Bullets[i].IsActive); + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ Infantry --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",Infantry.ActiveCount); + for (i = 0; i < INFANTRY_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d \n",i,Infantry[i].IsActive); + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ Overlays --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",Overlays.ActiveCount); + for (i = 0; i < OVERLAY_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d \n",i,Overlays[i].IsActive); + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ Reinforcements --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",Reinforcements.ActiveCount); + for (i = 0; i < REINFORCEMENT_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d \n",i,Reinforcements[i].IsActive); + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ Smudges --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",Smudges.ActiveCount); + for (i = 0; i < SMUDGE_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d \n",i,Smudges[i].IsActive); + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ Templates --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",Templates.ActiveCount); + for (i = 0; i < TEMPLATE_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d \n",i,Templates[i].IsActive); + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ Terrains --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",Terrains.ActiveCount); + for (i = 0; i < TERRAIN_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d \n",i,Terrains[i].IsActive); + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ Units --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",Units.ActiveCount); + for (i = 0; i < UNIT_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d \n",i,Units[i].IsActive); + } + fprintf(fp,"\n"); + + fprintf(fp,"------------------ Factories --------------------------\n"); + fprintf(fp,"ActiveCount: %d\n",Factories.ActiveCount); + for (i = 0; i < FACTORY_MAX; i++) { + fprintf(fp,"Entry %d: Active:%d \n",i,Factories[i].IsActive); + } + fprintf(fp,"\n"); + + fclose(fp); + + /* + ---------------------------- Flush the cache ----------------------------- + */ + fp = fopen("dummy.bin","wt"); + for (i = 0; i < 100; i++) { + fprintf(fp,"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); + } + fclose(fp); +} +#endif + diff --git a/SCENARIO.CPP b/SCENARIO.CPP new file mode 100644 index 0000000..2588895 --- /dev/null +++ b/SCENARIO.CPP @@ -0,0 +1,727 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\scenario.cpv 2.17 16 Oct 1995 16:52:08 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SCENARIO.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : August 24, 1995 [JLB] * + * * + * This module handles the scenario reading and writing. Scenario related * + * code that is executed between scenario play can also be here. * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Clear_Scenario -- Clears all data in preparation for scenario load. * + * Do_Lose -- Display losing comments. * + * Do_Restart -- Handle the restart mission process. * + * Do_Win -- Display winning congratulations. * + * Fill_In_Data -- Recreate all data that is not loaded with scenario. * + * Read_Scenario -- Reads a scenario from disk. * + * Restate_Mission -- Handles restating the mission objective. * + * Start_Scenario -- Starts the scenario. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +extern int PreserveVQAScreen; + + +/*********************************************************************************************** + * Start_Scenario -- Starts the scenario. * + * * + * This routine will start the scenario. In addition to loading the scenario data, it will * + * play the briefing and action movies. * + * * + * INPUT: root -- Pointer to the filename root for this scenario (e.g., "SCG01EA"). * + * * + * briefing -- Should the briefing be played? Normally this is true except when the * + * scenario is restarting. * + * * + * OUTPUT: Was the scenario started without error? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/04/1995 JLB : Created. * + *=============================================================================================*/ +bool Start_Scenario(char *root, bool briefing) +{ + + if (!Read_Scenario(root)) { + CCDebugString ("C&C95 - Failed to read scenario.\n"); + return(false); + } + CCDebugString ("C&C95 - Scenario read OK.\n"); + +#ifdef DEMO + + if (briefing) { + Play_Movie(BriefMovie); + Play_Movie(ActionMovie, TransitTheme); + } + Theme.Queue_Song(THEME_AOI); + +#else + + /* + ** Install some hacks around the movie playing to account for the choose- + ** sides introduction. We don't want an intro movie on scenario 1, and + ** we don't want a briefing movie on GDI scenario 1. + */ + if (Scenario < 20 && (!Special.IsJurassic || !AreThingiesEnabled)) { + if (Scenario != 1 || Whom == HOUSE_GOOD) { + Play_Movie(IntroMovie); + } + + if ((Scenario > 1 || Whom == HOUSE_BAD) && briefing) { + PreserveVQAScreen = (Scenario == 1); + Play_Movie(BriefMovie); + } + Play_Movie(ActionMovie, TransitTheme); + if (TransitTheme == THEME_NONE) { + Theme.Queue_Song(THEME_AOI); + } + } else { + Play_Movie(BriefMovie); + Play_Movie(ActionMovie, TransitTheme); + +#ifdef NEWMENU + + char buffer[25]; + sprintf(buffer, "%s.VQA", BriefMovie); + CCFileClass file(buffer); + + if (GameToPlay == GAME_NORMAL && !file.Is_Available()) { + VisiblePage.Clear(); + Set_Palette(GamePalette); +// Show_Mouse(); + /* + ** Show the mission briefing. Pretend we are inside the main loop so the palette + ** will be correct on the textured buttons. + */ + bool oldinmain = InMainLoop; + InMainLoop = true; + Restate_Mission(ScenarioName, TXT_OK, TXT_NONE); + InMainLoop = oldinmain; +// Hide_Mouse(); + if (TransitTheme == THEME_NONE) { + Theme.Queue_Song(THEME_AOI); + } + } + +#endif + } +#endif + + /* + ** Set the options values, since the palette has been initialized by Read_Scenario + */ + CCDebugString ("C&C95 - About to call Options.Set.\n"); + Options.Set(); + CCDebugString ("C&C95 - About to return from Start_Scenario.\n"); + return(true); +} + + +/*********************************************************************************************** + * Read_Scenario -- Reads a scenario from disk. * + * * + * This will read a scenario from disk. Use this to begin a scenario. * + * It doesn't perform any rendering, it merely sets up the system * + * with the proper data. Setting of the right game state will start * + * the scenario running. * + * * + * INPUT: root -- Scenario root filename * + * * + * OUTPUT: none * + * * + * WARNINGS: You must clear out the system variables before calling * + * this function. Use the Clear_Scenario() function. * + * It is assumed that Scenario is set to the current scenario number. * + * * + * HISTORY: * + * 07/22/1991 : Created. * + * 02/03/1992 JLB : Uses house identification. * + *=============================================================================================*/ +bool Read_Scenario(char *root) +{ + CCDebugString ("C&C95 - In Read_Scenario.\n"); + Clear_Scenario(); + ScenarioInit++; + if (Read_Scenario_Ini(root)) { + + Fill_In_Data(); + + + /* + ** SPECIAL CASE: + ** Clear out the tutor flags for scenarios one and two. This is designed + ** so that tutorial message will reappear in scenario two. + */ + if (Scenario < 5) { + TutorFlags[0] = 0L; + TutorFlags[1] = 0L; + } + + } else { + + Fade_Palette_To(GamePalette, FADE_PALETTE_FAST, Call_Back); + Show_Mouse(); + CCMessageBox().Process(TXT_UNABLE_READ_SCENARIO); + Hide_Mouse(); + return(false); + } + ScenarioInit--; + CCDebugString ("C&C95 - Leaving Read_Scenario.\n"); + return(true); +} + + +/*********************************************************************************************** + * Fill_In_Data -- Recreate all data that is not loaded with scenario. * + * * + * This routine is called after the INI file for the scenario has been processed. It will * + * infer the game state from the scenario INI data. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + *=============================================================================================*/ +void Fill_In_Data(void) +{ + /* + ** The basic scenario data load does not contain the full set of + ** game data. We now must fill in the missing pieces. + */ + ScenarioInit++; + + for (int index = 0; index < Buildings.Count(); index++) { + Buildings.Ptr(index)->Update_Buildables(); + } + + Map.Flag_To_Redraw(true); + + /* + ** Bring up the score display on the radar map when starting a multiplayer + ** game. + */ + if (GameToPlay != GAME_NORMAL) { + Map.Player_Names(1); + } + + ScenarioInit--; +} + + +/*********************************************************************************************** + * Clear_Scenario -- Clears all data in preparation for scenario load. * + * * + * This routine will clear out all data specific to a scenario in * + * preparation for a subsequent scenario data load. This will free * + * all units, animations, and icon maps. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/22/1991 : Created. * + * 03/21/1992 JLB : Changed buffer allocations, so changes memset code. * + * 07/13/1995 JLB : End count down moved here. * + *=============================================================================================*/ +void Clear_Scenario(void) +{ + EndCountDown = TICKS_PER_SECOND * 30; + CrateCount = 0; + CrateTimer = 0; + CrateMaker = false; + + /* + ** Call everyone's Init routine, except the Map's; for the Map, only call + ** MapClass::Init, which clears the Cell array. The Display::Init requires + ** a Theater argument, and the theater is not known at this point; also, it + ** would reload MixFiles, which isn't desired. Display::Read_INI calls its + ** own Init, which will Init the entire Map hierarchy. + */ + Map.Init_Clear(); + Score.Init(); + Logic.Init(); + + HouseClass::Init(); + ObjectClass::Init(); + TeamTypeClass::Init(); + TeamClass::Init(); + TriggerClass::Init(); + AircraftClass::Init(); + AnimClass::Init(); + BuildingClass::Init(); + BulletClass::Init(); + InfantryClass::Init(); + OverlayClass::Init(); + SmudgeClass::Init(); + TemplateClass::Init(); + TerrainClass::Init(); + UnitClass::Init(); + + FactoryClass::Init(); + + Base.Init(); + + CurrentObject.Clear(); + + Invalidate_Cached_Icons(); +} + + +/*********************************************************************************************** + * Do_Win -- Display winning congratulations. * + * * + * * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 08/05/1992 JLB : Created. * + * 01/01/1995 JLB : Carries money forward into next scenario. * + *=============================================================================================*/ +void Do_Win(void) +{ + Map.Set_Default_Mouse(MOUSE_NORMAL); + Hide_Mouse(); + + /* + ** If this is a multiplayer game, clear the game's name so we won't respond + ** to game queries any more (in Call_Back) + */ + if (GameToPlay != GAME_NORMAL) { + MPlayerGameName[0] = 0; + } + + /* + ** Determine a cosmetic center point for the text. + */ + int x = Map.TacPixelX + (Lepton_To_Pixel(Map.TacLeptonWidth)/2); + int y = Map.TacPixelY + (Lepton_To_Pixel(Map.TacLeptonHeight)/2) -32; + + /* + ** Announce win to player. + */ + Set_Logic_Page(SeenBuff); +#if !(GERMAN | FRENCH) + Fancy_Text_Print(TXT_MISSION, x, y, WHITE, TBLACK, TPF_CENTER|TPF_VCR); +#endif + Fancy_Text_Print(TXT_SCENARIO_WON, x, y+30, WHITE, TBLACK, TPF_CENTER|TPF_VCR); + CountDownTimer.Set(TIMER_SECOND * 3); + Stop_Speaking(); + Speak(VOX_ACCOMPLISHED); + while (CountDownTimer.Time() || Is_Speaking()) { + Call_Back(); + } + + /* + ** Stop here if this is a multiplayer game. + */ + if (GameToPlay != GAME_NORMAL) { + if (!PlaybackGame) { + MPlayerGamesPlayed++; + Multi_Score_Presentation(); + MPlayerCurGame++; + if (MPlayerCurGame >= MAX_MULTI_GAMES) { + MPlayerCurGame = MAX_MULTI_GAMES - 1; + } + } + GameActive = 0; + Show_Mouse(); + return; + } + + /* + ** Play the winning movie and then start the next scenario. + */ + if (RequiredCD != -2) { + if (Scenario >= 20 && Scenario <60 && GameToPlay == GAME_NORMAL) { + RequiredCD = 2; + } else { + if (Scenario >=60){ + RequiredCD = -1; + }else{ + if (PlayerPtr->Class->House == HOUSE_GOOD) { + RequiredCD = 0; + } else { + RequiredCD = 1; + } + } + } + } + +#ifndef DEMO + Play_Movie(WinMovie); +#endif + + Keyboard::Clear(); + + /* + ** Do the ending screens only if not playing back a recorded game. + */ + if (!PlaybackGame) { + +#ifdef DEMO + + switch (Scenario) { + case 1: + Score.Presentation(); + Scenario = 10; + break; + + case 10: + Score.Presentation(); + Scenario = 6; + break; + + default: + Score.Presentation(); + GDI_Ending(); + GameActive = false; + Show_Mouse(); + return; +// Prog_End(); +// exit(0); +// break; + } + +#else + +#ifdef NEWMENU + if (Scenario >= 20) { + Keyboard::Clear(); + Score.Presentation(); + GameActive = false; + Show_Mouse(); + return; + } +#endif + + if (PlayerPtr->Class->House == HOUSE_BAD && Scenario == 13) { + Nod_Ending(); + //Prog_End(); + //exit(0); + SeenBuff.Clear(); + Show_Mouse(); + GameActive = false; + return; + } + if (PlayerPtr->Class->House == HOUSE_GOOD && Scenario == 15) { + GDI_Ending(); + //Prog_End(); + //exit(0); + SeenBuff.Clear(); + Show_Mouse(); + GameActive = false; + return; + } + + if ( (Special.IsJurassic && AreThingiesEnabled) && Scenario == 5) { + Prog_End(); + exit(0); + } + + if (!Special.IsJurassic || !AreThingiesEnabled) { + Keyboard::Clear(); + InterpolationPaletteChanged = TRUE; + InterpolationPalette = Palette; + Score.Presentation(); + + /* + ** Skip scenario #7 if the airfield was blown up. + */ + if (Scenario == 6 && PlayerPtr->Class->House == HOUSE_GOOD && SabotagedType == STRUCT_AIRSTRIP) { + Scenario++; + } + + Map_Selection(); + } + Scenario++; +#endif + Keyboard::Clear(); + } + + CarryOverMoney = PlayerPtr->Credits; + + int pieces = PlayerPtr->NukePieces; + + /* + ** Generate a new scenario filename + */ + Set_Scenario_Name(ScenarioName, Scenario, ScenPlayer, ScenDir, ScenVar); + Start_Scenario(ScenarioName); + + PlayerPtr->NukePieces = pieces; + + /* + ** Destroy the building that was sabotaged in the previous scenario. This only + ** applies to GDI mission #7. + */ + if (SabotagedType != STRUCT_NONE && Scenario == 7 && PlayerPtr->Class->House == HOUSE_GOOD) { + for (int index = 0; index < Buildings.Count(); index++) { + BuildingClass * building = Buildings.Ptr(index); + + if (building && !building->IsInLimbo && building->House != PlayerPtr && building->Class->Type == SabotagedType) { + building->Limbo(); + delete building; + break; + } + } + + /* + ** Remove the building from the prebuild list. + */ + for (index = 0; index < Base.Nodes.Count(); index++) { + BaseNodeClass * node = Base.Get_Node(index); + + if (node && node->Type == SabotagedType) { + Base.Nodes.Delete(index); + break; + } + } + } + SabotagedType = STRUCT_NONE; + + Map.Render(); + Fade_Palette_To(GamePalette, FADE_PALETTE_FAST, Call_Back); + Show_Mouse(); +} + + +/*********************************************************************************************** + * Do_Lose -- Display losing comments. * + * * + * * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 08/05/1992 JLB : Created. * + *=============================================================================================*/ +void Do_Lose(void) +{ + Map.Set_Default_Mouse(MOUSE_NORMAL); + Hide_Mouse(); + + /* + ** If this is a multiplayer game, clear the game's name so we won't respond + ** to game queries any more (in Call_Back) + */ + if (GameToPlay != GAME_NORMAL) { + MPlayerGameName[0] = 0; + } + + /* + ** Determine a cosmetic center point for the text. + */ + int x = Map.TacPixelX + (Lepton_To_Pixel(Map.TacLeptonWidth)/2); + int y = Map.TacPixelY + (Lepton_To_Pixel(Map.TacLeptonHeight)/2) -32; + + /* + ** Announce win to player. + */ + Set_Logic_Page(SeenBuff); + Fancy_Text_Print(TXT_MISSION, x, y, WHITE, TBLACK, TPF_CENTER|TPF_VCR); + Fancy_Text_Print(TXT_SCENARIO_LOST, x, y+30, WHITE, TBLACK, TPF_CENTER|TPF_VCR); + CountDownTimer.Set(TIMER_SECOND * 3); + Stop_Speaking(); + Speak(VOX_FAIL); + while (CountDownTimer.Time() || Is_Speaking()) { + Call_Back(); + } + +#ifdef OBSOLETE + if (Debug_Play_Map) { + Go_Editor(true); + Show_Mouse(); + return; + } +#endif + + /* + ** Stop here if this is a multiplayer game. + */ + if (GameToPlay != GAME_NORMAL) { + if (!PlaybackGame) { + MPlayerGamesPlayed++; + Multi_Score_Presentation(); + MPlayerCurGame++; + if (MPlayerCurGame >= MAX_MULTI_GAMES) { + MPlayerCurGame = MAX_MULTI_GAMES - 1; + } + } + GameActive = 0; + Show_Mouse(); + return; + } + + Play_Movie(LoseMovie); + + /* + ** Start same scenario again + */ + Set_Palette(GamePalette); + Show_Mouse(); + if (!PlaybackGame && !CCMessageBox().Process(TXT_TO_REPLAY, TXT_YES, TXT_NO)) { + Hide_Mouse(); + Keyboard::Clear(); + Start_Scenario(ScenarioName, false); + Map.Render(); + } else { + Hide_Mouse(); + GameActive = 0; + } + + Fade_Palette_To(GamePalette, FADE_PALETTE_FAST, Call_Back); + Show_Mouse(); +} + + +/*********************************************************************************************** + * Do_Restart -- Handle the restart mission process. * + * * + * This routine is called in the main game loop when the mission must be restarted. This * + * routine will throw away the current game and reload the appropriate mission. The * + * game will "resume" at the start of the mission. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/24/1995 JLB : Created. * + *=============================================================================================*/ +void Do_Restart(void) +{ + bool hidden = Get_Mouse_State(); + + if (hidden) Show_Mouse(); + CCMessageBox().Process(TXT_RESTARTING, TXT_NONE); + Map.Set_Default_Mouse(MOUSE_NORMAL); + Keyboard::Clear(); + Start_Scenario(ScenarioName, false); + if (hidden) Hide_Mouse(); + Keyboard::Clear(); + Map.Render(); +} + + +/*********************************************************************************************** + * Restate_Mission -- Handles restating the mission objective. * + * * + * This routine will display the mission objective (as text). It will also give the * + * option to redisplay the mission briefing video. * + * * + * INPUT: name -- The scenario name. This is the unique identifier for the scenario * + * briefing text as it appears in the "MISSION.INI" file. * + * * + * OUTPUT: Returns the response from the dialog. This will either be 1 if the video was * + * requested, or 0 if the return to game options button was selected. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/23/1995 JLB : Created. * + * 08/06/1995 JLB : Uses preloaded briefing text. * + *=============================================================================================*/ +bool Restate_Mission(char const * name, int button1, int button2) +{ + if (name) { +#ifdef JAPANESE + char fname[14]; + strcpy(fname, name); + strcat(fname,".CPS"); + + if(CCFileClass(fname).Is_Available()) { + CCMessageBox box(TXT_NONE, true); + return(box.Process(fname, button1, button2)); + } +#else + /* + ** Make sure that if there is no briefing movie, that the briefing text is + ** the only option available. + */ + bool brief = true; +#ifdef NEWMENU + char buffer[25]; + char buffer1[25]; + sprintf(buffer, "%s.VQA", BriefMovie); + sprintf(buffer1, "%s.VQA", ActionMovie); + CCFileClass file1(buffer); + CCFileClass file2(buffer1); + if (!file1.Is_Available() && !file2.Is_Available()) { + button1 = TXT_OK; + button2 = TXT_NONE; + brief = false; + } +#endif + + /* + ** If mission object text was found, then display it. + */ + if (strlen(BriefingText)) { + static char _buff[512]; + + strcpy(_buff, BriefingText); +// strcpy(_ShapeBuffer, BriefingText); + + bool hidden = Get_Mouse_State(); + if (hidden) Show_Mouse(); + + if (CCMessageBox(TXT_OBJECTIVE).Process(_buff, button1, button2)) { + if (hidden) Hide_Mouse(); + return(true); + } + if (hidden) Hide_Mouse(); + if (!brief) return(true); + return(false); + } +#endif + } + return(false); +} diff --git a/SCORE.CPP b/SCORE.CPP new file mode 100644 index 0000000..e10c2e0 --- /dev/null +++ b/SCORE.CPP @@ -0,0 +1,2316 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\score.cpv 2.17 16 Oct 1995 16:49:54 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SCORE.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 19, 1994 * + * * + * Last Update : May 3, 1995 [BWG] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Call_Back_Delay -- Combines Call_Back() and Delay() functions * + * Draw_Bar_Graphs -- Draw "Casualties" bar graphs * + * Draw_InfantryMan -- Draw one guy in score screen, update animation * + * Draw_Infantrymen -- Draw all the guys on the score screen * + * New_Infantry_Anim -- Start up a new animation for one of the infantrymen * + * ScoreClass::Count_Up_Print -- Prints a number (up to its max) into a string, cleanly * + * ScoreClass::Delay -- Pauses waiting for keypress. * + * ScoreClass::DO_GDI_GRAPH -- Show # of people or buildings killed on GDI score screen * + * ScoreClass::Presentation -- Main routine to display score screen. * + * ScoreClass::Print_Graph_Title -- Prints title on score screen. * + * ScoreClass::Print_Minutes -- Print out hours/minutes up to max * + * ScoreClass::Pulse_Bar_Graph -- Pulses the bargraph color. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "textblit.h" +#define SCORETEXT_X 184 +#define SCORETEXT_Y 8 +#define CASUALTY_Y 88 +#define BUILDING_X 256 +#define BUILDING_Y 128 +#define BARGRAPH_X 266 +#define MAX_BAR_X 318 // max possible is 319 because of bar's right shadow +#define SIZEGBAR 119 +#define HALLFAME_X 19 +#define HALLFAME_Y 120 + +#define MULTISCOREX 30 + +#define TEDIT_FAME 1 +#define NUMINFANTRYMEN 15 +#define NUMFAMENAMES 7 +#define MAX_FAMENAME_LENGTH 12 + +struct InfantryAnim { + int xpos; + int ypos; + void const *shapefile; + void const *remap; + int anim; + int stage; + char delay; + InfantryTypeClass const *Class; +} InfantryMan[NUMINFANTRYMEN]; +void Draw_InfantryMen(void); +void Draw_InfantryMan(int index); +void New_Infantry_Anim(int index, int anim); +void Draw_Bar_Graphs(int i, int gkilled, int nkilled, int ckilled); +void Animate_Cursor(int pos, int ypos); +void Animate_Score_Objs(void); +void Cycle_Wait_Click(void); +int ScorePass; + +void const * Beepy6; +int ControlQ; // cheat key to skip past score/mapsel screens +bool StillUpdating; + +GraphicBufferClass *PseudoSeenBuff; +GraphicBufferClass *TextPrintBuffer; + +#ifdef WRITE_LBM +PUBLIC bool CCWrite_LBM_File(CCFileClass &lbmhandle, BufferClass& buff, short bitplanes, unsigned char *palette); +#endif + +unsigned char RemapCiv[256]={ + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0xD0,0x18,0x19,0xD1,0xD2,0xD3,0xD4,0x1E,0xD5, + 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, + 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F, + 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, + 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0xD6,0xD7,0xD8,0xD9,0x7E,0xDA, + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xDB,0xDC,0xDD,0xDE,0xDB,0xDC,0xDD,0xDE,0xDF, + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}; + +unsigned char const ScoreRemapGrey[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // 0..15 + 16, 17, 18, 19, 20,176, 22,208, 24, 25,209,210,211,212, 30,213, // 16..31 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 32..47 + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // 48..63 + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, // 64..79 + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, // 80..95 + 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, // 96..111 + 112,113,114,115,116,117,118,119,120,121,214,215,216,217,149,218, // 112..127 + 128,129,130,131,132,133,134,135,136,137,138,108,140,141,142,143, // 128..143 + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, // 144..159 + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, // 160..175 + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, // 176..191 + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, // 192..207 + 208,209,210,211,212,213,214,219,220,221,222,219,220,221,222,223, // 208..223 + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, // 224..239 + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 // 240..255 +}; + +unsigned char const ScoreRemapYellow[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // 0..15 + 16, 17, 18, 19, 20,176, 22,208, 24, 25,209,210,211,212, 30,213, // 16..31 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 32..47 + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // 48..63 + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, // 64..79 + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, // 80..95 + 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, // 96..111 + 112,113,114,115,116,117,118,119,120,121,214,215,216,217,149,218, // 112..127 + 128,129,130,131,132,133,134,135,136,137,138,108,140,141,142,143, // 128..143 + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, // 144..159 + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, // 160..175 + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, // 176..191 + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, // 192..207 + 208,209,210,211,212,213,214,219,220,221,222,219,220,221,222,223, // 208..223 + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, // 224..239 + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 // 240..255 +}; + +unsigned char const ScoreRemapBldg[256]={ + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, + 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, + 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F, + 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, + 0x70,0x71,0xCE,0xC5,0x49,0x48,0x47,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F, + 0x80,0x81,0xB8,0x83,0x7C,0x7A,0x76,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, + 0x74,0x91,0x92,0x93,0x94,0x95,0xB5,0x97,0x98,0xCF,0x4B,0x7F,0x9C,0x9D,0x9E,0x9F, + 0xA0,0xA1,0xA2,0x83,0x79,0xA5,0xA6,0xA7,0x43,0x99,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xBB,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF +}; +unsigned char const ScoreRemapFBall[256]={ + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F, + 0xB0,0xE5,0x82,0xE4,0xE3,0xE2,0xB1,0xD0,0xE1,0xE0,0xD1,0xD2,0xD3,0xD4,0xDF,0xD5, + 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0xE6,0xE7,0xA0,0xE8,0x2E,0x2F, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, + 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F, + 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, + 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F, + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF +}; + +TextBlitClass BlitList; + + +char *ScreenNames[2]={"S-GDIIN2.WSA","SCRSCN1.WSA"}; + +//extern short StreamLowImpact; + +struct Fame { + char name[MAX_FAMENAME_LENGTH]; + int score; + int level; +}; + +ScoreAnimClass *ScoreObjs[MAXSCOREOBJS]; + + +ScoreAnimClass::ScoreAnimClass(int x, int y, void const * data) +{ + BlitList.Add (x*2, y*2, x*2, y*2, 2* String_Pixel_Width ( (char*)data ) , 16); + XPos = x; + YPos = y; + Timer.Set(0); + Timer.Start(); + DataPtr = data; +} + + +ScoreTimeClass::ScoreTimeClass(int xpos, int ypos, void const * data, int max, int timer) : + ScoreAnimClass(xpos, ypos, data) +{ + Stage = 0; + MaxStage = max; + TimerReset = timer; +} + +void ScoreTimeClass::Update(void) +{ + GraphicViewPortClass *oldpage; + if (!Timer.Time()) { + Timer.Set(TimerReset); + if (++Stage >= MaxStage) Stage = 0; + oldpage = LogicPage; + Set_Logic_Page(PseudoSeenBuff); + CC_Draw_Shape(DataPtr, Stage, XPos, YPos, WINDOW_MAIN, SHAPE_WIN_REL, 0, 0); + Set_Logic_Page(oldpage); + } +} + +ScoreCredsClass::ScoreCredsClass(int xpos, int ypos, void const * data, int max, int timer) : + ScoreAnimClass(xpos, ypos, data) +{ + Stage = 0; + MaxStage = max; + TimerReset = timer; + Clock1 = MixFileClass::Retrieve("CLOCK1.AUD"); + CashTurn = MixFileClass::Retrieve("CASHTURN.AUD"); +} + + +void ScoreCredsClass::Update(void) +{ + GraphicViewPortClass *oldpage; + if (!Timer.Time()) { + Timer.Set(TimerReset); + if (++Stage >= MaxStage) Stage = 0; + oldpage = LogicPage; + Set_Logic_Page(PseudoSeenBuff); + if (Stage <22) { + Play_Sample(Clock1, 255, Options.Normalize_Sound(70)); + } else { + if (Stage==24) { + Play_Sample(CashTurn, 255, Options.Normalize_Sound(70)); + } + } + CC_Draw_Shape(DataPtr,Stage, XPos, YPos, WINDOW_MAIN,SHAPE_WIN_REL, 0, 0); + Set_Logic_Page(oldpage); + } +} + + +ScorePrintClass::ScorePrintClass(int string, int xpos, int ypos, void const * palette, int background) : + ScoreAnimClass(xpos, ypos, Text_String(string)) +{ + Background = background; + PrimaryPalette = palette; + Stage = 0; +} + + +ScorePrintClass::ScorePrintClass(void const * string, int xpos, int ypos, void const * palette, int background) : + ScoreAnimClass(xpos, ypos, string) +{ + Background = background; + PrimaryPalette = palette; + Stage = 0; +} + + +void ScorePrintClass::Update(void) +{ + static char localstr[2]={0,0}; + static char _whitepal[]={0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F}; + + if (Stage && (((char *)DataPtr)[Stage-1]==0) ) { + for (int i = 0; i < MAXSCOREOBJS; i++) { + if (ScoreObjs[i] == this) { + ScoreObjs[i] = 0; + } + } + BlitList.Add (XPos*2, YPos*2, XPos*2, YPos*2,(Stage*6)+14, 8*2); + delete this; + return; + } + + StillUpdating = true; + + if (!Timer.Time()) { + Timer.Set(1); + + int pos = XPos+(Stage*6); + if (Stage) { + + localstr[0]=((char *)DataPtr)[Stage-1]; + + /* + ** Clear out the white letter overlay + */ + static char const _blackpal[]={BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK}; + Set_Font_Palette(_blackpal); + TextPrintBuffer->Print(localstr, 2*(pos-6),2*(YPos-1), TBLACK, TBLACK); + TextPrintBuffer->Print(localstr, 2*(pos-6),2*(YPos+1), TBLACK, TBLACK); + TextPrintBuffer->Print(localstr, 2*(pos-6+1),2*(YPos), TBLACK, TBLACK); + + Set_Font_Palette(PrimaryPalette); + TextPrintBuffer->Print(localstr, 2*(pos-6),2*YPos, TBLACK, TBLACK); + } + if (((char *)DataPtr)[Stage]) { + localstr[0]=((char *)DataPtr)[Stage]; + Set_Font_Palette(_whitepal); + TextPrintBuffer->Print(localstr, pos*2, 2*(YPos-1), TBLACK, TBLACK); + TextPrintBuffer->Print(localstr, pos*2, 2*(YPos+1), TBLACK, TBLACK); + TextPrintBuffer->Print(localstr, (pos+1)*2,2*YPos , TBLACK, TBLACK); + } + Stage++; + } +} + + + + + + + + + + + +MultiStagePrintClass::MultiStagePrintClass(int string, int xpos, int ypos, void const * palette, int background) : + ScoreAnimClass(xpos, ypos, Text_String(string)) +{ + Background = background; + PrimaryPalette = palette; + Stage = 0; +} + + +MultiStagePrintClass::MultiStagePrintClass(void const * string, int xpos, int ypos, void const * palette, int background) : + ScoreAnimClass(xpos, ypos, string) +{ + Background = background; + PrimaryPalette = palette; + Stage = 0; +} + + +void MultiStagePrintClass::Update(void) +{ + static char localstr[2]={0,0}; + static char _whitepal[]={0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F}; + + if (Stage && (((char *)DataPtr)[Stage-1]==0) ) { + for (int i = 0; i < MAXSCOREOBJS; i++) { + if (ScoreObjs[i] == this) { + ScoreObjs[i] = 0; + } + } + BlitList.Add (XPos*2, YPos*2, XPos*2, YPos*2,(Stage*6)+14, 8*2); + delete this; + return; + } + + StillUpdating = true; + + if (!Timer.Time()) { + Timer.Set(1); + + /* + ** Do 10 stages at once + */ + for (int wibble = 0 ; wibble < 10 ; wibble ++){ + + int pos = XPos+(Stage*6); + if (Stage) { + + localstr[0]=((char *)DataPtr)[Stage-1]; + + /* + ** Clear out the white letter overlay + */ + static char const _blackpal[]={BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK,BLACK}; + Set_Font_Palette(_blackpal); + TextPrintBuffer->Print(localstr, 2*(pos-6),2*(YPos-1), TBLACK, TBLACK); + TextPrintBuffer->Print(localstr, 2*(pos-6),2*(YPos+1), TBLACK, TBLACK); + TextPrintBuffer->Print(localstr, 2*(pos-6+1),2*(YPos), TBLACK, TBLACK); + + Set_Font_Palette(PrimaryPalette); + TextPrintBuffer->Print(localstr, 2*(pos-6),2*YPos, TBLACK, TBLACK); + } + if (((char *)DataPtr)[Stage]) { + localstr[0]=((char *)DataPtr)[Stage]; + Set_Font_Palette(_whitepal); + TextPrintBuffer->Print(localstr, pos*2, 2*(YPos-1), TBLACK, TBLACK); + TextPrintBuffer->Print(localstr, pos*2, 2*(YPos+1), TBLACK, TBLACK); + TextPrintBuffer->Print(localstr, (pos+1)*2,2*YPos , TBLACK, TBLACK); + } + Stage++; + + if ( ( (char *) DataPtr) [Stage-1] == 0 ) break; + } + } +} + + + + + + +ScoreScaleClass::ScoreScaleClass(void const * string, int xpos, int ypos, char const palette[]) : + ScoreAnimClass(xpos, ypos, string) +{ + Palette = &palette[0]; + Stage = 5; +} + + +void ScoreScaleClass::Update(void) +{ + static int _destx[]={0,80,107,134,180,228}; + static int _destw[]={6,20, 30, 40, 60, 80}; + + /* + ** Restore the background for the scaled-up letter + */ + if (!Timer.Time()) { + Timer.Set(1); + if (Stage != 5) { + TextPrintBuffer->Blit(HidPage, _destx[Stage+1]*2, YPos*2, _destx[Stage+1]*2, YPos*2, _destw[Stage+1]*2, _destw[Stage+1]*2); + //SysMemPage.Blit(*PseudoSeenBuff, _destx[Stage+1], YPos, _destx[Stage+1], YPos, _destw[Stage+1], _destw[Stage+1]); + } + if (Stage) { + Set_Font_Palette(Palette); + TextPrintBuffer->Fill_Rect(0,0, 7*2,7*2, TBLACK); + TextPrintBuffer->Print((char *)DataPtr, 0,0, TBLACK, TBLACK); + TextPrintBuffer->Scale(HidPage, 0,0, _destx[Stage]*2, YPos*2, 5*2,5*2, _destw[Stage]*2, _destw[Stage]*2, true); + + //SysMemPage.Fill_Rect(0,0, 7,7, TBLACK); + //SysMemPage.Print((char *)DataPtr, 0,0, TBLACK, TBLACK); + //SysMemPage.Scale(*PseudoSeenBuff, 0,0, _destx[Stage], YPos, 5,5, _destw[Stage], _destw[Stage], true); + Stage--; + } else { + Set_Font_Palette(Palette); + for (int i = 0; i < MAXSCOREOBJS; i++) { + if (ScoreObjs[i]==this) ScoreObjs[i] = 0; + } + TextPrintBuffer->Print((char *)DataPtr, XPos*2,YPos*2, TBLACK, TBLACK); + //TextPrintBuffer->Blit(HidPage, XPos*2, YPos*2, XPos*2, YPos*2,2*6,2*6); + //BlitList.Add (XPos, YPos, XPos, YPos, 6,6); + + //SysMemPage.Print((char *)DataPtr, XPos,YPos, TBLACK, TBLACK); + //SysMemPage.Blit(*PseudoSeenBuff, XPos,YPos, XPos, YPos, 6,6); + delete this; + return; + } + } +} + +int Alloc_Object(ScoreAnimClass *obj) +{ + int i,ret; + + for (i = ret = 0; i < MAXSCOREOBJS; i++) { + if (!ScoreObjs[i]) { + ScoreObjs[i] = obj; + ret = i; + break; + } + } + return(ret); +} + + + + + +TextBlitClass::TextBlitClass (void) +{ + Clear(); +} + + + +void TextBlitClass::Add (int x, int y, int dx, int dy, int w, int h) +{ + if ( Count < MAX_ENTRIES ){ + + BlitListo [Count].SourceX = x; + BlitListo [Count].SourceY = y; + BlitListo [Count].DestX = dx; + BlitListo [Count].DestY = dy; + BlitListo [Count].Width = w; + BlitListo [Count].Height = h; + + Count++; + } +} + + +void TextBlitClass::Clear(void) +{ + Count = 0; +} + + + + +void TextBlitClass::Update(void) +{ + if (TextPrintBuffer){ + + if (HidPage.Lock()){ + + for (int i=0 ; iBlit (HidPage, BlitListo[i].SourceX, + BlitListo[i].SourceY, + BlitListo[i].DestX, + BlitListo[i].DestY, + BlitListo[i].Width, + BlitListo[i].Height, + true); + } + + HidPage.Unlock(); + } + } +} + + + +void Disable_Uncompressed_Shapes (void); +void Enable_Uncompressed_Shapes (void); + +/*********************************************************************************************** + * ScoreClass::Presentation -- Main routine to display score screen. * + * * + * This is the main routine that displays the score screen graphics. * + * It gets called at the end of each scenario and is used to present * + * the results and a rating of the player's battle. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/02/1994 : Created. * + *=============================================================================================*/ +void ScoreClass::Presentation(void) +{ + //static char const _redpal[]={0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x21,0x2F}; + static char const _redpal[]={0x20,0x22,0x24,0x26,0x28,0x28,0x28,0x28,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x21,0x2F}; + static char const _greenpal[]={0x10,0x12,0x14,0x16,0x18,0x18,0x18,0x18,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x10,0x1F}; + static char const _bluepal[]={0x60,0x62,0x64,0x66,0x68,0x68,0x68,0x68,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x61,0x6F}; + //static char const _bluepal[]={0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x61,0x6F}; + static char const _yellowpal[]={0x0,0x0,0xEC,0x0,0xEB,0x0,0xEA,0x0,0xE9,0x0,0x0,0x0,0x0,0x0,0xED,0x0}; + static int const _casuax[2]={144,146}; + static int const _casuay[2]={ 78, 90}; + static int const _gditxx[2]={150,224}; + static int const _gditxy[2]={ 90, 90}; + static int const _nodtxx[2]={150,224}; + static int const _nodtxy[2]={102,102}; +// static int _bargrx[2]={297,SCORETEXT_X+64}; +// static int _bargry[2]={ 90,CASUALTY_Y + 2}; + static int const _bldggy[2]={138,128}; + static int const _bldgny[2]={150,140}; + +// int gdikilled, nodkilled, civkilled, max, i, k, shapenum; + int i; + int max; + void const * yellowptr; + void const * redptr; + CCFileClass file(FAME_FILE_NAME); + struct Fame hallfame[NUMFAMENAMES]; + void *anim, *oldfont; + int oldfontxspacing = FontXSpacing; + int house = PlayerPtr->Class->House; // 0 or 1 + char inter_pal[15]; + + /* + ** Choose an appropriate palette file for the interpolation + */ + if (house == HOUSE_GOOD){ + sprintf(inter_pal,"SCORPAL1.PAL"); + }else{ + sprintf(inter_pal,"SNODPAL1.PAL"); + } + + + if (Special.IsJurassic && AreThingiesEnabled) return; + + PseudoSeenBuff = new GraphicBufferClass(320,200,(void*)NULL); + TextPrintBuffer = new GraphicBufferClass(SeenBuff.Get_Width(), SeenBuff.Get_Height(), (void*)NULL); + TextPrintBuffer->Clear(); + BlitList.Clear(); + Disable_Uncompressed_Shapes (); + + ControlQ = 0; + FontXSpacing = 0; + Map.Override_Mouse_Shape(MOUSE_NORMAL); + Theme.Queue_Song(THEME_WIN1); + + VisiblePage.Clear(); + PseudoSeenBuff->Clear(); + SysMemPage.Clear(); + WWMouse->Erase_Mouse(&HidPage, TRUE); + HiddenPage.Clear(); + Set_Palette(BlackPalette); + + Set_Logic_Page(SysMemPage); + + void const * country4 = MixFileClass::Retrieve("COUNTRY4.AUD"); + void const * sfx4 = MixFileClass::Retrieve("SFX4.AUD"); + Beepy6 = MixFileClass::Retrieve("BEEPY6.AUD"); + + /* + ** Load the background for the score screen + */ + anim = Open_Animation(ScreenNames[house],NULL,0L,(WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE),Palette); + + unsigned minutes = (unsigned)((ElapsedTime / (long)TIMER_MINUTE))+1; + + /* + ** Determine leadership rating. + */ + unsigned leadership = 0; + for (int index = 0; index < Logic.Count(); index++) { + ObjectClass * object = Logic[index]; + if (object->Owner() == house) { + leadership++; + } + } + + HouseClass *houses[3]; + for (index = 0; index < 3; index++) { + houses[index] =(HouseClass::As_Pointer((HousesType)(HOUSE_GOOD+index))); + } + + GKilled = (HouseClass::As_Pointer(HOUSE_GOOD))->UnitsLost; + NKilled = (HouseClass::As_Pointer(HOUSE_BAD))->UnitsLost; + CKilled = (HouseClass::As_Pointer(HOUSE_NEUTRAL))->UnitsLost; + GBKilled = (HouseClass::As_Pointer(HOUSE_GOOD))->BuildingsLost; + NBKilled = (HouseClass::As_Pointer(HOUSE_BAD))->BuildingsLost; + CBKilled = (HouseClass::As_Pointer(HOUSE_NEUTRAL))->BuildingsLost; + + /* + ** New - ST 6/12/96 2:40PM + */ + GHarvested = (HouseClass::As_Pointer(HOUSE_GOOD))->HarvestedCredits; + NHarvested = (HouseClass::As_Pointer(HOUSE_BAD))->HarvestedCredits; + + if (!leadership) leadership++; + leadership = Cardinal_To_Fixed(GKilled+GBKilled+leadership, leadership); + leadership = Fixed_To_Cardinal(100, leadership); + if (leadership > 100) leadership = 100; + + /* + ** Determine efficiency rating. + */ + int gharv = GHarvested; + int init = PlayerPtr->InitialCredits; + int cred = PlayerPtr->Available_Money(); + + unsigned efficiency = Cardinal_To_Fixed( (house == HOUSE_GOOD ? GHarvested : NHarvested) + (unsigned)PlayerPtr->InitialCredits+1, (unsigned)PlayerPtr->Available_Money()+1); + if (!efficiency) efficiency++; + efficiency = Fixed_To_Cardinal(100, efficiency); + + if (efficiency > 100) efficiency = 100; + /* + ** Calculate total score + */ + long total = ((leadership * 40) + (4600) + (efficiency * 14))/100; + if (!total) total++; + total *= (BuildLevel+1); + +// Load up the shapes for the Nod score screen + if (house == HOUSE_GOOD) { + yellowptr = MixFileClass::Retrieve("BAR3YLW.SHP"); + redptr = MixFileClass::Retrieve("BAR3RED.SHP"); + } + +/* Change to the six-point font for Text_Print */ + oldfont = Set_Font(ScoreFontPtr); + Call_Back(); + +/* --- Now display the background animation --- */ + Hide_Mouse(); + Animate_Frame(anim, SysMemPage, 1); + SysMemPage.Blit(*PseudoSeenBuff); + Increase_Palette_Luminance (Palette , 30,30,30,63); + InterpolationPalette = Palette; + InterpolationPaletteChanged = TRUE; + Read_Interpolation_Palette(inter_pal); + Interpolate_2X_Scale( PseudoSeenBuff , &SeenBuff , inter_pal); + Fade_Palette_To(Palette, FADE_PALETTE_FAST, Call_Back); + + Play_Sample(country4, 255, Options.Normalize_Sound(90)); + + int frame = 1; + StreamLowImpact = true; + while (frame < Get_Animation_Frame_Count(anim)) { + Animate_Frame(anim, *PseudoSeenBuff, frame++); + ////////////////Interpolate_2X_Scale( PseudoSeenBuff , &SeenBuff , NULL); + Call_Back_Delay(2); + } + StreamLowImpact = false; + Call_Back(); + Close_Animation(anim); + + /* + ** Background's up, so now load various shapes and animations + */ + void const * timeshape = MixFileClass::Retrieve("TIME.SHP"); + ScoreObjs[0] = new ScoreTimeClass(233, 2, timeshape, 30, 4); + + void const * hiscore1shape = MixFileClass::Retrieve("HISCORE1.SHP"); + void const * hiscore2shape = MixFileClass::Retrieve("HISCORE2.SHP"); + ScoreObjs[1] = new ScoreTimeClass(4, 97, hiscore1shape, 10, 4); + ScoreObjs[2] = new ScoreTimeClass(8, 172, hiscore2shape, 10, 4); + + /* Now display the stuff */ + PseudoSeenBuff->Blit(SysMemPage); + + + if (house == HOUSE_BAD) { + + /* + ** load the logo + */ + void const * logoptr = MixFileClass::Retrieve("LOGOS.SHP"); + CC_Draw_Shape(logoptr, 1, 0, 0, WINDOW_MAIN, SHAPE_WIN_REL, 0, 0); + + Bit_It_In_Scale(0,0, 128,104-16, &SysMemPage, PseudoSeenBuff, &SeenBuff , 1); + } + + Set_Logic_Page(PseudoSeenBuff); + +#ifdef FRENCH + Alloc_Object(new ScorePrintClass(TXT_SCORE_TIME, 200, 3,_greenpal)); +#else + Alloc_Object(new ScorePrintClass(TXT_SCORE_TIME, 206, 3,_greenpal)); +#endif + Alloc_Object(new ScorePrintClass(TXT_SCORE_LEAD, 182, 26,_greenpal)); + Alloc_Object(new ScorePrintClass(TXT_SCORE_EFFI, 182, 38,_greenpal)); + Alloc_Object(new ScorePrintClass(TXT_SCORE_TOTA, 182, 50,_greenpal)); + Play_Sample(sfx4, 255, Options.Normalize_Sound(120)); + Call_Back_Delay(13); + + max = MAX((long)leadership, (long)efficiency); + int scorecounter = 0; + Keyboard::Clear(); + + BlitList.Add (264*2, 26*2, 264*2, 26*2, 4*12 , 12); + BlitList.Add (264*2, 38*2, 264*2, 38*2, 4*12 , 12); + BlitList.Add (264*2, 50*2, 264*2, 50*2, 4*12 , 12); + BlitList.Add (275*2, 9*2, 275*2, 9*2, 64, 12 ); //Minutes + for (i = 0; i <= 160; i++) { + Set_Font_Palette(_greenpal); + Count_Up_Print("%3d%%", i, leadership, 264, 26); + if (i>=30) Count_Up_Print("%3d%%", i-30, efficiency, 264, 38); + if (i>=60) { + Count_Up_Print("%3d",scorecounter, total, 264, 50); + scorecounter += total/100; + } + Print_Minutes(minutes); + Call_Back_Delay(1); + Play_Sample(Beepy6, 255, Options.Normalize_Sound(60)); + if (Check_Key() && i < (max-5) ) { + i=158; + Keyboard::Clear(); + } + } + Count_Up_Print("%3d", total, total, 264, 50); + + Call_Back_Delay(60); + + if (house == HOUSE_BAD) Show_Credits(house, _greenpal); + + Call_Back_Delay(60); + + /* + ** Show stats on # of units killed + */ + Set_Logic_Page(*PseudoSeenBuff); + Play_Sample(sfx4, 255, Options.Normalize_Sound(90)); + Alloc_Object(new ScorePrintClass(TXT_SCORE_CASU, _casuax[house], _casuay[house],_redpal)); + Call_Back_Delay(9); + if (house == HOUSE_BAD) { + Alloc_Object(new ScorePrintClass(TXT_SCORE_NEUT, 200, 114,_redpal)); + Call_Back_Delay(4); + } + + Alloc_Object(new ScorePrintClass(TXT_SCORE_GDI, _gditxx[house], _gditxy[house],_redpal)); + Alloc_Object(new ScorePrintClass(TXT_SCORE_NOD, _nodtxx[house], _nodtxy[house],_redpal)); + Call_Back_Delay(6); + + Set_Font_Palette(_redpal); + if (house == HOUSE_BAD) { + Do_Nod_Casualties_Graph(); + } else { + Do_GDI_Graph(yellowptr, redptr, GKilled + CKilled, NKilled, 88); + } + + Set_Logic_Page(*PseudoSeenBuff); + + /* + ** Print out stats on buildings destroyed + */ + Play_Sample(sfx4, 255, Options.Normalize_Sound(90)); + if (house == HOUSE_GOOD) { + Alloc_Object(new ScorePrintClass(TXT_SCORE_BUIL, 144, 126,_greenpal)); + Call_Back_Delay(9); + } else { + Alloc_Object(new ScorePrintClass(TXT_SCORE_BUIL1, 146, 128,_greenpal)); + Alloc_Object(new ScorePrintClass(TXT_SCORE_BUIL2, 146, 136,_greenpal)); + Call_Back_Delay(9); + Alloc_Object(new ScorePrintClass(TXT_SCORE_NEUT, 200, 152,_greenpal)); + Call_Back_Delay(4); + } + Alloc_Object(new ScorePrintClass(TXT_SCORE_GDI, _gditxx[house], _bldggy[house],_greenpal)); + Alloc_Object(new ScorePrintClass(TXT_SCORE_NOD, _gditxx[house], _bldgny[house],_greenpal)); + Call_Back_Delay(7); + + if (house==HOUSE_BAD) { + Call_Back_Delay(6); + Set_Font_Palette(_greenpal); + Do_Nod_Buildings_Graph(); + } else { + Do_GDI_Graph(yellowptr, redptr, GBKilled + CBKilled, NBKilled, 136); + } + + // Wait for text printing to complete + while (StillUpdating){ + Call_Back_Delay(1); + } + + if (Keyboard::Check()) Keyboard::Clear(); + + if (house == HOUSE_GOOD) Show_Credits(house, _greenpal); + + /* + ** Hall of fame display and processing + */ + Play_Sample(sfx4, 255, Options.Normalize_Sound(90)); + Alloc_Object(new ScorePrintClass(TXT_SCORE_TOP, 28, 110,_bluepal)); + Call_Back_Delay(9); + + /* + ** First check for the existence of the file, and if there isn't one, + ** make a new one filled with blanks. + */ + if (!file.Is_Available()) { + + // hall of fame doesn't exist, so blank it out & write it + file.Open(WRITE); + + for (i = 0; i < NUMFAMENAMES; i++) { + hallfame[i].name[0] = + hallfame[i].score = + hallfame[i].level = 0; + file.Write(&hallfame[i], sizeof(struct Fame)); + } + + file.Close(); + } + + file.Open(READ); + for (i = 0; i < NUMFAMENAMES; i++) { + file.Read(&hallfame[i], sizeof(struct Fame)); + } + file.Close(); + + /* + ** If the player's score is good enough to bump someone off the list, + ** remove their data, move everyone down a notch, and set index = where + ** their info goes + */ + if(hallfame[NUMFAMENAMES-1].score >= total) + hallfame[NUMFAMENAMES-1].score = 0; + for (index = 0; index < NUMFAMENAMES; index++) { + if (total > hallfame[index].score) { + if (index < (NUMFAMENAMES-1)) for (i = (NUMFAMENAMES-1); i > index; i--) hallfame[i] = hallfame[i-1]; + hallfame[index].score = total; + hallfame[index].level = Scenario; +// hallfame[index].level = BuildLevel; + //hallfame[index].name[0] = 0; // blank out the name + memset (hallfame[index].name, ' ', sizeof (hallfame[index].name) -1); + hallfame[index].name[sizeof (hallfame[index].name)-1] = 0; + break; + } + } + + /* + ** Now display the hall of fame + */ + Set_Logic_Page(*PseudoSeenBuff); + for (i = 0; i < NUMFAMENAMES; i++) { + Alloc_Object(new ScorePrintClass(hallfame[i].name, HALLFAME_X, HALLFAME_Y + (i*8), _bluepal)); + if (hallfame[i].score) { + char *str = (char *)(SysMemPage.Get_Buffer()) + i*32; + sprintf(str,"%d", hallfame[i].score); + Alloc_Object(new ScorePrintClass(str, HALLFAME_X+(6*15), HALLFAME_Y + (i*8), _bluepal, BLACK)); + if (hallfame[i].level < 20) { + sprintf(str+16,"%d", hallfame[i].level); + } else { + strcpy(str+16, "**"); + } + Alloc_Object(new ScorePrintClass(str+16, HALLFAME_X+(6*12), HALLFAME_Y + (i*8), _bluepal, BLACK)); + Call_Back_Delay(13); + } + } + + // Wait for text printing to complete + while (StillUpdating){ + Call_Back_Delay(1); + } + + /* + ** If the player's on the hall of fame, have him enter his name now + */ + Keyboard::Clear(); + if (index < NUMFAMENAMES) { + Input_Name(hallfame[index].name, HALLFAME_X, HALLFAME_Y + (index*8), _bluepal); + + file.Open(WRITE); + for (i = 0; i < NUMFAMENAMES; i++) { + file.Write(&hallfame[i], sizeof(struct Fame)); + } + file.Close(); + } else { +#ifdef FRENCH + Alloc_Object(new ScorePrintClass(TXT_MAP_CLICK2, 145, 190, _yellowpal)); +#else + Alloc_Object(new ScorePrintClass(TXT_MAP_CLICK2, 149, 190, _yellowpal)); +#endif + Cycle_Wait_Click(); + } + +#ifdef WRITE_LBM + file.Open("e:scorscrn.lbm",WRITE); + SeenBuff.Blit(SysMemPage,0,0, 0,0, 320,200); + CCWrite_LBM_File(file, SysMemPage, 8, Palette); + file.Close(); +#endif + + Keyboard::Clear(); + +/* get rid of all the animating objects */ + for (i = 0; i < MAXSCOREOBJS; i++) if (ScoreObjs[i]) { + delete ScoreObjs[i]; + ScoreObjs[i] = 0; + } + Fade_Palette_To(BlackPalette, FADE_PALETTE_FAST, NULL); + VisiblePage.Clear(); + + Show_Mouse(); +// Map_Selection(); + + Theme.Queue_Song(THEME_NONE); + + Fade_Palette_To(BlackPalette, FADE_PALETTE_FAST, NULL); + VisiblePage.Clear(); + Set_Palette(GamePalette); + + Set_Font(oldfont); + FontXSpacing = oldfontxspacing; + ControlQ = 0; + + Set_Logic_Page (SeenBuff); + + delete PseudoSeenBuff; + delete TextPrintBuffer; + TextPrintBuffer = NULL; + BlitList.Clear(); + Enable_Uncompressed_Shapes(); +} + + +void Cycle_Wait_Click(void) +{ + int counter; + int minclicks = 20; + unsigned long timingtime = TickCount.Time(); + SerialPacketType sendpacket; + SerialPacketType receivepacket; + int packetlen; + + + Keyboard::Clear(); + while (minclicks || (!Check_Key() && !ControlQ) ) { + + if (GameToPlay == GAME_NULL_MODEM || + GameToPlay == GAME_MODEM){ + //GameToPlay == GAME_INTERNET) { + + // + // send a timing packet if enough time has gone by. + // + if ( (TickCount.Time() - timingtime) > PACKET_TIMING_TIMEOUT) { + sendpacket.Command = SERIAL_SCORE_SCREEN; + sendpacket.ResponseTime = NullModem.Response_Time(); + sendpacket.ID = ModemGameToPlay; + + NullModem.Send_Message (&sendpacket, sizeof(sendpacket), 0); + timingtime = TickCount.Time(); + } + + if (NullModem.Get_Message (&receivepacket, &packetlen) > 0) { + // throw packet away + packetlen = packetlen; + } + + NullModem.Service(); + } + + Call_Back_Delay(1); + if (minclicks) { + minclicks--; + Keyboard::Clear(); + } + + counter = ((++counter) & 7); + + if (!counter) { + unsigned char r,g,b; + + r = Palette[233*3+0]; + g = Palette[233*3+1]; + b = Palette[233*3+2]; + + for (int i = 233; i < 237; i++) { + Palette[i*3+0] = Palette[(i+1)*3+0]; + Palette[i*3+1] = Palette[(i+1)*3+1]; + Palette[i*3+2] = Palette[(i+1)*3+2]; + } + Palette[237*3+0] = r; + Palette[237*3+1] = g; + Palette[237*3+2] = b; + + Set_Palette(Palette); + } + } + Keyboard::Clear(); +} + +void ScoreClass::Do_Nod_Buildings_Graph(void) +{ + int shapenum; + InfantryTypeClass const *ramboclass; + + void const * factptr = MixFileClass::Retrieve("FACT.SHP"); + void const * rmboptr = MixFileClass::Retrieve("RMBO.SHP"); + void const * fball1ptr = MixFileClass::Retrieve("FBALL1.SHP"); + ramboclass = &InfantryTypeClass::As_Reference(INFANTRY_E5); + + /* + ** Print the # of buildings on the hidpage so we only need to do it once + */ + PseudoSeenBuff->Blit(SysMemPage); + Set_Logic_Page(SysMemPage); + Call_Back_Delay(30); + BlitList.Add (2* (BUILDING_X + 8), 2* (BUILDING_Y), 2* (BUILDING_X+8), 2* (BUILDING_Y), 5*12 , 12); + BlitList.Add (2* (BUILDING_X + 8), 2* (BUILDING_Y + 12), 2* (BUILDING_X+8), 2* (BUILDING_Y + 12), 5*12 , 12); + BlitList.Add (2* (BUILDING_X + 8), 2* (BUILDING_Y + 24), 2* (BUILDING_X+8), 2* (BUILDING_Y + 24), 5*12 , 12); + + TextPrintBuffer->Print( 0, (BUILDING_X + 8)*2, BUILDING_Y*2, TBLACK, TBLACK); + TextPrintBuffer->Print( 0, (BUILDING_X + 8)*2, (BUILDING_Y + 12)*2, TBLACK, TBLACK); + TextPrintBuffer->Print( 0, (BUILDING_X + 8)*2, (BUILDING_Y + 24)*2, TBLACK, TBLACK); + + /* + ** Here's the animation/draw loop for blowing up the factory + */ + for (int i=0; i<98; i++) { + SysMemPage.Blit(SysMemPage, BUILDING_X, BUILDING_Y, 0, 0, 320-BUILDING_X,48); + shapenum = 0; // no damage + if (i >= 60) { + shapenum = Extract_Shape_Count(factptr) - 2; // some damage + if (i == 60) { + Shake_Screen(6); + Sound_Effect(VOC_CRUMBLE, VOL_FULL, 0); + } + if (i > 65) { + shapenum = Extract_Shape_Count(factptr) - 1; // mega damage + } + } + + /* + ** Draw the building before Rambo + */ + if (i < 68) { + CC_Draw_Shape(factptr,shapenum, 0, 0, WINDOW_MAIN, + SHAPE_FADING|SHAPE_WIN_REL, ScoreRemapBldg,Map.UnitShadow); + + } + + /* + ** Now draw some fires, if appropriate + */ + if (i >= 61) { + int firecount = Extract_Shape_Count(fball1ptr); + int shapeindex = (i-61)>>1; + if (shapeindex < firecount) { + CC_Draw_Shape(fball1ptr, shapeindex, 10, 10, WINDOW_MAIN, + SHAPE_FADING|SHAPE_CENTER|SHAPE_WIN_REL,//|SHAPE_GHOST, + ScoreRemapFBall,0); + } + if (i > 64) { + shapeindex = (i-64)>>1; + if (shapeindex < firecount) { + CC_Draw_Shape(fball1ptr, shapeindex, 50, 30, WINDOW_MAIN, + SHAPE_FADING|SHAPE_CENTER|SHAPE_WIN_REL,//|SHAPE_GHOST, + ScoreRemapFBall,0); + } + } + } + /* + ** Draw the Rambo character running away from the building + */ + CC_Draw_Shape(rmboptr, (ramboclass->DoControls[DO_WALK].Frame + ramboclass->DoControls[DO_WALK].Jump*6) + ((i>>1)%ramboclass->DoControls[DO_WALK].Count), + i+32, 40, WINDOW_MAIN, + SHAPE_FADING|SHAPE_CENTER|SHAPE_WIN_REL,//|SHAPE_GHOST, + ScoreRemapYellow,Map.UnitShadow); + SysMemPage.Blit(*PseudoSeenBuff, 0, 0, BUILDING_X, BUILDING_Y, 320-BUILDING_X,48); + + /* + ** Extra font related stuff. ST - 7/29/96 2:22PM + */ + Interpolate_2X_Scale(PseudoSeenBuff , &HidPage ,NULL); + BlitList.Update(); + WWMouse->Draw_Mouse(&HidPage); + HidPage.Blit(SeenBuff); + WWMouse->Erase_Mouse(&HidPage, TRUE); + //Interpolate_2X_Scale( PseudoSeenBuff , &SeenBuff , NULL); + + if (!Check_Key()) Call_Back_Delay(1); + } + + i = MAX(GBKilled, NBKilled); + i = MAX(i, CBKilled); + + for (int q = 0; q <= i; q++) { + Count_Up_Print( "%d", q, GBKilled,BUILDING_X + 8,BUILDING_Y ); + Count_Up_Print( "%d", q, NBKilled,BUILDING_X + 8,BUILDING_Y + 12); + Count_Up_Print( "%d", q, CBKilled,BUILDING_X + 8,BUILDING_Y + 24); + if (!Check_Key()) { + Play_Sample(Beepy6, 255, Options.Normalize_Sound(110)); + Call_Back_Delay(1); + } + } +} + + +/*************************************************************************** + * DO_GDI_GRAPH -- Show # of people or buildings killed on GDI score screen* + * * + * * + * * + * INPUT: yellowptr, redptr = pointers to shape file for graphs * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 05/03/1995 BWG : Created. * + *=========================================================================*/ + +void ScoreClass::Do_GDI_Graph(void const * yellowptr, void const * redptr, int gkilled, int nkilled, int ypos) +{ + int i, max; + int gdikilled = gkilled, nodkilled=nkilled; + + max = MAX(gdikilled, nodkilled); + if (!max) max=1; + + gdikilled = (gdikilled * SIZEGBAR) / max; + nodkilled = (nodkilled * SIZEGBAR) / max; + if (max < 20) { + gdikilled = gkilled * 5; + nodkilled = nkilled * 5; + } + + max = MAX(gdikilled, nodkilled); + if (!max) max=1; + + // Draw the white-flash shape on the hidpage + Set_Logic_Page(SysMemPage); + SysMemPage.Fill_Rect(0,0, 124,9, TBLACK); + CC_Draw_Shape(redptr, 120, 0,0, WINDOW_MAIN,SHAPE_WIN_REL, 0, 0); + Set_Logic_Page(PseudoSeenBuff); + + BlitList.Add (2* 297, 2* (ypos+2), 2* 297, 2* (ypos+2), 5*12 , 12); + + for (i = 1; i <= gdikilled; i++) { + if (i != gdikilled) { + CC_Draw_Shape(yellowptr,i, 172, ypos, WINDOW_MAIN,SHAPE_WIN_REL, 0, 0); + } else { + SysMemPage.Blit(*PseudoSeenBuff,0,0, 172, ypos, 3+gdikilled,9); + } + + Count_Up_Print("%d", (i*gkilled) / max, gkilled, 297, ypos+2); + if (!Check_Key()) { + Play_Sample(Beepy6, 255, Options.Normalize_Sound(110)); + Call_Back_Delay(2); + } + } + CC_Draw_Shape(yellowptr,gdikilled, 172,ypos , WINDOW_MAIN,SHAPE_WIN_REL, 0, 0); + Count_Up_Print("%d", gkilled, gkilled, 297, ypos+ 2); + if (!Check_Key()) Call_Back_Delay(40); + + BlitList.Add (2* 297, 2* (ypos+14), 2* 297, 2* (ypos+14), 5*12 , 12); + for (i = 1; i <= nodkilled; i++) { + if (i != nodkilled) { + CC_Draw_Shape(redptr, i, 172, ypos+12, WINDOW_MAIN,SHAPE_WIN_REL, 0, 0); + } else { + SysMemPage.Blit(*PseudoSeenBuff,0,0, 172,ypos+12, 3+nodkilled,9); + } + + Count_Up_Print("%d", (i*nkilled) / max, nkilled, 297, ypos+14); + if (!Check_Key()) { + Play_Sample(Beepy6, 255, Options.Normalize_Sound(110)); + Call_Back_Delay(2); + } + } + +// if (Keyboard::Check()) Keyboard::Clear(); + + /* + ** Make sure accurate count is printed at end + */ + CC_Draw_Shape( redptr,nodkilled, 172,ypos+12, WINDOW_MAIN,SHAPE_WIN_REL, 0, 0); + Count_Up_Print("%d", nkilled, nkilled, 297, ypos+14); + if (!Check_Key()) Call_Back_Delay(40); +} + + +void ScoreClass::Do_Nod_Casualties_Graph(void) +{ + int i, gdikilled, nodkilled, civkilled, max; + + void const * e1ptr = MixFileClass::Retrieve("E1.SHP"); + void const * c1ptr = MixFileClass::Retrieve("C1.SHP"); + + gdikilled = GKilled; + nodkilled = NKilled; + civkilled = CKilled; + max = MAX(gdikilled, nodkilled); + max = MAX(max, civkilled); + + if (!max) max=1; + if ((gdikilled > (MAX_BAR_X - BARGRAPH_X)) || (nodkilled > (MAX_BAR_X - BARGRAPH_X)) || (civkilled > (MAX_BAR_X - BARGRAPH_X))) { + gdikilled = (gdikilled * (MAX_BAR_X - BARGRAPH_X)) / max; + nodkilled = (nodkilled * (MAX_BAR_X - BARGRAPH_X)) / max; + civkilled = (civkilled * (MAX_BAR_X - BARGRAPH_X)) / max; + } + + max = MAX(gdikilled, nodkilled); + max = MAX(max, civkilled); + if (!max) max=1; + + /* + ** Initialize a bunch of objects for the infantrymen who pose for the bar + ** graphs of casualties. + */ + int q = NUMINFANTRYMEN/3; + int r = q*2; + for (i = 0; i < NUMINFANTRYMEN/3; i++) { + InfantryMan[i+0].xpos = + InfantryMan[i+q].xpos = + InfantryMan[i+r].xpos = (i*10) + 7; + InfantryMan[i+0].ypos = 11; + InfantryMan[i+q].ypos = 21; + InfantryMan[i+r].ypos = 31; + InfantryMan[i+0].shapefile = + InfantryMan[i+q].shapefile = e1ptr; + InfantryMan[i+r].shapefile = c1ptr; + InfantryMan[i+0].remap = ScoreRemapYellow; + InfantryMan[i+q].remap = ScoreRemapGrey; + InfantryMan[i+r].remap = RemapCiv; + InfantryMan[i+0].anim = + InfantryMan[i+q].anim = + InfantryMan[i+r].anim = 0; + InfantryMan[i+0].stage = + InfantryMan[i+q].stage = + InfantryMan[i+r].stage = 0; + InfantryMan[i+0].delay = + InfantryMan[i+q].delay = + InfantryMan[i+r].delay = Random() & 0x1F; + InfantryMan[i+0].Class = + InfantryMan[i+q].Class = &InfantryTypeClass::As_Reference(INFANTRY_E1); + InfantryMan[i+r].Class = &InfantryTypeClass::As_Reference(INFANTRY_C1); + + } + + /* + ** Draw the infantrymen and pause briefly before running the graph + */ + Draw_InfantryMen(); + SysMemPage.Blit(*PseudoSeenBuff, 0, 0, BARGRAPH_X, CASUALTY_Y, 320-BARGRAPH_X, 34); + //Interpolate_2X_Scale( PseudoSeenBuff , &SeenBuff, NULL); + /* + ** Extra font related stuff. ST - 7/29/96 2:22PM + */ + Interpolate_2X_Scale(PseudoSeenBuff , &HidPage ,NULL); + BlitList.Update(); + WWMouse->Draw_Mouse(&HidPage); + HidPage.Blit(SeenBuff); + WWMouse->Erase_Mouse(&HidPage, TRUE); + + Call_Back_Delay(40); + + BlitList.Add (2* (SCORETEXT_X + 64), 2* (CASUALTY_Y + 2), 2* (SCORETEXT_X + 64), 2* (CASUALTY_Y + 2), 5*12 , 12); + BlitList.Add (2* (SCORETEXT_X + 64), 2* (CASUALTY_Y + 14), 2* (SCORETEXT_X + 64), 2* (CASUALTY_Y + 14), 5*12 , 12); + BlitList.Add (2* (SCORETEXT_X + 64), 2* (CASUALTY_Y + 26), 2* (SCORETEXT_X + 64), 2* (CASUALTY_Y + 26), 5*12 , 12); + + for (i = 1; i <= max; i++) { + // Draw & update infantrymen 3 times for every tick on the graph (i) + for (int q = 0; q < 3; q++) { + Draw_InfantryMen(); + Draw_Bar_Graphs(i, gdikilled, nodkilled, civkilled); + SysMemPage.Blit(*PseudoSeenBuff, 0, 0, BARGRAPH_X, CASUALTY_Y, 320-BARGRAPH_X, 34); + + Count_Up_Print("%d", (i*GKilled) / max, GKilled, SCORETEXT_X+64, CASUALTY_Y + 2); + Count_Up_Print("%d", (i*NKilled) / max, NKilled, SCORETEXT_X+64, CASUALTY_Y + 14); + Count_Up_Print("%d", (i*CKilled) / max, CKilled, SCORETEXT_X+64, CASUALTY_Y + 26); + if (!Check_Key()) Call_Back_Delay(3); + } + Play_Sample(Beepy6, 255, Options.Normalize_Sound(110)); + } + if (Check_Key()) Keyboard::Clear(); + + /* + ** Make sure accurate count is printed at end + */ + Count_Up_Print("%d", GKilled, GKilled, SCORETEXT_X+64, CASUALTY_Y + 2); + Count_Up_Print("%d", NKilled, NKilled, SCORETEXT_X+64, CASUALTY_Y + 14); + Count_Up_Print("%d", CKilled, CKilled, SCORETEXT_X+64, CASUALTY_Y + 26); + + /* + ** Finish up death animations, if there are any active + */ + int k = 1; + while (k) { + for (i=k=0; i= DO_GUN_DEATH) k=1; + if (k) Draw_InfantryMen(); + Draw_Bar_Graphs(max, gdikilled, nodkilled, civkilled); + SysMemPage.Blit(*PseudoSeenBuff, 0, 0, BARGRAPH_X, CASUALTY_Y, 320-BARGRAPH_X, 34); + Call_Back_Delay(1); + } +} + + +void ScoreClass::Show_Credits(int house, char const pal[]) +{ + static int _credsx[2]={276,276}; + static int _credsy[2]={173,58}; + static int _credpx[2]={228,236}; +#ifdef GERMAN + static int _credpy[2]={181, 74}; + static int _credtx[2]={162,162}; + static int _credty[2]={173, 62}; +#else + static int _credpy[2]={189-12, 74}; + static int _credtx[2]={182,182}; + static int _credty[2]={179-12, 62}; +#endif + + int credobj,i; + int min,add; + + void const * credshape = MixFileClass::Retrieve("CREDS.SHP"); + + Alloc_Object(new ScorePrintClass(TXT_SCORE_ENDCRED, _credtx[house], _credty[house],pal)); + Call_Back_Delay(15); + + credobj = Alloc_Object(new ScoreCredsClass(_credsx[house], _credsy[house], credshape, 32, 2)); + min = PlayerPtr->Available_Money() / 100; + + /* + ** Print out total credits left at end of scenario + */ + i = -50; + + BlitList.Add (2* (_credpx[house]), 2* (_credpy[house]), 2* (_credpx[house]), 2* (_credpy[house]), 5*12 , 12); + + do { + add = 5; + if ((PlayerPtr->Available_Money() - i) > 100 ) add += 15; + if ((PlayerPtr->Available_Money() - i) > 1000) add += 30; + if (add < min) add = min; + i += add; + + if (i < 0) i=0; + + Set_Font_Palette(pal); + Count_Up_Print("%d", i, PlayerPtr->Available_Money(), _credpx[house], _credpy[house]); + Call_Back_Delay(2); + if (Check_Key()) { + i=PlayerPtr->Available_Money() - 5; + Keyboard::Clear(); + } + } while (i < PlayerPtr->Available_Money()) ; + + // Make sure the credits object doesn't freeze on the white stage + while (((ScoreTimeClass *)ScoreObjs[credobj])->Stage >= 20 && !ControlQ) { + Call_Back_Delay(1); + } + delete ScoreObjs[credobj]; + ScoreObjs[credobj] = 0; +} + + +/*************************************************************************** + * SCORECLASS::PRINT_MINUTES -- Print out hours/minutes up to max * + * * + * Same as count-up-print, but for the time * + * * + * INPUT: current minute count and maximum * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/13/1995 BWG : Created. * + *=========================================================================*/ +void ScoreClass::Print_Minutes(int minutes) +{ + char str[20]; + if (minutes >= 60) { + if ((minutes/60) > 9) minutes = (9*60 + 59); + sprintf(str,Text_String(TXT_SCORE_TIMEFORMAT1), (minutes / 60),(minutes % 60)); + } else { + sprintf(str,Text_String(TXT_SCORE_TIMEFORMAT2), minutes); + } + TextPrintBuffer->Print(str, 275*2, 9*2, TBLACK, TBLACK); +} + + +/*********************************************************************************************** + * ScoreClass::Count_Up_Print -- Prints a number (up to its max) into a string, cleanly. * + * * + * This routine prints out a number (like 70) or its maximum number, into a string, onto * + * the screen, on a clean section of the screen, and blits it forward to the seenpage so you* + * can print without flashing and can print over something (to count up %'s). * + * * + * INPUT: str = string to print into * + * percent = # to print * + * max = # to print if percent > max * + * xpos = x pixel coord * + * ypos = y pixel coord * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/07/1995 BWG : Created. * + *=============================================================================================*/ +void ScoreClass::Count_Up_Print(char *str, int percent, int max, int xpos, int ypos) +{ + char destbuf[64]; + int width; + + sprintf(destbuf, str, percent <= max ? percent : max); + width = strlen(destbuf) * 7; + + + + +// HidPage.Blit(HidPage, xpos, ypos, 0, 0, width, 8); +// Set_Logic_Page(HidPage); +// LogicPage->Print( destbuf, 0, 0, WHITE, TBLACK); +// HidPage.Blit(SeenBuff, 0, 0, xpos, ypos, width, 8); + + TextPrintBuffer->Fill_Rect (xpos*2, ypos*2, (xpos + width)*2, (ypos+7)*2, BLACK); + TextPrintBuffer->Print (destbuf, xpos*2, ypos*2, WHITE, TBLACK); + + + //TextPrintBuffer->Blit(*TextPrintBuffer, xpos*2, ypos*2, 0, 0, width*2, 8*2); + //TextPrintBuffer->Print(destbuf, 0, 0, WHITE, TBLACK); + //TextPrintBuffer->Blit(SeenBuff, 0, 0, xpos*2, ypos*2, width*2, 8*2); + +// PseudoSeenBuff->Print( destbuf, xpos, ypos, TBLACK, BLACK); +// Interpolate_2X_Scale( PseudoSeenBuff , &SeenBuff , NULL); + +} + + +/*********************************************************************************************** + * ScoreClass::Input_Name -- Gets the name from the keyboard * + * * + * This routine handles keyboard input, and does a nifty zooming letter effect too. * + * * + * INPUT: str = string to put user's typing into * + * xpos = x pixel coord * + * ypos = y pixel coord * + * pal = text remapping palette to print using * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/15/1995 BWG : Created. * + *=============================================================================================*/ +void ScoreClass::Input_Name(char str[], int xpos, int ypos, char const pal[]) +{ + int key, ascii, index=0; + + void const * keystrok = MixFileClass::Retrieve("KEYSTROK.AUD"); + + /* + ** Ready the hidpage so it can restore background under zoomed letters + */ + PseudoSeenBuff->Blit(SysMemPage); + + do { + Call_Back(); + Animate_Score_Objs(); + Animate_Cursor(index, ypos); + /* + ** Extra font related stuff. ST - 7/29/96 2:22PM + */ + Interpolate_2X_Scale (PseudoSeenBuff , &HidPage ,NULL); + BlitList.Update(); + HidPage.Blit(SeenBuff); + + if (Check_Key()) { //if (Keyboard::Check()) { + key = Get_Key(); //key = Keyboard::Get(); + + if (index == MAX_FAMENAME_LENGTH-2) { + while (Check_Key()) { + Get_Key(); + } + } + + /* + ** If they hit 'backspace' when they're on the last letter, + ** turn it into a space instead. + */ + if ((key == KA_BACKSPACE) && (index == MAX_FAMENAME_LENGTH-2) ) { + if (str[index] && str[index]!=32) key = 32; + } + if (key == KA_BACKSPACE) { //if (key == KN_BACKSPACE) { + if (index) { + str[--index] = 0; + + int xposindex6 = xpos+(index*6); + + PseudoSeenBuff->Fill_Rect(xposindex6,ypos,xposindex6+6,ypos+6,TBLACK); + SysMemPage.Fill_Rect(xposindex6,ypos,xposindex6+6,ypos+6,TBLACK); + TextPrintBuffer->Fill_Rect(xposindex6*2,ypos*2,(xposindex6+6)*2,(ypos+6)*2, BLACK); + } + + } else if (key!=KA_RETURN) { //else if (key != KN_RETURN && key!=KN_KEYPAD_RETURN) { + ascii = key; //ascii = KN_To_KA(key); + if (ascii >= 'a' && ascii <= 'z') ascii -= ('a' - 'A'); +//if (ascii >='A' && ascii<='Z' || ascii == ' ') { +if ( (ascii >= '!' && ascii <= KA_TILDA) || ascii == ' ') { + PseudoSeenBuff->Fill_Rect(xpos + (index*6), ypos, xpos + (index*6)+6, ypos+5, TBLACK); + SysMemPage.Fill_Rect(xpos + (index*6), ypos, xpos + (index*6)+6, ypos+5, TBLACK); + TextPrintBuffer->Fill_Rect(2*(xpos + (index*6)), ypos*2, 2*(xpos + (index*6)+6), 2*(ypos+6), BLACK); + str[index] = ascii; + str[index+1] = 0; + + int objindex; + Play_Sample(keystrok, 255, Options.Normalize_Sound(255)); + objindex = Alloc_Object(new ScoreScaleClass(str+index,xpos+(index*6), ypos, pal)); + while (ScoreObjs[objindex]) Call_Back_Delay(1); + + if (index < (MAX_FAMENAME_LENGTH-2) ) index++; + } + } + } + } while(key!=KA_RETURN); // } while(key != KN_RETURN && key!=KN_KEYPAD_RETURN); +} + + +void Animate_Cursor(int pos, int ypos) +{ + static int _lastpos, _state; + static CountDownTimerClass _timer; + + ypos += 7; // move cursor to bottom of letter + + // If they moved the cursor, erase old one and force state=0, to make green draw right away + if (pos != _lastpos) { + PseudoSeenBuff->Draw_Line(HALLFAME_X + (_lastpos*6),ypos, HALLFAME_X + (_lastpos*6) + 5, ypos, TBLACK); + TextPrintBuffer->Fill_Rect(2*(HALLFAME_X + (_lastpos*6)),2*ypos, 2*(HALLFAME_X + (_lastpos*6) + 5), 2*ypos+1, BLACK); + _lastpos = pos; + _state = 0; + } + + PseudoSeenBuff->Draw_Line(HALLFAME_X + (pos*6),ypos, HALLFAME_X + (pos*6)+5, ypos, _state ? LTBLUE : TBLACK); + TextPrintBuffer->Fill_Rect(2*(HALLFAME_X + (pos*6)),2*ypos, 2*(HALLFAME_X + (pos*6)+5), 2*ypos+1, _state ? LTBLUE : BLACK); + + /* + ** Toggle the color of the cursor, green or black, if it's time to do so. + */ + if (!_timer.Time()) { + _state ^= 1; + _timer.Set(5); + } +} + + +/*************************************************************************** + * Draw_InfantryMen -- Draw all the guys on the score screen * + * * + * * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/13/1995 BWG : Created. * + *=========================================================================*/ +void Draw_InfantryMen() +{ + int k; + +// Only draw the infantrymen if we're playing Nod... GDI wouldn't execute +// people like that. + + /* + ** First restore the background + */ + SysMemPage.Blit(SysMemPage, BARGRAPH_X, CASUALTY_Y, 0, 0, 320-BARGRAPH_X, 34); + Set_Logic_Page(SysMemPage); + + /* + ** Then draw all the infantrymen on the clean SysMemPage + */ + for (k = 0; k < NUMINFANTRYMEN; k++) Draw_InfantryMan(k); + /* + ** They'll all be blitted over to the seenpage after the graphs are drawn + */ +} + +/*************************************************************************** + * Draw_InfantryMan -- Draw one guy in score screen, update animation * + * * + * This routine draws one of the infantrymen in the "Casualties" area * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/13/1995 BWG : Created. * + *=========================================================================*/ +void Draw_InfantryMan(int index) +{ + int stage; + + /* If the infantryman's dead, just abort this function */ + if (InfantryMan[index].anim == -1) return; + + stage = InfantryMan[index].stage + InfantryMan[index].Class->DoControls[InfantryMan[index].anim].Frame; + + CC_Draw_Shape(InfantryMan[index].shapefile, + stage, + InfantryMan[index].xpos, + InfantryMan[index].ypos, + WINDOW_MAIN, + SHAPE_FADING|SHAPE_CENTER|SHAPE_WIN_REL,//|SHAPE_GHOST, + InfantryMan[index].remap, + Map.UnitShadow); + /* + ** see if it's time to run a new anim + */ + if (--InfantryMan[index].delay < 0) { + InfantryMan[index].delay = 3; + if (++InfantryMan[index].stage >= InfantryMan[index].Class->DoControls[InfantryMan[index].anim].Count) { + + /* + ** was he playing a death anim? If so, and it's done, erase him + */ + if (InfantryMan[index].anim >= DO_GUN_DEATH) { + InfantryMan[index].anim = -1; + } else { + New_Infantry_Anim(index, DO_STAND_READY); + } + } + } +} + + +/*************************************************************************** + * New_Infantry_Anim -- Start up a new animation for one of the infantrymen* + * * + * * + * * + * INPUT: index: which of the 30 infantrymen to affect * + * anim: which animation sequence to start him into * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/13/1995 BWG : Created. * + *=========================================================================*/ +void New_Infantry_Anim(int index, int anim) +{ + InfantryMan[index].anim = anim; + InfantryMan[index].stage = 0; + if (anim >= DO_GUN_DEATH) { + InfantryMan[index].delay = 1; // start right away + } else { + InfantryMan[index].delay = Random() & 15; + } +} + + +/*************************************************************************** + * Draw_Bar_Graphs -- Draw "Casualties" bar graphs * + * * + * * + * * + * INPUT: i = current count of how far to draw graph * + * gkilled = # of GDI forces killed (adjusted to fit in space) * + * nkilled = # of Nod forces killed (adjusted to fit in space) * + * ckilled = # of civilians killed (adjusted to fit in space) * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/13/1995 BWG : Created. * + *=========================================================================*/ +void Draw_Bar_Graphs(int i, int gkilled, int nkilled, int ckilled) +{ + if (gkilled) { + LogicPage->Fill_Rect(0, 0+4, 0+MIN(i,gkilled), 0+5, LTCYAN); + LogicPage->Draw_Line(0+1, 0+6, 0+MIN(i,gkilled)+1, 0+6, TBLACK); + LogicPage->Draw_Line(0+MIN(i,gkilled)+1,0+5, 0+MIN(i,gkilled)+1, 0+5, TBLACK); + if (i <= gkilled) { + int anim = InfantryMan[i/11].anim; + if (anim!=-1 && anim < DO_GUN_DEATH) { + if (i/11) { + New_Infantry_Anim(i/11,DO_GUN_DEATH + (Random() & 3)); + } else { + New_Infantry_Anim(i/11,DO_GUN_DEATH); + } +// Sound_Effect(Random_Pick(VOC_SCREAM1, VOC_SCREAM5)); + } + } + } + if (nkilled) { + LogicPage->Fill_Rect(0, 0+16, 0+MIN(i,nkilled), 0+17, RED); + LogicPage->Draw_Line(0+1, 0+18, 0+MIN(i,nkilled)+1, 0+18, TBLACK); + LogicPage->Draw_Line(0+MIN(i,nkilled)+1, 0+17, 0+MIN(i,nkilled)+1, 0+17, TBLACK); + if (i <= nkilled) { + int anim = InfantryMan[(NUMINFANTRYMEN/3)+(i/11)].anim; + if (anim!=-1 && anim < DO_GUN_DEATH) { + if (i/11) { + New_Infantry_Anim((NUMINFANTRYMEN/3)+(i/11),DO_GUN_DEATH + (Random() & 3)); + } else { + New_Infantry_Anim((NUMINFANTRYMEN/3)+(i/11),DO_GUN_DEATH); + } +// Sound_Effect(Random_Pick(VOC_SCREAM1, VOC_SCREAM5)); + } + } + } + + if (ckilled) { + LogicPage->Fill_Rect(0, 0+28, 0+MIN(i,ckilled), 0+29, RED); + LogicPage->Draw_Line(0+1, 0+30, 0+MIN(i,ckilled)+1, 0+30, TBLACK); + LogicPage->Draw_Line(0+MIN(i,ckilled)+1, 0+29, 0+MIN(i,ckilled)+1, 0+29, TBLACK); + if (i <= ckilled) { + int anim = InfantryMan[((NUMINFANTRYMEN*2)/3)+(i/11)].anim; + if (anim!=-1 && anim < DO_GUN_DEATH) { + if (i/11) { + New_Infantry_Anim(((NUMINFANTRYMEN*2)/3)+(i/11),DO_GUN_DEATH + (Random() & 3)); + } else { + New_Infantry_Anim(((NUMINFANTRYMEN*2)/3)+(i/11),DO_GUN_DEATH); + } +// Sound_Effect(Random_Pick(VOC_SCREAM1, VOC_SCREAM5)); + } + } + } +} + + +/*************************************************************************** + * Call_Back_Delay -- Combines Call_Back() and Delay() functions * + * * + * This is just to cut down on code size and typing a little. * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 04/13/1995 BWG : Created. * + *=========================================================================*/ +void Call_Back_Delay(int time) +{ + CountDownTimerClass cd; + + if (!ControlQ) { + if (Keyboard::Down(KN_LCTRL) && Keyboard::Down(KN_Q)) { + ControlQ = 1; + Keyboard::Clear(); + } + } + if (ControlQ) time=0; + + cd.Set(time); + StreamLowImpact = true; + do { + Call_Back(); + //Animate_Score_Objs(); + //Interpolate_2X_Scale(PseudoSeenBuff , &SeenBuff ,NULL); + + //if (Get_Mouse_State()){ + // Interpolate_2X_Scale(PseudoSeenBuff , &SeenBuff ,NULL); + //BlitList.Update(); + //}else{ + Animate_Score_Objs(); + Interpolate_2X_Scale(PseudoSeenBuff , &HidPage ,NULL); + BlitList.Update(); + WWMouse->Draw_Mouse(&HidPage); + HidPage.Blit(SeenBuff); + WWMouse->Erase_Mouse(&HidPage, TRUE); + //} + } while(cd.Time()); + StreamLowImpact = false; +} + + +void Animate_Score_Objs() +{ + StillUpdating = false; + for (int i = 0; i < MAXSCOREOBJS; i++) { + if (ScoreObjs[i]) { + ScoreObjs[i]->Update(); + } + } +} + +char *Int_Print(int a) +{ + static char str[10]; + + sprintf(str,"%d",a); + return str; +} + + +/*********************************************************************************************** + * Multi_Score_Presentation -- Multiplayer routine to display score screen. * + * * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/11/1995 BWG: Created. * + *=============================================================================================*/ +void Multi_Score_Presentation(void) +{ + static char const _cycleyellowpal[]={0x0,0xec,0xEb,0xea,0xE9,0xe9,0xE9,0x0,0xE9,0x0,0x0,0x0,0x0,0x0,0xED,0x0}; + + static char const _greenpal[]= {0x0,0x12,0x14,0x16,0x18,0x18,0x18,0x0,0x18,0x0,0x0,0x0,0x0,0x0,0x10,0x0}; + static char const _redpal[]= {0x0,0x22,0x24,0x26,0x28,0x28,0x28,0x0,0x28,0x0,0x0,0x0,0x0,0x0,0x20,0x0}; + static char const _graypal[]= {0x0,0xca,0xCb,0xcc,0xCd,0xcd,0xCd,0x0,0xCD,0x0,0x0,0x0,0x0,0x0,0xC8,0x0}; + static char const _orangepal[]={0x0,0xd1,0xD2,0xd3,0xD4,0xd4,0xD4,0x0,0xD4,0x0,0x0,0x0,0x0,0x0,0xD0,0x0}; + static char const _bluepal[]= {0x0,0x2,0x0a,0xb,0x0b,0xb,0x0B,0x0,0x0B,0x0,0x0,0x0,0x0,0x0,0x09,0x0}; + static char const _yellowpal[]={0x0,0x5,0xee,0xf1,0xf2,0xf2,0xF2,0xf2,0xF2,0x0,0x0,0x0,0x0,0x0,0x7D,0x0}; + + //static char const _greenpal[]= {0x0,0x0,0x12,0x0,0x14,0x0,0x16,0x0,0x18,0x0,0x0,0x0,0x0,0x0,0x10,0x0}; + //static char const _redpal[]= {0x0,0x0,0x22,0x0,0x24,0x0,0x26,0x0,0x28,0x0,0x0,0x0,0x0,0x0,0x20,0x0}; + //static char const _graypal[]= {0x0,0x0,0xCA,0x0,0xCB,0x0,0xCC,0x0,0xCD,0x0,0x0,0x0,0x0,0x0,0xC8,0x0}; + //static char const _orangepal[]={0x0,0x0,0xD1,0x0,0xD2,0x0,0xD3,0x0,0xD4,0x0,0x0,0x0,0x0,0x0,0xD0,0x0}; + //static char const _bluepal[]= {0x0,0x0,0x02,0x0,0x0A,0x0,0x0B,0x0,0x0B,0x0,0x0,0x0,0x0,0x0,0x09,0x0}; + //static char const _yellowpal[]={0x0,0x0,0x05,0x0,0xEE,0x0,0xF1,0x0,0xF2,0x0,0x0,0x0,0x0,0x0,0x7D,0x0}; + + static char const *_colors[]={_yellowpal,_redpal,_bluepal,_orangepal,_greenpal,_graypal}; + + int i,k; + void *oldfont, *anim; + int oldfontxspacing = FontXSpacing; + char const *pal; + + + FontXSpacing = 0; + Map.Override_Mouse_Shape(MOUSE_NORMAL); + Theme.Queue_Song(THEME_WIN1); + + PseudoSeenBuff = new GraphicBufferClass(320,200,(void*)NULL); + TextPrintBuffer = new GraphicBufferClass(SeenBuff.Get_Width(), SeenBuff.Get_Height() ,(void*)NULL); + BlitList.Clear(); + + SysMemPage.Clear(); + PseudoSeenBuff->Clear(); + HiddenPage.Clear(); + TextPrintBuffer->Clear(); + + Set_Palette(BlackPalette); + + anim = Open_Animation("MLTIPLYR.WSA",NULL,0L,(WSAOpenType)(WSA_OPEN_FROM_MEM | WSA_OPEN_TO_PAGE),Palette); + Hide_Mouse(); + + /* + ** Display the background animation + */ + VisiblePage.Clear(); + InterpolationPaletteChanged = TRUE; + InterpolationPalette = Palette; + Increase_Palette_Luminance (Palette , 30,30,30,63); + Animate_Frame(anim, *PseudoSeenBuff, 1); + Interpolate_2X_Scale( PseudoSeenBuff , &SeenBuff , "MULTSCOR.PAL"); + Fade_Palette_To(Palette, FADE_PALETTE_FAST, Call_Back); + + int frame = 1; + while (frame < Get_Animation_Frame_Count(anim)) { + Animate_Frame(anim, *PseudoSeenBuff, frame++); + Call_Back_Delay(2); + } + Close_Animation(anim); + + /* Change to the six-point font for Text_Print */ + oldfont = Set_Font(ScoreFontPtr); + Call_Back(); + + Set_Logic_Page(*PseudoSeenBuff); + + /* + ** Move all the scores over a notch if there's more games than can be + ** shown (which is known by MPlayerCurGame == MAX_MULTI_GAMES-1); + */ + if (MPlayerCurGame == MAX_MULTI_GAMES-1) { + for(i = 0; i < MAX_MULTI_NAMES; i++) { + for(k = 0; k < MAX_MULTI_GAMES-1; k++) { + MPlayerScore[i].Kills[k] = MPlayerScore[i].Kills[k+1]; + } + } + } + + int y = 41; + for(i = 0; i < MAX_MULTI_NAMES; i++) { + if (strlen(MPlayerScore[i].Name)) { + pal = _colors[MPlayerScore[i].Color]; + + Alloc_Object(new ScorePrintClass(MPlayerScore[i].Name, 15, y,pal)); + Call_Back_Delay(20); + + Alloc_Object(new ScorePrintClass(Int_Print(MPlayerScore[i].Wins), 118, y, pal)); + Call_Back_Delay(6); + + for(k = 0; k <= MIN(MPlayerCurGame, MAX_MULTI_GAMES-2); k++) { + if (MPlayerScore[i].Kills[k] >= 0) { + Alloc_Object(new ScorePrintClass(Int_Print(MPlayerScore[i].Kills[k]), 225+(24*k), y, pal)); + Call_Back_Delay(6); + } + } + y += 12; + } + } + +#if (FRENCH) + Alloc_Object(new ScorePrintClass(TXT_MAP_CLICK2, 90 /*(320-strlen(Text_String(TXT_MAP_CLICK2)))/2*/, 185, _cycleyellowpal)); +#else + Alloc_Object(new ScorePrintClass(TXT_MAP_CLICK2, 109 /*(320-strlen(Text_String(TXT_MAP_CLICK2)))/2*/, 185, _cycleyellowpal)); +#endif + Cycle_Wait_Click(); + +/* get rid of all the animating objects */ + for (i = 0; i < MAXSCOREOBJS; i++) if (ScoreObjs[i]) { + delete ScoreObjs[i]; + ScoreObjs[i] = 0; + } + + Theme.Queue_Song(THEME_NONE); + + Fade_Palette_To(BlackPalette, FADE_PALETTE_FAST, NULL); + VisiblePage.Clear(); + Set_Palette(GamePalette); + + Set_Logic_Page (SeenBuff); + + delete PseudoSeenBuff; + delete TextPrintBuffer; + BlitList.Clear(); + + Set_Font(oldfont); + FontXSpacing = oldfontxspacing; + ControlQ = 0; + Show_Mouse(); +} + +#ifdef WRITE_LBM + // A BitMapHeader is stored in a BMHD chunk. This structure MUST be an even size +typedef struct { + unsigned short w, h; // raster width & height in pixels + unsigned short x, y; // position for this image + unsigned char planes; // # source bitplanes + unsigned char masking; // masking technique + unsigned char compression; // compression algoithm + unsigned char pad1; // UNUSED. For consistency, put 0 here. + unsigned short transcolor; // transparent "color number" + unsigned char xaspect, yaspect; // aspect ratio, a rational number x/y + unsigned short pagewidth, pageheight; // source "page" size in pixels +} BitMapHeaderType; + +// All values in LocalHeader are always the same except planes. This is set in Write_BMHD +// the WORD values must be in low-high order for compatibility. + +static BitMapHeaderType LocalHeader = { + 0x4001, 0xc800, 0, 0, 0, 0, // width, height, x, y, planes, mask + 1, 0, 0xFF00, 5, 6, // compress, pad1, transcolor, xasptect, yaspect + 0x4001, 0xC800 }; // pagewidth, pageheight + +#define BM_HEADER_SIZE (((sizeof(BitMapHeaderType) + 1) & 0xFFFE) + 8L) + +/*=========================================================================*/ +/* The following static functions are in this file: */ +/*=========================================================================*/ + +static long CCWrite_BMHD(CCFileClass &lbmhandle, short bitplanes); +static long CCWrite_CMAP(CCFileClass &lbmhandle, unsigned char * palette, short bitplanes); +static long CCWrite_BODY(CCFileClass &lbmhandle, BufferClass& buff, short bitplanes); +static long CCWrite_Row(CCFileClass &lbmhandle, unsigned char *buffer); + + +/*= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =*/ + +/*************************************************************************** + * CCWrite_LBM_File -- Writes out a file in LBM format * + * * + * INPUT: short lbmhandle -- lbm file handle already opened by caller * + * BufferClass buff -- buff where MCGA picture is * + * short bitplane -- number of bitplanes to convert to * + * char *palette -- pointer to palette for buff * + * * + * OUTPUT: Returns bool -- successfull or not * + * * + * WARNINGS: * + * * + * HISTORY: * + * 11/18/1991 SB : Created. * + *=========================================================================*/ +bool CCWrite_LBM_File(CCFileClass &lbmhandle, BufferClass& buff, short bitplanes, unsigned char *palette) +{ + long filesize; + + + lbmhandle.Seek(0L, SEEK_SET); // goto beginning of file + + lbmhandle.Write("FORM????ILBM", 12L); // First 12 bytes of all .lbm files + // size is unkown so write ???? + filesize = 12L; // 4 bytes for "ILBM" + + filesize += CCWrite_BMHD(lbmhandle, bitplanes); // write out BMHD (fixed size) + filesize += CCWrite_CMAP(lbmhandle, palette, bitplanes); // write out CMAP + + // Write out the body, or compressed picture image. This size will depend + // on the compression, but the value passed back is what the compressor + // assumed was written to file + + filesize += CCWrite_BODY(lbmhandle, buff, bitplanes); + + // Verify that we were able to write out the file without running out of space + if (lbmhandle.Seek(0L, SEEK_END) != filesize) { + return(false); + } + + lbmhandle.Seek(4L, SEEK_SET); // goto beginning of file + filesize = Reverse_LONG(filesize - 8L); // - 8 because of "FORM" + short (size) + lbmhandle.Write( (char *) &filesize, 4L); // patch in filesize + + return(true); +} + + +/*************************************************************************** + * CCWrite_BMHD -- writes out the bit map header (LocalHeader) * + * * + * INPUT: short lbmhandle -- file handle for lbm file * + * short pitplanes -- number of bitplanes to write out * + * * + * OUTPUT: LONG number of bytes hopefully written out to .LBM file * + * * + * WARNINGS: * + * * + * HISTORY: * + * 11/19/1991 SB : Created. * + *=========================================================================*/ +static long CCWrite_BMHD(CCFileClass &lbmhandle, short bitplanes) +{ + long size; + + lbmhandle.Write("BMHD", 4L); // write out chunk title + size = Reverse_LONG(sizeof(LocalHeader)); // write out size of LocalHeader chunk + lbmhandle.Write((char *) &size, 4L); + + LocalHeader.planes = bitplanes; // only nonconstant value in LocalHeader + + // Make sure size is even. Return 8 = "BMHD" + size of the bitmap header structure + + return(lbmhandle.Write((char *) &LocalHeader, + (sizeof(LocalHeader) + 1) & 0xFFFE) + 8L); +} + + +/*************************************************************************** + * CCWrite_CMAP -- Writes out CMAP (palette) information * + * * + * * + * INPUT: short lbmhandle -- file handle of lbm file * + * char * palette -- pointer to paletter information * + * short bitplanes -- used to figure out size of palette * + * * + * OUTPUT: long number of bytes that should have been written out to .LBM. * + * * + * WARNINGS: * + * * + * HISTORY: * + * 11/19/1991 SB : Created. * + *=========================================================================*/ +static long CCWrite_CMAP(CCFileClass &lbmhandle, unsigned char * palette, short bitplanes) +{ + short color, r, g, b, colors; + long size; + unsigned char *pal_ptr; + char rgb[3]; + + + lbmhandle.Write("CMAP", 4L); // write out palette info + colors = 1 << bitplanes; // colors = 2 to the bitplanes + size = Reverse_LONG(colors * 3L); // size = colors * 3 guns + + lbmhandle.Write((char *) &size, 4L); + + for (pal_ptr = palette, color = 0; color < colors; color++) { // for each color + + if ((r = *pal_ptr++) != 0) { // DPaint changes allows 0 - 100 for gun values + r = (r << 2) | 0x03; // this must be converted to 0 - 256 for LBM + } // so LBM_val = (DP_val * 4) | 3 if DP_val != 0 + if ((g = *pal_ptr++) != 0) { + g = (g << 2) | 0x03; + } + if ((b = *pal_ptr++) != 0) { + b = (b << 2) | 0x03; + } + rgb[0] = r; // assign gun values to an array to write out + rgb[1] = g; + rgb[2] = b; + + lbmhandle.Write(rgb, 3L); + } + // size = colors * 3 + return(((colors << 1) + colors) + 8L); // total size of CMAP 8 = "CMAP" + short (size) +} + + +/*************************************************************************** + * CCWrite_Body -- writes out compressed data in an LBM file * + * * + * INPUT: short lbmhandle -- file handle of lbm file * + * * + * OUTPUT: long - number of byte written * + * * + * WARNINGS: * + * * + * HISTORY: * + * 11/19/1991 SB : Created. * + *=========================================================================*/ +static long CCWrite_BODY(CCFileClass &lbmhandle, BufferClass& buff, short bitplanes) +{ + long bodysize = 0; + long actualsize; + long size; + short planebit; + short line, plane; + unsigned char buffer[40]; + unsigned char *buffptr; + + lbmhandle.Write("BODY????", 8L); // BODY chunk ID, ???? reserved for chuncksize + + buffptr = (unsigned char *) buff.Get_Buffer(); // point to beginning of buff + + for (line = 0; line < 200; line++) { + planebit = 1; // start with bit 1 set + + for (plane = 0; plane < bitplanes; plane++) { + Pack_2_Plane(buffer, buffptr, planebit); // convert to planar + bodysize += CCWrite_Row(lbmhandle, buffer); // write to to the BODY in the LBM + + planebit <<= 1; // set next bit + } + + buffptr += 320; // row size is 320 + } + + actualsize = bodysize + (bodysize&0x01); + + if (actualsize != bodysize) { + lbmhandle.Write(buffer, 1); // Padd the block. + } + + lbmhandle.Seek( -(actualsize + 4L), SEEK_CUR); // Patch in chunksize + size = Reverse_LONG(bodysize); + lbmhandle.Write( (char *) &size ,4L); + + return(actualsize + 8L); // total size of BODY, "BODY????" = 8 bytes +} + + +/*************************************************************************** + * CCWrite_Row -- compresses and writes a row plane to .lbm file * + * * + * INPUT: short lbmhandle -- lbm file handle * + * unsigned char *buffer -- pointer to buffer to be written out * + * * + * OUTPUT: long size of chunk that should have been written out * + * * + * WARNINGS: * + * * + * HISTORY: * + * 11/19/1991 SB : Created. * + *=========================================================================*/ +static long CCWrite_Row(CCFileClass &lbmhandle, unsigned char *buffer) +{ + short i; + short chunksize = 0; + short dataLength = 40; // 320 rows / 8 ( 1 plane per row) + unsigned char repCode, current, curr_plus_2; + unsigned char *buffptr; + + while (dataLength) { + + // If at least 2 more bytes and they are equal, then replicate + + if ((dataLength >= 2) && (buffer[0] == buffer[1])) { + buffptr = buffer; + for (i = 0; (i <= 128) && (i < (dataLength - 1)); i++) { + if (*buffptr != buffptr[1]) { + break; + } + buffptr++; + } + i++; + repCode = -i + 1; + lbmhandle.Write(&repCode, 1L); // Write count as -count+1 + lbmhandle.Write(buffer, 1L); // Write byte to replicate + buffer += i; + dataLength -= i; + chunksize += 2; + + } else { // Copy literally till 3 byte run or two 2 byte runs found + + for (i = 0; (i <= 128) && (i < dataLength); i++) { + current = buffer[i]; + curr_plus_2 = buffer[i + 2]; + + if (i == dataLength - 1) + continue; + if (current != buffer[i + 1]) + continue; + if (i == dataLength - 2) + continue; + if (current == curr_plus_2) + break; + if (i == dataLength - 3) + continue; + if (curr_plus_2 == buffer[i + 3]) + break; + } + repCode = i - 1; + lbmhandle.Write(&repCode, 1L); // Write count as count-1 + lbmhandle.Write(buffer, (long) i); // Write 'count' bytes + buffer += i; + dataLength -= i; + chunksize += i + 1; + } + } // end while + + return(chunksize); +} +#endif + diff --git a/SCORE.H b/SCORE.H new file mode 100644 index 0000000..dfeba99 --- /dev/null +++ b/SCORE.H @@ -0,0 +1,159 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\score.h_v 2.18 16 Oct 1995 16:46:18 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SCORE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 19, 1994 * + * * + * Last Update : April 19, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef SCORE_H +#define SCORE_H + +#include "unit.h" +#include "building.h" + +class ScoreClass { + public: + int Score; + int NKilled; + int GKilled; + int CKilled; + int NBKilled; + int GBKilled; + int CBKilled; + int NHarvested; + int GHarvested; + int CHarvested; + unsigned long ElapsedTime; + + void Init(void) {memset(this, 0, sizeof(ScoreClass));}; + void Presentation(void); + + /* + ** File I/O. + */ + bool Load(FileClass & file); + bool Save(FileClass & file); + void Code_Pointers(void); + void Decode_Pointers(void); + + protected: + + private: + unsigned char *ChangingGun; + + void ScoreDelay(int ticks); + void Pulse_Bar_Graph(void); + void Print_Graph_Title(int,int); + void Print_Minutes(int minutes); + void Count_Up_Print(char *str, int percent, int max, int xpos, int ypos); + void Show_Credits(int house, char const pal[]); + void Do_GDI_Graph(void const * yellowptr, void const * redptr, int gdikilled, int nodkilled, int ypos); + void Do_Nod_Casualties_Graph(void); + void Do_Nod_Buildings_Graph(void); + void Input_Name(char str[], int xpos, int ypos, char const pal[]); +}; + +class ScoreAnimClass { + public: + ScoreAnimClass(int x, int y, void const * data); + int XPos; + int YPos; + CountDownTimerClass Timer; + void const * DataPtr; + virtual void Update(void) {} ; + virtual ~ScoreAnimClass(void) {} ; +}; + +class ScoreCredsClass : public ScoreAnimClass { + public: + int Stage; + int MaxStage; + int TimerReset; + void const * CashTurn; + void const * Clock1; + + virtual void Update(void); + ScoreCredsClass(int xpos, int ypos, void const * data, int max, int timer); + virtual ~ScoreCredsClass(void) {}; +}; + +class ScoreTimeClass : public ScoreAnimClass { + public: + int Stage; + int MaxStage; + int TimerReset; + virtual void Update(void); + ScoreTimeClass(int xpos, int ypos, void const * data, int max, int timer); + virtual ~ScoreTimeClass(void) {}; +}; + +class ScorePrintClass : public ScoreAnimClass { + public: + int Background; + int Stage; + void const * PrimaryPalette; + virtual void Update(void); + ScorePrintClass(void const * string, int xpos, int ypos, void const * palette, int background=TBLACK); + ScorePrintClass( int string, int xpos, int ypos, void const * palette, int background=TBLACK); + virtual ~ScorePrintClass(void) {}; +}; + + +class MultiStagePrintClass : public ScoreAnimClass { + public: + int Background; + int Stage; + void const * PrimaryPalette; + virtual void Update(void); + MultiStagePrintClass(void const * string, int xpos, int ypos, void const * palette, int background=TBLACK); + MultiStagePrintClass( int string, int xpos, int ypos, void const * palette, int background=TBLACK); + virtual ~MultiStagePrintClass(void) {}; +}; + + +class ScoreScaleClass : public ScoreAnimClass { + public: + int Stage; + char const * Palette; + virtual void Update(void); + ScoreScaleClass(void const * data, int xpos, int ypos, char const pal[]); + virtual ~ScoreScaleClass(void) {}; + +}; + +#define MAXSCOREOBJS 8 +extern ScoreAnimClass *ScoreObjs[MAXSCOREOBJS]; + +void Multi_Score_Presentation(void); + +#endif diff --git a/SCREEN.H b/SCREEN.H new file mode 100644 index 0000000..c948cbb --- /dev/null +++ b/SCREEN.H @@ -0,0 +1,68 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\screen.h_v 2.15 16 Oct 1995 16:47:38 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SCREEN.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : June 2, 1994 * + * * + * Last Update : June 2, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef SCREEN_H +#define SCREEN_H + + +class ScreenClass +{ + /* + ** The mouse shape is controlled by these variables. These + ** hold the current mouse shape (so resetting won't be needlessly performed) and + ** the normal default mouse shape (when arrow shapes are needed). + */ + MouseShapeType CurrentMouseShape; + MouseShapeType NormalMouseShape; + + public: + + ScreenClass(void) { + CurrentMouseShape = SHP_NONE; + NormalMouseShape = SHP_MOUSE; + }; + + + Init(void); + Set_Default_Mouse(MouseShapeType mouse); + Force_Mouse_Shape(MouseShapeType mouse); + + unsigned char *GamePalette; + unsigned char *BlackPalette; +}; + +#endif diff --git a/SCROLL.CPP b/SCROLL.CPP new file mode 100644 index 0000000..6fd0347 --- /dev/null +++ b/SCROLL.CPP @@ -0,0 +1,256 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\scroll.cpv 2.18 16 Oct 1995 16:49:08 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SCROLL.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/08/95 * + * * + * Last Update : August 25, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * ScrollClass::AI -- Handles scroll AI processing. * + * ScrollClass::ScrollClass -- Constructor for the scroll class object. * + * ScrollClass::Set_Autoscroll -- Turns autoscrolling on or off. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +#define SCROLL_DELAY 1 + +CountDownTimerClass ScrollClass::Counter; + + +/*********************************************************************************************** + * ScrollClass::ScrollClass -- Constructor for the scroll class object. * + * * + * This is the constructor for the scroll class object. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/10/1995 JLB : Created. * + *=============================================================================================*/ +ScrollClass::ScrollClass(void) +{ + IsAutoScroll = true; + Counter.Set(SCROLL_DELAY); + Inertia = 0; + Counter.Start(); +} + + +/*********************************************************************************************** + * ScrollClass::AI -- Handles scroll AI processing. * + * * + * This routine is called every game frame for purposes of input processing. * + * * + * INPUT: input -- Reference to the keyboard/mouse event that just occurred. * + * * + * x,y -- The mouse coordinates. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/10/1995 JLB : Created. * + * 08/10/1995 JLB : Revamped for free smooth scrolling. * + * 08/25/1995 JLB : Handles new scrolling option. * + *=============================================================================================*/ +#define EVA_WIDTH 80 +void ScrollClass::AI(KeyNumType &input, int x, int y) +{ + static DirType direction; + bool player_scrolled=false; + + /* + ** If rubber band mode is in progress, then don't allow scrolling of the tactical map. + */ + if (!IsRubberBand /*&& !IsTentative*/) { + + /* + ** Special check to not scroll within the special no-scroll regions. + */ + bool noscroll = false; + if (Special.IsScrollMod && y == 0 && ((x > 3 && x < EVA_WIDTH) || (x > SeenBuff.Get_Width()-EVA_WIDTH && x < SeenBuff.Get_Width()-3))) { + noscroll = true; + } + + if (!noscroll) { + + /* + ** Verify that the mouse is over a scroll region. + */ + if (Inertia || y == 0 || x == 0 || x == (SeenBuff.Get_Width()-1) || y == (SeenBuff.Get_Height()-1)) { + + if (y == 0 || x == 0 || x == (SeenBuff.Get_Width()-1) || y == (SeenBuff.Get_Height()-1)) { + + player_scrolled=true; + /* + ** Adjust the mouse coordinates to emphasise the + ** cardinal directions over the diagonals. + */ + int altx = x; + if (altx < 50) altx -= (50-altx)*2; + altx = MAX(altx, 0); + if (altx > (SeenBuff.Get_Width()-50)) altx += (altx-(SeenBuff.Get_Width()-50))*2; + altx = MIN(altx, SeenBuff.Get_Width()); + if (altx > 50 && altx < (SeenBuff.Get_Width()-50)) { + altx += ((SeenBuff.Get_Width()/2)-altx)/2; + } + + int alty = y; + if (alty < 50) alty -= (50-alty); + alty = MAX(alty, 0); + if (alty > (SeenBuff.Get_Height()-50)) alty += ((alty-(SeenBuff.Get_Height()-50))); + alty = MIN(alty, SeenBuff.Get_Height()); + + direction = (DirType)Desired_Facing256((SeenBuff.Get_Width())/2, (SeenBuff.Get_Height())/2, altx, alty); + } + int control = Dir_Facing(direction); + + /* + ** The mouse is over a scroll region so set the mouse shape accordingly if the map + ** can be scrolled in the direction indicated. + */ + static int _rate[9] = { + 0x01C0, + 0x0180, + 0x0140, + 0x0100, + 0x00C0, + 0x0080, + 0x0040, + 0x0020, + 0x0010 + }; + + int rate = 8-Inertia; + + if (rate. +*/ + +/* $Header: F:\projects\c&c\vcs\code\scroll.h_v 2.16 16 Oct 1995 16:46:12 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SCROLL.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 11/18/94 * + * * + * Last Update : November 18, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef SCROLL_H +#define SCROLL_H + +#include "help.h" + + +class ScrollClass: public HelpClass +{ + /* + ** If map scrolling is automatic, then this flag is true. Automatic scrolling will + ** cause the map to scroll if the mouse is in the scroll region, regardless of + ** whether or not the mouse button is held down. + */ + unsigned IsAutoScroll:1; + + /* + ** Scroll speed is regulated by this count down timer. When this value reaches zero, + ** scroll the map in the direction required and reset this timer. + */ + static CountDownTimerClass Counter; + + /* + ** These are the delays used (as countdown timers) to regulate the scroll rate + ** of the map. These times are based on game ticks. + */ +// enum ScrollClassEnums { +// INITIAL_DELAY=8, // Delay before scrolling can start at all. +// SEQUENCE_DELAY=4 // Delay between scroll steps. +// }; + + int Inertia; + + + public: + ScrollClass(void); + + bool Set_Autoscroll(int control); + + virtual void AI(KeyNumType &input, int x, int y); + virtual void Init_IO(void) {Counter.Set(0);HelpClass::Init_IO();}; + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); +}; + +#endif diff --git a/SDATA.CPP b/SDATA.CPP new file mode 100644 index 0000000..c6c38c2 --- /dev/null +++ b/SDATA.CPP @@ -0,0 +1,473 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\sdata.cpv 2.17 16 Oct 1995 16:52:10 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SDATA.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : August 9, 1994 * + * * + * Last Update : August 12, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * SmudgeTypeClass::Draw_It -- Renders the smudge image at the coordinate specified. * + * SmudgeTypeClass::Create_One_Of -- Creates a smudge object of this type. * + * SmudgeTypeClass::Create_And_Place -- Creates and places on map, a smudge object. * + * SmudgeTypeClass::Prep_For_Add -- Prepares the scenario editor for adding a smudge object. * + * SmudgeTypeClass::Init -- Performs theater specific initializations. * + * SmudgeTypeClass::Display -- Draws a generic version of this smudge type. * + * SmudgetypeClass::Occupy_List -- Determines occupation list for smudge object. * + * SmudgeTypeClass::From_Name -- Converts an ASCII name into a smudge type. * + * SmudgeTypeClass::SmudgeTypeClass -- Constructor for smudge type objects. * + * SmudgeTypeClass::One_Time -- Performs one-time initialization * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "type.h" + + +static SmudgeTypeClass const Crater1 ( + + SMUDGE_CRATER1, + "CR1", + TXT_CRATER, + 1,1, // Width and height of smudge (in icons). + false, // Is this a building bib? + true // Is this a crater smudge? +); +static SmudgeTypeClass const Crater2 ( + SMUDGE_CRATER2, + "CR2", + TXT_CRATER, + 1,1, // Width and height of smudge (in icons). + false, // Is this a building bib? + true // Is this a crater smudge? +); +static SmudgeTypeClass const Crater3 ( + SMUDGE_CRATER3, + "CR3", + TXT_CRATER, + 1,1, // Width and height of smudge (in icons). + false, // Is this a building bib? + true // Is this a crater smudge? +); +static SmudgeTypeClass const Crater4 ( + SMUDGE_CRATER4, + "CR4", + TXT_CRATER, + 1,1, // Width and height of smudge (in icons). + false, // Is this a building bib? + true // Is this a crater smudge? +); +static SmudgeTypeClass const Crater5 ( + SMUDGE_CRATER5, + "CR5", + TXT_CRATER, + 1,1, // Width and height of smudge (in icons). + false, // Is this a building bib? + true // Is this a crater smudge? +); +static SmudgeTypeClass const Crater6 ( + SMUDGE_CRATER6, + "CR6", + TXT_CRATER, + 1,1, // Width and height of smudge (in icons). + false, // Is this a building bib? + true // Is this a crater smudge? +); +static SmudgeTypeClass const Scorch1 ( + SMUDGE_SCORCH1, + "SC1", + TXT_SCORCH, + 1,1, // Width and height of smudge (in icons). + false, // Is this a building bib? + false // Is this a crater smudge? +); +static SmudgeTypeClass const Scorch2 ( + SMUDGE_SCORCH2, + "SC2", + TXT_SCORCH, + 1,1, // Width and height of smudge (in icons). + false, // Is this a building bib? + false // Is this a crater smudge? +); +static SmudgeTypeClass const Scorch3 ( + SMUDGE_SCORCH3, + "SC3", + TXT_SCORCH, + 1,1, // Width and height of smudge (in icons). + false, // Is this a building bib? + false // Is this a crater smudge? +); +static SmudgeTypeClass const Scorch4 ( + SMUDGE_SCORCH4, + "SC4", + TXT_SCORCH, + 1,1, // Width and height of smudge (in icons). + false, // Is this a building bib? + false // Is this a crater smudge? +); +static SmudgeTypeClass const Scorch5 ( + SMUDGE_SCORCH5, + "SC5", + TXT_SCORCH, + 1,1, // Width and height of smudge (in icons). + false, // Is this a building bib? + false // Is this a crater smudge? +); +static SmudgeTypeClass const Scorch6 ( + SMUDGE_SCORCH6, + "SC6", + TXT_SCORCH, + 1,1, // Width and height of smudge (in icons). + false, // Is this a building bib? + false // Is this a crater smudge? +); + +static SmudgeTypeClass const Bibx1 ( + SMUDGE_BIB1, + "BIB1", + TXT_BIB, + 4,2, // Width and height of smudge (in icons). + true, // Is this a building bib? + false // Is this a crater smudge? +); +static SmudgeTypeClass const Bibx2 ( + SMUDGE_BIB2, + "BIB2", + TXT_BIB, + 3,2, // Width and height of smudge (in icons). + true, // Is this a building bib? + false // Is this a crater smudge? +); + +/* +** The watcom code optimiser screws up the last constructor call. Making it 'volatile' reduces the +** level of optimisation enough for the problem not to manifest. +*/ +volatile SmudgeTypeClass const Bibx3 ( + SMUDGE_BIB3, + "BIB3", + TXT_BIB, + 2,2, // Width and height of smudge (in icons). + true, // Is this a building bib? + false // Is this a crater smudge? +); + +/* +** Working array to the smudge control objects types. This routine is +** used for quick conversion from a SmudgeType number into an actual +** smudge type object pointer. +*/ +SmudgeTypeClass const * const SmudgeTypeClass::Pointers[SMUDGE_COUNT] = { + &Crater1, // SMUDGE_CRATER1 + &Crater2, // SMUDGE_CRATER2 + &Crater3, // SMUDGE_CRATER3 + &Crater4, // SMUDGE_CRATER4 + &Crater5, // SMUDGE_CRATER5 + &Crater6, // SMUDGE_CRATER6 + &Scorch1, // SMUDGE_SCORCH1 + &Scorch2, // SMUDGE_SCORCH2 + &Scorch3, // SMUDGE_SCORCH3 + &Scorch4, // SMUDGE_SCORCH4 + &Scorch5, // SMUDGE_SCORCH5 + &Scorch6, // SMUDGE_SCORCH6 + &Bibx1, // SMUDGE_BIB1 + &Bibx2, // SMUDGE_BIB2 + (SmudgeTypeClass const * const)&Bibx3 // SMUDGE_BIB3 +}; + + +/*********************************************************************************************** + * SmudgeTypeClass::SmudgeTypeClass -- Constructor for smudge type objects. * + * * + * This constructor is used to create the smudge type objects. These type objects contain * + * static information about the various smudge types supported in the game. * + * * + * INPUT: see below... * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/12/1994 JLB : Created. * + *=============================================================================================*/ +SmudgeTypeClass::SmudgeTypeClass(SmudgeType smudge, char const *ininame, int fullname, int width, int height, bool isbib, bool iscrater) : + ObjectTypeClass(false, false, false, true, false, false, true, true, fullname, ininame, ARMOR_NONE, 0) +{ + IsBib = isbib; + Width = width; + Height = height; + IsCrater = iscrater; + Type = smudge; +} + +/*********************************************************************************************** + * SmudgeTypeClass::From_Name -- Converts an ASCII name into a smudge type. * + * * + * This converts an ASCII name into a smudge type number. This is typically necessary * + * when processing scenario INI files and not used otherwise. * + * * + * INPUT: name -- Pointer to the name to convert. * + * * + * OUTPUT: Returns with the SmudgeType number that matches the name supplied. If no match * + * was found, then SMUDGE_NONE is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/12/1994 JLB : Created. * + *=============================================================================================*/ +SmudgeType SmudgeTypeClass::From_Name(char const *name) +{ + if (name) { + for (SmudgeType index = SMUDGE_FIRST; index < SMUDGE_COUNT; index++) { + if (stricmp(As_Reference(index).IniName, name) == 0) { + return(index); + } + } + } + return(SMUDGE_NONE); +} + + +/*********************************************************************************************** + * SmudgetypeClass::Occupy_List -- Determines occupation list for smudge object. * + * * + * Smudges are always only one icon in dimension, so this routine always returns a cell * + * occupation offset list of the center cell. * + * * + * INPUT: placement -- Is this for placement legality checking only? The normal condition * + * is for marking occupation flags. * + * * + * OUTPUT: Returns occupation list specifying all the cells that the overlay occupies. This * + * is just the center cell. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/12/1994 JLB : Created. * + *=============================================================================================*/ +short const * SmudgeTypeClass::Occupy_List(bool) const +{ + static short _occupy[4*4]; + short * ptr = &_occupy[0]; + + for (int x = 0; x < Width; x++) { + for (int y = 0; y < Height; y++) { + *ptr++ = x + (y*MAP_CELL_W); + } + } + *ptr = REFRESH_EOL; + return(_occupy); +} + + +/*********************************************************************************************** + * SmudgeTypeClass::Init -- Performs theater specific initializations. * + * * + * Smudge object imagery varies between theaters. This routine will load the appropriate * + * imagery for the theater specified. * + * * + * INPUT: theater -- The theater to prepare for. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/12/1994 JLB : Created. * + *=============================================================================================*/ +void SmudgeTypeClass::Init(TheaterType theater) +{ + if (theater != LastTheater){ + for (SmudgeType index = SMUDGE_FIRST; index < SMUDGE_COUNT; index++) { + SmudgeTypeClass const & smudge = As_Reference(index); + char fullname[_MAX_FNAME+_MAX_EXT]; // Fully constructed smudge data set name. + + _makepath(fullname, NULL, NULL, smudge.IniName, Theaters[theater].Suffix); + ((void const *&)smudge.ImageData) = MixFileClass::Retrieve(fullname); + } + } +} + + +#ifdef SCENARIO_EDITOR +/*********************************************************************************************** + * SmudgeTypeClass::Display -- Draws a generic version of this smudge type. * + * * + * The scenario object editor will call this routine to display a typical imagery of this * + * smudge object for graphical identification purposes. * + * * + * INPUT: x,y -- Coordinate to render the smudge at. * + * * + * window-- The window to base the coordinate rendering upon. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/12/1994 JLB : Created. * + *=============================================================================================*/ +void SmudgeTypeClass::Display(int x, int y, WindowNumberType window, HousesType ) const +{ + void const *ptr = Get_Image_Data(); + + x += WindowList[window][WINDOWX] << 3; + y += WindowList[window][WINDOWY]; + + if (ptr) { + for (int w = 0; w < Width; w++) { + for (int h = 0; h < Height; h++) { + CC_Draw_Shape(ptr, 0, x + w*ICON_PIXEL_W, y + h*ICON_PIXEL_H, WINDOW_TACTICAL, SHAPE_WIN_REL); + //LogicPage->Draw_Stamp(ptr, w + (h*Width), x + w*ICON_PIXEL_W, y + h*ICON_PIXEL_H, NULL, WINDOW_TACTICAL); + } + } + } +} + + +/*********************************************************************************************** + * SmudgeTypeClass::Prep_For_Add -- Prepares the scenario editor for adding a smudge object. * + * * + * This routine adds smudge objects to the list of objects that the scenario editor can * + * place upon the ground. It is only called from the scenario editor. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/12/1994 JLB : Created. * + *=============================================================================================*/ +void SmudgeTypeClass::Prep_For_Add(void) +{ + for (SmudgeType index = SMUDGE_FIRST; index < SMUDGE_COUNT; index++) { + if (As_Reference(index).Get_Image_Data()) { + Map.Add_To_List(&As_Reference(index)); + } + } +} +#endif + + +/*********************************************************************************************** + * SmudgeTypeClass::Create_And_Place -- Creates and places on map, a smudge object. * + * * + * This routine will, in one motion, create a smudge object and place it upon the map. * + * Since placing a smudge on the map will destroy the object, this routine will leave the * + * smudge object count unchanged. Typically, this routine is used by the scenario editor * + * for creating smudges and placing them on the map. * + * * + * INPUT: cell -- The cell to place the smudge object. * + * * + * OUTPUT: bool; Was the placement successful? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/12/1994 JLB : Created. * + *=============================================================================================*/ +bool SmudgeTypeClass::Create_And_Place(CELL cell, HousesType ) const +{ + if (new SmudgeClass(Type, Cell_Coord(cell))) { + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * SmudgeTypeClass::Create_One_Of -- Creates a smudge object of this type. * + * * + * This routine will create a smudge object of the appropriate type. Smudge objects are * + * transitory in nature. They exist only from the point of creation until they are given * + * a spot on the map to reside. At that time the map data is updated and the smudge * + * object is destroyed. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to a created smudge object. If none could be created, then * + * NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/12/1994 JLB : Created. * + *=============================================================================================*/ +ObjectClass * SmudgeTypeClass::Create_One_Of(HouseClass *) const +{ + return(new SmudgeClass(Type, -1)); +} + + +/*********************************************************************************************** + * SmudgeTypeClass::Draw_It -- Renders the smudge image at the coordinate specified. * + * * + * This routine will draw the smudge overlay image at the coordinate (upper left) * + * specified. The underlying terrain icon is presumed to have already been rendered. * + * * + * INPUT: x,y -- Coordinate of the upper left corner of icon to render the smudge object. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/12/1994 JLB : Created. * + *=============================================================================================*/ +void SmudgeTypeClass::Draw_It(int x, int y, int data) const +{ + void const * ptr = Get_Image_Data(); + if (ptr) { + IsTheaterShape = true; // Smudges are theater specific + CC_Draw_Shape(ptr, data, x, y, WINDOW_TACTICAL, SHAPE_WIN_REL); + IsTheaterShape = false; +// LogicPage->Draw_Stamp(ptr, data, x, y, NULL, WINDOW_TACTICAL); + } +} + + +/*********************************************************************************************** + * SmudgeTypeClass::One_Time -- Performs one-time initialization * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/12/1994 JLB : Created. * + *=============================================================================================*/ +void SmudgeTypeClass::One_Time(void) +{ +} + diff --git a/SEQCONN.CPP b/SEQCONN.CPP new file mode 100644 index 0000000..9526802 --- /dev/null +++ b/SEQCONN.CPP @@ -0,0 +1,561 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\seqconn.cpv 1.9 16 Oct 1995 16:51:30 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SEQCONN.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 20, 1994 * + * * + * Last Update : April 9, 1995 [BRR] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * SequencedConnClass::SequencedConnClass -- class constructor * + * SequencedConnClass::~SequencedConnClass -- class destructor * + * SequencedConnClass::Init -- Initializes connection queue to empty * + * SequencedConnClass::Send_Packet -- adds a packet to the send queue * + * SequencedConnClass::Receive_Packet -- adds packet to receive queue * + * SequencedConnClass::Get_Packet -- gets a packet from receive queue * + * SequencedConnClass::Service_Send_Queue -- services the send queue * + * SequencedConnClass::Service_Receive_Queue -- services recieve queue * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*************************************************************************** + * SequencedConnClass::SequencedConnClass -- class constructor * + * * + * INPUT: * + * numsend desired # of entries for the send queue * + * numreceive desired # of entries for the recieve queue * + * maxlen max length of an application packet * + * magicnum the packet "magic number" for this connection * + * retry_delta the time to wait between sends * + * max_retries the max # of retries allowed for a packet * + * (-1 means retry forever, based on this parameter) * + * timeout the max amount of time before we give up on a packet * + * (-1 means retry forever, based on this parameter) * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +SequencedConnClass::SequencedConnClass (int numsend, int numreceive, + int maxlen, unsigned short magicnum, unsigned long retry_delta, + unsigned long max_retries, unsigned long timeout) : + ConnectionClass (maxlen, magicnum, retry_delta, max_retries, timeout) +{ + NumRecNoAck = 0; + NumRecAck = 0; + NumSendNoAck = 0; + NumSendAck = 0; + + /*------------------------------------------------------------------------ + Allocate the packet Queue. This will store incoming packets (which will + be placed there by the Connection Manager), and outgoing packets (which + are placed there by this class when it "sends" a packet). + ------------------------------------------------------------------------*/ + Queue = new CommQueueClass (numsend, numreceive, MaxPacketLen); + +} /* end of SequencedConnClass */ + + +/*************************************************************************** + * SequencedConnClass::~SequencedConnClass -- class destructor * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +SequencedConnClass::~SequencedConnClass () +{ + delete Queue; +} + + +/*************************************************************************** + * SequencedConnClass::Init -- Initializes connection queue to empty * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +void SequencedConnClass::Init (void) +{ + Queue->Init(); + +} /* end of Init */ + + +/*************************************************************************** + * SequencedConnClass::Send_Packet -- adds a packet to the send queue * + * * + * This routine prefixes the given buffer with a CommHeaderType and * + * queues the resulting packet into the Send Queue. (It's actually the * + * Service() routine that handles the hardware-dependent Send of the data).* + * The packet's MagicNumber, Code, and PacketID are set here. * + * * + * INPUT: * + * buf buffer to send * + * buflen length of buffer * + * ack_req true = ACK is required for this packet; false = isn't * + * * + * OUTPUT: * + * 1 = packet was queue'd OK, 0 = wasn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int SequencedConnClass::Send_Packet (void * buf, int buflen, int ack_req) +{ + /*........................................................................ + Set the magic # for the packet + ........................................................................*/ + ((CommHeaderType *)PacketBuf)->MagicNumber = MagicNum; + + /*........................................................................ + Set the packet Code: DATA_ACK if it requires an ACK, NOACK if it doesn't + Set the packet ID to the appropriate counter value. + ........................................................................*/ + if (ack_req) { + ((CommHeaderType *)PacketBuf)->Code = PACKET_DATA_ACK; + ((CommHeaderType *)PacketBuf)->PacketID = NumSendAck; + } else { + ((CommHeaderType *)PacketBuf)->Code = PACKET_DATA_NOACK; + ((CommHeaderType *)PacketBuf)->PacketID = NumSendNoAck; + } + + /*........................................................................ + Now build the packet + ........................................................................*/ + memcpy(PacketBuf + sizeof(CommHeaderType), buf, buflen); + + /*........................................................................ + Add it to the queue. + ........................................................................*/ + if (Queue->Queue_Send(PacketBuf,buflen + sizeof(CommHeaderType))) { + if (ack_req) { + NumSendAck++; + } else { + NumSendNoAck++; + } + return(true); + + } else { + return(false); + } +} + + +/*************************************************************************** + * SequencedConnClass::Receive_Packet -- adds packet to the receive queue * + * * + * INPUT: * + * buf buffer to process (already includes CommHeaderType) * + * buflen length of buffer to process * + * * + * OUTPUT: * + * 1 = packet was processed OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int SequencedConnClass::Receive_Packet (void * buf, int buflen) +{ + CommHeaderType *packet; // ptr to this packet + SendQueueType *send_entry; // ptr to send entry header + ReceiveQueueType *rec_entry; // ptr to receive entry header + CommHeaderType *entry_data; // ptr to queue entry data + int save_packet = 1; // 0 = this is a resend, or + // out-of-order packet + + /* + --------------------------- Check the magic # ---------------------------- + */ + packet = (CommHeaderType *)buf; + if (packet->MagicNumber!=MagicNum) + return(false); + + /*------------------------------------------------------------------------ + Process the packet based on its Code + ------------------------------------------------------------------------*/ + switch (packet->Code) { + /*..................................................................... + DATA: If this is a No-Ack packet, always save it. Otherwise, only + save it if it's received in the proper sequence. + .....................................................................*/ + case PACKET_DATA_ACK: + case PACKET_DATA_NOACK: + if (packet->Code == PACKET_DATA_NOACK) { +#ifdef DEBUG_SEQ +printf("PACKET_DATA_NOACK received (%d)\n",packet->PacketID); +#endif + save_packet = 1; + } else { +#ifdef DEBUG_SEQ +printf("PACKET_DATA_ACK received (%d)\n",packet->PacketID); +#endif + if ((packet->PacketID == NumRecAck)) { + save_packet = 1; + } else { + save_packet = 0; + /*............................................................... + If this is a resend of our next-available received message, it + means the other app didn't get our ACK, so mark it as + non-acknowledged to tell Service_Receive_Queue to send an ACK. + ...............................................................*/ + rec_entry = Queue->Next_Receive(); + if (rec_entry) { + entry_data = (CommHeaderType *)rec_entry->Buffer; + if (entry_data->PacketID==packet->PacketID && + entry_data->Code == PACKET_DATA_ACK) { + rec_entry->IsACK = 0; +#ifdef DEBUG_SEQ +printf("(Resend)\n"); +#endif + } + } + } + } + + /* + ...................... queue this packet ........................ + */ + if (save_packet) { + if (!Queue->Queue_Receive(buf, buflen)) { + return(false); + } + if (packet->Code == PACKET_DATA_ACK) { + NumRecAck++; + } else { + NumRecNoAck++; + } + } + break; + + /*..................................................................... + ACK: If this ACK is for the latest-sent packet, mark that packet as + acknowledged, then throw this packet away. Otherwise, ignore the ACK + (if we re-sent before we received the other system's first ACK, this + ACK will be a leftover) + .....................................................................*/ + case PACKET_ACK: +#ifdef DEBUG_SEQ +printf("ACK received (%d)\n",packet->PacketID); +#endif + /* + ....................... Get queue entry ptr ........................ + */ + send_entry = Queue->Next_Send(); + /* + ............... If ptr is valid, get ptr to its data ............... + */ + if (send_entry!=NULL) { + entry_data = (CommHeaderType *)send_entry->Buffer; + + /* + .............. If ACK is for this entry, mark it ................ + */ + if (packet->PacketID==entry_data->PacketID && + entry_data->Code == PACKET_DATA_ACK) { + send_entry->IsACK = 1; + } + } + break; + + /*..................................................................... + Default: ignore the packet + .....................................................................*/ + default: + break; + + } /* end of switch */ + + return(true); +} + + +/*************************************************************************** + * SequencedConnClass::Get_Packet -- gets a packet from the receive queue * + * * + * INPUT: * + * buf location to store buffer * + * buflen filled in with length of 'buf' * + * * + * OUTPUT: * + * 1 = packet was read, 0 = wasn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int SequencedConnClass::Get_Packet (void * buf, int *buflen) +{ + ReceiveQueueType *rec_entry; // ptr to receive entry header + int packetlen; // size of received packet + + /* + ------------------ Get ptr to the next available entry ------------------- + */ + rec_entry = Queue->Next_Receive(); + + /* + ------------------------ Read it if it's un-read ------------------------- + */ + if (rec_entry!=NULL && rec_entry->IsRead==0) { + /* + ........................... Mark as read .............................. + */ + rec_entry->IsRead = 1; + + /* + .......................... Copy data packet ........................... + */ + packetlen = rec_entry->BufLen - sizeof(CommHeaderType); + if (packetlen > 0) { + memcpy(buf, rec_entry->Buffer + sizeof(CommHeaderType), packetlen); + } + (*buflen) = packetlen; + return(true); + } + + return(false); +} + + +/*************************************************************************** + * SequencedConnClass::Service_Send_Queue -- services the send queue * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int SequencedConnClass::Service_Send_Queue (void) +{ + SendQueueType *send_entry; // ptr to send queue entry + CommHeaderType *packet_hdr; // packet header + unsigned long curtime; // current time + + /*------------------------------------------------------------------------ + - If the next packet is ACK'd remove it from the queue + - If the next packet isn't ACK'd, [re-]send it + ------------------------------------------------------------------------*/ + /* + ......................... Get ptr to data to send ........................ + */ + send_entry = Queue->Next_Send(); + if (send_entry==NULL) + return(true); + + /* + ------------------ If ACK has been received, unqueue it ------------------ + */ + if (send_entry->IsACK) { + + /* + .................. Update this queue's response time .................. + */ + packet_hdr = (CommHeaderType *)send_entry->Buffer; + if (packet_hdr->Code == PACKET_DATA_ACK) { + Queue->Add_Delay(Time() - send_entry->FirstTime); + } + + /* + ......................... unqueue the packet .......................... + */ +#ifdef DEBUG_SEQ +printf(">>>Unqueueing Receive packet #%d<<<\n",packet_hdr->PacketID); +#endif + Queue->UnQueue_Send(NULL,NULL); + } else { + + /* + ----------------- ACK not received yet, [re-]send packet ----------------- + */ + /*..................................................................... + Only send the message if time has elapsed. (The message's Time + fields are init'd to 0 when a message is queue'd or unqueue'd, so the + first time through, the delta time will appear large.) + .....................................................................*/ + curtime = Time(); + if (curtime - send_entry->LastTime > RetryDelta) { + /* + ......................... Send the message ......................... + */ + Send (send_entry->Buffer, send_entry->BufLen); + /* + ....................... Fill in Time fields ........................ + */ + send_entry->LastTime = curtime; + if (send_entry->SendCount==0) { + send_entry->FirstTime = curtime; + /*............................................................... + If this is the 1st time we're sending this packet, and it doesn't + require an ACK, mark it as ACK'd; then, the next time through, + it will just be removed from the queue. + ...............................................................*/ + packet_hdr = (CommHeaderType *)send_entry->Buffer; + if (packet_hdr->Code == PACKET_DATA_NOACK) + send_entry->IsACK = 1; + } + +#ifdef DEBUG_SEQ +packet_hdr = (CommHeaderType *)send_entry->Buffer; +if (packet_hdr->Code == PACKET_DATA_NOACK) { + printf("Sending PACKET_DATA_NOACK (%d)\n",packet_hdr->PacketID); +} else { + printf("Sending PACKET_DATA_ACK (%d)\n",packet_hdr->PacketID); +} +#endif + /* + ......................... Update SendCount ......................... + */ + send_entry->SendCount++; + /*.................................................................. + Perform error detection, based on either MaxRetries or Timeout + ..................................................................*/ + if (MaxRetries != -1 && send_entry->SendCount > MaxRetries) + return(false); + if (Timeout != -1 && send_entry->LastTime - + send_entry->FirstTime > Timeout) + return(false); + + } + } + + return(true); +} + + +/*************************************************************************** + * SequencedConnClass::Service_Receive_Queue -- services the recieve queue * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = OK, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/20/1994 BR : Created. * + *=========================================================================*/ +int SequencedConnClass::Service_Receive_Queue (void) +{ + CommHeaderType ackpacket; // ACK packet to send + ReceiveQueueType *rec_entry; // ptr to receive entry header + CommHeaderType *packet_hdr; // packet header + + /*------------------------------------------------------------------------ + Get a pointer to the next received entry + ------------------------------------------------------------------------*/ + rec_entry = Queue->Next_Receive(); + if (rec_entry==NULL) + return(true); + + /*------------------------------------------------------------------------ + If this packet doesn't require an ACK, mark it as ACK'd. + ------------------------------------------------------------------------*/ + packet_hdr = (CommHeaderType *)(rec_entry->Buffer); + if (packet_hdr->Code==PACKET_DATA_NOACK) + rec_entry->IsACK = 1; + + /*------------------------------------------------------------------------ + If this packet hasn't been ACK'd, send an ACK: + - Fill in the MagicNum & the Code + - Set the PacketID to the same ID that the sending system used, so the + sending system knows which packet the ACK is for + ------------------------------------------------------------------------*/ + if (rec_entry->IsACK==0) { +#ifdef DEBUG_SEQ +printf("Sending ACK (%d)\n",packet_hdr->PacketID); +#endif + ackpacket.MagicNumber = MagicNum; + ackpacket.Code = PACKET_ACK; + ackpacket.PacketID = packet_hdr->PacketID; + + Send((char *)&ackpacket, sizeof(CommHeaderType)); + + rec_entry->IsACK = 1; + } + + /*------------------------------------------------------------------------ + If this packet has been read by the application, and has been ACK'd, and + there is another packet in the queue behind this one, it means the other + system got the ACK we sent for this packet; remove this packet from the + queue. + ------------------------------------------------------------------------*/ + if (rec_entry!=NULL && rec_entry->IsRead && rec_entry->IsACK && + Queue->Num_Receive() > 1) { +#ifdef DEBUG_SEQ +printf(">>>Unqueueing Send packet #%d<<<\n",packet_hdr->PacketID); +#endif + Queue->UnQueue_Receive(NULL,NULL); + } + + return(true); +} + diff --git a/SEQCONN.H b/SEQCONN.H new file mode 100644 index 0000000..0883933 --- /dev/null +++ b/SEQCONN.H @@ -0,0 +1,111 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\seqconn.h_v 1.12 16 Oct 1995 16:47:58 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SEQCONN.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 19, 1994 * + * * + * Last Update : April 9, 1995 [BR] * + * * + *-------------------------------------------------------------------------* + * * + * This class provides a "Sequenced" ACK/Retry approach to packet * + * transmission. It waits until the last packet has been ACK'd before * + * sending another packet. Thus, it guarantees order of delivery of * + * packets, but its performance will be slower than the Non-Sequenced * + * approach. * + * * + * A derived class must provide: * + * - Init: Initialization of any hardware-specific values. * + * - Send: a hardware-dependent send routine. * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef SEQCONN_H +#define SEQCONN_H + +#include "connect.h" +#include "comqueue.h" + + +/* +***************************** Class Declaration ***************************** +*/ +class SequencedConnClass : public ConnectionClass +{ + /* + ---------------------------- Public Interface ---------------------------- + */ + public: + /*..................................................................... + Constructor/destructor. + .....................................................................*/ + SequencedConnClass (int numsend, int numrecieve, int maxlen, + unsigned short magicnum, unsigned long retry_delta, + unsigned long max_retries, unsigned long timeout); + virtual ~SequencedConnClass (); + + /*..................................................................... + Initialization. + .....................................................................*/ + virtual void Init (void); + + /*..................................................................... + Send/Receive routines. + .....................................................................*/ + virtual int Send_Packet (void * buf, int buflen, int ack_req); + virtual int Receive_Packet (void * buf, int buflen); + virtual int Get_Packet (void * buf, int *buflen); + + /*..................................................................... + The packet queue. + .....................................................................*/ + CommQueueClass *Queue; + + /* + -------------------------- Protected Interface --------------------------- + */ + protected: + /*..................................................................... + Routines to service the Send & Receive queues. + .....................................................................*/ + virtual int Service_Send_Queue (void); + virtual int Service_Receive_Queue (void); + + /*..................................................................... + Running totals of # of packets we send & receive which require an ACK, + and those that don't. + .....................................................................*/ + unsigned long NumRecNoAck; + unsigned long NumRecAck; + unsigned long NumSendNoAck; + unsigned long NumSendAck; + +}; + +#endif +/*************************** end of seqconn.h ******************************/ diff --git a/SESSION.H b/SESSION.H new file mode 100644 index 0000000..f4b27c2 --- /dev/null +++ b/SESSION.H @@ -0,0 +1,553 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SESSION.H * + * * + * Programmer : Bill R. Randolph * + * * + * Start Date : 11/30/95 * + * * + * Last Update : November 30, 1995 [BRR] * + * * + * The purpose of this class is to contain those variables & routines * + * specifically related to a multiplayer game. * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#ifndef SESSION_H +#define SESSION_H + +#include "ipxaddr.h" +#include "msglist.h" +#include "connect.h" + +//--------------------------------------------------------------------------- +// Forward declarations +//--------------------------------------------------------------------------- +class AircraftClass; +class AnimClass; +class BuildingClass; +class BulletClass; +class InfantryClass; +class UnitClass; +class PhoneEntryClass; +class CellClass; + +//--------------------------------------------------------------------------- +// Defines +//--------------------------------------------------------------------------- +//........................................................................... +// Various limiting values +//........................................................................... +#define MAX_PLAYERS 6 // max # of players we can have +#define MPLAYER_BUILD_LEVEL_MAX 7 // max build level in multiplay +#define MAX_MPLAYER_COLORS 6 // max # of colors + +//........................................................................... +// Max sizes of packets we want to send +// The IPX packet's size is IPX's max size (546), rounded down to accommodate +// the max number of events possible. +//........................................................................... +#define MAX_IPX_PACKET_SIZE (((546 - sizeof(CommHeaderType)) / \ + sizeof(EventClass) ) * sizeof(EventClass)) +#define MAX_SERIAL_PACKET_SIZE 200 + +//........................................................................... +// Max length of player names fields; attempt to use the constant for the +// HouseClass, if it's been defined; otherwise, define it myself. +//........................................................................... +#ifdef HOUSE_NAME_MAX +#define MPLAYER_NAME_MAX HOUSE_NAME_MAX +#else +#define MPLAYER_NAME_MAX 12 // max length of a player's name +#endif + +//........................................................................... +// Values to control the multiplayer score screen +//........................................................................... +#define MAX_MULTI_NAMES 8 // max # names (rows) on the score screen +#define MAX_MULTI_GAMES 4 // max # games (columns) on the score screen + +//........................................................................... +// Min value for MaxAhead, for both net & modem; only applies for +// COMM_PROTOCOL_MULTI_E_COMP. +//........................................................................... +#define MODEM_MIN_MAX_AHEAD 5 +#define NETWORK_MIN_MAX_AHEAD 2 + +//........................................................................... +// Send period (in frames) for COMM_PROTOCOL_MULTI_E_COMP and above +//........................................................................... +#define DEFAULT_FRAME_SEND_RATE 3 + +//........................................................................... +// Modem-specific constants +//........................................................................... +#define PORTBUF_MAX 5 // dialog field sizes +#define IRQBUF_MAX 3 +#define BAUDBUF_MAX 7 +#define INITSTRBUF_MAX 41 +#define CWAITSTRBUF_MAX 16 +#define CREDITSBUF_MAX 5 +#define PACKET_TIMING_TIMEOUT 40 // ticks b/w sending a timing packet + +//--------------------------------------------------------------------------- +// Enums +//--------------------------------------------------------------------------- +//........................................................................... +// Types of games; used to tell which protocol we're using +//........................................................................... +typedef enum GameEnum { + GAME_NORMAL, // not multiplayer + GAME_MODEM, // modem game + GAME_NULL_MODEM, // NULL-modem + GAME_IPX, // IPX Network game + GAME_INTERNET // Winsock game +} GameType; + +//........................................................................... +// Various Modem-specific enums +//........................................................................... +typedef enum DetectPortType { + PORT_VALID = 0, + PORT_INVALID, + PORT_IRQ_INUSE +} DetectPortType; + +typedef enum DialStatusType { + DIAL_CONNECTED = 0, + DIAL_NO_CARRIER, + DIAL_BUSY, + DIAL_ERROR, + DIAL_CANCELED +} DialStatusType; + +typedef enum DialMethodType { + DIAL_TOUCH_TONE = 0, + DIAL_PULSE, + DIAL_METHODS +} DialMethodType; + +typedef enum CallWaitStringType { + CALL_WAIT_TONE_1 = 0, + CALL_WAIT_TONE_2, + CALL_WAIT_PULSE, + CALL_WAIT_CUSTOM, + CALL_WAIT_STRINGS_NUM +} CallWaitStringType; + +typedef enum ModemGameType { + MODEM_NULL_HOST = 0, + MODEM_NULL_JOIN, + MODEM_DIALER, + MODEM_ANSWERER, + INTERNET_HOST = MODEM_NULL_HOST, + INTERNET_JOIN = MODEM_NULL_JOIN +} ModemGameType; + +//........................................................................... +// Commands sent over the serial Global Channel +//........................................................................... +typedef enum SerialCommandType { + SERIAL_CONNECT = 100, // Are you there? Hello? McFly? + SERIAL_GAME_OPTIONS = 101, // Hey, dudes, here's some new game options + SERIAL_SIGN_OFF = 102, // Bogus, dudes, my boss is coming; I'm outta here! + SERIAL_GO = 103, // OK, dudes, jump into the game loop! + SERIAL_MESSAGE = 104, // Here's a message + SERIAL_TIMING = 105, // timimg packet + SERIAL_SCORE_SCREEN = 106, // player at score screen + SERIAL_LOADGAME = 107, // Start the game, loading a saved game first + SERIAL_LAST_COMMAND // last command +} SerialCommandType; + +//........................................................................... +// Commands sent over the network Global Channel +//........................................................................... +typedef enum NetCommandType { + NET_QUERY_GAME, // Hey, what games are out there? + NET_ANSWER_GAME, // Yo, Here's my game's name! + NET_QUERY_PLAYER, // Hey, what players are in this game? + NET_ANSWER_PLAYER, // Yo, I'm in that game! + NET_CHAT_ANNOUNCE, // I'm at the chat screen + NET_CHAT_REQUEST, // Respond with a CHAT_ANNOUNCE, please. + NET_QUERY_JOIN, // Hey guys, can I play too? + NET_CONFIRM_JOIN, // Well, OK, if you really want to. + NET_REJECT_JOIN, // No, you can't join; sorry, dude. + NET_GAME_OPTIONS, // Hey, dudes, here's some new game options + NET_SIGN_OFF, // Bogus, dudes, my boss is coming; I'm outta here! + NET_GO, // OK, jump into the game loop! + NET_MESSAGE, // Here's a message + NET_PING, // I'm pinging you to take a time measurement + NET_LOADGAME, // start a game by loading a saved game +} NetCommandType; + +//--------------------------------------------------------------------------- +// Structures +//--------------------------------------------------------------------------- +//........................................................................... +// An entry on the score screen is defined by this structure +//........................................................................... +typedef struct { + char Name[MPLAYER_NAME_MAX]; + int Wins; + int Kills[MAX_MULTI_GAMES]; + int Color; +} MPlayerScoreType; + +//........................................................................... +// Settings for the serial port +//........................................................................... +typedef struct { + int Port; + int IRQ; + int Baud; + DialMethodType DialMethod; + int InitStringIndex; + int CallWaitStringIndex; + char CallWaitString[ CWAITSTRBUF_MAX ]; +} SerialSettingsType; + +//........................................................................... +// This is a "node", used for the lists of available games & players. The +// 'Game' structure is used for games; the 'Player' structure for players. +//........................................................................... +typedef struct NodeNameTag { + char Name[MPLAYER_NAME_MAX]; // player or game name + IPXAddressClass Address; + union { + struct { + unsigned char IsOpen; // is the game open? + unsigned long LastTime; // last time we heard from this guy + } Game; + struct { + HousesType House; // "ActLike" House of this player + unsigned char Color; // Color of this player + HousesType ID; // Actual House of this player + } Player; + struct { + unsigned long LastTime; // last time we heard from this guy + unsigned char LastChance; // we're about to remove him from the list + unsigned char Color; // chat player's color + } Chat; + }; +} NodeNameType; + +//........................................................................... +// Packet sent over the serial Global Channel +//........................................................................... +typedef struct { + SerialCommandType Command; // One of the enum's defined above + char Name[MPLAYER_NAME_MAX]; // Player or Game Name + unsigned long MinVersion; // min version this game supports + unsigned long MaxVersion; // max version this game supports + HousesType House; // player's House + unsigned char Color; // player's color or SIGNOFF ID + unsigned char Scenario; // Scenario # + unsigned int Credits; // player's credits + unsigned int IsBases : 1; // 1 = bases are allowed + unsigned int IsTiberium : 1; // 1 = tiberium is allowed + unsigned int IsGoodies : 1; // 1 = goodies are allowed + unsigned int IsGhosties : 1; // 1 = ghosts are allowed + unsigned char BuildLevel; // buildable level + unsigned char UnitCount; // max # units + int Seed; // random number seed + SpecialClass Special; // command-line options + unsigned int GameSpeed; // Game Speed + unsigned long ResponseTime; // packet response time + char Message[COMPAT_MESSAGE_LENGTH]; // inter-player message + unsigned char ID; // unique ID of sender of message +} SerialPacketType; + +//........................................................................... +// Packet sent over the network Global Channel +//........................................................................... +typedef struct { + NetCommandType Command; // One of the enum's defined above + char Name[MPLAYER_NAME_MAX]; // Player or Game Name + union { + struct { + unsigned int IsOpen : 1; // 1 = game is open for joining + } GameInfo; + struct { + HousesType House; // player's House + unsigned int Color; // player's color + unsigned long NameCRC; // CRC of player's game's name + unsigned long MinVersion; // game's min supported version + unsigned long MaxVersion; // game's max supported version + } PlayerInfo; + struct { + unsigned char Scenario; // Scenario # + unsigned int Credits; // player's credits + unsigned int IsBases : 1; // 1 = bases are allowed + unsigned int IsTiberium : 1; // 1 = tiberium is allowed + unsigned int IsGoodies : 1; // 1 = goodies are allowed + unsigned int IsGhosties : 1; // 1 = ghosts are allowed + unsigned char BuildLevel; // buildable level + unsigned char UnitCount; // max # units + int Seed; // random number seed + SpecialClass Special; // command-line options + unsigned int GameSpeed; // Game Speed + unsigned long Version; // version # common to all players + } ScenarioInfo; + struct { + char Buf[COMPAT_MESSAGE_LENGTH]; // inter-user message + unsigned char Color; // color of sender of message + unsigned long NameCRC; // CRC of sender's Game Name + } Message; + struct { + int OneWay; // one-way response time + } ResponseTime; + struct { + int Why; // why were we rejected from the game? + } Reject; + struct { + unsigned long ID; // unique ID for this chat node + unsigned char Color; // my color + } Chat; + }; +} GlobalPacketType; + +//........................................................................... +// For finding sync bugs; filled in by the engine when certain conditions +// are met; the pointers allow examination of objects in the debugger. +//........................................................................... +typedef struct { + union { + AircraftClass *Aircraft; + AnimClass *Anim; + BuildingClass *Building; + BulletClass *Bullet; + InfantryClass *Infantry; + UnitClass *Unit; + void *All; + } Ptr; +} TrapObjectType; + +typedef struct { + int ScenarioIndex; + int Bases; + int Credits; + int Tiberium; + int Goodies; + int Ghosts; + int UnitCount; +} GameOptionsType; + +//--------------------------------------------------------------------------- +// Class Definition +//--------------------------------------------------------------------------- +class SessionClass +{ + //------------------------------------------------------------------------ + // Public interface + //------------------------------------------------------------------------ + public: + //..................................................................... + // Constructor/Destructor + //..................................................................... + SessionClass(void); + ~SessionClass(void); + + //..................................................................... + // Initialization + //..................................................................... + void One_Time(void); + void Init(void); + + //..................................................................... + // Reads/writes to the INI file + //..................................................................... + void Read_MultiPlayer_Settings (void); + void Write_MultiPlayer_Settings (void); + void Read_Scenario_Descriptions (void); + void Free_Scenario_Descriptions(void); + + //..................................................................... + // Utility functions + //..................................................................... + int Create_Connections(void); + bool Am_I_Master(void); + unsigned long Compute_Unique_ID(void); + + //..................................................................... + // File I/O + //..................................................................... + int Save(FileClass &file); + int Load(FileClass &file); + + //..................................................................... + // Debugging / Sync Bugs + //..................................................................... + void Trap_Object(void); + + //--------------------------------------------------------------------- + // Public Data + //--------------------------------------------------------------------- + //..................................................................... + // The type of session being played + //..................................................................... + GameType Type; + + //..................................................................... + // The current communications protocol + //..................................................................... + CommProtocolType CommProtocol; + + //..................................................................... + // Game options + //..................................................................... + GameOptionsType Options; + + //..................................................................... + // Unique workstation ID, for detecting my own packets + //..................................................................... + unsigned long UniqueID; + + //..................................................................... + // Player's local options + //..................................................................... + char Handle[MPLAYER_NAME_MAX]; // player name + int PrefColor; // preferred color index + int ColorIdx; // actual color index + HousesType House; // GDI / NOD + int Blitz; // 1 = AI blitzes + int ObiWan; // 1 = player can see all + int Solo; // 1 = player can play alone + + //..................................................................... + // Max allowable # of players & actual # of (human) players + //..................................................................... + int MaxPlayers; + int NumPlayers; + + //..................................................................... + // Frame-sync'ing timing variables + // 'MaxAhead' is the number of frames ahead of this one to execute + // a given packet. It's set by the RESPONSE_TIME event. + // 'FrameSendRate' is the # frames between data packets + // 'FrameRateDelay' is the time ticks to wait between frames, for + // smoothing. + //..................................................................... + unsigned long MaxAhead; + unsigned long FrameSendRate; + unsigned long FrameRateDelay; + + //..................................................................... + // This flag is set when we've loaded a multiplayer game. + //..................................................................... + int LoadGame; + + //..................................................................... + // This flag is set when the modem game saves the game due to a lost + // connection. + //..................................................................... + int EmergencySave; + + //..................................................................... + // List of scenarios & their file numbers + //..................................................................... + DynamicVectorClass Scenarios; + DynamicVectorClass Filenum; + + //..................................................................... + // This is the multiplayer messaging system + //..................................................................... + MessageListClass Messages; + IPXAddressClass MessageAddress; + char LastMessage[MAX_MESSAGE_LENGTH]; + int WWChat : 1; // 1 = go into special WW Chat mode + + //..................................................................... + // This is the multiplayer scorekeeping system + //..................................................................... + MPlayerScoreType Score[MAX_MULTI_NAMES]; + int GamesPlayed; // # games played this run + int NumScores; // # active entries in MPlayerScore + int Winner; // index of winner of last game + int CurGame; // index of current game being played + + //..................................................................... + // Static arrays + //..................................................................... + static int GColors[]; + static int TColors[]; + static char Descriptions[100][40]; + static int CountMin[2]; + static int CountMax[2]; + static char * GlobalPacketNames[]; + static char * SerialPacketNames[]; + + //..................................................................... + // For Recording & Playing back a file + //..................................................................... + CCFileClass RecordFile; + int Record : 1; + int Play : 1; + int Attract : 1; + + //..................................................................... + // IPX-specific variables + //..................................................................... + int IsBridge; // 1 = we're crossing a bridge + IPXAddressClass BridgeNet; // address of bridge + bool NetStealth; // makes us invisible + bool NetProtect; // keeps others from messaging us + bool NetOpen; // 1 = game is open for joining + char GameName[MPLAYER_NAME_MAX]; // game's name + GlobalPacketType GPacket; // global packet + int GPacketlen; // global packet length + IPXAddressClass GAddress; // address of sender + unsigned short GProductID; // product ID of sender + char MetaPacket[MAX_IPX_PACKET_SIZE]; // packet building buffer + int MetaSize; // size of MetaPacket + DynamicVectorClass Games; // list of games + DynamicVectorClass Players; // list of players + DynamicVectorClass Chat; // list of chat nodes + + //..................................................................... + // Modem-specific variables + //..................................................................... + bool ModemService : 1; // 1 = service modem in Call_Back + int CurPhoneIdx; // phone listing index + SerialSettingsType SerialDefaults; // default serial settings + ModemGameType ModemType; // caller or answerer? + + DynamicVectorClass PhoneBook; + DynamicVectorClass InitStrings; + static char * DialMethodCheck[ DIAL_METHODS ]; + static char * CallWaitStrings[ CALL_WAIT_STRINGS_NUM ]; + + //..................................................................... + // For finding Sync Bugs + //..................................................................... + long TrapFrame; + RTTIType TrapObjType; + TrapObjectType TrapObject; + COORD TrapCoord; + void * TrapThis; + CellClass * TrapCell; + int TrapCheckHeap; + +}; + +#endif // SESSION_H + +/*************************** end of session.h ******************************/ diff --git a/SHAPEBTN.CPP b/SHAPEBTN.CPP new file mode 100644 index 0000000..a2e2264 --- /dev/null +++ b/SHAPEBTN.CPP @@ -0,0 +1,166 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\shapebtn.cpv 2.17 16 Oct 1995 16:52:00 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SHAPEBTN.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 15, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * ShapeButtonClass::ShapeButtonClass -- Constructor for a shape type button. * + * ShapeButtonClass::Draw_Me -- Renders the shape button's imagery. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "shapebtn.h" + + +/*********************************************************************************************** + * ShapeButtonClass::ShapeButtonClass -- Default Constructor for a shape type button. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: You must call Set_Shape() before using a button constructed with this function, * + * and you must set X & Y, and ID. * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +ShapeButtonClass::ShapeButtonClass(void) : + ToggleClass(0, 0, 0, 0, 0) +{ + ReflectButtonState = false; +} + + +/*********************************************************************************************** + * ShapeButtonClass::ShapeButtonClass -- Constructor for a shape type button. * + * * + * This is the normal constructor for a shape type button. Shape buttons are ones that * + * have their imagery controlled by a shape file. The various states of the button are * + * given a visual form as one of these shapes. Button dimensions are controlled by the * + * first shape. * + * * + * INPUT: id -- The button ID. * + * * + * shape -- Pointer to the shape file that controls the button's display. * + * * + * x,y -- The pixel coordinate of the upper left corner of the button. * + * * + * OUTPUT: none * + * * + * WARNINGS: The width and height of the shape is controlled by the first shape in the * + * shape file provided. This means that all the shapes in the shape file must be * + * the same size. * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +ShapeButtonClass::ShapeButtonClass(unsigned id, void const * shape, int x, int y) : + ToggleClass(id, x, y, 0, 0) +{ + Width = 0; + Height = 0; + ReflectButtonState = false; + Set_Shape(shape); +} + + +void ShapeButtonClass::Set_Shape(void const * data) +{ + ShapeData = data; + if (ShapeData) { + Width = Get_Build_Frame_Width(ShapeData); + Height = Get_Build_Frame_Height(ShapeData); + } +} + + +/*********************************************************************************************** + * ShapeButtonClass::Draw_Me -- Renders the shape button's imagery. * + * * + * This function is called when the button detects that it must be redrawn. The actual * + * shape to use is controled by the button's state and the shape file provided when then * + * button was constructed. * + * * + * INPUT: forced -- Should the button be redrawn regardless of the redraw flag? * + * * + * OUTPUT: bool; Was the shape redrawn? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +int ShapeButtonClass::Draw_Me(int forced) +{ + if (ControlClass::Draw_Me(forced) && ShapeData) { + + /* + ** Hide the mouse. + */ + if (LogicPage == &SeenBuff) { + Conditional_Hide_Mouse(X, Y, X+Width-1, Y+Height-1); + } + + /* + ** Draw the body & set text color. + */ + int shapenum = 0; + if (IsDisabled) { + shapenum = DISABLED_SHAPE; + } else { + + if (!ReflectButtonState){ + if (IsPressed) { + shapenum = DOWN_SHAPE; + } else { + shapenum = UP_SHAPE; + } + }else{ + shapenum = IsOn; + } + } + CC_Draw_Shape(ShapeData, shapenum, X, Y, WINDOW_MAIN, SHAPE_NORMAL); + + /* + ** Display the mouse. + */ + if (LogicPage == &SeenBuff) { + Conditional_Show_Mouse(); + } + return(true); + } + return(false); +} + + + diff --git a/SHAPEBTN.H b/SHAPEBTN.H new file mode 100644 index 0000000..e44c8e3 --- /dev/null +++ b/SHAPEBTN.H @@ -0,0 +1,67 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\shapebtn.h_v 2.18 16 Oct 1995 16:46:30 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SHAPEBTN.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 15, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef SHAPEBTN_H +#define SHAPEBTN_H + +#include "toggle.h" + +class ShapeButtonClass : public ToggleClass +{ + public: + ShapeButtonClass(void); + ShapeButtonClass(unsigned id, void const * shapes, int x, int y); + virtual int Draw_Me(int forced=false); + virtual void Set_Shape(void const * data); + + enum ShapeButtonClassEnums { + UP_SHAPE, // Shape to use when button is "up". + DOWN_SHAPE, // Shape to use when button is "down". + DISABLED_SHAPE, // Shape to use when button is disabled. + }; + + unsigned ReflectButtonState:1; + + protected: + + /* + ** This points to the shape data file. This file contains the appropriate shapes + ** for this button in the offsets specified above. + */ + void const * ShapeData; +}; +#endif diff --git a/SIDEBAR.CPP b/SIDEBAR.CPP new file mode 100644 index 0000000..e6a7490 --- /dev/null +++ b/SIDEBAR.CPP @@ -0,0 +1,2535 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\sidebar.cpv 2.13 02 Aug 1995 17:03:22 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SIDEBAR.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : October 20, 1994 * + * * + * Last Update : January 25, 1996 [] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * SidebarClass::Abandon_Production -- Stops production of the object specified. * + * SidebarClass::Activate -- Controls the sidebar activation. * + * SidebarClass::Activate_Demolish -- Controls the demolish button on the sidebar. * + * SidebarClass::Activate_Repair -- Controls the repair button on the sidebar. * + * SidebarClass::Activate_Upgrade -- Controls the upgrade button on the sidebar. * + * SidebarClass::Add -- Adds a game object to the sidebar list. * + * SidebarClass::AI -- Handles player clicking on sidebar area. * + * SidebarClass::Draw_It -- Renders the sidebar display. * + * SidebarClass::Factory_Link -- Links a factory to a sidebar strip. * + * SidebarClass::Init_Clear -- Sets sidebar to a known (and deactivated) state * + * SidebarClass::Init_IO -- Adds buttons to the button list * + * SidebarClass::Init_Theater -- Performs theater-specific initialization * + * SidebarClass::One_Time -- Handles the one time game initializations. * + * SidebarClass::One_Time -- Handles the one time game initializations. * + * SidebarClass::Recalc -- Examines the sidebar data and updates it as necessary. * + * SidebarClass::Refresh_Cells -- Intercepts the refresh, looking for sidebar controls. * + * SidebarClass::SBGadgetClass::Action -- Special function that controls the mouse over the s* + * SidebarClass::Scroll -- Handles scrolling the sidebar object strip. * + * SidebarClass::Set_Current -- Sets a specified object that controls the sidebar display. * + * SidebarClass::SidebarClass -- Default constructor for the sidebar. * + * SidebarClass::StripClass::Abandon_Produ -- Abandons production associated with sidebar. * + * SidebarClass::StripClass::Activate -- Adds the strip buttons to the input system. * + * SidebarClass::StripClass::Add -- Add an object to the side strip. * + * SidebarClass::StripClass::AI -- Input and AI processing for the side strip. * + * SidebarClass::StripClass::Deactivate -- Removes the side strip buttons from the input syst* + * SidebarClass::StripClass::Draw_It -- Render the sidebar display. * + * SidebarClass::StripClass::Factory_Link -- Links a factory to a sidebar button. * + * SidebarClass::StripClass::Flag_To_Redra -- Flags the sidebar strip to be redrawn. * + * SidebarClass::StripClass::Get_Special_Cameo -- Fetches the special event cameo shape. * + * SidebarClass::StripClass::Init_Clear -- Sets sidebar to a known (and deactivated) state * + * SidebarClass::StripClass::Init_IO -- Adds buttons to the button list * + * SidebarClass::StripClass::Init_Theater -- Performs theater-specific initialization * + * SidebarClass::StripClass::One_Time -- Performs one time actions necessary for the side str* + * SidebarClass::StripClass::Recalc -- Revalidates the current sidebar list of objects. * + * SidebarClass::StripClass::Scroll -- Causes the side strip to scroll. * + * SidebarClass::StripClass::SelectClass:: -- Action function when buildable cameo is selecte* + * SidebarClass::StripClass::SelectClass:: -- Assigns special values to a buildable select bu* + * SidebarClass::StripClass::SelectClass::SelectClass -- Default constructor. * + * SidebarClass::StripClass::StripClass -- Default constructor for the side strip class. * + * SidebarClass::Which_Column -- Determines which column a given type should appear. * + * sortfunc -- Utility routine that handles 'qsort' the strip buttons. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +/* +** Define "_RETRIEVE" if the palette morphing tables are part of the loaded data. If this +** is undefined, then the files will be created. +*/ +#define _RETRIEVE + + +/*************************************************************************** +** This holds the translucent table for use with the construction clock +** animation. +*/ +char SidebarClass::StripClass::ClockTranslucentTable[(1+1)*256]; + + +/*************************************************************************** +** This points to the main sidebar shapes. These include the upgrade and +** repair buttons. +*/ +TheaterType SidebarClass::StripClass::LastTheater = THEATER_NONE; + +typedef enum ButtonNumberType { + BUTTON_RADAR = 100, + BUTTON_REPAIR, + BUTTON_DEMOLISH, + BUTTON_UPGRADE, + BUTTON_SELECT, + BUTTON_ZOOM +} ButtonNumberType; + +/* +** Sidebar buttons +*/ +SidebarClass::SBGadgetClass SidebarClass::Background; +ShapeButtonClass SidebarClass::Repair; +ShapeButtonClass SidebarClass::Upgrade; +ShapeButtonClass SidebarClass::Zoom; +ShapeButtonClass SidebarClass::StripClass::UpButton[COLUMNS]; +ShapeButtonClass SidebarClass::StripClass::DownButton[COLUMNS]; +SidebarClass::StripClass::SelectClass +SidebarClass::StripClass::SelectButton[COLUMNS][MAX_VISIBLE]; + +/* +** Shape data pointers +*/ +void const * SidebarClass::StripClass::LogoShapes; +void const * SidebarClass::StripClass::ClockShapes; +void const * SidebarClass::StripClass::SpecialShapes[3]; + +void const * SidebarClass::SidebarShape1; +void const * SidebarClass::SidebarShape2; + + +/*********************************************************************************************** + * SidebarClass::SidebarClass -- Default constructor for the sidebar. * + * * + * Constructor for the sidebar handler. It basically sets up the sidebar to the empty * + * condition. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/17/1994 JLB : Created. * + *=============================================================================================*/ +SidebarClass::SidebarClass(void) +{ + IsSidebarActive = false; + IsRepairActive = false; + IsUpgradeActive = false; + IsDemolishActive = false; + IsToRedraw = true; + +} + + +/*********************************************************************************************** + * SidebarClass::One_Time -- Handles the one time game initializations. * + * * + * This routine is used to load the graphic data that is needed by the sidebar display. It * + * should only be called ONCE. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Only call this routine once when the game first starts. * + * * + * HISTORY: * + * 10/28/94 JLB : Created. * + *=============================================================================================*/ +void SidebarClass::One_Time(void) +{ + PowerClass::One_Time(); + /* + ** Set up the pixel offsets and widths and heights used to render the + ** sidebar. They are now variables because we need to change them for + ** variable resolutions. + */ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + SideBarWidth = SIDEBARWIDTH * factor; + SideX = SeenBuff.Get_Width() - SideBarWidth; + SideY = Map.RadY + Map.RadHeight + 1; + SideWidth = SeenBuff.Get_Width() - SideX; + SideHeight = SeenBuff.Get_Height() - SideY; + MaxVisible = 4; + ButtonHeight = 9 * factor; + TopHeight = ButtonHeight + (4 * factor); + + Background.X = SideX+8 * factor; + Background.Y = SideY, + Background.Width = SideWidth-1; + Background.Height = SideHeight-1; + /* + ** This sets up the clipping window. This window is used by the shape drawing + ** code so that as the sidebar buildable buttons scroll, they get properly + ** clipped at the top and bottom edges. + */ + WindowList[WINDOW_SIDEBAR][WINDOWX] = (SideX+PowWidth) >> 3; + WindowList[WINDOW_SIDEBAR][WINDOWY] = SideY + 1 + TopHeight; + WindowList[WINDOW_SIDEBAR][WINDOWWIDTH] = SideWidth>>3; + WindowList[WINDOW_SIDEBAR][WINDOWHEIGHT] = (MaxVisible * (StripClass::OBJECT_HEIGHT * factor)) - 1; + + /* + ** Set up the coordinates for the sidebar strips. These coordinates are for + ** the upper left corner. + */ + int width = (SideWidth - PowWidth) - (((StripClass::STRIP_WIDTH ) * factor) << 1); + int spacing = width / 3; + + Column[0].X = SideX + PowWidth + spacing; + Column[0].Y = SideY + TopHeight + 1; + Column[1].X = Column[0].X + (StripClass::STRIP_WIDTH * factor) + spacing -1; + Column[1].Y = SideY + TopHeight + 1; + + Column[0].One_Time(0); + Column[1].One_Time(1); + + SidebarShape1 = Hires_Retrieve("SIDE1.SHP"); + SidebarShape2 = Hires_Retrieve("SIDE2.SHP"); + + +} + + +/*********************************************************************************************** + * SidebarClass::Init_Clear -- Sets sidebar to a known (and deactivated) state * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +void SidebarClass::Init_Clear(void) +{ + PowerClass::Init_Clear(); + + IsToRedraw = true; + IsRepairActive = false; + IsUpgradeActive = false; + IsDemolishActive = false; + + Column[0].Init_Clear(); + Column[1].Init_Clear(); + + Activate(false); +} + + +/*********************************************************************************************** + * SidebarClass::Init_IO -- Adds buttons to the button list * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +void SidebarClass::Init_IO(void) +{ + void *oldfont; + int oldx; + PowerClass::Init_IO(); + + /* + ** Add the sidebar's buttons only if we're not in editor mode. + */ + int buttonspacing = (SideBarWidth - (ButtonOneWidth + ButtonTwoWidth + ButtonThreeWidth)) / 4; + + + if (!Debug_Map) { + /* + ** Set the button widths based on the string that goes in them. + */ + oldfont = Set_Font(Font6Ptr); + oldx = FontXSpacing; + FontXSpacing = -1; + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, TPF_6POINT | TPF_NOSHADOW); + + int maxwidth = String_Pixel_Width(Text_String(TXT_REPAIR_BUTTON)) + 8; + maxwidth = MAX(maxwidth, String_Pixel_Width(Text_String(TXT_BUTTON_SELL)) + 8); + maxwidth = MAX(maxwidth, String_Pixel_Width(Text_String(TXT_MAP)) + 8); + Repair.Width = maxwidth; + Upgrade.Width = maxwidth; + Zoom.Width = maxwidth; +// Repair.Width = String_Pixel_Width(Text_String(TXT_REPAIR_BUTTON)) + 8; +// Upgrade.Width = String_Pixel_Width(Text_String(TXT_BUTTON_SELL)) + 8; +// Zoom.Width = String_Pixel_Width(Text_String(TXT_MAP)) + 8; + /* + ** find the spacing between buttons by getting remaining width + ** and dividing it between the buttons. + */ + int buttonspacing = (SideBarWidth - (Repair.Width + Upgrade.Width + Zoom.Width)) / 4; + + Repair.IsSticky = true; + Repair.ID = BUTTON_REPAIR; + Repair.X = 484; + Repair.Y = 160; + Repair.IsPressed = false; + Repair.IsToggleType = true; + Repair.ReflectButtonState = true; +#if (FRENCH) + Repair.Set_Shape(Hires_Retrieve("REPAIRF.SHP")); +#else +#if (GERMAN) + Repair.Set_Shape(Hires_Retrieve("REPAIRG.SHP")); +#else + Repair.Set_Shape(Hires_Retrieve("REPAIR.SHP")); +#endif +#endif + + Upgrade.IsSticky = true; + Upgrade.ID = BUTTON_UPGRADE; + Upgrade.X = 480+57; + Upgrade.Y = 160; + Upgrade.IsPressed = false; + Upgrade.IsToggleType = true; + Upgrade.ReflectButtonState = true; +#if (FRENCH) + Upgrade.Set_Shape(Hires_Retrieve("SELLF.SHP")); +#else +#if (GERMAN) + Upgrade.Set_Shape(Hires_Retrieve("SELLG.SHP")); +#else + Upgrade.Set_Shape(Hires_Retrieve("SELL.SHP")); +#endif +#endif + + Zoom.IsSticky = true; + Zoom.ID = BUTTON_ZOOM; + Zoom.X = 480 + 110; + Zoom.Y = 160; + Zoom.IsPressed = false; +#if (FRENCH) + Zoom.Set_Shape(Hires_Retrieve("MAPF.SHP")); +#else +#if (GERMAN) + Zoom.Set_Shape(Hires_Retrieve("MAPG.SHP")); +#else + Zoom.Set_Shape(Hires_Retrieve("MAP.SHP")); +#endif +#endif + + if (IsRadarActive || GameToPlay!=GAME_NORMAL) { + Zoom.Enable(); + } else { + Zoom.Disable(); + } + + Set_Font(oldfont); + FontXSpacing = oldx; + FontXSpacing = -1; + + + Column[0].Init_IO(0); + Column[1].Init_IO(1); + + /* + ** If a game was loaded & the sidebar was enabled, pop it up now + */ + if (IsSidebarActive) { + IsSidebarActive = false; + Activate(1); +// Background.Zap(); +// Add_A_Button(Background); + } + } +} + + +/*********************************************************************************************** + * SidebarClass::Init_Theater -- Performs theater-specific initialization * + * * + * INPUT: theater -- The theater that is being initialized. Sometimes this has an effect on * + * the data that is loaded. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +void SidebarClass::Init_Theater(TheaterType theater) +{ + PowerClass::Init_Theater(theater); + + Column[0].Init_Theater(theater); + Column[1].Init_Theater(theater); +} + + +/*********************************************************************************************** + * SidebarClass::Which_Column -- Determines which column a given type should appear. * + * * + * Use this function to resolve what column the specified object type should be placed * + * into. * + * * + * INPUT: otype -- Pointer to the object type class of the object in question. * + * * + * OUTPUT: Returns with the column number that the object should be placed in. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/01/1995 JLB : Created. * + *=============================================================================================*/ +int SidebarClass::Which_Column(RTTIType type) +{ + if (type == RTTI_BUILDINGTYPE || type == RTTI_BUILDING) { + return(0); + } + return(1); +} + + +/*********************************************************************************************** + * SidebarClass::Factory_Link -- Links a factory to a sidebar strip. * + * * + * This routine will link the specified factory to the sidebar strip. A factory must be * + * linked to the sidebar so that as the factory production progresses, the sidebar will * + * show the production progress. * + * * + * INPUT: factory -- The factory number to attach. * + * * + * type -- The object type number. * + * * + * id -- The object sub-type number. * + * * + * OUTPUT: Was the factory successfully attached to the sidebar strip? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/19/1995 JLB : Created. * + *=============================================================================================*/ +bool SidebarClass::Factory_Link(int factory, RTTIType type, int id) +{ + return(Column[Which_Column(type)].Factory_Link(factory, type, id)); +} + + +/*********************************************************************************************** + * SidebarClass::Refresh_Cells -- Intercepts the refresh, looking for sidebar controls. * + * * + * This routine intercepts the Refresh_Cells call in order to see if the sidebar needs * + * to be refreshed as well. If the special code to refresh the sidebar was found, it * + * flags the sidebar to be redrawn and then removes the code from the list. * + * * + * INPUT: cell -- The cell to base the refresh list on. * + * * + * list -- Pointer to the cell offset list that elaborates all the cells that * + * need to be flagged for redraw. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void SidebarClass::Refresh_Cells(CELL cell, short const *list) +{ + if (*list == REFRESH_SIDEBAR) { + IsToRedraw = true; + Column[0].IsToRedraw = true; + Column[1].IsToRedraw = true; + Flag_To_Redraw(false); + } + PowerClass::Refresh_Cells(cell, list); +} + + +/*********************************************************************************************** + * SidebarClass::Activate_Repair -- Controls the repair button on the sidebar. * + * * + * Use this routine to turn the repair sidebar button on and off. Typically, the button * + * is enabled when the currently selected structure is friendly and damaged. * + * * + * INPUT: control -- The controls how the button is to be activated or deactivated; * + * 0 -- Turn button off. * + * 1 -- Turn button on. * + * -1 -- Toggle button state. * + * * + * OUTPUT: bool; Was the button previously activated? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +bool SidebarClass::Activate_Repair(int control) +{ + bool old = IsRepairActive; + + if (control == -1) { + control = IsRepairActive ? 0 : 1; + } + switch (control) { + case 1: + IsRepairActive = true; + break; + + default: + case 0: + IsRepairActive = false; + break; + } + if (old != IsRepairActive) { + Flag_To_Redraw(false); + IsToRedraw = true; + + if (!IsRepairActive) { + Help_Text(TXT_NONE); + Set_Default_Mouse(MOUSE_NORMAL, false); + } + } + return(old); +} + + +/*********************************************************************************************** + * SidebarClass::Activate_Upgrade -- Controls the upgrade button on the sidebar. * + * * + * Use this routine to turn the upgrade sidebar button on and off. Typically, the button * + * is enabled when the currently selected structure can be upgraded and disabled otherwise. * + * * + * INPUT: control -- The controls how the button is to be activated or deactivated; * + * 0 -- Turn button off. * + * 1 -- Turn button on. * + * -1 -- Toggle button state. * + * * + * OUTPUT: bool; Was the button previously activated? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +bool SidebarClass::Activate_Upgrade(int control) +{ + bool old = IsUpgradeActive; + if (control == -1) { + control = IsUpgradeActive ? 0 : 1; + } + switch (control) { + case 1: + IsUpgradeActive = true; + break; + + default: + case 0: + IsUpgradeActive = false; + break; + } + if (old != IsUpgradeActive) { + Flag_To_Redraw(false); + IsToRedraw = true; + if (!IsUpgradeActive) { + Set_Default_Mouse(MOUSE_NORMAL, false); + } + } + return(old); +} + + +/*********************************************************************************************** + * SidebarClass::Activate_Demolish -- Controls the demolish button on the sidebar. * + * * + * Use this routine to turn the demolish/dismantle sidebar button on and off. Typically, * + * the button is enabled when a friendly building is selected and disabled otherwise. * + * * + * INPUT: control -- The controls how the button is to be activated or deactivated; * + * 0 -- Turn button off. * + * 1 -- Turn button on. * + * -1 -- Toggle button state. * + * * + * OUTPUT: bool; Was the button previously activated? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +bool SidebarClass::Activate_Demolish(int control) +{ + bool old = IsDemolishActive; + + if (control == -1) { + control = IsDemolishActive ? 0 : 1; + } + switch (control) { + case 1: + IsDemolishActive = true; + break; + + default: + case 0: + IsDemolishActive = false; + break; + } + if (old != IsDemolishActive) { + Flag_To_Redraw(false); + IsToRedraw = true; + if (!IsDemolishActive) { + Set_Default_Mouse(MOUSE_NORMAL, false); + } + } + return(old); +} + + +/*********************************************************************************************** + * SidebarClass::Add -- Adds a game object to the sidebar list. * + * * + * This routine is used to add a game object to the sidebar. Call this routine when a * + * factory type building is created. It handles the case of adding an item that has already * + * been added -- it just ignores it. * + * * + * INPUT: object -- Pointer to the object that is being added. * + * * + * OUTPUT: bool; Was the object added to the sidebar? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/17/1994 JLB : Created. * + *=============================================================================================*/ +bool SidebarClass::Add(RTTIType type, int id) +{ + int column; + + /* + ** Add the sidebar only if we're not in editor mode. + */ + if (!Debug_Map) { + column = Which_Column(type); + + if (Column[column].Add(type, id)) { + Activate(1); + IsToRedraw = true; + Flag_To_Redraw(false); + return(true); + } + return(false); + } + + return(false); +} + + +/*********************************************************************************************** + * SidebarClass::Scroll -- Handles scrolling the sidebar object strip. * + * * + * This routine is used to scroll the sidebar strip of objects. The strip appears whenever * + * a building is selected that can produce units. If the number of units to produce is * + * greater than what the sidebar can hold, this routine is used to scroll the other object * + * into view so they can be selected. * + * * + * INPUT: up -- Should the scroll be upwards? Upward scrolling reveals object that are * + * later in the list of objects. * + * * + * OUTPUT: bool; Did scrolling occur? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/28/94 JLB : Created. * + *=============================================================================================*/ +bool SidebarClass::Scroll(bool up, int column) +{ + if (Column[column].Scroll(up)) { + IsToRedraw = true; + Flag_To_Redraw(false); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * SidebarClass::Draw_It -- Renders the sidebar display. * + * * + * This routine performs the actual drawing of the sidebar display. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the sidebar imagery changed at all? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/28/94 JLB : Created. * + * 12/31/1994 JLB : Split rendering off into the sidebar strip class. * + *=============================================================================================*/ +void SidebarClass::Draw_It(bool complete) +{ + PowerClass::Draw_It(complete); + + if (IsSidebarActive && (IsToRedraw || complete) && !Debug_Map) { + IsToRedraw = false; + + if (LogicPage->Lock()){ + /* + ** Draw the outline box around the sidebar buttons. + */ + //CC_Draw_Shape(SidebarShape1, (int)complete, SideX, 158, WINDOW_MAIN, SHAPE_WIN_REL); + //CC_Draw_Shape(SidebarShape2, (int)complete, SideX, 158+118, WINDOW_MAIN, SHAPE_WIN_REL); + LogicPage->Draw_Line(SideX, 157, SeenBuff.Get_Width()-1, 157, 0); + CC_Draw_Shape(SidebarShape1, 0, SideX, 158, WINDOW_MAIN, SHAPE_WIN_REL); + CC_Draw_Shape(SidebarShape2, 0, SideX, 158+118, WINDOW_MAIN, SHAPE_WIN_REL); + + #if (0) + if ( complete ) { + LogicPage->Fill_Rect(SideX+Map.PowWidth, SideY, SideX+SideWidth-1, SideY+SideHeight-1, LTGREY); + } + LogicPage->Fill_Rect(SideX, SideY, SideX+SideWidth-1, SideY+TopHeight-1, LTGREY); + Draw_Box(SideX+Map.PowWidth, SideY+TopHeight, SideWidth-Map.PowWidth, SideHeight-TopHeight, BOXSTYLE_RAISED, false); + #endif //(0) + //Repair.Draw_Me(true); + //Upgrade.Draw_Me(true); + //Zoom.Draw_Me(true); + // } else { + // if (IsToRedraw || complete) { + // LogicPage->Fill_Rect(TacPixelX + Lepton_To_Pixel(TacLeptonWidth), SIDE_Y, 319, SIDE_Y+TOP_HEIGHT, BLACK); + // } + + LogicPage->Unlock(); + } + + } + /* + ** Draw the side strip elements by calling their respective draw functions. + */ + if (IsSidebarActive){ + Column[0].Draw_It(complete); + Column[1].Draw_It(complete); + Repair.Draw_Me(true); + Upgrade.Draw_Me(true); + Zoom.Draw_Me(true); + } + + IsToRedraw = false; +} + + +/*********************************************************************************************** + * SidebarClass::AI -- Handles player clicking on sidebar area. * + * * + * This routine handles the processing necessary when the player clicks on the sidebar. * + * Typically, this is selection of the item to build. * + * * + * INPUT: input -- Reference to the keyboard input value. * + * * + * x,y -- Mouse coordinates at time of input. * + * * + * OUTPUT: bool; Was the click handled? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/28/94 JLB : Created. * + * 11/11/1994 JLB : Processes input directly. * + * 12/26/1994 JLB : Uses factory manager class for construction handling. * + * 12/31/1994 JLB : Simplified to use the sidebar strip class handlers. * + * 12/31/1994 JLB : Uses mouse coordinate parameters. * + * 06/27/1995 JLB : key toggles sidebar. * + *=============================================================================================*/ +void SidebarClass::AI(KeyNumType & input, int x, int y) +{ + bool redraw = false; + + /* + ** Toggle the sidebar in and out with the key. + */ + if (input == KN_TAB) { + Activate(-1); + } + + if (!Debug_Map) { + Column[0].AI(input, x, y); + Column[1].AI(input, x, y); + } + + if (IsSidebarActive && !Debug_Map) { + + if (input == KN_DOWN) { + redraw |= Column[0].Scroll(false); + redraw |= Column[1].Scroll(false); + input = KN_NONE; + } + if (input == KN_UP) { + redraw |= Column[0].Scroll(true); + redraw |= Column[1].Scroll(true); + input = KN_NONE; + } + } + + if (IsSidebarActive) { + + /* + ** If there are any buildings in the payer's inventory, then allow the repair + ** option. + */ + if (PlayerPtr->BScan) { + Activate_Repair(true); + } else { + Activate_Repair(false); + } + + if (input == (BUTTON_REPAIR|KN_BUTTON)) { + Repair_Mode_Control(-1); + } + + if (input == (BUTTON_ZOOM|KN_BUTTON)) { + /* + ** If radar is active, cycle as follows: + ** Zoomed => not zoomed + ** not zoomed => player status (multiplayer only) + ** player status => zoomed + */ + if (IsRadarActive) { + if (Is_Zoomed() || GameToPlay==GAME_NORMAL) { + Zoom_Mode(Coord_Cell(TacticalCoord)); + } else { + if (!Is_Player_Names()) { + Player_Names(1); + } else { + Player_Names(0); + Zoom_Mode(Coord_Cell(TacticalCoord)); + } + } + } else { + if (GameToPlay!=GAME_NORMAL) { + Player_Names(Is_Player_Names()==0); + } + } + } + + if (input == (BUTTON_UPGRADE|KN_BUTTON)) { + Sell_Mode_Control(-1); + } + +#ifdef NEVER +// int index = -1; + if (index != -1) { + /* + ** Display help text if the mouse is over a sidebar button. + */ + switch (index) { + default: + case 2: + Map.Help_Text(TXT_UPGRADE, -1, -1, PlayerPtr->Class->Color); + break; + + case 1: + Map.Help_Text(PlayerPtr->Class->House == HOUSE_GOOD ? TXT_SELL : TXT_DEMOLISH, x, y, PlayerPtr->Class->Color); + break; + + case 0: + Map.Help_Text(TXT_REPAIR, x, y, PlayerPtr->Class->Color); + break; + } + } +#endif + + if (redraw) { + //IsToRedraw = true; + Column[0].Flag_To_Redraw(); + Column[1].Flag_To_Redraw(); + + Flag_To_Redraw(false); + } + } + + if ((!IsRepairMode) && Repair.IsOn){ + Repair.Turn_Off(); + } + + if ((!IsSellMode) && Upgrade.IsOn){ + Upgrade.Turn_Off(); + } + + PowerClass::AI(input, x, y); +} + + +/*********************************************************************************************** + * SidebarClass::Recalc -- Examines the sidebar data and updates it as necessary. * + * * + * Occasionally a factory gets destroyed. This routine must be called in such a case * + * because it might be possible that sidebar object need to be removed. This routine will * + * examine all existing objects in the sidebar class and if no possible factory can * + * produce it, then it will be removed. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine is exhaustive and thus time consuming. Only call it when really * + * necessary. Such as when a factory is destroyed rather than when a non-factory * + * is destroyed. * + * * + * HISTORY: * + * 11/30/1994 JLB : Created. * + *=============================================================================================*/ +void SidebarClass::Recalc(void) +{ + bool redraw = false; + + redraw |= Column[0].Recalc(); + redraw |= Column[1].Recalc(); + + if (redraw) { + IsToRedraw = true; + Flag_To_Redraw(false); + } +} + + +/*********************************************************************************************** + * SidebarClass::Activate -- Controls the sidebar activation. * + * * + * Use this routine to turn the sidebar on or off. This routine handles updating the * + * necessary flags. * + * * + * INPUT: control -- Tells what to do with the sidebar according to the following: * + * 0 = Turn sidebar off. * + * 1 = Turn sidebar on. * + * -1= Toggle sidebar on or off. * + * * + * OUTPUT: bool; Was the sidebar already on? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/09/1994 JLB : Created. * + *=============================================================================================*/ +bool SidebarClass::Activate(int control) +{ + bool old = IsSidebarActive; + + int sidex = SeenBuff.Get_Width() - SideBarWidth; + int sidey = Map.RadY + Map.RadHeight; + int topheight = 13; + int sidewidth = SeenBuff.Get_Width() - sidex; + int sideheight = SeenBuff.Get_Height() - sidey; + + if (PlaybackGame) + return (old); + + /* + ** Determine the new state of the sidebar. + */ + switch (control) { + case -1: + IsSidebarActive = IsSidebarActive == false; + break; + + case 1: + IsSidebarActive = true; + break; + + default: + case 0: + IsSidebarActive = false; + break; + } + + /* + ** Only if there is a change in the state of the sidebar will anything + ** be done to change it. + */ + if (IsSidebarActive != old) { + + /* + ** If the sidebar is activated but was on the right side of the screen, then + ** activate it on the left side of the screen. + */ + if (IsSidebarActive /*&& X*/) { + Set_View_Dimensions(0, Map.Get_Tab_Height(), SeenBuff.Get_Width() - sidewidth); + IsToRedraw = true; + Help_Text(TXT_NONE); + Repair.Zap(); + Add_A_Button(Repair); + Upgrade.Zap(); + Add_A_Button(Upgrade); + Zoom.Zap(); + Add_A_Button(Zoom); + Column[0].Activate(); + Column[1].Activate(); + Background.Zap(); + Add_A_Button(Background); + Map.RadarButton.Zap(); + Add_A_Button(Map.RadarButton); + Map.PowerButton.Zap(); + Add_A_Button(Map.PowerButton); + } else { + Help_Text(TXT_NONE); + Set_View_Dimensions(0, Map.Get_Tab_Height()); + Remove_A_Button(Repair); + Remove_A_Button(Upgrade); + Remove_A_Button(Zoom); + Remove_A_Button(Background); + Column[0].Deactivate(); + Column[1].Deactivate(); + Remove_A_Button(Map.RadarButton); + Remove_A_Button(Map.PowerButton); + } + + /* + ** Since the sidebar status has changed, update the map so that the graphics + ** will be rendered correctly. + */ + Flag_To_Redraw(true); + } + + return(old); +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::StripClass -- Default constructor for the side strip class. * + * * + * This constructor is used to reset the side strip to default empty state. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/31/1994 JLB : Created. * + *=============================================================================================*/ +SidebarClass::StripClass::StripClass(void) +{ + IsScrollingDown = false; + IsScrolling = false; + IsBuilding = false; TopIndex = 0; + Slid = 0; + BuildableCount = 0; + for (int index = 0; index < MAX_BUILDABLES; index++) { + Buildables[index].BuildableID = 0; + Buildables[index].BuildableType = RTTI_NONE; + Buildables[index].Factory = -1; + } +} + + + +/*********************************************************************************************** + * SidebarClass::StripClass::One_Time -- Performs one time actions necessary for the side stri * + * * + * Call this routine ONCE at the beginning of the game. It handles retrieving pointers to * + * the shape files it needs for rendering. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/31/1994 JLB : Created. * + *=============================================================================================*/ +void SidebarClass::StripClass::One_Time(int ) +{ + static char *_file[3] = { + "ION", + "ATOM", + "BOMB" + }; + int factor = Get_Resolution_Factor(); + + ObjectWidth = OBJECT_WIDTH << factor; + ObjectHeight = OBJECT_HEIGHT << factor; + StripWidth = STRIP_WIDTH << factor; + LeftEdgeOffset = (StripWidth - ObjectWidth) >> 1; + ButtonSpacingOffset = (StripWidth - ((BUTTON_WIDTH << factor) << 1)) / 3; + + LogoShapes = Hires_Retrieve("STRIP.SHP"); + ClockShapes = Hires_Retrieve("CLOCK.SHP"); + + char fullname[_MAX_FNAME+_MAX_EXT]; + char buffer[_MAX_FNAME]; + + for (int lp = 0; lp < 3; lp++) { + if ( Get_Resolution_Factor() ) { + sprintf(buffer, "%sICNH", _file[lp]); + } else { + sprintf(buffer, "%sICON", _file[lp]); + } + _makepath(fullname, NULL, NULL, buffer, ".SHP"); + SpecialShapes[lp] = MixFileClass::Retrieve(fullname); + } + + +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::Get_Special_Cameo -- Fetches the special event cameo shape. * + * * + * This routine will return with a pointer to the cameo data for the special objects that * + * can appear on the sidebar (e.g., nuclear bomb). * + * * + * INPUT: type -- The special type to fetch the cameo imagery for. * + * * + * OUTPUT: Returns with a pointer to the cameo imagery for the specified special object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/19/1995 JLB : commented * + *=============================================================================================*/ +void const * SidebarClass::StripClass::Get_Special_Cameo(int type) +{ + return(SpecialShapes[type]); +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::Init_Clear -- Sets sidebar to a known (and deactivated) state * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +void SidebarClass::StripClass::Init_Clear(void) +{ + IsScrollingDown = false; + IsScrolling = false; + IsBuilding = false; + Flasher = -1; + TopIndex = 0; + Slid = 0; + BuildableCount = 0; + + /* + ** Since we're resetting the strips, clear out all the buildables & factory pointers. + */ + for (int index = 0; index < MAX_BUILDABLES; index++) { + Buildables[index].BuildableID = 0; + Buildables[index].BuildableType = RTTI_NONE; + Buildables[index].Factory = -1; + } +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::Init_IO -- Initializes the strip's buttons * + * * + * This routine doesn't actually add any buttons to the list; + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +void SidebarClass::StripClass::Init_IO(int id) +{ + ID = id; + + UpButton[ID].IsSticky = true; + UpButton[ID].ID = BUTTON_UP+id; + UpButton[ID].X = X+ButtonSpacingOffset+1; + UpButton[ID].Y = Y+MAX_VISIBLE*ObjectHeight-1; + + UpButton[ID].Set_Shape(Hires_Retrieve("STRIPUP.SHP")); + + DownButton[ID].IsSticky = true; + DownButton[ID].ID = BUTTON_DOWN+id; + DownButton[ID].X = UpButton[ID].X + UpButton[ID].Width + ButtonSpacingOffset-2; + DownButton[ID].Y = Y+MAX_VISIBLE*ObjectHeight-1; + + DownButton[ID].Set_Shape(Hires_Retrieve("STRIPDN.SHP")); + + for (int index = 0; index < MAX_VISIBLE; index++) { + SelectClass & g = SelectButton[ID][index]; + g.ID = BUTTON_SELECT; + g.X = X; + g.Y = Y + (ObjectHeight*index); + g.Width = ObjectWidth; + g.Height = ObjectHeight; + g.Set_Owner(*this, index); + } + +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::Init_Theater -- Performs theater-specific initialization * + * * + * INPUT: theater * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/24/1994 JLB : Created. * + *=============================================================================================*/ +void SidebarClass::StripClass::Init_Theater(TheaterType theater) +{ + //if (theater != LastTheater) { + + + static char *_file[3] = { + "ION", + "ATOM", + "BOMB" + }; + int factor = Get_Resolution_Factor(); + char fullname[_MAX_FNAME+_MAX_EXT]; + char buffer[_MAX_FNAME]; + void const * cameo_ptr; + + for (int lp = 0; lp < 3; lp++) { + if ( Get_Resolution_Factor() ) { + sprintf(buffer, "%sICNH", _file[lp]); + } else { + sprintf(buffer, "%sICON", _file[lp]); + } + _makepath(fullname, NULL, NULL, buffer, Theaters[theater].Suffix); + cameo_ptr = MixFileClass::Retrieve(fullname); + if (cameo_ptr){ + SpecialShapes[lp] = cameo_ptr; + } + } + + +#ifndef _RETRIEVE + static TLucentType const ClockCols[1] = { +// {LTGREEN, BLACK, 0, 0}, + {GREEN, LTGREY, 180, 0} + }; + + /* + ** Make sure that remapping doesn't occur on the colors that cycle. + */ + Mem_Copy(GamePalette, OriginalPalette, 768); + memset(&GamePalette[CYCLE_COLOR_START*3], 0x3f, CYCLE_COLOR_COUNT*3); + + /* + ** Create the translucent table used for the sidebar. + */ + Build_Translucent_Table(GamePalette, &ClockCols[0], 1, (void*)ClockTranslucentTable); + CCFileClass(Fading_Table_Name("CLOCK", theater)).Write(ClockTranslucentTable, sizeof(ClockTranslucentTable)); + Mem_Copy(OriginalPalette, GamePalette, 768); +#else + CCFileClass(Fading_Table_Name("CLOCK", theater)).Read(ClockTranslucentTable, sizeof(ClockTranslucentTable)); +#endif + LastTheater = theater; + //} +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::Activate -- Adds the strip buttons to the input system. * + * * + * This routine will add the side strip buttons to the map's input system. This routine * + * should be called once when the sidebar activates. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Never call this routine a second time without first calling Deactivate(). * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void SidebarClass::StripClass::Activate(void) +{ + UpButton[ID].Zap(); + Map.Add_A_Button(UpButton[ID]); + + DownButton[ID].Zap(); + Map.Add_A_Button(DownButton[ID]); + + for (int index = 0; index < MAX_VISIBLE; index++) { + SelectButton[ID][index].Zap(); + Map.Add_A_Button(SelectButton[ID][index]); + } +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::Deactivate -- Removes the side strip buttons from the input syste * + * * + * Call this routine to remove all the buttons on the side strip from the map's input * + * system. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Never call this routine unless the Activate() function was prevously called. * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void SidebarClass::StripClass::Deactivate(void) +{ + Map.Remove_A_Button(UpButton[ID]); + Map.Remove_A_Button(DownButton[ID]); + for (int index = 0; index < MAX_VISIBLE; index++) { + Map.Remove_A_Button(SelectButton[ID][index]); + } +} + + +#ifdef NEVER +/*********************************************************************************************** + * sortfunc -- Utility routine that handles 'qsort' the strip buttons. * + * * + * This routine is called by qsort() in order to sort the sidebar buttons. This sorting * + * forces the sidebar buttons to always occur in the order that they can be built in, * + * rather than the order that they were added to the sidebar list. * + * * + * INPUT: ptr1 -- Pointer to the first sidebar class object. * + * * + * ptr2 -- Pointer to the second sidebar class object. * + * * + * OUTPUT: Returns <0 if the first object can be produced before the second. It returns * + * >0 if the reverse is true. It returns exactly 0 if the production scneario for * + * both objects is the same. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : Created. * + *=============================================================================================*/ +static int sortfunc(void const * ptr1, void const * ptr2) +{ + SidebarClass::StripClass::BuildType * b1 = (SidebarClass::StripClass::BuildType *)ptr1; + SidebarClass::StripClass::BuildType * b2 = (SidebarClass::StripClass::BuildType *)ptr2; + + TechnoTypeClass const * p1 = Fetch_Techno_Type(b1->BuildableType, b1->BuildableID); + TechnoTypeClass const * p2 = Fetch_Techno_Type(b2->BuildableType, b2->BuildableID); + + int i1 = 0; + int i2 = 0; + + if (p1) i1 = p1->What_Am_I()*2; + if (p2) i2 = p2->What_Am_I()*2; + + /* + ** Walls should be sorted after the regular buildings. + */ + if (p1 && p1->What_Am_I() == RTTI_BUILDINGTYPE && ((BuildingTypeClass * const)p1)->IsWall) { + i1++; + } + if (p2 && p2->What_Am_I() == RTTI_BUILDINGTYPE && ((BuildingTypeClass * const)p2)->IsWall) { + i2++; + } + + /* + ** If the object types are identical, then sort by scenario available. + */ + if (i1 == i2) { + + /* + ** In the case of walls (can tell if there is an odd value), then sort + ** by cost. + */ + if (i1 & 0x01) { + i1 = p1->Cost; + i2 = p2->Cost; + } else { + i1 = p1->Scenario; + i2 = p2->Scenario; + } + } + + return(i1 - i2); +} +#endif + + +/*********************************************************************************************** + * SidebarClass::StripClass::Add -- Add an object to the side strip. * + * * + * Use this routine to add a buildable object to the side strip. * + * * + * INPUT: object -- Pointer to the object type that can be built and is to be added to * + * the side strip. * + * * + * OUTPUT: bool; Was the object successfully added to the side strip? Failure could be the * + * result of running out of room in the side strip array or the object might * + * already be in the list. * + * * + * WARNINGS: none. * + * * + * HISTORY: * + * 12/31/1994 JLB : Created. * + *=============================================================================================*/ +bool SidebarClass::StripClass::Add(RTTIType type, int id) +{ + if (BuildableCount <= MAX_BUILDABLES) { + for (int index = 0; index < BuildableCount; index++) { + if (Buildables[index].BuildableType == type && Buildables[index].BuildableID == id) { + return(false); + } + } + if (!ScenarioInit && type != RTTI_SPECIAL) { + Speak(VOX_NEW_CONSTRUCT); + } + Buildables[BuildableCount].BuildableType = type; + Buildables[BuildableCount].BuildableID = id; + BuildableCount++; + IsToRedraw = true; +#ifdef OBSOLETE + if (GameToPlay == GAME_NORMAL) { + qsort(&Buildables[0], BuildableCount, sizeof(Buildables[0]), sortfunc); + } +#endif + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::Scroll -- Causes the side strip to scroll. * + * * + * Use this routine to flag the side strip to scroll. The direction scrolled is controlled * + * by the parameter. Scrolling is merely initiated by this routine. Subsequent calls to * + * the AI function and the Draw_It function are required to properly give the appearence * + * of scrolling. * + * * + * INPUT: bool; Should the side strip scroll UP? If it is to scroll down then pass false. * + * * + * OUTPUT: bool; Was the side strip started to scroll in the desired direction? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/31/1994 JLB : Created. * + * 07/29/1995 JLB : Simplified scrolling logic. * + *=============================================================================================*/ +bool SidebarClass::StripClass::Scroll(bool up) +{ + if (up) { + Scroller--; + } else { + Scroller++; + } +#ifdef NEVER + if (BuildableCount <= MAX_VISIBLE) return(false); + + /* + ** Top of list is moving toward lower ordered entries in the object list. It looks like + ** the "window" to the object list is moving up even though the actual object images are + ** scrolling downward. + */ + if (up) { + if (!TopIndex) return(false); + + TopIndex--; + Slid = 0; + } else { + if (TopIndex+MAX_VISIBLE >= BuildableCount) return(false); + + Slid = ObjectHeight; + } + IsScrollingDown = !up; + IsScrolling = true; +#endif + return(true); +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::Flag_To_Redra -- Flags the sidebar strip to be redrawn. * + * * + * This utility routine is called when something changes on the sidebar and it must be * + * reflected the next time drawing is performed. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : Created. * + *=============================================================================================*/ +void SidebarClass::StripClass::Flag_To_Redraw(void) +{ + IsToRedraw = true; + //Map.SidebarClass::IsToRedraw = true; + Map.Flag_To_Redraw(false); +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::AI -- Input and AI processing for the side strip. * + * * + * The side strip AI processing is performed by this function. This function not only * + * checks for player input, but also handles any graphic logic updating necessary as a * + * result of flashing or construction animation. * + * * + * INPUT: input -- The player input code. * + * * + * x,y -- Mouse coordinate to use. * + * * + * OUTPUT: bool; Did the AI detect that it will need a rendering change? If this routine * + * returns true, then the Draw_It function should be called at the * + * earliest opportunity. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/31/1994 JLB : Created. * + * 12/31/1994 JLB : Uses mouse coordinate parameters. * + *=============================================================================================*/ +bool SidebarClass::StripClass::AI(KeyNumType & input, int , int ) +{ + bool redraw = false; + + /* + ** If this is scroll button for this side strip, then scroll the strip as + ** indicated. + */ + if (input == (UpButton[ID].ID|KN_BUTTON)) { // && !IsScrolling + UpButton[ID].IsPressed = false; + Scroll(true); + } + if (input == (DownButton[ID].ID|KN_BUTTON)) { // && !IsScrolling + DownButton[ID].IsPressed = false; + Scroll(false); + } + + /* + ** Reflect the scroll desired direction/value into the scroll + ** logic handler. This might result in up or down scrolling. + */ + if (!IsScrolling && Scroller) { + if (BuildableCount <= MAX_VISIBLE) { + Scroller = 0; + } else { + + /* + ** Top of list is moving toward lower ordered entries in the object list. It looks like + ** the "window" to the object list is moving up even though the actual object images are + ** scrolling downward. + */ + if (Scroller < 0) { + if (!TopIndex) { + Scroller = 0; + } else { + Scroller++; + IsScrollingDown = false; + IsScrolling = true; + TopIndex--; + Slid = 0; + } + + } else { + if (TopIndex+MAX_VISIBLE >= BuildableCount) { + Scroller = 0; + } else { + Scroller--; + Slid = ObjectHeight; + IsScrollingDown = true; + IsScrolling = true; + } + } + } + } + + /* + ** Scroll logic is handled here. + */ + if (IsScrolling) { + if (IsScrollingDown) { + Slid -= SCROLL_RATE; + if (Slid <= 0) { + IsScrolling = false; + Slid = 0; + TopIndex++; + } + } else { + Slid += SCROLL_RATE; + if (Slid >= ObjectHeight) { + IsScrolling = false; + Slid = 0; + } + } + redraw = true; + } + + /* + ** Handle any flashing logic. Flashing occurs when the player selects an object + ** and provides the visual feedback of a recognized and legal selection. + */ + if (Flasher != -1) { + if (Graphic_Logic()) { + redraw = true; + if (Fetch_Stage() >= 7) { + Set_Rate(0); + Set_Stage(0); + Flasher = -1; + } + } + } + + /* + ** Handle any building clock animation logic. + */ + if (IsBuilding) { + for (int index = 0; index < BuildableCount; index++) { + int factoryid = Buildables[index].Factory; + + if (factoryid != -1) { + FactoryClass * factory = Factories.Raw_Ptr(factoryid); + + if (factory && factory->Has_Changed()) { + redraw = true; + if (factory->Has_Completed()) { + + /* + ** Construction has been completed. Announce this fact to the player and + ** try to get the object to automatically leave the factory. Buildings are + ** the main exception to the ability to leave the factory under their own + ** power. + */ + TechnoClass * pending = factory->Get_Object(); + if (pending) { + switch (pending->What_Am_I()) { + case RTTI_UNIT: + case RTTI_AIRCRAFT: + OutList.Add(EventClass(EventClass::PLACE, pending->What_Am_I(), -1)); + // Fall into next case. + + case RTTI_BUILDING: + Speak(VOX_CONSTRUCTION); + break; + + case RTTI_INFANTRY: + OutList.Add(EventClass(EventClass::PLACE, pending->What_Am_I(), -1)); + Speak(VOX_UNIT_READY); + break; + } + } + } + } + } + } + } + + /* + ** If any of the logic determined that this side strip needs to be redrawn, then + ** set the redraw flag for this side strip. + */ + if (redraw) { + Flag_To_Redraw(); + } + return(redraw); +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::Draw_It -- Render the sidebar display. * + * * + * Use this routine to render the sidebar display. It checks to see if it needs to be * + * redrawn and only redraw if necessary. If the "complete" parameter is true, then it * + * will force redraw the entire strip. * + * * + * INPUT: complete -- Should the redraw be forced? A force redraw will ignore the redraw * + * flag. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/31/1994 JLB : Created. * + * 08/06/1995 JLB : Handles multi factory tracking in same strip. * + *=============================================================================================*/ +void SidebarClass::StripClass::Draw_It(bool complete) +{ + if (IsToRedraw || complete) { + IsToRedraw = false; + + /* + ** Fills the background to the side strip. We shouldnt need to do this if the strip + ** has a full complement of icons. ST - 10/7/96 6:03PM + */ + if (BuildableCount < MAX_VISIBLE){ + CC_Draw_Shape(LogoShapes, ID, X+3, Y-1, WINDOW_MAIN, SHAPE_WIN_REL|SHAPE_NORMAL, 0); + } + + /* + ** Redraw the scroll buttons. + */ + UpButton[ID].Draw_Me(true); + DownButton[ID].Draw_Me(true); + + /* + ** Loop through all the buildable objects that are visible in the strip and render + ** them. Their Y offset may be adjusted if the strip is in the process of scrolling. + */ + for (int i = 0; i < MAX_VISIBLE + (IsScrolling ? 1 : 0); i++) { + bool production; + bool completed; + int stage; + bool darken = false; + void const * shapefile = 0; + int shapenum = 0; + void const * remapper = 0; + FactoryClass * factory = 0; + int index = i+TopIndex; + int x = X; + int y = Y + i*ObjectHeight; + y--; + + /* + ** If the strip is scrolling, then the offset is adjusted accordingly. + */ + if (IsScrolling) { + y -= ObjectHeight - Slid; + } + + /* + ** Fetch the shape number for the object type located at this current working + ** slot. This shape pointer is used to draw the underlying graphic there. + */ + if ((unsigned)index < BuildableCount) { + ObjectTypeClass const * obj = NULL; + int spc = 0; + + if (Buildables[index].BuildableType != RTTI_SPECIAL) { + + obj = Fetch_Techno_Type(Buildables[index].BuildableType, Buildables[index].BuildableID); + if (obj) { + bool isbusy = false; + switch (Buildables[index].BuildableType) { + case RTTI_INFANTRYTYPE: + isbusy = (PlayerPtr->InfantryFactory != -1); + break; + + case RTTI_BUILDINGTYPE: + isbusy = (PlayerPtr->BuildingFactory != -1); + if (!BuildingTypeClass::As_Reference((StructType)Buildables[index].BuildableID).IsWall) { + remapper = PlayerPtr->Remap_Table(false, false); + } + break; + + case RTTI_UNITTYPE: + isbusy = (PlayerPtr->UnitFactory != -1); + switch (Buildables[index].BuildableID) { + case UNIT_MCV: + case UNIT_HARVESTER: + remapper = PlayerPtr->Remap_Table(false, false); + break; + + default: + remapper = PlayerPtr->Remap_Table(false, true); + break; + } + break; + + case RTTI_AIRCRAFTTYPE: + isbusy = (PlayerPtr->AircraftFactory != -1); + remapper = PlayerPtr->Remap_Table(false, true); + break; + } + shapefile = obj->Get_Cameo_Data(); + shapenum = 0; + if (Buildables[index].Factory != -1) { + factory = Factories.Raw_Ptr(Buildables[index].Factory); + production = true; + completed = factory->Has_Completed(); + stage = factory->Completion(); + darken = false; + } else { + production = false; +// darken = IsBuilding; + + /* + ** Darken the imagery if a factory of a matching type is + ** already busy. + */ + darken = isbusy; + } + } + + } else { + + spc = Buildables[index].BuildableID; + shapefile = Get_Special_Cameo(spc - 1); + shapenum = 0; + + switch (spc) { + case SPC_ION_CANNON: + production = true; + completed = PlayerPtr->IonCannon.Is_Ready(); + stage = PlayerPtr->IonCannon.Anim_Stage(); + darken = false; + break; + + case SPC_AIR_STRIKE: + production = true; + completed = PlayerPtr->AirStrike.Is_Ready(); + stage = PlayerPtr->AirStrike.Anim_Stage(); + darken = false; + break; + + case SPC_NUCLEAR_BOMB: + production = true; + completed = PlayerPtr->NukeStrike.Is_Ready(); + stage = PlayerPtr->NukeStrike.Anim_Stage(); + darken = false; + break; + } + } + + if (obj || spc) { + /* + ** If this item is flashing then take care of it. + ** + */ + if (Flasher == index && (Fetch_Stage() & 0x01)) { + remapper = Map.FadingLight; + } + + } else { + shapefile = LogoShapes; + shapenum = SB_BLANK; + } + } else { + shapefile = LogoShapes; + shapenum = SB_BLANK; + production = false; + } + + remapper = 0; + + /* + ** Now that the shape of the object at the current working slot has been found, + ** draw it and any graphic overlays as necessary. + ** + ** Dont draw blank shapes over the new 640x400 sidebar art - ST 5/1/96 6:01PM + */ + if (shapenum != SB_BLANK || shapefile != LogoShapes){ + IsTheaterShape = true; // This shape is theater specific + CC_Draw_Shape(shapefile, shapenum, + x-(WindowList[WINDOW_SIDEBAR][WINDOWX]*8)+LeftEdgeOffset, + y-WindowList[WINDOW_SIDEBAR][WINDOWY], + WINDOW_SIDEBAR, + SHAPE_NORMAL|SHAPE_WIN_REL| (remapper ? SHAPE_FADING : SHAPE_NORMAL), + remapper); + IsTheaterShape = false; + + + /* + ** Darken this object because it cannot be produced or is otherwise + ** unavailable. + */ + if (darken) { + CC_Draw_Shape(ClockShapes, 0, + x-(WindowList[WINDOW_SIDEBAR][WINDOWX]*8)+LeftEdgeOffset, + y-WindowList[WINDOW_SIDEBAR][WINDOWY], + WINDOW_SIDEBAR, + SHAPE_NORMAL|SHAPE_WIN_REL|SHAPE_GHOST, + NULL, ClockTranslucentTable); + } + } + + /* + ** Draw the overlapping clock shape if this is object is being constructed. + ** If the object is completed, then display "Ready" with no clock shape. + */ + if (production) { + if (completed) { + + /* + ** Display text showing that the object is ready to place. + */ + CC_Draw_Shape(ObjectTypeClass::PipShapes, PIP_READY, + (x-(WindowList[WINDOW_SIDEBAR][WINDOWX]*8))+LeftEdgeOffset+(ObjectWidth >> 1), + (y-WindowList[WINDOW_SIDEBAR][WINDOWY])+ObjectHeight-Get_Build_Frame_Height(ObjectTypeClass::PipShapes) -8, + WINDOW_SIDEBAR, SHAPE_CENTER); +// Fancy_Text_Print(TXT_READY, x+TEXT_X_OFFSET, y+TEXT_Y_OFFSET, TEXT_COLOR, TBLACK, TPF_6POINT|TPF_CENTER|TPF_NOSHADOW); + } else { + CC_Draw_Shape(ClockShapes, stage+1, + x-(WindowList[WINDOW_SIDEBAR][WINDOWX]*8)+LeftEdgeOffset, + y-WindowList[WINDOW_SIDEBAR][WINDOWY], + WINDOW_SIDEBAR, + SHAPE_NORMAL|SHAPE_WIN_REL|SHAPE_GHOST, + NULL, ClockTranslucentTable); + /* + ** Display text showing that the construction is temporarily on hold. + */ + if (factory && !factory->Is_Building()) { + CC_Draw_Shape(ObjectTypeClass::PipShapes, PIP_HOLDING, + (x-(WindowList[WINDOW_SIDEBAR][WINDOWX]*8))+LeftEdgeOffset+(ObjectWidth >> 1), + (y-WindowList[WINDOW_SIDEBAR][WINDOWY])+ObjectHeight-Get_Build_Frame_Height(ObjectTypeClass::PipShapes) - 8, // Moved up now that icons have names on them + WINDOW_SIDEBAR, SHAPE_CENTER); +// Fancy_Text_Print(TXT_HOLDING, x+TEXT_X_OFFSET, y+TEXT_Y_OFFSET, TEXT_COLOR, TBLACK, TPF_6POINT|TPF_CENTER|TPF_NOSHADOW); + } + } + } + } + } +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::Recalc -- Revalidates the current sidebar list of objects. * + * * + * This routine will revalidate all the buildable objects in the sidebar. This routine * + * comes in handy when a factory has been destroyed, and the sidebar needs to reflect any * + * change that this requires. It checks every object to see if there is a factory available * + * that could produce it. If none can be found, then the object is removed from the * + * sidebar. * + * * + * INPUT: none * + * * + * OUTPUT: bool; The sidebar has changed as a result of this call? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + * 06/26/1995 JLB : Doesn't collapse sidebar when buildables removed. * + *=============================================================================================*/ +bool SidebarClass::StripClass::Recalc(void) +{ + int ok; + + if (Debug_Map || !BuildableCount) { + return(false); + } + + /* + ** Sweep through all objects listed in the sidebar. If any of those object can + ** not be created -- even in theory -- then they must be removed form the sidebar and + ** any current production must be abandoned. + */ + bool redraw = false; + for (int index = 0; index < BuildableCount; index++) { + TechnoTypeClass const * tech = Fetch_Techno_Type(Buildables[index].BuildableType, Buildables[index].BuildableID); + if (tech) { + ok = tech->Who_Can_Build_Me(true, false, PlayerPtr->Class->House) != NULL; + } else { + switch (Buildables[index].BuildableID) { + case SPC_ION_CANNON: + ok = PlayerPtr->IonCannon.Is_Present(); + break; + + case SPC_NUCLEAR_BOMB: + ok = PlayerPtr->NukeStrike.Is_Present(); + break; + + case SPC_AIR_STRIKE: + ok = PlayerPtr->AirStrike.Is_Present(); + break; + + default: + ok = false; + break; + } + +#ifdef OBSOLETE + } else { + switch (Buildables[index].BuildableID) { + case SPC_ION_CANNON: + ok = (PlayerPtr->BScan & STRUCTF_EYE) != 0 || PlayerPtr->IonOneTimeFlag; + if (!ok) { + PlayerPtr->Remove_Ion_Cannon(); + } + break; + + case SPC_NUCLEAR_BOMB: + ok = (PlayerPtr->BScan & STRUCTF_TEMPLE) != 0 && PlayerPtr->Has_Nuke_Device(); + ok = ok || PlayerPtr->NukeOneTimeFlag; + if (!ok) { + PlayerPtr->Remove_Nuke_Bomb(); + } + break; + + case SPC_AIR_STRIKE: +// ok = (PlayerPtr->BScan & STRUCTF_SAM) == 0; +// ok = !PlayerPtr->Does_Enemy_Building_Exist(STRUCT_SAM); + ok = (PlayerPtr->AirPresent /*&& !PlayerPtr->Does_Enemy_Building_Exist(STRUCT_SAM)*/) || PlayerPtr->AirOneTimeFlag; + if (!ok) { + PlayerPtr->Remove_Air_Strike(); + } + break; + + default: + ok = false; + break; + } +#endif + } + + if (!ok) { + + /* + ** If there was something in production, then abandon it before deleting the + ** factory manager. + */ + //if (Buildables[index].Factory != -1) { + //FactoryClass * factory = Factories.Raw_Ptr(Buildables[index].Factory); + //factory->Abandon(); + //delete factory; + //Buildables[index].Factory = -1; + //} +// Buildables[index].Factory = -1; + + /* + ** Removes this entry from the list. + */ + if (BuildableCount > 1 && index < BuildableCount-1) { + memcpy(&Buildables[index], &Buildables[index+1], sizeof(Buildables[0])*((BuildableCount-index)-1)); + } + TopIndex = 0; + IsToRedraw = true; + redraw = true; + BuildableCount--; + index--; + } + } + +#ifdef NEVER + /* + ** If there are no more buildable objects to display, make the sidebar go away. + */ + if (!BuildableCount) { + Map.SidebarClass::Activate(0); + } +#endif + return(redraw); +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::SelectClass::SelectClass -- Default constructor. * + * * + * This is the default constructor for the button that controls the buildable cameos on * + * the sidebar strip. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: The coordinates are set to zero by this routine. They must be set to the * + * correct values before this button will function. * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +SidebarClass::StripClass::SelectClass::SelectClass(void) : + ControlClass(0, 0, 0, 0, 0, LEFTPRESS|RIGHTPRESS|LEFTUP) +{ + int factor = Get_Resolution_Factor(); + + Strip = 0; + Index = 0; + Width = StripClass::OBJECT_WIDTH << factor; + Height = StripClass::OBJECT_HEIGHT << factor; +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::SelectClass:: -- Assigns special values to a buildable select but * + * * + * Use this routine to set custom buildable vars for this particular select button. It * + * uses this information to properly know what buildable object to start or stop production * + * on. * + * * + * INPUT: strip -- Reference to the strip that owns this buildable button. * + * * + * index -- The index (0 .. MAX_VISIBLE-1) of this button. This is used to let * + * the owning strip know what index this button refers to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void SidebarClass::StripClass::SelectClass::Set_Owner(StripClass & strip, int index) +{ + int factor = Get_Resolution_Factor(); + Strip = &strip; + Index = index; + X = strip.X; + Y = strip.Y + (index * (StripClass::OBJECT_HEIGHT << factor)); +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::SelectClass:: -- Action function when buildable cameo is selected * + * * + * This function is called when the buildable icon (cameo) is clicked on. It handles * + * starting and stopping production as indicated. * + * * + * INPUT: flags -- The input event that triggered the call. * + * * + * key -- The keyboard value at the time of the input. * + * * + * OUTPUT: Returns with whether the input list should be scanned further. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +int SidebarClass::StripClass::SelectClass::Action(unsigned flags, KeyNumType & key) +{ + int index = Strip->TopIndex + Index; + RTTIType otype = Strip->Buildables[index].BuildableType; + int oid = Strip->Buildables[index].BuildableID; + int fnumber = Strip->Buildables[index].Factory; + + FactoryClass * factory = NULL; + ObjectTypeClass const * choice = NULL; + int spc = 0; + + /* + ** Determine the factory number that would apply to objects of the type + ** the mouse is currently addressing. This doesn't mean that the factory number + ** fetched is actually producing the indicated object, merely that that particular + ** kind of factory is specified by the "genfactory" value. This can be used to see + ** if the factory type is currently busy or not. + */ + int genfactory = -1; + switch (otype) { + case RTTI_INFANTRYTYPE: + genfactory = PlayerPtr->InfantryFactory; + break; + + case RTTI_UNITTYPE: + genfactory = PlayerPtr->UnitFactory; + break; + + case RTTI_AIRCRAFTTYPE: + genfactory = PlayerPtr->AircraftFactory; + break; + + case RTTI_BUILDINGTYPE: + genfactory = PlayerPtr->BuildingFactory; + break; + + default: + genfactory = -1; + break; + } + + Map.Override_Mouse_Shape(MOUSE_NORMAL); + + if (index < Strip->BuildableCount) { + if (otype != RTTI_SPECIAL) { + choice = Fetch_Techno_Type(otype, oid); + } else { + spc = oid; + } + + if (fnumber != -1) { + factory = Factories.Raw_Ptr(fnumber); + } + + } else { + Map.Help_Text(TXT_NONE); + } + + if (spc) { + /* + ** Display the help text if the mouse is over the button. + */ + if (flags & LEFTUP) { + switch (spc) { + case SPC_ION_CANNON: + Map.Help_Text(TXT_ION_CANNON, X, Y, CC_GREEN, true); + break; + + case SPC_NUCLEAR_BOMB: + Map.Help_Text(TXT_NUKE_STRIKE, X, Y, CC_GREEN, true); + break; + + case SPC_AIR_STRIKE: + Map.Help_Text(TXT_AIR_STRIKE, X, Y, CC_GREEN, true); + break; + } + flags &= ~LEFTUP; + } + + /* + ** A right mouse button signals "cancel". If we are in targetting + ** mode then we don't want to be any more. + */ + if (flags & RIGHTPRESS) { + Map.IsTargettingMode = false; + } + /* + ** A left mouse press signal "activate". If our weapon type is + ** available then we should activate it. + */ + if (flags & LEFTPRESS) { + switch (spc) { + case SPC_ION_CANNON: + if (PlayerPtr->IonCannon.Is_Ready()) { + Map.IsTargettingMode = spc; + Unselect_All(); + Speak(VOX_SELECT_TARGET); + } else { + PlayerPtr->IonCannon.Impatient_Click(); + } + break; + + case SPC_AIR_STRIKE: + if (PlayerPtr->AirStrike.Is_Ready()) { + Map.IsTargettingMode = spc; + Unselect_All(); + Speak(VOX_SELECT_TARGET); + } else { + PlayerPtr->AirStrike.Impatient_Click(); + } + break; + + case SPC_NUCLEAR_BOMB: + if (PlayerPtr->NukeStrike.Is_Ready()) { + Map.IsTargettingMode = spc; + Unselect_All(); + Speak(VOX_SELECT_TARGET); + } else { + PlayerPtr->NukeStrike.Impatient_Click(); + } + break; + } + } + + } else { + + if (choice) { + + /* + ** Display the help text if the mouse is over the button. + */ + if (flags & LEFTUP) { + Map.Help_Text(choice->Full_Name(), X, Y, CC_GREEN, true, choice->Cost_Of()); + flags &= ~LEFTUP; + } + + /* + ** A right mouse button signals "cancel". + */ + if (flags & RIGHTPRESS) { + + /* + ** If production is in progress, put it on hold. If production is already + ** on hold, then abandon it. Money will be refunded, the factory + ** manager deleted, and the object under construction is returned to + ** the free pool. + */ + if (factory) { + + /* + ** Cancels placement mode if the sidebar factory is abandoned or + ** suspended. + */ + if (Map.PendingObjectPtr && Map.PendingObjectPtr->Is_Techno()) { + Map.PendingObjectPtr = 0; + Map.PendingObject = 0; + Map.PendingHouse = HOUSE_NONE; + Map.Set_Cursor_Shape(0); + } + + if (!factory->Is_Building()) { + Speak(VOX_CANCELED); + OutList.Add(EventClass(EventClass::ABANDON, otype, oid)); + } else { + Speak(VOX_SUSPENDED); + OutList.Add(EventClass(EventClass::SUSPEND, otype, oid)); + } + } + } + + if (flags & LEFTPRESS) { + + /* + ** If there is already a factory attached to this strip but the player didn't click + ** on the icon that has the attached factory, then say that the factory is busy and + ** ignore the click. + */ + if (fnumber == -1 && genfactory != -1) { + Speak(VOX_NO_FACTORY); + ControlClass::Action(flags, key); + return(true); + } + + if (factory) { + + /* + ** If this object is currently being built, then give a scold sound and text and then + ** bail. + */ + if (factory->Is_Building()) { + Speak(VOX_NO_FACTORY); + } else { + + /* + ** If production has completed, then attempt to have the object exit + ** the factory or go into placement mode. + */ + if (factory->Has_Completed()) { + + TechnoClass * pending = factory->Get_Object(); + if (!pending && factory->Get_Special_Item()) { + Map.IsTargettingMode = true; + } else { + BuildingClass * builder = pending->Who_Can_Build_Me(false, false); + if (!builder) { + OutList.Add(EventClass(EventClass::ABANDON, otype, oid)); + Speak(VOX_NO_FACTORY); + } else { + + /* + ** If the completed object is a building, then change the + ** game state into building placement mode. This fact is + ** not transmitted to any linked computers until the moment + ** the building is actually placed down. + */ + if (pending->What_Am_I() == RTTI_BUILDING) { + PlayerPtr->Manual_Place(builder, (BuildingClass *)pending); + } else { + + /* + ** For objects that can leave the factory under their own + ** power, queue this event and process through normal house + ** production channels. + */ + OutList.Add(EventClass(EventClass::PLACE, otype, -1)); + } + } + } + } else { + + /* + ** The factory must have been in a suspended state. Resume construction + ** normally. + */ + Speak(VOX_BUILDING); + OutList.Add(EventClass(EventClass::PRODUCE, Strip->Buildables[index].BuildableType, Strip->Buildables[index].BuildableID)); + } + } + + } else { + + /* + ** If this side strip is already busy with production, then ignore the + ** input and announce this fact. + */ +// if (Strip->IsBuilding) { +// Speak(VOX_NO_FACTORY); +// } else { + Speak(VOX_BUILDING); + OutList.Add(EventClass(EventClass::PRODUCE, Strip->Buildables[index].BuildableType, Strip->Buildables[index].BuildableID)); +// } + } + } + } else { + flags = 0; + } + } + + ControlClass::Action(flags, key); + return(true); +} + + +/*********************************************************************************************** + * SidebarClass::SBGadgetClass::Action -- Special function that controls the mouse over the si * + * * + * This routine is called whenever the mouse is over the sidebar. It makes sure that the * + * mouse is always the normal shape while over the sidebar. * + * * + * INPUT: flags -- The event flags that resuled in this routine being called. * + * * + * key -- Reference the keyboard code that may be present. * + * * + * OUTPUT: Returns that no further keyboard processing is necessary. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/28/1995 JLB : Created. * + *=============================================================================================*/ +int SidebarClass::SBGadgetClass::Action(unsigned , KeyNumType & ) +{ + Map.Help_Text(TXT_NONE); + Map.Override_Mouse_Shape(MOUSE_NORMAL, false); + return(true); +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::Factory_Link -- Links a factory to a sidebar button. * + * * + * This routine will link the specified factory to this sidebar strip. The exact button to * + * link to is determined from the object type and id specified. A linked button is one that * + * will show appropriate construction animation (clock shape) that matches the state of * + * the factory. * + * * + * INPUT: factory -- The factory number to link to the sidebar. * + * * + * type -- The object type that this factory refers to. * + * * + * id -- The object sub-type that this factory refers to. * + * * + * OUTPUT: Was the factory successfully attached? Failure would indicate that there is no * + * object of the specified type and sub-type in the sidebar list. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : Created. * + *=============================================================================================*/ +bool SidebarClass::StripClass::Factory_Link(int factory, RTTIType type, int id) +{ + for (int index = 0; index < BuildableCount; index++) { + if (Buildables[index].BuildableType == type && Buildables[index].BuildableID == id) { + Buildables[index].Factory = factory; + IsBuilding = true; + + /* + ** Flag that all the icons on this strip need to be redrawn + */ + Flag_To_Redraw(); + return(true); + } + } + return(false); +} + + +/*********************************************************************************************** + * SidebarClass::Abandon_Production -- Stops production of the object specified. * + * * + * This routine is used to abandon production of the object specified. The factory will * + * be completely disabled by this call. * + * * + * INPUT: type -- The object type that is to be abandoned. The sub-type is not needed * + * since it is presumed there can be only one type in production at any * + * one time. * + * * + * factory -- The factory number that is doing the production. * + * * + * OUTPUT: Was the factory successfully abandoned? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : Created. * + *=============================================================================================*/ +bool SidebarClass::Abandon_Production(RTTIType type, int factory) +{ + return(Column[Which_Column(type)].Abandon_Production(factory)); +} + + +/*********************************************************************************************** + * SidebarClass::StripClass::Abandon_Produ -- Abandons production associated with sidebar. * + * * + * Production of the object associated with this sidebar is abandoned when this routine is * + * called. * + * * + * INPUT: factory -- The factory index that is to be suspended. * + * * + * OUTPUT: Was the production abandonment successful? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/18/1995 JLB : Created. * + * 08/06/1995 JLB : More intelligent abandon logic for multiple factories. * + *=============================================================================================*/ +bool SidebarClass::StripClass::Abandon_Production(int factory) +{ + bool noprod = true; + bool abandon = false; + for (int index = 0; index < BuildableCount; index++) { + if (Buildables[index].Factory == factory) { + Factories.Raw_Ptr(factory)->Abandon(); + Buildables[index].Factory = -1; + abandon = true; + } else { + if (Buildables[index].Factory != -1) { + noprod = false; + } + } + } + + /* + ** If there was a change to the strip, then flag the strip to be redrawn. + */ + if (abandon) { + Flag_To_Redraw(); + } + + /* + ** If there is no production whatsoever on this strip, then flag it so. + */ + if (noprod) { + IsBuilding = false; + } + return(abandon); +} + + diff --git a/SIDEBAR.H b/SIDEBAR.H new file mode 100644 index 0000000..cb7ccd9 --- /dev/null +++ b/SIDEBAR.H @@ -0,0 +1,397 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\sidebar.h_v 2.18 16 Oct 1995 16:45:24 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SIDEBAR.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : October 20, 1994 * + * * + * Last Update : October 20, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef SIDEBAR_H +#define SIDEBAR_H + +#include "function.h" +#include "power.h" +#include "factory.h" + +class SidebarClass: public PowerClass +{ + public: + /* + ** These constants are used to control the sidebar rendering. They are instantiated + ** as enumerations since C++ cannot use "const" in this context. + */ + int SideX; // x position for side bar + int SideY; // y position for side bar + int SideBarWidth; // width of the sidebar + int SideWidth; // width of the sidebar + int SideHeight; // height of the sidebar + int TopHeight; // height of top of sidebar + int MaxVisible; // max production icons visible + int ButtonOneWidth; // Button width. + int ButtonTwoWidth; // Button width. + int ButtonThreeWidth; // Button width. + int ButtonHeight; // Button width. + + enum SideBarClassEnums { + BUTTON_ACTIVATOR=100, // Button ID for the activator. + SIDEBARWIDTH = 80, +#if 0 + SIDE_X=RADAR_X, // The X position of sidebar upper left corner. + SIDE_Y=RADAR_Y+RADAR_HEIGHT, // The Y position of sidebar upper left corner. + SIDE_WIDTH=320-SIDE_X, // Width of the entire sidebar (in pixels). + SIDE_HEIGHT=200-SIDE_Y, // Height of the entire sidebar (in pixels). + TOP_HEIGHT=13, // Height of top section (with repair/sell buttons). + COLUMN_ONE_X=SIDE_X+8, // Sidestrip upper left coordinates... + COLUMN_ONE_Y=SIDE_Y+TOP_HEIGHT, + COLUMN_TWO_X=COLUMN_ONE_X+((SIDE_WIDTH-16)/2)+3, + COLUMN_TWO_Y=SIDE_Y+TOP_HEIGHT, +#if (GERMAN | FRENCH) +//BGA: changes to all buttons + BUTTON_ONE_WIDTH=20, // Button width. + BUTTON_TWO_WIDTH=27, // Button width. + BUTTON_THREE_WIDTH=26, // Button width. + BUTTON_HEIGHT=9, // Button height. + BUTTON_ONE_X=SIDE_X+2, // Left button X coordinate. + BUTTON_ONE_Y=SIDE_Y+2, // Left button Y coordinate. + BUTTON_TWO_X=SIDE_X+24, // Right button X coordinate. + BUTTON_TWO_Y=SIDE_Y+2, // Right button Y coordinate. + BUTTON_THREE_X=SIDE_X+53, // Right button X coordinate. + BUTTON_THREE_Y=SIDE_Y+2, // Right button Y coordinate. +#else + BUTTON_ONE_WIDTH=32, // Button width. + BUTTON_TWO_WIDTH=20, // Button width. + BUTTON_THREE_WIDTH=20, // Button width. + BUTTON_HEIGHT=9, // Button height. + BUTTON_ONE_X=SIDE_X+2, // Left button X coordinate. + BUTTON_ONE_Y=SIDE_Y+2, // Left button Y coordinate. + BUTTON_TWO_X=SIDE_X+36, // Right button X coordinate. + BUTTON_TWO_Y=SIDE_Y+2, // Right button Y coordinate. + BUTTON_THREE_X=SIDE_X+58, // Right button X coordinate. + BUTTON_THREE_Y=SIDE_Y+2, // Right button Y coordinate. +#endif + BUTTON_ONE_WIDTH=32, // Button width. + BUTTON_TWO_WIDTH=20, // Button width. + BUTTON_THREE_WIDTH=20, // Button width. + BUTTON_HEIGHT=9, // Button height. +#endif + COLUMNS=2, // Number of side strips on sidebar. + }; + + SidebarClass(void); + + /* + ** Initialization + */ + virtual void One_Time(void); // One-time inits + virtual void Init_Clear(void); // Clears all to known state + virtual void Init_IO(void); // Inits button list + virtual void Init_Theater(TheaterType theater); // Theater-specific inits + + virtual void AI(KeyNumType & input, int x, int y); + virtual void Draw_It(bool complete); + virtual void Refresh_Cells(CELL cell, short const *list); + + bool Abandon_Production(RTTIType type, int factory); + bool Activate(int control); + bool Add(RTTIType type, int ID); + bool Sidebar_Click(KeyNumType & input, int x, int y); + void Recalc(void); + bool Factory_Link(int factory, RTTIType type, int id); + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /* + ** Each side strip is managed by this class. It handles all strip specific + ** actions. + */ + class StripClass : public StageClass + { + class SelectClass : public ControlClass + { + public: + SelectClass(void); + + void Set_Owner(StripClass & strip, int index); + StripClass * Strip; + int Index; + + protected: + virtual int Action(unsigned flags, KeyNumType & key); + }; + + public: + int ObjectWidth; + int ObjectHeight; + int StripWidth; + int LeftEdgeOffset; + int ButtonSpacingOffset; + + + StripClass(void); + bool Add(RTTIType type, int ID); + bool Abandon_Production(int factory); + bool Scroll(bool up); + bool AI(KeyNumType & input, int x, int y); + void Draw_It(bool complete); + void One_Time(int id); + void Init_Clear(void); + void Init_IO(int id); + void Init_Theater(TheaterType theater); + bool Recalc(void); + void Activate(void); + void Deactivate(void); + void Flag_To_Redraw(void); + bool Factory_Link(int factory, RTTIType type, int id); + void const * Get_Special_Cameo(int type); + + /* + ** File I/O. + */ + bool Load(FileClass & file); + bool Save(FileClass & file); + void Code_Pointers(void); + void Decode_Pointers(void); + + /* + ** Working numbers used when rendering and processing the side strip. + */ + enum SideBarGeneralEnums { + BUTTON_UP=200, + BUTTON_DOWN=210, + BUTTON_SELECT=220, + MAX_BUILDABLES=30, // Maximum number of object types in sidebar. + OBJECT_HEIGHT=24, // Pixel height of each buildable object. + OBJECT_WIDTH=32, // Pixel width of each buildable object. + STRIP_WIDTH=35, // Width of strip (not counting border lines). + MAX_VISIBLE=4, // Number of object slots visible at any one time. + SCROLL_RATE=8, // The pixel jump while scrolling (larger is faster). + BUTTON_SPACING_OFFSET = 4, // spacing info for buttons + UP_X_OFFSET=2, // Scroll up arrow coordinates. + UP_Y_OFFSET=MAX_VISIBLE*OBJECT_HEIGHT+1, + DOWN_X_OFFSET=18, // Scroll down arrow coordinates. + DOWN_Y_OFFSET=MAX_VISIBLE*OBJECT_HEIGHT+1, + BUTTON_WIDTH=16, // Width of the mini-scroll button. + BUTTON_HEIGHT=12, // Height of the mini-scroll button. + //LEFT_EDGE_OFFSET=2, // Offset from left edge for building shapes. + TEXT_X_OFFSET=18, // X offset to print "ready" text. + TEXT_Y_OFFSET=15, // Y offset to print "ready" text. + TEXT_COLOR=255, // Color to use for the "Ready" text. + //BUTTON_SPACING_OFFSET = 4, // spacing info for buttons + //LEFT_EDGE_OFFSET=0, // Offset from left edge for building shapes. + //BUTTON_SPACING_OFFSET = 0, // spacing info for buttons + + }; + + /* + ** This is the coordinate of the upper left corner that this side strip + ** uses for rendering. + */ + int X,Y; + + /* + ** This is a unique identifier for the sidebar strip. Using this identifier, + ** it is possible to differentiate the button messages that arrive from the + ** common input button list. It >MUST< be equal to the strip's index into + ** the Column[] array, because the strip uses it to access the stripclass + ** buttons. + */ + int ID; + + /* + ** Shape numbers for the shapes in the STRIP.SHP file. + */ + enum SideBarStipShapeEnums { + SB_BLANK, // The blank rectangle to use if there are no objects present. + SB_FRAME + }; + + /* + ** If this particular side strip needs to be redrawn, then this flag + ** will be true. + */ + unsigned IsToRedraw:1; + + /* + ** If construction is in progress (no other objects in this strip can + ** be started), then this flag will be true. It will be cleared when + ** the strip is free to start production again. + */ + unsigned IsBuilding:1; + + /* + ** This controls the sidebar slide direction. If this is true, then the sidebar + ** will scroll downward -- revealing previous objects. + */ + unsigned IsScrollingDown:1; + + /* + ** If the sidebar is scrolling, then this flag is true. Otherwise it is false. + */ + unsigned IsScrolling:1; + + /* + ** This is the object (sidebar slot) that is flashing. Only one slot can be flashing + ** at any one instant. This is usually the result of a click on the slot and construction + ** has commenced. + */ + int Flasher; + + /* + ** As the sidebar scrolls up and down, this variable holds the index for the topmost + ** visible sidebar slot. + */ + int TopIndex; + + /* + ** This is the queued scroll direction and amount. The sidebar + ** will scroll the number of slots indicated by this value. This + ** value is set according to the scroll buttons. + */ + int Scroller; + + /* + ** The sidebar has smooth scrolling. This is the number of pixels the sidebar + ** has slide down. Thus, if this value were 5, then there would be 5 pixels of + ** the TopIndex-1 sidebar object visible. When the Slid value reaches 24, then + ** the value resets to zero and the TopIndex is decremented. For sliding in the + ** opposite direction, change the IsScrollingDown flag. + */ + int Slid; + + /* + ** This is the count of the number of sidebar slots that are active. + */ + int BuildableCount; + + /* + ** This is the array of buildable object types. This array is sorted in the order + ** that it is to be displayed. This array keeps track of which objects are building + ** and ready to be placed. The very nature of this method precludes simultaneous + ** construction of the same object type. + */ + typedef struct BuildType { + int BuildableID; + RTTIType BuildableType; + int Factory; // Production manager. + } BuildType; + BuildType Buildables[MAX_BUILDABLES]; + + /* + ** Pointer to the shape data for small versions of the logos. These are used as + ** placeholder pieces on the side bar. + */ + static void const * LogoShapes; + + /* + ** This points to the animation sequence of frames used to mark the passage of time + ** as an object is undergoing construction. + */ + static void const * ClockShapes; + + /* + ** This points to the animation sequence which deals with special + ** shapes which handle non-production based icons. + */ + static void const * SpecialShapes[3]; + + /* + ** This is the last theater that the special palette remap table was loaded + ** for. If the current theater differs from this recorded value, then the + ** remap tables are reloaded. + */ + static TheaterType LastTheater; + + static ShapeButtonClass UpButton[COLUMNS]; + static ShapeButtonClass DownButton[COLUMNS]; + static SelectClass SelectButton[COLUMNS][MAX_VISIBLE]; + + /* + ** This points to the shapes that are used for the clock overlay. This displays + ** progress of construction. + */ + static char ClockTranslucentTable[(1+1)*256]; + + } Column[COLUMNS]; + + + /* + ** If the sidebar is active then this flag is true. + */ + unsigned IsSidebarActive:1; + + /* + ** This flag tells the rendering system that the sidebar needs to be redrawn. + */ + unsigned IsToRedraw:1; + + class SBGadgetClass: public GadgetClass { + public: +// SBGadgetClass(void) : GadgetClass(SIDE_X+8, SIDE_Y, SIDE_WIDTH-1, SIDE_HEIGHT-1, LEFTUP) {}; + SBGadgetClass(void) : GadgetClass(0,0,0,0,LEFTUP) {}; + + protected: + virtual int Action(unsigned flags, KeyNumType & key); + }; + + /* + ** This is the button that is used to collapse and expand the sidebar. + ** These buttons must be available to derived classes, for Save/Load. + */ + static ShapeButtonClass Repair; + static ShapeButtonClass Upgrade; + static ShapeButtonClass Zoom; + static SBGadgetClass Background; + + /* + ** Pointer to the shape data for the sidebar + */ + static void const * SidebarShape1; + static void const * SidebarShape2; + + + private: + bool Activate_Repair(int control); + bool Activate_Upgrade(int control); + bool Activate_Demolish(int control); + bool Scroll(bool up, int column); + int Which_Column(RTTIType type); + + unsigned IsRepairActive:1; + unsigned IsUpgradeActive:1; + unsigned IsDemolishActive:1; +}; + +#endif diff --git a/SLIDER.CPP b/SLIDER.CPP new file mode 100644 index 0000000..d6c68db --- /dev/null +++ b/SLIDER.CPP @@ -0,0 +1,408 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\slider.cpv 2.17 16 Oct 1995 16:51:54 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SLIDER.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 16, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * SliderClass::Action -- Handles input processing for the slider. * + * SliderClass::Bump -- Bumps the slider one "thumb size" up or down. * + * SliderClass::Recalc_Thumb -- Recalculates the thumb pixel size and starting offset. * + * SliderClass::Set_Maximum -- Sets the maximum value for this slider. * + * SliderClass::Set_Thumb_Size -- Sets the size fo the thumb in "slider units". * + * SliderClass::Set_Value -- Sets the current thumb position for the slider. * + * SliderClass::SliderClass -- Normal constructor for a slider (with thumb) gadget. * + * SliderClass::Step -- Steps the slider one value up or down. * + * SliderClass::Draw_Thumb -- Draws the "thumb" for this slider. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "slider.h" + + +/*********************************************************************************************** + * SliderClass::SliderClass -- Normal constructor for a slider (with thumb) gadget. * + * * + * This is the normal constructor for the slider gadget. * + * * + * INPUT: id -- The ID number to assign to this gadget. * + * x,y -- The pixel coordinate of the upper left corner for this gadget. * + * w,h -- The width and height of the slider gadget. The slider automatically * + * adapts for horizontal or vertical operation depending on which of these * + * dimensions is greater. * + * OUTPUT: none * + * WARNINGS: none * + * HISTORY: 01/15/1995 JLB : Created. * + *=============================================================================================*/ +SliderClass::SliderClass(unsigned id, int x, int y, int w, int h, int belong_to_list) + : GaugeClass(id, x, y, w, h) +{ + BelongToList = belong_to_list ? true : false; + + PlusGadget = 0; + MinusGadget = 0; + if (!BelongToList) { + PlusGadget = new ShapeButtonClass(id, MixFileClass::Retrieve("BTN-PLUS.SHP"), X+Width+2, Y); + MinusGadget = new ShapeButtonClass(id, MixFileClass::Retrieve("BTN-MINS.SHP"), X-6, Y); + + if (PlusGadget) { + PlusGadget->Make_Peer(*this); + PlusGadget->Add(*this); + PlusGadget->Flag_To_Redraw(); + } + if (MinusGadget) { + MinusGadget->Make_Peer(*this); + MinusGadget->Add(*this); + MinusGadget->Flag_To_Redraw(); + } + } + Set_Thumb_Size(1); + Recalc_Thumb(); + + /* + ** Gauges have at least 2 colors, but sliders should only have one. + */ + IsColorized = 0; +} + + +virtual SliderClass::~SliderClass(void) +{ + if (PlusGadget) { + delete PlusGadget; + PlusGadget = 0; + } + if (MinusGadget) { + delete MinusGadget; + MinusGadget = 0; + } +} + + +/*********************************************************************************************** + * SliderClass::Set_Maximum -- Sets the maximum value for this slider. * + * * + * This sets the maximum value that the slider can be set at. The maximum value controls * + * the size of the thumb and the resolution of the thumb's movement. * + * * + * INPUT: value -- The value to set for the slider's maximum. * + * OUTPUT: bool; Was the maximum value changed? A false indicates a set to the value it * + * is currently set to already. * + * WARNINGS: none * + * HISTORY: 01/15/1995 JLB : Created. * + *=============================================================================================*/ +int SliderClass::Set_Maximum(int value) +{ + if (GaugeClass::Set_Maximum(value)) { + Recalc_Thumb(); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * SliderClass::Set_Thumb_Size -- Sets the size fo the thumb in "slider units". * + * * + * This routine will set the size of the thumb as it relates to the maximum value the * + * slider can achieve. This serves to display a proportionally sized thumb as well as * + * control how the slider "bumps" up or down. * + * * + * INPUT: value -- The new value of the thumb. It should never be larger than the slider * + * maximum. * + * OUTPUT: none * + * WARNINGS: none * + * HISTORY: 01/15/1995 JLB : Created. * + *=============================================================================================*/ +void SliderClass::Set_Thumb_Size(int value) +{ + Thumb = MIN(value, MaxValue); + Thumb = MAX(Thumb, 1); + Flag_To_Redraw(); + Recalc_Thumb(); +} + + +/*********************************************************************************************** + * SliderClass::Set_Value -- Sets the current thumb position for the slider. * + * * + * This routine will set the thumb position for the slider. * + * * + * INPUT: value -- The position to set the slider. This position is relative to the maximum * + * value for the slider. * + * * + * OUTPUT: bool; Was the slider thumb position changed at all? * + * WARNINGS: none * + * HISTORY: 01/15/1995 JLB : Created. * + *=============================================================================================*/ +int SliderClass::Set_Value(int value) +{ + value = MIN(value, MaxValue-Thumb); + + if (GaugeClass::Set_Value(value)) { + Recalc_Thumb(); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * SliderClass::Recalc_Thumb -- Recalculates the thumb pixel size and starting offset. * + * * + * This takes the current thumb logical size and starting value and calculates the pixel * + * size and starting offset accordingly. This function should be called whenever one of * + * these elements has changed. * + * * + * INPUT: none * + * OUTPUT: none * + * WARNINGS: none * + * HISTORY: 01/15/1995 JLB : Created. * + *=============================================================================================*/ +void SliderClass::Recalc_Thumb(void) +{ + int length = IsHorizontal ? Width : Height; + int size = Fixed_To_Cardinal(length, Cardinal_To_Fixed(MaxValue, Thumb)); + ThumbSize = MAX(size, 4); + int start = Fixed_To_Cardinal(length, Cardinal_To_Fixed(MaxValue, CurValue)); + ThumbStart = MIN(start, length-ThumbSize); +} + + +/*********************************************************************************************** + * SliderClass::Action -- Handles input processing for the slider. * + * * + * This routine is called when a qualifying input event has occured. This routine will * + * process that event and make any adjustments to the slider as necessary. * + * * + * INPUT: flags -- Flag bits that tell the input event that caused this function to * + * be called. * + * key -- Reference to the key that caused the input event. * + * OUTPUT: bool; Was the event consumed and further processing of the gadget list should be * + * aborted? * + * WARNINGS: none * + * HISTORY: 01/15/1995 JLB : Created. * + *=============================================================================================*/ +int SliderClass::Action(unsigned flags, KeyNumType &key) +{ + /* + ** Handle the mouse click in a special way. If the click was not on the thumb, then + ** jump the thumb position one "step" in the appropriate direction. Otherwise, let normal + ** processing take place -- the slider then "sticks" and the thumb moves according to + ** mouse position. + */ + if (flags & LEFTPRESS) { + int mouse; // Mouse pixel position. + int edge; // Edge of slider. + + if (IsHorizontal) { + mouse = Get_Mouse_X(); + edge = X; + } else { + mouse = Get_Mouse_Y(); + edge = Y; + } + edge += 1; + + /* + ** Clicking outside the thumb: invoke parent's Action to process flags etc, + ** but turn off the event & return true so processing stops at this button. + */ + if (mouse < edge+ThumbStart) { + Bump(true); + GaugeClass::Action(0, key); + key = KN_NONE; + return(true); + } else { + if (mouse > edge+ThumbStart+ThumbSize) { + Bump(false); + GaugeClass::Action(0, key); + key = KN_NONE; + return(true); + } else { + GaugeClass::Action(flags, key); + key = KN_NONE; + return(true); + } + } + } + + /* + ** CHANGE GAUGECLASS::ACTION -- REMOVE (LEFTRELEASE) FROM IF STMT + */ + return(GaugeClass::Action(flags, key)); +} + + +/*********************************************************************************************** + * SliderClass::Bump -- Bumps the slider one "thumb size" up or down. * + * * + * This support function will bump the slider one "step" or the size of the thumb up or * + * down as specified. It is typically called when the slider is clicked outside of the * + * thumb region but still inside of the slider. * + * * + * INPUT: up -- Should the bump be to increase the current position? * + * OUTPUT: bool; Was the slider changed at all? A false indicates that the slider is already * + * at one end or the other. * + * WARNINGS: none * + * HISTORY: 01/15/1995 JLB : Created. * + *=============================================================================================*/ +int SliderClass::Bump(int up) +{ + if (up) { + return(Set_Value(CurValue - Thumb)); + } + return(Set_Value(CurValue + Thumb)); +} + + +/*********************************************************************************************** + * SliderClass::Step -- Steps the slider one value up or down. * + * * + * This routine will move the slider thumb one step in the direction specified. * + * * + * INPUT: up -- Should the step be up (i.e., forward)? * + * OUTPUT: bool; Was the slider changed at all? A false indicates that the slider is already * + * at one end or the other. * + * WARNINGS: none * + * HISTORY: 01/15/1995 JLB : Created. * + *=============================================================================================*/ +int SliderClass::Step(int up) +{ + if (up) { + return(Set_Value(CurValue - 1)); + } + return(Set_Value(CurValue + 1)); +} + + +/*********************************************************************************************** + * SliderClass::Draw_Thumb -- Draws the "thumb" for this slider. * + * * + * This will draw the thumb graphic for this slider. Sometimes the thumb requires special * + * drawing, thus the need for this function separate from the normal Draw_Me function. * + * * + * INPUT: none * + * OUTPUT: none * + * WARNINGS: The mouse is guaranteed to be hidden when this routine is called. * + * HISTORY: 01/16/1995 JLB : Created. * + *=============================================================================================*/ +void SliderClass::Draw_Thumb(void) +{ + if (IsHorizontal) { + Draw_Box(X+ThumbStart, Y, ThumbSize, Height, BOXSTYLE_GREEN_RAISED, true); + } else { + Draw_Box(X, Y+ThumbStart, Width, ThumbSize, BOXSTYLE_GREEN_RAISED, true); + } +} + + +/*********************************************************************************************** + * SliderClass::Draw_Me -- Draws the body of the gauge. * + * * + * This routine will draw the body of the gauge if necessary. * + * * + * INPUT: forced -- Should the gauge be redrawn regardless of the current redraw flag? * + * OUTPUT: bool; Was the gauge redrawn? * + * WARNINGS: none * + * HISTORY: 01/16/1995 JLB : Created. * + *=============================================================================================*/ +int SliderClass::Draw_Me(int forced) +{ + if (BelongToList) { + if (ControlClass::Draw_Me(forced)) { + + /* + ===================== Hide the mouse ===================== + */ + if (LogicPage == &SeenBuff) { + Conditional_Hide_Mouse(X, Y, X+Width, Y+Height); + } + + /* + =========== Draw the body & set text color =============== + */ + Draw_Box (X, Y, Width, Height, BOXSTYLE_GREEN_DOWN, true); +// if (IsHorizontal) { +// LogicPage->Fill_Rect(X, Y+1, X+Width-1, Y+Height-2, 141); +// LogicPage->Draw_Line(X, Y, X+Width-1, Y, 140); // top +// LogicPage->Draw_Line(X, Y+Height, X+Width, Y+Height, 159); // bottom +// } else { +// LogicPage->Fill_Rect(X+1, Y, X+Width-2, Y+Height-1, 141); +// LogicPage->Draw_Line(X, Y, X, Y+Height, 140); // left +// LogicPage->Draw_Line(X+Width-1, Y, X+Width-1, Y+Height, 159); // right +// } + Draw_Thumb(); + + /* + =================== Display the mouse =================== + */ + if (LogicPage == &SeenBuff) { + Conditional_Show_Mouse(); + } + return(true); + } + } + + /* + ** If it does not belong to a listbox... + */ + return(GaugeClass::Draw_Me(forced)); +} + + +/*********************************************************************************************** + * SliderClass::Peer_To_Peer -- A peer gadget was touched -- make adjustments. * + * * + * This routine is called when one of the peer gadgets (the scroll arrows or the slider) * + * was touched in some fashion. This routine will sort out whom and why and then make * + * any necessary adjustments to the list box. * + * * + * INPUT: flags -- The event flags that affected the peer gadget. * + * key -- The key value at the time of the event. * + * whom -- Which gadget is being touched. * + * OUTPUT: none * + * WARNINGS: none * + * HISTORY: 01/16/1995 JLB : Created. * + *=============================================================================================*/ +void SliderClass::Peer_To_Peer(unsigned flags, KeyNumType & , ControlClass & whom) +{ + if (flags & LEFTRELEASE) { + if (&whom == PlusGadget) { + Step(false); + } + if (&whom == MinusGadget) { + Step(true); + } + } +} + + diff --git a/SLIDER.H b/SLIDER.H new file mode 100644 index 0000000..5a9f995 --- /dev/null +++ b/SLIDER.H @@ -0,0 +1,110 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\slider.h_v 2.16 16 Oct 1995 16:45:38 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SLIDER.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 15, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef SLIDER_H +#define SLIDER_H + +#include "gauge.h" +#include "shapebtn.h" + +/*************************************************************************** + * SliderClass -- Like a Windows ListBox structure * + * * + * INPUT: int id-- id of gadget * + * int x -- x position of gadget * + * int y -- y position of gadget * + * int w -- width of gadget * + * int h -- height of gadget * + * int belong_to_list -- does this slider go with a listclass? * + * * + * OUTPUT: none. * + * WARNINGS: * + * HISTORY: 01/03/1995 MML : Created. * + *=========================================================================*/ +class SliderClass : public GaugeClass +{ + public: + SliderClass(unsigned id, int x, int y, int w, int h, int belong_to_list=false); + virtual ~SliderClass(void); +// static SliderClass * Create_One_Of(unsigned id, int x, int y, int w, int h, int belong_to_list=false); + + virtual void Set_Thumb_Size(int value); + virtual int Set_Maximum(int value); + virtual int Set_Value(int); + virtual int Bump(int up); + virtual int Step(int up); + virtual int Draw_Me(int forced); + virtual void Peer_To_Peer(unsigned flags, KeyNumType & key, ControlClass & whom); + + virtual int Thumb_Pixels(void) { return (ThumbSize);} + + protected: + + /* + ** If the slider bar has been created, these point to the respective gadgets + ** that it is composed of. + */ + ShapeButtonClass * PlusGadget; + ShapeButtonClass * MinusGadget; + + /* + ** If I belong to a listbox, I have to draw myself differently... + **/ + unsigned BelongToList:1; + + /* + ** This is the logical size of the thumb. This value is used when drawing + ** the thumb imagery. It is also the amount that is bumped when the + ** Bump() function is called. (This value is in application units.) + */ + int Thumb; + + /* + ** This is the current thumb pixel size and starting offset from beginning + ** of slider region. (These values are in pixels.) + */ + int ThumbSize; + int ThumbStart; // x or y position for the thumb + + virtual int Action(unsigned flags, KeyNumType &key); + virtual void Draw_Thumb(void); + + private: + void Recalc_Thumb(void); +}; + +#endif diff --git a/SMUDGE.CPP b/SMUDGE.CPP new file mode 100644 index 0000000..e3f1110 --- /dev/null +++ b/SMUDGE.CPP @@ -0,0 +1,406 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\smudge.cpv 2.18 16 Oct 1995 16:52:06 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SMUDGE.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : August 9, 1994 * + * * + * Last Update : July 24, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * SmudgeClass::Init -- Initialize the smudge tracking system. * + * SmudgeClass::SmudgeClass -- Constructor for smudge objects. * + * SmudgeClass::operator delete -- Deletes the smudge from the tracking system. * + * SmudgeClass::operator new -- Creator of smudge objects. * + * SmudgeClass::Mark -- Marks a smudge down on the map. * + * SmudgeClass::Read_INI -- Reads smudge data from an INI file. * + * SmudgeClass::Write_INI -- Writes the smudge data to an INI file. * + * SmudgeClass::Disown -- Disowns (removes) a building bib piece. * + * SmudgeClass::Validate -- validates smudge pointer * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "smudge.h" + + +/* +** This contains the value of the Virtual Function Table Pointer +*/ +void * SmudgeClass::VTable; + +HousesType SmudgeClass::ToOwn = HOUSE_NONE; + + +/*********************************************************************************************** + * SmudgeClass::Validate -- validates smudge pointer * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int SmudgeClass::Validate(void) const +{ + int num; + + num = Smudges.ID(this); + if (num < 0 || num >= SMUDGE_MAX) { + Validate_Error("SMUDGE"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*********************************************************************************************** + * SmudgeClass::operator new -- Creator of smudge objects. * + * * + * This routine will allocate a smudge object from the smudge tracking pool. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to a newly allocated smudge object. If one couldn't be * + * found, then NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + *=============================================================================================*/ +void * SmudgeClass::operator new(size_t ) +{ + void * ptr = Smudges.Allocate(); + if (ptr) { + ((SmudgeClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +/*********************************************************************************************** + * SmudgeClass::operator delete -- Deletes the smudge from the tracking system. * + * * + * This routine is used to remove the smudge from the tracking system. * + * * + * INPUT: ptr -- Pointer to the smudge to delete. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + *=============================================================================================*/ +void SmudgeClass::operator delete(void *ptr) +{ + if (ptr) { + ((SmudgeClass *)ptr)->IsActive = false; + } + Smudges.Free((SmudgeClass *)ptr); +} + + +/*********************************************************************************************** + * SmudgeClass::SmudgeClass -- Constructor for smudge objects. * + * * + * This is the typical constructor for smudge objects. If the position to place the * + * smudge is not given, then the smudge will be initialized in a limbo state. If the * + * smudge is placed on the map, then this operation causes the smudge object itself to be * + * deleted and special map values updated to reflect the presence of a smudge. * + * * + * INPUT: type -- The type of smudge to construct. * + * * + * pos -- The position to place the smudge. If -1, then the smudge is initialized * + * into a limbo state. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + *=============================================================================================*/ +SmudgeClass::SmudgeClass(SmudgeType type, COORDINATE pos, HousesType house) : + Class(&SmudgeTypeClass::As_Reference(type)) +{ + if (pos != -1) { + ToOwn = house; + if (!Unlimbo(pos)) { + delete this; + } + ToOwn = HOUSE_NONE; + } +} + + +/*********************************************************************************************** + * SmudgeClass::Init -- Initialize the smudge tracking system. * + * * + * This routine is used during the scenario clearing process to initialize the smudge * + * object tracking system to a null state. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + *=============================================================================================*/ +void SmudgeClass::Init(void) +{ + SmudgeClass *ptr; + + Smudges.Free_All(); + + ptr = new SmudgeClass(); + VTable = ((void **)(((char *)ptr) + sizeof(AbstractClass) - 4))[0]; + delete ptr; +} + + +/*********************************************************************************************** + * SmudgeClass::Mark -- Marks a smudge down on the map. * + * * + * This routine will place the smudge on the map. If the map cell allows. * + * * + * INPUT: mark -- The type of marking to perform. Only MARK_DOWN is supported. * + * * + * OUTPUT: bool; Was the smudge marked successfully? Failure occurs if the smudge isn't * + * marked DOWN. * + * * + * WARNINGS: The smudge object is DELETED by this routine. * + * * + * HISTORY: * + * 09/22/1994 JLB : Created. * + * 12/23/1994 JLB : Checks low level legality before proceeding. * + *=============================================================================================*/ +bool SmudgeClass::Mark(MarkType mark) +{ + Validate(); + if (ObjectClass::Mark(mark)) { + if (mark == MARK_DOWN) { + CELL origin = Coord_Cell(Coord); + + for (int w = 0; w < Class->Width; w++) { + for (int h = 0; h < Class->Height; h++) { + CELL newcell = origin + w + (h*MAP_CELL_W); + if (Map.In_Radar(newcell)) { + CellClass * cell = &Map[newcell]; + + if (Class->IsBib) { + cell->Smudge = Class->Type; + cell->SmudgeData = w + (h*Class->Width); + cell->Owner = ToOwn; + } else { + if (cell->Is_Generally_Clear()) { + if (Class->IsCrater && cell->Smudge != SMUDGE_NONE && SmudgeTypeClass::As_Reference(cell->Smudge).IsCrater) { + cell->SmudgeData++; + cell->SmudgeData = (int)MIN((int)cell->SmudgeData, (int)4); + } + + if (cell->Smudge == SMUDGE_NONE) { + + /* + ** Special selection of a crater that starts as close to the + ** specified coordinate as possible. + */ + if (Class->IsCrater) { + cell->Smudge = (SmudgeType)(SMUDGE_CRATER1 + CellClass::Spot_Index(Coord)); + } else { + cell->Smudge = Class->Type; + } + cell->SmudgeData = 0; + } + } + } + + /* + ** Flag everything that might be overlapping this cell to redraw itself. + */ + cell->Redraw_Objects(); + } + } + } + + /* + ** Whether it was successful in placing, or not, delete the smudge object. It isn't + ** needed once the map has been updated with the proper smudge data. + */ + delete this; + return(true); + } + } + return(false); +} + + +/*********************************************************************************************** + * SmudgeClass::Read_INI -- Reads smudge data from an INI file. * + * * + * This routine is used by the scenario loader to read the smudge data in an INI file and * + * create the appropriate smudge objects on the map. * + * * + * INPUT: buffer -- Pointer to the INI file staging buffer. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + * 07/24/1995 JLB : Sets the smudge data value as well. * + *=============================================================================================*/ +void SmudgeClass::Read_INI(char *buffer) +{ + char buf[128]; // Working string staging buffer. + + int len = strlen(buffer) + 2; + char * tbuffer = buffer + len; + + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-len, buffer); + while (*tbuffer != '\0') { + SmudgeType smudge; // Smudge type. + + WWGetPrivateProfileString(INI_Name(), tbuffer, NULL, buf, sizeof(buf)-1, buffer); + smudge = SmudgeTypeClass::From_Name(strtok(buf, ",")); + if (smudge != SMUDGE_NONE) { + char * ptr = strtok(NULL, ","); + if (ptr) { + int data = 0; + CELL cell = atoi(ptr); + ptr = strtok(NULL, ","); + if (ptr) data = atoi(ptr); + new SmudgeClass(smudge, Cell_Coord(cell)); + if (Map[cell].Smudge == smudge && data) { + Map[cell].SmudgeData = data; + } + } + } + tbuffer += strlen(tbuffer)+1; + } +} + + +/*********************************************************************************************** + * SmudgeClass::Write_INI -- Writes the smudge data to an INI file. * + * * + * This routine is used by the scenario editor to write the smudge data to an INI file. * + * * + * INPUT: buffer -- Pointer to the INI file staging buffer. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/01/1994 JLB : Created. * + * 07/24/1995 JLB : Records the smudge data as well. * + *=============================================================================================*/ +void SmudgeClass::Write_INI(char *buffer) +{ + char uname[10]; + char buf[127]; + char *tbuffer; // Accumulation buffer of unit IDs. + + /* + ** First, clear out all existing template data from the ini file. + */ + tbuffer = buffer + strlen(buffer) + 2; + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-strlen(buffer), buffer); + while (*tbuffer != '\0') { + WWWritePrivateProfileString(INI_Name(), tbuffer, NULL, buffer); + tbuffer += strlen(tbuffer)+1; + } + + /* + ** Find all templates and write them to the file. + */ + for (CELL index = 0; index < MAP_CELL_TOTAL; index++) { + CellClass * ptr; + + ptr = &Map[index]; + if (ptr->Smudge != SMUDGE_NONE) { + SmudgeTypeClass const * stype = &SmudgeTypeClass::As_Reference(ptr->Smudge); + if (!stype->IsBib) { + sprintf(uname, "%03d", index); + sprintf(buf, "%s,%d,%d", stype->IniName, index, ptr->SmudgeData); + WWWritePrivateProfileString(INI_Name(), uname, buf, buffer); + } + } + } +} + + +/*********************************************************************************************** + * SmudgeClass::Disown -- Disowns (removes) a building bib piece. * + * * + * This routine is used when a building is removed from the game. If there was any bib * + * attached, this routine will be called to disown the cells and remove the bib artwork. * + * * + * INPUT: cell -- The origin cell for this bib removal. * + * * + * OUTPUT: none * + * * + * WARNINGS: This is actually working on a temporary bib object. It is created for the sole * + * purpose of calling this routine. It will be deleted immediately afterward. * + * * + * HISTORY: * + * 07/04/1995 JLB : Created. * + *=============================================================================================*/ +void SmudgeClass::Disown(CELL cell) +{ + Validate(); + if (Class->IsBib) { + for (int w = 0; w < Class->Width; w++) { + for (int h = 0; h < Class->Height; h++) { + CellClass & cellptr = Map[cell + w + (h*MAP_CELL_W)]; + + if (cellptr.Overlay == OVERLAY_NONE || !OverlayTypeClass::As_Reference(cellptr.Overlay).IsWall) { + cellptr.Smudge = SMUDGE_NONE; + cellptr.SmudgeData = 0; + cellptr.Owner = HOUSE_NONE; + cellptr.Redraw_Objects(); + } + } + } + } +} diff --git a/SMUDGE.H b/SMUDGE.H new file mode 100644 index 0000000..0b87358 --- /dev/null +++ b/SMUDGE.H @@ -0,0 +1,104 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\smudge.h_v 2.16 16 Oct 1995 16:47:32 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SMUDGE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : August 9, 1994 * + * * + * Last Update : August 9, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef SMUDGE_H +#define SMUDGE_H + +#include "object.h" +#include "type.h" + +/****************************************************************************** +** This is the transitory form for smudges. They exist as independent objects +** only in the transition stage from creation to placement upon the map. Once +** they are placed on the map, they merely become 'smudges' in the cell data. This +** object is then destroyed. +*/ +class SmudgeClass : public ObjectClass +{ + public: + /*------------------------------------------------------------------- + ** Constructors and destructors. + */ + static void * operator new(size_t size); + static void operator delete(void *ptr); + SmudgeClass(SmudgeType type, COORDINATE pos=-1, HousesType house = HOUSE_NONE); + SmudgeClass(void) : Class(0) {}; + operator SmudgeType(void) const {return Class->Type;}; + virtual ~SmudgeClass(void) {if (GameActive) SmudgeClass::Limbo();}; + virtual RTTIType What_Am_I(void) const {return RTTI_SMUDGE;}; + + static void Init(void); + + /* + ** File I/O. + */ + static void Read_INI(char *); + static void Write_INI(char *); + static char *INI_Name(void) {return "SMUDGE";}; + bool Load(FileClass & file); + bool Save(FileClass & file); + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + virtual ObjectTypeClass const & Class_Of(void) const {return *Class;}; + virtual bool Mark(MarkType); + virtual void Draw_It(int , int , WindowNumberType ) {}; + + void Disown(CELL cell); + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + private: + + static HousesType ToOwn; + + /* + ** This is a pointer to the template object's class. + */ + SmudgeTypeClass const * const Class; + + /* + ** This contains the value of the Virtual Function Table Pointer + */ + static void * VTable; +}; + +#endif + diff --git a/SOUNDDLG.CPP b/SOUNDDLG.CPP new file mode 100644 index 0000000..a4e74d7 --- /dev/null +++ b/SOUNDDLG.CPP @@ -0,0 +1,434 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\sounddlg.cpv 2.17 16 Oct 1995 16:51:32 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SOUNDDLG.CPP * + * * + * Programmer : Maria del Mar McCready-Legg, Joe L. Bostic * + * * + * Start Date : Jan 8, 1995 * + * * + * Last Update : Jan 18, 1995 [MML] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * SoundControlsClass::Process -- Handles all the options graphic interface. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "sounddlg.h" + +class MusicListClass : public ListClass +{ + public: + MusicListClass(int id, int x, int y, int w, int h) : + ListClass(id, x, y, w, h, TPF_6PT_GRAD|TPF_NOSHADOW, Hires_Retrieve("BTN-UP.SHP"), Hires_Retrieve("BTN-DN.SHP")) + {}; + virtual ~MusicListClass(void) {}; + + protected: + virtual void Draw_Entry(int index, int x, int y, int width, int selected); +}; +int SoundControlsClass::Init(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + Option_Width =292 * factor; + Option_Height =146 * factor; + + Option_X =((SeenBuff.Get_Width() - Option_Width) / 2); + Option_Y =(SeenBuff.Get_Height() - Option_Height) / 2; + + Listbox_X =1 * factor; + Listbox_Y =54 * factor; + Listbox_W =290 * factor; + Listbox_H =73 * factor; + + Button_Width =85 * factor; + Button_X =Option_Width-(Button_Width + (7 * factor)); + Button_Y =130 * factor; + + Stop_X =5 * factor; + Stop_Y =129 * factor; + + Play_X =23 * factor; + Play_Y =129 * factor; + + OnOff_Width =25 * factor; + #ifdef GERMAN + Shuffle_X =79 * factor; + #else + Shuffle_X =91 * factor; + #endif + + Shuffle_Y =130 * factor; + + Repeat_X =166 * factor; + Repeat_Y =130 * factor; + + MSlider_X =147 * factor; + MSlider_Y =28 * factor; + MSlider_W =108 * factor; + MSlider_Height =5 * factor; + + FXSlider_X =147 * factor; + FXSlider_Y =40 * factor; + FXSlider_W =108 * factor; + FXSlider_Height=5 * factor; + return(factor); +} + +/*********************************************************************************************** + * OptionsClass::Process -- Handles all the options graphic interface. * + * * + * This routine is the main control for the visual representation of the options * + * screen. It handles the visual overlay and the player input. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: 12/31/1994 MML : Created. * + *=============================================================================================*/ +void SoundControlsClass::Process(void) +{ +// ThemeType theme; + + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + + Init(); + /* + ** List box that holds the score text strings. + */ + MusicListClass listbox(0, Option_X+Listbox_X, Option_Y+Listbox_Y, Listbox_W, Listbox_H); + + /* + ** Return to options menu button. + */ + TextButtonClass returnto(BUTTON_OPTIONS, TXT_OPTIONS_MENU, TPF_6PT_GRAD|TPF_NOSHADOW, +#ifdef FRENCH + Option_X+Button_X-8*2, Option_Y+Button_Y, Button_Width+11*2); +#else + Option_X+Button_X, Option_Y+Button_Y, Button_Width); +#endif + + /* + ** Stop playing button. + */ + char filename[30]; + if (factor == 1) + strcpy(filename,"BTN-ST.SHP"); + else + strcpy(filename,"BTN-STH.SHP"); + ShapeButtonClass stopbtn(BUTTON_STOP, MixFileClass::Retrieve(filename), + Option_X+Stop_X, Option_Y+Stop_Y); + + /* + ** Start playing button. + */ + if (factor == 1) + strcpy(filename,"BTN-PL.SHP"); + else + strcpy(filename,"BTN-PLH.SHP"); + + ShapeButtonClass playbtn(BUTTON_PLAY, MixFileClass::Retrieve(filename), + Option_X+Play_X, Option_Y+Play_Y); + + /* + ** Shuffle control. + */ + TextButtonClass shufflebtn(BUTTON_SHUFFLE, TXT_OFF, TPF_6PT_GRAD|TPF_NOSHADOW, + Option_X+Shuffle_X, Option_Y+Shuffle_Y, OnOff_Width); + + /* + ** Repeat control. + */ + TextButtonClass repeatbtn(BUTTON_REPEAT, TXT_OFF, TPF_6PT_GRAD|TPF_NOSHADOW, Option_X+Repeat_X, Option_Y+Repeat_Y, OnOff_Width); + + /* + ** Music volume slider. + */ + SliderClass music(SLIDER_MUSIC, Option_X+MSlider_X, Option_Y+MSlider_Y, MSlider_W, MSlider_Height); + + /* + ** Sound volume slider. + */ + SliderClass sound(SLIDER_SOUND, Option_X+FXSlider_X, Option_Y+FXSlider_Y, FXSlider_W, FXSlider_Height); + + /* + ** Causes left mouse clicks inside the dialog area, but not on any + ** particular button, to be ignored. + */ + GadgetClass area(Option_X, Option_Y, Option_Width, Option_Height, GadgetClass::LEFTPRESS); + + /* + ** Causes right clicks anywhere or left clicks outside of the dialog + ** box area to be the same a clicking the return to game options button. + */ + ControlClass ctrl(BUTTON_OPTIONS, 0, 0, SeenBuff.Get_Width(), SeenBuff.Get_Height(), GadgetClass::RIGHTPRESS|GadgetClass::LEFTPRESS); + + /* + ** The repeat and shuffle buttons are of the toggle type. They toggle + ** between saying "on" and "off". + */ + shufflebtn.IsToggleType = true; + if (Options.IsScoreShuffle) { + shufflebtn.Turn_On(); + } else { + shufflebtn.Turn_Off(); + } + shufflebtn.Set_Text(shufflebtn.IsOn ? TXT_ON : TXT_OFF); + + repeatbtn.IsToggleType = true; + if (Options.IsScoreRepeat) { + repeatbtn.Turn_On(); + } else { + repeatbtn.Turn_Off(); + } + repeatbtn.Set_Text(repeatbtn.IsOn ? TXT_ON : TXT_OFF); + + /* + ** Set the initial values of the sliders. + */ + music.Set_Maximum(255); + music.Set_Thumb_Size(16); + music.Set_Value(Options.ScoreVolume); + sound.Set_Maximum(255); + sound.Set_Thumb_Size(16); + sound.Set_Value(Options.Volume); + + /* + ** Set up the window. Window x-coords are in bytes not pixels. + */ + Set_Logic_Page(SeenBuff); + + /* + ** Create Buttons. + */ + GadgetClass * optionsbtn = &returnto; + listbox.Add_Tail(*optionsbtn); + stopbtn.Add_Tail(*optionsbtn); + playbtn.Add_Tail(*optionsbtn); + shufflebtn.Add_Tail(*optionsbtn); + repeatbtn.Add_Tail(*optionsbtn); + music.Add_Tail(*optionsbtn); + sound.Add_Tail(*optionsbtn); + area.Add_Tail(*optionsbtn); + ctrl.Add_Tail(*optionsbtn); + + /* + ** Add all the themes to the list box. The list box entries are constructed + ** and then stored into allocated EMS memory blocks. + */ + for (ThemeType index = THEME_FIRST; index < Theme.Max_Themes(); index++) { + if (Theme.Is_Allowed(index)) { + char buffer[100]; + int length = Theme.Track_Length(index); + char const * fullname = Theme.Full_Name(index); + + void * ptr = new char [sizeof(buffer)]; + if (ptr) { + sprintf((char *)ptr, "%cTrack %d\t%d:%02d\t%s", index, listbox.Count()+1, length / 60, length % 60, fullname); + listbox.Add_Item((char const *)ptr); + } + + if (Theme.What_Is_Playing() == index) { + listbox.Set_Selected_Index(listbox.Count()-1); + } + } + } + static int _tabs[] = { + 55*factor, 72*factor, 90*factor + }; + listbox.Set_Tabs(_tabs); + + /* + ** Main Processing Loop. + */ + bool display = true; + bool process = true; + + while (process) { + + /* + ** Invoke game callback. + */ + if (GameToPlay == GAME_NORMAL) { + Call_Back(); + } else { + if (Main_Loop()) { + process = false; + } + } + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=TRUE; + } + + /* + ** Refresh display if needed. + */ + if (display) { + + Hide_Mouse(); + + /* + ** Draw the background. + */ + Dialog_Box(Option_X, Option_Y, Option_Width, Option_Height); + + Draw_Caption(TXT_SOUND_CONTROLS, Option_X, Option_Y, Option_Width); + + /* + ** Draw the Music, Speech & Sound titles. + */ + Fancy_Text_Print(TXT_MUSIC_VOLUME, Option_X+MSlider_X-5, Option_Y+MSlider_Y-2, CC_GREEN, TBLACK, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW|TPF_RIGHT); + Fancy_Text_Print(TXT_SOUND_VOLUME, Option_X+FXSlider_X-5, Option_Y+FXSlider_Y-2, CC_GREEN, TBLACK, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW|TPF_RIGHT); + + Fancy_Text_Print(TXT_SHUFFLE, Option_X+Shuffle_X-5, Option_Y+Shuffle_Y+1, CC_GREEN, TBLACK, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW|TPF_RIGHT); + Fancy_Text_Print(TXT_REPEAT, Option_X+Repeat_X-5, Option_Y+Repeat_Y+1, CC_GREEN, TBLACK, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW|TPF_RIGHT); + + optionsbtn->Draw_All(); + Show_Mouse(); + display = false; + } + + /* + ** Get user input. + */ + KeyNumType input = optionsbtn->Input(); + + /* + ** Process Input. + */ + switch (input) { + + case KN_ESC: + case BUTTON_OPTIONS|KN_BUTTON: + process = false; + break; + + /* + ** Control music volume. + */ + case SLIDER_MUSIC|KN_BUTTON: + Options.Set_Score_Volume(music.Get_Value()); + break; + + /* + ** Control sound volume. + */ + case SLIDER_SOUND|KN_BUTTON: + Options.Set_Sound_Volume(sound.Get_Value(), true); + break; + + case BUTTON_LISTBOX|KN_BUTTON: +// Mono_Printf ("%d %s Listbox was pressed.\r",__LINE__, __FILE__); + break; + + /* + ** Stop all themes from playing. + */ + case BUTTON_STOP|KN_BUTTON: + Theme.Queue_Song(THEME_NONE); + break; + + /* + ** Start the currently selected theme to play. + */ + case KN_SPACE: + case BUTTON_PLAY|KN_BUTTON: + if (listbox.Count()) { + Theme.Queue_Song( (ThemeType)*((unsigned char *)listbox.Current_Item()) ); + } + break; + + /* + ** Toggle the shuffle button. + */ + case BUTTON_SHUFFLE|KN_BUTTON: + shufflebtn.Set_Text(shufflebtn.IsOn ? TXT_ON : TXT_OFF); + Options.Set_Shuffle(shufflebtn.IsOn); + break; + + /* + ** Toggle the repeat button. + */ + case BUTTON_REPEAT|KN_BUTTON: + repeatbtn.Set_Text(repeatbtn.IsOn ? TXT_ON : TXT_OFF); + Options.Set_Repeat(repeatbtn.IsOn); + break; + } + } + + /* + ** If the score volume was turned all the way down, then actually + ** stop the scores from being played. + */ + if (!Options.ScoreVolume) { + Theme.Stop(); + } + + /* + ** Save them settings - you know it makes sense + */ + Options.Save_Settings(); // save new value + + /* + ** Free the items from the list box. + */ + while (listbox.Count()) { + char const * ptr = listbox.Get_Item(0); + listbox.Remove_Item(ptr); + delete [] (void*)ptr; + } +} + + +void MusicListClass::Draw_Entry(int index, int x, int y, int width, int selected) +{ + if (TextFlags & TPF_6PT_GRAD) { + TextPrintType flags = TextFlags; + + if (selected) { + flags = flags | TPF_BRIGHT_COLOR; + LogicPage->Fill_Rect (x, y, x + width - 1, y + LineHeight - 1, CC_GREEN_SHADOW); + } else { + if (!(flags & TPF_USE_GRAD_PAL)) { + flags = flags | TPF_MEDIUM_COLOR; + } + } + + Conquer_Clip_Text_Print((char *)Add_Long_To_Pointer(List[index], 1), x, y, CC_GREEN, TBLACK, flags, width, Tabs); + + } else { + Conquer_Clip_Text_Print((char *)Add_Long_To_Pointer(List[index], 1), x, y, (selected ? BLUE : WHITE), TBLACK, TextFlags, width, Tabs); + } +} diff --git a/SOUNDDLG.H b/SOUNDDLG.H new file mode 100644 index 0000000..a8757c9 --- /dev/null +++ b/SOUNDDLG.H @@ -0,0 +1,159 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\sounddlg.h_v 2.18 16 Oct 1995 16:46:48 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : OPTIONS.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : June 8, 1994 * + * * + * Last Update : June 8, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef SOUNDDLG_H +#define SOUNDDLG_H + +#include "gadget.h" + +class SoundControlsClass +{ + enum SoundControlsClassEnums { +#ifdef FRENCH + OPTION_WIDTH=308, +#else + OPTION_WIDTH=292, +#endif + OPTION_HEIGHT=146, + + OPTION_X=((320 - OPTION_WIDTH) / 2), + OPTION_Y=(200 - OPTION_HEIGHT) / 2, + + LISTBOX_X=1, + LISTBOX_Y=54, + LISTBOX_W=290, + LISTBOX_H=72, + + BUTTON_WIDTH=85, + BUTTON_X=OPTION_WIDTH-(BUTTON_WIDTH+7), // Options button x pos + BUTTON_Y=130, // Options button y pos + + STOP_X=5, // Stop button X. + STOP_Y=129, // Stop button Y. + + PLAY_X=23, + PLAY_Y=129, + + ONOFF_WIDTH=25, +#ifdef GERMAN + SHUFFLE_X=79,//BGA:91, +#else +#ifdef FRENCH + SHUFFLE_X=99, +#else + SHUFFLE_X=91, +#endif +#endif + SHUFFLE_Y=130, + +#ifdef FRENCH + REPEAT_X=174, +#else + REPEAT_X=166, +#endif + REPEAT_Y=130, + + MSLIDER_X=147, + MSLIDER_Y=28, + MSLIDER_W=108, + MSLIDER_HEIGHT=5, + + FXSLIDER_X=147, + FXSLIDER_Y=40, + FXSLIDER_W=108, + FXSLIDER_HEIGHT=5, + + BUTTON_STOP = 605, + BUTTON_PLAY, + BUTTON_SHUFFLE, + BUTTON_REPEAT, + BUTTON_OPTIONS, + SLIDER_MUSIC, +// SLIDER_SPEECH, + SLIDER_SOUND, + BUTTON_LISTBOX, + }; + + public: + SoundControlsClass(void) {} + void Process(void); + int Init(void); + + private: + + int Option_Width; + int Option_Height; + + int Option_X; + int Option_Y; + + int Listbox_X; + int Listbox_Y; + int Listbox_W; + int Listbox_H; + + int Button_Width; + int Button_X; + int Button_Y; + + int Stop_X; + int Stop_Y; + + int Play_X; + int Play_Y; + + int OnOff_Width; + + int Shuffle_X; + int Shuffle_Y; + + int Repeat_X; + int Repeat_Y; + + int MSlider_X; + int MSlider_Y; + int MSlider_W; + int MSlider_Height; + + int FXSlider_X; + int FXSlider_Y; + int FXSlider_W; + int FXSlider_Height; + +}; + +#endif diff --git a/SPECIAL.CPP b/SPECIAL.CPP new file mode 100644 index 0000000..1be90b0 --- /dev/null +++ b/SPECIAL.CPP @@ -0,0 +1,274 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\special.cpv 1.4 16 Oct 1995 16:50:06 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SPECIAL.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 05/27/95 * + * * + * Last Update : May 27, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#define OPTION_WIDTH 236 +#define OPTION_HEIGHT 162 +#define OPTION_X ((320 - OPTION_WIDTH) / 2) +#define OPTION_Y (200 - OPTION_HEIGHT) / 2 + +void Special_Dialog(void) +{ + SpecialClass oldspecial = Special; + GadgetClass * buttons = NULL; + static struct { + int Description; + int Setting; + CheckBoxClass * Button; + } _options[] = { +// {TXT_DEFENDER_ADVANTAGE, 0, 0}, + {TXT_SEPARATE_HELIPAD, 0, 0}, + {TXT_VISIBLE_TARGET, 0, 0}, + {TXT_TREE_TARGET, 0, 0}, + {TXT_MCV_DEPLOY, 0, 0}, + {TXT_SMART_DEFENCE, 0, 0}, + {TXT_THREE_POINT, 0, 0}, +// {TXT_TIBERIUM_GROWTH, 0, 0}, +// {TXT_TIBERIUM_SPREAD, 0, 0}, + {TXT_TIBERIUM_FAST, 0, 0}, + {TXT_ROAD_PIECES, 0, 0}, + {TXT_SCATTER, 0, 0}, + {TXT_SHOW_NAMES, 0, 0}, + }; + + TextButtonClass ok(200, TXT_OK, TPF_6PT_GRAD|TPF_NOSHADOW, OPTION_X+5, OPTION_Y+OPTION_HEIGHT-15); + TextButtonClass cancel(201, TXT_CANCEL, TPF_6PT_GRAD|TPF_NOSHADOW, OPTION_X+OPTION_WIDTH-50, OPTION_Y+OPTION_HEIGHT-15); + buttons = &ok; + cancel.Add(*buttons); + + for (int index = 0; index < sizeof(_options)/sizeof(_options[0]); index++) { + _options[index].Button = new CheckBoxClass(100+index, OPTION_X+7, OPTION_Y+20+(index*10)); + if (_options[index].Button) { + _options[index].Button->Add(*buttons); + + bool value = false; + switch (_options[index].Description) { + case TXT_SEPARATE_HELIPAD: + value = Special.IsSeparate; + break; + + case TXT_SHOW_NAMES: + value = Special.IsNamed; + break; + + case TXT_DEFENDER_ADVANTAGE: + value = Special.IsDefenderAdvantage; + break; + + case TXT_VISIBLE_TARGET: + value = Special.IsVisibleTarget; + break; + + case TXT_TREE_TARGET: + value = Special.IsTreeTarget; + break; + + case TXT_MCV_DEPLOY: + value = Special.IsMCVDeploy; + break; + + case TXT_SMART_DEFENCE: + value = Special.IsSmartDefense; + break; + + case TXT_THREE_POINT: + value = Special.IsThreePoint; + break; + + case TXT_TIBERIUM_GROWTH: + value = Special.IsTGrowth; + break; + + case TXT_TIBERIUM_SPREAD: + value = Special.IsTSpread; + break; + + case TXT_TIBERIUM_FAST: + value = Special.IsTFast; + break; + + case TXT_ROAD_PIECES: + value = Special.IsRoad; + break; + + case TXT_SCATTER: + value = Special.IsScatter; + break; + } + + _options[index].Setting = value; + if (value) { + _options[index].Button->Turn_On(); + } else { + _options[index].Button->Turn_Off(); + } + } + } + + Map.Override_Mouse_Shape(MOUSE_NORMAL); + Set_Logic_Page(SeenBuff); + bool recalc = true; + bool display = true; + bool process = true; + while (process) { + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=TRUE; + } + + if (GameToPlay == GAME_NORMAL) { + Call_Back(); + } else { + if (Main_Loop()) { + process = false; + } + } + + if (display) { + display = false; + + Hide_Mouse(); + Dialog_Box(OPTION_X, OPTION_Y, OPTION_WIDTH, OPTION_HEIGHT); + Draw_Caption(TXT_SPECIAL_OPTIONS, OPTION_X, OPTION_Y, OPTION_WIDTH); + + for (index = 0; index < sizeof(_options)/sizeof(_options[0]); index++) { + Fancy_Text_Print(_options[index].Description, _options[index].Button->X+10, _options[index].Button->Y, CC_GREEN, TBLACK, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_NOSHADOW); + } + buttons->Draw_All(); + Show_Mouse(); + } + + KeyNumType input = buttons->Input(); + switch (input) { + case KN_ESC: + case 200|KN_BUTTON: + process = false; + for (index = 0; index < sizeof(_options)/sizeof(_options[0]); index++) { + switch (_options[index].Description) { + case TXT_SEPARATE_HELIPAD: + oldspecial.IsSeparate = _options[index].Setting; + break; + + case TXT_SHOW_NAMES: + oldspecial.IsNamed = _options[index].Setting; + break; + + case TXT_DEFENDER_ADVANTAGE: + oldspecial.IsDefenderAdvantage = _options[index].Setting; + break; + + case TXT_VISIBLE_TARGET: + oldspecial.IsVisibleTarget = _options[index].Setting; + break; + + case TXT_TREE_TARGET: + oldspecial.IsTreeTarget = _options[index].Setting; + break; + + case TXT_MCV_DEPLOY: + oldspecial.IsMCVDeploy = _options[index].Setting; + break; + + case TXT_SMART_DEFENCE: + oldspecial.IsSmartDefense = _options[index].Setting; + break; + + case TXT_THREE_POINT: + oldspecial.IsThreePoint = _options[index].Setting; + break; + + case TXT_TIBERIUM_GROWTH: + oldspecial.IsTGrowth = _options[index].Setting; + break; + + case TXT_TIBERIUM_SPREAD: + oldspecial.IsTSpread = _options[index].Setting; + break; + + case TXT_TIBERIUM_FAST: + oldspecial.IsTFast = _options[index].Setting; + break; + + case TXT_ROAD_PIECES: + oldspecial.IsRoad = _options[index].Setting; + break; + + case TXT_SCATTER: + oldspecial.IsScatter = _options[index].Setting; + break; + } + } + OutList.Add(EventClass(oldspecial)); + break; + + case 201|KN_BUTTON: + process = false; + break; + + case KN_NONE: + break; + + default: + index = (input & ~KN_BUTTON) - 100; + if ((unsigned)index < sizeof(_options)/sizeof(_options[0])) { + _options[index].Setting = (_options[index].Setting == false); + if (_options[index].Setting) { + _options[index].Button->Turn_On(); + } else { + _options[index].Button->Turn_Off(); + } + } + break; + } + } + + Map.Revert_Mouse_Shape(); + HiddenPage.Clear(); + Map.Flag_To_Redraw(true); + Map.Render(); +} + + + + + diff --git a/SPECIAL.H b/SPECIAL.H new file mode 100644 index 0000000..aaa7576 --- /dev/null +++ b/SPECIAL.H @@ -0,0 +1,241 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\special.h_v 2.15 16 Oct 1995 16:47:36 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SPECIAL.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 02/27/95 * + * * + * Last Update : February 27, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef SPECIAL_H +#define SPECIAL_H + +class SpecialClass +{ + public: + void Init(void) { + IsScrollMod = false; + IsGross = false; + IsHealthBar = true; + IsEasy = false; + IsDifficult = false; + IsSpeedBuild = false; + IsDefenderAdvantage = true; + IsVisibleTarget = false; + IsVariation = false; + IsJurassic = false; + IsJuvenile = false; + IsSmartDefense = false; + IsTreeTarget = false; + IsMCVDeploy = false; + IsMonoEnabled = false; + IsInert = false; + IsShowPath = false; + IsBarOn = false; + IsThreePoint = false; + IsTGrowth = true; + IsTSpread = true; + IsTFast = true; + IsRoad = false; + IsScatter = false; + IsCaptureTheFlag = false; + IsNamed = false; + IsFromInstall = false; + IsSeparate = false; + IsFromWChat = false; + } + + /* + ** Show the health bar on the enemy units? + */ + unsigned IsHealthBar:1; + + /* + ** Is the game flagged for easy mode? + */ + unsigned IsEasy:1; + + /* + ** Is the game flagged for difficult? + */ + unsigned IsDifficult:1; + + /* + ** Controls the speedy build option -- used for testing. + */ + unsigned IsSpeedBuild:1; + + /* + ** If the player can build the helipad separate from the helipad and + ** helicopter combo, then this flag will be true. + */ + unsigned IsSeparate:1; + + /* + ** If the defender has the advantage then this will be true. This flag + ** allows the defender to have a better advantage in combat than the + ** attacker. Moving units will not be able to dish out or take as much + ** damage when this flag is true. + */ + unsigned IsDefenderAdvantage:1; + + /* + ** If civilian structures are to have a name, then this flag will be + ** set to true. The default case is to just use generic names for + ** civilians. + */ + unsigned IsNamed:1; + + /* + ** If from install, then play the special installation movie and + ** skip asking them what type of game they want to play. + */ + unsigned IsFromInstall:1; + + /* + ** If capture the flag mode is on, this flag will be true. With this + ** flag enabled, then the flag is initially placed at the start of + ** the scenario. + */ + unsigned IsCaptureTheFlag:1; + + /* + ** Is target selecting by other human opponents visible to the player? + */ + unsigned IsVisibleTarget:1; + + /* + ** If human generated sound effects are to be used, then this + ** flag will be true. + */ + unsigned IsJuvenile:1; + + /* + ** If friendly units should return fire when fired upon, set this + ** flag to true. The default is only for the enemy units to do this. + */ + unsigned IsSmartDefense:1; + + /* + ** If targeting of trees is allowed, then this flag will be true. + */ + unsigned IsTreeTarget:1; + + /* + ** If this flag is true, then the construction yard can undeploy back into an MCV. + */ + unsigned IsMCVDeploy:1; + + /* + ** If the monochrome debugging output is enabled, then this flag will be true. + */ + unsigned IsMonoEnabled:1; + + /* + ** This flags controls whether weapons are inert. An inert weapon doesn't do any + ** damage. Effectively, if this is true, then the units never die. + */ + unsigned IsInert:1; + + /* + ** When this flag is true, the computer findpath algorithm reveals the route being + ** examined. This is used to trace findpath bugs. + */ + unsigned IsShowPath:1; + + /* + ** All units will display their health bargraph if this is true. + */ + unsigned IsBarOn:1; + + /* + ** If wheeled vehicles should do a 3-point turn when rotating in place, then + ** this flag is true. + */ + unsigned IsThreePoint:1; + + /* + ** If Tiberium is allowed to grow, then this flag will be true. + */ + unsigned IsTGrowth:1; + + /* + ** If Tiberium is allowed to spread, then this flag will be true. + */ + unsigned IsTSpread:1; + + /* + ** This controls whether Tiberium grows&spreads quickly or not. + */ + unsigned IsTFast:1; + + /* + ** This flag controls whether the road additional pieces are added to + ** the bottom of buildings. If true, then the roads are NOT added. + */ + unsigned IsRoad:1; + + /* + ** Controls whether units (especially infantry) will scatter when there + ** is an immediate threat. This gives infantry a "mind of their own" when + ** it comes to self preservation. If set to false, then units will not + ** scatter. + */ + unsigned IsScatter:1; + + /* + ** Special bonus scenario enabled. + */ + unsigned IsJurassic:1; + + /* + ** Are score variations allowed? + */ + unsigned IsVariation:1; + + /* + ** If the gross human splatter marks should be present. + */ + unsigned IsGross:1; + + /* + ** Disables scrolling over the "options" and "sidebar" tabs. + */ + unsigned IsScrollMod:1; + + /* + ** Flag that we were spawned from WChat. + */ + unsigned IsFromWChat:1; +}; + + +#endif diff --git a/STAGE.H b/STAGE.H new file mode 100644 index 0000000..650f982 --- /dev/null +++ b/STAGE.H @@ -0,0 +1,100 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\stage.h_v 2.15 16 Oct 1995 16:45:16 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : STAGE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : June 17, 1994 * + * * + * Last Update : June 17, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef STAGE_H +#define STAGE_H + +class StageClass { + + /* + ** This handles the animation stage of the object. This includes smoke, walking, + ** flapping, and rocket flames. + */ + unsigned short Stage; + + /* + ** This is the countdown timer for stage animation. When this counts down + ** to zero, then the stage increments by one and the time cycle starts + ** over again. + */ + unsigned char StageTimer; + + /* + ** This is the value to assign the StageTimer whenever it needs to be reset. Thus, + ** this value is the control of how fast the stage value increments. + */ + unsigned char Rate; + + public: + StageClass(void) { + StageTimer = 0; + Stage = 0; + Rate = 0; + }; + + int Fetch_Stage(void) const {return Stage;}; + int Fetch_Rate(void) const {return Rate;}; + void Set_Stage(int stage) {Stage = stage;}; + void Set_Rate(unsigned char rate) {Rate = StageTimer = rate;}; + void AI(void) {}; + bool Graphic_Logic(void) { + if (Rate) { + StageTimer--; + if (!StageTimer) { + Stage++; + StageTimer = Rate; + return true; + } + } + return false; + }; + #ifdef CHEAT_KEYS + void Debug_Dump(MonoClass *mono) const { + mono->Set_Cursor(56, 7); + mono->Printf("%3d[%d]", Stage, Rate); + }; + #endif + + /* + ** File I/O. + */ + void Code_Pointers(void) { return; } + void Decode_Pointers(void) { return; } +}; + + +#endif diff --git a/STARTUP.CPP b/STARTUP.CPP new file mode 100644 index 0000000..70a7f21 --- /dev/null +++ b/STARTUP.CPP @@ -0,0 +1,793 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\startup.cpv 2.17 16 Oct 1995 16:48:12 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : STARTUP.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : October 3, 1994 * + * * + * Last Update : August 27, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Delete_Swap_Files -- Deletes previously existing swap files. * + * Prog_End -- Cleans up library systems in prep for game exit. * + * main -- Initial startup routine (preps library systems). * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include +#include +#include "ccdde.h" + +bool Read_Private_Config_Struct(char *profile, NewConfigType *config); +void Delete_Swap_Files(void); +void Print_Error_End_Exit(char *string); +void Print_Error_Exit(char *string); +WinTimerClass *WinTimer; +extern void Create_Main_Window ( HANDLE instance , int command_show , int width , int height); + +extern bool ReadyToQuit; +void Read_Setup_Options(RawFileClass *config_file); + +bool VideoBackBufferAllowed = true; +void Check_From_WChat(char *wchat_name); +bool SpawnedFromWChat = false; + +extern "C"{ + bool __cdecl Detect_MMX_Availability(void); + void __cdecl Init_MMX(void); +} + + + +#if (0) +char WibbleBuffer[1024*1024]; + +void CD_Test(void) +{ + HANDLE handle; + DWORD size; + + handle= CreateFile("e:\\scores.mix", GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (handle== INVALID_HANDLE_VALUE){ + return; + } + + unsigned bytes_read; + + do{ + bytes_read = ReadFile (handle , WibbleBuffer , 1024*1024, &size, NULL); + + }while(size == 1024*1024); + + + CloseHandle (handle); +} +#endif //(0) + + + +/*********************************************************************************************** + * main -- Initial startup routine (preps library systems). * + * * + * This is the routine that is first called when the program starts up. It basically * + * handles the command line parsing and setting up library systems. * + * * + * INPUT: argc -- Number of command line arguments. * + * * + * argv -- Pointer to array of comman line argument strings. * + * * + * OUTPUT: Returns with execution failure code (if any). * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/20/1995 JLB : Created. * + *=============================================================================================*/ + +HINSTANCE ProgramInstance; +extern BOOL CC95AlreadyRunning; +void Move_Point(short &x, short &y, register DirType dir, unsigned short distance); + +void Check_Use_Compressed_Shapes (void); + +int PASCAL WinMain ( HINSTANCE instance , HINSTANCE , char * command_line , int command_show ) +{ +// Heap_Dump_Check( "first thing in main" ); +// malloc(1); + + CCDebugString ("C&C95 - Starting up.\n"); + + + //WindowsTimer = new WinTimerClass(60,FALSE); + //CD_Test(); + + + + /* + ** These values return 0x47 if code is working correctly + */ +// int temp = Desired_Facing256 (1070, 5419, 1408, 5504); + + + + /* + ** If we are already running then switch to the existing process and exit + */ + SpawnedFromWChat = false; + + if (CC95AlreadyRunning) { //Set in the DDEServer constructor + //MessageBox (NULL, "Error - attempt to restart C&C95 when already running.", "Command & Conquer", MB_ICONEXCLAMATION|MB_OK); + + HWND ccwindow; + ccwindow = FindWindow ("Command & Conquer", "Command & Conquer"); + if (ccwindow){ + SetForegroundWindow ( ccwindow ); + ShowWindow ( ccwindow, SW_RESTORE ); + } + + return (EXIT_SUCCESS); + } + + + DDSCAPS surface_capabilities; + + if (Ram_Free(MEM_NORMAL) < 5000000) { +#ifdef GERMAN + printf("Zuwenig Hauptspeicher verfgbar.\n"); +#else +#ifdef FRENCH + printf("M‚moire vive (RAM) insuffisante.\n"); +#else + printf("Insufficient RAM available.\n"); +#endif +#endif + return(EXIT_FAILURE); + } + + + //void *test_buffer = Alloc(20,MEM_NORMAL); + + //memset ((char*)test_buffer, 0, 21); + + //Free(test_buffer); + + + int argc; //Command line argument count + unsigned command_scan; + char command_char; + char * argv[20]; //Pointers to command line arguments + char path_to_exe[280]; + + ProgramInstance = instance; + + /* + ** Get the full path to the .EXE + */ + GetModuleFileName (instance, &path_to_exe[0], 280); + + /* + ** First argument is supposed to be a pointer to the .EXE that is running + ** + */ + argc=1; //Set argument count to 1 + argv[0]=&path_to_exe[0]; //Set 1st command line argument to point to full path + + /* + ** Get pointers to command line arguments just like if we were in DOS + ** + ** The command line we get is cr/zero? terminated. + ** + */ + + command_scan=0; + + do { + /* + ** Scan for non-space character on command line + */ + do { + command_char = *( command_line+command_scan++ ); + } while ( command_char==' ' ); + + if ( command_char!=0 && command_char != 13 ){ + argv[argc++]=command_line+command_scan-1; + + /* + ** Scan for space character on command line + */ + do { + command_char = *( command_line+command_scan++ ); + } while ( command_char!=' ' && command_char != 0 && command_char!=13 ); + *( command_line+command_scan-1 ) = 0; + } + + } while ( command_char != 0 && command_char != 13 && argc<20 ); + + + + /* + ** Remember the current working directory and drive. + */ + unsigned olddrive; + char oldpath[PATH_MAX]; + getcwd(oldpath, sizeof(oldpath)); + _dos_getdrive(&olddrive); + + + /* + ** Change directory to the where the executable is located. Handle the + ** case where there is no path attached to argv[0]. + */ + char drive[_MAX_DRIVE]; + char path[_MAX_PATH]; + unsigned drivecount; + _splitpath(argv[0], drive, path, NULL, NULL); + if (!drive[0]) { + drive[0] = ('A' + olddrive)-1; + } + if (!path[0]) { + strcpy(path, "."); + } + _dos_setdrive(toupper((drive[0])-'A')+1, &drivecount); + if (path[strlen(path)-1] == '\\') { + path[strlen(path)-1] = '\0'; + } + chdir(path); + +#ifdef JAPANESE + ForceEnglish = false; +#endif + if (Parse_Command_Line(argc, argv)) { + + + WindowsTimer = new WinTimerClass(60,FALSE); + + int time_test = WindowsTimer->Get_System_Tick_Count(); + Sleep (1000); + if (WindowsTimer->Get_System_Tick_Count() == time_test){ +#ifdef FRENCH + MessageBox(0, "Error - L'horloge systŠme n'a pas pu s'initialiser en raison de l'instabilit‚ du sytŠme. Vous devez red‚marrer Windows.", "Command & Conquer" , MB_OK|MB_ICONSTOP); +#else +#ifdef GERMAN + MessageBox(0, "Fehler - das Timer-System konnte aufgrund einer Instabilit„t des Systems nicht initialisiert werden. Bitte starten Sie Windows neu.", "Command & Conquer", MB_OK|MB_ICONSTOP); +#else + MessageBox(0, "Error - Timer system failed to start due to system instability. You need to restart Windows.", "Command & Conquer", MB_OK|MB_ICONSTOP); +#endif //GERMAN +#endif //FRENCH + return(EXIT_FAILURE); + } + + RawFileClass cfile("CONQUER.INI"); + +#ifdef JAPANESE + //////////////////////////////////////if(!ForceEnglish) KBLanguage = 1; +#endif + + + /* + ** Check for existance of MMX support on the processor + */ + if (Detect_MMX_Availability()){ + //MessageBox(NULL, "MMX extensions detected - enabling MMX support.", "Command & Conquer",MB_ICONEXCLAMATION|MB_OK); + MMXAvailable = true; + } + + /* + ** If there is loads of memory then use uncompressed shapes + */ + Check_Use_Compressed_Shapes(); + + /* + ** If there is not enough disk space free, dont allow the product to run. + */ + if (Disk_Space_Available() < INIT_FREE_DISK_SPACE) { +#ifdef GERMAN + char disk_space_message [512]; + sprintf (disk_space_message, "Nicht genug Festplattenplatz fr Command & Conquer.\nSie brauchen %d MByte freien Platz auf der Festplatte.", (INIT_FREE_DISK_SPACE) / (1024 * 1024)); + MessageBox(NULL, disk_space_message, "Command & Conquer", MB_ICONEXCLAMATION|MB_OK); + if ( WindowsTimer ) + delete WindowsTimer; + return (EXIT_FAILURE); +#endif +#ifdef FRENCH + char disk_space_message [512]; + sprintf (disk_space_message, "Espace disque insuffisant pour lancer Command & Conquer.\nVous devez disposer de %d Mo d'espace disponsible sur disque dur.", (INIT_FREE_DISK_SPACE) / (1024 * 1024)); + MessageBox(NULL, disk_space_message, "Command & Conquer", MB_ICONEXCLAMATION|MB_OK); + if ( WindowsTimer ) + delete WindowsTimer; + return (EXIT_FAILURE); +#endif +#if !(FRENCH | GERMAN) + int reply = MessageBox(NULL, "Warning - you are critically low on free disk space for virtual memory and save games. Do you want to play C&C anyway?", "Command & Conquer", MB_ICONQUESTION|MB_YESNO); + if (reply == IDNO){ + + if ( WindowsTimer ) + delete WindowsTimer; + return (EXIT_FAILURE); + } + +#endif + } + + CDFileClass::Set_CD_Drive (CDList.Get_First_CD_Drive()); + + if (cfile.Is_Available()) { + +#ifndef NOMEMCHECK + char * cdata = (char *)Load_Alloc_Data(cfile); + Read_Private_Config_Struct(cdata, &NewConfig); + delete [] cdata; +#else + Read_Private_Config_Struct((char *)Load_Alloc_Data(cfile), &NewConfig); +#endif + Read_Setup_Options( &cfile ); + + CCDebugString ("C&C95 - Creating main window.\n"); + + Create_Main_Window( instance , command_show , ScreenWidth , ScreenHeight ); + + CCDebugString ("C&C95 - Initialising audio.\n"); + + SoundOn = Audio_Init ( MainWindow , 16 , false , 11025*2 , 0 ); + + Palette = new(MEM_CLEAR) unsigned char[768]; + + BOOL video_success = FALSE; + CCDebugString ("C&C95 - Setting video mode.\n"); + /* + ** Set 640x400 video mode. If its not available then try for 640x480 + */ + if (ScreenHeight == 400){ + if (Set_Video_Mode (MainWindow, ScreenWidth, ScreenHeight, 8)){ + video_success = TRUE; + }else{ + if (Set_Video_Mode (MainWindow, ScreenWidth, 480, 8)){ + video_success = TRUE; + ScreenHeight = 480; + } + } + }else{ + if (Set_Video_Mode (MainWindow, ScreenWidth, ScreenHeight, 8)){ + video_success = TRUE; + } + } + + if (!video_success){ + CCDebugString ("C&C95 - Failed to set video mode.\n"); + MessageBox(MainWindow, Text_String(TXT_UNABLE_TO_SET_VIDEO_MODE), "Command & Conquer", MB_ICONEXCLAMATION|MB_OK); + if (WindowsTimer) delete WindowsTimer; + if (Palette) delete [] Palette; + return (EXIT_FAILURE); + } + + CCDebugString ("C&C95 - Initialising video surfaces.\n"); + + if (ScreenWidth==320){ + VisiblePage.Init( ScreenWidth , ScreenHeight , NULL , 0 , (GBC_Enum)0); + ModeXBuff.Init( ScreenWidth , ScreenHeight , NULL , 0 , (GBC_Enum)(GBC_VISIBLE | GBC_VIDEOMEM)); + } else { + VisiblePage.Init( ScreenWidth , ScreenHeight , NULL , 0 , (GBC_Enum)(GBC_VISIBLE | GBC_VIDEOMEM)); + + /* + ** Check that we really got a video memory page. Failure is fatal. + */ + memset (&surface_capabilities, 0, sizeof(surface_capabilities)); + VisiblePage.Get_DD_Surface()->GetCaps(&surface_capabilities); + if (surface_capabilities.dwCaps & DDSCAPS_SYSTEMMEMORY){ + /* + ** Aaaarrgghh! + */ + CCDebugString ("C&C95 - Unable to allocate primary surface.\n"); + MessageBox(MainWindow, Text_String(TXT_UNABLE_TO_ALLOCATE_PRIMARY_VIDEO_BUFFER), "Command & Conquer", MB_ICONEXCLAMATION|MB_OK); + if (WindowsTimer) delete WindowsTimer; + if (Palette) delete [] Palette; + return (EXIT_FAILURE); + } + + /* + ** If we have enough left then put the hidpage in video memory unless... + ** + ** If there is no blitter then we will get better performance with a system + ** memory hidpage + ** + ** Use a system memory page if the user has specified it via the ccsetup program. + */ + CCDebugString ("C&C95 - Allocating back buffer "); + long video_memory = Get_Free_Video_Memory(); + unsigned video_capabilities = Get_Video_Hardware_Capabilities(); + if (video_memory < ScreenWidth*ScreenHeight || + (! (video_capabilities & VIDEO_BLITTER)) || + (video_capabilities & VIDEO_NO_HARDWARE_ASSIST) || + !VideoBackBufferAllowed){ + CCDebugString ("in system memory.\n"); + HiddenPage.Init (ScreenWidth , ScreenHeight , NULL , 0 , (GBC_Enum)0); + } else { + //HiddenPage.Init (ScreenWidth , ScreenHeight , NULL , 0 , (GBC_Enum)0); + CCDebugString ("in video memory.\n"); + HiddenPage.Init (ScreenWidth , ScreenHeight , NULL , 0 , (GBC_Enum)GBC_VIDEOMEM); + + /* + ** Make sure we really got a video memory hid page. If we didnt then things + ** will run very slowly. + */ + memset (&surface_capabilities, 0, sizeof(surface_capabilities)); + HiddenPage.Get_DD_Surface()->GetCaps(&surface_capabilities); + if (surface_capabilities.dwCaps & DDSCAPS_SYSTEMMEMORY){ + /* + ** Oh dear, big trub. This must be an IBM Aptiva or something similarly cruddy. + ** We must redo the Hidden Page as system memory. + */ + AllSurfaces.Remove_DD_Surface(HiddenPage.Get_DD_Surface()); // Remove the old surface from the AllSurfaces list + HiddenPage.Get_DD_Surface()->Release(); + HiddenPage.Init (ScreenWidth , ScreenHeight , NULL , 0 , (GBC_Enum)0); + }else{ + VisiblePage.Attach_DD_Surface(&HiddenPage); + } + } + } + + ScreenHeight = 400; + + if (VisiblePage.Get_Height() == 480){ + SeenBuff.Attach(&VisiblePage,0, 40, 640, 400); + HidPage.Attach(&HiddenPage, 0, 40, 640, 400); + }else{ + SeenBuff.Attach(&VisiblePage,0, 0, 640, 400); + HidPage.Attach(&HiddenPage, 0, 0, 640, 400); + } + CCDebugString ("C&C95 - Adjusting variables for resolution.\n"); + Options.Adjust_Variables_For_Resolution(); + + CCDebugString ("C&C95 - Setting palette.\n"); + /////////Set_Palette(Palette); + + WindowList[0][WINDOWWIDTH] = SeenBuff.Get_Width() >> 3; + WindowList[0][WINDOWHEIGHT] = SeenBuff.Get_Height(); + + /* + ** Install the memory error handler + */ + Memory_Error = &Memory_Error_Handler; + + /* + ** Initialise MMX support if its available + */ + CCDebugString ("C&C95 - Entering MMX detection.\n"); + if (MMXAvailable){ + Init_MMX(); + } + + CCDebugString ("C&C95 - Creating mouse class.\n"); + WWMouse = new WWMouseClass(&SeenBuff, 32, 32); +// MouseInstalled = Install_Mouse(32,24,320,200); + MouseInstalled = TRUE; + + /* + ** See if we should run the intro + */ + CCDebugString ("C&C95 - Reading CONQUER.INI.\n"); + char *buffer = (char*)Alloc(64000 , MEM_NORMAL); //(char *)HidPage.Get_Buffer(); + cfile.Read(buffer, cfile.Size()); + buffer[cfile.Size()] = '\0'; + + /* + ** Check for forced intro movie run disabling. If the conquer + ** configuration file says "no", then don't run the intro. + */ + char tempbuff[5]; + WWGetPrivateProfileString("Intro", "PlayIntro", "Yes", tempbuff, 4, buffer); + if ((stricmp(tempbuff, "No") == 0) || SpawnedFromWChat) { + Special.IsFromInstall = false; + }else{ + Special.IsFromInstall = true; + } + SlowPalette = WWGetPrivateProfileInt("Options", "SlowPalette", 1, buffer); + +#ifdef DEMO + /* + ** Check for override directory path for CD searches. + */ + WWGetPrivateProfileString("CD", "Path", ".", OverridePath, sizeof(OverridePath), buffer); +#endif + + /* + ** Regardless of whether we should run it or not, here we're + ** gonna change it to say "no" in the future. + */ + WWWritePrivateProfileString("Intro", "PlayIntro", "No", buffer); + cfile.Write(buffer, strlen(buffer)); + + Free(buffer); + + CCDebugString ("C&C95 - Checking availability of C&CSPAWN.INI packet from WChat.\n"); + if (DDEServer.Get_MPlayer_Game_Info()){ + CCDebugString ("C&C95 - C&CSPAWN.INI packet available.\n"); + Check_From_WChat(NULL); + }else{ + CCDebugString ("C&C95 - C&CSPAWN.INI packet not arrived yet.\n"); + //Check_From_WChat("C&CSPAWN.INI"); + //if (Special.IsFromWChat){ + // DDEServer.Disable(); + //} + } + + /* + ** If the intro is being run for the first time, then don't + ** allow breaking out of it with the key. + */ + if (Special.IsFromInstall) { + BreakoutAllowed = false; + } + + Memory_Error_Exit = Print_Error_End_Exit; + + CCDebugString ("C&C95 - Entering main game.\n"); + Main_Game(argc, argv); + + VisiblePage.Clear(); + HiddenPage.Clear(); +// Set_Video_Mode(RESET_MODE); + + Memory_Error_Exit = Print_Error_Exit; + + CCDebugString ("C&C95 - About to exit.\n"); + ReadyToQuit = 1; + + PostMessage(MainWindow, WM_DESTROY, 0, 0); + do + { + Keyboard::Check(); + }while (ReadyToQuit == 1); + + CCDebugString ("C&C95 - Returned from final message loop.\n"); + //Prog_End(); + //Invalidate_Cached_Icons(); + //VisiblePage.Un_Init(); + //HiddenPage.Un_Init(); + //AllSurfaces.Release(); + //Reset_Video_Mode(); + //Stop_Profiler(); + return (EXIT_SUCCESS); + + } else { +#ifdef GERMAN + puts("Bitte erst das SETUP-Programm starten.\n"); +#else +#ifdef FRENCH + puts("Lancez d'abord le programme de configuration SETUP.\n"); +#else + puts("Run SETUP program first."); + puts("\n"); +#endif + Kbd.Get(); +#endif + } + +// Remove_Keyboard_Interrupt(); + if (WindowsTimer){ + delete WindowsTimer; + WindowsTimer = NULL; + } + + if (Palette){ + delete [] Palette; + Palette = NULL; + } + } + + /* + ** Restore the current drive and directory. + */ +#ifdef NOT_FOR_WIN95 + _dos_setdrive(olddrive, &drivecount); + chdir(oldpath); +#endif //NOT_FOR_WIN95 + + return(EXIT_SUCCESS); +} + + +/*********************************************************************************************** + * Prog_End -- Cleans up library systems in prep for game exit. * + * * + * This routine should be called before the game terminates. It handles cleaning up * + * library systems so that a graceful return to the host operating system is achieved. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/20/1995 JLB : Created. * + *=============================================================================================*/ +void __cdecl Prog_End(void) +{ +#ifndef DEMO + if (GameToPlay == GAME_MODEM || GameToPlay == GAME_NULL_MODEM) { + NullModem.Change_IRQ_Priority(0); + } +#endif + CCDebugString ("C&C95 - About to call Sound_End.\n"); + Sound_End(); + CCDebugString ("C&C95 - Returned from Sound_End.\n"); + if (WWMouse){ + CCDebugString ("C&C95 - Deleting mouse object.\n"); + delete WWMouse; + WWMouse = NULL; + } + if (WindowsTimer){ + CCDebugString ("C&C95 - Deleting windows timer.\n"); + delete WindowsTimer; + WindowsTimer = NULL; + } + + if (Palette){ + CCDebugString ("C&C95 - Deleting palette object.\n"); + delete [] Palette; + Palette = NULL; + } +} + + +/*********************************************************************************************** + * Delete_Swap_Files -- Deletes previously existing swap files. * + * * + * This routine will scan through the current directory and delete any swap files it may * + * find. This is used to clear out any left over swap files from previous runs (crashes) * + * of the game. This routine presumes that it cannot delete the swap file that is created * + * by the current run of the game. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/27/1995 JLB : Created. * + *=============================================================================================*/ +void Delete_Swap_Files(void) +{ + struct find_t ff; // for _dos_findfirst + + if (!_dos_findfirst("*.SWP", _A_NORMAL, &ff)) { + do { + unlink(ff.name); + } while(!_dos_findnext(&ff)); + } +} + + +void Print_Error_End_Exit(char *string) +{ + printf( "%s\n", string ); + Get_Key(); + Prog_End(); + printf( "%s\n", string ); + exit(1); +} + + +void Print_Error_Exit(char *string) +{ + printf( "%s\n", string ); + exit(1); +} + + + + + + + + +/*********************************************************************************************** + * Read_Setup_Options -- Read stuff in from the INI file that we need to know sooner * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/7/96 4:09PM ST : Created * + *=============================================================================================*/ +void Read_Setup_Options( RawFileClass *config_file ) +{ + char *buffer = new char [config_file->Size()]; + + if (config_file->Is_Available()){ + + config_file->Read (buffer, config_file->Size()); + + VideoBackBufferAllowed = WWGetPrivateProfileInt ("Options", "VideoBackBuffer", 1, buffer); + AllowHardwareBlitFills = WWGetPrivateProfileInt ("Options", "HardwareFills", 1, buffer); + ScreenHeight = WWGetPrivateProfileInt ("Options", "Resolution", 0, buffer) ? 480 : 400; + IsV107 = WWGetPrivateProfileInt ("Options", "Compatibility", 0, buffer); + + /* + ** See if an alternative socket number has been specified + */ + int socket = WWGetPrivateProfileInt ("Options", "Socket", 0, buffer); + if (socket >0 ){ + socket += 0x4000; + if (socket >= 0x4000 && socket < 0x8000) { + Ipx.Set_Socket (socket); + } + } + + /* + ** See if a destination network has been specified + */ + char netbuf [512]; + memset (netbuf, 0, sizeof (netbuf) ); + char *netptr = WWGetPrivateProfileString ("Options", "DestNet", NULL, netbuf, sizeof (netbuf), buffer); + + if (netptr && strlen (netbuf)){ + NetNumType net; + NetNodeType node; + + /* + ** Scan the string, pulling off each address piece + */ + int i = 0; + char * p = strtok(netbuf,"."); + int x; + while (p) { + sscanf(p,"%x",&x); // convert from hex string to int + if (i < 4) { + net[i] = (char)x; // fill NetNum + } else { + node[i-4] = (char)x; // fill NetNode + } + i++; + p = strtok(NULL,"."); + } + + /* + ** If all the address components were successfully read, fill in the + ** BridgeNet with a broadcast address to the network across the bridge. + */ + if (i >= 4) { + IsBridge = 1; + memset(node, 0xff, 6); + BridgeNet = IPXAddressClass(net, node); + } + } + + } + + delete [] buffer; +} diff --git a/STATS.CPP b/STATS.CPP new file mode 100644 index 0000000..5d95cb8 --- /dev/null +++ b/STATS.CPP @@ -0,0 +1,676 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : WINSTUB.CPP * + * * + * Programmer : Steve Tall * + * * + * Start Date : 05/29/1996 * + * * + * Last Update : May 29th 1996 [ST] * + * * + *---------------------------------------------------------------------------------------------* + * Overview: * + * Internet game statistics to collect and upload to the server * + * * + * * + *---------------------------------------------------------------------------------------------* + * * + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "tcpip.h" +#include "packet.h" +#include "ccdde.h" + +#define FIELD_PACKET_TYPE "TYPE" +#define FIELD_GAME_ID "IDNO" +#define FIELD_START_CREDITS "CRED" +#define FIELD_BASES "BASE" +#define FIELD_TIBERIUM "TIBR" +#define FIELD_CRATES "CRAT" +#define FIELD_AI_PLAYERS "AIPL" +#define FIELD_CAPTURE_THE_FLAG "FLAG" +#define FIELD_START_UNIT_COUNT "UNIT" +#define FIELD_TECH_LEVEL "TECH" +#define FIELD_SCENARIO "SCEN" +#define FIELD_COMPLETION "CMPL" +#define FIELD_START_TIME "TIME" +#define FIELD_GAME_DURATION "DURA" +#define FIELD_FRAME_RATE "AFPS" +#define FIELD_SPEED_SETTING "SPED" +#define FIELD_GAME_VERSION "VERS" +#define FIELD_GAME_BUILD_DATE "DATE" +#define FIELD_COVERT_PRESENT "COVT" +#define FIELD_CPU_TYPE "PROC" +#define FIELD_MEMORY "MEMO" +#define FIELD_VIDEO_MEMORY "VIDM" +#define FIELD_PLAYER1_HANDLE "NAM1" +#define FIELD_PLAYER2_HANDLE "NAM2" +#define FIELD_PLAYER1_TEAM "SID1" +#define FIELD_PLAYER2_TEAM "SID2" +#define FIELD_PLAYER1_COLOR "COL1" +#define FIELD_PLAYER2_COLOR "COL2" +#define FIELD_PLAYER1_CREDITS "CRD1" +#define FIELD_PLAYER2_CREDITS "CRD2" + +#define FIELD_PLAYER1_UNITS_LEFT "UNL1" +#define FIELD_PLAYER2_UNITS_LEFT "UNL2" +#define FIELD_PLAYER1_INFANTRY_LEFT "INL1" +#define FIELD_PLAYER2_INFANTRY_LEFT "INL2" +#define FIELD_PLAYER1_PLANES_LEFT "PLL1" +#define FIELD_PLAYER2_PLANES_LEFT "PLL2" +#define FIELD_PLAYER1_BUILDINGS_LEFT "BLL1" +#define FIELD_PLAYER2_BUILDINGS_LEFT "BLL2" + +#define FIELD_PLAYER1_UNITS_BOUGHT "UNB1" +#define FIELD_PLAYER2_UNITS_BOUGHT "UNB2" +#define FIELD_PLAYER1_INFANTRY_BOUGHT "INB1" +#define FIELD_PLAYER2_INFANTRY_BOUGHT "INB2" +#define FIELD_PLAYER1_PLANES_BOUGHT "PLB1" +#define FIELD_PLAYER2_PLANES_BOUGHT "PLB2" +#define FIELD_PLAYER1_BUILDINGS_BOUGHT "BLB1" +#define FIELD_PLAYER2_BUILDINGS_BOUGHT "BLB2" + +#define FIELD_PLAYER1_UNITS_KILLED "UNK1" +#define FIELD_PLAYER2_UNITS_KILLED "UNK2" +#define FIELD_PLAYER1_INFANTRY_KILLED "INK1" +#define FIELD_PLAYER2_INFANTRY_KILLED "INK2" +#define FIELD_PLAYER1_PLANES_KILLED "PLK1" +#define FIELD_PLAYER2_PLANES_KILLED "PLK2" +#define FIELD_PLAYER1_BUILDINGS_KILLED "BLK1" +#define FIELD_PLAYER2_BUILDINGS_KILLED "BLK2" + +#define FIELD_PLAYER1_BUILDINGS_CAPTURED "BLC1" +#define FIELD_PLAYER2_BUILDINGS_CAPTURED "BLC2" + +#define FIELD_PLAYER1_CRATES_FOUND "CRA1" +#define FIELD_PLAYER2_CRATES_FOUND "CRA2" +#define FIELD_PLAYER1_HARVESTED "HRV1" +#define FIELD_PLAYER2_HARVESTED "HRV2" + + +#define PACKET_TYPE_HOST_GAME_INFO (unsigned char) 50 +#define PACKET_TYPE_GUEST_GAME_INFO (unsigned char) 51 + +enum { + COMPLETION_CONNECTION_LOST, + COMPLETION_PLAYER_1_WON, + COMPLETION_PLAYER_1_WON_BY_RESIGNATION, + COMPLETION_PLAYER_1_WON_BY_DISCONNECTION, + COMPLETION_PLAYER_2_WON, + COMPLETION_PLAYER_2_WON_BY_RESIGNATION, + COMPLETION_PLAYER_2_WON_BY_DISCONNECTION +}; + + +extern unsigned long PlanetWestwoodGameID; +extern HINSTANCE ProgramInstance; +extern unsigned long PlanetWestwoodStartTime; + +extern "C" char CPUType; + + +bool GameTimerInUse = false; +TimerClass GameTimer; +long GameEndTime; +void *PacketLater = NULL; + +/*********************************************************************************************** + * Send_Statistics_To_Server -- sends internet game statistics to the Westeood server * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 5/29/96 12:38PM ST : Created * + *=============================================================================================*/ + +void Send_Statistics_Packet(void) +{ +#ifndef DEMO + + PacketClass stats; + HouseClass *player; + static int packet_size; + int index; + bool packet_later = false; // Should the packet be sent later + void *packet; + + static char field_player_handle[5] = { "NAM?" }; + static char field_player_team[5] = { "SID?" }; + static char field_player_color[5] = { "COL?" }; + static char field_player_credits[5] = { "CRD?" }; + static char field_player_units_left[5] = { "UNL?" }; + static char field_player_infantry_left[5] = { "INL?" }; + static char field_player_planes_left[5] = { "PLL?" }; + static char field_player_buildings_left[5] = { "BLL?" }; + static char field_player_units_bought[5] = { "UNB?" }; + static char field_player_infantry_bought[5] = { "INB?" }; + static char field_player_planes_bought[5] = { "PLB?" }; + static char field_player_buildings_bought[5] = { "BLB?" }; + static char field_player_units_killed[5] = { "UNK?" }; + static char field_player_infantry_killed[5] = { "INK?" }; + static char field_player_planes_killed[5] = { "PLK?" }; + static char field_player_buildings_killed[5] = { "BLK?" }; + static char field_player_buildings_captured[5] = { "BLC?" }; + static char field_player_crates_found[5] = { "CRA?" }; + static char field_player_harvested[5] = { "HRV?" }; + + static char *houses[] = { + "GDI", + "NOD", + "NUT", + "JUR", + "M01", + "M02", + "M03", + "M04", + "M05", + "M06" + }; + + CCDebugString ("C&C95 - In Send_Statistics_Packet.\n"); + + + if (!PacketLater){ + + CCDebugString ("C&C95 - PacketLater is false.\n"); + + + /* + ** Field to identify this as C&C 95 internet game statistics packet + */ + if (Server){ + stats.Add_Field(FIELD_PACKET_TYPE, PACKET_TYPE_HOST_GAME_INFO); + }else{ + stats.Add_Field(FIELD_PACKET_TYPE, PACKET_TYPE_GUEST_GAME_INFO); + } + + /* + ** Game ID. A unique game identifier assigned by WChat. + */ + stats.Add_Field(FIELD_GAME_ID, PlanetWestwoodGameID); + + /* + ** Start credits. + */ + stats.Add_Field(FIELD_START_CREDITS, (unsigned long)MPlayerCredits); + + /* + ** Bases (On/Off) + */ + stats.Add_Field(FIELD_BASES, MPlayerBases ? "ON" : "OFF"); + + /* + ** Tiberium (On/Off) + */ + stats.Add_Field(FIELD_TIBERIUM, MPlayerTiberium ? "ON" : "OFF"); + + /* + ** Crates (On/Off) + */ + stats.Add_Field(FIELD_CRATES, MPlayerGoodies ? "ON" : "OFF"); + + /* + ** AI Players (On/Off/Capture the flag) + */ + stats.Add_Field(FIELD_AI_PLAYERS, MPlayerGhosts ? "ON" : "OFF"); + stats.Add_Field(FIELD_CAPTURE_THE_FLAG, Special.IsCaptureTheFlag ? "ON" : "OFF"); + + /* + ** Start unit count + */ + stats.Add_Field(FIELD_START_UNIT_COUNT, (unsigned long)MPlayerUnitCount); + + /* + ** Tech level. + */ + stats.Add_Field(FIELD_TECH_LEVEL, (unsigned long)BuildLevel); + + CCDebugString ("C&C95 - Adding stats field for scenario.\n"); + /* + ** Scenario + */ + char fname[128]; + char namebuffer[40]; + char *abuffer = (char *)_ShapeBuffer; + memset(abuffer, '\0', _ShapeBufferSize); + sprintf(fname,"%s.INI",ScenarioName); + CCFileClass fileo; + fileo.Set_Name (fname); + fileo.Read(abuffer, _ShapeBufferSize-1); + fileo.Close(); + WWGetPrivateProfileString("Basic", "Name", "Nulls-Ville", namebuffer, 40, abuffer); + stats.Add_Field(FIELD_SCENARIO, namebuffer); + //stats.Add_Field(FIELD_SCENARIO, MPlayerScenarios[ScenarioIdx]); + + + /* + ** Game completion status. + ** + ** Connection lost. + ** Player 1 won + ** Player 2 won + ** Player 1 won by resignation + ** Player 2 won by resignation + ** Player 1 aborted + ** Player 2 aborted + ** + */ + CCDebugString ("C&C95 - Adding stats field for completion status.\n"); + HouseClass *player1 = HouseClass::As_Pointer(MPlayerHouses[0]); + HouseClass *player2 = HouseClass::As_Pointer(MPlayerHouses[1]); + + int completion = -1; + + if (ConnectionLost){ + completion = COMPLETION_CONNECTION_LOST; + CCDebugString ("C&C95 - Completion status is connection lost.\n"); + }else{ + + if (player1 && player2){ + if (player1->IGaveUp){ + completion = COMPLETION_PLAYER_2_WON_BY_DISCONNECTION; + CCDebugString ("C&C95 - Completion status is player 1 disconnected.\n"); + } + + if (player2->IGaveUp){ + completion = COMPLETION_PLAYER_1_WON_BY_DISCONNECTION; + CCDebugString ("C&C95 - Completion status is player 2 disconnected.\n"); + } + + + if (player2->IsDefeated){ + /* + ** Player 1 won. Find out how. + */ + completion = COMPLETION_PLAYER_1_WON; + if (player2->Resigned){ + completion = COMPLETION_PLAYER_1_WON_BY_RESIGNATION; + CCDebugString ("C&C95 - Completion status is player 2 resigned.\n"); + }else{ + if (player2->IGaveUp){ + completion = COMPLETION_PLAYER_1_WON_BY_DISCONNECTION; + CCDebugString ("C&C95 - Completion status is player 2 disconnected.\n"); + } + } + + + }else{ + + if (player1->IsDefeated){ + /* + ** Player 2 won. Find out how. + */ + completion = COMPLETION_PLAYER_2_WON; + if (player1->Resigned){ + completion = COMPLETION_PLAYER_2_WON_BY_RESIGNATION; + CCDebugString ("C&C95 - Completion status is player 1 resigned.\n"); + }else{ + if (player1->IGaveUp){ + completion = COMPLETION_PLAYER_2_WON_BY_DISCONNECTION; + CCDebugString ("C&C95 - Completion status is player 1 disconnected.\n"); + } + } + } + } + } + } + + stats.Add_Field (FIELD_COMPLETION, (char) completion); + + + + + + + /* + ** Game start time (GMT or Pacific?) + ** + ** Passed from WChat + */ + stats.Add_Field (FIELD_START_TIME, (long) PlanetWestwoodStartTime); + + /* + ** Game duration (seconds). + */ + stats.Add_Field (FIELD_GAME_DURATION, (long) GameEndTime/60); + + /* + ** Avg. frame rate. + */ + if (GameEndTime/60 == 0){ + stats.Add_Field (FIELD_FRAME_RATE, 0L ); + }else{ + stats.Add_Field (FIELD_FRAME_RATE, (long) Frame / (GameEndTime/60) ); + } + + + CCDebugString ("C&C95 - Adding hardware info stats.\n"); + /* + ** CPU type + */ + stats.Add_Field (FIELD_CPU_TYPE, (char)CPUType); + + /* + ** Memory + */ + MEMORYSTATUS mem_info; + mem_info.dwLength=sizeof(mem_info); + GlobalMemoryStatus(&mem_info); + stats.Add_Field (FIELD_MEMORY, (long)mem_info.dwTotalPhys); + + /* + ** Video memory + */ + DDCAPS video_capabilities; + long video_memory; + + if (DirectDrawObject){ + video_capabilities.dwSize = sizeof (video_capabilities); + if (DD_OK == DirectDrawObject->GetCaps (&video_capabilities , NULL)){ + video_memory = video_capabilities.dwVidMemTotal; + video_memory += 1024*1024 -1; + video_memory &= 0xfff00000; + stats.Add_Field (FIELD_VIDEO_MEMORY, (long) video_memory); + } + } + + CCDebugString ("C&C95 - Adding game info stats.\n"); + /* + ** Game speed setting. + */ + stats.Add_Field (FIELD_SPEED_SETTING, (char)Options.GameSpeed); + + /* + ** C&C 95 version/build date + */ + char version[128]; + sprintf (version, "%d%s", Version_Number(), VersionText); + stats.Add_Field (FIELD_GAME_VERSION, version); + + char path_to_exe[280]; + FILETIME write_time; //File time is 64 bits + + GetModuleFileName (ProgramInstance, path_to_exe, 280); + RawFileClass file; + file.Set_Name(path_to_exe); + file.Open(); + HANDLE handle = file.Get_File_Handle(); + + if (handle != INVALID_HANDLE_VALUE){ + if (GetFileTime (handle, NULL, NULL, &write_time)){ + write_time.dwLowDateTime = htonl (write_time.dwLowDateTime); + write_time.dwHighDateTime = htonl (write_time.dwHighDateTime); + stats.Add_Field (FIELD_GAME_BUILD_DATE, (void*)&write_time, sizeof (write_time)); + } + } + + /* + ** Covert installed? (Yes/No) + */ + stats.Add_Field(FIELD_COVERT_PRESENT, (char) Expansion_Present()); + + CCDebugString ("C&C95 - Adding house specific stats.\n"); + /* + ** Build the player specific statistics + ** + */ + for (int house = 0 ; house < 2 ; house++){ + + player = HouseClass::As_Pointer(MPlayerHouses[house]); + + if (player){ + /* + ** Player handle. + */ + field_player_handle[3] = '1' + (char)house; + stats.Add_Field (field_player_handle, (char*) MPlayerNames[house]); + + /* + ** Player team. (NOD or GDI) + */ + field_player_team[3] = '1' + (char)house; + stats.Add_Field (field_player_team, houses[player->ActLike]); + + /* + ** Player color + */ + field_player_color[3] = '1' + (char)house; + stats.Add_Field (field_player_color, (unsigned char) (player->Class->House - HOUSE_MULTI1)); + + /* + ** Player end credits. + */ + field_player_credits[3] = '1' + (char)house; + stats.Add_Field (field_player_credits, player->Credits + player->Tiberium); + + /* + ** Number of each unit/building type built + */ + field_player_infantry_bought[3] = '1' + (char)house; + field_player_units_bought[3] = '1' + (char)house; + field_player_planes_bought[3] = '1' + (char)house; + field_player_buildings_bought[3] = '1' + (char)house; + + player->InfantryTotals->To_Network_Format(); + player->UnitTotals->To_Network_Format(); + player->AircraftTotals->To_Network_Format(); + player->BuildingTotals->To_Network_Format(); + + stats.Add_Field (field_player_infantry_bought, (void*) player->InfantryTotals->Get_All_Totals(), player->InfantryTotals->Get_Unit_Count()*4); + stats.Add_Field (field_player_units_bought, (void*) player->UnitTotals->Get_All_Totals(), player->UnitTotals->Get_Unit_Count()*4); + stats.Add_Field (field_player_planes_bought, (void*) player->AircraftTotals->Get_All_Totals(), player->AircraftTotals->Get_Unit_Count()*4); + stats.Add_Field (field_player_buildings_bought, (void*) player->BuildingTotals->Get_All_Totals(), player->BuildingTotals->Get_Unit_Count()*4); + + player->InfantryTotals->To_PC_Format(); + player->UnitTotals->To_PC_Format(); + player->AircraftTotals->To_PC_Format(); + player->BuildingTotals->To_PC_Format(); + + /* + ** Clear out the counts and use the space to count up the current number of units/buildings + */ + player->InfantryTotals->Clear_Unit_Total(); + player->AircraftTotals->Clear_Unit_Total(); + player->UnitTotals->Clear_Unit_Total(); + player->BuildingTotals->Clear_Unit_Total(); + + /* + ** Number of units remaining to player + */ + for (index = 0; index < Units.Count(); index++) { + UnitClass const * unit = Units.Ptr(index); + if (unit->House == player){ + player->UnitTotals->Increment_Unit_Total (unit->Class->Type); + } + } + + for (index = 0; index < Infantry.Count(); index++) { + InfantryClass const * infantry = Infantry.Ptr(index); + if (infantry->House == player && !infantry->Class->IsCivilian){ + player->InfantryTotals->Increment_Unit_Total (infantry->Class->Type); + } + } + + for (index = 0; index < Aircraft.Count(); index++) { + AircraftClass const * aircraft = Aircraft.Ptr(index); + if (aircraft->House == player && aircraft->Class->Type != AIRCRAFT_CARGO){ + player->AircraftTotals->Increment_Unit_Total (aircraft->Class->Type); + } + } + + for (index = 0; index < Buildings.Count(); index++) { + BuildingClass const * building = Buildings.Ptr(index); + if (building->House == player){ + player->BuildingTotals->Increment_Unit_Total (building->Class->Type); + } + } + + player->InfantryTotals->To_Network_Format(); + player->UnitTotals->To_Network_Format(); + player->AircraftTotals->To_Network_Format(); + player->BuildingTotals->To_Network_Format(); + + field_player_infantry_left[3] = '1' + (char)house; + field_player_units_left[3] = '1' + (char)house; + field_player_planes_left[3] = '1' + (char)house; + field_player_buildings_left[3] = '1' + (char)house; + stats.Add_Field (field_player_infantry_left, (void*) player->InfantryTotals->Get_All_Totals(), player->InfantryTotals->Get_Unit_Count()*4); + stats.Add_Field (field_player_units_left, (void*) player->UnitTotals->Get_All_Totals(), player->UnitTotals->Get_Unit_Count()*4); + stats.Add_Field (field_player_planes_left, (void*) player->AircraftTotals->Get_All_Totals(), player->AircraftTotals->Get_Unit_Count()*4); + stats.Add_Field (field_player_buildings_left, (void*) player->BuildingTotals->Get_All_Totals(), player->BuildingTotals->Get_Unit_Count()*4); + + + /* + ** Number of enemy units/buildings of each type destroyed. + */ + + player->DestroyedInfantry->To_Network_Format(); + player->DestroyedUnits->To_Network_Format(); + player->DestroyedAircraft->To_Network_Format(); + player->DestroyedBuildings->To_Network_Format(); + + field_player_infantry_killed[3] = '1' + (char)house; + field_player_units_killed[3] = '1' + (char)house; + field_player_planes_killed[3] = '1' + (char)house; + field_player_buildings_killed[3] = '1' + (char)house; + stats.Add_Field (field_player_infantry_killed, (void*) player->DestroyedInfantry->Get_All_Totals(), player->DestroyedInfantry->Get_Unit_Count()*4); + stats.Add_Field (field_player_units_killed, (void*) player->DestroyedUnits->Get_All_Totals(), player->DestroyedUnits->Get_Unit_Count()*4); + stats.Add_Field (field_player_planes_killed, (void*) player->DestroyedAircraft->Get_All_Totals(), player->DestroyedAircraft->Get_Unit_Count()*4); + stats.Add_Field (field_player_buildings_killed, (void*) player->DestroyedBuildings->Get_All_Totals(), player->DestroyedBuildings->Get_Unit_Count()*4); + + + /* + ** Number and type of enemy buildings captured + */ + field_player_buildings_captured[3] = '1' + (char)house; + player->CapturedBuildings->To_Network_Format(); + stats.Add_Field (field_player_buildings_captured, (void*) player->CapturedBuildings->Get_All_Totals(), player->CapturedBuildings->Get_Unit_Count()*4); + + /* + ** Number of crates discovered and their contents + */ + field_player_crates_found[3] = '1' + (char)house; + player->TotalCrates->To_Network_Format(); + stats.Add_Field (field_player_crates_found, (void*) player->TotalCrates->Get_All_Totals(), player->TotalCrates->Get_Unit_Count()*4); + + /* + ** Amount of tiberium turned into credits + */ + field_player_harvested[3] = '1' + (char)house; + stats.Add_Field (field_player_harvested, (unsigned long) player->HarvestedCredits); + } + } + + CCDebugString ("C&C95 - Calling Create_Comms_Packet.\n"); + /* + ** Create the comms packet to be sent + */ + packet = stats.Create_Comms_Packet(packet_size); + CCDebugString ("C&C95 - Returned from Create_Comms_Packet.\n"); + + /* + ** If a player disconnected then dont send the packet at this time - save it for later + */ + if (completion == COMPLETION_PLAYER_1_WON_BY_DISCONNECTION + || completion == COMPLETION_PLAYER_2_WON_BY_DISCONNECTION){ + PacketLater = packet; + CCDebugString ("C&C95 - Flagging to send the packet later.\n"); + return; + } + + }else{ //else for if (!PacketLater) + + CCDebugString ("C&C95 - PacketLater is true.\n"); + + /* + ** Send the packet we calculated earlier when the disconnect occurred + */ + packet = PacketLater; + PacketLater = NULL; + } + + /* + ** Send it..... + */ + int times = 100; //100 times max + CountDownTimerClass send_timer; + + CCDebugString ("C&C95 - About to send stats packet to DDE server.\n"); + while ( ! Send_Data_To_DDE_Server ((char*)packet, packet_size, DDEServerClass::DDE_PACKET_GAME_RESULTS)){ + CCDebugString ("C&C95 - Stats packet send failed.\n"); + send_timer.Set (60, true); + while (send_timer.Time()){}; + } + + + /* + ** Save it to disk as well so I can see it + */ +#if (0) + RawFileClass anotherfile ("packet.net"); + anotherfile.Write(packet, packet_size); +#endif //(0) + /* + ** Tidy up + */ + CCDebugString ("C&C95 - About to delete packet memory.\n"); + delete [] packet; + + GameStatisticsPacketSent = true; + CCDebugString ("C&C95 - Returning from Send_Statistics_Packet.\n"); +#endif //DEMO + +} + + + + + + + + + +void Register_Game_Start_Time(void) +{ + + GameTimer.Set (0, true); + GameTimerInUse = true; +} + + +extern void Register_Game_End_Time(void) +{ + GameEndTime = GameTimer.Time(); + GameTimerInUse = false; +} + diff --git a/STD.LNT b/STD.LNT new file mode 100644 index 0000000..1807aae --- /dev/null +++ b/STD.LNT @@ -0,0 +1,102 @@ +// Watcom C, C++ (32 bit), -si4 -sp4, +// Standard lint options + +// Compiler Options for Watcom C, C++ 32 bit + +-cwc + +// This file contains options to allow PC-lint to process source +// files for your compiler. It is used as follows: +// +// lint co-wc32.lnt source-file(s) +// +-d_M_IX86=200 // assume Intel 80286 architecture -- modify to suit +-d__declspec()= // ignore this construct + + // additional reserve words needed ++rw(_loadds,_export) ++rw(__interrupt,__near,__far,__huge,__fortran,__pascal,__cdecl) ++rw(__export,__loadds,__saveregs,__asm,__fastcall,__stdcall) ++rw(_export) + ++fcd // makes cdecl significant -- used for proto generation ++fcu // chars are by default unsigned ++fsu // so are strings +-d__386__ // pre-defined macro for 386 version, not set by -cwc +-d__FLAT__ // not set by -cwc +-si4 // sizeof(int) is 4 +-spN4 // sizeof(near pointer) is 4 +-spF6 // sizeof( far pointer) is 6 +-sld10 // sizeof(long double) is 10. +-function(exit,_exit) // _exit() is like exit() +-emacro(734,putc) // don't complain about items being too large. +-emacro(506,putc) // don't complain about constant Boolean +-emacro(???,va_arg) // the va_arg() macro can yield 415, 416, 661, 662 + // 796 and 797 (out-of-bounds errors). + + // While processing compiler (library) header files ... +-elib(46) // an unsigned short bit field is used as __FILLER__ +-elib(522) // function return value ignored +-elib(537) // repeated include file (ios.h) +-elib(641) // converting enum to int +-elib(652) // suppress message about #define of earlier declared symbols +-elib(655) // ORing enum's +-elib(726) // extraneous comma in enumeration +-elib(760) // suppress message about multiple identical macro defs +-elib(762) // suppress message about multiple identical declarations and +-elib(806) // small bit field is signed +-elib(1053) // prototypes cannot be distinguished +-elib(1511) // member (rdbuf) hides nonvirtual member +-elib(1704) // private copy constructor +-elib(1712) // default constructor missing +-elib(1717) // empty prototypes +-elib(1720) // strange argument to assignment operator +-elib(1721) // unusual operator =() declaration +-elib(1722) // assignment operator does not return ref to class +-elib(1724) // strange argument to copy constructor + +-esym(1702,operator<<,operator>>) + +// The following functions exhibit variable return modes. +// That is, they may equally-usefully be called for a value +// as called just for their effects. Accordingly we inhibit +// Warning 534 for these functions. +// Feel free to add to or subtract from this list. + +-esym(534,close,creat,fclose,fflush,fprintf,fputc) +-esym(534,fputs,fscanf,fseek,fwrite,lseek,memcpy,memmove,memset) +-esym(534,printf,puts,scanf,sprintf,sscanf,strcat,strcpy) +-esym(534,strncat,strncpy,unlink,write) + +//------------------------------------------------------------------ + +// Please note -- this is a representative set of error suppression +// options. Please adjust to suit your own policies +// See PC-lint for C/C++ manual (chapter LIVING WITH LINT) +// for further details. + +-e502 -e713 -e737 -eau // don't report on signed/unsigned mismatches +-e734 // allow sub-integer loss of information +-e701 -e703 // shifting int left is OK +-e537 // don't care about repeated include file +-e641 // converting enum to int is ok +-e1042 // operator ++/-- don't need class parameters +-e963 -e763 // redundant declarations are ok +-e1712 // no default constructor defined is ok +-e1704 // private constructors are ok +-e534 // ignoring return value is ok +-e732 // going from signed to unsigned parameter is ok +-e1411 // functions hiding base functions is ok +-e788 // switch with default doesn't need all values specified +-e655 -e656 // compatable enum bit and arithmetic operations are ok +-e1542 // members possibly not initialized isn't a valid warning +-e522 // calling 'new' without assignment isn't always an error + +-e1401 // uninitialized by constructor warning disabled. + + +// 32 bit integer and pointer size is four bytes. +-si4 -sp4 + +// Include directories +-ic:\projects\c&czero\code\watcom\h;..\vq\include;..\gcl510\h diff --git a/SUPER.CPP b/SUPER.CPP new file mode 100644 index 0000000..7d6c01b --- /dev/null +++ b/SUPER.CPP @@ -0,0 +1,386 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\super.cpv 1.5 16 Oct 1995 16:49:04 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SUPER.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 07/28/95 * + * * + * Last Update : July 29, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * SuperClass::AI -- Process the super weapon AI. * + * SuperClass::Anim_Stage -- Fetches the animation stage for this super weapon. * + * SuperClass::Discharged -- Handles discharged action for special super weapon. * + * SuperClass::Enable -- Enable this super special weapon. * + * SuperClass::Forced_Charge -- Force the super weapon to full charge state. * + * SuperClass::Impatient_Click -- Called when player clicks on unfinished super weapon. * + * SuperClass::Recharge -- Starts the special super weapon recharging. * + * SuperClass::Remove -- Removes super weapon availability. * + * SuperClass::SuperClass -- Constructor for special super weapon objects. * + * SuperClass::Suspend -- Suspend the charging of the super weapon. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * SuperClass::SuperClass -- Constructor for special super weapon objects. * + * * + * This is the constructor for the super weapons. * + * * + * INPUT: recharge -- The recharge delay time (in game frames). * + * * + * charging -- Voice to announce that the weapon is charging. * + * * + * ready -- Voice to announce that the weapon is fully charged. * + * * + * impatient -- Voice to announce current charging state. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/28/1995 JLB : Created. * + *=============================================================================================*/ +SuperClass::SuperClass(int recharge, VoxType ready, VoxType charging, VoxType impatient, VoxType suspend) +{ + IsPresent = false; + IsOneTime = false; + IsReady = false; + IsSuspended = false; + OldStage = -1; + Control = 0; + RechargeTime = recharge; + SuspendTime = 0; + VoxRecharge = ready; + VoxCharging = charging; + VoxImpatient = impatient; + VoxSuspend = suspend; +} + + +/*********************************************************************************************** + * SuperClass::Suspend -- Suspend the charging of the super weapon. * + * * + * This will temporarily put on hold the charging of the special weapon. This might be the * + * result of insufficient power. * + * * + * INPUT: on -- Should the weapon charging be suspended? Else, it will unsuspend. * + * * + * OUTPUT: Was the weapon suspend state changed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/28/1995 JLB : Created. * + *=============================================================================================*/ +bool SuperClass::Suspend(bool on) +{ + if (IsPresent && !IsReady && !IsOneTime) { + if (on != IsSuspended) { + if (on) { + SuspendTime = Control; + } else { + Control = SuspendTime; + } + IsSuspended = on; + return(true); + } + } + return(false); +} + + +/*********************************************************************************************** + * SuperClass::Enable -- Enable this super special weapon. * + * * + * This routine is called when the special weapon needs to be activated. This is used for * + * both the normal super weapons and the speicial one-time super weapons (from crates). * + * * + * INPUT: onetime -- Is this a special one time super weapon? * + * * + * player -- Is this weapon for the player? If true, then there might be a voice * + * announcement of this weapon's availability. * + * * + * quiet -- Request that the weapon start in suspended state (quiet mode). * + * * + * OUTPUT: Was the special super weapon enabled? Failure might indicate that the weapon was * + * already available. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/28/1995 JLB : Created. * + *=============================================================================================*/ +bool SuperClass::Enable(bool onetime, bool player, bool quiet) +{ + if (!IsPresent) { + IsPresent = true; + IsOneTime = onetime; + bool retval = Recharge(player && !quiet); + if (quiet) Suspend(true); + return(retval); + } + return(false); +} + + +/*********************************************************************************************** + * SuperClass::Remove -- Removes super weapon availability. * + * * + * Call this routine when the super weapon should be removed because of some outside * + * force. For one time special super weapons, they can never be removed except as the * + * result of discharging them. * + * * + * INPUT: none * + * * + * OUTPUT: Was the special weapon removed and disabled? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/28/1995 JLB : Created. * + *=============================================================================================*/ +bool SuperClass::Remove(bool forced) +{ + if (IsPresent && (!IsOneTime || forced)) { + IsReady = false; + IsPresent = false; + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * SuperClass::Recharge -- Starts the special super weapon recharging. * + * * + * This routine is called when the special weapon is allowed to recharge. Suspension will * + * be disabled and the animation process will begin. * + * * + * INPUT: player -- Is this for a player owned super weapon? If so, then a voice * + * announcement might be in order. * + * * + * OUTPUT: Was the super weapon begun charging up? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/28/1995 JLB : Created. * + *=============================================================================================*/ +bool SuperClass::Recharge(bool player) +{ + if (IsPresent && !IsReady) { + IsSuspended = false; + OldStage = -1; +#ifdef CHEAT_KEYS + if (Special.IsSpeedBuild) { + Control = 1; + } else { + Control = RechargeTime; + } +#else + Control = RechargeTime; +#endif + if (player && VoxCharging != VOX_NONE) { + Speak(VoxCharging); + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * Superclass::Discharged -- Handles discharged action for special super weapon. * + * * + * This routine should be called when the special super weapon has been discharged. The * + * weapon will either begin charging anew or will be removed entirely -- depends on the * + * one time flag for the weapon. * + * * + * INPUT: player -- Is this special weapon for the player? If so, then there might be a * + * voice announcement. * + * * + * OUTPUT: Should the sidebar be reprocessed because the special weapon has been eliminated? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/28/1995 JLB : Created. * + *=============================================================================================*/ +bool SuperClass::Discharged(bool player) +{ + if (!IsSuspended && IsPresent && IsReady) { + IsReady = false; + if (IsOneTime) { + IsOneTime = false; + return(Remove()); + } else { + Recharge(player); + } + } + return(false); +} + + +/*********************************************************************************************** + * SuperClass::AI -- Process the super weapon AI. * + * * + * This routine will process the charge up AI for this super weapon object. If the weapon * + * has advanced far enough to change any sidebar graphic that might represent it, then * + * "true" will be returned. Use this return value to intelligenly update the sidebar. * + * * + * INPUT: player -- Is this for the player? If it is and the weapon is now fully charged, * + * then this fully charged state will be announced to the player. * + * * + * OUTPUT: Was the weapon's state changed such that a sidebar graphic update will be * + * necessary? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/28/1995 JLB : Created. * + *=============================================================================================*/ +bool SuperClass::AI(bool player) +{ + if (IsPresent && !IsReady) { + if (IsSuspended) { + if (OldStage != -1) { + OldStage = -1; + return(true); + } + } else { + if (Control.Expired()) { + IsReady = true; + if (player && VoxRecharge != VOX_NONE) { + Speak(VoxRecharge); + } + return(true); + } else { + if (Anim_Stage() != OldStage) { + OldStage = Anim_Stage(); + return(true); + } + } + } + } + return(false); +} + + +/*********************************************************************************************** + * SuperClass::Anim_Stage -- Fetches the animation stage for this super weapon. * + * * + * This will return the current animation stage for this super weapon. The value will be * + * between zero (uncharged) to ANIMATION_STAGES (fully charged). Use this value to render * + * the appropriate graphic on the sidebar. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the current animation stage for this special super weapon powerup. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/28/1995 JLB : Created. * + *=============================================================================================*/ +int SuperClass::Anim_Stage(void) const +{ + if (IsPresent) { + if (IsReady) { + return(ANIMATION_STAGES); + } + int time = Control.Time(); + if (IsSuspended) { + time = SuspendTime; + } + + return(Fixed_To_Cardinal(ANIMATION_STAGES, Cardinal_To_Fixed(RechargeTime, RechargeTime-time))); + } + return(0); +} + + +/*********************************************************************************************** + * SuperClass::Impatient_Click -- Called when player clicks on unfinished super weapon. * + * * + * This routine is called when the player clicks on the super weapon icon on the sidebar * + * when the super weapon is not ready yet. This results in a voice message feedback to the * + * player. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/28/1995 JLB : Created. * + *=============================================================================================*/ +void SuperClass::Impatient_Click(void) const +{ + if (IsSuspended) { + if (VoxSuspend != VOX_NONE) { + Speak(VoxSuspend); + } + } else { + if (VoxImpatient != VOX_NONE) { + Speak(VoxImpatient); + } + } +} + + +/*********************************************************************************************** + * SuperClass::Forced_Charge -- Force the super weapon to full charge state. * + * * + * This routine will force the special weapon to full charge state. Call it when the weapon * + * needs to be instantly charged. The airstrike (when it first becomes available) is a * + * good example. * + * * + * INPUT: player -- Is this for the player? If true, then the full charge state will be * + * announced. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +void SuperClass::Forced_Charge(bool player) +{ + if (IsPresent) { + IsReady = true; + IsSuspended = false; + if (player && VoxRecharge != VOX_NONE) { + Speak(VoxRecharge); + } + } +} diff --git a/SUPER.H b/SUPER.H new file mode 100644 index 0000000..a3daaad --- /dev/null +++ b/SUPER.H @@ -0,0 +1,84 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\super.h_v 1.5 16 Oct 1995 16:47:04 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : SUPER.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 07/28/95 * + * * + * Last Update : July 28, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef SUPER_H +#define SUPER_H + +#include "ftimer.h" + +class SuperClass { + public: + SuperClass(int recharge=0, VoxType charging=VOX_NONE, VoxType ready=VOX_NONE, VoxType impatient=VOX_NONE, VoxType suspend=VOX_NONE); + + bool Suspend(bool on); + bool Enable(bool onetime = false, bool player=false, bool quiet=false); + void Forced_Charge(bool player=false); + bool AI(bool player=false); + bool Remove(bool forced=false); + void Impatient_Click(void) const; + int Anim_Stage(void) const; + bool Discharged(bool player); + bool Is_Ready(void) const {return(IsReady);}; + bool Is_Present(void) const {return(IsPresent);}; + bool Is_One_Time(void) const {return(IsOneTime && IsPresent);}; + + private: + bool Recharge(bool player=false); + + unsigned IsPresent:1; + unsigned IsOneTime:1; + unsigned IsReady:1; + unsigned IsSuspended:1; + + TCountDownTimerClass Control; + int OldStage; + int SuspendTime; + + VoxType VoxRecharge; + VoxType VoxCharging; + VoxType VoxImpatient; + VoxType VoxSuspend; + int RechargeTime; + + enum { + ANIMATION_STAGES=102 + }; +}; + + + +#endif diff --git a/SUPPORT.ASM b/SUPPORT.ASM new file mode 100644 index 0000000..cac959c --- /dev/null +++ b/SUPPORT.ASM @@ -0,0 +1,459 @@ +; +; Command & Conquer(tm) +; Copyright 2025 Electronic Arts Inc. +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; + +; $Header: F:\projects\c&c\vcs\code\support.asv 2.13 16 Oct 1995 16:52:36 JOE_BOSTIC $ +;*************************************************************************** +;** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** +;*************************************************************************** +;* * +;* Project Name : Command & Conquer * +;* * +;* File Name : SUPPORT.ASM * +;* * +;* Programmer : Joe L. Bostic * +;* * +;* Start Date : September 23, 1993 * +;* * +;* Last Update : May 10, 1994 [JLB] * +;* * +;*-------------------------------------------------------------------------* +;* Functions: * +;* strtrim -- Remove the trailing white space from a string. * +;* Fat_Put_Pixel -- Draws a fat pixel. * +;* Conquer_Build_Fading_Table -- Builds custom shadow/light fading table.* +;* Remove_From_List -- Removes a pointer from a list of pointers. * +;* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * + +IDEAL +P386 +MODEL USE32 FLAT + +INCLUDE "gbuffer.inc" + DISPLAY "Command & Conquer assembly support routines." + + CODESEG + + +;*************************************************************************** +;* strtrim -- Remove the trailing white space from a string. * +;* * +;* Use this routine to remove white space characters from the beginning * +;* and end of the string. The string is modified in place by * +;* this routine. * +;* * +;* INPUT: buffer -- Pointer to the string to modify. * +;* * +;* OUTPUT: none * +;* * +;* WARNINGS: none * +;* * +;* HISTORY: * +;* 10/07/1992 JLB : Created. * +;*=========================================================================* +; VOID cdecl strtrim(BYTE *buffer); + global C strtrim :NEAR + PROC strtrim C near + USES ax, edi, esi + + ARG buffer:DWORD ; Pointer to string to modify. + + cmp [buffer],0 + je short ??fini + + ; Prepare for string scanning by loading pointers. + cld + mov esi,[buffer] + mov edi,esi + + ; Strip white space from the start of the string. +??looper: + lodsb + cmp al,20h ; Space + je short ??looper + cmp al,9 ; TAB + je short ??looper + stosb + + ; Copy the rest of the string. +??gruntloop: + lodsb + stosb + or al,al + jnz short ??gruntloop + dec edi + ; Strip the white space from the end of the string. +??looper2: + mov [edi],al + dec edi + mov ah,[edi] + cmp ah,20h + je short ??looper2 + cmp ah,9 + je short ??looper2 + +??fini: + ret + + ENDP strtrim + + +;*************************************************************************** +;* Fat_Put_Pixel -- Draws a fat pixel. * +;* * +;* Use this routine to draw a "pixel" that is bigger than 1 pixel * +;* across. This routine is faster than drawing a similar small shape * +;* and faster than calling Fill_Rect. * +;* * +;* INPUT: x,y -- Screen coordinates to draw the pixel's upper * +;* left corner. * +;* * +;* color -- The color to render the pixel in. * +;* * +;* size -- The number of pixels width of the big "pixel". * +;* * +;* page -- The pointer to a GraphicBuffer class or something * +;* * +;* OUTPUT: none * +;* * +;* WARNINGS: none * +;* * +;* HISTORY: * +;* 03/17/1994 JLB : Created. * +;*=========================================================================* +; VOID cdecl Fat_Put_Pixel(long x, long y, long color, long size, void *page) + global C Fat_Put_Pixel:NEAR + PROC Fat_Put_Pixel C near + USES eax, ebx, ecx, edx, edi, esi + + ARG x:DWORD ; X coordinate of upper left pixel corner. + ARG y:DWORD ; Y coordinate of upper left pixel corner. + ARG color:DWORD ; Color to use for the "pixel". + ARG siz:DWORD ; Size of "pixel" to plot (square). + ARG gpage:DWORD ; graphic page address to plot onto + + cmp [siz],0 + je short ??exit + + ; Set EDI to point to start of logical page memory. + ;*=================================================================== + ; Get the viewport information and put bytes per row in ecx + ;*=================================================================== + mov ebx,[gpage] ; get a pointer to viewport + mov edi,[(GraphicViewPort ebx).GVPOffset] ; get the correct offset + + ; Verify the the Y pixel offset is legal. + mov eax,[y] + cmp eax,[(GraphicViewPort ebx).GVPHeight] ;YPIXEL_MAX + jae short ??exit + mov ecx,[(GraphicViewPort ebx).GVPWidth] + add ecx,[(GraphicViewPort ebx).GVPXAdd] + add ecx,[(GraphicViewPort ebx).GVPPitch] + mul ecx + add edi,eax + + ; Verify the the X pixel offset is legal. + + mov edx,[(GraphicViewPort ebx).GVPWidth] + cmp edx,[x] + mov edx,ecx + jbe short ??exit + add edi,[x] + + ; Write the pixel to the screen. + mov ebx,[siz] ; Copy of pixel size. + sub edx,ebx ; Modulo to reach start of next row. + mov eax,[color] +??again: + mov ecx,ebx + rep stosb + add edi,edx ; EDI points to start of next row. + dec [siz] + jnz short ??again + +??exit: + ret + + ENDP Fat_Put_Pixel + + +;*************************************************************************** +;* Conquer_Build_Fading_Table -- Builds custom shadow/light fading table. * +;* * +;* This routine is used to build a special fading table for C&C. There * +;* are certain colors that get faded to and cannot be faded again. * +;* With this rule, it is possible to draw a shadow multiple times and * +;* not have it get any lighter or darker. * +;* * +;* INPUT: palette -- Pointer to the 768 byte IBM palette to build from. * +;* * +;* dest -- Pointer to the 256 byte remap table. * +;* * +;* color -- Color index of the color to "fade to". * +;* * +;* frac -- The fraction to fade to the specified color * +;* * +;* OUTPUT: Returns with pointer to the remap table. * +;* * +;* WARNINGS: none * +;* * +;* HISTORY: * +;* 10/07/1992 JLB : Created. * +;*=========================================================================*/ +;VOID * cdecl Conquer_Build_Fading_Table(VOID *palette, VOID *dest, long color, long frac); + global C Conquer_Build_Fading_Table : NEAR + PROC Conquer_Build_Fading_Table C near + USES ebx, ecx, edi, esi + + ARG palette:DWORD + ARG dest:DWORD + ARG color:DWORD + ARG frac:DWORD + + LOCAL matchvalue:DWORD ; Last recorded match value. + LOCAL targetred:BYTE ; Target gun red. + LOCAL targetgreen:BYTE ; Target gun green. + LOCAL targetblue:BYTE ; Target gun blue. + LOCAL idealred:BYTE + LOCAL idealgreen:BYTE + LOCAL idealblue:BYTE + LOCAL matchcolor:BYTE ; Tentative match color. + +ALLOWED_COUNT EQU 16 +ALLOWED_START EQU 256-ALLOWED_COUNT + + cld + + ; If the source palette is NULL, then just return with current fading table pointer. + cmp [palette],0 + je ??fini1 + cmp [dest],0 + je ??fini1 + + ; Fractions above 255 become 255. + mov eax,[frac] + cmp eax,0100h + jb short ??ok + mov [frac],0FFh +??ok: + + ; Record the target gun values. + mov esi,[palette] + mov ebx,[color] + add esi,ebx + add esi,ebx + add esi,ebx + lodsb + mov [targetred],al + lodsb + mov [targetgreen],al + lodsb + mov [targetblue],al + + ; Main loop. + xor ebx,ebx ; Remap table index. + + ; Transparent black never gets remapped. + mov edi,[dest] + mov [edi],bl + inc edi + + ; EBX = source palette logical number (1..255). + ; EDI = running pointer into dest remap table. +??mainloop: + inc ebx + mov esi,[palette] + add esi,ebx + add esi,ebx + add esi,ebx + + mov edx,[frac] + shr edx,1 + ; new = orig - ((orig-target) * fraction); + + lodsb ; orig + mov dh,al ; preserve it for later. + sub al,[targetred] ; al = (orig-target) + imul dl ; ax = (orig-target)*fraction + shl eax,1 + sub dh,ah ; dh = orig - ((orig-target) * fraction) + mov [idealred],dh ; preserve ideal color gun value. + + lodsb ; orig + mov dh,al ; preserve it for later. + sub al,[targetgreen] ; al = (orig-target) + imul dl ; ax = (orig-target)*fraction + shl eax,1 + sub dh,ah ; dh = orig - ((orig-target) * fraction) + mov [idealgreen],dh ; preserve ideal color gun value. + + lodsb ; orig + mov dh,al ; preserve it for later. + sub al,[targetblue] ; al = (orig-target) + imul dl ; ax = (orig-target)*fraction + shl eax,1 + sub dh,ah ; dh = orig - ((orig-target) * fraction) + mov [idealblue],dh ; preserve ideal color gun value. + + ; Sweep through a limited set of existing colors to find the closest + ; matching color. + + mov eax,[color] + mov [matchcolor],al ; Default color (self). + mov [matchvalue],-1 ; Ridiculous match value init. + mov ecx,ALLOWED_COUNT + + mov esi,[palette] ; Pointer to original palette. + add esi,(ALLOWED_START)*3 + + ; BH = color index. + mov bh,ALLOWED_START +??innerloop: + + xor edx,edx ; Comparison value starts null. + + ; Build the comparison value based on the sum of the differences of the color + ; guns squared. + lodsb + sub al,[idealred] + mov ah,al + imul ah + add edx,eax + + lodsb + sub al,[idealgreen] + mov ah,al + imul ah + add edx,eax + + lodsb + sub al,[idealblue] + mov ah,al + imul ah + add edx,eax + jz short ??perfect ; If perfect match found then quit early. + + cmp edx,[matchvalue] + jae short ??notclose + mov [matchvalue],edx ; Record new possible color. + mov [matchcolor],bh +??notclose: + inc bh ; Checking color index. + loop ??innerloop + mov bh,[matchcolor] +??perfect: + mov [matchcolor],bh + xor bh,bh ; Make BX valid main index again. + + ; When the loop exits, we have found the closest match. + mov al,[matchcolor] + stosb + cmp ebx,ALLOWED_START-1 + jne ??mainloop + + ; Fill the remainder of the remap table with values + ; that will remap the color to itself. + mov ecx,ALLOWED_COUNT +??fillerloop: + inc bl + mov al,bl + stosb + loop ??fillerloop + +??fini1: + mov esi,[dest] + mov eax,esi + ret + + ENDP Conquer_Build_Fading_Table + + +;*************************************************************************** +;* Remove_From_List -- Removes a pointer from a list of pointers. * +;* * +;* This low level routine is used to remove a pointer from a list of * +;* pointers. The trailing pointers are moved downward to fill the * +;* hole. * +;* * +;* INPUT: list -- Pointer to list of pointer. * +;* * +;* index -- Pointer to length of pointer list. * +;* * +;* ptr -- The pointer value to search for and remove. * +;* * +;* OUTPUT: none * +;* * +;* WARNINGS: none * +;* * +;* HISTORY: * +;* 04/11/1994 JLB : Created. * +;* 04/22/1994 JLB : Convert to assembly language. * +;* 05/10/1994 JLB : Short pointers now. * +;*=========================================================================*/ +;VOID cdecl Remove_From_List(VOID **list, long *index, long ptr); + global C Remove_From_List:NEAR + PROC Remove_From_List C near + USES edi, esi, ecx, eax + ARG list:DWORD ; Pointer to list. + ARG index:DWORD ; Pointer to count. + ARG element:DWORD ; Element to remove. + + ; Fetch the number of elements in the list. If there are no + ; elements, then just exit quickly. + mov edi,[index] + mov ecx,[edi] + jcxz short ??fini2 + + ; Fetch pointer to list. + cmp [list],0 + je short ??fini2 + mov edi,[list] + + ; Loop through all elements searching for a match. + mov eax,[element] + repne scasd + jne short ??fini2 ; No match found. + + ; Copy all remaining elements down. If this is the + ; last element in the list then nothing needs to be + ; copied -- just decrement the list size. + jcxz short ??nocopy ; No copy necessary. + mov esi,edi + sub edi,4 + rep movsd + + ; Reduce the list count by one. +??nocopy: + mov edi,[index] + dec [DWORD PTR edi] + +??fini2: + ret + + ENDP Remove_From_List + + +; long cdecl Get_EAX(); + global C Get_EAX :NEAR + PROC Get_EAX C near + ret + + ENDP Get_EAX + +;---------------------------------------------------------------------------- + + END diff --git a/TAB.CPP b/TAB.CPP new file mode 100644 index 0000000..891c4a7 --- /dev/null +++ b/TAB.CPP @@ -0,0 +1,253 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\tab.cpv 2.18 16 Oct 1995 16:52:04 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TAB.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/15/94 * + * * + * Last Update : August 25, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * TabClass::AI -- Handles player I/O with the tab buttons. * + * TabClass::Draw_It -- Displays the tab buttons as necessary. * + * TabClass::Set_Active -- Activates a "filefolder tab" button. * + * TabClass::TabClass -- Default construct for the tab button class. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +void const * TabClass::TabShape = NULL; + + +/*********************************************************************************************** + * TabClass::TabClass -- Default construct for the tab button class. * + * * + * The default constructor merely sets the tab buttons to default non-selected state. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/15/1994 JLB : Created. * + *=============================================================================================*/ +TabClass::TabClass(void) +{ + IsToRedraw = false; +// Select = -1; +} + + +/*********************************************************************************************** + * TabClass::Draw_It -- Displays the tab buttons as necessary. * + * * + * This routine is called whenever the display is being redrawn (in some fashion). The * + * parameter can be used to force the tab buttons to redraw completely. The default action * + * is to only redraw if the tab buttons have been explicitly flagged to be redraw. The * + * result of this is the elimination of unnecessary redraws. * + * * + * INPUT: complete -- bool; Force redraw of the entire tab button graphics? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/15/1994 JLB : Created. * + * 05/19/1995 JLB : New EVA style. * + *=============================================================================================*/ +void TabClass::Draw_It(bool complete) +{ + + SidebarClass::Draw_It(complete); + + if (Debug_Map){ + //HidPage.Unlock(); + return; + } + + /* + ** Redraw the top bar imagery if flagged to do so or if the entire display needs + ** to be redrawn. + */ + int width = SeenBuff.Get_Width(); + int rightx = width - 1; + + if (complete || IsToRedraw) { + + if (LogicPage->Lock()){ + + LogicPage->Fill_Rect(0, 0, rightx, Tab_Height-2, BLACK); + CC_Draw_Shape(TabShape, 0, 0, 0, WINDOW_MAIN, SHAPE_NORMAL); + CC_Draw_Shape(TabShape, 0, width-Eva_Width, 0, WINDOW_MAIN, SHAPE_NORMAL); + Draw_Credits_Tab(); + LogicPage->Draw_Line(0, Tab_Height-1, rightx, Tab_Height-1, BLACK); + + Fancy_Text_Print(TXT_TAB_BUTTON_CONTROLS, Eva_Width/2, 0, 11, TBLACK, TPF_GREEN12_GRAD|TPF_CENTER | TPF_USE_GRAD_PAL); + Fancy_Text_Print(TXT_TAB_SIDEBAR, width-(Eva_Width/2), 0, 11, TBLACK, TPF_GREEN12_GRAD|TPF_CENTER | TPF_USE_GRAD_PAL); + } + LogicPage->Unlock(); + } + + Credits.Graphic_Logic(complete || IsToRedraw); + IsToRedraw = false; +} + + +void TabClass::Draw_Credits_Tab(void) +{ + CC_Draw_Shape(TabShape, 0, 320, 0, WINDOW_MAIN, SHAPE_NORMAL); +} + + +/*********************************************************************************************** + * TC::Hilite_Tab -- Draw a tab in its depressed state * + * * + * * + * * + * INPUT: Tab to draw (not used) * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 5/21/96 10:47AM ST : Created * + *=============================================================================================*/ + +void TabClass::Hilite_Tab(int tab) +{ + int xpos = 0; + int text = TXT_TAB_BUTTON_CONTROLS; + tab = tab; + + CC_Draw_Shape(TabShape, 1 , xpos, 0, WINDOW_MAIN, SHAPE_NORMAL); + Fancy_Text_Print(TXT_TAB_BUTTON_CONTROLS, 80, 0, 11, TBLACK, TPF_GREEN12|TPF_CENTER | TPF_USE_GRAD_PAL); +} + + +/*********************************************************************************************** + * TabClass::AI -- Handles player I/O with the tab buttons. * + * * + * This routine is called every game tick and passed whatever key the player has supplied. * + * If the input selects a tab button, then the graphic gets updated accordingly. * + * * + * INPUT: input -- The player's input character (might be mouse click). * + * * + * x,y -- Mouse coordinates at time of input. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/15/1994 JLB : Created. * + * 12/31/1994 JLB : Uses mouse coordinate parameters. * + * 05/31/1995 JLB : Fixed to handle mouse shape properly. * + * 08/25/1995 JLB : Handles new scrolling option. * + *=============================================================================================*/ +void TabClass::AI(KeyNumType &input, int x, int y) +{ + if (y >= 0 && y < Tab_Height && x < (SeenBuff.Get_Width() - 1) && x > 0) { + + bool ok = false; + int width = SeenBuff.Get_Width(); + + /* + ** If the mouse is at the top of the screen, then the tab bars only work + ** in certain areas. If the special scroll modification is not active, then + ** the tabs never work when the mouse is at the top of the screen. + */ + if (y > 0 || (Special.IsScrollMod && ((x > 3 && x < Eva_Width) || (x < width-3 && x > width-Eva_Width)))) { + ok = true; + } + + if (ok) { + if (input == KN_LMOUSE) { + int sel = -1; + if (x < Eva_Width) sel = 0; + if (x > width-Eva_Width) sel = 1; + if (sel >= 0) { + Set_Active(sel); + input = KN_NONE; + } + } + + Override_Mouse_Shape(MOUSE_NORMAL, false); + } + } + + Credits.AI(); + + SidebarClass::AI(input, x, y); +} + + +/*********************************************************************************************** + * TabClass::Set_Active -- Activates a "filefolder tab" button. * + * * + * This function is used to activate one of the file folder tab buttons that appear at the * + * top edge of the screen. * + * * + * INPUT: select -- The button to activate. 0 = left button, 1=next button, etc. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/15/1994 JLB : Created. * + *=============================================================================================*/ + void TabClass::Set_Active(int select) +{ + switch (select) { + case 0: + Queue_Options(); + break; + + case 1: + Map.SidebarClass::Activate(-1); + break; + + default: + break; + } +} + +void TabClass::One_Time(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + Eva_Width = 80 * factor; + Tab_Height = 8 * factor; + + SidebarClass::One_Time(); + TabShape = Hires_Retrieve("TABS.SHP"); +} diff --git a/TAB.H b/TAB.H new file mode 100644 index 0000000..158bb9a --- /dev/null +++ b/TAB.H @@ -0,0 +1,82 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\tab.h_v 2.18 16 Oct 1995 16:45:26 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TAB.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/15/94 * + * * + * Last Update : December 15, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef TAB_H +#define TAB_H + +#include "sidebar.h" +#include "credits.h" + +class TabClass: public SidebarClass +{ + public: + TabClass(void); + + virtual void AI(KeyNumType &input, int x, int y); + virtual void Draw_It(bool complete=false); + + virtual void One_Time(void); // One-time inits + static void Draw_Credits_Tab(void); + static void Hilite_Tab(int tab); + void Redraw_Tab(void) {IsToRedraw = true;Flag_To_Redraw(false);}; + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + inline int Get_Tab_Height(void) { return(Tab_Height); }; + + CreditClass Credits; + + protected: + + /* + ** If the tab graphic is to be redrawn, then this flag is true. + */ + unsigned IsToRedraw:1; + int Eva_Width; + int Tab_Height; + + private: + void Set_Active(int select); + + static void const * TabShape; +}; + + +#endif diff --git a/TARCOM.CPP b/TARCOM.CPP new file mode 100644 index 0000000..4c667fd --- /dev/null +++ b/TARCOM.CPP @@ -0,0 +1,183 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\tarcom.cpv 2.17 16 Oct 1995 16:52:20 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TARCOM.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 16, 1994 * + * * + * Last Update : July 19, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * TarComClass::Debug_Dump -- Displays the status of the tarcom class to the mono screen. * + * TarComClass::AI -- Handles the logical AI for the tarcom class. * + * TarComClass::~TarComClass -- Destructor for turret object. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * TarComClass::~TarComClass -- Destructor for turret object. * + * * + * This is the destructor for turret objects. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1995 JLB : Created. * + *=============================================================================================*/ +TarComClass::~TarComClass(void) +{ +} + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * TarComClass::Debug_Dump -- Displays the status of the tarcom class to the mono screen. * + * * + * This routine is used to display the tarcom class status to the monochrome monitor. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/02/1994 JLB : Created. * + *=============================================================================================*/ +void TarComClass::Debug_Dump(MonoClass *mono) const +{ + TurretClass::Debug_Dump(mono); +} +#endif + + +/*********************************************************************************************** + * TarComClass::AI -- Handles the logical AI for the tarcom class. * + * * + * This handles the AI logic for the targeting computer. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/02/1994 JLB : Created. * + *=============================================================================================*/ +void TarComClass::AI(void) +{ + TurretClass::AI(); + + if (Class->Primary != WEAPON_NONE) { + + /* + ** Determine which weapon can fire. First check for the primary weapon. If that weapon + ** cannot fire, then check any secondary weapon. If neither weapon can fire, then the + ** failure code returned is that from the primary weapon. + */ + WeaponTypeClass const * weapon = &Weapons[Class->Primary]; + int primary = 0; + FireErrorType ok = Can_Fire(TarCom, 0); + if (ok != FIRE_OK) { + if (Can_Fire(TarCom, 1) == FIRE_OK) { + ok = FIRE_OK; + primary = 1; + weapon = &Weapons[Class->Secondary]; + } + } + + switch (ok) { + case FIRE_OK: + if (What_Am_I() != RTTI_UNIT) { + IsFiring = false; + } else { + if (!((UnitClass *)this)->Class->IsFireAnim) { + IsFiring = false; + } + } + + if (TurretClass::Fire_At(TarCom, primary)) { + Sound_Effect(weapon->Sound, Coord); + } + break; + + case FIRE_FACING: + if (Class->IsLockTurret) { + if (!Target_Legal(NavCom) && !IsDriving) { + PrimaryFacing.Set_Desired(Direction(TarCom)); + SecondaryFacing.Set_Desired(PrimaryFacing.Desired()); + } + } else { + if (*this == UNIT_FTANK) { + SecondaryFacing.Set_Desired(Facing_Dir(Dir_Facing(Direction(TarCom)))); + } else { + SecondaryFacing.Set_Desired(Direction(TarCom)); + } +// SecondaryFacing.Set_Desired(Direction256(Center_Coord(), As_Coord(TarCom))); + } + break; + + case FIRE_CLOAKED: + IsFiring = false; + Do_Uncloak(); + break; + } + } + + if (Target_Legal(TarCom) && !IsRotating) { + DirType dir = Direction(TarCom); + + if (Class->IsTurretEquipped) { + SecondaryFacing.Set_Desired(dir); + } else { + + /* + ** Non turret equipped vehicles will rotate their body to face the target only + ** if the vehicle isn't currently moving or facing the correct direction. This + ** applies only to tracked vehicles. Wheeled vehicles never rotate to face the + ** target, since they aren't maneuverable enough. + */ + if ((Class->Speed == SPEED_TRACK || *this == UNIT_BIKE) && !Target_Legal(NavCom) && !IsDriving && PrimaryFacing.Difference(dir)) { + if (*this == UNIT_FTANK) { + PrimaryFacing.Set_Desired(Facing_Dir(Dir_Facing(dir))); + } else { + PrimaryFacing.Set_Desired(dir); + } + } + } + } +} + + diff --git a/TARCOM.H b/TARCOM.H new file mode 100644 index 0000000..4649034 --- /dev/null +++ b/TARCOM.H @@ -0,0 +1,78 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\tarcom.h_v 2.16 16 Oct 1995 16:45:54 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TARCOM.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 23, 1994 * + * * + * Last Update : April 23, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef TARCOM_H +#define TARCOM_H + +#include "turret.h" +#include "bullet.h" + +/**************************************************************************** +** Units that can perform combat are handled by this class. It performs +** such operations as determining threat value down to actually launching the +** projectile. +*/ +class TarComClass : public TurretClass +{ + public: + /*--------------------------------------------------------------------- + ** Constructors, Destructors, and overloaded operators. + */ + TarComClass(void) {}; + TarComClass(UnitType classid, HousesType house) : TurretClass(classid, house) {}; + virtual ~TarComClass(void); + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + #ifdef CHEAT_KEYS + virtual void Debug_Dump(MonoClass *mono) const; + #endif + virtual void AI(void); +// virtual bool Target_Something_Nearby(ThreatType rangmatters=THREAT_NORMAL); + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + +}; + +#endif + + diff --git a/TARGET.CPP b/TARGET.CPP new file mode 100644 index 0000000..7938350 --- /dev/null +++ b/TARGET.CPP @@ -0,0 +1,502 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\target.cpv 2.17 16 Oct 1995 16:51:06 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TARGET.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : August 27, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * As_Aircraft -- Converts the target value into an aircraft pointer. * + * As_Animation -- Converts target value into animation pointer. * + * As_Building -- Converts a target value into a building object pointer. * + * As_Bullet -- Converts the target into a bullet pointer. * + * As_Cell -- Converts a target value into a cell number. * + * As_Coord -- Converts a target value into a coordinate value. * + * As_Infantry -- If the target is infantry, return a pointer to it. * + * As_Movement_Coord -- Fetches coordinate if trying to move to this target. * + * As_Object -- Converts a target value into an object pointer. * + * As_Team -- Converts a target number into a team pointer. * + * As_TeamType -- Converts a target into a team type pointer. * + * As_Techno -- Converts a target value into a TechnoClass pointer. * + * As_Trigger -- Converts specified target into a trigger pointer. * + * As_Unit -- Converts a target value into a unit pointer. * + * Target_Legal -- Determines if the specified target is legal. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "target.h" + + +/*********************************************************************************************** + * As_Trigger -- Converts specified target into a trigger pointer. * + * * + * This routine will convert the specified target number into a trigger pointer. * + * * + * INPUT: target -- The target number to convert. * + * * + * OUTPUT: Returns with the trigger pointer that the specified target number represents. If * + * it doesn't represent a legal trigger object, then NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +TriggerClass * As_Trigger(TARGET target) +{ + return(Is_Target_Trigger(target) ? Triggers.Raw_Ptr(Target_Value(target)) : NULL); +} + + +/*********************************************************************************************** + * As_Team -- Converts a target number into a team pointer. * + * * + * This routine will convert the specified target number into a team pointer. * + * * + * INPUT: target -- The target number to convert. * + * * + * OUTPUT: Returns with the team object that the specified target number represents. If it * + * doesn't represent a legal team then NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +TeamClass * As_Team(TARGET target) +{ + return(Is_Target_Team(target) ? Teams.Raw_Ptr(Target_Value(target)) : NULL); +} + + +/*********************************************************************************************** + * As_TeamType -- Converts a target into a team type pointer. * + * * + * This routine will convert the specified target number into a team type pointer. * + * * + * INPUT: target -- The target number to convert. * + * * + * OUTPUT: Returns with a pointer to the team type represented by the target number. If the * + * target number doesn't represent a legal team type, then NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +TeamTypeClass * As_TeamType(TARGET target) +{ + return(Is_Target_TeamType(target) ? TeamTypes.Raw_Ptr(Target_Value(target)) : NULL); +} + + +/*********************************************************************************************** + * As_Animation -- Converts target value into animation pointer. * + * * + * This routine will convert the specified target number into an animation pointer. * + * * + * INPUT: target -- The target number to convert into an animation pointer. * + * * + * OUTPUT: Returns with a pointer to the legal animation that this target represents. If it * + * doesn't represent a legal animation, then NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +AnimClass * As_Animation(TARGET target) +{ + return(Is_Target_Animation(target) ? Anims.Raw_Ptr(Target_Value(target)) : NULL); +} + + +/*********************************************************************************************** + * As_Bullet -- Converts the target into a bullet pointer. * + * * + * This routine will convert the specified target number into a bullet pointer. * + * * + * INPUT: target -- The target number to convert. * + * * + * OUTPUT: Returns with a pointer to the bullet it specifies. If the target doesn't refer to * + * a legal bullet, then NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +BulletClass * As_Bullet(TARGET target) +{ + return(Is_Target_Bullet(target) ? Bullets.Raw_Ptr(Target_Value(target)) : NULL); +} + + +/*********************************************************************************************** + * As_Aircraft -- Converts the target value into an aircraft pointer. * + * * + * This routine will convert the specified target value into an aircraft object pointer. * + * * + * INPUT: target -- The target value to convert. * + * * + * OUTPUT: Returns with a pointer to the aircraft that this target value represents. If the * + * specified target value doesn't represent an aircraft, then NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/27/1995 JLB : Created. * + *=============================================================================================*/ +AircraftClass * As_Aircraft(TARGET target) +{ + return(Is_Target_Aircraft(target) ? Aircraft.Raw_Ptr(Target_Value(target)) : NULL); +} + + +/*********************************************************************************************** + * As_Techno -- Converts a target value into a TechnoClass pointer. * + * * + * This routine will take the target value specified and convert it into a TechnoClass * + * pointer if the target represents an object that has a TechnoClass. * + * * + * INPUT: target -- The target value to convert into a TechnoClass pointer. * + * * + * OUTPUT: Returns with a pointer to the associated object's TechnoClass. If the target * + * cannot be converted into a TechnoClass pointer, then NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/02/1994 JLB : Created. * + *=============================================================================================*/ +TechnoClass * As_Techno(TARGET target) +{ + ObjectClass * obj = As_Object(target); + + if (obj && obj->Is_Techno()) { + return(TechnoClass *)obj; + } + return(NULL); +} + + +/*********************************************************************************************** + * As_Object -- Converts a target value into an object pointer. * + * * + * This routine is used to convert the target value specified into an object pointer. If * + * the target doesn't represent an object or the target value is illegal, then NULL is * + * returned. * + * * + * INPUT: target -- The target value to convert from. * + * * + * OUTPUT: Returns with a pointer to the object it represent, or NULL if not an object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/27/1994 JLB : Created. * + *=============================================================================================*/ +ObjectClass * As_Object(TARGET target) +{ + int val = Target_Value(target); + ObjectClass * object = NULL; + + switch (Target_Kind(target)) { + case KIND_INFANTRY: + object = Infantry.Raw_Ptr(val); + break; + + case KIND_UNIT: + object = Units.Raw_Ptr(val); + break; + + case KIND_BUILDING: + object = Buildings.Raw_Ptr(val); + break; + + case KIND_AIRCRAFT: + object = Aircraft.Raw_Ptr(val); + break; + + case KIND_TERRAIN: + object = Terrains.Raw_Ptr(val); + break; + + case KIND_BULLET: + object = Bullets.Raw_Ptr(val); + break; + + case KIND_ANIMATION: + object = Anims.Raw_Ptr(val); + break; + + default: + break; + } +#if (0) + /* + ** Special check to ensure that a target value that references an + ** invalid object will not be converted back into an object pointer. + ** This condition is rare, but could occur in a network game if the + ** object it refers to is destroyed between the time an event message + ** is sent and when it is received. + */ + if (object != NULL && !object->IsActive) { + object = NULL; + } +#endif //(0) + + return(object); +} + + +/*********************************************************************************************** + * As_Unit -- Converts a target value into a unit pointer. * + * * + * This routine is used to convert the target value specified into a pointer to a unit * + * object. * + * * + * INPUT: target -- The target value to convert into a unit pointer. * + * * + * OUTPUT: Returns with a pointer to the unit the target value represents or NULL if not * + * a unit. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/27/1994 JLB : Created. * + *=============================================================================================*/ +UnitClass * As_Unit(TARGET target) +{ + return(Is_Target_Unit(target) ? Units.Raw_Ptr(Target_Value(target)) : NULL); +} + + +/*********************************************************************************************** + * As_Infantry -- If the target is infantry, return a pointer to it. * + * * + * This routine will translate the specified target value into an infantry pointer if the * + * target actually represents an infantry object. * + * * + * INPUT: target -- The target to convert to a pointer. * + * * + * OUTPUT: Returns a pointer to the infantry object that this target value represents. If * + * the target doesn't represent an infantry object, then return NULL. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +InfantryClass * As_Infantry(TARGET target) +{ + return(Is_Target_Infantry(target) ? Infantry.Raw_Ptr(Target_Value(target)) : NULL); +} + + +#ifdef NEVER +TerrainClass * As_Terrain(TARGET target) +{ + return(Is_Target_Terrain(target) ? &Terrains[Target_Value(target)] : NULL); +} +#endif + + +/*********************************************************************************************** + * As_Building -- Converts a target value into a building object pointer. * + * * + * This routine is used to convert the target value specified into a building pointer. * + * * + * INPUT: target -- The target value to convert from. * + * * + * OUTPUT: Returns with a pointer to the building object that the target value represents. * + * If it doesn't represent a building, then return NULL. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/27/1994 JLB : Created. * + *=============================================================================================*/ +BuildingClass * As_Building(TARGET target) +{ + return(Is_Target_Building(target) ? Buildings.Raw_Ptr(Target_Value(target)) : NULL); +} + + +#ifdef NEVER +/*********************************************************************************************** + * Target_Legal -- Determines if the specified target is legal. * + * * + * This routine is used to check for the legality of the target value specified. It is * + * necessary to call this routine if there is doubt about the the legality of the target. * + * It is possible for the unit that a target value represents to be eliminated and thus * + * rendering the target value invalid. * + * * + * INPUT: target -- The target value to check. * + * * + * OUTPUT: bool; Is the target value legal? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/27/1994 JLB : Created. * + *=============================================================================================*/ +bool Target_Legal(TARGET target) +{ + if (!Target_Legal(target)) return(false); + + ObjectClass * obj = As_Object(target); + if (obj) { + return(obj->Get_Strength() > 0 && obj->IsActive && !obj->IsInLimbo && obj->Class_Of().IsLegalTarget); + } + return(true); +} +#endif + + +/*********************************************************************************************** + * As_Cell -- Converts a target value into a cell number. * + * * + * This routine is used to convert the target value specified, into a cell value. This is * + * necessary for find path and other procedures that need a cell value. * + * * + * INPUT: target -- The target value to convert to a cell value. * + * * + * OUTPUT: Returns with the target value expressed as a cell location. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/27/1994 JLB : Created. * + *=============================================================================================*/ +CELL As_Cell(TARGET target) +{ + return(Coord_Cell(As_Coord(target))); +} + + +/*********************************************************************************************** + * As_Coord -- Converts a target value into a coordinate value. * + * * + * This routine is used to convert the target value specified into a coordinate value. It * + * is necessary for those procedures that require a coordinate value. * + * * + * INPUT: target -- The target value to convert. * + * * + * OUTPUT: Returns with the target expressed as a COORD value. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/27/1994 JLB : Created. * + * 11/16/1994 JLB : Simplified. * + *=============================================================================================*/ +COORDINATE As_Coord(TARGET target) +{ + if (Target_Legal(target)) { + /* + ** Cell target values are handled as a special case. The value of the target number is + ** actually the cell index number. + */ + if (Is_Target_Cell(target)) { + return(Cell_Coord((CELL)Target_Value(target))); + } + + /* + ** Normal targets correspond to game objects. Fetch the object pointer and then ask it + ** for the center coordinate. Return the center coordinate as the target's coordinate. + */ + ObjectClass * obj = As_Object(target); + if (obj) { + + /* + ** If this is invalid memory or the object is dead then return 0 + ** This is a kludge to fix the problem of team target objects being assigned after + ** the object is already destroyed - 1/15/97 3:13PM + */ + if (IsBadReadPtr ((void*)obj, sizeof (ObjectClass) ) || !obj->IsActive){ +//OutputDebugString ("C&C95 - As_Coord called for invalid target object\m"); + return(0x00000000L); + } + + return(obj->Target_Coord()); + } + } + + /* + ** An unrecognized target value results in a null coordinate value. + */ + return(0x00000000L); +} + + +/*********************************************************************************************** + * As_Movement_Coord -- Fetches coordinate if trying to move to this target. * + * * + * This routine will convert the specified target into a coordinate location. This location * + * is used when moving to the target specified. For cells, this is the center of the cell. * + * For special buildings that allow docking, it is the center location of the docking * + * bay. * + * * + * INPUT: target -- The target to convert into a coordinate value. * + * * + * OUTPUT: Returns with the docking coordinate of the target value specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/27/1995 JLB : Created. * + *=============================================================================================*/ +COORDINATE As_Movement_Coord(TARGET target) +{ + if (Target_Legal(target)) { + /* + ** Cell target values are handled as a special case. The value of the target number is + ** actually the cell index number. + */ + if (Is_Target_Cell(target)) { + return(Cell_Coord((CELL)Target_Value(target))); + } + + /* + ** Normal targets correspond to game objects. Fetch the object pointer and then ask it + ** for the center coordinate. Return the center coordinate as the target's coordinate. + */ + ObjectClass * obj = As_Object(target); + if (obj) { + return(obj->Docking_Coord()); + } + } + + /* + ** An unrecognized target value results in a null coordinate value. + */ + return(0x00000000L); +} diff --git a/TARGET.H b/TARGET.H new file mode 100644 index 0000000..00f4a38 --- /dev/null +++ b/TARGET.H @@ -0,0 +1,157 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\target.h_v 2.16 16 Oct 1995 16:45:04 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TARGET.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 25, 1994 * + * * + * Last Update : April 25, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef TARGET_H +#define TARGET_H + +/************************************************************************** +** When a unit proceeds with carrying out its mission, it can have several +** intermediate goals. Each goal (or target if you will) can be one of the +** following kinds. +*/ +typedef enum KindType { + KIND_NONE, + KIND_CELL, + KIND_UNIT, + KIND_INFANTRY, + KIND_BUILDING, + KIND_TERRAIN, + KIND_AIRCRAFT, + KIND_TEMPLATE, + KIND_BULLET, + KIND_ANIMATION, + KIND_TRIGGER, + KIND_TEAM, + KIND_TEAMTYPE +} KindType; + + +#define TARGET_MANTISSA 12 // Bits of value precision. +#define TARGET_MANTISSA_MASK (~((~0)<>TARGET_EXPONENT)) + +inline KindType Target_Kind(TARGET a){return (KindType)(((unsigned)a)>>TARGET_MANTISSA);} +inline unsigned Target_Value(TARGET a){return (((unsigned)a) & TARGET_MANTISSA_MASK);} + +inline bool Is_Target_Team(TARGET a) {return (Target_Kind(a) == KIND_TEAM);} +inline bool Is_Target_TeamType(TARGET a) {return (Target_Kind(a) == KIND_TEAMTYPE);} +inline bool Is_Target_Trigger(TARGET a) {return (Target_Kind(a) == KIND_TRIGGER);} +inline bool Is_Target_Infantry(TARGET a) {return (Target_Kind(a) == KIND_INFANTRY);} +inline bool Is_Target_Bullet(TARGET a){return (Target_Kind(a) == KIND_BULLET);} +inline bool Is_Target_Terrain(TARGET a){return (Target_Kind(a) == KIND_TERRAIN);} +inline bool Is_Target_Cell(TARGET a){return (Target_Kind(a) == KIND_CELL);} +inline bool Is_Target_Unit(TARGET a){return (Target_Kind(a) == KIND_UNIT);} +inline bool Is_Target_Building(TARGET a){return (Target_Kind(a) == KIND_BUILDING);} +inline bool Is_Target_Template(TARGET a){return (Target_Kind(a) == KIND_TEMPLATE);} +inline bool Is_Target_Aircraft(TARGET a){return (Target_Kind(a) == KIND_AIRCRAFT);} +inline bool Is_Target_Animation(TARGET a) {return (Target_Kind(a) == KIND_ANIMATION);} + +inline TARGET Build_Target(KindType kind, int value) {return (TARGET)((((unsigned)kind) << TARGET_MANTISSA) | (unsigned)value);} +inline TARGET As_Target(CELL cell){return (TARGET)(((unsigned)KIND_CELL << TARGET_MANTISSA) | cell);} + +class UnitClass; +class BuildingClass; +class TechnoClass; +class TerrainClass; +class ObjectClass; +class InfantryClass; +class BulletClass; +class TriggerClass; +class TeamClass; +class TeamTypeClass; +class AnimClass; +class AircraftClass; + +#ifdef NEVER +class TargetClass +{ + public: + TargetClass(void) {Target.Raw = 0;}; + + /* + ** This handles assignment from an integer and conversion + ** to an integer. + */ + inline TargetClass(int val, KindType kind) {Target.Component.Value=val; Target.Component.Kind=kind;}; + inline TargetClass(int val) {Target.Raw = val;}; + inline operator int () {return Target.Raw;}; + //inline TargetClass & operator = (const int &val) {*((int*)this)=val; return *this;}; + + inline bool Is_Filled(void) {return Target.Component.Kind != KIND_NONE;}; + inline void Invalidate(void) {Target.Component.Kind = 0;}; + inline bool Is_Cell(void) {return Target.Component.Kind == KIND_CELL;}; + inline bool Is_Unit(void) {return Target.Component.Kind == KIND_UNIT;}; + inline bool Is_Building(void) {return Target.Component.Kind == KIND_BUILDING;}; + inline bool Is_Aircraft(void) {return Target.Component.Kind == KIND_AIRCRAFT;}; + inline int As_Value(void) {return Target.Component.Value;}; + inline KindType As_Kind(void) {return Target.Component.Kind;}; + + // Allows comparing one target to another (for equality). + inline bool operator == (TargetClass t1) { + return (Target.Raw == t1.Target.Raw); + }; + + UnitClass * As_Unit(void); + BuildingClass * As_Building(void); + bool Legal(void); + CELL As_Cell(void); + COORDINATE As_Coord(void); + int Distance(TechnoClass *base); + static int As_Target(UnitClass *unit); + static int As_Target(BuildingClass *building); + static int As_Target(CELL cell); + + private: + + /* + ** This is the special encoded target value. + */ + union { + struct { + unsigned Value:TARGET_MANTISSA; + KindType Kind:TARGET_EXPONENT; + } Component; + int Raw; + } Target; + + + static int Build(int value, KindType kind); +}; +#endif + +#endif diff --git a/TCPIP.CPP b/TCPIP.CPP new file mode 100644 index 0000000..cc19bbb --- /dev/null +++ b/TCPIP.CPP @@ -0,0 +1,1004 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TCPIP.CPP * + * * + * Programmer : Steve Tall * + * * + * Start Date : March 11th, 1996 * + * * + * Last Update : March 20th, 1996 [ST] * + * * + *-------------------------------------------------------------------------* + * Overview: * + * * + * Member functions of the TcpipManagerClass which provides the Winsock * + * interface for C&C * + * * + * * + *-------------------------------------------------------------------------* + * Functions: * + * * + * TMC::TcpipManagerClass -- constructor for the TcpipManagerClass * + * TMC::~TcpipManagerClass -- destructor for the TcpipManagerClass * + * TMC::Close -- restores any currently in use Winsock resources * + * TMC::Init -- Initialised Winsock for use. * + * TMC::Start_Server -- Initialise connection and start listening. * + * TMC::Read -- read any pending input from the stream socket * + * TMC::Write -- Send data via the Winsock streaming socket * + * TMC::Add_Client -- A client has requested to connect. * + * TMC::Message_Handler -- Message handler for Winsock. * + * TMC::Set_Host_Address -- Set the address of the host * + * TMC::Start_Client -- Start trying to connect to a game host * + * TMC::Close_Socket -- Close an opened Winsock socket. * + * TMC::Copy_To_In_Buffer -- copy data from our winsock buffer * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + +#include "function.h" +#include "tcpip.h" + +#ifdef FORCE_WINSOCK + + +/* +** Nasty globals +*/ +BOOL Server; //Is this player acting as client or server +TcpipManagerClass Winsock; //The object for interfacing with Winsock + + + +/*********************************************************************************************** + * TMC::TcpipManagerClass -- constructor for the TcpipManagerClass * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 3/20/96 2:51PM ST : Created * + *=============================================================================================*/ +TcpipManagerClass::TcpipManagerClass(void) +{ + WinsockInitialised = FALSE; + Connected = FALSE; + UseUDP = TRUE; + SocketReceiveBuffer = 4096; + SocketSendBuffer = 4096; + +} + + +/*********************************************************************************************** + * TMC::~TcpipManagerClass -- destructor for the TcpipManagerClass * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 3/20/96 2:52PM ST : Created * + *=============================================================================================*/ + +TcpipManagerClass::~TcpipManagerClass(void) +{ + Close(); +} + + +/*********************************************************************************************** + * TMC::Close -- restores any currently in use Winsock resources * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 3/20/96 2:52PM ST : Created * + *=============================================================================================*/ + +void TcpipManagerClass::Close(void) +{ + /* + ** If we never initialised the class in the first place then just return + */ + if (!WinsockInitialised) return; + + /* + ** Cancel any outstaning asyncronous events + */ + if (Async){ + WSACancelAsyncRequest(Async); + } + + /* + ** Close any open sockets + */ + if (ConnectSocket != INVALID_SOCKET){ + Close_Socket(ConnectSocket); + ConnectSocket = INVALID_SOCKET; + } + + if (ListenSocket != INVALID_SOCKET){ + Close_Socket(ListenSocket); + ListenSocket = INVALID_SOCKET; + } + + if (UDPSocket != INVALID_SOCKET){ + Close_Socket(ListenSocket); + UDPSocket = INVALID_SOCKET; + } + + /* + ** Call the Winsock cleanup function to say we are finished using Winsock + */ + WSACleanup(); + + WinsockInitialised = FALSE; + Connected = FALSE; +} + + + +/*********************************************************************************************** + * TMC::Init -- Initialised Winsock for use. * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: TRUE if Winsock is available and was initialised * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 3/20/96 2:54PM ST : Created * + *=============================================================================================*/ + +BOOL TcpipManagerClass::Init(void) +{ + short version; + int rc; + + /* + ** Just return true if we are already set up + */ + if (WinsockInitialised) return (TRUE); + + /* + ** Initialise sockets to null + */ + ListenSocket = INVALID_SOCKET; + ConnectSocket =INVALID_SOCKET; + UDPSocket = INVALID_SOCKET; + + /* + ** Start WinSock, and fill in our WinSockData + */ + version = (WINSOCK_MINOR_VER << 8) | WINSOCK_MAJOR_VER; + rc = WSAStartup(version, &WinsockInfo); + if (rc != 0) { + return (FALSE); + } + + /* + ** Check the Winsock version number + */ + if ((WinsockInfo.wVersion & 0x00ff) != (version & 0x00ff) || + (WinsockInfo.wVersion >> 8) != (version >> 8)) { + return (FALSE); + } + + /* + ** Everything is OK so return success + */ + WinsockInitialised = TRUE; + return (TRUE); + +} + + + + +/*********************************************************************************************** + * TMC::Start_Server -- initialise out connection as the server. Start listening for clients. * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 3/20/96 2:56PM ST : Created * + *=============================================================================================*/ + +void TcpipManagerClass::Start_Server(void) +{ + int i; + //struct sockaddr_in addr; + + Start_Client(); + + /* + ** Set up the incoming and outgoing data buffers head and tail pointers + */ + //InBufferHead = 0; + //InBufferTail = 0; + //OutBufferHead= 0; + //OutBufferTail= 0; + + TXBufferHead = 0; + TXBufferTail = 0; + RXBufferHead = 0; + RXBufferTail = 0; + + for (i=0 ; ih_name); + } + Async = 0; + return; + + }else{ + /* + ** We are the client + */ + ConnectStatus = CONTACTING_SERVER; + if (WSAGETASYNCERROR(lParam)==0) { + hentry = (struct hostent *)&HostBuff[0]; + strcpy (Server.Name, hentry->h_name); + } + else { + Server.Name[0] = 0; + } + Async = 0; + return; + } + + + /* + ** Retrieve host by name: Start connecting now that we have the + ** address. + */ + case WM_HOSTBYNAME: + if (WSAGETASYNCERROR(lParam)==0) { + hentry = (struct hostent *)&HostBuff[0]; + memcpy (&(Server.Addr.s_addr), hentry->h_addr, 4); + memcpy(&UDPIPAddress, hentry->h_addr, 4); + strcpy (Server.DotAddr, inet_ntoa(Server.Addr)); + ConnectStatus = CONNECTED_OK; + Connected = TRUE; + } + else { + Server.Name[0] = 0; + strcpy (Server.DotAddr, "????"); + ConnectStatus = SERVER_ADDRESS_LOOKUP_FAILED; + } + Async = 0; + return; + + + /* + ** Connection is ready - accept the client + */ + case WM_ACCEPT: + rc = WSAGETSELECTERROR(lParam); + if (rc != 0) { + ConnectStatus = UNABLE_TO_ACCEPT_CLIENT; + return; + } + if (Add_Client()) { + ConnectStatus = CONNECTED_OK; + Connected = TRUE; + } + else { + ConnectStatus = UNABLE_TO_ACCEPT_CLIENT; + } + return; + + + + /* + ** Handle UDP packet events + */ + case WM_UDPASYNCEVENT: + event = WSAGETSELECTEVENT(lParam); + switch (event) { + + case FD_READ: + rc = WSAGETSELECTERROR(lParam); + if (rc != 0) { + Clear_Socket_Error(UDPSocket); + return; + } + addr_len = sizeof(addr); + rc = recvfrom(UDPSocket, ReceiveBuffer, WS_RECEIVE_BUFFER_LEN, 0, + (LPSOCKADDR)&addr, &addr_len); + if (rc == SOCKET_ERROR) { + Clear_Socket_Error(UDPSocket); + return; + } + memcpy(&UDPIPAddress, &addr.sin_addr.s_addr, 4); + Copy_To_In_Buffer(rc); + return; + + + case FD_WRITE: + if (UseUDP){ + rc = WSAGETSELECTERROR(lParam); + if (rc != 0) { + Clear_Socket_Error(UDPSocket); + return; + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(PlanetWestwoodPortNumber); + memcpy (&addr.sin_addr.s_addr, &UDPIPAddress, 4); + + /* + ** Send as many bytes as there are in the buffer; if there's + ** an error, just bail out. If we get a WOULDBLOCK error, + ** WinSock will send us another message when the socket is + ** available for another write. + */ + while (TransmitBuffers[TXBufferTail].InUse){ + rc = sendto(UDPSocket, + TransmitBuffers[TXBufferTail].Buffer, + TransmitBuffers[TXBufferTail].DataLength, + 0, + (LPSOCKADDR)&addr, + sizeof (addr)); + + if (rc == SOCKET_ERROR){ + if (WSAGetLastError() != WSAEWOULDBLOCK) { + Clear_Socket_Error(UDPSocket); + } + break; + } + TransmitBuffers[TXBufferTail++].InUse = false; + TXBufferTail &= WS_NUM_TX_BUFFERS-1; + } + return; + } + } + + + + /* + ** Handle the asynchronous event callbacks + */ + case WM_ASYNCEVENT: + event = WSAGETSELECTEVENT(lParam); + switch (event) { + /* + ** FD_CLOSE: the client has gone away. Remove the client from our system. + */ + case FD_CLOSE: + rc = WSAGETSELECTERROR(lParam); + if (rc != 0 && rc != WSAECONNRESET) { + ConnectStatus = CONNECTION_LOST; + return; + } + if (Async != 0) { + WSACancelAsyncRequest(Async); + } + WSAAsyncSelect (ConnectSocket, MainWindow, WM_ASYNCEVENT, 0); + Close_Socket (ConnectSocket); + ConnectSocket = INVALID_SOCKET; + //Connected = FALSE; + ConnectStatus = CONNECTION_LOST; + break; +#if (0) + /* + ** FD_READ: Data is available on our socket. This message is sent every time + ** data becomes available so we only have to do one read + */ + case FD_READ: + if (!UseUDP){ + rc = WSAGETSELECTERROR(lParam); + if (rc != 0) { + return; + } + rc = recv(ConnectSocket, ReceiveBuffer, WS_RECEIVE_BUFFER_LEN, 0); + if (rc == SOCKET_ERROR) { + Clear_Socket_Error(ConnectSocket); + return; + } + Copy_To_In_Buffer(rc); + return; + } + + /* + ** FD_WRITE: The socket is available for writing. We may actually have sent this + ** message from elewhere in the class. + */ + case FD_WRITE: + rc = WSAGETSELECTERROR(lParam); + if (rc != 0) { + return; + } + /* + ** Send as many bytes as there are in the buffer; if there's + ** an error, just bail out. If we get a WOULDBLOCK error, + ** WinSock will send us another message when the socket is + ** available for another write. + */ + while (OutBufferHead > OutBufferTail){ + rc = send(ConnectSocket, OutBuffer + OutBufferTail, + OutBufferHead - OutBufferTail, 0); + if (rc == SOCKET_ERROR){ + if (WSAGetLastError() != WSAEWOULDBLOCK) { + Clear_Socket_Error(ConnectSocket); + } + break; + } + OutBufferTail+=rc; + } + if (OutBufferHead == OutBufferTail){ + OutBufferHead = OutBufferTail = 0; + } + return; +#endif //(0) + /* + ** FD_CONNECT: A connection was made, or an error occurred. + */ + case FD_CONNECT: + rc = WSAGETSELECTERROR(lParam); + if (rc != 0) { + ConnectStatus = UNABLE_TO_CONNECT; + return; + } + + ConnectStatus = CONNECTED_OK; + Connected = TRUE; + return; + + } + } +} + + + +/*********************************************************************************************** + * TMC::Copy_To_In_Buffer -- copy data from our winsock buffer to our internal buffer * + * * + * * + * * + * INPUT: bytes to copy * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 3/20/96 3:17PM ST : Created * + *=============================================================================================*/ +void TcpipManagerClass::Copy_To_In_Buffer(int bytes) +{ + if (!ReceiveBuffers[RXBufferHead].InUse){ + memcpy (ReceiveBuffers[RXBufferHead].Buffer, ReceiveBuffer, MIN(bytes, WS_INTERNET_BUFFER_LEN)); + ReceiveBuffers[RXBufferHead].InUse = true; + ReceiveBuffers[RXBufferHead++].DataLength = MIN(bytes, WS_INTERNET_BUFFER_LEN); + RXBufferHead &= WS_NUM_RX_BUFFERS-1; + } +} + + + +/*********************************************************************************************** + * TMC::Set_Host_Address -- Set the address of the host game we want to connect to * + * * + * * + * * + * INPUT: ptr to address string * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 3/20/96 3:19PM ST : Created * + *=============================================================================================*/ +void TcpipManagerClass::Set_Host_Address(char *address) +{ + strcpy(HostAddress, address); +} + + + +/*********************************************************************************************** + * TMC::Start_Client -- Start trying to connect to a game host * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 3/20/96 3:19PM ST : Created * + *=============================================================================================*/ + +void TcpipManagerClass::Start_Client(void) +{ + struct sockaddr_in addr; + bool delay = true; + int i; + + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + /* + ** Set up the incoming and outgoing data buffers head and tail pointers + */ +// InBufferHead = 0; +// InBufferTail = 0; +// OutBufferHead= 0; +// OutBufferTail= 0; + + TXBufferHead = 0; + TXBufferTail = 0; + RXBufferHead = 0; + RXBufferTail = 0; + + for (i=0 ; i. +*/ + + +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TCPIP.CPP * + * * + * Programmer : Steve Tall * + * * + * Start Date : March 11th, 1996 * + * * + * Last Update : March 11th, 1996 [ST] * + * * + *-------------------------------------------------------------------------* + * * + * * + * * + *-------------------------------------------------------------------------* + * Functions: * + * * + * * + * * + * * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +extern bool Server; + +#define FORCE_WINSOCK 1 + +#define WINSOCK_MINOR_VER 1 +#define WINSOCK_MAJOR_VER 1 +#define PORTNUM 0x1000 +#define UDP_PORT 0x1001 +#define WS_INTERNET_BUFFER_LEN 1024 +#define WS_NUM_TX_BUFFERS 16 //Must be a power of 2 +#define WS_NUM_RX_BUFFERS 16 //MUst be a power of 2 +#define WS_RECEIVE_BUFFER_LEN 1024 +//#define WS_IN_BUFFER_LEN 8192 +//#define WS_OUT_BUFFER_LEN 8192 + +#define PLANET_WESTWOOD_HANDLE_MAX 20 +#define PLANET_WESTWOOD_PASSWORD_MAX 20 +#define IP_ADDRESS_MAX 40 +#define PORT_NUMBER_MAX 6 + +//........................................................................... +// Custom messages: WM_USER + 1 to WM_USER + 100 +// These will be sent to the dialog procedure, for display only. +//........................................................................... +#define WM_UPDATE_STATUS (WM_USER + 1) // update status text +#define WM_UPDATE_CLIENTS (WM_USER + 2) // update client list box +#define WM_UPDATE_MESSAGE (WM_USER + 3) // update received message list + +//........................................................................... +// Messages for Async processing. +//........................................................................... +#define WM_ACCEPT (WM_USER + 101) // client wants to connect +#define WM_HOSTBYADDRESS (WM_USER + 102) // async get host by address +#define WM_HOSTBYNAME (WM_USER + 103) // async get host by name +#define WM_ASYNCEVENT (WM_USER + 104) // other Async event +#define WM_UDPASYNCEVENT (WM_USER + 105) // UDP socket Async event + + +#define VSS_ID -1 // ID of the VSS connection. + +class TcpipManagerClass { + + public: + + TcpipManagerClass(void); + ~TcpipManagerClass(void); + + BOOL Init(void); + void Start_Server(void); + void Start_Client(void); + void Close_Socket(SOCKET s); + void Message_Handler(HWND window, UINT message, UINT wParam, LONG lParam); + void Copy_To_In_Buffer(int bytes); + int Read(void *buffer, int buffer_len); + void Write(void *buffer, int buffer_len); + BOOL Add_Client(void); + void Close(void); + void Set_Host_Address(char *address); + void Set_Protocol_UDP(BOOL state); + void Clear_Socket_Error(SOCKET socket); + + inline BOOL Get_Connected(void) {return (Connected);} + + typedef enum ConnectStatusEnum { + CONNECTED_OK = 0, + NOT_CONNECTING, + CONNECTING, + UNABLE_TO_CONNECT_TO_SERVER, + CONTACTING_SERVER, + SERVER_ADDRESS_LOOKUP_FAILED, + RESOLVING_HOST_ADDRESS, + UNABLE_TO_ACCEPT_CLIENT, + UNABLE_TO_CONNECT, + CONNECTION_LOST + } ConnectStatusEnum; + + inline ConnectStatusEnum Get_Connection_Status(void) {return (ConnectStatus);} + + private: + + //........................................................................... + // This structure defines all the info we need about a host + //........................................................................... + typedef struct { + struct in_addr Addr; // address + char DotAddr[16]; // decimal-dot address string + char Name[255]; // character-string name + } HostType; + + typedef struct { + char Buffer[WS_INTERNET_BUFFER_LEN]; + int DataLength; + bool InUse:1; + } InternetBufferType; + + + BOOL WinsockInitialised; + WSADATA WinsockInfo; + SOCKET ListenSocket; + SOCKET ConnectSocket; + SOCKET UDPSocket; + IN_ADDR ClientIPAddress; + HANDLE Async; + char HostBuff[MAXGETHOSTSTRUCT]; + char ClientName[128]; + char ReceiveBuffer[WS_RECEIVE_BUFFER_LEN]; + //char InBuffer[WS_IN_BUFFER_LEN]; + //int InBufferHead; + //int InBufferTail; + //char OutBuffer[WS_OUT_BUFFER_LEN]; + //int OutBufferHead; + //int OutBufferTail; + BOOL IsServer; + BOOL Connected; + HostType Server; + char HostAddress[IP_ADDRESS_MAX]; + ConnectStatusEnum ConnectStatus; + BOOL UseUDP; + IN_ADDR UDPIPAddress; + int SocketReceiveBuffer; + int SocketSendBuffer; + InternetBufferType ReceiveBuffers[WS_NUM_TX_BUFFERS]; + InternetBufferType TransmitBuffers[WS_NUM_RX_BUFFERS]; + int TXBufferHead; + int TXBufferTail; + int RXBufferHead; + int RXBufferTail; + +}; + + +extern TcpipManagerClass Winsock; + +extern char PlanetWestwoodIPAddress[IP_ADDRESS_MAX]; +extern long PlanetWestwoodPortNumber; +extern bool PlanetWestwoodIsHost; +extern int Read_Game_Options(char *); +extern bool UseVirtualSubnetServer; +extern int InternetMaxPlayers; + + +#define TXT_WINSOCK_CONNECTING 4567+13 +#define TXT_WINSOCK_NOT_CONNECTING 4567+14 +#define TXT_WINSOCK_UNABLE_TO_CONNECT_TO_SERVER 4567+15 +#define TXT_WINSOCK_CONTACTING_SERVER 4567+16 +#define TXT_WINSOCK_SERVER_ADDRESS_LOOKUP_FAILED 4567+17 +#define TXT_WINSOCK_UNABLE_TO_ACCEPT_CLIENT 4567+18 +#define TXT_WINSOCK_UNABLE_TO_CONNECT 4567+19 +#define TXT_WINSOCK_CONNECTION_LOST 4567+20 +#define TXT_WINSOCK_RESOLVING_HOST_ADDRESS 4567+21 + + +#if (0) + + +struct tag tGameStatisticsStruct{ + char WinnersName[20]; + char LosersName[20]; + int WinnersTeam; + int LosersTeam; + int WinnersCredits; + int LosersCredits; + int WinnersKills; + int LosersKills; + int ScenarioPlayed; + int GameTimeElapsed; + int VersionNumber; + char TimeDateStamp[12]; +} GameStatisticsStruct; + + + + + +extern GameStatisticsStruct GameStatistics; + +#endif //(0) + + diff --git a/TDATA.CPP b/TDATA.CPP new file mode 100644 index 0000000..d5f728f --- /dev/null +++ b/TDATA.CPP @@ -0,0 +1,1029 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\tdata.cpv 2.16 16 Oct 1995 16:52:04 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TDATA.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 2, 1994 * + * * + * Last Update : May 8, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * TerrainTypeClass::Prep_For_Add -- Prepares to add terrain object. * + * TerrainTypeClass::Display -- Display a generic terrain object. * + * TerrainTypeClass::From_Name -- Convert name to terrain type. * + * TerrainTypeClass::Init -- Loads terrain object shape files. * + * TerrainTypeClass::Create_And_Place -- Creates and places terrain object on map. * + * TerrainTypeClass::Create_On_Of -- Creates a terrain object from type. * + * TerrainTypeClass::TerrainTypeClass -- The general constructor for the terrain type objects* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "terrain.h" +#include "type.h" + + +#define TREE_NORMAL 600 +#define TREE_WEAK 400 +#define TREE_STRONG 800 + +static short const _List1[] = {0, REFRESH_EOL}; +static short const _List000010[] = {MAP_CELL_W+1, REFRESH_EOL}; +static short const _List000011101000[] = {MAP_CELL_W, MAP_CELL_W+1, MAP_CELL_W+2, MAP_CELL_W*2, REFRESH_EOL}; +static short const _List00001[] = {4, REFRESH_EOL}; +static short const _List000110[] = {MAP_CELL_W, MAP_CELL_W+1, REFRESH_EOL}; +static short const _List000111[] = {MAP_CELL_W, MAP_CELL_W+1, MAP_CELL_W+2, REFRESH_EOL}; +static short const _List001011100110[] = {2, MAP_CELL_W, MAP_CELL_W+1, MAP_CELL_W+2, MAP_CELL_W*2+1, MAP_CELL_W*2+2, REFRESH_EOL}; +static short const _List0010[] = {MAP_CELL_W, REFRESH_EOL}; +static short const _List0011[] = {MAP_CELL_W, MAP_CELL_W+1, REFRESH_EOL}; +static short const _List001[] = {2, REFRESH_EOL}; +static short const _List010110[] = {1, MAP_CELL_W, MAP_CELL_W+1, REFRESH_EOL}; +static short const _List01[] = {1, REFRESH_EOL}; +static short const _List1001[] = {0, MAP_CELL_W+1, REFRESH_EOL}; +static short const _List101001[] = {0, 2, MAP_CELL_W+2, REFRESH_EOL}; +static short const _List10[] = {0, REFRESH_EOL}; +static short const _List110000011001[] = {0, 1, MAP_CELL_W+3, MAP_CELL_W*2, MAP_CELL_W*2+3, REFRESH_EOL}; +static short const _List110000[] = {0, 1, REFRESH_EOL}; +static short const _List110001[] = {0, 1, MAP_CELL_W+2, REFRESH_EOL}; +static short const _List1100[] = {0, 1, REFRESH_EOL}; +static short const _List110110[] = {0, 1, MAP_CELL_W, MAP_CELL_W+1, REFRESH_EOL}; +static short const _List1101[] = {0, 1, MAP_CELL_W+1, REFRESH_EOL}; +static short const _List111000010110[] = {0, 1, 2, MAP_CELL_W+3, MAP_CELL_W*2+1, MAP_CELL_W*2+2, REFRESH_EOL}; +static short const _List111001[] = {0, 1, 2, MAP_CELL_W+2, REFRESH_EOL}; +static short const _List111101[] = {0, 1, 2, MAP_CELL_W, MAP_CELL_W+2, REFRESH_EOL}; +static short const _List11110[] = {0, 1, 2, 3, REFRESH_EOL}; + + +static TerrainTypeClass const Tree1Class( + TERRAIN_TREE1, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(11,41), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T01", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0010, + (short const *)_List1001 +); + +static TerrainTypeClass const Tree2Class( + TERRAIN_TREE2, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(11,44), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T02", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0010, + (short const *)_List1001 +); + +static TerrainTypeClass const Tree3Class( + TERRAIN_TREE3, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(12,45), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T03", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0010, + (short const *)_List1001 +); + +static TerrainTypeClass const Tree4Class( + TERRAIN_TREE4, + THEATERF_DESERT, + XYP_COORD(8,9), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T04", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List1, + NULL +); + + +static TerrainTypeClass const Tree5Class( + TERRAIN_TREE5, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(15,41), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T05", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0010, + (short const *)_List1001 +); + +static TerrainTypeClass const Tree6Class( + TERRAIN_TREE6, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(16,37), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T06", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0010, + (short const *)_List1001 +); + +static TerrainTypeClass const Tree7Class( + TERRAIN_TREE7, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(15,41), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T07", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0010, + (short const *)_List1001 +); + +static TerrainTypeClass const Tree8Class( + TERRAIN_TREE8, + THEATERF_WINTER|THEATERF_TEMPERATE|THEATERF_DESERT, + XYP_COORD(14,22), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T08", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List10, + (short const *)_List01 +); + +static TerrainTypeClass const Tree9Class( + TERRAIN_TREE9, + THEATERF_DESERT, + XYP_COORD(11,22), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T09", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List10, + (short const *)_List01 +); + +static TerrainTypeClass const Tree10Class( + TERRAIN_TREE10, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(25,43), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T10", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0011, + (short const *)_List1100 +); + +static TerrainTypeClass const Tree11Class( + TERRAIN_TREE11, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(23,44), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T11", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0011, + (short const *)_List1100 +); + +static TerrainTypeClass const Tree12Class( + TERRAIN_TREE12, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(14,36), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T12", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0010, + (short const *)_List1001 +); + +static TerrainTypeClass const Tree13Class( + TERRAIN_TREE13, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(19,40), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T13", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0010, + (short const *)_List1101 +); + +static TerrainTypeClass const Tree14Class( + TERRAIN_TREE14, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(19,40), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T14", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0011, + (short const *)_List1100 +); + +static TerrainTypeClass const Tree15Class( + TERRAIN_TREE15, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(19,40), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T15", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0011, + (short const *)_List1100 +); + +static TerrainTypeClass const Tree16Class( + TERRAIN_TREE16, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(13,36), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T16", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0010, + (short const *)_List1001 +); + +static TerrainTypeClass const Tree17Class( + TERRAIN_TREE17, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(18,44), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T17", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0010, + (short const *)_List1001 +); + +static TerrainTypeClass const Tree18Class( + TERRAIN_TREE18, + THEATERF_DESERT, + XYP_COORD(33,40), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + true, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + true, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + false, // Is it immune to normal combat damage? + "T18", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List000010, + (short const *)_List111101 +); + +static TerrainTypeClass const Split1Class( + TERRAIN_BLOSSOMTREE1, + THEATERF_TEMPERATE|THEATERF_WINTER, + XYP_COORD(18,44), // Center base coordinate offset. + true, // Spawns Tiberium spontaneously? + false, // Does it have destruction animation? + true, // Does it have transformation (blossom tree) anim? + false, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + true, // Is it immune to normal combat damage? + "SPLIT2", + TXT_BLOSSOM_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0010, + (short const *)_List1101 +); + +static TerrainTypeClass const Split2Class( + TERRAIN_BLOSSOMTREE2, + THEATERF_TEMPERATE|THEATERF_WINTER|THEATERF_DESERT, + XYP_COORD(18,44), // Center base coordinate offset. + true, // Spawns Tiberium spontaneously? + false, // Does it have destruction animation? + true, // Does it have transformation (blossom tree) anim? + false, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + true, // Is it immune to normal combat damage? + "SPLIT3", + TXT_BLOSSOM_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List0010, + (short const *)_List1101 +); + +static TerrainTypeClass const Clump1Class( + TERRAIN_CLUMP1, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(28,41), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + false, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + false, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + true, // Is it immune to normal combat damage? + "TC01", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List000110, + (short const *)_List110001 +); + +static TerrainTypeClass const Clump2Class( + TERRAIN_CLUMP2, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(38,41), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + false, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + false, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + true, // Is it immune to normal combat damage? + "TC02", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List010110, + (short const *)_List101001 +); + +static TerrainTypeClass const Clump3Class( + TERRAIN_CLUMP3, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(33,35), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + false, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + false, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + true, // Is it immune to normal combat damage? + "TC03", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List110110, + (short const *)_List001 +); + +static TerrainTypeClass const Clump4Class( + TERRAIN_CLUMP4, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(44,49), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + false, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + false, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + true, // Is it immune to normal combat damage? + "TC04", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List000011101000, + (short const *)_List111000010110 +); + +static TerrainTypeClass const Clump5Class( + TERRAIN_CLUMP5, + THEATERF_WINTER|THEATERF_TEMPERATE, + XYP_COORD(49,58), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + false, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + false, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + true, // Is it immune to normal combat damage? + "TC05", + TXT_TREE, + TREE_NORMAL, + ARMOR_WOOD, + (short const *)_List001011100110, + (short const *)_List110000011001 +); + +static TerrainTypeClass const Rock1Class( + TERRAIN_ROCK1, + THEATERF_DESERT, + XYP_COORD(33,41), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + false, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + false, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + true, // Is it immune to normal combat damage? + "ROCK1", + TXT_ROCK, + 1000, + ARMOR_STEEL, + (short const *)_List0011, + (short const *)_List111001 +); + +static TerrainTypeClass const Rock2Class( + TERRAIN_ROCK2, + THEATERF_DESERT, + XYP_COORD(24,23), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + false, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + false, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + true, // Is it immune to normal combat damage? + "ROCK2", + TXT_ROCK, + 1000, + ARMOR_STEEL, + (short const *)_List1100, + (short const *)_List001 +); + +static TerrainTypeClass const Rock3Class( + TERRAIN_ROCK3, + THEATERF_DESERT, + XYP_COORD(20,39), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + false, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + false, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + true, // Is it immune to normal combat damage? + "ROCK3", + TXT_ROCK, + 1000, + ARMOR_STEEL, + (short const *)_List000110, + (short const *)_List110001 +); + +static TerrainTypeClass const Rock4Class( + TERRAIN_ROCK4, + THEATERF_DESERT, + XYP_COORD(12,20), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + false, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + false, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + true, // Is it immune to normal combat damage? + "ROCK4", + TXT_ROCK, + 1000, + ARMOR_STEEL, + (short const *)_List10, + (short const *)_List01 +); + +static TerrainTypeClass const Rock5Class( + TERRAIN_ROCK5, + THEATERF_DESERT, + XYP_COORD(17,19), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + false, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + false, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + true, // Is it immune to normal combat damage? + "ROCK5", + TXT_ROCK, + 1000, + ARMOR_STEEL, + (short const *)_List10, + (short const *)_List01 +); + +static TerrainTypeClass const Rock6Class( + TERRAIN_ROCK6, + THEATERF_DESERT, + XYP_COORD(28,40), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + false, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + false, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + true, // Is it immune to normal combat damage? + "ROCK6", + TXT_ROCK, + 1000, + ARMOR_STEEL, + (short const *)_List000111, + (short const *)_List110000 +); + +static TerrainTypeClass const Rock7Class( + TERRAIN_ROCK7, + THEATERF_DESERT, + XYP_COORD(57,22), // Center base coordinate offset. + false, // Spawns Tiberium spontaneously? + false, // Does it have destruction animation? + false, // Does it have transformation (blossom tree) anim? + false, // Does it catch fire? + false, // Is this object crushable by heavy vehicles? + false, // Can this object be selected by the player? + false, // Can it be the target of a move or attack order? + true, // Don't make a big deal about it if it gets destroyed? + true, // Is it immune to normal combat damage? + "ROCK7", + TXT_ROCK, + 1000, + ARMOR_STEEL, + (short const *)_List11110, + (short const *)_List00001 +); + + +TerrainTypeClass const * const TerrainTypeClass::Pointers[TERRAIN_COUNT] = { + &Tree1Class, // TERRAIN_TREE1 + &Tree2Class, // TERRAIN_TREE2 + &Tree3Class, // TERRAIN_TREE3 + &Tree4Class, // TERRAIN_TREE4 + &Tree5Class, // TERRAIN_TREE5 + &Tree6Class, // TERRAIN_TREE6 + &Tree7Class, // TERRAIN_TREE7 + &Tree8Class, // TERRAIN_TREE8 + &Tree9Class, // TERRAIN_TREE9 + &Tree10Class, // TERRAIN_TREE10 + &Tree11Class, // TERRAIN_TREE11 + &Tree12Class, // TERRAIN_TREE12 + &Tree13Class, // TERRAIN_TREE13 + &Tree14Class, // TERRAIN_TREE14 + &Tree15Class, // TERRAIN_TREE15 + &Tree16Class, // TERRAIN_TREE16 + &Tree17Class, // TERRAIN_TREE17 + &Tree18Class, // TERRAIN_TREE18 + &Split1Class, // TERRAIN_BLOSSOMTREE1 + &Split2Class, // TERRAIN_BLOSSOMTREE2 + &Clump1Class, // TERRAIN_CLUMP1 + &Clump2Class, // TERRAIN_CLUMP2 + &Clump3Class, // TERRAIN_CLUMP3 + &Clump4Class, // TERRAIN_CLUMP4 + &Clump5Class, // TERRAIN_CLUMP5 + &Rock1Class, // TERRAIN_ROCK1 + &Rock2Class, // TERRAIN_ROCK2 + &Rock3Class, // TERRAIN_ROCK3 + &Rock4Class, // TERRAIN_ROCK4 + &Rock5Class, // TERRAIN_ROCK5 + &Rock6Class, // TERRAIN_ROCK6 + &Rock7Class // TERRAIN_ROCK7 +}; + + +/*********************************************************************************************** + * TerrainTypeClass::TerrainTypeClass -- The general constructor for the terrain type objects. * + * * + * This is the constructor for terrain type objects. It is only used to construct the * + * static (constant) terrain type objects. * + * * + * INPUT: see below.. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1994 JLB : Created. * + *=============================================================================================*/ +TerrainTypeClass::TerrainTypeClass( + TerrainType terrain, + int theater, + COORDINATE centerbase, + bool is_spawn, + bool is_destroyable, + bool is_transformable, + bool is_flammable, + bool is_crushable, + bool is_selectable, + bool is_legal_target, + bool is_insignificant, + bool is_immune, + char const *ininame, + int fullname, unsigned short strength, ArmorType armor, short const *occupy, short const *overlap) : + ObjectTypeClass(true, is_flammable, is_crushable, true, is_selectable, + is_legal_target, is_insignificant, is_immune, + fullname, ininame, armor, strength) +{ + CenterBase = centerbase; + IsTiberiumSpawn = is_spawn; + IsDestroyable = is_destroyable; + IsTransformable = is_transformable; + Theater = theater; + Type = terrain; + Occupy = occupy; + Overlap = overlap; +} + + +/*********************************************************************************************** + * TerrainTypeClass::Init -- Loads terrain object shape files. * + * * + * This routine is used to load up the terrain object shape files. * + * The shape files loaded depends on theater. * + * * + * INPUT: theater -- The theater to load the terrain shape data for. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/16/1994 JLB : Created. * + *=============================================================================================*/ +void TerrainTypeClass::Init(TheaterType theater) +{ + if (theater != LastTheater){ + for (TerrainType index = TERRAIN_FIRST; index < TERRAIN_COUNT; index++) { + TerrainTypeClass const & terrain = As_Reference(index); + char fullname[_MAX_FNAME+_MAX_EXT]; + + /* + ** Clear any existing shape pointer. All terrain is theater specific, thus if + ** it isn't loaded in this routine, it shouldn't exist at all. + */ + ((void const *&)terrain.ImageData) = NULL; + + if (terrain.Theater & (1 << theater)) { + + /* + ** Load in the appropriate object shape data. + */ + _makepath(fullname, NULL, NULL, terrain.IniName, Theaters[theater].Suffix); + ((void const *&)terrain.ImageData) = MixFileClass::Retrieve(fullname); + + IsTheaterShape = true; + if (terrain.RadarIcon) delete[] (char *)terrain.RadarIcon; + ((void const *&)terrain.RadarIcon) = Get_Radar_Icon(terrain.Get_Image_Data(), 0, 1, 3); + IsTheaterShape = false; + } + } + } +} + + +/*********************************************************************************************** + * TerrainTypeClass::From_Name -- Convert name to terrain type. * + *  * + * This routine is used to convert a text name into the matching * + * terrain type number. This is used during scenario initialization. * + * * + * INPUT: name -- The name to convert. * + * * + * OUTPUT: Returns the TerrainType that matches the name specified. If * + * no match was found, then TERRAIN_NONE is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/16/1994 JLB : Created. * + *=============================================================================================*/ +TerrainType TerrainTypeClass::From_Name(char const * name) +{ + TerrainType index; + + if (name) { + for (index = TERRAIN_FIRST; index < TERRAIN_COUNT; index++) { + if (stricmp(name, Pointers[index]->IniName) == 0) { + return(index); + } + } + } + return(TERRAIN_NONE); +} + + +#ifdef SCENARIO_EDITOR +/*********************************************************************************************** + * TerrainTypeClass::Display -- Display a generic terrain object. * + * * + * This routine is used to display a generic terrain object. Typical * + * use is during scenario editing. * + * * + * INPUT: x,y -- Pixel coordinates to display object at (centered). * + * * + * window-- The window to display the object within. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/16/1994 JLB : Created. * + *=============================================================================================*/ +void TerrainTypeClass::Display(int x, int y, WindowNumberType window, HousesType) const +{ + CC_Draw_Shape(Get_Image_Data(), 0, x, y, window, SHAPE_NORMAL|SHAPE_CENTER|SHAPE_WIN_REL); +} + + +/*********************************************************************************************** + * TerrainTypeClass::Prep_For_Add -- Prepares to add terrain object. * + * * + * Submits all of the valid terrain objects to the scenario editor for possible selection * + * and subsequent placement on the map. All terrain objects, that have a valid shape * + * file available, are added. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1994 JLB : Created. * + *=============================================================================================*/ +void TerrainTypeClass::Prep_For_Add(void) +{ + for (TerrainType index = TERRAIN_FIRST; index < TERRAIN_COUNT; index++) { + if (As_Reference(index).Get_Image_Data()) { + Map.Add_To_List(&As_Reference(index)); + } + } +} +#endif + + +/*********************************************************************************************** + * TerrainTypeClass::Create_And_Place -- Creates and places terrain object on map. * + * * + * This support routine is used by the scenario editor to add a terrain object to the map. * + * * + * INPUT: cell -- The cell to place the terrain object in. * + * * + * OUTPUT: bool; Was the placement successful? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +bool TerrainTypeClass::Create_And_Place(CELL cell, HousesType ) const +{ + if (new TerrainClass(Type, cell)) { + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TerrainTypeClass::Create_On_Of -- Creates a terrain object from type. * + * * + * This is used to create a terrain object by using the terrain type as a guide. This * + * routine is typically used by the scenario editor in order to place a terrain object * + * onto the map. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the created terrain object or NULL if one couldn't be * + * created. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/19/1994 JLB : Created. * + *=============================================================================================*/ +ObjectClass * TerrainTypeClass::Create_One_Of(HouseClass *) const +{ + return(new TerrainClass(Type, -1)); +} + + +short const * TerrainTypeClass::Occupy_List(bool ) const +{ + if (Occupy) return(Occupy); + + static short const _simple[1] = { + REFRESH_EOL + }; + return(&_simple[0]); +} + + +short const * TerrainTypeClass::Overlap_List(void) const +{ + if (Overlap) return(Overlap); + + static short const _simple[1] = { + REFRESH_EOL + }; + return(&_simple[0]); +} diff --git a/TEAM.CPP b/TEAM.CPP new file mode 100644 index 0000000..355be64 --- /dev/null +++ b/TEAM.CPP @@ -0,0 +1,1505 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\team.cpv 2.18 16 Oct 1995 16:48:34 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TEAM.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/11/94 * + * * + * Last Update : August 6, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Assign_Mission_Target -- Sets teams mission target and clears old target * + * TeamClass::Add -- Adds specified object to team. * + * TeamClass::AI -- Process team logic. * + * TeamClass::As_Target -- Converts this team object into a target number. * + * TeamClass::Calc_Center -- Determines average location of team members. * + * TeamClass::Control -- Updates control on a member unit. * + * TeamClass::Coordinate_Attack -- Handles coordinating a team attack. * + * TeamClass::Coordinate_Conscript -- Gives orders to new recruit. * + * TeamClass::Coordinate_Move -- Handles team movement coordination. * + * TeamClass::Coordinate_Regroup -- Handles team idling (regrouping). * + * TeamClass::Coordinate_Unload -- Tells the team to unload passengers now. * + * TeamClass::Detach -- Removes specified target from team tracking. * + * TeamClass::Init -- Initializes the team objects for scenario preparation. * + * TeamClass::Is_A_Member -- Tests if a unit is a member of a team * + * TeamClass::Recruit -- Attempts to recruit members to the team for the given index ID. * + * TeamClass::Remove -- Removes the specified object from the team. * + * TeamClass::Suspend_Teams -- Suspends activity for low priority teams * + * TeamClass::Took_Damage -- Informs the team when the team member takes damage. * + * TeamClass::Validate -- validates team pointer * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "mission.h" + +/* +** This array records the number of teams in existance of each type. +*/ +unsigned char TeamClass::Number[TEAMTYPE_MAX]; + +/* +** This array records the success rating of each of the team types. +*/ +unsigned char TeamClass::Success[TEAMTYPE_MAX]; + +/* +** This contains the value of the Virtual Function Table Pointer +*/ +void * TeamClass::VTable; + + +/*********************************************************************************************** + * TeamClass::Validate -- validates team pointer * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int TeamClass::Validate(void) const +{ + int num; + + num = Teams.ID(this); + if (num < 0 || num >= TEAM_MAX) { + Validate_Error("TEAM"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*********************************************************************************************** + * TeamClass::Init -- Initializes the team objects for scenario preparation. * + * * + * This routine clears out the team object array in preparation for starting a new * + * scenario. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/29/1994 JLB : Created. * + *=============================================================================================*/ +void TeamClass::Init(void) +{ + TeamClass *ptr; + + Teams.Free_All(); + memset(Number, 0, sizeof(Number)); + memset(Success, 0, sizeof(Success)); + + ptr = new TeamClass(); + VTable = ((void **)(((char *)ptr) + sizeof(AbstractClass) - 4))[0]; + delete ptr; +} + + +void * TeamClass::operator new (size_t) +{ + void * ptr = Teams.Allocate(); + if (ptr) { + ((TeamClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +void TeamClass::operator delete(void * ptr) +{ + if (ptr) { + ((TeamClass *)ptr)->IsActive = false; + } + Teams.Free((TeamClass *)ptr); +} + + +TeamClass::~TeamClass(void) +{ + if (GameActive && Class) { + Number[TeamTypes.ID(Class)]--; + while (Member) { + Remove(Member); + } + + if (Class->IsTransient && !Number[TeamTypes.ID(Class)]) { + delete (TeamTypeClass *)Class; + } + } +} + + +TeamClass::TeamClass(TeamTypeClass const * type, HouseClass * owner) : + Class(type), + House(owner) +{ + memset(Quantity, 0, sizeof(Quantity)); + IsAltered = true; + IsForcedActive = false; + IsFullStrength = false; + IsUnderStrength = true; + IsReforming = false; + IsLagging = false; + IsMoving = false; + IsHasBeen = false; + Center = 0; + Target = TARGET_NONE; + ObjectiveCenter = 0; + MissionTarget = TARGET_NONE; + Member = 0; + Total = 0; + Risk = 0; + CurrentMission = -1; + IsNextMission = true; + TimeOut = 0; + SuspendTimer.Clear(); + Suspended = false; + Number[TeamTypes.ID(Class)]++; +} + + +/*************************************************************************** + * TeamClass::Assign_Mission_Target -- Sets mission target and clears old * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 05/16/1995 PWG : Created. * + *=========================================================================*/ +void TeamClass::Assign_Mission_Target(TARGET new_target) +{ + Validate(); + /* + ** First go through and find anyone who is currently targetting + ** the old mission target and clear their Tarcom. + */ + FootClass * unit = Member; + while (unit) { + bool tar = (unit->TarCom == MissionTarget); + bool nav = (unit->NavCom == MissionTarget); + if (tar || nav) { + + /* + ** If the unit was doing something related to the team mission + ** then we kick him into guard mode so that he is easy to change + ** missions for. + */ + unit->Assign_Mission(MISSION_GUARD); + + /* + ** If the unit's tarcom is set to the old mission target, then + ** clear it, so that it will be reset by whatever happens next. + */ + if (nav) { + unit->Assign_Destination(TARGET_NONE); + } + + /* + ** If the unit's navcom is set to the old mission target, then + ** clear it, so that it will be reset by whatever happens next. + */ + if (tar) { + unit->Assign_Target(TARGET_NONE); + } + } + unit = (FootClass *)unit->Member; + } + + /* + ** If there is not currently an override on the current mission target + ** then assign both MissionTarget and Target to the new target. If + ** there is an overide, allow the team to keep fighting the overide but + ** make sure they pick up on the new mission when they are ready. + */ + if (Target == MissionTarget || !Target_Legal(Target)) { + MissionTarget = Target = new_target; + } else { + MissionTarget = new_target; + } +} + + +/*********************************************************************************************** + * TeamClass::AI -- Process team logic. * + * * + * General purpose team logic is handled by this routine. It should be called once per * + * active team per game tick. This routine handles recruitment and assigning orders to * + * member units. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/29/1994 JLB : Created. * + * 01/06/1995 JLB : Choreographed gesture. * + *=============================================================================================*/ +void TeamClass::AI(void) +{ + Validate(); + int desired = 0; + int old_under = IsUnderStrength; + int old_full = IsFullStrength; + + /* + ** If the team has been suspended then we need to check if its time for + ** us to reactivate the team. If not, no team logic will be processed + ** for this team. + */ + if (Suspended) { + if (!SuspendTimer.Expired()) { + return; + } + Suspended = false; + } + + /* + ** If this team senses that its composition has been altered, then it should + ** recalculate the under strength and full strength flags. + */ + if (IsAltered) { + + for (int index = 0; index < Class->ClassCount; index++) { + desired += Class->DesiredNum[index]; + } + + if (Total) { + IsFullStrength = (Total == desired); + + /* + ** Human controlled teams are always considered full strength. This ensures + ** that no new team members will be recruited and the team won't go into + ** regroup logic. + */ + if (House->IsHuman) { + IsUnderStrength = false; + } else { + + /* + ** Reinforcable teams will revert (or snap out of) the under strength + ** mode when the members transition the magic 1/3 strength threshhold. + */ + if (Class->IsReinforcable) { + IsUnderStrength = (Total <= desired / 3); + } else { + + /* + ** Teams that are not flagged as reinforcable are never considered under + ** strength if the team has already started its main mission. This + ** ensures that once the team has started, it won't dally to pick up + ** new members. + */ + IsUnderStrength = !IsHasBeen; + } + } + IsAltered = false; + } else { + IsUnderStrength = true; + IsFullStrength = false; + Center = 0; + + /* + ** A team that exists on the player's side is automatically destroyed + ** when there are no team members left. This team was created as a + ** result of reinforcement logic and no longer needs to exist when there + ** are no more team members. + */ + if (House->IsHuman || IsHasBeen) { + delete this; + return; + } + } + + /* + ** If the team has gone from under strength to no longer under + ** strength than the team needs to reform. + */ + if (old_under != IsUnderStrength) { + IsReforming = true; + } + } + + /* + ** If the team is under strength, then flag it to regroup. + */ + if (IsMoving && IsUnderStrength) { + IsMoving = false; + CurrentMission = -1; + if (Total) { + Calc_Center(Center, ObjectiveCenter); + + /* + ** When a team is badly damaged and needs to regroup it should + ** pick a friendly building to go and regroup at. Its first preference + ** should be somewhere near repair factory. If it cannot find a repair + ** factory then it should pick another structure that is friendly to + ** its side. + */ + CELL dest = Center; + int max = 0x7FFFFFFF; + + for (int index = 0; index < Buildings.Count(); index++) { + BuildingClass * b = Buildings.Ptr(index); + + if (b && !b->IsInLimbo && b->House == House && b->Class->Primary == WEAPON_NONE) { + CELL cell = Coord_Cell(b->Center_Coord()); + int dist = Map.Cell_Distance(cell, Center) * (Map.Cell_Threat(cell, House->Class->House) + 1); + + if (*b == STRUCT_REPAIR) { + dist >>= 1; + } + if (dist < max) { + cell = Member->Safety_Point(Center, cell, 2, 4); + if (cell != -1) { + max = dist; + dest = cell; + } + } + } + } + + // Should calculate a regroup location. + Target = ::As_Target(dest); + Coordinate_Move(); + return; + } else { + Center = 0; + } + } + + /* + ** Flag this team into action when it gets to full strength. Human owned teams are only + ** used for reinforcement purposes -- always consider them at full strength. + */ + if (!IsMoving && (IsFullStrength || IsForcedActive)) { + IsMoving = true; + IsHasBeen = true; + IsUnderStrength = false; + + /* + ** Infantry can do a gesture when they start their mission. Pick + ** a gesture at random. + */ + FootClass * techno = Member; + DoType doaction = (Random_Pick(1, 2) == 1) ? DO_GESTURE1 : DO_GESTURE2; + while (techno) { + if (!techno->IsInLimbo && techno->What_Am_I() == RTTI_INFANTRY) { + ((InfantryClass *)techno)->Do_Action(doaction); + } + + if (IsReforming || IsForcedActive) { + techno->IsInitiated = true; + } + + techno = techno->Member; + } + CurrentMission = -1; + IsNextMission = true; + IsForcedActive = false; + } + + /* + ** If the team is moving or if there is no center position for + ** the team, then the center position must be recalculated. + */ + if (IsReforming || IsMoving || Center == 0) { + Calc_Center(Center, ObjectiveCenter); + } + + /* + ** Try to recruit members if there is room to do so for this team. + ** Only try to recruit members for a non player controlled team. + */ + if (!IsMoving || (!IsFullStrength && Class->IsReinforcable) && !House->IsHuman) { + for (int index = 0; index < Class->ClassCount; index++) { + if (Quantity[index] < Class->DesiredNum[index]) { + Recruit(index); + } + } + } + + /* + ** If there are no members of the team and the team has reached + ** full strength at one time, then delete the team. + */ + if (!Member && IsHasBeen) { + delete this; + return; + } + + /* + ** If the mission should be advanced to the next entry, then do so at + ** this time. Various events may cause the mission to advance, but it + ** all boils down to the following change-mission code. + */ + if (IsMoving && !IsReforming && IsNextMission) { + IsNextMission = false; + CurrentMission++; + if (CurrentMission < Class->MissionCount) { + TeamMissionStruct const * mission = &Class->MissionList[CurrentMission]; + + TimeOut = mission->Argument * (TICKS_PER_MINUTE/10); + Target = TARGET_NONE; + switch (mission->Mission) { + case TMISSION_MOVECELL: + Assign_Mission_Target(::As_Target((CELL)mission->Argument)); + break; + + case TMISSION_MOVE: + case TMISSION_UNLOAD: + Assign_Mission_Target(::As_Target((CELL)Waypoint[mission->Argument])); + break; + + case TMISSION_ATTACKTARCOM: + Assign_Mission_Target(mission->Argument); + break; + + default: + Assign_Mission_Target(TARGET_NONE); + break; + } + } else { + delete this; + return; + } + } + + /* + ** Perform mission of the team. This depends on the mission list. + */ + if (Member && IsMoving && !IsReforming && !IsUnderStrength) { + /* + ** If the current Target has been dealt with but the mission target + ** has not, then the current target needs to be reset to the mission + ** target. + */ + if (!Target_Legal(Target)) { + Target = MissionTarget; + } + + /* + ** If the current mission is one that times out, then check for + ** this case. If it has timed out then advance to the next + ** mission in the list or disband the team. + */ + TeamMissionStruct const * mission = &Class->MissionList[CurrentMission]; + switch (mission->Mission) { + case TMISSION_ATTACKBASE: + if (!Target_Legal(MissionTarget)) { + Assign_Mission_Target(Member->Greatest_Threat(THREAT_BUILDINGS)); + if (!Target_Legal(MissionTarget)) IsNextMission = true; + } + Coordinate_Attack(); + break; + + case TMISSION_ATTACKUNITS: + if (!Target_Legal(MissionTarget)) { + Assign_Mission_Target(Member->Greatest_Threat(THREAT_VEHICLES|THREAT_INFANTRY)); + if (!Target_Legal(MissionTarget)) IsNextMission = true; + } + Coordinate_Attack(); + break; + + case TMISSION_ATTACKCIVILIANS: + if (!Target_Legal(MissionTarget)) { + Assign_Mission_Target(Member->Greatest_Threat(THREAT_CIVILIANS)); + if (!Target_Legal(MissionTarget)) IsNextMission = true; + } + Coordinate_Attack(); + break; + + case TMISSION_ATTACKTARCOM: + case TMISSION_RAMPAGE: + if (!Target_Legal(MissionTarget)) { + Assign_Mission_Target(Member->Greatest_Threat(THREAT_NORMAL)); + if (!Target_Legal(MissionTarget)) IsNextMission = true; + } + Coordinate_Attack(); + break; + + case TMISSION_DEFENDBASE: + Coordinate_Move(); + break; + +// case TMISSION_HARVEST: +// Coordinate_Move(); +// break; + + case TMISSION_UNLOAD: + Coordinate_Unload(); + break; + + case TMISSION_MOVE: + Coordinate_Move(); + break; + + case TMISSION_RETREAT: + Coordinate_Move(); + break; + + case TMISSION_GUARD: + Coordinate_Regroup(); + break; + + case TMISSION_LOOP: + CurrentMission = mission->Argument-1; + IsNextMission = true; + break; + } + + /* + ** Check for mission time out condition. If the mission does in fact time out, then + ** flag it so that the team mission list will advance. + */ + switch (mission->Mission) { + case TMISSION_ATTACKBASE: + case TMISSION_ATTACKUNITS: + case TMISSION_ATTACKCIVILIANS: + case TMISSION_RAMPAGE: + case TMISSION_DEFENDBASE: + case TMISSION_UNLOAD: + case TMISSION_RETREAT: + case TMISSION_GUARD: + if (TimeOut.Expired()) { + IsNextMission = true; + } + break; + } + + } else { + if (IsMoving) { + IsReforming = !Coordinate_Regroup(); + } else { + Coordinate_Move(); + } + } +} + + +/*********************************************************************************************** + * TeamClass::Add -- Adds specified object to team. * + * * + * Use this routine to add the specified object to the team. The object is checked to make * + * sure that it can be assigned to the team. If it can't, then the object will be left * + * alone and false will be returned. * + * * + * INPUT: obj -- Pointer to the object that is to be assigned to this team. * + * * + * typeindex-- Optional value that specifies the index in the team type class array * + * that this object belongs. * + * * + * OUTPUT: bool; Was the unit added to the team? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/29/1994 JLB : Created. * + * 01/02/1995 JLB : Initiation flag setup. * + * 08/06/1995 JLB : Allows member stealing from lesser priority teams. * + *=============================================================================================*/ +bool TeamClass::Add(FootClass * obj, int typeindex) +{ + Validate(); + /* + ** If this team doesn't accept new members, then don't accept this one either. + */ +// if (!Class->IsReinforcable && IsMoving) { +// return(false); +// } + + if (!obj || !obj->Strength || (obj->IsInLimbo && !ScenarioInit) || obj->In_Radio_Contact() || obj->House != House) { + return(false); + } + + TeamClass * team = obj->Team; + + /* + ** Trying to add the team member to itself is an error condition. Just return + ** with success, since the end result is the same. + */ + if (team == this) { + return(true); + } + + /* + ** If the object is doing some mission that precludes it from joining + ** a team then don't add it. + */ + if (obj->Mission == MISSION_STICKY || obj->Mission == MISSION_SLEEP || obj->Mission == MISSION_GUARD_AREA || obj->Mission == MISSION_HUNT || obj->Mission == MISSION_HARVEST) { + return(false); + } + + /* + ** If this object is part of another team, then check to make sure that it + ** is permitted to leave the other team in order to join this one. If not, + ** then no further processing is allowed -- bail. + */ + if (team && + (/*team->Total >= Total || team->IsMoving ||*/ + team->Class->RecruitPriority >= Class->RecruitPriority)) { + return(false); + } + + /* + ** If the proper team index was not provided, then find it in the type type class. + ** On the chance that a match could not be found, then it is illegal to add this + ** object to this team -- return with failure flag. + */ + if (typeindex == -1) { + for (typeindex = 0; typeindex < Class->ClassCount; typeindex++) { + if (Class->Class[typeindex] == &obj->Class_Of()) { + break; + } + } + } + + /* + ** If the team is already full of this type, then adding the object is not allowed. + ** Return with a failure flag in this case. + */ + if (Quantity[typeindex] >= Class->DesiredNum[typeindex]) { + return(false); + } + + /* + ** All is ok to add the object to the team, but if the object is already part of + ** another team, then it must be removed from that team first. + */ + if (team) { + team->Remove(obj); + } + + /* + ** Actually add the object to the team. + */ + Quantity[typeindex]++; + obj->IsInitiated = (Member == NULL); + obj->Member = Member; + Member = obj; + obj->Team = this; + Total++; + Risk += obj->Risk(); + if (!Center) { + Calc_Center(Center, ObjectiveCenter); + } + + /* + ** Return with success, since the object was added to the team. + */ + IsAltered = true; + return(true); +} + + +/*********************************************************************************************** + * TeamClass::Remove -- Removes the specified object from the team. * + * * + * Use this routine to remove an object from a team. Objects removed from the team are * + * then available to be recruited by other teams, or even by the same team at a later time. * + * * + * INPUT: obj -- Pointer to the object that is to be removed from this team. * + * * + * typeindex-- Optional index of where this object type is specified in the type * + * type class. This parameter can be omitted. It only serves to make * + * the removal process faster. * + * * + * OUTPUT: bool; Was the object removed from this team? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/29/1994 JLB : Created. * + * 01/02/1995 JLB : Initiation tracking and team captain selection. * + *=============================================================================================*/ +bool TeamClass::Remove(FootClass * obj, int typeindex) +{ + Validate(); + /* + ** Make sure that the object is in fact a member of this team. If not, then it can't + ** be removed. Return success because the end result is the same. + */ + if (obj->Team != this) { + return(true); + } + + /* + ** If the proper team index was not provided, then find it in the type type class. The + ** team type class will not be set if the appropriate type could not be found + ** for this object. This indicates that the object was illegally added. Continue to + ** process however, since removing this object from the team is a good idea. + */ + if (typeindex == -1) { + for (typeindex = 0; typeindex < Class->ClassCount; typeindex++) { + if (Class->Class[typeindex] == &obj->Class_Of()) { + break; + } + } + } + + /* + ** Decrement the counter for the team class. There is now one less of this object type. + */ + if ((unsigned)typeindex < Class->ClassCount) { + Quantity[typeindex]--; + } + + /* + ** Actually remove the object from the team. Scan through the team members + ** looking for the one that matches the one specified. If it is found, it + ** is unlinked from the member chain. During this scan, a check is made to + ** ensure that at least one remaining member is still initiated. If not, then + ** a new team captain must be chosen. + */ + bool initiated = false; + FootClass * prev = 0; + FootClass * curr = Member; + bool found = false; + while (curr && (!found || !initiated)) { + if (curr == obj) { + if (prev) { + prev->Member = curr->Member; + } else { + Member = curr->Member; + } + FootClass * temp = curr->Member; + curr->Member = 0; + curr->Team = 0; + curr = temp; + Total--; + found = true; + Risk -= obj->Risk(); + continue; + } + + /* + ** If this (remaining) member is initiated, then keep a record of this. + */ + initiated |= curr->IsInitiated; + + prev = curr; + curr = curr->Member; + } + + /* + ** If, after removing the team member, there are no initiated members left + ** in the team, then just make the first remaining member of the team the + ** team captain. Mark the center location of the team as invalid so that + ** it will be centered around the captain. + */ + if (!initiated && Member) { + Member->IsInitiated = true; + Center = 0; + } + + /* + ** Must record that the team composition has changed. At the next opportunity, + ** the team members will be counted and appropriate AI adjustments made. + */ + IsAltered = true; + return(true); +} + + +/*********************************************************************************************** + * TeamClass::Recruit -- Attempts to recruit members to the team for the given index ID. * + * * + * This routine will take the given index ID and scan for available objects of that type * + * to recruit to the team. Recruiting will continue until that object type has either * + * been exhausted or if the team's requirment for that type has been filled. * + * * + * INPUT: typeindex -- The index for the object type to recruit. The index is used to * + * look into the type type's array of object types that make up this * + * team. * + * * + * OUTPUT: Returns with the number of objects added to this team. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/29/1994 JLB : Created. * + * 04/10/1995 JLB : Scans for units too. * + *=============================================================================================*/ +int TeamClass::Recruit(int typeindex) +{ + Validate(); + int added = 0; // Total number added to team. + + /* + ** Quick check to see if recruiting is really allowed for this index or not. + */ + if (Class->DesiredNum[typeindex] > Quantity[typeindex]) { + + /* + ** For infantry objects, sweep through the infantry in the game looking for + ** ones owned by the house that owns the team. When found, try to add. + */ + if (Class->Class[typeindex]->What_Am_I() == RTTI_INFANTRYTYPE) { + + for (int index = 0; index < Infantry.Count(); index++) { + InfantryClass * infantry = Infantry.Ptr(index); + + if (infantry->House == House && infantry->Class == Class->Class[typeindex]) { + if (Add(infantry, typeindex)) { + added++; + } + } + + /* + ** If there is sufficient quantity of this type of object recruited to the + ** team, then abort further scanning for members. + */ + if (Quantity[typeindex] >= Class->DesiredNum[typeindex]) { + break; + } + } + } + + if (Class->Class[typeindex]->What_Am_I() == RTTI_UNITTYPE) { + + for (int index = 0; index < Units.Count(); index++) { + UnitClass * unit = Units.Ptr(index); + + if (unit->House == House && unit->Class == Class->Class[typeindex]) { + if (Add(unit, typeindex)) { + added++; + + /* + ** If a transport is added to the team, the occupants + ** are added by default. + */ + FootClass * f = unit->Attached_Object(); + while (f) { + Add(f); + f = (FootClass *)f->Next; + } + } + } + + /* + ** If there is sufficient quantity of this type of object recruited to the + ** team, then abort further scanning for members. + */ + if (Quantity[typeindex] >= Class->DesiredNum[typeindex]) { + break; + } + } + } + } + + return(added); +} + + +/*********************************************************************************************** + * TeamClass::Detach -- Removes specified target from team tracking. * + * * + * When a target object is about to be removed from the game (e.g., it was killed), then * + * any team that is looking at that target must abort from that target. * + * * + * INPUT: target -- The target object that is going to be removed from the game. * + * * + * all -- Is the target going away for good as opposed to just cloaking/hiding? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/29/1994 JLB : Created. * + *=============================================================================================*/ +void TeamClass::Detach(TARGET target, bool ) +{ + Validate(); + + /* + ** If the target to detatch matches the target of this team, then remove + ** the target from this team's Tar/Nav com and let the chips fall + ** where they may. + */ + if (Target == target) { + Target = TARGET_NONE; + } + if (MissionTarget == target) { + MissionTarget = TARGET_NONE; + } + +} + + +/*********************************************************************************************** + * TeamClass::As_Target -- Converts this team object into a target number. * + * * + * This routine is used by the save/load code to produce a persistant identifier for this * + * team object. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the team represented as a target number. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/14/1995 JLB : Created. * + *=============================================================================================*/ +TARGET TeamClass::As_Target(void) const +{ + Validate(); + return(Build_Target(KIND_TEAM, Teams.ID(this))); +} + + +/*********************************************************************************************** + * TeamClass::Calc_Center -- Determines average location of team members. * + * * + * Use this routine to calculate the "center" location of the team. This is the average * + * position of all members of the team. Using this center value it is possible to tell * + * if a team member is too far away and where to head to in order to group up. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the cell number of the team's center point. If the team contains * + * no members, then the return value will be zero. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/29/1994 JLB : Created. * + *=============================================================================================*/ +void TeamClass::Calc_Center(CELL ¢er, CELL &obj_center) const +{ + Validate(); + long x = 0; + long y = 0; + int dist = 0x7FFFFFFF; + int quantity = 0; + FootClass * unit; + + obj_center = 0; + center = 0; + + unit = Member; + while (unit) { + if (unit->IsInitiated && !unit->IsInLimbo) { + CELL c = Coord_Cell(unit->Center_Coord()); + if (unit->Distance(Target) < dist) { + dist = unit->Distance(Target); + obj_center = c; + } + x += Cell_X(c); + y += Cell_Y(c); + quantity++; + } + unit = unit->Member; + } + if (quantity) { + x /= quantity; + y /= quantity; + CELL cell = XY_Cell((int)x, (int)y); + center = cell; + } +} + + +/*********************************************************************************************** + * TeamClass::Took_Damage -- Informs the team when the team member takes damage. * + * * + * This routine is used when a team member takes damage. Usually the team will react in * + * some fashion to the attack. This reaction can range from running away to assigning this * + * new target as the team's target. * + * * + * INPUT: obj -- The team member that was damaged. * + * * + * result -- The severity of the damage taken. * + * * + * source -- The purpetrator of the damage. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/29/1994 JLB : Created. * + *=============================================================================================*/ +void TeamClass::Took_Damage(FootClass * , ResultType result, TechnoClass * source) +{ + Validate(); + if ((result != RESULT_NONE) && (!Class->IsSuicide)) { + if (!IsMoving) { + // Should run to a better hiding place or disband into a group of hunting units. + } else { + + if (source && !Is_A_Member(source) && Member && Member->What_Am_I() != RTTI_AIRCRAFT) { + if (Target != source->As_Target()) { + + /* + ** Don't change target if the team's target is one that can fire as well. There is + ** no point in endlessly shuffling between targets that have firepower. + */ + if (Target_Legal(Target)) { + TechnoClass * techno = As_Techno(Target); + + if (techno && ((TechnoTypeClass const &)techno->Class_Of()).Primary != WEAPON_NONE) { + if (techno->In_Range(Cell_Coord(Center), 0)) { + return; + } + } + } + Target = source->As_Target(); + } + } + } + } +} + + +/*********************************************************************************************** + * TeamClass::Coordinate_Attack -- Handles coordinating a team attack. * + * * + * This function is called when the team knows what it should attack. This routine will * + * give the necessary orders to the members of the team. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/06/1995 JLB : Created. * + *=============================================================================================*/ +void TeamClass::Coordinate_Attack(void) +{ + Validate(); + if (!Target_Legal(Target)) { + Target = MissionTarget; + } + + if (!Target_Legal(Target)) { + IsNextMission = true; + + } else { + + FootClass * unit = Member; + while (unit) { + + Coordinate_Conscript(unit); + + if (unit->IsInitiated && !unit->IsInLimbo) { + + if (unit->Mission != MISSION_ATTACK && unit->Mission != MISSION_ENTER && unit->Mission != MISSION_CAPTURE) { + unit->Assign_Mission(MISSION_ATTACK); + unit->Assign_Target(TARGET_NONE); + unit->Assign_Destination(TARGET_NONE); + } + + if (unit->TarCom != Target) { + unit->Assign_Target(Target); + } + } + + unit = unit->Member; + } + } +} + + +/*********************************************************************************************** + * TeamClass::Coordinate_Regroup -- Handles team idling (regrouping). * + * * + * This routine is called when the team must delay at its current location. Team members * + * are grouped together by this function. It is called when the team needs to sit and * + * wait. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/06/1995 JLB : Created. * + *=============================================================================================*/ +bool TeamClass::Coordinate_Regroup(void) +{ + Validate(); + FootClass * unit = Member; + bool retval = true; + + /* + ** Regroup default logic. + */ + while (unit) { + + Coordinate_Conscript(unit); + + if (unit->IsInitiated && !unit->IsInLimbo) { + + if (unit->Distance(Center) > STRAY_DISTANCE && (unit->Mission != MISSION_GUARD_AREA || !Target_Legal(unit->TarCom))) { + if (unit->Mission != MISSION_MOVE || !Target_Legal(unit->NavCom) || ::Distance(As_Cell(unit->NavCom), Center) > STRAY_DISTANCE) { + unit->Assign_Mission(MISSION_MOVE); + unit->Assign_Destination(::As_Target(Center)); + } + retval = false; + } else { + + /* + ** This unit has gotten close enough to the team center so that it is + ** now considered intiated. An initiated unit is considered when calculating + ** the center of the team. + */ + unit->IsInitiated = true; + + /* + ** The team is regrouping, so just sit here and wait. + */ + if (unit->Mission != MISSION_GUARD_AREA) { + unit->Assign_Mission(MISSION_GUARD); + unit->Assign_Destination(TARGET_NONE); + } + } + + } + + unit = unit->Member; + } + return(retval); +} + + +/*********************************************************************************************** + * TeamClass::Coordinate_Move -- Handles team movement coordination. * + * * + * This routine is called when the team must move to a new location. Movement and grouping * + * commands associated with this task are initiated here. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/06/1995 JLB : Created. * + *=============================================================================================*/ +void TeamClass::Coordinate_Move(void) +{ + Validate(); + FootClass * unit = Member; + bool finished = true; + + if (!Target_Legal(Target)) { + Target = MissionTarget; + } + + if (Target_Legal(Target)) { + + if (!Lagging_Units()) { + + + while (unit) { + + Coordinate_Conscript(unit); + + if (unit->IsInitiated && !unit->IsInLimbo) { + + if (unit->What_Am_I() != RTTI_AIRCRAFT && unit->Distance(Center) > STRAY_DISTANCE) { + IsLagging = true; + finished = false; + } else { + + if ((unit->Distance(Target)/ICON_LEPTON_W) > STRAY_DISTANCE || + (unit->What_Am_I() == RTTI_AIRCRAFT && + ((AircraftClass *)unit)->Altitude > 0 && + Class->MissionList[CurrentMission+1].Mission != TMISSION_MOVE)) { + + if (unit->Mission != MISSION_MOVE) { + unit->Assign_Mission(MISSION_MOVE); + } + if (unit->NavCom != Target) { + unit->Assign_Destination(Target); + } + finished = false; + } else { + if (unit->Mission == MISSION_MOVE && !Target_Legal(unit->NavCom)) { + unit->Enter_Idle_Mode(); + } + } + } + } + + unit = unit->Member; + } + } else { + finished = false; + } + } + + /* + ** If all the team members are close enough to the desired destination, then + ** move to the next mission. + */ + if (finished && IsMoving) { + IsNextMission = true; + } +} + + +/*************************************************************************** + * Lagging_Units -- processes units that cant keep up with the pack * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * HISTORY: * + * 08/01/1995 PWG : Created. * + *=========================================================================*/ +bool TeamClass::Lagging_Units(void) +{ + Validate(); + FootClass * unit = Member; + bool lag = false; + + /* + ** If the IsLagging bit is not set, then obviously there are no lagging + ** units. + */ + if (!IsLagging) return(false); + + /* + ** Scan through all of the units, searching for units who are having + ** trouble keeping up with the pack. + */ + while (unit) { + + if (!unit->IsInLimbo) { + /* + ** If we find a unit who has fallen to far away from the center of + ** the pack, then we need to order that unit to catch up with the + ** first unit. + */ + if (unit->Distance(ObjectiveCenter) > STRAY_DISTANCE) { + if (unit->Mission != MISSION_MOVE || !Target_Legal(unit->NavCom) || ::Distance(As_Cell(unit->NavCom), ObjectiveCenter) > STRAY_DISTANCE) { + unit->Assign_Mission(MISSION_MOVE); + unit->Assign_Destination(::As_Target(ObjectiveCenter)); + } + lag = true; + } else { + /* + ** We need to order all of the other units to hold there + ** position until all lagging units catch up. + */ + unit->Assign_Mission(MISSION_GUARD); + unit->Assign_Destination(TARGET_NONE); + } + } + unit = unit->Member; + } + + /* + ** Once we have handled the loop we know whether there are any lagging + ** units or not. + */ + IsLagging = lag; + return(lag); +} + + +/*********************************************************************************************** + * TeamClass::Coordinate_Unload -- Tells the team to unload passengers now. * + * * + * This routine tells all transport vehicles to unload passengers now. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/14/1995 JLB : Created. * + *=============================================================================================*/ +void TeamClass::Coordinate_Unload(void) +{ + Validate(); + FootClass * unit = Member; + bool finished = true; + + while (unit) { + + Coordinate_Conscript(unit); + + if (unit->IsInitiated && !unit->IsInLimbo) { + if (unit->Is_Something_Attached()) { + + /* + ** Loaner transports will break off of the team at this time. The normal + ** unload logic for the transport will proceed normally. The rest of the team + ** members will be in a dormant state until they are unloaded. + */ + if (unit->IsALoaner) { + Remove(unit); + unit->Commence(); + unit->Assign_Mission(MISSION_UNLOAD); + unit->Assign_Destination(Target); + } else { + if (unit->Mission != MISSION_UNLOAD) { + unit->Assign_Mission(MISSION_UNLOAD); + unit->Assign_Destination(Target); + } + } + finished = false; + } + } + + unit = unit->Member; + } + + if (finished) { + IsNextMission = true; + } +} + + +/*********************************************************************************************** + * TeamClass::Coordinate_Conscript -- Gives orders to new recruit. * + * * + * This routine will give the movement orders to the conscript so that it will group * + * with the other members of the team. * + * * + * INPUT: unit -- Pointer to the conscript unit. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/06/1995 JLB : Created. * + *=============================================================================================*/ +void TeamClass::Coordinate_Conscript(FootClass * unit) +{ + Validate(); + if (unit && !unit->IsInitiated && !unit->IsInLimbo) { + if (unit->Distance(Center) > STRAY_DISTANCE) { + if (!Target_Legal(unit->NavCom)) { + unit->Assign_Mission(MISSION_MOVE); + unit->Assign_Target(TARGET_NONE); + unit->Assign_Destination(::As_Target(Center)); + } + } else { + + /* + ** This unit has gotten close enough to the team center so that it is + ** now considered intiated. An initiated unit is considered when calculating + ** the center of the team. + */ + unit->IsInitiated = true; + } + } +} + + +/*************************************************************************** + * TeamClass::Is_A_Member -- Tests if a unit is a member of a team * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: * + * * + * HISTORY: * + * 05/16/1995 PWG : Created. * + *=========================================================================*/ +bool TeamClass::Is_A_Member(void const * who) const +{ + Validate(); + FootClass * unit = Member; + while (unit) { + if (unit == who) { + return(true); + } + unit = unit->Member; + } + return(false); +} + + +/*************************************************************************** + * TeamClass::Suspend_Teams -- Suspends activity for low priority teams * + * * + * INPUT: int priority - determines what is considered low priority. * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 06/19/1995 PWG : Created. * + *=========================================================================*/ +void TeamClass::Suspend_Teams(int priority) +{ + for (int index = 0; index < Teams.Count(); index++) { + TeamClass *team = Teams.Ptr(index); + + /* + ** If a team is below the "survival priority level", then it gets + ** destroyed. The team members are then free to be reassigned. + */ + if (team && team->Class->RecruitPriority < priority) { + FootClass * unit = team->Member; + while (team->Member) { + team->Remove(team->Member); + } + team->IsAltered = true; + team->SuspendTimer = TICKS_PER_MINUTE*2; + team->Suspended = true; + } + } +} diff --git a/TEAM.H b/TEAM.H new file mode 100644 index 0000000..b8ffc5c --- /dev/null +++ b/TEAM.H @@ -0,0 +1,249 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\team.h_v 2.16 16 Oct 1995 16:48:04 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TEAM.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 12/11/94 * + * * + * Last Update : December 11, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef TEAM_H +#define TEAM_H + +#include +#include "teamtype.h" +#include "abstract.h" + +/* +** Units are only allowed to stray a certain distance away from their +** team. When they exceed this distance, some sort of fixup must be +** done. +*/ +#define STRAY_DISTANCE 2 + +class TeamClass : public AbstractClass +{ + public: + /* + ** This specifies the type of team this is. + */ + TeamTypeClass const * const Class; + + /* + ** This specifies the owner of this team. + */ + HouseClass * const House; + + /* + ** This flag forces the team into active state regardless of whether it + ** is understrength or not. + */ + unsigned IsForcedActive:1; + + /* + ** This flag is set to true when the team initiates into active mode. The + ** flag is never cleared. By examining this flag, it is possible to determine + ** if the team has ever launched into active mode. + */ + unsigned IsHasBeen:1; + + /* + ** If the team is full strength, then this flag is true. A full strength + ** team will not try to recruit members. + */ + unsigned IsFullStrength:1; + + /* + ** A team that is below half strength has this flag true. It means that the + ** the team should hide back at the owner's base and try to recruit + ** members. + */ + unsigned IsUnderStrength:1; + + /* + ** If a team is not understrength but is not yet full strength, then + ** the team is regrouping. If this flag is set and the team becomes + ** full strength, the all members of the team will become initiated + ** and this flag will be reset. + */ + unsigned IsReforming:1; + + /* + ** This bit should be set if a team is determined to have lagging + ** units in its formation. + */ + unsigned IsLagging:1; + + private: + /* + ** If a team member was removed or added, then this flag will be set to true. The + ** team system uses this flag to tell whether it should recalculate the team + ** under strength or full strength flags. This process does not need to occur + ** EVERY time a unit added or deleted from a team, just every so often if the + ** team has been changed. + */ + unsigned IsAltered:1; + + /* + ** If the team is working on it's primary mission (it is past the build up stage) + ** then this flag will be true. The transition between "moving" and "stationary" + ** stages usually requires some action on the team's part. + */ + unsigned IsMoving:1; + + /* + ** When the team determines that the next mission should be advanced to, it will + ** set this flag to true. Mission advance will either change the behavior of the + ** team or cause it to disband. + */ + unsigned IsNextMission:1; + /* + ** Records whether the team is suspended from production. + */ + unsigned Suspended:1; + + public: + /* + ** A team will have a center point. This is the point used to determine if + ** any member of the team is "too far" from the team and must return. This + ** center point is usually calculated as the average position of all the + ** team members. + */ + CELL Center; + CELL ObjectiveCenter; + + /* + ** This is the target of the team. Typically, it is a unit or structure, but + ** for the case of teams with a movement mission, it might represent a + ** destination cell. + */ + TARGET MissionTarget; + TARGET Target; + + /* + ** This is the total number of members in this team. + */ + int Total; + + /* + ** This is the teams combined risk value + */ + int Risk; + /* + ** This is the amount of time the team is suspended for. + */ + TCountDownTimerClass SuspendTimer; + + //------------------------------------------------------------ + TeamClass(void) : Class(0), House(0) {IsActive=false;Member=0;IsAltered=true;}; + TeamClass(TeamTypeClass const * team, HouseClass * owner); + virtual ~TeamClass(void); + virtual RTTIType What_Am_I(void) const {return RTTI_TEAM;}; + static void operator delete(void *ptr); + static void * operator new(size_t size); + static void Init(void); + static void Suspend_Teams(int priority); + + TARGET As_Target(void) const; + + /* + ** File I/O. + */ + bool Load(FileClass & file); + bool Save(FileClass & file); + void Code_Pointers(void); + void Decode_Pointers(void); + + void Force_Active(void) {IsForcedActive = true;IsUnderStrength=false;}; + bool Remove(FootClass *, int typeindex=-1); + void Detach(TARGET target, bool all); + void AI(void); + void Took_Damage(FootClass * obj, ResultType result, TechnoClass * source); + bool Add(FootClass *, int typeindex=-1); + void Assign_Mission_Target(TARGET new_target); + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + /* + ** This is a record of the current number of active teams of each + ** type. It can range from zero to MaxAllowed. + */ + static unsigned char Number[TEAMTYPE_MAX]; + + private: + + /* + ** The current mission index into the mission list is recorded here. + */ + int CurrentMission; + + /* + ** Some missions will time out. This is the timer that keeps track of the + ** time to transition between missions. + */ + TCountDownTimerClass TimeOut; + + void Coordinate_Unload(void); + bool Coordinate_Regroup(void); + void Coordinate_Attack(void); + void Coordinate_Move(void); + void Coordinate_Conscript(FootClass * unit); +// void Control(FootClass *, bool initial=false); + void Calc_Center(CELL ¢er, CELL &obj_center) const; + int Recruit(int typeindex); + bool Is_A_Member(void const * who) const; + bool Lagging_Units(void); + + /* + ** Points to the first member in the list of members for this team. + */ + FootClass * Member; + + unsigned char Quantity[TeamTypeClass::MAX_TEAM_CLASSCOUNT]; + + /* + ** This records the success of each team type. As the team carries out its + ** mission, it increments this counter if it considers the mission + ** to have been successfully completed. Teams with greater success + ** will be created more than the others. + */ + static unsigned char Success[TEAMTYPE_MAX]; + + /* + ** This contains the value of the Virtual Function Table Pointer + */ + static void * VTable; +}; + +#endif diff --git a/TEAMTYPE.CPP b/TEAMTYPE.CPP new file mode 100644 index 0000000..39c50bf --- /dev/null +++ b/TEAMTYPE.CPP @@ -0,0 +1,978 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\teamtype.cpv 2.17 16 Oct 1995 16:48:52 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TEAMTYPE.CPP * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 7, 1994 * + * * + * Last Update : July 21, 1995 [JLB] * + * * + *-------------------------------------------------------------------------* + * Functions: * + * TeamTypeClass::TeamTypeClass -- class constructor * + * TeamTypeClass::~TeamTypeClass -- class destructor * + * TeamTypeClass::Init -- pre-scenario initialization * + * TeamTypeClass::Read_INI -- reads INI data * + * TeamTypeClass::Write_INI -- writes INI data * + * TeamTypeClass::Read_Old_INI -- reads old INI format * + * TeamTypeClass::As_Pointer -- gets ptr for team type with given name * + * TeamTypeClass::Remove -- removes this team from the system * + * TeamTypeClass::Mission_From_Name -- returns mission for given name * + * TeamTypeClass::Name_From_Mission -- returns name for given mission * + * TeamTypeClass::operator new -- 'new' operator * + * TeamTypeClass::operator delete -- 'delete' operator * + * TeamTypeClass::Suggested_New_Team -- Suggests a new team to create. * + * TeamTypeClass::Validate -- validates teamtype pointer * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/* +********************************** Globals ********************************** +*/ +char const * TeamTypeClass::TMissions[TMISSION_COUNT] = { + "Attack Base", + "Attack Units", + "Attack Civil.", + "Rampage", + "Defend Base", +// "Harvest", + "Move", + "Move to Cell", + "Retreat", + "Guard", + "Loop", + "Attack Tarcom", + "Unload", +}; + +/* +** This contains the value of the Virtual Function Table Pointer +*/ +void * TeamTypeClass::VTable; + + +/*********************************************************************************************** + * TeamTypeClass::Validate -- validates teamtype pointer * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int TeamTypeClass::Validate(void) const +{ + int num; + + num = TeamTypes.ID(this); + if (num < 0 || num >= TEAMTYPE_MAX) { + Validate_Error("TEAMTYPE"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*************************************************************************** + * TeamTypeClass::TeamTypeClass -- class constructor * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 12/07/1994 BR : Created. * + *=========================================================================*/ +TeamTypeClass::TeamTypeClass(void) +{ + IsPrebuilt = true; + IsReinforcable = true; + IsRoundAbout = false; + IsLearning = false; + IsSuicide = false; + IsAutocreate = false; + IsTransient = false; + IsMercenary = false; + RecruitPriority = 7; + MaxAllowed = 0; + Fear = 0; + InitNum = 0; + House = HOUSE_NONE; + MissionCount = 0; + IniName[0] = '\0'; + ClassCount = 0; + for (int i = 0; i < MAX_TEAM_CLASSCOUNT; i++) { + Class[i] = NULL; + DesiredNum[i] = 0; + } +} + + +/*************************************************************************** + * TeamTypeClass::Init -- pre-scenario initialization * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 12/07/1994 BR : Created. * + *=========================================================================*/ +void TeamTypeClass::Init(void) +{ + TeamTypeClass *ptr; + + TeamTypes.Free_All(); + + ptr = new TeamTypeClass(); + VTable = ((void **)(((char *)ptr) + sizeof(AbstractTypeClass) - 4))[0]; + delete ptr; +} + + +/*************************************************************************** + * TeamTypeClass::Read_INI -- reads INI data * + * * + * INI entry format: * + * TeamName = Housename,Roundabout,Learning,Suicide,Spy,Mercenary, * + * RecruitPriority,MaxAllowed,InitNum,Fear, * + * ClassCount,Class:Num,Class:Num,..., * + * MissionCount,Mission:Arg,Mission:Arg,Mission:Arg,... * + * * + * INPUT: * + * buffer buffer to hold the INI data * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/07/1994 BR : Created. * + * 02/01/1995 BR : No del team if no classes (editor needs empty teams!) * + *=========================================================================*/ +void TeamTypeClass::Read_INI(char *buffer) +{ + TeamTypeClass * team; // Working team pointer. + char *tbuffer; // Accumulation buffer of team names. + int len; // Length of data in buffer. + char buf[500]; // INI entry buffer + + /*------------------------------------------------------------------------ + Set 'tbuffer' to point just past the INI buffer + ------------------------------------------------------------------------*/ + len = strlen(buffer) + 2; + tbuffer = buffer + len; + + /*------------------------------------------------------------------------ + Read all TeamType entry names into 'tbuffer' + ------------------------------------------------------------------------*/ + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-len, buffer); + + /* + ----------------------- Loop for all team entries ------------------------ + */ + while (*tbuffer != '\0') { + /* + ....................... Create a new team type ........................ + */ + team = new TeamTypeClass(); + + /* + ......................... Get the team entry .......................... + */ + WWGetPrivateProfileString(INI_Name(), tbuffer, NULL, buf, sizeof(buf)-1, buffer); + + /* + .......................... Fill the team in ........................... + */ + team->Fill_In(tbuffer, buf); + + /* + ...................... Go to the next INI entry ....................... + */ + tbuffer += strlen(tbuffer)+1; + } + + /* + ** If no teams were read in, try reading the old INI format. + */ + if (TeamTypes.Count()==0) { + Read_Old_INI(buffer); + } +} + + +/*********************************************************************************************** + * TeamTypeClass::Fill_In -- fills in trigger from the given INI entry * + * * + * This routine fills in the given teamtype with the given name, and values from * + * the given INI entry. * + * * + * (This routine is used by the scenario editor, to import teams from the MASTER.INI file.) * + * * + * INI entry format: * + * TeamName = Housename,Roundabout,Learning,Suicide,Spy,Mercenary, * + * RecruitPriority,MaxAllowed,InitNum,Fear, * + * ClassCount,Class:Num,Class:Num,..., * + * MissionCount,Mission:Arg,Mission:Arg,Mission:Arg,... * + * * + * INPUT: * + * name mnemonic for the desired trigger * + * entry INI entry to parse * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/28/1994 BR : Created. * + *=============================================================================================*/ +void TeamTypeClass::Fill_In(char * name, char *entry) +{ + Validate(); + int num_classes; + char *p1; // parsing pointer + char *p2; // parsing pointer + int i; // loop counter + TechnoTypeClass const *otype; // ptr to type of object + InfantryType i_id; // infantry ID + UnitType u_id; // unit ID + AircraftType a_id; // aircraft ID + TeamMissionStruct mission; + + /* + ------------------------------ Set its name ------------------------------ + */ + Set_Name(name); + + /* + ---------------------------- 1st token: House ---------------------------- + */ + House = HouseTypeClass::From_Name(strtok(entry,",")); + + /* + -------------------------- 2nd token: RoundAbout ------------------------- + */ + IsRoundAbout = atoi(strtok(NULL,",")); + + /* + --------------------------- 3rd token: Learning -------------------------- + */ + IsLearning = atoi(strtok(NULL,",")); + + /* + --------------------------- 4th token: Suicide --------------------------- + */ + IsSuicide = atoi(strtok(NULL,",")); + + /* + ----------------------------- 5th token: Spy ----------------------------- + */ + IsAutocreate = atoi(strtok(NULL,",")); + + /* + -------------------------- 6th token: Mercenary -------------------------- + */ + IsMercenary = atoi(strtok(NULL,",")); + + /* + ----------------------- 7th token: RecruitPriority ----------------------- + */ + RecruitPriority = atoi(strtok(NULL,",")); + + /* + -------------------------- 8th token: MaxAllowed ------------------------- + */ + MaxAllowed = atoi(strtok(NULL,",")); + + /* + --------------------------- 9th token: InitNum --------------------------- + */ + InitNum = atoi(strtok(NULL,",")); + + /* + ------------------------- 10th token: Fear level ------------------------- + */ + Fear = atoi(strtok(NULL,",")); + + /* + ------------------------ 11th token: Class count ------------------------- + */ + num_classes = atoi(strtok(NULL,",")); + + /* + -------------- Loop through entries, setting class ptr & num ------------- + */ + ClassCount = 0; + for (i = 0; i < num_classes; i++) { + p1 = strtok(NULL,",:"); + p2 = strtok(NULL,",:"); + otype = NULL; + + /* + ------------------- See if this is an infantry name ------------------- + */ + i_id = InfantryTypeClass::From_Name(p1); + if (i_id != INFANTRY_NONE) { + otype = &InfantryTypeClass::As_Reference(i_id); + } + + /* + ---------------------- See if this is a unit name --------------------- + */ + u_id = UnitTypeClass::From_Name(p1); + if (u_id != UNIT_NONE) { + otype = &UnitTypeClass::As_Reference(u_id); + } + + /* + ------------------- See if this is an aircraft name ------------------- + */ + a_id = AircraftTypeClass::From_Name(p1); + if (a_id != AIRCRAFT_NONE) { + otype = &AircraftTypeClass::As_Reference(a_id); + } + + /* + --------------- If the name was resolved, add this class -------------- + */ + if (otype) { + Class[ClassCount] = otype; + DesiredNum[ClassCount] = atoi(p2); + ClassCount++; + } + } + + /* + ----------------------- next token: Mission count ------------------------ + */ + MissionCount = atoi(strtok(NULL,",")); + + for (i = 0; i < MissionCount; i++) { + p1 = strtok(NULL,",:"); + p2 = strtok(NULL,",:"); + mission.Mission = Mission_From_Name(p1); + mission.Argument = atoi(p2); + MissionList[i] = mission; + } + + char * ptr = strtok(NULL, ","); + if (ptr) { + IsReinforcable = atoi(ptr); + } + ptr = strtok(NULL, ","); + if (ptr) { + IsPrebuilt = atoi(ptr); + } +} + + +/*************************************************************************** + * TeamTypeClass::Write_INI -- writes INI data * + * * + * INI entry format: * + * TeamName = Housename,Roundabout,Learning,Suicide,Spy,Mercenary, * + * RecruitPriority,MaxAllowed,InitNum,Fear, * + * ClassCount,Class:Num,Class:Num,..., * + * MissionCount,Mission,Arg,Mission,Arg,Mission,Arg,... * + * * + * INPUT: * + * buffer buffer to store INI data in * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/07/1994 BR : Created. * + *=========================================================================*/ +void TeamTypeClass::Write_INI(char *buffer, bool refresh) +{ + int index; + int i; + char buf[500]; + TeamTypeClass * team; + char const *hname; + + /*------------------------------------------------------------------------ + First, clear out all existing teamtypes in the old-style format. + ------------------------------------------------------------------------*/ + WWWritePrivateProfileString("Teams", NULL, NULL, buffer); + + /*------------------------------------------------------------------------ + Clear out all existing teamtype data from the INI file. + ------------------------------------------------------------------------*/ + if (refresh) { + WWWritePrivateProfileString(INI_Name(), NULL, NULL, buffer); + } + + /*------------------------------------------------------------------------ + Now write all the team data out + ------------------------------------------------------------------------*/ + buf[0] = 0; + for (index = 0; index < TeamTypes.Count(); index++) { + /* + .................. Get ptr to next active teamtype .................... + */ + team = TeamTypes.Ptr(index); + + /* + .......................... Find house's name .......................... + */ + if (team->House==HOUSE_NONE) { + hname = "None"; + } else { + hname = HouseClass::As_Pointer(team->House)->Class->IniName; + } + + /* + ......................... Generate INI entry .......................... + */ + sprintf(buf,"%s,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + hname, + team->IsRoundAbout, + team->IsLearning, + team->IsSuicide, + team->IsAutocreate, + team->IsMercenary, + team->RecruitPriority, + team->MaxAllowed, + team->InitNum, + team->Fear, + team->ClassCount); + + /*..................................................................... + For every class in the team, record the class's name & desired count + .....................................................................*/ + for (i = 0; i < team->ClassCount; i++) { + sprintf (buf + strlen(buf), ",%s:%d", + team->Class[i]->IniName, + team->DesiredNum[i]); + } + + /*..................................................................... + Record the # of missions, and each mission name & argument value. + .....................................................................*/ + sprintf(buf + strlen(buf),",%d",team->MissionCount); + for (i = 0; i < team->MissionCount; i++) { + sprintf (buf + strlen(buf), ",%s:%d", + Name_From_Mission(team->MissionList[i].Mission), + team->MissionList[i].Argument); + } + + if (team->IsReinforcable) { + strcat(buf, ",1"); + } else { + strcat(buf, ",0"); + } + if (team->IsPrebuilt) { + strcat(buf, ",1"); + } else { + strcat(buf, ",0"); + } + + WWWritePrivateProfileString(INI_Name(), team->IniName, buf, buffer); + } +} + + +/*************************************************************************** + * TeamTypeClass::Read_Old_INI -- reads old INI format * + * * + * INI entry format: * + * TeamName = Housename,Roundabout,Learning,Suicide,Spy,Mercenary, * + * RecruitPriority,MaxAllowed,InitNum,Class:Num,Class:Num,...,Fear * + * * + * INPUT: * + * buffer buffer to hold the INI data * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/07/1994 BR : Created. * + * 02/01/1995 BR : No del team if no classes (editor needs empty teams!) * + *=========================================================================*/ +void TeamTypeClass::Read_Old_INI(char *buffer) +{ + TeamTypeClass * team; // Working team pointer. + char *tbuffer; // Accumulation buffer of team names. + int len; // Length of data in buffer. + char buf[256]; // INI entry buffer + char *p1; // parsing pointer + char *p2; // parsing pointer + int index; + TechnoTypeClass const *otype; // ptr to type of object + InfantryType i_id; // infantry ID + UnitType u_id; // unit ID + AircraftType a_id; // infantry ID + + /*------------------------------------------------------------------------ + Set 'tbuffer' to point just past the INI buffer + ------------------------------------------------------------------------*/ + len = strlen(buffer) + 2; + tbuffer = buffer + len; + + /*------------------------------------------------------------------------ + Read all TeamType entry names into 'tbuffer' + ------------------------------------------------------------------------*/ + WWGetPrivateProfileString("Teams", NULL, NULL, tbuffer, ShapeBufferSize-len, buffer); + + /* + ----------------------- Loop for all team entries ------------------------ + */ + while (*tbuffer != '\0') { + /* + ........................ Create a new trigger ......................... + */ + team = new TeamTypeClass(); + + /* + ............................ Set its name ............................. + */ + team->Set_Name(tbuffer); + + /* + ......................... Get the team entry .......................... + */ + WWGetPrivateProfileString("Teams", tbuffer, NULL, buf, sizeof(buf)-1, buffer); + + /* + .......................... 1st token: House ........................... + */ + team->House = HouseTypeClass::From_Name(strtok(buf,",")); + + /* + ........................ 2nd token: RoundAbout ........................ + */ + team->IsRoundAbout = atoi(strtok(NULL,",")); + + /* + ......................... 3rd token: Learning ......................... + */ + team->IsLearning = atoi(strtok(NULL,",")); + + /* + ......................... 4th token: Suicide .......................... + */ + team->IsSuicide = atoi(strtok(NULL,",")); + + /* + ........................... 5th token: Spy ............................ + */ + team->IsAutocreate = atoi(strtok(NULL,",")); + + /* + ........................ 6th token: Mercenary ......................... + */ + team->IsMercenary = atoi(strtok(NULL,",")); + + /* + ..................... 7th token: RecruitPriority ...................... + */ + team->RecruitPriority = atoi(strtok(NULL,",")); + + /* + ........................ 8th token: MaxAllowed ........................ + */ + team->MaxAllowed = atoi(strtok(NULL,",")); + + /* + ......................... 9th token: InitNum .......................... + */ + team->InitNum = atoi(strtok(NULL,",")); + + /* + ....................... 10th token: Mission name ...................... + */ + strtok(NULL,","); // just throw it away + + /* + ............ Loop through entries, setting class ptr & num ............ + */ + index = 0; + p1 = strtok(NULL,",:"); + p2 = strtok(NULL,",:"); + while (p1 && p2) { + otype = NULL; + + /* + ................. See if this is an infantry name .................. + */ + i_id = InfantryTypeClass::From_Name(p1); + if (i_id!=INFANTRY_NONE) { + otype = &InfantryTypeClass::As_Reference(i_id); + } + + /* + .................... See if this is a unit name .................... + */ + u_id = UnitTypeClass::From_Name(p1); + if (u_id != UNIT_NONE) { + otype = &UnitTypeClass::As_Reference(u_id); + } + + /* + ................. See if this is an aircraft name .................. + */ + a_id = AircraftTypeClass::From_Name(p1); + if (a_id != AIRCRAFT_NONE) { + otype = &AircraftTypeClass::As_Reference(a_id); + } + + /* + ............. If the name was resolved, add this class ............. + */ + if (otype) { + team->Class[index] = otype; + team->DesiredNum[index] = atoi(p2); + index++; + team->ClassCount = index; + } + + /* + ................. Go to the next entry on the line ................. + */ + p1 = strtok(NULL,",:"); + p2 = strtok(NULL,",:"); + } + + team->Fear = 0; + + /* + ...................... Go to the next INI entry ....................... + */ + tbuffer += strlen(tbuffer)+1; + } +} + + +/*************************************************************************** + * TeamTypeClass::As_Pointer -- gets ptr for team type with given name * + * * + * INPUT: * + * name name of teamtype * + * * + * OUTPUT: * + * ptr to TeamType with that name * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/07/1994 BR : Created. * + *=========================================================================*/ +TeamTypeClass * TeamTypeClass::As_Pointer(char *name) +{ + int i; + + if (name == NULL) { + return(NULL); + } + + for (i = 0; i < TeamTypes.Count(); i++) { + if (!stricmp(name, TeamTypes.Ptr(i)->IniName)) { + return(TeamTypes.Ptr(i)); + } + } + + return(NULL); +} + + +/*************************************************************************** + * TeamTypeClass::Remove -- removes this team from the system * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/09/1994 BR : Created. * + *=========================================================================*/ +void TeamTypeClass::Remove(void) +{ + Validate(); + int i; + TriggerClass * trigger; + + /* + ** Remove all trigger references to this team. + */ + for (i = 0; i < Triggers.Count(); i++) { + trigger = Triggers.Ptr(i); + if (trigger->Team == this) { + trigger->Team = NULL; + } + } + + /* + ** Delete myself. + */ + delete this; +} + + +/*************************************************************************** + * TeamTypeClass::Mission_From_Name -- returns team mission for given name * + * * + * INPUT: * + * name name to compare * + * * + * OUTPUT: * + * mission for that name * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/13/1994 BR : Created. * + *=========================================================================*/ +TeamMissionType TeamTypeClass::Mission_From_Name(char const *name) +{ + int order; + + if (name) { + for (order = TMISSION_FIRST; order < TMISSION_COUNT; order++) { + if (stricmp(TMissions[order], name) == 0) { + return((TeamMissionType) order); + } + } + } + + return(TMISSION_NONE); +} + + +/*************************************************************************** + * TeamTypeClass::Name_From_Mission -- returns name for given mission * + * * + * INPUT: * + * order mission to get name for * + * * + * OUTPUT: * + * name of mission * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/13/1994 BR : Created. * + *=========================================================================*/ +char const * TeamTypeClass::Name_From_Mission(TeamMissionType order) +{ + if (order <= TMISSION_NONE || order >= TMISSION_COUNT) { + return("None"); + } else { + return(TMissions[order]); + } +} + + +/*************************************************************************** + * TeamTypeClass::operator new -- 'new' operator * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * pointer to new TeamType * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/28/1994 BR : Created. * + *=========================================================================*/ +void * TeamTypeClass::operator new(size_t ) +{ + void * ptr = TeamTypes.Allocate(); + if (ptr) { + ((TeamTypeClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +/*************************************************************************** + * TeamTypeClass::operator delete -- 'delete' operator * + * * + * INPUT: * + * ptr pointer to delete * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/28/1994 BR : Created. * + *=========================================================================*/ +void TeamTypeClass::operator delete(void *ptr) +{ + if (ptr) { + ((TeamTypeClass *)ptr)->IsActive = false; + } + TeamTypes.Free((TeamTypeClass *)ptr); +} + + + +TeamClass * TeamTypeClass::Create_One_Of(void) const +{ + if (ScenarioInit || TeamClass::Number[TeamTypes.ID(this)] < MaxAllowed) { + return(new TeamClass(this, HouseClass::As_Pointer(House))); + } + return(NULL); +} + + +TARGET TeamTypeClass::As_Target(void) const +{ + Validate(); + return(Build_Target(KIND_TEAMTYPE, TeamTypes.ID(this))); +} + + +void TeamTypeClass::Destroy_All_Of(void) const +{ + for (int index = 0; index < Teams.Count(); index++) { + TeamClass * team = Teams.Ptr(index); + + if (team->Class == this) { + delete team; + index--; + } + } +} + + +/*********************************************************************************************** + * TeamTypeClass::Suggested_New_Team -- Suggests a new team to create. * + * * + * This routine will scan through the team types available and create teams of the * + * type that can best utilize the existing unit mix. * + * * + * INPUT: house -- Pointer to the house that this team is to be created for. * + * * + * utype -- A bit mask of the unit types available for this house. * + * * + * itypes -- A bit mask of the infantry types available for this house. * + * * + * alerted -- Is this house alerted? If true, then the Autocreate teams will be * + * considered in the selection process. * + * * + * OUTPUT: Returns with a pointer to the team type that should be created. If no team should * + * be created, then it returns NULL. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/13/1995 JLB : Created. * + * 07/21/1995 JLB : Will autocreate team even if no members in field. * + *=============================================================================================*/ +TeamTypeClass const * TeamTypeClass::Suggested_New_Team(HouseClass * house, long utypes, long itypes, bool alerted) +{ + TeamTypeClass const * best = NULL; + int bestvalue = 0; + + for (int index = 0; index < TeamTypes.Count(); index++) { + TeamTypeClass const * ttype = TeamTypes.Ptr(index); + + if (ttype && + ttype->House == house->Class->House && + TeamClass::Number[index] < ((alerted || !ttype->IsAutocreate) ? ttype->MaxAllowed : 0)) { + + /* + ** Determine what kind of units this team requires. + */ + long uneeded = 0; + long ineeded = 0; + for (int ctype = 0; ctype < ttype->ClassCount; ctype++) { + switch (ttype->Class[ctype]->What_Am_I()) { + case RTTI_INFANTRYTYPE: + ineeded |= (1 << ((InfantryTypeClass *)ttype->Class[ctype])->Type); + break; + + case RTTI_UNITTYPE: + uneeded |= (1 << ((UnitTypeClass *)ttype->Class[ctype])->Type); + break; + } + } + + /* + ** If this team can use the types required, then consider it a possible + ** team type to create. + */ + int value = 0; + if ((ineeded & itypes) || (uneeded & utypes)) { + value = ttype->RecruitPriority; + } else { + value = ttype->RecruitPriority/2; + } + + if (!best || bestvalue < value) { + bestvalue = value; + best = ttype; + } + } + } + + return(best); +} diff --git a/TEAMTYPE.H b/TEAMTYPE.H new file mode 100644 index 0000000..da2569b --- /dev/null +++ b/TEAMTYPE.H @@ -0,0 +1,259 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\teamtype.h_v 2.18 16 Oct 1995 16:45:40 JOE_BOSTIC $ */ +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TEAMTYPE.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : December 7, 1994 * + * * + * Last Update : December 7, 1994 [BR] * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef TEAMTYPE_H +#define TEAMTYPE_H + +/* +********************************** Defines ********************************** +*/ +//#define TEAMTYPE_MAX 20 // max # of different team types + +/* +** TeamMissionType: the various missions that a team can have. +*/ +typedef enum TeamMissionType { + TMISSION_NONE=-1, + TMISSION_ATTACKBASE, // Attack nearest enemy base. + TMISSION_ATTACKUNITS, // Attack all enemy units. + TMISSION_ATTACKCIVILIANS, // Attack all civilians + TMISSION_RAMPAGE, // attack & destroy anything that's not mine + TMISSION_DEFENDBASE, // Protect my base. +// TMISSION_HARVEST, // stake out a Tiberium claim, defend & harvest it + TMISSION_MOVE, // moves to waypoint specified. + TMISSION_MOVECELL, // moves to cell # specified. + TMISSION_RETREAT, // order given by superior team, for coordinating + TMISSION_GUARD, // works like an infantry's guard mission + TMISSION_LOOP, // loop back to start of mission list + TMISSION_ATTACKTARCOM, // attack tarcom + TMISSION_UNLOAD, // Unload at current location. + TMISSION_COUNT, + TMISSION_FIRST=0 +} TeamMissionType; + +/* +** Forward declarations. +*/ +class TechnoTypeClass; + + +/* +** This structure contains one team mission value & its argument. +*/ +typedef struct TeamMissionTag +{ + TeamMissionType Mission; + int Argument; +} TeamMissionStruct; + +/* +** TeamTypeClass declaration +*/ +class TeamTypeClass : public AbstractTypeClass +{ + public: + enum TeamTypeClassEnums { + MAX_TEAM_CLASSCOUNT=5, + MAX_TEAM_MISSIONS=20 + }; + + /* + ** Constructor/Destructor + */ + TeamTypeClass(void); + virtual ~TeamTypeClass(void) {}; + + /* + ** Initialization: clears all team types in preparation for new scenario + */ + static void Init(void); + + /* + ** File I/O routines + */ + static void Read_INI(char *buffer); + void Fill_In(char *name, char *entry); + static void Write_INI(char *buffer, bool refresh); + static void Read_Old_INI(char *buffer); + static char * INI_Name(void) {return "TeamTypes";}; + bool Load(FileClass & file); + bool Save(FileClass & file); + void Code_Pointers(void); + void Decode_Pointers(void); + + /* + ** As_Pointer gets a pointer to the trigger object give its name + */ + static TeamTypeClass *As_Pointer(char *name); + + /* + ** Processing routines + */ + void Remove(void); + TeamClass * Create_One_Of(void) const; + void Destroy_All_Of(void) const; + + /* + ** Utility routines + */ + static char const * Name_From_Mission(TeamMissionType order); + static TeamMissionType Mission_From_Name(char const *name); + static TeamTypeClass const * Suggested_New_Team(HouseClass * house, long utypes, long itypes, bool alerted); + + TARGET As_Target(void) const; + + /* + ** Overloaded operators + */ + void * operator new(size_t ); + void operator delete(void *ptr); + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + /* + ** If this teamtype object is active, then this flag will be true. + ** TeamType objects that are not active are either not yet created or have + ** been deleted after fulfilling their action. + */ + unsigned IsActive:1; + + /* + ** If RoundAbout, the team avoids high-threat areas + */ + unsigned IsRoundAbout:1; + + /* + ** If Learning, the team learns from mistakes + */ + unsigned IsLearning:1; + + /* + ** If Suicide, the team won't stop until it achieves its mission or it's + ** dead + */ + unsigned IsSuicide:1; + + /* + ** Is this team type allowed to be created automatically by the computer + ** when the appropriate trigger indicates? + */ + unsigned IsAutocreate:1; + + /* + ** Mercenaries will change sides if they start to lose. + */ + unsigned IsMercenary:1; + + /* + ** This flag tells the computer that it should build members to fill + ** a team of this type regardless of whether there actually is a team + ** of this type active. + */ + unsigned IsPrebuilt:1; + + /* + ** If this team should allow recruitment of new members, then this flag + ** will be true. A false value results in a team that fights until it + ** is dead. This is similar to IsSuicide, but they will defend themselves. + */ + unsigned IsReinforcable:1; + + /* + ** A transient team type was created exclusively to bring on reinforcements + ** as a result of some special event. As soon as there are no teams + ** existing of this type, then this team type should be deleted. + */ + unsigned IsTransient:1; + + /* + ** Priority given the team for recruiting purposes; higher priority means + ** it can steal members from other teams (scale: 0 - 15) + */ + int RecruitPriority; + + /* + ** Initial # of this type of team + */ + unsigned char InitNum; + + /* + ** Max # of this type of team allowed at one time + */ + unsigned char MaxAllowed; + + /* + ** Fear level of this team + */ + unsigned char Fear; + + /* + ** House the team belongs to + */ + HousesType House; + + /* + ** The mission list for this team + */ + int MissionCount; + TeamMissionStruct MissionList[MAX_TEAM_MISSIONS]; + + /* + ** Number of different classes in the team + */ + unsigned char ClassCount; + + /* + ** Array of object types comprising the team + */ + TechnoTypeClass const * Class[MAX_TEAM_CLASSCOUNT]; + + /* + ** Desired # of each type of object comprising the team + */ + unsigned char DesiredNum[MAX_TEAM_CLASSCOUNT]; + + private: + static char const * TMissions[TMISSION_COUNT]; + + /* + ** This contains the value of the Virtual Function Table Pointer + */ + static void * VTable; +}; + +#endif diff --git a/TECHNO.CPP b/TECHNO.CPP new file mode 100644 index 0000000..96f5443 --- /dev/null +++ b/TECHNO.CPP @@ -0,0 +1,3996 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\techno.cpv 2.13 02 Aug 1995 17:01:08 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : BASE.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 8, 1994 * + * * + * Last Update : August 23, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * TechnoClass::AI -- Handles AI processing for techno object. * + * TechnoClass::Assign_Destination -- Assigns movement destination to the object. * + * TechnoClass::Assign_Target -- Assigns the targeting computer with specified target. * + * TechnoClass::Base_Is_Attacked -- Handle panic response to base being attacked. * + * TechnoClass::Can_Fire -- Determines if this techno object can fire. * + * TechnoClass::Can_Player_Fire -- Determines if the player can give this object a fire order* + * TechnoClass::Can_Player_Move -- Determines if the object can move be moved by player. * + * TechnoClass::Can_Repair -- Determines if the object can and should be repaired. * + * TechnoClass::Captured -- Handles capturing this object. * + * TechnoClass::Clicked_As_Target -- Sets the flash count for this techno object. * + * TechnoClass::Crew_Type -- Fetches the kind of crew this object contains. * + * TechnoClass::Debug_Dump -- Displays the base class data to the monochrome screen. * + * TechnoClass::Desired_Load_Dir -- Fetches loading parameters for this object. * + * TechnoClass::Detach -- Handles removal of target from tracking system. * + * TechnoClass::Do_Cloak -- Start the object into cloaking stage. * + * TechnoClass::Do_Shimmer -- Causes this object to shimmer if it is cloaked. * + * TechnoClass::Do_Uncloak -- Cause the stealth tank to uncloak. * + * TechnoClass::Draw_It -- Draws the health bar (if necessary). * + * TechnoClass::Draw_Pips -- Draws the transport pips and other techno graphics. * + * TechnoClass::Enter_Idle_Mode -- Object enters its default idle condition. * + * TechnoClass::Evaluate_Cell -- Determine the value and object of specified cell. * + * TechnoClass::Evaluate_Object -- Determines score value of specified object. * + * TechnoClass::Exit_Object -- Causes specified object to leave this object. * + * TechnoClass::Find_Docking_Bay -- Searches for a close docking bay. * + * TechnoClass::Find_Exit_Cell -- Finds an appropriate exit cell for this object. * + * TechnoClass::Fire_At -- Fires projectile at target specified. * + * TechnoClass::Fire_Direction -- Fetches the direction projectile fire will take. * + * TechnoClass::Get_Ownable -- Fetches the ownable bits for this object. * + * TechnoClass::Greatest_Threat -- Determines best target given search criteria. * + * TechnoClass::Hidden -- Returns the object back into the hidden state. * + * TechnoClass::In_Range -- Determines if specified target is within weapon range. * + * TechnoClass::In_Range -- Determines if specified target is within weapon range. * + * TechnoClass::In_Range -- Determines if the specified coordinate is within range. * + * TechnoClass::Is_Techno -- Confirms that this is a TechnoClass object. * + * TechnoClass::Is_Weapon_Equipped -- Determines if this object has a combat weapon. * + * TechnoClass::Kill_Cargo -- Destroys any cargo attached to this object. * + * TechnoClass::Mark -- Handles marking of techno objects. * + * TechnoClass::Nearby_Location -- Radiates outward looking for clear cell nearby. * + * TechnoClass::Owner -- Who is the owner of this object? * + * TechnoClass::Per_Cell_Process -- Handles once-per-cell operations for techno type objects.* + * TechnoClass::Pip_Count -- Fetches the number of pips to display on this object. * + * TechnoClass::Player_Assign_Mission -- Assigns a mission as result of player input. * + * TechnoClass::Random_Animate -- Performs some idle animation for the object. * + * TechnoClass::Rearm_Delay -- Calculates the delay before firing can occur. * + * TechnoClass::Receive_Message -- Handles inbound message as appropriate. * + * TechnoClass::Record_The_Kill -- Records the death of this object. * + * TechnoClass::Remap_Table -- Fetches the appropriate remap table to use. * + * TechnoClass::Response_Attack -- Handles the voice response when given attack order. * + * TechnoClass::Response_Move -- Handles the voice repsonse to a movement request. * + * TechnoClass::Response_Select -- Handles the voice response when selected. * + * TechnoClass::Revealed -- Handles revealing an object to the house specified. * + * TechnoClass::Risk -- Fetches the risk associated with this object. * + * TechnoClass::Scatter -- Causes the object to scatter to an adjacent cell. * + * TechnoClass::Select -- Selects object and checks to see if can be selected. * + * TechnoClass::Set_Mission -- Forced mission set (used by editor). * + * TechnoClass::Stun -- Prepares the object for removal from the game. * + * TechnoClass::Take_Damage -- Records damage assessed to this object. * + * TechnoClass::Target_Something_Nearby -- Handles finding and assigning a nearby target. * + * TechnoClass::TechnoClass -- Constructor for techno type objects. * + * TechnoClass::TechnoClass -- Default constructor for techno objects. * + * TechnoClass::Techno_Draw_Object -- General purpose draw object routine. * + * TechnoClass::Threat_Range -- Returns the range to scan based on threat control. * + * TechnoClass::Tiberium_Load -- Fetches the current tiberium load percentage. * + * TechnoClass::Unlimbo -- Performs unlimbo process for all techno type objects. * + * TechnoClass::Value -- Fetches the target value for this object. * + * TechnoClass::Visual_Character -- Determine the visual character of the object. * + * TechnoClass::Weapon_Range -- Determines the maximum range for the weapon. * + * TechnoClass::What_Action -- Determines action to perform if cell is clicked on. * + * TechnoClass::What_Action -- Determines what action to perform if object is selected. * + * TechnoTypeClass::Cost_Of -- Fetches the cost of this object type. * + * TechnoTypeClass::Get_Cameo_Data -- Fetches the cameo image for this object type. * + * TechnoTypeClass::Get_Ownable -- Fetches the ownable bits for this object type. * + * TechnoTypeClass::Max_Passengers -- Fetches the maximum passengers allowed. * + * TechnoTypeClass::Repair_Cost -- Fetches the cost to repair one step. * + * TechnoTypeClass::Repair_Step -- Fetches the health to repair one step. * + * TechnoTypeClass::TechnoTypeClass -- Constructor for techno type objects. * + * TechnoTypeClass::Time_To_Build -- Fetches the time to build this object. * + * TechnoTypeClass::Raw_Cost -- Fetches the raw (base) cost of the object. * + * TechnoClass::Refund_Amount -- Returns with the money to refund if this object is sold. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*************************************************************************** +** Cloaking control values. +*/ +#define MAX_UNCLOAK_STAGE 38 +#define UNCLOAK_VIS_TIME (1*TICKS_PER_SECOND) + + +/*************************************************************************** +** Which shape to use depending on which facing is controlled by these arrays. +*/ +int const TechnoClass::BodyShape[32] = {0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1}; + + +/*********************************************************************************************** + * TechnoTypeClass::TechnoTypeClass -- Constructor for techno type objects. * + * * + * This is the normal constructor for techno type objects. It is called in the process of * + * constructing all the object type (constant) data for the various techno type objects. * + * * + * INPUT: see below... * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/19/1995 JLB : Created. * + *=============================================================================================*/ +TechnoTypeClass::TechnoTypeClass( + int name, + char const *ininame, + unsigned char level, + long pre, + bool is_leader, + bool is_scanner, + bool is_nominal, + bool is_transporter, + bool is_flammable, + bool is_crushable, + bool is_stealthy, + bool is_selectable, + bool is_legal_target, + bool is_insignificant, + bool is_immune, + bool is_theater, + bool is_twoshooter, + bool is_turret_equipped, + bool is_repairable, + bool is_buildable, + bool is_crew, + int ammo, + unsigned short strength, + MPHType maxspeed, + int sightrange, + int cost, + int scenario, + int risk, + int reward, + int ownable, + WeaponType primary, + WeaponType secondary, + ArmorType armor) : + ObjectTypeClass( true, + is_flammable, + is_crushable, + is_stealthy, + is_selectable, + is_legal_target, + is_insignificant, + is_immune, + name, + ininame, + armor, + strength) +{ + Level = level; + Pre = pre; + MaxAmmo = ammo; + MaxSpeed = maxspeed; + CameoData = NULL; + Primary = primary, + Secondary = secondary, + Cost = cost; + IsLeader = is_leader; + IsScanner = is_scanner; + IsTransporter = is_transporter; + IsTwoShooter = is_twoshooter; + IsBuildable = is_buildable; + IsCrew = is_crew; + IsTheater = is_theater; + IsRepairable = is_repairable; + IsTurretEquipped= is_turret_equipped; + IsNominal = is_nominal; + Ownable = ownable; + Reward = reward; + Scenario = scenario; + SightRange = sightrange; + + /* + ** Units risk value is based on the type of weapon he has and the + ** rate of fire it shoots at. + */ + risk = risk; + Risk = 0; + if (primary != WEAPON_NONE) { + Risk = (Weapons[primary].Attack * (Weapons[primary].Range >> 4)) / Weapons[primary].ROF; + } +} + + +/*********************************************************************************************** + * TechnoTypeClass::Raw_Cost -- Fetches the raw (base) cost of the object. * + * * + * This routine is used to find the underlying cost for this object. The underlying cost * + * does not include any free items that normally come with the object when purchased * + * directly. Example: The raw cost of a refinery is the normal cost minus the cost of a * + * harvester. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the credit cost of the base object type. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +int TechnoTypeClass::Raw_Cost(void) const +{ + return(Cost); +} + + +/*********************************************************************************************** + * TechnoTypeClass::Get_Ownable -- Fetches the ownable bits for this object type. * + * * + * This routine will return the ownable bits for this object type. The ownable bits are * + * a bitflag composite of the houses that can own (build) this object type. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the ownable bits for this object type. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +unsigned short TechnoTypeClass::Get_Ownable(void) const +{ + return(Ownable); +} + + +/*********************************************************************************************** + * TechnoTypeClass::Time_To_Build -- Fetches the time to build this object. * + * * + * This routine will return the time it takes to construct this object. Usually the time * + * to produce is directly related to cost. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the time to produce this object type. The time is expressed in the * + * form of game ticks. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +int TechnoTypeClass::Time_To_Build(HousesType house) const +{ + int cost = Raw_Cost(); + + /* + ** For computer controlled buildings, slow down production on + ** cheaper buildings. + */ + if (!Special.IsDifficult && + GameToPlay == GAME_NORMAL && + What_Am_I() == RTTI_BUILDINGTYPE && + PlayerPtr->Class->House != house) { + + cost = (cost + (Special.IsEasy ? 4000 : 2000)) / 2; + } + + /* + ** Fudge factor, so that Nod builds a bit faster if the object must be delivered to + ** an airfield. + */ + if (What_Am_I() == RTTI_UNITTYPE && !(Ownable & HOUSEF_GOOD)) { + return (cost - (cost/4)); + } + + return(cost); +} + + +/*********************************************************************************************** + * TechnoTypeClass::Cost_Of -- Fetches the cost of this object type. * + * * + * This routine will return the cost to produce an object of this type. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the cost to produce one object of this type. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +int TechnoTypeClass::Cost_Of(void) const +{ + return(Cost); +} + + +/*********************************************************************************************** + * TechnoTypeClass::Get_Cameo_Data -- Fetches the cameo image for this object type. * + * * + * This routine will fetch the cameo (sidebar small image) shape of this object type. * + * If there is no cameo data available (typical for non-producable units), then NULL will * + * be returned. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the cameo data for this object type if present. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +void const * TechnoTypeClass::Get_Cameo_Data(void) const +{ + return(CameoData); +} + + +/*********************************************************************************************** + * TechnoTypeClass::Repair_Cost -- Fetches the cost to repair one step. * + * * + * This routine will return the cost to repair one step. At the TechnoTypeClass level, * + * this merely serves as a placeholder function. The derived classes will provide a * + * functional version of this routine. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the cost to repair one step. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +int TechnoTypeClass::Repair_Cost(void) const +{ + return(0); +} + + +/*********************************************************************************************** + * TechnoTypeClass::Repair_Step -- Fetches the health to repair one step. * + * * + * This routine merely serves as placeholder virtual function. The various type classes * + * will override this routine to return the number of health points to repair in one * + * "step". * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of health points to repair in one step. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +int TechnoTypeClass::Repair_Step(void) const +{ + return(0); +} + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * TechnoClass::Debug_Dump -- Displays the base class data to the monochrome screen. * + * * + * This routine is used to dump the status of the object class to the monochrome screen. * + * This display can be used to track down or prevent bugs. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/02/1994 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Debug_Dump(MonoClass *mono) const +{ + mono->Set_Cursor(0,0);mono->Printf("(%04X)p=%d,d=%d", House->Power_Fraction(), House->Power, House->Drain); +// mono->Set_Cursor(0,0);mono->Printf("(%d)", House->Blockage); + mono->Text_Print("X", 16 + (IsALoaner?2:0), 11); + mono->Text_Print("X", 16 + (IsLocked?2:0), 9); + + mono->Text_Print("X", 16 + (IsInRecoilState?2:0), 17); + mono->Text_Print("X", 16 + (IsTethered?2:0), 8); + mono->Text_Print("X", 16 + (IsOwnedByPlayer?2:0), 5); + mono->Text_Print("X", 16 + (IsDiscoveredByPlayer?2:0), 6); +// mono->Text_Print("X", 16 + (IsALemon?2:0), 9); + mono->Set_Cursor(28, 7);mono->Printf("%2d", Arm); + mono->Set_Cursor(34, 1);mono->Printf("%04X", TarCom); + mono->Set_Cursor(29, 3);mono->Printf("%02X", PrimaryFacing.Current()); + + FlasherClass::Debug_Dump(mono); + StageClass::Debug_Dump(mono); + RadioClass::Debug_Dump(mono); +} +#endif + + +/*********************************************************************************************** + * TechnoClass::TechnoClass -- Default constructor for techno objects. * + * * + * This default constructor for techno objects is used to reset all internal variables to * + * their default state. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/09/1994 JLB : Created. * + *=============================================================================================*/ +TechnoClass::TechnoClass(void) : + TarCom(TARGET_NONE), + House(0) +{ + Arm = 0; + Ammo = -1; + PurchasePrice = 0; + IsTickedOff = false; + Cloak = UNCLOAKED; + CloakingDevice.Set_Stage(1); + CloakingDevice.Set_Rate(0); + IsCloakable = false; + IsALemon = false; + IsALoaner = false; + IsDiscoveredByPlayer = false; + IsDiscoveredByComputer = false; + IsInRecoilState = false; + IsLeader = false; + IsLocked = false; + IsOwnedByPlayer = false; + IsSecondShot = true; + IsTethered = false; + SuspendedTarCom = TARGET_NONE; + PrimaryFacing.Set(DIR_N); +} + + +/*********************************************************************************************** + * TechnoClass::Revealed -- Handles revealing an object to the house specified. * + * * + * When a unit moves out from under the shroud or when it gets delivered into already * + * explored terrain, then it must be "revealed". Objects that are revealed may be * + * announced to the player. The discovered bit updated accordingly. * + * * + * INPUT: house -- The house that this object is revealed to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/02/1994 JLB : Created. * + * 12/27/1994 JLB : Discovered trigger event processing. * + *=============================================================================================*/ +bool TechnoClass::Revealed(HouseClass * house) +{ + if (house == PlayerPtr && IsDiscoveredByPlayer) return(false); + if (house != PlayerPtr && IsDiscoveredByComputer) return(false); + + if (RadioClass::Revealed(house)) { + + /* + ** An enemy object that is discovered will go into hunt mode if + ** its current mission is to ambush. + */ + if (!house->IsHuman && Mission == MISSION_AMBUSH) { + Assign_Mission(MISSION_HUNT); + } + + if (house == PlayerPtr) { + + if (!IsOwnedByPlayer) { + + /* + ** If there is a trigger event associated with this object, then process + ** it for discovery purposes. + */ + if (Trigger) { + Trigger->Spring(EVENT_DISCOVERED, this); + } + + /* + ** Alert the enemy house to presence of the friendly side. + */ + House->IsDiscovered = true; + + } else { + + /* + ** A newly revealed object will always perform a look operation. + */ + if (house == PlayerPtr) IsDiscoveredByPlayer = true; + if (house != PlayerPtr) IsDiscoveredByComputer = true; + Look(); + } + } + + if (house == PlayerPtr) IsDiscoveredByPlayer = true; + if (house != PlayerPtr) IsDiscoveredByComputer = true; + + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TechnoClass::Hidden -- Returns the object back into the hidden state. * + * * + * This routine is called for every object that returns to the darkness shroud or when * + * it gets destroyed. This also occurs when an object enters another (such as infantry * + * entering a transport helicopter). * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/02/1994 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Hidden(void) +{ + if (!IsDiscoveredByPlayer) return; + if (!House->IsHuman) { + IsDiscoveredByPlayer = false; + } +} + + +/*********************************************************************************************** + * TechnoClass::Mark -- Handles marking of techno objects. * + * * + * On the Techno-level, marking handles transmission of the redraw command to any object * + * that it is 'connected' with. This only occurs during loading and unloading. * + * * + * INPUT: mark -- The marking method. This routine just passes this on to base classes. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +bool TechnoClass::Mark(MarkType mark) +{ + if (RadioClass::Mark(mark)) { + /* + ** When redrawing an object, if there is another object teathered to this one, + ** redraw it as well. + */ + if (IsTethered) { + Transmit_Message(RADIO_REDRAW); + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TechnoClass::Receive_Message -- Handles inbound message as appropriate. * + * * + * This routine is used to handle inbound radio messages. It only handles those messages * + * that deal with techno objects. Typically, these include loading and unloading. * + * * + * INPUT: from -- Pointer to the originator of the radio message. * + * * + * message -- The inbound radio message. * + * * + * param -- Reference to optional parameter that might be used to transfer * + * more information than is possible with the simple radio message * + * type. * + * * + * OUTPUT: Returns with the radio response. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + * 06/17/1995 JLB : Handles tether contact messages. * + *=============================================================================================*/ +RadioMessageType TechnoClass::Receive_Message(RadioClass * from, RadioMessageType message, long & param) +{ + switch (message) { + + /* + ** Just received instructions to attack the specified target. + */ + case RADIO_ATTACK_THIS: + if (Techno_Type_Class()->Primary != WEAPON_NONE) { + Assign_Target(param); + Assign_Mission(MISSION_ATTACK); + return(RADIO_ROGER); + } + break; + + /* + ** Establish a tethered connection to the object in radio contact. + */ + case RADIO_TETHER: + if (!IsTethered) { + IsTethered = true; + Transmit_Message(RADIO_TETHER, from); + return(RADIO_ROGER); + } + break; + + /* + ** Break the tethered connection to the object in radio contact. + */ + case RADIO_UNTETHER: + if (IsTethered) { + IsTethered = false; + Transmit_Message(RADIO_UNTETHER, from); + return(RADIO_ROGER); + } + break; + + /* + ** A teathered unit has reached it's destination. All is + ** clear, so radio contact can be broken. + */ + case RADIO_UNLOADED: + Transmit_Message(RADIO_UNTETHER, from); + return(Transmit_Message(RADIO_OVER_OUT, from)); + + /* + ** When this message is received, it means that the other object + ** has already turned its radio off. Turn this radio off as well. + */ + case RADIO_OVER_OUT: + Transmit_Message(RADIO_UNTETHER, from); + RadioClass::Receive_Message(from, message, param); + return(RADIO_ROGER); + + /* + ** Request to be informed when unloaded. This message is transmitted + ** by the transport unit to the transported unit as it is about to be + ** unloaded. It is saying, "All is clear. Drive off now." + */ + case RADIO_UNLOAD: + case RADIO_BACKUP_NOW: + case RADIO_HOLD_STILL: + Transmit_Message(RADIO_TETHER, from); + RadioClass::Receive_Message(from, message, param); + return(RADIO_ROGER); + + /* + ** Handle reloading one ammo point for this unit. + */ + case RADIO_RELOAD: + if (Ammo == Techno_Type_Class()->MaxAmmo) return(RADIO_NEGATIVE); + Ammo++; + return(RADIO_ROGER); + + /* + ** Handle repair of this unit. + */ + case RADIO_REPAIR: + if (/*param > 0 &&*/ Health_Ratio() < 0x0100) { + int cost = Techno_Type_Class()->Repair_Cost(); +// int cost = Fixed_To_Cardinal(Techno_Type_Class()->Repair_Cost(), param); + cost = MAX(cost, 1); + int step = Techno_Type_Class()->Repair_Step(); +// int step = Fixed_To_Cardinal(Techno_Type_Class()->Repair_Step(), param); + step = MAX(step, 1); + if (House->Available_Money() >= cost) { +#ifdef OBSOLETE + if (Health_Ratio() >= 0x0100) { + Strength = Class_Of().MaxStrength; + from->Scatter(true); + return(RADIO_NEGATIVE); + } +#endif + House->Spend_Money(cost); + Strength += step; + return(RADIO_ROGER); + } + } + break; + + default: + break; + } + return(RadioClass::Receive_Message(from, message, param)); +} + + +/*********************************************************************************************** + * TechnoClass::TechnoClass -- Constructor for techno type objects. * + * * + * This constructor sets the owner of this object and its strength. Any object not owned * + * by the player is marked as a loaner. This is a special flag that indicates off-map * + * movement is allowed. The flag is cleared when the object finally enters the map. * + * * + * INPUT: house -- The house (owner) of this object. * + * * + * strength -- The strength to assign to this object. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + *=============================================================================================*/ +TechnoClass::TechnoClass(HousesType house) : + House(HouseClass::As_Pointer(house)), + TarCom(TARGET_NONE) +{ + Arm = 0; + Ammo = -1; + PurchasePrice = 0; + IsTickedOff = false; + Cloak = UNCLOAKED; + CloakingDevice.Set_Stage(1); + CloakingDevice.Set_Rate(0); + IsCloakable = false; + IsALemon = false; + IsALoaner = false; + IsDiscoveredByComputer = false; + IsOwnedByPlayer = (house == PlayerPtr->Class->House); + IsDiscoveredByPlayer = false; + IsInRecoilState = false; + IsLeader = false; + IsLocked = false; + IsSecondShot = false; + IsTethered = false; + + SuspendedTarCom = TARGET_NONE; + + PrimaryFacing.Set(DIR_N); + + /* + ** There is a chance that a vehicle will be a "lemon". + */ + if (Random_Pick(0, 255) < House->Class->Lemon) { + IsALemon = true; + } +} + + +/*********************************************************************************************** + * TechnoClass::Per_Cell_Process -- Handles once-per-cell operations for techno type objects. * + * * + * This routine handles marking a game object as not a loaner. It is set only if the unit * + * is not player owned and is on the regular map. This is necessary so that enemy objects * + * can exist off-map but as soon as they move onto the map, are flagged so that can never * + * leave it again. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + * 10/26/94 JLB : Handles scanner units. * + * 12/27/1994 JLB : Checks for an processes any trigger in cell. * + *=============================================================================================*/ +void TechnoClass::Per_Cell_Process(bool ) +{ + CELL cell = Coord_Cell(Center_Coord()); + + /* + ** When enemy units enter the proper map area from off map, they are + ** flagged so that they won't travel back off the map again. + */ + if (Map.In_Radar(cell)) { + + if (What_Am_I() == RTTI_UNIT) { + UnitClass * u = (UnitClass *)this; + + if (*u != UNIT_HOVER && *u != UNIT_GUNBOAT) { + IsLocked = true; + } + } else { + IsLocked = true; + } + } + + /* + ** If this object somehow moves into mapped terrain, but is not yet + ** discovered, then flag it to be discovered. + */ + if (!IsDiscoveredByPlayer && Map[cell].IsVisible) { + Revealed(PlayerPtr); + } +} + + +/*********************************************************************************************** + * TechnoClass::Draw_It -- Draws the health bar (if necessary). * + * * + * This routine will draw the common elements for techno type objects. This element is * + * the health bar. The main game object has already been rendered by the time this * + * routine is called. * + * * + * INPUT: x,y -- The coordinate of the center of the unit. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/17/1994 JLB : Created. * + * 10/26/94 JLB : Knows about radar scanned cells. * + * 12/13/1994 JLB : Clips health bar against map edge. * + * 01/23/1995 JLB : Dynamic selected object rectangle. * + *=============================================================================================*/ +void TechnoClass::Draw_It(int x, int y, WindowNumberType window) +{ + Clear_Redraw_Flag(); + if (IsSelected || Special.IsBarOn) { + GraphicViewPortClass draw_window( LogicPage->Get_Graphic_Buffer(), + WindowList[window][WINDOWX] << 3 + LogicPage->Get_XPos(), + WindowList[window][WINDOWY] + LogicPage->Get_YPos(), + WindowList[window][WINDOWWIDTH] << 3, + WindowList[window][WINDOWHEIGHT]); + + + /* + ** The infantry select box should be a bit higher than normal. + */ + if (What_Am_I() == RTTI_INFANTRY) { + y -= 6; + } + + if (What_Am_I() == RTTI_BUILDING && ((BuildingTypeClass const &)Class_Of()).Type == STRUCT_BARRACKS) { + y -= 5; + } + + /* + ** Fetch the dimensions of the object. These dimensions will be used to draw + ** the selection box and the health bar. + */ + int width,height; + Class_Of().Dimensions(width, height); + + if (Strength && (House->Is_Ally(PlayerPtr) || Special.IsHealthBar)) { + unsigned ratio = Health_Ratio(); + int pwidth; // Pixel width of bar interior. + int color; // The color to give the interior of the bargraph. + + int xx = x-width/2; + int yy = y-(height/2); + + /* + ** Draw the outline of the bargraph. + */ + draw_window.Remap(xx+1, yy+1, width-1, 3-1, Map.FadingShade); + draw_window.Draw_Rect(xx, yy, xx+width-1, yy+3, BLACK); + + /* + ** Determine the width of the interior strength + ** graph. + */ + pwidth = Fixed_To_Cardinal(width-2, ratio); + + pwidth = Bound(pwidth, 1, width-2); + + color = LTGREEN; + if (ratio < 0x7F) { + color = YELLOW; + } + if (ratio < 0x3F) { + color = RED; + } + draw_window.Fill_Rect(xx+1, yy+1, xx+pwidth, yy+(3-1), color); + } + + /* + ** Draw the selected object graphic. + */ + if (IsSelected) { + int lx = width/2; + int ly = height/2; + int dx = width/5; + int dy = height/5; + int fudge = (House->Is_Ally(PlayerPtr) || Special.IsHealthBar) ? 4 : 0; + + // Upper left corner. + draw_window.Draw_Line(x-lx, fudge+y-ly, x-lx+dx, fudge+y-ly, WHITE); + draw_window.Draw_Line(x-lx, fudge+y-ly, x-lx, fudge+y-ly+dy, WHITE); + + // Upper right corner. + draw_window.Draw_Line(x+lx, fudge+y-ly, x+lx-dx, fudge+y-ly, WHITE); + draw_window.Draw_Line(x+lx, fudge+y-ly, x+lx, fudge+y-ly+dy, WHITE); + + // Lower right corner. + draw_window.Draw_Line(x+lx, y+ly, x+lx-dx, y+ly, WHITE); + draw_window.Draw_Line(x+lx, y+ly, x+lx, y+ly-dy, WHITE); + + // Lower left corner. + draw_window.Draw_Line(x-lx, y+ly, x-lx+dx, y+ly, WHITE); + draw_window.Draw_Line(x-lx, y+ly, x-lx, y+ly-dy, WHITE); + + if (House->Is_Ally(PlayerPtr)) { + Draw_Pips((x-lx)+5, y+ly-3, window); + } + } + } +} + + +/*********************************************************************************************** + * TechnoClass::Unlimbo -- Performs unlimbo process for all techno type objects. * + * * + * This routine handles the common operation between techno objects when they are * + * unlimboed. This includes revealing the map. * + * * + * INPUT: coord -- The coordinate to unlimbo object at. * + * * + * dir (optional) -- initial facing direction for this object * + * * + * OUTPUT: bool; Was the unlimbo successful? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/14/1994 JLB : Created. * + *=============================================================================================*/ +bool TechnoClass::Unlimbo(COORDINATE coord, DirType dir) +{ + if (RadioClass::Unlimbo(coord, dir)) { + PrimaryFacing = dir; + Enter_Idle_Mode(true); + Commence(); + + IsLocked = Map.In_Radar(Coord_Cell(coord)); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TechnoClass::In_Range -- Determines if specified target is within weapon range. * + * * + * This routine is used to compare the distance to the specified target with the range * + * of the weapon. If the target is outside of weapon range, then false is returned. * + * * + * INPUT: target -- The target to check if it is within weapon range. * + * * + * which -- Which weapon to use in determining range. 0=primary, 1=secondary. * + * * + * OUTPUT: bool; Is the specified target within weapon range? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/14/1994 JLB : Created. * + *=============================================================================================*/ +bool TechnoClass::In_Range(TARGET target, int which) const +{ + if (IsLocked && Target_Legal(target)) { + int range = Weapon_Range(which); + BuildingClass const * building = As_Building(target); + if (building) { + range += ((building->Class->Width() + building->Class->Height()) * (ICON_LEPTON_W / 4)); + } + if (::Distance(Fire_Coord(which), As_Coord(target)) <= range) { + return(true); + } + } + return(false); +} + + +/*********************************************************************************************** + * TechnoClass::In_Range -- Determines if specified target is within weapon range. * + * * + * This routine will determine if the pointer to the target object passed into this * + * routine is within weapon range. * + * * + * INPUT: target -- Pointer to the target object to check if within weapon range. * + * * + * which -- Which weapon to use in determining range. 0=primary, 1=secondary. * + * * + * OUTPUT: bool; Is the target within weapon range? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/14/1994 JLB : Created. * + *=============================================================================================*/ +bool TechnoClass::In_Range(ObjectClass const * target, int which) const +{ + if (IsLocked && target) { + int range = Weapon_Range(which); + if (target->What_Am_I() == RTTI_BUILDING) { + BuildingClass const * building = (BuildingClass const *)target; + range += ((building->Class->Width() + building->Class->Height()) * (ICON_LEPTON_W / 4)); + } + + if (::Distance(Fire_Coord(which), target->Center_Coord()) <= range) { + return(true); + } + } + return(false); +} + + +/*********************************************************************************************** + * TechnoClass::In_Range -- Determines if the specified coordinate is within range. * + * * + * Use this routine to determine if the specified coordinate is within weapon range. * + * * + * INPUT: coord -- The coordinate to examine against the object to determine range. * + * * + * which -- The weapon to consider when determining range. 0=primary, 1=secondary. * + * * + * OUTPUT: bool; Is the weapon within range? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/16/1995 JLB : Created. * + *=============================================================================================*/ +bool TechnoClass::In_Range(COORDINATE coord, int which) const +{ + return(IsLocked && ::Distance(Fire_Coord(which), coord) <= Weapon_Range(which)); +} + + +/*********************************************************************************************** + * TechnoClass::Evaluate_Object -- Determines score value of specified object. * + * * + * This routine is used to determing the score value (value as a potential target) of the * + * object specified. This routine will check the specified object for all the various * + * legality checks that threat scanning requires. This is the main workhorse routine for * + * target searching. * + * * + * INPUT: method -- The threat method requested. This is a combined bitflag value that * + * not only specified the kind of targets to consider, but how far away * + * they are allowed to be. * + * * + * mask -- This is an RTTI mask to use for quickly eliminating object types. * + * The mask is created outside of this routine because this routine is * + * usually called from within a loop and this value is constant in that * + * context. * + * * + * range -- The range at which potential target objects are rejected. * + * 0 = must be within weapon range. * + * >0 = must be within this lepton distance. * + * <0 = range doesn't matter. * + * * + * object -- Pointer to the object itself. * + * * + * value -- Reference to the value variable that this routine will fill in. The * + * higher the value the more likely this object will be selected as best. * + * * + * OUTPUT: Did the target pass all legality checks? If this value is returned true, then the * + * value parameter will be filled in correctly. * + * * + * WARNINGS: This routine is time consuming. Don't call unless necessary. * + * * + * HISTORY: * + * 06/30/1995 JLB : Created. * + * 07/14/1995 JLB : Forces SAM site to not fire on landed aircraft. * + *=============================================================================================*/ +bool TechnoClass::Evaluate_Object(ThreatType method, int mask, int range, TechnoClass const * object, int & value) const +{ + /* + ** An object in limbo can never be a valid target. + */ + if (object->IsInLimbo) return(false); + + /* + ** Friendly units are never considered a good target. Bail if this + ** object is a friend. + */ + if (House->Is_Ally(object)) return(false); + + /* + ** If the object is farther away than allowed, bail. + */ + int dist = Distance(object); + if (range > 0 && dist > range) return(false); + if (range == 0 && !In_Range(object, 0) && !In_Range(object, 1)) return(false); + + /* + ** If the object is not visible, then bail. Human controled units + ** are always considered to be visible. + */ + if (!object->IsOwnedByPlayer && !object->IsDiscoveredByPlayer && GameToPlay == GAME_NORMAL && object->What_Am_I() != RTTI_AIRCRAFT) { + return(false); + } + + /* + ** Quickly eliminate all unit types that are not allowed according to the mask + ** value. + */ + RTTIType otype = object->What_Am_I(); + if (!((1 << otype) & mask)) return(false); // Mask failure. + + /* + ** If the object is cloaked, then it isn't a legal target. + */ + if (object->Cloak == CLOAKED) return(false); + + /* + ** Determine if the target is theoretically allowed to be a target. If + ** not, then bail. + */ + TechnoTypeClass const * tclass = object->Techno_Type_Class(); + if (!tclass->IsLegalTarget) return(false); // Legality failure. + + /* + ** Never consider agent Delphi a valid target. + */ + if (otype == RTTI_INFANTRY && ((InfantryTypeClass const *)tclass)->Type == INFANTRY_DELPHI) { + return(false); + } + + /* + ** Special case so that SAM site doesn't fire on aircraft that are landed. + */ + if (otype == RTTI_AIRCRAFT && What_Am_I() == RTTI_BUILDING && *((BuildingClass *)this) == STRUCT_SAM) { + if (((AircraftClass *)object)->Altitude == 0) return(false); + } + + /* + ** If only allowed to attack civilians, then eliminate all other types. + */ + if ((method & THREAT_CIVILIANS) && object->Owner() != HOUSE_NEUTRAL) { + return(false); + } + + /* + ** If the scan is limited to capturable buildings only, then bail if the examined + ** object isn't a capturable building. + */ + if ((method & THREAT_CAPTURE) && (otype != RTTI_BUILDING || !((BuildingTypeClass const *)tclass)->IsCaptureable)) { + return(false); + } + + /* + ** If not allowed to attack boats, then eliminate them from consideration. + */ + if (!(method & THREAT_BOATS) && + otype == RTTI_UNIT && + (((UnitTypeClass const *)tclass)->Speed == SPEED_HOVER || ((UnitTypeClass const *)tclass)->Speed == SPEED_FLOAT)) { + return(false); + } + + /* + ** SPECIAL CASE: Friendly units won't automatically fire on buildings + ** if the building is not aggressive. + */ + if (House->IsHuman && otype == RTTI_BUILDING && tclass->Primary == WEAPON_NONE) return(false); + + /* + ** If the search is restricted to Tiberium processing objects, then + ** perform the special qualification check now. + */ + if (method & THREAT_TIBERIUM) { + switch (otype) { + case RTTI_UNIT: + if (!((UnitTypeClass const *)tclass)->IsToHarvest) return(false); + break; + + case RTTI_BUILDING: + if (!((BuildingTypeClass const *)tclass)->Capacity) return(false); + break; + + default: + return(false); + } + } + + /* + ** If this target value is better than the previously recorded best + ** target value then record this target for possible return as the + ** best target available. + */ + int rawval = object->Value(); + value = rawval + object->Kills; + +#ifdef ADVANCED + /* + ** Lessen threat as a factor of distance. + */ + if (rawval) { + + value = (value * 32000) / (((dist/ICON_LEPTON_W)*(dist/ICON_LEPTON_W))+1); + + //value = Fixed_To_Cardinal(value, Cardinal_To_Fixed(MAP_CELL_W*2, (MAP_CELL_W*2) - (dist/ICON_LEPTON_W))); + //value = MAX(value, 2); + + if (value < MAP_CELL_W*2) value = dist/ICON_LEPTON_W; + value = MAX(value, 1); + return(true); + } + value = 0; + return(false); + +#else + + /* + ** Lessen threat as a factor of distance. + */ + int modifier = dist; + int crange = range / ICON_LEPTON_W; + if (crange) modifier /= crange; + if (modifier) value /= modifier; + if (rawval) { + value = MAX(value, 2); + } + return(true); +#endif +} + + +/*********************************************************************************************** + * TechnoClass::Evaluate_Cell -- Determine the value and object of specified cell. * + * * + * This routine will examine the specified cell and return with the potential target * + * object it contains and the value of it. Use this routine when searching for threats. * + * * + * INPUT: method -- The scan method to use for target searching. * + * * + * mask -- Prebuilt mask of object RTTI types acceptable for scanning. * + * * + * range -- Scan range limit to use for elimination purposes. This ensures that * + * objects in the "corner" of a square scan get properly discarded. * + * * + * object -- Pointer to object pointer to be filled in with the object at this * + * cell as a valid target. * + * * + * value -- Reference to the value of the object in this cell. It will be set * + * according to the object's value. * + * * + * OUTPUT: Was a valid potential target found in this cell? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/19/1995 JLB : Created. * + *=============================================================================================*/ +bool TechnoClass::Evaluate_Cell(ThreatType method, int mask, CELL cell, int range, TechnoClass const * * object, int & value) const +{ + *object = NULL; + value = 0; + + /* + ** If the cell is not on the legal map, then always ignore it. + */ + if (cell & 0xF000) return(false); + if (!Map.In_Radar(cell)) return(false); + + /* + ** Fetch the techno object from the cell. If there is no + ** techno object there, then bail. + */ + CellClass * cellptr = &Map[cell]; + TechnoClass const * tentative = (TechnoClass const *)cellptr->Cell_Occupier(); + while (tentative) { + if (tentative->Is_Techno() && !House->Is_Ally(tentative)) break; + tentative = (TechnoClass const *)tentative->Next; + } + + if (!tentative) return(false); +// if (!tentative->Is_Techno()) return(false); + *object = tentative; + + return(Evaluate_Object(method, mask, range, tentative, value)); +} + + +/*********************************************************************************************** + * TechnoClass::Greatest_Threat -- Determines best target given search criteria. * + * * + * This routine will scan game objects looking for the best target. It is used by the * + * general target searching processes. The type of target scan to perform is controlled * + * by the method control parameter. * + * * + * INPUT: method -- The method control parameter is used to control the type of target * + * scan performed. It consists of a series of bit flags (see ThreatType) * + * that are combined to form the target scan desired. * + * * + * OUTPUT: Returns the target value of a suitable target. If no target was found then the * + * value TARGET_NONE is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/14/1994 JLB : Created. * + * 06/20/1995 JLB : Greatly optimized scan method. * + *=============================================================================================*/ +TARGET TechnoClass::Greatest_Threat(ThreatType method) const +{ + ObjectClass const * bestobject = NULL; + int bestval = -1; + + /* + ** Build a quick elimination mask. If the RTTI of the object doesn't + ** qualify with this mask, then we KNOW that it shouldn't be considered. + */ + int mask = 0; + if (method & THREAT_CIVILIANS) mask |= ((1 << RTTI_BUILDING) | (1 << RTTI_INFANTRY) | (1 << RTTI_UNIT)); + if (method & THREAT_AIR) mask |= (1 << RTTI_AIRCRAFT); + if (method & THREAT_CAPTURE) mask |= (1 << RTTI_BUILDING); + if (method & THREAT_BUILDINGS) mask |= (1 << RTTI_BUILDING); + if (method & THREAT_INFANTRY) mask |= (1 << RTTI_INFANTRY); + if (method & THREAT_VEHICLES) mask |= (1 << RTTI_UNIT); + + /* + ** Limit area target scans use a method where the actual map cells are + ** examined for occupants. The occupant is then examined in turn. The + ** best target within the area is returned as a target. + */ + if (method & (THREAT_AREA|THREAT_RANGE)) { + int range = Threat_Range((method & THREAT_RANGE) ? 0 : 1); + +// int range = MAX(Weapon_Range(0), Weapon_Range(1)); +// if (!(method & THREAT_RANGE)) range *= 2; +// range = Bound(range, 0x0100, 0x1400); // Limit maximum scan distance. + int crange = range / ICON_LEPTON_W; + if (range == 0) { + crange = MAX(Weapon_Range(0), Weapon_Range(1)) / ICON_LEPTON_W; + crange++; + } + CELL cell = Coord_Cell(Fire_Coord(0)); +// CELL cell = Coord_Cell(Center_Coord()); + + /* + ** If aircraft are a legal target, then scan through all of them at this time. + ** Scanning by cell is not possible for aircraft since they are not recorded + ** at the cell level. + */ + if (method & THREAT_AIR) { + for (int index = 0; index < Aircraft.Count(); index++) { + TechnoClass * object = Aircraft.Ptr(index); + + int value = 0; + if (Evaluate_Object(method, mask, range, object, value)) { + if (value > bestval) { + bestobject = object; + bestval = value; + } + } + } + } + + /* + ** When scanning the ground, always consider landed aircraft as a valid + ** potential target. This is only true if vehicles are considered a + ** valid target. A landed aircraft is considered a vehicle. + */ + if (method & THREAT_VEHICLES) { + mask |= (1 << RTTI_AIRCRAFT); + } + + /* + ** Radiate outward from the object's location, looking for the best + ** target. + */ + TechnoClass const * object; + int value; + for (int radius = 1; radius < crange; radius++) { + + /* + ** Scan the top and bottom rows of the "box". + */ + for (int x = -radius; x <= radius; x++) { + CELL newcell; + + if ((Cell_X(cell) + x) < Map.MapCellX) continue; + if ((Cell_X(cell) + x) >= (Map.MapCellX+Map.MapCellWidth)) continue; + + if ((Cell_Y(cell) - radius) >= Map.MapCellY) { + newcell = XY_Cell(Cell_X(cell) + x, Cell_Y(cell)-radius); + if (Evaluate_Cell(method, mask, newcell, range, &object, value)) { + if (bestval < value) { + bestobject = object; + } + } + } + + if ((Cell_Y(cell) + radius) < (Map.MapCellY+Map.MapCellHeight)) { + newcell = XY_Cell(Cell_X(cell)+x, Cell_Y(cell)+radius); + if (Evaluate_Cell(method, mask, newcell, range, &object, value)) { + if (bestval < value) { + bestobject = object; + } + } + } + } + + /* + ** Scan the left and right columns of the "box". + */ + for (int y = -(radius-1); y < radius; y++) { + CELL newcell; + + if ((Cell_Y(cell) + y) < Map.MapCellY) continue; + if ((Cell_Y(cell) + y) >= (Map.MapCellY+Map.MapCellHeight)) continue; + + if ((Cell_X(cell) - radius) >= Map.MapCellX) { + newcell = XY_Cell(Cell_X(cell)-radius, Cell_Y(cell)+y); + if (Evaluate_Cell(method, mask, newcell, range, &object, value)) { + if (bestval < value) { + bestobject = object; + } + } + } + + if ((Cell_X(cell) + radius) < (Map.MapCellX+Map.MapCellWidth)) { + newcell = XY_Cell(Cell_X(cell)+radius, Cell_Y(cell)+y); + if (Evaluate_Cell(method, mask, newcell, range, &object, value)) { + if (bestval < value) { + bestobject = object; + } + } + } + } + + /* + ** Bail early if a target has already been found and the range is at + ** one of the breaking points (i.e., normal range or range * 2). + */ + if (bestobject) { + if (radius == crange/4) { + return(bestobject->As_Target()); + } + if (radius == crange/2) { + return(bestobject->As_Target()); + } + } + } + + } else { + + /* + ** A full map scan was requested. First scan through aircraft. The top map layer + ** is NOT scanned since that layer will probably contain more bullets and animations + ** than aircraft. + */ + for (int index = 0; index < Aircraft.Count(); index++) { + TechnoClass * object = Aircraft.Ptr(index); + + int value = 0; + if (Evaluate_Object(method, mask, -1, object, value)) { + if (value > bestval) { + bestobject = object; + bestval = value; + } + } + } + + /* + ** Now scan through the entire ground layer. This is painful, but what other + ** choice is there? + */ + for (index = 0; index < Map.Layer[LAYER_GROUND].Count(); index++) { + ObjectClass const * object = Map.Layer[LAYER_GROUND][index]; + + int value = 0; + if (object->Is_Techno() && Evaluate_Object(method, mask, -1, (TechnoClass const *)object, value)) { + if (value > bestval) { + bestobject = object; + bestval = value; + } + } + } + } + + /* + ** If a good target object was found, then return with the target value + ** of it. + */ + if (bestobject) { + return(bestobject->As_Target()); + } + return(TARGET_NONE); +} + + +/*********************************************************************************************** + * TechnoClass::Owner -- Who is the owner of this object? * + * * + * Use this routine to examine this object and return who the owner is. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the house number of the owner of this object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/09/1994 JLB : Created. * + *=============================================================================================*/ +HousesType TechnoClass::Owner(void) const +{ + return(House->Class->House); +} + + +/*********************************************************************************************** + * TechnoClass::Clicked_As_Target -- Sets the flash count for this techno object. * + * * + * Use this routine to set the flash count for the object. This flash count is the number * + * of times the object will "flash". Typically it is called as a result of the player * + * clicking on this object in order to make it the target of a move or attack. * + * * + * INPUT: count -- The number of times the object should flash. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/09/1994 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Clicked_As_Target(int count) +{ + FlashCount = count; +} + + +/*********************************************************************************************** + * TechnoClass::AI -- Handles AI processing for techno object. * + * * + * This routine handles AI processing for techno objects. Typically, this merely dispatches * + * to the appropriate AI routines for the base classes. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Make sure that this routine is only called ONCE per game tick. * + * * + * HISTORY: * + * 12/09/1994 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::AI(void) +{ + CargoClass::AI(); + RadioClass::AI(); + DoorClass::AI(); + + /* + ** Handle decision to re-cloak here. Process the cloaking/decloaking operation. + */ + if (IsCloakable) { + + /* + ** If this object is uncloaked, but it can be cloaked and it thinks that it + ** is a good time do so, then begin cloaking. + */ + if (Cloak == UNCLOAKED) { + if (IsOwnedByPlayer) Mark(MARK_CHANGE); + CloakingDevice.Graphic_Logic(); + if (!Arm && CloakingDevice.Fetch_Stage()) { + if (Health_Ratio() > 0x0040) { + Do_Cloak(); + } else { + if (Random_Pick(0, 25) == 1) { + Do_Cloak(); + } + } + } + } else { + + VisualType pre = Visual_Character(true); + CloakingDevice.Graphic_Logic(); + switch (Cloak) { + + /* + ** Handle the uncloaking process. Always mark to redraw + ** the object and when cloaking is complete, stabilize into + ** the normal uncloaked state. + */ + case UNCLOAKING: + Mark(MARK_CHANGE); + if (Visual_Character(true) == VISUAL_NORMAL) { + CloakingDevice.Set_Rate(UNCLOAK_VIS_TIME); + CloakingDevice.Set_Stage(0); // re-start the stage counter + Cloak = UNCLOAKED; + } + break; + + /* + ** Handle the cloaking process. Always mark to redraw the object + ** and when the cloaking process is complete, stabilize into the + ** normal cloaked state. + */ + case CLOAKING: + Mark(MARK_CHANGE); + switch (Visual_Character(true)) { + + /* + ** If badly damaged, then it can never fully cloak. + */ + case VISUAL_DARKEN: + if (Health_Ratio() < 0x0040 && Random_Pick(1, 3) == 1) { + Cloak = UNCLOAKING; + } + break; + +#ifdef NEVER + case VISUAL_SHADOWY: + if (pre != Visual_Character(true)) { + Detach_All(false); + } + break; +#endif + + case VISUAL_HIDDEN: + Cloak = CLOAKED; + CloakingDevice.Set_Rate(0); + + /* + ** Special check to ensure that if the unit is carring a captured + ** flag, it will never fully cloak. + */ + if (What_Am_I() == RTTI_UNIT && ((UnitClass *)this)->Flagged != HOUSE_NONE) { + Do_Shimmer(); + } else { + Detach_All(false); + } + + /* + ** A computer controlled unit will try to scatter if possible so + ** that it will be much harder to locate. + */ + if (What_Am_I() == RTTI_UNIT && !House->IsHuman) { + Scatter(0, true); + } + break; + } + break; + + /* + ** A cloaked object will always be redrawn if it is owned by the + ** player. This ensures that the shimmering effect will animate. + */ + case CLOAKED: + if (IsOwnedByPlayer) { + Mark(MARK_CHANGE); + } + break; + } + } + } + + /* + ** Arming delay always counts down to zero. + */ + if (Arm) Arm--; + + /* + ** Update the animation timer system. If the animation stage + ** changes, then flag the object to be redrawn as well as determine + ** if the current animation process needs to change. + */ + if (What_Am_I() != RTTI_BUILDING) { + if (StageClass::Graphic_Logic() || Time_To_Redraw()) { + Mark(MARK_CHANGE); + } + } + + /* + ** If the object is flashing and a change of flash state has occured, then mark the + ** object to be redrawn. + */ + if (FlasherClass::Process()) { + Mark(MARK_CHANGE); + } +} + + +/*********************************************************************************************** + * TechnoClass::Select -- Selects object and checks to see if can be selected. * + * * + * This function checks to see if this techno object can be selected. If it can, then it * + * is selected. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/11/1994 JLB : Created. * + *=============================================================================================*/ +bool TechnoClass::Select(void) +{ + if (!IsDiscoveredByPlayer && !IsOwnedByPlayer && !Debug_Unshroud) { + return(false); + } + + if (RadioClass::Select()) { + + /* + ** Speak a confirmation of selection. + */ + if (IsOwnedByPlayer && AllowVoice) { + Response_Select(); + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TechnoClass::Can_Fire -- Determines if this techno object can fire. * + * * + * This performs a simple check to make sure that this techno object can fire. At this * + * level, the only thing checked for is the rearming delay. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the fire legality control code. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/23/1994 JLB : Created. * + *=============================================================================================*/ +FireErrorType TechnoClass::Can_Fire(TARGET target, int which) const +{ + /* + ** Don't allow firing if the target is illegal. + */ + if (!Target_Legal(target)) { + return(FIRE_ILLEGAL); + } + + ObjectClass * object = As_Object(target); + + /* + ** If the object is completely cloaked, then you can't fire on it. + */ +//Mono_Printf("Units[0]=%p.\n", Units.Raw_Ptr(0)); +//Mono_Printf("Infantry[0]=%p.\n", Infantry.Raw_Ptr(0)); +//Mono_Printf("Buildings[0]=%p.\n", Buildings.Raw_Ptr(0)); +//Mono_Printf("Aircraft[0]=%p.\n", Aircraft.Raw_Ptr(0)); +//Mono_Printf("object=%p, Strength=%d, IsActive=%d, IsInLimbo=%d.\n", object, (long)object->Strength, object->IsActive, object->IsInLimbo);Get_Key(); + if (object && /*(object->IsActive || GameToPlay != GAME_NORMAL) &&*/ object->Is_Techno() && ((TechnoClass *)object)->Cloak == CLOAKED) { + return(FIRE_CANT); + } + + /* + ** If there is no weapon, then firing is not allowed. + */ + WeaponType weap = (which == 0) ? Techno_Type_Class()->Primary : Techno_Type_Class()->Secondary; + if (weap == WEAPON_NONE) { + return(FIRE_CANT); + } + + /* + ** Can only fire anti-aircraft weapons against aircraft unless the aircraft is + ** sitting on the ground. + */ + if (object && object->What_Am_I() == RTTI_AIRCRAFT && + !BulletTypeClass::As_Reference(Weapons[weap].Fires).IsAntiAircraft && + ((AircraftClass *)object)->Altitude > 0) { + + return(FIRE_CANT); + } + + /* + ** Don't allow firing if still rearming. + */ + if (Arm) return(FIRE_REARM); + + /* + ** The target must be within range in order to allow firing. + */ + if (!In_Range(target, which)) { + return(FIRE_RANGE); + } + + /* + ** If there is no ammo left, then it can't fire. + */ + if (!Ammo) { + return(FIRE_AMMO); + } + + /* + ** If cloaked, then firing is disabled. + */ + if (Cloak != UNCLOAKED) { + return(FIRE_CLOAKED); + } + + return(FIRE_OK); +} + + +/*********************************************************************************************** + * TechnoClass::Stun -- Prepares the object for removal from the game. * + * * + * This routine handles cleaning up this techno object from the game system so that when * + * it is subsequently removed, it doesn't leave any loose ends. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/23/1994 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Stun(void) +{ + Assign_Target(TARGET_NONE); + Assign_Destination(TARGET_NONE); + Transmit_Message(RADIO_OVER_OUT); + Detach_All(); + Unselect(); +} + + +/*********************************************************************************************** + * TechnoClass::Assign_Target -- Assigns the targeting computer with specified target. * + * * + * Use this routine to set the targeting computer for this object. It checks to make sure * + * that targeting of itself is prohibited. * + * * + * INPUT: target -- The target for this object to attack. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/23/1994 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Assign_Target(TARGET target) +{ + if (target == TarCom) return; + + if (!Target_Legal(target)) { + target = TARGET_NONE; + } else { + + /* + ** Prevent targeting of self. + */ + if (target == As_Target()) { + target = ::As_Target(Coord_Cell(Coord)); + } else { + + /* + ** Make sure that the target is not already dead. + */ + ObjectClass * object = As_Object(target); + if (object && (object->IsActive == false || object->Strength == 0)) { + target = TARGET_NONE; + } + } + } + + /* + ** Set the unit's targeting computer. + */ + TarCom = target; +} + + +/*********************************************************************************************** + * TechnoClass::Rearm_Delay -- Calculates the delay before firing can occur. * + * * + * This function calculates the delay between shots. It determines this from the standard * + * rate of fire (ROF) of the base class and modifies it according to game speed and * + * whether this is the first or second shot. All single shot attackers consider their * + * shots to be "second" since the second shot is the one handled normally. The first shot * + * usually gets assigned a much shorter delay time before the next shot can fire. * + * * + * INPUT: second -- bool; Is this the second of a two shot salvo? * + * * + * OUTPUT: Returns with the number of game frames to delay before the next shot may fire. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + *=============================================================================================*/ +int TechnoClass::Rearm_Delay(bool second) const +{ + if (second) { + return(Weapons[Techno_Type_Class()->Primary].ROF + 3); + } + return(9); +} + + +/*********************************************************************************************** + * TechnoClass::Fire_At -- Fires projectile at target specified. * + * * + * This is the main projectile firing code. Buildings, units, and infantry route fire * + * requests through this function. * + * * + * INPUT: target -- The target that the projectile is to be fired at. * + * * + * which -- Which weapon to fire. * + * * + * OUTPUT: Returns with a pointer to the projectile object that was fired. If no projectile * + * could be created or there was some other illegality detected, the return value * + * will be NULL. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/26/1994 JLB : Created. * + * 07/03/1995 JLB : Moving platforms fire inaccurate projectiles. * + *=============================================================================================*/ +BulletClass * TechnoClass::Fire_At(TARGET target, int which) +{ + BulletClass *bullet; // Projectile. + DirType dir; // The facing to impart upon the projectile. + COORDINATE target_coord; // Coordinate of the target. + COORDINATE fire_coord; // Coordinate of firing position. + TechnoTypeClass const & tclass = *Techno_Type_Class(); + ObjectClass *object; + WeaponTypeClass const *weapon = (which == 0) ? &Weapons[tclass.Primary] : &Weapons[tclass.Secondary]; + BulletTypeClass const &btype = BulletTypeClass::As_Reference(weapon->Fires); + + /* + ** Perform a quick legality check to see if firing can occur. + */ + if (Debug_Map || weapon->Fires == BULLET_NONE || !Target_Legal(target)) { + return(NULL); + } + + /* + ** Fetch the target coordinate for the target specified. + */ + object = As_Object(target); + if (object) { + target_coord = object->Target_Coord(); + } else { + target_coord = As_Coord(target); + } + + /* + ** Get the location where the projectile should appear. + */ + fire_coord = Fire_Coord(which); + + /* + ** If the projectile is a homing type (such as a missile), then it will + ** launch in the direction the turret is facing, NOT necessarily the same + ** direction as the target. + */ + if (btype.IsHoming || btype.IsDropping) { + dir = Fire_Direction(); + if (btype.IsDropping) { + fire_coord = Center_Coord(); + } + } else { + dir = ::Direction(fire_coord, target_coord); + } + + /* + ** Create the projectile. Then process any special operations that + ** need to be performed according to the style of projectile + ** created. + */ + bullet = new BulletClass(weapon->Fires); + if (bullet) { + bullet->Assign_Target(target); + bullet->Payback = this; + bullet->Strength = weapon->Attack; + + /* + ** If this is firing from a moving platform, then the projectile is inaccurate. + */ + if (Special.IsDefenderAdvantage && What_Am_I() != RTTI_BUILDING && ((FootClass const *)this)->IsDriving) { + bullet->IsInaccurate = true; + } + + if (bullet->Unlimbo(fire_coord, dir)) { +//Mono_Printf("Units[0]=%p.\n", Units.Raw_Ptr(0)); +//Mono_Printf("Infantry[0]=%p.\n", Infantry.Raw_Ptr(0)); +//Mono_Printf("Buildings[0]=%p.\n", Buildings.Raw_Ptr(0)); +//Mono_Printf("Aircraft[0]=%p.\n", Aircraft.Raw_Ptr(0)); +//Mono_Printf("object=%p, Strength=%d, IsActive=%d, IsInLimbo=%d.\n", object, (long)object->Strength, object->IsActive, object->IsInLimbo);Get_Key(); + bullet->Payback = this; + bullet->Strength = weapon->Attack; + } else { + delete bullet; + } + if (!bullet->Class->IsFueled) { + IsInRecoilState = true; + } + Arm = Rearm_Delay(IsSecondShot); + if (tclass.IsTwoShooter) { + IsSecondShot = (IsSecondShot == false); + } + + /* + ** Perform any animation effect for this weapon. + */ + AnimType a = weapon->Anim; + switch (a) { + case ANIM_GUN_N: + case ANIM_CHEM_N: + case ANIM_FLAME_N: + a = (AnimType)(a + Dir_Facing(Fire_Direction())); + break; + } + + /* + ** If there is a special firing animation, then create and attach it + ** now. + */ + if (a != ANIM_NONE) { + AnimClass * anim = new AnimClass(a, Fire_Coord(which)); + if (anim) { + anim->Attach_To(this); + } + } + + /* + ** Reduce ammunition for this object. + */ + if (Ammo > 0) { + Ammo--; + } + + /* + ** Firing will in all likelihood, require the unit to be redraw. Flag it to be + ** redrawn here. + */ + Mark(MARK_CHANGE); + + /* + ** If a projectile was fired from a unit that is hidden in the darkness, + ** reveal that unit and a little area around it. + ** For multiplayer games, only reveal the unit if the target is the + ** local player. + */ + if ((!IsOwnedByPlayer && !IsDiscoveredByPlayer) || !Map[Coord_Cell(Center_Coord())].IsMapped) { + if (GameToPlay == GAME_NORMAL) { + Map.Sight_From(Coord_Cell(Center_Coord()), 1, false); + } else { + ObjectClass *obj = As_Object(target); + if (obj) { + HousesType tgt_owner = obj->Owner(); + + if (PlayerPtr->Class->House == tgt_owner) { + Map.Sight_From(Coord_Cell(Center_Coord()), 1, false); + } + } + } + } + } + + return(bullet); +} + + +/*********************************************************************************************** + * TechnoClass::Player_Assign_Mission -- Assigns a mission as result of player input. * + * * + * This routine is called when the mission for an object needs to change as a result of * + * player input. The basic operation would be to queue the event and let the action * + * occur at the frame dictated by the queing system. However, if a voice response is * + * indicated, then perform it at this time. This will give a greater illusion of * + * immediate response. * + * * + * INPUT: order -- The mission order to assign to this object. * + * * + * target -- The target of this object. This will be used for combat and attack. * + * * + * destination -- The movement destination for this object. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Player_Assign_Mission(MissionType mission, TARGET target, TARGET destination) +{ + if (AllowVoice) { + if (mission == MISSION_ATTACK) { + Response_Attack(); + } else { + Response_Move(); + } + } + Queue_Mission(As_Target(), mission, target, destination); +} + + +/*********************************************************************************************** + * TechnoClass::What_Action -- Determines what action to perform if object is selected. * + * * + * This routine will examine the object specified and return with the action that will * + * be performed if the mouse button were clicked over the object. * + * * + * INPUT: object -- The object that the mouse button might be clicked on. * + * * + * OUTPUT: Returns with the action that will be performed if the object was clicked on. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + * 03/21/1995 JLB : Special target control for trees. * + *=============================================================================================*/ +ActionType TechnoClass::What_Action(ObjectClass * object) const +{ + if (object) { + + /* + ** Return the ACTION_SELF flag if clicking on itself. However, if this + ** object cannot do anything special with itself, then just return with + ** the no action flag. + */ + if (object == this && CurrentObject.Count() == 1 && House == PlayerPtr) { + return(ACTION_SELF); + } + + bool altdown = (Keyboard::Down(KN_LALT) || Keyboard::Down(KN_RALT)); + bool ctrldown = (Keyboard::Down(KN_LCTRL) || Keyboard::Down(KN_RCTRL)); + bool shiftdown = (Keyboard::Down(KN_LSHIFT) || Keyboard::Down(KN_RSHIFT)); + + /* + ** Special guard area mission is possible if both the control and the + ** alt keys are held down. + */ + if (IsOwnedByPlayer && ctrldown && altdown && Can_Player_Move() && Can_Player_Fire()) { + return(ACTION_GUARD_AREA); + } + + /* + ** Special override to force a move regardless of what is occupying the location. + */ + if (altdown) { + if (IsOwnedByPlayer && Can_Player_Move()) { + return(ACTION_MOVE); + } + } + + /* + ** Override so that toggled select state can be performed while the key + ** is held down. + */ + if (shiftdown) { + if (IsOwnedByPlayer && !IsALoaner) { + return(ACTION_TOGGLE_SELECT); + } + } + + /* + ** If firing is possible and legal, then return this action potential. + */ + bool control = Keyboard::Down(KN_LCTRL) || Keyboard::Down(KN_RCTRL); + if (IsOwnedByPlayer && (ctrldown || !House->Is_Ally(object)) && (ctrldown || object->Class_Of().IsLegalTarget || (Special.IsTreeTarget && object->What_Am_I() == RTTI_TERRAIN))) { + if (Can_Player_Move() || In_Range(object, 0)) { + return(ACTION_ATTACK); + } + } + + /* + ** Possibly try to select the specified object, if that is warranted. + */ + if (!Is_Weapon_Equipped() || !IsOwnedByPlayer || object->Owner() == Owner()) { + if ((!IsALoaner || !IsOwnedByPlayer) && object->Class_Of().IsSelectable && !object->IsSelected) { + return(ACTION_SELECT); + } + return(ACTION_NONE); + } + } + return(ACTION_NONE); +} + + +/*********************************************************************************************** + * TechnoClass::What_Action -- Determines action to perform if cell is clicked on. * + * * + * Use this routine to determine what action will be performed if the specified cell * + * is clicked on. Usually this action is either a ACTION_MOVE or ACTION_NOMOVE. The action * + * nomove is used to perform special case checking for nearby cells if in fact the mouse * + * is clicked over the cell. * + * * + * INPUT: cell -- The cell to check for being clicked over. * + * * + * OUTPUT: Returns with the action that will occur if the cell is clicked on. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + * 07/10/1995 JLB : Force fire for buildings is explicitely disabled. * + *=============================================================================================*/ +ActionType TechnoClass::What_Action(CELL cell) const +{ + CellClass const * cellptr = &Map[cell]; + OverlayTypeClass const * optr = NULL; + + bool ctrldown = Keyboard::Down(KN_LCTRL) || Keyboard::Down(KN_RCTRL); + bool shiftdown = Keyboard::Down(KN_LSHIFT) || Keyboard::Down(KN_RSHIFT); + bool altdown = (Keyboard::Down(KN_LALT) || Keyboard::Down(KN_RALT)); + + /* + ** Disable recognizing the key forced fire option when dealing with buildings. + */ + if (What_Am_I() == RTTI_BUILDING) ctrldown = false; + + if (cellptr->Overlay != OVERLAY_NONE) { + optr = &OverlayTypeClass::As_Reference(cellptr->Overlay); + } + + /* + ** Special guard area mission is possible if both the control and the + ** alt keys are held down. + */ + if (IsOwnedByPlayer && ctrldown && altdown && Can_Player_Move() && Can_Player_Fire()) { + return(ACTION_GUARD_AREA); + } + + if (IsOwnedByPlayer && Techno_Type_Class()->Primary != WEAPON_NONE && (ctrldown || (optr && optr->IsLegalTarget))) { + WarheadTypeClass const * whead = &Warheads[BulletTypeClass::As_Reference(Weapons[Techno_Type_Class()->Primary].Fires).Warhead]; + if (!optr || (optr->IsWall && (whead->IsWallDestroyer || (whead->IsWoodDestroyer && optr->IsWooden)))) { + if (Can_Player_Move() || In_Range(::As_Target(cell), 0)) { + return(ACTION_ATTACK); + } + } + } + + if (IsOwnedByPlayer && Can_Player_Move()) { + + /* + ** Special override to force a move regardless of what is occupying the location. + */ + if (shiftdown) { + return(ACTION_MOVE); + } + + /* + ** If the object can enter the cell specified, then allow + ** movement to it. + */ + if (Can_Enter_Cell(cell) <= MOVE_CLOAK) { + return(ACTION_MOVE); + } + return(ACTION_NOMOVE); + } + return(ACTION_NONE); +} + + +/*********************************************************************************************** + * TechnoClass::Can_Player_Move -- Determines if the object can move be moved by player. * + * * + * Use this routine to determine whether a movement order can be given to this object. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Can this object be given a movement order by the player? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +bool TechnoClass::Can_Player_Move(void) const +{ + return(PlayerPtr == House); +} + + +/*********************************************************************************************** + * TechnoClass::Can_Player_Fire -- Determines if the player can give this object a fire order. * + * * + * Call this routine to determine if this object can be given a fire order by the player. * + * Such objects will affect the mouse cursor accordingly -- usually causes the targeting * + * cursor to appear. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Can this object be given firing orders by the player? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +bool TechnoClass::Can_Player_Fire(void) const +{ + if (House->IsHuman && Is_Techno() && Techno_Type_Class()->Primary != WEAPON_NONE) { + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TechnoClass::Is_Weapon_Equipped -- Determines if this object has a combat weapon. * + * * + * Use this routine to determine if this object is equipped with a combat weapon. Such * + * determination is used by the AI system to gauge the threat potential of the object. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Is this object equipped with a combat weapon? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +bool TechnoClass::Is_Weapon_Equipped(void) const +{ + return(Techno_Type_Class()->Primary != WEAPON_NONE); +} + + +/*********************************************************************************************** + * TechnoClass::Can_Repair -- Determines if the object can and should be repaired. * + * * + * Use this routine to determine if the specified object is a candidate for repair. In * + * order to qualify, the object must be allowed to be repaired (in theory) and it must * + * be below full strength. If these conditions are met, then it can be repaired. * + * * + * INPUT: none * + * * + * OUTPUT: bool; May this unit be repaired? A return value of false may mean that the object * + * is not allowed to be repaired, or it might be full strength already. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +bool TechnoClass::Can_Repair(void) const +{ + /* + ** Temporary hack to disable repair cursor over non-buildings. + */ + if (What_Am_I() == RTTI_UNIT || What_Am_I() == RTTI_INFANTRY || What_Am_I() == RTTI_AIRCRAFT) { + return(false); + } + return(Techno_Type_Class()->IsRepairable && Strength != Class_Of().MaxStrength); +} + + +/*********************************************************************************************** + * TechnoClass::Weapon_Range -- Determines the maximum range for the weapon. * + * * + * Use this routine to determine the maximum range for the weapon indicated. * + * * + * INPUT: which -- Which weapon to use when determining the range. 0=primary, 1=secondary. * + * * + * OUTPUT: Returns with the range of the weapon (in leptons). * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/19/1995 JLB : Created. * + *=============================================================================================*/ +int TechnoClass::Weapon_Range(int which) const +{ + WeaponType weapon = WEAPON_NONE; + TechnoTypeClass const & ttype = *Techno_Type_Class(); + + switch (which) { + case 0: + weapon = ttype.Primary; + break; + + case 1: + weapon = ttype.Secondary; + break; + } + if (weapon != WEAPON_NONE) { + if (weapon == WEAPON_NIKE && GameToPlay == GAME_NORMAL) { + return(Weapons[weapon].Range*2); + } + return(Weapons[weapon].Range); + } + return(0); +} + + +/*************************************************************************** + * TechnoClass::Override_Mission -- temporarily overides a units mission * + * * + * * + * * + * INPUT: MissionType mission - the mission we want to overide * + * TARGET tarcom - the new target we want to overide * + * TARGET navcom - the new navigation point to overide * + * * + * OUTPUT: none * + * * + * WARNINGS: If a mission is already overidden, the current mission is * + * just re-assigned. * + * * + * HISTORY: * + * 04/28/1995 PWG : Created. * + *=========================================================================*/ +void TechnoClass::Override_Mission(MissionType mission, TARGET tarcom, TARGET navcom) +{ + SuspendedTarCom = TarCom; + RadioClass::Override_Mission(mission, tarcom, navcom); + Assign_Target(tarcom); +} + + +/*************************************************************************** + * TechnoClass::Restore_Mission -- Restores an overidden mission * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/28/1995 PWG : Created. * + *=========================================================================*/ +bool TechnoClass::Restore_Mission(void) +{ + if (RadioClass::Restore_Mission()) { + Assign_Target(SuspendedTarCom); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TechnoClass::Captured -- Handles capturing this object. * + * * + * This routine is called when this object gets captured by the house specified. It handles * + * removing this object from any targeting computers and then changes the ownership of * + * the object to the new house. * + * * + * INPUT: newowner -- Pointer to the house that is now the new owner. * + * * + * OUTPUT: Was the object captured? Failure would mean that it is already under control of * + * the house specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +bool TechnoClass::Captured(HouseClass * newowner) +{ + if (newowner != House) { + + /* + ** Capture attempt springs any "entered" trigger. The entered trigger + ** occurs first since there may be a special trigger attached to this + ** object that flags a capture as a win and a destroy as a loss. This + ** order is necessary because the object is recorded as a kill as well. + */ + if (Trigger /*&& Trigger->House == newowner->Class->House*/) { + Trigger->Spring(EVENT_PLAYER_ENTERED, this); + } + + /* + ** Record this as a kill. + */ + Record_The_Kill(NULL); + + /* + ** Special kill record logic for capture process. + */ + switch (What_Am_I()) { + case RTTI_BUILDING: + if (newowner) newowner->BuildingsKilled[Owner()]++; + break; + + case RTTI_AIRCRAFT: + case RTTI_INFANTRY: + case RTTI_UNIT: + if (newowner) newowner->UnitsKilled[Owner()]++; + break; + + default: + break; + } + House->WhoLastHurtMe = newowner->Class->House; + + /* + ** Remove from targeting computers. + */ + Detach_All(false); + +#ifdef NEVER + /* + ** Break off any radio contact. + */ + Transmit_Message(RADIO_OVER_OUT); +#endif + + /* + ** Change ownership now. + */ + House = newowner; + IsOwnedByPlayer = (House == PlayerPtr); + + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TechnoClass::Take_Damage -- Records damage assessed to this object. * + * * + * This routine is called when this object has taken damage. It handles recording whether * + * this object has been destroyed. If it has, then mark the appropriate kill records as * + * necessary. * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 06/20/1995 JLB : Created. * + *=============================================================================================*/ +ResultType TechnoClass::Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source) +{ + ResultType result = ObjectClass::Take_Damage(damage, distance, warhead, source); + + switch (result) { + case RESULT_DESTROYED: + Transmit_Message(RADIO_OVER_OUT); + Stun(); + break; + + /* + ** If some damage was received and this object is cloaked, shimmer + ** the cloak a bit. + */ + default: + if (source && !House->Is_Ally(source)) { + IsTickedOff = true; + } + Do_Shimmer(); + break; + + case RESULT_NONE: + break; + } + return(result); +} + + +/*********************************************************************************************** + * TechnoTypeClass::Max_Passengers -- Fetches the maximum passengers allowed. * + * * + * This routine will return with the maximum number of passengers allowed in this * + * transport. This typically applies to APCs and possibley transport helicopters. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of passengers this transport can carry. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/20/1995 JLB : Created. * + *=============================================================================================*/ +int TechnoTypeClass::Max_Passengers(void) const +{ + if (IsTransporter) { + return(5); + } + return(0); +} + + +/*********************************************************************************************** + * TechnoClass::Record_The_Kill -- Records the death of this object. * + * * + * This routine is used to record the death of this object. It will handle updating the * + * owner house with the kill record as well as springing any trigger events associated with * + * this object's death. * + * * + * INPUT: source -- Pointer to the source of this object's death (if there is a source). * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + * 08/23/1995 JLB : Building loss is only counted if it received damage. * + *=============================================================================================*/ +void TechnoClass::Record_The_Kill(TechnoClass * source) +{ + /* + ** Handle any trigger event associated with this object. + */ + if (Trigger && source) Trigger->Spring(EVENT_ATTACKED, this); + + if (Trigger && source) Trigger->Spring(EVENT_DISCOVERED, this); + + if (Trigger) Trigger->Spring(EVENT_DESTROYED, this); + + if (source) { + /* + ** Call the explicity cast versions of the Made_A_Kill function. This + ** is necessary because we don't want to add a virtual function to the + ** CrewClass. Doing so would complicate the save/load process. + */ + switch (source->What_Am_I()) { + case RTTI_INFANTRY: + ((InfantryClass *)source)->Made_A_Kill(); + break; + + case RTTI_UNIT: + ((UnitClass *)source)->Made_A_Kill(); + break; + + case RTTI_BUILDING: + ((BuildingClass *)source)->Made_A_Kill(); + break; + + case RTTI_AIRCRAFT: + ((AircraftClass *)source)->Made_A_Kill(); + break; + } + + House->WhoLastHurtMe = source->Owner(); + } + + switch (What_Am_I()) { + case RTTI_BUILDING: + if ( ((BuildingClass *)this)->WhoLastHurtMe != HOUSE_NONE) { + House->BuildingsLost++; + } + if (source){ + if (GameToPlay == GAME_INTERNET){ + source->House->DestroyedBuildings->Increment_Unit_Total( ((BuildingClass*)this)->Class->Type ); + } + source->House->BuildingsKilled[Owner()]++; + } + + /* + ** If the map is displaying the multiplayer player names & their + ** # of kills, tell it to redraw. + */ + if (Map.Is_Player_Names()) { + Map.Player_Names(true); + } + break; + + + case RTTI_AIRCRAFT: + House->UnitsLost++; + if (source){ + if (GameToPlay == GAME_INTERNET){ + source->House->DestroyedAircraft->Increment_Unit_Total( ((AircraftClass*)this)->Class->Type ); + } + source->House->UnitsKilled[Owner()]++; + } + /* + ** If the map is displaying the multiplayer player names & their + ** # of kills, tell it to redraw. + */ + if (Map.Is_Player_Names()) { + Map.Player_Names(true); + } + break; + + + case RTTI_INFANTRY: + House->UnitsLost++; + if (source){ + if (GameToPlay == GAME_INTERNET){ + source->House->DestroyedInfantry->Increment_Unit_Total( ((InfantryClass*)this)->Class->Type ); + } + source->House->UnitsKilled[Owner()]++; + } + /* + ** If the map is displaying the multiplayer player names & their + ** # of kills, tell it to redraw. + */ + if (Map.Is_Player_Names()) { + Map.Player_Names(true); + } + break; + + + case RTTI_UNIT: + House->UnitsLost++; + if (source){ + if (GameToPlay == GAME_INTERNET){ + source->House->DestroyedUnits->Increment_Unit_Total( ((UnitClass*)this)->Class->Type ); + } + source->House->UnitsKilled[Owner()]++; + } + + /* + ** If the map is displaying the multiplayer player names & their + ** # of kills, tell it to redraw. + */ + if (Map.Is_Player_Names()) { + Map.Player_Names(true); + } + break; + + default: + break; + } +} + + +/*********************************************************************************************** + * TechnoClass::Nearby_Location -- Radiates outward looking for clear cell nearby. * + * * + * This routine is used to find a nearby location from center of this object. It can lean * + * toward finding a location closest to an optional object. * + * * + * INPUT: object -- Optional object that the finding algorithm will try to find a close * + * spot to. * + * * + * OUTPUT: Returns with the cell that is closest to this object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/06/1995 JLB : Created. * + *=============================================================================================*/ +CELL TechnoClass::Nearby_Location(TechnoClass const * ) const +{ + /* + ** Radiate outward from the object's location, looking for the best + ** target. + */ + CELL best = 0; + CELL cell = Coord_Cell(Center_Coord()); + for (int radius = 0; radius < MAP_CELL_W/2; radius++) { + + /* + ** Scan the top and bottom rows of the "box". + */ + for (int x = -radius; x <= radius; x++) { + CELL newcell = cell + XY_Cell(x, -radius); + if (Map.In_Radar(newcell) && Map[newcell].Is_Generally_Clear()) { + best = newcell; + } + + newcell = cell + XY_Cell(x, radius); + if (Map.In_Radar(newcell) && Map[newcell].Is_Generally_Clear()) { + best = newcell; + } + } + + /* + ** Scan the left and right columns of the "box". + */ + for (int y = -(radius-1); y < radius; y++) { + CELL newcell = cell + XY_Cell(-radius, y); + if (Map.In_Radar(newcell) && Map[newcell].Is_Generally_Clear()) { + best = newcell; + } + + newcell = cell + XY_Cell(radius, y); + if (Map.In_Radar(newcell) && Map[newcell].Is_Generally_Clear()) { + best = newcell; + } + } + + if (best) break; + } + + return(best); +} + + +/*********************************************************************************************** + * TechnoClass::Do_Uncloak -- Cause the stealth tank to uncloak. * + * * + * This routine will start the stealth tank to uncloak. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Do_Uncloak(void) +{ + if (IsCloakable && (Cloak == CLOAKED || Cloak == CLOAKING)) { + Sound_Effect(VOC_CLOAK, Coord); + Cloak = UNCLOAKING; + CloakingDevice.Set_Stage(0); + CloakingDevice.Set_Rate(1); + } +} + + +/*********************************************************************************************** + * TechnoClass::Do_Cloak -- Start the object into cloaking stage. * + * * + * This routine will start the object into its cloaking state. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Do_Cloak(void) +{ + if (IsCloakable && (Cloak == UNCLOAKED || Cloak == UNCLOAKING)) { + Sound_Effect(VOC_CLOAK, Coord); + Detach_All(false); + Cloak = CLOAKING; + CloakingDevice.Set_Stage(0); + CloakingDevice.Set_Rate(1); + } +} + + +/*********************************************************************************************** + * TechnoClass::Do_Shimmer -- Causes this object to shimmer if it is cloaked. * + * * + * This routine is called when this object should shimmer. If the object is cloaked, then * + * a shimmering effect (partial decloak) occurs. For objects that are not cloaked, no * + * affect occurs. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Do_Shimmer(void) +{ + if (IsCloakable && Cloak == CLOAKED) { + Cloak = CLOAKING; + CloakingDevice.Set_Stage(MAX_UNCLOAK_STAGE/2); + CloakingDevice.Set_Rate(1); + } +} + + +/*********************************************************************************************** + * TechnoClass::Visual_Character -- Determine the visual character of the object. * + * * + * This routine will determine how this object should be drawn. Typically, this is the * + * unmodified visible state, but cloaked objects have a different character. * + * * + * INPUT: raw -- Should the check be based on the unmodified cloak condition of the * + * object? If false, then an object owned by the player will never become * + * completely invisible. * + * * + * OUTPUT: Returns with the visual character to use when displaying this object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/07/1995 JLB : Created. * + *=============================================================================================*/ +VisualType TechnoClass::Visual_Character(bool raw) +{ + /* + ** When uncloaked or in map editor mode, always draw the object normally. + */ + if (Cloak == UNCLOAKED || Debug_Map) return(VISUAL_NORMAL); + + /* + ** A cloaked unit will not be visible at all unless it is owned + ** by the player. + */ + if (Cloak == CLOAKED) { + if (!raw && IsOwnedByPlayer) return(VISUAL_SHADOWY); + return(VISUAL_HIDDEN); + } + + int stage = CloakingDevice.Fetch_Stage(); + if (Cloak == UNCLOAKING) stage = MAX_UNCLOAK_STAGE - stage; + if (stage <= 0) { + return(VISUAL_NORMAL); + } + + stage = Cardinal_To_Fixed(MAX_UNCLOAK_STAGE, stage); + + if (stage < 0x0040) return(VISUAL_INDISTINCT); + if (stage < 0x0080) return(VISUAL_DARKEN); + if (stage < 0x00C0) return(VISUAL_SHADOWY); + if (!raw && IsOwnedByPlayer) return(VISUAL_SHADOWY); + if (stage < 0x00FF) return(VISUAL_RIPPLE); + return(VISUAL_HIDDEN); +} + + +/*********************************************************************************************** + * TechnoClass::Techno_Draw_Object -- General purpose draw object routine. * + * * + * This routine is used to draw the object. It will handle any remapping or cloaking * + * effects required. This logic is isolated here since all techno object share the same * + * render logic when it comes to remapping and cloaking. * + * * + * INPUT: shapefile -- Pointer to the shape file that the shape will be drawn from. * + * * + * shapenum -- The shape number of the object in the file to use. * + * * + * x,y -- Center pixel coordinate to use for rendering this object. * + * * + * window -- The clipping window to use when rendering. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Techno_Draw_Object(void const * shapefile, int shapenum, int x, int y, WindowNumberType window) +{ + if (shapefile) { + VisualType visual = Visual_Character(); + void const * remap = Remap_Table(); + + if (visual != VISUAL_HIDDEN && visual != VISUAL_RIPPLE) { + if (visual == VISUAL_SHADOWY) { + CC_Draw_Shape(shapefile, shapenum, x, y, window, SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_FADING|SHAPE_PREDATOR, NULL, Map.FadingShade); + } else { + CC_Draw_Shape(shapefile, shapenum, x, y, window, SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_FADING|SHAPE_GHOST, remap, Map.UnitShadow); + } + if (visual == VISUAL_DARKEN) { + CC_Draw_Shape(shapefile, shapenum, x, y, window, SHAPE_PREDATOR|SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_FADING, remap, Map.FadingShade); + } + } + if (visual != VISUAL_NORMAL && visual != VISUAL_HIDDEN) { + CC_Draw_Shape(shapefile, shapenum, x, y, window, SHAPE_PREDATOR|SHAPE_CENTER|SHAPE_WIN_REL); + } + } +} + + +/*********************************************************************************************** + * TechnoClass::Remap_Table -- Fetches the appropriate remap table to use. * + * * + * This routine is used to fetch the appropriate remap table to use for this object. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the remap table to use for this object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +void const * TechnoClass::Remap_Table(void) +{ + return(House->Remap_Table(IsBlushing, true)); +} + + +/*********************************************************************************************** + * TechnoClass::Detach -- Handles removal of target from tracking system. * + * * + * This routine is called when the specified object is about to be removed from the game * + * system. The target object is removed from any tracking computers that this object may * + * have. * + * * + * INPUT: target -- The target object (as a target value) that is being removed from the * + * game. * + * * + * all -- Is the target about to die? A false value might indicate that the * + * object is merely cloaking. In such a case, radio contact will not * + * be affected. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Detach(TARGET target, bool all) +{ + RadioClass::Detach(target, all); + + if (SuspendedMission != MISSION_NONE && SuspendedTarCom == target) { + SuspendedMission = MISSION_NONE; + SuspendedTarCom = TARGET_NONE; + } + + /* + ** If the targeting computer is assigned to the target, then the targeting + ** computer must be cleared. + */ + if (TarCom == target) { + Assign_Target(TARGET_NONE); + Restore_Mission(); + } + + /* + ** If it is in radio contact with another object, then that radio contact + ** must be broken. + */ + if (all && In_Radio_Contact() && Contact_With_Whom()->As_Target() == target) { + Transmit_Message(RADIO_OVER_OUT); + } +} + + +/*********************************************************************************************** + * TechnoClass::Kill_Cargo -- Destroys any cargo attached to this object. * + * * + * This routine handles the destruction of any cargo this object may contain. Typical of * + * this would be when a transport helicopter gets destroyed. * + * * + * INPUT: source -- The source of the destruction of the cargo. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Kill_Cargo(TechnoClass * source) +{ + while (Is_Something_Attached()) { + FootClass * foot = Detach_Object(); + if (foot) { + foot->Record_The_Kill(source); + delete foot; + } + } +} + + +/*********************************************************************************************** + * TechnoClass::Crew_Type -- Fetches the kind of crew this object contains. * + * * + * This routine is called when generating survivors to this object. This routine returns * + * the type of survivor to generate. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the infantry type of a survivor. * + * * + * WARNINGS: This routine is designed to be called repeatedly. Once for each survivor to * + * generate. * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +InfantryType TechnoClass::Crew_Type(void) const +{ + InfantryType infantry = INFANTRY_E1; + if (House->ActLike == HOUSE_NEUTRAL) { + infantry = Random_Pick(INFANTRY_C1, INFANTRY_C9); + } else { + if (Techno_Type_Class()->Primary == WEAPON_NONE && Random_Pick(0, 6) == 1) { + if (Random_Pick(0, 1) == 0) { + infantry = INFANTRY_C1; + } else { + infantry = INFANTRY_C7; + } + } + } + return(infantry); +} + + +/*********************************************************************************************** + * TechnoClass::Value -- Fetches the target value for this object. * + * * + * This routine is used to fetch the target value for this object. The greater the value * + * returned, the better this object is as a target. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the target value for this object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + * 08/16/1995 JLB : Adjusted for early mission lame-out. * + *=============================================================================================*/ +int TechnoClass::Value(void) const +{ + int value = 0; + + /* + ** In early missions, contents of transports are not figured + ** into the total value. - 8/16/95 + */ + if (BuildLevel > 8 || GameToPlay != GAME_NORMAL) { + if (Is_Something_Attached()) { + FootClass * object = Attached_Object(); + + while (object) { + value += object->Value(); + object = (FootClass *)object->Next; + } + } + } + return Risk() + Techno_Type_Class()->Reward + value; +} + + +/*********************************************************************************************** + * TechnoClass::Threat_Range -- Returns the range to scan based on threat control. * + * * + * This routine will return the range to scan based on the control value specified. The * + * value returned by this routine is typically used when scanning for enemies. * + * * + * INPUT: control -- The range control parameter. * + * 0 = Use weapon range (zero is returned in this special case). * + * -1 = Scan without range restrictions (-1 is returned in this case). * + * 1 = Scan up to twice weapon range. * + * * + * OUTPUT: Returns with a range (or special value) that can be used in the threat scan * + * process. If zero is returned, then always check threat against In_Range(). If * + * -1 is returned, then no range limitation restriction exists. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +int TechnoClass::Threat_Range(int control) const +{ + if (control == -1) return(-1); + if (control == 0) return(0); + + int range = MAX(Weapon_Range(0), Weapon_Range(1)); + range *= 2; + range = Bound(range, 0x0000, 0x0A00); + return(range); +} + + +/*********************************************************************************************** + * TechnoClass::Base_Is_Attacked -- Handle panic response to base being attacked. * + * * + * This routine is called when the base is being attacked. It will pull units off of the * + * field and send them back to defend the base. This routine will make taking an enemy * + * base much more difficult. * + * * + * INPUT: enemy -- Pointer to the enemy object that did the damage on the base. * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine can drastically affect the game play. The computer will probably * + * call off its attacks as a result. * + * * + * HISTORY: * + * 06/25/1995 JLB : Commented. * + *=============================================================================================*/ +void TechnoClass::Base_Is_Attacked(TechnoClass const *enemy) +{ + FootClass *defender[6]; + int value[6]; + int count = 0; + int weakest = 0; + int desired = enemy->Risk() * 2; + int risktotal = 0; + + /* + ** Humans have to deal with their own base is attacked problems. + */ + if (!enemy || House->Is_Ally(enemy) || House->IsHuman) { + return; + } + + /* + ** Don't overreact if this building can defend itself. + */ + if (Techno_Type_Class()->Primary != WEAPON_NONE) return; + + /* + ** If the enemy is not an infantry or a unit there is not much we can + ** do about it. + */ + if (enemy->What_Am_I() != RTTI_INFANTRY && enemy->What_Am_I() != RTTI_UNIT ) { + return; + } + + /* + ** If the enemy is a gunboat, then don't do anything. + */ + // This should allow helicopters to retaliate however. Hmmm. + if (enemy->What_Am_I() == RTTI_UNIT && (*((UnitClass const *)enemy) == UNIT_GUNBOAT || *((UnitClass const *)enemy) == UNIT_HOVER)) { + return; + } + + /* + ** If the threat has already been dealt with then we don't need to do + ** any work. Check for that here. + */ + if (!((FootClass *)enemy)->BaseAttackTimer.Expired()) { + return; + } + + /* + ** We will need units to defend our base. We need to suspend teams until + ** the situation has been dealt with. + */ + TeamClass::Suspend_Teams(20); + + /* + ** Loop through the infantry looking for those who are capable of going + ** on a rescue mission. + */ + for (int index = 0; index < Infantry.Count() && desired > 0; index++) { + InfantryClass * infantry = Infantry.Ptr(index); + if (infantry && infantry->Owner() == Owner()) { + + /* + ** Never recruite sticky guard units to defend a base. + */ + if (infantry->Mission == MISSION_STICKY || infantry->Mission == MISSION_SLEEP) continue; + + /* + ** Find the amount of threat that this unit can apply to the + ** enemy. + */ + int threat = infantry->Rescue_Mission(enemy->As_Target()); + + /* + ** If it can't apply any threat then do just skip it and do not + ** add it to the list. + */ + if (!threat) { + continue; + } + + /* + ** If the value returned is negative then this unit is already + ** assigned to fighting the enemy, so subtract its value from + ** the enemys desired value. + */ + if (threat < 0) { + desired += threat; + continue; + } + + if (count < 6) { + defender[count] = (FootClass *)infantry; + value[count] = threat; + count++; + continue; + } + + if (threat > weakest) { + int newweakest = threat; + + for (int lp = 0; lp < count; lp ++) { + if (value[lp] == weakest) { + value[lp] = threat; + defender[lp] = (FootClass *) infantry; + continue; + } + if (value[count] < newweakest) { + newweakest = value[lp]; + } + } + weakest = newweakest; + } + } + } + + /* + ** Loop through the units looking for those who are capable of going + ** on a rescue mission. + */ + for (index = 0; index < Units.Count() && desired > 0; index++) { + UnitClass * unit = Units.Ptr(index); + if (unit && unit->Owner() == Owner()) { + + /* + ** Never recruite sticky guard units to defend a base. + */ + if (unit->Mission == MISSION_STICKY || unit->Mission == MISSION_SLEEP) continue; + + /* + ** Find the amount of threat that this unit can apply to the + ** enemy. + */ + int threat = unit->Rescue_Mission(enemy->As_Target()); + + /* + ** If it can't apply any threat then do just skip it and do not + ** add it to the list. + */ + if (!threat) { + continue; + } + + /* + ** If the value returned is negative then this unit is already + ** assigned to fighting the enemy, so subtract its value from + ** the enemys desired value. + */ + if (threat < 0) { + desired += threat; + continue; + } + + if (count < 6) { + defender[count] = (FootClass *)unit; + value[count] = threat; + count++; + continue; + } + if (threat > weakest) { + int newweakest = threat; + + for (int lp = 0; lp < count; lp ++) { + if (value[lp] == weakest) { + value[lp] = threat; + defender[lp] = (FootClass *) unit; + continue; + } + if (value[count] < newweakest) { + newweakest = value[lp]; + } + } + weakest = newweakest; + } + } + } + + if (desired > 0) { + + /* + ** Sort the defenders by value, this doesn't take very long and will + ** help the closest defenders to respond. + */ + for (int lp = 0; lp < count - 1; lp ++) { + for (int lp2 = lp + 1; lp2 < count; lp2++) { + if (value[lp] < value[lp2]) { + + value[lp] ^= value[lp2]; + value[lp2] ^= value[lp]; + value[lp] ^= value[lp2]; + + FootClass *temp; + temp = defender[lp]; + defender[lp] = defender[lp2]; + defender[lp2] = temp; + } + } + } + + for (lp = 0; lp < count; lp ++) { + defender[lp]->Assign_Mission(MISSION_RESCUE); + defender[lp]->Assign_Target(enemy->As_Target()); + risktotal += defender[lp]->Risk(); + if (risktotal > desired) { + break; + } + } + } + + if (risktotal > desired) { + ((FootClass *)enemy)->BaseAttackTimer.Set(15 * 15); + } +} + + +/*********************************************************************************************** + * TechnoClass::Get_Ownable -- Fetches the ownable bits for this object. * + * * + * This routine will return the ownable bits for this object. The ownable bits represent * + * the houses that are allowed to own this object. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the ownable bits for this object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +unsigned char TechnoClass::Get_Ownable(void) const +{ + return ((TechnoTypeClass const &)Class_Of()).Ownable; +} + + +/*********************************************************************************************** + * TechnoClass::Is_Techno -- Confirms that this is a TechnoClass object. * + * * + * This routine is called to confirm if this object is derived from the TechnoClass * + * object. At this level, the return value will always be true. * + * * + * INPUT: none * + * * + * OUTPUT: Is this object a TechnoClass or derived from it? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +bool TechnoClass::Is_Techno(void) const +{ + return(true); +} + + +/*********************************************************************************************** + * TechnoClass::Risk -- Fetches the risk associated with this object. * + * * + * This routine is called when the risk value for this object needs to be determined. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the risk value for this object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +int TechnoClass::Risk(void) const +{ + return(Techno_Type_Class()->Risk); +} + + +/*********************************************************************************************** + * TechnoClass::Tiberium_Load -- Fetches the current tiberium load percentage. * + * * + * This routine will return the current Tiberium load (expressed as a fixed point fraction) * + * that this object currently contains. Typical implementor of this function would be * + * the harvester. Any object that can return a non-zero value should derive from this * + * function in order to return the appropriate value. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the current Tiberium load expressed as a fixed point number. * + * 0x0000 = empty * + * 0x0080 = half full * + * 0x0100 = full * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +int TechnoClass::Tiberium_Load(void) const +{ + return(0x0000); +} + + +/*********************************************************************************************** + * TechnoClass::Desired_Load_Dir -- Fetches loading parameters for this object. * + * * + * This routine is called when an object desires to load up on this object. The object * + * desiring to load is specified. The cell that the loading object should move to is * + * determined. The direction that this object should face is also calculated. This routine * + * will be overridden by those objects that can actually load up passengers. * + * * + * INPUT: object -- The object that is desiring to load up. * + * * + * moveto -- Reference to the cell that the loading object should move to before * + * the final load process occurs (this value will be filled in). * + * * + * OUTPUT: Returns with the direction that the transport object should face. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +DirType TechnoClass::Desired_Load_Dir(ObjectClass * , CELL & moveto) const +{ + moveto = 0; + return(DIR_N); +} + + +/*********************************************************************************************** + * TechnoClass::Pip_Count -- Fetches the number of pips to display on this object. * + * * + * This routine will return the number of pips to display on this object when the object * + * is selected. The default condition is to return no pips at all. This routine is * + * derived for those objects that can have pips. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of pips to display on this object when selected. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +int TechnoClass::Pip_Count(void) const +{ + return(0); +} + + +/*********************************************************************************************** + * TechnoClass::Fire_Direction -- Fetches the direction projectile fire will take. * + * * + * This routine will fetch the direction that a fired projectile will take. This is * + * usually the facing of the object's weapon. This routine will be derived for the objects * + * that have their weapon barrel facing a different direction than the body. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the direction a fired projectile will take. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +DirType TechnoClass::Fire_Direction(void) const +{ + return(PrimaryFacing.Current()); +} + + +/*********************************************************************************************** + * TechnoClass::Response_Select -- Handles the voice response when selected. * + * * + * This routine is called when a voice reponse to a select action is desired. This routine * + * should be overridden for any object that actually has a voice response. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This routine can generate an audio response. * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Response_Select(void) +{ +} + + +/*********************************************************************************************** + * TechnoClass::Response_Move -- Handles the voice repsonse to a movement request. * + * * + * This routine is called when a voice response to a movement order is desired. This * + * routine should be overridden for any object that actually has a voice response. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This can generate an audio repsonse. * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Response_Move(void) +{ +} + + +/*********************************************************************************************** + * TechnoClass::Response_Attack -- Handles the voice response when given attack order. * + * * + * This routine is called when a voice response to an attack order is desired. This routine * + * should be overridden for any object that actually have a voice response. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: This can generate an audio response. * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Response_Attack(void) +{ +} + + +/*********************************************************************************************** + * TechnoClass::Target_Something_Nearby -- Handles finding and assigning a nearby target. * + * * + * This routine will search for a nearby target and assign it to this object's TarCom. * + * The method to use when scanning for a target is controlled by the parameter passed. * + * * + * INPUT: threat -- The threat control parameter used to control the range searched. The * + * only values recognized are THREAT_RANGE and THREAT_AREA. * + * * + * OUTPUT: Was a suitable target aquired and assigned to the TarCom? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +bool TechnoClass::Target_Something_Nearby(ThreatType threat) +{ + threat = threat & (THREAT_RANGE|THREAT_AREA); + + /* + ** Determine that if there is an existing target it is still legal + ** and within range. + */ + if (Target_Legal(TarCom)) { + if ((threat & THREAT_RANGE) && !In_Range(TarCom)) { + Assign_Target(TARGET_NONE); + } + } + + /* + ** If there is no target, then try to find one and assign it as + ** the target for this unit. + */ + if (!Target_Legal(TarCom)) { + Assign_Target(Greatest_Threat(threat)); + } + + /* + ** Return with answer to question: Does this unit have a target? + */ + return(Target_Legal(TarCom)); +} + + +/*********************************************************************************************** + * TechnoClass::Exit_Object -- Causes specified object to leave this object. * + * * + * This routine is called when there is an attached object that should detach and leave * + * this object. Typical of this would be the refinery and APC. * + * * + * INPUT: object -- The object that is trying to leave this object. * + * * + * OUTPUT: Was the object successfully launched from this object? Failure might indicate that * + * there is insufficient room to detach the specified object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/24/1995 JLB : Created. * + *=============================================================================================*/ +int TechnoClass::Exit_Object(TechnoClass *) +{ + return(0); +} + + +/*********************************************************************************************** + * TechnoClass::Random_Animate -- Performs some idle animation for the object. * + * * + * This is a maintenance routine that is called when the object might want to check to see * + * if it should go into some idle animation. Infantry are a good example of objects that * + * perform idle animations. This routine must be overridden by the derived object types * + * in order to give it some functionality. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/24/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Random_Animate(void) +{ +} + + +/*********************************************************************************************** + * TechnoClass::Assign_Destination -- Assigns movement destination to the object. * + * * + * This routine is called when the object needs to have a new movement destination * + * assigned. This routine must be overridden since at this level, movement is not allowed. * + * * + * INPUT: destination -- The destination to assign to this object. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/24/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Assign_Destination(TARGET ) +{ +} + + +/*********************************************************************************************** + * TechnoClass::Scatter -- Causes the object to scatter to an adjacent cell. * + * * + * This routine is called when the object needs to get out of the way. This might be as a * + * result of combat or findpath reasons. * + * * + * INPUT: coord -- The source of the reason to scatter. The object should try to run * + * away from this coordinate. * + * * + * forced -- Is the scatter a forced scatter? If false, then this is merely a * + * request that scattering might be a good idea. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/24/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Scatter(COORDINATE , bool ) +{ +} + + +/*********************************************************************************************** + * TechnoClass::Enter_Idle_Mode -- Object enters its default idle condition. * + * * + * This routine is called when the object should intelligently revert to an idle state. * + * Typically this routine is called after some mission has completed. This routine must * + * be overridden by the various object types. It is located at this level merely to provide * + * a virtual function entry point. * + * * + * INPUT: initial -- Is this called when the unit just leaves a factory or is initially * + * or is initially placed on the map? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/24/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Enter_Idle_Mode(bool ) +{ +} + + +/*********************************************************************************************** + * TechnoClass::Draw_Pips -- Draws the transport pips and other techno graphics. * + * * + * This routine is used to render the small transportation pip (occupant feedback graphic) * + * used for transporter object. It will also display if the techno object is "primary" * + * if necessary. * + * * + * INPUT: x,y -- The pixel coordinate for the center of the first pip. Subsiquent pips * + * are drawn rightward. * + * * + * window-- The window that pip clipping is relative to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/08/1995 JLB : Created. * + *=============================================================================================*/ +void TechnoClass::Draw_Pips(int x, int y, WindowNumberType window) +{ + /* + ** Transporter type objects have a different graphic representation for the pips. The + ** pip color represents the type of occupant. + */ + if (Techno_Type_Class()->IsTransporter) { + ObjectClass const * object = Attached_Object(); + + for (int index = 0; index < Class_Of().Max_Pips(); index++) { + PipEnum pip = PIP_EMPTY; + + if (object) { + pip = PIP_FULL; + if (object->What_Am_I() == RTTI_INFANTRY) { + if (*((InfantryClass *)object) == INFANTRY_RAMBO) { + pip = PIP_COMMANDO; + } + if (*((InfantryClass *)object) == INFANTRY_E7) { + pip = PIP_ENGINEER; + } + if (((InfantryClass *)object)->Class->IsCivilian) { + pip = PIP_CIVILIAN; + } + } + object = object->Next; + } + CC_Draw_Shape(Class_Of().PipShapes, pip, x+index*3, y, window, SHAPE_CENTER|SHAPE_WIN_REL); + } + + } else { + + /* + ** Display number of how many attached objects there are. This is also used + ** to display the fullness rating for a harvester. + */ + int pips = Pip_Count(); + for (int index = 0; index < Class_Of().Max_Pips(); index++) { + CC_Draw_Shape(Class_Of().PipShapes, (index < pips) ? PIP_FULL : PIP_EMPTY, x+index*3, y, window, SHAPE_CENTER|SHAPE_WIN_REL); + } + } + + /* + ** Display whether this unit is a leader unit or not. + */ + if (IsLeader) { + CC_Draw_Shape(Class_Of().PipShapes, PIP_PRIMARY, x-2, y-3, window, /*SHAPE_CENTER|*/SHAPE_WIN_REL); + } +} + + +/*********************************************************************************************** + * TechnoClass::Find_Docking_Bay -- Searches for a close docking bay. * + * * + * This routine will be used to find a building that can serve as a docking bay. The * + * closest building that qualifies will be returned. If no building could be found then * + * return with NULL. * + * * + * INPUT: b -- The structure type to look for. * + * * + * friendly -- Allow searching for allied buildings as well. * + * * + * OUTPUT: Returns with a pointer to the building that can serve as the best docking bay. * + * * + * WARNINGS: This routine might return NULL even if there are buildings of the specified * + * type available. This is the case when the building(s) are currently busy and * + * cannot serve as a docking bay at the moment. * + * * + * HISTORY: * + * 07/18/1995 JLB : Created. * + * 08/13/1995 JLB : Recognizes the "IsLeader" method of building preference. * + *=============================================================================================*/ +BuildingClass * TechnoClass::Find_Docking_Bay(StructType b, bool friendly) const +{ + BuildingClass * best = 0; + + /* + ** First check to see if there are ANY buildings of the specified + ** type in thi house's inventory. If not, then don't bother to scan + ** for one. + */ + if (House->BScan & (1L << b)) { + int bestval = -1; + + /* + ** Loop through all the buildings and find the one that matches the specification + ** and is willing to dock with this object. + */ + for (int index = 0; index < Buildings.Count(); index++) { + BuildingClass * building = Buildings.Ptr(index); + + /* + ** Check to see if the building qualifies (preliminary scan). + */ + if (building && + (friendly ? building->House->Is_Ally(this) : building->House == House) && + !building->IsInLimbo && + *building == b && + ((TechnoClass *)this)->Transmit_Message(RADIO_CAN_LOAD, building) == RADIO_ROGER) { + + /* + ** If the building qualifies and this building is better than the + ** last qualifying building (as rated by distance), then record + ** this building and keep scanning. + */ + if (bestval == -1 || Distance(building) < bestval || building->IsLeader) { + best = building; + bestval = Distance(building); + } + } + } + } + return(best); +} + + +/*********************************************************************************************** + * TechnoClass::Find_Exit_Cell -- Finds an appropriate exit cell for this object. * + * * + * This routine is called when an object would like to exit from this (presumed) transport. * + * A suitable cell should be returned by this routine. The specified object will probably * + * be unloaded at that cell. * + * * + * INPUT: techno -- Pointer to the object that would like to unload. This is used to * + * determine suitability for placement. * + * * + * OUTPUT: Returns with the cell that is recommended for object exit. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/12/1995 JLB : Created. * + *=============================================================================================*/ +CELL TechnoClass::Find_Exit_Cell(TechnoClass const * ) const +{ + return(Coord_Cell(Docking_Coord())); +} + + +/*********************************************************************************************** + * TechnoClass::Refund_Amount -- Returns with the money to refund if this object is sold. * + * * + * This routine is used by the selling back mechanism in order to credit the owning house * + * with some refund credits. The value returned is the credits to refund to the owner. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the credits to refund to the owner. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +int TechnoClass::Refund_Amount(void) const +{ + int cost = Techno_Type_Class()->Raw_Cost(); + + /* + ** If the object is carrying Tiberium directly (i.e., the harvester), then + ** account for the credits of the load. + */ + cost += Fixed_To_Cardinal(UnitTypeClass::FULL_LOAD_CREDITS, Tiberium_Load())/2; + + if (House->IsHuman) { + cost /= 2; + } + return(cost); +} diff --git a/TECHNO.H b/TECHNO.H new file mode 100644 index 0000000..5aa2376 --- /dev/null +++ b/TECHNO.H @@ -0,0 +1,319 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\techno.h_v 2.17 16 Oct 1995 16:46:58 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TECHNO.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 14, 1994 * + * * + * Last Update : April 14, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef TECHNO_H +#define TECHNO_H + +#include "radio.h" +#include "stage.h" +#include "cargo.h" +#include "flasher.h" +#include "house.h" +#include "target.h" +#include "bullet.h" +#include "door.h" +#include "crew.h" + +/**************************************************************************** +** This is the common data between building and units. +*/ +class TechnoClass : public RadioClass, + public FlasherClass, + public StageClass, + public CargoClass, + public DoorClass, + public CrewClass +{ + public: + + /* + ** This flag will be true if the object has been damaged with malace. + ** Damage received due to friendly fire or wear and tear does not count. + ** The computer is not allowed to sell a building unless it has been + ** damaged with malace. + */ + unsigned IsTickedOff:1; + + /* + ** If this object has inherited the ability to cloak, then this bit will + ** be set to true. + */ + unsigned IsCloakable:1; + + /* + ** If this object is designated as special then this flag will be true. For + ** buildings, this means that it is the primary factory. For units, it means + ** that the unit is the team leader. + */ + unsigned IsLeader:1; + + /* + ** Certain units are flagged as "loaners". These units are typically transports that + ** are created solely for the purpose of delivering reinforcements. Such "loaner" + ** units are not owned by the player and thus cannot be directly controlled. These + ** units will leave the game as soon as they have fulfilled their purpose. + */ + unsigned IsALoaner:1; + + /* + ** Once a unit enters the map, then this flag is set. This flag is used to make + ** sure that a unit doesn't leave the map once it enters the map. + */ + unsigned IsLocked:1; + + /* + ** Buildings and units with turrets usually have a recoil animation when they + ** fire. If this flag is true, then the next rendering of the object will be + ** in the "recoil state". The flag will then be cleared pending the next + ** firing event. + */ + unsigned IsInRecoilState:1; + + /* + ** If this unit is "loosely attached" to another unit it is given special + ** processing. A unit is in such a condition when it is in the process of + ** unloading from a transport type object. During the unloading process + ** the transport object must stay still until the unit is free and clear. + ** At that time it radios the transport object and the "tether" is broken - + ** freeing both the unit and the transport object. + */ + unsigned IsTethered:1; + + /* + ** Is this object owned by the player? If not, then it is owned by the computer + ** or remote opponent. This flag facilitates the many logic differences when dealing + ** with player's or computer's units or buildings. + */ + unsigned IsOwnedByPlayer:1; + + /* + ** The more sophisticated game objects must keep track of whether they are discovered + ** or not. This is because the state of discovery can often control how the object + ** behaves. In addition, this fact is used in radar and user I/O processing. + */ + unsigned IsDiscoveredByPlayer:1; + + /* + ** This is used to control the computer recognizing this object. + */ + unsigned IsDiscoveredByComputer:1; + + /* + ** Some game objects can be of the "lemon" variety. This means that they take damage + ** even when everything is ok. This adds a little variety to the game. + */ + unsigned IsALemon:1; + + /* + ** This flag is used to control second shot processing for those units or buildings + ** that fire two shots in quick succession. When this flag is true, it indicates that + ** the second shot is ready to fire. After this shot is fired, regular rearm timing + ** is used rather than the short rearm time. + */ + unsigned IsSecondShot:1; + + /* + ** This is the house that the unit belongs to. + */ + HouseClass * House; + + /* + ** This records the current cloak state for this vehicle. + */ + CloakType Cloak; + StageClass CloakingDevice; + + /* (Targeting Computer) + ** This is the target value for the item that this vehicle should ATTACK. If this + ** is a vehicle with a turret, then it may differ from its movement destination. + */ + TARGET TarCom; + TARGET SuspendedTarCom; + + /* + ** This is the visible facing for the unit or building. + */ + FacingClass PrimaryFacing; + + /* + ** This is the arming countdown. It represents the time necessary + ** to reload the weapon. + */ + unsigned char Arm; + + /* + ** The number of shot this object can fire before running out of ammo. If this + ** value is zero, then firing is not allowed. If -1, then there is no ammunition + ** limit. + */ + int Ammo; + + /* + ** This is the amount of money spent to produce this object. This value really + ** only comes into play for the case of buildings that have special "free" + ** objects available when purchased at the more expensive rate. + */ + int PurchasePrice; + + /*--------------------------------------------------------------------- + ** Constructors, Destructors, and overloaded operators. + */ + TechnoClass(void); + TechnoClass(HousesType house); + virtual ~TechnoClass(void) {}; + + /* + ** Query functions. + */ + virtual int Refund_Amount(void) const; + virtual CELL Find_Exit_Cell(TechnoClass const * techno) const; + virtual BuildingClass * Find_Docking_Bay(StructType b, bool friendly) const; + virtual int Threat_Range(int control) const; + virtual InfantryType Crew_Type(void) const; + TechnoTypeClass const * Techno_Type_Class(void) const {return((TechnoTypeClass const *)&Class_Of());}; + CELL Nearby_Location(TechnoClass const * from=NULL) const; + virtual unsigned char Get_Ownable(void) const; + virtual bool Can_Player_Fire(void) const; + virtual bool Can_Player_Move(void) const; + virtual bool Is_Weapon_Equipped(void) const; + virtual bool Can_Repair(void) const; + virtual bool Is_Techno(void) const; + virtual HousesType Owner(void) const; + virtual int Risk(void) const; + virtual int Value(void) const; + virtual int Rearm_Delay(bool second=true) const; + virtual ActionType What_Action(ObjectClass * target) const; + virtual ActionType What_Action(CELL cell) const; + virtual int Tiberium_Load(void) const; + virtual DirType Desired_Load_Dir(ObjectClass * , CELL & moveto) const; + virtual int Pip_Count(void) const; + virtual DirType Fire_Direction(void) const; + + /* + ** User I/O. + */ + virtual void Clicked_As_Target(int count=7); + virtual bool Select(void); + virtual void Response_Select(void); + virtual void Response_Move(void); + virtual void Response_Attack(void); + virtual void Player_Assign_Mission(MissionType order, TARGET target=TARGET_NONE, TARGET destination=TARGET_NONE); + + /* + ** Combat related. + */ + void Base_Is_Attacked(TechnoClass const *enemy); + void Kill_Cargo(TechnoClass * source); + virtual void Record_The_Kill(TechnoClass * source); + virtual bool Target_Something_Nearby(ThreatType threat=THREAT_NORMAL); + virtual void Stun(void); + virtual bool In_Range(COORDINATE coord, int which=0) const; + virtual bool In_Range(TARGET target, int which=0) const; + virtual bool In_Range(ObjectClass const * target, int which=0) const; + virtual void Death_Announcement(TechnoClass const * source=0) const = 0; + virtual FireErrorType Can_Fire(TARGET target, int which=0) const; + virtual TARGET Greatest_Threat(ThreatType threat) const; + virtual void Assign_Target(TARGET target); + virtual void Override_Mission(MissionType mission, TARGET tarcom, TARGET navcom); + virtual bool Restore_Mission(void); + virtual BulletClass * Fire_At(TARGET target, int which=0); + virtual int Weapon_Range(int which) const; + virtual bool Captured(HouseClass * newowner); + virtual ResultType Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source); + bool Evaluate_Cell(ThreatType method, int mask, CELL cell, int range, TechnoClass const ** object, int & value) const; + bool Evaluate_Object(ThreatType method, int mask, int range, TechnoClass const * object, int & value) const; + + /* + ** AI. + */ + virtual void AI(void); + virtual bool Revealed(HouseClass * house); + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, long & param); + + /* + ** Scenario and debug support. + */ + #ifdef CHEAT_KEYS + virtual void Debug_Dump(MonoClass *mono) const; + #endif + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /* + ** Display and rendering support functionality. Supports imagery and how + ** object interacts with the map and thus indirectly controls rendering. + */ + virtual void const * Remap_Table(void); + VisualType Visual_Character(bool raw = false); + void Techno_Draw_Object(void const * shapefile, int shapenum, int x, int y, WindowNumberType window); + virtual void Draw_It(int x, int y, WindowNumberType window); + virtual void Draw_Pips(int x, int y, WindowNumberType window); + virtual void Hidden(void); + virtual bool Mark(MarkType mark); + virtual int Exit_Object(TechnoClass *); + virtual void Do_Uncloak(void); + virtual void Do_Cloak(void); + virtual void Do_Shimmer(void); + + /* + ** Movement and animation. + */ + virtual void Random_Animate(void); + virtual void Assign_Destination(TARGET target); + virtual void Scatter(COORDINATE source = NULL, bool forced=false); + virtual void Per_Cell_Process(bool); + virtual void Enter_Idle_Mode(bool initial=false); + + /* + ** Map entry and exit logic. + */ + virtual bool Unlimbo(COORDINATE , DirType facing=DIR_N); + virtual void Detach(TARGET target, bool all); + + /* + ** Facing translation tables that fix the flaw with 3D studio when + ** it renders 45 degree angles. + */ + static int const BodyShape[32]; +// static int const TurretShape[32]; +}; + +#endif diff --git a/TEMP.CPP b/TEMP.CPP new file mode 100644 index 0000000..052327f --- /dev/null +++ b/TEMP.CPP @@ -0,0 +1,41 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + + int hfile; + long length; + + hfile = _lopen(lpstrFileName, OF_READ | OF_SHARE_DENY_WRITE); + if (hfile == -1) { + return(0); + } + + hfile = _lopen(lpstrFileName, OF_READ | OF_SHARE_EXCLUSIVE); + if (hfile == -1) { + hfile = _lcreat(lpstrFileName, 0); + if (hfile == -1) { + return(0); + } + } + length = sizeof(MobileClass); + if (length != _lwrite(hfile, this, lenght)) { + _lclose(hfile); + return(0); + } + _lclose(hfile); + return(TRUE); +} \ No newline at end of file diff --git a/TEMPLATE.CPP b/TEMPLATE.CPP new file mode 100644 index 0000000..14d31d3 --- /dev/null +++ b/TEMPLATE.CPP @@ -0,0 +1,411 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\template.cpv 2.18 16 Oct 1995 16:51:46 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TEMPLATE.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 17, 1994 * + * * + * Last Update : January 23, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * TemplateClass::As_Target -- Converts a template object into a target number. * + * TemplateClass::Init -- Resets the template object system. * + * TemplateClass::Mark -- Lifts or drops a template object. * + * TemplateClass::Read_INI -- Reads the scenario control INI file. * + * TemplateClass::Select -- Select the template object. * + * TemplateClass::TemplateClass -- Default constructor for template class objects. * + * TemplateClass::TemplateClass -- Template object constructor. * + * TemplateClass::Unlimbo -- Places a template object into the game/map system. * + * TemplateClass::Write_INI -- Writes the template objects to the INI file. * + * TemplateClass::delete -- Returns a template object to the pool. * + * TemplateClass::new -- Allocates a template object from pool * + * TemplateClass::Validate -- validates template pointer * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "template.h" + + +/* +** This contains the value of the Virtual Function Table Pointer +*/ +void * TemplateClass::VTable; + + +/*********************************************************************************************** + * TemplateClass::Validate -- validates template pointer * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int TemplateClass::Validate(void) const +{ + int num; + + num = Templates.ID(this); + if (num < 0 || num >= TEMPLATE_MAX) { + Validate_Error("TEMPLATE"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*********************************************************************************************** + * TemplateClass::Read_INI -- Reads the scenario control INI file. * + * * + * This routine reads the scenario control INI file and creates all * + * templates specified therein. * + * * + * INPUT: buffer -- Pointer to the loaded scenario control INI file. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/24/1994 JLB : Created. * + *=============================================================================================*/ +void TemplateClass::Read_INI(char *buffer) +{ + char *tbuffer; // Accumulation buffer of unit IDs. + int len; // Size of data in buffer. + CELL cell; // Cell of building. + char buf[128]; // Working string staging buffer. + + len = strlen(buffer) + 2; + tbuffer = buffer + len; + + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-len, buffer); + while (*tbuffer != '\0') { + TemplateType temp; // Terrain type. + + cell = atoi(tbuffer); + WWGetPrivateProfileString(INI_Name(), tbuffer, NULL, buf, sizeof(buf)-1, buffer); + temp = TemplateTypeClass::From_Name(strtok(buf, ",\r\n")); + if (temp != TEMPLATE_NONE) { + new TemplateClass(temp, cell); + } + tbuffer += strlen(tbuffer)+1; + } +} + + +/*********************************************************************************************** + * TemplateClass::Write_INI -- Writes the template objects to the INI file. * + * * + * This routine is used to write all the template objects out to the INI file specified. * + * It is used by the scenario editor when saving the game. * + * * + * INPUT: buffer -- Pointer to the INI file staging buffer. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +void TemplateClass::Write_INI(char *buffer) +{ + char uname[10]; + char buf[127]; + char *tbuffer; // Accumulation buffer of unit IDs. + + /* + ** First, clear out all existing template data from the ini file. + */ + tbuffer = buffer + strlen(buffer) + 2; + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-strlen(buffer), buffer); + while (*tbuffer != '\0') { + WWWritePrivateProfileString(INI_Name(), tbuffer, NULL, buffer); + tbuffer += strlen(tbuffer)+1; + } + + /* + ** Find all templates and write them to the file. + */ + for (int index = 0; index < MAP_CELL_TOTAL; index++) { + CellClass * ptr; + + ptr = &Map[index]; + if (ptr->TType != TEMPLATE_NONE && ptr->TIcon == 0) { + sprintf(uname, "%03d", index); + sprintf(buf, "%s", TemplateTypeClass::As_Reference(ptr->TType).IniName); + WWWritePrivateProfileString(INI_Name(), uname, buf, buffer); + } + } +} + + +/*********************************************************************************************** + * TemplateClass::TemplateClass -- Default constructor for template class objects. * + * * + * This is the default constructor for a template class object. This construction method * + * should NEVER be used by the game except as a consequence of declaring an array of * + * uninitialized template objects. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +TemplateClass::TemplateClass(void) : + Class(0) +{ +} + + +/*********************************************************************************************** + * TemplateClass::As_Target -- Converts a template object into a target number. * + * * + * This routine will convert a template object into a target number. Because templates * + * never exist as a template object in the game system, this routine will never be called. * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 09/25/1994 JLB : Created. * + *=============================================================================================*/ +TARGET TemplateClass::As_Target(void) const +{ + Validate(); + return(Build_Target(KIND_TEMPLATE, Templates.ID(this))); +} + + +/*********************************************************************************************** + * TemplateClass::Init -- Resets the template object system. * + * * + * This routine resets the template object system. It is called * + * prior to loading a new scenario. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/24/1994 JLB : Created. * + *=============================================================================================*/ +void TemplateClass::Init(void) +{ + TemplateClass *ptr; + + Templates.Free_All(); + + ptr = new TemplateClass(); + VTable = ((void **)(((char *)ptr) + sizeof(AbstractClass) - 4))[0]; + delete ptr; +} + + +/*********************************************************************************************** + * TemplateClass::Mark -- Lifts or drops a template object. * + * * + * This routine handles placing or removing a template object. This * + * entails marking the map as appropriate and redisplaying affected * + * cells. * + * * + * INPUT: mark -- The marking operation to perform. * + * * + * OUTPUT: bool; Was the template successfully marked? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/17/1994 JLB : Created. * + * 12/23/1994 JLB : Examines low level legality before processing. * + *=============================================================================================*/ +bool TemplateClass::Mark(MarkType mark) +{ + Validate(); + static bool noup = false; + void const * iset = Class->Get_Image_Data(); + if (iset && ObjectClass::Mark(mark)) { + + void * map = Get_Icon_Set_Map(iset); + + for (int y = 0; y < Class->Height; y++) { + for (int x = 0; x < Class->Width; x++) { + CELL cell = Coord_Cell(Coord) + y*MAP_CELL_W + x; + if (Map.In_Radar(cell)) { + CellClass * cellptr = &Map[cell]; + int number = y*Class->Width + x; + + /* + ** Determine if this logical icon actually maps to a real icon. If no real + ** icon is associated with this logical position, then don't do any action + ** since none is required. + */ + char * mapptr = (char*)map; + bool real = (mapptr[number] != -1); + + if (real) { + /* + ** Lift the terrain object from the map. + */ + if (mark == MARK_UP && !noup) { + if (cellptr->TType == Class->Type && cellptr->TIcon == number) { + cellptr->TType = TEMPLATE_NONE; + cellptr->TIcon = 0; + } + } + + /* + ** Place the terrain object down. + */ + if (mark == MARK_DOWN) { + if (*this == TEMPLATE_CLEAR1) { + cellptr->TType = TEMPLATE_NONE; + cellptr->TIcon = 0; + } else { + cellptr->TType = Class->Type; + // cellptr->TIcon = real; + cellptr->TIcon = number; + } + } + + cellptr->Redraw_Objects(); + cellptr->Recalc_Attributes(); + } + } + } + } + + /* + ** When marking this template down onto the map, the map template numbers are update + ** but the template is removed from existence. Make sure that the deletion of the + ** template object doesn't also lift the template numbers up from the map. + */ + if (mark == MARK_DOWN) { + noup = true; + delete this; + noup = false; + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TemplateClass::new -- Allocates a template object from pool * + * * + * This routine is used to allocate a template object from the * + * template object pool. * + * * + * INPUT: size -- The size of a template object (not used). * + * * + * OUTPUT: Returns with a pointer to an available template object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/17/1994 JLB : Created. * + *=============================================================================================*/ +void * TemplateClass::operator new(size_t ) +{ + void * ptr = Templates.Allocate(); + if (ptr) { + ((TemplateClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +/*********************************************************************************************** + * TemplateClass::delete -- Returns a template object to the pool. * + * * + * This routine will return a template object to the template object * + * pool. A template so returned is available for allocation again. * + * * + * INPUT: ptr -- Pointer to the object to be returned. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/17/1994 JLB : Created. * + *=============================================================================================*/ +void TemplateClass::operator delete(void *ptr) +{ + if (ptr) { + ((TemplateClass *)ptr)->IsActive = false; + } + Templates.Free((TemplateClass *)ptr); +} + + +/*********************************************************************************************** + * TemplateClass::TemplateClass -- Template object constructor. * + * * + * This is the constructor for a template object. * + * * + * INPUT: type -- The template object this is to become. * + * * + * pos -- The position on the map to place the object. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/17/1994 JLB : Created. * + *=============================================================================================*/ +TemplateClass::TemplateClass(TemplateType type, CELL pos) : + Class(&TemplateTypeClass::As_Reference(type)) +{ + if (pos != -1) { + Unlimbo(Cell_Coord(pos)); + } +} diff --git a/TEMPLATE.H b/TEMPLATE.H new file mode 100644 index 0000000..cd3eaaa --- /dev/null +++ b/TEMPLATE.H @@ -0,0 +1,120 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\template.h_v 2.18 16 Oct 1995 16:45:20 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TEMPLATE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : May 17, 1994 * + * * + * Last Update : May 17, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef TEMPLATE_H +#define TEMPLATE_H + +#include "object.h" +#include "type.h" + +/****************************************************************************** +** This class controls the template object. Template objects function congruously +** to carpet on a floor. They have no depth, but merely control the icon to be rendered +** as the cell's bottom most layer. +*/ +class TemplateClass : public ObjectClass +{ + public: + /*------------------------------------------------------------------- + ** Constructors and destructors. + */ + static void * operator new(size_t size); + static void operator delete(void *ptr); + TemplateClass(void); + TemplateClass(TemplateType type, CELL pos=-1); + virtual ~TemplateClass(void) {if (GameActive) TemplateClass::Limbo();}; + operator TemplateType(void) const {return Class->Type;}; + virtual RTTIType What_Am_I(void) const {return RTTI_TEMPLATE;}; + + static void Init(void); + + /* + ** Query functions. + */ + virtual ObjectTypeClass const & Class_Of(void) const {return *Class;}; + int Icon_Number(CELL cell); + + /* + ** Object entry and exit from the game system. + */ + + /* + ** Display and rendering support functionality. Supports imagery and how + ** object interacts with the map and thus indirectly controls rendering. + */ + virtual void Draw_It(int , int , WindowNumberType ) {}; + virtual bool Mark(MarkType mark); + + /* + ** User I/O. + */ + + /* + ** Combat related. + */ + virtual TARGET As_Target(void) const; + + /* + ** File I/O. + */ + static void Read_INI(char *buffer); + static void Write_INI(char *buffer); + static char *INI_Name(void) {return "TEMPLATE";}; + bool Load(FileClass & file); + bool Save(FileClass & file); + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + private: + + /* + ** This is a pointer to the template object's class. + */ + TemplateTypeClass const * const Class; + + /* + ** This contains the value of the Virtual Function Table Pointer + */ + static void * VTable; +}; + +#endif diff --git a/TERRAIN.CPP b/TERRAIN.CPP new file mode 100644 index 0000000..4682951 --- /dev/null +++ b/TERRAIN.CPP @@ -0,0 +1,900 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\terrain.cpv 2.16 16 Oct 1995 16:51:44 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TERRAIN.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 29, 1994 * + * * + * Last Update : May 8, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * TerrainClass::AI -- Process the terrain object AI. * + * TerrainClass::As_Target -- Converts the terrain object into a target number. * + * TerrainClass::Can_Enter_Cell -- Determines if the terrain object can exist in the cell. * + * TerrainClass::Catch_Fire -- Catches the terrain object on fire. * + * TerrainClass::Center_Coord -- Fetches the center point coordinate for terrain object. * + * TerrainClass::Debug_Dump -- Displays the status of the terrain object. * + * TerrainClass::Draw_It -- Renders the terrain object at the location specified. * + * TerrainClass::Fire_Out -- Handles when fire has gone out. * + * TerrainClass::Heath_Ratio -- Determines the health ratio for the terrain object. * + * TerrainClass::Init -- Initialize the terrain object tracking system. * + * TerrainClass::Limbo -- Handles terrain specific limbo action. * + * TerrainClass::Mark -- Marks the terrain object on the map. * + * TerrainClass::Radar_Icon -- Fetches pointer to radar icon to use. * + * TerrainClass::Read_INI -- Reads terrain objects from INI file. * + * TerrainClass::Start_To_Crumble -- Initiates crumbling of terrain (tree) object. * + * TerrainClass::Take_Damage -- Damages the terrain object as specified. * + * TerrainClass::TerrainClass -- Constructor for a terrain class object. * + * TerrainClass::TerrainClass -- This is the constructor for a terrain object. * + * TerrainClass::Unlimbo -- Unlimbo terrain object onto the map. * + * TerrainClass::Write_INI -- Writes all terrain objects to the INI file. * + * TerrainClass::delete -- Deletes a terrain object. * + * TerrainClass::new -- Creates a new terrain object. * + * TerrainClass::~TerrainClass -- Default destructor for terrain class objects. * + * TerrainClass::Validate -- validates terrain pointer * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "terrain.h" + +#define BARNACLE_STAGE 22 +#define FIRST_SPORE_STAGE 30 +#define FIRST_SPORABLE_LEVEL 7 + +/* +** This contains the value of the Virtual Function Table Pointer +*/ +void * TerrainClass::VTable; + + +/*********************************************************************************************** + * TerrainClass::Validate -- validates terrain pointer * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int TerrainClass::Validate(void) const +{ + int num; + + num = Terrains.ID(this); + if (num < 0 || num >= TERRAIN_MAX) { + Validate_Error("TERRAIN"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*********************************************************************************************** + * TerrainClass::~TerrainClass -- Default destructor for terrain class objects. * + * * + * This is the default destructor for terrain objects. It will remove the object from the * + * map and tracking systems, but only if the game is running. Otherwise, it does nothing. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +TerrainClass::~TerrainClass(void) +{ + if (GameActive && Class) { + TerrainClass::Limbo(); + } +} + + +/*********************************************************************************************** + * TerrainClass::Take_Damage -- Damages the terrain object as specified. * + * * + * This routine is called when damage is to be inflicted upon the terrain object. It is * + * through this routine that terrain objects are attacked and thereby destroyed. Not all * + * terrain objects can be damaged by this routine however. * + * * + * INPUT: damage -- The damage points to inflict (raw). * + * * + * warhead -- The warhead type the indicates the kind of damage. This is used to * + * determine if the terrain object is damaged and if so, by how much. * + * * + * OUTPUT: bool; Was the terrain object destroyed by this damage? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + * 11/22/1994 JLB : Shares base damage handler for techno objects. * + * 12/11/1994 JLB : Shortens attached burning animations. * + *=============================================================================================*/ +ResultType TerrainClass::Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source) +{ + Validate(); + ResultType res = RESULT_NONE; + + /* + ** Small arms can never destroy a terrain element. + */ + if ((!IsOnFire || warhead == WARHEAD_FIRE) && warhead != WARHEAD_SA && !Class->IsImmune) { + + res = ObjectClass::Take_Damage(damage, distance, warhead, source); + + if (damage && warhead == WARHEAD_FIRE) { + Catch_Fire(); + } + + /* + ** If the terrain object is destroyed by this damage, then only remove it if it + ** currently isn't on fire and isn't in the process of crumbling. + */ + if (res == RESULT_DESTROYED) { + + /* + ** Remove this terrain object from the targeting computers of all other + ** game objects. No use beating a dead horse. + */ + Detach_All(); + + if (IsOnFire) { + + /* + ** Attached flame animation should be shortened as much as possible so that + ** crumbling can begin soon. + */ + Shorten_Attached_Anims(this); + } else { + Start_To_Crumble(); + } + } + } + return(res); +} + + +/*********************************************************************************************** + * TerrainClass::As_Target -- Converts the terrain object into a target number. * + * * + * This routine will take the terrain object and generate a unique targeting number. This * + * number is used for the NavCom and TarCom of other units. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the target number of this terrain object. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +TARGET TerrainClass::As_Target(void) const +{ + Validate(); + return(Build_Target(KIND_TERRAIN, Terrains.ID(this))); +} + + +/*********************************************************************************************** + * TerrainClass::new -- Creates a new terrain object. * + * * + * This routine creates a new terrain object by grabbing a free slot * + * out of the terrain object pool. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the terrain object allocated. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/14/1994 JLB : Created. * + *=============================================================================================*/ +void * TerrainClass::operator new(size_t) +{ + void * ptr = Terrains.Allocate(); + if (ptr) { + ((TerrainClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +/*********************************************************************************************** + * TerrainClass::delete -- Deletes a terrain object. * + * * + * This routine deletes a terrain object by returning it to the * + * terrain object pool. * + * * + * INPUT: ptr -- Pointer to the terrain object to delete. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/14/1994 JLB : Created. * + *=============================================================================================*/ +void TerrainClass::operator delete(void *ptr) +{ + if (ptr) { + ((TerrainClass *)ptr)->IsActive = false; + } + Terrains.Free((TerrainClass *)ptr); +} + + +/*********************************************************************************************** + * TerrainClass::TerrainClass -- This is the constructor for a terrain object * + * * + * This constructor for a terrain object will initialize the terrain * + * object with it's proper type and insert it into the access * + * tracking system. * + * * + * INPUT: type -- The terrain object type. * + * * + * cell -- The location of the terrain object. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/02/1994 JLB : Created. * + *=============================================================================================*/ +TerrainClass::TerrainClass(TerrainType type, CELL cell) : + Class(&TerrainTypeClass::As_Reference(type)) +{ + IsBlossoming = false; + IsBarnacled = false; + IsSporing = false; + IsCrumbling = false; + IsOnFire = false; + Strength = Class->MaxStrength; + if (cell != -1) { + if (!Unlimbo(Cell_Coord(cell))) { + delete this; + } + } + Set_Rate(0); // turn off animation +} + + +/*********************************************************************************************** + * TerrainClass::Mark -- Marks the terrain object on the map. * + * * + * This routine will mark or remove the terrain object from the map * + * tracking system. This is typically called when the terrain object * + * is first created, when it is destroyed, and whenever it needs to be * + * redrawn. * + * * + * INPUT: mark -- The marking operation to perform. * + * * + * OUTPUT: bool; Was the terrain object successfully marked? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/02/1994 JLB : Created. * + * 12/23/1994 JLB : Performs low level legality check before proceeding. * + *=============================================================================================*/ +bool TerrainClass::Mark(MarkType mark) +{ + Validate(); + if (ObjectClass::Mark(mark)) { + short const *overlap = Class->Overlap_List(); + short const *occupy = Class->Occupy_List(); + CELL cell = Coord_Cell(Coord); + + switch (mark) { + case MARK_UP: + Map.Pick_Up(cell, this); + break; + + case MARK_DOWN: + Map.Place_Down(cell, this); + break; + + default: + Map.Refresh_Cells(cell, overlap); + Map.Refresh_Cells(cell, occupy); + break; + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TerrainClass::Draw_It -- Renders the terrain object at the location specified. * + * * + * This routine is used to render the terrain object at the location specified and * + * clipped to the window specified. This is the gruntwork drawing routine for the * + * terrain objects as they are displayed on the map. * + * * + * INPUT: x,y -- The coordinate to draw the terrain object at (centered). * + * * + * window -- The clipping window to draw to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/27/1994 JLB : Created. * + * 11/09/1994 JLB : Changed selected terrain highlight method. * + *=============================================================================================*/ +void TerrainClass::Draw_It(int x, int y, WindowNumberType window) +{ + Validate(); + void const * shapedata; + + shapedata = Class->Get_Image_Data(); + if (shapedata) { + int shapenum = 0; + + /* + ** Determine the animation stage to render the terrain object. If it is crumbling, then + ** it will display the crumbling animation. + */ + if (IsCrumbling || Class->IsTransformable) { + shapenum = Fetch_Stage()+IsCrumbling; + } else { + if (Strength < 2) { + shapenum++; + } + } + + ShapeFlags_Type flags = SHAPE_NORMAL; + if (IsSelected && Debug_Map) flags = flags | SHAPE_FADING; + + IsTheaterShape = true; + CC_Draw_Shape(shapedata, shapenum, x, y, window, flags|SHAPE_WIN_REL|SHAPE_GHOST, Map.FadingLight, Map.UnitShadow); + IsTheaterShape = false; + } +} + + +/*********************************************************************************************** + * TerrainClass::Init -- Initialize the terrain object tracking system. * + * * + * This routine will clear out the terrain object system so that no terrain objects will * + * exists. It is called prior to loading or starting a scenario. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + *=============================================================================================*/ +void TerrainClass::Init(void) +{ + TerrainClass *ptr; + + Terrains.Free_All(); + + ptr = new TerrainClass(); + VTable = ((void **)(((char *)ptr) + sizeof(AbstractClass) - 4))[0]; + delete ptr; +} + + +/*********************************************************************************************** + * TerrainClass::Can_Enter_Cell -- Determines if the terrain object can exist in the cell. * + * * + * This routine will examine the cell specified and determine if the the terrain object * + * can legally exist there. * + * * + * INPUT: cell -- The cell to examine. * + * * + * OUTPUT: If the terrain object can be placed in the cell specified, then a value less than * + * 256 will be returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/24/1994 JLB : Created. * + * 01/01/1995 JLB : Actually works now. * + *=============================================================================================*/ +MoveType TerrainClass::Can_Enter_Cell(CELL cell, FacingType) const +{ + Validate(); + short const *offset; // Pointer to cell offset list. + + if ((unsigned)cell >= MAP_CELL_TOTAL) return(MOVE_NO); + + offset = Occupy_List(); + while (*offset != REFRESH_EOL) { + if (!Map[(CELL)(cell + *offset++)].Is_Generally_Clear()) { + return(MOVE_NO); + } + } + return(MOVE_OK); +} + + +/*********************************************************************************************** + * TerrainClass::Catch_Fire -- Catches the terrain object on fire. * + * * + * This routine is called if the terrain object is supposed to catch on fire. The routine * + * performs checking to make sure that only flammable terrain objects that aren't already * + * on fire get caught on fire. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the terrain object caught on fire by this routine? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/27/1994 JLB : Created. * + * 12/11/1994 JLB : Don't catch fire if already on fire or crumbling. * + *=============================================================================================*/ +bool TerrainClass::Catch_Fire(void) +{ + Validate(); + if (!IsCrumbling && !IsOnFire && Class->IsFlammable) { + AnimClass * anim = new AnimClass(ANIM_BURN_BIG, Coord_Add(Sort_Y(), 0xFFB00000L)); + if (anim) { + anim->Attach_To(this); + } + anim = new AnimClass(ANIM_BURN_MED, Coord_Add(Sort_Y(), 0xFF200000L), 15); + if (anim) { + anim->Attach_To(this); + } + IsOnFire = true; + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TerrainClass::Fire_Out -- Handles when fire has gone out. * + * * + * When the fire has gone out on a burning terrain object, this routine is called. The * + * animation has already been terminated prior to calling this routine. All this routine * + * needs to perform is any necessary local flag updating. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/27/1994 JLB : Created. * + *=============================================================================================*/ +void TerrainClass::Fire_Out(void) +{ + Validate(); + if (IsOnFire) { + IsOnFire = false; + if (!IsCrumbling && !Strength) { + Detach_All(); + Mark(); + Start_To_Crumble(); + new AnimClass(ANIM_SMOKE_M, Coord_Add(Coord, Class->CenterBase)); + } + } +} + + +/*********************************************************************************************** + * TerrainClass::AI -- Process the terrain object AI. * + * * + * This is used to handle any AI processing necessary for terrain objects. This might * + * include animation effects. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/27/1994 JLB : Created. * + * 09/28/1994 JLB : Crumbling animation. * + *=============================================================================================*/ +void TerrainClass::AI(void) +{ + Validate(); + ObjectClass::AI(); + + if (StageClass::Graphic_Logic()) { + Mark(); + + /* + ** If the terrain object is in the process of crumbling, then when at the + ** last stage of the crumbling animation, delete the terrain object. + */ + if (IsCrumbling && Fetch_Stage() == Get_Build_Frame_Count(Class->Get_Image_Data())-1) { + delete this; + } + } + + /* + ** if this is a blossom tree, let's update it at this time + */ + if (Class->IsTransformable) { + // If it's already blossomed, is it at barnacled stage? + if (IsBlossoming) { + // if it's not barnacled yet, check if we're at that stage + if (!IsBarnacled) { + if (Fetch_Stage() == BARNACLE_STAGE) { + IsBarnacled = true; + Set_Rate(0); // turn off animation + } + } else { + /* + ** If it's barnacled, see if it's pulsing and spore-ing + */ + if (IsSporing) { + if (Fetch_Stage() >= Get_Build_Frame_Count(Class->Get_Image_Data())-1) { + Explosion_Damage(Sort_Y(), 5, NULL, WARHEAD_SPORE); + Set_Stage(FIRST_SPORE_STAGE); + if (Random() & 1) { + IsSporing = false; + StageClass::Set_Rate(0); + } + } + } else { + if (Random() == 255) { // is it time to start sporing? + IsSporing = true; + StageClass::Set_Rate(Options.Normalize_Delay(1)); + } + } + } + } else { + + // If it hasn't tried to blossom yet, can it do so now? + if (Random_Picky((int)1, (int)5000, (char*)NULL, (int)0) == 1) { + IsBlossoming = true; + StageClass::Set_Stage(1); + StageClass::Set_Rate(Options.Normalize_Delay(1)); + } + } + } +} + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * TerrainClass::Debug_Dump -- Displays the status of the terrain object. * + * * + * This debugging support routine is used to display the status of the terrain object to * + * the debug screen. * + * * + * INPUT: mono -- The mono screen to display the status to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/27/1994 JLB : Created. * + *=============================================================================================*/ +void TerrainClass::Debug_Dump(MonoClass *mono) const +{ + Validate(); + ObjectClass::Debug_Dump(mono); +} +#endif + + +/*********************************************************************************************** + * TerrainClass::Unlimbo -- Unlimbo terrain object onto the map. * + * * + * This routine is used to unlimbo the terrain object onto a location on the map. Normal * + * unlimbo procedures are sufficient except that the coordinate location of a terrain * + * object is based on the upper left corner of a cell rather than the center. Mask the * + * coordinate value so that it snaps to the upper left corner and then proceed with a * + * normal unlimbo process. * + * * + * INPUT: coord -- The coordinate to mark as the terrain's location. * + * * + * dir -- unused * + * * + * OUTPUT: bool; Was the terrain object successful in the unlimbo process? Failure could be * + * the result of illegal positioning. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 11/02/1994 JLB : Created. * + * 11/16/1994 JLB : Checks for theater legality. * + *=============================================================================================*/ +bool TerrainClass::Unlimbo(COORDINATE coord, DirType dir) +{ + Validate(); + if (Class->Theater & (1 << Map.Theater)) { + return(ObjectClass::Unlimbo(coord, dir)); + } + return(false); +} + + +/*********************************************************************************************** + * TerrainClass::Start_To_Crumble -- Initiates crumbling of terrain (tree) object. * + * * + * This routine is used to start the crumbling process for terrain object. This only * + * applies to trees. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/22/1994 JLB : Created. * + *=============================================================================================*/ +void TerrainClass::Start_To_Crumble(void) +{ + Validate(); + if (!IsCrumbling) { + IsCrumbling = true; + Set_Rate(2); + Set_Stage(0); + } +} + + +/*********************************************************************************************** + * TerrainClass::Limbo -- Handles terrain specific limbo action. * + * * + * This routine (called as a part of the limbo process) will remove the terrain occupation * + * flag in the cell it occupies. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the terrain object unlimboed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/22/1994 JLB : Created. * + *=============================================================================================*/ +bool TerrainClass::Limbo(void) +{ + Validate(); + if (!IsInLimbo) { + CELL cell = Coord_Cell(Coord); + Map[cell].Flag.Occupy.Monolith = false; + } + return(ObjectClass::Limbo()); +} + + +/*********************************************************************************************** + * TerrainClass::Center_Coord -- Fetches the center point coordinate for terrain object. * + * * + * Use this routine to fetch the center point terrain * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +COORDINATE TerrainClass::Center_Coord(void) const +{ + Validate(); + return(Coord_Add(Coord, Class->CenterBase)); +} + + +/*********************************************************************************************** + * TerrainClass::TerrainClass -- Constructor for a terrain class object. * + * * + * This is the default constructor for a terrain class object. It basically initializes * + * the object to a null -- do nothing -- state. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +TerrainClass::TerrainClass(void) : + Class(0) +{ + IsOnFire = false; + IsCrumbling = false; + IsBlossoming = false; + IsBarnacled = false; + IsSporing = false; + Strength = 0; +} + + +/*********************************************************************************************** + * TerrainClass::Radar_Icon -- Fetches pointer to radar icon to use. * + * * + * This routine will return with a pointer to the radar icon to use for the cell number * + * specified. * + * * + * INPUT: cell -- The cell number to use when determine what icon pointer to return. * + * * + * OUTPUT: Returns with a pointer to the 9 pixel values that make up the icon of this * + * terrain object located at the cell specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +unsigned char * TerrainClass::Radar_Icon(CELL cell) +{ + Validate(); + unsigned char *icon = (unsigned char *)Class->Get_Radar_Data(); // get a pointer to radar icons + int width = *icon++; // extract the width from data + int height = *icon++; // extract the width from data + + /* + ** Icon number that we need can be found by converting the cell and base + ** cell to and x and y offset from the upper left of the cell, and then + ** multiplying it by the width of the terrain in icons, which we + ** conveniantly stored out as the first byte of every icon we made. + */ + int basecell = Coord_Cell(Coord); // find the base cell of terrain + int ydiff = Cell_Y(cell) - Cell_Y(basecell); + int xdiff = Cell_X(cell) - Cell_X(basecell); + if (xdiff < width && ydiff < height) { + int iconnum = (ydiff * width) + xdiff; + return(icon + (iconnum * 9)); + } + return(NULL); +} + + +/*********************************************************************************************** + * TerrainClass::Read_INI -- Reads terrain objects from INI file. * + * * + * This routine reads a scenario control INI file and creates all * + * terrain objects specified therein. Objects so created are placed * + * upon the map. * + * * + * INI entry format: * + * cellnum = TypeName, Triggername * + * * + * INPUT: buffer -- Pointer to the loaded scenario INI file data. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/24/1994 JLB : Created. * + *=============================================================================================*/ +void TerrainClass::Read_INI(char *buffer) +{ + char *tbuffer; // Accumulation buffer of unit IDs. + int len; // Size of data in buffer. + char buf[128]; + TerrainClass * tptr; + + len = strlen(buffer) + 2; + tbuffer = buffer + len; + + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-len, buffer); + while (*tbuffer != '\0') { + TerrainType terrain; // Terrain type. + CELL cell; + + cell = atoi(tbuffer); + WWGetPrivateProfileString(INI_Name(), tbuffer, NULL, buf, sizeof(buf)-1, buffer); + terrain = TerrainTypeClass::From_Name(strtok(buf, ",")); + if (terrain != TERRAIN_NONE) { + tptr = new TerrainClass(terrain, cell); + tptr->Trigger = TriggerClass::As_Pointer(strtok(NULL,",")); + if (tptr->Trigger) + tptr->Trigger->AttachCount++; + } + tbuffer += strlen(tbuffer)+1; + } +} + + +/*********************************************************************************************** + * TerrainClass::Write_INI -- Writes all terrain objects to the INI file. * + * * + * This routine is used to write all the terrain objects out to the INI file specified. * + * It is used by the scenario editor to write out the map data. * + * * + * INI entry format: * + * cellnum = TypeName, Triggername * + * * + * INPUT: buffer -- Pointer to the INI file staging area. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +void TerrainClass::Write_INI(char *buffer) +{ + int index; + char uname[10]; + char buf[127]; + char *tbuffer; // Accumulation buffer of unit IDs. + + /* + ** First, clear out all existing terrain data from the ini file. + */ + tbuffer = buffer + strlen(buffer) + 2; + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-strlen(buffer), buffer); + while (*tbuffer != '\0') { + WWWritePrivateProfileString(INI_Name(), tbuffer, NULL, buffer); + tbuffer += strlen(tbuffer)+1; + } + + /* + ** Write the terrain data out. + */ + for (index = 0; index < Terrains.Count(); index++) { + TerrainClass * terrain; + + terrain = Terrains.Ptr(index); + if (!terrain->IsInLimbo && terrain->IsActive) { + + sprintf(uname, "%d", Coord_Cell(terrain->Coord)); + sprintf(buf, "%s,%s", + terrain->Class->IniName, + terrain->Trigger ? terrain->Trigger->Get_Name() : "None" ); + WWWritePrivateProfileString(INI_Name(), uname, buf, buffer); + } + } +} diff --git a/TERRAIN.H b/TERRAIN.H new file mode 100644 index 0000000..a4db80b --- /dev/null +++ b/TERRAIN.H @@ -0,0 +1,177 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\terrain.h_v 2.16 16 Oct 1995 16:47:48 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TERRAIN.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 29, 1994 * + * * + * Last Update : April 29, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef TERRAIN_H +#define TERRAIN_H + +#include "object.h" +#include "type.h" + + +/**************************************************************************** +** Each type of terrain has certain pieces of static information associated +** with it. This class elaborates this data. +*/ +class TerrainClass : public ObjectClass, public StageClass +{ + public: + TerrainTypeClass const * const Class; + operator TerrainType(void) const {return Class->Type;}; + + /* + ** Constructor for terrain object class. + */ + static void * TerrainClass::operator new(size_t size); + static void TerrainClass::operator delete(void *ptr); + TerrainClass(void); + TerrainClass(TerrainType id, CELL cell); + virtual ~TerrainClass(void); + virtual RTTIType What_Am_I(void) const {return RTTI_TERRAIN;}; + + static void Init(void); + + /* + ** Terrain specific support functions. + */ + void Start_To_Crumble(void); + + /* + ** Query functions. + */ + virtual ObjectTypeClass const & Class_Of(void) const {return *Class;}; + + /* + ** Coordinate inquiry functions. These are used for both display and + ** combat purposes. + */ + virtual COORDINATE Center_Coord(void) const; + virtual COORDINATE Render_Coord(void) const {return Coord;}; + virtual COORDINATE Sort_Y(void) const {return Coord_Add(Coord, Class->CenterBase);}; + virtual COORDINATE Target_Coord(void) const {return Sort_Y();}; + + /* + ** Object entry and exit from the game system. + */ + virtual bool Unlimbo(COORDINATE coord, DirType dir=DIR_N); + virtual bool Limbo(void); + virtual MoveType Can_Enter_Cell(CELL cell, FacingType facing = FACING_NONE) const; + + /* + ** Display and rendering support functionality. Supports imagery and how + ** object interacts with the map and thus indirectly controls rendering. + */ + virtual void Draw_It(int x, int y, WindowNumberType window); + virtual bool Mark(MarkType mark=MARK_CHANGE); + unsigned char *Radar_Icon(CELL cell); + + /* + ** User I/O. + */ + virtual void Clicked_As_Target(int) {}; + + /* + ** Combat related. + */ + virtual void Fire_Out(void); + virtual bool Catch_Fire(void); + virtual ResultType Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source); + virtual TARGET As_Target(void) const; + + /* + ** AI. + */ + virtual void AI(void); + + /* + ** Scenario and debug support. + */ + #ifdef CHEAT_KEYS + virtual void Debug_Dump(MonoClass *mono) const; + #endif + + /* + ** File I/O. + */ + static void Read_INI(char *buffer); + static void Write_INI(char *buffer); + static char *INI_Name(void) {return "TERRAIN";}; + bool Load(FileClass & file); + bool Save(FileClass & file); + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + private: + + /* + ** If this terrain object is on fire, then this flag will be true. + */ + unsigned IsOnFire:1; + + /* + ** Is this a terrain object that undergoes crumbling animation and it is + ** in fact crumbling at this time? + */ + unsigned IsCrumbling:1; + + /* + ** If this is a tree that becomes a blossom tree, is it currently doing so? + */ + unsigned IsBlossoming:1; + + /* + ** If this is a blossom tree, is it barnacled? + */ + unsigned IsBarnacled:1; + + /* + ** If this is a blossom tree that is barnacled, is it pulsing and spewing + ** out spores? + */ + unsigned IsSporing:1; + + /* + ** This contains the value of the Virtual Function Table Pointer + */ + static void * VTable; +}; + +#endif diff --git a/TEXTBLIT.H b/TEXTBLIT.H new file mode 100644 index 0000000..907daab --- /dev/null +++ b/TEXTBLIT.H @@ -0,0 +1,53 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + + +#define MAX_ENTRIES 128 + +class TextBlitClass { + + public: + + TextBlitClass(void); + ~TextBlitClass(void){}; + + void Add (int x, int y, int dx, int dy, int w, int h); + void Clear (void); + void Update (void); + + + private: + + typedef struct { + int SourceX; + int SourceY; + int DestX; + int DestY; + int Width; + int Height; + } BlitEntryType; + + BlitEntryType BlitListo [MAX_ENTRIES]; + int Count; + +}; + + +extern GraphicBufferClass *TextPrintBuffer; +extern TextBlitClass BlitList; + diff --git a/TEXTBTN.CPP b/TEXTBTN.CPP new file mode 100644 index 0000000..f875feb --- /dev/null +++ b/TEXTBTN.CPP @@ -0,0 +1,377 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\textbtn.cpv 2.18 16 Oct 1995 16:49:16 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TEXTBTN.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 19, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * TextButtonClass::Draw_Background -- Draws the background to the text button. * + * TextButtonClass::Draw_Me -- Draws the text buttons as indicated. * + * TextButtonClass::Draw_Text -- This draws the text for the text button. * + * TextButtonClass::Set_Text -- Assigns a new text string to this button. * + * TextButtonClass::Set_Text -- Sets the text for this text button. * + * TextButtonClass::TextButtonClass -- Normal constructor for a text button. * + * TextButtonClass::TextButtonClass -- Normal constructor for a text button. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "textbtn.h" + + +/*********************************************************************************************** + * TextButtonClass::TextButtonClass -- Normal constructor for a text button. * + * * + * This is the constructor for text buttons if the text is provided as a string pointer. * + * * + * INPUT: id -- The ID number to assign to this button. * + * text -- Pointer to the text string to display on top of the button. * + * x,y -- Pixel coordinate of button's upper left corner. * + * w,h -- Dimensions of the button. If these are not filled in (or with -1), then * + * the dimensions are adapted to fit the text assigned to the button. * + * style -- The print style for the text in the button. These are the TPF_ flags * + * used by Fancy_Text_Print(). * + * border-- If the button is to be surrounded by a black border, then this flag * + * should be set to true. * + * * + * OUTPUT: none * + * WARNINGS: Call Set_Text & Set_Style, & init X,Y,Width,Height,ID before using this button. * + * HISTORY: 01/15/1995 JLB : Created. * + *=============================================================================================*/ +TextButtonClass::TextButtonClass(unsigned id, char const * text, TextPrintType style, int x, int y, int w, int h, int blackborder) : + ToggleClass(id, x, y, w, h), + String(text) +{ + PrintFlags = style; + IsBlackBorder = blackborder; + + if (w == -1 || h == -1) { + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, PrintFlags); + if (w == -1) { + Width = String_Pixel_Width(String)+8; +// if (SeenBuff.Get_Width() != 320) Width *= 2; + } + if (h == -1) { + Height = FontHeight + FontYSpacing + 2; +// if (SeenBuff.Get_Height() != 200) Height *= 2; + } + } +} + + +/*********************************************************************************************** + * TextButtonClass::TextButtonClass -- Default constructor for a text button. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: 01/15/1995 JLB : Created. * + *=============================================================================================*/ +TextButtonClass::TextButtonClass(void) : + ToggleClass(0, 0, 0, 0, 0) +{ + X = Y = 0; + Width = Height = 0; + IsBlackBorder = 0; + String = 0; + PrintFlags = TPF_8POINT; +} + + +/*********************************************************************************************** + * TextButtonClass::TextButtonClass -- Normal constructor for a text button. * + * * + * This is the constructor for text buttons if the text is provided as a string pointer. * + * * + * INPUT: id -- The ID number to assign to this button. * + * * + * text -- The text number to use for displaying on top of the button. * + * * + * x,y -- Pixel coordinate of button's upper left corner. * + * * + * w,h -- Dimensions of the button. If these are not filled in (or with -1), then * + * the dimensions are adapted to fit the text assigned to the button. * + * * + * * + * style -- The print style for the text in the button. These are the TPF_ flags * + * used by Fancy_Text_Print(). * + * * + * border-- If the button is to be surrounded by a black border, then this flag * + * should be set to true. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +TextButtonClass::TextButtonClass (unsigned id, int text, TextPrintType style, int x, int y, int w, int h, int blackborder) : + ToggleClass (id, x, y, w, h), + String(0) +{ + PrintFlags = style; + IsBlackBorder = blackborder; + Set_Text(text); + + if (w == -1 || h == -1) { + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, PrintFlags); + if (w == -1) { + Width = String_Pixel_Width(String)+8; +// if (SeenBuff.Get_Width() != 320) Width *= 2; + } + if (h == -1) { + Height = FontHeight + FontYSpacing + 2; +// if (SeenBuff.Get_Height() != 200) Height *= 2; + } + } +} + + +/*********************************************************************************************** + * TextButtonClass::Draw_Me -- Draws the text buttons as indicated. * + * * + * This routine will draw the text button. * + * * + * INPUT: forced -- If the button is to be redrawn regardless of the state of the redraw * + * flag, then this parameter will be true. * + * * + * OUTPUT: bool; Was the button redrawn? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/03/1995 MML : Created. * + * 01/16/1995 JLB : Modified * + *=============================================================================================*/ +int TextButtonClass::Draw_Me(int forced) +{ + if (ControlClass::Draw_Me(forced)) { + /* + ** Hide the mouse. + */ + if (LogicPage == &SeenBuff) { + //Conditional_Hide_Mouse(X, Y, X+Width-1, Y+Height-1); + Conditional_Hide_Mouse(X, Y, X+Width, Y+Height); + } + + /* + ** Draw the background and overlaying text. These are virtual function + ** calls so that they may be overridden. + */ + Draw_Background(); + Draw_Text(String); + + /* + ** Display the mouse. + */ + if (LogicPage == &SeenBuff) { + Conditional_Show_Mouse(); + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TextButtonClass::Set_Text -- Assigns a new text string to this button. * + * * + * Use this routine to assign a new text string to this button. By using this function it * + * is possible to dynmaically change the button's text. An example of this would be an * + * on/off button that every time it is clicked, the text toggles between "on" and "off". * + * * + * INPUT: text -- Pointer to the text string to assign to this button. * + * * + * OUTPUT: none * + * * + * WARNINGS: The text is NOT copied to this button. You must make sure that the text * + * remains valid throughout the lifetime of this text button. * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +void TextButtonClass::Set_Text(char const * text, bool resize) +{ + String = text; + Flag_To_Redraw(); + if (resize && String) { + Fancy_Text_Print(TXT_NONE, 0, 0, TBLACK, TBLACK, PrintFlags); + Width = String_Pixel_Width(String)+8; + Height = FontHeight + FontYSpacing + 2; + } +} + + +/*********************************************************************************************** + * TextButtonClass::Set_Text -- Sets the text for this text button. * + * * + * This will set the text for this button. The text is provided as a text number. This * + * number is automatically converted to the appropriate text string before being stored * + * in the button's structure. * + * * + * INPUT: text -- The text number to assign to this button. * + * * + * OUTPUT: none * + * * + * WARNINGS: The text number information is lost when it is assigned to the button. Once * + * the assignment takes place, the text number is NOT remembered by the button. * + * Only the associated text string is. * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +void TextButtonClass::Set_Text(int text, bool resize) +{ + if (text != TXT_NONE) { + Set_Text(Text_String(text), resize); + } +} + + +/*********************************************************************************************** + * TextButtonClass::Draw_Background -- Draws the background to the text button. * + * * + * This localizes the drawing of the background for the text button. By overriding this * + * function you can give a different background to the button. The text is drawn using * + * a different routine. The mouse is hidden, if necessary, before this routine is called. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void TextButtonClass::Draw_Background(void) +{ + /* + ** Draw a border if selected style. + */ + if (IsBlackBorder) { + LogicPage->Draw_Rect (X-1, Y-1, X+Width+2, Y+Height+2, BLACK); + } + + /* + ** Draw the body & set text color. + */ + BoxStyleEnum style; + //if (FontPtr == GradFont6Ptr) { + if (PrintFlags & TPF_6PT_GRAD) { + if (IsDisabled) { + style = BOXSTYLE_GREEN_DIS_RAISED; + } else { + if (IsPressed) { + style = BOXSTYLE_GREEN_DOWN; + } else { + style = BOXSTYLE_GREEN_RAISED; + } + } + } else { + if (IsDisabled) { + style = BOXSTYLE_DIS_RAISED; + } else { + if (IsPressed) { + style = BOXSTYLE_DOWN; + } else { + style = BOXSTYLE_RAISED; + } + } + } + Draw_Box(X, Y, Width, Height, style, true); +} + + +/*********************************************************************************************** + * TextButtonClass::Draw_Text -- This draws the text for the text button. * + * * + * This routine draws the text for the text button. You can override this routine if you * + * wish different text rendering styles or colors. The background has already been drawn * + * by the time this function is called. The mouse is hidden, if necessary, as well. * + * * + * INPUT: text -- Pointer to the text string to print over the button. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +void TextButtonClass::Draw_Text(char const * text) +{ + /* + ** Display the text. + */ + if (String) { + int color; + //if (FontPtr == GradFont6Ptr) { + if (PrintFlags & TPF_6PT_GRAD) { + TextPrintType flags; + + color = CC_GREEN; + + if (IsDisabled) { + flags = (TextPrintType)0; + } else { + if (IsPressed || IsOn) { + flags = TPF_USE_GRAD_PAL|TPF_BRIGHT_COLOR; + } else { + flags = TPF_USE_GRAD_PAL|TPF_MEDIUM_COLOR; + } + } + + Fancy_Text_Print(text, X+(Width>>1)-1, Y+1, color, TBLACK, PrintFlags|flags|TPF_CENTER); + } else { + if (IsDisabled) { +// color = DKGREY; + color = LTGREY; + } else { + if (IsPressed) { + if (PrintFlags & TPF_NOSHADOW) { + color = DKGREY; + } else { + color = LTGREY; + } + } else { + color = WHITE; + } + } + + Fancy_Text_Print(text, X+(Width>>1)-1, Y+1, IsOn ? RED : color, TBLACK, PrintFlags|TPF_CENTER); + } + + } +} diff --git a/TEXTBTN.H b/TEXTBTN.H new file mode 100644 index 0000000..e18318a --- /dev/null +++ b/TEXTBTN.H @@ -0,0 +1,74 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\textbtn.h_v 2.18 16 Oct 1995 16:46:54 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TEXTBTN.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 15, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef TEXTBTN_H +#define TEXTBTN_H + +#include "toggle.h" + + +class TextButtonClass : public ToggleClass +{ + public: + TextButtonClass(void); + TextButtonClass(unsigned id, char const * text, TextPrintType style, int x, int y, int w=-1, int h=-1, int blackborder=false); + TextButtonClass(unsigned id, int text, TextPrintType style, int x, int y, int w=-1, int h=-1, int blackborder=false); + virtual int Draw_Me(int forced=false); + virtual void Set_Text(char const *text, bool resize = false); + virtual void Set_Text(int text, bool resize = false); + virtual void Set_Style (TextPrintType style) {PrintFlags = style;} + + protected: + + virtual void Draw_Background(void); + virtual void Draw_Text(char const * text); + + unsigned IsBlackBorder:1; + + /* + ** This points to a constant string that is used for the button's text. + */ + char const * String; + + /* + ** This is the print flags to use when rendering this button's text. + */ + TextPrintType PrintFlags; +}; + +#endif + diff --git a/THEME.CPP b/THEME.CPP new file mode 100644 index 0000000..16756cf --- /dev/null +++ b/THEME.CPP @@ -0,0 +1,562 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\theme.cpv 2.18 16 Oct 1995 16:51:10 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : THEME.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : August 14, 1994 * + * * + * Last Update : May 29, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * ThemeClass::Scan -- Scans all scores for availability. * + * ThemeClass::AI -- Process the theme engine and restart songs. * + * ThemeClass::Base_Name -- Fetches the base filename for the theme specified. * + * ThemeClass::From_Name -- Determines theme number from specified name. * + * ThemeClass::Full_Name -- Retrieves the full score name. * + * ThemeClass::Is_Allowed -- Checks to see if the specified theme is legal. * + * ThemeClass::Next_Song -- Calculates the next song number to play. * + * ThemeClass::Play_Song -- Starts the specified song play NOW. * + * ThemeClass::Queue_Song -- Queues the song to the play queue. * + * ThemeClass::Still_Playing -- Determines if music is still playing. * + * ThemeClass::Stop -- Stops the current theme from playing. * + * ThemeClass::ThemeClass -- Default constructor for the theme manager class. * + * ThemeClass::Theme_File_Name -- Constructs a filename for the specified theme. * + * ThemeClass::Track_Length -- Caclulates the length of the song (in seconds). * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +#include "theme.h" + + +/* +** These are the actual filename list for the theme sample files. +*/ +ThemeClass::ThemeControl ThemeClass::_themes[THEME_COUNT] = { + {"AIRSTRIK", TXT_THEME_AIRSTRIKE, 0, 200, false, false,false,true}, + {"80MX226M", TXT_THEME_80MX, 0, 248, false, false,false,true}, + {"CHRG226M", TXT_THEME_CHRG, 0, 256, true, false,false,true}, + {"CREP226M", TXT_THEME_CREP, 0, 222, true, false,false,true}, + {"DRIL226M", TXT_THEME_DRIL, 0, 272, true, false,false,true}, + {"DRON226M", TXT_THEME_DRON, 0, 275, true, false,false,true}, + {"FIST226M", TXT_THEME_FIST, 0, 212, true, false,false,true}, + {"RECN226M", TXT_THEME_RECON, 0, 261, true, false,false,true}, + {"VOIC226M", TXT_THEME_VOICE, 0, 306, true, false,false,true}, + {"HEAVYG", TXT_THEME_HEAVYG, 0, 180, true, false,false,true}, + {"J1", TXT_THEME_J1, 4, 187, true, false,false,true}, +// {"J1", TXT_THEME_J1, 4, 187, false, false,false,true}, + {"JDI_V2", TXT_THEME_JDI_V2, 5, 183, true, false,false,true}, + {"RADIO", TXT_THEME_RADIO, 6, 183, true, false,false,true}, + {"RAIN", TXT_THEME_RAIN, 7, 156, true, false,false,true}, + {"AOI", TXT_THEME_AOI, 0, 168, true, true, false,true}, + {"CCTHANG", TXT_THEME_CCTHANG, 12, 193, true, false,false,true}, + {"DIE", TXT_THEME_DIE, 11, 162, false, false,false,true}, + {"FWP", TXT_THEME_FWP, 10, 53, true, false,false,true}, + {"IND", TXT_THEME_IND, 1, 175, true, false,false,true}, + {"IND2", TXT_THEME_IND2, 1, 38, true, false,false,true}, + {"JUSTDOIT", TXT_THEME_JUSTDOIT, 9, 142, true, false,false,true}, + {"LINEFIRE", TXT_THEME_LINEFIRE, 8, 125, true, false,false,true}, + {"MARCH", TXT_THEME_MARCH, 7, 157, true, false,false,true}, + {"TARGET", TXT_THEME_TARGET, 0, 173, true, false,false,true}, + {"NOMERCY", TXT_THEME_NOMERCY, 2, 204, true, false,false,true}, + {"OTP", TXT_THEME_OTP, 3, 182, true, false,false,true}, + {"PRP", TXT_THEME_PRP, 4, 211, true, false,false,true}, + {"ROUT", TXT_THEME_ROUT, 12, 121, false, true, false,true}, + {"HEART", TXT_THEME_HEART, 5, 206, false, true, false,true}, + {"STOPTHEM", TXT_THEME_STOPTHEM, 0, 190, true, false,false,true}, + {"TROUBLE", TXT_THEME_TROUBLE, 6, 191, true, true, false,true}, + {"WARFARE", TXT_THEME_WARFARE, 0, 182, true, false,false,true}, + {"BEFEARED", TXT_THEME_BEFEARED, 13, 164, false, true, false,true}, + {"I_AM", TXT_THEME_IAM, 6, 161, false, false,false,true}, + {"WIN1", TXT_THEME_WIN1, 0, 41, false, true, true,true}, + {"MAP1", TXT_THEME_WIN1, 0, 61, false, false,true,true}, + {"VALKYRIE", TXT_THEME_VALK, 0, 306, false, false,true,true}, +}; + + +/*********************************************************************************************** + * ThemeClass::Base_Name -- Fetches the base filename for the theme specified. * + * * + * This routine is used to retrieve a pointer to the base filename for the theme * + * specified. * + * * + * INPUT: theme -- The theme number to convert into a base filename. * + * * + * OUTPUT: Returns with a pointer to the base filename for the theme specified. If the * + * theme number is invalid, then a pointer to "No Theme" is returned instead. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/29/1995 JLB : Created. * + *=============================================================================================*/ +char const * ThemeClass::Base_Name(ThemeType theme) const +{ + if (theme != THEME_NONE) { + return(_themes[theme].Name); + } + return("No theme"); +} + + +/*********************************************************************************************** + * ThemeClass::ThemeClass -- Default constructor for the theme manager class. * + * * + * This is the default constructor for the theme class object. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +ThemeClass::ThemeClass(void) +{ + Current = -1; + Score = THEME_NONE; + Pending = THEME_NONE; +} + + +/*********************************************************************************************** + * ThemeClass::Full_Name -- Retrieves the full score name. * + * * + * This routine will fetch and return with a pointer to the full name of the theme * + * specified. * + * * + * INPUT: theme -- The theme to fetch the full name for. * + * * + * OUTPUT: Returns with a pointer to the full name for this score. This pointer may point to * + * EMS memory. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +char const * ThemeClass::Full_Name(ThemeType theme) const +{ + if (theme != THEME_NONE) { + return(Text_String(_themes[theme].Fullname)); + } + return(NULL); +} + + +/*********************************************************************************************** + * ThemeClass::AI -- Process the theme engine and restart songs. * + * * + * This is a maintenance function that will restart an appropriate theme if the current one * + * has finished. This routine should be called frequently. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/08/1994 JLB : Created. * + * 01/23/1995 JLB : Picks new song just as it is about to play it. * + *=============================================================================================*/ +void ThemeClass::AI(void) +{ + if (SampleType && !Debug_Quiet) { + if (ScoresPresent && Options.ScoreVolume && !Still_Playing() && Pending != THEME_NONE) { + + /* + ** If the pending song needs to be picked, then pick it now. + */ + if (Pending == THEME_PICK_ANOTHER) { + Pending = Next_Song(Score); + } + + /* + ** Start the song playing and then flag it so that a new song will + ** be picked when this one ends. + */ + Play_Song(Pending); + Pending = THEME_PICK_ANOTHER; + } + Sound_Callback(); + } +} + + +/*********************************************************************************************** + * ThemeClass::Next_Song -- Calculates the next song number to play. * + * * + * use this routine to figure out what song number to play. It examines the option settings * + * for repeat and shuffle so that it can return the correct value. * + * * + * INPUT: theme -- The origin (last) index. The new value is related to this for all but * + * the shuffling method of play. * + * * + * OUTPUT: Returns with the song number for the next song to play. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + * 01/19/1995 JLB : Will not play the same song twice when in shuffle mode. * + *=============================================================================================*/ +ThemeType ThemeClass::Next_Song(ThemeType theme) +{ + if (theme == THEME_NONE) { + theme = Next_Song(THEME_PICK_ANOTHER); + } else { + if (theme == THEME_PICK_ANOTHER || (!_themes[theme].Repeat && !Options.IsScoreRepeat)) { + if (Options.IsScoreShuffle) { + + /* + ** Shuffle the theme, but never pick the same theme that was just + ** playing. + */ + ThemeType newtheme; + do { + newtheme = Sim_Random_Pick(THEME_FIRST, THEME_LAST); + } while(newtheme == theme || !Is_Allowed(newtheme)); + theme = newtheme; + + } else { + + /* + ** Sequential score playing. + */ + do { + theme++; + if (theme > THEME_LAST) { + theme = THEME_FIRST; + } + } while(!Is_Allowed(theme)); + } + } + } + return(theme); +} + + +/*********************************************************************************************** + * ThemeClass::Queue_Song -- Queues the song to the play queue. * + * * + * This routine will cause the current song to fade and the specified song to start. This * + * is the normal and friendly method of changing the current song. * + * * + * INPUT: theme -- The song to start playing. If -1 is pssed in, then just the current song * + * is faded. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +void ThemeClass::Queue_Song(ThemeType theme) +{ + if (ScoresPresent && SampleType && !Debug_Quiet && (Pending == THEME_NONE || Pending == THEME_PICK_ANOTHER)) { + if (!Options.ScoreVolume && theme != THEME_NONE) return; + + Pending = theme; + Fade_Sample(Current, THEME_DELAY); + } +} + + +/*********************************************************************************************** + * ThemeClass::Play_Song -- Starts the specified song play NOW. * + * * + * This routine is used to start the specified theme playing right now. If there is already * + * a theme playing, it is cut short so that this one may start. * + * * + * INPUT: theme -- The theme number to start playing. * + * * + * OUTPUT: Returns with the sample play handle. * + * * + * WARNINGS: This cuts off any current song in a abrubt manner. Only use this routine when * + * necessary. * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +int ThemeClass::Play_Song(ThemeType theme) +{ + if (ScoresPresent && SampleType && !Debug_Quiet && Options.ScoreVolume) { + Stop(); + Score = theme; + if (theme >= THEME_FIRST) { + +#ifdef DEMO + if (_themes[theme].Scenario != 99) { + CCFileClass file(Theme_File_Name(theme)); + if (file.Is_Available()) { + Current = File_Stream_Sample_Vol(Theme_File_Name(theme), 0xFF, true); + } else { + Current = -1; + } + } else { + Current = -1; + } +#else + Current = File_Stream_Sample_Vol(Theme_File_Name(theme), 0xFF, true); +#endif + } + } + return(Current); +} + + +/*********************************************************************************************** + * ThemeClass::Theme_File_Name -- Constructs a filename for the specified theme. * + * * + * This routine will construct (into a static buffer) a filename that matches the theme * + * number specified. This constructed filename is returned as a pointer. The filename will * + * remain valid until the next call to this routine. * + * * + * INPUT: theme -- The theme number to convert to a filename. * + * * + * OUTPUT: Returns with a pointer to the constructed filename for the specified theme number. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + * 05/09/1995 JLB : Theme variation support. * + *=============================================================================================*/ +char const * ThemeClass::Theme_File_Name(ThemeType theme) +{ + static char name[_MAX_FNAME+_MAX_EXT]; + + if (_themes[theme].Variation && Special.IsVariation) { + _makepath(name, NULL, NULL, _themes[theme].Name, ".VAR"); + CCFileClass file(name); + if (file.Is_Available()) { + return((char const *)(&name[0])); + } + } + _makepath(name, NULL, NULL, _themes[theme].Name, ".AUD"); + return((char const *)(&name[0])); +} + + +/*********************************************************************************************** + * ThemeClass::Track_Length -- Caclulates the length of the song (in seconds). * + * * + * Use this routine to calculate the length of the song. The length is determined by * + * reading the header of the song and dividing the sample rate into the sample length. * + * * + * INPUT: theme -- The song number to examine to find its length. * + * * + * OUTPUT: Returns with the length of the specified theme. This length is in the form of * + * seconds. * + * * + * WARNINGS: This routine goes to disk to fetch this information. Don't call frivolously. * + * * + * HISTORY: * + * 01/16/1995 JLB : Created. * + *=============================================================================================*/ +int ThemeClass::Track_Length(ThemeType theme) +{ + if ((unsigned)theme < THEME_COUNT) { + return(_themes[theme].Duration); + } + return(0); +} + + +/*********************************************************************************************** + * ThemeClass::Stop -- Stops the current theme from playing. * + * * + * Use this routine to stop the current theme. After this routine is called, no more music * + * will play until the Start() function is called. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/08/1994 JLB : Created. * + *=============================================================================================*/ +void ThemeClass::Stop(void) +{ + if (ScoresPresent && SampleType && !Debug_Quiet) { + if (Current != -1) { + Stop_Sample(Current); + Current = -1; + Score = THEME_NONE; + Pending = THEME_NONE; + } + } +} + + +/*********************************************************************************************** + * ThemeClass::Still_Playing -- Determines if music is still playing. * + * * + * Use this routine to determine if music is still playing. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Is the music still audible? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/20/1994 JLB : Created. * + *=============================================================================================*/ +int ThemeClass::Still_Playing(void) +{ + if (ScoresPresent && SampleType && Current != -1 && !Debug_Quiet) { + return(Sample_Status(Current)); + } + return(false); +} + + +/*********************************************************************************************** + * ThemeClass::Is_Allowed -- Checks to see if the specified theme is legal. * + * * + * Use this routine to determine if a theme is allowed to be played. A theme is not allowed * + * if the scenario is too early for that score, or the score only is allowed in special * + * cases. * + * * + * INPUT: index -- The score the check to see if it is allowed to play. * + * * + * OUTPUT: Is the specified score allowed to play in the normal score playlist? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/09/1995 JLB : Created. * + *=============================================================================================*/ +bool ThemeClass::Is_Allowed(ThemeType index) const +{ +#ifdef DEMO + char buffer[128]; + + sprintf(buffer, "%s.AUD", Base_Name(index)); + CCFileClass file(buffer); + if (_themes[index].Scenario == 99 || !file.Is_Available()) { + _themes[index].Scenario = 99; + return(false); + } +#endif + + return( + _themes[index].Available && + (_themes[index].Normal || +// (index == THEME_MAP1 && ScenarioInit) || + ((Special.IsVariation && _themes[index].Variation && index != THEME_WIN1) && +#ifndef DEMO + (GameToPlay != GAME_NORMAL || _themes[index].Scenario <= Scenario) && +#endif + (index != THEME_J1 || Special.IsJurassic)))); +} + + +/*********************************************************************************************** + * ThemeClass::From_Name -- Determines theme number from specified name. * + * * + * Use this routine to convert a name (either the base filename of the theme, or a partial * + * substring of the full name) into the matching ThemeType value. Typical use of this is * + * when parsing the INI file for theme control values. * + * * + * INPUT: name -- Pointer to base filename of theme or a partial substring of the full * + * theme name. * + * * + * OUTPUT: Returns with the matching theme number. If no match could be found, then * + * THEME_NONE is returned. * + * * + * WARNINGS: If a filename is specified the comparison is case insensitive. When scanning * + * the full theme name, the comparison is case sensitive. * + * * + * HISTORY: * + * 05/29/1995 JLB : Created. * + *=============================================================================================*/ +ThemeType ThemeClass::From_Name(char const * name) +{ + if (name && strlen(name) > 0) { + /* + ** First search for an exact name match with the filename + ** of the theme. This is guaranteed to be unique. + */ + for (ThemeType theme = THEME_FIRST; theme < THEME_COUNT; theme++) { + if (stricmp(_themes[theme].Name, name) == 0) { + return(theme); + } + } + + /* + ** If the filename scan failed to find a match, then scan for + ** a substring within the full name of the score. This might + ** yeild a match, but is not guaranteed to be unique. + */ + for (theme = THEME_FIRST; theme < THEME_COUNT; theme++) { + if (strstr(Text_String(_themes[theme].Fullname), name) != NULL) { + return(theme); + } + } + } + + return(THEME_NONE); +} + + +/*********************************************************************************************** + * ThemeClass::Scan -- Scans all scores for availability. * + * * + * This routine should be called whenever a score mixfile is registered. It will scan * + * to see if any score is unavailable. If this is the case, then the score will be so * + * flagged in order not to appear on the play list. This condition is likely to occur * + * when expansion mission disks contain a different score mix than the release version. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/04/1996 JLB : Created. * + *=============================================================================================*/ +void ThemeClass::Scan(void) +{ + for (ThemeType theme = THEME_FIRST; theme < THEME_COUNT; theme++) { +// if (theme == THEME_J1 && !Special.IsJurassic) { +// _themes[theme].Available = false; +// } else { + _themes[theme].Available = CCFileClass(Theme_File_Name(theme)).Is_Available(); +// } + } +} + diff --git a/THEME.H b/THEME.H new file mode 100644 index 0000000..3591837 --- /dev/null +++ b/THEME.H @@ -0,0 +1,87 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\theme.h_v 2.16 16 Oct 1995 16:45:48 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : THEME.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : August 14, 1994 * + * * + * Last Update : August 14, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef THEME_H +#define THEME_H + +class ThemeClass +{ + private: + static char const * Theme_File_Name(ThemeType theme); + + int Current; // Handle to current score. + ThemeType Score; // Score number currently being played. + ThemeType Pending; // Score to play next. + + typedef struct { + char const * Name; // Filename of score. + int Fullname; // Text number for full score name. + int Scenario; // Scenario when it first becomes available. + int Duration; // Duration of theme in seconds. + bool Normal; // Allowed in normal game play? + bool Variation; // Is there a variation to the score? + bool Repeat; // Always repeat this score? + bool Available; // Is the score available? + } ThemeControl; + + static ThemeControl _themes[THEME_COUNT]; + + enum { + THEME_DELAY=TIMER_SECOND + }; + + public: + ThemeClass(void); + + ThemeType From_Name(char const * name); + int Track_Length(ThemeType index); + int Max_Themes(void) {return THEME_COUNT;}; + char const * Full_Name(ThemeType index) const; + char const * Base_Name(ThemeType index) const; + void AI(void); + void Queue_Song(ThemeType index); + int Play_Song(ThemeType index); + ThemeType What_Is_Playing(void) {return Score;}; + void Stop(void); + void Fade_Out(void) {Queue_Song(THEME_NONE);}; + int Still_Playing(void); + ThemeType Next_Song(ThemeType index); + bool Is_Allowed(ThemeType index) const; + static void _pascal Scan(void); +}; + +#endif diff --git a/TOGGLE.CPP b/TOGGLE.CPP new file mode 100644 index 0000000..bbb3ebb --- /dev/null +++ b/TOGGLE.CPP @@ -0,0 +1,199 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\toggle.cpv 2.18 16 Oct 1995 16:50:56 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TOGGLE.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : February 2, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * ToggleClass::ToggleClass -- Normal constructor for toggle button gadgets. * + * ToggleClass::Turn_Off -- Turns the toggle button to the "OFF" state. * + * ToggleClass::Turn_On -- Turns the toggle button to the "ON" state. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "toggle.h" + + +/*********************************************************************************************** + * ToggleClass::ToggleClass -- Normal constructor for toggle button gadgets. * + * * + * This is the normal constructor for toggle buttons. A toggle button is one that most * + * closesly resembles the Windows style. It has an up and down state as well as an on * + * and off state. * + * * + * INPUT: id -- ID number for this button. * + * * + * x,y -- Pixel coordinate of upper left corner of this button. * + * * + * w,h -- Width and height of the button. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +ToggleClass::ToggleClass(unsigned id, int x, int y, int w, int h) + : ControlClass(id, x, y, w, h, LEFTPRESS|LEFTRELEASE, true) +{ + IsPressed = false; + IsOn = false; + IsToggleType = false; +} + + +/*********************************************************************************************** + * ToggleClass::Turn_On -- Turns the toggle button to the "ON" state. * + * * + * This routine will turn the button on. The button will also be flagged to be redrawn * + * at the next opportunity. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +void ToggleClass::Turn_On(void) +{ + IsOn = true; + Flag_To_Redraw(); +} + + +/*********************************************************************************************** + * ToggleClass::Turn_Off -- Turns the toggle button to the "OFF" state. * + * * + * This routine will turn the toggle button "off". It will also be flagged to be redrawn * + * at the next opportunity. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/15/1995 JLB : Created. * + *=============================================================================================*/ +void ToggleClass::Turn_Off(void) +{ + IsOn = false; + Flag_To_Redraw(); +} + + +/*********************************************************************************************** + * ToggleClass::Action -- Handles mouse clicks on a text button. * + * * + * This routine will process any mouse or keyboard event that is associated with this * + * button object. It detects and flags the text button so that it will properly be drawn * + * in a pressed or raised state. It also handles any toggle state for the button. * + * * + * INPUT: flags -- The event flags that triggered this button. * + * * + * key -- The keyboard code associated with this event. Usually this is KN_LMOUSE * + * or similar, but it could be a regular key if this text button is given * + * a hotkey. * + * * + * OUTPUT: Returns whatever the lower level processing for buttons decides. This is usually * + * true. * + * * + * WARNINGS: The button is flagged to be redrawn by this routine. * + * * + * HISTORY: * + * 01/14/1995 JLB : Created. * + * 02/02/1995 JLB : Left press doesn't get passed to other buttons now * + *=============================================================================================*/ +int ToggleClass::Action(unsigned flags, KeyNumType &key) +{ + /* + ** If there are no action flag bits set, then this must be a forced call. A forced call + ** must never actually function like a real call, but rather only performs any necessary + ** graphic updating. + */ + if (!flags) { + if ((unsigned)(Get_Mouse_X() - X) < Width && (unsigned)(Get_Mouse_Y() - Y) < Height ) { + if (!IsPressed) { + IsPressed = true; + Flag_To_Redraw(); + } + } else { + if (IsPressed) { + IsPressed = false; + Flag_To_Redraw(); + } + } + } + + /* + ** Handle the sticky state for this gadget. It must be processed here + ** because the event flags might be cleared before the action function + ** is called. + */ + Sticky_Process(flags); + + /* + ** Flag the button to show the pressed down imagery if this mouse button + ** was pressed over this gadget. + */ + if (flags & LEFTPRESS) { + IsPressed = true; + Flag_To_Redraw(); + flags &= ~LEFTPRESS; + ControlClass::Action(flags, key); + key = KN_NONE; // erase the event + return(true); // stop processing other buttons now + } + + if (flags & LEFTRELEASE) { + if (IsPressed) { + if (IsToggleType) { + IsOn = (IsOn == false); + } + IsPressed = false; + } else { + flags &= ~LEFTRELEASE; + } + } + + /* + ** Do normal button processing. This ends up causing the button's ID number to + ** be returned from the controlling Input() function. + */ + return(ControlClass::Action(flags, key)); +} + + diff --git a/TOGGLE.H b/TOGGLE.H new file mode 100644 index 0000000..ba88826 --- /dev/null +++ b/TOGGLE.H @@ -0,0 +1,82 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\toggle.h_v 2.16 16 Oct 1995 16:47:34 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TOGGLE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 01/15/95 * + * * + * Last Update : January 15, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef TOGGLE_H +#define TOGGLE_H + +#include "control.h" + +/* +** This class handles gadgets that behave like the Windows buttons. That is, once the mouse +** button is clicked over them, they capture the focus until the mouse button is released. +** They have a different imagery for the pressed and released states. They only recognize +** a valid selection when the mouse button is release while over the button. +*/ +class ToggleClass : public ControlClass +{ + public: + ToggleClass(unsigned id, int x, int y, int w, int h); + virtual void Turn_On(void); + virtual void Turn_Off(void); + + /* + ** Is this button in a pressed down state? This occurs when the mouse is clicked on the + ** button and the mouse is still being held down. + */ + unsigned IsPressed:1; + + /* + ** This is the button on/off state. Sometimes a button that is "on" has a different + ** imagery than one that is "off". If the on/off state is not necessary, then just + ** ignore this flag. + */ + unsigned IsOn:1; + + /* + ** If this button can be turned "on" or "off", then this flag should be set to + ** true. Sometimes a button needs to display its on/off state. In the render routine, + ** examine the IsOn flag and display accordingly. If this flag is false, then the + ** IsOn flag will not be changed, regardless of button clicking. + */ + unsigned IsToggleType:1; + + protected: + + virtual int Action(unsigned flags, KeyNumType &key); +}; + +#endif diff --git a/TOOLS/MIXFILE.EXE b/TOOLS/MIXFILE.EXE new file mode 100644 index 0000000000000000000000000000000000000000..8c0aa867776ae4c1df4d935f65ddf963db15c3d7 GIT binary patch literal 78224 zcmeFaeSA|z_CG#(Xp*KOEv1%6Lq&-~6_ILH3W$ie;6tqxQVJM++RD1hYPl(_3ndsA zn%>e?bWvH>6~TvPSwFg>7JMqDQUr0KxLCnOwji5u5fHE~CFFkJXXYkN3-0H$pWpBI z&+qj`Pw$<%^Kjj@HOCa!)TiV_#NO2Kr`SP<7nGR zz+V9$0y0gbZ36+hfCmAO0)7S94)_uf0VJ76+wuUD040F?06u^Mcn+`S0Z+U5Z~0(cK_hxvH&1i*N} z6L!YVCNri2-Ubu{t_So1{AgqB2Y?FLYh~0q+AY1?;|!u{Qvd z0XD!tQ1+>*j2!|r12zI)0j!Z=&<4QUfZqY;0LBCQ0nz~mK&yFZ@?JnKAPATNm<+fDa06ffpu;pYc{|`Gz;eKE z0KW$011<*i0i*%0w z@BttQSOjN1IVMeC7u4FZPSI=kFmM2)yRnCILYFMbyu&SKZ)aBGE%}VX z#eGAPuhqZ9-}ZITDYFemw)t%|LBf@L??)UCg*c*i9(cmJ!Srb+qN*;*7+ z$1~>C4Xd#{YW&3CV?)2n?7u}$tqqLXpOjHP(LZKBGnI{-YxR#=z+7cF)*7aMU}QTk zDI2=}A;q;n32#~JCwyR_sNQ2&*RTnwWFi7~N{I#c5{}G$hMe!G0I5jdsl1|(@9*)+ z6Fowkn3h`xcnmNb@Ce{xw&Dp$u7yZlZ{&$R#UDS>1E_`eu$kWN+m)m1@2HZ@J1*IH zJ9AA!vUKCLfwd+^IaV88wd}ebmjqqYKpaD-+AK2>!D!H_@jFAH!2*&^QH}5YxkD?rBgOceOG<)LbZnuB(aLRFxdrjs)qw`P;;|``#Uw>%dX?|)+3qQ>&E&K# z7*v8J&ugMUPjO((SaX&ysn)>M0g7v^`EBFwgCcM6`O+o9B@ZsqEMv_%bu+#95C&Ae zUd@5NB_-#_?$r`vUF&ZdQTM6G0uq(DM4GQ`j?+Dzd%HqaZ8}VMn!pw;|;2?G2b9HO>>Sl#~P&l@xdk3Q05CG z(=X3yu5o6(>^p})^0`2Th5331D4$JpNoLJ;n^`NT1SRLFh4mK3Tb@%_i0Unzcsema zwn9Wx>>`kq@IX+H4b3JhgYrm3sY2d3)z@EFC4nl>R@cYruzoP2T%USupaMmfIPcE+ zOwGh&1Nz2EPa{(;^fa+4o?_*waz>HR&kn`wR0`5kKJ(d9n&h9S?2~`i)@We90Vz$t zvbXJRYYyyXfxkIYK11m1wta!GS)k3Vgg0cd;v6lfb=QGiO}qB&`YP{SS;|~<-l?*r z>{I>EjcxhY^tR1LCR>y(%2&J8w#{ZHTlLa_-ZmR+JJr^R1cqffP3rg4okjjLX>-z) z=G_N&x3qrk-;}mnP1(KOA5L4AQ@6WiyVYq)`D}rsZFk$Qz-B{W7gMh`ZJuLZRL|0? z!f%_KzBB$<71|v#?X3EAa4Ne%@?F`K{0NetLGpi%*q6OKF$3=*#bZusn%_InF}1?9 zv6uxj9TsJ8)e@)W1!bq|Y}=ccS!G{%xY6RUXj(9{g4Jr&Y4t|Yzv%PO`Ce=zNt?54I;&$OM&t;~=P+SL;jr1z9M-|3|V``f-YeGw_htukL}pJO(UR82<7 zytd`oTWqbBx>&OFd0g*|4@EH%fOD;#6~2Z4?<)-5%bTMQSKw{hx9If)N!q#fo+3GXwra1VQL-Oef^xw=QY4$tTD`Y+mPNG)rZ7oo zHkcf<&tmRP!c^5g zw(@E(!}Me9gEy@xV@Vp-U07I~#0n8vSo@ldre&e4w#-t9>N<#{>=}oOR+U@8SP`0l z0bNt?4pP9IFJy41;MjS{^0>L>kf_Q=n&9ex0{^Ug^qQI1TUouKhu{>e%9q3kIY*`Sa z0@yjJ9mH~#d~<`>;tu8$Lz|b{r9A>g8U#d0#>!x!6 z^mdP|;O8pA#`sxI%?`iprNtkxW5bB>e3dbTttf->p#D0u5DK#FGp?`i=dgAm>Z@z6C1`xboM&l4ET>qPGY%ij$HQh*$;L3 z%q)6ND{_wP^2x}ItcRV3%@{WCzDFMU^LVQ{!=U?Ik6uSv88aw6h^svf+uf@P_zcy+~r$vU!ir>DCO6gcum_@zrU&XZf<< z)XB!Sg9nqIX*+D%w7l)0X^(gF-Blq7lANJd9o(&!n6_7a1I`*an3}iCXUyBYSu}^3 zMC+0RNk-X1^FqyVt-pzL3Wr#Vz$&mDzuXb$6p6xyGqgcNPsbVKP){)*&y7+CEMff{ zD$-gdElak!8%77DH1lS+GTLRun{l%xqLe#oB&U0wy-uDzYOwt13(sp(hnVl0$6Qj9 zQa69-3n|*Xk^YaWECyfBv-W-S2KYar_lvrxT-Xv`X%cyhG>-+e*9tn+m-8Csl_It< z9vfCn;Yw4Z)T(KyehbiZuX`a_?sz^$o8PzU4^@^CTHx$O<)-lMCeyxQ+UsXipASEv zg{PSO1)4$K9XLLGNkuEteg?MN_Ai6fs(ykmHgyC3n$>sk*Qox*U#fkpzK-`Sb(JD@ zWH*5f2H6v~YaiQ~>iF2sRO`oS3}d0Stuc%Fk^|aQ*-Ry1hO`S_q)4rUwS|Qqoh0Sp z2If$MDbSD+sAy*ueW*xZswPE3&qx1^fLS&!`*Zk>=zLT74P$tzF~nH-HDmYYFDh-- z7SgA8jgv-=l>4UCRbV0#lkFtYwCa88KuYtBaXP+dOe*T0V)`^*ds=8f6bLM%>a%3y zvwsc`iH3U_JKIpJExc4})wwtZEj8hZZdJ>%wRtzDY-GntbTzF2rhlZY0 z%3D)f7nnXaFxho}!SgfqItv!Ih%CS+-1A)cEVE?SMKY~REB0(iW2gRXb+5xPIX=qk zv!LVC-0Rk}s+~&v+os*eH&_jWwl#fc`^6sjx=~CXGU^`ricwej4!B2V%-!eqBlvgs zx}o**KaD$gej50SAz^f@@3gur{3gRt=4}U-I>z%N1@os~0MU}=%6`;#jB4B7^xabr z?^*t=Y(u6r*`}uQXEHCMZms&!`LvPp%3BwR1P;-A1|KKY;0XWZ;CU?|9=;0VL)_Ad z8>omF0^SI-Vo?m&uGI&{=`Xlcg~n@azg9Vw2UjufZI;c?vSxUR=w$2E>@$K8(Cd)(=~#WCISf#Z6| zXosh`$gd7B8{|KmT%PVfJ9KW6U$v?xzj`^vq>TJ}j&=O__V+QIr=OYWCC(c-YVj0< z`-t@){L%m-=wynqzsFyAwso`LXZxgwwbfwoefz9d;GF-!sBGDa!O$-%X%sFxFWboM zRwb$wI1YLOXD*wY<3D388#ZdVZyw}x-{X`euWOc3^Wy1Tr0(rY3Rr-EGcC zvg=+9zt6&J*r?%+lEcn?ms45CyOxD7HiiPtqo(?$3iH&A&t^iA~HvNx8i_T_D* z4%3NV9lqg|8^bcO%{=8_qtDLyS{=5$?Vryt+1A!#YU{P>`Sd-$yD9w@7fAmk(l>QS zR{3hVze4NOSg;Z)-iX_9aYIFh;_Yw;FIF=#8aHYSjgsd7k_DQm?GQ%yh|c#EZ%AV5 zAmwY*zD6vLjMZkawy?5OY2LDAl37bCFe`h5OYSw_oafx1RM!0RB%zLOKZRw>XIbPf zG;8N-nX$0da<|zCP6iE*IGK<(3%Gv2E<4cRc)3($To%jtU_9f`ct)Vqnel#}aSmk^ zqX^;%R=W-oq|tBvs_J2bXl`B7!)lY0DUftPQ_b`eU%g%HZ`Sp3UD4ihlD<`K2^m>c z{g-Kr58!3-0rS(BXNy7*x&yFHU0g}Sjq^Eg2ljH@FCQHY`9*wT+5)~wS?rb85<*I-3N zsfaX`Tew7!4vCO0+QM2 zKWChio>KQK3$#9z7nIuHHHBwu{&QyES3#*Y`f2z+3e4H|u33{>!;`gTR}OyxndbI= z*SJJ#hs3jM*8ycGH1Y$cdV|y+9>Meeeu>ly)#u&BZ>`}>4Rxf2f2J)eh%y5(JsvL6 z-nXgt_wB0X{j_(&#=TplA6cd6Y)(_9r$b<=roC_90%n6)5%FSvez4qvMx{Xk$yF{w zu)@5w;sbP7dPmRo{eGm6h*a$2s^n+G$kp_+4Vja-{)ofi7dIVu|{F|^?9 z(t?fIXO{d81?KHqXjFEES7AM~#E~T$(*pXzh(o!-B$!3Q4V~Y4CB7cH3Peq_(Pd51 z)RbJ3JI&_mx|Bv=lFRyu(-xA>Dxl^t$nrrGp$Zm`&r8_c&U z4M}E(7dx!%csO%|bJKDg^Ns&_7=O*!es0Wn@W1K!yDE*6_RC$BPR{|kt1|Gk<_29E zYFm zh5og`c3@(I`B$^B1i6L%V02$#a5`G;B`d%`oZYJbCMfO2nqy%iKCmzyT9SCLo|&#E z&QA|Y2Wq?rH1FOT?|!~SsW!Z!z|`CDCpoN@x8Qj$ZUlFRehu+Xn9>v4oQ5_#|6QBY z&}L_1n`0te3~jxHNKopCVVseGaYhW|4A_-iHQuP^?HCBm9tw$}$BVPYtP4I`n@7TG zVJ?tDP37Q zwN%+!3da{SD=jSj1CPkrM5#=yRB4ed^lE0RQ#n^O<+hD=RO{Se>RwOjQd4o=9y4W0X zk~K7RH?vmPxlKq`(<~4*Y(`d*v95Zb$%QYQ-K&kPdS9LkhQq!!77MF!nNg>OxvTMl zhSm44*|!<^5~7=jt*Y-23F4CK-77n4cIHxg^qPfLXPQ~f?mzH6lzB7HNF~r$VIe|P zLw`(u>@B3CL3RCoNujr+z%y0j1R(LTkiU_18(Inrg&B4xIb&lQ%ic*_lE_~;DJ0W} zsf9WFs=k3;O5J^ce<&&pO{WjjM{Ang2Yaocbr6-*V9%4mfN#X+AKRQ$_u}RYoZJo)^*7af>QRPNo~zZX)I?SYWv=Z+NSa9OykrYRI;*n2#9UU1X=g4Mx_`a z%3e<8eK7CmN7#bx8DpaF3@ z7Y$Q#xAgbm@7DevXQ&LuH*9$LjMFy{tf{xSiWIaVa@!|nrnEtnIrS>4yuP|_Y)xHt z^Vpi^91ViTwnb-HTDfThOWXBN`tQ`Mw5L*@R$Z6G(kLN4n$_LBP8hk*5xIzn!}2g$ zGA?CgEXY$|+~)Eh zEN^R>bFNj{joQ>pLgO@bKxk7`?LDc`85$Kub*iHXuNMq%2EuqGDHdEGpy3+#b4dZHz{G7Ku#4%q&a|AmXYjmecgMIpjHHvQ*MoHA&`P zq&ue0w=SH5$v3x90Y7p_Z3N zLbXv~VcV!_gb21iIuxm$w@GjmY;xMykFSE@M>|qZ+Vyqt-B+_xuVU+?Fm%l!&Q(a8 zba{%Wd;|}Ne^Ae0CnFu3>-Wpnm71$#iHZULPV%a^lLe}n2}z@2l^YG*QlzMiLZg~5 z4YnrD2S*0J{XLD0d!o{xc& z`RC+SfBFw<-1AS=_-9_@pW-!UpholmxW@lVUiFv%pvDKO#{c3q{v}@HLeyyc|Ek85 z2QE-!#eY=e>&dHL?bgOy|4l#osm9lN8(+m=EtgelxzQ!Hm-1fz%K_SLI=??Ly|F>x zM0>&?C9h|BlEp`3E_qy_!!kdN(W=`?xOL7yW=%5l^=!19UP8m=gc7R%~n15&3*cAq`gI@QLvEvH;si1g}XD8c5-;!%$eBHI!; z&O6`2KF*^K4x%BZ-iA~uz8wFUS5S{cTRceh1l1E+vbtjlPrBd|z9)(gGG*Ulc_rDP z_CrRqFM}u0kN_$#5)@Ga1$v}}9U)8=AxguR$*J;EG)5I}@{%t|i`X=sj51QBi0^VR z+tCp3?4vW8&xtxZzR9WEacQmTjvbfivv%435_Pa%SmP4j?VunQ9B;I0$!7En1PLGsIlJ{8sN}ZavG(gaVk%75WdpReNIL*DZeJla$0d zJzUn00lUK1xg(Z9II1k}ERB<(NsDCxyW#>pS1~PAgL)5&pzXGQV!Mq~=&qQ5LD|i% z^?u(CwT5vypDDXg%{7w>YmIsU(my!dw34y`wQL-|U6kjEmO17oRC|hg3$>_7Y19utI;%jhxl3*2vof4kq`;)*CpwwZjw7{$m zAT16JFv(6VpVR?bv;@|P_J9=0z!bqY6nbNCYzY-3TPyFQm1k<@8CrR|R*oG!Sk5QmZzND)ZSllJPbIf#p*FnYq5CjYgAd z^PjoTm%7oIjP!o`N)lgAT{u8_krCPbXR_s96!PDc?HlYrBg2Gs=6+v)#QQ8FRiGsj z*k-083@F0HkO)TfZEURSqNMcdS5sHnEQ48H%1))ewZSjLDjONwXqf;TOru3Im*h8i z(TBa^NP=PHhUr81wt&Tm3w4-_f;d?6qrwp()KhElQl}fC8x7?{dO;547Zi{pnZZ(Y zKf6ZqW}uXn`DT0Ff;4*NqneL(bqa-J6XCOcm^*SZMJ>7ujs>&{Kmi&VUH1V*x1qaN z_kukinR>-z#zer#nlPtRDXW+po-}wE@#!IafOs#(1`!P*J|lah*)(nnX}@CF{v>Qv zOv~F@_H8_Yg_oP?jv_=vkSFv8Ja5R-06!Cv3}cMsIYBo;4wBd3yx=sIF&1^tz(4ol zj)@qyWXQNYmLFviMMFoawNA_zlQ#;&Y3s=ch}PctQ~@^O2sXq(bu-PyJjqrL_ZXXM z)gv%5=?&9ntj`p#W?(Sz;WLGw#3bj*=lqIzns1I)VBf>y@pE z9gL}sFGRh3hiD^TuZF0p;EfH~&GX-6_nF9^yJ;fE+z~Ysk=PvW9*^dT38ZUQ0!t9T z<_xWP!qoggpu%bJfsa_Ik_zxuTTw{TUK}FX`+6J{z33t1%&1t~x~&~?HXI*BzGe&} z(*Nw#wF!!ukeje1kc30)B9$CtA~#VkF&8PXjq;kX7osBt50Bq4p4bGBbCM=(@&hFB zQaO+?;^nk`#mgbfAeCdKa*XP|NG2wBVx8M5Ev4!~sbb38wL*ClxCnZMZj8QYkRm-j zQTW*MkrBE)3i3qy!aCXA%PlrqP1syc7^}DM>l~|a`Ndf6D~i&}V`H_^meWLo^mZ|Q zZ;XA%62}MO>-bKRWS(Rv&$%czYZC_X(|^|oG1uPnBuCjN7_SK&28dz9H-p7EsOsXz z)O_P$W*W^*z7%~ep3_!R(q$oT=7k7Fmc}J#?55D4u#{k)&xPpFRh~$Y6;uaEOv!@e zfRgMEJY@V9{Ba0H5wjv2vT^XjXpbzIRq~BuMsOKQ7bq}=Vpaj_I?W2Az~Y|_fsJO@ zRA(VqaN-hQeQd;3B!LB~<749|)!mj4RtC!>7^uOQ!sEf*!q4ywShyfMamkQ)cM;Y} zOsjHUB~~PAVpmS4{+eQSO8hZOL)ukqtn4zTkENjTDCSy98XM@%r%Om-hif=y8xT-? z^5PSzKysAJKgL+u!^e{m%a@<#89=_5jg?|^oBZanua2_`5T1mEpM-C6YLy2PYxdv7 zFnR{lM%^ahb1rZ$pLU({k)II(LO5 zr(T_7SW~lC@c4Kdq<~8>e1oeFM961cHXlr0J8@U|)Su@V*2YtD&X2W`aC3$}h0zi2 zy>z-Do`MuDOh0`EdFrbcswt9FC(r~EV6Ya`4+W+(cj-0tTr)BIM0PuBdv#8d z>4d3$O=MuaM9xa7LLoCaL(5UEUO_w&sx_}4r7L&bPqBR$KF0j@VaCe-EW2s_SR9s# zd3gtpg6Fo9({-|tCzB=nstQ=`IyhI<$zS&av|KDf5WiPfFeHa-Q@E0?vRf{JAGPH8 zlOL0ZhIweoE^cJHF)+qqfeWRU>#(S~t&nElr_?Rz z6+T0So8@8HB`pl)?Ft`dT6sHGr0O3^)Hq9whSEd^F#g3X{3+6O3i@@7qe{*Lzu--v z?9U%10W9){Uv@7lu(K>w;+HQUtTC{boF-_NKwVVy=uz~h3SX@h!oIvc)BtRw26-Sg zTJFm+??uUxUMJ4#6UagieDg;&TJGTT_ig6Qy^+C$C6uL+=@F)mMJ-x;N;4dYu3wnEF>Ck7SR?VHw* zCQD&I-YxdQT5S*JhyIVk;Gjbb02JUee~Ytt68Ci~(Vi%Xqz@Y|IHSw+ELHXe0|R3! zaJIOQ;;LZ6YV$d)HrsSVlnQrv($bco&$-txST#5RSVWeqwwyEst}wTr<{?+@?;NDl z{_8ZP9sN6r&xrammf{7T;!{eIfNAY5H-KTnyGPJ?sbnt;e-f3 z_F|<@kAFw(E9Oc{1)ur_6R|FI^ymQ@a}+FEL@D6; zn-g61H-)b-gtH7(A~HQK)(Io66R3jL2{Zpj3Jx5#$%61GEnqTKQ9CKHV|Xzr`Wvh# zHj>EOnz865V)e4Q5<+kn#MZNJh^^KKI!ZixTu1D;3~n1#C@mqK;!odB@ke~3j%fb^ z1C^KQbL(vx>l;9W*;Tv3(W%4tPDMJbL%xCHo0(RwgkqfHGpYDQkS;2I5*m`|UL|@F zZ1O?uGQ_$_-%`L564P32^3^KDbIdrHwCzwDurj?+=nM^`TteKdC-Wb439za?aB&r~ z23L2;J6@KaX{Qg*xD)V_cv$g%|LAKS?Wdd?o&T2O7zTA1OgpS!|6y(*s|i zfMMw57{XWb0u`%CvdwpMsl|a68e7shVPI9E0#cKJL0$}lv*<(=94l3u>x7i#K#Gzx zVCYm4FATZST(=?L5#E9R71dv`e>*Yj^OHE=+KKZ*rv8e`ZM)U7NgjGBcU97w%JG_<>{gOq4UUhtw9zVvvn||g(TCJMvvKYi`imJg$0!zAlf_;s_FU*h z4smA9WvN8HDRm1m$Km(Q<4Bxt53hr1%{-xPn7xuRL+^%)*Aeu#BX(D)YjhL}G8e+z z;q@IWu>#CT7y_uc(DhoZbRHQ`MfR(>xqxUxsk! zhQwyJ2Bp(N1yP3tDnbkg0Q8z#wer)U*Q2ZKl?LfFY#(`2`y82v;#P|U9D`B_g7-)2 zxC@}hzb&vK?+~e-=B8*0Z?FU`Lrm@^3a0OneyFap_w?to#0JFawlGa7M543AnIOm% zk!;?1$AXf+l?*qrpmaiox#gr*enKlhrj;KJ?K;P+J23>Qq!W44={czMC_S5LozF#Q zN|BtVZ0R)4_u&}U7mY#bNc@XgbgRX*Gp8=w6S)}2ohF6;j3nkf>BtU&%(PXmwp7Nl+u>oCxYA#L{zlGPYaeDUI=M&axTK(hp^RIAf%W14Ig}-2naE4+b984SF zSn`sC`BE2ESnfCbY;Z$yKn%_Zj9H1;9*8Y4`{10i60uYkt(h5wC%TPhF|qCkk8`$o z0n__q`-QC>YU3$kU>9=)_HG!j;Xcx7#3sz-hXNI8to*)4%XluMjWho~Uq9tH2nr6W z8iHLQk0S^rsXnfPnN@D2xC-crBYZKtc&rLVB3~#Aw~{20G=RN_xFn)0TodCxxC&;i zJjq-JW`HBLVvbW6qxRR~GXwr_H-RB*|H3HjWuUXz6o-`&S~O!0XBNePwlrY!li z-n~zoNHXsjUC1iP&At%hAVRsgV>_5lCmRHbo&pK5W!M38i=Iq)<`?iFBnCJYZ?Lit zzPp*XyfxPH{b)HncRQF5A`4lDA=_Y9MTamyNAbyV18kP|%LGLTn}wOQ2op z&YYnHuYst(syt7`_Z!M8Ukzk=xfs4M!?y$HG_J4l0&YeDKb+GG&Ck%#S!0z9hs-?^i(ir!LkEHOC@{kF1nf|H4c|q9 zjUl|BqmL}U0se|t1&%wuVP;f^%Q{f=?VyALvbdMhQm!55NS8Re)9)oZ@t-o z=dCw$gG!7*0#(u0PDX_r4&X+TxY+`N&}S}c%r&~T>(Q4FLu;b#Uy3wXi!AP%d}DQ; zb-G&_h}#?ZsOlW;9$1sfI>BnM9!Gl>vD39=VGTXJGd|xgoQh1nw{|9qxJb?aYD678 z_D{ZPS?beZH{kDWyRy$%23nW2wQYg!Z(Ku~KiLVERo8cF;?;Yh#VgHp-nmULqivTG zg9L7iX0gauPHKY_b!Z~VNg zjD=pG6q7JGk?>MtkKN3So?_rA>?G9!-D_1)z)m*_>b$JHc5)raWxg3@5kye>bQt6}K_g!rG zJ2i`=lBa;`>8p#siA@@hf`V5$bZJn%zDw}~x)hJCNn$l9P3@OZ(Y`L_f0E#@BQ_f& zE)ZAQqo!PW->ANGlgu|mTwOP)&Ua#2^kEI{Gxc zn7fJ>XFds!huXx&i#fDIl52>|7&f_AOi2`r{nvG|#hy&)V|@_%J0~@U8V*%7*l?&p z$OEs_IeOiL!*e<)okTUT;;6sY3gC8oQj<=i8!M%@QSjHGJ&7&W9$R@5HfLY<&cHsFvTy!v*xBsxv$AnrzhGocib*Z=q3B~1idMCBGR|{~-h-;MQFS5Z ztLFYCWR3wZEYr>_N~uq&E5B+Ij^jLr!*^U1y1cW{jCi3CE*+-&Y}irYY~N`q2Ze=@ zb|A^1pAf#pwSFujVe->Y2&dAf1Fjk%ktuB|*IpT0sBN`#ODj&{&@D-426@6 zaBlfo9XJmedLa~ez96S%l>h46&GeFDlq@;4F!6D}v%H9_p>a7$~@vo|O;u>zZBh@)nR zKgj>m-@0I<-&Bw>kqjYsV~4rZDg3;v0uG9%b-OCy&aMS^Ht*jbr1zlFNv!iA{c27O zlc0$HmoKV1ccg>#Enjp$_8(suP69Vew}bR^H~hbGkbdHwxI{7QLiZeKngvZ@Y~MuQ zOM^r&g(m{j@m~nEF=QYpA!5YGokM1l9_OMsv5_th!j+<^&njIgLG@fr+PB|3JP&+LiQK2{-8bc|Gp`&-Imyt^>jGmS39~j<)M!{ zo8r-%QbcijQ%3NnkVvd?p{84NF8-f2r}gtL<5Vp|B3bs`j%4?L-fbRkyDg@k zp&ZP={e`F>W;)z&u|E@+f%LqS#=$Yz5cMhc-}lC~cO+Ij?hfQvw+jm7c^qO-vjUOZ zws)b`g`27O)So{1ANSN8y;z;D7aVv38?r6YX_^nWPn{;%=KuEE3NuT~va=bWy||kr zYbaRb|Mn^mPjT>@;EqX!+dDVPUHDB zXt3u>V>&P2=zEZdxz?9w^?4G$rsNyn@cU>3wX2flK~Fo%z?~2&Qo9Vi z$z=@QpT8g-pA6GTmv0$tVxvu+sxIVdZh(W4jBF~-P#XOKTlf*$=Z>SPpA-8DG#~$ukej9>2h#!+SM0Rt zm|b>Z%(fyqVaE0PWRBZhP51tcyEM_ZB=@$@^Vog55&I%x!Oq9=Tmnpmr+kRRaO5Via%7O2wyS2eAKS0`{eguzj z%PV&HEEt-0`4W!7GIUs%1S$D;cS^AX9$WKp99Def8!kZ)_Ag-|L7-TVpi7m)$DwHJ z1~!;voS{SH@*X22HZG$^c~~o){NyXBFg`R~WTA?w_?>xS=R*&~O)7Dk3vRpPbDbT5 zLMbpGTm$2mpUriMb8xNsW`j)E8$R^5ep>D>vq3*1hXZ0teij`YprtTAMLG{C^6&8Q z^z|hFZN0?6L*^*CQ|Zip{Py@QbbP;0G@7iV`WUBeiqKco0zSG|3fk#|f{sAapa3DV znN4wXi{8D%G2E6uG^PlJz~EKyBb7XOO7LDI0VjW}HE)02kpZ=ha9#Nm3s zkzbc{a0>c>)7h+f;O}AyKLqBL`oGlpssP0)*c>$&17^Mx|LFe z`y~Xuj&OwkhlDNIPcY*&QBGaabSkGQF^uo^`W~q0)|@&jr>DRu9))DT zHz?eWNi`saqch}nl$3<$8=xt}q!nujC&0k)&$>l(Dhv!4P+@xO^uqKIYPyE!>1>I| zF)(~_w>U52k_}-?XD6Ixyc6D`PS7d#kl3s-ho|Eufheevwv>57-Uo!0ExbW&*O|k= zh~!c8UD;cOxNQfaS4upk- z6PYNS9nY#}C8Ps2)U<>!D4{Z#A|MQJuASU4dB^1alTS^KOdc>LdrH!jUQ_6~PXBM@ z6luz&DGyF5pR#C5^^}!Ua*A#(DlYm(QJ`p9(W;_%i?$W*Ejm(ku4wgS@n6r$11Ark zoIlw+`H{)Lp8VU%Ty*1pZB9C!4u@w9Ld>Xy*%iU;!iwVHvkR99Ir!}A!0dAH+0}vB z)q~Hj4$LkGpIsf8UF01&5ez5qL#ZMG3DdznK z$Kr&3(z&4MCvZX0PZ7~i#OKgYqIV+L6#}D6nfnXgAx}-!dxOYn2EHXKw4oR=ljD?x zo;p&bRQX3VY$%Ay>8fUlH^jli_WZo@hW4F0XlnM<+^M%ry?yFKQ_H3nO}&RdNr{;@ zo&KKx@138B7sv5ePNf{Q2V!FD^>okzN_ zh#%>8!6GuC!Mk7a98jeFByaFejsQ{+y>==Rp$AC-Z}5M~hs!(O_TUWyJKny;8(fu<@f$_j zi=xvJk;Ws^xS2_8>LCK#eV5U340^c&2U!~P-6B{$vyX@!z+?L@xtp?cN?l2BF**%L z$-{UAj+CI9;w(hCc-l<74Z<6aG~CFm*phz(R>Njt|3V8v5v{z5l-$?gBW!P#zBI`9 zB6Q_PTsw{FCtHG21HZ8$;b02cQ#(rt`Pv|FiEA2Y2k^v4_)|)LNB{g#@orHH8fxmF z1vW5zqy{Q3W4|v!y9X=AJU-?kT>7rtNVW$fr;BF%xARL7_;lwLH)kUxV zg*V`y7-9KGf2)`-bdyAGHJk-SQnIcL(!n-FGO~x(DE(_g&wb-z9@e^7hER|Zx%Bi5ce+=%Lm}3gR z$4c)FmbTVr;cipt%doOP5v7?j|4Yo2()*O<^P>u)g3IsFf{Sm{l;wVeD-bS3xB%g5 zgsTvqjPN9cpGLR_;i(9F5PlBfWeCqecpAblAiNymdlB{`{1n1ZBK#o2_iOwnj7_1m zAZ;~B?ZL&fwZ_F`aRp2DYh&;>ir(HFsi_ll>T;Tbi;-#h{m6*ImV5C;CCjJbiOQFI z@B~uJC*cWXmlxoPMl44or1nOXkBbseyn>n))ST3Z4wVO&%ScwHEvo2u-090$RMF>) z4j;@hmx0ZarNyi|M-E}q3pEJg8=#>J2Dz{47jF%@P`0a;gYvJT!& zyMK#UMjIDT;3*_jz5=1~JTwk%oQJpS<>iQ97v&^c{2HDh?c%j~f`p6Tz!Rih{4$>C zg2gZ4iB>Is1<(7jLj#Sf51dzz2R-Y8(ptengPym7(wpMkYtZvvP+H$8{gqBEUX~)c zefCCa5Dl;_y2*DCD@&gXBSuzgc|1N9DYL+S#94+R`bpNM(neD=hBO>c4>XitW;)?k zCSz)wagmP^_XddzigKFrJcG)1kWnzJYzuttum`aC%_v-3)?z#^6zaQgM5{EwAm7m_ ztz&hK(%Qi=B-08h(2h+6LJ>$~BH#kIcLN1)8oL)aT(<_8!oPr*s2Ji0m{Nm_Cc?vO z7K5r^D~m^JZaC{L9)m}|QCU2Wo@QmSL{E!?k*J=(E%$vC%mfA9g)o;g6wCw#-H7lYe9uxa6BNt@gxv^VrYy(E z$GAjzD8g4Li%apIf2D%k=Yq|qSqGNhSNezT@TeB;>lbSuH3;qF?Lqsf zEz~}01KNinf~QU3jC+tcuK5muBkl;|xaFq_obuBJE@+p)BU8ttkHEt^@V-B6SBZNc zdQbVzN}H+Er%x-Hx}D^+z4z1K^ZyAMZXW-tU#Wyk640j~Jbgb(TJ2vc+hCpQ#^age)TkW7=%E~sr?*U+v;R`E^j zyajO(I z!sE0ALPC$yw`1;0G=S^&VBxR=iOCrkg+gc1C8)uN^N^hebKT6{nZ8qm32hS@u;_C& zL^adN)dX_FGggq3FDx;8IhYQ{O)!6iPFhp=aU&(eknzLt`XCQL4}fak*4)2tEcd~L zd%qpSG|2b$X)^%B23BGma)O^TJT!=t!EJ-=k)6Yl#FAFtEqCFj!q!d0Wr*VFq?ox? zObv=Kwr)z3FQsBy^Bp);0YL$D!C)ovD;HDZh0n?Kl5J?Mr*kH27z)F4C7!rdSw+s1 zR0^=^#IQC0qBI#=!pUkcIV0d^PIMHeGFkjZ!RaVG%^5QNONFT?%qK{tBj|05GL5u2~p%y2@3q4xXF8r#su9Ia17%*9zH=~q=%qJTuSkt2%JrVdupn%CY=RG z7ion|7R*eqT%A@)6*T6j8<@)q^VR7o%IsFR=1Qx)XQ2KCH#Y9TCeSh9q2KDJf(IV~ zlfDcto<8&#y${WUK4OG%!vnYFBSB9nC>?I_9*z&-qwrG3cEuK+nh$FFDtIp3p0)BL zpqhq^Sp+S`Kb03lC75f4f&uv_qM1sM*Rq?0ewUS^9{ zejqf;PD4*RsX{2jrW$vLgzf|H5`HhnAH^Zz&3k6;|2h5F^XJe0oG*$vqNM9I+*}h4 zUCfUflFHESyWo1EbLViTn=3G+$|SHZ@(ttLQn*`EbTwTfKwEt}FGC0Gs+~L>h0!(+ zw7A%0qJXZs@x2HH&7lm;^E7a9uuI}oa5}~Y0x#6+#_px>k8$?7dFaq_x7;#s)-@dK z8#pJ2h6Nr-E0PCSXkcP;3<&q)ei(Lr^cBP#=^UZtvfki5fx5&9Jpv0ElA@51KC%&v z4fewcQGBomr9(s-(iXw$-oT|-=$=Su8*Vt4kc@>}D6Du}#N-Q$ z$#>`M#O8fzh&68;)*F#iZ{c3pun4&rYuXEwOsEcA69>ch2PIgHzDbexqxDZCCmnqN z3%QKmJp1tp6Y2^?;MfyB9l+aAjxJ0?m>Rgu;F~xl_XMRch0uayLpZmkmG7jExcMw~ z1O${OVj+Qw7LyOQj3Zh(;Ss|L29Et&`8T22?I5V)*#nc7Qhun$dsy*?l!7D9(3At= zAGi+B`_pIVtp(2CVCq5s*(O8{VJze`g^NE#W2ghDIAbZ&P$(a3Ya)3vGb>kTR954+ z&^)LS{OU0H6?lXz1N_QMcE;=CbjH1*G={*GN-tPPtYmP~t$~WIY(XDN#%@1pmkRS2O=TlM=jJV94b&SyuvrvoT~>{)sCA+;Rpn zOh^P+wrDQshBMkJ4P67SL54F6Og_lwUF!_}{WQW*5Gh=M@TXW8NkORnQ#TG)kgnd3 z61zJ@1S&F^40#po4DxFHrc!v9=_8w1OMFGmg0&EHAOGVB1_>`>ETX>N@KQ|6`7AKo z4zrIvPjb##mVm%eIv17B$gYc$^ZU`wAoK#O&tX_5z+J?l?sRcTCC_uXEQ;w!Mgn!{UAP$+S%32T36h0QSagHIXCan>Y5D?hB2qZ32= zN&qee)`d}3jZx*K<6U}3P%_TZ_J}0T&>z8L)A%TAln!Bv&kn)oGn6MCEi;9GjZIM^ z9j6|VE;eRG=N*Lkgp*BQpghiyO1%2+~6c>fQDMEPsx`DRD$y` zi3Ga$2C0iYBEKKMMT&a^IdN}+kABdAZf}9a&>H&T`Jg8)D8Z2jGYJ)^VfKeMKBJ5D z1*aA7Nj+ub^Ft(OUyqOx22uf}AB5g|9z-_LwhuYN>a*x(%o#CosXP@IDnL-6%^`O- zMjj=MClJ*N+6bx?fGU_+SeZ`Z!g52vdzz)}^d;KSNgl#4lYOnLzln6P4K8FjEaT%Y zPdLM=$z`3Dn@Ca??1c2*_HuLH;rfN0Q9V2`2ZSd(BX$(ADhi5XHA!i#)})ZW*F zJ|^ANoFzlOff;2WEGZ-dqL1+lN}N&Bclchzipsx(#f2A|$VY17l|Gmm+u2+!`!JW} zn}z!q)|`9b4uY=3E*$Coa7~8`LShWbMT!u3tby65?O};zi1^ec5k2T36{%4Q(F8sQ z6pZBWL24g#j@X#&OsJPyWnbi5q`=ANljly4pZ-v;8yJw44c<87CY$DAv39Uf(PL6KPEKBi6|BF z>an1Q5Em#5zC?-(39dpZ(a{iz=o9!7)R++109j)@%B;RjI%wJx5$MH$7e8Jgfr_t3 zMKgq^PV^J4g!*>0RXWb zST4-}7}Gn!PpXO>*J59Y^Hrzd6JjY(Op(upMCoXJl)eNZkmELiaGOnXI1M!P$gT7i z9}a?$h{(@`jNXU59gl&n>yJKo%~WCk22Ji18Xo4}!-_hF7n1Kpo-{HRQXbfH=#d6M zE4ViJuW!jx(2M7X{}{y~e}+<0!lxkTwP0Q}O`zpMi|!Zg;$;YvB5jiEJ5*N-36oTH zV^TZrQ;UF))4i*%+CMokB5A=u@z-rcrBK%RaSm7$p=agmXD&J|lzphh@NkXC(8cbZ zA+f72JYczxn=r`1Gac2Tju(E5Eo@?PILrWA#BXM_uBpJPL`zuwe|jtAdlR_q0{deU zez?zBySSpz8Ol5ezvgcIkJc+<{_!&&oS~5iF3~x$A{eyfPN%$2f^nxjvfYs^$`vxo zqrc*UQS6+>PKb0psi0py!_9Var(SNU=2|oQ8MnDYOvn9{Bng^%1oxfG=P|_?(NX%K zucWZB(&i>I(--3S_`)Sq{sknA(s5wi)6(Vas5leV1?MMr#~a4)mr*iZ4hG9L^QE^93okFk%HE9Un@b#Z7w?ofj+%iGnXr z?X=U=N`Fq%AK0LTh`#->EsZVBCSj{XMRdD`&Fr(1sUUnBcQIlOLabP? zF6@=exvF$1MLG<=6~rNj-<~JNqZh^H#-m_u z3PemUWyY2UN+ftZm*~@=GU!eD1u;Y36jS&Z#A{+v{9yfnpWcfXg5WVadA||TFMI?# z1!^?yb?m6MkoGK4@M5WlzIXyRx=M}|$w`Od$#9bAIp$jcS?& z;FNT0s4YAL`{i(=?!`}_b@rb41TcfjYkkRyC~Gija+ zjfjNL3k+%#zX&F#0cMj_C9Fa*3;%@&vDl1Arz^8C7KL<7>xDDqp=+6$ILGMC5}(g*X zCP{Ej7r#K?v&K@+l<36gO}mid2JSg33=Z5}%~swt7|_Og4Ge^~}*TJN5CYR!nc zwKe}(a&pPn%4TKfEuo9q+>|m27d-R~rL(y|;zx^Y2$)AD`woq$Qx9OwSjVlk*ZB;$ zEIu%z?##i9)=j)}keq5BG-`&=>K`@8XIwWirFHYuPlr!GSz_9T3;%((>yYCX(>~Ka z<+O4%TPHn zdkk>YvFeax^}}pc$gz4hTXop6`VpqNk2qkK@f5GW4eqiv^lRU^tC{Y@$B$6l{hO(@ zf^koi$8jKxI^+SrejbSn6ttMq(YVIv#StL=tPaMeuoU3aSJ&e zLzA}~L>=q#>^$?f88ya!+gH=iy#3A3kruK9$KckV#$d_?Fxa~&5rgi9wJ-n4=GZP! z*wQYA@#YYzmMtPuMN&kg>hUx)I><*cX4Z~3`d3BY4F(ZlE0Jd}zR#&Pf*TlcQ zeBQUUJciRg(b9!6)7}UMi|1p%6aAncD(o)LahZkpe!iaY0^_e3(oy&E_szn8*ZUpM z{bcXkFHqQT|7~G^`RT$wLMyvtHm*xy5zWl<&7*A(I!D`xzOjGph=2G3@vWMHy^45W zxAc^+8KcSs?n)&IvuqaL$t;_WcpKi+@xJvHhU4nBqir7)vK8O=W~{VP`jWEaxs#ra z(iimSbH-e=USTWl!;h=r$4Ov=ve3_i8y5^JeVE~Y{s&L&Ty86zHQM&{ET3~l-x-t` zMuS7?D5AjtQO`zuOVXC`S|jd$gLfswTj-^|gVM&Jr?$acOQ+ICUxh_%^wnghZieWF zxO~G4g)3I3Glok_w$i04+s*uHk*%_gU#b#*&Oo=S09X9@-UK7i#dEANFy?7ypFf~0 zhSlEp;C;6q>csl+dNisDSk)#Ov7~P-Tyfoa!Wcgvzk=P$8yfVy7L?XBc-L^mhQacv zNoy3>(`?g0Y_+_mc-91$tnCqe{*4~4Szi(%{&z(*p@^M5G|STr(rRGLGN64`ceMZ7 zD81DTwBO1U1b#41;P4bfLq%<%(TICU-t%hEo8tFq>oeSXfgcI{*o%{Cddi6tm%?4iv=zoB>VX-Mkm>u!KQJW7!LoSQ1|vJQ22Nfme?{>v zoH(6iQe4Z3BEjdM>=9hzM?;pea2=5Y|Hp<#wf9Xpf}KJO)`j!5(m=&dCJ*hNWNoAL zhQqK_dgHuCBEy%=caLk(LwmQhWXAb?UhF9d_S+6`bMd4G^(jPzZ#D8 zc^N)#ls@zz!-wZ(z+7=*D1NS$e}Pth=?qy6Pvd2$GxS~(UUFZ;Z;Cv}D8@Bwwn3kc zVuq;$5FK&NdRs(4Y*=A4Q~T^Iwmm>o7&BJA7D?LUNxBjrQ@5ep)EyMdij9{(u2~;< zE7`(7)3uuTLBCAeOa5&8t!yH@n-#HQgVUIAxXVB+ZJ6bJmR10kwInOHC=#ckWzfX- zft?A4MN6WlrSqlnlzA)UN5QBJNt{u^GQD~Q=B{J)N``*HJ?rO@`O8MBiOAe^9+_RU z`k6oqOXnyeJ3WfobAe)Zg0Ea`S}J{UUNM0&EAUHcX;hMH*7YX6JHUqyTfkY`!4oD@ zHG5WEeiiiu^ybe46`yNz?!%cYrl6THg^=YHI@KWL>BsZn#0FH3``FVM+@-miGYwsL zIGw5grT&GR|JC8VL>jy2^gJCP&i@fxqiP!#+Rn?+oeSO_V{0Ygg@lu9mciLM z8shlKL*mFrK`&5{C)62WLL=4mfOG`hB1F3~V1KFAm1+rFIU@>M(wOlR0i;h3oXc@hjW7;s9-s0NN7 zLE;GW)$@C%97N|8PFx8BI)jlBj-nJRC562U+2~a-0Xw@WI?u|l6Z&ksC^i;ZLAJTi zw&&EL9IR8=6&~{(W2sNI8bPK_2aJU)mf89ACoehP{`1Lx0vp~{Ap5cN$PP)Y8}}kh zh-l1ty>}Ed=~WbSNIKLjYOoy!FIY0wA4dOS2*EWCZ*wzZBs(0C zI?PfY)}Z-KrMO6t$j|fw+SQ@FfOc#cm9Bb;4}rAK7!ci2YCx8N)UFq?6AWb5bIA{z z^+oOE3g>7{4J>1&vv7$o_~CPGMc&o1C^L_GNkj!2E744mt_WkD=1 zF~u(}y74;$g%|$lNcf171C2U{tI=P-sB+jG37=p@KX}8=)qxDuGbDKPo8w(Fgx3q5PnN zBKnr9@}p8fZ4?QlCJhoqjZ%wRaldo#%{j{Y8_2y120z#q zgBFahQ{vF8ba^B>o$oxFzNC>#p6G!iJ=iyVun8+b^2FR_Dly7PlELpg)PAFM`RGwv z8Zep2juhI#*`{!IT~zb*oj3E1=u0)K9kU5ln@xvke>O$10n`2>4Lf?zH4cYAIX*Ls zbJ};#^?vVEDBNCAhSp0f9wvzgNID;rjpZf!_yK)<2*ATV>8*ZhdV-`>TG`guF8*<=nHLN`A%$2ka^#J<1d|%yJs$3)BXgffH>}&Pvh9^pPL}=Km7{4 zaH9LWQAfy=-qp{iYBBg0a*0kmR(W1?2(KdcF=`am5ZQXIiW+yEV5j92M9^We1qn_t_r0OHv9m*n>LmXQP5v)-xroH>PbxSL0 z@2YiBmsA6II{IN~`m_IVWiPCfwDLx~D2k+?@F0i|7{EWBP*O{@*(Du2&$m?Wr%U4= ztuW*(zoE$Rw1+GmukQTmiNp2IpMI%yTUhC1q2~5wEt(q-gi{$_d77cZJTT=K;%P$L zIEB8L{{EQ_4Ni85Ow@Qe;1PJT1ggA`!3NERb|;{1gGi|i zP08bMQ`#NF=4wuJV%cimnifFVB)*UF@jTRW;|~U2(J74Cc{ObGAZURf%5+`R@fs=d zufv4P{h{}q!NHR46oN@x4W&m=6iM{)aP@u~h9avu=A>(}52z7vbMPDK}tBG+<(2Uyp@xz-4>3leKG+@k#LF5j6>7eGzt9sMCZ2 zF(g-vc)U6t?_nwT$8v_fR_|yDgJ3Hhv1)}bx%aG%m8$g82s*LC(14vrFtOo)Yz!gX}2JJ+6x)J3p}QiF3L_g&A13&{fR_4@`f zPd=0T-WvBKI6%1u?(hDBxF5Tc`|AaMbB*=qVApUBtUp6GCZ8YpNabH%5ns0SnvKIp zGO59_dJ87Q@3upng;iE#It95AdlA{tEThx=4XcTg;Q{S@ts^Fp^K4?nD20Dk7=lX~(U)a6qtM|Yce zV8R?GgqAfxs|lHqn$x-_eP&bQa>vZAIMf}ccT(z>yI-Y=1;a4f92IOx2YN7NTQJqb z+McV`1}g^C^9$0Reu4Fbs8y=Fu)kJi5EWxscCR59sej9Ko&DiF5P2^|epC8A ze~B_ArtZ2=+{i4e=)9O%3Z8qY~m^2@b zr%$V2-KZ^732vkDgXe}|n>-X+Pku$)Ct#-Oyzz|mmY;5Vd_0yUR2igQP?r4L6N$Ie zr%$}q0y~L>63{T!6iYRJVcC3|9x09R-t;#48uk z=W*jyO1u;$URoq^HcFgbB#{P^D)y5_66d1CxsIblb8wIk&-im4barti1DB(mmutmD z;>qIT(gmD=eVVwW&IPYaTpBuBFD@-sLc~v_xIVGAcHKG%Oz}YCQcEL=OLvB1Up$pm zw^tJwk1L*Q0;5|F+`MNw1&B?H@n0)@*PaI+kR$hvjzAQ@GTiHWCCkalku_`NfE;nB zpYn=@$v|$n=bqbSl(@qQO6$b7;!H7TOC_RqV$-^&Fh768b63x}bH_vJ9S@Aky&Klu zF8i~qOfl}da>{a^lJ?CnYMz-c=9*I9>=q@Zow&j z)6PkEI>7ZZU$P4I3DcHmP1%9%A=;WK*)njE*>+^nnJx!1Uo6>T7oy5R^fSf-5^C9E z@gtsX1*mnZctDjPZ|(DMHAkl=>|BnRnb>D%17B{DeK~X74Xl#R<%3m{yO~F^WfI)qk~dz+d8g5+MIQrz;jE)-jLj!!(FQSYMN5kr%4?KWvS=~(#@-y z95mUdmQGvQ{d7sSfG|7#8;lxFiPg)Hd$+>U$2Tblo}kZI7?e+xi#BSSwmpk5*V&4j zse^{983(0R0_YB4B0lm(j6G^WSgO4leZFC63;o-Os|F?Rr!mBRl_@?T@}4^-b#5w1 zU!d#stF`GbgOUb>mqV**tP~x&W1A^FON_YXQcgOq;<~QqGoG8ZeINI7steH-eZElh zh?pf9g*OqqUbVw_%U;&joKnY&2W@-5%v;4$*|WvYqAo8sqEtCgPFy(xV>yHx8mbv1 zVPh}H+=oUaM4M4Rn|I-$%rr>#Q414@GmQF-Gl&~8>ThYkgSdMQe8j?! z>VL*k`0GLhQljB;1Ak2~880nNt&15ZyEL)PFq)bZt*x!8p5|p;&CN@V-c@V+DX4$V z&8t?d=!%Er+MA8F$<@fP?8ci6qd94;=wH2Tb(rPmuABN-+}yXUUySd}h;_Hza!XX_ z>n%d;#yX0HXUd*(Js7NENT|?2A^K7Kuc8|1caw^2u7+Bk7PH6fXfup{;LiJbm%aP&w&|M8-d`g9;MS{-oB^3bg zfB6oz-utC1x_2 zY&BDJE0tQB>PV%c^jQ1eg}U+2Uyeoa*K0?>XliXwE*Cu~+HuVZ^P{#GK(B&+ z3;HYQ9Oyrw_V$n3BxoaOHz*78K*vDOgI)l=0{R2!UC=*3v!J$)kJ@eo4S?2xz7ARs z+5p-Jx(#$Y=nl}Gpu0e$puM1dpr=94fDVEVfewR?fSv^%1$_(jZO}2$dZF~>a=qB$ z%vhx&_WiPxRnnPLhJ@sixaF6#lX?SnJ((+F-wtT)$YA4E`*hwdJGq+hrvx@7K8OtM zTi9VC?B#5S1VtF(>Y~EpcdB(rT2M7wGVm;a5}9^k^S2$hT$qg3sfathY{`j|o82$1 zT+V|$P-WeAW{RHcOxaFQjmy(UTKj2bNrI4BH)mI~kVGY_?abLTMJPSD27x^_O=2VD z7!OR&*fLx8z-ADGfEe{5cc69ZDu9J$RZAN+zAUd$R!nX63R?0DAWTmb3u5$uot2tJ zEy9Zt&-MKQT^#Z{`(p!hCjnk9Y|Qq{r2y=?vV=%mkOTrj4$^Lpq;yD_zQ~}oA_>wt z#1d$+;vnJdcibnPu;xK2OaZ%Ngw2q?V8lEmA|D1~8X9yoHETI}h=2K#3t0qW*tAR0 zx&gi#3&gUWTjWlJq>%=^`e_)X74m>JR56omOPi9uar$3UKR9?!{WZuWnfn^EOYym6 z;zs)HIRp;mK8okS2rvSS03*N%FanGKBftnS0*nA7zz8q`i~u9R2rvSS03*N%FanGK zBftnS0*nA7zz8q`i~u9R2rvSS03*N%FanGKBftnS0*nA7zz8q`i~u9R2rvSS03*N% zFanGKBftnS0*nA7zz8q`i~u9R2rvSS03*N%FanGKBftnS0*nA7zz8q`i~u9R2rvSS z03*N%FanGKBftnS0*nA7zz8q`i~u9R2rvSS03*N%FanGKBftnS0*nA7zz8q`i~u9R X2rvSS03*N%FanGKBftn;2Lk^E%t5Nk literal 0 HcmV?d00001 diff --git a/TRIGGER.CPP b/TRIGGER.CPP new file mode 100644 index 0000000..fa33c3f --- /dev/null +++ b/TRIGGER.CPP @@ -0,0 +1,1486 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\trigger.cpv 2.17 16 Oct 1995 16:51:20 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TRIGGER.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 11/12/94 * + * * + * Last Update : August 27, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Do_All_To_Hunt -- Forces all computer controlled units into hunt mode. * + * TriggerClass::Action_From_Name -- retrieves ActionType for given name * + * TriggerClass::Action_Need_Team -- Determines if this action event requires a team. * + * TriggerClass::As_Pointer -- returns pointer for the given trigger name * + * TriggerClass::As_Target -- Converts trigger to a target value * + * TriggerClass::Event_From_Name -- retrieves EventType for given name * + * TriggerClass::Event_Need_Data -- Determines if this event requires a data value. * + * TriggerClass::Event_Need_House -- Determines if this event requires a house identifier. * + * TriggerClass::Event_Need_Object -- Determines if the specified event requires an object. * + * TriggerClass::Init -- clears triggers for new scenario * + * TriggerClass::Name_From_Action -- retrieves name for ActionType * + * TriggerClass::Name_From_Event -- retrieves name for EventType * + * TriggerClass::Read_INI -- reads triggers from the INI file * + * TriggerClass::Remove -- removes this trigger from the game * + * TriggerClass::Spring -- Trigger processing routine for cell-based triggers * + * TriggerClass::Spring -- Trigger processing routine for house-based triggers * + * TriggerClass::Spring -- Trigger processing routine for object-based triggers * + * TriggerClass::TriggerClass -- constructor * + * TriggerClass::Validate -- validates trigger pointer * + * TriggerClass::Write_INI -- writes triggers to the INI file * + * TriggerClass::operator delete -- 'delete' operator * + * TriggerClass::operator new -- 'new' operator * + * TriggerClass::~TriggerClass -- Destructor for trigger objects. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +static void Do_All_To_Hunt(void); + +#define FIXUP 0 + +/* +********************************** Globals ********************************** +*/ +static const char * EventText[EVENT_COUNT + 1] = { + "None", + "Player Enters", + "Discovered", + "Attacked", + "Destroyed", + "Any", + "House Discov.", + "Units Destr.", + "Bldgs Destr.", + "All Destr.", + "Credits", + "Time", + "# Bldgs Dstr.", + "# Units Dstr.", + "No Factories", + "Civ. Evac.", + "Built It" +}; + + +static const char * ActionText[TriggerClass::ACTION_COUNT + 1] = { + "None", + "Win", + "Lose", + "Production", + "Create Team", + "Dstry Teams", + "All to Hunt", + "Reinforce.", + "DZ at 'Z'", + "Airstrike", + "Nuclear Missile", + "Ion Cannon", + "Dstry Trig 'XXXX'", + "Dstry Trig 'YYYY'", + "Dstry Trig 'ZZZZ'", + "Autocreate", + "Cap=Win/Des=Lose", + "Allow Win" +}; + + +/*********************************************************************************************** + * TriggerClass::Validate -- validates trigger pointer * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int TriggerClass::Validate(void) const +{ + int num; + + num = Triggers.ID(this); + if (num < 0 || num >= TRIGGER_MAX) { + Validate_Error("TRIGGER"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*********************************************************************************************** + * TriggerClass::Event_Need_Object -- Determines if the specified event requires an object. * + * * + * This routine determines if the specified event must be attached to an object. Such * + * events can only exist in a parasitic fashion attached to object(s) in the game. * + * * + * INPUT: event -- The event type to examine. * + * * + * OUTPUT: Does the specified event require attachement to an object? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/27/1995 JLB : Created. * + *=============================================================================================*/ +bool TriggerClass::Event_Need_Object(EventType event) +{ + switch (event) { + case EVENT_PLAYER_ENTERED: + case EVENT_DISCOVERED: + case EVENT_ATTACKED: + case EVENT_DESTROYED: + case EVENT_ANY: + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TriggerClass::Event_Need_House -- Determines if this event requires a house identifier. * + * * + * This routine is used to determine if the specified event requires a house identifier. * + * All trigger events that affect a house will require a house identifier. * + * * + * INPUT: event -- The event type to examine. * + * * + * OUTPUT: Does the specified event type require a house identifier? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/27/1995 JLB : Created. * + *=============================================================================================*/ +bool TriggerClass::Event_Need_House(EventType event) +{ + switch (event) { + case EVENT_PLAYER_ENTERED: + case EVENT_HOUSE_DISCOVERED: + case EVENT_UNITS_DESTROYED: + case EVENT_BUILDINGS_DESTROYED: + case EVENT_ALL_DESTROYED: + case EVENT_CREDITS: + case EVENT_TIME: + case EVENT_NBUILDINGS_DESTROYED: + case EVENT_NUNITS_DESTROYED: + case EVENT_NOFACTORIES: + case EVENT_EVAC_CIVILIAN: + case EVENT_BUILD: + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TriggerClass::Event_Need_Data -- Determines if this event requires a data value. * + * * + * This routine will determine if the specified event requires a data number parameter. * + * This is commonly needed for trigger events. * + * * + * INPUT: event -- The event to examine. * + * * + * OUTPUT: Does the specified event require a data number parameter? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/27/1995 JLB : Created. * + *=============================================================================================*/ +bool TriggerClass::Event_Need_Data(EventType event) +{ + switch (event) { + case EVENT_CREDITS: + case EVENT_TIME: + case EVENT_NBUILDINGS_DESTROYED: + case EVENT_NUNITS_DESTROYED: + case EVENT_BUILD: + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TriggerClass::Action_Need_Team -- Determines if this action event requires a team. * + * * + * This routine will determine if the specified action requires a team name parameter. * + * Typically, this is needed for reinforcements or other trigger events that affect * + * a particular team type. * + * * + * INPUT: action -- The action that is to be examined. * + * * + * OUTPUT: Does the specified action require a team type name? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/27/1995 JLB : Created. * + *=============================================================================================*/ +bool TriggerClass::Action_Need_Team(TriggerClass::ActionType action) +{ + switch (action) { + case ACTION_CREATE_TEAM: + case ACTION_DESTROY_TEAM: + case ACTION_REINFORCEMENTS: + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TriggerClass::TriggerClass -- constructor * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/28/1994 BR : Created. * + *=============================================================================================*/ +TriggerClass::TriggerClass(void) +{ + IsPersistant = VOLATILE; + AttachCount = 0; + Event = EVENT_NONE; + Action = ACTION_NONE; + House = HOUSE_NONE; + DataCopy = Data = 0L; + Name[0] = '\0'; + Team = NULL; +} + + +/*********************************************************************************************** + * TriggerClass::~TriggerClass -- Destructor for trigger objects. * + * * + * This destructor will update the house blockage value if necessary. No other action need * + * be performed on trigger destruction. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/29/1995 JLB : Created. * + *=============================================================================================*/ +TriggerClass::~TriggerClass(void) +{ + if (GameActive && House != HOUSE_NONE && Action == ACTION_ALLOWWIN) { + if (Houses.Ptr(House)->Blockage) Houses.Ptr(House)->Blockage--; + Houses.Ptr(House)->BorrowedTime = TICKS_PER_SECOND*4; + } +} + + +/*********************************************************************************************** + * TriggerClass::Init -- clears triggers for new scenario * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/29/1994 BR : Created. * + *=============================================================================================*/ +void TriggerClass::Init(void) +{ + Triggers.Free_All(); +} + + +/*********************************************************************************************** + * TriggerClass::Spring -- Trigger processing routine * + * * + * Checks whether this trigger should "spring" for the given event & object; * + * If it should, then some really cool undocumented stuff magically happens. * + * * + * INPUT: * + * event EventType: What happened? * + * object Ptr to object containing this trigger: What did it happen to? * + * * + * OUTPUT: * + * 0 = nothing happened; 1 = the trigger was sprung * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/06/1994 BR : Created. * + * 06/25/1995 JLB : Added more trigger events. * + *=============================================================================================*/ +bool TriggerClass::Spring(EventType event, ObjectClass *obj) +{ + Validate(); + /* + ** If this is not the event for this trigger, just return. + */ + if (event != Event && Event != EVENT_ANY) { + return(false); + } + + /* + ** If time-based, decrement the minute counter; return if it's not time yet + */ + if (Event == EVENT_TIME) { + Data--; + if (Data > 0) { + return(false); + } + Data = DataCopy; + } + + /* + ** Semi-persistant trigger: first detach it from the calling object, then + ** see if this is the last object we're attached to; if so, the trigger + ** will spring. + */ + if (IsPersistant == SEMIPERSISTANT) { + + /* + ** Detach ourselves from the object + */ + obj->Trigger = NULL; + + /* + ** Decrement our attachment counter + */ + AttachCount--; + + /* + ** If we're attached to more objects, don't spring; otherwise, spring. + ** And, mark ourselves as volatile so we'll completely remove ourselves + ** from the game after we go off. + */ + if (AttachCount > 0) { + return(false); + } else { + IsPersistant = VOLATILE; + } + } + + /* + ** Otherwise, take an appropriate action. + */ + bool success = true; + TriggerClass * trig = NULL; + switch (Action) { + case ACTION_NUKE: + HouseClass::As_Pointer(HOUSE_BAD)->NukeStrike.Enable(true, false); + HouseClass::As_Pointer(HOUSE_BAD)->NukeStrike.Forced_Charge(PlayerPtr->Class->House == HOUSE_BAD); + break; + + case ACTION_ION: + HouseClass::As_Pointer(HOUSE_GOOD)->IonCannon.Enable(true, false); + HouseClass::As_Pointer(HOUSE_GOOD)->IonCannon.Forced_Charge(PlayerPtr->Class->House == HOUSE_GOOD); + break; + + case ACTION_WINLOSE: + switch (event) { + case EVENT_DESTROYED: + if (!PlayerPtr->IsToWin || PlayerPtr->Blockage > 0) PlayerPtr->Flag_To_Lose(); + success = true; + break; + + case EVENT_PLAYER_ENTERED: + if (!PlayerPtr->IsToLose) PlayerPtr->Flag_To_Win(); + success = true; + break; + + default: + success = false; + break; + } + break; + + case ACTION_DESTROY_XXXX: + trig = As_Pointer("XXXX"); + if (trig) { + trig->Remove(); + } + delete trig; + break; + + case ACTION_DESTROY_YYYY: + trig = As_Pointer("YYYY"); + if (trig) { + trig->Remove(); + } + delete trig; + break; + + case ACTION_DESTROY_ZZZZ: + trig = As_Pointer("ZZZZ"); + if (trig) { + trig->Remove(); + } + delete trig; + break; + + case ACTION_AIRSTRIKE: + PlayerPtr->IsAirstrikePending = true; +// PlayerPtr->Make_Air_Strike_Available(true); + break; + + case ACTION_DZ: + new AnimClass(ANIM_LZ_SMOKE, Cell_Coord(Waypoint[25])); + break; + + case ACTION_NONE: + break; + + case ACTION_WIN: + PlayerPtr->Flag_To_Win(); + break; + + case ACTION_LOSE: + PlayerPtr->Flag_To_Lose(); + break; + + case ACTION_BEGIN_PRODUCTION: + HouseClass::As_Pointer(House)->Begin_Production(); + break; + + case ACTION_AUTOCREATE: + if (obj && obj->Is_Techno()) { + ((TechnoClass *)obj)->House->IsAlerted = true; + } + break; + + case ACTION_CREATE_TEAM: + if (Team) { + ScenarioInit++; + Team->Create_One_Of(); + ScenarioInit--; + } + break; + + case ACTION_DESTROY_TEAM: + if (Team) { + Team->Destroy_All_Of(); + } + break; + + case ACTION_REINFORCEMENTS: + if (Team) { + success = Do_Reinforcements(Team); + } + break; + + case ACTION_ALL_HUNT: + Do_All_To_Hunt(); + break; + + default: + break; + } + + if (!success && Event == EVENT_TIME) Data = 1; + + /* + ** Remove trigger from the game. + */ + if (success && IsPersistant == VOLATILE) { + Remove(); + } + + return(true); +} + + +/*********************************************************************************************** + * TriggerClass::Spring -- Trigger processing routine * + * * + * This version of Spring is for cell-based triggers. * + * * + * INPUT: * + * data elapsed time, or credits, depending on what 'Event' is. * + * * + * OUTPUT: * + * 0 = nothing happened; 1 = the trigger was sprung * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/06/1994 BR : Created. * + *=============================================================================================*/ +bool TriggerClass::Spring(EventType event, CELL cell) +{ + Validate(); + /* + ** If this is not the event for this trigger, just return. + */ + if (event != Event) { + return(false); + } + + /* + ** If time-based, decrement the minute counter; return if it's not time yet + */ + if (Event == EVENT_TIME) { + Data--; + if (Data > 0) { + return(false); + } + Data = DataCopy; + } + + /* + ** Semi-persistant trigger: first detach it from the calling cell, then + ** see if this is the last cell we're attached to; if so, the trigger + ** will spring. + */ + if (IsPersistant == SEMIPERSISTANT) { + + /* + ** Detach ourselves from the cell + */ + Map[cell].IsTrigger = 0; + + /* + ** Decrement our attachment counter + */ + AttachCount--; + + /* + ** If we're attached to more cells, don't spring; otherwise, spring. + ** And, mark ourselves as volatile so we'll completely remove ourselves + ** from the game after we go off. + */ + if (AttachCount > 0) { + return(false); + } else { + IsPersistant = VOLATILE; + } + } + + /* + ** Otherwise, take an appropriate action. + */ + bool success = true; + TriggerClass * trig = NULL; + int index; + switch (Action) { + case ACTION_NUKE: + HouseClass::As_Pointer(HOUSE_BAD)->NukeStrike.Enable(true, false); + HouseClass::As_Pointer(HOUSE_BAD)->NukeStrike.Forced_Charge(PlayerPtr->Class->House == HOUSE_BAD); + break; + + case ACTION_ION: + HouseClass::As_Pointer(HOUSE_GOOD)->IonCannon.Enable(true, false); + HouseClass::As_Pointer(HOUSE_GOOD)->IonCannon.Forced_Charge(PlayerPtr->Class->House == HOUSE_GOOD); + break; + + case ACTION_AUTOCREATE: + for (index = 0; index < Houses.Count(); index++) { + Houses.Ptr(index)->IsAlerted = true; + } + break; + + case ACTION_DESTROY_XXXX: + trig = As_Pointer("XXXX"); + if (trig) { + trig->Remove(); + } + delete trig; + break; + + case ACTION_DESTROY_YYYY: + trig = As_Pointer("YYYY"); + if (trig) { + trig->Remove(); + } + delete trig; + break; + + case ACTION_DESTROY_ZZZZ: + trig = As_Pointer("ZZZZ"); + if (trig) { + trig->Remove(); + } + delete trig; + break; + + case ACTION_AIRSTRIKE: + HouseClass::As_Pointer(House)->AirStrike.Enable(false, true); + if (House == PlayerPtr->Class->House) { + PlayerPtr->AirStrike.Forced_Charge(true); + Map.Add(RTTI_SPECIAL, SPC_AIR_STRIKE); + Map.Column[1].Flag_To_Redraw(); + } +// PlayerPtr->Make_Air_Strike_Available(true); + break; + + case ACTION_DZ: + new AnimClass(ANIM_LZ_SMOKE, Cell_Coord(Waypoint[25])); + break; + + case ACTION_NONE: + break; + + case ACTION_WIN: + PlayerPtr->Flag_To_Win(); + break; + + case ACTION_LOSE: + PlayerPtr->Flag_To_Lose(); + break; + + case ACTION_BEGIN_PRODUCTION: + if (PlayerPtr->Class->House == HOUSE_GOOD) { + HouseClass::As_Pointer(HOUSE_BAD)->Begin_Production(); + } else { + HouseClass::As_Pointer(HOUSE_GOOD)->Begin_Production(); + } + break; + + case ACTION_CREATE_TEAM: + if (Team) { + ScenarioInit++; + Team->Create_One_Of(); + ScenarioInit--; + } + break; + + case ACTION_DESTROY_TEAM: + if (Team) { + Team->Destroy_All_Of(); + } + break; + + case ACTION_REINFORCEMENTS: + if (Team) { + success = Do_Reinforcements(Team); + } + break; + + case ACTION_ALL_HUNT: + Do_All_To_Hunt(); + break; + + default: + break; + } + + if (!success && Event == EVENT_TIME) Data = 1; + + /* + ** Remove trigger from the game. + */ + if (success && IsPersistant == VOLATILE) { + Remove(); + } + + return(true); +} + + +/*********************************************************************************************** + * TriggerClass::Spring -- Trigger processing routine * + * * + * This version of Spring is for house-specific triggers. * + * For a time-based trigger, 'data' will the the current TickCount. * + * For a credit-based trigger, 'data' will be the credits for the HouseClass * + * containing this trigger. * + * * + * INPUT: * + * event the event that happened * + * house house that this event relates to * + * data elapsed time, or credits, depending on what 'Event' is. * + * * + * OUTPUT: * + * 0 = nothing happened; 1 = the trigger was sprung * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/06/1994 BR : Created. * + * 06/25/1995 JLB : Added more trigger events. * + *=============================================================================================*/ +bool TriggerClass::Spring(EventType event, HousesType house, long data) +{ + Validate(); + /* + ** If this is not the event for this trigger, just return. + */ + if (event != Event || house != House) { + return(false); + } + + /* + ** If credits-based, check 'data' + */ + if (Event == EVENT_CREDITS && data < Data) { + return(false); + } + + /* + ** Building event check to ensure that the building number matches. + */ + if (Event == EVENT_BUILD && data != Data) { + return(false); + } + + /* + ** Number of objects destroyed checker. If the data supplied indicates that + ** the correct number of objects have been destroyed, then this trigger + ** will succeed. + */ + if (Event == EVENT_NBUILDINGS_DESTROYED || Event == EVENT_NUNITS_DESTROYED) { + if (data < Data) { + return(false); + } + } + + /* + ** If time-based, decrement the minute counter; return if it's not time yet + */ + if (Event == EVENT_TIME) { + Data--; + if (Data > 0) { + return(false); + } + Data = DataCopy; + } + + /* + ** The trigger has gone off; take appropriate action + */ + bool success = true; + TriggerClass * trig = NULL; + switch (Action) { + + case ACTION_NUKE: + HouseClass::As_Pointer(HOUSE_BAD)->NukeStrike.Enable(true, false); + HouseClass::As_Pointer(HOUSE_BAD)->NukeStrike.Forced_Charge(PlayerPtr->Class->House == HOUSE_BAD); + break; + + case ACTION_ION: + HouseClass::As_Pointer(HOUSE_GOOD)->IonCannon.Enable(true, false); + HouseClass::As_Pointer(HOUSE_GOOD)->IonCannon.Forced_Charge(PlayerPtr->Class->House == HOUSE_GOOD); + break; + + /* + ** This will remove a blockage to the win condition. No action need + ** be performed here since the act of deleting the trigger will + ** remove the blockage. + */ + case ACTION_ALLOWWIN: + break; + + case ACTION_AUTOCREATE: + HouseClass::As_Pointer(House)->IsAlerted = true; + break; + + case ACTION_DESTROY_XXXX: + trig = As_Pointer("XXXX"); + if (trig) { + trig->Remove(); + } + delete trig; + break; + + case ACTION_DESTROY_YYYY: + trig = As_Pointer("YYYY"); + if (trig) { + trig->Remove(); + } + delete trig; + break; + + case ACTION_DESTROY_ZZZZ: + trig = As_Pointer("ZZZZ"); + if (trig) { + trig->Remove(); + } + delete trig; + break; + + case ACTION_AIRSTRIKE: + PlayerPtr->AirStrike.Enable(false, true); + if (House == PlayerPtr->Class->House) { + PlayerPtr->AirStrike.Forced_Charge(true); + Map.Add(RTTI_SPECIAL, SPC_AIR_STRIKE); + Map.Column[1].Flag_To_Redraw(); + } + break; + + case ACTION_NONE: + break; + + case ACTION_DZ: + new AnimClass(ANIM_LZ_SMOKE, Cell_Coord(Waypoint[25])); + break; + + case ACTION_WIN: + PlayerPtr->Flag_To_Win(); + break; + + case ACTION_LOSE: + PlayerPtr->Flag_To_Lose(); + break; + + case ACTION_BEGIN_PRODUCTION: + HouseClass::As_Pointer(House)->Begin_Production(); + break; + + case ACTION_CREATE_TEAM: + if (Team) { + ScenarioInit++; + Team->Create_One_Of(); + ScenarioInit--; + } + break; + + case ACTION_DESTROY_TEAM: + if (Team) { + Team->Destroy_All_Of(); + } + break; + + case ACTION_REINFORCEMENTS: + if (Team) { + success = Do_Reinforcements(Team); + } + break; + + case ACTION_ALL_HUNT: + Do_All_To_Hunt(); + break; + + default: + break; + } + + if (!success && Event == EVENT_TIME) Data = 1; + + /* + ** Remove trigger from the game. + */ + if (success && IsPersistant == VOLATILE) { + Remove(); + } + + return(true); +} + + +/*********************************************************************************************** + * TriggerClass::Remove -- removes this trigger from the game * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = trigger was removed, 0 = it wasn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 12/06/1994 BR : Created. * + *=============================================================================================*/ +bool TriggerClass::Remove(void) +{ + Validate(); + CELL cell; + HousesType h; + int index; + + /* + ** Loop through all cells; remove any reference to this trigger + */ + for (cell = 0; cell < MAP_CELL_TOTAL; cell++) { + if (Map[cell].IsTrigger) { + if (CellTriggers[cell] == this) { + Map[cell].IsTrigger = 0; + CellTriggers[cell] = NULL; + } + } + } + + /* + ** Loop through all objects, removing any reference to this trigger + */ + for (index = 0; index < Infantry.Count(); index++) { + if (Infantry.Ptr(index)->Trigger == this) { + Infantry.Ptr(index)->Trigger = NULL; + } + } + for (index = 0; index < Buildings.Count(); index++) { + if (Buildings.Ptr(index)->Trigger == this) { + Buildings.Ptr(index)->Trigger = NULL; + } + } + for (index = 0; index < Units.Count(); index++) { + if (Units.Ptr(index)->Trigger == this) { + Units.Ptr(index)->Trigger = NULL; + } + } + for (index = 0; index < Terrains.Count(); index++) { + if (Terrains.Ptr(index)->Trigger == this) { + Terrains.Ptr(index)->Trigger = NULL; + } + } + + /* + ** Remove this trigger from any house list it's in. Invoking '-=' with a + ** pointer not in the list has no effect; loop through all houses just to + ** be on the safe side. + */ + for (h = HOUSE_FIRST; h < HOUSE_COUNT; h++) { + HouseTriggers[h].Delete(this); + } + + delete this; + + return(true); +} + + +/*********************************************************************************************** + * TriggerClass::Read_INI -- reads triggers from the INI file * + * * + * INI entry format: * + * Triggername = Eventname, Actionname, Data, Housename, TeamName, IsPersistant * + * * + * This routine reads in the triggers & creates them. Then, other classes can * + * get pointers to the triggers they're linked to. * + * * + * The routine relies on the TeamTypeClasses already being loaded so it can resolve * + * references to teams in this function. * + * * + * Cell Trigger pointers & IsTrigger flags are set in DisplayClass::Read_INI(), * + * and cleared in the Map::Init() routine (which clears all cell objects to 0's). * + * * + * Object's pointers are set in: * + * InfantryClass::Read_INI() * + * BuildingClass::Read_INI() * + * UnitClass::Read_INI() * + * TerrainClass::Read_INI() * + * The object trigger pointers are cleared in the ObjectClass constructor. * + * * + * The House's EMSListOf triggers is set in this routine, and cleared in the * + * HouseClass::Init() routine. * + * * + * INPUT: * + * buffer buffer to hold the INI data * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * This function must be called before any other class's Read_INI. * + * * + * HISTORY: * + * 11/28/1994 BR : Created. * + *=============================================================================================*/ +void TriggerClass::Read_INI(char *buffer) +{ + TriggerClass *trigger; // Working trigger pointer. + char *tbuffer; // Accumulation buffer of trigger IDs. + int len; // Length of data in buffer. + char buf[128]; + + /* + ** Set 'tbuffer' to point just past the INI buffer + */ + len = strlen(buffer) + 2; + tbuffer = buffer + len; + + /* + ** Read all TRIGGER entry names into 'tbuffer' + */ + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-len, buffer); + + /* + ** Loop for all trigger entries. + */ + while (*tbuffer != '\0') { + + /* + ** Create a new trigger. + */ + trigger = new TriggerClass(); + + /* + ** Set its name. + */ + trigger->Set_Name (tbuffer); + + /* + ** Get the trigger entry. + */ + WWGetPrivateProfileString(INI_Name(), tbuffer, NULL, buf, sizeof(buf)-1, buffer); + + /* + ** Fill in the trigger. + */ + trigger->Fill_In(tbuffer,buf); + + /* + ** Add 'trigger' to the House's list. + */ +// if (trigger->House != HOUSE_NONE && trigger->Event != EVENT_PLAYER_ENTERED) { +// if (Event_Need_House(trigger->Event) && !Event_Need_Object(trigger->Event)) { + if (trigger->House != HOUSE_NONE) { + if (trigger->Action == ACTION_ALLOWWIN) HouseClass::As_Pointer(trigger->House)->Blockage++; + HouseTriggers[trigger->House].Add(trigger); + trigger->AttachCount++; + } + + /* + ** Go to next entry. + */ + tbuffer += strlen(tbuffer)+1; + } +} + + +/*********************************************************************************************** + * TriggerClass::Fill_In -- fills in trigger from the given INI entry * + * * + * This routine fills in the given trigger with the given name, and values from * + * the given INI entry. * + * * + * (This routine is used by the scenario editor, to import teams from the MASTER.INI file.) * + * * + * INI entry format: * + * Triggername = Eventname, Actionname, Data, Housename, TeamName, IsPersistant * + * * + * INPUT: * + * name mnemonic for the desired trigger * + * entry INI entry to parse * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/28/1994 BR : Created. * + *=============================================================================================*/ +void TriggerClass::Fill_In(char * name, char *entry) +{ + Validate(); + char *p; + + /* + ** Set its name. + */ + Set_Name(name); + + /* + ** 1st token: Event. + */ + Event = Event_From_Name(strtok(entry, ",")); + + /* + ** 2nd token: Action. + */ + Action = Action_From_Name(strtok(NULL, ",")); + + /* + ** 3rd token: Data. + */ + DataCopy = Data = atol(strtok(NULL, ",")); + + /* + ** 4th token: House. + */ + House = HouseTypeClass::From_Name(strtok(NULL, ",")); + if (House == HOUSE_NONE && Event == EVENT_PLAYER_ENTERED) { + House = PlayerPtr->Class->House; + } + + /* + ** 5th token: Team. + */ + Team = TeamTypeClass::As_Pointer(strtok(NULL, ",")); + + /* + ** 6th token: IsPersistant. This token was added later, so we must check + ** for its existence. + */ + p = strtok(NULL, ","); + if (p) { + IsPersistant = (PersistantType)atoi(p); + } else { + IsPersistant = VOLATILE; + } +} + + +/*********************************************************************************************** + * TriggerClass::Write_INI -- writes triggers to the INI file * + * * + * INI entry format: * + * Triggername = Eventname, Actionname, Data, Housename, TeamName, IsPersistant * + * * + * INPUT: * + * buffer buffer to hold the INI data * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/28/1994 BR : Created. * + *=============================================================================================*/ +void TriggerClass::Write_INI(char *buffer, bool refresh) +{ + int index; + char buf[128]; + TriggerClass *trigger; + char const *hname; + char const *tname; + + /* + ** First, clear out all existing trigger data from the INI file. + */ + if (refresh) { + WWWritePrivateProfileString(INI_Name(), NULL, NULL, buffer); + } + + /* + ** Now write all the trigger data out + */ + for (index = 0; index < Triggers.Count(); index++) { + + /* + ** Get ptr to next active trigger. + */ + trigger = Triggers.Ptr(index); + + /* + ** Generate INI entry. + */ + if (trigger->House==HOUSE_NONE) { + hname = "None"; + } else { + hname = HouseClass::As_Pointer(trigger->House)->Class->IniName; + } + + if (trigger->Team==NULL) { + tname = "None"; + } else { + tname = trigger->Team->IniName; + } + + sprintf(buf,"%s,%s,%ld,%s,%s,%d", + TriggerClass::Name_From_Event(trigger->Event), + TriggerClass::Name_From_Action(trigger->Action), + trigger->Data, + hname, + tname, + trigger->IsPersistant); + WWWritePrivateProfileString(INI_Name(), trigger->Get_Name(), buf, buffer); + } +} + + +/*********************************************************************************************** + * TriggerClass::As_Pointer -- returns pointer for the given trigger name * + * * + * This function is designed for use at initialization time, ie when the * + * trigger mnemonics are read from the INI and the Read_INI functions need * + * to get a pointer to that trigger. * + * * + * INPUT: * + * name mnemonic for the desired trigger * + * * + * OUTPUT: * + * near pointer to that trigger, NULL if not found * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/28/1994 BR : Created. * + *=============================================================================================*/ +TriggerClass * TriggerClass::As_Pointer(char const * name) +{ + if (name == NULL) { + return(NULL); + } + + for (int i = 0; i < Triggers.Count(); i++) { + TriggerClass * trigger = Triggers.Ptr(i); + + if (!stricmp(name, trigger->Name)) { + return(trigger); + } + } + + return(NULL); +} + + +/*********************************************************************************************** + * TriggerClass::operator new -- 'new' operator * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * pointer to new trigger * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/28/1994 BR : Created. * + *=============================================================================================*/ +void * TriggerClass::operator new(size_t ) +{ + void * ptr = Triggers.Allocate(); + if (ptr) { + ((TriggerClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +/*********************************************************************************************** + * TriggerClass::operator delete -- 'delete' operator * + * * + * INPUT: * + * ptr pointer to delete * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/28/1994 BR : Created. * + *=============================================================================================*/ +void TriggerClass::operator delete(void *ptr) +{ + if (ptr) { + ((TriggerClass *)ptr)->IsActive = false; + } + Triggers.Free((TriggerClass *)ptr); +} + + +/*********************************************************************************************** + * TriggerClass::Event_From_Name -- retrieves EventType for given name * + * * + * INPUT: * + * name name to get event for * + * * + * OUTPUT: * + * EventType for given name * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/29/1994 BR : Created. * + *=============================================================================================*/ +EventType TriggerClass::Event_From_Name (char const *name) +{ + int i; + + if (name == NULL) { + return(EVENT_NONE); + } + + for (i = EVENT_NONE; i < EVENT_COUNT; i++) { + if (!stricmp(name,EventText[i + 1])) { + return((EventType)i); + } + } + + return(EVENT_NONE); +} + + +/*********************************************************************************************** + * TriggerClass::Name_From_Event -- retrieves name for EventType * + * * + * INPUT: * + * event EventType to get name for * + * * + * OUTPUT: * + * name for EventType * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/29/1994 BR : Created. * + *=============================================================================================*/ +char const *TriggerClass::Name_From_Event(EventType event) +{ + return(EventText[event + 1]); +} + + +/*********************************************************************************************** + * TriggerClass::Action_From_Name -- retrieves ActionType for given name * + * * + * INPUT: * + * name name to get ActionType for * + * * + * OUTPUT: * + * ActionType for given name * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/29/1994 BR : Created. * + *=============================================================================================*/ +TriggerClass::ActionType TriggerClass::Action_From_Name (char const *name) +{ + int i; + + if (name == NULL) { + return(ACTION_NONE); + } + + for (i = ACTION_NONE; i < ACTION_COUNT; i++) { + if (!stricmp(name,ActionText[i + 1])) { + return((ActionType)i); + } + } + + return(ACTION_NONE); +} + + +/*********************************************************************************************** + * TriggerClass::Name_From_Action -- retrieves name for ActionType * + * * + * INPUT: * + * action ActionType to get name for * + * * + * OUTPUT: * + * name of ActionType * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 11/29/1994 BR : Created. * + *=============================================================================================*/ +char const *TriggerClass::Name_From_Action(ActionType action) +{ + return(ActionText[action + 1]); +} + + +/*********************************************************************************************** + * TriggerClass::As_Target -- Converts trigger to a target value * + * * + * INPUT: none * + * * + * OUTPUT: TARGET value * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +TARGET TriggerClass::As_Target(void) const +{ + Validate(); + return(Build_Target(KIND_TRIGGER, Triggers.ID(this))); +} + + +/*********************************************************************************************** + * Do_All_To_Hunt -- Forces all computer controlled units into hunt mode. * + * * + * This trigger action will cause the computer units and infantry to go into hunt mode. * + * Use it to bring a scenario to a sudden conclusion. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/20/1995 JLB : Created. * + * 08/14/1995 JLB : Removes the member from a team if necessary. * + *=============================================================================================*/ +static void Do_All_To_Hunt(void) +{ + int index; + + for (index = 0; index < Units.Count(); index++) { + UnitClass * unit = Units.Ptr(index); + + if (!unit->House->IsHuman && unit->IsDown && !unit->IsInLimbo) { + if (unit->Team) unit->Team->Remove(unit); + unit->Assign_Mission(MISSION_HUNT); + } + } + + for (index = 0; index < Infantry.Count(); index++) { + InfantryClass * infantry = Infantry.Ptr(index); + + if (!infantry->House->IsHuman && infantry->IsDown && !infantry->IsInLimbo) { + if (infantry->Team) infantry->Team->Remove(infantry); + infantry->Assign_Mission(MISSION_HUNT); + } + } +} diff --git a/TRIGGER.H b/TRIGGER.H new file mode 100644 index 0000000..cb5bc12 --- /dev/null +++ b/TRIGGER.H @@ -0,0 +1,259 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\trigger.h_v 2.15 16 Oct 1995 16:46:32 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TRIGGER.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 11/12/94 * + * * + * Last Update : November 12, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef TRIGGER_H +#define TRIGGER_H + +typedef enum EventType { + EVENT_NONE=-1, + + /* + .......................... Cell-specific events .......................... + */ + EVENT_PLAYER_ENTERED, // player enters this square + EVENT_CELLFIRST = EVENT_PLAYER_ENTERED, + + /* + ......................... Object-specific events ......................... + */ + EVENT_DISCOVERED, // player discovers this object + EVENT_OBJECTFIRST = EVENT_DISCOVERED, + EVENT_ATTACKED, // player attacks this object + EVENT_DESTROYED, // player destroys this object + EVENT_ANY, // Any object event will cause the trigger. + + /* + ......................... House-specific events .......................... + */ + EVENT_HOUSE_DISCOVERED, // any object in this house discovered + EVENT_HOUSEFIRST = EVENT_HOUSE_DISCOVERED, + EVENT_UNITS_DESTROYED, // all house's units destroyed + EVENT_BUILDINGS_DESTROYED, // all house's buildings destroyed + EVENT_ALL_DESTROYED, // all house's units & buildings destroyed + EVENT_CREDITS, // house reaches this many credits + EVENT_TIME, // time elapses for this house + EVENT_NBUILDINGS_DESTROYED, // Number of buildings destroyed. + EVENT_NUNITS_DESTROYED, // Number of units destroyed. + EVENT_NOFACTORIES, // No factories left. + EVENT_EVAC_CIVILIAN, // Civilian has been evacuated. + EVENT_BUILD, // If specified building has been built. + + EVENT_COUNT, + EVENT_FIRST=0 +} EventType; + + +class TriggerClass { + public: + typedef enum ActionType { + ACTION_NONE=-1, + + ACTION_WIN, // player wins! + ACTION_LOSE, // player loses. + ACTION_BEGIN_PRODUCTION, // computer begins production + ACTION_CREATE_TEAM, // computer creates a certain type of team + ACTION_DESTROY_TEAM, + ACTION_ALL_HUNT, // all enemy units go into hunt mode (teams destroyed). + ACTION_REINFORCEMENTS, // player gets reinforcements + // (house that gets them is determined by + // the Reinforcement instance) + ACTION_DZ, // Deploy drop zone smoke. + ACTION_AIRSTRIKE, // Enable airstrike. + ACTION_NUKE, // Enable nuke for computer. + ACTION_ION, // Give ion cannon to computer. + ACTION_DESTROY_XXXX, // Destroy trigger XXXX. + ACTION_DESTROY_YYYY, // Destroy trigger YYYY. + ACTION_DESTROY_ZZZZ, // Destroy trigger ZZZZ. + ACTION_AUTOCREATE, // Computer to autocreat teams. + ACTION_WINLOSE, // Win if captured, lose if destroyed. + ACTION_ALLOWWIN, // Allows winning if triggered. + + ACTION_COUNT, + ACTION_FIRST=0 + } ActionType; + + typedef enum PersistantType { + VOLATILE = 0, + SEMIPERSISTANT = 1, + PERSISTANT = 2, + } PersistantType; + + /* + ** Functions: + ** + ** Constructor/Destructor + */ + TriggerClass(void); + ~TriggerClass(void); + + /* + ** Initialization: clears all triggers in preparation for new scenario + */ + static void Init(void); + + /* + ** Processing routines + */ + bool Spring(EventType event, ObjectClass * object); // object-based + bool Spring(EventType event, CELL cell); // cell-based + bool Spring(EventType event, HousesType house, long data=0); // house-based + bool Remove(void); + + /* + ** File I/O routines + */ + static void Read_INI (char *buffer); + void Fill_In(char *name, char *entry); + static void Write_INI (char *buffer, bool refresh); + static char * INI_Name(void) {return "Triggers";}; + bool Load(FileClass & file); + bool Save(FileClass & file); + void Code_Pointers(void); + void Decode_Pointers(void); + + /* + ** As_Pointer gets a pointer to the trigger object give the mnemonic + */ + static TriggerClass * As_Pointer(char const * name); + + /* + ** Data Access routines + */ +// EventType Get_Event(void) const {return (Event);} +// void Set_Event(EventType event) {Event = event;} +// ActionType Get_Action(void) const {return (Action);} +// void Set_Action(ActionType action) {Action = action;} +// HousesType Get_House(void) const {return(House);} +// void Set_House(HousesType house) {House = house;} +// long Get_Data(void) const {return(Data);} +// void Set_Data(long credits) {Data = credits;} + char const * Get_Name(void) const {return (Name);} + void Set_Name(char const *buf) {strncpy(Name, buf, sizeof(Name)); Name[sizeof(Name)-1] = '\0';} + + /* + ** Utility routines + */ + TARGET As_Target(void) const; + static bool Event_Need_Object(EventType event); + static bool Event_Need_House(EventType event); + static bool Event_Need_Data(EventType event); + static bool Action_Need_Team(ActionType action); + static EventType Event_From_Name(char const *name); + static char const *Name_From_Event(EventType event); + static ActionType Action_From_Name(char const *name); + static char const *Name_From_Action(ActionType action); + + /* + ** Overloaded operators + */ + static void * operator new(size_t size); + static void operator delete(void *ptr); + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + /* + ** This is the pointer to the team that gets created or destroyed when + ** a team-related trigger goes off, or for reinforcements. The house + ** for reinforcements is determined by the house for that team. + */ + TeamTypeClass *Team; + + /* + ** If this trigger object is active, then this flag will be true. Trigger + ** objects that are not active are either not yet created or have been + ** deleted after fulfilling their action. + */ + unsigned IsActive:1; + + /* + ** This flag controls whether the trigger destroys itself after it goes + ** off. + ** 0 = trigger destroys itself immediately after going off, and removes + ** itself from all objects it's attached to + ** 1 = trigger is "Semi-Persistent"; it maintains a count of all objects + ** it's attached to, and only actually "springs" after its been + ** triggered from all the objects; then, it removes itself. + ** 2 = trigger is Fully Persistent; it just won't go away. + */ + PersistantType IsPersistant; + + /* + ** This value tells how many objects or cells this trigger is attached + ** to. The Read_INI routine for all classes that point to a trigger must + ** increment this value! + */ + int AttachCount; + + /* + ** Each trigger must have an event which activates it. This is the event that is + ** used to activate this trigger. + */ + EventType Event; + + /* + ** This is the action to perform when the trigger event occurs. + */ + ActionType Action; + + /* + ** For house-specific events, this is the house for that event. + */ + HousesType House; + + /* + ** For credit-related triggers, this is the number of credits that + ** generate the trigger. For time-based triggers, this is the number + ** of minutes that must elapse. + */ + long Data; + long DataCopy; + + private: + + /* + ** Triggers can be referred to by their name, which can be up to 4 + ** characters. + */ + char Name[5]; + +}; + + +#endif diff --git a/TURRET.CPP b/TURRET.CPP new file mode 100644 index 0000000..a635d2f --- /dev/null +++ b/TURRET.CPP @@ -0,0 +1,504 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\turret.cpv 2.18 16 Oct 1995 16:50:38 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TURRET.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 25, 1994 * + * * + * Last Update : August 13, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * TurretClass::AI -- Handles the reloading of the turret weapon. * + * TurretClass::Can_Fire -- Determines if turret can fire upon target. * + * TurretClass::Debug_Dump -- Debug printing of turret values. * + * TurretClass::Fire_At -- Try to fire upon the target specified. * + * TurretClass::Fire_Coord -- Determines the coorindate that projectile would appear. * + * TurretClass::Fire_Direction -- Determines the directinon of firing. * + * TurretClass::Ok_To_Move -- Queries whether the vehicle can move. * + * TurretClass::TurretClass -- Normal constructor for the turret class. * + * TurretClass::TurretClass -- The default constructor for turret class objects. * + * TurretClass::Unlimbo -- Unlimboes turret object. * + * TurretClass::~TurretClass -- Default destructor for turret class objects. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "turret.h" + + +/*********************************************************************************************** + * TurretClass::~TurretClass -- Default destructor for turret class objects. * + * * + * This is the default destructor for turret class objects. It does nothing. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +TurretClass::~TurretClass(void) +{ +} + + +/*********************************************************************************************** + * TurretClass::TurretClass -- The default constructor for turret class objects. * + * * + * This is the default constructor for turret class objects. It does nothing. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +TurretClass::TurretClass(void) +{ +} + + +/*********************************************************************************************** + * TurretClass::TurretClass -- Normal constructor for the turret class. * + * * + * This is the normal constructor for the turret class. It merely sets the turret up to * + * face north. * + * * + * INPUT: classid -- The type id for this particular unit. * + * * + * house -- The house that this unit will belong to. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 02/02/1995 JLB : Created. * + *=============================================================================================*/ +TurretClass::TurretClass(UnitType classid, HousesType house) : + DriveClass(classid, house) +{ + Reload = 0; +} + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * TurretClass::Debug_Dump -- Debug printing of turret values. * + * * + * This routine is used to display the current values of this turret * + * class instance. It is primarily used in the debug output screen. * + * * + * INPUT: x,y -- Monochrome screen coordinates to display data. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/12/1994 JLB : Created. * + *=============================================================================================*/ +void TurretClass::Debug_Dump(MonoClass *mono) const +{ + mono->Set_Cursor(36, 3);mono->Printf("%02X:%02X", SecondaryFacing.Current(), SecondaryFacing.Desired()); + mono->Set_Cursor(28, 7);mono->Printf("%2d", Arm); + DriveClass::Debug_Dump(mono); +} +#endif + + +/*********************************************************************************************** + * TurretClass::Ok_To_Move -- Queries whether the vehicle can move. * + * * + * This virtual routine is used to determine if the vehicle is allowed * + * to start moving. It is typically called when the vehicle desires * + * to move but needs confirmation from the turret logic before * + * proceeding. This happens when dealing with a vehicle that must have * + * its turret face the same direction as the body before the vehicle * + * may begin movement. * + * * + * INPUT: dir -- The facing the unit wants to travel in. * + * * + * OUTPUT: bool; Can the unit begin forward movement now? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/12/1994 JLB : Created. * + *=============================================================================================*/ +bool TurretClass::Ok_To_Move(DirType dir) +{ + if (Class->IsLockTurret) { + if (IsRotating) { + return(false); + } else { + if (SecondaryFacing.Difference(dir)) { + SecondaryFacing.Set_Desired(dir); + return(false); + } + } + } + return(true); +} + + +/*********************************************************************************************** + * TurretClass::AI -- Handles the reloading of the turret weapon. * + * * + * This processes the reloading of the turret. It does this by decrementing the arming * + * countdown timer and when it reaches zero, the turret may fire. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/21/1994 JLB : Created. * + *=============================================================================================*/ +void TurretClass::AI(void) +{ + DriveClass::AI(); + + /* + ** A unit with a constant rotating radar dish is handled here. + */ + if (Class->IsRadarEquipped) { + SecondaryFacing.Set((DirType)(SecondaryFacing.Current() + 8)); + Mark(MARK_CHANGE); + } else { + + IsRotating = false; + if (Class->IsTurretEquipped) { + if (IsTurretLockedDown) { + SecondaryFacing.Set_Desired(PrimaryFacing.Current()); + } + + if (SecondaryFacing.Is_Rotating()) { + if (SecondaryFacing.Rotation_Adjust(Class->ROT+1)) { + Mark(MARK_CHANGE); + } + + /* + ** If no further rotation is necessary, flag that the rotation + ** has stopped. + */ + IsRotating = SecondaryFacing.Is_Rotating(); + } else { + if (!IsTurretLockedDown && !Target_Legal(TarCom)) { + if (!Target_Legal(NavCom)) { + SecondaryFacing.Set_Desired(PrimaryFacing.Current()); + } else { + SecondaryFacing.Set_Desired(Direction(NavCom)); + } + } + } + } + } +} + + +/*********************************************************************************************** + * TurretClass::Fire_At -- Try to fire upon the target specified. * + * * + * This routine is the auto-fire logic for the turret. It will check * + * to see if firing is technically legal given the specified target. * + * If it is legal to fire, it does so. It is safe to call this routine * + * every game tick. * + * * + * INPUT: target -- The target to fire upon. * + * * + * which -- Which weapon to use when firing. 0=primary, 1=secondary. * + * * + * OUTPUT: bool; Did firing occur? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/26/1994 JLB : Created. * + *=============================================================================================*/ +BulletClass * TurretClass::Fire_At(TARGET target, int which) +{ + BulletClass * bullet = NULL; + WeaponTypeClass const * weapon = (which == 0) ? &Weapons[Class->Primary] : &Weapons[Class->Secondary]; + + if (Can_Fire(target, which) == FIRE_OK) { + bullet = DriveClass::Fire_At(target, which); + + if (bullet) { + + /* + ** Possible reload timer set. + */ + if (*this == UNIT_MSAM && Reload == 0) { + Reload = TICKS_PER_SECOND * 30; + } + } + } + + return(bullet); +} + + +/*********************************************************************************************** + * TurretClass::Can_Fire -- Determines if turret can fire upon target. * + * * + * This routine determines if the turret can fire upon the target * + * specified. * + * * + * INPUT: target -- The target to fire upon. * + * * + * which -- Which weapon to use to determine legality to fire. 0=primary, * + * 1=secondary. * + * * + * OUTPUT: Returns the fire status type that indicates if firing is allowed and if not, why. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/26/1994 JLB : Created. * + * 06/01/1994 JLB : Returns reason why it can't fire. * + *=============================================================================================*/ +FireErrorType TurretClass::Can_Fire(TARGET target, int which) const +{ + DirType dir; // The facing to impart upon the projectile. + int diff; + FireErrorType fire = DriveClass::Can_Fire(target, which); + + if (fire == FIRE_OK) { + WeaponTypeClass const * weapon = (which == 0) ? &Weapons[Class->Primary] : &Weapons[Class->Secondary]; + + /* + ** If this unit cannot fire while moving, then bail. + */ + if ((!Class->IsTurretEquipped || Class->IsLockTurret) && Target_Legal(NavCom)) { + return(FIRE_MOVING); + } + + /* + ** If the turret is rotating and the projectile isn't a homing type, then + ** firing must be delayed until the rotation stops. + */ + if (!IsFiring && IsRotating && !BulletTypeClass::As_Reference(weapon->Fires).IsHoming) { + return(FIRE_ROTATING); + } + + dir = Direction(target); + + /* + ** Determine if the turret facing isn't too far off of facing the target. + */ + if (Class->IsTurretEquipped) { + diff = SecondaryFacing.Difference(dir); + } else { + diff = PrimaryFacing.Difference(dir); + } + diff = ABS(diff); + + /* + ** Special flame tank logic. + */ + if (weapon->Fires == BULLET_FLAME) { + if (Dir_Facing(dir) == Dir_Facing(PrimaryFacing)) { + diff = 0; + } + } + + if (BulletTypeClass::As_Reference(weapon->Fires).IsHoming) { + diff >>= 2; + } + if (diff < 8) { + return(DriveClass::Can_Fire(target, which)); + } + return(FIRE_FACING); + } + return(fire); +} + + +/*********************************************************************************************** + * TurretClass::Fire_Coord -- Determines the coorindate that projectile would appear. * + * * + * Use this routine to determine the exact coordinate that a projectile would appear if it * + * were fired from this unit. For units with turrets, typically, this would be at the end * + * of the barrel. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with coordinate of where a projectile should appear if this unit were * + * to fire one. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/28/1994 JLB : Created. * + *=============================================================================================*/ +COORDINATE TurretClass::Fire_Coord(int which) const +{ + COORDINATE coord = Center_Coord(); + int dist = 0; + int lateral = 0; + DirType dir = PrimaryFacing.Current(); + + if (Class->IsTurretEquipped) { + dir = SecondaryFacing.Current(); + } + + switch (Class->Type) { + case UNIT_GUNBOAT: + coord = Coord_Move(coord, PrimaryFacing.Current(), Pixel2Lepton[Class->TurretOffset]); + dist = 0x0060; + break; + + case UNIT_ARTY: + coord = Coord_Move(coord, DIR_N, 0x0040); + dist = 0x0060; + break; + + case UNIT_FTANK: + dist = 0x30; + if (IsSecondShot) { + coord = Coord_Move(coord, (DirType)(dir+DIR_E), 0x20); + } else { + coord = Coord_Move(coord, (DirType)(dir+DIR_W), 0x20); + } + break; + + case UNIT_HTANK: + coord = Coord_Move(coord, DIR_N, 0x0040); + if (which == 0) { + dist = 0x00C0; + lateral = 0x0028; + } else { + dist = 0x0008; + lateral = 0x0040; + } + if (IsSecondShot) { + coord = Coord_Move(coord, (DirType)(dir+DIR_E), lateral); + } else { + coord = Coord_Move(coord, (DirType)(dir+DIR_W), lateral); + } + break; + + case UNIT_LTANK: + coord = Coord_Move(coord, DIR_N, 0x0020); + dist = 0x00C0; + break; + + case UNIT_MTANK: + coord = Coord_Move(coord, DIR_N, 0x0030); + dist = 0x00C0; + break; + + case UNIT_APC: + case UNIT_JEEP: + case UNIT_BUGGY: + coord = Coord_Move(coord, DIR_N, 0x0030); + dist = 0x0030; + break; + } + + if (dist) { + coord = Coord_Move(coord, dir, dist); + } + + return(coord); +} + + +/*********************************************************************************************** + * TurretClass::Unlimbo -- Unlimboes turret object. * + * * + * This routine is called when a turret equipped unit unlimboes. It sets the turret to * + * face the same direction as the body. * + * * + * INPUT: coord -- The coordinate where the unit is unlimboing. * + * * + * dir -- The desired body and turret facing to use. * + * * + * OUTPUT: Was the unit unlimboed successfully? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +bool TurretClass::Unlimbo(COORDINATE coord, DirType dir) +{ + if (DriveClass::Unlimbo(coord, dir)) { + SecondaryFacing = dir; + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * TurretClass::Fire_Direction -- Determines the directinon of firing. * + * * + * This routine will return with the facing that a projectile will travel if it was * + * fired at this instant. The facing should match the turret facing for those units * + * equipped with a turret. If the unit doesn't have a turret, then it will be the facing * + * of the body. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the default firing direction for a projectile. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +DirType TurretClass::Fire_Direction(void) const +{ + if (Class->IsTurretEquipped) { + if (*this == UNIT_MSAM) { + int diff1 = SecondaryFacing.Difference(DIR_E); + int diff2 = SecondaryFacing.Difference(DIR_W); + diff1 = ABS(diff1); + diff2 = ABS(diff2); + int diff = MIN(diff1, diff2); + int adj = Fixed_To_Cardinal(ABS(SecondaryFacing.Difference(DIR_N)), 64-diff); + if (SecondaryFacing.Difference(DIR_N) < 0) { + return(DirType)(SecondaryFacing - (DirType)adj); + } else { + return(DirType)(SecondaryFacing + (DirType)adj); + } + } + return(SecondaryFacing.Current()); + } + + return(PrimaryFacing.Current()); +} diff --git a/TURRET.H b/TURRET.H new file mode 100644 index 0000000..9af7255 --- /dev/null +++ b/TURRET.H @@ -0,0 +1,85 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\turret.h_v 2.17 16 Oct 1995 16:48:00 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TURRET.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 25, 1994 * + * * + * Last Update : April 25, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef TURRET_H +#define TURRET_H + +#include "drive.h" + +class TurretClass : public DriveClass +{ + public: + + /* + ** This is the timer that controls the reload rate. The MSAM rocket + ** launcher is the primary user of this. + */ + TCountDownTimerClass Reload; + + /* + ** This is the facing of the turret. It can be, and usually is, + ** rotated independently of the body it is attached to. + */ + FacingClass SecondaryFacing; + + #ifdef CHEAT_KEYS + virtual void Debug_Dump(MonoClass *mono) const; + #endif + virtual bool Unlimbo(COORDINATE , DirType facing=DIR_N); + + /* + ** File I/O. + */ + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + protected: + TurretClass(UnitType classid, HousesType house); + TurretClass(void); + virtual ~TurretClass(void); + + BulletClass * Fire_At(TARGET target, int which); + + virtual DirType Fire_Direction(void) const; + virtual FireErrorType Can_Fire(TARGET target, int which) const; + virtual bool Ok_To_Move(DirType facing); + virtual void AI(void); + virtual COORDINATE Fire_Coord(int which) const; +}; + + +#endif diff --git a/TXTLABEL.CPP b/TXTLABEL.CPP new file mode 100644 index 0000000..71a88aa --- /dev/null +++ b/TXTLABEL.CPP @@ -0,0 +1,98 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\txtlabel.cpv 1.9 16 Oct 1995 16:49:44 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TXTLABEL.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : 02/06/95 * + * * + * Last Update : February 6, 1995 [BR] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + + +/*********************************************************************************************** + * TextLabelClass -- Constructor * + * * + * INPUT: * + * txt pointer to text buffer to print from * + * x x-coord for text printing * + * y y-coord for text printing * + * color color to print in * + * style style to print (determines the meaning of x & y) * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/24/1995 BRR : Created. * + *=============================================================================================*/ +TextLabelClass::TextLabelClass(char *txt, int x, int y, int color, + TextPrintType style) : GadgetClass(x,y,1,1,0,0) +{ + Text = txt; + Color = color; + Style = style; + UserData = 0; + PixWidth = -1; + Segments = 0; +} + + +/*********************************************************************************************** + * Draw_Me -- Graphical update routine * + * * + * INPUT: * + * forced true = draw regardless of the current redraw flag state * + * * + * OUTPUT: * + * true = gadget was redrawn, false = wasn't * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 03/24/1995 BRR : Created. * + *=============================================================================================*/ +virtual int TextLabelClass::Draw_Me(int forced) +{ + if (GadgetClass::Draw_Me(forced)) { + if (PixWidth == -1) { + Fancy_Text_Print("%s", X, Y, Color, TBLACK, Style, Text); + } else { + Conquer_Clip_Text_Print(Text, X, Y, Color, TBLACK, Style, PixWidth); + } + return(true); + } + return(false); +} diff --git a/TXTLABEL.H b/TXTLABEL.H new file mode 100644 index 0000000..8b369ac --- /dev/null +++ b/TXTLABEL.H @@ -0,0 +1,72 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\txtlabel.h_v 1.14 16 Oct 1995 16:46:08 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TXTLABEL.H * + * * + * Programmer : Bill Randolph * + * * + * Start Date : 02/06/95 * + * * + * Last Update : February 6, 1995 [BR] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef TXTLABEL_H +#define TXTLABEL_H + +class TextLabelClass : public GadgetClass +{ + public: + /* + ** Constructor/Destructor + */ + TextLabelClass(char *txt, int x, int y, int color, TextPrintType style); + + /* + ** Overloaded draw routine + */ + virtual int Draw_Me(int forced = false); + + /* + ** Sets the displayed text of the label + */ + virtual void Set_Text(char *txt) {Text = txt;}; + + /* + ** General-purpose data field + */ + unsigned long UserData; + TextPrintType Style; + char *Text; + int Color; + int PixWidth; + char Segments; + unsigned short CRC; +}; + +#endif + diff --git a/TXTPRNT.ASM b/TXTPRNT.ASM new file mode 100644 index 0000000..95bf7b1 --- /dev/null +++ b/TXTPRNT.ASM @@ -0,0 +1,514 @@ +; +; Command & Conquer(tm) +; Copyright 2025 Electronic Arts Inc. +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; + +;*************************************************************************** +;** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** +;*************************************************************************** +;* * +;* Project Name : Westwood 32 bit Library * +;* * +;* File Name : TXTPRNT.ASM * +;* * +;* Programmer : Phil W. Gorrow * +;* * +;* Start Date : January 17, 1995 * +;* * +;* Last Update : January 17, 1995 [PWG] * +;* * +;*-------------------------------------------------------------------------* +;* Functions: * +;* Buffer_Print -- Assembly text print to a buffer * +;* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * + +IDEAL +P386 +MODEL USE32 FLAT + +;INCLUDE "mcgaprim.inc" +;INCLUDE ".\gbuffer.inc" + +GLOBAL C Buffer_Print : NEAR + +STRUC GraphicViewPort +GVPOffset DD ? ; offset to virtual viewport +GVPWidth DD ? ; width of virtual viewport +GVPHeight DD ? ; height of virtual viewport +GVPXAdd DD ? ; x mod to get to next line +GVPXPos DD ? ; x pos relative to Graphic Buff +GVPYPos DD ? ; y pos relative to Graphic Buff +GVPPitch DD ? ; modulo of graphic view port +GVPBuffPtr DD ? ; ptr to associated Graphic Buff +ENDS + + +;*=========================================================================* +;* Extern the font pointer which is defined by the font class * +;*=========================================================================* +GLOBAL C FontPtr:DWORD +GLOBAL C FontXSpacing:DWORD +GLOBAL C FontYSpacing:DWORD +GLOBAL C ColorXlat:BYTE + +;*=========================================================================* +;* Define the necessary equates for structures and bounds checking * +;*=========================================================================* +; The header of the font file looks like this: +; UWORD FontLength; 0 +; BYTE FontCompress; 2 +; BYTE FontDataBlocks; 3 +; UWORD InfoBlockOffset; 4 +; UWORD OffsetBlockOffset; 6 +; UWORD WidthBlockOffset; 8 +; UWORD DataBlockOffset; 10 +; UWORD HeightOffset; 12 +; For this reason the following equates have these values: +FONTINFOBLOCK EQU 4 +FONTOFFSETBLOCK EQU 6 +FONTWIDTHBLOCK EQU 8 +FONTDATABLOCK EQU 10 +FONTHEIGHTBLOCK EQU 12 + +FONTINFOMAXHEIGHT EQU 4 +FONTINFOMAXWIDTH EQU 5 + + +LOCALS ?? +;*=========================================================================* +;* Define the color xlate table in the data segment * +;*=========================================================================* + DATASEG + +ColorXlat DB 000H,001H,002H,003H,004H,005H,006H,007H + DB 008H,009H,00AH,00BH,00CH,00DH,00EH,00FH + + DB 001H,000H,000H,000H,000H,000H,000H,000H + DB 000H,000H,000H,000H,000H,000H,000H,000H + + DB 002H,000H,000H,000H,000H,000H,000H,000H + DB 000H,000H,000H,000H,000H,000H,000H,000H + + DB 003H,000H,000H,000H,000H,000H,000H,000H + DB 000H,000H,000H,000H,000H,000H,000H,000H + + DB 004H,000H,000H,000H,000H,000H,000H,000H + DB 000H,000H,000H,000H,000H,000H,000H,000H + + DB 005H,000H,000H,000H,000H,000H,000H,000H + DB 000H,000H,000H,000H,000H,000H,000H,000H + + DB 006H,000H,000H,000H,000H,000H,000H,000H + DB 000H,000H,000H,000H,000H,000H,000H,000H + + DB 007H,000H,000H,000H,000H,000H,000H,000H + DB 000H,000H,000H,000H,000H,000H,000H,000H + + DB 008H,000H,000H,000H,000H,000H,000H,000H + DB 000H,000H,000H,000H,000H,000H,000H,000H + + DB 009H,000H,000H,000H,000H,000H,000H,000H + DB 000H,000H,000H,000H,000H,000H,000H,000H + + DB 00AH,000H,000H,000H,000H,000H,000H,000H + DB 000H,000H,000H,000H,000H,000H,000H,000H + + DB 00BH,000H,000H,000H,000H,000H,000H,000H + DB 000H,000H,000H,000H,000H,000H,000H,000H + + DB 00CH,000H,000H,000H,000H,000H,000H,000H + DB 000H,000H,000H,000H,000H,000H,000H,000H + + DB 00DH,000H,000H,000H,000H,000H,000H,000H + DB 000H,000H,000H,000H,000H,000H,000H,000H + + DB 00EH,000H,000H,000H,000H,000H,000H,000H + DB 000H,000H,000H,000H,000H,000H,000H,000H + + DB 00FH + + CODESEG + + +;*************************************************************************** +;* Buffer_Print -- Assembly text print to graphic buffer routine * +;* * +;* * +;* * +;* INPUT: * +;* * +;* OUTPUT: * +;* * +;* PROTO: * +;* * +;* WARNINGS: * +;* * +;* HISTORY: * +;* 01/17/1995 PWG : Created. * +;*=========================================================================* + PROC Buffer_Print C near + USES ebx,ecx,edx,esi,edi + + ARG this_object:DWORD + ARG string:DWORD + ARG x_pixel:DWORD + ARG y_pixel:DWORD + ARG fcolor:DWORD + ARG bcolor:DWORD + + LOCAL infoblock:DWORD ; pointer to info block + LOCAL offsetblock:DWORD ; pointer to offset block (UWORD *) + LOCAL widthblock:DWORD ; pointer to width block (BYTE *) + LOCAL heightblock:DWORD ; pointer to height block (UWORD *) + + LOCAL curline:DWORD ; pointer to first column of current row. + LOCAL bufferwidth:DWORD ; width of buffer (vpwidth + Xadd) + LOCAL nextdraw:DWORD ; bufferwidth - width of cur character. + LOCAL startdraw:DWORD ; where next character will start being drawn. + + LOCAL char:DWORD ; current character value. + + LOCAL maxheight:BYTE ; max height of characters in font. + LOCAL bottomblank:BYTE ; amount of empty space below current character. + LOCAL charheight:BYTE ; true height of current character. + LOCAL vpwidth:DWORD + LOCAL vpheight:DWORD + LOCAL original_x:DWORD ; Starting X position. + + +;-------------------------------- Where to draw ----------------------------------------------- + ; Set up memory location to start drawing. + mov ebx,[this_object] ; get a pointer to dest + mov eax,[(GraphicViewPort ebx).GVPHeight] ; get height of viewport + mov [vpheight],eax ; save off height of viewport + mov eax,[(GraphicViewPort ebx).GVPWidth] ; get width of viewport + mov [vpwidth],eax ; save it off for later + add eax,[(GraphicViewPort ebx).GVPXAdd] ; add in xadd for bytes_per_line + add eax,[(GraphicViewPort ebx).GVPPitch] ; add in pitch for direct daraw + mov [bufferwidth],eax ; save it off for later use. + + mul [y_pixel] ; multiply rowsize * y_pixel start. + mov edi,[(GraphicViewPort ebx).GVPOffset] ; get start of the viewport + add edi,eax ; add y position to start of vp + mov [curline],edi ; save 0,y address for line feed stuff. + add edi,[x_pixel] ; add to get starting column in starting row. + mov [startdraw],edi ; save it off. + + mov eax,[x_pixel] + mov [original_x],eax + +;-------------------------------- Create block pointers ---------------------------------------- + ; Get the pointer to the font. + ; We could check for NULL but why waste the time. + ; It is up to programmer to make sure it is set. + mov esi,[FontPtr] ; Get the font pointer + or esi,esi + jz ??overflow + + ; Set up some pointers to the different memory blocks. + ; esi (FontPtr) is added to each to get the true address of each block. + ; Many registers are used for P5 optimizations. + ; ebx is used for InfoBlock which is then used in the next section. + movzx eax,[WORD PTR esi+FONTOFFSETBLOCK] ; get offset to offset block + movzx ebx,[WORD PTR esi+FONTINFOBLOCK] ; get offset to info block (must be ebx for height test) + movzx ecx,[WORD PTR esi+FONTWIDTHBLOCK] ; get offset to width block + movzx edx,[WORD PTR esi+FONTHEIGHTBLOCK] ; get offset to height block + + add eax,esi ; add offset of FontPtr to offset block + add ebx,esi ; add offset of FontPtr to info block + add ecx,esi ; add offset of FontPtr to width block + add edx,esi ; add offset of FontPtr to height block + + mov [offsetblock],eax ; save offset to offset block + mov [infoblock],ebx ; save offset to info block + mov [widthblock],ecx ; save offset to width block + mov [heightblock],edx ; save offset to height block + +;------------------------------------------ Test for fit ---------------------------------------------- + ; Test to make sure the height of the max character will fit on this line + ; and and not fall out of the viewport. + ; remember we set ebx to FONTINFOBLOCK above. + movzx eax,[BYTE PTR ebx + FONTINFOMAXHEIGHT]; get the max height in font. + mov [maxheight],al ; save it for later use. + add eax,[y_pixel] ; add current y_value. + cmp eax,[vpheight] ; are we over the edge? + jg ??overflow ; if so, we're outa here. + + mov [y_pixel],eax ; save for next line feed. y value for next line. + + cld ; Make sure we are always forward copying. + +;------------------------ Set palette foreground and background ---------------------------------- + mov eax,[fcolor] ; foreground color + mov [ColorXlat+1],al + mov [ColorXlat+16],al + + mov eax,[bcolor] ; background color + mov [ColorXlat],al + +;------------------------------------------------------------------------------------------------- +;----------------------------------------- Main loop ---------------------------------------------- + ; Now we go into the main loop of reading each character in the string and doing + ; something with it. +??next_char: + ; while (*string++) + xor eax,eax ; zero out since we will just load al. + mov esi,[string] ; get address on next character. + lodsb ; load the character into al. + test eax,0FFH ; test to see if character is a NULL + jz ??done ; character is NULL, get outa here. + + mov edi,[startdraw] ; Load the starting address. + + mov [string],esi ; save index into string. (incremented by lodsb) + + cmp al,10 ; is the character a line feed? + je ??line_feed ; if so, go to special case. + + cmp al,13 ; is the character a line feed? + je ??line_feed ; if so, go to special case. + + mov [char],eax ; save the character off for later reference. + mov ebx,eax ; save it in ebx for later use also. + + add eax,[widthblock] ; figure address of width of character. + mov ecx,[x_pixel] ; get current x_pixel. + movzx edx,[BYTE PTR eax] ; get the width of the character in dl. + add ecx,edx ; add width of char to current x_pixel. + mov eax,[FontXSpacing] + add ecx,eax + add [startdraw],edx ; save start draw for next character. + add [startdraw],eax ; adjust for the font spacing value + + cmp ecx,[vpwidth] ; is the pixel greater then the vp width? + jg ??force_line_feed ; if so, force a line feed. + + mov [x_pixel],ecx ; save value of start of next character. + mov ecx,[bufferwidth] ; get amount to next y same x (one row down) + sub ecx,edx ; take the current width off. + mov [nextdraw],ecx ; save it to add to edi when done with a row. + + ; At this point we got the character. It is now time to find out specifics + ; about drawing the darn thing. + ; ebx = char so they can be used as an indexes. + ; edx = width of character for loop later. + + ; get offset of data for character into esi. + shl ebx,1 ; mult by 2 to later use as a WORD index. + mov esi,[offsetblock] ; get pointer to begining of offset block. + add esi,ebx ; index into offset block. + movzx esi,[WORD PTR esi] ; get true offset into data block from FontPtr. + add esi,[FontPtr] ; Now add FontPtr address to get true address. + + ; Get top and bottom blank sizes and the true height of the character. + add ebx,[heightblock] ; point ebx to element in height array. + mov al,[ebx+1] ; load the data height into dl. + mov cl,[ebx] ; load the first data row into cl. + mov bl,[maxheight] ; get the max height of characters. + mov [charheight],al ; get number of rows with data. + add al,cl ; add the two heights. + sub bl,al ; subract topblank + char height from maxheight. + mov [bottomblank],bl ; save off the number of blank rows on the bottom. + ; leaving this section: + ; dl is still the width of the character. + ; cl is the height of the top blank area. + + mov ebx,OFFSET ColorXlat ; setup ebx for xlat commands. + mov dh,dl ; save the width of the character to restore each loop. + + cmp cl,0 ; is there any blank rows on top? + jz ??draw_char ; if not go and draw the real character. + + xor eax,eax ; get color 0 for background. + xlat [ebx] ; get translated color into al + test al,al ; is it transparent black + jnz ??loop_top ; if not go and write the color + +;----------------------------------------- skip Top blank area ---------------------------------------- + ; this case, the top is transparrent, but we need to increase our dest pointer to correct row. + movzx eax,cl ; get number of rows into eax; + mov ecx,edx ; save width since edx will be destroyed by mul. + mul [bufferwidth] ; multiply that by the width of the buffer. + mov edx,ecx ; restore the width + add edi,eax ; update the pointer. + jmp short ??draw_char ; now go draw the character. + +;----------------------------------------- fill Top blank area ---------------------------------------- + ; edi was set a long time ago. + ; al is the translated color +??loop_top: + stosb ; store the value + dec dh ; decrement our width. + jnz ??loop_top ; if more width, continue on. + + add edi,[nextdraw] ; add amount for entire row. + + dec cl ; decrement or row count + mov dh,dl ; restore width in dh for loop. + jz ??draw_char ; we are done here, go draw the character. + jmp short ??loop_top ; go back to top of loop. + + +;----------------------------------------- Draw character ---------------------------------------------- +??draw_char: + movzx ecx,[charheight] ; get the height of character to count down rows. + test ecx,ecx ; is there any data? (blank would not have any) + jz ??next_char ; if no data, go on to next character. + +??while_data: + lodsb ; get byte value from font data + mov ah,al ; save hinibble + and eax,0F00FH ; mask of low nibble in al hi nibble in ah. + xlat [ebx] ; get new color + + test al,al ; is it a transparent? + jz short ??skiplo ; skip over write + mov [es:edi],al ; write it out +??skiplo: + inc edi + dec dh ; decrement our width. + jz short ??nextrow ; check if done with width of char + + mov al,ah ; restore to get + ; test the time difference between looking up in a large table when shr al,4 is not done as + ; compared to using only a 16 byte table when using the shr al,4 + ;shr al,4 ; shift the hi nibble down to low nibble + xlat [ebx] ; get new color + + test al,al ; is it a transparent? + jz short ??skiphi ; skip over write + mov [es:edi],al ; write it out +??skiphi: + + inc edi + dec dh ; decrement our width. + jnz short ??while_data ; check if done with width of char + +??nextrow: + add edi,[nextdraw] ; go to next line. + dec ecx ; decrement the number of rows to go + mov dh,dl ; restore our column count for row. + jnz ??while_data ; more data for character. + + ; Now it is time to setup for clearing out the bottom of the character. + movzx ecx,[bottomblank] ; get amount on bottom that is blank + cmp ecx,0 ; if there is no blank bottom... + jz ??next_char ; then skip to go to next character + + xor eax,eax ; get color 0 for background. + xlat [ebx] ; get translated color into al + test al,al ; is it transparent black + jz ??next_char ; skip the top black section to let the background through + + mov dh,dl ; restore width in dh for loop. + +;----------------------------------------- Blank below character ----------------------------------- +??loop_bottom: + stosb ; store the value + dec dh ; decrement our width. + jnz ??loop_bottom ; if more width, continue on. + + add edi,[nextdraw] ; add amount for entire row. + + mov dh,dl ; restore width in dh for loop. + dec cl ; decrement or row count + jz ??next_char ; we are done here, go to the next character. + jmp short ??loop_bottom ; go back to top of loop. + +;----------------------------------- end of next_char (main) loop ------------------------------------ +;------------------------------------------------------------------------------------------------- + + +;----------------------------------- special case line feeds ---------------------------------------- + +??force_line_feed: + ; decrement pointer *string so that it will be back at same character + ; when it goes through the loop. + mov eax,[string] ; get string pointer. + dec eax ; decrement it to point to previos char + mov [string],eax ; save it back off. + xor eax,eax + ; Now go into the line feed code..... + +??line_feed: + mov bl,al + mov edx,[y_pixel] ; get the current y pixel value. + movzx ecx,[maxheight] ; get max height for later use. + add ecx,[FontYSpacing] + add edx,ecx ; add max height to y_pixel + cmp edx,[vpheight] ; are we over the edge? + jg ??overflow ; if so, we are outa here. + + mov eax,[bufferwidth] ; get bytes to next line. + mov edi,[curline] ; get start of current line. + mul ecx ; mult max height * next line. + + add edi,eax ; add adjustment to current line. + add [y_pixel],ecx ; increment to our next y position. +;;; DRD + mov [curline],edi ; save it off for next line_feed. + + ; Move the cursor to either the left edge of the screen + ; or the left margin of the print position depending + ; on whether or was specified. = left margin + ; = left edge of screen + xor eax,eax + cmp bl,10 + je ??lfeed + mov eax,[original_x] +??lfeed: + mov [x_pixel],eax ; zero out x_pixel + + add edi,eax +;;; DRD mov [curline],edi ; save it off for next line_feed. + mov [startdraw],edi ; save it off so we know where to draw next char.w + + jmp ??next_char + +??overflow: + mov [startdraw],0 ; Indicate that there is no valid next pos. +??done: + mov eax,[startdraw] ; return this so calling routine + ret ; can figure out where to draw next. + + ENDP Buffer_Print + + + +;*************************************************************************** +;* GET_FONT_PALETTE_PTR -- Returns a pointer to the 256 byte font palette * +;* * +;* INPUT: none * +;* * +;* OUTPUT: none * +;* * +;* PROTO: void *Get_Font_Palette_Ptr(void); * +;* * +;* HISTORY: * +;* 08/18/1995 PWG : Created. * +;*=========================================================================* + + GLOBAL C Get_Font_Palette_Ptr:NEAR + + PROC Get_Font_Palette_Ptr C near + + mov eax, OFFSET ColorXlat + ret + + ENDP Get_Font_Palette_Ptr + + +END diff --git a/TYPE.H b/TYPE.H new file mode 100644 index 0000000..f6237cd --- /dev/null +++ b/TYPE.H @@ -0,0 +1,1970 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\type.h_v 2.20 16 Oct 1995 16:45:42 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : TYPE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 14, 1994 * + * * + * Last Update : April 14, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef TYPE_H +#define TYPE_H + +#include "mission.h" +#include "target.h" + +class MapEditClass; +class HouseClass; + + +/********************************************************************** +** This is the constant data associated with a weapon. Some objects +** can have multiple weapons and this class is used to isolate and +** specify this data in a convenient and selfcontained way. +*/ +class WeaponTypeClass +{ + public: + + /* + ** This is the unit class of the projectile fired. A subset of the unit types + ** represent projectiles. It is one of these classes that is specified here. + ** If this object does not fire anything, then this value will be UNIT_NONE. + */ + BulletType Fires; + + /* + ** This is the damage (explosive load) to be assigned to the projectile that + ** this object fires. + */ + unsigned char Attack; + + /* + ** Objects that fire (which can be buildings as well) will fire at a + ** frequency controlled by this value. This value serves as a count + ** down timer between shots. The smaller the value, the faster the + ** rate of fire. + */ + unsigned char ROF; + + /* + ** When this object fires, the range at which it's projectiles travel is + ** controlled by this value. The value represents the number of cells the + ** projectile will travel. Objects outside of this range will not be fired + ** upon (in normal circumstances). + */ + int Range; + + /* + ** This is the typical sound generated when firing. + */ + VocType Sound; + + /* + ** This is the animation to display at the firing coordinate. + */ + AnimType Anim; +}; + + +/********************************************************************** +** Each of the warhead types has specific characteristics. This structure +** holds these characteristics. +*/ +class WarheadTypeClass +{ + public: + + /* + ** This value control how damage from this warhead type will reduce + ** over distance. The larger the number, the less the damage is reduced + ** the farther the distance from the source of the damage. + */ + int SpreadFactor; + + /* + ** If this warhead type can destroy walls, then this flag will be true. + */ + bool IsWallDestroyer; + + /* + ** If this warhead can destroy wooden walls, then this flag will be true. + */ + bool IsWoodDestroyer; + + /* + ** Does this warhead damage tiberium? + */ + bool IsTiberiumDestroyer; + + /* + ** The warhead damage is reduced depending on the the type of armor the + ** defender has. This table is what gives weapons their "character". + */ + unsigned char Modifier[ARMOR_COUNT]; +}; + + +/********************************************************************** +** Each house has certain unalienable characteristics. This structure +** elaborates these. +*/ +class HouseTypeClass { + public: + + /* + ** This is the house number (enum). This is a unique identification + ** number for the house. + */ + HousesType House; + + /* + ** The INI name of the house is pointed to by this element. This is the + ** identification name used in the scenario INI file. + */ + char const *IniName; + + /* + ** The full name (translated) of the house is identified by this number. + ** The actual text of the name is located in a text file loaded at run + ** time. + */ + int FullName; + + /* + ** This is the filename suffix to use when creating a house specific + ** file name. It is three characters long. + */ + char Suffix[4]; + + /* + ** This is the "lemon percentage" to use when determining if a particular + ** object owned by this house is to be flagged as a "lemon". Objects so + ** flagged have a greater break-down chance. The percentage is expressed + ** as a fixed point number with 0x000 meaning 0% and 0x100 meaning 100%. + */ + unsigned Lemon; + + /* + ** Each house is assigned a unique identification color to be used on the + ** radar map and other color significant areas. + */ + unsigned char Color; + + unsigned char BrightColor; + + /* + ** This points to the default remap table for this house. + */ + unsigned char const * RemapTable; + PlayerColorType RemapColor; + + /* + ** This is a unique ASCII character used when constructing filenames. It + ** serves a similar purpose as the "Suffix" element, but is only one + ** character long. + */ + char Prefix; + + //------------------------------------------------------------------------ + HouseTypeClass(HousesType house, + char const *ini, + int fullname, + char const *ext, + int lemon, + int color, + int bright_color, + PlayerColorType remapcolor, + unsigned char const * remap, + char prefix); + + static HousesType From_Name(char const *name); + static HouseTypeClass const & As_Reference(HousesType house); + static void One_Time(void); + + private: + static HouseTypeClass const * const Pointers[HOUSE_COUNT]; +}; + + +/*************************************************************************** +** This is the abstract type class. It holds information common to all +** objects that might exist. This contains the name of +** the object type. +*/ +class AbstractTypeClass +{ + public: + + /* + ** This is the internal control name of the object. This name does + ** not change regardless of language specified. This is the name + ** used in scenario control files and for other text based unique + ** identification purposes. + */ + char IniName[9]; + + /* + ** The translated (language specific) text name number of this object. + ** This number is used to fetch the object's name from the language + ** text file. Whenever the name of the object needs to be displayed, + ** this is used to determine the text string. + */ + int Name; + + AbstractTypeClass(void) {}; + AbstractTypeClass(int name, char const * ini); + virtual RTTIType What_Am_I(void) const; + + virtual COORDINATE Coord_Fixup(COORDINATE coord) const; + virtual int Full_Name(void) const; + void Set_Name(char const *buf) const + { + strncpy((char *)IniName, buf, sizeof(IniName)); + ((char &)IniName[sizeof(IniName)-1]) = '\0'; + }; + virtual unsigned short Get_Ownable(void) const; +}; + + +/*************************************************************************** +** This the the common base class of game objects. Since these values +** represent the unchanging object TYPES, this data is initialized at game +** start and not changed during play. It is "const" data. +*/ +class ObjectTypeClass : public AbstractTypeClass +{ + public: + + /* + ** Is this object squashable by heavy vehicles? If it is, then the vehicle + ** can travel over this object and destroy it in the process. + */ + unsigned IsCrushable:1; + + /* + ** Does this object type NOT show up on radar scans? If true, then in any + ** radar display, only the underlying ground will be show, not this object. + ** Most terrain falls into this category, but only a few special real units/buildings + ** do. + */ + unsigned IsStealthy:1; + + /* + ** It is legal to "select" some objects in the game. If it is legal to select this + ** object type then this flag will be true. Selected game objects typically display + ** a floating health bar and allows special user I/O control. + */ + unsigned IsSelectable:1; + + /* + ** Can this object be the target of an attack or move command? Typically, only objects + ** that take damage or can be destroyed are allowed to be a target. + */ + unsigned IsLegalTarget:1; + + /* + ** "Insignificant" objects will not be announced when they are destroyed or when they + ** appear. Terrain elements and some lesser vehicles have this characteristic. + */ + unsigned IsInsignificant:1; + + /* + ** Is this object immune to normal combat damage? Rocks and other inert type terrain + ** object are typically of this type. + */ + unsigned IsImmune:1; + + /* + ** If this terrain object is flammable (such as trees are) then this + ** flag will be true. Flammable objects can catch fire if damaged by + ** flame type weapons. + */ + unsigned IsFlammable:1; + + /* + ** "Sentient" objects are ones that have logic AI processing performed on them. All + ** vehicles, buildings, infantry, and aircraft are so flagged. Terrain elements also + ** fall under this category, but only because certain animation effects require this. + */ + unsigned IsSentient:1; + + /* + ** The defense of this object is greatly affected by the type of armor + ** it possesses. This value specifies the type of armor. + */ + ArmorType Armor; + + /* + ** This is the maximum strength of this object type. + */ + unsigned short MaxStrength; + + /* + ** These point to the shape imagery for this object type. Since the shape imagery + ** exists in a separate file, the data is filled in after this object is constructed. + ** The "mutable" keyword allows easy modification to this otherwise const object. + */ + void const * ImageData; + + /* + ** This points to the radar imagery for this object. + */ + void const * RadarIcon; + + //-------------------------------------------------------------------- + ObjectTypeClass( bool is_sentient, + bool is_flammable, + bool is_crushable, + bool is_stealthy, + bool is_selectable, + bool is_legal_target, + bool is_insignificant, + bool is_immune, + int fullname, + char const *name, + ArmorType armor, + unsigned short strength); + + static void One_Time(void); + + virtual int Max_Pips(void) const; + virtual void Dimensions(int &width, int &height) const; + virtual bool Create_And_Place(CELL , HousesType =HOUSE_NONE) const = 0; + virtual int Cost_Of(void) const; + virtual int Time_To_Build(HousesType house) const; + virtual ObjectClass * Create_One_Of(HouseClass *) const = 0; + virtual short const * Occupy_List(bool placement=false) const; + virtual short const * Overlap_List(void) const; + virtual BuildingClass * Who_Can_Build_Me(bool, bool, HousesType) const; + virtual void const * Get_Cameo_Data(void) const; + void const * Get_Image_Data(void) const {return ImageData;}; + void const * Get_Radar_Data(void) const {return RadarIcon;}; + + #ifdef SCENARIO_EDITOR + virtual void Display(int, int, WindowNumberType, HousesType) const {}; + #endif + + static void const * SelectShapes; + static void const * PipShapes; +}; + + +/*************************************************************************** +** This class is the common data for all objects that can be owned, produced, +** or delivered as reinforcements. These are objects that typically contain +** crews and weapons -- the fighting objects of the game. +*/ +class TechnoTypeClass : public ObjectTypeClass +{ + public: + + /* + ** If this object can serve as a good leader for a group selected + ** series of objects, then this flag will be true. Unarmed or + ** ability challenged units do not make good leaders. + */ + unsigned IsLeader:1; + + /* + ** Does this object have the ability to detect the presence of a nearby + ** cloaked object? + */ + unsigned IsScanner:1; + + /* + ** If this object is always given its proper name rather than a generic + ** name, then this flag will be true. Typically, civilians and Dr. Mobius + ** fall under this catagory. + */ + unsigned IsNominal:1; + + /* + ** If the artwork for this object (only for generics) is theater specific, then + ** this flag will be true. Civilian buildings are a good example of this. + */ + unsigned IsTheater:1; + + /* + ** Does this object type contain a rotating turret? Gun emplacements, SAM launchers, + ** and many vehicles contain a turret. If a turret is present, special rendering and + ** combat logic must be performed. + */ + unsigned IsTurretEquipped:1; + + /* + ** Certain units and buildings fire two shots in quick succession. If this is + ** the case, then this flag is true. + */ + unsigned IsTwoShooter:1; + + /* + ** Certain objects can be repaired. For buildings, they repair "in place". For units, + ** they must travel to a repair center to be repaired. If this flag is true, then + ** allow the player or computer AI to repair the object. + */ + unsigned IsRepairable:1; + + /* + ** Is this object possible to be constructed? Certain buildings and units cannot + ** be constructed using normal means. They are either initially placed in the scenario + ** or can only arrive by pre arranged reinforcement scheduling. Civilian buildings and + ** vehicles are typical examples of this type of object. They would set this flag to + ** false. + */ + unsigned IsBuildable:1; + + /* + ** Does this object contain a crew? If it does, then when the object is destroyed, there + ** is a distinct possibility that infantry will "pop out". Only units with crews can + ** become "heros". + */ + unsigned IsCrew:1; + + /* + ** Is this object typically used to transport reinforcements or other cargo? + ** Transport aircraft, helicopters, and hovercraft are typicall examples of + ** this. + */ + unsigned IsTransporter:1; + + /* + ** Most objects have the ability to reveal the terrain around themselves. + ** This sight range (expressed in cell distance) is specified here. If + ** this value is 0, then this unit never reveals terrain. Bullets are + ** typically of this nature. + */ + int SightRange; + + /* + ** These values control the cost to produce, the time to produce, and + ** the scenario when production can first start. + */ + int Cost; + unsigned char Scenario; + + /* + ** Special build prerequisite control values. These are primarily used for + ** multi-player or special events. + */ + unsigned char Level; + long Pre; + + /* + ** The risk and reward values are used to determine targets and paths + ** toward targets. When heading toward a target, a path of least + ** risk will be followed. When picking a target, the object of + ** greatest reward will be selected. The values assigned are + ** arbitrary. + */ + int Risk,Reward; + + /* + ** This value indicates the maximum speed that this object can achieve. + */ + MPHType MaxSpeed; + + /* + ** This is the maximum number of ammo shots this object can hold. If + ** this number is -1, then this indicates unlimited ammo. + */ + int MaxAmmo; + + /* + ** This is a bit field representing the houses that are allowed to + ** own (by normal means) this particular object type. This value is + ** typically used in production contexts. It is possible for a side + ** to take possession of an object type otherwise not normally allowed. + ** This event usually occurs as a result of capture. + */ + unsigned short Ownable; + + /* + ** This is the small icon image that is used to display the object in + ** the sidebar for construction selection purposes. + */ + void const * CameoData; + + /* + ** These are the weapons that this techno object is armed with. + */ + WeaponType Primary; + WeaponType Secondary; + + //-------------------------------------------------------------------- + TechnoTypeClass( + int name, + char const *ininame, + unsigned char level, + long pre, + bool is_leader, + bool is_scanner, + bool is_nominal, + bool is_transporter, + bool is_flammable, + bool is_crushable, + bool is_stealthy, + bool is_selectable, + bool is_legal_target, + bool is_insignificant, + bool is_immune, + bool is_theater, + bool is_twoshooter, + bool is_turret_equipped, + bool is_repairable, + bool is_buildable, + bool is_crew, + int ammo, + unsigned short strength, + MPHType maxspeed, + int sightrange, + int cost, + int scenario, + int risk, + int reward, + int ownable, + WeaponType primary, + WeaponType secondary, + ArmorType armor); + + virtual int Raw_Cost(void) const; + virtual int Max_Passengers(void) const; + virtual int Repair_Cost(void) const; + virtual int Repair_Step(void) const; + virtual void const * Get_Cameo_Data(void) const; + virtual int Cost_Of(void) const; + virtual int Time_To_Build(HousesType house) const; + virtual unsigned short Get_Ownable(void) const; +}; + + +/*************************************************************************** +** Building types need some special information custom to buildings. This +** is a derived class that elaborates these additional data elements. +*/ +class BuildingTypeClass : public TechnoTypeClass { + enum BuildingTypeClassRepairEnums { + //REPAIR_COST=1, // Cost to repair a single "step". + REPAIR_PERCENT=102, // 40% fixed point number. + REPAIR_STEP=5 // Number of damage points recovered per "step". + }; + + public: + + /* + ** This flag controls whether the building is equiped with a dirt + ** bib or not. A building with a bib has a dirt patch automatically + ** attached to the structure when it is placed. + */ + unsigned IsBibbed:1; + + /* + ** If this building is a special wall type, such that it exists as a building + ** for purposes of construction but transforms into an overlay wall object when + ** it is placed on the map, then this flag will be true. + */ + unsigned IsWall:1; + + /* + ** Some buildings are producers. This flag will be true in that case. Producer, + ** or factory, type buildings have special logic performed. + */ + unsigned IsFactory:1; + + /* + ** Buildings can have either simple or complex damage stages. If simple, + ** then the second to the last frame is the half damage stage, and the last + ** frame is the complete damage stage. For non-simple damage, buildings + ** have a complete animation set for damaged as well as undamaged condition. + ** Turrets, oil pumps, and repair facilities are a few examples. + */ + unsigned IsSimpleDamage:1; + + /* + ** Some buildings can be placed directly on raw ground. Such buildings don't require + ** and are not affected by concrete or lack thereof. Typically, concrete itself is + ** considered sturdy. The same goes for walls and similar generic type structures. + ** The more sophisticated buildings are greatly affected by lack of concrete and thus + ** would have this flag set to false. + */ + unsigned IsSturdy:1; + + /* + ** Certain building types can be captures by enemy infantry. For those + ** building types, this flag will be true. Typically, military or hardened + ** structures such as turrets cannot be captured. + */ + unsigned IsCaptureable:1; + + /* + ** If this building really only has cosmetic idle animation, then this flag will be + ** true if this animation should run at a relatively constant rate regardless of game + ** speed setting. + */ + unsigned IsRegulated:1; + + /* + ** This flag specifies the type of object this factory building can "produce". For non + ** factory buildings, this value will be RTTI_NONE. + */ + RTTIType ToBuild; + + /* + ** For building that produce ground units (infantry and vehicles), there is a default + ** exit poit defined. This point is where the object is first placed on the map. + ** Typically, this is located next to a door. The unit will then travel on to a clear + ** terrain area and enter normal game processing. + */ + COORDINATE ExitPoint; + + /* + ** When determine which cell to head toward when exiting a building, use the + ** list elaborated by this variable. There are directions of exit that are + ** more suitable than others. This list is here to inform the system which + ** directions those are. + */ + short const *ExitList; + + /* + ** This is the structure type identifier. It can serve as a unique + ** identification number for building types. + */ + StructType Type; + + /* + ** This is a bitflag that represents which unit types can enter this + ** building. Determine if a unit can enter by taking 1 and shifting it + ** left by the unit type ID. If the corresponding bit is set, then that + ** unit type can enter this building. + */ + unsigned long CanEnter; + + /* + ** This is the starting facing to give this building when it first + ** gets constructed. The facing matches the final stage of the + ** construction animation. + */ + DirType StartFace; + + /* + ** This is the Tiberium storage capacity of the building. The sum of all + ** building's storage capacity is used to determine how much Tiberium can + ** be accumulated. + */ + unsigned Capacity; + + /* + ** Each building type produces and consumes power. These values tell how + ** much. + */ + int Power; + int Drain; + + /* + ** This is the size of the building. This size value is a rough indication + ** of the building's "footprint". + */ + BSizeType Size; + + /********************************************************************** + ** For each stage that a building may be in, its animation is controlled + ** by this structure. It dictates the starting and length of the animation + ** frames needed for the specified state. In addition it specifies how long + ** to delay between changes in animation. With this data it is possible to + ** control the appearance of all normal buildings. Turrets and SAM sites are + ** an exception since their animation is not merely cosmetic. + */ + typedef struct { + int Start; // Starting frame of animation. + int Count; // Number of frames in this animation. + int Rate; // Number of ticks to delay between each frame. + } AnimControlType; + AnimControlType Anims[BSTATE_COUNT]; + + /* + ** This is a mask flag used to determine if all the necessary prerequisite + ** buildings have been built. + */ + // long Prerequisite; + + /*--------------------------------------------------------------------------- + ** This is the building type explicit constructor. + */ + BuildingTypeClass ( + StructType type, + int name, + char const *ininame, + COORDINATE exitpoint, + unsigned char level, + long pre, + bool is_scanner, + bool is_regulated, + bool is_bibbed, + bool is_nominal, + bool is_wall, + bool is_factory, + bool is_capturable, + bool is_flammable, + bool is_simpledamage, + bool is_stealthy, + bool is_selectable, + bool is_legal_target, + bool is_insignificant, + bool is_immune, + bool is_theater, + bool is_turret_equipped, + bool is_twoshooter, + bool is_repairable, + bool is_buildable, + bool is_crew, + bool is_sturdy, + RTTIType tobuild, + DirType sframe, + unsigned short strength, + int sightrange, + int cost, + int scenario, + int risk, + int reward, + int ownable, + WeaponType primary, + WeaponType secondary, + ArmorType armor, + unsigned long canenter, + unsigned capacity, + int power, + int drain, + BSizeType size, + short const *exitlist, + short const *sizelist, + short const *overlap); + virtual RTTIType What_Am_I(void) const {return RTTI_BUILDINGTYPE;}; + operator StructType(void) const {return(Type);}; + + static BuildingTypeClass const & As_Reference(StructType type); + static StructType From_Name(char const *name); + static void Init(TheaterType theater); + static void One_Time(void); + static void Prep_For_Add(void); + + int Width(void) const; + int Height(void) const; + + virtual int Cost_Of(void) const; + virtual int Full_Name(void) const; + virtual COORDINATE Coord_Fixup(COORDINATE coord) const {return coord & 0xFF00FF00L;} + virtual int Max_Pips(void) const; + virtual void Dimensions(int &width, int &height) const; + virtual int Legal_Placement(CELL pos) const; + virtual bool Create_And_Place(CELL cell, HousesType house) const; + virtual ObjectClass * Create_One_Of(HouseClass * house) const; + virtual short const * Occupy_List(bool placement=false) const; + virtual short const * Overlap_List(void) const; + virtual BuildingClass * Who_Can_Build_Me(bool intheory, bool legal, HousesType house) const; + virtual void const * Get_Buildup_Data(void) const {return(BuildupData);}; + + virtual int Raw_Cost(void) const; + virtual int Repair_Cost(void) const; + virtual int Repair_Step(void) const; + bool Bib_And_Offset(SmudgeType & bib, CELL & cell) const; + + #ifdef SCENARIO_EDITOR + virtual void Display(int x, int y, WindowNumberType window, HousesType house) const; + #endif + + + private: + + /* + ** This is a pointer to a list of offsets (from the upper left corner) that + ** are used to indicate the building's "footprint". This footprint is used + ** to determine building placement legality and terrain passibility. + */ + short const *OccupyList; + + /* + ** Buildings can often times overlap a cell but not actually "occupy" it for + ** purposes of movement. This points to a list of offsets that indicate which + ** cells the building has visual overlap but does not occupy. + */ + short const *OverlapList; + + static BuildingTypeClass const * const Pointers[STRUCT_COUNT]; + + /* + ** The construction animation graphic data pointer is + ** pointed to by this element. + */ + void const * BuildupData; + + void Init_Anim(BStateType state, int start, int count, int rate) const; +}; + + +/*************************************************************************** +** The various unit types need specific data that is unique to units as +** opposed to buildings. This derived class elaborates these additional +** data types. +*/ +class UnitTypeClass : public TechnoTypeClass +{ + public: + enum UnitTypeClassRepairEnums { + TIBERIUM_STEP=25, // Credits per step of Tiberium. + STEP_COUNT=28, // Number of steps a harvester can carry. + FULL_LOAD_CREDITS=(TIBERIUM_STEP*STEP_COUNT), + REPAIR_PERCENT=102, // 40% fixed point number. + REPAIR_STEP=4 // Number of damage points recovered per "step". + }; + + /* + ** If this unit can appear out of a crate, then this flag will be true. + */ + unsigned IsCrateGoodie:1; + + /* + ** Does this unit have only 8 facings? Special test units have limited + ** facings. + */ + unsigned IsPieceOfEight:1; + + /* + ** Can this unit squash infantry? If it can then if the player selects + ** an (enemy) infantry unit as the movement target, it will ride over and + ** squish the infantry unit. + */ + unsigned IsCrusher:1; + + /* + ** Does this unit go into harvesting mode when it stops on a tiberium + ** field? Typically, only one unit does this and that is the harvester. + */ + unsigned IsToHarvest:1; + + /* + ** Does this unit's shape data consist of "chunky" facings? This kind of unit + ** art has the unit in only 4 facings (N, W, S, and E) and in each of those + ** directions, the unit's turrets rotates 32 facings (counter clockwise from north). + ** This will result in 32 x 4 = 128 unit shapes in the shape data file. + */ + unsigned IsChunkyShape:1; + + /* + ** Some units are equipped with a rotating radar dish. These units have special + ** animation processing. The rotating radar dish is similar to a turret, but + ** always rotates and does not affect combat. + */ + unsigned IsRadarEquipped:1; + + /* + ** If this unit has a firing animation, this flag is true. Infantry and some special + ** vehicles are the ones with firing animations. + */ + unsigned IsFireAnim:1; + + /* + ** Many vehicles have a turret with restricted motion. These vehicles must move the + ** turret into a locked down position while travelling. Rocket launchers and artillery + ** are good examples of this kind of unit. + */ + unsigned IsLockTurret:1; + + /* + ** Does this unit lay tracks when it travels? Most tracked vehicles and some wheeled + ** vehicles have this ability. + */ + unsigned IsTracked:1; + + /* + ** Is this unit of the humongous size? Harvesters and mobile construction vehicles are + ** of this size. If the vehicle is greater than 24 x 24 but less than 48 x 48, it is + ** considered "Gigundo". + */ + unsigned IsGigundo:1; + + /* + ** Is the unit capable of cloaking? Only Stealth Tank can do so now. + */ + unsigned IsCloakable:1; + + /* + ** Does this unit have a constant animation (like Visceroid?) + */ + unsigned IsAnimating:1; + + /* + ** This value represents the unit class. It can serve as a unique + ** identification number for this unit class. + */ + UnitType Type; + + /* + ** This indicates the speed (locomotion) type for this unit. Through this + ** value the movement capabilities are deduced. + */ + SpeedType Speed; + + /* + ** This is the rotational speed of the unit. This value represents the + ** turret rotation speed. + */ + unsigned char ROT; + + /* + ** This is the distance along the centerline heading in the direction the body + ** is facing used to reach the center point of the turret. This distance is + ** in leptons. + */ + signed char TurretOffset; + + /* + ** This value is used to provide the unit with a default mission order when + ** first created. Usually, this is a resting or idle type of order. + */ + MissionType Mission; + + /* + ** This is the default explosion to use when this vehicle is destroyed. + */ + AnimType Explosion; + + /* + ** The width or height of the largest dimension for this unit. + */ + int MaxSize; + + /* + ** This is the explicit unit class constructor. + */ + UnitTypeClass ( + UnitType type, + int name, + char const *ininame, + AnimType exp, + unsigned char level, + long pre, + bool is_goodie, + bool is_leader, + bool is_eight, + bool is_nominal, + bool is_transporter, + bool is_crushable, + bool is_crusher, + bool is_harvest, + bool is_stealthy, + bool is_selectable, + bool is_legal_target, + bool is_insignificant, + bool is_immune, + bool is_turret_equipped, + bool is_twoshooter, + bool is_repairable, + bool is_buildable, + bool is_crew, + bool is_radar_equipped, + bool is_fire_anim, + bool is_lock_turret, + bool is_tracked, + bool is_gigundo, + bool is_chunky, + bool is_cloakable, + bool is_animating, + int ammo, + unsigned short strength, + int sightrange, + int cost, + int scenario, + int risk, + int reward, + int ownable, + WeaponType primary, + WeaponType secondary, + ArmorType armor, + SpeedType speed, + MPHType maxSpeed, + unsigned rot, + int toffset, + MissionType order); + virtual RTTIType What_Am_I(void) const {return RTTI_UNITTYPE;}; + + static UnitType From_Name(char const *name); + static UnitTypeClass const & As_Reference(UnitType type); + static void Init(TheaterType ); + static void One_Time(void); + static void Prep_For_Add(void); + + virtual void Dimensions(int &width, int &height) const; + virtual bool Create_And_Place(CELL cell, HousesType house) const; + virtual ObjectClass * Create_One_Of(HouseClass * house) const; + virtual short const * Occupy_List(bool placement=false) const; + virtual BuildingClass * Who_Can_Build_Me(bool intheory, bool legal, HousesType house) const; + virtual int Max_Pips(void) const; + + virtual int Repair_Cost(void) const; + virtual int Repair_Step(void) const; + + #ifdef SCENARIO_EDITOR + virtual void Display(int x, int y, WindowNumberType window, HousesType house) const; + #endif + + /* + ** This is a pointer to the wake shape (as needed by the gunboat). + */ + static void const * WakeShapes; + + private: + static UnitTypeClass const * const Pointers[UNIT_COUNT]; +}; + + +/*************************************************************************** +** The various unit types need specific data that is unique to units as +** opposed to buildings. This derived class elaborates these additional +** data types. +*/ +class InfantryTypeClass : public TechnoTypeClass +{ + private: + static InfantryTypeClass const * const Pointers[INFANTRY_COUNT]; + + public: + + /* + ** If this civilian infantry type is female, then this flag + ** will be true. This information is used to get the correct + ** voice response. + */ + unsigned IsFemale:1; + + /* + ** Does this infantry unit have crawling animation? If not, then this + ** means that the "crawling" frames are actually running animation frames. + */ + unsigned IsCrawling:1; + + /* + ** For those infantry types that can capture buildings, this flag + ** will be set to true. Typically, this is minigun soldiers. + */ + unsigned IsCapture:1; + + /* + ** For infantry types that will run away from any damage causing + ** events, this flag will be true. Typically, this is so for all + ** civilians as well as the flame thrower guys. + */ + unsigned IsFraidyCat:1; + + /* + ** This flags whether this infantry is actually a civilian. A + ** civilian uses different voice responses, has less ammunition, + ** and runs from danger more often. + */ + unsigned IsCivilian:1; + + /* + ** This value represents the unit class. It can serve as a unique + ** identification number for this unit class. + */ + InfantryType Type; + + /* + ** This is an array of the various animation frame data for the actions that + ** the infantry may perform. + */ + DoInfoStruct DoControls[DO_COUNT]; + + /* + ** There are certain units with special animation sequences built into the + ** shape file. These values tell how many frames are used for the firing animation. + */ + char FireLaunch; + char ProneLaunch; + + /* + ** This is the explicit unit class constructor. + */ + InfantryTypeClass ( + InfantryType type, + int name, + char const *ininame, + unsigned char level, + long pre, + bool is_female, + bool is_leader, + bool is_crawling, + bool is_civilian, + bool is_nominal, + bool is_fraidycat, + bool is_capture, + bool is_theater, + int ammo, + int *do_table, + int firelaunch, + int pronelaunch, + unsigned short strength, + int sightrange, + int cost, + int scenario, + int risk, + int reward, + int ownable, + WeaponType primary, + WeaponType secondary, + MPHType maxSpeed); + virtual RTTIType What_Am_I(void) const {return RTTI_INFANTRYTYPE;}; + + static InfantryType From_Name(char const *name); + static InfantryTypeClass const & As_Reference(InfantryType type) {return *Pointers[type];}; + static void Init(TheaterType ); + static void One_Time(void); + static void Prep_For_Add(void); + + virtual void Dimensions(int &width, int &height) const {width = 12;height = 16;}; + virtual bool Create_And_Place(CELL cell, HousesType house) const; + virtual ObjectClass * Create_One_Of(HouseClass * house) const; + virtual short const * Occupy_List(bool placement=false) const; + virtual BuildingClass * Who_Can_Build_Me(bool intheory, bool legal, HousesType house) const; + virtual int Full_Name(void) const; + + #ifdef SCENARIO_EDITOR + virtual void Display(int x, int y, WindowNumberType window, HousesType house) const; + #endif +}; + + +/*************************************************************************** +** Bullets and other projectiles need some specific information according +** to their type. +*/ +class BulletTypeClass : public ObjectTypeClass +{ + public: + + /* + ** Does this bullet type fly over walls? + */ + unsigned IsHigh:1; + + /* + ** If this projecile is one that balistically arcs from ground level, up into the air and + ** then back to the ground, where it explodes. Typical uses of this are for grenades and + ** artillery shells. + */ + unsigned IsArcing:1; + + /* + ** If the projectile changes course as it flies in order to home in on the + ** projectile's target, then this flag is true. Missiles are typically ones + ** that do this. + */ + unsigned IsHoming:1; + + /* + ** Certain projectiles do not travel horizontally, but rather, vertically -- they drop + ** from a height. Bombs fall into this category and will have this value set to + ** true. Dropping projectiles do not calculate collision with terrain (such as walls). + */ + unsigned IsDropping:1; + + /* + ** Is this projectile invisible? Some bullets and weapon effects are not directly + ** rendered. Small caliber bullets and flame thrower flames are treated like + ** normal projectiles for damage purposes, but are displayed using custom + ** rules. + */ + unsigned IsInvisible:1; + + /* + ** Does this bullet explode when near the target? Some bullets only explode if + ** it actually hits the target. Some explode even if nearby. + */ + unsigned IsProximityArmed:1; + + /* + ** Does this projectile spew puffs of smoke out its tail while it + ** travels? Missiles are prime examples of this projectile type. + */ + unsigned IsFlameEquipped:1; + + /* + ** Should fuel consumption be tracked for this projectile? Rockets are the primary + ** projectile with this characteristic, but even for bullets it should be checked so that + ** bullets don't travel too far. + */ + unsigned IsFueled:1; + + /* + ** Is this projectile without different facing visuals? Most plain bullets do not change + ** visual imagery if their facing changes. Rockets, on the other hand, are equipped with + ** the full 32 facing imagery. + */ + unsigned IsFaceless:1; + + /* + ** If this is a typically inaccurate projectile, then this flag will be true. Artillery + ** is a prime example of this type. + */ + unsigned IsInaccurate:1; + + /* + ** If the bullet contains translucent pixels, then this flag will be true. These + ** translucent pixels really are "shadow" pixels in the same style as the shadow + ** cast by regular ground units. + */ + unsigned IsTranslucent:1; + + /* + ** If this bullet can be fired on aircraft, then this flag will be true. + */ + unsigned IsAntiAircraft:1; + + /* + ** This element is a unique identification number for the bullet + ** type. + */ + BulletType Type; + + /* + ** Maximum speed for this bullet type. + */ + MPHType MaxSpeed; + + /* + ** This projectile has a certain style of warhead. This value specifies + ** what that warhead type is. + */ + WarheadType Warhead; + + /* + ** This is the "explosion" effect to use when this projectile impacts + */ + AnimType Explosion; + + /* + ** This is the rotation speed of the bullet. It only has practical value + ** for those projectiles that performing homing action during flight -- such + ** as with rockets. + */ + unsigned char ROT; + + /* + ** Some projectiles have a built in arming distance that must elapse before the + ** projectile may explode. If this value is non-zero, then this override is + ** applied. + */ + int Arming; + + /* + ** Some projectiles have a built in override for distance travelled before it + ** automatically explodes. This value, if non-zero, specifies this distance. + */ + int Range; + + //--------------------------------------------------------------------- + BulletTypeClass( + BulletType type, + char const *ininame, + bool is_high, + bool is_homing, + bool is_arcing, + bool is_dropping, + bool is_invisible, + bool is_proximity_armed, + bool is_flame_equipped, + bool is_fueled, + bool is_faceless, + bool is_inaccurate, + bool is_translucent, + bool is_antiair, + int arming, + int range, + MPHType maxspeed, + unsigned rot, + WarheadType warhead, + AnimType explosion); + + virtual RTTIType What_Am_I(void) const {return RTTI_BULLETTYPE;}; + + static BulletTypeClass const & As_Reference(BulletType type) {return *Pointers[type];}; + static void Init(TheaterType ) {}; + static void One_Time(void); + + virtual bool Create_And_Place(CELL , HousesType =HOUSE_NONE) const {return false;}; + virtual ObjectClass * Create_One_Of(HouseClass *) const {return 0;}; + + private: + static BulletTypeClass const * const Pointers[BULLET_COUNT]; +}; + + +/**************************************************************************** +** These are the different TYPES of terrain objects. Every terrain object must +** be one of these types. +*/ +class TerrainTypeClass : public ObjectTypeClass +{ + public: + /* + ** Which terrain object does this class type represent. + */ + TerrainType Type; + + /* + ** Does this terrain element consist of a normal frame followed by a + ** series of crumble frames? Trees fall under this case. + */ + unsigned IsDestroyable:1; + + /* + ** Does this object have the capability to transform after a period + ** of time (such as a blossom tree? + */ + unsigned IsTransformable:1; + + /* + ** Does this terrain object spawn the growth of Tiberium? Blossom trees are + ** a good example of this. + */ + unsigned IsTiberiumSpawn:1; + + /* + ** This is the fully translated name for the terrain element. + */ + short FullName; + + /* + ** This is the coordinate offset (from upper left) of where the center base + ** position of the terrain object lies. For trees, this would be the base of + ** the trunk. This is used for sorting purposes. + */ + COORDINATE CenterBase; + + /* + ** This is the bitfield control that tells which theater this terrain object is + ** valid for. If the bit (1 << TheaterType) is true, then this terrain object + ** is allowed. + */ + unsigned char Theater; + + //---------------------------------------------------------------- + TerrainTypeClass( + TerrainType terrain, + int theater, + COORDINATE centerbase, + bool is_spawn, + bool is_destroyable, + bool is_transformable, + bool is_flammable, + bool is_crushable, + bool is_selectable, + bool is_legal_target, + bool is_insignificant, + bool is_immune, + char const *ininame, + int fullname, + unsigned short strength, + ArmorType armor, + short const *occupy, + short const *overlap); + virtual RTTIType What_Am_I(void) const {return RTTI_TERRAINTYPE;}; + + static TerrainType From_Name(char const*name); + static TerrainTypeClass const & As_Reference(TerrainType type) {return *Pointers[type];}; + static void Init(TheaterType theater = THEATER_TEMPERATE); + static void One_Time(void) {}; + static void Prep_For_Add(void); + + virtual COORDINATE Coord_Fixup(COORDINATE coord) const {return coord & 0xFF00FF00L;} + virtual bool Create_And_Place(CELL cell, HousesType house) const; + virtual ObjectClass * Create_One_Of(HouseClass *) const; + virtual short const * Occupy_List(bool placement=false) const; + virtual short const * Overlap_List(void) const; + + #ifdef SCENARIO_EDITOR + virtual void Display(int x, int y, WindowNumberType window, HousesType house=HOUSE_NONE) const; + #endif + + private: + short const *Occupy; + short const *Overlap; + + static TerrainTypeClass const * const Pointers[TERRAIN_COUNT]; +}; + + +/**************************************************************************** +** The tile type objects are controlled by this class. It specifies the form +** of the tile set for the specified object as well as other related datum. +** It is derived from the ObjectTypeClass solely for the purpose of scenario +** editing and creation. +*/ +class TemplateTypeClass: public ObjectTypeClass +{ + public: + /* + ** What template is this. + */ + TemplateType Type; + + /* + ** A bitfield container that indicates which theaters this template is allowed + ** in. A bit set in the (1<. +*/ + +/* $Header: F:\projects\c&c\vcs\code\udata.cpv 2.17 16 Oct 1995 16:50:42 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : UDATA.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : June 26, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * UnitTypeClass::As_Reference -- Fetches a reference to the unit type class specified. * + * UnitTypeClass::Create_And_Place -- Creates and places a unit object onto the map. * + * UnitTypeClass::Create_One_Of -- Creates a unit in limbo. * + * UnitTypeClass::Dimensions -- Determines the unit's pixel dimensions. * + * UnitTypeClass::Display -- Displays a generic unit shape. * + * UnitTypeClass::From_Name -- Fetch class pointer from specified name. * + * UnitTypeClass::Max_Pips -- Fetches the maximum pips allowed for this unit. * + * UnitTypeClass::Occupy_List -- Returns with unit occupation list. * + * UnitTypeClass::One_Time -- Performs one time processing for unit type class objects. * + * UnitTypeClass::Prep_For_Add -- Prepares scenario editor to add unit. * + * UnitTypeClass::UnitTypeClass -- Constructor for unit types. * + * UnitTypeClass::Who_Can_Build_Me -- Determines which factory can build this unit type. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +void const * UnitTypeClass::WakeShapes = 0; + +// Visceroid +static UnitTypeClass const UnitVisceroid( + UNIT_VICE, + TXT_VISCEROID, // NAME: Text name of this unit type. + "VICE", // NAME: Text name of this unit type. + ANIM_NAPALM2, // EXPLOSION: Type of explosion when destroyed. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + false, // Only has eight facings? + true, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + true, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + true, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + true, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired in a repair facility? + false, // Can the player construct or order this unit? + false, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + false, // Does it lay tracks while moving? + false, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + true, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 150, // STRENGTH: Strength (in damage points). + 4, // SIGHTRANGE: Range of sighting. + 800, // COST: Cost to build (Credits). + 1, // SCENARIO: Starting availability scenario. + 80,20, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_GOOD| + HOUSEF_BAD| + HOUSEF_NEUTRAL| + HOUSEF_JP, // OWNABLE: Ownable by house (bit field). + WEAPON_CHEMSPRAY,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_MEDIUM, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Flame tank +static UnitTypeClass const UnitFTank( + UNIT_FTANK, + TXT_FTANK, // NAME: Text name of this unit type. + "FTNK", // NAME: Text name of this unit type. + ANIM_NAPALM3, // EXPLOSION: Type of explosion when destroyed. + 4, // Build level. + STRUCTF_RADAR, // Building prerequisite. + true, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + false, // Only has eight facings? + false, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + true, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Is it equipped with a combat turret? + true, // Fires multiple shots in quick succession? + true, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + true, // Does it lay tracks while moving? + false, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 300, // STRENGTH: Strength (in damage points). + 4, // SIGHTRANGE: Range of sighting. + 800, // COST: Cost to build (Credits). + 9, // SCENARIO: Starting availability scenario. + 80,66, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_FLAME_TONGUE,WEAPON_NONE, + ARMOR_STEEL, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_MEDIUM, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Stealth tank +static UnitTypeClass const UnitSTank( + UNIT_STANK, + TXT_STANK, // NAME: Text name of this unit type. + "STNK", // NAME: Text name of this unit type. + ANIM_FRAG2, // EXPLOSION: Type of explosion when destroyed. + 5, // Build level. + STRUCTF_RADAR, // Building prerequisite. + true, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + false, // Only has eight facings? + false, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + true, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + true, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Is it equipped with a combat turret? + true, // Fires multiple shots in quick succession? + true, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + true, // Does it lay tracks while moving? + false, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + true, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 110, // STRENGTH: Strength (in damage points). + 4, // SIGHTRANGE: Range of sighting. + 900, // COST: Cost to build (Credits). + 12, // SCENARIO: Starting availability scenario. + 80,81, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_DRAGON,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_MEDIUM_FAST, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Light tank +static UnitTypeClass const UnitLTank( + UNIT_LTANK, + TXT_LTANK, // NAME: Text name of this unit type. + "LTNK", // NAME: Text name of this unit type. + ANIM_FRAG1, // EXPLOSION: Type of explosion when destroyed. + 3, // Build level. + STRUCTF_NONE, // Building prerequisite. + true, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + false, // Only has eight facings? + false, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + true, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + true, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + true, // Does it lay tracks while moving? + false, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 300, // STRENGTH: Strength (in damage points). + 3, // SIGHTRANGE: Range of sighting. + 600, // COST: Cost to build (Credits). + 5, // SCENARIO: Starting availability scenario. + 80,56, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_75MM,WEAPON_NONE, + ARMOR_STEEL, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_MEDIUM, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Medium tank +static UnitTypeClass const UnitMTank( + UNIT_MTANK, + TXT_MTANK, // NAME: Text name of this unit type. + "MTNK", // NAME: Text name of this unit type. + ANIM_FRAG2, // EXPLOSION: Type of explosion when destroyed. + 3, // Build level. + STRUCTF_NONE, // Building prerequisite. + true, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + false, // Only has eight facings? + false, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + true, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + true, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + true, // Does it lay tracks while moving? + true, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 400, // STRENGTH: Strength (in damage points). + 3, // SIGHTRANGE: Range of sighting. + 800, // COST: Cost to build (Credits). + 7, // SCENARIO: Starting availability scenario. + 80,62, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_105MM,WEAPON_NONE, + ARMOR_STEEL, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_MEDIUM, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Mastadon tank +static UnitTypeClass const UnitHTank( + UNIT_HTANK, + TXT_HTANK, // NAME: Text name of this unit type. + "HTNK", // NAME: Text name of this unit type. + ANIM_ART_EXP1, // EXPLOSION: Type of explosion when destroyed. + 5, // Build level. + STRUCTF_REPAIR, // Building prerequisite. + true, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + false, // Only has eight facings? + false, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + true, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + true, // Is it equipped with a combat turret? + true, // Fires multiple shots in quick succession? + true, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + true, // Does it lay tracks while moving? + true, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 600, // STRENGTH: Strength (in damage points). + 4, // SIGHTRANGE: Range of sighting. + 1500, // COST: Cost to build (Credits). + 13, // SCENARIO: Starting availability scenario. + 80,80, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_120MM,WEAPON_MAMMOTH_TUSK, + ARMOR_STEEL, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_MEDIUM_SLOW, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Mobile HQ +static UnitTypeClass const UnitMHQ( + UNIT_MHQ, + TXT_MHQ, // NAME: Text name of this unit type. + "MHQ", // NAME: Text name of this unit type. + ANIM_FRAG2, // EXPLOSION: Type of explosion when destroyed. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Can this be a goodie surprise from a crate? + false, // Is a leader type? + false, // Only has eight facings? + false, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + true, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + true, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + true, // Does it lay tracks while moving? + false, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 110, // STRENGTH: Strength (in damage points). + 5, // SIGHTRANGE: Range of sighting. + 600, // COST: Cost to build (Credits). + 99, // SCENARIO: Starting availability scenario. + 80,100, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_MEDIUM, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Landing craft +static UnitTypeClass const UnitHover( + UNIT_HOVER, + TXT_HOVER, // NAME: Text name of this unit type. + "LST", // NAME: Text name of this unit type. + ANIM_FBALL1, // EXPLOSION: Type of explosion when destroyed. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Can this be a goodie surprise from a crate? + false, // Is a leader type? + false, // Only has eight facings? + true, // Always use the given name for the vehicle? + true, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + false, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? +// true, // Is selectable by player? + false, // Is selectable by player? + false, // Can it be a target for attack or move? + true, // Is it insignificant (won't be announced)? + true, // Is it immune to normal combat damage? + false, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired in a repair facility? + false, // Can the player construct or order this unit? + false, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + false, // Does it lay tracks while moving? + true, // Is this a gigundo-rotund-enormous unit? + true, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 400, // STRENGTH: Strength (in damage points). + 3, // SIGHTRANGE: Range of sighting. + 300, // COST: Cost to build (Credits). + 99, // SCENARIO: Starting availability scenario. + 80,40, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + SPEED_HOVER, // MOVE: Locomotion type. + MPH_MEDIUM_FAST, // SPEED: Miles per hour. + 127, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Mobile sam launcher +static UnitTypeClass const UnitSAM( + UNIT_MSAM, + TXT_MSAM, // NAME: Text name of this unit type. + "MLRS", // NAME: Text name of this unit type. + ANIM_FRAG2, // EXPLOSION: Type of explosion when destroyed. + 7, // Build level. + STRUCTF_ATOWER, // Building prerequisite. + true, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + false, // Only has eight facings? + false, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + false, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + true, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + true, // Must the turret be in a locked down position while moving? + true, // Does it lay tracks while moving? + true, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + 2, // AMMO: Number of shots it has (default). + 120, // STRENGTH: Strength (in damage points). + 4, // SIGHTRANGE: Range of sighting. + 750, // COST: Cost to build (Credits). + 98, // SCENARIO: Starting availability scenario. + 80,30, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| +// HOUSEF_GOOD| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_HONEST_JOHN,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_MEDIUM, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Artillery +static UnitTypeClass const UnitArty( + UNIT_ARTY, + TXT_ARTY, // NAME: Text name of this unit type. + "ARTY", // NAME: Text name of this unit type. + ANIM_ART_EXP1, // EXPLOSION: Type of explosion when destroyed. + 6, // Build level. + STRUCTF_NONE, // Building prerequisite. + true, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + false, // Only has eight facings? + false, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + false, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + true, // Does it lay tracks while moving? + false, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 75, // STRENGTH: Strength (in damage points). + 4, // SIGHTRANGE: Range of sighting. + 450, // COST: Cost to build (Credits). + 9, // SCENARIO: Starting availability scenario. + 80,73, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_155MM,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_MEDIUM_SLOW, // SPEED: Miles per hour. + 2, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Harvester +static UnitTypeClass const UnitHarvester( + UNIT_HARVESTER, + TXT_HARVESTER, // NAME: Text name of this unit type. + "HARV", // NAME: Text name of this unit type. + ANIM_FBALL1, // EXPLOSION: Type of explosion when destroyed. + 2, // Build level. + STRUCTF_REFINERY, // Building prerequisite. + true, // Can this be a goodie surprise from a crate? + false, // Is a leader type? + false, // Only has eight facings? + true, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + true, // Can this unit squash infantry? + true, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + true, // Does it lay tracks while moving? + true, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 600, // STRENGTH: Strength (in damage points). +// 300, // STRENGTH: Strength (in damage points). + 2, // SIGHTRANGE: Range of sighting. + 1400, // COST: Cost to build (Credits). + 7, // SCENARIO: Starting availability scenario. + 80,85, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + SPEED_WHEEL, // MOVE: Locomotion type. + MPH_MEDIUM_SLOW, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HARVEST // ORDERS: Default order to give new unit. +); + +// Mobile construction vehicle +static UnitTypeClass const UnitMCV( + UNIT_MCV, + TXT_MCV, // NAME: Text name of this unit type. + "MCV", // NAME: Text name of this unit type. + ANIM_FBALL1, // EXPLOSION: Type of explosion when destroyed. + 7, // Build level. + STRUCTF_EYE, // Building prerequisite. + true, // Can this be a goodie surprise from a crate? + false, // Is a leader type? + false, // Only has eight facings? + false, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + true, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + true, // Does it lay tracks while moving? + true, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 600, // STRENGTH: Strength (in damage points). + 2, // SIGHTRANGE: Range of sighting. + 5000, // COST: Cost to build (Credits). + 15, // SCENARIO: Starting availability scenario. + 80,86, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_NONE,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + SPEED_WHEEL, // MOVE: Locomotion type. + MPH_MEDIUM_SLOW, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Jeep (hummer) +static UnitTypeClass const UnitJeep( + UNIT_JEEP, + TXT_JEEP, // NAME: Text name of this unit type. + "JEEP", // NAME: Text name of this unit type. + ANIM_FRAG1, // EXPLOSION: Type of explosion when destroyed. + 2, // Build level. + STRUCTF_NONE, // Building prerequisite. + true, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + false, // Only has eight facings? + false, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + false, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + true, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + true, // Does it lay tracks while moving? + false, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 150, // STRENGTH: Strength (in damage points). + 2, // SIGHTRANGE: Range of sighting. + 400, // COST: Cost to build (Credits). + 5, // SCENARIO: Starting availability scenario. + 80,41, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_M60MG,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + SPEED_WHEEL, // MOVE: Locomotion type. + MPH_MEDIUM_FAST, // SPEED: Miles per hour. + 10, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Buggy +static UnitTypeClass const UnitBuggy( + UNIT_BUGGY, + TXT_DUNE_BUGGY, // NAME: Text name of this unit type. + "BGGY", // NAME: Text name of this unit type. + ANIM_FRAG1, // EXPLOSION: Type of explosion when destroyed. + 4, // Build level. + STRUCTF_NONE, // Building prerequisite. + true, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + false, // Only has eight facings? + false, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + false, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + true, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + false, // Does it lay tracks while moving? + false, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 140, // STRENGTH: Strength (in damage points). + 2, // SIGHTRANGE: Range of sighting. + 300, // COST: Cost to build (Credits). + 5, // SCENARIO: Starting availability scenario. + 80,42, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_M60MG,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + SPEED_WHEEL, // MOVE: Locomotion type. + MPH_MEDIUM_FAST, // SPEED: Miles per hour. + 10, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Attack cycle +static UnitTypeClass const UnitBike( + UNIT_BIKE, + TXT_BIKE, // NAME: Text name of this unit type. + "BIKE", // NAME: Text name of this unit type. + ANIM_FRAG1, // EXPLOSION: Type of explosion when destroyed. + 2, // Build level. + STRUCTF_NONE, // Building prerequisite. + true, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + false, // Only has eight facings? + false, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + true, // Can it be crushed by a heavy vehicle? + false, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + false, // Does it lay tracks while moving? + false, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). +#ifdef ADVANCED + 90, // STRENGTH: Strength (in damage points). +#else + 160, // STRENGTH: Strength (in damage points). +#endif + 2, // SIGHTRANGE: Range of sighting. + 500, // COST: Cost to build (Credits). + 5, // SCENARIO: Starting availability scenario. + 80,45, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_DRAGON,WEAPON_NONE, + ARMOR_WOOD, // ARMOR: Armor type + SPEED_WHEEL, // MOVE: Locomotion type. + MPH_FAST, // SPEED: Miles per hour. + 10, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Rocket launcher +static UnitTypeClass const UnitMLRS( + UNIT_MLRS, + TXT_MLRS, // NAME: Text name of this unit type. + "MSAM", // NAME: Text name of this unit type. + ANIM_ART_EXP1, // EXPLOSION: Type of explosion when destroyed. + 7, // Build level. + STRUCTF_EYE, // Building prerequisite. + true, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + false, // Only has eight facings? + false, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + false, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + true, // Is it equipped with a combat turret? + true, // Fires multiple shots in quick succession? + true, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + true, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + true, // Must the turret be in a locked down position while moving? + true, // Does it lay tracks while moving? + false, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 100, // STRENGTH: Strength (in damage points). + 4, // SIGHTRANGE: Range of sighting. + 800, // COST: Cost to build (Credits). + 11, // SCENARIO: Starting availability scenario. + 80,72, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_BAD| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_MLRS,WEAPON_NONE, + ARMOR_ALUMINUM, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_MEDIUM, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_GUARD // ORDERS: Default order to give new unit. +); + +// Armored personnel carrier +static UnitTypeClass const UnitAPC( + UNIT_APC, + TXT_APC, // NAME: Text name of this unit type. + "APC", // NAME: Text name of this unit type. + ANIM_FRAG2, // EXPLOSION: Type of explosion when destroyed. + 4, // Build level. + STRUCTF_BARRACKS, // Building prerequisite. + true, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + false, // Only has eight facings? + false, // Always use the given name for the vehicle? + true, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + true, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + true, // Can it be repaired in a repair facility? + true, // Can the player construct or order this unit? + false, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + true, // Does it lay tracks while moving? + false, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 200, // STRENGTH: Strength (in damage points). + 4, // SIGHTRANGE: Range of sighting. + 700, // COST: Cost to build (Credits). + 5, // SCENARIO: Starting availability scenario. + 80,15, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_BAD| + HOUSEF_JP| + HOUSEF_GOOD, // OWNABLE: Ownable by house (bit field). + WEAPON_M60MG,WEAPON_NONE, + ARMOR_STEEL, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_MEDIUM_FASTER, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_HUNT // ORDERS: Default order to give new unit. +); + +// Gunboat +static UnitTypeClass const UnitGunBoat( + UNIT_GUNBOAT, + TXT_GUNBOAT, // NAME: Text name of this unit type. + "BOAT", // NAME: Text name of this unit type. + ANIM_FBALL1, // EXPLOSION: Type of explosion when destroyed. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + false, // Only has eight facings? + true, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + false, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + false, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + true, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + true, // Is it equipped with a combat turret? + true, // Fires multiple shots in quick succession? + false, // Can it be repaired in a repair facility? + false, // Can the player construct or order this unit? + true, // Is there a crew inside? + false, // Does it have a rotating radar dish? + false, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + false, // Does it lay tracks while moving? + true, // Is this a gigundo-rotund-enormous unit? + true, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 700, // STRENGTH: Strength (in damage points). + 5, // SIGHTRANGE: Range of sighting. + 300, // COST: Cost to build (Credits). + 99, // SCENARIO: Starting availability scenario. + 80,40, // RISK/RWRD: Risk/reward rating values. + HOUSEF_MULTI1| + HOUSEF_MULTI2| + HOUSEF_MULTI3| + HOUSEF_MULTI4| + HOUSEF_MULTI5| + HOUSEF_MULTI6| + HOUSEF_JP| + HOUSEF_GOOD| + HOUSEF_BAD, // OWNABLE: Ownable by house (bit field). + WEAPON_TOMAHAWK,WEAPON_NONE, + ARMOR_STEEL, // ARMOR: Armor type + SPEED_FLOAT, // MOVE: Locomotion type. + MPH_SLOW, // SPEED: Miles per hour. + 1, // ROT: Rate of turn (degrees per tick). + 14, // Turret center offset along body centerline. + MISSION_GUARD // ORDERS: Default order to give new unit. +); + +// Triceratops +static UnitTypeClass const UnitTric( + UNIT_TRIC, + TXT_TRIC, // NAME: Text name of this unit type. + "TRIC", // NAME: Text name of this unit type. + ANIM_TRIC_DIE, // EXPLOSION: Type of explosion when destroyed. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + true, // Only has eight facings? + true, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + true, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + true, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired in a repair facility? + false, // Can the player construct or order this unit? + false, // Is there a crew inside? + false, // Does it have a rotating radar dish? + true, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + false, // Does it lay tracks while moving? + true, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 700, // STRENGTH: Strength (in damage points). + 5, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to build (Credits). + 99, // SCENARIO: Starting availability scenario. + 50,50, // RISK/RWRD: Risk/reward rating values. + HOUSEF_JP, // OWNABLE: Ownable by house (bit field). + WEAPON_STEG,WEAPON_NONE, + ARMOR_STEEL, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_SLOW, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_GUARD // ORDERS: Default order to give new unit. +); + +// Tyrannosaurus Rex +static UnitTypeClass const UnitTrex( + UNIT_TREX, + TXT_TREX, // NAME: Text name of this unit type. + "TREX", // NAME: Text name of this unit type. + ANIM_TREX_DIE, // EXPLOSION: Type of explosion when destroyed. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + true, // Only has eight facings? + true, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + true, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + true, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired in a repair facility? + false, // Can the player construct or order this unit? + false, // Is there a crew inside? + false, // Does it have a rotating radar dish? + true, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + false, // Does it lay tracks while moving? + true, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 750, // STRENGTH: Strength (in damage points). + 5, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to build (Credits). + 99, // SCENARIO: Starting availability scenario. + 50,50, // RISK/RWRD: Risk/reward rating values. + HOUSEF_JP, // OWNABLE: Ownable by house (bit field). + WEAPON_TREX,WEAPON_NONE, + ARMOR_STEEL, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_MEDIUM, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_GUARD // ORDERS: Default order to give new unit. +); + +// Velociraptor +static UnitTypeClass const UnitRapt( + UNIT_RAPT, + TXT_RAPT, // NAME: Text name of this unit type. + "RAPT", // NAME: Text name of this unit type. + ANIM_RAPT_DIE, // EXPLOSION: Type of explosion when destroyed. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + true, // Only has eight facings? + true, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + true, // Can it be crushed by a heavy vehicle? + false, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + true, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired in a repair facility? + false, // Can the player construct or order this unit? + false, // Is there a crew inside? + false, // Does it have a rotating radar dish? + true, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + false, // Does it lay tracks while moving? + true, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 180, // STRENGTH: Strength (in damage points). + 5, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to build (Credits). + 99, // SCENARIO: Starting availability scenario. + 50,50, // RISK/RWRD: Risk/reward rating values. + HOUSEF_JP, // OWNABLE: Ownable by house (bit field). + WEAPON_TREX,WEAPON_NONE, + ARMOR_STEEL, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_FAST, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_GUARD // ORDERS: Default order to give new unit. +); + +// Stegosaurus +static UnitTypeClass const UnitSteg( + UNIT_STEG, + TXT_STEG, // NAME: Text name of this unit type. + "STEG", // NAME: Text name of this unit type. + ANIM_STEG_DIE, // EXPLOSION: Type of explosion when destroyed. + 99, // Build level. + STRUCTF_NONE, // Building prerequisite. + false, // Can this be a goodie surprise from a crate? + true, // Is a leader type? + true, // Only has eight facings? + true, // Always use the given name for the vehicle? + false, // Is this a typical transport vehicle? + false, // Can it be crushed by a heavy vehicle? + true, // Can this unit squash infantry? + false, // Does this unit harvest Tiberium? + true, // Is invisible to radar? + true, // Is selectable by player? + true, // Can it be a target for attack or move? + false, // Is it insignificant (won't be announced)? + false, // Is it immune to normal combat damage? + false, // Is it equipped with a combat turret? + false, // Fires multiple shots in quick succession? + false, // Can it be repaired in a repair facility? + false, // Can the player construct or order this unit? + false, // Is there a crew inside? + false, // Does it have a rotating radar dish? + true, // Is there an associated firing animation? + false, // Must the turret be in a locked down position while moving? + false, // Does it lay tracks while moving? + true, // Is this a gigundo-rotund-enormous unit? + false, // Is the unit's art as "chunky" cardinal facing only? + false, // Is the unit capable of cloaking? + false, // Does the unit have a constant animation? + -1, // AMMO: Number of shots it has (default). + 600, // STRENGTH: Strength (in damage points). + 5, // SIGHTRANGE: Range of sighting. + 0, // COST: Cost to build (Credits). + 99, // SCENARIO: Starting availability scenario. + 50,50, // RISK/RWRD: Risk/reward rating values. + HOUSEF_JP, // OWNABLE: Ownable by house (bit field). + WEAPON_STEG,WEAPON_NONE, + ARMOR_STEEL, // ARMOR: Armor type + SPEED_TRACK, // MOVE: Locomotion type. + MPH_SLOW, // SPEED: Miles per hour. + 5, // ROT: Rate of turn (degrees per tick). + 0, // Turret center offset along body centerline. + MISSION_GUARD // ORDERS: Default order to give new unit. +); + + +/* +** This is the array of pointers to the static data associated with each +** vehicle type. +*/ +UnitTypeClass const * const UnitTypeClass::Pointers[UNIT_COUNT] = { + &UnitHTank, // UNIT_HTANK + &UnitMTank, // UNIT_MTANK + &UnitLTank, // UNIT_LTANK + &UnitSTank, // UNIT_STANK + &UnitFTank, // UNIT_FTANK + &UnitVisceroid, // UNIT_VICE + &UnitAPC, // UNIT_APC + &UnitMLRS, // UNIT_MLRS + &UnitJeep, // UNIT_JEEP + &UnitBuggy, // UNIT_BUGGY + &UnitHarvester, // UNIT_HARVESTER + &UnitArty, // UNIT_ARTY + &UnitSAM, // UNIT_MSAM + &UnitHover, // UNIT_HOVER + &UnitMHQ, // UNIT_MHQ + &UnitGunBoat, // UNIT_GUNBOAT + &UnitMCV, // UNIT_MCV + &UnitBike, // UNIT_BIKE + &UnitTric, // UNIT_TRIC + &UnitTrex, // UNIT_TREX + &UnitRapt, // UNIT_RAPT + &UnitSteg, // UNIT_STEG +}; + + +/*********************************************************************************************** + * UnitTypeClass::UnitTypeClass -- Constructor for unit types. * + * * + * This is the constructor for the unit types. It is used to initialize the unit type class * + * structure. The unit type class is used to control the behavior of the various types * + * of units in the game. This constructor is called for every unique unit type as it * + * exists in the array of unit types. * + * * + * INPUT: bla bla bla... see below * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/20/1994 JLB : Created. * + *=============================================================================================*/ +UnitTypeClass::UnitTypeClass(UnitType type, + int name, + char const *ininame, + AnimType exp, + unsigned char level, + long pre, + bool is_goodie, + bool is_leader, + bool is_eight, + bool is_nominal, + bool is_transporter, + bool is_crushable, + bool is_crusher, + bool is_harvest, + bool is_stealthy, + bool is_selectable, + bool is_legal_target, + bool is_insignificant, + bool is_immune, + bool is_turret_equipped, + bool is_twoshooter, + bool is_repairable, + bool is_buildable, + bool is_crew, + bool is_radar_equipped, + bool is_fire_anim, + bool is_lock_turret, + bool is_tracked, + bool is_gigundo, + bool is_chunky, + bool is_cloakable, + bool is_animating, + int ammo, + unsigned short strength, + int sightrange, + int cost, + int scenario, + int risk, + int reward, + int ownable, + WeaponType primary, WeaponType secondary, + ArmorType armor, + SpeedType speed, + MPHType maxSpeed, + unsigned rot, + int toffset, + MissionType order) : + TechnoTypeClass(name, + ininame, + level, + pre, + is_leader, + false, + is_nominal, + is_transporter, + false, + is_crushable, + is_stealthy, + is_selectable, + is_legal_target, + is_insignificant, + is_immune, + false, + is_twoshooter, + is_turret_equipped, + is_repairable, + is_buildable, + is_crew, + ammo, + strength, + maxSpeed, + sightrange, + cost, + scenario, + risk, + reward, + ownable, + primary,secondary, + armor) +{ + Explosion = exp; + IsCrateGoodie = is_goodie; + IsPieceOfEight = is_eight; + IsCloakable = is_cloakable; + IsChunkyShape = is_chunky; + IsCrusher = is_crusher; + IsFireAnim = is_fire_anim; + IsGigundo = is_gigundo; + IsLockTurret = is_lock_turret; + IsRadarEquipped = is_radar_equipped; + IsToHarvest = is_harvest; + IsTracked = is_tracked; + IsAnimating = is_animating; + Mission = order; + ROT = rot; + Speed = speed; + TurretOffset = toffset; + Type = type; +} + + +/*********************************************************************************************** + * UnitTypeClass::Occupy_List -- Returns with unit occupation list. * + * * + * This routine returns with an occupation list for the unit type. * + * The unit occupation list is used for placing units. * + * * + * INPUT: placement -- Is this for placement legality checking only? The normal condition * + * is for marking occupation flags. * + * * + * OUTPUT: Returns with a pointer to the unit occupation list. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/14/1994 JLB : Created. * + *=============================================================================================*/ +short const * UnitTypeClass::Occupy_List(bool ) const +{ + static short const _simple[] = {0, REFRESH_EOL}; + static short const _gun[] = {0, -1, 1, REFRESH_EOL}; + + if (Type == UNIT_GUNBOAT) { + return(&_gun[0]); + } + return(&_simple[0]); +} + + +/*********************************************************************************************** + * UnitTypeClass::From_Name -- Fetch class pointer from specified name. * + * * + * This routine converts an ASCII representation of a unit class and * + * converts it into a real unit class number. * + * * + * INPUT: name -- ASCII name representing a unit class. * + * * + * OUTPUT: Returns with the actual unit class number that the string * + * represents. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 10/07/1992 JLB : Created. * + * 05/02/1994 JLB : Converted to member function. * + *=============================================================================================*/ +UnitType UnitTypeClass::From_Name(char const *name) +{ + if (name) { + for (UnitType classid = UNIT_FIRST; classid < UNIT_COUNT; classid++) { + if (stricmp(Pointers[classid]->IniName, name) == 0) { + return(classid); + } + } + } + return(UNIT_NONE); +} + + +#ifdef SCENARIO_EDITOR +/*********************************************************************************************** + * UnitTypeClass::Display -- Displays a generic unit shape. * + * * + * This routine displays a generic representation of a unit of this * + * type. Typically, it is used when adding objects to the game map. * + * * + * INPUT: x,y -- Coordinate to render the unit shape. * + * * + * window-- Window to render within. * + * * + * house -- House to render the unit colors. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/14/1994 JLB : Created. * + * 11/08/1994 JLB : Handles chunky type vehicles now. * + *=============================================================================================*/ +void UnitTypeClass::Display(int x, int y, WindowNumberType window, HousesType house) const +{ + int shape = 0; + void const * ptr = Get_Cameo_Data(); + if (!ptr) { + ptr = Get_Image_Data(); + shape = IsChunkyShape ? 0 : 5; + } + CC_Draw_Shape(ptr, shape, x, y, window, SHAPE_FADING|SHAPE_CENTER|SHAPE_WIN_REL, HouseTypeClass::As_Reference(house).RemapTable); +} + + +/*********************************************************************************************** + * UnitTypeClass::Prep_For_Add -- Prepares scenario editor to add unit. * + * * + * This routine is used to prepare the generic object adder dialog * + * box so that it will be able to add a unit object. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1994 JLB : Created. * + * 06/04/1994 JLB : Uses map editing interface functions. * + *=============================================================================================*/ +void UnitTypeClass::Prep_For_Add(void) +{ + for (UnitType index = UNIT_FIRST; index < UNIT_COUNT; index++) { + if (As_Reference(index).Get_Image_Data() != NULL) { + Map.Add_To_List(&As_Reference(index)); + } + } +} +#endif + + +/*********************************************************************************************** + * UnitTypeClass::One_Time -- Performs one time processing for unit type class objects. * + * * + * This routine is used to perform the action necessary only once for the unit type class. * + * It loads unit shapes and brain files. This routine should only be called once. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: Only call this routine once. * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +void UnitTypeClass::One_Time(void) +{ + for (UnitType index = UNIT_FIRST; index < UNIT_COUNT; index++) { + char fullname[_MAX_FNAME+_MAX_EXT]; + char buffer[_MAX_FNAME]; + UnitTypeClass const & uclass = As_Reference(index); + CCFileClass file; + int largest; // Largest dimension of shape (so far). + + void const *ptr; // Shape pointer and set pointer. + + largest = 0; + if (uclass.IsBuildable) { + + /* + ** Fetch the supporting data files for the unit. + */ + if ( Get_Resolution_Factor() ) { + sprintf(buffer, "%sICNH", uclass.IniName); + } else { + sprintf(buffer, "%sICON", uclass.IniName); + } + _makepath(fullname, NULL, NULL, buffer, ".SHP"); + ((void const *&)uclass.CameoData) = MixFileClass::Retrieve(fullname); + } + + /* + ** Fetch a pointer to the unit's shape data. + */ + if (!uclass.IsPieceOfEight || (Special.IsJurassic && AreThingiesEnabled) ) { + _makepath(fullname, NULL, NULL, uclass.IniName, ".SHP"); + ptr = MixFileClass::Retrieve(fullname); + } else { + ptr = NULL; + } + + ((void const *&)uclass.ImageData) = ptr; + if (ptr) { + + if (index == UNIT_MLRS || index == UNIT_MSAM) { + largest = 26; + } else { + largest = MAX(largest, (int)Get_Build_Frame_Width(ptr)); + largest = MAX(largest, (int)Get_Build_Frame_Height(ptr)); + } + } + + ((int &)uclass.MaxSize) = MAX(largest, 8); + } + + /* + ** Load the wake shapes in at this time. + */ + if (!WakeShapes) { + WakeShapes = MixFileClass::Retrieve("WAKE.SHP"); + } +} + + + +/*********************************************************************************************** + * UTC::Init -- fetches the sidebar icons for the unittypeclass objects * + * * + * * + * * + * INPUT: theater type * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 4/26/96 4:07PM ST : Created * + *=============================================================================================*/ + +void UnitTypeClass::Init(TheaterType theater) +{ + + if (Get_Resolution_Factor()){ + + if (theater != LastTheater){ + + void const * cameo_ptr; + char fullname[_MAX_FNAME+_MAX_EXT]; + char buffer[_MAX_FNAME]; + + for (UnitType index = UNIT_FIRST; index < UNIT_COUNT; index++) { + + UnitTypeClass const & uclass = As_Reference(index); + + ((void const *&)uclass.CameoData) = NULL; + + if (uclass.IsBuildable) { + sprintf(buffer, "%sICNH", uclass.IniName); + _makepath(fullname, NULL, NULL, buffer, Theaters[theater].Suffix); + cameo_ptr = MixFileClass::Retrieve(fullname); + if (cameo_ptr){ + ((void const *&)uclass.CameoData) = cameo_ptr; + } + } + } + } + } +} + + +/*********************************************************************************************** + * UnitTypeClass::Create_And_Place -- Creates and places a unit object onto the map. * + * * + * This routine is used by the scenario editor to create and place a unit object of this * + * type onto the map. * + * * + * INPUT: cell -- The cell that the unit is to be placed into. * + * * + * house -- The house that the unit belongs to. * + * * + * OUTPUT: bool; Was the unit created and placed successfully? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +bool UnitTypeClass::Create_And_Place(CELL cell, HousesType house) const +{ + UnitClass * unit = new UnitClass(Type, house); + if (unit) { + return(unit->Unlimbo(Cell_Coord(cell), Random_Pick(DIR_N, DIR_MAX))); + } + return(false); +} + + +/*********************************************************************************************** + * UnitTypeClass::Create_One_Of -- Creates a unit in limbo. * + * * + * This function creates a unit of this type and keeps it in limbo. A pointer to the * + * created unit object is returned. It is presumed that this object will later be * + * unlimboed at the correct time and place. * + * * + * INPUT: house -- Pointer to the house that is to own the unit. * + * * + * OUTPUT: Returns with a pointer to the created unit object. If the unit object * + * could not be created, then NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/07/1994 JLB : Created. * + *=============================================================================================*/ +ObjectClass * UnitTypeClass::Create_One_Of(HouseClass * house) const +{ + return(new UnitClass(Type, house->Class->House)); +} + + +/*********************************************************************************************** + * UnitTypeClass::Who_Can_Build_Me -- Determines which factory can build this unit type. * + * * + * Use this routine to examine the buildings on the map in order to determine which one * + * can build the unit type. * + * * + * INPUT: intheory -- If this parameter is true, then no examination of whether the factory * + * is currently busy is performed. It just checks to see if the unit * + * could be produced "in theory". * + * * + * legal -- Should building prerequisite legality checks be performed as well? * + * For building placements, this is usually false. For sidebar button * + * adding, this is usually true. * + * * + * house -- The owner of the unit to be produced. * + * * + * OUTPUT: Returns with pointer to the factory that can produce the unit. If no suitable * + * factory could be found then NULL is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/12/1994 JLB : Created. * + *=============================================================================================*/ +BuildingClass * UnitTypeClass::Who_Can_Build_Me(bool intheory, bool legal, HousesType house) const +{ + BuildingClass * anybuilding = NULL; + + for (int index = 0; index < Buildings.Count(); index++) { + BuildingClass * building = Buildings.Ptr(index); + + if (building && + !building->IsInLimbo && + building->House->Class->House == house && + building->Class->ToBuild == RTTI_UNITTYPE && + building->Mission != MISSION_DECONSTRUCTION && + ((1L << building->ActLike) & Ownable) && + (!legal || building->House->Can_Build(Type, building->ActLike)) && + (intheory || !building->In_Radio_Contact())) { + + if (building->IsLeader) return(building); + anybuilding = building; + } + } + return(anybuilding); +} + + +/*********************************************************************************************** + * UnitTypeClass::As_Reference -- Fetches a reference to the unit type class specified. * + * * + * Use this routine to return a reference to the UnitTypeClass object as indicated by * + * the unit type number speicified. * + * * + * INPUT: type -- The unit type number to convert into a UnitTypeClass object reference. * + * * + * OUTPUT: Returns with a reference to the unit type class object specified. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +UnitTypeClass const & UnitTypeClass::As_Reference(UnitType type) +{ + return(*Pointers[type]); +} + + +/*********************************************************************************************** + * UnitTypeClass::Dimensions -- Determines the unit's pixel dimensions. * + * * + * This routine will fill in the width and height for this unit type. This width and * + * height are used to render the selection rectangle and the positioning of the health * + * bargraph. * + * * + * INPUT: width -- Reference to the width of the unit (to be filled in). * + * * + * height -- Reference to the height of the unit (to be filled in). * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/23/1995 JLB : Created. * + *=============================================================================================*/ +void UnitTypeClass::Dimensions(int &width, int &height) const +{ + if (Type == UNIT_GUNBOAT) { + width = 46; + height = 18; + } else { + width = MaxSize-(MaxSize/4); + height = MaxSize-(MaxSize/4); + } +} + +/*********************************************************************************************** + * UnitTypeClass::Repair_Cost -- Determines cost per "step" of repair. * + * * + * Use this routine to determine how much it will cost to repair the unit one * + * step. A step is defined as the number of hit points returned from the Repair_Step() * + * function. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the credit cost to repair this unit one step. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/03/1995 BWG : Created. * + *=============================================================================================*/ +int UnitTypeClass::Repair_Cost(void) const +{ + return(Fixed_To_Cardinal(Cost/(MaxStrength/REPAIR_STEP), REPAIR_PERCENT)); +} + + +/*********************************************************************************************** + * UnitTypeClass::Repair_Step -- Determines the repair step rate. * + * * + * This routine will determine how many strength points get healed for each "step". The * + * cost to repair one step is determine from the Repair_Cost() function. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of health points repaired for each "step". * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/03/1995 BWG : Created. * + *=============================================================================================*/ +int UnitTypeClass::Repair_Step(void) const +{ + return(REPAIR_STEP); +} + + +/*********************************************************************************************** + * UnitTypeClass::Max_Pips -- Fetches the maximum pips allowed for this unit. * + * * + * This routine will determine the number of pips (maximum) allowed for this unit type. * + * Typically, this is the number of passengers allowed, but for harvesters, it is the * + * number of credits it holds divided by 100. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the maximum number of pips allowed for this unit type. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/26/1995 JLB : Created. * + *=============================================================================================*/ +int UnitTypeClass::Max_Pips(void) const +{ + if (Type == UNIT_HARVESTER) { + return(FULL_LOAD_CREDITS/100); + } + + if (IsTransporter) { + return(Max_Passengers()); + } + return(0); +} diff --git a/UNIT.CPP b/UNIT.CPP new file mode 100644 index 0000000..989740c --- /dev/null +++ b/UNIT.CPP @@ -0,0 +1,4051 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\unit.cpv 2.17 16 Oct 1995 16:48:28 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : UNIT.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : September 10, 1993 * + * * + * Last Update : August 16, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * Recoil_Adjust -- Adjust pixel values in direction specified. * + * Turret_Adjust -- Turret adjustment routine for MLRS and MSAM units. * + * UnitClass::AI -- AI processing for the unit. * + * UnitClass::APC_Close_Door -- Closes an APC door. * + * UnitClass::APC_Open_Door -- Opens an APC door. * + * UnitClass::Active_Click_With -- Intercepts the active click to see if deployment is possib* + * UnitClass::As_Target -- Returns the unit as a target value. * + * UnitClass::Blocking_Object -- Determines how a object blocks a unit * + * UnitClass::Can_Enter_Building -- Determines building entry legality. * + * UnitClass::Can_Fire -- Determines if this unit can fire. * + * UnitClass::Can_Player_Move -- Determines if the player is legally allowed to move it. * + * UnitClass::Click_With -- Handles player map clicking while this unit is selected. * + * UnitClass::Crew_Type -- Fetches the kind of crew that this object produces. * + * UnitClass::Debug_Dump -- Displays the status of the unit to the mono monitor. * + * UnitClass::Desired_Load_Dir -- Determines the best cell and facing for loading. * + * UnitClass::Draw_It -- Draws a unit object. * + * UnitClass::Enter_Idle_Mode -- Unit enters idle mode state. * + * UnitClass::Find_LZ -- Maintenance function for transport units. * + * UnitClass::Flag_Attach -- Attaches a house flag to this unit. * + * UnitClass::Flag_Remove -- Removes the house flag from this unit. * + * UnitClass::Goto_Clear_Spot -- Finds a clear spot to deploy. * + * UnitClass::Goto_Tiberium -- Search for and head toward nearest available Tiberium patch. * + * UnitClass::Harvesting -- Harvests tiberium at the current location. * + * UnitClass::Init -- Clears all units for scenario preparation. * + * UnitClass::Limbo -- Prepares vehicle and then limbos it. * + * UnitClass::Look -- Perform map revelation from a unit's position. * + * UnitClass::Mission_Attack -- Handles the mission attack logic. * + * UnitClass::Mission_Guard -- Special guard mission override processor. * + * UnitClass::Mission_Harvest -- Handles the harvesting process used by harvesters. * + * UnitClass::Mission_Hunt -- This is the AI process for aggressive enemy units. * + * UnitClass::Mission_Move -- Handles special move mission overrides. * + * UnitClass::Mission_Unload -- Handles unloading cargo. * + * UnitClass::Overlap_List -- Determines overlap list for units. * + * UnitClass::Per_Cell_Process -- Performs operations necessary on a per cell basis. * + * UnitClass::Pip_Count -- Fetchs the number of pips to display on unit. * + * UnitClass::Random_Animate -- Handles random idle animation for the unit. * + * UnitClass::Read_INI -- Reads units from scenario INI file. * + * UnitClass::Receive_Message -- Handles receiving a radio message. * + * UnitClass::Remap_Table -- Fetches the remap table to use for this object. * + * UnitClass::Response_Attack -- Voice feedback when ordering the unit to attack a target. * + * UnitClass::Response_Move -- Voice feedback when ordering the unit to move. * + * UnitClass::Response_Select -- Voice feedback when selecting the unit. * + * UnitClass::Scatter -- Causes the unit to travel to a nearby safe cell. * + * UnitClass::Set_Speed -- Initiate unit movement physics. * + * UnitClass::Sort_Y -- Give Y coordinate sort value for unit. * + * UnitClass::Start_Driver -- Starts driving and reserves destination cell. * + * UnitClass::Stop_Driver -- Handles removing occupation bits when driving stops. * + * UnitClass::Stun -- Stuns the unit in preparation for unit removal. * + * UnitClass::Take_Damage -- Inflicts damage points on a unit. * + * UnitClass::Target_Coord -- The coordinate to use when targeting this unit. * + * UnitClass::Try_To_Deploy -- The unit attempts to "deploy" at current location. * + * UnitClass::UnitClass -- Constructor for units. * + * UnitClass::Unlimbo -- Removes unit from stasis. * + * UnitClass::Unload_Hovercraft_Process -- Handles unloading hovercraft transport. * + * UnitClass::Validate -- validates unit pointer. * + * UnitClass::What_Action -- Determines what action would occur if clicked on object. * + * UnitClass::What_Am_I -- Returns with the RTTI type this object is. * + * UnitClass::Write_INI -- Writes all the units out to an INI file. * + * UnitClass::delete -- Deletion operator for units. * + * UnitClass::new -- Allocate a unit slot and adjust access arrays. * + * UnitClass::~UnitClass -- Destructor for unit objects. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" + +/* +** This contains the value of the Virtual Function Table Pointer +*/ +void * UnitClass::VTable; + + +/*********************************************************************************************** + * UnitClass::Validate -- validates unit pointer. * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * 1 = ok, 0 = error * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 08/09/1995 BRR : Created. * + *=============================================================================================*/ +#ifdef CHEAT_KEYS +int UnitClass::Validate(void) const +{ + int num; + + num = Units.ID(this); + if (num < 0 || num >= UNIT_MAX) { + Validate_Error("UNIT"); + return (0); + } + else + return (1); +} +#else +#define Validate() +#endif + + +/*********************************************************************************************** + * Recoil_Adjust -- Adjust pixel values in direction specified. * + * * + * This is a helper routine that modifies the pixel coordinates provided according to the * + * direction specified. The effect is the simulate recoil effects by moving an object 'back'* + * one pixel. Since the pixels moved depend on facing, this routine handles the pixel * + * adjustment quickly. * + * * + * INPUT: dir -- The direction to base the recoil on. * + * * + * x,y -- References to the pixel coordinates that will be adjusted. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +void Recoil_Adjust(DirType dir, int &x, int &y) +{ + static struct { + signed char X,Y; + } _adjust[32] = { + {0,1}, // N + {0,1}, + {0,1}, + {-1,1}, + {-1,1}, // NE + {-1,1}, + {-1,0}, + {-1,0}, + {-1,0}, // E + {-1,0}, + {-1,-1}, + {-1,-1}, + {-1,-1}, // SE + {-1,-1}, + {-1,-1}, + {0,-1}, + {0,-1}, // S + {0,-1}, + {0,-1}, + {1,-1}, + {1,-1}, // SW + {1,-1}, + {1,0}, + {1,0}, + {1,0}, // W + {1,0}, + {1,1}, + {1,1}, + {1,1}, // NW + {1,1}, + {0,1}, + {0,1} + }; + + int index = Facing_To_32(dir); + x += _adjust[index].X; + y += _adjust[index].Y; +} + + +/*********************************************************************************************** + * Turret_Adjust -- Turret adjustment routine for MLRS and MSAM units. * + * * + * This routine adjusts the pixel coordinates specified to account for the displacement of * + * the turret on the MLRS and MSAM vehicles. * + * * + * INPUT: dir -- The direction of the body of the vehicle. * + * * + * x,y -- References to the turret center pixel position. These will be modified as * + * necessary. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +void Turret_Adjust(DirType dir, int &x, int &y) +{ + static struct { + signed char X,Y; + } _adjust[32] = { + {1,2}, // N + {-1,1}, + {-2,0}, + {-3,0}, + {-3,1}, // NW + {-4,-1}, + {-4,-1}, + {-5,-2}, + {-5,-3}, // W + {-5,-3}, + {-3,-3}, + {-3,-4}, + {-3,-4}, // SW + {-3,-5}, + {-2,-5}, + {-1,-5}, + {0,-5}, // S + {1,-6}, + {2,-5}, + {3,-5}, + {4,-5}, // SE + {6,-4}, + {6,-3}, + {6,-3}, + {6,-3}, // E + {5,-1}, + {5,-1}, + {4,0}, + {3,0}, // NE + {2,0}, + {2,1}, + {1,2} + }; + + int index = Facing_To_32(dir); + x += _adjust[index].X; + y += _adjust[index].Y; +} + + +/*********************************************************************************************** + * UnitClass::As_Target -- Returns the unit as a target value. * + * * + * This routine will convert the unit into a target value that is then returned. Target * + * values are typically used for navigation and other computer uses. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with target value of unit. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +TARGET UnitClass::As_Target(void) const +{ + Validate(); + return(Build_Target(KIND_UNIT, Units.ID(this))); +} + + +#ifdef CHEAT_KEYS +/*********************************************************************************************** + * UnitClass::Debug_Dump -- Displays the status of the unit to the mono monitor. * + * * + * This displays the current status of the unit class to the mono monitor. By this display * + * bugs may be tracked down or prevented. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/02/1994 JLB : Created. * + *=============================================================================================*/ +void UnitClass::Debug_Dump(MonoClass *mono) const +{ + Validate(); + mono->Set_Cursor(0, 0); + mono->Print( + "ÚName:ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂMission:ÄÄÄÂTarCom:ÂNavCom:ÂRadio:ÂCoord:ÄÄÂHeadTo:ÄÂSt:Ä¿\n" + "³ ³ ³ ³ ³ ³ ³ ³ ³\n" + "ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂNÂYÂHealth:ÄÂBody:ÂTurret:ÂSpeed:ÂPath:ÁÄÄÄÄÄÄÂCargo:ÄÄÄÄÁÄÄÄÄ´\n" + "³Active........³ ³ ³ ³ ³ ³ ³ ³ ³\n" + "³Limbo.........³ ³ ÃÄÄÄÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´\n" + "³Owned.........³ ³ ³Last Message: ³\n" + "³Discovered....³ ³ ÃTimer:ÂArm:ÂTrack:ÂTiberium:ÂFlash:ÂStage:ÂTeam:ÄÄÄÄÂArch:´\n" + "³Selected......³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³\n" + "³Teathered.....³ ³ ÃÄÄÄÄÄÄÁÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÙ\n" + "³Locked on Map.³ ³ ³ \n" + "³Turret Locked.³ ³ ³ \n" + "³Is A Loaner...³ ³ ³ \n" + "³Deploying.....³ ³ ³ \n" + "³Rotating......³ ³ ³ \n" + "³Firing........³ ³ ³ \n" + "³Driving.......³ ³ ³ \n" + "³To Look.......³ ³ ³ \n" + "³Recoiling.....³ ³ ³ \n" + "³To Display....³ ³ ³ \n" + "ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÁÄÙ \n"); + mono->Set_Cursor(1, 1);mono->Printf("%s:%s", House->Class->IniName, Class->IniName); + CargoClass::Debug_Dump(mono); + MissionClass::Debug_Dump(mono); + TarComClass::Debug_Dump(mono); +} +#endif + + +/*********************************************************************************************** + * UnitClass::Sort_Y -- Give Y coordinate sort value for unit. * + * * + * This routine is used by the rendering system in order to sort the * + * game objects in a back to front order. This is now the correct * + * overlap effect is achieved. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a coordinate value that can be used for sorting. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/17/1994 JLB : Created. * + *=============================================================================================*/ +COORDINATE UnitClass::Sort_Y(void) const +{ + Validate(); + if (IsTethered && *this == UNIT_HOVER) { + return(Coord_Add(Coord, 0xFF800000L)); + } + return(Coord_Add(Coord, 0x00800000L)); +} + + +/*********************************************************************************************** + * UnitClass::AI -- AI processing for the unit. * + * * + * This routine will perform the AI processing necessary for the unit. These are non- * + * graphic related operations. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + *=============================================================================================*/ +void UnitClass::AI(void) +{ + Validate(); + + /* + ** Act on new orders if the unit is at a good position to do so. + */ + if (!IsDriving && Is_Door_Closed() /*Mission != MISSION_UNLOAD*/) { + Commence(); + } + + TarComClass::AI(); + + /* + ** Delete this unit if it finds itself off the edge of the map and it is in + ** guard or other static mission mode. + */ + if (!Team && Mission == MISSION_GUARD && !Map.In_Radar(Coord_Cell(Coord))) { + Stun(); + delete this; + return; + } + + /* + ** Rocket launchers will reload every so often. + */ + if (*this == UNIT_MSAM && Ammo < Class->MaxAmmo) { + if (IsDriving) { + Reload = Reload + 1; + } else { + if (Reload.Expired()) { + Ammo++; + if (Ammo < Class->MaxAmmo) { + Reload = TICKS_PER_SECOND*30; + } + Mark(MARK_CHANGE); + } + } + } + + /* + ** Hover landers always are flagged to redraw since they don't record themselves + ** on the map in the normal fashion. + */ + if (*this == UNIT_HOVER) { +// Mark_For_Redraw(); +//if (IsDown) Mono_Printf("*"); + Mark(MARK_CHANGE); + } + + /* + ** If this is a vehicle that heals itself (e.g., Mammoth Tank), then it will perform + ** the heal logic here. + */ + if (*this == UNIT_HTANK && (Frame % 16) == 0 && Health_Ratio() < 0x0080) { + Strength++; + Mark(MARK_CHANGE); + } + if (*this == UNIT_VICE && Map[Coord_Cell(Coord)].Land_Type() == LAND_TIBERIUM && Health_Ratio() < 0x0100 && (Frame % 16) == 0) { + Strength++; + Mark(MARK_CHANGE); + } + + /* + ** Transporters require special logic handled here since there isn't a MISSION_WAIT_FOR_PASSENGERS + ** mission that they can follow. Passenger loading is merely a part of their normal operation. + */ + if (Class->IsTransporter) { + + /* + ** Double check that there is a passenger that is trying to load or unload. + ** If not, then close the door. + */ + if (!Is_Door_Closed() && Mission != MISSION_UNLOAD && Transmit_Message(RADIO_TRYING_TO_LOAD) != RADIO_ROGER) { + APC_Close_Door(); + } + } + + /* + ** Don't start a new mission unless the vehicle is in the center of + ** a cell (not driving) and the door (if any) is closed. + */ + if (!IsDriving && Is_Door_Closed()/*&& Mission != MISSION_UNLOAD*/) { + Commence(); + } + + /* + ** Handle recoil recovery here. + */ + if (IsInRecoilState) { + IsInRecoilState = false; + Mark(MARK_CHANGE); + } + + /* + ** For animating objects such as Visceroids, max-out their animation + ** stages here + */ + if (Class->IsAnimating) { + if (!Fetch_Rate()) Set_Rate(2); + StageClass::Graphic_Logic(); + if (Fetch_Stage() >= Get_Build_Frame_Count(Class->Get_Image_Data())-1) { + Set_Stage(0); + } + } + + /* + ** for Jurassic objects, animate them if they're walking + */ + if (Class->IsPieceOfEight && Special.IsJurassic && AreThingiesEnabled) { + // Only animate if they're walking + if (IsDriving || IsFiring) { + if (!Fetch_Rate()) { + Set_Rate(Options.Normalize_Delay(2)); + Set_Stage(0); + } + StageClass::Graphic_Logic(); + if (Fetch_Stage() >= ( (IsDriving || *this == UNIT_TREX || *this == UNIT_RAPT) ? 8 : 12) ) { + Set_Stage(0); + if (IsFiring) { + Set_Rate(0); + IsFiring = false; + } + } + } + } + + /* + ** A cloaked object that is carrying the flag will always shimmer. + */ + if (Cloak == CLOAKED && Flagged != HOUSE_NONE) { + Do_Shimmer(); + } + +} + + +/*********************************************************************************************** + * UnitClass::Can_Fire -- Determines if this unit can fire. * + * * + * INPUT: target -- The target that is desired to fire upon. * + * * + * which -- The weapon (primary=0, secondary=1) to fire. * + * * + * OUTPUT: Returns with the fire error number if it cannot fire or else FIRE_OK. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + *=============================================================================================*/ +FireErrorType UnitClass::Can_Fire(TARGET target, int which) const +{ + Validate(); + FireErrorType cf; + + cf = TarComClass::Can_Fire(target, which); + if (cf == FIRE_OK) { + + /* + ** If it's a dinosaur, when it's OK to fire we should start the firing + ** animation, but wait for the proper attack stage before starting the + ** bullet, so things won't die prematurely. + */ + if (Class->IsFireAnim) { + if (!IsFiring) { + UnitClass *nonconst; + + nonconst = (UnitClass *)this; + nonconst->Set_Rate(Options.Normalize_Delay(2)); + nonconst->Set_Stage(0); + IsFiring = true; + cf = FIRE_BUSY; + } else { + if (Fetch_Stage() < 4) cf = FIRE_BUSY; + } + } + } + return(cf); +} + + +/*********************************************************************************************** + * UnitClass::Receive_Message -- Handles receiving a radio message. * + * * + * This is the handler function for when a unit receives a radio * + * message. Typical use of this is when a unit unloads from a hover * + * class so that clearing of the transport is successful. * + * * + * INPUT: from -- Pointer to the originator of the message. * + * * + * message -- The radio message received. * + * * + * param -- Reference to an optional parameter the might be needed to return * + * information back to the originator of the message. * + * * + * OUTPUT: Returns with the radio message response. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1994 JLB : Created. * + *=============================================================================================*/ +RadioMessageType UnitClass::Receive_Message(RadioClass * from, RadioMessageType message, long & param) +{ + Validate(); + switch (message) { + + /* + ** Asks if the passenger can load on this transport. + */ + case RADIO_CAN_LOAD: + if (Class->IsTransporter && How_Many() < Class->Max_Passengers() && from && House->Class->House == from->Owner()) { + return(RADIO_ROGER); + } + return(RADIO_NEGATIVE); + + /* + ** The refinery has told this harvester that it should begin the backup procedure + ** so that proper unloading may take place. + */ + case RADIO_BACKUP_NOW: + TarComClass::Receive_Message(from, message, param); + if (!IsRotating && PrimaryFacing != DIR_SW) { + Do_Turn(DIR_SW); + } else { + if (!IsDriving) { + Force_Track(BACKUP_INTO_REFINERY, Adjacent_Cell(Center_Coord(), FACING_N)); + Set_Speed(128); + } + } + return(RADIO_ROGER); + + /* + ** This message is sent by the passenger when it determines that it has + ** entered the transport. + */ + case RADIO_IM_IN: + if (How_Many() == Class->Max_Passengers()) { + APC_Close_Door(); + } + return(RADIO_ATTACH); + + /* + ** Docking maintenance message received. Check to see if new orders should be given + ** to the impatient unit. + */ + case RADIO_DOCKING: + if (Class->IsTransporter && *this == UNIT_APC && How_Many() < Class->Max_Passengers()) { + TarComClass::Receive_Message(from, message, param); + + if (!IsDriving && !IsRotating && !IsTethered) { + + /* + ** If the potential passenger needs someplace to go, then figure out a good + ** spot and tell it to go. + */ + if (Transmit_Message(RADIO_NEED_TO_MOVE, from) == RADIO_ROGER) { + + CELL cell; + DirType dir = Desired_Load_Dir(from, cell); + + /* + ** If no adjacent free cells are detected, then passenger loading + ** cannot occur. Break radio contact. + */ + if (cell == 0) { + Transmit_Message(RADIO_OVER_OUT, from); + } else { + param = (long)::As_Target(cell); + Do_Turn(dir); + + /* + ** If it is now facing the correct direction, then open the + ** transport doors. Close the doors if the transport is or needs + ** to rotate. + */ + if (IsRotating) { + if (!Is_Door_Closed()) { + APC_Close_Door(); + } + } else { + if (!Is_Door_Open()) { + APC_Open_Door(); + } + } + + /* + ** Tell the potential passenger where it should go. If the passenger is + ** already at the staging location, then tell it to move onto the transport + ** directly. + */ + if (Transmit_Message(RADIO_MOVE_HERE, param, from) == RADIO_YEA_NOW_WHAT) { + if (Is_Door_Open()) { + param = (long)As_Target(); + Transmit_Message(RADIO_TETHER); + if (Transmit_Message(RADIO_MOVE_HERE, param, from) != RADIO_ROGER) { + Transmit_Message(RADIO_OVER_OUT, from); + } else { + Contact_With_Whom()->Unselect(); + } + } + } + } + } + } + return(RADIO_ROGER); + } + break; + + /* + ** When this message is received, it means that the other object + ** has already turned its radio off. Turn this radio off as well. + */ + case RADIO_OVER_OUT: + if (Mission == MISSION_RETURN) { + Assign_Mission(MISSION_GUARD); + } + TarComClass::Receive_Message(from, message, param); + return(RADIO_ROGER); + + } + return(TarComClass::Receive_Message(from, message, param)); +} + + +/*********************************************************************************************** + * UnitClass::Unlimbo -- Removes unit from stasis. * + * * + * This routine will place a unit into the game and out of its limbo * + * state. This occurs whenever a unit is unloaded from a transport. * + * * + * INPUT: coord -- The coordinate to make the unit appear. * + * * + * dir -- The initial facing to impart upon the unit. * + * * + * OUTPUT: bool; Was the unit unlimboed successfully? If the desired * + * coordinate is illegal, then this might very well return * + * false. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/22/1994 JLB : Created. * + *=============================================================================================*/ +bool UnitClass::Unlimbo(COORDINATE coord, DirType dir) +{ + Validate(); + /* + ** All units must start out facing one of the 8 major directions. + */ + dir = Facing_Dir(Dir_Facing(dir)); + + if (TarComClass::Unlimbo(coord, dir)) { + + /* + ** Ensure that the owning house knows about the + ** new object. + */ + House->UScan |= (1L << Class->Type); + House->ActiveUScan |= (1L << Class->Type); + + /* + ** If it starts off the edge of the map, then it already starts cloaked. + */ + if (IsCloakable && !IsLocked) Cloak = CLOAKED; + + /* + ** Units default to no special animation. + */ + Set_Rate(0); + Set_Stage(0); + + /* + ** A gun boat and a hover craft are allowed to exit the map thus we + ** flag them so they can. This undoes the work of Techno::Unlimbo which + ** stole their IsALoaner flag. + */ + if (*this == UNIT_GUNBOAT || *this == UNIT_HOVER) { + IsALoaner = true; + } + + /* + ** Start the gunboat animating when it is unlimboed. + */ + if (*this == UNIT_GUNBOAT) { + Set_Rate(2); + Set_Stage(0); + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * UnitClass::Take_Damage -- Inflicts damage points on a unit. * + * * + * This routine will inflict the specified number of damage points on * + * the given unit. If the unit is destroyed, then this routine will * + * remove the unit cleanly from the game. The return value indicates * + * whether the unit was destroyed. This will allow appropriate death * + * animation or whatever. * + * * + * INPUT: damage-- The number of damage points to inflict. * + * * + * distance -- The distance from the damage center point to the object's center point.* + * * + * warhead--The type of damage to inflict. * + * * + * source -- Who is responsible for this damage? * + * * + * OUTPUT: Returns the result of the damage process. This can range from RESULT_NONE up to * + * RESULT_DESTROYED. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/30/1991 JLB : Created. * + * 07/12/1991 JLB : Script initiated by unit destruction. * + * 04/15/1994 JLB : Converted to member function. * + * 04/16/1994 JLB : Warhead modifier. * + * 06/03/1994 JLB : Added the source of the damage target value. * + * 06/20/1994 JLB : Source is a base class pointer. * + * 11/22/1994 JLB : Shares base damage handler for techno objects. * + * 06/30/1995 JLB : Lasers do maximum damage against gunboat. * + * 08/16/1995 JLB : Harvester crushing doesn't occur on early missions. * + *=============================================================================================*/ +ResultType UnitClass::Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source) +{ + Validate(); + ResultType res = RESULT_NONE; + + /* + ** Special case: If this is a laser attacking a gunboat, then the gunboat is ALWAYS toasted. + */ + if (*this == UNIT_GUNBOAT && warhead == WARHEAD_LASER) { + damage = Strength*3; + } + + /* + ** Remember if this object was selected. If it was and it gets destroyed and it has + ** passengers that pop out, then the passengers will inherit the select state. + */ + bool select = (IsSelected && IsOwnedByPlayer); + + /* + ** In order for a this to be damaged, it must either be a unit + ** with a crew or a sandworm. + */ + res = TarComClass::Take_Damage(damage, distance, warhead, source); + + if (res == RESULT_DESTROYED) { + Death_Announcement(source); + if (Class->Explosion != ANIM_NONE) { + AnimType anim = Class->Explosion; + + /* + ** SSM launchers will really explode big if they are carrying + ** missiles at the time of the explosion. + */ + if (*this == UNIT_MSAM && Ammo) { + anim = ANIM_NAPALM3; + } + + if (*this == UNIT_TRIC || *this == UNIT_TREX || *this == UNIT_RAPT || *this == UNIT_STEG) { + Sound_Effect(VOC_DINODIE1, Coord); + } + + new AnimClass(anim, Coord); + + /* + ** When the flame tank blows up, it really blows up. It is + ** equivalent to a napalm strike. + */ + if (Class->Explosion == ANIM_NAPALM3) { + Explosion_Damage(Coord, 200, source, WARHEAD_FIRE); + } + + /* + ** Very strong units that have an explosion will also rock the + ** screen when they are destroyed. + */ + if (Class->MaxStrength > 400) { + Shake_Screen(3); + } + } + + /* + ** Possibly have the crew member run away. + */ + Mark(MARK_UP); + if (Class->IsCrew && !Class->IsTransporter) { + if (Random_Pick(0, 1) == 0) { + InfantryClass * i = 0; + if (Class->Primary == WEAPON_NONE) { + i = new InfantryClass(INFANTRY_C1, House->Class->House); + i->IsTechnician = true; + } else { + i = new InfantryClass(INFANTRY_E1, House->Class->House); + } + if (i) { + if (i->Unlimbo(Coord, DIR_N)) { + i->Strength = Random_Pick(5, (int)i->Class->MaxStrength/2); + i->Scatter(0, true); + if (!House->IsHuman) { + i->Assign_Mission(MISSION_HUNT); + } else { + i->Assign_Mission(MISSION_GUARD); + } + if (select) i->Select(); + } else { + delete i; + } + } + } + } else { + if (*this != UNIT_HOVER) { + while (Is_Something_Attached()) { + FootClass * object = Detach_Object(); + + if (!object) break; // How can this happen? + + /* + ** Only infantry can run from a destroyed vehicle. Even then, it is not a sure + ** thing. + */ + if (object->Is_Infantry() && object->Unlimbo(Coord, DIR_N)) { + // object->Strength = Random_Pick(5, (int)((InfantryClass*)object)->Class->MaxStrength/2); + object->Scatter(0, true); + if (select) object->Select(); + } else { + object->Record_The_Kill(source); + delete object; + } + } + } else { + Kill_Cargo(source); + } + } + + /* + ** When the mobile head quarters blows up, the entire side blows up. + */ + if (*this == UNIT_MHQ) { + House->Flag_To_Die(); + } + + /* + ** Finally, delete the vehicle. + */ + delete this; + + } else { + + /* + ** When damaged and below half strength, start smoking if + ** it isn't already smoking. + */ + if (Health_Ratio() < 0x0080 && !IsAnimAttached && *this != UNIT_VICE && *this != UNIT_STEG && *this != UNIT_TREX && *this != UNIT_TRIC && *this != UNIT_RAPT) { + AnimClass * anim = new AnimClass(ANIM_SMOKE_M, Coord_Add(Coord, XYP_Coord(0, -8))); + if (anim) anim->Attach_To(this); + } + + /* + ** If at half damage, then start smoking or burning as appropriate. + */ + if (res == RESULT_HALF) { + if (*this == UNIT_GUNBOAT) { + AnimClass * anim = new AnimClass(ANIM_ON_FIRE_MED, Coord_Add(Coord, XYP_Coord(Random_Pick(0, 16)-8, -2))); + if (anim) anim->Attach_To(this); + } + } + + /* + ** Try to crush anyone that fires on this unit if possible. The harvester + ** typically is the only one that will qualify here. + */ + if (!Team && source && !IsTethered && !House->Is_Ally(source) && (!House->IsHuman || Special.IsSmartDefense)) { + + /* + ** Try to crush the attacker if it can be crushed by this unit and this unit is + ** not equipped with a flame type weapon. If this unit has a weapon and the target + ** is not very close, then fire on it instead. In easy mode, they never run over the + ** player. In hard mode, they always do. In normal mode, they only overrun past + ** mission #8. + */ + if ((Class->Primary == WEAPON_NONE || (Distance(source) < 0x0180 && BulletTypeClass::As_Reference(Weapons[Class->Primary].Fires).Warhead != WARHEAD_FIRE)) && + (GameToPlay != GAME_NORMAL || + *this != UNIT_HARVESTER || + BuildLevel > 8 || + Special.IsDifficult) && + !Special.IsEasy && + Class->IsCrusher && source->Is_Techno() && ((TechnoTypeClass const &)source->Class_Of()).IsCrushable) { + + Assign_Destination(source->As_Target()); + Assign_Mission(MISSION_MOVE); + } else { + + /* + ** Try to return to base if possible. + */ + if (*this == UNIT_HARVESTER && Pip_Count() && Health_Ratio() < 0x0080) { + /* + ** Find nearby refinery and head to it? + */ + BuildingClass * building = Find_Docking_Bay(STRUCT_REFINERY, false); + + /* + ** Since the refinery said it was ok to load, establish radio + ** contact with the refinery and then await docking orders. + */ + if (building && Transmit_Message(RADIO_HELLO, building) == RADIO_ROGER) { + Assign_Mission(MISSION_ENTER); + } + } + } + } + + /* + ** Computer controlled harvester will radio for help if they are attacked. + */ + if (*this == UNIT_HARVESTER && !House->IsHuman && source) { + Base_Is_Attacked(source); + } + } + return(res); +} + + +/*********************************************************************************************** + * UnitClass::new -- Allocate a unit slot and adjust access arrays. * + * * + * This routine will allocate a unit from the available unit pool and * + * fixup all the access lists to match. It will allocate a unit slot * + * from within the range allowed for the specified unit type. If no * + * slot was found, then it will fail. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the allocated unit. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/11/1994 JLB : Created. * + * 04/21/1994 JLB : Converted to operator new. * + *=============================================================================================*/ +void * UnitClass::operator new(size_t) +{ + void * ptr = (UnitClass *)Units.Allocate(); + if (ptr) { + ((UnitClass *)ptr)->IsActive = true; + } + return(ptr); +} + + +/*********************************************************************************************** + * UnitClass::delete -- Deletion operator for units. * + * * + * This removes the unit from the local allocation system. Since this * + * is a fixed block of memory, not much has to be done to delete the * + * unit. Merely marking it as inactive is enough. * + * * + * INPUT: ptr -- Pointer to the unit to delete. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/21/1994 JLB : Created. * + *=============================================================================================*/ +void UnitClass::operator delete(void *ptr) +{ + if (ptr) { + ((UnitClass *)ptr)->IsActive = false; + } + Units.Free((UnitClass *)ptr); +} + + +/*********************************************************************************************** + * UnitClass::~UnitClass -- Destructor for unit objects. * + * * + * This destructor will lower the unit count for the owning house as well as inform any * + * other units in communication, that this unit is about to leave reality. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/15/1994 JLB : Created. * + *=============================================================================================*/ +UnitClass::~UnitClass(void) +{ + if (GameActive && Class) { + + /* + ** If there are any cargo members, delete them. + */ + while (Is_Something_Attached()) { + delete Detach_Object(); + } + + Limbo(); + } + if (GameActive && Team) Team->Remove(this); +} + + +/*********************************************************************************************** + * UnitClass::UnitClass -- Constructor for units. * + * * + * This constructor for units will initialize the unit into the game * + * system. It will be placed in all necessary tracking lists. The initial condition will * + * be in a state of limbo. * + * * + * INPUT: classid -- The type of unit to create. * + * * + * house -- The house owner of this unit. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/21/1994 JLB : Created. * + *=============================================================================================*/ +UnitClass::UnitClass(UnitType classid, HousesType house) : + TarComClass(classid, house) +{ + Flagged = HOUSE_NONE; + Reload = 0; + Ammo = Class->MaxAmmo; + IsCloakable = Class->IsCloakable; + if (Class->IsAnimating) Set_Rate(Options.Normalize_Delay(3)); + + /* + ** Keep count of the number of units created. + */ + if (GameToPlay == GAME_INTERNET){ + House->UnitTotals->Increment_Unit_Total((int)classid); + } +} + + +/*********************************************************************************************** + * UnitClass::Active_Click_With -- Intercepts the active click to see if deployment is possible* + * * + * This routine intercepts the active click operation. It check to see if this is a self * + * deployment request (MCV's have this ability). If it is, then the object is initiated * + * to self deploy. In the other cases, it passes the operation down to the lower * + * classes for processing. * + * * + * INPUT: action -- The action requested of the unit. * + * * + * object -- The object that the mouse pointer is over. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +void UnitClass::Active_Click_With(ActionType action, ObjectClass * object) +{ + Validate(); + if (action != What_Action(object)) { + switch (action) { + case ACTION_SABOTAGE: + case ACTION_CAPTURE: + action = ACTION_ATTACK; + break; + + case ACTION_ENTER: + action = ACTION_MOVE; + break; + + default: + action = ACTION_NONE; + break; + } + } + TarComClass::Active_Click_With(action, object); +} + + +void UnitClass::Active_Click_With(ActionType action, CELL cell) {TarComClass::Active_Click_With(action, cell);}; + + +/*********************************************************************************************** + * UnitClass::Enter_Idle_Mode -- Unit enters idle mode state. * + * * + * This routine is called when the unit completes one mission but does not have a clear * + * follow up mission to perform. In such a case, the unit should enter a default idle * + * state. This idle state varies depending on what the current internal computer * + * settings of the unit is as well as what kind of unit it is. * + * * + * INPUT: initial -- Is this called when the unit just leaves a factory or is initially * + * or is initially placed on the map? * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + * 06/03/1994 JLB : Fixed to handle non-combat vehicles. * + * 06/18/1995 JLB : Allows a harvester to stop harvesting. * + *=============================================================================================*/ +void UnitClass::Enter_Idle_Mode(bool initial) +{ + Validate(); + MissionType order; + + /* + ** A movement mission without a NavCom would be pointless to have a radio contact since + ** no radio coordination occurs on a just a simple movement mission. + */ + if (Mission == MISSION_MOVE && !Target_Legal(NavCom)) { + Transmit_Message(RADIO_OVER_OUT); + } + + if (Class->Primary == WEAPON_NONE) { + if (Class->IsToHarvest) { + if (!In_Radio_Contact() && Mission != MISSION_HARVEST) { + if (initial || !House->IsHuman || Map[Coord_Cell(Coord)].Land_Type() == LAND_TIBERIUM) { + order = MISSION_HARVEST; + } else { + order = MISSION_GUARD; + } + Assign_Target(TARGET_NONE); + Assign_Destination(TARGET_NONE); + } else { + return; + } + } else { + if (IsALoaner && Class->IsTransporter && Is_Something_Attached() && !Team) { + order = MISSION_UNLOAD; + } else { + order = MISSION_GUARD; + Assign_Target(TARGET_NONE); + Assign_Destination(TARGET_NONE); + } + } + } else { + if (Target_Legal(TarCom)) { + order = MISSION_ATTACK; + } else { + if (Target_Legal(NavCom)) { + order = MISSION_MOVE; + } else { + if (GameToPlay == GAME_NORMAL || House->IsHuman) { + order = MISSION_GUARD; + } else { + if (GameToPlay != GAME_NORMAL) { + order = MISSION_TIMED_HUNT; + } else { + order = MISSION_HUNT; + } + } + } + } + } + Assign_Mission(order); +} + + +/*********************************************************************************************** + * UnitClass::Find_LZ -- Maintenance function for transport units. * + * * + * This is a maintenance function for transport units. It checks to see if it is loaded * + * with cargo, but it doesn't know where to put it. In such a case, the unit must look * + * for a landing zone (LZ) to unload the cargo. This routine should be called every so * + * often. Typically, calling this routine is controlled by the scripts. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + *=============================================================================================*/ +void UnitClass::Find_LZ(void) +{ + Validate(); + CELL cell; // Map exit cell number. + + if (*this == UNIT_HOVER) { + + if (!IsRotating && Is_Something_Attached() && !Target_Legal(NavCom)) { + cell = Map.Calculated_Cell(SOURCE_BEACH, House->Class->House); + Assign_Destination(::As_Target(cell)); + } + } +} + + +/*********************************************************************************************** + * UnitClass::Unload_Hovercraft_Process -- Handles unloading hovercraft transport. * + * * + * This is a maintenance routine to handle the special operations necessary for a * + * hovercraft transport. This routine checks to see if special unloading operations are * + * necessary and performs them. These operations can include unloading the transported * + * object, finding a new beach cell, and rotating to a convenient unloading facing. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Has the transport finished its unloading mission? This is true after the * + * hovercraft has completely dispatched its cargo. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/31/1994 JLB : Created. * + * 07/31/1995 JLB : Second infantry unloaded MUST be the one tethered. * + *=============================================================================================*/ +bool UnitClass::Unload_Hovercraft_Process(void) +{ + Validate(); + bool unloaded = false; + FootClass *unit; // The unit to be unloaded. + CELL cell; // Cell to unload to. + + /* + ** If the hovercraft is currently waiting for the last unit + ** to completely unload, then don't do anything. + */ + if (IsTethered || IsRotating) { + return(false); + } + + if (Is_Something_Attached()) { + + /* + ** Only unload if the hovercraft has reached the beach. + */ + if (!Target_Legal(NavCom)) { + + cell = Coord_Cell(Adjacent_Cell(Coord, Dir_Facing(PrimaryFacing.Current()))); + + unit = (FootClass *)Attached_Object(); + + Mark(MARK_UP); + if (Map.In_Radar(cell) && !Map[cell].Cell_Unit()) { + + if (unit->Can_Enter_Cell(cell, FACING_NONE) == MOVE_OK) { + + /* + ** Place all the transported units onto the map. + */ + int count = 0; + bool first = true; + FootClass * secondary = 0; + while (Attached_Object()) { + FootClass * u = (FootClass *)Detach_Object(); + + if (!first && !secondary) secondary = u; + first = false; + + /* + ** Place the unit on the map and start it heading in the right direction. + */ + ScenarioInit++; + if (u->Unlimbo(Coord_Add(Coord & 0xFF00FF00L, StoppingCoordAbs[count]), DIR_N)) { + u->Assign_Mission(MISSION_MOVE); + u->NavCom = ::As_Target(cell); + u->Path[0] = Dir_Facing(u->PrimaryFacing.Current()); + u->Path[1] = FACING_NONE; + u->Set_Speed(0x80); + u->IsUnloading = true; + + } else { + + /* + ** Couldn't unlimbo for some strange reason. Kill the unit off. + */ + delete u; + } + ScenarioInit--; + count++; + } + if (secondary) unit = secondary; + + /* + ** Establish radio contact bond with the transport + ** hovercraft. This bond tells the hovercraft to + ** not move until the unit has completely unloaded. + */ + if (Transmit_Message(RADIO_HELLO, unit) == RADIO_ROGER) { + Transmit_Message(RADIO_UNLOAD); + } + Mark(MARK_DOWN); + Map.Layer[LAYER_GROUND].Sort(); + Map.Layer[LAYER_GROUND].Sort(); + Map.Layer[LAYER_GROUND].Sort(); + return(false); + } else { + + /* + ** If the attached object cannot unload because the desired destination + ** cell is impassable, then abort the landing. This is faked by pretending + ** that the unload was successful. The controlling routine will cause + ** the transport to leave. + */ +Mark(MARK_DOWN); +return(false); +// return(true); + } + } + + Mark(MARK_DOWN); + } + } else { + return(true); + } + + return(unloaded); +} + + +/*********************************************************************************************** + * UnitClass::Goto_Clear_Spot -- Finds a clear spot to deploy. * + * * + * This routine is used by the MCV to find a clear spot to deploy. If a clear spot * + * is found, then the MCV will assign that location to its navigation computer. This only * + * occurs if the MCV isn't already heading toward a spot. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Is the located at a spot where it can deploy? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/27/1994 JLB : Created. * + *=============================================================================================*/ +bool UnitClass::Goto_Clear_Spot(void) +{ + Validate(); + Mark(MARK_UP); + if (!Target_Legal(NavCom) && BuildingTypeClass::As_Reference(STRUCT_CONST).Legal_Placement(Coord_Cell(Coord))) { + Mark(MARK_DOWN); + return(true); + } + + if (!Target_Legal(NavCom)) { + /* + ** This scan table is skewed to north scanning only. This should + ** probably be converted to a more flexible method. + */ + static int _offsets[] = { + -MAP_CELL_W*1, + -MAP_CELL_W*2, + -(MAP_CELL_W*2)+1, + -(MAP_CELL_W*2)-1, + -MAP_CELL_W*3, + -(MAP_CELL_W*3)+1, + -(MAP_CELL_W*3)-1, + -(MAP_CELL_W*3)+2, + -(MAP_CELL_W*3)-2, + -MAP_CELL_W*4, + -(MAP_CELL_W*4)+1, + -(MAP_CELL_W*4)-1, + -(MAP_CELL_W*4)+2, + -(MAP_CELL_W*4)-2, + 0 + }; + int *ptr; + + ptr = &_offsets[0]; + while (*ptr) { + CELL cell = Coord_Cell(Coord)+*ptr++; + + if (BuildingTypeClass::As_Reference(STRUCT_CONST).Legal_Placement(cell)) { + Assign_Destination(::As_Target(cell)); + break; + } + } + } + Mark(MARK_DOWN); + + return(false); +} + + +/*********************************************************************************************** + * UnitClass::Try_To_Deploy -- The unit attempts to "deploy" at current location. * + * * + * Certain units have the ability to deploy into a building. When this routine is called * + * for one of those units, it will attempt to deploy at its current location. If the unit * + * is in motion to a destination or it isn't one of the special units that can deploy or * + * it isn't allowed to deploy at this location for some reason it won't deploy. In all * + * other cases, it will begin to deploy and once it begins only a player abort action will * + * stop it. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was deployment begun? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/18/1994 JLB : Created. * + *=============================================================================================*/ +bool UnitClass::Try_To_Deploy(void) +{ + Validate(); + if (!Target_Legal(NavCom) && !IsRotating) { + if (*this == UNIT_MCV) { + + /* + ** Determine if it is legal to deploy at this location. If not, tell the + ** player. + */ + Mark(MARK_UP); + if (!BuildingTypeClass::As_Reference(STRUCT_CONST).Legal_Placement(Coord_Cell(Adjacent_Cell(Center_Coord(), FACING_NW)))) { + if (PlayerPtr == House) { + Speak(VOX_DEPLOY); + } + Mark(MARK_DOWN); + IsDeploying = false; + return(false); + } + Mark(MARK_DOWN); + + /* + ** If the unit is not facing the correct direction, then start it rotating + ** toward the right facing, but still flag it as if it had deployed. This is + ** because it will deploy as soon as it reaches the correct facing. + */ + if (PrimaryFacing.Current() != DIR_SW) { + Do_Turn(DIR_SW); +// PrimaryFacing.Set_Desired(DIR_SW); + IsDeploying = true; + return(true); + } + + /* + ** Since the unit is already facing the correct direction, actually do the + ** deploy logic. If for some reason this cannot occur, then don't delete the + ** unit, just mark it as not deploying. + */ + Mark(MARK_UP); + BuildingClass * building = new BuildingClass(STRUCT_CONST, House->Class->House); + if (building) { + if (building->Unlimbo(Adjacent_Cell(Coord, FACING_NW))) { + + /* + ** Always reveal the construction yard to the player that owned the + ** mobile construction vehicle. + */ + building->Revealed(House); + + /* + ** Force the newly placed construction yard to be in the same strength + ** ratio as the MCV that deployed into it. + */ + int ratio = Health_Ratio(); + building->Strength = Fixed_To_Cardinal(building->Class->MaxStrength, ratio); + /* + ** Force the MCV to drop any flag it was carrying. This will also set + ** the owner house's flag home cell (since the house's FlagHome is + ** presumably 0 at this point). + */ + Stun(); + delete this; + return(true); + } else { + + /* + ** Could not deploy the construction yard at this location! Just revert + ** back to normal "just sitting there" mode and await further instructions. + */ + delete building; + } + } + Mark(MARK_DOWN); + IsDeploying = false; + } + } + return(false); +} + + +/*********************************************************************************************** + * UnitClass::Per_Cell_Process -- Performs operations necessary on a per cell basis. * + * * + * This routine will perform the operations necessary that occur when a unit is at the * + * center of a cell. These operations could entail deploying into a construction yard, * + * radioing a transport unit, and looking around for the enemy. * + * * + * INPUT: center -- Is the unit safely at the center of a cell? If it is merely "close" * + * to the center, then this parameter will be false. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/18/1994 JLB : Created. * + * 06/17/1995 JLB : Handles case when building says "NO!" * + * 06/30/1995 JLB : Gunboats head back and forth now. * + *=============================================================================================*/ +void UnitClass::Per_Cell_Process(bool center) +{ + Validate(); + CELL cell = Coord_Cell(Coord); + TechnoClass * whom; + HousesType house; + + /* + ** If this is a unit that is driving onto a building then the unit must enter + ** the building as the final step. + */ + whom = Contact_With_Whom(); + if (IsTethered && whom && center) { + if (whom->What_Am_I() == RTTI_BUILDING && Mission == MISSION_ENTER) { + if (whom == Map[cell].Cell_Building()) { + switch (Transmit_Message(RADIO_IM_IN, whom)) { + case RADIO_ROGER: + break; + + case RADIO_ATTACH: + Mark(MARK_UP); + SpecialFlag = true; + Limbo(); + SpecialFlag = false; + whom->Attach(this); + return; + + default: + Scatter(true); + break; + } + } + } + } + + /* + ** When breaking away from a transport object or building, possibly + ** scatter or otherwise begin normal unit operations. + */ + if (IsTethered && center && Mission != MISSION_ENTER) { + if (Transmit_Message(RADIO_UNLOADED) == RADIO_RUN_AWAY) { + if (!Target_Legal(NavCom)) { + Scatter(0, true); + } + } else { + if (*this == UNIT_HARVESTER) { + if (Target_Legal(ArchiveTarget)) { + Assign_Mission(MISSION_HARVEST); + Assign_Destination(ArchiveTarget); + ArchiveTarget = TARGET_NONE; + } else { + + /* + ** Since there is no place to go, move away to clear + ** the pad for another harvester. + */ + if (!Target_Legal(NavCom)) { + Scatter(0, true); + } + } + } + } + } + +#ifdef OBSOLETE + /* + ** If this unit is on a teather, then cut it at this time so that + ** the "parent" unit is free to proceed. Note that the parent + ** unit might actually be a building. + */ + if (IsTethered && center && !Map[cell].Cell_Building()) { + if (!Tiberium || *this != UNIT_HARVESTER) { + Transmit_Message(RADIO_UNLOADED); + if (*this == UNIT_HARVESTER) { + if (Target_Legal(ArchiveTarget)) { + Assign_Mission(MISSION_HARVEST); + Assign_Destination(ArchiveTarget); + ArchiveTarget = TARGET_NONE; + } else { + + /* + ** Since there is no place to go, move away to clear + ** the pad for another harvester. + */ + Scatter(0, true); + } + } + } + } +#endif + +#ifdef OBSOLETE + /* + ** If the unit is at the center of the repair facility, and that was his + ** destination, then start him repairing. + */ + if (center && !IsRepairing) { + BuildingClass * b = As_Building(NavCom); + if (b && *b==STRUCT_REPAIR && Coord == b->Center_Coord()) { + NavCom = 0; + IsRepairing = true; + Transmit_Message(RADIO_REPAIR_BEGIN_ANIM); + } + } +#endif + + /* + ** Check to see if this is merely the end of a rotation for the MCV as it is + ** preparing to deploy. In this case, it should begin its deploy process. + */ + if (center && IsDeploying) { + Try_To_Deploy(); + if (!IsActive) return; // Unit no longer exists -- bail. + } + + /* + ** If this is a loaner unit and is is off the edge of the + ** map, then it gets eliminated. That is, unless it is carrying cargo. This means that + ** it is probably carrying an incoming reinforcement and it should not be eliminated. + */ + if (center && IsALoaner && !Map.In_Radar(cell)) { + if (IsReturning || !Is_Something_Attached()) { + if (*this == UNIT_GUNBOAT) { + CELL cell = Coord_Cell(Coord); + if (Cell_X(cell) <= Map.MapCellX) { + Assign_Mission(MISSION_HUNT); + Assign_Destination(::As_Target(XY_Cell(Map.MapCellX+Map.MapCellWidth, Cell_Y(cell)))); + Set_Speed(255); + PrimaryFacing = DIR_E; + } else { + Assign_Mission(MISSION_HUNT); + Assign_Destination(::As_Target(XY_Cell(Map.MapCellX-1, Cell_Y(cell)))); + Set_Speed(255); + PrimaryFacing = DIR_W; + } + Mark(MARK_DOWN); + } else { + Mark(MARK_DOWN); + Stun(); + delete this; + return; + } + } + } + +#ifdef OBSOLETE + /* + ** Destroy any crushable wall that is driven over by a tracked vehicle. + */ + CellClass * cellptr = &Map[cell]; + if (center && Class->Speed == SPEED_TRACK && cellptr->Overlay != OVERLAY_NONE) { + OverlayTypeClass const * optr = &OverlayTypeClass::As_Reference(cellptr->Overlay); + + if (optr->IsCrushable) { + cellptr->Reduce_Wall(100); + cellptr->Reduce_Wall(100); + cellptr->Reduce_Wall(100); + cellptr->Reduce_Wall(100); + cellptr->Reduce_Wall(100); + cellptr->Reduce_Wall(100); + } + } +#endif + + /* + ** Check to see if crushing of any unfortunate infantry is warranted. + */ + Overrun_Square(Coord_Cell(Coord), false); + + /* + ** The unit performs looking around at this time. If the + ** unit moved further than one square during the last track + ** move, don't do an incremental look. Do a full look around + ** instead. + */ + if (IsPlanningToLook) { + IsPlanningToLook = false; + Look(false); + } else { + Look(true); + } + + /* + ** Act on new orders if the unit is at a good position to do so. + */ + if (center) { + Commence(); + } + + /* + ** Certain units require some setup time after they come to a halt. + */ + if (Special.IsDefenderAdvantage && /*center &&*/ !Target_Legal(NavCom) && Path[0] == FACING_NONE) { + if (*this == UNIT_MLRS || *this == UNIT_ARTY || *this == UNIT_MSAM) { + Arm = Rearm_Delay(false)*2; + } + } + + /* + ** If there is a house flag here, then this unit just might pick it up. + */ + if (center && Flagged == HOUSE_NONE) { + + if (Map[cell].IsFlagged && Map[cell].Owner != House->Class->House) { + HouseClass::As_Pointer(Map[cell].Owner)->Flag_Attach(this); + } + } + + /* + ** If this is the unit's own flag-home-cell and the unit is carrying + ** a flag, destroy the house of the flag the unit is carrying. + */ + if (Flagged != HOUSE_NONE) { + + /* + ** If this vehicle is carrying your flag, then it will reveal the + ** map for you as well as itself. This gives you and opportunity to + ** attack the unit. + */ + if (!IsOwnedByPlayer && Flagged == PlayerPtr->Class->House) { + Map.Sight_From(Coord_Cell(Coord), Class->SightRange, true); + } + + /* + ** If the flag reaches the home cell for the player, then the flag's + ** owner will be destroyed. + */ + if (cell == HouseClass::As_Pointer(Owner())->FlagHome && center) { + house = Flagged; // Flag_Remove will clear 'Flagged', so save it + HouseClass::As_Pointer(house)->Flag_Remove(As_Target(),true); + HouseClass::As_Pointer(house)->Flag_To_Die(); + } + } + + TarComClass::Per_Cell_Process(center); +} + + +/*********************************************************************************************** + * UnitClass::Draw_It -- Draws a unit object. * + * * + * This routine is the one that actually draws a unit object. It displays the unit * + * according to its current state flags and centered at the location specified. * + * * + * INPUT: x,y -- The X and Y coordinate of where to draw the unit. * + * * + * window -- The clipping window to use. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/20/1994 JLB : Created. * + * 06/27/1994 JLB : Takes a window parameter. * + * 08/15/1994 JLB : Removed infantry support. * + * 01/07/1995 JLB : Harvester animation support. * + * 07/08/1995 JLB : Uses general purpose draw routine. * + *=============================================================================================*/ +void UnitClass::Draw_It(int x, int y, WindowNumberType window) +{ + Validate(); + int shapenum; // Working shape number. + void const *shapefile; // Working shape file pointer. + int facing = Facing_To_32(PrimaryFacing.Current()); + int tfacing = Facing_To_32(SecondaryFacing.Current()); + + /* + ** Verify the legality of the unit class. + */ + shapefile = Class->Get_Image_Data(); + if (!shapefile) return; + + /* + ** If drawing of this unit is not explicitly prohibited, then proceed + ** with the render process. + */ + if (Visual_Character() != VISUAL_HIDDEN) { + + /* + ** For eight facing units, adjust the facing number accordingly. + */ + if (Class->IsPieceOfEight) { + facing = Dir_Facing(PrimaryFacing.Current()); + } + + /* + ** Some units have only four facings, but are equipped with a turret + ** that has 32 facings. + */ + if (Class->IsChunkyShape) { + + /* + ** Special wake drawing occurs here. + */ + if (*this == UNIT_GUNBOAT) { + int shapestart; + int xx,yy; + + xx = x; + yy = y; + switch (Dir_Facing(PrimaryFacing.Current())) { + case FACING_NE: + case FACING_E: + case FACING_SE: + shapenum = UnitClass::BodyShape[tfacing]+96; + shapestart = 0; + //xx -= 4; + break; + + case FACING_W: + default: + shapenum = UnitClass::BodyShape[tfacing]; + shapestart = 6; + xx += 4; + break; + } + + CC_Draw_Shape(UnitTypeClass::WakeShapes, shapestart + (Fetch_Stage() % 6), xx-1, yy+3, window, SHAPE_CENTER|SHAPE_WIN_REL); + + if (Health_Ratio() < 0x0080) shapenum += 32; + if (Health_Ratio() < 0x0040) shapenum += 32; + + } else { + + /* + ** Special hovercraft shape is ALWAYS N/S. + */ + shapenum = 0; + + //shapenum = ((UnitClass::BodyShape[facing] + 4) / 8) & 0x03; + } + + } else { + + /* + ** Fetch the harvesting animation stage as appropriate. + */ + if (IsHarvesting && !PrimaryFacing.Is_Rotating() && !NavCom && !IsDriving) { + static char _hstage[6] = { + 0, 1, 2, 3, 2, 1 + }; + shapenum = 32 + (((UnitClass::BodyShape[facing]+2)/4)*4)+_hstage[Fetch_Stage()%sizeof(_hstage)]; + } else { + shapenum = UnitClass::BodyShape[facing]; + if (Class->IsAnimating) { + shapenum = Fetch_Stage(); + } + if (Class->IsPieceOfEight) { + shapenum = 0; + if (facing) shapenum = UnitClass::BodyShape[24+facing]; + if (IsDriving) shapenum = Fetch_Stage() + 16 + shapenum*8; + if (IsFiring) shapenum = Fetch_Stage() + 80 + shapenum*( ((*this == UNIT_TREX) || (*this == UNIT_RAPT)) ? 8 : 12); + } else { + + /* + ** Door opening and closing animation must be handled carefully. There are only + ** certain directions where this door animation will work. + */ + if (!Is_Door_Closed() && (PrimaryFacing == DIR_NW || PrimaryFacing == DIR_NE)) { + if (PrimaryFacing == DIR_NE) { + shapenum = 32; + } else { + if (PrimaryFacing == DIR_NW) { + shapenum = 35; + } + } + shapenum += Door_Stage(); + } + } + } + } + + /* + ** The artillery unit should have its entire body recoil when it fires. + */ + if (*this == UNIT_ARTY && IsInRecoilState) { + Recoil_Adjust(PrimaryFacing.Current(), x, y); + } + + /* + ** Actually perform the draw. Overlay an optional shimmer effect as necessary. + */ +//if (*this == UNIT_HOVER) { +// Mono_Printf("Display hover %p %d.\n", shapefile, shapenum); +//} + Techno_Draw_Object(shapefile, shapenum, x, y, window); + + /* + ** If there is a rotating radar dish, draw it now. + */ + if (Class->IsRadarEquipped) { + shapenum = 32 + (Frame % 32); + Techno_Draw_Object(shapefile, shapenum, x, y-5, window); + } + + /* + ** If there is a turret, then it must be rendered as well. This may include + ** firing animation if required. + */ + if (!Class->IsChunkyShape && Class->IsTurretEquipped) { + int x1 = x; + int y1 = y; + + /* + ** Determine which turret shape to use. This depends on if there + ** is any firing animation in progress. + */ + shapenum = TechnoClass::BodyShape[tfacing]+32; + + /* + ** The shape to use for the rocket launcher is dependant on the + ** ammo remaining. + */ + if (*this == UNIT_MSAM) { + if (Ammo == 0) shapenum += 64; + if (Ammo == 1) shapenum += 32; + } + + /* + ** A recoiling turret moves "backward" one pixel. + */ + if (IsInRecoilState) { + Recoil_Adjust(SecondaryFacing.Current(), x1, y1); + } + + /* + ** The Mobile SAM and the Missile Launchers need their turrets moved based + ** on the facing of the vehicle. + */ + if (*this == UNIT_MSAM || *this == UNIT_MLRS) { + Turret_Adjust(PrimaryFacing, x1, y1); + } + if (*this == UNIT_JEEP || *this == UNIT_BUGGY) { + y1 -= 4; + } + + /* + ** Actually perform the draw. Overlay an optional shimmer effect as necessary. + */ + Techno_Draw_Object(shapefile, shapenum, x1, y1, window); + } + + /* + ** If this unit has "piggy back" unit(s), then render it at the same time. + */ + if (*this == UNIT_HOVER && Is_Something_Attached()) { + TechnoClass * u = (TechnoClass *)Attached_Object(); + + int counter = 0; + for (;;) { + int x1,y1; + + if (Map.Coord_To_Pixel(Coord_Add(Coord_Add(Coord, 0xFF80FF80L), StoppingCoordAbs[counter++]), x1, y1)) { + u->Draw_It(x1, y1, WINDOW_TACTICAL); + } + if (!u->Next) break; + u = (TechnoClass *)u->Next; + } + } + } + + /* + ** If this unit is carrying the flag, then draw that on top of everything else. + */ + if (Flagged != HOUSE_NONE) { + CC_Draw_Shape(MixFileClass::Retrieve("FLAGFLY.SHP"), Frame % 14, x, y, window, SHAPE_CENTER|SHAPE_FADING|SHAPE_GHOST, HouseClass::As_Pointer(Flagged)->Remap_Table(false, false), Map.UnitShadow); + } + + TarComClass::Draw_It(x, y, window); +} + + +/*********************************************************************************************** + * UnitClass::Goto_Tiberium -- Search for and head toward nearest available Tiberium patch. * + * * + * This routine is used to move a harvester to a place where it can load up with * + * Tiberium. It will return true only if it can start harvesting. Otherwise, it sets * + * the navigation computer toward the nearest Tiberium and lets the unit head there * + * automatically. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Is it located directly over a Tiberium patch? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + *=============================================================================================*/ +bool UnitClass::Tiberium_Check(CELL ¢er, int x, int y) +{ + Validate(); + /* + ** If the specified offset from the origin will cause it + ** to spill past the map edge, then abort this cell check. + */ + if (Cell_X(center)+x < Map.MapCellX) return(false); + if (Cell_X(center)+x >= Map.MapCellX+Map.MapCellWidth) return(false); + if (Cell_Y(center)+y < Map.MapCellY) return(false); + if (Cell_Y(center)+y >= Map.MapCellY+Map.MapCellHeight) return(false); + + center = XY_Cell(Cell_X(center)+x, Cell_Y(center)+y); + + if ((GameToPlay != GAME_NORMAL || (!IsOwnedByPlayer || Map[center].IsVisible))) { + if (!Map[center].Cell_Techno() && Map[center].Land_Type() == LAND_TIBERIUM) { + return(true); + } + } + return(false); +} + + +bool UnitClass::Goto_Tiberium(void) +{ + Validate(); + if (!Target_Legal(NavCom)) { + CELL center = Coord_Cell(Center_Coord()); + if (Map[center].Land_Type() == LAND_TIBERIUM) { + return(true); + } else { + + /* + ** Perform a ring search outward from the center. + */ + for (int radius = 1; radius < 32; radius++) { + for (int x = -radius; x <= radius; x++) { + CELL cell = center; + if (Tiberium_Check(cell, x, -radius)) { + Assign_Destination(::As_Target(cell)); + return(false); + } + + cell = center; + if (Tiberium_Check(cell, x, +radius)) { + Assign_Destination(::As_Target(cell)); + return(false); + } + + cell = center; + if (Tiberium_Check(cell, -radius, x)) { + Assign_Destination(::As_Target(cell)); + return(false); + } + + + cell = center; + if (Tiberium_Check(cell, +radius, x)) { + Assign_Destination(::As_Target(cell)); + return(false); + } + } + } + } + } + + return(false); +} + + +/*********************************************************************************************** + * UnitClass::Harvesting -- Harvests tiberium at the current location. * + * * + * This routine is used to by the harvester to harvest Tiberium at the current location. * + * When harvesting is complete, this routine will return true. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Is harvesting complete? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + *=============================================================================================*/ +bool UnitClass::Harvesting(void) +{ + Validate(); + CELL cell = Coord_Cell(Coord); + CellClass * ptr = &Map[cell]; + + /* + ** Keep waiting if still heading toward a spot to harvest. + */ + if (Target_Legal(NavCom)) return(true); + + if (Tiberium_Load() < 0x0100 && ptr->Land_Type() == LAND_TIBERIUM) { + + /* + ** Lift some Tiberium from the ground. Try to lift a complete + ** "level" of Tiberium. A level happens to be 6 steps. If there + ** is a partial level, then lift that instead. Never lift more + ** than the harvester can carry. + */ + int reducer = (ptr->OverlayData % 6) + 1; + reducer = ptr->Reduce_Tiberium(MIN(reducer, UnitTypeClass::STEP_COUNT-Tiberium)); + Tiberium += reducer; + Set_Stage(0); + Set_Rate(2); + + } else { + + /* + ** If the harvester is stopped on a non Tiberium field and the harvester + ** isn't loaded with Tiberium, then no further action can be performed + ** by this logic routine. Bail with a failure and thus cause a branch to + ** a better suited logic processor. + */ + Set_Stage(0); + Set_Rate(0); + return(false); + } + return(true); +} + + +/*********************************************************************************************** + * UnitClass::Mission_Unload -- Handles unloading cargo. * + * * + * This is the AI control sequence for when a transport desires to unload its cargo and * + * then exit the map. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the delay before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + *=============================================================================================*/ +int UnitClass::Mission_Unload(void) +{ + Validate(); + enum { + INITIAL_CHECK, + MANEUVERING, + OPENING_DOOR, + UNLOADING, + CLOSING_DOOR + }; + DirType dir; + CELL cell; + + switch (Class->Type) { + case UNIT_APC: + switch (Status) { + case INITIAL_CHECK: + dir = Desired_Load_Dir(NULL, cell); + if (How_Many() && cell != 0) { + Do_Turn(dir); + Status = MANEUVERING; + return(1); + } else { + Assign_Mission(MISSION_GUARD); + } + break; + + case MANEUVERING: + if (!IsRotating) { + APC_Open_Door(); + if (Is_Door_Opening()) { + Status = OPENING_DOOR; + return(1); + } + } + break; + + case OPENING_DOOR: + if (Is_Door_Open()) { + Status = UNLOADING; + return(1); + } else { + if (!Is_Door_Opening()) { + Status = INITIAL_CHECK; + } + } + break; + + case UNLOADING: + if (How_Many()) { + FootClass * passenger = Detach_Object(); + + if (passenger) { + DirType toface = DIR_S + PrimaryFacing; + bool placed = false; + + for (FacingType face = FACING_N; face < FACING_COUNT; face++) { + DirType newface = toface + face; + CELL newcell = Adjacent_Cell(Coord_Cell(Coord), newface); + + if (passenger->Can_Enter_Cell(newcell) == MOVE_OK) { + ScenarioInit++; + passenger->Unlimbo(Coord_Move(Coord, newface, 0x0080), newface); + ScenarioInit--; + passenger->Assign_Mission(MISSION_MOVE); + passenger->Assign_Destination(::As_Target(newcell)); + placed = true; + break; + } + } + + /* + ** If the attached unit could NOT be deployed, then re-attach + ** it and then bail out of this deploy process. + */ + if (!placed) { + Attach(passenger); + Status = CLOSING_DOOR; + } + } + } else { + Status = CLOSING_DOOR; + } + break; + + /* + ** Close APC door in preparation for normal operation. + */ + case CLOSING_DOOR: + if (Is_Door_Open()) { + APC_Close_Door(); + } + if (Is_Door_Closed()) { + Assign_Mission(MISSION_GUARD); + } + break; + } + break; + + case UNIT_MCV: + switch (Status) { + case 0: + Path[0] = FACING_NONE; + Status = 1; + break; + + case 1: + if (!IsDriving) { + Try_To_Deploy(); + if (IsDeploying) { + Status = 2; + } else { + Assign_Mission(MISSION_GUARD); + } + } + break; + + case 2: + break; + } + return(1); + + case UNIT_HOVER: + switch (Status) { + case 0: + if (Unload_Hovercraft_Process()) { + Status = 1; + } + break; + + /* + ** Hovercraft always leave the map when they finish + ** unloading. + */ + case 1: + if (Team) { + Team->Remove(this); + } + Exit_Map(); + break; + } + break; + } + return(TICKS_PER_SECOND); +} + + +/*********************************************************************************************** + * UnitClass::Mission_Harvest -- Handles the harvesting process used by harvesters. * + * * + * This is the AI process used by harvesters when they are doing their harvesting action. * + * This entails searching for nearby Tiberium, heading there, harvesting, and then * + * returning to a refinery for unloading. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the delay before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + * 06/21/1995 JLB : Force guard mode if no Tiberium found. * + *=============================================================================================*/ +int UnitClass::Mission_Harvest(void) +{ + Validate(); + enum { + LOOKING, + HARVESTING, + FINDHOME, + HEADINGHOME, + GOINGTOIDLE, + }; + + /* + ** A non-harvesting type unit will just sit still if it is given the harvest mission. This + ** allows combat units to act "brain dead". + */ + if (!Class->IsToHarvest) return(TICKS_PER_SECOND*30); + + switch (Status) { + + /* + ** Go and find a Tiberium field to harvest. + */ + case LOOKING: + IsHarvesting = false; + if (Goto_Tiberium()) { + IsHarvesting = true; + Set_Rate(2); + Set_Stage(0); + Status = HARVESTING; + return(1); + } else { + + /* + ** If the harvester isn't on Tiberium and it is not heading toward Tiberium, then + ** force it to go into guard mode. This will prevent the harvester from repeatedly + ** searching for Tiberium. + */ + if (!Target_Legal(NavCom)) { + Status = GOINGTOIDLE; + return(TICKS_PER_SECOND*15); + } + } + break; + + /* + ** Harvest at current location until full or Tiberium exhausted. + */ + case HARVESTING: + if (!Harvesting()) { + IsHarvesting = false; + if (Tiberium_Load() == 0x0100) { + Status = FINDHOME; + ArchiveTarget = ::As_Target(Coord_Cell(Coord)); + } else { + if (!Goto_Tiberium() && !Target_Legal(NavCom)) { + ArchiveTarget = TARGET_NONE; + Status = FINDHOME; + } else { + Status = HARVESTING; + IsHarvesting = true; + } + } + return(1); + } + break; + + /* + ** Find and head to refinery. + */ + case FINDHOME: + if (!Target_Legal(NavCom)) { + + /* + ** Find nearby refinery and head to it? + */ + BuildingClass * nearest = Find_Docking_Bay(STRUCT_REFINERY, false); + + /* + ** Since the refinery said it was ok to load, establish radio + ** contact with the refinery and then await docking orders. + */ + if (nearest && Transmit_Message(RADIO_HELLO, nearest) == RADIO_ROGER) { + Status = HEADINGHOME; + } else { + ScenarioInit++; + nearest = Find_Docking_Bay(STRUCT_REFINERY, false); + ScenarioInit--; + if (nearest) { + Assign_Destination(::As_Target(nearest->Nearby_Location(this))); + } + } + } + break; + + /* + ** In communication with refinery so that it will successfully dock and + ** unload. If, for some reason, radio contact was lost, then hunt for + ** another refinery to unload at. + */ + case HEADINGHOME: + Assign_Mission(MISSION_ENTER); + return(1); + + case GOINGTOIDLE: + Assign_Mission(MISSION_GUARD); + break; + + } + return(TICKS_PER_SECOND); +} + + +/*********************************************************************************************** + * UnitClass::Mission_Hunt -- This is the AI process for aggressive enemy units. * + * * + * Computer controlled units must be intelligent enough to find enemies as well as to * + * attack them. This AI process will handle both the simple attack process as well as the * + * scanning for enemy units to attack. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the delay before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1994 JLB : Created. * + *=============================================================================================*/ +int UnitClass::Mission_Hunt(void) +{ + Validate(); + if (*this == UNIT_MCV) { + switch (Status) { + + /* + ** This stage handles locating a convenient spot, rotating to face the correct + ** direction and then commencing the deployment operation. + */ + case 0: + if (Goto_Clear_Spot()) { + if (Try_To_Deploy()) { + Status = 1; + } + } + break; + + /* + ** This stage watchdogs the deployment operation and if for some reason, the deployment + ** is aborted (the IsDeploying flag becomes false), then it reverts back to hunting for + ** a convenient spot to deploy. + */ + case 1: + if (!IsDeploying) { + Status = 0; + } + break; + } + } else { + + if (*this == UNIT_GUNBOAT) { + if (!Target_Legal(NavCom)) { + if (PrimaryFacing == DIR_W) { + Assign_Destination( ::As_Target(XY_Cell(Map.MapCellX-1, Cell_Y(Coord_Cell(Coord)))) ); + } else { + Assign_Destination( ::As_Target(XY_Cell(Map.MapCellX+Map.MapCellWidth, Cell_Y(Coord_Cell(Coord)))) ); + } + Set_Speed(255); + } + if (!Speed) { + Set_Speed(255); + } + if (!Target_Legal(TarCom) || !In_Range(TarCom, 0)) { + Target_Something_Nearby(THREAT_AREA); + } + } else { + return(TarComClass::Mission_Hunt()); + } + } + return(TICKS_PER_SECOND); +} + + +/*********************************************************************************************** + * UnitClass::Look -- Perform map revelation from a unit's position. * + * * + * Reveal the map around the specified unit with the sighting range * + * associated with the specified unit. * + * * + * INPUT: incremental -- If looking is to process only the cells in the * + * outer ring of the unit's search radius, then * + * set this parameter to true. This method is * + * quite a bit faster than processing all cells, * + * but must be used with caution. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/08/1992 JLB : Created. * + * 03/08/1994 JLB : Added incremental option. * + * 04/15/1994 JLB : Converted to member function. * + *=============================================================================================*/ +void UnitClass::Look(bool incremental) +{ + Validate(); + if (!IsInLimbo && IsOwnedByPlayer) { + int sight = Class->SightRange; + + if (sight) { + Map.Sight_From(Coord_Cell(Coord), sight, (*this == UNIT_GUNBOAT) ? false : incremental); + } + } +} + + +/*********************************************************************************************** + * UnitClass::Overlap_List -- Determines overlap list for units. * + * * + * The unit overlap list is used to keep track of which cells are to * + * be marked for redraw. This is critical in order to keep the units * + * displayed correctly. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the overlap list pointer for the unit at its * + * present position. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/26/1994 JLB : Created. * + * 06/19/1994 JLB : Uses Coord_Spillable_List function. * + *=============================================================================================*/ +short const * UnitClass::Overlap_List(void) const +{ + Validate(); + static short const _gunboat[] = {-3, -2, 2, 3, REFRESH_EOL}; + int size; + + /* + ** The gunboat is a special case. + */ + if (*this == UNIT_GUNBOAT) { + return(&_gunboat[0]); + } + + size = ICON_PIXEL_W; + if (IsSelected || IsFiring) { + size += 24; + } + if (IsSelected || Class->IsGigundo || IsAnimAttached) { + size = ICON_PIXEL_W*2; + } + return(Coord_Spillage_List(Coord, size)+1); +} + + +#ifdef NEVER +/********************************************************************************************* * + * UnitClass::Blocking_Object -- Determines how a object blocks a unit * + * * + * This routine is used by the Can_Enter_Cell logic when an object is in the desired cell * + * and it needs to know if that causes blockage. If blocked, this routine will return why. * + * * + * INPUT: TechnoClass * pointer to object that is blocking unit * + * * + * CELL the cell the unit is being blocked in * + * * + * OUTPUT: MoveBitType the way that the object is blocking the unit * + * * + * HISTORY: * + * 06/08/1995 PWG : Created. * + *=============================================================================================*/ +MoveBitType UnitClass::Blocking_Object(TechnoClass const *techno, CELL cell) const +{ + Validate(); + /* + ** There are some extra checks we need to make if the techno is a unit + */ + bool unit = (techno->What_Am_I() == RTTI_INFANTRY || techno->What_Am_I() == RTTI_UNIT); + CellClass const * cellptr = &Map[cell]; + + if (House->Is_Ally(techno)) { + + if (techno == Contact_With_Whom() && IsTethered) { + return(MOVE_BIT_OK); + } + + if (unit) { + /* + ** If the unit in question has a destination than we should + ** be prepared to wait for the unit to get out of our way. + */ + if (((FootClass *)techno)->NavCom != TARGET_NONE) { + int face = Dir_Facing(PrimaryFacing); + int techface = Dir_Facing(((FootClass const *)techno)->PrimaryFacing) ^4; + if (face != techface && Distance((AbstractClass const *)techno) > 0x1FF) { + return(MOVE_BIT_MOVING_BLOCK); + } else { +// Mono_Printf("Move No!\r"); + return(MOVE_BIT_NO); + } + } + + return(MOVE_BIT_TEMP); + } + } else { + + /* + ** If its an enemy unit, things are dealt with a little differently + */ + if (unit) { + +#ifdef NEVER + /* + ** If this is an enemy unit and we are not doing a find path then + ** we need to tell the unit to uncloak just in case it is a + ** stealth tank. + */ + if (!IsFindPath) { + techno->Do_Uncloak(); + } +#endif + + /* + ** Can we just run it over? + */ + if (techno->Class_Of().IsCrushable && (cellptr->Flag.Composite & 0xE0) == 0 && Class->IsCrusher) { + + /* + ** Now lets run it over. + */ + return(MOVE_BIT_OK); + } + + /* + ** If the object is cloaked, then consider it passable for findpath purposes, + ** but not so for all other cases. + */ + if (techno->Cloak == CLOAKED) { + if (House == techno->House) return(MOVE_BIT_NO); + if (IsFindPath) return(MOVE_BIT_OK); + return(MOVE_BIT_CLOAK); + } + + /* + ** If our vehicle is weapon equipped, then report that the cell occupier + ** needs only to be destroyed in order to make the cell passable. + */ + if (Class->Primary != WEAPON_NONE) { + return(MOVE_BIT_DESTROYABLE); + } + } + } + return(MOVE_BIT_NO); +} +#endif + + +/*********************************************************************************************** + * UnitClass::Can_Enter_Cell -- Determines cell entry legality. * + * * + * Use this routine to determine if the unit can enter the cell * + * specified and given the direction of entry specified. Typically, * + * this is used when determining unit travel path. * + * * + * INPUT: cell -- The cell to examine. * + * * + * facing -- The facing that the unit would enter the specified * + * cell. If this value is -1, then don't consider * + * facing when performing the check. * + * * + * OUTPUT: Returns the reason why it couldn't enter the cell or MOVE_OK if movement is * + * allowed. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/07/1992 JLB : Created. * + * 04/16/1994 JLB : Converted to member function. * + * 07/04/1995 JLB : Allowed to drive on building trying to enter it. * + *=============================================================================================*/ +MoveType UnitClass::Can_Enter_Cell(CELL cell, FacingType ) const +{ + Validate(); + CellClass const * cellptr = &Map[cell]; + + if ((unsigned)cell >= MAP_CELL_TOTAL) return(MOVE_NO); + + /* + ** The gunboat can always move. This prevents it from trying to move around possible hover + ** craft blockage. + */ + if (*this == UNIT_GUNBOAT) return(MOVE_OK); + + /* + ** Moving off the edge of the map is not allowed unless + ** this is a loaner vehicle. + */ + if (IsLocked && !IsALoaner && !ScenarioInit && !Map.In_Radar(cell)) { + return(MOVE_NO); + } + + MoveType retval = MOVE_OK; + + /* + ** Certain vehicles can drive over walls. Check for this case and + ** and return the appropriate flag. Other units treat walls as impassable. + */ + if (cellptr->Overlay != OVERLAY_NONE) { + OverlayTypeClass const * optr = &OverlayTypeClass::As_Reference(cellptr->Overlay); + + if (optr->IsCrate && !House->IsHuman) { + return(MOVE_NO); + } + + if (optr->IsWall) { + if (Class->Primary != WEAPON_NONE) { + WarheadTypeClass const * whead = &Warheads[BulletTypeClass::As_Reference(Weapons[Class->Primary].Fires).Warhead]; + + if (whead->IsWallDestroyer || (whead->IsWoodDestroyer && optr->IsWooden)) { + if (!House->IsHuman && !House->Is_Ally(cellptr->Owner)) { + if (retval < MOVE_DESTROYABLE) retval = MOVE_DESTROYABLE; + } else { + return(MOVE_NO); + } + } else { + return(MOVE_NO); + } + } else { + return(MOVE_NO); + } + } + } + + /* + ** If the cell is out and out impassable because of underlying terrain, then + ** return this immutable fact. + */ +#ifdef ADVANCED + if (retval != MOVE_DESTROYABLE && !Ground[cellptr->Land_Type()].Cost[Class->Speed]) { +#else + if (!Ground[cellptr->Land_Type()].Cost[Class->Speed]) { +#endif + return(MOVE_NO); + } + + /* + ** Loop through all of the objects in the square setting a bit + ** for how they affect movement. + */ + bool crushable = false; + ObjectClass *obj = cellptr->Cell_Occupier(); + while (obj) { + + if (obj != this) { + + /* + ** Always allow entry if trying to move on a cell with + ** authorization from the occupier. + */ + if (obj == Contact_With_Whom() && (IsTethered || (obj->What_Am_I() == RTTI_BUILDING && *((BuildingClass *)obj) == STRUCT_REPAIR))) { + return(MOVE_OK); + } + + bool is_moving = (obj->What_Am_I() == RTTI_INFANTRY || obj->What_Am_I() == RTTI_UNIT) && Target_Legal(((FootClass *)obj)->NavCom); + + if (House->Is_Ally(obj)) { + if (is_moving) { + int face = Dir_Facing(PrimaryFacing); + int techface = Dir_Facing(((FootClass const *)obj)->PrimaryFacing) ^4; + if (face == techface && Distance((AbstractClass const *)obj) <= 0x1FF) { + return(MOVE_NO); + } + if (retval < MOVE_MOVING_BLOCK) retval = MOVE_MOVING_BLOCK; + } else { + if (obj->What_Am_I() == RTTI_BUILDING) return(MOVE_NO); + if (retval < MOVE_TEMP) retval = MOVE_TEMP; + } + } else { + + /* + ** Cloaked enemy objects are not considered if this is a Find_Path() + ** call. + */ + if (!obj->Is_Techno() || ((TechnoClass *)obj)->Cloak != CLOAKED) { + + /* + ** If this unit can crush infantry, and there is an enemy infantry in the + ** cell, don't consider the cell impassible. This is true even if the unit + ** doesn't contain a legitimate weapon. + */ + if (!Class->IsCrusher || !obj->Class_Of().IsCrushable) { + + /* + ** Any non-allied blockage is considered impassible if the unit + ** is not equipped with a weapon. + */ + if (Class->Primary == WEAPON_NONE) return(MOVE_NO); + + /* + ** Some kinds of terrain are considered destroyable if the unit is equipped + ** with the weapon that can destroy it. Otherwise, the terrain is considered + ** impassable. + */ + switch (obj->What_Am_I()) { + case RTTI_TERRAIN: + if (((TerrainClass *)obj)->Class->IsFlammable && + BulletTypeClass::As_Reference(Weapons[Class->Primary].Fires).Warhead == WARHEAD_FIRE) { + + if (retval < MOVE_DESTROYABLE) retval = MOVE_DESTROYABLE; + } else { + return(MOVE_NO); + } + break; + + default: + if (retval < MOVE_DESTROYABLE) retval = MOVE_DESTROYABLE; + break; + } + } else { + crushable = true; + } + } else { + if (retval < MOVE_CLOAK) retval = MOVE_CLOAK; + } + } + } + + /* + ** Move to next object in chain. + */ + obj = obj->Next; + } + + /* + ** If some allied object has reserved the cell, then consider the cell + ** as blocked by a moving object. + */ + if (retval == MOVE_OK && !crushable && cellptr->Flag.Composite) { + + /* + ** If reserved by a vehicle, then consider this blocked terrain. + */ + if (cellptr->Flag.Occupy.Vehicle) { + retval = MOVE_MOVING_BLOCK; + } else { + if (cellptr->InfType != HOUSE_NONE && House->Is_Ally(cellptr->InfType)) { + retval = MOVE_MOVING_BLOCK; + } else { + + /* + ** Enemy infantry have reserved the cell. If this unit can crush + ** infantry, consider the cell passable. If not, then consider the + ** cell destroyable if it has a weapon. If neither case applies, then + ** this vehicle should avoid the cell altogether. + */ + if (!Class->IsCrusher) { + if (Class->Primary != WEAPON_NONE) { + retval = MOVE_DESTROYABLE; + } else { + return(MOVE_NO); + } + } + } + } + } + + /* + ** If its ok to move into the cell because we can crush whats in the cell, then + ** make sure no one else is already moving into the cell to crush something. + */ + if (retval == MOVE_OK && crushable && cellptr->Flag.Occupy.Vehicle) { + +#ifdef ADVANCED + /* + ** However, if the cell is occupied by a crushable vehicle, then we can + ** never be sure if some other friendly vehicle is also trying to crush + ** the cell at the same time. In the case of a crushable vehicle in the + ** cell, then allow entry. + */ + if (!cellptr->Cell_Unit() || !cellptr->Cell_Unit()->Class->IsCrushable) { + return(MOVE_MOVING_BLOCK); + } +#else + return(MOVE_MOVING_BLOCK); +#endif + } + + /* + ** Return with the most severe reason why this cell would be impassable. + */ + return(retval); +} + + +/*********************************************************************************************** + * UnitClass::Init -- Clears all units for scenario preparation. * + * * + * This routine will zero out the unit list and unit objects. This routine is typically * + * used in preparation for a new scenario load. All units are guaranteed to be eliminated * + * by this routine. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/15/1994 JLB : Created. * + *=============================================================================================*/ +void UnitClass::Init(void) +{ + UnitClass * ptr; + + Units.Free_All(); + + ptr = new UnitClass(); + VTable = ((void **)(((char *)ptr) + sizeof(AbstractClass) - 4))[0]; + delete ptr; +} + + +/*********************************************************************************************** + * UnitClass::Target_Coord -- The coordinate to use when targeting this unit. * + * * + * Sometimes the coordinate to use when targeting an object is not the same as its center * + * coordinate. This is especially true for boats since leading their movement is needed * + * in order have any chance of hitting. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the coordinate to fire upon when attacking the unit. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/19/1994 JLB : Created. * + *=============================================================================================*/ +COORDINATE UnitClass::Target_Coord(void) const +{ + Validate(); +// if (*this == UNIT_GUNBOAT) { +// return(Coord_Move(Coord, PrimaryFacing.Current(), 0x0080)); +// } + return(TarComClass::Center_Coord()); +} + + +/*********************************************************************************************** + * UnitClass::Scatter -- Causes the unit to travel to a nearby safe cell. * + * * + * This routine is called when the unit discovers that it should get out of the "hot seat" * + * and move to an adjacent cell. Since the safety of the adjacent cell is not determined * + * before the move begins, it will appear that the unit is just scattering (which it * + * should). * + * * + * INPUT: threat -- The coordinate of the source of the threat. The unit will try to move * + * roughly away from the threat. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/25/1994 JLB : Created. * + *=============================================================================================*/ +void UnitClass::Scatter(COORDINATE threat, bool forced) +{ + Validate(); + if (*this != UNIT_GUNBOAT && *this != UNIT_HOVER) { + if ((!Target_Legal(TarCom) && !Target_Legal(NavCom)) || forced || Random_Pick(1, 4) == 1) { + FacingType toface; + FacingType newface; + CELL newcell; + + if (threat) { + toface = Dir_Facing(Direction8(threat, Coord)); + toface = toface + (Random_Pick(0, 2)-1); + } else { + toface = Dir_Facing(PrimaryFacing.Current()); + } + + for (FacingType face = FACING_N; face < FACING_COUNT; face++) { + newface = toface + face; + newcell = Adjacent_Cell(Coord_Cell(Coord), newface); + + if (Map.In_Radar(newcell) && Can_Enter_Cell(newcell) == MOVE_OK) { + Assign_Destination(::As_Target(newcell)); + } + } + } + } +} + + +/*********************************************************************************************** + * UnitClass::Stop_Driver -- Handles removing occupation bits when driving stops. * + * * + * This routine will remove the "reservation" flag (if present) when the vehicle is * + * required to stop movement. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the vehicle stopped? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/22/1994 JLB : Created. * + *=============================================================================================*/ +bool UnitClass::Stop_Driver(void) +{ + Validate(); + + /* + ** We only need to do something if the vehicle is actually going + ** somewhere. + */ + if (Head_To_Coord()) { + + /* + ** Safe off whether the vehicle is down or not so we know whether + ** we have to put it back down. + */ + int temp = IsDown; + + /* + ** If the vehicle is down, pick it up so it doesnt interfere with + ** our flags. + */ + if (temp) { + Mark(MARK_UP); + } + + /* + ** Call the drive class function which will let us release the + ** reserved track. + */ + Mark_Track(Head_To_Coord(), MARK_UP); + + /* + ** If it was down it should be down when we are done. + */ + if (temp) { + Mark(MARK_DOWN); + } + } + return(TarComClass::Stop_Driver()); +} + + +/*********************************************************************************************** + * UnitClass::Start_Driver -- Starts driving and reserves destination cell. * + * * + * This routine will start the vehicle moving by marking the destination cell as * + * reserved. Cells must be reserved in this fashion or else they might become occupied as * + * the vehicle is proceeding toward it. * + * * + * INPUT: headto -- The location where the vehicle will be heading. * + * * + * OUTPUT: bool; Was the vehicle started to move? Failure could be the result of the cell * + * being occupied. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/22/1994 JLB : Created. * + *=============================================================================================*/ +bool UnitClass::Start_Driver(COORDINATE & headto) +{ + Validate(); + if (TarComClass::Start_Driver(headto)) { + Mark_Track(headto, MARK_DOWN); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * UnitClass::Limbo -- Prepares vehicle and then limbos it. * + * * + * This routine removes the occupation bits for the vehicle and also handles cleaning up * + * any vehicle reservation bits. After this, it then proceeds with limboing the unit. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Was the vehicle limboed? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/22/1994 JLB : Created. * + *=============================================================================================*/ +bool UnitClass::Limbo(void) +{ + Validate(); + if (!IsInLimbo) { + Stop_Driver(); + } + return(TarComClass::Limbo()); +} + + +/*********************************************************************************************** + * UnitClass::Response_Select -- Voice feedback when selecting the unit. * + * * + * This is the voice to play when the unit is selected. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/30/1994 JLB : Created. * + *=============================================================================================*/ +void UnitClass::Response_Select(void) +{ + Validate(); + static VocType _response[] = { + VOC_VEHIC, + VOC_UNIT, + VOC_YESSIR, + VOC_YESSIR, + VOC_YESSIR, + VOC_AWAIT + }; + VocType response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; + + if (*this == UNIT_TRIC || *this == UNIT_TREX || *this == UNIT_RAPT || *this == UNIT_STEG) { + response = VOC_DINOYES; + } + + if (AllowVoice) { + Sound_Effect(response, 0, -(Units.ID(this)+1)); + } +} + + +/*********************************************************************************************** + * UnitClass::Response_Move -- Voice feedback when ordering the unit to move. * + * * + * This plays the audio feedback when ordering this unit to move to a new destination. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/30/1994 JLB : Created. * + *=============================================================================================*/ +void UnitClass::Response_Move(void) +{ + Validate(); + static VocType _response[] = { + VOC_MOVEOUT, + VOC_MOVEOUT, + VOC_MOVEOUT, + VOC_ACKNOWL, + VOC_AFFIRM, + VOC_AFFIRM + }; + VocType response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; + + if (*this == UNIT_TRIC || *this == UNIT_TREX || *this == UNIT_RAPT || *this == UNIT_STEG) { + response = VOC_DINOMOUT; + } + + if (AllowVoice) { + Sound_Effect(response, 0, -(Units.ID(this)+1)); + } +} + + +/*********************************************************************************************** + * UnitClass::Response_Attack -- Voice feedback when ordering the unit to attack a target. * + * * + * This plays the audio feedback when ordering this unit to attack. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 12/30/1994 JLB : Created. * + *=============================================================================================*/ +void UnitClass::Response_Attack(void) +{ + Validate(); + static VocType _response[] = { + VOC_AFFIRM, + VOC_ACKNOWL, + VOC_YESSIR, + VOC_YESSIR, + VOC_YESSIR + }; + VocType response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; + + if (*this == UNIT_TRIC || *this == UNIT_TREX || *this == UNIT_RAPT || *this == UNIT_STEG) { + response = VOC_DINOMOUT; + } + + if (AllowVoice) { + Sound_Effect(response, 0, -(Units.ID(this)+1)); + } +} + + +/*********************************************************************************************** + * UnitClass::What_Action -- Determines what action would occur if clicked on object. * + * * + * Use this function to determine what action would likely occur if the specified object * + * were clicked on while this unit was selected as current. This function controls, not * + * only the action to perform, but indirectly controls the cursor shape to use as well. * + * * + * INPUT: object -- The object that to check for against "this" object. * + * * + * OUTPUT: Returns with the default action to perform. If no clear action can be determined, * + * then ACTION_NONE is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/11/1995 JLB : Created. * + *=============================================================================================*/ +ActionType UnitClass::What_Action(ObjectClass * object) const +{ + Validate(); + ActionType action = TarComClass::What_Action(object); + + /* + ** If the unit doesn't have a weapon, but can crush the object, then consider + ** the object as a movable location. + */ + if (action == ACTION_ATTACK && !Can_Player_Fire()) { + if (Class->IsCrusher && object->Class_Of().IsCrushable) { + action = ACTION_MOVE; + } else { + action = ACTION_SELECT; + } + } + + /* + ** Don't allow special deploy action unless there is something to deploy. + */ + if (action == ACTION_SELF) { + if (*this != UNIT_MCV) { + if (!Class->IsTransporter || !How_Many()) { + action = ACTION_NONE; + } + } else { + ((ObjectClass &)(*this)).Mark(MARK_UP); + if (!BuildingTypeClass::As_Reference(STRUCT_CONST).Legal_Placement(Coord_Cell(Adjacent_Cell(Center_Coord(), FACING_NW)))) { + action = ACTION_NONE; + } + ((ObjectClass &)(*this)).Mark(MARK_DOWN); + } + } + + /* + ** Special return to friendly refinery action. + */ + if (IsOwnedByPlayer && object->Is_Techno() && ((TechnoClass const *)object)->House->Is_Ally(this)) { + if (object->What_Am_I() == RTTI_BUILDING && ((UnitClass *)this)->Transmit_Message(RADIO_CAN_LOAD, (TechnoClass*)object) == RADIO_ROGER) { + action = ACTION_ENTER; + } + } + + /* + ** Special return to friendly repair factory action. + */ + if (IsOwnedByPlayer && action == ACTION_SELECT && object->What_Am_I() == RTTI_BUILDING) { + BuildingClass * building = (BuildingClass *)object; + if (building->Class->Type == STRUCT_REPAIR && !building->In_Radio_Contact() && !building->Is_Something_Attached()) { + action = ACTION_MOVE; + } + } + + return(action); +} + + +ActionType UnitClass::What_Action(CELL cell) const +{ + Validate(); + ActionType action = TarComClass::What_Action(cell); + if (action == ACTION_MOVE && Map[cell].Land_Type() == LAND_TIBERIUM && Class->IsToHarvest) { + return(ACTION_HARVEST); + } + return(action); +} + + +/*********************************************************************************************** + * UnitClass::Can_Player_Move -- Determines if the player is legally allowed to move it. * + * * + * Use this routine to see if the player can move this object. If the player can move the * + * object, even only in theory, then this function returns true. In all other cases, such * + * as for enemy units, gunboats, or hovercraft, it returns false. * + * * + * INPUT: none * + * * + * OUTPUT: bool; Can the player give this object a movement order? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 01/19/1995 JLB : Created. * + *=============================================================================================*/ +bool UnitClass::Can_Player_Move(void) const +{ + Validate(); + return(TarComClass::Can_Player_Move() && *this != UNIT_GUNBOAT && *this != UNIT_HOVER); +} + + +/*********************************************************************************************** + * UnitClass::Read_INI -- Reads units from scenario INI file. * + * * + * This routine is used to read all the starting units from the * + * scenario control INI file. The units are created and placed on the * + * map by this routine. * + * * + * INI entry format: * + * Housename, Typename, Strength, Coord, Facingnum, Missionname, Triggername * + * * + * INPUT: buffer -- Pointer to the loaded scenario INI file. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/24/1994 JLB : Created. * + *=============================================================================================*/ +void UnitClass::Read_INI(char *buffer) +{ + UnitClass *unit; // Working unit pointer. + char *tbuffer; // Accumulation buffer of unit IDs. + HousesType inhouse; // Unit house. + UnitType classid; // Unit class. + int len; // Length of data in buffer. + char buf[128]; + + len = strlen(buffer) + 2; + tbuffer = buffer + len; + + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-len, buffer); + while (*tbuffer != '\0') { + + WWGetPrivateProfileString(INI_Name(), tbuffer, NULL, buf, sizeof(buf)-1, buffer); + inhouse = HouseTypeClass::From_Name(strtok(buf, ",")); + if (inhouse != HOUSE_NONE) { + classid = UnitTypeClass::From_Name(strtok(NULL, ",")); + + if (classid != UNIT_NONE) { + + unit = new UnitClass(classid, inhouse); + if (unit) { + + /* + ** Read the raw data. + */ + int strength = atoi(strtok(NULL, ",\r\n")); + COORDINATE coord = Cell_Coord((CELL)atoi(strtok(NULL, ",\r\n"))); + DirType dir = (DirType)atoi(strtok(NULL, ",\r\n")); + MissionType mission = MissionClass::Mission_From_Name(strtok(NULL, ",\n\r")); + unit->Trigger = TriggerClass::As_Pointer(strtok(NULL,",\r\n")); + if (unit->Trigger) { + unit->Trigger->AttachCount++; + } + + if (unit->Unlimbo(coord, dir)) { + unit->Strength = Fixed_To_Cardinal(unit->Class->MaxStrength, strength); + if (GameToPlay == GAME_NORMAL || unit->House->IsHuman) { + unit->Assign_Mission(mission); + unit->Commence(); + } else { + unit->Enter_Idle_Mode(); + } + + /* + ** The gunboat is a special case: It must "drive" off the edge of the map. + ** Just pick the map edge that it is facing and set that as the destination + ** of the drive. + */ + if (*unit == UNIT_GUNBOAT) { + unit->PrimaryFacing.Set_Desired(DIR_W); + unit->PrimaryFacing.Set_Current(DIR_W); + unit->Assign_Mission(MISSION_HUNT); + unit->Commence(); + unit->Assign_Destination( ::As_Target(XY_Cell(Map.MapCellX-1, Cell_Y(Coord_Cell(unit->Coord))))); + } + + } else { + + /* + ** If the unit could not be unlimboed, then this is a catastrophic error + ** condition. Delete the unit. + */ + delete unit; + } + } + } + } + tbuffer += strlen(tbuffer)+1; + } +} + + +/*********************************************************************************************** + * UnitClass::Write_INI -- Writes all the units out to an INI file. * + * * + * This routine writes all of the units in the game out to an INI file. This is used * + * in the scenario editor when the game needs to be saved. * + * * + * INI entry format: * + * Housename, Typename, Strength, Coord, Facingnum, Missionname, Triggername * + * * + * INPUT: buffer -- A pointer to the loaded INI file staging area. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/28/1994 JLB : Created. * + *=============================================================================================*/ +void UnitClass::Write_INI(char *buffer) +{ + int index; + char uname[10]; + char buf[128]; + char *tbuffer; // Accumulation buffer of unit IDs. + + /* + ** First, clear out all existing unit data from the ini file. + */ + tbuffer = buffer + strlen(buffer) + 2; + WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-strlen(buffer), buffer); + while (*tbuffer != '\0') { + WWWritePrivateProfileString(INI_Name(), tbuffer, NULL, buffer); + tbuffer += strlen(tbuffer)+1; + } + + /* + ** Write the unit data out. + */ + for (index = 0; index < Units.Count(); index++) { + UnitClass * unit; + + unit = Units.Ptr(index); + if (!unit->IsInLimbo && unit->IsActive) { + + sprintf(uname, "%03d", index); + sprintf(buf, "%s,%s,%d,%u,%d,%s,%s", + unit->House->Class->IniName, + unit->Class->IniName, + unit->Health_Ratio(), + Coord_Cell(unit->Coord), + unit->PrimaryFacing.Current(), + MissionClass::Mission_Name(unit->Mission), + unit->Trigger ? unit->Trigger->Get_Name() : "None" + ); + WWWritePrivateProfileString(INI_Name(), uname, buf, buffer); + } + } +} + + +/*********************************************************************************************** + * UnitClass::Exit_Repair -- Drive the unit off the repair facility. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 04/03/1995 BWG : Created. * + *=============================================================================================*/ +#define XYCELL(x,y) (y*MAP_CELL_W+x) +void UnitClass::Exit_Repair(void) +{ + Validate(); + int i; + CELL cell; + bool found = false; + static short const ExitRepair[] = { + XYCELL(0,-2), + XYCELL(1,-1), + XYCELL(2, 0), + XYCELL(1, 1), + XYCELL(0, 2), + XYCELL(-1,1), + XYCELL(-2,0), + XYCELL(-1,-1) + }; + + cell = Coord_Cell(Coord) + ExitRepair[Dir_Facing(PrimaryFacing.Current())]; + if (Can_Enter_Cell(cell) == MOVE_OK) found = true; + + if (!found) for (i=0; i<8; i++) { + cell = Coord_Cell(Coord) + ExitRepair[i]; + if (Can_Enter_Cell(cell) == MOVE_OK) { + found = true; + break; + } + } + if (found) { + DirType dir = Direction(cell); + + Assign_Mission(MISSION_MOVE); + Assign_Destination(::As_Target(cell)); + } +} + + +/*********************************************************************************************** + * UnitClass::Mission_Guard -- Special guard mission override processor. * + * * + * This routine will intercept the guard mission and if it is for a hovercraft, assign * + * it the unload mission instead. This prevents the hovercraft from being stuck in the * + * water if something unexpected causes it to drop into guard mode. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the time delay before this command is executed again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/08/1995 JLB : Created. * + * 05/08/1995 JLB : Fixes gunboat problems. * + *=============================================================================================*/ +int UnitClass::Mission_Guard(void) +{ + Validate(); + if (*this == UNIT_HOVER) { + if (Is_Something_Attached()) { + Assign_Mission(MISSION_UNLOAD); + Find_LZ(); + } else { + Exit_Map(); + } + return(TICKS_PER_SECOND); + } + + if (*this == UNIT_GUNBOAT) { + Assign_Mission(MISSION_HUNT); + return(TICKS_PER_SECOND); + } + + if (*this == UNIT_HARVESTER && !House->IsHuman) { + Assign_Mission(MISSION_HARVEST); + return(TICKS_PER_SECOND); + } + return(TarComClass::Mission_Guard()); +} + + +/*********************************************************************************************** + * UnitClass::Mission_Move -- Handles special move mission overrides. * + * * + * This routine intercepts the normal move mission and if a gunboat is being processed, * + * changes its mission to hunt. This is an attempt to keep the gunboat on the hunt mission * + * regardless of what the player did. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the number of ticks before this routine should be called again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/09/1995 JLB : Created. * + *=============================================================================================*/ +int UnitClass::Mission_Move(void) +{ + Validate(); + IsHarvesting = false; + + /* + ** Always make sure that that transport door is closed if the vehcile is moving. + */ + if (!Is_Door_Closed()) { + APC_Close_Door(); + } + + /* + ** Gunboats must always have the hunt mission. + */ + if (*this == UNIT_GUNBOAT) { + Assign_Mission(MISSION_HUNT); + return(TICKS_PER_SECOND); + } + return(TarComClass::Mission_Move()); +} + + +/*********************************************************************************************** + * UnitClass::Desired_Load_Dir -- Determines the best cell and facing for loading. * + * * + * This routine examines the unit and adjacent cells in order to find the best facing * + * for the transport and best staging cell for the potential passengers. This location is * + * modified by adjacent cell passability and direction of the potential passenger. * + * * + * INPUT: passenger -- Pointer to the potential passenger. * + * * + * moveto -- Reference to the cell number that specifies where the potential * + * passenger should move to first. * + * * + * OUTPUT: Returns with the direction the transport should face before opening the transport * + * door. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1995 JLB : Created. * + *=============================================================================================*/ +DirType UnitClass::Desired_Load_Dir(ObjectClass * passenger, CELL & moveto) const +{ + Validate(); + /* + ** Determine the ideal facing that provides the least resistance. This would be the direction + ** of the potential passenger or the current transport facing if it is going to unload. + */ + DirType faceto; + if (passenger) { + faceto = Direction(passenger); + } else { + faceto = PrimaryFacing.Current() + DIR_S; + } + + /* + ** Sweep through the adjacent cells in order to find the best candidate. + */ + FacingType bestdir; + int bestval = -1; + for (FacingType face = FACING_N; face < FACING_COUNT; face++) { + int value = 0; + CELL cellnum = Adjacent_Cell(Coord_Cell(Coord), face); + + /* + ** Base the initial value of the potential cell according to whether the passenger is + ** allowed to enter the cell. If it can't, then give such a negative value to the + ** cell so that it is prevented from ever choosing that cell for load/unload. + */ + if (passenger) { + value = (passenger->Can_Enter_Cell(cellnum) == MOVE_OK || Coord_Cell(passenger->Coord) == cellnum) ? 128 : -128; + } else { + CellClass * cell = &Map[cellnum]; + if (Ground[cell->Land_Type()].Cost[SPEED_FOOT] == 0 || cell->Flag.Occupy.Building || cell->Flag.Occupy.Vehicle || cell->Flag.Occupy.Monolith || (cell->Flag.Composite & 0x01F) == 0x01F) { + value = -128; + } else { + if (cell->Cell_Techno() && !House->Is_Ally(cell->Cell_Techno())) { + value = -128; + } else { + value = 128; + } + } + } + + /* + ** Give more weight to the cells that require the least rotation of the transport or the + ** least roundabout movement for the potential passenger. + */ + value -= (int)ABS(Dir_Diff(Facing_Dir(face), faceto)); + if (face == FACING_S) { + value -= 100; + } + if (face == FACING_SW || face == FACING_SE) value += 64; + + /* + ** If the value for the potiential cell is greater than the last recorded potential + ** value, then record this cell as the best candidate. + */ + if (bestval == -1 || value > bestval) { + bestval = value; + bestdir = face; + } + } + + /* + ** If a suitable direction was found, then return with the direction value. + */ + moveto = 0; + if (bestval > 0) { + static DirType _desired_to_actual[FACING_COUNT] = {DIR_S, DIR_SW, DIR_NW, DIR_NW, DIR_NE, DIR_NE, DIR_NE, DIR_SE}; + + moveto = Adjacent_Cell(Coord_Cell(Coord), bestdir); + return(_desired_to_actual[bestdir]); + } + return(DIR_S); +} + + +/*********************************************************************************************** + * UnitClass::Mission_Attack -- Handles the mission attack logic. * + * * + * This routine intercepts the normal mission attack logic. If a gunboat is assigned the * + * attack mission then it must be converted back to a hunt mission. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the time before calling this routine again. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1995 JLB : Created. * + *=============================================================================================*/ +int UnitClass::Mission_Attack(void) +{ + Validate(); + if (*this == UNIT_GUNBOAT) { + Assign_Mission(MISSION_HUNT); + return(TICKS_PER_SECOND); + } + return(TarComClass::Mission_Attack()); +} + + +/*********************************************************************************************** + * UnitClass::Flag_Attach -- Attaches a house flag to this unit. * + * * + * This routine will attach a house flag to this unit. * + * * + * INPUT: house -- The house that is having its flag attached to it. * + * * + * OUTPUT: Was the house flag successfully attached to this unit? * + * * + * WARNINGS: A unit can only carry one flag at a time. This might be a reason for failure * + * of this routine. * + * * + * HISTORY: * + * 05/23/1995 JLB : Created. * + *=============================================================================================*/ +bool UnitClass::Flag_Attach(HousesType house) +{ + Validate(); + if (house != HOUSE_NONE && Flagged == HOUSE_NONE) { + Flagged = house; + Mark(MARK_CHANGE); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * UnitClass::Flag_Remove -- Removes the house flag from this unit. * + * * + * This routine will remove the house flag that is attached to this unit. * + * * + * INPUT: none * + * * + * OUTPUT: Was the flag successfully removed? * + * * + * WARNINGS: This routine doesn't put the flag into a new location. That operation must * + * be performed or else the house flag will cease to exist. * + * * + * HISTORY: * + * 05/23/1995 JLB : Created. * + *=============================================================================================*/ +bool UnitClass::Flag_Remove(void) +{ + Validate(); + if (Flagged != HOUSE_NONE) { + Flagged = HOUSE_NONE; + Mark(MARK_CHANGE); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * UnitClass::Stun -- Stuns the unit in preparation for unit removal. * + * * + * This routine intercepts the stun operation for the unit and if there is a house flag * + * attached, it will drop it to the ground. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 05/23/1995 JLB : Created. * + *=============================================================================================*/ +void UnitClass::Stun(void) +{ + Validate(); + if (Flagged != HOUSE_NONE) { + HouseClass::As_Pointer(Flagged)->Flag_Attach(Coord_Cell(Coord)); + } + TarComClass::Stun(); +} + + +/*********************************************************************************************** + * UnitClass::Pip_Count -- Fetchs the number of pips to display on unit. * + * * + * This routine is used to fetch the number of "fullness" pips to display on the unit. * + * This will either be the number of passengers or the percentage full (in 1/5ths) of * + * a harvester. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with the number of pips to draw on this unit. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +int UnitClass::Pip_Count(void) const +{ + Validate(); + if (Class->IsTransporter) { + return(How_Many()); + } + if (Class->IsToHarvest) { + return(Fixed_To_Cardinal(UnitTypeClass::FULL_LOAD_CREDITS/100, Tiberium_Load())); + } + return(0); +} + + +/*********************************************************************************************** + * UnitClass::APC_Close_Door -- Closes an APC door. * + * * + * This routine will initiate closing of the APC door. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +void UnitClass::APC_Close_Door(void) +{ + Validate(); + Close_Door(10, 2); +} + + +/*********************************************************************************************** + * UnitClass::APC_Open_Door -- Opens an APC door. * + * * + * This routine will initiate opening of the APC door. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/25/1995 JLB : Created. * + *=============================================================================================*/ +void UnitClass::APC_Open_Door(void) +{ + Validate(); + if (!IsDriving && !IsRotating) { + if (PrimaryFacing == DIR_NW || PrimaryFacing == DIR_NE) { + Open_Door(10, 2); + } else { + Open_Door(1, 2); + } + } +} + + +/*********************************************************************************************** + * UnitClass::Remap_Table -- Fetches the remap table to use for this object. * + * * + * Use this routine to determine the rendering remap table to use for this object. The * + * remap table is normally the unit remap table, except for the MCV and the Harvestor. * + * These units use the building remap table since these units become part of the building * + * animation. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a pointer to the remap table to use for this unit. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/08/1995 JLB : Created. * + *=============================================================================================*/ +void const * UnitClass::Remap_Table(void) +{ + Validate(); + if (*this == UNIT_MCV || *this == UNIT_HARVESTER) { + return(House->Remap_Table(IsBlushing, false)); + } + return(TarComClass::Remap_Table()); +} + + +/*********************************************************************************************** + * UnitClass::Crew_Type -- Fetches the kind of crew that this object produces. * + * * + * When a unit is destroyed, a crew member might be generated. This routine will return * + * with the infantry type to produce for this unit. This routine will be called for every * + * survivor that is generated. * + * * + * INPUT: none * + * * + * OUTPUT: Returns with a suggested infantry type to generate as a survivor from this unit. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +InfantryType UnitClass::Crew_Type(void) const +{ + Validate(); + if (Class->Primary == WEAPON_NONE) { + if (Random_Pick(0, 1) == 0) { + return(INFANTRY_C1); + } else { + return(INFANTRY_C7); + } + } + return(TarComClass::Crew_Type()); +} + + +/*********************************************************************************************** + * UnitClass::What_Am_I -- Returns with the RTTI type this object is. * + * * + * This will return that this is a normal vehicle unit type. Each object class overrides * + * this function in order to provide run time type identification support. * + * * + * INPUT: none * + * * + * OUTPUT: Returns the RTTI type that this object is (i.e., RTTI_UNIT). * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 08/13/1995 JLB : Created. * + *=============================================================================================*/ +RTTIType UnitClass::What_Am_I(void) const +{ + Validate(); + return(RTTI_UNIT); +} diff --git a/UNIT.H b/UNIT.H new file mode 100644 index 0000000..91aae41 --- /dev/null +++ b/UNIT.H @@ -0,0 +1,200 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\unit.h_v 2.19 16 Oct 1995 16:45:56 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : UNIT.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : April 14, 1994 * + * * + * Last Update : April 14, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef UNIT_H +#define UNIT_H + +#include "tarcom.h" +#include "radio.h" +#include "cargo.h" +#include "mission.h" +#include "target.h" + + +/**************************************************************************** +** For each instance of a unit (vehicle) in the game, there is one of +** these structures. This structure holds information that is specific +** and dynamic for a particular unit. +*/ +class UnitClass : public TarComClass +{ + public: + + /* + ** This records the house flag that this object is currently carrying. + */ + HousesType Flagged; + + /*--------------------------------------------------------------------- + ** Constructors, Destructors, and overloaded operators. + */ + static void * operator new(size_t size); + static void operator delete(void *ptr); + UnitClass(void) {}; + UnitClass(UnitType classid, HousesType house); + operator UnitType(void) const {return Class->Type;}; + virtual ~UnitClass(void); + virtual RTTIType What_Am_I(void) const; + + /*--------------------------------------------------------------------- + ** Member function prototypes. + */ + static void Init(void); + + bool Goto_Clear_Spot(void); + bool Try_To_Deploy(void); + + bool Tiberium_Check(CELL ¢er, int x, int y); + bool Flag_Attach(HousesType house); + bool Flag_Remove(void); + void Find_LZ(void); + bool Unload_Hovercraft_Process(void); + bool Goto_Tiberium(void); + bool Harvesting(void); + void APC_Close_Door(void); + void APC_Open_Door(void); + + /* + ** Query functions. + */ + virtual bool Can_Player_Move(void) const; + virtual int Pip_Count(void) const; + virtual InfantryType Crew_Type(void) const; + + /* + ** Coordinate inquiry functions. These are used for both display and + ** combat purposes. + */ + virtual COORDINATE Sort_Y(void) const; + + /* + ** Object entry and exit from the game system. + */ + virtual bool Unlimbo(COORDINATE , DirType facing=DIR_N); + virtual bool Limbo(void); + + /* + ** Display and rendering support functionality. Supports imagery and how + ** object interacts with the map and thus indirectly controls rendering. + */ + virtual void const * Remap_Table(void); + virtual void Look(bool incremental=false); + virtual short const * Overlap_List(void) const; + virtual void Draw_It(int x, int y, WindowNumberType window); + + /* + ** User I/O. + */ + virtual ActionType What_Action(CELL cell) const; + virtual ActionType What_Action(ObjectClass * object) const; + virtual void Active_Click_With(ActionType action, ObjectClass * object); + virtual void Active_Click_With(ActionType action, CELL cell); + virtual void Response_Select(void); + virtual void Response_Move(void); + virtual void Response_Attack(void); + + /* + ** Combat related. + */ + virtual COORDINATE Target_Coord(void) const; + virtual ResultType Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source=0); + virtual TARGET As_Target(void) const; + virtual void Stun(void); + + /* + ** Driver control support functions. These are used to control cell + ** occupation flags and driver instructions. + */ + virtual bool Stop_Driver(void); + virtual bool Start_Driver(COORDINATE & coord); + + /* + ** AI. + */ + virtual DirType Desired_Load_Dir(ObjectClass * passenger, CELL & moveto) const; + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, long & param); + virtual void AI(void); + virtual int Mission_Attack(void); + virtual int Mission_Unload(void); + virtual int Mission_Guard(void); + virtual int Mission_Harvest(void); + virtual int Mission_Hunt(void); + virtual int UnitClass::Mission_Move(void); + virtual FireErrorType Can_Fire(TARGET, int which) const; + + /* + ** Scenario and debug support. + */ + #ifdef CHEAT_KEYS + virtual void Debug_Dump(MonoClass *mono) const; + #endif + + /* + ** Movement and animation. + */ + virtual void Enter_Idle_Mode(bool initial=false); + virtual MoveType Can_Enter_Cell(CELL cell, FacingType facing=FACING_NONE) const; + virtual void Per_Cell_Process(bool center); + virtual void Scatter(COORDINATE threat, bool forced=false); + void Exit_Repair(void); +// MoveType Blocking_Object(TechnoClass const *techno, CELL cell) const; + + /* + ** File I/O. + */ + static void Read_INI(char *buffer); + static void Write_INI(char *buffer); + static char *INI_Name(void) {return "UNITS";}; + bool Load(FileClass & file); + bool Save(FileClass & file); + virtual void Code_Pointers(void); + virtual void Decode_Pointers(void); + + /* + ** Dee-buggin' support. + */ + int Validate(void) const; + + private: + + /* + ** This contains the value of the Virtual Function Table Pointer + */ + static void * VTable; +}; + +#endif diff --git a/UTRACKER.CPP b/UTRACKER.CPP new file mode 100644 index 0000000..f253c99 --- /dev/null +++ b/UTRACKER.CPP @@ -0,0 +1,226 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : UTRACKER.CPP * + * * + * Programmer : Steve Tall * + * * + * Start Date : June 3rd, 1996 * + * * + * Last Update : June 7th, 1996 [ST] * + * * + *-------------------------------------------------------------------------* + * The UnitTracker class exists to track the various statistics * + * required for internet games. * + * * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * + * * + * Functions: * + * * + * * + * * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + + + +#include "function.h" + + +/*********************************************************************************************** + * UTC::UnitTrackerClass -- Class constructor * + * * + * * + * * + * INPUT: Number of unit types to reserve space for * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/7/96 0:10AM ST : Created * + *=============================================================================================*/ +UnitTrackerClass::UnitTrackerClass (int unit_count) +{ + UnitTotals = new long [unit_count]; // Allocate memory for the unit totals + UnitCount = unit_count; // Keep a record of how many unit entries there are + InNetworkFormat = 0; // The unit entries are in host format + Clear_Unit_Total(); // Clear each entry +} + + +/*********************************************************************************************** + * UTC::~UnitTrackerClass -- Class destructor * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/7/96 0:10AM ST : Created * + *=============================================================================================*/ +UnitTrackerClass::~UnitTrackerClass (void) +{ + delete UnitTotals; +} + + + +/*********************************************************************************************** + * UTC::Increment_Unit_Total -- Increment the total for the specefied unit * + * * + * * + * * + * INPUT: Unit number * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/7/96 0:12AM ST : Created * + *=============================================================================================*/ +void UnitTrackerClass::Increment_Unit_Total(int unit_type) +{ + UnitTotals[unit_type]++; +} + + +/*********************************************************************************************** + * UTC::Decrement_Unit_Total -- Decrement the total for the specefied unit * + * * + * * + * * + * INPUT: Unit number * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/7/96 0:13AM ST : Created * + *=============================================================================================*/ +void UnitTrackerClass::Decrement_Unit_Total(int unit_type) +{ + UnitTotals[unit_type]--; +} + + +/*********************************************************************************************** + * UTC::Get_All_Totals -- Returns a pointer to the start of the unit totals list * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Ptr to unit totals list * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/7/96 0:13AM ST : Created * + *=============================================================================================*/ +long *UnitTrackerClass::Get_All_Totals (void) +{ + return (UnitTotals); +} + + +/*********************************************************************************************** + * UTC::Clear_Unit_Total -- Clear out all the unit totals * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/7/96 0:14AM ST : Created * + *=============================================================================================*/ +void UnitTrackerClass::Clear_Unit_Total (void) +{ + memset (UnitTotals, 0, UnitCount * sizeof(long) ); +} + + + +/*********************************************************************************************** + * UTC::To_Network_Format -- Changes all unit totals to network format for the internet * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 6/7/96 0:15AM ST : Created * + *=============================================================================================*/ +void UnitTrackerClass::To_Network_Format (void) +{ + if (!InNetworkFormat){ + for (int i=0 ; i. +*/ + +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ** + *************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : UTRACKER.H * + * * + * Programmer : Steve Tall * + * * + * Start Date : June 3rd, 1996 * + * * + * Last Update : June 7th, 1996 [ST] * + * * + *-------------------------------------------------------------------------* + * The UnitTracker class exists to track the various statistics * + * required for internet games. * + * * + * * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + + +/* +** UnitTracker Class +*/ + +class UnitTrackerClass { + + public: + + UnitTrackerClass(int unit_count); + ~UnitTrackerClass(void); + + void Increment_Unit_Total (int unit_type); + void Decrement_Unit_Total (int unit_type); + void Clear_Unit_Total(void); + + int Get_Unit_Total (int unit_type); + long *Get_All_Totals (void); + int Get_Unit_Count (void){return (UnitCount);}; + + void To_Network_Format(void); + void To_PC_Format(void); + + private: + + long *UnitTotals; + int UnitCount; + int InNetworkFormat; + +}; + + + + + + + + + + + + + + + + diff --git a/VECTOR.CPP b/VECTOR.CPP new file mode 100644 index 0000000..ffdf110 --- /dev/null +++ b/VECTOR.CPP @@ -0,0 +1,898 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\vector.cpv 2.17 16 Oct 1995 16:49:26 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : VECTOR.CPP * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 02/19/95 * + * * + * Last Update : July 18, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * BooleanVectorClass::BooleanVectorClass -- Copy constructor fo boolean array. * + * BooleanVectorClass::BooleanVectorClass -- Explicit data buffer constructor. * + * BooleanVectorClass::Clear -- Resets boolean vector to empty state. * + * BooleanVectorClass::Fixup -- Updates the boolean vector to a known state. * + * BooleanVectorClass::Reset -- Clear all boolean values in array. * + * BooleanVectorClass::Resize -- Resizes a boolean vector object. * + * BooleanVectorClass::Set -- Forces all boolean elements to true. * + * BooleanVectorClass::operator = -- Assignment operator. * + * BooleanVectorClass::operator == -- Comparison operator for boolean vector. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "vector.h" +#include +#include + +/* +** The following template function can be located here ONLY if all the instatiations are +** declared in a header file this module includes. By placing the template functions here, +** it speeds up compiler operation and reduces object module size. +*/ + +/*********************************************************************************************** + * VectorClass::VectorClass -- Constructor for vector class. * + * * + * This constructor for the vector class is passed the initial size of the vector and an * + * optional pointer to a preallocated block of memory that the vector will be placed in. * + * If this optional pointer is NULL (or not provided), then the vector is allocated out * + * of free store (with the "new" operator). * + * * + * INPUT: size -- The number of elements to initialize this vector to. * + * * + * array -- Optional pointer to a previously allocated memory block to hold the * + * vector. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +template +VectorClass::VectorClass(unsigned size, T const * array) +{ + Vector = 0; + VectorMax = size; + IsAllocated = false; + + /* + ** Allocate the vector. The default constructor will be called for every + ** object in this vector. + */ + if (size) { + if (array) { + Vector = new((void*)array) T[size]; + } else { + Vector = new T[size]; + IsAllocated = true; + } + } +} + + +/*********************************************************************************************** + * VectorClass::~VectorClass -- Default destructor for vector class. * + * * + * This is the default destructor for the vector class. It will deallocate any memory * + * that it may have allocated. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +template +VectorClass::~VectorClass(void) +{ + VectorClass::Clear(); +} + + +/*********************************************************************************************** + * VectorClass::VectorClass -- Copy constructor for vector object. * + * * + * This is the copy constructor for the vector class. It will duplicate the provided * + * vector into the new vector being created. * + * * + * INPUT: vector -- Reference to the vector to use as a copy. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +template +VectorClass::VectorClass(VectorClass const & vector) +{ + VectorMax = 0; + IsAllocated = false; + Vector = 0; + *this = vector; +} + + +/*********************************************************************************************** + * VectorClass::operator = -- The assignment operator. * + * * + * This the the assignment operator for vector objects. It will alter the existing lvalue * + * vector to duplicate the rvalue one. * + * * + * INPUT: vector -- The rvalue vector to copy into the lvalue one. * + * * + * OUTPUT: Returns with reference to the newly copied vector. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +template +VectorClass & VectorClass::operator =(VectorClass const & vector) +{ + Clear(); + VectorMax = vector.Length(); + if (VectorMax) { + Vector = new T[VectorMax]; + if (Vector) { + IsAllocated = true; + for (int index = 0; index < VectorMax; index++) { + Vector[index] = vector[index]; + } + } + } else { + Vector = 0; + IsAllocated = false; + } + return(*this); +} + + +/*********************************************************************************************** + * VectorClass::operator == -- Equality operator for vector objects. * + * * + * This operator compares two vectors for equality. It does this by performing an object * + * by object comparison between the two vectors. * + * * + * INPUT: vector -- The right vector expression. * + * * + * OUTPUT: bool; Are the two vectors essentially equal? (do they contain comparable elements * + * in the same order?) * + * * + * WARNINGS: The equality operator must exist for the objects that this vector contains. * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +template +int VectorClass::operator == (VectorClass const & vector) const +{ + if (VectorMax == vector.Length()) { + for (int index = 0; index < VectorMax; index++) { + if (Vector[index] != vector[index]) { + return(false); + } + } + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * VectorClass::ID -- Pointer based conversion to index number. * + * * + * Use this routine to convert a pointer to an element in the vector back into the index * + * number of that object. This routine ONLY works with actual pointers to object within * + * the vector. For "equivalent" object index number (such as with similar integral values) * + * then use the "by value" index number ID function. * + * * + * INPUT: pointer -- Pointer to an actual object in the vector. * + * * + * OUTPUT: Returns with the index number for the object pointed to by the parameter. * + * * + * WARNINGS: This routine is only valid for actual pointers to object that exist within * + * the vector. All other object pointers will yield undefined results. * + * * + * HISTORY: * + * 03/13/1995 JLB : Created. * + *=============================================================================================*/ +template +inline int VectorClass::ID(T const * ptr) +{ + return(((unsigned long)ptr - (unsigned long)&(*this)[0]) / sizeof(T)); +} + + +/*********************************************************************************************** + * VectorClass::ID -- Finds object ID based on value. * + * * + * Use this routine to find the index value of an object with equivalent value in the * + * vector. Typical use of this would be for integral types. * + * * + * INPUT: object -- Reference to the object that is to be looked up in the vector. * + * * + * OUTPUT: Returns with the index number of the object that is equivalent to the one * + * specified. If no matching value could be found then -1 is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/13/1995 JLB : Created. * + *=============================================================================================*/ +template +int VectorClass::ID(T const & object) +{ + for (int index = 0; index < VectorMax; index++) { + if ((*this)[index] == object) { + return(index); + } + } + return(-1); +} + + +/*********************************************************************************************** + * VectorClass::Clear -- Frees and clears the vector. * + * * + * Use this routine to reset the vector to an empty (non-allocated) state. A vector will * + * free all allocated memory when this routine is called. In order for the vector to be * + * useful after this point, the Resize function must be called to give it element space. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +template +void VectorClass::Clear(void) +{ + if (Vector && IsAllocated) { + delete[] Vector; + Vector = 0; + } + IsAllocated = false; + VectorMax = 0; +} + + +/*********************************************************************************************** + * VectorClass::Resize -- Changes the size of the vector. * + * * + * This routine is used to change the size (usually to increase) the size of a vector. This * + * is the only way to increase the vector's working room (number of elements). * + * * + * INPUT: newsize -- The desired size of the vector. * + * * + * array -- Optional pointer to a previously allocated memory block that the * + * array will be located in. If this parameter is not supplied, then * + * the array will be allocated from free store. * + * * + * OUTPUT: bool; Was the array resized successfully? * + * * + * WARNINGS: Failure to succeed could be the result of running out of memory. * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +template +int VectorClass::Resize(unsigned newsize, T const * array) +{ + if (newsize) { + + /* + ** Allocate a new vector of the size specified. The default constructor + ** will be called for every object in this vector. + */ + T * newptr; + if (!array) { + newptr = new T[newsize]; + } else { + newptr = new((void*)array) T[newsize]; + } + if (!newptr) { + return(false); + } + + /* + ** If there is an old vector, then it must be copied (as much as is feasable) + ** to the new vector. + */ + if (Vector) { + + /* + ** Copy as much of the old vector into the new vector as possible. This + ** presumes that there is a functional assignment operator for each + ** of the objects in the vector. + */ + int copycount = (newsize < VectorMax) ? newsize : VectorMax; + for (int index = 0; index < copycount; index++) { + newptr[index] = Vector[index]; + } + + /* + ** Delete the old vector. This might cause the destructors to be called + ** for all of the old elements. This makes the implementation of suitable + ** assignment operator very important. The default assigment operator will + ** only work for the simplist of objects. + */ + if (IsAllocated) { + delete[] Vector; + Vector = 0; + } + } + + /* + ** Assign the new vector data to this class. + */ + Vector = newptr; + VectorMax = newsize; + IsAllocated = (Vector && !array); + + } else { + + /* + ** Resizing to zero is the same as clearing the vector. + */ + Clear(); + } + return(true); +} + + +/*********************************************************************************************** + * DynamicVectorClass::DynamicVectorClass -- Constructor for dynamic vector. * + * * + * This is the normal constructor for the dynamic vector class. It is similar to the normal * + * vector class constructor. The vector is initialized to contain the number of elements * + * specified in the "size" parameter. The memory is allocated from free store unless the * + * optional array parameter is provided. In this case it will place the vector at the * + * memory location specified. * + * * + * INPUT: size -- The maximum number of objects allowed in this vector. * + * * + * array -- Optional pointer to the memory area to place the vector at. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +template +DynamicVectorClass::DynamicVectorClass(unsigned size, T const * array) + : VectorClass(size, array) +{ + GrowthStep = 10; + ActiveCount = 0; +} + + +/*********************************************************************************************** + * DynamicVectorClass::Resize -- Changes the size of a dynamic vector. * + * * + * Use this routine to change the size of the vector. The size changed is the maximum * + * number of allocated objects within this vector. If a memory buffer is provided, then * + * the vector will be located there. Otherwise, the memory will be allocated out of free * + * store. * + * * + * INPUT: newsize -- The desired maximum size of this vector. * + * * + * array -- Optional pointer to a previosly allocated memory array. * + * * + * OUTPUT: bool; Was vector successfully resized according to specifications? * + * * + * WARNINGS: Failure to resize the vector could be the result of lack of free store. * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +template +int DynamicVectorClass::Resize(unsigned newsize, T const * array) +{ + if (VectorClass::Resize(newsize, array)) { + if (Length() < ActiveCount) ActiveCount = Length(); + return(true); + } + return(false); +} + + +/*********************************************************************************************** + * DynamicVectorClass::ID -- Find matching value in the dynamic vector. * + * * + * Use this routine to find a matching object (by value) in the vector. Unlike the base * + * class ID function of similar name, this one restricts the scan to the current number * + * of valid objects. * + * * + * INPUT: object -- A reference to the object that a match is to be found in the * + * vector. * + * * + * OUTPUT: Returns with the index number of the object that is equivalent to the one * + * specified. If no equivalent object could be found then -1 is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/13/1995 JLB : Created. * + *=============================================================================================*/ +template +int DynamicVectorClass::ID(T const & object) +{ + for (int index = 0; index < Count(); index++) { + if ((*this)[index] == object) return(index); + } + return(-1); +} + + +/*********************************************************************************************** + * DynamicVectorClass::Add -- Add an element to the vector. * + * * + * Use this routine to add an element to the vector. The vector will automatically be * + * resized to accomodate the new element IF the vector was allocated previosly and the * + * growth rate is not zero. * + * * + * INPUT: object -- Reference to the object that will be added to the vector. * + * * + * OUTPUT: bool; Was the object added successfully? If so, the object is added to the end * + * of the vector. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +template +int DynamicVectorClass::Add(T const & object) +{ + if (ActiveCount >= Length()) { + if ((IsAllocated || !VectorMax) && GrowthStep > 0) { + if (!Resize(Length() + GrowthStep)) { + + /* + ** Failure to increase the size of the vector is an error condition. + ** Return with the error flag. + */ + return(false); + } + } else { + + /* + ** Increasing the size of this vector is not allowed! Bail this + ** routine with the error code. + */ + return(false); + } + } + + /* + ** There is room for the new object now. Add it to the end of the object vector. + */ + (*this)[ActiveCount++] = object; + return(true); +} + + +template +int DynamicVectorClass::Add_Head(T const & object) +{ + if (ActiveCount >= Length()) { + if ((IsAllocated || !VectorMax) && GrowthStep > 0) { + if (!Resize(Length() + GrowthStep)) { + + /* + ** Failure to increase the size of the vector is an error condition. + ** Return with the error flag. + */ + return(false); + } + } else { + + /* + ** Increasing the size of this vector is not allowed! Bail this + ** routine with the error code. + */ + return(false); + } + } + + /* + ** There is room for the new object now. Add it to the end of the object vector. + */ + if (ActiveCount) { + memmove(&(*this)[1], &(*this)[0], ActiveCount * sizeof(T)); + } + (*this)[0] = object; + ActiveCount++; +// (*this)[ActiveCount++] = object; + return(true); +} + + +/*********************************************************************************************** + * DynamicVectorClass::Delete -- Remove the specified object from the vector. * + * * + * This routine will delete the object referenced from the vector. All objects in the * + * vector that follow the one deleted will be moved "down" to fill the hole. * + * * + * INPUT: object -- Reference to the object in this vector that is to be deleted. * + * * + * OUTPUT: bool; Was the object deleted successfully? This should always be true. * + * * + * WARNINGS: Do no pass a reference to an object that is NOT part of this vector. The * + * results of this are undefined and probably catastrophic. * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +template +int DynamicVectorClass::Delete(T const & object) +{ + int index = ID(object); + if (index != -1){ + return(Delete(index)); + }else{ + return (false); + } +} + + +/*********************************************************************************************** + * DynamicVectorClass::Delete -- Deletes the specified index from the vector. * + * * + * Use this routine to delete the object at the specified index from the objects in the * + * vector. This routine will move all the remaining objects "down" in order to fill the * + * hole. * + * * + * INPUT: index -- The index number of the object in the vector that is to be deleted. * + * * + * OUTPUT: bool; Was the object index deleted successfully? Failure might mean that the index * + * specified was out of bounds. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 03/10/1995 JLB : Created. * + *=============================================================================================*/ +template +int DynamicVectorClass::Delete(int index) +{ + if ((unsigned)index < ActiveCount) { + ActiveCount--; + + /* + ** If there are any objects past the index that was deleted, copy those + ** objects down in order to fill the hole. A simple memory copy is + ** not sufficient since the vector could contain class objects that + ** need to use the assignment operator for movement. + */ + for (int i = index; i < ActiveCount; i++) { + (*this)[i] = (*this)[i+1]; + } + return(true); + } + return(false); +} + +//---------------------------------------------------------------------------------------------- + +/*********************************************************************************************** + * BooleanVectorClass::BooleanVectorClass -- Explicit data buffer constructor. * + * * + * This is the constructor for a boolean array. This constructor takes the memory pointer * + * provided as assigns that as the array data pointer. * + * * + * INPUT: size -- The size of the array (in bits). * + * * + * array -- Pointer to the memory that the array is to use. * + * * + * OUTPUT: none * + * * + * WARNINGS: You must make sure that the memory specified is large enough to contain the * + * bits specified. * + * * + * HISTORY: * + * 07/18/1995 JLB : Created. * + *=============================================================================================*/ +BooleanVectorClass::BooleanVectorClass(unsigned size, unsigned char * array) +{ + BitArray.Resize(((size + (8-1)) / 8), array); + LastIndex = -1; + BitCount = size; +} + + +/*********************************************************************************************** + * BooleanVectorClass::BooleanVectorClass -- Copy constructor fo boolean array. * + * * + * This is the copy constructor for a boolean array. It is used to make a duplicate of the * + * boolean array. * + * * + * INPUT: vector -- Reference to the vector to be duplicated. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1995 JLB : Created. * + *=============================================================================================*/ +BooleanVectorClass::BooleanVectorClass(BooleanVectorClass const & vector) +{ + LastIndex = -1; + *this = vector; +} + + +/*********************************************************************************************** + * BooleanVectorClass::operator = -- Assignment operator. * + * * + * This routine will make a copy of the specifed boolean vector array. The vector is * + * copied into an already constructed existing vector. The values from the existing vector * + * are destroyed by this copy. * + * * + * INPUT: vector -- Reference to the vector to make a copy of. * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1995 JLB : Created. * + *=============================================================================================*/ +BooleanVectorClass & BooleanVectorClass::operator =(BooleanVectorClass const & vector) +{ + Fixup(); + Copy = vector.Copy; + LastIndex = vector.LastIndex; + BitArray = vector.BitArray; + BitCount = vector.BitCount; + return(*this); +} + + +/*********************************************************************************************** + * BooleanVectorClass::operator == -- Comparison operator for boolean vector. * + * * + * This is the comparison operator for a boolean vector class. Boolean vectors are equal * + * if the bit count and bit values are identical. * + * * + * INPUT: vector -- Reference to the vector to compare to. * + * * + * OUTPUT: Are the boolean vectors identical? * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1995 JLB : Created. * + *=============================================================================================*/ +int BooleanVectorClass::operator == (const BooleanVectorClass & vector) +{ + Fixup(LastIndex); + return(BitCount == vector.BitCount && BitArray == vector.BitArray); +} + + +/*********************************************************************************************** + * BooleanVectorClass::Resize -- Resizes a boolean vector object. * + * * + * This routine will resize the boolean vector object. An index value used with a boolean * + * vector must be less than the value specified in as the new size. * + * * + * INPUT: size -- The new maximum size of this boolean vector. * + * * + * OUTPUT: Was the boolean vector sized successfully? * + * * + * WARNINGS: The boolean array might be reallocated or even deleted by this routine. * + * * + * HISTORY: * + * 07/18/1995 JLB : Created. * + *=============================================================================================*/ +int BooleanVectorClass::Resize(unsigned size) +{ + Fixup(); + + if (size) { + + /* + ** Record the previous bit count of the boolean vector. This is used + ** to determine if the array has grown in size and thus clearing is + ** necessary. + */ + int oldsize = BitCount; + + /* + ** Actually resize the bit array. Since this is a bit packed array, + ** there are 8 elements per byte (rounded up). + */ + int success = BitArray.Resize(((size + (8-1)) / 8)); + + /* + ** Since there is no default constructor for bit packed integers, a manual + ** clearing of the bits is required. + */ + BitCount = size; + if (success && oldsize < size) { + for (int index = oldsize; index < size; index++) { + (*this)[index] = 0; + } + } + + return(success); + } + + /* + ** Resizing to zero is the same as clearing and deallocating the array. + ** This is always successful. + */ + Clear(); + return(true); +} + + +/*********************************************************************************************** + * BooleanVectorClass::Clear -- Resets boolean vector to empty state. * + * * + * This routine will clear out the boolean array. This will free any allocated memory and * + * result in the boolean vector being unusable until the Resize function is subsiquently * + * called. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: The boolean vector cannot be used until it is resized to a non null condition. * + * * + * HISTORY: * + * 07/18/1995 JLB : Created. * + *=============================================================================================*/ +void BooleanVectorClass::Clear(void) +{ + Fixup(); + BitCount = 0; + BitArray.Clear(); +} + + +/*********************************************************************************************** + * BooleanVectorClass::Reset -- Clear all boolean values in array. * + * * + * This is the preferred (and quick) method to clear the boolean array to a false condition.* + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1995 JLB : Created. * + *=============================================================================================*/ +void BooleanVectorClass::Reset(void) +{ + LastIndex = -1; + if (BitArray.Length()) { + memset(&BitArray[0], '\0', BitArray.Length()); + } +} + + +/*********************************************************************************************** + * BooleanVectorClass::Set -- Forces all boolean elements to true. * + * * + * This is the preferred (and fast) way to set all boolean elements to true. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 07/18/1995 JLB : Created. * + *=============================================================================================*/ +void BooleanVectorClass::Set(void) +{ + LastIndex = -1; + if (BitArray.Length()) { + memset(&BitArray[0], '\xFF', BitArray.Length()); + } +} + + +/*********************************************************************************************** + * BooleanVectorClass::Fixup -- Updates the boolean vector to a known state. * + * * + * Use this routine to set the boolean value copy to match the appropriate bit in the * + * boolean array. The boolean array will be updated with any changes from the last time * + * a value was fetched from the boolean vector. By using this update method, the boolean * + * array can be treated as a normal array even though the elements are composed of * + * otherwise inaccessable bits. * + * * + * INPUT: index -- The index to set the new copy value to. If the index is -1, then the * + * previous value will be updated into the vector array, but no new value * + * will be fetched from it. * + * * + * OUTPUT: none * + * * + * WARNINGS: Always call this routine with "-1" if any direct manipulation of the bit * + * array is to occur. This ensures that the bit array is accurate. * + * * + * HISTORY: * + * 07/18/1995 JLB : Created. * + *=============================================================================================*/ +void BooleanVectorClass::Fixup(int index) const +{ + /* + ** If the requested index value is illegal, then force the index + ** to be -1. This is the default non-index value. + */ + if ((unsigned)index >= BitCount) { + index = -1; + } + + /* + ** If the new index is different than the previous index, there might + ** be some fixing up required. + */ + if (index != LastIndex) { + + /* + ** If the previously fetched boolean value was changed, then update + ** the boolean array accordingly. + */ + if (LastIndex != -1) { + Set_Bit((void*)&BitArray[0], LastIndex, Copy); + } + + /* + ** If this new current index is valid, then fill in the reference boolean + ** value with the approriate data from the bit array. + */ + if (index != -1) { + ((unsigned char&)Copy) = Get_Bit(&BitArray[0], index); + } + + ((int &)LastIndex) = index; + } +} + + diff --git a/VECTOR.H b/VECTOR.H new file mode 100644 index 0000000..541abe8 --- /dev/null +++ b/VECTOR.H @@ -0,0 +1,296 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\vector.h_v 2.15 16 Oct 1995 16:47:38 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : VECTOR.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 02/19/95 * + * * + * Last Update : March 13, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * VectorClass::VectorClass -- Constructor for vector class. * + * VectorClass::~VectorClass -- Default destructor for vector class. * + * VectorClass::VectorClass -- Copy constructor for vector object. * + * VectorClass::operator = -- The assignment operator. * + * VectorClass::operator == -- Equality operator for vector objects. * + * VectorClass::Clear -- Frees and clears the vector. * + * VectorClass::Resize -- Changes the size of the vector. * + * DynamicVectorClass::DynamicVectorClass -- Constructor for dynamic vector. * + * DynamicVectorClass::Resize -- Changes the size of a dynamic vector. * + * DynamicVectorClass::Add -- Add an element to the vector. * + * DynamicVectorClass::Delete -- Remove the specified object from the vector. * + * DynamicVectorClass::Delete -- Deletes the specified index from the vector. * + * VectorClass::ID -- Pointer based conversion to index number. * + * VectorClass::ID -- Finds object ID based on value. * + * DynamicVectorClass::ID -- Find matching value in the dynamic vector. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef VECTOR_H +#define VECTOR_H + +#ifndef false +#define false 0 +#endif +#ifndef true +#define true 1 +#endif + +#include +#include + +inline void * operator new(size_t , void * pointer) {return(pointer);} +inline void * operator new[](size_t , void * pointer) {return(pointer);} + + +/************************************************************************** +** This is a general purpose vector class. A vector is defined by this +** class, as an array of arbitrary objects where the array can be dynamically +** sized. Because is deals with arbitrary object types, it can handle everything. +** As a result of this, it is not terribly efficient for integral objects (such +** as char or int). It will function correctly, but the copy constructor and +** equality operator could be highly optimized if the integral type were known. +** This efficiency can be implemented by deriving an integral vector template +** from this one in order to supply more efficient routines. +*/ +template +class VectorClass +{ + public: + VectorClass(unsigned size=0, T const * array=0); + VectorClass(VectorClass const &); // Copy constructor. + virtual ~VectorClass(void); + + T & operator[](unsigned index) {return(Vector[index]);}; + T const & operator[](unsigned index) const {return(Vector[index]);}; + virtual VectorClass & operator =(VectorClass const &); // Assignment operator. + virtual int operator == (VectorClass const &) const; // Equality operator. + virtual int Resize(unsigned newsize, T const * array=0); + virtual void Clear(void); + unsigned Length(void) const {return VectorMax;}; + virtual int ID(T const * ptr); // Pointer based identification. + virtual int ID(T const & ptr); // Value based identification. + + protected: + + /* + ** This is a pointer to the allocated vector array of elements. + */ + T * Vector; + + /* + ** This is the maximum number of elements allowed in this vector. + */ + unsigned VectorMax; + + /* + ** Does the vector data pointer refer to memory that this class has manually + ** allocated? If so, then this class is responsible for deleting it. + */ + unsigned IsAllocated:1; +}; + + +/************************************************************************** +** This derivative vector class adds the concept of adding and deleting +** objects. The objects are packed to the beginning of the vector array. +** If this is instantiated for a class object, then the assignment operator +** and the equality operator must be supported. If the vector allocates its +** own memory, then the vector can grow if it runs out of room adding items. +** The growth rate is controlled by setting the growth step rate. A growth +** step rate of zero disallows growing. +*/ +template +class DynamicVectorClass : public VectorClass +{ + public: + DynamicVectorClass(unsigned size=0, T const * array=0); + + // Change maximum size of vector. + virtual int Resize(unsigned newsize, T const * array=0); + + // Resets and frees the vector array. + virtual void Clear(void) {ActiveCount = 0;VectorClass::Clear();}; + + // Fetch number of "allocated" vector objects. + int Count(void) const {return(ActiveCount);}; + + // Add object to vector (growing as necessary). + int Add(T const & object); + int Add_Head(T const & object); + + // Delete object just like this from vector. + int Delete(T const & object); + + // Delete object at this vector index. + int Delete(int index); + + // Deletes all objects in the vector. + void Delete_All(void) {ActiveCount = 0;}; + + // Set amount that vector grows by. + int Set_Growth_Step(int step) {return(GrowthStep = step);}; + + // Fetch current growth step rate. + int Growth_Step(void) {return GrowthStep;}; + + virtual int ID(T const * ptr) {return(VectorClass::ID(ptr));}; + virtual int ID(T const & ptr); + + protected: + + /* + ** This is a count of the number of active objects in this + ** vector. The memory array often times is bigger than this + ** value. + */ + int ActiveCount; + + /* + ** If there is insufficient room in the vector array for a new + ** object to be added, then the vector will grow by the number + ** of objects specified by this value. This is controlled by + ** the Set_Growth_Step() function. + */ + int GrowthStep; +}; + + +/************************************************************************** +** This is a derivative of a vector class that supports boolean flags. Since +** a boolean flag can be represented by a single bit, this class packs the +** array of boolean flags into an array of bytes containing 8 boolean values +** each. For large boolean arrays, this results in an 87.5% savings. Although +** the indexing "[]" operator is supported, DO NOT pass pointers to sub elements +** of this bit vector class. A pointer derived from the indexing operator is +** only valid until the next call. Because of this, only simple +** direct use of the "[]" operator is allowed. +*/ +class BooleanVectorClass +{ + public: + BooleanVectorClass(unsigned size=0, unsigned char * array=0); + BooleanVectorClass(BooleanVectorClass const & vector); + + // Assignment operator. + BooleanVectorClass & operator =(BooleanVectorClass const & vector); + + // Equivalency operator. + int operator == (BooleanVectorClass const & vector); + + // Fetch number of boolean objects in vector. + int Length(void) {return BitCount;}; + + // Set all boolean values to false; + void Reset(void); + + // Set all boolean values to true. + void Set(void); + + // Resets vector to zero length (frees memory). + void Clear(void); + + // Change size of this boolean vector. + int Resize(unsigned size); + + // Fetch reference to specified index. + bool const & operator[](int index) const { + if (LastIndex != index) Fixup(index); + return(Copy); + }; + bool & operator[](int index) { + if (LastIndex != index) Fixup(index); + return(Copy); + }; + + // Quick check on boolean state. + bool Is_True(int index) const { + if (index == LastIndex) return(Copy); + return(Get_Bit(&BitArray[0], index)); + }; + + // Find first index that is false. + int First_False(void) const { + if (LastIndex != -1) Fixup(-1); + + int retval = First_False_Bit(&BitArray[0]); + if (retval < BitCount) return(retval); + + /* + ** Failure to find a false boolean value in the vector. Return this + ** fact in the form of an invalid index number. + */ + return(-1); + } + + // Find first index that is true. + int First_True(void) const { + if (LastIndex != -1) Fixup(-1); + + int retval = First_True_Bit(&BitArray[0]); + if (retval < BitCount) return(retval); + + /* + ** Failure to find a true boolean value in the vector. Return this + ** fact in the form of an invalid index number. + */ + return(-1); + } + + private: + void Fixup(int index=-1) const; + + /* + ** This is the number of boolean values in the vector. This value is + ** not necessarily a multiple of 8, even though the underlying character + ** vector contains a multiple of 8 bits. + */ + int BitCount; + + /* + ** This is a referential copy of an element in the bit vector. The + ** purpose of this copy is to allow normal reference access to this + ** object (for speed reasons). This hides the bit packing scheme from + ** the user of this class. + */ + bool Copy; + + /* + ** This records the index of the value last fetched into the reference + ** boolean variable. This index is used to properly restore the value + ** when the reference copy needs updating. + */ + int LastIndex; + + /* + ** This points to the allocated bitfield array. + */ + VectorClass BitArray; +}; + + +#endif diff --git a/VISUDLG.CPP b/VISUDLG.CPP new file mode 100644 index 0000000..f9ee4fc --- /dev/null +++ b/VISUDLG.CPP @@ -0,0 +1,424 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\visudlg.cpv 2.17 16 Oct 1995 16:51:40 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : VISUDLG.CPP * + * * + * Programmer : Maria del Mar McCready Legg * + * Joe L. Bostic * + * * + * Start Date : Jan 8, 1995 * + * * + * Last Update : June 18, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * VisualControlsClass::Process -- Process the visual control dialog box. * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "visudlg.h" +int VisualControlsClass::Init(void) +{ + int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; + Option_Width = 216 * factor; + Option_Height = 122 * factor; + Option_X = (((SeenBuff.Get_Width() - Option_Width) / 2)); + Option_Y = ((SeenBuff.Get_Height() - Option_Height) / 2); + Text_X = Option_X + (28 * factor); + Text_Y = Option_Y + (30 * factor); + Slider_X = Option_X + (105 * factor); + Slider_Y = Option_Y + (30 * factor); + Slider_Width = 70 * factor; + Slider_Height = 5 * factor; + Slider_Y_Spacing = 11 * factor; + Button_X = Option_X + (63 * factor); + Button_Y = Option_Y + (102 * factor); + return(factor); +} +/*********************************************************************************************** + * VisualControlsClass::Process -- Process the visual control dialog box. * + * * + * This routine displays and processes the visual controls dialog box. * + * * + * INPUT: none * + * * + * OUTPUT: none * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 06/18/1995 JLB : Created. * + *=============================================================================================*/ +void VisualControlsClass::Process(void) +{ + static int _titles[4] = { + TXT_BRIGHTNESS, + TXT_COLOR, + TXT_CONTRAST, + TXT_TINT + }; + + enum { + NUM_OF_BUTTONS = 6, + }; + + /* + ** Variables. + */ + int selection; + int factor; + bool pressed; + int curbutton; + TextButtonClass *buttons[NUM_OF_BUTTONS]; + SliderClass *buttonsliders[NUM_OF_BUTTONS]; + + factor = Init(); + Set_Logic_Page(SeenBuff); + + /* + ** Create Buttons. Button coords are in pixels, but are window-relative. + */ + TextButtonClass optionsbtn( + BUTTON_OPTIONS, + TXT_GAME_CONTROLS, + TPF_6PT_GRAD | TPF_NOSHADOW, + 0, + Button_Y ); + + TextButtonClass resetbtn( + BUTTON_RESET, + TXT_RESET_MENU, + TPF_6PT_GRAD | TPF_NOSHADOW, + 0, + Button_Y); + + /* + ** Centers options button. + */ + optionsbtn.X = Option_X + (Option_Width - optionsbtn.Width - (15 * factor)); + resetbtn.X = Option_X + (15 *factor); + + resetbtn.Add_Tail(optionsbtn); + + /* + ** Brightness (value) control. + */ + SliderClass brightness(BUTTON_BRIGHTNESS, Slider_X, Slider_Y + (Slider_Y_Spacing*0), Slider_Width, Slider_Height); + brightness.Set_Thumb_Size(20); + brightness.Set_Value(Options.Get_Brightness()); + brightness.Add_Tail(optionsbtn); + + /* + ** Color (saturation) control. + */ + SliderClass color(BUTTON_COLOR, Slider_X, Slider_Y + (Slider_Y_Spacing*1), Slider_Width, Slider_Height); + color.Set_Thumb_Size(20); + color.Set_Value(Options.Get_Color()); + color.Add_Tail(optionsbtn); + + /* + ** Contrast control. + */ + SliderClass contrast(BUTTON_CONTRAST, Slider_X, Slider_Y + (Slider_Y_Spacing*2), Slider_Width, Slider_Height); + contrast.Set_Thumb_Size(20); + contrast.Set_Value(Options.Get_Contrast()); + contrast.Add_Tail(optionsbtn); + + /* + ** Tint (hue) control. + */ + SliderClass tint(BUTTON_TINT, Slider_X, Slider_Y + (Slider_Y_Spacing*3), Slider_Width, Slider_Height); + tint.Set_Thumb_Size(20); + tint.Set_Value(Options.Get_Tint()); + tint.Add_Tail(optionsbtn); + + /* + ** This causes left mouse button clicking within the confines of the dialog to + ** be ignored if it wasn't recognized by any other button or slider. + */ + GadgetClass dialog(Option_X, Option_Y, Option_Width, Option_Height, GadgetClass::LEFTPRESS); + dialog.Add_Tail(optionsbtn); + + /* + ** This causes a right click anywhere or a left click outside the dialog region + ** to be equivalent to clicking on the return to options dialog. + */ + ControlClass background(BUTTON_OPTIONS, 0, 0, SeenBuff.Get_Width(), SeenBuff.Get_Height(), GadgetClass::LEFTPRESS|GadgetClass::RIGHTPRESS); + background.Add_Tail(optionsbtn); + + curbutton = 0; + buttons[0] = NULL; + buttons[1] = NULL; + buttons[2] = NULL; + buttons[3] = NULL; + buttons[4] = &resetbtn; + buttons[5] = &optionsbtn; + + buttonsliders[0] = &brightness; + buttonsliders[1] = &color; + buttonsliders[2] = &contrast; + buttonsliders[3] = ∭ + buttonsliders[4] = NULL; + buttonsliders[5] = NULL; + + /* + ** Main Processing Loop. + */ + bool display = true; + bool process = true; + bool partial = true; + pressed = false; + while (process) { + + /* + ** Invoke game callback. + */ + if (GameToPlay == GAME_NORMAL) { + Call_Back(); + } else { + if (Main_Loop()) { + process = false; + } + } + + /* + ** If we have just received input focus again after running in the background then + ** we need to redraw. + */ + if (AllSurfaces.SurfacesRestored){ + AllSurfaces.SurfacesRestored=FALSE; + display=TRUE; + } + + /* + ** Refresh display if needed. + */ + if (display) { + Hide_Mouse(); + Dialog_Box(Option_X, Option_Y, Option_Width, Option_Height); + Draw_Caption(TXT_VISUAL_CONTROLS, Option_X, Option_Y, Option_Width); + Show_Mouse(); + display = false; + partial = true; + } + + /* + ** If just the buttons and captions need to be redrawn, then do so now. + */ + if (partial) { + Hide_Mouse(); + + /* + ** Draw the titles. + */ + for (int i = 0; i < (sizeof(_titles)/sizeof(_titles[0])); i++) { + Fancy_Text_Print(_titles[i], Slider_X-8, Text_Y + (i*Slider_Y_Spacing), + CC_GREEN, TBLACK, TPF_6PT_GRAD|TPF_RIGHT|TPF_NOSHADOW| ((curbutton == i) ? TPF_BRIGHT_COLOR : TPF_USE_GRAD_PAL)); + } + optionsbtn.Draw_All(); + Show_Mouse(); + partial = false; + } + + /* + ** Get and process player input. + */ + KeyNumType input = optionsbtn.Input(); + switch (input) { + case (BUTTON_BRIGHTNESS | KN_BUTTON): + Options.Set_Brightness(brightness.Get_Value()); + break; + + case (BUTTON_COLOR | KN_BUTTON): + Options.Set_Color(color.Get_Value()); + break; + + case (BUTTON_CONTRAST | KN_BUTTON): + Options.Set_Contrast(contrast.Get_Value()); + break; + + case (BUTTON_TINT | KN_BUTTON): + Options.Set_Tint(tint.Get_Value()); + break; + + case (BUTTON_RESET | KN_BUTTON): + selection = BUTTON_RESET; + pressed = true; + break; + + case KN_ESC: + case BUTTON_OPTIONS|KN_BUTTON: + selection = BUTTON_OPTIONS; + pressed = true; + break; + + case (KN_LEFT): + if (curbutton <= (BUTTON_TINT - BUTTON_BRIGHTNESS)) { + buttonsliders[curbutton]->Bump(1); + switch (curbutton) { + case (BUTTON_BRIGHTNESS - BUTTON_BRIGHTNESS): + Options.Set_Brightness(brightness.Get_Value()); + break; + + case (BUTTON_COLOR - BUTTON_BRIGHTNESS): + Options.Set_Color(color.Get_Value()); + break; + + case (BUTTON_CONTRAST - BUTTON_BRIGHTNESS): + Options.Set_Contrast(contrast.Get_Value()); + break; + + case (BUTTON_TINT - BUTTON_BRIGHTNESS): + Options.Set_Tint(tint.Get_Value()); + break; + } + } else { + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + + curbutton--; + if (curbutton < (BUTTON_RESET - BUTTON_BRIGHTNESS) ) { + curbutton = (BUTTON_OPTIONS - BUTTON_BRIGHTNESS); + } + + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + } + break; + + case (KN_RIGHT): + if (curbutton <= (BUTTON_TINT - BUTTON_BRIGHTNESS)) { + buttonsliders[curbutton]->Bump(0); + switch (curbutton) { + case (BUTTON_BRIGHTNESS - BUTTON_BRIGHTNESS): + Options.Set_Brightness(brightness.Get_Value()); + break; + + case (BUTTON_COLOR - BUTTON_BRIGHTNESS): + Options.Set_Color(color.Get_Value()); + break; + + case (BUTTON_CONTRAST - BUTTON_BRIGHTNESS): + Options.Set_Contrast(contrast.Get_Value()); + break; + + case (BUTTON_TINT - BUTTON_BRIGHTNESS): + Options.Set_Tint(tint.Get_Value()); + break; + } + } else { + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + + curbutton++; + if (curbutton > (BUTTON_OPTIONS - BUTTON_BRIGHTNESS) ) { + curbutton = (BUTTON_RESET - BUTTON_BRIGHTNESS); + } + + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + } + break; + + case (KN_UP): + if (curbutton <= (BUTTON_TINT - BUTTON_BRIGHTNESS) ) { + partial = true; + } else { + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + } + + curbutton--; + if (curbutton == (BUTTON_RESET - BUTTON_BRIGHTNESS) ) { + curbutton--; + } + + if (curbutton < 0) { + curbutton = (BUTTON_RESET - BUTTON_BRIGHTNESS); + } + + if (curbutton <= (BUTTON_TINT - BUTTON_BRIGHTNESS) ) { + partial = true; + } else { + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + } + break; + + case (KN_DOWN): + if (curbutton <= (BUTTON_TINT - BUTTON_BRIGHTNESS) ) { + partial = true; + } else { + buttons[curbutton]->Turn_Off(); + buttons[curbutton]->Flag_To_Redraw(); + } + + curbutton++; + if (curbutton > (BUTTON_RESET - BUTTON_BRIGHTNESS) ) { + curbutton = 0; + } + + if (curbutton <= (BUTTON_TINT - BUTTON_BRIGHTNESS) ) { + partial = true; + } else { + buttons[curbutton]->Turn_On(); + buttons[curbutton]->Flag_To_Redraw(); + } + break; + + case (KN_RETURN): + selection = curbutton + BUTTON_BRIGHTNESS; + pressed = true; + break; + + default: + break; + } + + + if (pressed) { + switch (selection) { + case (BUTTON_RESET): + brightness.Set_Value(128); + contrast.Set_Value(128); + color.Set_Value(128); + tint.Set_Value(128); + + Options.Set_Brightness(128); + Options.Set_Contrast(128); + Options.Set_Color(128); + Options.Set_Tint(128); + break; + + case (BUTTON_OPTIONS): + process = false; + break; + } + + pressed = false; + } + } +} + diff --git a/VISUDLG.H b/VISUDLG.H new file mode 100644 index 0000000..ef125da --- /dev/null +++ b/VISUDLG.H @@ -0,0 +1,92 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\visudlg.h_v 2.15 16 Oct 1995 16:47:46 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : VISUDLG.H * + * * + * Programmer : Maria del Mar McCready Legg * + * Joe L. Bostic * + * * + * Start Date : Jan 8, 1995 * + * * + * Last Update : Jan 18, 1995 [MML] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + *---------------------------------------------------------------------------------------------*/ + +#ifndef VISUDLG_H +#define VISUDLG_H + +#include "gadget.h" + +class VisualControlsClass +{ + private: + + enum VisualControlEnums { + BUTTON_BRIGHTNESS=1, + BUTTON_COLOR, + BUTTON_CONTRAST, + BUTTON_TINT, + BUTTON_RESET, + BUTTON_OPTIONS, // Button number for "Options menu" + OPTION_WIDTH=216, // Width of dialog box. + OPTION_HEIGHT=122, // Height of dialog box. + OPTION_X=(((320 - OPTION_WIDTH) / 2)), + OPTION_Y=((200 - OPTION_HEIGHT) / 2), + TEXT_X=OPTION_X+28, // Title's x pos + TEXT_Y=OPTION_Y+30, // Add 11 for each following line + SLIDER_X=OPTION_X+105, // Slider's x pos + SLIDER_Y=OPTION_Y+30, // Add 11 for each following line + SLIDER_WIDTH=70, // Width of each control slider. + SLIDER_HEIGHT=5, // Height of each control slider. + SLIDER_Y_SPACING=11, // Vertical spacing between sliders. + BUTTON_X=OPTION_X+63, // Options button x pos + BUTTON_Y=OPTION_Y+102, // Options button y pos + }; + + public: + + VisualControlsClass(void) {}; + void Process(void); + int Init(void); + + int Option_Width; // Width of dialog box. + int Option_Height; // Height of dialog box. + int Option_X; + int Option_Y; + int Text_X; // Title's x pos + int Text_Y; // Add 11 for each following line + int Slider_X; // Slider's x pos + int Slider_Y; // Add 11 for each following line + int Slider_Width; // Width of each control slider. + int Slider_Height; // Height of each control slider. + int Slider_Y_Spacing; // Vertical spacing between sliders. + int Button_X; // Options button x pos + int Button_Y; // Options button y pos + +}; + +#endif diff --git a/WATCOM.H b/WATCOM.H new file mode 100644 index 0000000..678e03b --- /dev/null +++ b/WATCOM.H @@ -0,0 +1,77 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\watcom.h_v 2.13 16 Oct 1995 16:45:58 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : WATCOM.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : 03/12/95 * + * * + * Last Update : March 12, 1995 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef WATCOM_H +#define WATCOM_H + +// Turn all warnings into errors. +#pragma warning * 0 + +// Disables warning when "sizeof" is used on an object with virtual functions. +#pragma warning 549 9 + +// Disable the "Integral value may be truncated during assignment or initialization". +#pragma warning 389 9 + +// Allow constructing a temporary to be used as a parameter. +#pragma warning 604 9 + +// Disable the construct resolved as an expression warning. +#pragma warning 595 9 + +// Disable the strange "construct resolved as a declaration/type" warning. +#pragma warning 594 9 + +// Disable the "pre-compiled header file cannot be used" warning. +#pragma warning 698 9 + +// Disable the "temporary object used to initialize a non-constant reference" warning. +#pragma warning 665 9 + +// Disable the "pointer or reference truncated by cast. Cast is supposed to REMOVE warnings, not create them. +#pragma warning 579 9 + +// Disable the warning about moving empty constructors/destructors to the class declaration. +#pragma warning 657 9 + +// Turns off unreferenced function parameter warning. +//#pragma off(unreferenced) + +// Turns off "expression with side effect in sizeof()". This is needed if memchecker is used. +#pragma warning 472 9 + +#endif diff --git a/WIDEFUNC.MAC b/WIDEFUNC.MAC new file mode 100644 index 0000000..2f32a9e --- /dev/null +++ b/WIDEFUNC.MAC @@ -0,0 +1 @@ +y#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#41381#4138#41431#4138#4143#4157#4157#4157#4157#4157#4157#4157#4157#4157#4157#4157#4157#4157#4157#4157#4157#4157#4157#4157#41571#4143#4190#4143#4138#4190#4188#4143#4188#4138 \ No newline at end of file diff --git a/WIDEHDR.MAC b/WIDEHDR.MAC new file mode 100644 index 0000000..09b003a --- /dev/null +++ b/WIDEHDR.MAC @@ -0,0 +1 @@ +#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138#4138y#4138#41431#4138#4143#4141#4141#4141#4141#4141#4141#4141#4141#4141#41411#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#4141#41411 \ No newline at end of file diff --git a/WINASM.ASM b/WINASM.ASM new file mode 100644 index 0000000..7381e46 --- /dev/null +++ b/WINASM.ASM @@ -0,0 +1,889 @@ +; +; Command & Conquer(tm) +; Copyright 2025 Electronic Arts Inc. +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; + +;*************************************************************************** +;** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S I N C ** +;*************************************************************************** +;* * +;* Project Name : Command & Conquer * +;* * +;* File Name : WINSAM.ASM * +;* * +;* Programmer : Steve Tall * +;* * +;* Start Date : October 26th, 1995 * +;* * +;* Last Update : October 26th, 1995 [ST] * +;* * +;*-------------------------------------------------------------------------* +;* Functions: * +;* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * + + +IDEAL +P386 +MODEL USE32 FLAT + +global C _AbortModemFunctionPtr:dword +global C Memory_Error_Exit :dword +global C MouseQX :dword +global C MouseQY :dword + +global FastGetPortHardware_ :near +global FastSetPortHardware_ :near +global PortOpenGreenleafFast_ :near +global HMWaitForOK_ :near +global HMSetDialingMethod_ :near +global HMDial_ :near +global HMInputLine_ :near +global HMAnswer_ :near +global PortKillTime_ :near +global HMSendStringNoWait_ :near +global HMSetUpEchoRoutine_ :near +global HMSetUpAbortKey_ :near +global SetAbortModemFunctionPtr_:near +global Change8259Priority_ :near +global HMSendString_ :near +global C Stop_Execution :near + + +global _IPX_Initialise:near +global _ASM_IPX_Initialise:near + + + codeseg + +proc _ASM_IPX_Initialise near + + int 3 + jmp _IPX_Initialise + +endp + + + + +global _Int3:near +proc _Int3 near + ;int 3 + ret +endp + + +proc Stop_Execution C near + + nop + ret + +endp + + + +; +; Stuff needed from the shape library +; +; +; + + +INCLUDE "shape.inc" + + + + +;*************************************************************************** +;* ModeX_Blit -- Copy a 320x200 graphic view port to a modex screen * +;* * +;* * +;* INPUT: eax - graphic view port * +;* * +;* OUTPUT: none * +;* * +;* PROTO: extern "C" void ModeX_Blit (GraphicBufferClass *source); * +;* * +;* HISTORY: * +;* 10/26/1994 PWG : Created. * +;*=========================================================================* + +SEQUENCER =3c4h ; sequencer port +MAP_MASK =2 ; map mask register + + INCLUDE "gbuffer.inc" + global ModeX_Blit_:near + + +proc ModeX_Blit_ NEAR + + pushad + mov ebx,eax + + mov esi,[(GraphicViewPort ebx).GVPOffset] + mov eax,[(GraphicViewPort ebx).GVPXAdd] + add eax,[(GraphicViewPort ebx).GVPPitch] + mov edi,0a0000h + mov ebx,eax + + mov al,MAP_MASK + + mov ebp,200 + +??each_line_lp: mov ah,1 ;1st plane + push ebx + push esi + +??each_plane_lp:mov edx,SEQUENCER + out dx,ax + + push esi + push edi + push eax + + rept 10 + mov al,[esi] + mov bl,[esi+8] + mov cl,[esi+16] + mov dl,[esi+24] + mov ah,[esi+4] + mov bh,[esi+12] + mov ch,[esi+20] + mov dh,[esi+28] + shl ebx,16 + shl edx,16 + or ebx,eax + or edx,ecx + mov [edi],ebx + mov [edi+4],edx + add esi,32 + add edi,8 + endm + + pop eax + pop edi + pop esi + inc esi + shl ah,1 + cmp ah,16 + jl ??each_plane_lp + + + pop esi + pop ebx + lea esi,[esi+ebx+320] + add edi,80 + dec ebp + jnz ??each_line_lp + + popad + + ret + +endp ModeX_Blit_ + + + + + + +ifdef cuts + pushad + mov ebx,eax + + mov esi,[(GraphicViewPort ebx).GVPOffset] + mov eax,[(GraphicViewPort ebx).GVPXAdd] + add eax,[(GraphicViewPort ebx).GVPPitch] + mov edi,0a0000h + mov ebx,eax + + mov al,MAP_MASK + mov ah,1 ;1st plane + +??each_plane_lp:mov edx,SEQUENCER + out dx,ax + mov ebp,200 ;do 200 lines + push esi + push edi + +??each_line_lp: mov ecx,320/4 + +??each_pixel_lp:mov dl,[esi] + mov [edi],dl + add esi,4 + inc edi + dec ecx + jnz ??each_pixel_lp + + add esi,ebx + dec ebp + jnz ??each_line_lp + + pop edi + pop esi + inc esi + shl ah,1 + + cmp ah,16 + jl ??each_plane_lp +endif + + + + + + + + + + +proc FastGetPortHardware_ NEAR +endp + +proc FastSetPortHardware_ NEAR +endp + +proc PortOpenGreenleafFast_ NEAR +endp + +proc HMWaitForOK_ NEAR +endp + +proc HMSetDialingMethod_ NEAR +endp + +proc HMDial_ NEAR +endp + +proc HMInputLine_ NEAR +endp + +proc HMAnswer_ NEAR +endp + +proc PortKillTime_ NEAR +endp + +proc HMSendStringNoWait_ NEAR +endp + +proc HMSetUpEchoRoutine_ NEAR +endp + +proc HMSetUpAbortKey_ NEAR +endp + +proc SetAbortModemFunctionPtr_ NEAR +endp + +proc Change8259Priority_ NEAR +endp + +proc HMSendString_ NEAR +endp + + ret + + + + + + + + masm +; +; Change a DAC colour register directly +; +; register number in al +; +; bh=red bl=green cl=blue +; + +set_dac_col proc near + pushad + cli + push eax + mov dx,03dah + in al,dx + jmp @@1 +@@1: mov dx,03c8h + pop eax + out dx,al + jmp @@2 +@@2: inc dl + mov al,bh + out dx,al + jmp @@3 +@@3: mov al,bl + out dx,al + jmp @@4 +@@4: mov al,cl + out dx,al + jmp @@5 +@@5: sti + popad + ret +set_dac_col endp + + ideal + + +global Set_Palette_Register_:near + + +proc Set_Palette_Register_ near + + pushad + and cl,63 + mov bh,dl + and bh,63 + and bl,63 + call set_dac_col + popad + ret + +endp Set_Palette_Register_ + + + + + locals ?? + + + + ends + + dataseg + +LineBuffer dd 640 dup (?) + ends + + + + segment mycode page public use32 'code' ; Need stricter segment alignment + +global C Asm_Interpolate:near +global C Asm_Interpolate_Line_Double:near +global C Asm_Interpolate_Line_Interpolate:near +global C PaletteInterpolationTable:byte + + +;********************************************************************************************* +;* Asm_Interpolate -- interpolate a 320x200 buffer to a 640x400 screen * +;* * +;* INPUT: ptr to source buffer (320x200 image) * +;* ptr to dest buffer (640x400) * +;* height of source buffer * +;* width of source buffer * +;* width of dest buffer * +;* * +;* * +;* OUTPUT: none * +;* * +;* Warnings: * +;* * +;* HISTORY: * +;* 12/15/95 ST : Created. * +;*===========================================================================================* + +PROC Asm_Interpolate C near + + ARG src_ptr:dword + ARG dest_ptr:dword + ARG source_height:dword + ARG source_width:dword + ARG dest_width:dword + + LOCAL old_dest:dword + + pushad + + mov eax,[dest_ptr] + mov [old_dest],eax + + mov esi,[src_ptr] + +??each_line_loop: + mov ecx,[source_width] + sub ecx,2 + shr ecx,1 + mov edi,[old_dest] + jmp ??interpolate_loop + + align 32 +; +; convert 2 pixels of source into 4 pixels of destination +; so we can write to video memory with dwords +; +??interpolate_loop: + mov eax,[esi] + lea esi,[esi+2] + mov edx,eax + mov ebx,eax + and edx,65535 + ror ebx,8 + mov bl,[edx+PaletteInterpolationTable] + mov bh,ah + and eax,000ffff00h + ror ebx,8 + +;1st 3 pixels now in ebx + + shr eax,8 + mov bh,[eax+PaletteInterpolationTable] + ror ebx,16 + mov [edi],ebx + add edi,4 + + dec ecx + jnz ??interpolate_loop + +; do the last three pixels and a blank on the end of a row + xor eax,eax + mov ax,[esi] + mov [edi],al + inc edi + lea esi,[esi+2] + mov al,[eax+PaletteInterpolationTable] + mov [edi],al + inc edi + mov [edi],ah + inc edi + mov [byte edi],0 + + mov edi,[dest_width] + add [old_dest],edi + + dec [source_height] + jnz ??each_line_loop + + popad + ret + +endp Asm_Interpolate + + + + + + + + + + + + + + +PROC Asm_Interpolate_Line_Double C near + ARG src_ptr:dword + ARG dest_ptr:dword + ARG source_height:dword + ARG source_width:dword + ARG dest_width:dword + + LOCAL old_dest:dword + LOCAL width_counter:dword + LOCAL pixel_count:dword + + pushad + + mov eax,[dest_ptr] + mov [old_dest],eax + + mov esi,[src_ptr] + mov edi,[dest_ptr] + +??each_line_loop: + mov [width_counter],0 + mov ecx,[source_width] + sub ecx,2 + shr ecx,1 + mov [pixel_count],ecx + mov ecx,offset LineBuffer + mov edi,[old_dest] + jmp ??interpolate_loop + align 16 + +; convert 2 pixels of source into 4 pixels of destination +??interpolate_loop: + mov eax,[esi] + lea esi,[esi+2] + mov edx,eax + mov ebx,eax + and edx,65535 + ror ebx,8 + mov bl,[edx+PaletteInterpolationTable] + mov bh,ah + and eax,000ffff00h + ror ebx,8 + + ;1st 3 pixels now in ebx + shr eax,8 + mov bh,[eax+PaletteInterpolationTable] + ror ebx,16 + mov [edi],ebx + mov [ecx],ebx + add edi,4 + add ecx,4 + + dec [pixel_count] + jnz ??interpolate_loop + +; do the last three pixels and a blank + + xor eax,eax + mov ax,[esi] + mov [edi],al + mov [ecx],al + inc edi + inc ecx + lea esi,[esi+2] + mov al,[eax+PaletteInterpolationTable] + mov [edi],al + mov [ecx],al + inc edi + inc ecx + mov [edi],ah + mov [ecx],ah + inc edi + inc ecx + mov [byte edi],0 + mov [byte ecx],0 + + mov edi,[dest_width] + shr edi,1 + add [old_dest],edi + push esi + push edi + mov esi,offset LineBuffer + mov edi,[old_dest] + mov ecx,[source_width] + shr ecx,1 + rep movsd + pop edi + pop esi + add [old_dest],edi + mov edi,[old_dest] + + dec [source_height] + jnz ??each_line_loop + + popad + ret + +endp Asm_Interpolate_Line_Double + + + + + + + + + + ends + + dataseg + +TopLine dd 640 dup (?) +BottomLine dd 640 dup (?) + + + segment mycode page public use32 'code' ; Need stricter segment alignment + + +proc Interpolate_Single_Line C near + + ARG source_ptr:dword + ARG dest_ptr:dword + ARG source_width:dword + + pushad + + mov ecx,[source_width] + sub ecx,2 + shr ecx,1 + + mov esi,[source_ptr] + mov edi,[dest_ptr] + +??interpolate_loop: + mov eax,[esi] + lea esi,[esi+2] + mov edx,eax + mov ebx,eax + and edx,65535 + ror ebx,8 + mov bl,[edx+PaletteInterpolationTable] + mov bh,ah + and eax,000ffff00h + ror ebx,8 + + ;1st 3 pixels now in ebx + shr eax,8 + mov bh,[eax+PaletteInterpolationTable] + ror ebx,16 + mov [edi],ebx + add edi,4 + + dec ecx + jnz ??interpolate_loop + +; do the last three pixels and a blank + + xor eax,eax + mov ax,[esi] + mov [edi],al + inc edi + mov al,[eax+PaletteInterpolationTable] + mov [edi],al + inc edi + mov [edi],ah + inc edi + mov [byte edi],0 + + popad + ret + + +endp Interpolate_Single_Line + + +proc Interpolate_Between_Lines C near + + ARG source1:dword + ARG source2:dword + ARG destination:dword + ARG source_width:dword + + pushad + mov esi,[source1] + mov edi,[destination] + mov ebx,[source2] + xor eax,eax + mov ecx,[source_width] + add ecx,ecx + +??interpolate_each_pixel_loop: + mov al,[esi] + mov ah,[ebx] + inc esi + inc ebx + mov dl,[eax+PaletteInterpolationTable] + mov [edi],dl + inc edi + dec ecx + jnz ??interpolate_each_pixel_loop + + popad + ret + +endp Interpolate_Between_Lines + + + + +macro Lineswp + push [next_line] + push [last_line] + pop [next_line] + pop [last_line] +endm + + +PROC Asm_Interpolate_Line_Interpolate C near + + + ARG src_ptr:dword + ARG dest_ptr:dword + ARG source_lines:dword + ARG source_width:dword + ARG dest_width:dword + + LOCAL old_dest:dword + LOCAL pixel_count:dword + LOCAL next_line:dword + LOCAL last_line:dword + + pushad + + mov eax,[dest_ptr] + mov [old_dest],eax + + mov [next_line],offset TopLine + mov [last_line],offset BottomLine + mov ecx,[source_width] + shr ecx,1 + mov [pixel_count],ecx + shr [dest_width],1 + + call Interpolate_Single_Line C,[src_ptr],[next_line],[source_width] + mov esi,[source_width] + Lineswp + add [src_ptr],esi + dec [source_lines] + + +??each_line_pair_loop: + call Interpolate_Single_Line C,[src_ptr],[next_line],[source_width] + call Interpolate_Between_Lines C,[last_line],[next_line],offset LineBuffer,[source_width] + + mov esi,[last_line] + mov edi,[old_dest] + mov ecx,[pixel_count] + rep movsd + + mov edi,[old_dest] + mov esi,offset LineBuffer + add edi,[dest_width] + mov ecx,[pixel_count] + mov [old_dest],edi + rep movsd + + mov edi,[old_dest] + mov esi,[source_width] + add edi,[dest_width] + add [src_ptr],esi + mov [old_dest],edi + + Lineswp + dec [source_lines] + jnz ??each_line_pair_loop + + call Interpolate_Single_Line C,[src_ptr],[next_line],[source_width] + mov esi,[next_line] + mov edi,[old_dest] + mov ecx,[pixel_count] + rep movsd + + popad + ret + + +endp Asm_Interpolate_Line_Interpolate + + ends mycode + + + + +global C Asm_Create_Palette_Interpolation_Table:near +global C InterpolationPalette:dword + + codeseg + + +proc Asm_Create_Palette_Interpolation_Table C near + + LOCAL palette_counter:dword + LOCAL first_palette:dword + LOCAL second_palette:dword + LOCAL dest_ptr:dword + LOCAL count:dword + LOCAL closest_colour:dword + LOCAL distance_of_closest:dword + + pushad + + mov [dest_ptr],0 + mov [palette_counter],256 + mov esi,[InterpolationPalette] + +??palette_outer_loop: + mov edi,[InterpolationPalette] + mov ecx,256 + +??palette_inner_loop: + mov bl,[esi] + add bl,[edi] + shr bl,1 + + mov bh,[esi+1] + add bh,[edi+1] + shr bh,1 + + mov dl,[esi+2] + add dl,[edi+2] + shr dl,1 + + mov [closest_colour],0 + mov [distance_of_closest],-1 + + push edi + push ecx + mov edi,[InterpolationPalette] + mov [count],0 + +??cmp_pal_lp: xor eax,eax + xor ecx,ecx + mov al,[edi] + sub al,bl + imul al + mov ecx,eax + mov al,[edi+1] + sub al,bh + imul al + add ecx,eax + mov al,[edi+2] + sub al,dl + imul al + add ecx,eax + + cmp ecx,[distance_of_closest] + ja ??end_cmp_lp + mov [distance_of_closest],ecx + mov eax,[count] + mov [closest_colour],eax + test ecx,ecx + jz ??got_perfect + +??end_cmp_lp: lea edi,[edi+3] + inc [count] + cmp [count],256 + jb ??cmp_pal_lp + + +??got_perfect: mov edi,[dest_ptr] + mov eax,[closest_colour] + mov [edi+PaletteInterpolationTable],al + inc [dest_ptr] + + pop ecx + pop edi + lea edi,[edi+3] + dec ecx + jnz ??palette_inner_loop + + lea esi,[esi+3] + dec [palette_counter] + jnz ??palette_outer_loop + + popad + ret + +endp Asm_Create_Palette_Interpolation_Table + + + + + DATASEG + +_AbortModemFunctionPtr dd 0 +Memory_Error_Exit dd 0 +MouseQX dd 0 +MouseQY dd 0 + +end diff --git a/WINSTUB.CPP b/WINSTUB.CPP new file mode 100644 index 0000000..6e42667 --- /dev/null +++ b/WINSTUB.CPP @@ -0,0 +1,921 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Command & Conquer * + * * + * File Name : WINSTUB.CPP * + * * + * Programmer : Steve Tall * + * * + * Start Date : 10/04/95 * + * * + * Last Update : October 4th 1995 [ST] * + * * + *---------------------------------------------------------------------------------------------* + * Overview: * + * This file contains stubs for undefined externals when linked under Watcom for Win 95 * + * * + *---------------------------------------------------------------------------------------------* + * * + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#include "function.h" +#include "tcpip.h" + +void output(short,short) +{} + +bool InDebugger = false; +bool ReadyToQuit = false; + +#if (0) +/*************************************************************************** + * Extract_Shape_Count -- returns # of shapes in the given shape block * + * * + * The # of shapes in a shape block is the first WORD in the block, so * + * this is the value returned. * + * * + * INPUT: * + * buffer pointer to shape block, created with MAKESHPS.EXE * + * * + * OUTPUT: * + * # shapes in the block * + * * + * WARNINGS: * + * none * + * * + * HISTORY: * + * 06/09/1992 JLB : Created. * + * 08/19/1993 SKB : Split drawshp.asm into several modules. * + * 05/25/1994 BR : Converted to 32-bit * + *=========================================================================*/ +int __cdecl Extract_Shape_Count(VOID const *buffer) +{ + ShapeBlock_Type *block = (ShapeBlock_Type *)buffer; + + return (block->NumShapes); + +} /* end of Extract_Shape_Count */ + + +/*************************************************************************** + * Extract_Shape -- Gets pointer to shape in given shape block * + * * + * INPUT: * + * buffer pointer to shape block, created with MAKESHPS.EXE * + * shape index of shape to get * + * * + * OUTPUT: * + * pointer to shape in the shape block * + * * + * WARNINGS: * + * none * + * * + * HISTORY: * + * 06/09/1992 JLB : Created. * + * 08/19/1993 SKB : Split drawshp.asm into several modules. * + * 05/25/1994 BR : Converted to 32-bit * + *=========================================================================*/ +VOID * __cdecl Extract_Shape(VOID const *buffer, int shape) +{ + ShapeBlock_Type *block = (ShapeBlock_Type*) buffer; + long offset; // Offset of shape data, from start of block + char *bytebuf = (char*) buffer; + + /* + ----------------------- Return if invalid argument ----------------------- + */ + if (!buffer || shape < 0 || shape >= block->NumShapes) + return(NULL); + + offset = block->Offsets[shape]; + + return(bytebuf + 2 + offset); + +} /* end of Extract_Shape */ +#endif //(0) + + + +unsigned long CCFocusMessage = WM_USER+50; //Private message for receiving application focus +extern void VQA_PauseAudio(void); +extern void VQA_ResumeAudio(void); + + +ThemeType OldTheme = THEME_NONE; + + +/*********************************************************************************************** + * Focus_Loss -- this function is called when a library function detects focus loss * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 2/1/96 2:10PM ST : Created * + *=============================================================================================*/ + +void Focus_Loss(void) +{ + if (SoundOn){ + if (OldTheme == THEME_NONE){ + OldTheme = Theme.What_Is_Playing(); + } + } + Theme.Stop(); + Stop_Primary_Sound_Buffer(); + if (WWMouse) WWMouse->Clear_Cursor_Clip(); +} + + +void Focus_Restore(void) +{ + Restore_Cached_Icons(); + Map.Flag_To_Redraw(true); + Start_Primary_Sound_Buffer(TRUE); + if (WWMouse) WWMouse->Set_Cursor_Clip(); + VisiblePage.Clear(); + HiddenPage.Clear(); +} + + + +/*********************************************************************************************** + * Check_For_Focus_Loss -- check for the end of the focus loss * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 2/2/96 10:49AM ST : Created * + *=============================================================================================*/ + +void Check_For_Focus_Loss(void) +{ + static BOOL focus_last_time = 1; + MSG msg; + + if ( !GameInFocus ){ + Focus_Loss(); + while( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE | PM_NOYIELD ) ){ + if( !GetMessage( &msg, NULL, 0, 0 ) ){ + return; + } + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + if (!focus_last_time && GameInFocus){ + + VQA_PauseAudio(); + CountDownTimerClass cd; + cd.Set(60*1); + + do { + while (PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE )) { + if( !GetMessage( &msg, NULL, 0, 0 ) ){ + return; + } + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + } while(cd.Time()); + VQA_ResumeAudio(); + //AllSurfaces.Restore_Surfaces(); + //VisiblePage.Clear(); + //HiddenPage.Clear(); + //Map.Flag_To_Redraw(true); + PostMessage (MainWindow, CCFocusMessage,0,0); + } + + focus_last_time = GameInFocus; + +} + + + +extern BOOL InMovie; + +long FAR PASCAL _export Windows_Procedure (HWND hwnd, UINT message, UINT wParam, LONG lParam) +{ + + int low_param = LOWORD(wParam); + + if (message == CCFocusMessage){ + Start_Primary_Sound_Buffer(TRUE); + if (!InMovie){ + Theme.Queue_Song(OldTheme); + OldTheme = THEME_NONE; + } + return(0); + } + + + switch ( message ){ + + case WM_MOUSEMOVE: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case WM_KEYDOWN: + case WM_KEYUP: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_RBUTTONDBLCLK: + Kbd.Message_Handler(hwnd, message, wParam, lParam); + return(0); + + case WM_DESTROY: + CCDebugString ("C&C95 - WM_DESTROY message received.\n"); + CCDebugString ("C&C95 - About to call Prog_End.\n"); + Prog_End(); + CCDebugString ("C&C95 - About to Invalidate_Cached_Icons.\n"); + Invalidate_Cached_Icons(); + CCDebugString ("C&C95 - About to release the video surfaces.\n"); + VisiblePage.Un_Init(); + HiddenPage.Un_Init(); + AllSurfaces.Release(); + if (!InDebugger){ + CCDebugString ("C&C95 - About to reset the video mode.\n"); + Reset_Video_Mode(); + } + CCDebugString ("C&C95 - About to stop the profiler.\n"); + Stop_Profiler(); + CCDebugString ("C&C95 - Posting the quit message.\n"); + PostQuitMessage( 0 ); + /* + ** If we are shutting down gracefully than flag that the message loop has finished. + ** If this is a forced shutdown (ReadyToQuit == 0) then try and close down everything + ** before we exit. + */ + if (ReadyToQuit){ + CCDebugString ("C&C95 - We are now ready to quit.\n"); + ReadyToQuit = 2; + }else{ + CCDebugString ("C&C95 - Emergency shutdown.\n"); + CCDebugString ("C&C95 - Shut down the network stuff.\n"); +#ifndef DEMO + Shutdown_Network(); +#endif + CCDebugString ("C&C95 - Kill the Winsock stuff.\n"); + if (Winsock.Get_Connected()) Winsock.Close(); + CCDebugString ("C&C95 - Call ExitProcess.\n"); + ExitProcess(0); + } + CCDebugString ("C&C95 - Clean & ready to quit.\n"); + return(0); + + case WM_ACTIVATEAPP: + GameInFocus=(BOOL)wParam; + if (!GameInFocus) { + Focus_Loss(); + } + AllSurfaces.Set_Surface_Focus (GameInFocus); + AllSurfaces.Restore_Surfaces(); +// if (GameInFocus){ +// Restore_Cached_Icons(); +// Map.Flag_To_Redraw(true); +// Start_Primary_Sound_Buffer(TRUE); +// if (WWMouse) WWMouse->Set_Cursor_Clip(); +// } + return(0); +#if (0) + case WM_ACTIVATE: + if (low_param == WA_INACTIVE){ + GameInFocus = FALSE; + Focus_Loss(); + } + return(0); +#endif //(0) + + case WM_SYSCOMMAND: + switch ( wParam ) { + + case SC_CLOSE: + /* + ** Windows sent us a close message. Probably in response to Alt-F4. Ignore it by + ** pretending to handle the message and returning 0; + */ + return (0); + + case SC_SCREENSAVE: + /* + ** Windoze is about to start the screen saver. If we just return without passing + ** this message to DefWindowProc then the screen saver will not be allowed to start. + */ + return (0); + } + break; + +#ifdef FORCE_WINSOCK + case WM_ACCEPT: + case WM_HOSTBYADDRESS: + case WM_HOSTBYNAME: + case WM_ASYNCEVENT: + case WM_UDPASYNCEVENT: + Winsock.Message_Handler(hwnd, message, wParam, lParam); + return (0); +#endif //FORCE_WINSOCK + } + + return (DefWindowProc (hwnd, message, wParam, lParam)); +} + + + + + + + + + + +/*********************************************************************************************** + * Create_Main_Window -- opens the MainWindow for C&C * + * * + * * + * * + * INPUT: instance -- handle to program instance * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 10/10/95 4:08PM ST : Created * + *=============================================================================================*/ + +#define CC_ICON 1 +int ShowCommand; + +void Create_Main_Window ( HANDLE instance ,int command_show , int width , int height ) + +{ + HWND hwnd ; + WNDCLASS wndclass ; + // + // Register the window class + // + + wndclass.style = CS_HREDRAW | CS_VREDRAW ; + wndclass.lpfnWndProc = Windows_Procedure ; + wndclass.cbClsExtra = 0 ; + wndclass.cbWndExtra = 0 ; + wndclass.hInstance = instance ; + wndclass.hIcon = LoadIcon (instance, MAKEINTRESOURCE(CC_ICON)) ; + wndclass.hCursor = NULL; + wndclass.hbrBackground = NULL; + wndclass.lpszMenuName = "Command & Conquer"; //NULL + wndclass.lpszClassName = "Command & Conquer"; + + RegisterClass (&wndclass) ; + + + // + // Create our main window + // + hwnd = CreateWindowEx ( + WS_EX_TOPMOST, + "Command & Conquer", + "Command & Conquer", + WS_POPUP | WS_MAXIMIZE, + 0, + 0, + width, + height, + NULL, + NULL, + instance, + NULL ); + + ShowWindow (hwnd, command_show ); + ShowCommand = command_show; + UpdateWindow (hwnd); + SetFocus (hwnd); + MainWindow=hwnd; //Save the handle to our main window + hInstance = instance; + + CCFocusMessage = RegisterWindowMessage ("CC_GOT_FOCUS"); + + Audio_Focus_Loss_Function = &Focus_Loss; + Misc_Focus_Loss_Function = &Focus_Loss; + Misc_Focus_Restore_Function = &Focus_Restore; + Gbuffer_Focus_Loss_Function = &Focus_Loss; +} + +void Window_Dialog_Box(HANDLE hinst, LPCTSTR lpszTemplate, HWND hwndOwner, DLGPROC dlgprc) +{ + MSG msg; + /* + ** Get rid of the Westwood mouse cursor because we are showing a + ** dialog box and we want to have the right windows cursor showing + ** for it. + */ + Hide_Mouse(); + ShowCursor(TRUE); + + /* + ** Pop up the dialog box and then run a standard message handler + ** until the dialog box is closed. + */ + + DialogBox(hinst, lpszTemplate, hwndOwner, dlgprc); + while (GetMessage(&msg, NULL, 0, 0) && !AllDone) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + /* + ** Restore the westwood mouse cursor and get rid of the windows one + ** because it is now time to restore back to the westwood way of + ** doing things. + */ + ShowCursor(FALSE); + Show_Mouse(); +} + + + +typedef struct tColourList { + + char Red; + char Green; + char Blue; +} ColourList; + +ColourList ColourLookup[9]={ + 0,0,0, + 63,0,0, + 0,63,0, + 0,0,63, + 63,0,63, + 63,63,0, + 0,63,63, + 32,32,32, + 63,63,63 +}; + + + + +int DebugColour=1; + + +extern "C" void Set_Palette_Register(int number,int red ,int green ,int blue); +#pragma off (unreferenced) +void Colour_Debug (int call_number) +{ + //#if 0 + //if (DebugColour==call_number || !call_number){ + + //if (call_number){ + // Wait_Vert_Blank(); + //} + + Set_Palette_Register (0,ColourLookup[call_number].Red , + ColourLookup[call_number].Green, + ColourLookup[call_number].Blue); + //} + //#endif +} + +#pragma on (unreferenced) + + + + + +BOOL Any_Locked (void) +{ + if (SeenBuff.Get_LockCount() || + HidPage.Get_LockCount()){ + return (TRUE); + }else{ + return(FALSE); + } +} + + + + + + + +HANDLE DebugFile = INVALID_HANDLE_VALUE; + +/*********************************************************************************************** + * CCDebugString -- sends a string to the debugger and echos it to disk * + * * + * * + * * + * INPUT: string * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 10/28/96 12:48PM ST : Created * + *=============================================================================================*/ +void CCDebugString (char *string) +{ +#if (0) + + char outstr[256]; + + sprintf (outstr, "%s", string); + + DWORD actual; + if (DebugFile == INVALID_HANDLE_VALUE){ + DebugFile = CreateFile("debug.txt", GENERIC_WRITE, 0, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + }else{ + DebugFile = CreateFile("debug.txt", GENERIC_WRITE, 0, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + } + + if (DebugFile != INVALID_HANDLE_VALUE){ + SetFilePointer (DebugFile, 0, NULL, FILE_END); + WriteFile(DebugFile, outstr, strlen(outstr)+1, &actual, NULL); + CloseHandle (DebugFile); + } + + OutputDebugString (string); + +#else + + string = string; + +#endif + +} + + + + + + + + + + + + + + +// +// Miscellaneous stubs. Mainly for multi player stuff +// +// +// + +//IPXAddressClass::IPXAddressClass(void) { +// int i; +// i++; +//} +//int IPXManagerClass::Num_Connections(void){ return (0); } +//int IPXManagerClass::Connection_ID( int ) { return (0); } +//IPXAddressClass * IPXManagerClass::Connection_Address( int ) { return ((IPXAddressClass*)0); } +//char * IPXManagerClass::Connection_Name( int ) { return ((char*)0); } +//int IPXAddressClass::Is_Broadcast() { return (0); } +//int IPXManagerClass::Send_Global_Message( void *, int, int, IPXAddressClass * ) { return (0); } +//int IPXManagerClass::Service() { return (0); } +//int IPXManagerClass::Get_Global_Message( void *, int *, IPXAddressClass *, short unsigned * ) { return (0); } +//int IPXAddressClass::operator ==( IPXAddressClass & ) { return (0); } +//IPXManagerClass::IPXManagerClass( int, int, int, int, short unsigned, short unsigned ) {} +//IPXManagerClass::~IPXManagerClass() { +// int i; +// i++; +// } +//int IPXManagerClass::Delete_Connection( int ) { return (0); } +//IPXAddressClass::IPXAddressClass( char unsigned *, char unsigned * ){} +//void IPXManagerClass::Set_Socket( short unsigned ){} +//int IPXManagerClass::Is_IPX() { return (0); } +//int IPXManagerClass::Init() { return (0); } +//void IPXAddressClass::Get_Address( char unsigned *, char unsigned * ){} +//void IPXManagerClass::Set_Bridge( char unsigned * ){} +//int IPXManagerClass::Global_Num_Send() { return (0); } +//void IPXManagerClass::Set_Timing( long unsigned, long unsigned, long unsigned ){} +//unsigned long IPXManagerClass::Global_Response_Time() { return (0); } +//int IPXManagerClass::Create_Connection( int, char *, IPXAddressClass * ) { return (0); } +//int IPXAddressClass::operator !=( IPXAddressClass & ) { return (0); } +//int IPXManagerClass::Send_Private_Message( void *, int, int, int ) { return (0); } +//int IPXManagerClass::Get_Private_Message( void *, int *, int * ) { return (0); } +//int IPXManagerClass::Connection_Index( int ) { return (0); } +//void IPXManagerClass::Reset_Response_Time(){} +//long unsigned IPXManagerClass::Response_Time() { return (0); } +//int IPXManagerClass::Private_Num_Send( int ) { return (0); } + +//_VQAHandle * VQA_Alloc(void){ return ((_VQAHandle *)0); } +//void VQA_Init( _VQAHandle *, long ( *)()) {} +//long VQA_Open( _VQAHandle *, char const *, _VQAConfig * ) { return (0); } +//void VQA_Free( _VQAHandle * ) {} +//void VQA_Close( _VQAHandle * ) {} +//long VQA_Play( _VQAHandle *, long ) { return (0); } + +//void VQA_Init(VQAHandle *, long(*)(VQAHandle *vqa, long action, void *buffer, long nbytes)){} + +//long VQA_Open(VQAHandle *, char const *, VQAConfig *) +//{ +// return (0); +//} + +//void VQA_Close(VQAHandle *){} + +//long VQA_Play(VQAHandle *, long) +//{ +// return (0); +//} + + +unsigned char *VQPalette; +long VQNumBytes; +unsigned long VQSlowpal; +bool VQPaletteChange = false; + + +extern "C"{ + void __cdecl SetPalette(unsigned char *palette,long numbytes,unsigned long slowpal); +} + + + +void Flag_To_Set_Palette(unsigned char *palette,long numbytes,unsigned long slowpal) +{ + VQPalette = palette; + VQNumBytes = numbytes; + VQSlowpal = slowpal; + VQPaletteChange = true; +} + + + +void Check_VQ_Palette_Set(void) +{ + if (VQPaletteChange){ + SetPalette(VQPalette, VQNumBytes, VQSlowpal); + VQPaletteChange = false; + } +} + + + + + +void __cdecl SetPalette(unsigned char *palette,long,unsigned long) +{ + for (int i=0 ; i<256*3 ; i++){ + *(palette+i)&=63; + } + Increase_Palette_Luminance(palette , 15 , 15 , 15 ,63); + + if (PalettesRead){ + memcpy (&PaletteInterpolationTable[0][0] , InterpolatedPalettes[PaletteCounter++] , 65536); + } + Set_Palette(palette); +} + + + +/*********************************************************************************************** + * Memory_Error_Handler -- Handle a possibly fatal failure to allocate memory * + * * + * * + * * + * INPUT: Nothing * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 5/22/96 3:57PM ST : Created * + *=============================================================================================*/ +void Memory_Error_Handler(void) +{ + VisiblePage.Clear(); + Set_Palette(GamePalette); + while (Get_Mouse_State()){Show_Mouse();}; + CCMessageBox().Process("Error - out of memory.", "Abort", false); + Prog_End(); + Invalidate_Cached_Icons(); + PostQuitMessage( 0 ); + ExitProcess(0); +} + + + + + + + + + + + +GraphicBufferClass* Read_PCX_File(char* name, char* Palette, void *Buff, long Size); + + + +/*********************************************************************************************** + * Load_Title_Screen -- loads the title screen into the given video buffer * + * * + * * + * * + * INPUT: screen name * + * video buffer * + * ptr to buffer for palette * + * * + * OUTPUT: Nothing * + * * + * WARNINGS: None * + * * + * HISTORY: * + * 7/5/96 11:30AM ST : Created * + *=============================================================================================*/ + +void Load_Title_Screen(char *name, GraphicViewPortClass *video_page, unsigned char *palette) +{ + + GraphicBufferClass *load_buffer; + + + load_buffer = Read_PCX_File (name, (char*)palette, NULL, 0); + + if (load_buffer){ + load_buffer->Blit(*video_page); + delete load_buffer; + } +} + + + +#include "filepcx.h" + +/*************************************************************************** + * READ_PCX_FILE -- read a pcx file into a Graphic Buffer * + * * + * GraphicBufferClass* Read_PCX_File (char* name, char* palette ,void *Buff, long size ); * + * * + * * + * INPUT: name is a NULL terminated string of the fromat [xxxx.pcx] * + * palette is optional, if palette != NULL the the color palette of * + * the pcx file will be place in the memory block pointed * + * by palette. * + * Buff is optinal, if Buff == NULL a new memory Buffer * + * will be allocated, otherwise the file will be placed * + * at location pointd by Buffer; * + * Size is the size in bytes of the memory block pointed by Buff * + * is also optional; + * * + * OUTPUT: on succes a pointer to a GraphicBufferClass cointaining the * + * pcx file, NULL othewise. * + * * + * WARNINGS: * + * Appears to be a comment-free zone * + * * + * HISTORY: * + * 05/03/1995 JRJ : Created. * + * 04/30/1996 ST : Tidied up and modified to use CCFileClass * + *=========================================================================*/ + +#define POOL_SIZE 2048 +#define READ_CHAR() *file_ptr++ ; \ + if ( file_ptr >= & pool [ POOL_SIZE ] ) { \ + file_handle.Read (pool , POOL_SIZE ); \ + file_ptr = pool ; \ + } + + +GraphicBufferClass* Read_PCX_File(char* name, char* palette, void *Buff, long Size) +{ + unsigned i, j; + unsigned rle; + unsigned color; + unsigned scan_pos; + char *file_ptr; + int width; + int height; + char *buffer; + PCX_HEADER header; + RGB *pal; + char pool [POOL_SIZE]; + GraphicBufferClass *pic; + + CCFileClass file_handle(name); + + if (!file_handle.Is_Available()) return (NULL); + + file_handle.Open(READ); + + file_handle.Read (&header, sizeof (PCX_HEADER)); + + if (header.id != 10 && header.version != 5 && header.pixelsize != 8 ) return NULL ; + + width = header.width - header.x + 1; + height = header.height - header.y + 1; + + if (Buff) { + buffer = (char *)Buff; + i = Size / width; + height = MIN (i - 1, height); + pic = new GraphicBufferClass(width, height, buffer, Size); + if ( !(pic && pic->Get_Buffer()) ) return NULL ; + } else { + pic = new GraphicBufferClass(width, height, NULL, width*(height+4)); + if ( !(pic && pic->Get_Buffer()) ) return NULL ; + } + + buffer = (char *) pic->Get_Buffer(); + file_ptr = pool ; + file_handle.Read (pool , POOL_SIZE); + + if ( header.byte_per_line != width ){ + + for ( scan_pos = j = 0 ; j < height ; j ++, scan_pos += width ) { + for ( i = 0 ; i < width ; ) { + rle = READ_CHAR (); + if ( rle > 192 ) { + rle -= 192 ; + color = READ_CHAR (); ; + memset ( buffer + scan_pos + i , color , rle ); + i += rle; + } else { + *(buffer+scan_pos + i++ ) = (char)rle; + } + } + } + + if ( i == width ) rle = READ_CHAR (); + if ( rle > 192 ) rle = READ_CHAR (); + + } else { + + for ( i = 0 ; i < width * height ; ) { + rle = READ_CHAR (); + rle &= 0xff; + if ( rle > 192 ) { + rle -= 192 ; + color = READ_CHAR (); + memset ( buffer + i , color , rle ); + i += rle ; + }else{ + *(buffer + i++) = (char)rle; + } + } + } + + if ( palette ) { + file_handle.Seek (- (256 * sizeof ( RGB )) , SEEK_END ); + file_handle.Read (palette , 256L * sizeof ( RGB )); + + pal = ( RGB * ) palette; + for (i = 0 ; i < 256 ; i ++) { + pal ->red >>= 2; + pal ->green >>= 2; + pal ->blue >>= 2; + pal ++ ; + } + } + + file_handle.Close(); + return pic; +} + diff --git a/WWALLOC.H b/WWALLOC.H new file mode 100644 index 0000000..441fc2b --- /dev/null +++ b/WWALLOC.H @@ -0,0 +1,68 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\wwalloc.h_v 2.18 16 Oct 1995 16:47:52 JOE_BOSTIC $ */ + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* +** This should be located in the wwlib32.h file, but is located here for +** test purposes. +*/ +#ifdef __FLAT__ +#define PRIVATE static +#endif + +typedef enum MemoryFlagType { + MEM_NORMAL = 0x0000, // Default memory (normal). + MEM_PUBLIC = 0x0000, // Default memory (normal). + MEM_CHIP = 0x0000, // Graphic & sound buffer memory (Amiga). + MEM_UNUSED = 0x0001, // + MEM_SYSTEM = 0x0002, // Allocate out of system heap (XMS or EMS only). + MEM_RELAXED= 0x0004, // Don't worry about page conservation in EMS. + MEM_TEMP = 0x0008, // Temporary allocation (used by library only). + MEM_CLEAR = 0x0010, // Fill memory with '\0' characters. + MEM_PARA = 0x0020, // Paragraph aligned (IBM only). + MEM_XMS = 0x0040, // XMS memory. + MEM_EMS = 0x0080, // EMS memory (not implemented). + MEM_X = 0x8000 // Here to force this enum to be unsigned sized. +} MemoryFlagType; +MemoryFlagType operator |(MemoryFlagType, MemoryFlagType); +MemoryFlagType operator &(MemoryFlagType, MemoryFlagType); +MemoryFlagType operator ~(MemoryFlagType); + + +/* Prototypes for functions defined in this file */ +void * __cdecl Alloc(unsigned long bytes_to_alloc, MemoryFlagType flags); +void __cdecl Free(void const *pointer); +void * __cdecl Resize_Alloc(void const *original_ptr, unsigned long new_size_in_bytes); +long __cdecl Ram_Free(MemoryFlagType flag); +long __cdecl Total_Ram_Free(MemoryFlagType flag); +long __cdecl Heap_Size(MemoryFlagType flag); + +extern unsigned long __cdecl MinRam; // Record of least memory at worst case. +extern unsigned long __cdecl MaxRam; // Record of total allocated at worst case. + +#ifdef __cplusplus +} +#endif + diff --git a/WWFILE.H b/WWFILE.H new file mode 100644 index 0000000..cafef97 --- /dev/null +++ b/WWFILE.H @@ -0,0 +1,71 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* $Header: F:\projects\c&c\vcs\code\wwfile.h_v 2.15 16 Oct 1995 16:46:06 JOE_BOSTIC $ */ +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : Westwood Library * + * * + * File Name : WWFILE.H * + * * + * Programmer : Joe L. Bostic * + * * + * Start Date : August 8, 1994 * + * * + * Last Update : August 8, 1994 [JLB] * + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#ifndef WWFILE_H +#define WWFILE_H + +//#include + +#ifndef READ +#define READ _READ +#endif +#ifndef WRITE +#define WRITE _WRITE +#endif + +class FileClass +{ + public: + virtual ~FileClass(void) {}; + virtual char const * File_Name(void) const = 0; + virtual char const * Set_Name(char const *filename) = 0; + virtual int Create(void) = 0; + virtual int Delete(void) = 0; + virtual int Is_Available(int forced=false) = 0; + virtual int Is_Open(void) const = 0; + virtual int Open(char const *filename, int rights=READ) = 0; + virtual int Open(int rights=READ) = 0; + virtual long Read(void *buffer, long size) = 0; + virtual long Seek(long pos, int dir=SEEK_CUR) = 0; + virtual long Size(void) = 0; + virtual long Write(void const *buffer, long size) = 0; + virtual void Close(void) = 0; + + operator char const * () {return File_Name();}; +}; + +#endif