// Copyright (c) 1999 Philip A. Hardin (pahardin@cs.utexas.edu) // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License v2 or later. // BattleBall // by Philip A. Hardin #include // to get sprintf() #include // to get strncpy() #include // to get time(time_t *) #if defined(__FreeBSD__) && !defined(__alpha__) && !defined(__amd64__) #include #endif #include "bb.h" #include "bbgfxtarget.h" #include "player.h" const int MAXTEAMS= NUMINSIGNIA, // max # of teams is the # of team insignias MAXMEMBERS= 4, // max # of team members per team MAXPLAYERS= MAXTEAMS*MAXMEMBERS, // max # of total players TREES= 12, // default # of trees MTNS= 8; // default # of mountains const coord HQDIST= 100, // default dist from hq to center of playfield MINHQDIST= 50, // min dist from hq to center of playfield MAXHQDIST= 450; // max dist from hq to center of playfield /*=========================================================================*/ // The states of a game round enum stateOfRound { counting, // timer is counting down to begin playing // (teams shown in text area) playing, // playing the round; no goal scored yet // (game status shown in text area) toppling, // the (first) hq is now toppling over // (teams & scores shown in text area) bouncing, // the hq has fallen & bounced at least once // (teams & updated scores shown in text area) roundEnding, // the round is over (but _not_ the game) gameEnding // a team has won, game is over }; struct roundInfo { int loserTeamNum, // team whose hq was hit; -1 if no hq hit winnerTeamNum; // team which hit ball before a goal was scored; // -1 if no hq hit yet stateOfRound state, // state of game round prevState; // state of game round from last iteration long cycles; // # of iterations this round has been going int timer; // in seconds }; /*=========================================================================*/ // BattleBall class. // This is the main class for the game. A single object of this class exists // for the duration of the game. class battleBall { char* progName; int numPlayers; // starts w/ 0 player players[MAXPLAYERS]; int numTeams; // starts w/ 0 team teams[MAXTEAMS]; int pointsToWin; // points required to win game gobList sceneryGobs; // the "scenery" game objects, such // as trees, mntns, playfield, etc. coord hqDist; // HQ-to-playfield-center distance bounGob *bounds; // "boundary" (playfield) game object ballGob *ball; // ball game object hrznGob *horizon; // horizon game object tranGob *train; // train game object; NULL if none int intrinsicDelay; // delay between each iteration (ms) int startupDelay; // delay before game starts (seconds) int testIterations; // =0: normal game play // >0: number of iterations to play // before game automatically // ends (used for testing) bool keepInBounds; // keep vhcls, etc. in playfield bool wantTrain; // user wants the train in the game int flybys; // 0->none, 1->few, 2->many gobList track; // train track int numTrees; // number of tree game objects int numMtns; // number of mountain game objects bool useGL; // use OpenGL rendering public: battleBall(int argc, char *argv[]); ~battleBall(); void Play(); void ShowTeams(bbGfxTarget& gt); private: void ReadCmdLine(int argc, char *argv[]); void ShowHelp(); void ShowBriefHelp(); void ActGobs(gobList& gobs); void AddTeam(char *list); void OpenAllDisplays(); void CreateWindows(int argc, char *argv[]); void PlayOneRound(const gobList& sceneryGobs, int startTime, bool& done); void InitForRound(gobList& gobs, int startTime, roundInfo& ri); void FreeRound(gobList& gobs); void InitTrack(gobList& gobs); void InitScenery(gobList& gobs); void SetStatus(bool); void DrawStartingMsg(bbGfxTarget& gt, int startTime); void GetNextState(gobList& gobs, roundInfo& ri); void DoFlyby(gobList& gobs); }; battleBall* bb; void ShowTeams(bbGfxTarget& gt) {bb->ShowTeams(gt);} /*-------------------------------------------------------------------------*/ battleBall::battleBall(int argc, char *argv[]) : progName(argv[0]), numPlayers(0), numTeams(0), pointsToWin(3), train(NULL), intrinsicDelay(30), startupDelay(0), testIterations(0), hqDist(HQDIST), keepInBounds(true), wantTrain(false), flybys(1), numTrees(TREES), numMtns(MTNS), useGL(false) { srand(time(0)); team::insigniaRandomizer= rand()%NUMINSIGNIA; ReadCmdLine(argc,argv); InitFixed(); // init sine & cosine lookup tables InitRegions(numTeams, gob::fancy, hqDist, team::insigniaRandomizer); OpenAllDisplays(); CreateWindows(argc,argv); InitScenery(sceneryGobs); } /*-------------------------------------------------------------------------*/ battleBall::~battleBall() { forii(numPlayers) if (players[i].active) players[i].CloseXStuff(); } /*-------------------------------------------------------------------------*/ void battleBall::Play() { bool done= false; XEvent event; // The following loop seems to be necessary for X windows // to not lose initial drawing commands. forii(numPlayers) if (players[i].active) if (XPending(players[i].gt.disp) >0) XNextEvent(players[i].gt.disp, &event); while (not done) { PlayOneRound(sceneryGobs,startupDelay,done); startupDelay= 0; } } /*-------------------------------------------------------------------------*/ /* private methods */ /*-------------------------------------------------------------------------*/ // Parse BattleBall's command line, setting game options and creating // players and teams void battleBall::ReadCmdLine(int argc, char *argv[]) { bool helpRequested= false; for (int i=1; i -0.01) gob::gravity= -0.01; if (gob::gravity <-0.20) gob::gravity= -0.20; } else if (!strcmp(s,"-gl")) { #ifndef NO_OPENGL useGL= true; #else cout << "Sorry, this binary was compiled without OpenGL support; option -gl ignored" <MAXHQDIST) hqDist= MAXHQDIST; } else if (!strcmp(s,"-sd")) {if (i+1 5.0) vhclGob::muzzleVel= 5.0; } else if (!strcmp(s,"-insig")) { if (i+1 train dude if (helpRequested) { ShowHelp(); exit(1); } else if (numTeams==0) { ShowBriefHelp(); exit(1); } } /*-------------------------------------------------------------------------*/ void battleBall::ShowBriefHelp() { cout << "Usage: battleball [options...] XDisplay[,XDisplay] [XDisplay]...\n"; cout << "Type battleball -help for more info.\n"; } /*-------------------------------------------------------------------------*/ void battleBall::ShowHelp() { cout << " BattleBall 2.1\n" " A 3-D multiplayer game for X11 Windows\n" " by Philip A. Hardin \n\n"; cout << "SYNTAX\n\n" " battleball [options...] XDisplay[,XDisplay] [XDisplay]...\n\n" "DESCRIPTION\n\n" " BattleBall is a 3-D multiplayer game for X11 Windows. Teams of human\n" " and computer players use tanks and helicopters to play soccer on a\n" " large playfield.\n\n" "OPTIONS\n\n" " -ag ##l Set players' auto-gunner settings. The argument of this\n" " option is three characters: a single-digit number\n" " specifying firing accuracy, a single-digit number\n" " specifying firing frequency, and one of the following\n" " letters specifying target selection:\n" " a - fire at all targets\n" " b - fire only at the ball\n" " v - fire only at vehicles\n" " n - no targets (i.e. do not fire)\n" " This option affects players which appear after it on the\n" " command line. The default is 43a.\n" " -ff d|b|t Make accidental 'friendly fire' from fellow teammates\n" " d - dangerous (the default)\n" " b - blocked\n" " t - transparent\n" " If you dislike your teammates, use the 'dangerous' option.\n" #ifndef NO_OPENGL " -gl Use OpenGL rendering. The default is not to use OpenGL.\n" " This option affects players which appear after it on the\n" " command line.\n" #endif " -grav # Set gravity. Defaults to 0.031 m/iteration^2.\n" " -help Show this help screen\n" " -id # Set the inter-frame delay. If the game runs too slow, set\n" " this lower; if the game seems to lag behind your\n" " keystrokes, set this higher. Defaults to 30 milliseconds.\n" " -mtns # Set the number of mountains. Defaults to 8.\n" " -noag Disable human players' auto-gunner capability.\n" " -noap Disable human players' auto-pilot capability.\n" " -nobang Disable 'bangs' (the flashes at the end of a gun barrel)\n" " -noflyby Disable aircraft fly-bys.\n" #ifndef NO_OPENGL " -nogl Do not use OpenGL rendering. This is the default. This\n" " option affects players which appear after it on the command\n" " line.\n" #endif " -nopause Disable players' use of the pause ('P') key.\n" " -noresize Do not automatically resize the window to fit the graphics.\n" " -noshade Disable shadows. Uses less cpu time.\n" " -out Allow vehicles to go outside of the playfield.\n" " -pts # Set number of points required to win. Defaults to 3.\n" " -rad # Set the 'radius' of the playfield. Defaults to 100 meters.\n" " -sd # Set the startup delay. The game will wait for this many\n" " seconds for players to get ready.\n" " -simple Use simpler graphics. Uses less cpu time.\n" " -snum # Set number of shells per player. Defaults to 3.\n" " -spow # Set shell power. Defaults to 1.6.\n" " -svel # Set shell muzzle velocity. Defaults to 1.5 meters/frame.\n" " At higher velocities, some collisions may not be reliably\n" " detected.\n" " -train Include a train running on a track around the playfield.\n" " -trees # Set the number of trees. Defaults to 12.\n" " -wf Use wireframe rendering. Uses less cpu time.\n\n" "PLAYERS & TEAMS\n\n" " Human players are created by specifying X displays on the command line.\n" " Computer players are created by specifying 'comp' in place of the X\n" " displays.\n\n" " Commas between X displays put players on the same team; spaces between\n" " X displays separate teams. Teams may have any mix of human and\n" " computer players.\n\n" " Computer players may be created with specific auto-gunner settings.\n" " Use 'comp', followed by (no space) the three characters used in the -ag\n" " option, e.g. 'comp67b'. This overrides the -ag option.\n\n" "EXAMPLES\n\n" " battleball mitre:0.0\n" " Creates one team which has one human player on the X display\n" " 'mitre:0.0'.\n\n" " battleball flavio:0.0 chirp:0.0,roar:0.0\n" " Sets up a one-player team against a two-player team.\n\n" " battleball parrot:0.0,comp raven:0.0 comp,comp\n" " Creates three teams with a mix of human and computer\n" " players.\n\n" " battleball dunce:0.0 comp75a comp,comp\n" " Creates three teams. The first computer player gets\n" " special auto-gunner settings; the other computer players\n" " get default settings.\n\n" ; } /*-------------------------------------------------------------------------*/ // Add a team of one or more players to the game // (This includes adding the players to the game) void battleBall::AddTeam(char *list) { int numMembers= 0; do { int dispNameLen= strcspn(list,","); // num chars before next comma char* newDispName= new char[dispNameLen+1]; strncpy(newDispName,list,dispNameLen); newDispName[dispNameLen]= '\0'; if (numPlayers > ang; if (pos.z <0.001) pos.z= 0; } gobs.push_back(new pllrGob(pt3d(-46.4,0,0))); gobs.push_back(new pllrGob(pt3d( 46.4,0,0))); } /*-------------------------------------------------------------------------*/ // Create the "scenery" game objects which only need to be created once per // game, as opposed to once per game round. void battleBall::InitScenery(gobList& gobs) { tcomp pos; bool blocked; gobList::iterator gi; gobs.push_back(bounds= new bounGob(tcomp(pt3d(0,0)),keepInBounds)); // gobs.push_back(new dstrGob(pt3d(-3*hqDist/2,2*hqDist,0), // pt3d(1./8,0))); if (wantTrain) InitTrack(gobs); forii(numMtns +numTrees) { do { if (i >= rand()%(2*MA_PI); blocked= false; for_(gi,gobs) if ((**gi).Contains(pos.Cart())) blocked= true; } while (blocked); pos.Ang()= rand()%(MA_PI/2); if (i 0) { players[i].autoPilot= player::autoPilotAllowed; players[i].autoGunner= player::autoGunnerAllowed; } if (players[i].TeamNum()==-1) train= (tranGob*) players[i].Vhcl(); } ball= new ballGob(pt3d(0,0),pt3d(0,0),-1); gobs.push_back(ball); ri.loserTeamNum= -1; ri.cycles= 0; if (startTime) { ri.timer= startTime +1; ri.state= ri.prevState= counting; SetStatus(false); } else { ri.timer= 0; ri.state= ri.prevState= playing; } } /*-------------------------------------------------------------------------*/ // Free things when a game round ends void battleBall::FreeRound(gobList& gobs) { // legacy stuff... } /*-------------------------------------------------------------------------*/ // Draw a message while the startup delay counts down void battleBall::DrawStartingMsg(bbGfxTarget& gt, int startTime) { char msg[60]; sprintf(msg," Game starts in %d seconds ",startTime); XDrawImageString(gt.disp,gt.win,gt.gc, (int)(29*gt.fontSize.x), (int)(2*gt.fontSize.y), msg,strlen(msg)); XDrawRectangle(gt.disp,gt.win,gt.gc, (int)(29*gt.fontSize.x -1), (int)gt.fontSize.y +2, (int)(strlen(msg)*gt.fontSize.x+1), (int)gt.fontSize.y); } /*-------------------------------------------------------------------------*/ // Advance the game round to its next state when appropriate void battleBall::GetNextState(gobList& gobs, roundInfo& ri) { int i; bldgGob *hq=NULL; ri.prevState= ri.state; if (ri.loserTeamNum != -1) hq= teams[ri.loserTeamNum].hq; if (testIterations >0) testIterations--; (ri.cycles)++; switch (ri.state) { case counting: if (ri.timer >0) ri.timer--; if (ri.timer==0) { ri.state= playing; SetStatus(true); } break; case playing: for(i= 0; i vel.Ang().xz != 0) { ri.loserTeamNum= i; ri.winnerTeamNum= ball->teamNum; ri.state= toppling; SetStatus(false); break; } break; case toppling: if (hq->pos.Ang().xz != -MA_PI/2) hq->Topple(); else { for(i= 0; i =3 and ri.winnerTeamNum != -1 and ri.winnerTeamNum != ri.loserTeamNum) teams[ri.winnerTeamNum].score++; ri.state= bouncing; } break; case bouncing: if (hq->Altitude()==0 and hq->vel.Cart().z==0) { ri.state= roundEnding; for(i= 0; i = pointsToWin) ri.state= gameEnding; } break; case roundEnding: case gameEnding: break; } } /*-------------------------------------------------------------------------*/ // Make each game object "act", i.e. make it do whatever it does, for this // iteration. Depending on the game object and its state, "acting" can // include morphing, setting its velocity, moving, colliding with other // game objects, etc. void battleBall::ActGobs(gobList& gobs) { gobList::iterator gi= gobs.begin(); gobList::iterator nxtgob= gi; int numGobs= gobs.size(); while (numGobs-- >0) { nxtgob++; gobs.Update((**gi).Act(),gi); gi=nxtgob; } } /*-------------------------------------------------------------------------*/ // Create an airplane or saucer ship game object to do a fly-by over the // playfield. void battleBall::DoFlyby(gobList& gobs) { bool isSaucer= rand()%2; tcomp pos(rand()%(2*MA_PI)); tcomp vel= isSaucer ? pt3d(1.5,0) : pt3d(1,0); pos.Cart()= pt3d(-384*vel.Cart().x,0,24) >> pos.Ang(); wingGob* g; if (isSaucer) g= new saucGob(pos,vel,-1); // dummy team number else g= new plneGob(pos,vel,-1); g->animDir= 1; g->ctrl= vel; gobs.push_back(g); } /*-------------------------------------------------------------------------*/ // Play one full game round. // Out: done = true if no human players are playing any longer void battleBall::PlayOneRound(const gobList& sceneryGobs, int startTime, bool& done) { gobList gobs; int numActivePlayers= numPlayers; roundInfo ri; gobs= sceneryGobs; InitForRound(gobs,startTime,ri); while (ri.state != roundEnding and numActivePlayers >0) { if (not player::paused) { GetNextState(gobs,ri); if ((ri.cycles %512)==0 and vhclGob::testVhcl==NULL and (flybys==2 or (flybys==1 and (rand() & 0x700)==0x700)) ) DoFlyby(gobs); } forii(numPlayers) { if (not player::paused and ri.state != counting) players[i].AutoPlay(gobs,numTeams,teams,*ball); if (testIterations==1) if (players[i].active) players[i].CloseXStuff(); // also sets active= false if (players[i].active) players[i].HandleEvents(gobs,train); } if (not player::paused) ActGobs(gobs); numActivePlayers= 0; fori(numPlayers) { player& dude= players[i]; if (dude.active) { numActivePlayers++; if (ri.state != ri.prevState) dude.InvalidateStatus(); dude.Draw(gobs,teams,i,horizon,::ShowTeams); if (ri.state==counting) DrawStartingMsg(dude.gt,ri.timer); XFlush(dude.gt.disp); } } if (ri.state==counting) SleepFor(990*1000); else if (intrinsicDelay >0) SleepFor((intrinsicDelay)*1000); } done= (numActivePlayers==0); FreeRound(gobs); } /*=========================================================================*/ // A long and complicated main() function! main (int argc, char *argv[]) { #if defined(__FreeBSD__) && !defined(__alpha__) && !defined(__amd64__) fpsetmask(0); #endif bb= new battleBall(argc,argv); bb->Play(); delete bb; }